update: to hyprland version

This commit is contained in:
Kaley, Fischer 2024-04-01 20:11:15 +02:00
commit 538219d389
421 changed files with 93083 additions and 0 deletions

38
.builds/alpine.yml Normal file
View file

@ -0,0 +1,38 @@
image: alpine/edge
packages:
- eudev-dev
- glslang
- libdisplay-info-dev
- libinput-dev
- libliftoff-dev
- libxkbcommon-dev
- mesa-dev
- meson
- pixman-dev
- vulkan-headers
- vulkan-loader-dev
- wayland-dev
- wayland-protocols
- xcb-util-image-dev
- xcb-util-renderutil-dev
- xcb-util-wm-dev
- xwayland-dev
- libseat-dev
- hwdata-dev
sources:
- https://gitlab.freedesktop.org/wlroots/wlroots.git
tasks:
- setup: |
cd wlroots
meson setup build --fatal-meson-warnings --default-library=both -Dauto_features=enabled -Dxcb-errors=disabled
- build: |
cd wlroots
ninja -C build
sudo ninja -C build install
- build-features-disabled: |
cd wlroots
meson build --reconfigure -Dauto_features=disabled
ninja -C build
- tinywl: |
cd wlroots/tinywl
make

48
.builds/archlinux.yml Normal file
View file

@ -0,0 +1,48 @@
image: archlinux
packages:
- clang
- libinput
- libdisplay-info
- libliftoff
- libxkbcommon
- mesa
- meson
- pixman
- wayland
- wayland-protocols
- xcb-util-errors
- xcb-util-image
- xcb-util-renderutil
- xcb-util-wm
- xorg-xwayland
- seatd
- vulkan-icd-loader
- vulkan-headers
- glslang
- hwdata
sources:
- https://gitlab.freedesktop.org/wlroots/wlroots.git
tasks:
- setup: |
cd wlroots
CC=gcc meson setup build-gcc --fatal-meson-warnings --default-library=both -Dauto_features=enabled --prefix /usr -Db_sanitize=address,undefined
CC=clang meson setup build-clang --fatal-meson-warnings -Dauto_features=enabled
- gcc: |
cd wlroots/build-gcc
ninja
sudo ninja install
cd ../tinywl
CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" make
- clang: |
cd wlroots/build-clang
ninja
- smoke-test: |
cd wlroots/tinywl
sudo modprobe vkms
udevadm settle
export WLR_BACKENDS=drm
export WLR_RENDERER=pixman
export WLR_DRM_DEVICES=/dev/dri/by-path/platform-vkms-card
export UBSAN_OPTIONS=halt_on_error=1
sudo chmod ugo+rw /dev/dri/by-path/platform-vkms-card
sudo -E seatd-launch -- ./tinywl -s 'kill $PPID' || [ $? = 143 ]

37
.builds/freebsd.yml Normal file
View file

@ -0,0 +1,37 @@
image: freebsd/latest
packages:
- devel/evdev-proto
- devel/libudev-devd
- devel/meson # implies ninja
- devel/pkgconf
- graphics/glslang
- graphics/libdrm
- graphics/libliftoff
- graphics/mesa-libs
- graphics/vulkan-headers
- graphics/vulkan-loader
- graphics/wayland
- graphics/wayland-protocols
- x11/libinput
- x11/libxcb
- x11/libxkbcommon
- x11/pixman
- x11/xcb-util-errors
- x11/xcb-util-renderutil
- x11/xcb-util-wm
- x11-servers/xwayland-devel
- sysutils/libdisplay-info
- sysutils/seatd
- gmake
- hwdata
sources:
- https://gitlab.freedesktop.org/wlroots/wlroots.git
tasks:
- wlroots: |
cd wlroots
meson setup build --fatal-meson-warnings -Dauto_features=enabled
ninja -C build
sudo ninja -C build install
- tinywl: |
cd wlroots/tinywl
gmake

14
.editorconfig Normal file
View file

@ -0,0 +1,14 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
indent_style = tab
indent_size = 4
max_line_length = 100
[*.xml]
indent_style = space
indent_size = 2

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/subprojects/

7
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,7 @@
include: https://git.sr.ht/~emersion/dalligi/blob/master/templates/multi.yml
alpine:
extends: .dalligi
archlinux:
extends: .dalligi
freebsd:
extends: .dalligi

View file

@ -0,0 +1,15 @@
<!--
Please read the following before submitting:
- If you are a user of a wlroots-based compositor, please report first your
issue to your compositor's issue tracker. The issue you are experiencing
might be a compositor bug rather than a wlroots bug.
- Do not open issues related to proprietary drivers.
- Do not use this issue tracker for questions. Instead, use the IRC channel
(#wlroots on Libera Chat).
- Please attach full debug logs of your compositor (redact information you
might consider sensitive). For crashes, please attach a full stack trace with
debug symbols.
-->

1
.mailmap Normal file
View file

@ -0,0 +1 @@
Isaac Freund <mail@isaacfreund.com> <ifreund@ifreund.xyz>

449
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,449 @@
# Contributing to wlroots
Contributing just involves sending a merge request. You will probably be more
successful with your contribution if you visit [#wlroots on Libera Chat]
upfront and discuss your plans.
Note: rules are made to be broken. Adjust or ignore any/all of these as you see
fit, but be prepared to justify it to your peers.
## Forking
New GitLab accounts may not have the permission to fork repositories. You will
need to [file a user verification request] to get this permission. See the
[freedesktop wiki] for more details.
The fork must be marked as public to allow CI to run.
## Merge Requests
If you already have your own merge request habits, feel free to use them. If you
don't, however, allow me to make a suggestion: feature branches pulled from
upstream. Try this:
1. Fork wlroots
2. `git clone git@gitlab.freedesktop.org:<username>/wlroots.git && cd wlroots`
3. `git remote add upstream https://gitlab.freedesktop.org/wlroots/wlroots.git`
You only need to do this once. You're never going to use your fork's master
branch. Instead, when you start working on a feature, do this:
1. `git fetch upstream`
2. `git checkout -b add-so-and-so-feature upstream/master`
3. Add and commit your changes
4. `git push -u origin add-so-and-so-feature`
5. Make a merge request from your feature branch
When you submit your merge request, your commit log should do most of the talking
when it comes to describing your changes and their motivation. In addition to
this, your merge request's comments will ideally include a test plan that the
reviewers can use to (1) demonstrate the problem on master, if applicable and
(2) verify that the problem no longer exists with your changes applied (or that
your new features work correctly). Document all of the edge cases you're aware
of so we can adequately test them - then verify the test plan yourself before
submitting.
## Commit Log
Unlike many projects using GitHub and GitLab, wlroots has a [linear, "recipe"
style] history. This means that every commit should be small, digestible,
stand-alone, and functional. Rather than a purely chronological commit history
like this:
```
doc: final docs for view transforms
fix tests when disabled, redo broken doc formatting
better transformed-view iteration (thanks Hannah!)
try to catch more cases in tests
tests: add new spline test
fix compilation on splines
doc: notes on reticulating splines
compositor: add spline reticulation for view transforms
```
We aim to have a clean history which only reflects the final state, broken up
into functional groupings:
```
compositor: add spline reticulation for view transforms
compositor: new iterator for view transforms
tests: add view-transform correctness tests
doc: fix formatting for view transforms
```
This ensures that the final patch series only contains the final state,
without the changes and missteps taken along the development process. A linear
history eases reviewing, cherry-picking and reverting changes.
If you aren't comfortable with manipulating the Git history, have a look at
[git-rebase.io].
## Commit Messages
Please strive to write good commit messages. Here's some guidelines to follow:
The first line should be limited to 50 characters and should be a sentence that
completes the thought [When applied, this commit will...] *"Implement
cmd_move"* or *"Improve performance of arrange_windows on ARM"* or similar.
The subsequent lines should be separated from the subject line by a single
blank line, and include optional details. In this you can give justification
for the change, [reference issues], or explain some of the subtler
details of your patch. This is important because when someone finds a line of
code they don't understand later, they can use the `git blame` command to find
out what the author was thinking when they wrote it. It's also easier to review
your merge requests if they're separated into logical commits that have good
commit messages and justify themselves in the extended commit description.
As a good rule of thumb, anything you might put into the merge request
description on GitLab is probably fair game for going into the extended commit
message as well.
See [How to Write a Git Commit Message] for more details.
## Code Review
When your changes are submitted for review, one or more core committers will
look over them. Smaller changes might be merged with little fanfare, but larger
changes will typically see review from several people. Be prepared to receive
some feedback - you may be asked to make changes to your work. Our code review
process is:
1. **Triage** the merge request. Do the commit messages make sense? Is a test
plan necessary and/or present? Add anyone as reviewers that you think should
be there (using the relevant GitLab feature, if you have the permissions, or
with an @mention if necessary).
2. **Review** the code. Look for code style violations, naming convention
violations, buffer overflows, memory leaks, logic errors, non-portable code
(including GNU-isms), etc. For significant changes to the public API, loop in
a couple more people for discussion.
3. **Execute** the test plan, if present.
4. **Merge** the merge request when all reviewers approve.
5. **File** follow-up tickets if appropriate.
## Code of Conduct
Note that as a project hosted on freedesktop.org, wlroots follows its
[Code of Conduct], based on the Contributor Covenant. Please conduct yourself
in a respectful and civilized manner when communicating with community members
on IRC and bug tracker.
## Style Reference
wlroots is written in C with a style similar to the [kernel style], but with a
few notable differences.
Try to keep your code conforming to C11 and POSIX as much as possible, and do
not use GNU extensions.
### Brackets
Brackets always go on the same line, including in functions.
Always include brackets for if/while/for, even if it's a single statement.
```c
void function(void) {
if (condition1) {
do_thing1();
}
if (condition2) {
do_thing2();
} else {
do_thing3();
}
}
```
### Indentation
Indentations are a single tab.
For long lines that need to be broken, the continuation line should be indented
with an additional tab.
If the line being broken is opening a new block (functions, if, while, etc.),
the continuation line should be indented with two tabs, so they can't be
misread as being part of the block.
```c
really_long_function(argument1, argument2, ...,
argument3, argument4);
if (condition1 && condition2 && ...
condition3 && condition4) {
do_thing();
}
```
Try to break the line in the place which you think is the most appropriate.
### Line Length
Try to keep your lines under 100 columns, but you can break this rule if it
improves readability. Don't break lines indiscriminately, try to find nice
breaking points so your code is easy to read.
### Names
Global function and type names should be prefixed with `wlr_submodule_` (e.g.
`struct wlr_output`, `wlr_output_set_cursor`). For static functions and
types local to a file, the names chosen aren't as important. Local function
names shouldn't have a `wlr_` prefix.
For include guards, use the header's filename relative to include. Uppercase
all of the characters, and replace any invalid characters with an underscore.
### Construction/Destruction Functions
Functions that are responsible for constructing objects should take one of the
two following forms:
* `init`: for functions which accept a pointer to a pre-allocated object (e.g.
a member of a struct) and initialize it. Such functions must zero out the
memory before initializing it to avoid leaving unset fields.
* `create`: for functions which allocate the memory for an object, initialize
it, and return a pointer. Such functions should allocate the memory with
`calloc()` to avoid leaving unset fields.
Likewise, functions that are responsible for destructing objects should take
one of the two following forms:
* `finish`: for functions which accept a pointer to an object and deinitialize
it. If a finished object isn't destroyed but kept for future use, it must be
reinitialized to be used again.
* `destroy`: for functions which accept a pointer to an object, deinitialize
it, and free the memory. Such functions should always be able to accept a NULL
pointer.
### Error Codes
For functions not returning a value, they should return a (stdbool.h) bool to
indicate whether they succeeded or not.
### Macros
Try to keep the use of macros to a minimum, especially if a function can do the
job. If you do need to use them, try to keep them close to where they're being
used and `#undef` them after.
### Documentation
* Documentation comments for declarations start with `/**` and end with `*/`.
* Cross-reference other declarations by ending function names with `()`, and
writing `struct`, `union`, `enum` or `typedef` before types.
* Document fields which can be NULL with a `// may be NULL` comment, optionally
with more details describing when this can happen.
* Document the bits of a bitfield with a `// enum bar` comment.
* Document the `data` argument of a `struct wl_signal` with a `// struct foo`
comment.
* Document the contents and container of a `struct wl_list` with a
`// content.link` and `// container.list` comment.
### Safety
* Avoid string manipulation functions which don't take the size of the
destination buffer as input: for instance, prefer `snprintf` over `sprintf`.
* Avoid repeating type names in `sizeof()` where possible. For instance, prefer
`ptr = calloc(1, sizeof(*ptr))` over `ptr = calloc(1, sizeof(struct foo))`.
* Prefer `*ptr = (struct foo){0}` over `memset(ptr, 0, sizeof(*ptr))`.
* Prefer `*foo = *bar` over `memcpy(foo, bar, sizeof(*foo))`.
### Example
```c
struct wlr_backend *wlr_backend_autocreate(struct wl_display *display) {
struct wlr_backend *backend;
if (getenv("WAYLAND_DISPLAY") || getenv("_WAYLAND_DISPLAY")) {
backend = attempt_wl_backend(display);
if (backend) {
return backend;
}
}
const char *x11_display = getenv("DISPLAY");
if (x11_display) {
return wlr_x11_backend_create(display, x11_display);
}
// Attempt DRM+libinput
struct wlr_session *session = wlr_session_create(display);
if (!session) {
wlr_log(WLR_ERROR, "Failed to start a DRM session");
return NULL;
}
int gpu = wlr_session_find_gpu(session);
if (gpu == -1) {
wlr_log(WLR_ERROR, "Failed to open DRM device");
goto error_session;
}
backend = wlr_multi_backend_create(session);
if (!backend) {
goto error_gpu;
}
struct wlr_backend *libinput = wlr_libinput_backend_create(display, session);
if (!libinput) {
goto error_multi;
}
struct wlr_backend *drm = wlr_drm_backend_create(display, session, gpu);
if (!drm) {
goto error_libinput;
}
wlr_multi_backend_add(backend, libinput);
wlr_multi_backend_add(backend, drm);
return backend;
error_libinput:
wlr_backend_destroy(libinput);
error_multi:
wlr_backend_destroy(backend);
error_gpu:
wlr_session_close_file(session, gpu);
error_session:
wlr_session_destroy(session);
return NULL;
}
```
## Wayland protocol implementation
Each protocol generally lives in a file with the same name, usually containing
at least one struct for each interface in the protocol. For instance,
`xdg_shell` lives in `types/wlr_xdg_shell.h` and has a `wlr_xdg_surface` struct.
### Globals
Global interfaces generally have public constructors and destructors. Their
struct has a field holding the `wl_global` itself, a destroy signal and a
`wl_display` destroy listener. Example:
```c
struct wlr_compositor {
struct wl_global *global;
struct wl_listener display_destroy;
struct {
struct wl_signal new_surface;
struct wl_signal destroy;
} events;
};
```
When the destructor is called, it should emit the destroy signal, remove the
display destroy listener, destroy the `wl_global` and then destroy the struct.
The destructor can assume all clients and resources have been already
destroyed.
### Resources
Resources are the representation of Wayland objects on the compositor side. They
generally have an associated struct, called the _object struct_, stored in their
`user_data` field.
Object structs can be retrieved from resources via `wl_resource_get_data`. To
prevent bad casts, a safe helper function checking the type of the resource is
used:
```c
static const struct wl_surface_interface surface_impl;
struct wlr_surface *wlr_surface_from_resource(struct wl_resource *resource) {
assert(wl_resource_instance_of(resource, &wl_surface_interface,
&surface_impl));
return wl_resource_get_user_data(resource);
}
```
If a pointer to a `wl_resource` is stored, a resource destroy handler needs to
be registered to clean it up. libwayland will automatically destroy resources
in an arbitrary order when a client is disconnected, the compositor must handle
this correctly.
### Destroying resources
Object structs should only be destroyed when their resource is destroyed, ie.
in the resource destroy handler (set with `wl_resource_set_implementation`).
- If the object has a destructor request: the request handler should just call
`wl_resource_destroy` and do nothing else. The compositor must not destroy
resources on its own outside the destructor request handler.
- If the protocol specifies that an object is destroyed when an event is sent:
it's the only case where the compositor is allowed to send the event and then
call `wl_resource_destroy`. An example of this is `wl_callback`.
### Inert resources
Some resources can become inert in situations described in the protocol or when
the compositor decides to get rid of them. All requests made to inert resources
should be ignored, except the destructor. This is achieved by:
1. When the resource becomes inert: destroy the object struct and call
`wl_resource_set_user_data(resource, NULL)`. Do not destroy the resource.
2. For each request made to a resource that can be inert: add a NULL check to
ignore the request if the resource is inert.
3. When the client calls the destructor request on the resource: call
`wl_resource_destroy(resource)` as usual.
4. When the resource is destroyed, if the resource isn't inert, destroy the
object struct.
Example:
```c
// Handles the destroy request
static void subsurface_handle_destroy(struct wl_client *client,
struct wl_resource *resource) {
wl_resource_destroy(resource);
}
// Handles a regular request
static void subsurface_set_position(struct wl_client *client,
struct wl_resource *resource, int32_t x, int32_t y) {
struct wlr_subsurface *subsurface = subsurface_from_resource(resource);
if (subsurface == NULL) {
return;
}
}
// Destroys the wlr_subsurface struct
static void subsurface_destroy(struct wlr_subsurface *subsurface) {
if (subsurface == NULL) {
return;
}
wl_resource_set_user_data(subsurface->resource, NULL);
free(subsurface);
}
// Resource destroy listener
static void subsurface_handle_resource_destroy(struct wl_resource *resource) {
struct wlr_subsurface *subsurface = subsurface_from_resource(resource);
subsurface_destroy(subsurface);
}
// Makes the resource inert
static void subsurface_handle_surface_destroy(struct wl_listener *listener,
void *data) {
struct wlr_subsurface *subsurface =
wl_container_of(listener, subsurface, surface_destroy);
subsurface_destroy(subsurface);
}
```
[#wlroots on Libera Chat]: https://web.libera.chat/gamja/?channels=#wlroots
[file a user verification request]: https://gitlab.freedesktop.org/freedesktop/freedesktop/-/issues/new?issuable_template=User%20verification
[freedesktop wiki]: https://gitlab.freedesktop.org/freedesktop/freedesktop/-/wikis/home
[linear, "recipe" style]: https://www.bitsnbites.eu/git-history-work-log-vs-recipe/
[git-rebase.io]: https://git-rebase.io/
[reference issues]: https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically
[Code of Conduct]: https://www.freedesktop.org/wiki/CodeOfConduct/
[How to Write a Git Commit Message]: https://chris.beams.io/posts/git-commit/
[kernel style]: https://www.kernel.org/doc/Documentation/process/coding-style.rst

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
Copyright (c) 2017, 2018 Drew DeVault
Copyright (c) 2014 Jari Vetoniemi
Copyright (c) 2023 The wlroots contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

87
README.md Normal file
View file

@ -0,0 +1,87 @@
# wlroots
Pluggable, composable, unopinionated modules for building a [Wayland]
compositor; or about 60,000 lines of code you were going to write anyway.
- wlroots provides backends that abstract the underlying display and input
hardware, including KMS/DRM, libinput, Wayland, X11, and headless backends,
plus any custom backends you choose to write, which can all be created or
destroyed at runtime and used in concert with each other.
- wlroots provides unopinionated, mostly standalone implementations of many
Wayland interfaces, both from wayland.xml and various protocol extensions.
We also promote the standardization of portable extensions across
many compositors.
- wlroots provides several powerful, standalone, and optional tools that
implement components common to many compositors, such as the arrangement of
outputs in physical space.
- wlroots provides an Xwayland abstraction that allows you to have excellent
Xwayland support without worrying about writing your own X11 window manager
on top of writing your compositor.
- wlroots provides a renderer abstraction that simple compositors can use to
avoid writing GL code directly, but which steps out of the way when your
needs demand custom rendering code.
wlroots implements a huge variety of Wayland compositor features and implements
them *right*, so you can focus on the features that make your compositor
unique. By using wlroots, you get high performance, excellent hardware
compatibility, broad support for many wayland interfaces, and comfortable
development tools - or any subset of these features you like, because all of
them work independently of one another and freely compose with anything you want
to implement yourself.
Check out our [wiki] to get started with wlroots. Join our IRC channel:
[#wlroots on Libera Chat].
A variety of [wrapper libraries] are available for using it with your favorite
programming language.
## Building
Install dependencies:
* meson
* wayland
* wayland-protocols
* EGL and GLESv2 (optional, for the GLES2 renderer)
* Vulkan loader, headers and glslang (optional, for the Vulkan renderer)
* libdrm
* GBM (optional, for the GBM allocator)
* libinput (optional, for the libinput backend)
* xkbcommon
* udev (optional, for the session)
* pixman
* [libseat] (optional, for the session)
* [hwdata] (optional, for the DRM backend)
* [libdisplay-info] (optional, for the DRM backend)
* [libliftoff] (optional, for the DRM backend)
If you choose to enable X11 support:
* xwayland (build-time only, optional at runtime)
* libxcb
* libxcb-render-util
* libxcb-wm
* libxcb-errors (optional, for improved error reporting)
Run these commands:
meson setup build/
ninja -C build/
Install like so:
sudo ninja -C build/ install
## Contributing
See [CONTRIBUTING.md].
[Wayland]: https://wayland.freedesktop.org/
[wiki]: https://gitlab.freedesktop.org/wlroots/wlroots/-/wikis/Getting-started
[#wlroots on Libera Chat]: https://web.libera.chat/gamja/?channels=#wlroots
[wrapper libraries]: https://gitlab.freedesktop.org/wlroots/wlroots/-/wikis/Projects-which-use-wlroots#wrapper-libraries
[libseat]: https://git.sr.ht/~kennylevinsen/seatd
[hwdata]: https://github.com/vcrhonek/hwdata
[libdisplay-info]: https://gitlab.freedesktop.org/emersion/libdisplay-info
[libliftoff]: https://gitlab.freedesktop.org/emersion/libliftoff
[CONTRIBUTING.md]: https://gitlab.freedesktop.org/wlroots/wlroots/-/blob/master/CONTRIBUTING.md

447
backend/backend.c Normal file
View file

@ -0,0 +1,447 @@
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/backend/headless.h>
#include <wlr/backend/interface.h>
#include <wlr/backend/multi.h>
#include <wlr/backend/wayland.h>
#include <wlr/config.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/util/log.h>
#include "backend/backend.h"
#include "backend/multi.h"
#include "render/allocator/allocator.h"
#include "util/env.h"
#include "util/time.h"
#if WLR_HAS_SESSION
#include <wlr/backend/session.h>
#endif
#if WLR_HAS_DRM_BACKEND
#include <wlr/backend/drm.h>
#include "backend/drm/monitor.h"
#endif
#if WLR_HAS_LIBINPUT_BACKEND
#include <wlr/backend/libinput.h>
#endif
#if WLR_HAS_X11_BACKEND
#include <wlr/backend/x11.h>
#endif
#define WAIT_SESSION_TIMEOUT 10000 // ms
void wlr_backend_init(struct wlr_backend *backend,
const struct wlr_backend_impl *impl) {
*backend = (struct wlr_backend){
.impl = impl,
};
wl_signal_init(&backend->events.destroy);
wl_signal_init(&backend->events.new_input);
wl_signal_init(&backend->events.new_output);
}
void wlr_backend_finish(struct wlr_backend *backend) {
wl_signal_emit_mutable(&backend->events.destroy, backend);
}
bool wlr_backend_start(struct wlr_backend *backend) {
if (backend->impl->start) {
return backend->impl->start(backend);
}
return true;
}
void wlr_backend_destroy(struct wlr_backend *backend) {
if (!backend) {
return;
}
if (backend->impl && backend->impl->destroy) {
backend->impl->destroy(backend);
} else {
free(backend);
}
}
static struct wlr_session *session_create_and_wait(struct wl_event_loop *loop) {
#if WLR_HAS_SESSION
struct wlr_session *session = wlr_session_create(loop);
if (!session) {
wlr_log(WLR_ERROR, "Failed to start a session");
return NULL;
}
if (!session->active) {
wlr_log(WLR_INFO, "Waiting for a session to become active");
int64_t started_at = get_current_time_msec();
int64_t timeout = WAIT_SESSION_TIMEOUT;
while (!session->active) {
int ret = wl_event_loop_dispatch(loop, (int)timeout);
if (ret < 0) {
wlr_log_errno(WLR_ERROR, "Failed to wait for session active: "
"wl_event_loop_dispatch failed");
return NULL;
}
int64_t now = get_current_time_msec();
if (now >= started_at + WAIT_SESSION_TIMEOUT) {
break;
}
timeout = started_at + WAIT_SESSION_TIMEOUT - now;
}
if (!session->active) {
wlr_log(WLR_ERROR, "Timeout waiting session to become active");
return NULL;
}
}
return session;
#else
wlr_log(WLR_ERROR, "Cannot create session: disabled at compile-time");
return NULL;
#endif
}
int wlr_backend_get_drm_fd(struct wlr_backend *backend) {
if (!backend->impl->get_drm_fd) {
return -1;
}
return backend->impl->get_drm_fd(backend);
}
uint32_t backend_get_buffer_caps(struct wlr_backend *backend) {
if (!backend->impl->get_buffer_caps) {
return 0;
}
return backend->impl->get_buffer_caps(backend);
}
static size_t parse_outputs_env(const char *name) {
const char *outputs_str = getenv(name);
if (outputs_str == NULL) {
return 1;
}
char *end;
int outputs = (int)strtol(outputs_str, &end, 10);
if (*end || outputs < 0) {
wlr_log(WLR_ERROR, "%s specified with invalid integer, ignoring", name);
return 1;
}
return outputs;
}
/**
* Helper to destroy the multi backend when one of its nested backends is
* destroyed.
*/
struct wlr_auto_backend_monitor {
struct wlr_backend *multi;
struct wlr_backend *primary;
struct wl_listener multi_destroy;
struct wl_listener primary_destroy;
};
static void auto_backend_monitor_destroy(struct wlr_auto_backend_monitor *monitor) {
wl_list_remove(&monitor->multi_destroy.link);
wl_list_remove(&monitor->primary_destroy.link);
free(monitor);
}
static void monitor_handle_multi_destroy(struct wl_listener *listener, void *data) {
struct wlr_auto_backend_monitor *monitor = wl_container_of(listener, monitor, multi_destroy);
auto_backend_monitor_destroy(monitor);
}
static void monitor_handle_primary_destroy(struct wl_listener *listener, void *data) {
struct wlr_auto_backend_monitor *monitor = wl_container_of(listener, monitor, primary_destroy);
wlr_backend_destroy(monitor->multi);
}
static struct wlr_auto_backend_monitor *auto_backend_monitor_create(
struct wlr_backend *multi, struct wlr_backend *primary) {
struct wlr_auto_backend_monitor *monitor = calloc(1, sizeof(*monitor));
if (monitor == NULL) {
return NULL;
}
monitor->multi = multi;
monitor->primary = primary;
monitor->multi_destroy.notify = monitor_handle_multi_destroy;
wl_signal_add(&multi->events.destroy, &monitor->multi_destroy);
monitor->primary_destroy.notify = monitor_handle_primary_destroy;
wl_signal_add(&primary->events.destroy, &monitor->primary_destroy);
return monitor;
}
static struct wlr_backend *attempt_wl_backend(struct wl_event_loop *loop) {
struct wlr_backend *backend = wlr_wl_backend_create(loop, NULL);
if (backend == NULL) {
return NULL;
}
size_t outputs = parse_outputs_env("WLR_WL_OUTPUTS");
for (size_t i = 0; i < outputs; ++i) {
wlr_wl_output_create(backend);
}
return backend;
}
static struct wlr_backend *attempt_x11_backend(struct wl_event_loop *loop,
const char *x11_display) {
#if WLR_HAS_X11_BACKEND
struct wlr_backend *backend = wlr_x11_backend_create(loop, x11_display);
if (backend == NULL) {
return NULL;
}
size_t outputs = parse_outputs_env("WLR_X11_OUTPUTS");
for (size_t i = 0; i < outputs; ++i) {
wlr_x11_output_create(backend);
}
return backend;
#else
wlr_log(WLR_ERROR, "Cannot create X11 backend: disabled at compile-time");
return NULL;
#endif
}
static struct wlr_backend *attempt_headless_backend(struct wl_event_loop *loop) {
struct wlr_backend *backend = wlr_headless_backend_create(loop);
if (backend == NULL) {
return NULL;
}
size_t outputs = parse_outputs_env("WLR_HEADLESS_OUTPUTS");
for (size_t i = 0; i < outputs; ++i) {
wlr_headless_add_output(backend, 1280, 720);
}
return backend;
}
static struct wlr_backend *attempt_drm_backend(struct wlr_backend *backend, struct wlr_session *session) {
#if WLR_HAS_DRM_BACKEND
struct wlr_device *gpus[8];
ssize_t num_gpus = wlr_session_find_gpus(session, 8, gpus);
if (num_gpus < 0) {
wlr_log(WLR_ERROR, "Failed to find GPUs");
return NULL;
}
if (num_gpus == 0) {
wlr_log(WLR_ERROR, "Found 0 GPUs, cannot create backend");
return NULL;
} else {
wlr_log(WLR_INFO, "Found %zu GPUs", num_gpus);
}
struct wlr_backend *primary_drm = NULL;
for (size_t i = 0; i < (size_t)num_gpus; ++i) {
struct wlr_backend *drm = wlr_drm_backend_create(session, gpus[i], primary_drm);
if (!drm) {
wlr_log(WLR_ERROR, "Failed to create DRM backend");
continue;
}
if (!primary_drm) {
primary_drm = drm;
}
wlr_multi_backend_add(backend, drm);
}
if (!primary_drm) {
wlr_log(WLR_ERROR, "Could not successfully create backend on any GPU");
return NULL;
}
if (getenv("WLR_DRM_DEVICES") == NULL) {
drm_backend_monitor_create(backend, primary_drm, session);
}
return primary_drm;
#else
wlr_log(WLR_ERROR, "Cannot create DRM backend: disabled at compile-time");
return NULL;
#endif
}
static struct wlr_backend *attempt_libinput_backend(struct wlr_session *session) {
#if WLR_HAS_LIBINPUT_BACKEND
return wlr_libinput_backend_create(session);
#else
wlr_log(WLR_ERROR, "Cannot create libinput backend: disabled at compile-time");
return NULL;
#endif
}
static bool attempt_backend_by_name(struct wl_event_loop *loop,
struct wlr_backend *multi, char *name,
struct wlr_session **session_ptr) {
struct wlr_backend *backend = NULL;
if (strcmp(name, "wayland") == 0) {
backend = attempt_wl_backend(loop);
} else if (strcmp(name, "x11") == 0) {
backend = attempt_x11_backend(loop, NULL);
} else if (strcmp(name, "headless") == 0) {
backend = attempt_headless_backend(loop);
} else if (strcmp(name, "drm") == 0 || strcmp(name, "libinput") == 0) {
// DRM and libinput need a session
if (*session_ptr == NULL) {
*session_ptr = session_create_and_wait(loop);
if (*session_ptr == NULL) {
wlr_log(WLR_ERROR, "failed to start a session");
return false;
}
}
if (strcmp(name, "libinput") == 0) {
backend = attempt_libinput_backend(*session_ptr);
} else {
// attempt_drm_backend() adds the multi drm backends itself
return attempt_drm_backend(multi, *session_ptr) != NULL;
}
} else {
wlr_log(WLR_ERROR, "unrecognized backend '%s'", name);
return false;
}
if (backend == NULL) {
return false;
}
return wlr_multi_backend_add(multi, backend);
}
struct wlr_backend *wlr_backend_autocreate(struct wl_event_loop *loop,
struct wlr_session **session_ptr) {
if (session_ptr != NULL) {
*session_ptr = NULL;
}
struct wlr_session *session = NULL;
struct wlr_backend *multi = wlr_multi_backend_create(loop);
if (!multi) {
wlr_log(WLR_ERROR, "could not allocate multibackend");
return NULL;
}
char *names = getenv("WLR_BACKENDS");
if (names) {
wlr_log(WLR_INFO, "Loading user-specified backends due to WLR_BACKENDS: %s",
names);
names = strdup(names);
if (names == NULL) {
wlr_log(WLR_ERROR, "allocation failed");
goto error;
}
char *saveptr;
char *name = strtok_r(names, ",", &saveptr);
while (name != NULL) {
if (!attempt_backend_by_name(loop, multi, name, &session)) {
wlr_log(WLR_ERROR, "failed to add backend '%s'", name);
free(names);
goto error;
}
name = strtok_r(NULL, ",", &saveptr);
}
free(names);
goto success;
}
if (getenv("WAYLAND_DISPLAY") || getenv("WAYLAND_SOCKET")) {
struct wlr_backend *wl_backend = attempt_wl_backend(loop);
if (!wl_backend) {
goto error;
}
wlr_multi_backend_add(multi, wl_backend);
if (!auto_backend_monitor_create(multi, wl_backend)) {
goto error;
}
goto success;
}
const char *x11_display = getenv("DISPLAY");
if (x11_display) {
struct wlr_backend *x11_backend = attempt_x11_backend(loop, x11_display);
if (!x11_backend) {
goto error;
}
wlr_multi_backend_add(multi, x11_backend);
if (!auto_backend_monitor_create(multi, x11_backend)) {
goto error;
}
goto success;
}
// Attempt DRM+libinput
session = session_create_and_wait(loop);
if (!session) {
wlr_log(WLR_ERROR, "Failed to start a DRM session");
goto error;
}
struct wlr_backend *libinput = attempt_libinput_backend(session);
if (libinput) {
wlr_multi_backend_add(multi, libinput);
if (!auto_backend_monitor_create(multi, libinput)) {
goto error;
}
} else if (env_parse_bool("WLR_LIBINPUT_NO_DEVICES")) {
wlr_log(WLR_INFO, "WLR_LIBINPUT_NO_DEVICES is set, "
"starting without libinput backend");
} else {
wlr_log(WLR_ERROR, "Failed to start libinput backend");
wlr_log(WLR_ERROR, "Set WLR_LIBINPUT_NO_DEVICES=1 to skip libinput");
goto error;
}
struct wlr_backend *primary_drm = attempt_drm_backend(multi, session);
if (primary_drm == NULL) {
wlr_log(WLR_ERROR, "Failed to open any DRM device");
goto error;
}
if (!auto_backend_monitor_create(multi, primary_drm)) {
goto error;
}
success:
if (session_ptr != NULL) {
*session_ptr = session;
}
return multi;
error:
wlr_backend_destroy(multi);
#if WLR_HAS_SESSION
wlr_session_destroy(session);
#endif
return NULL;
}

434
backend/drm/atomic.c Normal file
View file

@ -0,0 +1,434 @@
#include <drm_fourcc.h>
#include <stdlib.h>
#include <stdio.h>
#include <wlr/util/log.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include "backend/drm/drm.h"
#include "backend/drm/fb.h"
#include "backend/drm/iface.h"
#include "backend/drm/util.h"
static char *atomic_commit_flags_str(uint32_t flags) {
const char *const l[] = {
(flags & DRM_MODE_PAGE_FLIP_EVENT) ? "PAGE_FLIP_EVENT" : NULL,
(flags & DRM_MODE_PAGE_FLIP_ASYNC) ? "PAGE_FLIP_ASYNC" : NULL,
(flags & DRM_MODE_ATOMIC_TEST_ONLY) ? "ATOMIC_TEST_ONLY" : NULL,
(flags & DRM_MODE_ATOMIC_NONBLOCK) ? "ATOMIC_NONBLOCK" : NULL,
(flags & DRM_MODE_ATOMIC_ALLOW_MODESET) ? "ATOMIC_ALLOW_MODESET" : NULL,
};
char *buf = NULL;
size_t size = 0;
FILE *f = open_memstream(&buf, &size);
if (f == NULL) {
return NULL;
}
for (size_t i = 0; i < sizeof(l) / sizeof(l[0]); i++) {
if (l[i] == NULL) {
continue;
}
if (ftell(f) > 0) {
fprintf(f, " | ");
}
fprintf(f, "%s", l[i]);
}
if (ftell(f) == 0) {
fprintf(f, "none");
}
fclose(f);
return buf;
}
struct atomic {
drmModeAtomicReq *req;
bool failed;
};
static void atomic_begin(struct atomic *atom) {
*atom = (struct atomic){0};
atom->req = drmModeAtomicAlloc();
if (!atom->req) {
wlr_log_errno(WLR_ERROR, "Allocation failed");
atom->failed = true;
return;
}
}
static bool atomic_commit(struct atomic *atom, struct wlr_drm_backend *drm,
struct wlr_drm_connector *conn, struct wlr_drm_page_flip *page_flip,
uint32_t flags) {
if (atom->failed) {
return false;
}
int ret = drmModeAtomicCommit(drm->fd, atom->req, flags, page_flip);
if (ret != 0) {
enum wlr_log_importance log_level = WLR_ERROR;
if (flags & DRM_MODE_ATOMIC_TEST_ONLY) {
log_level = WLR_DEBUG;
}
if (conn != NULL) {
wlr_drm_conn_log_errno(conn, log_level, "Atomic commit failed");
} else {
wlr_log_errno(log_level, "Atomic commit failed");
}
char *flags_str = atomic_commit_flags_str(flags);
wlr_log(WLR_DEBUG, "(Atomic commit flags: %s)",
flags_str ? flags_str : "<error>");
free(flags_str);
return false;
}
return true;
}
static void atomic_finish(struct atomic *atom) {
drmModeAtomicFree(atom->req);
}
static void atomic_add(struct atomic *atom, uint32_t id, uint32_t prop, uint64_t val) {
if (!atom->failed && drmModeAtomicAddProperty(atom->req, id, prop, val) < 0) {
wlr_log_errno(WLR_ERROR, "Failed to add atomic DRM property");
atom->failed = true;
}
}
bool create_mode_blob(struct wlr_drm_connector *conn,
const struct wlr_drm_connector_state *state, uint32_t *blob_id) {
if (!state->active) {
*blob_id = 0;
return true;
}
if (drmModeCreatePropertyBlob(conn->backend->fd, &state->mode,
sizeof(drmModeModeInfo), blob_id)) {
wlr_log_errno(WLR_ERROR, "Unable to create mode property blob");
return false;
}
return true;
}
bool create_gamma_lut_blob(struct wlr_drm_backend *drm,
size_t size, const uint16_t *lut, uint32_t *blob_id) {
if (size == 0) {
*blob_id = 0;
return true;
}
struct drm_color_lut *gamma = malloc(size * sizeof(*gamma));
if (gamma == NULL) {
wlr_log(WLR_ERROR, "Failed to allocate gamma table");
return false;
}
const uint16_t *r = lut;
const uint16_t *g = lut + size;
const uint16_t *b = lut + 2 * size;
for (size_t i = 0; i < size; i++) {
gamma[i].red = r[i];
gamma[i].green = g[i];
gamma[i].blue = b[i];
}
if (drmModeCreatePropertyBlob(drm->fd, gamma,
size * sizeof(*gamma), blob_id) != 0) {
wlr_log_errno(WLR_ERROR, "Unable to create gamma LUT property blob");
free(gamma);
return false;
}
free(gamma);
return true;
}
bool create_fb_damage_clips_blob(struct wlr_drm_backend *drm,
int width, int height, const pixman_region32_t *damage, uint32_t *blob_id) {
if (!pixman_region32_not_empty(damage)) {
*blob_id = 0;
return true;
}
pixman_region32_t clipped;
pixman_region32_init(&clipped);
pixman_region32_intersect_rect(&clipped, damage, 0, 0, width, height);
int rects_len;
const pixman_box32_t *rects = pixman_region32_rectangles(&clipped, &rects_len);
int ret = drmModeCreatePropertyBlob(drm->fd, rects, sizeof(*rects) * rects_len, blob_id);
pixman_region32_fini(&clipped);
if (ret != 0) {
wlr_log_errno(WLR_ERROR, "Failed to create FB_DAMAGE_CLIPS property blob");
return false;
}
return true;
}
static uint64_t max_bpc_for_format(uint32_t format) {
switch (format) {
case DRM_FORMAT_XRGB2101010:
case DRM_FORMAT_ARGB2101010:
case DRM_FORMAT_XBGR2101010:
case DRM_FORMAT_ABGR2101010:
return 10;
case DRM_FORMAT_XBGR16161616F:
case DRM_FORMAT_ABGR16161616F:
case DRM_FORMAT_XBGR16161616:
case DRM_FORMAT_ABGR16161616:
return 16;
default:
return 8;
}
}
static uint64_t pick_max_bpc(struct wlr_drm_connector *conn, struct wlr_drm_fb *fb) {
uint32_t format = DRM_FORMAT_INVALID;
struct wlr_dmabuf_attributes attribs = {0};
if (wlr_buffer_get_dmabuf(fb->wlr_buf, &attribs)) {
format = attribs.format;
}
uint64_t target_bpc = max_bpc_for_format(format);
if (target_bpc < conn->max_bpc_bounds[0]) {
target_bpc = conn->max_bpc_bounds[0];
}
if (target_bpc > conn->max_bpc_bounds[1]) {
target_bpc = conn->max_bpc_bounds[1];
}
return target_bpc;
}
static void destroy_blob(struct wlr_drm_backend *drm, uint32_t id) {
if (id == 0) {
return;
}
if (drmModeDestroyPropertyBlob(drm->fd, id) != 0) {
wlr_log_errno(WLR_ERROR, "Failed to destroy blob");
}
}
static void commit_blob(struct wlr_drm_backend *drm,
uint32_t *current, uint32_t next) {
if (*current == next) {
return;
}
destroy_blob(drm, *current);
*current = next;
}
static void rollback_blob(struct wlr_drm_backend *drm,
uint32_t *current, uint32_t next) {
if (*current == next) {
return;
}
destroy_blob(drm, next);
}
static void plane_disable(struct atomic *atom, struct wlr_drm_plane *plane) {
uint32_t id = plane->id;
const union wlr_drm_plane_props *props = &plane->props;
atomic_add(atom, id, props->fb_id, 0);
atomic_add(atom, id, props->crtc_id, 0);
}
static void set_plane_props(struct atomic *atom, struct wlr_drm_backend *drm,
struct wlr_drm_plane *plane, struct wlr_drm_fb *fb, uint32_t crtc_id,
int32_t x, int32_t y) {
uint32_t id = plane->id;
const union wlr_drm_plane_props *props = &plane->props;
if (fb == NULL) {
wlr_log(WLR_ERROR, "Failed to acquire FB for plane %"PRIu32, plane->id);
atom->failed = true;
return;
}
uint32_t width = fb->wlr_buf->width;
uint32_t height = fb->wlr_buf->height;
// The src_* properties are in 16.16 fixed point
atomic_add(atom, id, props->src_x, 0);
atomic_add(atom, id, props->src_y, 0);
atomic_add(atom, id, props->src_w, (uint64_t)width << 16);
atomic_add(atom, id, props->src_h, (uint64_t)height << 16);
atomic_add(atom, id, props->crtc_w, width);
atomic_add(atom, id, props->crtc_h, height);
atomic_add(atom, id, props->fb_id, fb->id);
atomic_add(atom, id, props->crtc_id, crtc_id);
atomic_add(atom, id, props->crtc_x, (uint64_t)x);
atomic_add(atom, id, props->crtc_y, (uint64_t)y);
}
static bool atomic_crtc_commit(struct wlr_drm_connector *conn,
const struct wlr_drm_connector_state *state,
struct wlr_drm_page_flip *page_flip, uint32_t flags, bool test_only) {
struct wlr_drm_backend *drm = conn->backend;
struct wlr_output *output = &conn->output;
struct wlr_drm_crtc *crtc = conn->crtc;
bool modeset = state->modeset;
bool active = state->active;
uint32_t mode_id = crtc->mode_id;
if (modeset) {
if (!create_mode_blob(conn, state, &mode_id)) {
return false;
}
}
uint32_t gamma_lut = crtc->gamma_lut;
if (state->base->committed & WLR_OUTPUT_STATE_GAMMA_LUT) {
// Fallback to legacy gamma interface when gamma properties are not
// available (can happen on older Intel GPUs that support gamma but not
// degamma).
if (crtc->props.gamma_lut == 0) {
if (!drm_legacy_crtc_set_gamma(drm, crtc,
state->base->gamma_lut_size,
state->base->gamma_lut)) {
return false;
}
} else {
if (!create_gamma_lut_blob(drm, state->base->gamma_lut_size,
state->base->gamma_lut, &gamma_lut)) {
return false;
}
}
}
uint32_t fb_damage_clips = 0;
if ((state->base->committed & WLR_OUTPUT_STATE_DAMAGE) &&
crtc->primary->props.fb_damage_clips != 0) {
create_fb_damage_clips_blob(drm, state->primary_fb->wlr_buf->width,
state->primary_fb->wlr_buf->height, &state->base->damage, &fb_damage_clips);
}
bool prev_vrr_enabled =
output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED;
bool vrr_enabled = prev_vrr_enabled;
if ((state->base->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED)) {
if (!drm_connector_supports_vrr(conn)) {
return false;
}
vrr_enabled = state->base->adaptive_sync_enabled;
}
if (test_only) {
flags |= DRM_MODE_ATOMIC_TEST_ONLY;
}
if (modeset) {
flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
}
if (!test_only && state->nonblock) {
flags |= DRM_MODE_ATOMIC_NONBLOCK;
}
struct atomic atom;
atomic_begin(&atom);
atomic_add(&atom, conn->id, conn->props.crtc_id, active ? crtc->id : 0);
if (modeset && active && conn->props.link_status != 0) {
atomic_add(&atom, conn->id, conn->props.link_status,
DRM_MODE_LINK_STATUS_GOOD);
}
if (active && conn->props.content_type != 0) {
atomic_add(&atom, conn->id, conn->props.content_type,
DRM_MODE_CONTENT_TYPE_GRAPHICS);
}
if (modeset && active && conn->props.max_bpc != 0 && conn->max_bpc_bounds[1] != 0) {
atomic_add(&atom, conn->id, conn->props.max_bpc, pick_max_bpc(conn, state->primary_fb));
}
atomic_add(&atom, crtc->id, crtc->props.mode_id, mode_id);
atomic_add(&atom, crtc->id, crtc->props.active, active);
if (active) {
if (crtc->props.gamma_lut != 0) {
atomic_add(&atom, crtc->id, crtc->props.gamma_lut, gamma_lut);
}
if (crtc->props.vrr_enabled != 0) {
atomic_add(&atom, crtc->id, crtc->props.vrr_enabled, vrr_enabled);
}
set_plane_props(&atom, drm, crtc->primary, state->primary_fb, crtc->id,
0, 0);
if (crtc->primary->props.fb_damage_clips != 0) {
atomic_add(&atom, crtc->primary->id,
crtc->primary->props.fb_damage_clips, fb_damage_clips);
}
if (crtc->cursor) {
if (drm_connector_is_cursor_visible(conn)) {
set_plane_props(&atom, drm, crtc->cursor, state->cursor_fb,
crtc->id, conn->cursor_x, conn->cursor_y);
} else {
plane_disable(&atom, crtc->cursor);
}
}
} else {
plane_disable(&atom, crtc->primary);
if (crtc->cursor) {
plane_disable(&atom, crtc->cursor);
}
}
bool ok = atomic_commit(&atom, drm, conn, page_flip, flags);
atomic_finish(&atom);
if (ok && !test_only) {
if (!crtc->own_mode_id) {
crtc->mode_id = 0; // don't try to delete previous master's blobs
}
crtc->own_mode_id = true;
commit_blob(drm, &crtc->mode_id, mode_id);
commit_blob(drm, &crtc->gamma_lut, gamma_lut);
if (vrr_enabled != prev_vrr_enabled) {
output->adaptive_sync_status = vrr_enabled ?
WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED :
WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED;
wlr_drm_conn_log(conn, WLR_DEBUG, "VRR %s",
vrr_enabled ? "enabled" : "disabled");
}
} else {
rollback_blob(drm, &crtc->mode_id, mode_id);
rollback_blob(drm, &crtc->gamma_lut, gamma_lut);
}
destroy_blob(drm, fb_damage_clips);
return ok;
}
bool drm_atomic_reset(struct wlr_drm_backend *drm) {
struct atomic atom;
atomic_begin(&atom);
for (size_t i = 0; i < drm->num_crtcs; i++) {
struct wlr_drm_crtc *crtc = &drm->crtcs[i];
atomic_add(&atom, crtc->id, crtc->props.mode_id, 0);
atomic_add(&atom, crtc->id, crtc->props.active, 0);
}
struct wlr_drm_connector *conn;
wl_list_for_each(conn, &drm->connectors, link) {
atomic_add(&atom, conn->id, conn->props.crtc_id, 0);
}
for (size_t i = 0; i < drm->num_planes; i++) {
plane_disable(&atom, &drm->planes[i]);
}
uint32_t flags = DRM_MODE_ATOMIC_ALLOW_MODESET;
bool ok = atomic_commit(&atom, drm, NULL, NULL, flags);
atomic_finish(&atom);
return ok;
}
const struct wlr_drm_interface atomic_iface = {
.crtc_commit = atomic_crtc_commit,
.reset = drm_atomic_reset,
};

262
backend/drm/backend.c Normal file
View file

@ -0,0 +1,262 @@
#include <assert.h>
#include <errno.h>
#include <drm_fourcc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/backend/interface.h>
#include <wlr/backend/session.h>
#include <wlr/interfaces/wlr_output.h>
#include <wlr/util/log.h>
#include <xf86drm.h>
#include "backend/drm/drm.h"
#include "backend/drm/fb.h"
struct wlr_drm_backend *get_drm_backend_from_backend(
struct wlr_backend *wlr_backend) {
assert(wlr_backend_is_drm(wlr_backend));
struct wlr_drm_backend *backend = wl_container_of(wlr_backend, backend, backend);
return backend;
}
static bool backend_start(struct wlr_backend *backend) {
struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend);
scan_drm_connectors(drm, NULL);
return true;
}
static void backend_destroy(struct wlr_backend *backend) {
if (!backend) {
return;
}
struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend);
struct wlr_drm_connector *conn, *next;
wl_list_for_each_safe(conn, next, &drm->connectors, link) {
conn->crtc = NULL; // leave CRTCs on when shutting down
destroy_drm_connector(conn);
}
struct wlr_drm_page_flip *page_flip, *page_flip_tmp;
wl_list_for_each_safe(page_flip, page_flip_tmp, &drm->page_flips, link) {
drm_page_flip_destroy(page_flip);
}
wlr_backend_finish(backend);
wl_list_remove(&drm->session_destroy.link);
wl_list_remove(&drm->session_active.link);
wl_list_remove(&drm->parent_destroy.link);
wl_list_remove(&drm->dev_change.link);
wl_list_remove(&drm->dev_remove.link);
if (drm->parent) {
finish_drm_renderer(&drm->mgpu_renderer);
}
finish_drm_resources(drm);
struct wlr_drm_fb *fb, *fb_tmp;
wl_list_for_each_safe(fb, fb_tmp, &drm->fbs, link) {
drm_fb_destroy(fb);
}
free(drm->name);
wlr_session_close_file(drm->session, drm->dev);
wl_event_source_remove(drm->drm_event);
free(drm);
}
static int backend_get_drm_fd(struct wlr_backend *backend) {
struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend);
return drm->fd;
}
static uint32_t drm_backend_get_buffer_caps(struct wlr_backend *backend) {
return WLR_BUFFER_CAP_DMABUF;
}
static const struct wlr_backend_impl backend_impl = {
.start = backend_start,
.destroy = backend_destroy,
.get_drm_fd = backend_get_drm_fd,
.get_buffer_caps = drm_backend_get_buffer_caps,
};
bool wlr_backend_is_drm(struct wlr_backend *b) {
return b->impl == &backend_impl;
}
struct wlr_backend *wlr_drm_backend_get_parent(struct wlr_backend *backend) {
struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend);
return drm->parent ? &drm->parent->backend : NULL;
}
static void handle_session_active(struct wl_listener *listener, void *data) {
struct wlr_drm_backend *drm =
wl_container_of(listener, drm, session_active);
struct wlr_session *session = drm->session;
wlr_log(WLR_INFO, "DRM FD %s", session->active ? "resumed" : "paused");
if (!session->active) {
return;
}
scan_drm_connectors(drm, NULL);
restore_drm_device(drm);
}
static void handle_dev_change(struct wl_listener *listener, void *data) {
struct wlr_drm_backend *drm = wl_container_of(listener, drm, dev_change);
struct wlr_device_change_event *change = data;
if (!drm->session->active) {
return;
}
switch (change->type) {
case WLR_DEVICE_HOTPLUG:
wlr_log(WLR_DEBUG, "Received hotplug event for %s", drm->name);
scan_drm_connectors(drm, &change->hotplug);
break;
case WLR_DEVICE_LEASE:
wlr_log(WLR_DEBUG, "Received lease event for %s", drm->name);
scan_drm_leases(drm);
break;
default:
wlr_log(WLR_DEBUG, "Received unknown change event for %s", drm->name);
}
}
static void handle_dev_remove(struct wl_listener *listener, void *data) {
struct wlr_drm_backend *drm = wl_container_of(listener, drm, dev_remove);
wlr_log(WLR_INFO, "Destroying DRM backend for %s", drm->name);
backend_destroy(&drm->backend);
}
static void handle_session_destroy(struct wl_listener *listener, void *data) {
struct wlr_drm_backend *drm =
wl_container_of(listener, drm, session_destroy);
backend_destroy(&drm->backend);
}
static void handle_parent_destroy(struct wl_listener *listener, void *data) {
struct wlr_drm_backend *drm =
wl_container_of(listener, drm, parent_destroy);
backend_destroy(&drm->backend);
}
struct wlr_backend *wlr_drm_backend_create(struct wlr_session *session,
struct wlr_device *dev, struct wlr_backend *parent) {
assert(session && dev);
assert(!parent || wlr_backend_is_drm(parent));
char *name = drmGetDeviceNameFromFd2(dev->fd);
drmVersion *version = drmGetVersion(dev->fd);
wlr_log(WLR_INFO, "Initializing DRM backend for %s (%s)", name, version->name);
drmFreeVersion(version);
struct wlr_drm_backend *drm = calloc(1, sizeof(*drm));
if (!drm) {
wlr_log_errno(WLR_ERROR, "Allocation failed");
return NULL;
}
wlr_backend_init(&drm->backend, &backend_impl);
drm->session = session;
wl_list_init(&drm->fbs);
wl_list_init(&drm->connectors);
wl_list_init(&drm->page_flips);
drm->dev = dev;
drm->fd = dev->fd;
drm->name = name;
if (parent != NULL) {
drm->parent = get_drm_backend_from_backend(parent);
drm->parent_destroy.notify = handle_parent_destroy;
wl_signal_add(&parent->events.destroy, &drm->parent_destroy);
} else {
wl_list_init(&drm->parent_destroy.link);
}
drm->dev_change.notify = handle_dev_change;
wl_signal_add(&dev->events.change, &drm->dev_change);
drm->dev_remove.notify = handle_dev_remove;
wl_signal_add(&dev->events.remove, &drm->dev_remove);
drm->drm_event = wl_event_loop_add_fd(session->event_loop, drm->fd,
WL_EVENT_READABLE, handle_drm_event, drm);
if (!drm->drm_event) {
wlr_log(WLR_ERROR, "Failed to create DRM event source");
goto error_fd;
}
drm->session_active.notify = handle_session_active;
wl_signal_add(&session->events.active, &drm->session_active);
if (!check_drm_features(drm)) {
goto error_event;
}
if (!init_drm_resources(drm)) {
goto error_event;
}
if (drm->parent) {
if (!init_drm_renderer(drm, &drm->mgpu_renderer)) {
wlr_log(WLR_ERROR, "Failed to initialize renderer");
goto error_resources;
}
// We'll perform a multi-GPU copy for all submitted buffers, we need
// to be able to texture from them
struct wlr_renderer *renderer = drm->mgpu_renderer.wlr_rend;
const struct wlr_drm_format_set *texture_formats =
wlr_renderer_get_dmabuf_texture_formats(renderer);
if (texture_formats == NULL) {
wlr_log(WLR_ERROR, "Failed to query renderer texture formats");
goto error_mgpu_renderer;
}
// Forbid implicit modifiers, because their meaning changes from one
// GPU to another.
for (size_t i = 0; i < texture_formats->len; i++) {
const struct wlr_drm_format *fmt = &texture_formats->formats[i];
for (size_t j = 0; j < fmt->len; j++) {
uint64_t mod = fmt->modifiers[j];
if (mod == DRM_FORMAT_MOD_INVALID) {
continue;
}
wlr_drm_format_set_add(&drm->mgpu_formats, fmt->format, mod);
}
}
}
drm->session_destroy.notify = handle_session_destroy;
wl_signal_add(&session->events.destroy, &drm->session_destroy);
return &drm->backend;
error_mgpu_renderer:
finish_drm_renderer(&drm->mgpu_renderer);
error_resources:
finish_drm_resources(drm);
error_event:
wl_list_remove(&drm->session_active.link);
wl_event_source_remove(drm->drm_event);
error_fd:
wl_list_remove(&drm->dev_remove.link);
wl_list_remove(&drm->dev_change.link);
wl_list_remove(&drm->parent_destroy.link);
wlr_session_close_file(drm->session, dev);
free(drm);
return NULL;
}

2069
backend/drm/drm.c Normal file

File diff suppressed because it is too large Load diff

258
backend/drm/fb.c Normal file
View file

@ -0,0 +1,258 @@
#include <drm_fourcc.h>
#include <stdlib.h>
#include <wlr/render/drm_format_set.h>
#include <wlr/types/wlr_buffer.h>
#include <wlr/util/addon.h>
#include <wlr/util/log.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include "backend/drm/drm.h"
#include "backend/drm/fb.h"
#include "render/pixel_format.h"
void drm_fb_clear(struct wlr_drm_fb **fb_ptr) {
if (*fb_ptr == NULL) {
return;
}
struct wlr_drm_fb *fb = *fb_ptr;
wlr_buffer_unlock(fb->wlr_buf); // may destroy the buffer
*fb_ptr = NULL;
}
struct wlr_drm_fb *drm_fb_lock(struct wlr_drm_fb *fb) {
wlr_buffer_lock(fb->wlr_buf);
return fb;
}
static void drm_fb_handle_destroy(struct wlr_addon *addon) {
struct wlr_drm_fb *fb = wl_container_of(addon, fb, addon);
drm_fb_destroy(fb);
}
static const struct wlr_addon_interface fb_addon_impl = {
.name = "wlr_drm_fb",
.destroy = drm_fb_handle_destroy,
};
static uint32_t get_fb_for_bo(struct wlr_drm_backend *drm,
struct wlr_dmabuf_attributes *dmabuf, uint32_t handles[static 4]) {
uint64_t modifiers[4] = {0};
for (int i = 0; i < dmabuf->n_planes; i++) {
// KMS requires all BO planes to have the same modifier
modifiers[i] = dmabuf->modifier;
}
uint32_t id = 0;
if (drm->addfb2_modifiers && dmabuf->modifier != DRM_FORMAT_MOD_INVALID) {
if (drmModeAddFB2WithModifiers(drm->fd, dmabuf->width, dmabuf->height,
dmabuf->format, handles, dmabuf->stride, dmabuf->offset,
modifiers, &id, DRM_MODE_FB_MODIFIERS) != 0) {
wlr_log_errno(WLR_DEBUG, "drmModeAddFB2WithModifiers failed");
}
} else {
if (dmabuf->modifier != DRM_FORMAT_MOD_INVALID &&
dmabuf->modifier != DRM_FORMAT_MOD_LINEAR) {
wlr_log(WLR_ERROR, "Cannot import DRM framebuffer with explicit "
"modifier 0x%"PRIX64, dmabuf->modifier);
return 0;
}
int ret = drmModeAddFB2(drm->fd, dmabuf->width, dmabuf->height,
dmabuf->format, handles, dmabuf->stride, dmabuf->offset, &id, 0);
if (ret != 0 && dmabuf->format == DRM_FORMAT_ARGB8888 &&
dmabuf->n_planes == 1 && dmabuf->offset[0] == 0) {
// Some big-endian machines don't support drmModeAddFB2. Try a
// last-resort fallback for ARGB8888 buffers, like Xorg's
// modesetting driver does.
wlr_log(WLR_DEBUG, "drmModeAddFB2 failed (%s), falling back to "
"legacy drmModeAddFB", strerror(-ret));
uint32_t depth = 32;
uint32_t bpp = 32;
ret = drmModeAddFB(drm->fd, dmabuf->width, dmabuf->height, depth,
bpp, dmabuf->stride[0], handles[0], &id);
if (ret != 0) {
wlr_log_errno(WLR_DEBUG, "drmModeAddFB failed");
}
} else if (ret != 0) {
wlr_log_errno(WLR_DEBUG, "drmModeAddFB2 failed");
}
}
return id;
}
static void close_all_bo_handles(struct wlr_drm_backend *drm,
uint32_t handles[static 4]) {
for (int i = 0; i < 4; ++i) {
if (handles[i] == 0) {
continue;
}
// If multiple planes share the same BO handle, avoid double-closing it
bool already_closed = false;
for (int j = 0; j < i; ++j) {
if (handles[i] == handles[j]) {
already_closed = true;
break;
}
}
if (already_closed) {
continue;
}
if (drmCloseBufferHandle(drm->fd, handles[i]) != 0) {
wlr_log_errno(WLR_ERROR, "drmCloseBufferHandle failed");
}
}
}
static void drm_poisoned_fb_handle_destroy(struct wlr_addon *addon) {
wlr_addon_finish(addon);
free(addon);
}
static const struct wlr_addon_interface poisoned_fb_addon_impl = {
.name = "wlr_drm_poisoned_fb",
.destroy = drm_poisoned_fb_handle_destroy,
};
static bool is_buffer_poisoned(struct wlr_drm_backend *drm,
struct wlr_buffer *buf) {
return wlr_addon_find(&buf->addons, drm, &poisoned_fb_addon_impl) != NULL;
}
/**
* Mark the buffer as "poisoned", ie. it cannot be imported into KMS. This
* allows us to avoid repeatedly trying to import it when it's not
* scanout-capable.
*/
static void poison_buffer(struct wlr_drm_backend *drm,
struct wlr_buffer *buf) {
struct wlr_addon *addon = calloc(1, sizeof(*addon));
if (addon == NULL) {
wlr_log_errno(WLR_ERROR, "Allocation failed");
return;
}
wlr_addon_init(addon, &buf->addons, drm, &poisoned_fb_addon_impl);
wlr_log(WLR_DEBUG, "Poisoning buffer");
}
static struct wlr_drm_fb *drm_fb_create(struct wlr_drm_backend *drm,
struct wlr_buffer *buf, const struct wlr_drm_format_set *formats) {
struct wlr_dmabuf_attributes attribs;
if (!wlr_buffer_get_dmabuf(buf, &attribs)) {
wlr_log(WLR_DEBUG, "Failed to get DMA-BUF from buffer");
return NULL;
}
if (is_buffer_poisoned(drm, buf)) {
wlr_log(WLR_DEBUG, "Buffer is poisoned");
return NULL;
}
struct wlr_drm_fb *fb = calloc(1, sizeof(*fb));
if (!fb) {
wlr_log_errno(WLR_ERROR, "Allocation failed");
return NULL;
}
if (formats && !wlr_drm_format_set_has(formats, attribs.format,
attribs.modifier)) {
// The format isn't supported by the plane. Try stripping the alpha
// channel, if any.
const struct wlr_pixel_format_info *info =
drm_get_pixel_format_info(attribs.format);
if (info != NULL && info->opaque_substitute != DRM_FORMAT_INVALID &&
wlr_drm_format_set_has(formats, info->opaque_substitute, attribs.modifier)) {
attribs.format = info->opaque_substitute;
} else {
wlr_log(WLR_DEBUG, "Buffer format 0x%"PRIX32" with modifier "
"0x%"PRIX64" cannot be scanned out",
attribs.format, attribs.modifier);
goto error_fb;
}
}
uint32_t handles[4] = {0};
for (int i = 0; i < attribs.n_planes; ++i) {
int ret = drmPrimeFDToHandle(drm->fd, attribs.fd[i], &handles[i]);
if (ret != 0) {
wlr_log_errno(WLR_DEBUG, "drmPrimeFDToHandle failed");
goto error_bo_handle;
}
}
fb->id = get_fb_for_bo(drm, &attribs, handles);
if (!fb->id) {
wlr_log(WLR_DEBUG, "Failed to import BO in KMS");
poison_buffer(drm, buf);
goto error_bo_handle;
}
close_all_bo_handles(drm, handles);
fb->backend = drm;
fb->wlr_buf = buf;
wlr_addon_init(&fb->addon, &buf->addons, drm, &fb_addon_impl);
wl_list_insert(&drm->fbs, &fb->link);
return fb;
error_bo_handle:
close_all_bo_handles(drm, handles);
error_fb:
free(fb);
return NULL;
}
void drm_fb_destroy(struct wlr_drm_fb *fb) {
struct wlr_drm_backend *drm = fb->backend;
wl_list_remove(&fb->link);
wlr_addon_finish(&fb->addon);
int ret = drmModeCloseFB(drm->fd, fb->id);
if (ret == -EINVAL) {
ret = drmModeRmFB(drm->fd, fb->id);
}
if (ret != 0) {
wlr_log(WLR_ERROR, "Failed to close FB: %s", strerror(-ret));
}
free(fb);
}
bool drm_fb_import(struct wlr_drm_fb **fb_ptr, struct wlr_drm_backend *drm,
struct wlr_buffer *buf, const struct wlr_drm_format_set *formats) {
struct wlr_drm_fb *fb;
struct wlr_addon *addon = wlr_addon_find(&buf->addons, drm, &fb_addon_impl);
if (addon != NULL) {
fb = wl_container_of(addon, fb, addon);
} else {
fb = drm_fb_create(drm, buf, formats);
if (!fb) {
return false;
}
}
wlr_buffer_lock(buf);
drm_fb_move(fb_ptr, &fb);
return true;
}
void drm_fb_move(struct wlr_drm_fb **new, struct wlr_drm_fb **old) {
drm_fb_clear(new);
*new = *old;
*old = NULL;
}
void drm_fb_copy(struct wlr_drm_fb **new, struct wlr_drm_fb *old) {
drm_fb_clear(new);
if (old != NULL) {
*new = drm_fb_lock(old);
}
}

26
backend/drm/gen_pnpids.sh Executable file
View file

@ -0,0 +1,26 @@
#!/bin/sh -eu
#
# usage: gen_pnpids.sh < pnp.ids > pnpids.c
gen_pnps()
{
while read -r id vendor; do
[ "${#id}" = 3 ] || exit 1
printf "\tcase PNP_ID('%c', '%c', '%c'): return \"%s\";\n" \
"$id" "${id#?}" "${id#??}" "$vendor"
done
}
cat << EOF
#include "backend/drm/util.h"
#define PNP_ID(a, b, c) ((a & 0x1f) << 10) | ((b & 0x1f) << 5) | (c & 0x1f)
const char *get_pnp_manufacturer(const char code[static 3]) {
switch (PNP_ID(code[0], code[1], code[2])) {
$(gen_pnps)
}
return NULL;
}
#undef PNP_ID
EOF

246
backend/drm/legacy.c Normal file
View file

@ -0,0 +1,246 @@
#include <assert.h>
#include <stdlib.h>
#include <wlr/util/log.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include "backend/drm/drm.h"
#include "backend/drm/fb.h"
#include "backend/drm/iface.h"
#include "backend/drm/util.h"
static bool legacy_fb_props_match(struct wlr_drm_fb *fb1,
struct wlr_drm_fb *fb2) {
struct wlr_dmabuf_attributes dmabuf1 = {0}, dmabuf2 = {0};
if (!wlr_buffer_get_dmabuf(fb1->wlr_buf, &dmabuf1) ||
!wlr_buffer_get_dmabuf(fb2->wlr_buf, &dmabuf2)) {
return false;
}
if (dmabuf1.width != dmabuf2.width ||
dmabuf1.height != dmabuf2.height ||
dmabuf1.format != dmabuf2.format ||
dmabuf1.modifier != dmabuf2.modifier ||
dmabuf1.n_planes != dmabuf2.n_planes) {
return false;
}
for (int i = 0; i < dmabuf1.n_planes; i++) {
if (dmabuf1.stride[i] != dmabuf2.stride[i] ||
dmabuf1.offset[i] != dmabuf2.offset[i]) {
return false;
}
}
return true;
}
static bool legacy_crtc_test(struct wlr_drm_connector *conn,
const struct wlr_drm_connector_state *state) {
struct wlr_drm_crtc *crtc = conn->crtc;
if ((state->base->committed & WLR_OUTPUT_STATE_BUFFER) && !state->modeset) {
struct wlr_drm_fb *pending_fb = state->primary_fb;
struct wlr_drm_fb *prev_fb = crtc->primary->queued_fb;
if (!prev_fb) {
prev_fb = crtc->primary->current_fb;
}
/* Legacy is only guaranteed to be able to display a FB if it's been
* allocated the same way as the previous one. */
if (prev_fb != NULL && !legacy_fb_props_match(prev_fb, pending_fb)) {
wlr_drm_conn_log(conn, WLR_DEBUG,
"Cannot change scan-out buffer parameters with legacy KMS API");
return false;
}
}
return true;
}
static bool legacy_crtc_commit(struct wlr_drm_connector *conn,
const struct wlr_drm_connector_state *state,
struct wlr_drm_page_flip *page_flip, uint32_t flags, bool test_only) {
if (!legacy_crtc_test(conn, state)) {
return false;
}
if (test_only) {
return true;
}
struct wlr_drm_backend *drm = conn->backend;
struct wlr_output *output = &conn->output;
struct wlr_drm_crtc *crtc = conn->crtc;
struct wlr_drm_plane *cursor = crtc->cursor;
uint32_t fb_id = 0;
if (state->active) {
if (state->primary_fb == NULL) {
wlr_log(WLR_ERROR, "%s: failed to acquire primary FB",
conn->output.name);
return false;
}
fb_id = state->primary_fb->id;
}
if (state->modeset) {
uint32_t *conns = NULL;
size_t conns_len = 0;
drmModeModeInfo *mode = NULL;
if (state->active) {
conns = &conn->id;
conns_len = 1;
mode = (drmModeModeInfo *)&state->mode;
}
uint32_t dpms = state->active ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF;
if (drmModeConnectorSetProperty(drm->fd, conn->id, conn->props.dpms,
dpms) != 0) {
wlr_drm_conn_log_errno(conn, WLR_ERROR,
"Failed to set DPMS property");
return false;
}
if (drmModeSetCrtc(drm->fd, crtc->id, fb_id, 0, 0,
conns, conns_len, mode)) {
wlr_drm_conn_log_errno(conn, WLR_ERROR, "Failed to set CRTC");
return false;
}
}
if (state->base->committed & WLR_OUTPUT_STATE_GAMMA_LUT) {
if (!drm_legacy_crtc_set_gamma(drm, crtc,
state->base->gamma_lut_size, state->base->gamma_lut)) {
return false;
}
}
if (state->base->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) {
if (!drm_connector_supports_vrr(conn)) {
return false;
}
if (drmModeObjectSetProperty(drm->fd, crtc->id, DRM_MODE_OBJECT_CRTC,
crtc->props.vrr_enabled,
state->base->adaptive_sync_enabled) != 0) {
wlr_drm_conn_log_errno(conn, WLR_ERROR,
"drmModeObjectSetProperty(VRR_ENABLED) failed");
return false;
}
output->adaptive_sync_status = state->base->adaptive_sync_enabled ?
WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED :
WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED;
wlr_drm_conn_log(conn, WLR_DEBUG, "VRR %s",
state->base->adaptive_sync_enabled ? "enabled" : "disabled");
}
if (cursor != NULL && drm_connector_is_cursor_visible(conn)) {
struct wlr_drm_fb *cursor_fb = state->cursor_fb;
if (cursor_fb == NULL) {
wlr_drm_conn_log(conn, WLR_DEBUG, "Failed to acquire cursor FB");
return false;
}
drmModeFB *drm_fb = drmModeGetFB(drm->fd, cursor_fb->id);
if (drm_fb == NULL) {
wlr_drm_conn_log_errno(conn, WLR_DEBUG, "Failed to get cursor "
"BO handle: drmModeGetFB failed");
return false;
}
uint32_t cursor_handle = drm_fb->handle;
uint32_t cursor_width = drm_fb->width;
uint32_t cursor_height = drm_fb->height;
drmModeFreeFB(drm_fb);
int ret = drmModeSetCursor(drm->fd, crtc->id, cursor_handle,
cursor_width, cursor_height);
int set_cursor_errno = errno;
if (drmCloseBufferHandle(drm->fd, cursor_handle) != 0) {
wlr_log_errno(WLR_ERROR, "drmCloseBufferHandle failed");
}
if (ret != 0) {
wlr_drm_conn_log(conn, WLR_DEBUG, "drmModeSetCursor failed: %s",
strerror(set_cursor_errno));
return false;
}
if (drmModeMoveCursor(drm->fd,
crtc->id, conn->cursor_x, conn->cursor_y) != 0) {
wlr_drm_conn_log_errno(conn, WLR_ERROR, "drmModeMoveCursor failed");
return false;
}
} else {
if (drmModeSetCursor(drm->fd, crtc->id, 0, 0, 0)) {
wlr_drm_conn_log_errno(conn, WLR_DEBUG, "drmModeSetCursor failed");
return false;
}
}
if (flags & DRM_MODE_PAGE_FLIP_EVENT) {
if (drmModePageFlip(drm->fd, crtc->id, fb_id, flags, page_flip)) {
wlr_drm_conn_log_errno(conn, WLR_ERROR, "drmModePageFlip failed");
return false;
}
}
return true;
}
static void fill_empty_gamma_table(size_t size,
uint16_t *r, uint16_t *g, uint16_t *b) {
assert(0xFFFF < UINT64_MAX / (size - 1));
for (uint32_t i = 0; i < size; ++i) {
uint16_t val = (uint64_t)0xFFFF * i / (size - 1);
r[i] = g[i] = b[i] = val;
}
}
bool drm_legacy_crtc_set_gamma(struct wlr_drm_backend *drm,
struct wlr_drm_crtc *crtc, size_t size, uint16_t *lut) {
uint16_t *linear_lut = NULL;
if (size == 0) {
// The legacy interface doesn't offer a way to reset the gamma LUT
size = drm_crtc_get_gamma_lut_size(drm, crtc);
if (size == 0) {
return false;
}
linear_lut = malloc(3 * size * sizeof(uint16_t));
if (linear_lut == NULL) {
wlr_log_errno(WLR_ERROR, "Allocation failed");
return false;
}
fill_empty_gamma_table(size, linear_lut, linear_lut + size,
linear_lut + 2 * size);
lut = linear_lut;
}
uint16_t *r = lut, *g = lut + size, *b = lut + 2 * size;
if (drmModeCrtcSetGamma(drm->fd, crtc->id, size, r, g, b) != 0) {
wlr_log_errno(WLR_ERROR, "Failed to set gamma LUT on CRTC %"PRIu32,
crtc->id);
free(linear_lut);
return false;
}
free(linear_lut);
return true;
}
static bool legacy_reset(struct wlr_drm_backend *drm) {
bool ok = true;
for (size_t i = 0; i < drm->num_crtcs; i++) {
struct wlr_drm_crtc *crtc = &drm->crtcs[i];
if (drmModeSetCrtc(drm->fd, crtc->id, 0, 0, 0, NULL, 0, NULL) != 0) {
wlr_log_errno(WLR_ERROR, "Failed to disable CRTC %"PRIu32,
crtc->id);
ok = false;
}
}
return ok;
}
const struct wlr_drm_interface legacy_iface = {
.crtc_commit = legacy_crtc_commit,
.reset = legacy_reset,
};

513
backend/drm/libliftoff.c Normal file
View file

@ -0,0 +1,513 @@
#include <fcntl.h>
#include <libliftoff.h>
#include <sys/stat.h>
#include <unistd.h>
#include <wlr/util/log.h>
#include "backend/drm/drm.h"
#include "backend/drm/fb.h"
#include "backend/drm/iface.h"
static bool init(struct wlr_drm_backend *drm) {
// TODO: lower log level
liftoff_log_set_priority(LIFTOFF_DEBUG);
int drm_fd = fcntl(drm->fd, F_DUPFD_CLOEXEC, 0);
if (drm_fd < 0) {
wlr_log_errno(WLR_ERROR, "fcntl(F_DUPFD_CLOEXEC) failed");
return false;
}
drm->liftoff = liftoff_device_create(drm_fd);
if (!drm->liftoff) {
wlr_log(WLR_ERROR, "Failed to create liftoff device");
close(drm_fd);
return false;
}
for (size_t i = 0; i < drm->num_planes; i++) {
struct wlr_drm_plane *plane = &drm->planes[i];
if (plane->initial_crtc_id != 0) {
continue;
}
plane->liftoff = liftoff_plane_create(drm->liftoff, plane->id);
if (plane->liftoff == NULL) {
wlr_log(WLR_ERROR, "Failed to create liftoff plane");
return false;
}
}
for (size_t i = 0; i < drm->num_crtcs; i++) {
struct wlr_drm_crtc *crtc = &drm->crtcs[i];
crtc->liftoff = liftoff_output_create(drm->liftoff, crtc->id);
if (!crtc->liftoff) {
wlr_log(WLR_ERROR, "Failed to create liftoff output");
return false;
}
crtc->liftoff_composition_layer = liftoff_layer_create(crtc->liftoff);
if (!crtc->liftoff_composition_layer) {
wlr_log(WLR_ERROR, "Failed to create liftoff composition layer");
return false;
}
liftoff_output_set_composition_layer(crtc->liftoff,
crtc->liftoff_composition_layer);
if (crtc->primary) {
crtc->primary->liftoff_layer = liftoff_layer_create(crtc->liftoff);
if (!crtc->primary->liftoff_layer) {
wlr_log(WLR_ERROR, "Failed to create liftoff layer for primary plane");
return false;
}
}
if (crtc->cursor) {
crtc->cursor->liftoff_layer = liftoff_layer_create(crtc->liftoff);
if (!crtc->cursor->liftoff_layer) {
wlr_log(WLR_ERROR, "Failed to create liftoff layer for cursor plane");
return false;
}
}
}
return true;
}
static bool register_planes_for_crtc(struct wlr_drm_backend *drm,
struct wlr_drm_crtc *crtc) {
// When performing the first modeset on a CRTC, we need to be a bit careful
// when it comes to planes: we don't want to allow libliftoff to make use
// of planes currently already in-use on another CRTC. We need to wait for
// a modeset to happen on the other CRTC before being able to use these.
for (size_t i = 0; i < drm->num_planes; i++) {
struct wlr_drm_plane *plane = &drm->planes[i];
if (plane->liftoff != NULL || plane->initial_crtc_id != crtc->id) {
continue;
}
plane->liftoff = liftoff_plane_create(drm->liftoff, plane->id);
if (plane->liftoff == NULL) {
wlr_log(WLR_ERROR, "Failed to create liftoff plane");
return false;
}
}
return true;
}
static void finish(struct wlr_drm_backend *drm) {
for (size_t i = 0; i < drm->num_crtcs; i++) {
struct wlr_drm_crtc *crtc = &drm->crtcs[i];
if (crtc->primary) {
liftoff_layer_destroy(crtc->primary->liftoff_layer);
}
if (crtc->cursor) {
liftoff_layer_destroy(crtc->cursor->liftoff_layer);
}
liftoff_layer_destroy(crtc->liftoff_composition_layer);
liftoff_output_destroy(crtc->liftoff);
}
for (size_t i = 0; i < drm->num_planes; i++) {
struct wlr_drm_plane *plane = &drm->planes[i];
liftoff_plane_destroy(plane->liftoff);
}
liftoff_device_destroy(drm->liftoff);
}
static bool add_prop(drmModeAtomicReq *req, uint32_t obj,
uint32_t prop, uint64_t val) {
if (drmModeAtomicAddProperty(req, obj, prop, val) < 0) {
wlr_log_errno(WLR_ERROR, "drmModeAtomicAddProperty failed");
return false;
}
return true;
}
static void commit_blob(struct wlr_drm_backend *drm,
uint32_t *current, uint32_t next) {
if (*current == next) {
return;
}
if (*current != 0) {
drmModeDestroyPropertyBlob(drm->fd, *current);
}
*current = next;
}
static void rollback_blob(struct wlr_drm_backend *drm,
uint32_t *current, uint32_t next) {
if (*current == next) {
return;
}
if (next != 0) {
drmModeDestroyPropertyBlob(drm->fd, next);
}
}
static bool set_plane_props(struct wlr_drm_plane *plane,
struct liftoff_layer *layer, struct wlr_drm_fb *fb, int32_t x, int32_t y, uint64_t zpos) {
if (fb == NULL) {
wlr_log(WLR_ERROR, "Failed to acquire FB for plane %"PRIu32, plane->id);
return false;
}
uint32_t width = fb->wlr_buf->width;
uint32_t height = fb->wlr_buf->height;
// The SRC_* properties are in 16.16 fixed point
return liftoff_layer_set_property(layer, "zpos", zpos) == 0 &&
liftoff_layer_set_property(layer, "SRC_X", 0) == 0 &&
liftoff_layer_set_property(layer, "SRC_Y", 0) == 0 &&
liftoff_layer_set_property(layer, "SRC_W", (uint64_t)width << 16) == 0 &&
liftoff_layer_set_property(layer, "SRC_H", (uint64_t)height << 16) == 0 &&
liftoff_layer_set_property(layer, "CRTC_X", (uint64_t)x) == 0 &&
liftoff_layer_set_property(layer, "CRTC_Y", (uint64_t)y) == 0 &&
liftoff_layer_set_property(layer, "CRTC_W", width) == 0 &&
liftoff_layer_set_property(layer, "CRTC_H", height) == 0 &&
liftoff_layer_set_property(layer, "FB_ID", fb->id) == 0;
}
static bool disable_plane(struct wlr_drm_plane *plane) {
return liftoff_layer_set_property(plane->liftoff_layer, "FB_ID", 0) == 0;
}
static uint64_t to_fp16(double v) {
return (uint64_t)round(v * (1 << 16));
}
static bool set_layer_props(struct wlr_drm_backend *drm,
const struct wlr_output_layer_state *state, uint64_t zpos,
struct wl_array *fb_damage_clips_arr) {
struct wlr_drm_layer *layer = get_drm_layer(drm, state->layer);
uint32_t width = 0, height = 0;
if (state->buffer != NULL) {
width = state->buffer->width;
height = state->buffer->height;
}
struct wlr_drm_fb *fb = layer->pending_fb;
int ret = 0;
if (state->buffer == NULL) {
ret = liftoff_layer_set_property(layer->liftoff, "FB_ID", 0);
} else if (fb == NULL) {
liftoff_layer_set_fb_composited(layer->liftoff);
} else {
ret = liftoff_layer_set_property(layer->liftoff, "FB_ID", fb->id);
}
if (ret != 0) {
return false;
}
uint64_t crtc_x = (uint64_t)state->dst_box.x;
uint64_t crtc_y = (uint64_t)state->dst_box.y;
uint64_t crtc_w = (uint64_t)state->dst_box.width;
uint64_t crtc_h = (uint64_t)state->dst_box.height;
struct wlr_fbox src_box = state->src_box;
if (wlr_fbox_empty(&src_box)) {
src_box = (struct wlr_fbox){
.width = width,
.height = height,
};
}
uint64_t src_x = to_fp16(src_box.x);
uint64_t src_y = to_fp16(src_box.y);
uint64_t src_w = to_fp16(src_box.width);
uint64_t src_h = to_fp16(src_box.height);
uint32_t fb_damage_clips = 0;
if (state->damage != NULL) {
uint32_t *ptr = wl_array_add(fb_damage_clips_arr, sizeof(fb_damage_clips));
if (ptr == NULL) {
return false;
}
create_fb_damage_clips_blob(drm, width, height,
state->damage, &fb_damage_clips);
*ptr = fb_damage_clips;
}
return
liftoff_layer_set_property(layer->liftoff, "zpos", zpos) == 0 &&
liftoff_layer_set_property(layer->liftoff, "CRTC_X", crtc_x) == 0 &&
liftoff_layer_set_property(layer->liftoff, "CRTC_Y", crtc_y) == 0 &&
liftoff_layer_set_property(layer->liftoff, "CRTC_W", crtc_w) == 0 &&
liftoff_layer_set_property(layer->liftoff, "CRTC_H", crtc_h) == 0 &&
liftoff_layer_set_property(layer->liftoff, "SRC_X", src_x) == 0 &&
liftoff_layer_set_property(layer->liftoff, "SRC_Y", src_y) == 0 &&
liftoff_layer_set_property(layer->liftoff, "SRC_W", src_w) == 0 &&
liftoff_layer_set_property(layer->liftoff, "SRC_H", src_h) == 0 &&
liftoff_layer_set_property(layer->liftoff, "FB_DAMAGE_CLIPS", fb_damage_clips) == 0;
}
static bool devid_from_fd(int fd, dev_t *devid) {
struct stat stat;
if (fstat(fd, &stat) != 0) {
wlr_log_errno(WLR_ERROR, "fstat failed");
return false;
}
*devid = stat.st_rdev;
return true;
}
static void update_layer_feedback(struct wlr_drm_backend *drm,
struct wlr_drm_layer *layer) {
bool changed = false;
for (size_t i = 0; i < drm->num_planes; i++) {
struct wlr_drm_plane *plane = &drm->planes[i];
bool is_candidate = liftoff_layer_is_candidate_plane(layer->liftoff,
plane->liftoff);
if (layer->candidate_planes[i] != is_candidate) {
layer->candidate_planes[i] = is_candidate;
changed = true;
}
}
if (!changed) {
return;
}
dev_t target_device;
if (!devid_from_fd(drm->fd, &target_device)) {
return;
}
struct wlr_drm_format_set formats = {0};
for (size_t i = 0; i < drm->num_planes; i++) {
struct wlr_drm_plane *plane = &drm->planes[i];
if (!layer->candidate_planes[i]) {
continue;
}
for (size_t j = 0; j < plane->formats.len; j++) {
const struct wlr_drm_format *format = &plane->formats.formats[j];
for (size_t k = 0; k < format->len; k++) {
wlr_drm_format_set_add(&formats, format->format,
format->modifiers[k]);
}
}
}
struct wlr_output_layer_feedback_event event = {
.target_device = target_device,
.formats = &formats,
};
wl_signal_emit_mutable(&layer->wlr->events.feedback, &event);
wlr_drm_format_set_finish(&formats);
}
static bool crtc_commit(struct wlr_drm_connector *conn,
const struct wlr_drm_connector_state *state,
struct wlr_drm_page_flip *page_flip, uint32_t flags, bool test_only) {
struct wlr_drm_backend *drm = conn->backend;
struct wlr_output *output = &conn->output;
struct wlr_drm_crtc *crtc = conn->crtc;
bool modeset = state->modeset;
bool active = state->active;
if (modeset && !register_planes_for_crtc(drm, crtc)) {
return false;
}
uint32_t mode_id = crtc->mode_id;
if (modeset) {
if (!create_mode_blob(conn, state, &mode_id)) {
return false;
}
}
uint32_t gamma_lut = crtc->gamma_lut;
if (state->base->committed & WLR_OUTPUT_STATE_GAMMA_LUT) {
// Fallback to legacy gamma interface when gamma properties are not
// available (can happen on older Intel GPUs that support gamma but not
// degamma).
if (crtc->props.gamma_lut == 0) {
if (!drm_legacy_crtc_set_gamma(drm, crtc,
state->base->gamma_lut_size,
state->base->gamma_lut)) {
return false;
}
} else {
if (!create_gamma_lut_blob(drm, state->base->gamma_lut_size,
state->base->gamma_lut, &gamma_lut)) {
return false;
}
}
}
struct wl_array fb_damage_clips_arr = {0};
uint32_t primary_fb_damage_clips = 0;
if ((state->base->committed & WLR_OUTPUT_STATE_DAMAGE) &&
crtc->primary->props.fb_damage_clips != 0) {
uint32_t *ptr = wl_array_add(&fb_damage_clips_arr, sizeof(primary_fb_damage_clips));
if (ptr == NULL) {
return false;
}
create_fb_damage_clips_blob(drm, state->primary_fb->wlr_buf->width,
state->primary_fb->wlr_buf->height, &state->base->damage,
&primary_fb_damage_clips);
*ptr = primary_fb_damage_clips;
}
bool prev_vrr_enabled =
output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED;
bool vrr_enabled = prev_vrr_enabled;
if ((state->base->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) &&
drm_connector_supports_vrr(conn)) {
vrr_enabled = state->base->adaptive_sync_enabled;
}
if (test_only) {
flags |= DRM_MODE_ATOMIC_TEST_ONLY;
}
if (modeset) {
flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
}
if (!test_only && state->nonblock) {
flags |= DRM_MODE_ATOMIC_NONBLOCK;
}
drmModeAtomicReq *req = drmModeAtomicAlloc();
if (req == NULL) {
wlr_log(WLR_ERROR, "drmModeAtomicAlloc failed");
return false;
}
bool ok = add_prop(req, conn->id, conn->props.crtc_id,
active ? crtc->id : 0);
if (modeset && active && conn->props.link_status != 0) {
ok = ok && add_prop(req, conn->id, conn->props.link_status,
DRM_MODE_LINK_STATUS_GOOD);
}
if (active && conn->props.content_type != 0) {
ok = ok && add_prop(req, conn->id, conn->props.content_type,
DRM_MODE_CONTENT_TYPE_GRAPHICS);
}
// TODO: set "max bpc"
ok = ok &&
add_prop(req, crtc->id, crtc->props.mode_id, mode_id) &&
add_prop(req, crtc->id, crtc->props.active, active);
if (active) {
if (crtc->props.gamma_lut != 0) {
ok = ok && add_prop(req, crtc->id, crtc->props.gamma_lut, gamma_lut);
}
if (crtc->props.vrr_enabled != 0) {
ok = ok && add_prop(req, crtc->id, crtc->props.vrr_enabled, vrr_enabled);
}
ok = ok &&
set_plane_props(crtc->primary, crtc->primary->liftoff_layer, state->primary_fb, 0, 0, 0) &&
set_plane_props(crtc->primary, crtc->liftoff_composition_layer, state->primary_fb, 0, 0, 0);
liftoff_layer_set_property(crtc->primary->liftoff_layer,
"FB_DAMAGE_CLIPS", primary_fb_damage_clips);
liftoff_layer_set_property(crtc->liftoff_composition_layer,
"FB_DAMAGE_CLIPS", primary_fb_damage_clips);
if (state->base->committed & WLR_OUTPUT_STATE_LAYERS) {
for (size_t i = 0; i < state->base->layers_len; i++) {
const struct wlr_output_layer_state *layer_state = &state->base->layers[i];
ok = ok && set_layer_props(drm, layer_state, i + 1,
&fb_damage_clips_arr);
}
}
if (crtc->cursor) {
if (drm_connector_is_cursor_visible(conn)) {
ok = ok && set_plane_props(crtc->cursor, crtc->cursor->liftoff_layer,
state->cursor_fb, conn->cursor_x, conn->cursor_y,
wl_list_length(&crtc->layers) + 1);
} else {
ok = ok && disable_plane(crtc->cursor);
}
}
} else {
ok = ok && disable_plane(crtc->primary);
if (crtc->cursor) {
ok = ok && disable_plane(crtc->cursor);
}
}
if (!ok) {
goto out;
}
int ret = liftoff_output_apply(crtc->liftoff, req, flags);
if (ret != 0) {
wlr_drm_conn_log(conn, test_only ? WLR_DEBUG : WLR_ERROR,
"liftoff_output_apply failed: %s", strerror(-ret));
ok = false;
goto out;
}
if (crtc->cursor &&
liftoff_layer_needs_composition(crtc->cursor->liftoff_layer)) {
wlr_drm_conn_log(conn, WLR_DEBUG, "Failed to scan-out cursor plane");
ok = false;
goto out;
}
ret = drmModeAtomicCommit(drm->fd, req, flags, page_flip);
if (ret != 0) {
wlr_drm_conn_log_errno(conn, test_only ? WLR_DEBUG : WLR_ERROR,
"Atomic commit failed");
ok = false;
goto out;
}
if (state->base->committed & WLR_OUTPUT_STATE_LAYERS) {
for (size_t i = 0; i < state->base->layers_len; i++) {
struct wlr_output_layer_state *layer_state = &state->base->layers[i];
struct wlr_drm_layer *layer = get_drm_layer(drm, layer_state->layer);
layer_state->accepted =
!liftoff_layer_needs_composition(layer->liftoff);
if (!test_only && !layer_state->accepted) {
update_layer_feedback(drm, layer);
}
}
}
out:
drmModeAtomicFree(req);
if (ok && !test_only) {
if (!crtc->own_mode_id) {
crtc->mode_id = 0; // don't try to delete previous master's blobs
}
crtc->own_mode_id = true;
commit_blob(drm, &crtc->mode_id, mode_id);
commit_blob(drm, &crtc->gamma_lut, gamma_lut);
if (vrr_enabled != prev_vrr_enabled) {
output->adaptive_sync_status = vrr_enabled ?
WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED :
WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED;
wlr_drm_conn_log(conn, WLR_DEBUG, "VRR %s",
vrr_enabled ? "enabled" : "disabled");
}
} else {
rollback_blob(drm, &crtc->mode_id, mode_id);
rollback_blob(drm, &crtc->gamma_lut, gamma_lut);
}
uint32_t *fb_damage_clips_ptr;
wl_array_for_each(fb_damage_clips_ptr, &fb_damage_clips_arr) {
if (drmModeDestroyPropertyBlob(drm->fd, *fb_damage_clips_ptr) != 0) {
wlr_log_errno(WLR_ERROR, "Failed to destroy FB_DAMAGE_CLIPS property blob");
}
}
wl_array_release(&fb_damage_clips_arr);
return ok;
}
const struct wlr_drm_interface liftoff_iface = {
.init = init,
.finish = finish,
.crtc_commit = crtc_commit,
.reset = drm_atomic_reset,
};

56
backend/drm/meson.build Normal file
View file

@ -0,0 +1,56 @@
hwdata = dependency(
'hwdata',
required: 'drm' in backends,
native: true,
not_found_message: 'Required for the DRM backend.',
)
libdisplay_info = dependency(
'libdisplay-info',
required: 'drm' in backends,
fallback: 'libdisplay-info',
not_found_message: 'Required for the DRM backend.',
)
libliftoff = dependency(
'libliftoff',
version: '>=0.4.0',
fallback: 'libliftoff',
required: false,
)
if not (hwdata.found() and libdisplay_info.found() and features['session'])
subdir_done()
endif
hwdata_dir = hwdata.get_variable(pkgconfig: 'pkgdatadir')
pnpids_c = custom_target(
'pnpids.c',
output: 'pnpids.c',
input: files(hwdata_dir / 'pnp.ids'),
feed: true,
capture: true,
command: files('gen_pnpids.sh'),
)
wlr_files += pnpids_c
wlr_files += files(
'atomic.c',
'backend.c',
'drm.c',
'fb.c',
'legacy.c',
'monitor.c',
'properties.c',
'renderer.c',
'util.c',
)
if libliftoff.found()
wlr_files += files('libliftoff.c')
endif
features += { 'drm-backend': true }
internal_features += { 'libliftoff': libliftoff.found() }
wlr_deps += libdisplay_info
wlr_deps += libliftoff

91
backend/drm/monitor.c Normal file
View file

@ -0,0 +1,91 @@
#include <wlr/util/log.h>
#include <stdlib.h>
#include "backend/drm/monitor.h"
#include "backend/multi.h"
#include "backend/session/session.h"
static void drm_backend_monitor_destroy(struct wlr_drm_backend_monitor* monitor) {
wl_list_remove(&monitor->session_add_drm_card.link);
wl_list_remove(&monitor->session_destroy.link);
wl_list_remove(&monitor->primary_drm_destroy.link);
wl_list_remove(&monitor->multi_destroy.link);
free(monitor);
}
static void handle_add_drm_card(struct wl_listener *listener, void *data) {
struct wlr_session_add_event *event = data;
struct wlr_drm_backend_monitor *backend_monitor =
wl_container_of(listener, backend_monitor, session_add_drm_card);
struct wlr_device *dev =
session_open_if_kms(backend_monitor->session, event->path);
if (!dev) {
wlr_log(WLR_ERROR, "Unable to open %s as DRM device", event->path);
return;
}
wlr_log(WLR_DEBUG, "Creating DRM backend for %s after hotplug", event->path);
struct wlr_backend *child_drm = wlr_drm_backend_create(backend_monitor->session,
dev, backend_monitor->primary_drm);
if (!child_drm) {
wlr_log(WLR_ERROR, "Failed to create DRM backend after hotplug");
return;
}
if (!wlr_multi_backend_add(backend_monitor->multi, child_drm)) {
wlr_log(WLR_ERROR, "Failed to add new drm backend to multi backend");
wlr_backend_destroy(child_drm);
return;
}
if (!wlr_backend_start(child_drm)) {
wlr_log(WLR_ERROR, "Failed to start new child DRM backend");
wlr_backend_destroy(child_drm);
}
}
static void handle_session_destroy(struct wl_listener *listener, void *data) {
struct wlr_drm_backend_monitor *backend_monitor =
wl_container_of(listener, backend_monitor, session_destroy);
drm_backend_monitor_destroy(backend_monitor);
}
static void handle_primary_drm_destroy(struct wl_listener *listener, void *data) {
struct wlr_drm_backend_monitor *backend_monitor =
wl_container_of(listener, backend_monitor, primary_drm_destroy);
drm_backend_monitor_destroy(backend_monitor);
}
static void handle_multi_destroy(struct wl_listener *listener, void *data) {
struct wlr_drm_backend_monitor *backend_monitor =
wl_container_of(listener, backend_monitor, multi_destroy);
drm_backend_monitor_destroy(backend_monitor);
}
struct wlr_drm_backend_monitor *drm_backend_monitor_create(
struct wlr_backend *multi, struct wlr_backend *primary_drm,
struct wlr_session *session) {
struct wlr_drm_backend_monitor *monitor = calloc(1, sizeof(*monitor));
if (!monitor) {
wlr_log_errno(WLR_ERROR, "Allocation failed");
return NULL;
}
monitor->multi = multi;
monitor->primary_drm = primary_drm;
monitor->session = session;
monitor->session_add_drm_card.notify = handle_add_drm_card;
wl_signal_add(&session->events.add_drm_card, &monitor->session_add_drm_card);
monitor->session_destroy.notify = handle_session_destroy;
wl_signal_add(&session->events.destroy, &monitor->session_destroy);
monitor->primary_drm_destroy.notify = handle_primary_drm_destroy;
wl_signal_add(&primary_drm->events.destroy, &monitor->primary_drm_destroy);
monitor->multi_destroy.notify = handle_multi_destroy;
wl_signal_add(&multi->events.destroy, &monitor->multi_destroy);
return monitor;
}

209
backend/drm/properties.c Normal file
View file

@ -0,0 +1,209 @@
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <wlr/util/log.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include "backend/drm/properties.h"
/*
* Creates a mapping between property names and an array index where to store
* the ids. The prop_info arrays must be sorted by name, as bsearch is used to
* search them.
*/
struct prop_info {
const char *name;
size_t index;
};
static const struct prop_info connector_info[] = {
#define INDEX(name) (offsetof(union wlr_drm_connector_props, name) / sizeof(uint32_t))
{ "CRTC_ID", INDEX(crtc_id) },
{ "DPMS", INDEX(dpms) },
{ "EDID", INDEX(edid) },
{ "PATH", INDEX(path) },
{ "content type", INDEX(content_type) },
{ "link-status", INDEX(link_status) },
{ "max bpc", INDEX(max_bpc) },
{ "non-desktop", INDEX(non_desktop) },
{ "panel orientation", INDEX(panel_orientation) },
{ "subconnector", INDEX(subconnector) },
{ "vrr_capable", INDEX(vrr_capable) },
#undef INDEX
};
static const struct prop_info crtc_info[] = {
#define INDEX(name) (offsetof(union wlr_drm_crtc_props, name) / sizeof(uint32_t))
{ "ACTIVE", INDEX(active) },
{ "GAMMA_LUT", INDEX(gamma_lut) },
{ "GAMMA_LUT_SIZE", INDEX(gamma_lut_size) },
{ "MODE_ID", INDEX(mode_id) },
{ "VRR_ENABLED", INDEX(vrr_enabled) },
#undef INDEX
};
static const struct prop_info plane_info[] = {
#define INDEX(name) (offsetof(union wlr_drm_plane_props, name) / sizeof(uint32_t))
{ "CRTC_H", INDEX(crtc_h) },
{ "CRTC_ID", INDEX(crtc_id) },
{ "CRTC_W", INDEX(crtc_w) },
{ "CRTC_X", INDEX(crtc_x) },
{ "CRTC_Y", INDEX(crtc_y) },
{ "FB_DAMAGE_CLIPS", INDEX(fb_damage_clips) },
{ "FB_ID", INDEX(fb_id) },
{ "IN_FORMATS", INDEX(in_formats) },
{ "SRC_H", INDEX(src_h) },
{ "SRC_W", INDEX(src_w) },
{ "SRC_X", INDEX(src_x) },
{ "SRC_Y", INDEX(src_y) },
{ "rotation", INDEX(rotation) },
{ "type", INDEX(type) },
#undef INDEX
};
static int cmp_prop_info(const void *arg1, const void *arg2) {
const char *key = arg1;
const struct prop_info *elem = arg2;
return strcmp(key, elem->name);
}
static bool scan_properties(int fd, uint32_t id, uint32_t type, uint32_t *result,
const struct prop_info *info, size_t info_len) {
drmModeObjectProperties *props = drmModeObjectGetProperties(fd, id, type);
if (!props) {
wlr_log_errno(WLR_ERROR, "Failed to get DRM object properties");
return false;
}
for (uint32_t i = 0; i < props->count_props; ++i) {
drmModePropertyRes *prop = drmModeGetProperty(fd, props->props[i]);
if (!prop) {
wlr_log_errno(WLR_ERROR, "Failed to get DRM object property");
continue;
}
const struct prop_info *p =
bsearch(prop->name, info, info_len, sizeof(info[0]), cmp_prop_info);
if (p) {
result[p->index] = prop->prop_id;
}
drmModeFreeProperty(prop);
}
drmModeFreeObjectProperties(props);
return true;
}
bool get_drm_connector_props(int fd, uint32_t id,
union wlr_drm_connector_props *out) {
return scan_properties(fd, id, DRM_MODE_OBJECT_CONNECTOR, out->props,
connector_info, sizeof(connector_info) / sizeof(connector_info[0]));
}
bool get_drm_crtc_props(int fd, uint32_t id, union wlr_drm_crtc_props *out) {
return scan_properties(fd, id, DRM_MODE_OBJECT_CRTC, out->props,
crtc_info, sizeof(crtc_info) / sizeof(crtc_info[0]));
}
bool get_drm_plane_props(int fd, uint32_t id, union wlr_drm_plane_props *out) {
return scan_properties(fd, id, DRM_MODE_OBJECT_PLANE, out->props,
plane_info, sizeof(plane_info) / sizeof(plane_info[0]));
}
bool get_drm_prop(int fd, uint32_t obj, uint32_t prop, uint64_t *ret) {
drmModeObjectProperties *props =
drmModeObjectGetProperties(fd, obj, DRM_MODE_OBJECT_ANY);
if (!props) {
return false;
}
bool found = false;
for (uint32_t i = 0; i < props->count_props; ++i) {
if (props->props[i] == prop) {
*ret = props->prop_values[i];
found = true;
break;
}
}
drmModeFreeObjectProperties(props);
return found;
}
void *get_drm_prop_blob(int fd, uint32_t obj, uint32_t prop, size_t *ret_len) {
uint64_t blob_id;
if (!get_drm_prop(fd, obj, prop, &blob_id)) {
return NULL;
}
drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(fd, blob_id);
if (!blob) {
return NULL;
}
void *ptr = malloc(blob->length);
if (!ptr) {
drmModeFreePropertyBlob(blob);
return NULL;
}
memcpy(ptr, blob->data, blob->length);
*ret_len = blob->length;
drmModeFreePropertyBlob(blob);
return ptr;
}
char *get_drm_prop_enum(int fd, uint32_t obj, uint32_t prop_id) {
uint64_t value;
if (!get_drm_prop(fd, obj, prop_id, &value)) {
return NULL;
}
drmModePropertyRes *prop = drmModeGetProperty(fd, prop_id);
if (!prop) {
return NULL;
}
char *str = NULL;
for (int i = 0; i < prop->count_enums; i++) {
if (prop->enums[i].value == value) {
str = strdup(prop->enums[i].name);
break;
}
}
drmModeFreeProperty(prop);
return str;
}
bool introspect_drm_prop_range(int fd, uint32_t prop_id,
uint64_t *min, uint64_t *max) {
drmModePropertyRes *prop = drmModeGetProperty(fd, prop_id);
if (!prop) {
return false;
}
if (drmModeGetPropertyType(prop) != DRM_MODE_PROP_RANGE) {
drmModeFreeProperty(prop);
return false;
}
assert(prop->count_values == 2);
if (min != NULL) {
*min = prop->values[0];
}
if (max != NULL) {
*max = prop->values[1];
}
drmModeFreeProperty(prop);
return true;
}

172
backend/drm/renderer.c Normal file
View file

@ -0,0 +1,172 @@
#include <assert.h>
#include <drm_fourcc.h>
#include <wlr/render/swapchain.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/util/log.h>
#include "backend/drm/drm.h"
#include "backend/drm/fb.h"
#include "backend/drm/renderer.h"
#include "backend/backend.h"
#include "render/drm_format_set.h"
#include "render/allocator/allocator.h"
#include "render/pixel_format.h"
#include "render/wlr_renderer.h"
bool init_drm_renderer(struct wlr_drm_backend *drm,
struct wlr_drm_renderer *renderer) {
renderer->wlr_rend = renderer_autocreate_with_drm_fd(drm->fd);
if (!renderer->wlr_rend) {
wlr_log(WLR_ERROR, "Failed to create renderer");
return false;
}
uint32_t backend_caps = backend_get_buffer_caps(&drm->backend);
renderer->allocator = allocator_autocreate_with_drm_fd(backend_caps,
renderer->wlr_rend, drm->fd);
if (renderer->allocator == NULL) {
wlr_log(WLR_ERROR, "Failed to create allocator");
wlr_renderer_destroy(renderer->wlr_rend);
return false;
}
return true;
}
void finish_drm_renderer(struct wlr_drm_renderer *renderer) {
if (!renderer) {
return;
}
wlr_allocator_destroy(renderer->allocator);
wlr_renderer_destroy(renderer->wlr_rend);
}
void finish_drm_surface(struct wlr_drm_surface *surf) {
if (!surf || !surf->renderer) {
return;
}
wlr_swapchain_destroy(surf->swapchain);
*surf = (struct wlr_drm_surface){0};
}
bool init_drm_surface(struct wlr_drm_surface *surf,
struct wlr_drm_renderer *renderer, int width, int height,
const struct wlr_drm_format *drm_format) {
if (surf->swapchain != NULL && surf->swapchain->width == width &&
surf->swapchain->height == height) {
return true;
}
finish_drm_surface(surf);
surf->swapchain = wlr_swapchain_create(renderer->allocator, width, height,
drm_format);
if (surf->swapchain == NULL) {
wlr_log(WLR_ERROR, "Failed to create swapchain");
return false;
}
surf->renderer = renderer;
return true;
}
struct wlr_buffer *drm_surface_blit(struct wlr_drm_surface *surf,
struct wlr_buffer *buffer) {
struct wlr_renderer *renderer = surf->renderer->wlr_rend;
if (surf->swapchain->width != buffer->width ||
surf->swapchain->height != buffer->height) {
wlr_log(WLR_ERROR, "Surface size doesn't match buffer size");
return NULL;
}
struct wlr_texture *tex = wlr_texture_from_buffer(renderer, buffer);
if (tex == NULL) {
wlr_log(WLR_ERROR, "Failed to import source buffer into multi-GPU renderer");
return NULL;
}
struct wlr_buffer *dst = wlr_swapchain_acquire(surf->swapchain, NULL);
if (!dst) {
wlr_log(WLR_ERROR, "Failed to acquire multi-GPU swapchain buffer");
goto error_tex;
}
struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, dst, NULL);
if (pass == NULL) {
wlr_log(WLR_ERROR, "Failed to begin render pass with multi-GPU destination buffer");
goto error_dst;
}
wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options){
.texture = tex,
.blend_mode = WLR_RENDER_BLEND_MODE_NONE,
});
if (!wlr_render_pass_submit(pass)) {
wlr_log(WLR_ERROR, "Failed to submit multi-GPU render pass");
goto error_dst;
}
wlr_texture_destroy(tex);
return dst;
error_dst:
wlr_buffer_unlock(dst);
error_tex:
wlr_texture_destroy(tex);
return NULL;
}
bool drm_plane_pick_render_format(struct wlr_drm_plane *plane,
struct wlr_drm_format *fmt, struct wlr_drm_renderer *renderer) {
const struct wlr_drm_format_set *render_formats =
wlr_renderer_get_render_formats(renderer->wlr_rend);
if (render_formats == NULL) {
wlr_log(WLR_ERROR, "Failed to get render formats");
return false;
}
const struct wlr_drm_format_set *plane_formats = &plane->formats;
uint32_t format = DRM_FORMAT_ARGB8888;
if (!wlr_drm_format_set_get(&plane->formats, format)) {
const struct wlr_pixel_format_info *format_info =
drm_get_pixel_format_info(format);
assert(format_info != NULL &&
format_info->opaque_substitute != DRM_FORMAT_INVALID);
format = format_info->opaque_substitute;
}
const struct wlr_drm_format *render_format =
wlr_drm_format_set_get(render_formats, format);
if (render_format == NULL) {
wlr_log(WLR_DEBUG, "Renderer doesn't support format 0x%"PRIX32, format);
return false;
}
const struct wlr_drm_format *plane_format =
wlr_drm_format_set_get(plane_formats, format);
if (plane_format == NULL) {
wlr_log(WLR_DEBUG, "Plane %"PRIu32" doesn't support format 0x%"PRIX32,
plane->id, format);
return false;
}
if (!wlr_drm_format_intersect(fmt, plane_format, render_format)) {
wlr_log(WLR_DEBUG, "Failed to intersect plane and render "
"modifiers for format 0x%"PRIX32, format);
return false;
}
if (fmt->len == 0) {
wlr_drm_format_finish(fmt);
wlr_log(WLR_DEBUG, "Failed to find matching plane and renderer modifiers");
return false;
}
return true;
}

279
backend/drm/util.c Normal file
View file

@ -0,0 +1,279 @@
#include <assert.h>
#include <drm_fourcc.h>
#include <drm_mode.h>
#include <drm.h>
#include <libdisplay-info/cvt.h>
#include <libdisplay-info/edid.h>
#include <libdisplay-info/info.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wlr/util/log.h>
#include "backend/drm/drm.h"
#include "backend/drm/util.h"
int32_t calculate_refresh_rate(const drmModeModeInfo *mode) {
int32_t refresh = (mode->clock * 1000000LL / mode->htotal +
mode->vtotal / 2) / mode->vtotal;
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
refresh *= 2;
}
if (mode->flags & DRM_MODE_FLAG_DBLSCAN) {
refresh /= 2;
}
if (mode->vscan > 1) {
refresh /= mode->vscan;
}
return refresh;
}
enum wlr_output_mode_aspect_ratio get_picture_aspect_ratio(const drmModeModeInfo *mode) {
switch (mode->flags & DRM_MODE_FLAG_PIC_AR_MASK) {
case DRM_MODE_FLAG_PIC_AR_NONE:
return WLR_OUTPUT_MODE_ASPECT_RATIO_NONE;
case DRM_MODE_FLAG_PIC_AR_4_3:
return WLR_OUTPUT_MODE_ASPECT_RATIO_4_3;
case DRM_MODE_FLAG_PIC_AR_16_9:
return WLR_OUTPUT_MODE_ASPECT_RATIO_16_9;
case DRM_MODE_FLAG_PIC_AR_64_27:
return WLR_OUTPUT_MODE_ASPECT_RATIO_64_27;
case DRM_MODE_FLAG_PIC_AR_256_135:
return WLR_OUTPUT_MODE_ASPECT_RATIO_256_135;
default:
wlr_log(WLR_ERROR, "Unknown mode picture aspect ratio: %u",
mode->flags & DRM_MODE_FLAG_PIC_AR_MASK);
return WLR_OUTPUT_MODE_ASPECT_RATIO_NONE;
}
}
void parse_edid(struct wlr_drm_connector *conn, size_t len, const uint8_t *data) {
struct wlr_output *output = &conn->output;
free(output->make);
free(output->model);
free(output->serial);
output->make = NULL;
output->model = NULL;
output->serial = NULL;
struct di_info *info = di_info_parse_edid(data, len);
if (info == NULL) {
wlr_log(WLR_ERROR, "Failed to parse EDID");
return;
}
const struct di_edid *edid = di_info_get_edid(info);
const struct di_edid_vendor_product *vendor_product = di_edid_get_vendor_product(edid);
char pnp_id[] = {
vendor_product->manufacturer[0],
vendor_product->manufacturer[1],
vendor_product->manufacturer[2],
'\0',
};
const char *manu = get_pnp_manufacturer(vendor_product->manufacturer);
if (!manu) {
manu = pnp_id;
}
output->make = strdup(manu);
output->model = di_info_get_model(info);
output->serial = di_info_get_serial(info);
di_info_destroy(info);
}
const char *drm_connector_status_str(drmModeConnection status) {
switch (status) {
case DRM_MODE_CONNECTED:
return "connected";
case DRM_MODE_DISCONNECTED:
return "disconnected";
case DRM_MODE_UNKNOWNCONNECTION:
return "unknown";
}
return "<unsupported>";
}
static bool is_taken(size_t n, const uint32_t arr[static n], uint32_t key) {
for (size_t i = 0; i < n; ++i) {
if (arr[i] == key) {
return true;
}
}
return false;
}
/*
* Store all of the non-recursive state in a struct, so we aren't literally
* passing 12 arguments to a function.
*/
struct match_state {
const size_t num_objs;
const uint32_t *restrict objs;
const size_t num_res;
size_t score;
size_t replaced;
uint32_t *restrict res;
uint32_t *restrict best;
const uint32_t *restrict orig;
bool exit_early;
};
/*
* skips: The number of SKIP elements encountered so far.
* score: The number of resources we've matched so far.
* replaced: The number of changes from the original solution.
* i: The index of the current element.
*
* This tries to match a solution as close to st->orig as it can.
*
* Returns whether we've set a new best element with this solution.
*/
static bool match_obj_(struct match_state *st, size_t skips, size_t score, size_t replaced, size_t i) {
// Finished
if (i >= st->num_res) {
if (score > st->score ||
(score == st->score && replaced < st->replaced)) {
st->score = score;
st->replaced = replaced;
memcpy(st->best, st->res, sizeof(st->best[0]) * st->num_res);
st->exit_early = (st->score == st->num_res - skips
|| st->score == st->num_objs)
&& st->replaced == 0;
return true;
} else {
return false;
}
}
if (st->orig[i] == SKIP) {
st->res[i] = SKIP;
return match_obj_(st, skips + 1, score, replaced, i + 1);
}
bool has_best = false;
/*
* Attempt to use the current solution first, to try and avoid
* recalculating everything
*/
if (st->orig[i] != UNMATCHED && !is_taken(i, st->res, st->orig[i])) {
st->res[i] = st->orig[i];
size_t obj_score = st->objs[st->res[i]] != 0 ? 1 : 0;
if (match_obj_(st, skips, score + obj_score, replaced, i + 1)) {
has_best = true;
}
}
if (st->orig[i] == UNMATCHED) {
st->res[i] = UNMATCHED;
if (match_obj_(st, skips, score, replaced, i + 1)) {
has_best = true;
}
}
if (st->exit_early) {
return true;
}
if (st->orig[i] != UNMATCHED) {
++replaced;
}
for (size_t candidate = 0; candidate < st->num_objs; ++candidate) {
// We tried this earlier
if (candidate == st->orig[i]) {
continue;
}
// Not compatible
if (!(st->objs[candidate] & (1 << i))) {
continue;
}
// Already taken
if (is_taken(i, st->res, candidate)) {
continue;
}
st->res[i] = candidate;
size_t obj_score = st->objs[candidate] != 0 ? 1 : 0;
if (match_obj_(st, skips, score + obj_score, replaced, i + 1)) {
has_best = true;
}
if (st->exit_early) {
return true;
}
}
if (has_best) {
return true;
}
// Maybe this resource can't be matched
st->res[i] = UNMATCHED;
return match_obj_(st, skips, score, replaced, i + 1);
}
size_t match_obj(size_t num_objs, const uint32_t objs[static restrict num_objs],
size_t num_res, const uint32_t res[static restrict num_res],
uint32_t out[static restrict num_res]) {
uint32_t solution[num_res];
for (size_t i = 0; i < num_res; ++i) {
solution[i] = UNMATCHED;
}
struct match_state st = {
.num_objs = num_objs,
.num_res = num_res,
.score = 0,
.replaced = SIZE_MAX,
.objs = objs,
.res = solution,
.best = out,
.orig = res,
.exit_early = false,
};
match_obj_(&st, 0, 0, 0, 0);
return st.score;
}
void generate_cvt_mode(drmModeModeInfo *mode, int hdisplay, int vdisplay,
float vrefresh) {
// TODO: depending on capabilities advertised in the EDID, use reduced
// blanking if possible (and update sync polarity)
struct di_cvt_options options = {
.red_blank_ver = DI_CVT_REDUCED_BLANKING_NONE,
.h_pixels = hdisplay,
.v_lines = vdisplay,
.ip_freq_rqd = vrefresh ? vrefresh : 60,
};
struct di_cvt_timing timing;
di_cvt_compute(&timing, &options);
uint16_t hsync_start = hdisplay + timing.h_front_porch;
uint16_t vsync_start = timing.v_lines_rnd + timing.v_front_porch;
uint16_t hsync_end = hsync_start + timing.h_sync;
uint16_t vsync_end = vsync_start + timing.v_sync;
*mode = (drmModeModeInfo){
.clock = roundf(timing.act_pixel_freq * 1000),
.hdisplay = hdisplay,
.vdisplay = timing.v_lines_rnd,
.hsync_start = hsync_start,
.vsync_start = vsync_start,
.hsync_end = hsync_end,
.vsync_end = vsync_end,
.htotal = hsync_end + timing.h_back_porch,
.vtotal = vsync_end + timing.v_back_porch,
.vrefresh = roundf(timing.act_frame_rate),
.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC,
};
snprintf(mode->name, sizeof(mode->name), "%dx%d", hdisplay, vdisplay);
}

View file

@ -0,0 +1,88 @@
#include <assert.h>
#include <stdlib.h>
#include <wlr/interfaces/wlr_output.h>
#include <wlr/util/log.h>
#include "backend/headless.h"
struct wlr_headless_backend *headless_backend_from_backend(
struct wlr_backend *wlr_backend) {
assert(wlr_backend_is_headless(wlr_backend));
struct wlr_headless_backend *backend = wl_container_of(wlr_backend, backend, backend);
return backend;
}
static bool backend_start(struct wlr_backend *wlr_backend) {
struct wlr_headless_backend *backend =
headless_backend_from_backend(wlr_backend);
wlr_log(WLR_INFO, "Starting headless backend");
struct wlr_headless_output *output;
wl_list_for_each(output, &backend->outputs, link) {
wl_signal_emit_mutable(&backend->backend.events.new_output,
&output->wlr_output);
}
backend->started = true;
return true;
}
static void backend_destroy(struct wlr_backend *wlr_backend) {
struct wlr_headless_backend *backend =
headless_backend_from_backend(wlr_backend);
if (!wlr_backend) {
return;
}
wlr_backend_finish(wlr_backend);
struct wlr_headless_output *output, *output_tmp;
wl_list_for_each_safe(output, output_tmp, &backend->outputs, link) {
wlr_output_destroy(&output->wlr_output);
}
wl_list_remove(&backend->event_loop_destroy.link);
free(backend);
}
static uint32_t get_buffer_caps(struct wlr_backend *wlr_backend) {
return WLR_BUFFER_CAP_DATA_PTR
| WLR_BUFFER_CAP_DMABUF
| WLR_BUFFER_CAP_SHM;
}
static const struct wlr_backend_impl backend_impl = {
.start = backend_start,
.destroy = backend_destroy,
.get_buffer_caps = get_buffer_caps,
};
static void handle_event_loop_destroy(struct wl_listener *listener, void *data) {
struct wlr_headless_backend *backend =
wl_container_of(listener, backend, event_loop_destroy);
backend_destroy(&backend->backend);
}
struct wlr_backend *wlr_headless_backend_create(struct wl_event_loop *loop) {
wlr_log(WLR_INFO, "Creating headless backend");
struct wlr_headless_backend *backend = calloc(1, sizeof(*backend));
if (!backend) {
wlr_log(WLR_ERROR, "Failed to allocate wlr_headless_backend");
return NULL;
}
wlr_backend_init(&backend->backend, &backend_impl);
backend->event_loop = loop;
wl_list_init(&backend->outputs);
backend->event_loop_destroy.notify = handle_event_loop_destroy;
wl_event_loop_add_destroy_listener(loop, &backend->event_loop_destroy);
return &backend->backend;
}
bool wlr_backend_is_headless(struct wlr_backend *backend) {
return backend->impl == &backend_impl;
}

View file

@ -0,0 +1,4 @@
wlr_files += files(
'backend.c',
'output.c',
)

146
backend/headless/output.c Normal file
View file

@ -0,0 +1,146 @@
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <wlr/interfaces/wlr_output.h>
#include <wlr/types/wlr_output_layer.h>
#include <wlr/util/log.h>
#include "backend/headless.h"
#include "types/wlr_output.h"
static const uint32_t SUPPORTED_OUTPUT_STATE =
WLR_OUTPUT_STATE_BACKEND_OPTIONAL |
WLR_OUTPUT_STATE_BUFFER |
WLR_OUTPUT_STATE_ENABLED |
WLR_OUTPUT_STATE_MODE;
static size_t last_output_num = 0;
static struct wlr_headless_output *headless_output_from_output(
struct wlr_output *wlr_output) {
assert(wlr_output_is_headless(wlr_output));
struct wlr_headless_output *output = wl_container_of(wlr_output, output, wlr_output);
return output;
}
static void output_update_refresh(struct wlr_headless_output *output,
int32_t refresh) {
if (refresh <= 0) {
refresh = HEADLESS_DEFAULT_REFRESH;
}
output->frame_delay = 1000000 / refresh;
}
static bool output_test(struct wlr_output *wlr_output,
const struct wlr_output_state *state) {
uint32_t unsupported = state->committed & ~SUPPORTED_OUTPUT_STATE;
if (unsupported != 0) {
wlr_log(WLR_DEBUG, "Unsupported output state fields: 0x%"PRIx32,
unsupported);
return false;
}
if (state->committed & WLR_OUTPUT_STATE_MODE) {
assert(state->mode_type == WLR_OUTPUT_STATE_MODE_CUSTOM);
}
if (state->committed & WLR_OUTPUT_STATE_LAYERS) {
for (size_t i = 0; i < state->layers_len; i++) {
state->layers[i].accepted = true;
}
}
return true;
}
static bool output_commit(struct wlr_output *wlr_output,
const struct wlr_output_state *state) {
struct wlr_headless_output *output =
headless_output_from_output(wlr_output);
if (!output_test(wlr_output, state)) {
return false;
}
if (state->committed & WLR_OUTPUT_STATE_MODE) {
output_update_refresh(output, state->custom_mode.refresh);
}
if (output_pending_enabled(wlr_output, state)) {
struct wlr_output_event_present present_event = {
.commit_seq = wlr_output->commit_seq + 1,
.presented = true,
};
output_defer_present(wlr_output, present_event);
wl_event_source_timer_update(output->frame_timer, output->frame_delay);
}
return true;
}
static void output_destroy(struct wlr_output *wlr_output) {
struct wlr_headless_output *output =
headless_output_from_output(wlr_output);
wl_list_remove(&output->link);
wl_event_source_remove(output->frame_timer);
free(output);
}
static const struct wlr_output_impl output_impl = {
.destroy = output_destroy,
.commit = output_commit,
};
bool wlr_output_is_headless(struct wlr_output *wlr_output) {
return wlr_output->impl == &output_impl;
}
static int signal_frame(void *data) {
struct wlr_headless_output *output = data;
wlr_output_send_frame(&output->wlr_output);
return 0;
}
struct wlr_output *wlr_headless_add_output(struct wlr_backend *wlr_backend,
unsigned int width, unsigned int height) {
struct wlr_headless_backend *backend =
headless_backend_from_backend(wlr_backend);
struct wlr_headless_output *output = calloc(1, sizeof(*output));
if (output == NULL) {
wlr_log(WLR_ERROR, "Failed to allocate wlr_headless_output");
return NULL;
}
output->backend = backend;
struct wlr_output *wlr_output = &output->wlr_output;
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_custom_mode(&state, width, height, 0);
wlr_output_init(wlr_output, &backend->backend, &output_impl, backend->event_loop, &state);
wlr_output_state_finish(&state);
output_update_refresh(output, 0);
size_t output_num = ++last_output_num;
char name[64];
snprintf(name, sizeof(name), "HEADLESS-%zu", output_num);
wlr_output_set_name(wlr_output, name);
char description[128];
snprintf(description, sizeof(description), "Headless output %zu", output_num);
wlr_output_set_description(wlr_output, description);
output->frame_timer = wl_event_loop_add_timer(backend->event_loop, signal_frame, output);
wl_list_insert(&backend->outputs, &output->link);
if (backend->started) {
wl_signal_emit_mutable(&backend->backend.events.new_output, wlr_output);
}
return wlr_output;
}

245
backend/libinput/backend.c Normal file
View file

@ -0,0 +1,245 @@
#include <assert.h>
#include <libinput.h>
#include <stdlib.h>
#include <stdio.h>
#include <wlr/backend/interface.h>
#include <wlr/backend/session.h>
#include <wlr/util/log.h>
#include "backend/libinput.h"
#include "util/env.h"
static struct wlr_libinput_backend *get_libinput_backend_from_backend(
struct wlr_backend *wlr_backend) {
assert(wlr_backend_is_libinput(wlr_backend));
struct wlr_libinput_backend *backend = wl_container_of(wlr_backend, backend, backend);
return backend;
}
static int libinput_open_restricted(const char *path,
int flags, void *_backend) {
struct wlr_libinput_backend *backend = _backend;
struct wlr_device *dev = wlr_session_open_file(backend->session, path);
if (dev == NULL) {
return -1;
}
return dev->fd;
}
static void libinput_close_restricted(int fd, void *_backend) {
struct wlr_libinput_backend *backend = _backend;
struct wlr_device *dev;
bool found = false;
wl_list_for_each(dev, &backend->session->devices, link) {
if (dev->fd == fd) {
found = true;
break;
}
}
if (found) {
wlr_session_close_file(backend->session, dev);
}
}
static const struct libinput_interface libinput_impl = {
.open_restricted = libinput_open_restricted,
.close_restricted = libinput_close_restricted
};
static int handle_libinput_readable(int fd, uint32_t mask, void *_backend) {
struct wlr_libinput_backend *backend = _backend;
int ret = libinput_dispatch(backend->libinput_context);
if (ret != 0) {
wlr_log(WLR_ERROR, "Failed to dispatch libinput: %s", strerror(-ret));
wlr_backend_destroy(&backend->backend);
return 0;
}
struct libinput_event *event;
while ((event = libinput_get_event(backend->libinput_context))) {
handle_libinput_event(backend, event);
libinput_event_destroy(event);
}
return 0;
}
static enum wlr_log_importance libinput_log_priority_to_wlr(
enum libinput_log_priority priority) {
switch (priority) {
case LIBINPUT_LOG_PRIORITY_ERROR:
return WLR_ERROR;
case LIBINPUT_LOG_PRIORITY_INFO:
return WLR_INFO;
default:
return WLR_DEBUG;
}
}
static void log_libinput(struct libinput *libinput_context,
enum libinput_log_priority priority, const char *fmt, va_list args) {
enum wlr_log_importance importance = libinput_log_priority_to_wlr(priority);
static char wlr_fmt[1024];
snprintf(wlr_fmt, sizeof(wlr_fmt), "[libinput] %s", fmt);
_wlr_vlog(importance, wlr_fmt, args);
}
static bool backend_start(struct wlr_backend *wlr_backend) {
struct wlr_libinput_backend *backend =
get_libinput_backend_from_backend(wlr_backend);
wlr_log(WLR_DEBUG, "Starting libinput backend");
backend->libinput_context = libinput_udev_create_context(&libinput_impl,
backend, backend->session->udev);
if (!backend->libinput_context) {
wlr_log(WLR_ERROR, "Failed to create libinput context");
return false;
}
if (libinput_udev_assign_seat(backend->libinput_context,
backend->session->seat) != 0) {
wlr_log(WLR_ERROR, "Failed to assign libinput seat");
return false;
}
// TODO: More sophisticated logging
libinput_log_set_handler(backend->libinput_context, log_libinput);
libinput_log_set_priority(backend->libinput_context, LIBINPUT_LOG_PRIORITY_ERROR);
int libinput_fd = libinput_get_fd(backend->libinput_context);
if (!env_parse_bool("WLR_LIBINPUT_NO_DEVICES") && wl_list_empty(&backend->devices)) {
handle_libinput_readable(libinput_fd, WL_EVENT_READABLE, backend);
if (wl_list_empty(&backend->devices)) {
wlr_log(WLR_ERROR, "libinput initialization failed, no input devices");
wlr_log(WLR_ERROR, "Set WLR_LIBINPUT_NO_DEVICES=1 to suppress this check");
return false;
}
}
if (backend->input_event) {
wl_event_source_remove(backend->input_event);
}
backend->input_event = wl_event_loop_add_fd(backend->session->event_loop, libinput_fd,
WL_EVENT_READABLE, handle_libinput_readable, backend);
if (!backend->input_event) {
wlr_log(WLR_ERROR, "Failed to create input event on event loop");
return false;
}
wlr_log(WLR_DEBUG, "libinput successfully initialized");
return true;
}
static void backend_destroy(struct wlr_backend *wlr_backend) {
if (!wlr_backend) {
return;
}
struct wlr_libinput_backend *backend =
get_libinput_backend_from_backend(wlr_backend);
struct wlr_libinput_input_device *dev, *tmp;
wl_list_for_each_safe(dev, tmp, &backend->devices, link) {
destroy_libinput_input_device(dev);
}
wlr_backend_finish(wlr_backend);
wl_list_remove(&backend->session_destroy.link);
wl_list_remove(&backend->session_signal.link);
if (backend->input_event) {
wl_event_source_remove(backend->input_event);
}
libinput_unref(backend->libinput_context);
free(backend);
}
static const struct wlr_backend_impl backend_impl = {
.start = backend_start,
.destroy = backend_destroy,
};
bool wlr_backend_is_libinput(struct wlr_backend *b) {
return b->impl == &backend_impl;
}
static void session_signal(struct wl_listener *listener, void *data) {
struct wlr_libinput_backend *backend =
wl_container_of(listener, backend, session_signal);
struct wlr_session *session = backend->session;
if (!backend->libinput_context) {
return;
}
if (session->active) {
libinput_resume(backend->libinput_context);
} else {
libinput_suspend(backend->libinput_context);
}
}
static void handle_session_destroy(struct wl_listener *listener, void *data) {
struct wlr_libinput_backend *backend =
wl_container_of(listener, backend, session_destroy);
backend_destroy(&backend->backend);
}
struct wlr_backend *wlr_libinput_backend_create(struct wlr_session *session) {
struct wlr_libinput_backend *backend = calloc(1, sizeof(*backend));
if (!backend) {
wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno));
return NULL;
}
wlr_backend_init(&backend->backend, &backend_impl);
wl_list_init(&backend->devices);
backend->session = session;
backend->session_signal.notify = session_signal;
wl_signal_add(&session->events.active, &backend->session_signal);
backend->session_destroy.notify = handle_session_destroy;
wl_signal_add(&session->events.destroy, &backend->session_destroy);
return &backend->backend;
}
struct libinput_device *wlr_libinput_get_device_handle(
struct wlr_input_device *wlr_dev) {
struct wlr_libinput_input_device *dev = NULL;
switch (wlr_dev->type) {
case WLR_INPUT_DEVICE_KEYBOARD:
dev = device_from_keyboard(wlr_keyboard_from_input_device(wlr_dev));
break;
case WLR_INPUT_DEVICE_POINTER:
dev = device_from_pointer(wlr_pointer_from_input_device(wlr_dev));
break;
case WLR_INPUT_DEVICE_SWITCH:
dev = device_from_switch(wlr_switch_from_input_device(wlr_dev));
break;
case WLR_INPUT_DEVICE_TOUCH:
dev = device_from_touch(wlr_touch_from_input_device(wlr_dev));
break;
case WLR_INPUT_DEVICE_TABLET:
dev = device_from_tablet(wlr_tablet_from_input_device(wlr_dev));
break;
case WLR_INPUT_DEVICE_TABLET_PAD:
dev = device_from_tablet_pad(wlr_tablet_pad_from_input_device(wlr_dev));
break;
}
return dev->handle;
}
uint32_t usec_to_msec(uint64_t usec) {
return (uint32_t)(usec / 1000);
}
const char *get_libinput_device_name(struct libinput_device *device) {
// libinput guarantees that the name is non-NULL, and an empty string if
// unset. However wlroots uses NULL to indicate that the name is unset.
const char *name = libinput_device_get_name(device);
if (name[0] == '\0') {
return NULL;
}
return name;
}

263
backend/libinput/events.c Normal file
View file

@ -0,0 +1,263 @@
#include <libinput.h>
#include <stdlib.h>
#include <wlr/backend/session.h>
#include <wlr/interfaces/wlr_keyboard.h>
#include <wlr/interfaces/wlr_pointer.h>
#include <wlr/interfaces/wlr_touch.h>
#include <wlr/interfaces/wlr_tablet_tool.h>
#include <wlr/interfaces/wlr_tablet_pad.h>
#include <wlr/interfaces/wlr_switch.h>
#include <wlr/util/log.h>
#include "backend/libinput.h"
void destroy_libinput_input_device(struct wlr_libinput_input_device *dev) {
if (dev->keyboard.impl) {
wlr_keyboard_finish(&dev->keyboard);
}
if (dev->pointer.impl) {
wlr_pointer_finish(&dev->pointer);
}
if (dev->switch_device.impl) {
wlr_switch_finish(&dev->switch_device);
}
if (dev->touch.impl) {
wlr_touch_finish(&dev->touch);
}
if (dev->tablet.impl) {
finish_device_tablet(dev);
}
if (dev->tablet_pad.impl) {
finish_device_tablet_pad(dev);
}
libinput_device_unref(dev->handle);
wl_list_remove(&dev->link);
free(dev);
}
bool wlr_input_device_is_libinput(struct wlr_input_device *wlr_dev) {
switch (wlr_dev->type) {
case WLR_INPUT_DEVICE_KEYBOARD:
return wlr_keyboard_from_input_device(wlr_dev)->impl ==
&libinput_keyboard_impl;
case WLR_INPUT_DEVICE_POINTER:
return wlr_pointer_from_input_device(wlr_dev)->impl ==
&libinput_pointer_impl;
case WLR_INPUT_DEVICE_TOUCH:
return wlr_touch_from_input_device(wlr_dev)->impl ==
&libinput_touch_impl;
case WLR_INPUT_DEVICE_TABLET:
return wlr_tablet_from_input_device(wlr_dev)-> impl ==
&libinput_tablet_impl;
case WLR_INPUT_DEVICE_TABLET_PAD:
return wlr_tablet_pad_from_input_device(wlr_dev)->impl ==
&libinput_tablet_pad_impl;
case WLR_INPUT_DEVICE_SWITCH:
return wlr_switch_from_input_device(wlr_dev)->impl ==
&libinput_switch_impl;
default:
return false;
}
}
static void handle_device_added(struct wlr_libinput_backend *backend,
struct libinput_device *libinput_dev) {
int vendor = libinput_device_get_id_vendor(libinput_dev);
int product = libinput_device_get_id_product(libinput_dev);
const char *name = libinput_device_get_name(libinput_dev);
wlr_log(WLR_DEBUG, "Adding %s [%d:%d]", name, vendor, product);
struct wlr_libinput_input_device *dev = calloc(1, sizeof(*dev));
if (dev == NULL) {
wlr_log_errno(WLR_ERROR, "failed to allocate wlr_libinput_input_device");
return;
}
dev->handle = libinput_dev;
libinput_device_ref(libinput_dev);
libinput_device_set_user_data(libinput_dev, dev);
wl_list_insert(&backend->devices, &dev->link);
if (libinput_device_has_capability(
libinput_dev, LIBINPUT_DEVICE_CAP_KEYBOARD)) {
init_device_keyboard(dev);
wl_signal_emit_mutable(&backend->backend.events.new_input,
&dev->keyboard.base);
}
if (libinput_device_has_capability(
libinput_dev, LIBINPUT_DEVICE_CAP_POINTER)) {
init_device_pointer(dev);
wl_signal_emit_mutable(&backend->backend.events.new_input,
&dev->pointer.base);
}
if (libinput_device_has_capability(
libinput_dev, LIBINPUT_DEVICE_CAP_SWITCH)) {
init_device_switch(dev);
wl_signal_emit_mutable(&backend->backend.events.new_input,
&dev->switch_device.base);
}
if (libinput_device_has_capability(
libinput_dev, LIBINPUT_DEVICE_CAP_TOUCH)) {
init_device_touch(dev);
wl_signal_emit_mutable(&backend->backend.events.new_input,
&dev->touch.base);
}
if (libinput_device_has_capability(libinput_dev,
LIBINPUT_DEVICE_CAP_TABLET_TOOL)) {
init_device_tablet(dev);
wl_signal_emit_mutable(&backend->backend.events.new_input,
&dev->tablet.base);
}
if (libinput_device_has_capability(
libinput_dev, LIBINPUT_DEVICE_CAP_TABLET_PAD)) {
init_device_tablet_pad(dev);
wl_signal_emit_mutable(&backend->backend.events.new_input,
&dev->tablet_pad.base);
}
if (libinput_device_has_capability(
libinput_dev, LIBINPUT_DEVICE_CAP_GESTURE)) {
wlr_log(WLR_DEBUG, "libinput gesture not handled");
}
}
static void handle_device_removed(struct wlr_libinput_backend *backend,
struct wlr_libinput_input_device *dev) {
int vendor = libinput_device_get_id_vendor(dev->handle);
int product = libinput_device_get_id_product(dev->handle);
const char *name = libinput_device_get_name(dev->handle);
wlr_log(WLR_DEBUG, "Removing %s [%d:%d]", name, vendor, product);
destroy_libinput_input_device(dev);
}
void handle_libinput_event(struct wlr_libinput_backend *backend,
struct libinput_event *event) {
struct libinput_device *libinput_dev = libinput_event_get_device(event);
struct wlr_libinput_input_device *dev =
libinput_device_get_user_data(libinput_dev);
enum libinput_event_type event_type = libinput_event_get_type(event);
if (dev == NULL && event_type != LIBINPUT_EVENT_DEVICE_ADDED) {
wlr_log(WLR_ERROR, "libinput_device has no wlr_libinput_input_device");
return;
}
switch (event_type) {
case LIBINPUT_EVENT_DEVICE_ADDED:
handle_device_added(backend, libinput_dev);
break;
case LIBINPUT_EVENT_DEVICE_REMOVED:
handle_device_removed(backend, dev);
break;
case LIBINPUT_EVENT_KEYBOARD_KEY:
handle_keyboard_key(event, &dev->keyboard);
break;
case LIBINPUT_EVENT_POINTER_MOTION:
handle_pointer_motion(event, &dev->pointer);
break;
case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
handle_pointer_motion_abs(event, &dev->pointer);
break;
case LIBINPUT_EVENT_POINTER_BUTTON:
handle_pointer_button(event, &dev->pointer);
break;
case LIBINPUT_EVENT_POINTER_AXIS:
#if !HAVE_LIBINPUT_SCROLL_VALUE120
/* This event must be ignored in favour of the SCROLL_* events */
handle_pointer_axis(event, &dev->pointer);
#endif
break;
#if HAVE_LIBINPUT_SCROLL_VALUE120
case LIBINPUT_EVENT_POINTER_SCROLL_WHEEL:
handle_pointer_axis_value120(event, &dev->pointer,
WL_POINTER_AXIS_SOURCE_WHEEL);
break;
case LIBINPUT_EVENT_POINTER_SCROLL_FINGER:
handle_pointer_axis_value120(event, &dev->pointer,
WL_POINTER_AXIS_SOURCE_FINGER);
break;
case LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS:
handle_pointer_axis_value120(event, &dev->pointer,
WL_POINTER_AXIS_SOURCE_CONTINUOUS);
break;
#endif
case LIBINPUT_EVENT_TOUCH_DOWN:
handle_touch_down(event, &dev->touch);
break;
case LIBINPUT_EVENT_TOUCH_UP:
handle_touch_up(event, &dev->touch);
break;
case LIBINPUT_EVENT_TOUCH_MOTION:
handle_touch_motion(event, &dev->touch);
break;
case LIBINPUT_EVENT_TOUCH_CANCEL:
handle_touch_cancel(event, &dev->touch);
break;
case LIBINPUT_EVENT_TOUCH_FRAME:
handle_touch_frame(event, &dev->touch);
break;
case LIBINPUT_EVENT_TABLET_TOOL_AXIS:
handle_tablet_tool_axis(event, &dev->tablet);
break;
case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY:
handle_tablet_tool_proximity(event, &dev->tablet);
break;
case LIBINPUT_EVENT_TABLET_TOOL_TIP:
handle_tablet_tool_tip(event, &dev->tablet);
break;
case LIBINPUT_EVENT_TABLET_TOOL_BUTTON:
handle_tablet_tool_button(event, &dev->tablet);
break;
case LIBINPUT_EVENT_TABLET_PAD_BUTTON:
handle_tablet_pad_button(event, &dev->tablet_pad);
break;
case LIBINPUT_EVENT_TABLET_PAD_RING:
handle_tablet_pad_ring(event, &dev->tablet_pad);
break;
case LIBINPUT_EVENT_TABLET_PAD_STRIP:
handle_tablet_pad_strip(event, &dev->tablet_pad);
break;
case LIBINPUT_EVENT_SWITCH_TOGGLE:
handle_switch_toggle(event, &dev->switch_device);
break;
case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN:
handle_pointer_swipe_begin(event, &dev->pointer);
break;
case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE:
handle_pointer_swipe_update(event, &dev->pointer);
break;
case LIBINPUT_EVENT_GESTURE_SWIPE_END:
handle_pointer_swipe_end(event, &dev->pointer);
break;
case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN:
handle_pointer_pinch_begin(event, &dev->pointer);
break;
case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE:
handle_pointer_pinch_update(event, &dev->pointer);
break;
case LIBINPUT_EVENT_GESTURE_PINCH_END:
handle_pointer_pinch_end(event, &dev->pointer);
break;
#if HAVE_LIBINPUT_HOLD_GESTURES
case LIBINPUT_EVENT_GESTURE_HOLD_BEGIN:
handle_pointer_hold_begin(event, &dev->pointer);
break;
case LIBINPUT_EVENT_GESTURE_HOLD_END:
handle_pointer_hold_end(event, &dev->pointer);
break;
#endif
default:
wlr_log(WLR_DEBUG, "Unknown libinput event %d", event_type);
break;
}
}

View file

@ -0,0 +1,51 @@
#include <assert.h>
#include <libinput.h>
#include <stdlib.h>
#include <wlr/interfaces/wlr_keyboard.h>
#include "backend/libinput.h"
struct wlr_libinput_input_device *device_from_keyboard(
struct wlr_keyboard *kb) {
assert(kb->impl == &libinput_keyboard_impl);
struct wlr_libinput_input_device *dev = wl_container_of(kb, dev, keyboard);
return dev;
}
static void keyboard_set_leds(struct wlr_keyboard *wlr_kb, uint32_t leds) {
struct wlr_libinput_input_device *dev = device_from_keyboard(wlr_kb);
libinput_device_led_update(dev->handle, leds);
}
const struct wlr_keyboard_impl libinput_keyboard_impl = {
.name = "libinput-keyboard",
.led_update = keyboard_set_leds
};
void init_device_keyboard(struct wlr_libinput_input_device *dev) {
const char *name = get_libinput_device_name(dev->handle);
struct wlr_keyboard *wlr_kb = &dev->keyboard;
wlr_keyboard_init(wlr_kb, &libinput_keyboard_impl, name);
libinput_device_led_update(dev->handle, 0);
}
void handle_keyboard_key(struct libinput_event *event,
struct wlr_keyboard *kb) {
struct libinput_event_keyboard *kbevent =
libinput_event_get_keyboard_event(event);
struct wlr_keyboard_key_event wlr_event = {
.time_msec = usec_to_msec(libinput_event_keyboard_get_time_usec(kbevent)),
.keycode = libinput_event_keyboard_get_key(kbevent),
.update_state = true,
};
switch (libinput_event_keyboard_get_key_state(kbevent)) {
case LIBINPUT_KEY_STATE_RELEASED:
wlr_event.state = WL_KEYBOARD_KEY_STATE_RELEASED;
break;
case LIBINPUT_KEY_STATE_PRESSED:
wlr_event.state = WL_KEYBOARD_KEY_STATE_PRESSED;
break;
}
wlr_keyboard_notify_key(kb, &wlr_event);
}

View file

@ -0,0 +1,34 @@
msg = ['Required for libinput backend support.']
if 'libinput' in backends
msg += 'Install "libinput" or disable the libinput backend.'
endif
libinput = dependency(
'libinput',
version: '>=1.14.0',
required: 'libinput' in backends,
not_found_message: '\n'.join(msg),
)
if not (libinput.found() and features['session'])
subdir_done()
endif
wlr_files += files(
'backend.c',
'events.c',
'keyboard.c',
'pointer.c',
'switch.c',
'tablet_pad.c',
'tablet_tool.c',
'touch.c',
)
features += { 'libinput-backend': true }
wlr_deps += libinput
# libinput hold gestures and high resolution scroll are available since 1.19.0
internal_config.set10('HAVE_LIBINPUT_HOLD_GESTURES', libinput.version().version_compare('>=1.19.0'))
internal_config.set10('HAVE_LIBINPUT_SCROLL_VALUE120', libinput.version().version_compare('>=1.19.0'))
internal_config.set10('HAVE_LIBINPUT_BUSTYPE', libinput.version().version_compare('>=1.26.0'))

286
backend/libinput/pointer.c Normal file
View file

@ -0,0 +1,286 @@
#include <assert.h>
#include <libinput.h>
#include <wlr/interfaces/wlr_pointer.h>
#include "backend/libinput.h"
const struct wlr_pointer_impl libinput_pointer_impl = {
.name = "libinput-pointer",
};
void init_device_pointer(struct wlr_libinput_input_device *dev) {
const char *name = get_libinput_device_name(dev->handle);
struct wlr_pointer *wlr_pointer = &dev->pointer;
wlr_pointer_init(wlr_pointer, &libinput_pointer_impl, name);
}
struct wlr_libinput_input_device *device_from_pointer(
struct wlr_pointer *wlr_pointer) {
assert(wlr_pointer->impl == &libinput_pointer_impl);
struct wlr_libinput_input_device *dev =
wl_container_of(wlr_pointer, dev, pointer);
return dev;
}
void handle_pointer_motion(struct libinput_event *event,
struct wlr_pointer *pointer) {
struct libinput_event_pointer *pevent =
libinput_event_get_pointer_event(event);
struct wlr_pointer_motion_event wlr_event = {
.pointer = pointer,
.time_msec = usec_to_msec(libinput_event_pointer_get_time_usec(pevent)),
.delta_x = libinput_event_pointer_get_dx(pevent),
.delta_y = libinput_event_pointer_get_dy(pevent),
.unaccel_dx = libinput_event_pointer_get_dx_unaccelerated(pevent),
.unaccel_dy = libinput_event_pointer_get_dy_unaccelerated(pevent),
};
wl_signal_emit_mutable(&pointer->events.motion, &wlr_event);
wl_signal_emit_mutable(&pointer->events.frame, pointer);
}
void handle_pointer_motion_abs(struct libinput_event *event,
struct wlr_pointer *pointer) {
struct libinput_event_pointer *pevent =
libinput_event_get_pointer_event(event);
struct wlr_pointer_motion_absolute_event wlr_event = {
.pointer = pointer,
.time_msec = usec_to_msec(libinput_event_pointer_get_time_usec(pevent)),
.x = libinput_event_pointer_get_absolute_x_transformed(pevent, 1),
.y = libinput_event_pointer_get_absolute_y_transformed(pevent, 1),
};
wl_signal_emit_mutable(&pointer->events.motion_absolute, &wlr_event);
wl_signal_emit_mutable(&pointer->events.frame, pointer);
}
void handle_pointer_button(struct libinput_event *event,
struct wlr_pointer *pointer) {
struct libinput_event_pointer *pevent =
libinput_event_get_pointer_event(event);
struct wlr_pointer_button_event wlr_event = {
.pointer = pointer,
.time_msec = usec_to_msec(libinput_event_pointer_get_time_usec(pevent)),
.button = libinput_event_pointer_get_button(pevent),
};
// Ignore events which aren't a seat-wide state change. For instance, if
// the same button is pressed twice on the same seat, ignore the second
// press.
uint32_t seat_count = libinput_event_pointer_get_seat_button_count(pevent);
switch (libinput_event_pointer_get_button_state(pevent)) {
case LIBINPUT_BUTTON_STATE_PRESSED:
wlr_event.state = WL_POINTER_BUTTON_STATE_PRESSED;
if (seat_count != 1) {
return;
}
break;
case LIBINPUT_BUTTON_STATE_RELEASED:
wlr_event.state = WL_POINTER_BUTTON_STATE_RELEASED;
if (seat_count != 0) {
return;
}
break;
}
wl_signal_emit_mutable(&pointer->events.button, &wlr_event);
wl_signal_emit_mutable(&pointer->events.frame, pointer);
}
void handle_pointer_axis(struct libinput_event *event,
struct wlr_pointer *pointer) {
struct libinput_event_pointer *pevent =
libinput_event_get_pointer_event(event);
struct wlr_pointer_axis_event wlr_event = {
.pointer = pointer,
.time_msec = usec_to_msec(libinput_event_pointer_get_time_usec(pevent)),
};
switch (libinput_event_pointer_get_axis_source(pevent)) {
case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL:
wlr_event.source = WL_POINTER_AXIS_SOURCE_WHEEL;
break;
case LIBINPUT_POINTER_AXIS_SOURCE_FINGER:
wlr_event.source = WL_POINTER_AXIS_SOURCE_FINGER;
break;
case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS:
wlr_event.source = WL_POINTER_AXIS_SOURCE_CONTINUOUS;
break;
case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT:
wlr_event.source = WL_POINTER_AXIS_SOURCE_WHEEL_TILT;
break;
}
const enum libinput_pointer_axis axes[] = {
LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL,
};
for (size_t i = 0; i < sizeof(axes) / sizeof(axes[0]); ++i) {
if (!libinput_event_pointer_has_axis(pevent, axes[i])) {
continue;
}
switch (axes[i]) {
case LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL:
wlr_event.orientation = WL_POINTER_AXIS_VERTICAL_SCROLL;
break;
case LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL:
wlr_event.orientation = WL_POINTER_AXIS_HORIZONTAL_SCROLL;
break;
}
wlr_event.delta =
libinput_event_pointer_get_axis_value(pevent, axes[i]);
wlr_event.delta_discrete =
libinput_event_pointer_get_axis_value_discrete(pevent, axes[i]);
wlr_event.delta_discrete *= WLR_POINTER_AXIS_DISCRETE_STEP;
wlr_event.relative_direction = WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL;
if (libinput_device_config_scroll_get_natural_scroll_enabled(libinput_event_get_device(event))) {
wlr_event.relative_direction = WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED;
}
wl_signal_emit_mutable(&pointer->events.axis, &wlr_event);
}
wl_signal_emit_mutable(&pointer->events.frame, pointer);
}
#if HAVE_LIBINPUT_SCROLL_VALUE120
void handle_pointer_axis_value120(struct libinput_event *event,
struct wlr_pointer *pointer, enum wl_pointer_axis_source source) {
struct libinput_event_pointer *pevent =
libinput_event_get_pointer_event(event);
struct wlr_pointer_axis_event wlr_event = {
.pointer = pointer,
.time_msec = usec_to_msec(libinput_event_pointer_get_time_usec(pevent)),
.source = source,
};
const enum libinput_pointer_axis axes[] = {
LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL,
LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL,
};
for (size_t i = 0; i < sizeof(axes) / sizeof(axes[0]); ++i) {
if (!libinput_event_pointer_has_axis(pevent, axes[i])) {
continue;
}
switch (axes[i]) {
case LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL:
wlr_event.orientation = WL_POINTER_AXIS_VERTICAL_SCROLL;
break;
case LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL:
wlr_event.orientation = WL_POINTER_AXIS_HORIZONTAL_SCROLL;
break;
}
wlr_event.delta =
libinput_event_pointer_get_scroll_value(pevent, axes[i]);
if (source == WL_POINTER_AXIS_SOURCE_WHEEL) {
wlr_event.delta_discrete =
libinput_event_pointer_get_scroll_value_v120(pevent, axes[i]);
}
wl_signal_emit_mutable(&pointer->events.axis, &wlr_event);
}
wl_signal_emit_mutable(&pointer->events.frame, pointer);
}
#endif
void handle_pointer_swipe_begin(struct libinput_event *event,
struct wlr_pointer *pointer) {
struct libinput_event_gesture *gevent =
libinput_event_get_gesture_event(event);
struct wlr_pointer_swipe_begin_event wlr_event = {
.pointer = pointer,
.time_msec =
usec_to_msec(libinput_event_gesture_get_time_usec(gevent)),
.fingers = libinput_event_gesture_get_finger_count(gevent),
};
wl_signal_emit_mutable(&pointer->events.swipe_begin, &wlr_event);
}
void handle_pointer_swipe_update(struct libinput_event *event,
struct wlr_pointer *pointer) {
struct libinput_event_gesture *gevent =
libinput_event_get_gesture_event(event);
struct wlr_pointer_swipe_update_event wlr_event = {
.pointer = pointer,
.time_msec =
usec_to_msec(libinput_event_gesture_get_time_usec(gevent)),
.fingers = libinput_event_gesture_get_finger_count(gevent),
.dx = libinput_event_gesture_get_dx(gevent),
.dy = libinput_event_gesture_get_dy(gevent),
};
wl_signal_emit_mutable(&pointer->events.swipe_update, &wlr_event);
}
void handle_pointer_swipe_end(struct libinput_event *event,
struct wlr_pointer *pointer) {
struct libinput_event_gesture *gevent =
libinput_event_get_gesture_event(event);
struct wlr_pointer_swipe_end_event wlr_event = {
.pointer = pointer,
.time_msec =
usec_to_msec(libinput_event_gesture_get_time_usec(gevent)),
.cancelled = libinput_event_gesture_get_cancelled(gevent),
};
wl_signal_emit_mutable(&pointer->events.swipe_end, &wlr_event);
}
void handle_pointer_pinch_begin(struct libinput_event *event,
struct wlr_pointer *pointer) {
struct libinput_event_gesture *gevent =
libinput_event_get_gesture_event(event);
struct wlr_pointer_pinch_begin_event wlr_event = {
.pointer = pointer,
.time_msec =
usec_to_msec(libinput_event_gesture_get_time_usec(gevent)),
.fingers = libinput_event_gesture_get_finger_count(gevent),
};
wl_signal_emit_mutable(&pointer->events.pinch_begin, &wlr_event);
}
void handle_pointer_pinch_update(struct libinput_event *event,
struct wlr_pointer *pointer) {
struct libinput_event_gesture *gevent =
libinput_event_get_gesture_event(event);
struct wlr_pointer_pinch_update_event wlr_event = {
.pointer = pointer,
.time_msec =
usec_to_msec(libinput_event_gesture_get_time_usec(gevent)),
.fingers = libinput_event_gesture_get_finger_count(gevent),
.dx = libinput_event_gesture_get_dx(gevent),
.dy = libinput_event_gesture_get_dy(gevent),
.scale = libinput_event_gesture_get_scale(gevent),
.rotation = libinput_event_gesture_get_angle_delta(gevent),
};
wl_signal_emit_mutable(&pointer->events.pinch_update, &wlr_event);
}
void handle_pointer_pinch_end(struct libinput_event *event,
struct wlr_pointer *pointer) {
struct libinput_event_gesture *gevent =
libinput_event_get_gesture_event(event);
struct wlr_pointer_pinch_end_event wlr_event = {
.pointer = pointer,
.time_msec =
usec_to_msec(libinput_event_gesture_get_time_usec(gevent)),
.cancelled = libinput_event_gesture_get_cancelled(gevent),
};
wl_signal_emit_mutable(&pointer->events.pinch_end, &wlr_event);
}
void handle_pointer_hold_begin(struct libinput_event *event,
struct wlr_pointer *pointer) {
struct libinput_event_gesture *gevent =
libinput_event_get_gesture_event(event);
struct wlr_pointer_hold_begin_event wlr_event = {
.pointer = pointer,
.time_msec =
usec_to_msec(libinput_event_gesture_get_time_usec(gevent)),
.fingers = libinput_event_gesture_get_finger_count(gevent),
};
wl_signal_emit_mutable(&pointer->events.hold_begin, &wlr_event);
}
void handle_pointer_hold_end(struct libinput_event *event,
struct wlr_pointer *pointer) {
struct libinput_event_gesture *gevent =
libinput_event_get_gesture_event(event);
struct wlr_pointer_hold_end_event wlr_event = {
.pointer = pointer,
.time_msec =
usec_to_msec(libinput_event_gesture_get_time_usec(gevent)),
.cancelled = libinput_event_gesture_get_cancelled(gevent),
};
wl_signal_emit_mutable(&pointer->events.hold_end, &wlr_event);
}

49
backend/libinput/switch.c Normal file
View file

@ -0,0 +1,49 @@
#include <assert.h>
#include <libinput.h>
#include <wlr/interfaces/wlr_switch.h>
#include "backend/libinput.h"
const struct wlr_switch_impl libinput_switch_impl = {
.name = "libinput-switch",
};
void init_device_switch(struct wlr_libinput_input_device *dev) {
const char *name = get_libinput_device_name(dev->handle);
struct wlr_switch *wlr_switch = &dev->switch_device;
wlr_switch_init(wlr_switch, &libinput_switch_impl, name);
}
struct wlr_libinput_input_device *device_from_switch(
struct wlr_switch *wlr_switch) {
assert(wlr_switch->impl == &libinput_switch_impl);
struct wlr_libinput_input_device *dev =
wl_container_of(wlr_switch, dev, switch_device);
return dev;
}
void handle_switch_toggle(struct libinput_event *event,
struct wlr_switch *wlr_switch) {
struct libinput_event_switch *sevent =
libinput_event_get_switch_event (event);
struct wlr_switch_toggle_event wlr_event = {
.time_msec = usec_to_msec(libinput_event_switch_get_time_usec(sevent)),
};
switch (libinput_event_switch_get_switch(sevent)) {
case LIBINPUT_SWITCH_LID:
wlr_event.switch_type = WLR_SWITCH_TYPE_LID;
break;
case LIBINPUT_SWITCH_TABLET_MODE:
wlr_event.switch_type = WLR_SWITCH_TYPE_TABLET_MODE;
break;
}
switch (libinput_event_switch_get_switch_state(sevent)) {
case LIBINPUT_SWITCH_STATE_OFF:
wlr_event.switch_state = WLR_SWITCH_STATE_OFF;
break;
case LIBINPUT_SWITCH_STATE_ON:
wlr_event.switch_state = WLR_SWITCH_STATE_ON;
break;
}
wl_signal_emit_mutable(&wlr_switch->events.toggle, &wlr_event);
}

View file

@ -0,0 +1,201 @@
#include <assert.h>
#include <string.h>
#include <libinput.h>
#include <stdlib.h>
#include <wlr/interfaces/wlr_tablet_pad.h>
#include <wlr/util/log.h>
#include "backend/libinput.h"
const struct wlr_tablet_pad_impl libinput_tablet_pad_impl = {
.name = "libinput-tablet-pad",
};
static void group_destroy(struct wlr_tablet_pad_group *group) {
free(group->buttons);
free(group->strips);
free(group->rings);
free(group);
}
static void add_pad_group_from_libinput(struct wlr_tablet_pad *pad,
struct libinput_device *device, unsigned int index) {
struct libinput_tablet_pad_mode_group *li_group =
libinput_device_tablet_pad_get_mode_group(device, index);
struct wlr_tablet_pad_group *group = calloc(1, sizeof(*group));
if (!group) {
wlr_log_errno(WLR_ERROR, "failed to allocate wlr_tablet_pad_group");
return;
}
for (size_t i = 0; i < pad->ring_count; ++i) {
if (libinput_tablet_pad_mode_group_has_ring(li_group, i)) {
++group->ring_count;
}
}
group->rings = calloc(group->ring_count, sizeof(unsigned int));
if (group->rings == NULL) {
goto group_fail;
}
size_t ring = 0;
for (size_t i = 0; i < pad->ring_count; ++i) {
if (libinput_tablet_pad_mode_group_has_ring(li_group, i)) {
group->rings[ring++] = i;
}
}
for (size_t i = 0; i < pad->strip_count; ++i) {
if (libinput_tablet_pad_mode_group_has_strip(li_group, i)) {
++group->strip_count;
}
}
group->strips = calloc(group->strip_count, sizeof(unsigned int));
if (group->strips == NULL) {
goto group_fail;
}
size_t strip = 0;
for (size_t i = 0; i < pad->strip_count; ++i) {
if (libinput_tablet_pad_mode_group_has_strip(li_group, i)) {
group->strips[strip++] = i;
}
}
for (size_t i = 0; i < pad->button_count; ++i) {
if (libinput_tablet_pad_mode_group_has_button(li_group, i)) {
++group->button_count;
}
}
group->buttons = calloc(group->button_count, sizeof(unsigned int));
if (group->buttons == NULL) {
goto group_fail;
}
size_t button = 0;
for (size_t i = 0; i < pad->button_count; ++i) {
if (libinput_tablet_pad_mode_group_has_button(li_group, i)) {
group->buttons[button++] = i;
}
}
group->mode_count = libinput_tablet_pad_mode_group_get_num_modes(li_group);
libinput_tablet_pad_mode_group_ref(li_group);
wl_list_insert(&pad->groups, &group->link);
return;
group_fail:
wlr_log(WLR_ERROR, "failed to configure wlr_tablet_pad_group");
group_destroy(group);
}
void init_device_tablet_pad(struct wlr_libinput_input_device *dev) {
struct libinput_device *handle = dev->handle;
const char *name = get_libinput_device_name(handle);
struct wlr_tablet_pad *wlr_tablet_pad = &dev->tablet_pad;
wlr_tablet_pad_init(wlr_tablet_pad, &libinput_tablet_pad_impl, name);
wlr_tablet_pad->button_count =
libinput_device_tablet_pad_get_num_buttons(handle);
wlr_tablet_pad->ring_count =
libinput_device_tablet_pad_get_num_rings(handle);
wlr_tablet_pad->strip_count =
libinput_device_tablet_pad_get_num_strips(handle);
struct udev_device *udev = libinput_device_get_udev_device(handle);
char **dst = wl_array_add(&wlr_tablet_pad->paths, sizeof(char *));
*dst = strdup(udev_device_get_syspath(udev));
int groups = libinput_device_tablet_pad_get_num_mode_groups(handle);
for (int i = 0; i < groups; ++i) {
add_pad_group_from_libinput(wlr_tablet_pad, handle, i);
}
}
void finish_device_tablet_pad(struct wlr_libinput_input_device *dev) {
struct wlr_tablet_pad_group *group, *tmp;
wl_list_for_each_safe(group, tmp, &dev->tablet_pad.groups, link) {
group_destroy(group);
}
wlr_tablet_pad_finish(&dev->tablet_pad);
int groups = libinput_device_tablet_pad_get_num_mode_groups(dev->handle);
for (int i = 0; i < groups; ++i) {
struct libinput_tablet_pad_mode_group *li_group =
libinput_device_tablet_pad_get_mode_group(dev->handle, i);
libinput_tablet_pad_mode_group_unref(li_group);
}
}
struct wlr_libinput_input_device *device_from_tablet_pad(
struct wlr_tablet_pad *wlr_tablet_pad) {
assert(wlr_tablet_pad->impl == &libinput_tablet_pad_impl);
struct wlr_libinput_input_device *dev =
wl_container_of(wlr_tablet_pad, dev, tablet_pad);
return dev;
}
void handle_tablet_pad_button(struct libinput_event *event,
struct wlr_tablet_pad *tablet_pad) {
struct libinput_event_tablet_pad *pevent =
libinput_event_get_tablet_pad_event(event);
struct wlr_tablet_pad_button_event wlr_event = {
.time_msec = usec_to_msec(libinput_event_tablet_pad_get_time_usec(pevent)),
.button = libinput_event_tablet_pad_get_button_number(pevent),
.mode = libinput_event_tablet_pad_get_mode(pevent),
.group = libinput_tablet_pad_mode_group_get_index(
libinput_event_tablet_pad_get_mode_group(pevent)),
};
switch (libinput_event_tablet_pad_get_button_state(pevent)) {
case LIBINPUT_BUTTON_STATE_PRESSED:
wlr_event.state = WLR_BUTTON_PRESSED;
break;
case LIBINPUT_BUTTON_STATE_RELEASED:
wlr_event.state = WLR_BUTTON_RELEASED;
break;
}
wl_signal_emit_mutable(&tablet_pad->events.button, &wlr_event);
}
void handle_tablet_pad_ring(struct libinput_event *event,
struct wlr_tablet_pad *tablet_pad) {
struct libinput_event_tablet_pad *pevent =
libinput_event_get_tablet_pad_event(event);
struct wlr_tablet_pad_ring_event wlr_event = {
.time_msec = usec_to_msec(libinput_event_tablet_pad_get_time_usec(pevent)),
.ring = libinput_event_tablet_pad_get_ring_number(pevent),
.position = libinput_event_tablet_pad_get_ring_position(pevent),
.mode = libinput_event_tablet_pad_get_mode(pevent),
};
switch (libinput_event_tablet_pad_get_ring_source(pevent)) {
case LIBINPUT_TABLET_PAD_RING_SOURCE_UNKNOWN:
wlr_event.source = WLR_TABLET_PAD_RING_SOURCE_UNKNOWN;
break;
case LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER:
wlr_event.source = WLR_TABLET_PAD_RING_SOURCE_FINGER;
break;
}
wl_signal_emit_mutable(&tablet_pad->events.ring, &wlr_event);
}
void handle_tablet_pad_strip(struct libinput_event *event,
struct wlr_tablet_pad *tablet_pad) {
struct libinput_event_tablet_pad *pevent =
libinput_event_get_tablet_pad_event(event);
struct wlr_tablet_pad_strip_event wlr_event = {
.time_msec = usec_to_msec(libinput_event_tablet_pad_get_time_usec(pevent)),
.strip = libinput_event_tablet_pad_get_strip_number(pevent),
.position = libinput_event_tablet_pad_get_strip_position(pevent),
.mode = libinput_event_tablet_pad_get_mode(pevent),
};
switch (libinput_event_tablet_pad_get_strip_source(pevent)) {
case LIBINPUT_TABLET_PAD_STRIP_SOURCE_UNKNOWN:
wlr_event.source = WLR_TABLET_PAD_STRIP_SOURCE_UNKNOWN;
break;
case LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER:
wlr_event.source = WLR_TABLET_PAD_STRIP_SOURCE_FINGER;
break;
}
wl_signal_emit_mutable(&tablet_pad->events.strip, &wlr_event);
}

View file

@ -0,0 +1,277 @@
#include <string.h>
#include <assert.h>
#include <libinput.h>
#include <linux/input.h>
#include <stdlib.h>
#include <wlr/interfaces/wlr_tablet_tool.h>
#include <wlr/util/log.h>
#include "backend/libinput.h"
struct tablet_tool {
struct wlr_tablet_tool wlr_tool;
struct libinput_tablet_tool *handle;
struct wl_list link; // wlr_libinput_input_device.tablet_tools
};
const struct wlr_tablet_impl libinput_tablet_impl = {
.name = "libinput-tablet-tool",
};
void init_device_tablet(struct wlr_libinput_input_device *dev) {
const char *name = get_libinput_device_name(dev->handle);
struct wlr_tablet *wlr_tablet = &dev->tablet;
wlr_tablet_init(wlr_tablet, &libinput_tablet_impl, name);
#if HAVE_LIBINPUT_BUSTYPE
if (libinput_device_get_id_bustype(dev->handle) == BUS_USB)
#endif
{
wlr_tablet->usb_vendor_id = libinput_device_get_id_vendor(dev->handle);
wlr_tablet->usb_product_id = libinput_device_get_id_product(dev->handle);
}
libinput_device_get_size(dev->handle, &wlr_tablet->width_mm,
&wlr_tablet->height_mm);
struct udev_device *udev = libinput_device_get_udev_device(dev->handle);
char **dst = wl_array_add(&wlr_tablet->paths, sizeof(char *));
*dst = strdup(udev_device_get_syspath(udev));
wl_list_init(&dev->tablet_tools);
}
static void tool_destroy(struct tablet_tool *tool) {
wl_signal_emit_mutable(&tool->wlr_tool.events.destroy, &tool->wlr_tool);
libinput_tablet_tool_unref(tool->handle);
libinput_tablet_tool_set_user_data(tool->handle, NULL);
wl_list_remove(&tool->link);
free(tool);
}
void finish_device_tablet(struct wlr_libinput_input_device *dev) {
struct tablet_tool *tool, *tmp;
wl_list_for_each_safe(tool, tmp, &dev->tablet_tools, link) {
tool_destroy(tool);
}
wlr_tablet_finish(&dev->tablet);
}
struct wlr_libinput_input_device *device_from_tablet(
struct wlr_tablet *wlr_tablet) {
assert(wlr_tablet->impl == &libinput_tablet_impl);
struct wlr_libinput_input_device *dev =
wl_container_of(wlr_tablet, dev, tablet);
return dev;
}
static enum wlr_tablet_tool_type wlr_type_from_libinput_type(
enum libinput_tablet_tool_type value) {
switch (value) {
case LIBINPUT_TABLET_TOOL_TYPE_PEN:
return WLR_TABLET_TOOL_TYPE_PEN;
case LIBINPUT_TABLET_TOOL_TYPE_ERASER:
return WLR_TABLET_TOOL_TYPE_ERASER;
case LIBINPUT_TABLET_TOOL_TYPE_BRUSH:
return WLR_TABLET_TOOL_TYPE_BRUSH;
case LIBINPUT_TABLET_TOOL_TYPE_PENCIL:
return WLR_TABLET_TOOL_TYPE_PENCIL;
case LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH:
return WLR_TABLET_TOOL_TYPE_AIRBRUSH;
case LIBINPUT_TABLET_TOOL_TYPE_MOUSE:
return WLR_TABLET_TOOL_TYPE_MOUSE;
case LIBINPUT_TABLET_TOOL_TYPE_LENS:
return WLR_TABLET_TOOL_TYPE_LENS;
case LIBINPUT_TABLET_TOOL_TYPE_TOTEM:
return WLR_TABLET_TOOL_TYPE_TOTEM;
}
abort(); // unreachable
}
static struct tablet_tool *get_tablet_tool(
struct wlr_libinput_input_device *dev,
struct libinput_tablet_tool *libinput_tool) {
struct tablet_tool *tool =
libinput_tablet_tool_get_user_data(libinput_tool);
if (tool) {
return tool;
}
tool = calloc(1, sizeof(*tool));
if (tool == NULL) {
wlr_log_errno(WLR_ERROR, "failed to allocate wlr_libinput_tablet_tool");
return NULL;
}
tool->wlr_tool.type = wlr_type_from_libinput_type(
libinput_tablet_tool_get_type(libinput_tool));
tool->wlr_tool.hardware_serial =
libinput_tablet_tool_get_serial(libinput_tool);
tool->wlr_tool.hardware_wacom =
libinput_tablet_tool_get_tool_id(libinput_tool);
tool->wlr_tool.pressure = libinput_tablet_tool_has_pressure(libinput_tool);
tool->wlr_tool.distance = libinput_tablet_tool_has_distance(libinput_tool);
tool->wlr_tool.tilt = libinput_tablet_tool_has_tilt(libinput_tool);
tool->wlr_tool.rotation = libinput_tablet_tool_has_rotation(libinput_tool);
tool->wlr_tool.slider = libinput_tablet_tool_has_slider(libinput_tool);
tool->wlr_tool.wheel = libinput_tablet_tool_has_wheel(libinput_tool);
wl_signal_init(&tool->wlr_tool.events.destroy);
tool->handle = libinput_tablet_tool_ref(libinput_tool);
libinput_tablet_tool_set_user_data(libinput_tool, tool);
wl_list_insert(&dev->tablet_tools, &tool->link);
return tool;
}
void handle_tablet_tool_axis(struct libinput_event *event,
struct wlr_tablet *wlr_tablet) {
struct libinput_event_tablet_tool *tevent =
libinput_event_get_tablet_tool_event(event);
struct wlr_libinput_input_device *dev = device_from_tablet(wlr_tablet);
struct tablet_tool *tool =
get_tablet_tool(dev, libinput_event_tablet_tool_get_tool(tevent));
struct wlr_tablet_tool_axis_event wlr_event = {
.tablet = wlr_tablet,
.tool = &tool->wlr_tool,
.time_msec = usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)),
};
if (libinput_event_tablet_tool_x_has_changed(tevent)) {
wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_X;
wlr_event.x = libinput_event_tablet_tool_get_x_transformed(tevent, 1);
wlr_event.dx = libinput_event_tablet_tool_get_dx(tevent);
}
if (libinput_event_tablet_tool_y_has_changed(tevent)) {
wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_Y;
wlr_event.y = libinput_event_tablet_tool_get_y_transformed(tevent, 1);
wlr_event.dy = libinput_event_tablet_tool_get_dy(tevent);
}
if (libinput_event_tablet_tool_pressure_has_changed(tevent)) {
wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_PRESSURE;
wlr_event.pressure = libinput_event_tablet_tool_get_pressure(tevent);
}
if (libinput_event_tablet_tool_distance_has_changed(tevent)) {
wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_DISTANCE;
wlr_event.distance = libinput_event_tablet_tool_get_distance(tevent);
}
if (libinput_event_tablet_tool_tilt_x_has_changed(tevent)) {
wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_TILT_X;
wlr_event.tilt_x = libinput_event_tablet_tool_get_tilt_x(tevent);
}
if (libinput_event_tablet_tool_tilt_y_has_changed(tevent)) {
wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_TILT_Y;
wlr_event.tilt_y = libinput_event_tablet_tool_get_tilt_y(tevent);
}
if (libinput_event_tablet_tool_rotation_has_changed(tevent)) {
wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_ROTATION;
wlr_event.rotation = libinput_event_tablet_tool_get_rotation(tevent);
}
if (libinput_event_tablet_tool_slider_has_changed(tevent)) {
wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_SLIDER;
wlr_event.slider = libinput_event_tablet_tool_get_slider_position(tevent);
}
if (libinput_event_tablet_tool_wheel_has_changed(tevent)) {
wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_WHEEL;
wlr_event.wheel_delta = libinput_event_tablet_tool_get_wheel_delta(tevent);
}
wl_signal_emit_mutable(&wlr_tablet->events.axis, &wlr_event);
}
void handle_tablet_tool_proximity(struct libinput_event *event,
struct wlr_tablet *wlr_tablet) {
struct libinput_event_tablet_tool *tevent =
libinput_event_get_tablet_tool_event(event);
struct wlr_libinput_input_device *dev = device_from_tablet(wlr_tablet);
struct tablet_tool *tool =
get_tablet_tool(dev, libinput_event_tablet_tool_get_tool(tevent));
struct wlr_tablet_tool_proximity_event wlr_event = {
.tablet = wlr_tablet,
.tool = &tool->wlr_tool,
.time_msec = usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)),
.x = libinput_event_tablet_tool_get_x_transformed(tevent, 1),
.y = libinput_event_tablet_tool_get_y_transformed(tevent, 1),
};
switch (libinput_event_tablet_tool_get_proximity_state(tevent)) {
case LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT:
wlr_event.state = WLR_TABLET_TOOL_PROXIMITY_OUT;
break;
case LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN:
wlr_event.state = WLR_TABLET_TOOL_PROXIMITY_IN;
break;
}
wl_signal_emit_mutable(&wlr_tablet->events.proximity, &wlr_event);
if (libinput_event_tablet_tool_get_proximity_state(tevent) ==
LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN) {
handle_tablet_tool_axis(event, wlr_tablet);
}
// If the tool is not unique, libinput will not find it again after the
// proximity out, so we should destroy it
if (!libinput_tablet_tool_is_unique(tool->handle)
&& libinput_event_tablet_tool_get_proximity_state(tevent) ==
LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT) {
// The tool isn't unique, it can't be on multiple tablets
tool_destroy(tool);
}
}
void handle_tablet_tool_tip(struct libinput_event *event,
struct wlr_tablet *wlr_tablet) {
handle_tablet_tool_axis(event, wlr_tablet);
struct libinput_event_tablet_tool *tevent =
libinput_event_get_tablet_tool_event(event);
struct wlr_libinput_input_device *dev = device_from_tablet(wlr_tablet);
struct tablet_tool *tool =
get_tablet_tool(dev, libinput_event_tablet_tool_get_tool(tevent));
struct wlr_tablet_tool_tip_event wlr_event = {
.tablet = wlr_tablet,
.tool = &tool->wlr_tool,
.time_msec = usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)),
.x = libinput_event_tablet_tool_get_x_transformed(tevent, 1),
.y = libinput_event_tablet_tool_get_y_transformed(tevent, 1),
};
switch (libinput_event_tablet_tool_get_tip_state(tevent)) {
case LIBINPUT_TABLET_TOOL_TIP_UP:
wlr_event.state = WLR_TABLET_TOOL_TIP_UP;
break;
case LIBINPUT_TABLET_TOOL_TIP_DOWN:
wlr_event.state = WLR_TABLET_TOOL_TIP_DOWN;
break;
}
wl_signal_emit_mutable(&wlr_tablet->events.tip, &wlr_event);
}
void handle_tablet_tool_button(struct libinput_event *event,
struct wlr_tablet *wlr_tablet) {
handle_tablet_tool_axis(event, wlr_tablet);
struct libinput_event_tablet_tool *tevent =
libinput_event_get_tablet_tool_event(event);
struct wlr_libinput_input_device *dev = device_from_tablet(wlr_tablet);
struct tablet_tool *tool =
get_tablet_tool(dev, libinput_event_tablet_tool_get_tool(tevent));
struct wlr_tablet_tool_button_event wlr_event = {
.tablet = wlr_tablet,
.tool = &tool->wlr_tool,
.time_msec = usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)),
.button = libinput_event_tablet_tool_get_button(tevent),
};
switch (libinput_event_tablet_tool_get_button_state(tevent)) {
case LIBINPUT_BUTTON_STATE_RELEASED:
wlr_event.state = WLR_BUTTON_RELEASED;
break;
case LIBINPUT_BUTTON_STATE_PRESSED:
wlr_event.state = WLR_BUTTON_PRESSED;
break;
}
wl_signal_emit_mutable(&wlr_tablet->events.button, &wlr_event);
}

83
backend/libinput/touch.c Normal file
View file

@ -0,0 +1,83 @@
#include <assert.h>
#include <libinput.h>
#include <wlr/interfaces/wlr_touch.h>
#include "backend/libinput.h"
const struct wlr_touch_impl libinput_touch_impl = {
.name = "libinput-touch",
};
void init_device_touch(struct wlr_libinput_input_device *dev) {
const char *name = get_libinput_device_name(dev->handle);
struct wlr_touch *wlr_touch = &dev->touch;
wlr_touch_init(wlr_touch, &libinput_touch_impl, name);
libinput_device_get_size(dev->handle, &wlr_touch->width_mm,
&wlr_touch->height_mm);
}
struct wlr_libinput_input_device *device_from_touch(
struct wlr_touch *wlr_touch) {
assert(wlr_touch->impl == &libinput_touch_impl);
struct wlr_libinput_input_device *dev =
wl_container_of(wlr_touch, dev, touch);
return dev;
}
void handle_touch_down(struct libinput_event *event,
struct wlr_touch *touch) {
struct libinput_event_touch *tevent =
libinput_event_get_touch_event(event);
struct wlr_touch_down_event wlr_event = { 0 };
wlr_event.touch = touch;
wlr_event.time_msec =
usec_to_msec(libinput_event_touch_get_time_usec(tevent));
wlr_event.touch_id = libinput_event_touch_get_seat_slot(tevent);
wlr_event.x = libinput_event_touch_get_x_transformed(tevent, 1);
wlr_event.y = libinput_event_touch_get_y_transformed(tevent, 1);
wl_signal_emit_mutable(&touch->events.down, &wlr_event);
}
void handle_touch_up(struct libinput_event *event,
struct wlr_touch *touch) {
struct libinput_event_touch *tevent =
libinput_event_get_touch_event(event);
struct wlr_touch_up_event wlr_event = {
.touch = touch,
.time_msec = usec_to_msec(libinput_event_touch_get_time_usec(tevent)),
.touch_id = libinput_event_touch_get_seat_slot(tevent),
};
wl_signal_emit_mutable(&touch->events.up, &wlr_event);
}
void handle_touch_motion(struct libinput_event *event,
struct wlr_touch *touch) {
struct libinput_event_touch *tevent =
libinput_event_get_touch_event(event);
struct wlr_touch_motion_event wlr_event = {
.touch = touch,
.time_msec = usec_to_msec(libinput_event_touch_get_time_usec(tevent)),
.touch_id = libinput_event_touch_get_seat_slot(tevent),
.x = libinput_event_touch_get_x_transformed(tevent, 1),
.y = libinput_event_touch_get_y_transformed(tevent, 1),
};
wl_signal_emit_mutable(&touch->events.motion, &wlr_event);
}
void handle_touch_cancel(struct libinput_event *event,
struct wlr_touch *touch) {
struct libinput_event_touch *tevent =
libinput_event_get_touch_event(event);
struct wlr_touch_cancel_event wlr_event = {
.touch = touch,
.time_msec = usec_to_msec(libinput_event_touch_get_time_usec(tevent)),
.touch_id = libinput_event_touch_get_seat_slot(tevent),
};
wl_signal_emit_mutable(&touch->events.cancel, &wlr_event);
}
void handle_touch_frame(struct libinput_event *event,
struct wlr_touch *touch) {
wl_signal_emit_mutable(&touch->events.frame, NULL);
}

29
backend/meson.build Normal file
View file

@ -0,0 +1,29 @@
wlr_files += files('backend.c')
all_backends = ['drm', 'libinput', 'x11']
backends = get_option('backends')
if 'auto' in backends and get_option('auto_features').enabled()
backends = all_backends
elif 'auto' in backends and get_option('auto_features').disabled()
backends = []
endif
session_required = 'drm' in backends or 'libinput' in backends or get_option('session').enabled()
if get_option('session').disabled()
if session_required
error('Session support is required for the DRM or libinput backends')
endif
session_required = disabler()
endif
subdir('session')
foreach backend : all_backends
if backend in backends or 'auto' in backends
subdir(backend)
endif
endforeach
subdir('multi')
subdir('wayland')
subdir('headless')

227
backend/multi/backend.c Normal file
View file

@ -0,0 +1,227 @@
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#include <wlr/backend/interface.h>
#include <wlr/types/wlr_buffer.h>
#include <wlr/util/log.h>
#include "backend/backend.h"
#include "backend/multi.h"
struct subbackend_state {
struct wlr_backend *backend;
struct wlr_backend *container;
struct wl_listener new_input;
struct wl_listener new_output;
struct wl_listener destroy;
struct wl_list link;
};
static struct wlr_multi_backend *multi_backend_from_backend(
struct wlr_backend *wlr_backend) {
assert(wlr_backend_is_multi(wlr_backend));
struct wlr_multi_backend *backend = wl_container_of(wlr_backend, backend, backend);
return backend;
}
static bool multi_backend_start(struct wlr_backend *wlr_backend) {
struct wlr_multi_backend *backend = multi_backend_from_backend(wlr_backend);
struct subbackend_state *sub;
wl_list_for_each(sub, &backend->backends, link) {
if (!wlr_backend_start(sub->backend)) {
wlr_log(WLR_ERROR, "Failed to initialize backend.");
return false;
}
}
return true;
}
static void subbackend_state_destroy(struct subbackend_state *sub) {
wl_list_remove(&sub->new_input.link);
wl_list_remove(&sub->new_output.link);
wl_list_remove(&sub->destroy.link);
wl_list_remove(&sub->link);
free(sub);
}
static void multi_backend_destroy(struct wlr_backend *wlr_backend) {
struct wlr_multi_backend *backend = multi_backend_from_backend(wlr_backend);
wl_list_remove(&backend->event_loop_destroy.link);
wlr_backend_finish(wlr_backend);
// Some backends may depend on other backends, ie. destroying a backend may
// also destroy other backends
while (!wl_list_empty(&backend->backends)) {
struct subbackend_state *sub =
wl_container_of(backend->backends.next, sub, link);
wlr_backend_destroy(sub->backend);
}
free(backend);
}
static int multi_backend_get_drm_fd(struct wlr_backend *backend) {
struct wlr_multi_backend *multi = multi_backend_from_backend(backend);
struct subbackend_state *sub;
wl_list_for_each(sub, &multi->backends, link) {
if (sub->backend->impl->get_drm_fd) {
return wlr_backend_get_drm_fd(sub->backend);
}
}
return -1;
}
static uint32_t multi_backend_get_buffer_caps(struct wlr_backend *backend) {
struct wlr_multi_backend *multi = multi_backend_from_backend(backend);
if (wl_list_empty(&multi->backends)) {
return 0;
}
uint32_t caps = WLR_BUFFER_CAP_DATA_PTR | WLR_BUFFER_CAP_DMABUF
| WLR_BUFFER_CAP_SHM;
struct subbackend_state *sub;
wl_list_for_each(sub, &multi->backends, link) {
uint32_t backend_caps = backend_get_buffer_caps(sub->backend);
if (backend_caps != 0) {
// only count backend capable of presenting a buffer
caps = caps & backend_caps;
}
}
return caps;
}
static const struct wlr_backend_impl backend_impl = {
.start = multi_backend_start,
.destroy = multi_backend_destroy,
.get_drm_fd = multi_backend_get_drm_fd,
.get_buffer_caps = multi_backend_get_buffer_caps,
};
static void handle_event_loop_destroy(struct wl_listener *listener, void *data) {
struct wlr_multi_backend *backend =
wl_container_of(listener, backend, event_loop_destroy);
multi_backend_destroy((struct wlr_backend*)backend);
}
struct wlr_backend *wlr_multi_backend_create(struct wl_event_loop *loop) {
struct wlr_multi_backend *backend = calloc(1, sizeof(*backend));
if (!backend) {
wlr_log(WLR_ERROR, "Backend allocation failed");
return NULL;
}
wl_list_init(&backend->backends);
wlr_backend_init(&backend->backend, &backend_impl);
wl_signal_init(&backend->events.backend_add);
wl_signal_init(&backend->events.backend_remove);
backend->event_loop_destroy.notify = handle_event_loop_destroy;
wl_event_loop_add_destroy_listener(loop, &backend->event_loop_destroy);
return &backend->backend;
}
bool wlr_backend_is_multi(struct wlr_backend *b) {
return b->impl == &backend_impl;
}
static void new_input_reemit(struct wl_listener *listener, void *data) {
struct subbackend_state *state = wl_container_of(listener,
state, new_input);
wl_signal_emit_mutable(&state->container->events.new_input, data);
}
static void new_output_reemit(struct wl_listener *listener, void *data) {
struct subbackend_state *state = wl_container_of(listener,
state, new_output);
wl_signal_emit_mutable(&state->container->events.new_output, data);
}
static void handle_subbackend_destroy(struct wl_listener *listener,
void *data) {
struct subbackend_state *state = wl_container_of(listener, state, destroy);
subbackend_state_destroy(state);
}
static struct subbackend_state *multi_backend_get_subbackend(struct wlr_multi_backend *multi,
struct wlr_backend *backend) {
struct subbackend_state *sub = NULL;
wl_list_for_each(sub, &multi->backends, link) {
if (sub->backend == backend) {
return sub;
}
}
return NULL;
}
bool wlr_multi_backend_add(struct wlr_backend *_multi,
struct wlr_backend *backend) {
assert(_multi && backend);
assert(_multi != backend);
struct wlr_multi_backend *multi = multi_backend_from_backend(_multi);
if (multi_backend_get_subbackend(multi, backend)) {
// already added
return true;
}
struct subbackend_state *sub = calloc(1, sizeof(*sub));
if (sub == NULL) {
wlr_log(WLR_ERROR, "Could not add backend: allocation failed");
return false;
}
wl_list_insert(multi->backends.prev, &sub->link);
sub->backend = backend;
sub->container = &multi->backend;
wl_signal_add(&backend->events.destroy, &sub->destroy);
sub->destroy.notify = handle_subbackend_destroy;
wl_signal_add(&backend->events.new_input, &sub->new_input);
sub->new_input.notify = new_input_reemit;
wl_signal_add(&backend->events.new_output, &sub->new_output);
sub->new_output.notify = new_output_reemit;
wl_signal_emit_mutable(&multi->events.backend_add, backend);
return true;
}
void wlr_multi_backend_remove(struct wlr_backend *_multi,
struct wlr_backend *backend) {
struct wlr_multi_backend *multi = multi_backend_from_backend(_multi);
struct subbackend_state *sub =
multi_backend_get_subbackend(multi, backend);
if (sub) {
wl_signal_emit_mutable(&multi->events.backend_remove, backend);
subbackend_state_destroy(sub);
}
}
bool wlr_multi_is_empty(struct wlr_backend *_backend) {
assert(wlr_backend_is_multi(_backend));
struct wlr_multi_backend *backend = (struct wlr_multi_backend *)_backend;
return wl_list_length(&backend->backends) < 1;
}
void wlr_multi_for_each_backend(struct wlr_backend *_backend,
void (*callback)(struct wlr_backend *backend, void *data), void *data) {
assert(wlr_backend_is_multi(_backend));
struct wlr_multi_backend *backend = (struct wlr_multi_backend *)_backend;
struct subbackend_state *sub;
wl_list_for_each(sub, &backend->backends, link) {
callback(sub->backend, data);
}
}

View file

@ -0,0 +1 @@
wlr_files += files('backend.c')

View file

@ -0,0 +1,17 @@
msg = 'Required for session support.'
udev = dependency('libudev', required: session_required, not_found_message: msg)
libseat = dependency(
'libseat',
version: '>=0.2.0',
fallback: 'seatd',
default_options: ['server=disabled', 'man-pages=disabled', 'examples=disabled'],
required: session_required,
not_found_message: msg,
)
if not (udev.found() and libseat.found())
subdir_done()
endif
wlr_files += files('session.c')
wlr_deps += [udev, libseat]
features += { 'session': true }

551
backend/session/session.c Normal file
View file

@ -0,0 +1,551 @@
#include <assert.h>
#include <libudev.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/backend/session.h>
#include <wlr/config.h>
#include <wlr/util/log.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include "backend/session/session.h"
#include "util/time.h"
#include <libseat.h>
#define WAIT_GPU_TIMEOUT 10000 // ms
static void handle_enable_seat(struct libseat *seat, void *data) {
struct wlr_session *session = data;
session->active = true;
wl_signal_emit_mutable(&session->events.active, NULL);
}
static void handle_disable_seat(struct libseat *seat, void *data) {
struct wlr_session *session = data;
session->active = false;
wl_signal_emit_mutable(&session->events.active, NULL);
libseat_disable_seat(session->seat_handle);
}
static int libseat_event(int fd, uint32_t mask, void *data) {
struct wlr_session *session = data;
if (libseat_dispatch(session->seat_handle, 0) == -1) {
wlr_log_errno(WLR_ERROR, "Failed to dispatch libseat");
wlr_session_destroy(session);
}
return 1;
}
static struct libseat_seat_listener seat_listener = {
.enable_seat = handle_enable_seat,
.disable_seat = handle_disable_seat,
};
static enum wlr_log_importance libseat_log_level_to_wlr(
enum libseat_log_level level) {
switch (level) {
case LIBSEAT_LOG_LEVEL_ERROR:
return WLR_ERROR;
case LIBSEAT_LOG_LEVEL_INFO:
return WLR_INFO;
default:
return WLR_DEBUG;
}
}
static void log_libseat(enum libseat_log_level level,
const char *fmt, va_list args) {
enum wlr_log_importance importance = libseat_log_level_to_wlr(level);
static char wlr_fmt[1024];
snprintf(wlr_fmt, sizeof(wlr_fmt), "[libseat] %s", fmt);
_wlr_vlog(importance, wlr_fmt, args);
}
static int libseat_session_init(struct wlr_session *session,
struct wl_event_loop *event_loop) {
libseat_set_log_handler(log_libseat);
libseat_set_log_level(LIBSEAT_LOG_LEVEL_INFO);
// libseat will take care of updating the logind state if necessary
setenv("XDG_SESSION_TYPE", "wayland", 1);
session->seat_handle = libseat_open_seat(&seat_listener, session);
if (session->seat_handle == NULL) {
wlr_log_errno(WLR_ERROR, "Unable to create seat");
return -1;
}
const char *seat_name = libseat_seat_name(session->seat_handle);
if (seat_name == NULL) {
wlr_log_errno(WLR_ERROR, "Unable to get seat info");
goto error;
}
snprintf(session->seat, sizeof(session->seat), "%s", seat_name);
session->libseat_event = wl_event_loop_add_fd(event_loop, libseat_get_fd(session->seat_handle),
WL_EVENT_READABLE, libseat_event, session);
if (session->libseat_event == NULL) {
wlr_log(WLR_ERROR, "Failed to create libseat event source");
goto error;
}
// We may have received enable_seat immediately after the open_seat result,
// so, dispatch once without timeout to speed up activation.
if (libseat_dispatch(session->seat_handle, 0) == -1) {
wlr_log_errno(WLR_ERROR, "libseat dispatch failed");
goto error_dispatch;
}
wlr_log(WLR_INFO, "Successfully loaded libseat session");
return 0;
error_dispatch:
wl_event_source_remove(session->libseat_event);
session->libseat_event = NULL;
error:
libseat_close_seat(session->seat_handle);
session->seat_handle = NULL;
return -1;
}
static void libseat_session_finish(struct wlr_session *session) {
libseat_close_seat(session->seat_handle);
wl_event_source_remove(session->libseat_event);
session->seat_handle = NULL;
session->libseat_event = NULL;
}
static bool is_drm_card(const char *sysname) {
const char prefix[] = DRM_PRIMARY_MINOR_NAME;
if (strncmp(sysname, prefix, strlen(prefix)) != 0) {
return false;
}
for (size_t i = strlen(prefix); sysname[i] != '\0'; i++) {
if (sysname[i] < '0' || sysname[i] > '9') {
return false;
}
}
return true;
}
static void read_udev_change_event(struct wlr_device_change_event *event,
struct udev_device *udev_dev) {
const char *hotplug = udev_device_get_property_value(udev_dev, "HOTPLUG");
if (hotplug != NULL && strcmp(hotplug, "1") == 0) {
event->type = WLR_DEVICE_HOTPLUG;
struct wlr_device_hotplug_event *hotplug = &event->hotplug;
const char *connector =
udev_device_get_property_value(udev_dev, "CONNECTOR");
if (connector != NULL) {
hotplug->connector_id = strtoul(connector, NULL, 10);
}
const char *prop =
udev_device_get_property_value(udev_dev, "PROPERTY");
if (prop != NULL) {
hotplug->prop_id = strtoul(prop, NULL, 10);
}
return;
}
const char *lease = udev_device_get_property_value(udev_dev, "LEASE");
if (lease != NULL && strcmp(lease, "1") == 0) {
event->type = WLR_DEVICE_LEASE;
return;
}
}
static int handle_udev_event(int fd, uint32_t mask, void *data) {
struct wlr_session *session = data;
struct udev_device *udev_dev = udev_monitor_receive_device(session->mon);
if (!udev_dev) {
return 1;
}
const char *sysname = udev_device_get_sysname(udev_dev);
const char *devnode = udev_device_get_devnode(udev_dev);
const char *action = udev_device_get_action(udev_dev);
wlr_log(WLR_DEBUG, "udev event for %s (%s)", sysname, action);
if (!is_drm_card(sysname) || !action || !devnode) {
goto out;
}
const char *seat = udev_device_get_property_value(udev_dev, "ID_SEAT");
if (!seat) {
seat = "seat0";
}
if (session->seat[0] != '\0' && strcmp(session->seat, seat) != 0) {
goto out;
}
if (strcmp(action, "add") == 0) {
wlr_log(WLR_DEBUG, "DRM device %s added", sysname);
struct wlr_session_add_event event = {
.path = devnode,
};
wl_signal_emit_mutable(&session->events.add_drm_card, &event);
} else if (strcmp(action, "change") == 0 || strcmp(action, "remove") == 0) {
dev_t devnum = udev_device_get_devnum(udev_dev);
struct wlr_device *dev;
wl_list_for_each(dev, &session->devices, link) {
if (dev->dev != devnum) {
continue;
}
if (strcmp(action, "change") == 0) {
wlr_log(WLR_DEBUG, "DRM device %s changed", sysname);
struct wlr_device_change_event event = {0};
read_udev_change_event(&event, udev_dev);
wl_signal_emit_mutable(&dev->events.change, &event);
} else if (strcmp(action, "remove") == 0) {
wlr_log(WLR_DEBUG, "DRM device %s removed", sysname);
wl_signal_emit_mutable(&dev->events.remove, NULL);
} else {
assert(0);
}
break;
}
}
out:
udev_device_unref(udev_dev);
return 1;
}
static void handle_event_loop_destroy(struct wl_listener *listener, void *data) {
struct wlr_session *session =
wl_container_of(listener, session, event_loop_destroy);
wlr_session_destroy(session);
}
struct wlr_session *wlr_session_create(struct wl_event_loop *event_loop) {
struct wlr_session *session = calloc(1, sizeof(*session));
if (!session) {
wlr_log_errno(WLR_ERROR, "Allocation failed");
return NULL;
}
session->event_loop = event_loop;
wl_signal_init(&session->events.active);
wl_signal_init(&session->events.add_drm_card);
wl_signal_init(&session->events.destroy);
wl_list_init(&session->devices);
if (libseat_session_init(session, event_loop) == -1) {
wlr_log(WLR_ERROR, "Failed to load session backend");
goto error_open;
}
session->udev = udev_new();
if (!session->udev) {
wlr_log_errno(WLR_ERROR, "Failed to create udev context");
goto error_session;
}
session->mon = udev_monitor_new_from_netlink(session->udev, "udev");
if (!session->mon) {
wlr_log_errno(WLR_ERROR, "Failed to create udev monitor");
goto error_udev;
}
udev_monitor_filter_add_match_subsystem_devtype(session->mon, "drm", NULL);
udev_monitor_enable_receiving(session->mon);
int fd = udev_monitor_get_fd(session->mon);
session->udev_event = wl_event_loop_add_fd(event_loop, fd,
WL_EVENT_READABLE, handle_udev_event, session);
if (!session->udev_event) {
wlr_log_errno(WLR_ERROR, "Failed to create udev event source");
goto error_mon;
}
session->event_loop_destroy.notify = handle_event_loop_destroy;
wl_event_loop_add_destroy_listener(event_loop, &session->event_loop_destroy);
return session;
error_mon:
udev_monitor_unref(session->mon);
error_udev:
udev_unref(session->udev);
error_session:
libseat_session_finish(session);
error_open:
free(session);
return NULL;
}
void wlr_session_destroy(struct wlr_session *session) {
if (!session) {
return;
}
wl_signal_emit_mutable(&session->events.destroy, session);
wl_list_remove(&session->event_loop_destroy.link);
wl_event_source_remove(session->udev_event);
udev_monitor_unref(session->mon);
udev_unref(session->udev);
struct wlr_device *dev, *tmp_dev;
wl_list_for_each_safe(dev, tmp_dev, &session->devices, link) {
wlr_session_close_file(session, dev);
}
libseat_session_finish(session);
free(session);
}
struct wlr_device *wlr_session_open_file(struct wlr_session *session,
const char *path) {
int fd;
int device_id = libseat_open_device(session->seat_handle, path, &fd);
if (device_id == -1) {
wlr_log_errno(WLR_ERROR, "Failed to open device: '%s'", path);
return NULL;
}
struct wlr_device *dev = malloc(sizeof(*dev));
if (!dev) {
wlr_log_errno(WLR_ERROR, "Allocation failed");
goto error;
}
struct stat st;
if (fstat(fd, &st) < 0) {
wlr_log_errno(WLR_ERROR, "Stat failed");
goto error;
}
dev->fd = fd;
dev->dev = st.st_rdev;
dev->device_id = device_id;
wl_signal_init(&dev->events.change);
wl_signal_init(&dev->events.remove);
wl_list_insert(&session->devices, &dev->link);
return dev;
error:
libseat_close_device(session->seat_handle, device_id);
free(dev);
close(fd);
return NULL;
}
void wlr_session_close_file(struct wlr_session *session,
struct wlr_device *dev) {
if (libseat_close_device(session->seat_handle, dev->device_id) == -1) {
wlr_log_errno(WLR_ERROR, "Failed to close device %d", dev->device_id);
}
close(dev->fd);
wl_list_remove(&dev->link);
free(dev);
}
bool wlr_session_change_vt(struct wlr_session *session, unsigned vt) {
if (!session) {
return false;
}
return libseat_switch_session(session->seat_handle, vt) == 0;
}
/* Tests if 'path' is KMS compatible by trying to open it. Returns the opened
* device on success. */
struct wlr_device *session_open_if_kms(struct wlr_session *restrict session,
const char *restrict path) {
if (!path) {
return NULL;
}
struct wlr_device *dev = wlr_session_open_file(session, path);
if (!dev) {
return NULL;
}
if (!drmIsKMS(dev->fd)) {
wlr_log(WLR_DEBUG, "Ignoring '%s': not a KMS device", path);
wlr_session_close_file(session, dev);
return NULL;
}
return dev;
}
static ssize_t explicit_find_gpus(struct wlr_session *session,
size_t ret_len, struct wlr_device *ret[static ret_len], const char *str) {
char *gpus = strdup(str);
if (!gpus) {
wlr_log_errno(WLR_ERROR, "Allocation failed");
return -1;
}
size_t i = 0;
char *save;
char *ptr = strtok_r(gpus, ":", &save);
do {
if (i >= ret_len) {
break;
}
ret[i] = session_open_if_kms(session, ptr);
if (!ret[i]) {
wlr_log(WLR_ERROR, "Unable to open %s as DRM device", ptr);
} else {
++i;
}
} while ((ptr = strtok_r(NULL, ":", &save)));
free(gpus);
return i;
}
static struct udev_enumerate *enumerate_drm_cards(struct udev *udev) {
struct udev_enumerate *en = udev_enumerate_new(udev);
if (!en) {
wlr_log(WLR_ERROR, "udev_enumerate_new failed");
return NULL;
}
udev_enumerate_add_match_subsystem(en, "drm");
udev_enumerate_add_match_sysname(en, DRM_PRIMARY_MINOR_NAME "[0-9]*");
if (udev_enumerate_scan_devices(en) != 0) {
wlr_log(WLR_ERROR, "udev_enumerate_scan_devices failed");
udev_enumerate_unref(en);
return NULL;
}
return en;
}
struct find_gpus_add_handler {
bool added;
struct wl_listener listener;
};
static void find_gpus_handle_add(struct wl_listener *listener, void *data) {
struct find_gpus_add_handler *handler =
wl_container_of(listener, handler, listener);
handler->added = true;
}
ssize_t wlr_session_find_gpus(struct wlr_session *session,
size_t ret_len, struct wlr_device **ret) {
const char *explicit = getenv("WLR_DRM_DEVICES");
if (explicit) {
return explicit_find_gpus(session, ret_len, ret, explicit);
}
struct udev_enumerate *en = enumerate_drm_cards(session->udev);
if (!en) {
return -1;
}
if (udev_enumerate_get_list_entry(en) == NULL) {
udev_enumerate_unref(en);
wlr_log(WLR_INFO, "Waiting for a DRM card device");
struct find_gpus_add_handler handler = {0};
handler.listener.notify = find_gpus_handle_add;
wl_signal_add(&session->events.add_drm_card, &handler.listener);
int64_t started_at = get_current_time_msec();
int64_t timeout = WAIT_GPU_TIMEOUT;
while (!handler.added) {
int ret = wl_event_loop_dispatch(session->event_loop, (int)timeout);
if (ret < 0) {
wlr_log_errno(WLR_ERROR, "Failed to wait for DRM card device: "
"wl_event_loop_dispatch failed");
udev_enumerate_unref(en);
return -1;
}
int64_t now = get_current_time_msec();
if (now >= started_at + WAIT_GPU_TIMEOUT) {
break;
}
timeout = started_at + WAIT_GPU_TIMEOUT - now;
}
wl_list_remove(&handler.listener.link);
en = enumerate_drm_cards(session->udev);
if (!en) {
return -1;
}
}
struct udev_list_entry *entry;
size_t i = 0;
udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(en)) {
if (i == ret_len) {
break;
}
bool is_boot_vga = false;
const char *path = udev_list_entry_get_name(entry);
struct udev_device *dev = udev_device_new_from_syspath(session->udev, path);
if (!dev) {
continue;
}
const char *seat = udev_device_get_property_value(dev, "ID_SEAT");
if (!seat) {
seat = "seat0";
}
if (session->seat[0] && strcmp(session->seat, seat) != 0) {
udev_device_unref(dev);
continue;
}
// This is owned by 'dev', so we don't need to free it
struct udev_device *pci =
udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL);
if (pci) {
const char *id = udev_device_get_sysattr_value(pci, "boot_vga");
if (id && strcmp(id, "1") == 0) {
is_boot_vga = true;
}
}
struct wlr_device *wlr_dev =
session_open_if_kms(session, udev_device_get_devnode(dev));
if (!wlr_dev) {
udev_device_unref(dev);
continue;
}
udev_device_unref(dev);
ret[i] = wlr_dev;
if (is_boot_vga) {
struct wlr_device *tmp = ret[0];
ret[0] = ret[i];
ret[i] = tmp;
}
++i;
}
udev_enumerate_unref(en);
return i;
}

701
backend/wayland/backend.c Normal file
View file

@ -0,0 +1,701 @@
#include <assert.h>
#include <fcntl.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <drm_fourcc.h>
#include <wayland-server-core.h>
#include <xf86drm.h>
#include <wlr/backend/interface.h>
#include <wlr/interfaces/wlr_output.h>
#include <wlr/util/log.h>
#include "backend/wayland.h"
#include "render/drm_format_set.h"
#include "render/pixel_format.h"
#include "drm-client-protocol.h"
#include "linux-dmabuf-v1-client-protocol.h"
#include "pointer-gestures-unstable-v1-client-protocol.h"
#include "presentation-time-client-protocol.h"
#include "xdg-activation-v1-client-protocol.h"
#include "xdg-decoration-unstable-v1-client-protocol.h"
#include "xdg-shell-client-protocol.h"
#include "tablet-unstable-v2-client-protocol.h"
#include "relative-pointer-unstable-v1-client-protocol.h"
#include "viewporter-client-protocol.h"
struct wlr_wl_linux_dmabuf_feedback_v1 {
struct wlr_wl_backend *backend;
dev_t main_device_id;
struct wlr_wl_linux_dmabuf_v1_table_entry *format_table;
size_t format_table_size;
dev_t tranche_target_device_id;
};
struct wlr_wl_linux_dmabuf_v1_table_entry {
uint32_t format;
uint32_t pad; /* unused */
uint64_t modifier;
};
struct wlr_wl_backend *get_wl_backend_from_backend(struct wlr_backend *wlr_backend) {
assert(wlr_backend_is_wl(wlr_backend));
struct wlr_wl_backend *backend = wl_container_of(wlr_backend, backend, backend);
return backend;
}
static int dispatch_events(int fd, uint32_t mask, void *data) {
struct wlr_wl_backend *wl = data;
if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) {
if (mask & WL_EVENT_ERROR) {
wlr_log(WLR_ERROR, "Failed to read from remote Wayland display");
}
wlr_backend_destroy(&wl->backend);
return 0;
}
int count = 0;
if (mask & WL_EVENT_READABLE) {
count = wl_display_dispatch(wl->remote_display);
}
if (mask & WL_EVENT_WRITABLE) {
wl_display_flush(wl->remote_display);
}
if (mask == 0) {
count = wl_display_dispatch_pending(wl->remote_display);
wl_display_flush(wl->remote_display);
}
if (count < 0) {
wlr_log(WLR_ERROR, "Failed to dispatch remote Wayland display");
wlr_backend_destroy(&wl->backend);
return 0;
}
return count;
}
static void xdg_wm_base_handle_ping(void *data,
struct xdg_wm_base *base, uint32_t serial) {
xdg_wm_base_pong(base, serial);
}
static const struct xdg_wm_base_listener xdg_wm_base_listener = {
xdg_wm_base_handle_ping,
};
static void presentation_handle_clock_id(void *data,
struct wp_presentation *presentation, uint32_t clock) {
struct wlr_wl_backend *wl = data;
if (clock != CLOCK_MONOTONIC) {
wp_presentation_destroy(wl->presentation);
wl->presentation = NULL;
}
}
static const struct wp_presentation_listener presentation_listener = {
.clock_id = presentation_handle_clock_id,
};
static void linux_dmabuf_v1_handle_format(void *data,
struct zwp_linux_dmabuf_v1 *linux_dmabuf_v1, uint32_t format) {
// Note, this event is deprecated
struct wlr_wl_backend *wl = data;
wlr_drm_format_set_add(&wl->linux_dmabuf_v1_formats, format,
DRM_FORMAT_MOD_INVALID);
}
static void linux_dmabuf_v1_handle_modifier(void *data,
struct zwp_linux_dmabuf_v1 *linux_dmabuf_v1, uint32_t format,
uint32_t modifier_hi, uint32_t modifier_lo) {
struct wlr_wl_backend *wl = data;
uint64_t modifier = ((uint64_t)modifier_hi << 32) | modifier_lo;
wlr_drm_format_set_add(&wl->linux_dmabuf_v1_formats, format, modifier);
}
static const struct zwp_linux_dmabuf_v1_listener linux_dmabuf_v1_listener = {
.format = linux_dmabuf_v1_handle_format,
.modifier = linux_dmabuf_v1_handle_modifier,
};
static void linux_dmabuf_feedback_v1_handle_done(void *data,
struct zwp_linux_dmabuf_feedback_v1 *feedback) {
// This space is intentionally left blank
}
static void linux_dmabuf_feedback_v1_handle_format_table(void *data,
struct zwp_linux_dmabuf_feedback_v1 *feedback, int fd, uint32_t size) {
struct wlr_wl_linux_dmabuf_feedback_v1 *feedback_data = data;
feedback_data->format_table = NULL;
void *table_data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (table_data == MAP_FAILED) {
wlr_log_errno(WLR_ERROR, "failed to mmap DMA-BUF format table");
} else {
feedback_data->format_table = table_data;
feedback_data->format_table_size = size;
}
close(fd);
}
static void linux_dmabuf_feedback_v1_handle_main_device(void *data,
struct zwp_linux_dmabuf_feedback_v1 *feedback,
struct wl_array *dev_id_arr) {
struct wlr_wl_linux_dmabuf_feedback_v1 *feedback_data = data;
dev_t dev_id;
assert(dev_id_arr->size == sizeof(dev_id));
memcpy(&dev_id, dev_id_arr->data, sizeof(dev_id));
feedback_data->main_device_id = dev_id;
drmDevice *device = NULL;
if (drmGetDeviceFromDevId(dev_id, 0, &device) != 0) {
wlr_log_errno(WLR_ERROR, "drmGetDeviceFromDevId failed");
return;
}
const char *name = NULL;
if (device->available_nodes & (1 << DRM_NODE_RENDER)) {
name = device->nodes[DRM_NODE_RENDER];
} else {
// Likely a split display/render setup. Pick the primary node and hope
// Mesa will open the right render node under-the-hood.
assert(device->available_nodes & (1 << DRM_NODE_PRIMARY));
name = device->nodes[DRM_NODE_PRIMARY];
wlr_log(WLR_DEBUG, "DRM device %s has no render node, "
"falling back to primary node", name);
}
feedback_data->backend->drm_render_name = strdup(name);
drmFreeDevice(&device);
}
static void linux_dmabuf_feedback_v1_handle_tranche_done(void *data,
struct zwp_linux_dmabuf_feedback_v1 *feedback) {
struct wlr_wl_linux_dmabuf_feedback_v1 *feedback_data = data;
feedback_data->tranche_target_device_id = 0;
}
static void linux_dmabuf_feedback_v1_handle_tranche_target_device(void *data,
struct zwp_linux_dmabuf_feedback_v1 *feedback,
struct wl_array *dev_id_arr) {
struct wlr_wl_linux_dmabuf_feedback_v1 *feedback_data = data;
dev_t dev_id;
assert(dev_id_arr->size == sizeof(dev_id));
memcpy(&dev_id, dev_id_arr->data, sizeof(dev_id));
feedback_data->tranche_target_device_id = dev_id;
}
static void linux_dmabuf_feedback_v1_handle_tranche_formats(void *data,
struct zwp_linux_dmabuf_feedback_v1 *feedback,
struct wl_array *indices_arr) {
struct wlr_wl_linux_dmabuf_feedback_v1 *feedback_data = data;
if (feedback_data->format_table == NULL) {
return;
}
if (feedback_data->tranche_target_device_id != feedback_data->main_device_id) {
return;
}
size_t table_cap = feedback_data->format_table_size /
sizeof(struct wlr_wl_linux_dmabuf_v1_table_entry);
uint16_t *index_ptr;
wl_array_for_each(index_ptr, indices_arr) {
assert(*index_ptr < table_cap);
const struct wlr_wl_linux_dmabuf_v1_table_entry *entry =
&feedback_data->format_table[*index_ptr];
wlr_drm_format_set_add(&feedback_data->backend->linux_dmabuf_v1_formats,
entry->format, entry->modifier);
}
}
static void linux_dmabuf_feedback_v1_handle_tranche_flags(void *data,
struct zwp_linux_dmabuf_feedback_v1 *feedback, uint32_t flags) {
// TODO: handle SCANOUT flag
}
static const struct zwp_linux_dmabuf_feedback_v1_listener
linux_dmabuf_feedback_v1_listener = {
.done = linux_dmabuf_feedback_v1_handle_done,
.format_table = linux_dmabuf_feedback_v1_handle_format_table,
.main_device = linux_dmabuf_feedback_v1_handle_main_device,
.tranche_done = linux_dmabuf_feedback_v1_handle_tranche_done,
.tranche_target_device = linux_dmabuf_feedback_v1_handle_tranche_target_device,
.tranche_formats = linux_dmabuf_feedback_v1_handle_tranche_formats,
.tranche_flags = linux_dmabuf_feedback_v1_handle_tranche_flags,
};
static bool device_has_name(const drmDevice *device, const char *name) {
for (size_t i = 0; i < DRM_NODE_MAX; i++) {
if (!(device->available_nodes & (1 << i))) {
continue;
}
if (strcmp(device->nodes[i], name) == 0) {
return true;
}
}
return false;
}
static char *get_render_name(const char *name) {
uint32_t flags = 0;
int devices_len = drmGetDevices2(flags, NULL, 0);
if (devices_len < 0) {
wlr_log(WLR_ERROR, "drmGetDevices2 failed: %s", strerror(-devices_len));
return NULL;
}
drmDevice **devices = calloc(devices_len, sizeof(*devices));
if (devices == NULL) {
wlr_log_errno(WLR_ERROR, "Allocation failed");
return NULL;
}
devices_len = drmGetDevices2(flags, devices, devices_len);
if (devices_len < 0) {
free(devices);
wlr_log(WLR_ERROR, "drmGetDevices2 failed: %s", strerror(-devices_len));
return NULL;
}
const drmDevice *match = NULL;
for (int i = 0; i < devices_len; i++) {
if (device_has_name(devices[i], name)) {
match = devices[i];
break;
}
}
char *render_name = NULL;
if (match == NULL) {
wlr_log(WLR_ERROR, "Cannot find DRM device %s", name);
} else if (!(match->available_nodes & (1 << DRM_NODE_RENDER))) {
// Likely a split display/render setup. Pick the primary node and hope
// Mesa will open the right render node under-the-hood.
wlr_log(WLR_DEBUG, "DRM device %s has no render node, "
"falling back to primary node", name);
assert(match->available_nodes & (1 << DRM_NODE_PRIMARY));
render_name = strdup(match->nodes[DRM_NODE_PRIMARY]);
} else {
render_name = strdup(match->nodes[DRM_NODE_RENDER]);
}
for (int i = 0; i < devices_len; i++) {
drmFreeDevice(&devices[i]);
}
free(devices);
return render_name;
}
static void legacy_drm_handle_device(void *data, struct wl_drm *drm,
const char *name) {
struct wlr_wl_backend *wl = data;
wl->drm_render_name = get_render_name(name);
}
static void legacy_drm_handle_format(void *data, struct wl_drm *drm,
uint32_t format) {
// This space is intentionally left blank
}
static void legacy_drm_handle_authenticated(void *data, struct wl_drm *drm) {
// This space is intentionally left blank
}
static void legacy_drm_handle_capabilities(void *data, struct wl_drm *drm,
uint32_t caps) {
// This space is intentionally left blank
}
static const struct wl_drm_listener legacy_drm_listener = {
.device = legacy_drm_handle_device,
.format = legacy_drm_handle_format,
.authenticated = legacy_drm_handle_authenticated,
.capabilities = legacy_drm_handle_capabilities,
};
static void shm_handle_format(void *data, struct wl_shm *shm,
uint32_t shm_format) {
struct wlr_wl_backend *wl = data;
uint32_t drm_format = convert_wl_shm_format_to_drm(shm_format);
wlr_drm_format_set_add(&wl->shm_formats, drm_format, DRM_FORMAT_MOD_INVALID);
}
static const struct wl_shm_listener shm_listener = {
.format = shm_handle_format,
};
static void registry_global(void *data, struct wl_registry *registry,
uint32_t name, const char *iface, uint32_t version) {
struct wlr_wl_backend *wl = data;
wlr_log(WLR_DEBUG, "Remote wayland global: %s v%" PRIu32, iface, version);
if (strcmp(iface, wl_compositor_interface.name) == 0) {
wl->compositor = wl_registry_bind(registry, name,
&wl_compositor_interface, 4);
} else if (strcmp(iface, wl_seat_interface.name) == 0) {
uint32_t target_version = version;
if (version < 5) {
target_version = 5;
}
if (version > 9) {
target_version = 9;
}
struct wl_seat *wl_seat = wl_registry_bind(registry, name,
&wl_seat_interface, target_version);
if (!create_wl_seat(wl_seat, wl, name)) {
wl_seat_destroy(wl_seat);
}
} else if (strcmp(iface, xdg_wm_base_interface.name) == 0) {
wl->xdg_wm_base = wl_registry_bind(registry, name,
&xdg_wm_base_interface, 1);
xdg_wm_base_add_listener(wl->xdg_wm_base, &xdg_wm_base_listener, NULL);
} else if (strcmp(iface, zxdg_decoration_manager_v1_interface.name) == 0) {
wl->zxdg_decoration_manager_v1 = wl_registry_bind(registry, name,
&zxdg_decoration_manager_v1_interface, 1);
} else if (strcmp(iface, zwp_pointer_gestures_v1_interface.name) == 0) {
wl->zwp_pointer_gestures_v1 = wl_registry_bind(registry, name,
&zwp_pointer_gestures_v1_interface, version < 3 ? version : 3);
} else if (strcmp(iface, wp_presentation_interface.name) == 0) {
wl->presentation = wl_registry_bind(registry, name,
&wp_presentation_interface, 1);
wp_presentation_add_listener(wl->presentation,
&presentation_listener, wl);
} else if (strcmp(iface, zwp_tablet_manager_v2_interface.name) == 0) {
wl->tablet_manager = wl_registry_bind(registry, name,
&zwp_tablet_manager_v2_interface, 1);
} else if (strcmp(iface, zwp_linux_dmabuf_v1_interface.name) == 0 &&
version >= 3) {
wl->zwp_linux_dmabuf_v1 = wl_registry_bind(registry, name,
&zwp_linux_dmabuf_v1_interface, version >= 4 ? 4 : version);
zwp_linux_dmabuf_v1_add_listener(wl->zwp_linux_dmabuf_v1,
&linux_dmabuf_v1_listener, wl);
} else if (strcmp(iface, zwp_relative_pointer_manager_v1_interface.name) == 0) {
wl->zwp_relative_pointer_manager_v1 = wl_registry_bind(registry, name,
&zwp_relative_pointer_manager_v1_interface, 1);
} else if (strcmp(iface, wl_drm_interface.name) == 0) {
wl->legacy_drm = wl_registry_bind(registry, name, &wl_drm_interface, 1);
wl_drm_add_listener(wl->legacy_drm, &legacy_drm_listener, wl);
} else if (strcmp(iface, wl_shm_interface.name) == 0) {
wl->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
wl_shm_add_listener(wl->shm, &shm_listener, wl);
} else if (strcmp(iface, xdg_activation_v1_interface.name) == 0) {
wl->activation_v1 = wl_registry_bind(registry, name,
&xdg_activation_v1_interface, 1);
} else if (strcmp(iface, wl_subcompositor_interface.name) == 0) {
wl->subcompositor = wl_registry_bind(registry, name,
&wl_subcompositor_interface, 1);
} else if (strcmp(iface, wp_viewporter_interface.name) == 0) {
wl->viewporter = wl_registry_bind(registry, name,
&wp_viewporter_interface, 1);
}
}
static void registry_global_remove(void *data, struct wl_registry *registry,
uint32_t name) {
struct wlr_wl_backend *wl = data;
struct wlr_wl_seat *seat;
wl_list_for_each(seat, &wl->seats, link) {
if (seat->global_name == name) {
destroy_wl_seat(seat);
break;
}
}
}
static const struct wl_registry_listener registry_listener = {
.global = registry_global,
.global_remove = registry_global_remove
};
/*
* Initializes the wayland backend. Opens a connection to a remote wayland
* compositor and creates surfaces for each output, then registers globals on
* the specified display.
*/
static bool backend_start(struct wlr_backend *backend) {
struct wlr_wl_backend *wl = get_wl_backend_from_backend(backend);
wlr_log(WLR_INFO, "Starting Wayland backend");
wl->started = true;
struct wlr_wl_seat *seat;
wl_list_for_each(seat, &wl->seats, link) {
if (seat->wl_keyboard) {
init_seat_keyboard(seat);
}
if (seat->wl_touch) {
init_seat_touch(seat);
}
if (wl->tablet_manager) {
init_seat_tablet(seat);
}
}
for (size_t i = 0; i < wl->requested_outputs; ++i) {
wlr_wl_output_create(&wl->backend);
}
return true;
}
static void backend_destroy(struct wlr_backend *backend) {
if (!backend) {
return;
}
struct wlr_wl_backend *wl = get_wl_backend_from_backend(backend);
struct wlr_wl_output *output, *tmp_output;
wl_list_for_each_safe(output, tmp_output, &wl->outputs, link) {
wlr_output_destroy(&output->wlr_output);
}
// Avoid using wl_list_for_each_safe() here: destroying a buffer may
// have the side-effect of destroying the next one in the list
while (!wl_list_empty(&wl->buffers)) {
struct wlr_wl_buffer *buffer = wl_container_of(wl->buffers.next, buffer, link);
destroy_wl_buffer(buffer);
}
wlr_backend_finish(backend);
wl_list_remove(&wl->event_loop_destroy.link);
wl_event_source_remove(wl->remote_display_src);
close(wl->drm_fd);
wlr_drm_format_set_finish(&wl->shm_formats);
wlr_drm_format_set_finish(&wl->linux_dmabuf_v1_formats);
struct wlr_wl_seat *seat, *tmp_seat;
wl_list_for_each_safe(seat, tmp_seat, &wl->seats, link) {
destroy_wl_seat(seat);
}
if (wl->activation_v1) {
xdg_activation_v1_destroy(wl->activation_v1);
}
if (wl->zxdg_decoration_manager_v1) {
zxdg_decoration_manager_v1_destroy(wl->zxdg_decoration_manager_v1);
}
if (wl->zwp_pointer_gestures_v1) {
zwp_pointer_gestures_v1_destroy(wl->zwp_pointer_gestures_v1);
}
if (wl->tablet_manager) {
zwp_tablet_manager_v2_destroy(wl->tablet_manager);
}
if (wl->presentation) {
wp_presentation_destroy(wl->presentation);
}
if (wl->zwp_linux_dmabuf_v1) {
zwp_linux_dmabuf_v1_destroy(wl->zwp_linux_dmabuf_v1);
}
if (wl->legacy_drm != NULL) {
wl_drm_destroy(wl->legacy_drm);
}
if (wl->shm) {
wl_shm_destroy(wl->shm);
}
if (wl->zwp_relative_pointer_manager_v1) {
zwp_relative_pointer_manager_v1_destroy(wl->zwp_relative_pointer_manager_v1);
}
if (wl->subcompositor) {
wl_subcompositor_destroy(wl->subcompositor);
}
if (wl->viewporter) {
wp_viewporter_destroy(wl->viewporter);
}
free(wl->drm_render_name);
free(wl->activation_token);
xdg_wm_base_destroy(wl->xdg_wm_base);
wl_compositor_destroy(wl->compositor);
wl_registry_destroy(wl->registry);
wl_display_flush(wl->remote_display);
if (wl->own_remote_display) {
wl_display_disconnect(wl->remote_display);
}
free(wl);
}
static int backend_get_drm_fd(struct wlr_backend *backend) {
struct wlr_wl_backend *wl = get_wl_backend_from_backend(backend);
return wl->drm_fd;
}
static uint32_t get_buffer_caps(struct wlr_backend *backend) {
struct wlr_wl_backend *wl = get_wl_backend_from_backend(backend);
return (wl->zwp_linux_dmabuf_v1 ? WLR_BUFFER_CAP_DMABUF : 0)
| (wl->shm ? WLR_BUFFER_CAP_SHM : 0);
}
static const struct wlr_backend_impl backend_impl = {
.start = backend_start,
.destroy = backend_destroy,
.get_drm_fd = backend_get_drm_fd,
.get_buffer_caps = get_buffer_caps,
};
bool wlr_backend_is_wl(struct wlr_backend *b) {
return b->impl == &backend_impl;
}
static void handle_event_loop_destroy(struct wl_listener *listener, void *data) {
struct wlr_wl_backend *wl = wl_container_of(listener, wl, event_loop_destroy);
backend_destroy(&wl->backend);
}
struct wlr_backend *wlr_wl_backend_create(struct wl_event_loop *loop,
struct wl_display *remote_display) {
wlr_log(WLR_INFO, "Creating wayland backend");
struct wlr_wl_backend *wl = calloc(1, sizeof(*wl));
if (!wl) {
wlr_log_errno(WLR_ERROR, "Allocation failed");
return NULL;
}
wlr_backend_init(&wl->backend, &backend_impl);
wl->event_loop = loop;
wl_list_init(&wl->outputs);
wl_list_init(&wl->seats);
wl_list_init(&wl->buffers);
if (remote_display != NULL) {
wl->remote_display = remote_display;
} else {
wl->remote_display = wl_display_connect(NULL);
if (!wl->remote_display) {
wlr_log_errno(WLR_ERROR, "Could not connect to remote display");
goto error_wl;
}
wl->own_remote_display = true;
}
wl->registry = wl_display_get_registry(wl->remote_display);
if (!wl->registry) {
wlr_log_errno(WLR_ERROR, "Could not obtain reference to remote registry");
goto error_display;
}
wl_registry_add_listener(wl->registry, &registry_listener, wl);
wl_display_roundtrip(wl->remote_display); // get globals
if (!wl->compositor) {
wlr_log(WLR_ERROR,
"Remote Wayland compositor does not support wl_compositor");
goto error_registry;
}
if (!wl->xdg_wm_base) {
wlr_log(WLR_ERROR,
"Remote Wayland compositor does not support xdg-shell");
goto error_registry;
}
struct zwp_linux_dmabuf_feedback_v1 *linux_dmabuf_feedback_v1 = NULL;
struct wlr_wl_linux_dmabuf_feedback_v1 feedback_data = { .backend = wl };
if (wl->zwp_linux_dmabuf_v1 != NULL &&
zwp_linux_dmabuf_v1_get_version(wl->zwp_linux_dmabuf_v1) >=
ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION) {
linux_dmabuf_feedback_v1 =
zwp_linux_dmabuf_v1_get_default_feedback(wl->zwp_linux_dmabuf_v1);
if (linux_dmabuf_feedback_v1 == NULL) {
wlr_log(WLR_ERROR, "Allocation failed");
goto error_registry;
}
zwp_linux_dmabuf_feedback_v1_add_listener(linux_dmabuf_feedback_v1,
&linux_dmabuf_feedback_v1_listener, &feedback_data);
if (wl->legacy_drm != NULL) {
wl_drm_destroy(wl->legacy_drm);
wl->legacy_drm = NULL;
}
}
wl_display_roundtrip(wl->remote_display); // get linux-dmabuf formats
if (feedback_data.format_table != NULL) {
munmap(feedback_data.format_table, feedback_data.format_table_size);
}
if (linux_dmabuf_feedback_v1 != NULL) {
zwp_linux_dmabuf_feedback_v1_destroy(linux_dmabuf_feedback_v1);
}
int fd = wl_display_get_fd(wl->remote_display);
wl->remote_display_src = wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE,
dispatch_events, wl);
if (!wl->remote_display_src) {
wlr_log(WLR_ERROR, "Failed to create event source");
goto error_registry;
}
wl_event_source_check(wl->remote_display_src);
if (wl->drm_render_name != NULL) {
wlr_log(WLR_DEBUG, "Opening DRM render node %s", wl->drm_render_name);
wl->drm_fd = open(wl->drm_render_name, O_RDWR | O_NONBLOCK | O_CLOEXEC);
if (wl->drm_fd < 0) {
wlr_log_errno(WLR_ERROR, "Failed to open DRM render node %s",
wl->drm_render_name);
goto error_remote_display_src;
}
} else {
wl->drm_fd = -1;
}
wl->event_loop_destroy.notify = handle_event_loop_destroy;
wl_event_loop_add_destroy_listener(loop, &wl->event_loop_destroy);
const char *token = getenv("XDG_ACTIVATION_TOKEN");
if (token != NULL) {
wl->activation_token = strdup(token);
unsetenv("XDG_ACTIVATION_TOKEN");
}
return &wl->backend;
error_remote_display_src:
wl_event_source_remove(wl->remote_display_src);
error_registry:
free(wl->drm_render_name);
if (wl->compositor) {
wl_compositor_destroy(wl->compositor);
}
if (wl->xdg_wm_base) {
xdg_wm_base_destroy(wl->xdg_wm_base);
}
wl_registry_destroy(wl->registry);
error_display:
if (wl->own_remote_display) {
wl_display_disconnect(wl->remote_display);
}
error_wl:
wlr_backend_finish(&wl->backend);
free(wl);
return NULL;
}
struct wl_display *wlr_wl_backend_get_remote_display(struct wlr_backend *backend) {
struct wlr_wl_backend *wl = get_wl_backend_from_backend(backend);
return wl->remote_display;
}

View file

@ -0,0 +1,30 @@
wayland_client = dependency('wayland-client',
fallback: 'wayland',
default_options: wayland_project_options,
)
wlr_deps += wayland_client
wlr_files += files(
'backend.c',
'output.c',
'seat.c',
'pointer.c',
'tablet_v2.c',
)
client_protos = [
'drm',
'linux-dmabuf-v1',
'pointer-gestures-unstable-v1',
'presentation-time',
'relative-pointer-unstable-v1',
'tablet-unstable-v2',
'viewporter',
'xdg-activation-v1',
'xdg-decoration-unstable-v1',
'xdg-shell',
]
foreach proto : client_protos
wlr_files += protocols_client_header[proto]
endforeach

932
backend/wayland/output.c Normal file
View file

@ -0,0 +1,932 @@
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <drm_fourcc.h>
#include <wayland-client.h>
#include <wlr/interfaces/wlr_output.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_output_layer.h>
#include <wlr/util/log.h>
#include "backend/wayland.h"
#include "render/pixel_format.h"
#include "render/wlr_renderer.h"
#include "types/wlr_output.h"
#include "linux-dmabuf-v1-client-protocol.h"
#include "presentation-time-client-protocol.h"
#include "viewporter-client-protocol.h"
#include "xdg-activation-v1-client-protocol.h"
#include "xdg-decoration-unstable-v1-client-protocol.h"
#include "xdg-shell-client-protocol.h"
static const uint32_t SUPPORTED_OUTPUT_STATE =
WLR_OUTPUT_STATE_BACKEND_OPTIONAL |
WLR_OUTPUT_STATE_BUFFER |
WLR_OUTPUT_STATE_ENABLED |
WLR_OUTPUT_STATE_MODE |
WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED;
static size_t last_output_num = 0;
static const char *surface_tag = "wlr_wl_output";
static struct wlr_wl_output *get_wl_output_from_output(
struct wlr_output *wlr_output) {
assert(wlr_output_is_wl(wlr_output));
struct wlr_wl_output *output = wl_container_of(wlr_output, output, wlr_output);
return output;
}
struct wlr_wl_output *get_wl_output_from_surface(struct wlr_wl_backend *wl,
struct wl_surface *surface) {
if (wl_proxy_get_tag((struct wl_proxy *)surface) != &surface_tag) {
return NULL;
}
struct wlr_wl_output *output = wl_surface_get_user_data(surface);
assert(output != NULL);
if (output->backend != wl) {
return NULL;
}
return output;
}
static void surface_frame_callback(void *data, struct wl_callback *cb,
uint32_t time) {
struct wlr_wl_output *output = data;
if (cb == NULL) {
return;
}
assert(output->frame_callback == cb);
wl_callback_destroy(cb);
output->frame_callback = NULL;
wlr_output_send_frame(&output->wlr_output);
}
static const struct wl_callback_listener frame_listener = {
.done = surface_frame_callback
};
static void presentation_feedback_destroy(
struct wlr_wl_presentation_feedback *feedback) {
wl_list_remove(&feedback->link);
wp_presentation_feedback_destroy(feedback->feedback);
free(feedback);
}
static void presentation_feedback_handle_sync_output(void *data,
struct wp_presentation_feedback *feedback, struct wl_output *output) {
// This space is intentionally left blank
}
static void presentation_feedback_handle_presented(void *data,
struct wp_presentation_feedback *wp_feedback, uint32_t tv_sec_hi,
uint32_t tv_sec_lo, uint32_t tv_nsec, uint32_t refresh_ns,
uint32_t seq_hi, uint32_t seq_lo, uint32_t flags) {
struct wlr_wl_presentation_feedback *feedback = data;
struct timespec t = {
.tv_sec = ((uint64_t)tv_sec_hi << 32) | tv_sec_lo,
.tv_nsec = tv_nsec,
};
struct wlr_output_event_present event = {
.commit_seq = feedback->commit_seq,
.presented = true,
.when = &t,
.seq = ((uint64_t)seq_hi << 32) | seq_lo,
.refresh = refresh_ns,
.flags = flags,
};
wlr_output_send_present(&feedback->output->wlr_output, &event);
presentation_feedback_destroy(feedback);
}
static void presentation_feedback_handle_discarded(void *data,
struct wp_presentation_feedback *wp_feedback) {
struct wlr_wl_presentation_feedback *feedback = data;
struct wlr_output_event_present event = {
.commit_seq = feedback->commit_seq,
.presented = false,
};
wlr_output_send_present(&feedback->output->wlr_output, &event);
presentation_feedback_destroy(feedback);
}
static const struct wp_presentation_feedback_listener
presentation_feedback_listener = {
.sync_output = presentation_feedback_handle_sync_output,
.presented = presentation_feedback_handle_presented,
.discarded = presentation_feedback_handle_discarded,
};
void destroy_wl_buffer(struct wlr_wl_buffer *buffer) {
if (buffer == NULL) {
return;
}
wl_list_remove(&buffer->buffer_destroy.link);
wl_list_remove(&buffer->link);
wl_buffer_destroy(buffer->wl_buffer);
if (!buffer->released) {
wlr_buffer_unlock(buffer->buffer);
}
free(buffer);
}
static void buffer_handle_release(void *data, struct wl_buffer *wl_buffer) {
struct wlr_wl_buffer *buffer = data;
buffer->released = true;
wlr_buffer_unlock(buffer->buffer); // might free buffer
}
static const struct wl_buffer_listener buffer_listener = {
.release = buffer_handle_release,
};
static void buffer_handle_buffer_destroy(struct wl_listener *listener,
void *data) {
struct wlr_wl_buffer *buffer =
wl_container_of(listener, buffer, buffer_destroy);
destroy_wl_buffer(buffer);
}
static bool test_buffer(struct wlr_wl_backend *wl,
struct wlr_buffer *wlr_buffer) {
struct wlr_dmabuf_attributes dmabuf;
struct wlr_shm_attributes shm;
if (wlr_buffer_get_dmabuf(wlr_buffer, &dmabuf)) {
return wlr_drm_format_set_has(&wl->linux_dmabuf_v1_formats,
dmabuf.format, dmabuf.modifier);
} else if (wlr_buffer_get_shm(wlr_buffer, &shm)) {
return wlr_drm_format_set_has(&wl->shm_formats, shm.format,
DRM_FORMAT_MOD_INVALID);
} else {
return false;
}
}
static struct wl_buffer *import_dmabuf(struct wlr_wl_backend *wl,
struct wlr_dmabuf_attributes *dmabuf) {
uint32_t modifier_hi = dmabuf->modifier >> 32;
uint32_t modifier_lo = (uint32_t)dmabuf->modifier;
struct zwp_linux_buffer_params_v1 *params =
zwp_linux_dmabuf_v1_create_params(wl->zwp_linux_dmabuf_v1);
for (int i = 0; i < dmabuf->n_planes; i++) {
zwp_linux_buffer_params_v1_add(params, dmabuf->fd[i], i,
dmabuf->offset[i], dmabuf->stride[i], modifier_hi, modifier_lo);
}
struct wl_buffer *wl_buffer = zwp_linux_buffer_params_v1_create_immed(
params, dmabuf->width, dmabuf->height, dmabuf->format, 0);
zwp_linux_buffer_params_v1_destroy(params);
// TODO: handle create() errors
return wl_buffer;
}
static struct wl_buffer *import_shm(struct wlr_wl_backend *wl,
struct wlr_shm_attributes *shm) {
enum wl_shm_format wl_shm_format = convert_drm_format_to_wl_shm(shm->format);
uint32_t size = shm->stride * shm->height;
struct wl_shm_pool *pool = wl_shm_create_pool(wl->shm, shm->fd, size);
if (pool == NULL) {
return NULL;
}
struct wl_buffer *wl_buffer = wl_shm_pool_create_buffer(pool, shm->offset,
shm->width, shm->height, shm->stride, wl_shm_format);
wl_shm_pool_destroy(pool);
return wl_buffer;
}
static struct wlr_wl_buffer *create_wl_buffer(struct wlr_wl_backend *wl,
struct wlr_buffer *wlr_buffer) {
if (!test_buffer(wl, wlr_buffer)) {
return NULL;
}
struct wlr_dmabuf_attributes dmabuf;
struct wlr_shm_attributes shm;
struct wl_buffer *wl_buffer;
if (wlr_buffer_get_dmabuf(wlr_buffer, &dmabuf)) {
wl_buffer = import_dmabuf(wl, &dmabuf);
} else if (wlr_buffer_get_shm(wlr_buffer, &shm)) {
wl_buffer = import_shm(wl, &shm);
} else {
return NULL;
}
if (wl_buffer == NULL) {
return NULL;
}
struct wlr_wl_buffer *buffer = calloc(1, sizeof(*buffer));
if (buffer == NULL) {
wl_buffer_destroy(wl_buffer);
return NULL;
}
buffer->wl_buffer = wl_buffer;
buffer->buffer = wlr_buffer_lock(wlr_buffer);
wl_list_insert(&wl->buffers, &buffer->link);
wl_buffer_add_listener(wl_buffer, &buffer_listener, buffer);
buffer->buffer_destroy.notify = buffer_handle_buffer_destroy;
wl_signal_add(&wlr_buffer->events.destroy, &buffer->buffer_destroy);
return buffer;
}
static struct wlr_wl_buffer *get_or_create_wl_buffer(struct wlr_wl_backend *wl,
struct wlr_buffer *wlr_buffer) {
struct wlr_wl_buffer *buffer;
wl_list_for_each(buffer, &wl->buffers, link) {
// We can only re-use a wlr_wl_buffer if the parent compositor has
// released it, because wl_buffer.release is per-wl_buffer, not per
// wl_surface.commit.
if (buffer->buffer == wlr_buffer && buffer->released) {
buffer->released = false;
wlr_buffer_lock(buffer->buffer);
return buffer;
}
}
return create_wl_buffer(wl, wlr_buffer);
}
static bool output_test(struct wlr_output *wlr_output,
const struct wlr_output_state *state) {
struct wlr_wl_output *output =
get_wl_output_from_output(wlr_output);
uint32_t unsupported = state->committed & ~SUPPORTED_OUTPUT_STATE;
if (unsupported != 0) {
wlr_log(WLR_DEBUG, "Unsupported output state fields: 0x%"PRIx32,
unsupported);
return false;
}
// Adaptive sync is effectively always enabled when using the Wayland
// backend. This is not something we have control over, so we set the state
// to enabled on creating the output and never allow changing it.
assert(wlr_output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED);
if (state->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) {
if (!state->adaptive_sync_enabled) {
wlr_log(WLR_DEBUG, "Disabling adaptive sync is not supported");
return false;
}
}
if (state->committed & WLR_OUTPUT_STATE_MODE) {
assert(state->mode_type == WLR_OUTPUT_STATE_MODE_CUSTOM);
if (state->custom_mode.refresh != 0) {
wlr_log(WLR_DEBUG, "Refresh rates are not supported");
return false;
}
}
if ((state->committed & WLR_OUTPUT_STATE_BUFFER) &&
!test_buffer(output->backend, state->buffer)) {
wlr_log(WLR_DEBUG, "Unsupported buffer format");
return false;
}
if (state->committed & WLR_OUTPUT_STATE_LAYERS) {
// If we can't use a sub-surface for a layer, then we can't use a
// sub-surface for any layer underneath
bool supported = output->backend->subcompositor != NULL;
for (ssize_t i = state->layers_len - 1; i >= 0; i--) {
struct wlr_output_layer_state *layer_state = &state->layers[i];
if (layer_state->buffer != NULL) {
int x = layer_state->dst_box.x;
int y = layer_state->dst_box.y;
int width = layer_state->dst_box.width;
int height = layer_state->dst_box.height;
bool needs_viewport = width != layer_state->buffer->width ||
height != layer_state->buffer->height;
if (!wlr_fbox_empty(&layer_state->src_box)) {
needs_viewport = needs_viewport ||
layer_state->src_box.x != 0 ||
layer_state->src_box.y != 0 ||
layer_state->src_box.width != width ||
layer_state->src_box.height != height;
}
if (x < 0 || y < 0 ||
x + width > wlr_output->width ||
y + height > wlr_output->height ||
(output->backend->viewporter == NULL && needs_viewport)) {
supported = false;
}
supported = supported &&
test_buffer(output->backend, layer_state->buffer);
}
layer_state->accepted = supported;
}
}
return true;
}
static void output_layer_handle_addon_destroy(struct wlr_addon *addon) {
struct wlr_wl_output_layer *layer = wl_container_of(addon, layer, addon);
wlr_addon_finish(&layer->addon);
if (layer->viewport != NULL) {
wp_viewport_destroy(layer->viewport);
}
wl_subsurface_destroy(layer->subsurface);
wl_surface_destroy(layer->surface);
free(layer);
}
static const struct wlr_addon_interface output_layer_addon_impl = {
.name = "wlr_wl_output_layer",
.destroy = output_layer_handle_addon_destroy,
};
static struct wlr_wl_output_layer *get_or_create_output_layer(
struct wlr_wl_output *output, struct wlr_output_layer *wlr_layer) {
assert(output->backend->subcompositor != NULL);
struct wlr_wl_output_layer *layer;
struct wlr_addon *addon = wlr_addon_find(&wlr_layer->addons, output,
&output_layer_addon_impl);
if (addon != NULL) {
layer = wl_container_of(addon, layer, addon);
return layer;
}
layer = calloc(1, sizeof(*layer));
if (layer == NULL) {
return NULL;
}
wlr_addon_init(&layer->addon, &wlr_layer->addons, output,
&output_layer_addon_impl);
layer->surface = wl_compositor_create_surface(output->backend->compositor);
layer->subsurface = wl_subcompositor_get_subsurface(
output->backend->subcompositor, layer->surface, output->surface);
// Set an empty input region so that input events are handled by the main
// surface
struct wl_region *region = wl_compositor_create_region(output->backend->compositor);
wl_surface_set_input_region(layer->surface, region);
wl_region_destroy(region);
if (output->backend->viewporter != NULL) {
layer->viewport = wp_viewporter_get_viewport(output->backend->viewporter, layer->surface);
}
return layer;
}
static bool has_layers_order_changed(struct wlr_wl_output *output,
struct wlr_output_layer_state *layers, size_t layers_len) {
// output_basic_check() ensures that layers_len equals the number of
// registered output layers
size_t i = 0;
struct wlr_output_layer *layer;
wl_list_for_each(layer, &output->wlr_output.layers, link) {
assert(i < layers_len);
const struct wlr_output_layer_state *layer_state = &layers[i];
if (layer_state->layer != layer) {
return true;
}
i++;
}
assert(i == layers_len);
return false;
}
static void output_layer_unmap(struct wlr_wl_output_layer *layer) {
if (!layer->mapped) {
return;
}
wl_surface_attach(layer->surface, NULL, 0, 0);
wl_surface_commit(layer->surface);
layer->mapped = false;
}
static void damage_surface(struct wl_surface *surface,
const pixman_region32_t *damage) {
if (damage == NULL) {
wl_surface_damage_buffer(surface,
0, 0, INT32_MAX, INT32_MAX);
return;
}
int rects_len;
const pixman_box32_t *rects = pixman_region32_rectangles(damage, &rects_len);
for (int i = 0; i < rects_len; i++) {
const pixman_box32_t *r = &rects[i];
wl_surface_damage_buffer(surface, r->x1, r->y1,
r->x2 - r->x1, r->y2 - r->y1);
}
}
static bool output_layer_commit(struct wlr_wl_output *output,
struct wlr_wl_output_layer *layer,
const struct wlr_output_layer_state *state) {
if (state->layer->dst_box.x != state->dst_box.x ||
state->layer->dst_box.y != state->dst_box.y) {
wl_subsurface_set_position(layer->subsurface, state->dst_box.x, state->dst_box.y);
}
if (state->buffer == NULL) {
output_layer_unmap(layer);
return true;
}
struct wlr_wl_buffer *buffer =
get_or_create_wl_buffer(output->backend, state->buffer);
if (buffer == NULL) {
return false;
}
if (layer->viewport != NULL &&
(state->layer->dst_box.width != state->dst_box.width ||
state->layer->dst_box.height != state->dst_box.height)) {
wp_viewport_set_destination(layer->viewport, state->dst_box.width, state->dst_box.height);
}
if (layer->viewport != NULL && !wlr_fbox_equal(&state->layer->src_box, &state->src_box)) {
struct wlr_fbox src_box = state->src_box;
if (wlr_fbox_empty(&src_box)) {
// -1 resets the box
src_box = (struct wlr_fbox){
.x = -1,
.y = -1,
.width = -1,
.height = -1,
};
}
wp_viewport_set_source(layer->viewport,
wl_fixed_from_double(src_box.x),
wl_fixed_from_double(src_box.y),
wl_fixed_from_double(src_box.width),
wl_fixed_from_double(src_box.height));
}
wl_surface_attach(layer->surface, buffer->wl_buffer, 0, 0);
damage_surface(layer->surface, state->damage);
wl_surface_commit(layer->surface);
layer->mapped = true;
return true;
}
static bool commit_layers(struct wlr_wl_output *output,
struct wlr_output_layer_state *layers, size_t layers_len) {
if (output->backend->subcompositor == NULL) {
return true;
}
bool reordered = has_layers_order_changed(output, layers, layers_len);
struct wlr_wl_output_layer *prev_layer = NULL;
for (size_t i = 0; i < layers_len; i++) {
struct wlr_wl_output_layer *layer =
get_or_create_output_layer(output, layers[i].layer);
if (layer == NULL) {
return false;
}
if (!layers[i].accepted) {
output_layer_unmap(layer);
continue;
}
if (prev_layer != NULL && reordered) {
wl_subsurface_place_above(layer->subsurface,
prev_layer->surface);
}
if (!output_layer_commit(output, layer, &layers[i])) {
return false;
}
prev_layer = layer;
}
return true;
}
static bool output_commit(struct wlr_output *wlr_output,
const struct wlr_output_state *state) {
struct wlr_wl_output *output =
get_wl_output_from_output(wlr_output);
if (!output_test(wlr_output, state)) {
return false;
}
if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && !state->enabled) {
wl_surface_attach(output->surface, NULL, 0, 0);
wl_surface_commit(output->surface);
}
if (state->committed & WLR_OUTPUT_STATE_BUFFER) {
const pixman_region32_t *damage = NULL;
if (state->committed & WLR_OUTPUT_STATE_DAMAGE) {
damage = &state->damage;
}
struct wlr_buffer *wlr_buffer = state->buffer;
struct wlr_wl_buffer *buffer =
get_or_create_wl_buffer(output->backend, wlr_buffer);
if (buffer == NULL) {
return false;
}
wl_surface_attach(output->surface, buffer->wl_buffer, 0, 0);
damage_surface(output->surface, damage);
}
if ((state->committed & WLR_OUTPUT_STATE_LAYERS) &&
!commit_layers(output, state->layers, state->layers_len)) {
return false;
}
if (output_pending_enabled(wlr_output, state)) {
if (output->frame_callback != NULL) {
wl_callback_destroy(output->frame_callback);
}
output->frame_callback = wl_surface_frame(output->surface);
wl_callback_add_listener(output->frame_callback, &frame_listener, output);
struct wp_presentation_feedback *wp_feedback = NULL;
if (output->backend->presentation != NULL) {
wp_feedback = wp_presentation_feedback(output->backend->presentation,
output->surface);
}
wl_surface_commit(output->surface);
if (wp_feedback != NULL) {
struct wlr_wl_presentation_feedback *feedback =
calloc(1, sizeof(*feedback));
if (feedback == NULL) {
wp_presentation_feedback_destroy(wp_feedback);
return false;
}
feedback->output = output;
feedback->feedback = wp_feedback;
feedback->commit_seq = output->wlr_output.commit_seq + 1;
wl_list_insert(&output->presentation_feedbacks, &feedback->link);
wp_presentation_feedback_add_listener(wp_feedback,
&presentation_feedback_listener, feedback);
} else {
struct wlr_output_event_present present_event = {
.commit_seq = wlr_output->commit_seq + 1,
.presented = true,
};
output_defer_present(wlr_output, present_event);
}
}
wl_display_flush(output->backend->remote_display);
return true;
}
static bool output_set_cursor(struct wlr_output *wlr_output,
struct wlr_buffer *wlr_buffer, int hotspot_x, int hotspot_y) {
struct wlr_wl_output *output = get_wl_output_from_output(wlr_output);
struct wlr_wl_backend *backend = output->backend;
output->cursor.hotspot_x = hotspot_x;
output->cursor.hotspot_y = hotspot_y;
if (output->cursor.surface == NULL) {
output->cursor.surface =
wl_compositor_create_surface(backend->compositor);
}
struct wl_surface *surface = output->cursor.surface;
if (wlr_buffer != NULL) {
struct wlr_wl_buffer *buffer =
get_or_create_wl_buffer(output->backend, wlr_buffer);
if (buffer == NULL) {
return false;
}
wl_surface_attach(surface, buffer->wl_buffer, 0, 0);
wl_surface_damage_buffer(surface, 0, 0, INT32_MAX, INT32_MAX);
wl_surface_commit(surface);
} else {
wl_surface_attach(surface, NULL, 0, 0);
wl_surface_commit(surface);
}
update_wl_output_cursor(output);
wl_display_flush(backend->remote_display);
return true;
}
static const struct wlr_drm_format_set *output_get_formats(
struct wlr_output *wlr_output, uint32_t buffer_caps) {
struct wlr_wl_output *output = get_wl_output_from_output(wlr_output);
if (buffer_caps & WLR_BUFFER_CAP_DMABUF) {
return &output->backend->linux_dmabuf_v1_formats;
} else if (buffer_caps & WLR_BUFFER_CAP_SHM) {
return &output->backend->shm_formats;
}
return NULL;
}
static void output_destroy(struct wlr_output *wlr_output) {
struct wlr_wl_output *output = get_wl_output_from_output(wlr_output);
if (output == NULL) {
return;
}
wl_list_remove(&output->link);
if (output->cursor.surface) {
wl_surface_destroy(output->cursor.surface);
}
if (output->frame_callback) {
wl_callback_destroy(output->frame_callback);
}
struct wlr_wl_presentation_feedback *feedback, *feedback_tmp;
wl_list_for_each_safe(feedback, feedback_tmp,
&output->presentation_feedbacks, link) {
presentation_feedback_destroy(feedback);
}
if (output->zxdg_toplevel_decoration_v1) {
zxdg_toplevel_decoration_v1_destroy(output->zxdg_toplevel_decoration_v1);
}
if (output->xdg_toplevel) {
xdg_toplevel_destroy(output->xdg_toplevel);
}
if (output->xdg_surface) {
xdg_surface_destroy(output->xdg_surface);
}
if (output->own_surface) {
wl_surface_destroy(output->surface);
}
wl_display_flush(output->backend->remote_display);
free(output);
}
void update_wl_output_cursor(struct wlr_wl_output *output) {
struct wlr_wl_pointer *pointer = output->cursor.pointer;
if (pointer) {
assert(pointer->output == output);
assert(output->enter_serial);
struct wlr_wl_seat *seat = pointer->seat;
wl_pointer_set_cursor(seat->wl_pointer, output->enter_serial,
output->cursor.surface, output->cursor.hotspot_x,
output->cursor.hotspot_y);
}
}
static bool output_move_cursor(struct wlr_output *_output, int x, int y) {
// TODO: only return true if x == current x and y == current y
return true;
}
static const struct wlr_output_impl output_impl = {
.destroy = output_destroy,
.test = output_test,
.commit = output_commit,
.set_cursor = output_set_cursor,
.move_cursor = output_move_cursor,
.get_cursor_formats = output_get_formats,
.get_primary_formats = output_get_formats,
};
bool wlr_output_is_wl(struct wlr_output *wlr_output) {
return wlr_output->impl == &output_impl;
}
static void xdg_surface_handle_configure(void *data,
struct xdg_surface *xdg_surface, uint32_t serial) {
struct wlr_wl_output *output = data;
assert(output && output->xdg_surface == xdg_surface);
output->configured = true;
xdg_surface_ack_configure(xdg_surface, serial);
// nothing else?
}
static const struct xdg_surface_listener xdg_surface_listener = {
.configure = xdg_surface_handle_configure,
};
static void xdg_toplevel_handle_configure(void *data,
struct xdg_toplevel *xdg_toplevel,
int32_t width, int32_t height, struct wl_array *states) {
struct wlr_wl_output *output = data;
assert(output && output->xdg_toplevel == xdg_toplevel);
if (width == 0 || height == 0) {
return;
}
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_custom_mode(&state, width, height, 0);
wlr_output_send_request_state(&output->wlr_output, &state);
wlr_output_state_finish(&state);
}
static void xdg_toplevel_handle_close(void *data,
struct xdg_toplevel *xdg_toplevel) {
struct wlr_wl_output *output = data;
assert(output && output->xdg_toplevel == xdg_toplevel);
wlr_output_destroy(&output->wlr_output);
}
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
.configure = xdg_toplevel_handle_configure,
.close = xdg_toplevel_handle_close,
};
static struct wlr_wl_output *output_create(struct wlr_wl_backend *backend,
struct wl_surface *surface) {
struct wlr_wl_output *output = calloc(1, sizeof(*output));
if (output == NULL) {
wlr_log(WLR_ERROR, "Failed to allocate wlr_wl_output");
return NULL;
}
struct wlr_output *wlr_output = &output->wlr_output;
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_custom_mode(&state, 1280, 720, 0);
wlr_output_init(wlr_output, &backend->backend, &output_impl,
backend->event_loop, &state);
wlr_output_state_finish(&state);
wlr_output->adaptive_sync_status = WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED;
size_t output_num = ++last_output_num;
char name[64];
snprintf(name, sizeof(name), "WL-%zu", output_num);
wlr_output_set_name(wlr_output, name);
char description[128];
snprintf(description, sizeof(description), "Wayland output %zu", output_num);
wlr_output_set_description(wlr_output, description);
output->surface = surface;
output->backend = backend;
wl_list_init(&output->presentation_feedbacks);
wl_proxy_set_tag((struct wl_proxy *)output->surface, &surface_tag);
wl_surface_set_user_data(output->surface, output);
wl_list_insert(&backend->outputs, &output->link);
return output;
}
static void output_start(struct wlr_wl_output *output) {
struct wlr_output *wlr_output = &output->wlr_output;
struct wlr_wl_backend *backend = output->backend;
wl_signal_emit_mutable(&backend->backend.events.new_output, wlr_output);
struct wlr_wl_seat *seat;
wl_list_for_each(seat, &backend->seats, link) {
if (seat->wl_pointer) {
create_pointer(seat, output);
}
}
}
struct wlr_output *wlr_wl_output_create(struct wlr_backend *wlr_backend) {
struct wlr_wl_backend *backend = get_wl_backend_from_backend(wlr_backend);
if (!backend->started) {
++backend->requested_outputs;
return NULL;
}
struct wl_surface *surface = wl_compositor_create_surface(backend->compositor);
if (surface == NULL) {
wlr_log(WLR_ERROR, "Could not create output surface");
return NULL;
}
struct wlr_wl_output *output = output_create(backend, surface);
if (output == NULL) {
wl_surface_destroy(surface);
return NULL;
}
output->own_surface = true;
output->xdg_surface =
xdg_wm_base_get_xdg_surface(backend->xdg_wm_base, output->surface);
if (!output->xdg_surface) {
wlr_log_errno(WLR_ERROR, "Could not get xdg surface");
goto error;
}
output->xdg_toplevel =
xdg_surface_get_toplevel(output->xdg_surface);
if (!output->xdg_toplevel) {
wlr_log_errno(WLR_ERROR, "Could not get xdg toplevel");
goto error;
}
if (backend->zxdg_decoration_manager_v1) {
output->zxdg_toplevel_decoration_v1 =
zxdg_decoration_manager_v1_get_toplevel_decoration(
backend->zxdg_decoration_manager_v1, output->xdg_toplevel);
if (!output->zxdg_toplevel_decoration_v1) {
wlr_log_errno(WLR_ERROR, "Could not get xdg toplevel decoration");
goto error;
}
zxdg_toplevel_decoration_v1_set_mode(output->zxdg_toplevel_decoration_v1,
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
}
wlr_wl_output_set_title(&output->wlr_output, NULL);
xdg_toplevel_set_app_id(output->xdg_toplevel, "wlroots");
xdg_surface_add_listener(output->xdg_surface,
&xdg_surface_listener, output);
xdg_toplevel_add_listener(output->xdg_toplevel,
&xdg_toplevel_listener, output);
wl_surface_commit(output->surface);
while (!output->configured) {
int ret = wl_event_loop_dispatch(backend->event_loop, -1);
if (ret < 0) {
wlr_log(WLR_ERROR, "wl_event_loop_dispatch() failed");
goto error;
}
}
output_start(output);
// TODO: let the compositor do this bit
if (backend->activation_v1 && backend->activation_token) {
xdg_activation_v1_activate(backend->activation_v1,
backend->activation_token, output->surface);
}
return &output->wlr_output;
error:
wlr_output_destroy(&output->wlr_output);
return NULL;
}
struct wlr_output *wlr_wl_output_create_from_surface(struct wlr_backend *wlr_backend,
struct wl_surface *surface) {
struct wlr_wl_backend *backend = get_wl_backend_from_backend(wlr_backend);
assert(backend->started);
struct wlr_wl_output *output = output_create(backend, surface);
if (output == NULL) {
wl_surface_destroy(surface);
return NULL;
}
output_start(output);
return &output->wlr_output;
}
void wlr_wl_output_set_title(struct wlr_output *output, const char *title) {
struct wlr_wl_output *wl_output = get_wl_output_from_output(output);
assert(wl_output->xdg_toplevel != NULL);
char wl_title[32];
if (title == NULL) {
if (snprintf(wl_title, sizeof(wl_title), "wlroots - %s", output->name) <= 0) {
return;
}
title = wl_title;
}
xdg_toplevel_set_title(wl_output->xdg_toplevel, title);
wl_display_flush(wl_output->backend->remote_display);
}
struct wl_surface *wlr_wl_output_get_surface(struct wlr_output *output) {
struct wlr_wl_output *wl_output = get_wl_output_from_output(output);
return wl_output->surface;
}

557
backend/wayland/pointer.c Normal file
View file

@ -0,0 +1,557 @@
#include <assert.h>
#include <stdlib.h>
#include <wayland-client.h>
#include <wlr/interfaces/wlr_pointer.h>
#include <wlr/util/log.h>
#include "backend/wayland.h"
#include "pointer-gestures-unstable-v1-client-protocol.h"
#include "relative-pointer-unstable-v1-client-protocol.h"
static struct wlr_wl_pointer *output_get_pointer(struct wlr_wl_output *output,
const struct wl_pointer *wl_pointer) {
struct wlr_wl_seat *seat;
wl_list_for_each(seat, &output->backend->seats, link) {
if (seat->wl_pointer != wl_pointer) {
continue;
}
struct wlr_wl_pointer *pointer;
wl_list_for_each(pointer, &seat->pointers, link) {
if (pointer->output == output) {
return pointer;
}
}
}
return NULL;
}
static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer,
uint32_t serial, struct wl_surface *surface, wl_fixed_t sx,
wl_fixed_t sy) {
struct wlr_wl_seat *seat = data;
if (surface == NULL) {
return;
}
struct wlr_wl_output *output = get_wl_output_from_surface(seat->backend, surface);
if (output == NULL) {
return;
}
struct wlr_wl_pointer *pointer = output_get_pointer(output, wl_pointer);
seat->active_pointer = pointer;
// Manage cursor icon/rendering on output
struct wlr_wl_pointer *current = output->cursor.pointer;
if (current && current != pointer) {
wlr_log(WLR_INFO, "Ignoring seat '%s' pointer in favor of seat '%s'",
seat->name, current->seat->name);
return;
}
output->enter_serial = serial;
output->cursor.pointer = pointer;
update_wl_output_cursor(output);
}
static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer,
uint32_t serial, struct wl_surface *surface) {
struct wlr_wl_seat *seat = data;
if (surface == NULL) {
return;
}
struct wlr_wl_output *output = get_wl_output_from_surface(seat->backend, surface);
if (output == NULL) {
return;
}
if (seat->active_pointer != NULL &&
seat->active_pointer->output == output) {
seat->active_pointer = NULL;
}
if (output->cursor.pointer == seat->active_pointer) {
output->enter_serial = 0;
output->cursor.pointer = NULL;
}
}
static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer,
uint32_t time, wl_fixed_t sx, wl_fixed_t sy) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_pointer *pointer = seat->active_pointer;
if (pointer == NULL) {
return;
}
struct wlr_output *wlr_output = &pointer->output->wlr_output;
struct wlr_pointer_motion_absolute_event event = {
.pointer = &pointer->wlr_pointer,
.time_msec = time,
.x = wl_fixed_to_double(sx) / wlr_output->width,
.y = wl_fixed_to_double(sy) / wlr_output->height,
};
wl_signal_emit_mutable(&pointer->wlr_pointer.events.motion_absolute, &event);
}
static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer,
uint32_t serial, uint32_t time, uint32_t button, uint32_t state) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_pointer *pointer = seat->active_pointer;
if (pointer == NULL) {
return;
}
struct wlr_pointer_button_event event = {
.pointer = &pointer->wlr_pointer,
.button = button,
.state = state,
.time_msec = time,
};
wl_signal_emit_mutable(&pointer->wlr_pointer.events.button, &event);
}
static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer,
uint32_t time, uint32_t axis, wl_fixed_t value) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_pointer *pointer = seat->active_pointer;
if (pointer == NULL) {
return;
}
struct wlr_pointer_axis_event event = {
.pointer = &pointer->wlr_pointer,
.delta = wl_fixed_to_double(value),
.delta_discrete = pointer->axis_discrete,
.orientation = axis,
.time_msec = time,
.source = pointer->axis_source,
.relative_direction = pointer->axis_relative_direction,
};
wl_signal_emit_mutable(&pointer->wlr_pointer.events.axis, &event);
pointer->axis_discrete = 0;
}
static void pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_pointer *pointer = seat->active_pointer;
if (pointer == NULL) {
return;
}
wl_signal_emit_mutable(&pointer->wlr_pointer.events.frame,
&pointer->wlr_pointer);
}
static void pointer_handle_axis_source(void *data,
struct wl_pointer *wl_pointer, uint32_t axis_source) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_pointer *pointer = seat->active_pointer;
if (pointer == NULL) {
return;
}
pointer->axis_source = axis_source;
}
static void pointer_handle_axis_stop(void *data, struct wl_pointer *wl_pointer,
uint32_t time, uint32_t axis) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_pointer *pointer = seat->active_pointer;
if (pointer == NULL) {
return;
}
struct wlr_pointer_axis_event event = {
.pointer = &pointer->wlr_pointer,
.delta = 0,
.delta_discrete = 0,
.orientation = axis,
.time_msec = time,
.source = pointer->axis_source,
};
wl_signal_emit_mutable(&pointer->wlr_pointer.events.axis, &event);
}
static void pointer_handle_axis_discrete(void *data,
struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_pointer *pointer = seat->active_pointer;
if (pointer == NULL) {
return;
}
pointer->axis_discrete = discrete * WLR_POINTER_AXIS_DISCRETE_STEP;
}
static void pointer_handle_axis_value120(void *data,
struct wl_pointer *wl_pointer, uint32_t axis, int32_t value120) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_pointer *pointer = seat->active_pointer;
if (pointer == NULL) {
return;
}
pointer->axis_discrete = value120;
}
static void pointer_handle_axis_relative_direction(void *data,
struct wl_pointer *wl_pointer, uint32_t axis, uint32_t direction) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_pointer *pointer = seat->active_pointer;
if (pointer == NULL) {
return;
}
pointer->axis_relative_direction = direction;
}
static const struct wl_pointer_listener pointer_listener = {
.enter = pointer_handle_enter,
.leave = pointer_handle_leave,
.motion = pointer_handle_motion,
.button = pointer_handle_button,
.axis = pointer_handle_axis,
.frame = pointer_handle_frame,
.axis_source = pointer_handle_axis_source,
.axis_stop = pointer_handle_axis_stop,
.axis_discrete = pointer_handle_axis_discrete,
.axis_value120 = pointer_handle_axis_value120,
.axis_relative_direction = pointer_handle_axis_relative_direction,
};
static void gesture_swipe_begin(void *data,
struct zwp_pointer_gesture_swipe_v1 *zwp_pointer_gesture_swipe_v1,
uint32_t serial, uint32_t time, struct wl_surface *surface,
uint32_t fingers) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_pointer *pointer = seat->active_pointer;
if (pointer == NULL) {
return;
}
pointer->fingers = fingers;
struct wlr_pointer_swipe_begin_event wlr_event = {
.pointer = &pointer->wlr_pointer,
.time_msec = time,
.fingers = fingers,
};
wl_signal_emit_mutable(&pointer->wlr_pointer.events.swipe_begin, &wlr_event);
}
static void gesture_swipe_update(void *data,
struct zwp_pointer_gesture_swipe_v1 *zwp_pointer_gesture_swipe_v1,
uint32_t time, wl_fixed_t dx, wl_fixed_t dy) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_pointer *pointer = seat->active_pointer;
if (pointer == NULL) {
return;
}
struct wlr_pointer_swipe_update_event wlr_event = {
.pointer = &pointer->wlr_pointer,
.time_msec = time,
.fingers = pointer->fingers,
.dx = wl_fixed_to_double(dx),
.dy = wl_fixed_to_double(dy),
};
wl_signal_emit_mutable(&pointer->wlr_pointer.events.swipe_update, &wlr_event);
}
static void gesture_swipe_end(void *data,
struct zwp_pointer_gesture_swipe_v1 *zwp_pointer_gesture_swipe_v1,
uint32_t serial, uint32_t time, int32_t cancelled) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_pointer *pointer = seat->active_pointer;
if (pointer == NULL) {
return;
}
struct wlr_pointer_swipe_end_event wlr_event = {
.pointer = &pointer->wlr_pointer,
.time_msec = time,
.cancelled = cancelled,
};
wl_signal_emit_mutable(&pointer->wlr_pointer.events.swipe_end, &wlr_event);
}
static const struct zwp_pointer_gesture_swipe_v1_listener gesture_swipe_impl = {
.begin = gesture_swipe_begin,
.update = gesture_swipe_update,
.end = gesture_swipe_end,
};
static void gesture_pinch_begin(void *data,
struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1,
uint32_t serial, uint32_t time, struct wl_surface *surface,
uint32_t fingers) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_pointer *pointer = seat->active_pointer;
if (pointer == NULL) {
return;
}
pointer->fingers = fingers;
struct wlr_pointer_pinch_begin_event wlr_event = {
.pointer = &pointer->wlr_pointer,
.time_msec = time,
.fingers = pointer->fingers,
};
wl_signal_emit_mutable(&pointer->wlr_pointer.events.pinch_begin, &wlr_event);
}
static void gesture_pinch_update(void *data,
struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1,
uint32_t time, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t scale,
wl_fixed_t rotation) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_pointer *pointer = seat->active_pointer;
if (pointer == NULL) {
return;
}
struct wlr_pointer_pinch_update_event wlr_event = {
.pointer = &pointer->wlr_pointer,
.time_msec = time,
.fingers = pointer->fingers,
.dx = wl_fixed_to_double(dx),
.dy = wl_fixed_to_double(dy),
.scale = wl_fixed_to_double(scale),
.rotation = wl_fixed_to_double(rotation),
};
wl_signal_emit_mutable(&pointer->wlr_pointer.events.pinch_update, &wlr_event);
}
static void gesture_pinch_end(void *data,
struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1,
uint32_t serial, uint32_t time, int32_t cancelled) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_pointer *pointer = seat->active_pointer;
if (pointer == NULL) {
return;
}
struct wlr_pointer_pinch_end_event wlr_event = {
.pointer = &pointer->wlr_pointer,
.time_msec = time,
.cancelled = cancelled,
};
wl_signal_emit_mutable(&pointer->wlr_pointer.events.pinch_end, &wlr_event);
}
static const struct zwp_pointer_gesture_pinch_v1_listener gesture_pinch_impl = {
.begin = gesture_pinch_begin,
.update = gesture_pinch_update,
.end = gesture_pinch_end,
};
static void gesture_hold_begin(void *data,
struct zwp_pointer_gesture_hold_v1 *zwp_pointer_gesture_hold_v1,
uint32_t serial, uint32_t time, struct wl_surface *surface,
uint32_t fingers) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_pointer *pointer = seat->active_pointer;
if (pointer == NULL) {
return;
}
pointer->fingers = fingers;
struct wlr_pointer_hold_begin_event wlr_event = {
.pointer = &pointer->wlr_pointer,
.time_msec = time,
.fingers = fingers,
};
wl_signal_emit_mutable(&pointer->wlr_pointer.events.hold_begin, &wlr_event);
}
static void gesture_hold_end(void *data,
struct zwp_pointer_gesture_hold_v1 *zwp_pointer_gesture_hold_v1,
uint32_t serial, uint32_t time, int32_t cancelled) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_pointer *pointer = seat->active_pointer;
if (pointer == NULL) {
return;
}
struct wlr_pointer_hold_end_event wlr_event = {
.pointer = &pointer->wlr_pointer,
.time_msec = time,
.cancelled = cancelled,
};
wl_signal_emit_mutable(&pointer->wlr_pointer.events.hold_end, &wlr_event);
}
static const struct zwp_pointer_gesture_hold_v1_listener gesture_hold_impl = {
.begin = gesture_hold_begin,
.end = gesture_hold_end,
};
static void relative_pointer_handle_relative_motion(void *data,
struct zwp_relative_pointer_v1 *relative_pointer, uint32_t utime_hi,
uint32_t utime_lo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dx_unaccel,
wl_fixed_t dy_unaccel) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_pointer *pointer = seat->active_pointer;
if (pointer == NULL) {
return;
}
uint64_t time_usec = (uint64_t)utime_hi << 32 | utime_lo;
struct wlr_pointer_motion_event wlr_event = {
.pointer = &pointer->wlr_pointer,
.time_msec = (uint32_t)(time_usec / 1000),
.delta_x = wl_fixed_to_double(dx),
.delta_y = wl_fixed_to_double(dy),
.unaccel_dx = wl_fixed_to_double(dx_unaccel),
.unaccel_dy = wl_fixed_to_double(dy_unaccel),
};
wl_signal_emit_mutable(&pointer->wlr_pointer.events.motion, &wlr_event);
}
static const struct zwp_relative_pointer_v1_listener relative_pointer_listener = {
.relative_motion = relative_pointer_handle_relative_motion,
};
const struct wlr_pointer_impl wl_pointer_impl = {
.name = "wl-pointer",
};
static void destroy_pointer(struct wlr_wl_pointer *pointer) {
if (pointer->output->cursor.pointer == pointer) {
pointer->output->cursor.pointer = NULL;
}
if (pointer->seat->active_pointer == pointer) {
pointer->seat->active_pointer = NULL;
}
wlr_pointer_finish(&pointer->wlr_pointer);
wl_list_remove(&pointer->output_destroy.link);
wl_list_remove(&pointer->link);
free(pointer);
}
static void pointer_output_destroy(struct wl_listener *listener, void *data) {
struct wlr_wl_pointer *pointer =
wl_container_of(listener, pointer, output_destroy);
destroy_pointer(pointer);
}
void create_pointer(struct wlr_wl_seat *seat, struct wlr_wl_output *output) {
assert(seat->wl_pointer);
if (output_get_pointer(output, seat->wl_pointer)) {
wlr_log(WLR_DEBUG,
"pointer for output '%s' from seat '%s' already exists",
output->wlr_output.name, seat->name);
return;
}
wlr_log(WLR_DEBUG, "creating pointer for output '%s' from seat '%s'",
output->wlr_output.name, seat->name);
struct wlr_wl_pointer *pointer = calloc(1, sizeof(*pointer));
if (pointer == NULL) {
wlr_log(WLR_ERROR, "failed to allocate wlr_wl_pointer");
return;
}
char name[64] = {0};
snprintf(name, sizeof(name), "wayland-pointer-%s", seat->name);
wlr_pointer_init(&pointer->wlr_pointer, &wl_pointer_impl, name);
pointer->wlr_pointer.output_name = strdup(output->wlr_output.name);
pointer->seat = seat;
pointer->output = output;
wl_signal_add(&output->wlr_output.events.destroy, &pointer->output_destroy);
pointer->output_destroy.notify = pointer_output_destroy;
wl_signal_emit_mutable(&seat->backend->backend.events.new_input,
&pointer->wlr_pointer.base);
wl_list_insert(&seat->pointers, &pointer->link);
}
void init_seat_pointer(struct wlr_wl_seat *seat) {
assert(seat->wl_pointer);
struct wlr_wl_backend *backend = seat->backend;
wl_list_init(&seat->pointers);
struct wlr_wl_output *output;
wl_list_for_each(output, &backend->outputs, link) {
create_pointer(seat, output);
}
if (backend->zwp_pointer_gestures_v1) {
uint32_t version = zwp_pointer_gestures_v1_get_version(
backend->zwp_pointer_gestures_v1);
seat->gesture_swipe = zwp_pointer_gestures_v1_get_swipe_gesture(
backend->zwp_pointer_gestures_v1, seat->wl_pointer);
zwp_pointer_gesture_swipe_v1_add_listener(seat->gesture_swipe,
&gesture_swipe_impl, seat);
seat->gesture_pinch = zwp_pointer_gestures_v1_get_pinch_gesture(
backend->zwp_pointer_gestures_v1, seat->wl_pointer);
zwp_pointer_gesture_pinch_v1_add_listener(seat->gesture_pinch,
&gesture_pinch_impl, seat);
if (version >= ZWP_POINTER_GESTURES_V1_GET_HOLD_GESTURE) {
seat->gesture_hold = zwp_pointer_gestures_v1_get_hold_gesture(
backend->zwp_pointer_gestures_v1, seat->wl_pointer);
zwp_pointer_gesture_hold_v1_add_listener(seat->gesture_hold,
&gesture_hold_impl, seat);
}
}
if (backend->zwp_relative_pointer_manager_v1) {
seat->relative_pointer =
zwp_relative_pointer_manager_v1_get_relative_pointer(
backend->zwp_relative_pointer_manager_v1, seat->wl_pointer);
zwp_relative_pointer_v1_add_listener(seat->relative_pointer,
&relative_pointer_listener, seat);
}
wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, seat);
}
void finish_seat_pointer(struct wlr_wl_seat *seat) {
assert(seat->wl_pointer);
wl_pointer_release(seat->wl_pointer);
struct wlr_wl_pointer *pointer, *tmp;
wl_list_for_each_safe(pointer, tmp, &seat->pointers, link) {
destroy_pointer(pointer);
}
if (seat->gesture_swipe != NULL) {
zwp_pointer_gesture_swipe_v1_destroy(seat->gesture_swipe);
}
if (seat->gesture_pinch != NULL) {
zwp_pointer_gesture_pinch_v1_destroy(seat->gesture_pinch);
}
if (seat->gesture_hold != NULL) {
zwp_pointer_gesture_hold_v1_destroy(seat->gesture_hold);
}
if (seat->relative_pointer != NULL) {
zwp_relative_pointer_v1_destroy(seat->relative_pointer);
}
seat->wl_pointer = NULL;
seat->active_pointer = NULL;
}

387
backend/wayland/seat.c Normal file
View file

@ -0,0 +1,387 @@
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wlr/interfaces/wlr_keyboard.h>
#include <wlr/interfaces/wlr_output.h>
#include <wlr/interfaces/wlr_touch.h>
#include <wlr/interfaces/wlr_tablet_tool.h>
#include <wlr/interfaces/wlr_tablet_pad.h>
#include <wlr/types/wlr_input_device.h>
#include <wlr/util/log.h>
#include "backend/wayland.h"
#include "util/time.h"
static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard,
uint32_t format, int32_t fd, uint32_t size) {
close(fd);
}
static void keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard,
uint32_t serial, struct wl_surface *surface, struct wl_array *keys) {
struct wlr_keyboard *keyboard = data;
uint32_t *keycode_ptr;
wl_array_for_each(keycode_ptr, keys) {
struct wlr_keyboard_key_event event = {
.keycode = *keycode_ptr,
.state = WL_KEYBOARD_KEY_STATE_PRESSED,
.time_msec = get_current_time_msec(),
.update_state = false,
};
wlr_keyboard_notify_key(keyboard, &event);
}
}
static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard,
uint32_t serial, struct wl_surface *surface) {
struct wlr_keyboard *keyboard = data;
size_t num_keycodes = keyboard->num_keycodes;
uint32_t pressed[num_keycodes + 1];
memcpy(pressed, keyboard->keycodes,
num_keycodes * sizeof(uint32_t));
for (size_t i = 0; i < num_keycodes; ++i) {
uint32_t keycode = pressed[i];
struct wlr_keyboard_key_event event = {
.keycode = keycode,
.state = WL_KEYBOARD_KEY_STATE_RELEASED,
.time_msec = get_current_time_msec(),
.update_state = false,
};
wlr_keyboard_notify_key(keyboard, &event);
}
}
static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard,
uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
struct wlr_keyboard *keyboard = data;
struct wlr_keyboard_key_event wlr_event = {
.keycode = key,
.state = state,
.time_msec = time,
.update_state = false,
};
wlr_keyboard_notify_key(keyboard, &wlr_event);
}
static void keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboard,
uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched,
uint32_t mods_locked, uint32_t group) {
struct wlr_keyboard *keyboard = data;
wlr_keyboard_notify_modifiers(keyboard, mods_depressed, mods_latched,
mods_locked, group);
}
static void keyboard_handle_repeat_info(void *data,
struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) {
// This space is intentionally left blank
}
static const struct wl_keyboard_listener keyboard_listener = {
.keymap = keyboard_handle_keymap,
.enter = keyboard_handle_enter,
.leave = keyboard_handle_leave,
.key = keyboard_handle_key,
.modifiers = keyboard_handle_modifiers,
.repeat_info = keyboard_handle_repeat_info
};
static const struct wlr_keyboard_impl keyboard_impl = {
.name = "wl-keyboard",
};
void init_seat_keyboard(struct wlr_wl_seat *seat) {
assert(seat->wl_keyboard);
char name[128] = {0};
snprintf(name, sizeof(name), "wayland-keyboard-%s", seat->name);
wlr_keyboard_init(&seat->wlr_keyboard, &keyboard_impl, name);
wl_keyboard_add_listener(seat->wl_keyboard, &keyboard_listener,
&seat->wlr_keyboard);
wl_signal_emit_mutable(&seat->backend->backend.events.new_input,
&seat->wlr_keyboard.base);
}
static void touch_coordinates_to_absolute(struct wlr_wl_seat *seat,
wl_fixed_t x, wl_fixed_t y, double *sx, double *sy) {
/**
* TODO: multi-output touch support
* Although the wayland backend supports multi-output pointers, the support
* for multi-output touch has been left on the side for simplicity reasons.
* If this is a feature you want/need, please open an issue on the wlroots
* tracker here https://gitlab.freedesktop.org/wlroots/wlroots/-/issues
*/
struct wlr_wl_output *output, *tmp;
wl_list_for_each_safe(output, tmp, &seat->backend->outputs, link) {
*sx = wl_fixed_to_double(x) / output->wlr_output.width;
*sy = wl_fixed_to_double(y) / output->wlr_output.height;
return; // Choose the first output in the list
}
*sx = *sy = 0;
}
static void touch_handle_down(void *data, struct wl_touch *wl_touch,
uint32_t serial, uint32_t time, struct wl_surface *surface,
int32_t id, wl_fixed_t x, wl_fixed_t y) {
struct wlr_wl_seat *seat = data;
struct wlr_touch *touch = &seat->wlr_touch;
struct wlr_wl_touch_points *points = &seat->touch_points;
assert(points->len != sizeof(points->ids) / sizeof(points->ids[0]));
points->ids[points->len++] = id;
struct wlr_touch_down_event event = {
.touch = touch,
.time_msec = time,
.touch_id = id,
};
touch_coordinates_to_absolute(seat, x, y, &event.x, &event.y);
wl_signal_emit_mutable(&touch->events.down, &event);
}
static bool remove_touch_point(struct wlr_wl_touch_points *points, int32_t id) {
size_t i = 0;
for (; i < points->len; i++) {
if (points->ids[i] == id) {
size_t remaining = points->len - i - 1;
memmove(&points->ids[i], &points->ids[i + 1], remaining * sizeof(id));
points->len--;
return true;
}
}
return false;
}
static void touch_handle_up(void *data, struct wl_touch *wl_touch,
uint32_t serial, uint32_t time, int32_t id) {
struct wlr_wl_seat *seat = data;
struct wlr_touch *touch = &seat->wlr_touch;
remove_touch_point(&seat->touch_points, id);
struct wlr_touch_up_event event = {
.touch = touch,
.time_msec = time,
.touch_id = id,
};
wl_signal_emit_mutable(&touch->events.up, &event);
}
static void touch_handle_motion(void *data, struct wl_touch *wl_touch,
uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y) {
struct wlr_wl_seat *seat = data;
struct wlr_touch *touch = &seat->wlr_touch;
struct wlr_touch_motion_event event = {
.touch = touch,
.time_msec = time,
.touch_id = id,
};
touch_coordinates_to_absolute(seat, x, y, &event.x, &event.y);
wl_signal_emit_mutable(&touch->events.motion, &event);
}
static void touch_handle_frame(void *data, struct wl_touch *wl_touch) {
struct wlr_wl_seat *seat = data;
wl_signal_emit_mutable(&seat->wlr_touch.events.frame, NULL);
}
static void touch_handle_cancel(void *data, struct wl_touch *wl_touch) {
struct wlr_wl_seat *seat = data;
struct wlr_touch *touch = &seat->wlr_touch;
// wayland's cancel event applies to all active touch points
for (size_t i = 0; i < seat->touch_points.len; i++) {
struct wlr_touch_cancel_event event = {
.touch = touch,
.time_msec = 0,
.touch_id = seat->touch_points.ids[i],
};
wl_signal_emit_mutable(&touch->events.cancel, &event);
}
seat->touch_points.len = 0;
}
static void touch_handle_shape(void *data, struct wl_touch *wl_touch,
int32_t id, wl_fixed_t major, wl_fixed_t minor) {
// no-op
}
static void touch_handle_orientation(void *data, struct wl_touch *wl_touch,
int32_t id, wl_fixed_t orientation) {
// no-op
}
static const struct wl_touch_listener touch_listener = {
.down = touch_handle_down,
.up = touch_handle_up,
.motion = touch_handle_motion,
.frame = touch_handle_frame,
.cancel = touch_handle_cancel,
.shape = touch_handle_shape,
.orientation = touch_handle_orientation,
};
static const struct wlr_touch_impl touch_impl = {
.name = "wl-touch",
};
void init_seat_touch(struct wlr_wl_seat *seat) {
assert(seat->wl_touch);
char name[128] = {0};
snprintf(name, sizeof(name), "wayland-touch-%s", seat->name);
wlr_touch_init(&seat->wlr_touch, &touch_impl, name);
struct wlr_wl_output *output;
wl_list_for_each(output, &seat->backend->outputs, link) {
/* Multi-output touch not supproted */
seat->wlr_touch.output_name = strdup(output->wlr_output.name);
break;
}
wl_touch_add_listener(seat->wl_touch, &touch_listener, seat);
wl_signal_emit_mutable(&seat->backend->backend.events.new_input,
&seat->wlr_touch.base);
}
static const struct wl_seat_listener seat_listener;
bool create_wl_seat(struct wl_seat *wl_seat, struct wlr_wl_backend *wl,
uint32_t global_name) {
struct wlr_wl_seat *seat = calloc(1, sizeof(*seat));
if (!seat) {
wlr_log_errno(WLR_ERROR, "Allocation failed");
return false;
}
seat->wl_seat = wl_seat;
seat->backend = wl;
seat->global_name = global_name;
wl_list_insert(&wl->seats, &seat->link);
wl_seat_add_listener(wl_seat, &seat_listener, seat);
return true;
}
void destroy_wl_seat(struct wlr_wl_seat *seat) {
if (seat->wl_touch) {
wl_touch_release(seat->wl_touch);
wlr_touch_finish(&seat->wlr_touch);
}
if (seat->wl_pointer) {
finish_seat_pointer(seat);
}
if (seat->wl_keyboard) {
wl_keyboard_release(seat->wl_keyboard);
if (seat->backend->started) {
wlr_keyboard_finish(&seat->wlr_keyboard);
}
}
if (seat->zwp_tablet_seat_v2) {
finish_seat_tablet(seat);
}
free(seat->name);
assert(seat->wl_seat);
wl_seat_destroy(seat->wl_seat);
wl_list_remove(&seat->link);
free(seat);
}
bool wlr_input_device_is_wl(struct wlr_input_device *dev) {
switch (dev->type) {
case WLR_INPUT_DEVICE_KEYBOARD:
return wlr_keyboard_from_input_device(dev)->impl == &keyboard_impl;
case WLR_INPUT_DEVICE_POINTER:
return wlr_pointer_from_input_device(dev)->impl == &wl_pointer_impl;
case WLR_INPUT_DEVICE_TOUCH:
return wlr_touch_from_input_device(dev)->impl == &touch_impl;
case WLR_INPUT_DEVICE_TABLET:
return wlr_tablet_from_input_device(dev)-> impl == &wl_tablet_impl;
case WLR_INPUT_DEVICE_TABLET_PAD:
return wlr_tablet_pad_from_input_device(dev)->impl == &wl_tablet_pad_impl;
default:
return false;
}
}
static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
enum wl_seat_capability caps) {
struct wlr_wl_seat *seat = data;
struct wlr_wl_backend *backend = seat->backend;
if ((caps & WL_SEAT_CAPABILITY_POINTER) && seat->wl_pointer == NULL) {
wlr_log(WLR_DEBUG, "seat '%s' offering pointer", seat->name);
seat->wl_pointer = wl_seat_get_pointer(wl_seat);
init_seat_pointer(seat);
}
if (!(caps & WL_SEAT_CAPABILITY_POINTER) && seat->wl_pointer != NULL) {
wlr_log(WLR_DEBUG, "seat '%s' dropping pointer", seat->name);
finish_seat_pointer(seat);
}
if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && seat->wl_keyboard == NULL) {
wlr_log(WLR_DEBUG, "seat '%s' offering keyboard", seat->name);
struct wl_keyboard *wl_keyboard = wl_seat_get_keyboard(wl_seat);
seat->wl_keyboard = wl_keyboard;
if (backend->started) {
init_seat_keyboard(seat);
}
}
if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && seat->wl_keyboard != NULL) {
wlr_log(WLR_DEBUG, "seat '%s' dropping keyboard", seat->name);
wl_keyboard_release(seat->wl_keyboard);
wlr_keyboard_finish(&seat->wlr_keyboard);
seat->wl_keyboard = NULL;
}
if ((caps & WL_SEAT_CAPABILITY_TOUCH) && seat->wl_touch == NULL) {
wlr_log(WLR_DEBUG, "seat '%s' offering touch", seat->name);
seat->wl_touch = wl_seat_get_touch(wl_seat);
if (backend->started) {
init_seat_touch(seat);
}
}
if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && seat->wl_touch != NULL) {
wlr_log(WLR_DEBUG, "seat '%s' dropping touch", seat->name);
wl_touch_release(seat->wl_touch);
wlr_touch_finish(&seat->wlr_touch);
seat->wl_touch = NULL;
}
}
static void seat_handle_name(void *data, struct wl_seat *wl_seat,
const char *name) {
struct wlr_wl_seat *seat = data;
free(seat->name);
seat->name = strdup(name);
}
static const struct wl_seat_listener seat_listener = {
.capabilities = seat_handle_capabilities,
.name = seat_handle_name,
};

929
backend/wayland/tablet_v2.c Normal file
View file

@ -0,0 +1,929 @@
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <wlr/interfaces/wlr_tablet_pad.h>
#include <wlr/interfaces/wlr_tablet_tool.h>
#include <wlr/util/log.h>
#include "backend/wayland.h"
#include "util/time.h"
#include "tablet-unstable-v2-client-protocol.h"
struct tablet_tool {
/* static */
struct wlr_wl_seat *seat;
/* semi-static */
struct wlr_wl_output *output;
double pre_x, pre_y;
/* per frame */
double x, y;
double pressure;
double distance;
double tilt_x, tilt_y;
double rotation;
double slider;
double wheel_delta;
bool is_in;
bool is_out;
bool is_up;
bool is_down;
};
struct tablet_pad_ring {
struct wl_list link; // tablet_pad_group.rings
/* static */
struct zwp_tablet_pad_ring_v2 *ring;
struct tablet_pad_group *group;
size_t index;
/* per frame */
enum wlr_tablet_pad_ring_source source;
double angle;
bool stopped;
};
struct tablet_pad_strip {
struct wl_list link; // tablet_pad_group.strips
struct zwp_tablet_pad_strip_v2 *strip;
struct tablet_pad_group *group;
size_t index;
enum wlr_tablet_pad_strip_source source;
double position;
bool stopped;
};
struct tablet_pad_group {
struct zwp_tablet_pad_group_v2 *pad_group;
struct wlr_tablet_pad *pad;
unsigned int mode;
struct wlr_tablet_pad_group group;
struct wl_list rings; // tablet_pad_ring.link
struct wl_list strips; // tablet_pad_strips.link
};
static void handle_tablet_pad_ring_source(void *data,
struct zwp_tablet_pad_ring_v2 *zwp_tablet_pad_ring_v2,
uint32_t source) {
struct tablet_pad_ring *ring = data;
ring->source = source;
}
static void handle_tablet_pad_ring_angle(void *data,
struct zwp_tablet_pad_ring_v2 *zwp_tablet_pad_ring_v2,
wl_fixed_t degrees) {
struct tablet_pad_ring *ring = data;
ring->angle = wl_fixed_to_double(degrees);
}
static void handle_tablet_pad_ring_stop(void *data,
struct zwp_tablet_pad_ring_v2 *zwp_tablet_pad_ring_v2) {
struct tablet_pad_ring *ring = data;
ring->stopped = true;
}
static void handle_tablet_pad_ring_frame(void *data,
struct zwp_tablet_pad_ring_v2 *zwp_tablet_pad_ring_v2,
uint32_t time) {
struct tablet_pad_ring *ring = data;
struct wlr_tablet_pad_ring_event evt = {
.time_msec = time,
.source = ring->source,
.ring = ring->index,
.position = ring->angle,
.mode = ring->group->mode,
};
if (ring->angle >= 0) {
wl_signal_emit_mutable(&ring->group->pad->events.ring, &evt);
}
if (ring->stopped) {
evt.position = -1;
wl_signal_emit_mutable(&ring->group->pad->events.ring, &evt);
}
ring->angle = -1;
ring->stopped = false;
ring->source = 0;
}
static const struct zwp_tablet_pad_ring_v2_listener tablet_pad_ring_listener = {
.source = handle_tablet_pad_ring_source,
.angle = handle_tablet_pad_ring_angle,
.stop = handle_tablet_pad_ring_stop,
.frame = handle_tablet_pad_ring_frame,
};
static void handle_tablet_pad_strip_source(void *data,
struct zwp_tablet_pad_strip_v2 *zwp_tablet_pad_strip_v2,
uint32_t source) {
struct tablet_pad_strip *strip = data;
strip->source = source;
}
static void handle_tablet_pad_strip_position(void *data,
struct zwp_tablet_pad_strip_v2 *zwp_tablet_pad_strip_v2,
uint32_t position) {
struct tablet_pad_strip *strip = data;
strip->position = (double) position / 65536.0;
}
static void handle_tablet_pad_strip_stop(void *data,
struct zwp_tablet_pad_strip_v2 *zwp_tablet_pad_strip_v2) {
struct tablet_pad_strip *strip = data;
strip->stopped = true;
}
static void handle_tablet_pad_strip_frame(void *data,
struct zwp_tablet_pad_strip_v2 *zwp_tablet_pad_strip_v2,
uint32_t time) {
struct tablet_pad_strip *strip = data;
struct wlr_tablet_pad_strip_event evt = {
.time_msec = time,
.source = strip->source,
.strip = strip->index,
.position = strip->position,
.mode = strip->group->mode,
};
if (strip->position >= 0) {
wl_signal_emit_mutable(&strip->group->pad->events.strip, &evt);
}
if (strip->stopped) {
evt.position = -1;
wl_signal_emit_mutable(&strip->group->pad->events.strip, &evt);
}
strip->position = -1;
strip->stopped = false;
strip->source = 0;
}
static const struct zwp_tablet_pad_strip_v2_listener tablet_pad_strip_listener = {
.source = handle_tablet_pad_strip_source,
.position = handle_tablet_pad_strip_position,
.stop = handle_tablet_pad_strip_stop,
.frame = handle_tablet_pad_strip_frame,
};
static void handle_tablet_pad_group_buttons(void *data,
struct zwp_tablet_pad_group_v2 *pad_group,
struct wl_array *buttons) {
struct tablet_pad_group *group = data;
free(group->group.buttons);
group->group.buttons = calloc(1, buttons->size);
if (!group->group.buttons) {
// FIXME: Add actual error handling
return;
}
group->group.button_count = buttons->size / sizeof(int);
memcpy(group->group.buttons, buttons->data, buttons->size);
}
static void handle_tablet_pad_group_modes(void *data,
struct zwp_tablet_pad_group_v2 *pad_group, uint32_t modes) {
struct tablet_pad_group *group = data;
group->group.mode_count = modes;
}
static void handle_tablet_pad_group_ring(void *data,
struct zwp_tablet_pad_group_v2 *pad_group,
struct zwp_tablet_pad_ring_v2 *ring) {
struct tablet_pad_group *group = data;
struct tablet_pad_ring *tablet_ring = calloc(1, sizeof(*tablet_ring));
if (!tablet_ring) {
zwp_tablet_pad_ring_v2_destroy(ring);
return;
}
tablet_ring->index = group->pad->ring_count++;
tablet_ring->group = group;
zwp_tablet_pad_ring_v2_add_listener(ring, &tablet_pad_ring_listener,
tablet_ring);
group->group.rings = realloc(group->group.rings,
++group->group.ring_count * sizeof(unsigned int));
group->group.rings[group->group.ring_count - 1] =
tablet_ring->index;
}
static void handle_tablet_pad_group_strip(void *data,
struct zwp_tablet_pad_group_v2 *pad_group,
struct zwp_tablet_pad_strip_v2 *strip) {
struct tablet_pad_group *group = data;
struct tablet_pad_strip *tablet_strip = calloc(1, sizeof(*tablet_strip));
if (!tablet_strip) {
zwp_tablet_pad_strip_v2_destroy(strip);
return;
}
tablet_strip->index = group->pad->strip_count++;
tablet_strip->group = group;
zwp_tablet_pad_strip_v2_add_listener(strip, &tablet_pad_strip_listener,
tablet_strip);
group->group.strips = realloc(group->group.strips,
++group->group.strip_count * sizeof(unsigned int));
group->group.strips[group->group.strip_count - 1] =
tablet_strip->index;
}
static void handle_tablet_pad_group_done(void *data,
struct zwp_tablet_pad_group_v2 *pad_group) {
/* Empty for now */
}
static void handle_tablet_pad_group_mode_switch(void *data,
struct zwp_tablet_pad_group_v2 *pad_group,
uint32_t time, uint32_t serial, uint32_t mode) {
struct tablet_pad_group *group = data;
group->mode = mode;
}
static void destroy_tablet_pad_group(struct tablet_pad_group *group) {
/* No need to remove the link on strips rings as long as we do *not*
* wl_list_remove on the wl_groups ring/strip attributes here */
struct tablet_pad_ring *ring, *tmp_ring;
wl_list_for_each_safe(ring, tmp_ring, &group->rings, link) {
zwp_tablet_pad_ring_v2_destroy(ring->ring);
free(ring);
}
struct tablet_pad_strip *strip, *tmp_strip;
wl_list_for_each_safe(strip, tmp_strip, &group->strips, link) {
zwp_tablet_pad_strip_v2_destroy(strip->strip);
free(strip);
}
zwp_tablet_pad_group_v2_destroy(group->pad_group);
free(group->group.buttons);
free(group->group.strips);
free(group->group.rings);
wl_list_remove(&group->group.link);
free(group);
}
static const struct zwp_tablet_pad_group_v2_listener tablet_pad_group_listener = {
.buttons = handle_tablet_pad_group_buttons,
.modes = handle_tablet_pad_group_modes,
.ring = handle_tablet_pad_group_ring,
.strip = handle_tablet_pad_group_strip,
.done = handle_tablet_pad_group_done,
.mode_switch = handle_tablet_pad_group_mode_switch,
};
static void handle_tablet_pad_group(void *data,
struct zwp_tablet_pad_v2 *zwp_tablet_pad,
struct zwp_tablet_pad_group_v2 *pad_group) {
struct wlr_wl_seat *seat = data;
struct wlr_tablet_pad *pad = &seat->wlr_tablet_pad;
struct tablet_pad_group *group = calloc(1, sizeof(*group));
if (!group) {
wlr_log_errno(WLR_ERROR, "failed to allocate tablet_pad_group");
zwp_tablet_pad_group_v2_destroy(pad_group);
return;
}
group->pad_group = pad_group;
group->pad = pad;
wl_list_init(&group->rings);
wl_list_init(&group->strips);
zwp_tablet_pad_group_v2_add_listener(pad_group,
&tablet_pad_group_listener, group);
wl_list_insert(&pad->groups, &group->group.link);
}
static void handle_tablet_pad_path(void *data,
struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2, const char *path) {
struct wlr_wl_seat *seat = data;
struct wlr_tablet_pad *tablet_pad = &seat->wlr_tablet_pad;
char **dst = wl_array_add(&tablet_pad->paths, sizeof(char *));
*dst = strdup(path);
}
static void handle_tablet_pad_buttons(void *data,
struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2, uint32_t buttons) {
struct wlr_wl_seat *seat = data;
struct wlr_tablet_pad *tablet_pad = &seat->wlr_tablet_pad;
tablet_pad->button_count = buttons;
}
static void handle_tablet_pad_button(void *data,
struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2,
uint32_t time, uint32_t button, uint32_t state) {
struct wlr_wl_seat *seat = data;
struct wlr_tablet_pad_button_event evt = {
.time_msec = time,
.button = button,
.state = state,
.mode = 0,
.group = 0,
};
wl_signal_emit_mutable(&seat->wlr_tablet_pad.events.button, &evt);
}
static void handle_tablet_pad_done(void *data,
struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2) {
struct wlr_wl_seat *seat = data;
wl_signal_emit_mutable(&seat->backend->backend.events.new_input,
&seat->wlr_tablet_pad.base);
}
static void handle_tablet_pad_enter(void *data,
struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2,
uint32_t serial, struct zwp_tablet_v2 *tablet_p,
struct wl_surface *surface) {
struct wlr_wl_seat *seat = data;
assert(seat->zwp_tablet_v2 == tablet_p);
wl_signal_emit_mutable(&seat->wlr_tablet_pad.events.attach_tablet,
&seat->wlr_tablet_tool);
}
static void handle_tablet_pad_leave(void *data,
struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2,
uint32_t serial, struct wl_surface *surface) {
/* Empty. Probably staying that way, unless we want to create/destroy
* tablet on enter/leave events (ehh) */
}
static void handle_tablet_pad_removed(void *data,
struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2) {
struct wlr_wl_seat *seat = data;
struct wlr_tablet_pad *tablet_pad = &seat->wlr_tablet_pad;
struct tablet_pad_group *group, *it;
wl_list_for_each_safe(group, it, &tablet_pad->groups, group.link) {
destroy_tablet_pad_group(group);
}
wlr_tablet_pad_finish(tablet_pad);
zwp_tablet_pad_v2_destroy(seat->zwp_tablet_pad_v2);
seat->zwp_tablet_pad_v2 = NULL;
}
static const struct zwp_tablet_pad_v2_listener tablet_pad_listener = {
.group = handle_tablet_pad_group,
.path = handle_tablet_pad_path,
.buttons = handle_tablet_pad_buttons,
.button = handle_tablet_pad_button,
.done = handle_tablet_pad_done,
.enter = handle_tablet_pad_enter,
.leave = handle_tablet_pad_leave,
.removed = handle_tablet_pad_removed,
};
const struct wlr_tablet_pad_impl wl_tablet_pad_impl = {
.name = "wl-tablet-pad",
};
static void handle_pad_added(void *data,
struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2,
struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2) {
struct wlr_wl_seat *seat = data;
if (seat->zwp_tablet_pad_v2 != NULL) {
wlr_log(WLR_ERROR, "zwp_tablet_pad_v2 is already present");
return;
}
seat->zwp_tablet_pad_v2 = zwp_tablet_pad_v2;
zwp_tablet_pad_v2_add_listener(zwp_tablet_pad_v2, &tablet_pad_listener,
seat);
wlr_tablet_pad_init(&seat->wlr_tablet_pad, &wl_tablet_pad_impl,
"wlr_tablet_v2");
}
static void handle_tablet_tool_done(void *data,
struct zwp_tablet_tool_v2 *id) {
/* empty */
}
static enum wlr_tablet_tool_type tablet_type_to_wlr_type(
enum zwp_tablet_tool_v2_type type) {
switch (type) {
case ZWP_TABLET_TOOL_V2_TYPE_PEN:
return WLR_TABLET_TOOL_TYPE_PEN;
case ZWP_TABLET_TOOL_V2_TYPE_ERASER:
return WLR_TABLET_TOOL_TYPE_ERASER;
case ZWP_TABLET_TOOL_V2_TYPE_BRUSH:
return WLR_TABLET_TOOL_TYPE_BRUSH;
case ZWP_TABLET_TOOL_V2_TYPE_PENCIL:
return WLR_TABLET_TOOL_TYPE_PENCIL;
case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH:
return WLR_TABLET_TOOL_TYPE_AIRBRUSH;
case ZWP_TABLET_TOOL_V2_TYPE_MOUSE:
return WLR_TABLET_TOOL_TYPE_MOUSE;
case ZWP_TABLET_TOOL_V2_TYPE_LENS:
return WLR_TABLET_TOOL_TYPE_LENS;
case ZWP_TABLET_TOOL_V2_TYPE_FINGER:
// unused, see:
// https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/18
abort();
}
abort(); // unreachable
}
static void handle_tablet_tool_type(void *data,
struct zwp_tablet_tool_v2 *id, uint32_t tool_type) {
struct tablet_tool *tool = data;
struct wlr_tablet_tool *wlr_tool = &tool->seat->wlr_tablet_tool;
wlr_tool->type = tablet_type_to_wlr_type(tool_type);
}
static void handle_tablet_tool_serial(void *data,
struct zwp_tablet_tool_v2 *id, uint32_t high, uint32_t low) {
struct tablet_tool *tool = data;
struct wlr_tablet_tool *wlr_tool = &tool->seat->wlr_tablet_tool;
wlr_tool->hardware_serial = ((uint64_t) high) << 32 | (uint64_t) low;
}
static void handle_tablet_tool_id_wacom(void *data,
struct zwp_tablet_tool_v2 *id, uint32_t high, uint32_t low) {
struct tablet_tool *tool = data;
struct wlr_tablet_tool *wlr_tool = &tool->seat->wlr_tablet_tool;
wlr_tool->hardware_wacom = ((uint64_t) high) << 32 | (uint64_t) low;
}
static void handle_tablet_tool_capability(void *data,
struct zwp_tablet_tool_v2 *id, uint32_t capability) {
struct tablet_tool *tool = data;
struct wlr_tablet_tool *wlr_tool = &tool->seat->wlr_tablet_tool;
/* One event is sent for each capability */
switch (capability) {
case ZWP_TABLET_TOOL_V2_CAPABILITY_TILT:
wlr_tool->tilt = true;
break;
case ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE:
wlr_tool->pressure = true;
break;
case ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE:
wlr_tool->distance = true;
break;
case ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION:
wlr_tool->rotation = true;
break;
case ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER:
wlr_tool->slider = true;
break;
case ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL:
wlr_tool->wheel = true;
break;
}
}
static void handle_tablet_tool_proximity_in(void *data,
struct zwp_tablet_tool_v2 *id, uint32_t serial,
struct zwp_tablet_v2 *tablet_id, struct wl_surface *surface) {
struct tablet_tool *tool = data;
assert(tablet_id == tool->seat->zwp_tablet_v2);
struct wlr_wl_output *output = get_wl_output_from_surface(tool->seat->backend, surface);
if (output == NULL) {
return;
}
tool->is_in = true;
tool->output = output;
}
static void handle_tablet_tool_proximity_out(void *data,
struct zwp_tablet_tool_v2 *id) {
struct tablet_tool *tool = data;
tool->is_out = true;
tool->output = NULL;
}
static void handle_tablet_tool_down(void *data, struct zwp_tablet_tool_v2 *id,
unsigned int serial) {
struct tablet_tool *tool = data;
tool->is_down = true;
}
static void handle_tablet_tool_up(void *data, struct zwp_tablet_tool_v2 *id) {
struct tablet_tool *tool = data;
tool->is_up = true;
}
static void handle_tablet_tool_motion(void *data, struct zwp_tablet_tool_v2 *id,
wl_fixed_t x, wl_fixed_t y) {
struct tablet_tool *tool = data;
struct wlr_wl_output *output = tool->output;
assert(output);
tool->x = wl_fixed_to_double(x) / output->wlr_output.width;
tool->y = wl_fixed_to_double(y) / output->wlr_output.height;
}
static void handle_tablet_tool_pressure(void *data,
struct zwp_tablet_tool_v2 *id, uint32_t pressure) {
struct tablet_tool *tool = data;
tool->pressure = (double) pressure / 65535.0;
}
static void handle_tablet_tool_distance(void *data,
struct zwp_tablet_tool_v2 *id, uint32_t distance) {
struct tablet_tool *tool = data;
tool->distance = (double) distance / 65535.0;
}
static void handle_tablet_tool_tilt(void *data, struct zwp_tablet_tool_v2 *id,
wl_fixed_t x, wl_fixed_t y) {
struct tablet_tool *tool = data;
tool->tilt_x = wl_fixed_to_double(x);
tool->tilt_y = wl_fixed_to_double(y);
}
static void handle_tablet_tool_rotation(void *data,
struct zwp_tablet_tool_v2 *id, wl_fixed_t rotation) {
struct tablet_tool *tool = data;
tool->rotation = wl_fixed_to_double(rotation);
}
static void handle_tablet_tool_slider(void *data, struct zwp_tablet_tool_v2 *id,
int slider) {
struct tablet_tool *tool = data;
tool->slider = (double) slider / 65535.0;;
}
// TODO: This looks wrong :/
static void handle_tablet_tool_wheel(void *data, struct zwp_tablet_tool_v2 *id,
wl_fixed_t degree, int clicks) {
struct tablet_tool *tool = data;
tool->wheel_delta = wl_fixed_to_double(degree);
}
static void handle_tablet_tool_button(void *data,
struct zwp_tablet_tool_v2 *id,
uint32_t serial, uint32_t button, uint32_t state) {
struct tablet_tool *tool = data;
struct wlr_wl_seat *seat = tool->seat;
struct wlr_tablet *tablet = &seat->wlr_tablet;
struct wlr_tablet_tool_button_event evt = {
.tablet = tablet,
.tool = &seat->wlr_tablet_tool,
.time_msec = get_current_time_msec(),
.button = button,
.state = state == ZWP_TABLET_TOOL_V2_BUTTON_STATE_RELEASED ?
WLR_BUTTON_RELEASED : WLR_BUTTON_PRESSED,
};
wl_signal_emit_mutable(&tablet->events.button, &evt);
}
static void clear_tablet_tool_values(struct tablet_tool *tool) {
tool->is_out = tool->is_in = false;
tool->is_up = tool->is_down = false;
tool->x = tool->y = NAN;
tool->pressure = NAN;
tool->distance = NAN;
tool->tilt_x = tool->tilt_y = NAN;
tool->rotation = NAN;
tool->slider = NAN;
tool->wheel_delta = NAN;
}
static void handle_tablet_tool_frame(void *data,
struct zwp_tablet_tool_v2 *id,
uint32_t time) {
struct tablet_tool *tool = data;
struct wlr_wl_seat *seat = tool->seat;
if (tool->is_out && tool->is_in) {
/* we got a tablet tool coming in and out of proximity before
* we could process it. Just ignore anything it did */
goto clear_values;
}
struct wlr_tablet *tablet = &seat->wlr_tablet;
if (tool->is_in) {
struct wlr_tablet_tool_proximity_event evt = {
.tablet = tablet,
.tool = &seat->wlr_tablet_tool,
.time_msec = time,
.x = tool->x,
.y = tool->y,
.state = WLR_TABLET_TOOL_PROXIMITY_IN,
};
wl_signal_emit_mutable(&tablet->events.proximity, &evt);
}
{
struct wlr_tablet_tool_axis_event evt = {
.tablet = tablet,
.tool = &seat->wlr_tablet_tool,
.time_msec = time,
.updated_axes = 0,
};
if (!isnan(tool->x) && !tool->is_in) {
evt.updated_axes |= WLR_TABLET_TOOL_AXIS_X;
evt.x = tool->x;
}
if (!isnan(tool->y) && !tool->is_in) {
evt.updated_axes |= WLR_TABLET_TOOL_AXIS_Y;
evt.y = tool->y;
}
if (!isnan(tool->pressure)) {
evt.updated_axes |= WLR_TABLET_TOOL_AXIS_PRESSURE;
evt.pressure = tool->pressure;
}
if (!isnan(tool->distance)) {
evt.updated_axes |= WLR_TABLET_TOOL_AXIS_DISTANCE;
evt.distance = tool->distance;
}
if (!isnan(tool->tilt_x)) {
evt.updated_axes |= WLR_TABLET_TOOL_AXIS_TILT_X;
evt.tilt_x = tool->tilt_x;
}
if (!isnan(tool->tilt_y)) {
evt.updated_axes |= WLR_TABLET_TOOL_AXIS_TILT_Y;
evt.tilt_y = tool->tilt_y;
}
if (!isnan(tool->rotation)) {
evt.updated_axes |= WLR_TABLET_TOOL_AXIS_ROTATION;
evt.rotation = tool->rotation;
}
if (!isnan(tool->slider)) {
evt.updated_axes |= WLR_TABLET_TOOL_AXIS_SLIDER;
evt.slider = tool->slider;
}
if (!isnan(tool->wheel_delta)) {
evt.updated_axes |= WLR_TABLET_TOOL_AXIS_WHEEL;
evt.wheel_delta = tool->wheel_delta;
}
if (evt.updated_axes) {
wl_signal_emit_mutable(&tablet->events.axis, &evt);
}
}
/* This will always send down then up if we got both.
* Maybe we should send them right away, in case we get up then both in
* series?
* Downside: Here we have the frame time, if we sent right away, we
* need to generate the time */
if (tool->is_down) {
struct wlr_tablet_tool_tip_event evt = {
.tablet = tablet,
.tool = &seat->wlr_tablet_tool,
.time_msec = time,
.x = tool->x,
.y = tool->y,
.state = WLR_TABLET_TOOL_TIP_DOWN,
};
wl_signal_emit_mutable(&tablet->events.tip, &evt);
}
if (tool->is_up) {
struct wlr_tablet_tool_tip_event evt = {
.tablet = tablet,
.tool = &seat->wlr_tablet_tool,
.time_msec = time,
.x = tool->x,
.y = tool->y,
.state = WLR_TABLET_TOOL_TIP_UP,
};
wl_signal_emit_mutable(&tablet->events.tip, &evt);
}
if (tool->is_out) {
struct wlr_tablet_tool_proximity_event evt = {
.tablet = tablet,
.tool = &seat->wlr_tablet_tool,
.time_msec = time,
.x = tool->x,
.y = tool->y,
.state = WLR_TABLET_TOOL_PROXIMITY_OUT,
};
wl_signal_emit_mutable(&tablet->events.proximity, &evt);
}
clear_values:
clear_tablet_tool_values(tool);
}
static void handle_tablet_tool_removed(void *data,
struct zwp_tablet_tool_v2 *id) {
struct tablet_tool *tool = data;
struct wlr_wl_seat *seat = tool->seat;
zwp_tablet_tool_v2_destroy(seat->zwp_tablet_tool_v2);
seat->zwp_tablet_tool_v2 = NULL;
free(tool);
}
static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = {
.removed = handle_tablet_tool_removed,
.done = handle_tablet_tool_done,
.type = handle_tablet_tool_type,
.hardware_serial = handle_tablet_tool_serial,
.hardware_id_wacom = handle_tablet_tool_id_wacom,
.capability = handle_tablet_tool_capability,
.proximity_in = handle_tablet_tool_proximity_in,
.proximity_out = handle_tablet_tool_proximity_out,
.down = handle_tablet_tool_down,
.up = handle_tablet_tool_up,
.motion = handle_tablet_tool_motion,
.pressure = handle_tablet_tool_pressure,
.distance = handle_tablet_tool_distance,
.tilt = handle_tablet_tool_tilt,
.rotation = handle_tablet_tool_rotation,
.slider = handle_tablet_tool_slider,
.wheel = handle_tablet_tool_wheel,
.button = handle_tablet_tool_button,
.frame = handle_tablet_tool_frame,
};
static void handle_tool_added(void *data,
struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2,
struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) {
struct wlr_wl_seat *seat = data;
if (seat->zwp_tablet_tool_v2 != NULL) {
wlr_log(WLR_ERROR, "zwp_tablet_tool_v2 already present");
return;
}
wl_signal_init(&seat->wlr_tablet_tool.events.destroy);
struct tablet_tool *tool = calloc(1, sizeof(*tool));
if (tool == NULL) {
wlr_log_errno(WLR_ERROR, "failed to allocate tablet_tool");
zwp_tablet_tool_v2_destroy(zwp_tablet_tool_v2);
return;
}
tool->seat = seat;
clear_tablet_tool_values(tool);
seat->zwp_tablet_tool_v2 = zwp_tablet_tool_v2;
zwp_tablet_tool_v2_add_listener(seat->zwp_tablet_tool_v2, &tablet_tool_listener,
tool);
}
static void handle_tablet_name(void *data, struct zwp_tablet_v2 *zwp_tablet_v2,
const char *name) {
struct wlr_wl_seat *seat = data;
struct wlr_tablet *tablet = &seat->wlr_tablet;
free(tablet->base.name);
tablet->base.name = strdup(name);
}
static void handle_tablet_id(void *data, struct zwp_tablet_v2 *zwp_tablet_v2,
uint32_t vid, uint32_t pid) {
struct wlr_wl_seat *seat = data;
struct wlr_tablet *tablet = &seat->wlr_tablet;
tablet->usb_vendor_id = vid;
tablet->usb_product_id = pid;
}
static void handle_tablet_path(void *data, struct zwp_tablet_v2 *zwp_tablet_v2,
const char *path) {
struct wlr_wl_seat *seat = data;
struct wlr_tablet *tablet = &seat->wlr_tablet;
char **dst = wl_array_add(&tablet->paths, sizeof(char *));
*dst = strdup(path);
}
static void handle_tablet_done(void *data, struct zwp_tablet_v2 *zwp_tablet_v2) {
struct wlr_wl_seat *seat = data;
wl_signal_emit_mutable(&seat->backend->backend.events.new_input,
&seat->wlr_tablet.base);
}
static void handle_tablet_removed(void *data,
struct zwp_tablet_v2 *zwp_tablet_v2) {
struct wlr_wl_seat *seat = data;
wlr_tablet_finish(&seat->wlr_tablet);
zwp_tablet_v2_destroy(seat->zwp_tablet_v2);
seat->zwp_tablet_v2 = NULL;
}
static const struct zwp_tablet_v2_listener tablet_listener = {
.name = handle_tablet_name,
.id = handle_tablet_id,
.path = handle_tablet_path,
.done = handle_tablet_done,
.removed = handle_tablet_removed,
};
const struct wlr_tablet_impl wl_tablet_impl = {
.name = "wl-tablet-tool",
};
static void handle_tab_added(void *data,
struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2,
struct zwp_tablet_v2 *zwp_tablet_v2) {
struct wlr_wl_seat *seat = data;
if (seat->zwp_tablet_v2 != NULL) {
wlr_log(WLR_ERROR, "zwp_tablet_v2 already present");
return;
}
seat->zwp_tablet_v2 = zwp_tablet_v2;
zwp_tablet_v2_add_listener(zwp_tablet_v2, &tablet_listener, seat);
wlr_tablet_init(&seat->wlr_tablet, &wl_tablet_impl, "wlr_tablet_v2");
}
static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = {
.tablet_added = handle_tab_added,
.tool_added = handle_tool_added,
.pad_added = handle_pad_added,
};
void init_seat_tablet(struct wlr_wl_seat *seat) {
struct zwp_tablet_manager_v2 *manager = seat->backend->tablet_manager;
assert(manager);
/**
* TODO: multi tablet support
* The wlr_wl_seat should support multiple tablet_v2 devices, but for
* the sake of simplicity, it supports only one device of each.
* If this is a feature you want/need, please open an issue on the wlroots
* tracker here https://gitlab.freedesktop.org/wlroots/wlroots/-/issues
*/
seat->zwp_tablet_seat_v2 =
zwp_tablet_manager_v2_get_tablet_seat(manager, seat->wl_seat);
if (seat->zwp_tablet_seat_v2 == NULL) {
wlr_log(WLR_ERROR, "failed to get zwp_tablet_manager_v2 from seat '%s'",
seat->name);
return;
}
zwp_tablet_seat_v2_add_listener(seat->zwp_tablet_seat_v2,
&tablet_seat_listener, seat);
}
void finish_seat_tablet(struct wlr_wl_seat *seat) {
if (seat->zwp_tablet_v2 != NULL) {
wlr_tablet_finish(&seat->wlr_tablet);
zwp_tablet_v2_destroy(seat->zwp_tablet_v2);
}
if (seat->zwp_tablet_tool_v2 != NULL) {
struct tablet_tool *tool =
zwp_tablet_tool_v2_get_user_data(seat->zwp_tablet_tool_v2);
free(tool);
zwp_tablet_tool_v2_destroy(seat->zwp_tablet_tool_v2);
}
if (seat->zwp_tablet_pad_v2 != NULL) {
struct wlr_tablet_pad *tablet_pad = &seat->wlr_tablet_pad;
struct tablet_pad_group *group, *it;
wl_list_for_each_safe(group, it, &tablet_pad->groups, group.link) {
destroy_tablet_pad_group(group);
}
wlr_tablet_pad_finish(tablet_pad);
zwp_tablet_pad_v2_destroy(seat->zwp_tablet_pad_v2);
}
zwp_tablet_seat_v2_destroy(seat->zwp_tablet_seat_v2);
seat->zwp_tablet_seat_v2 = NULL;
}

736
backend/x11/backend.c Normal file
View file

@ -0,0 +1,736 @@
#include <assert.h>
#include <fcntl.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <xf86drm.h>
#include <wlr/config.h>
#include <drm_fourcc.h>
#include <wayland-server-core.h>
#include <xcb/xcb.h>
#include <xcb/dri3.h>
#include <xcb/present.h>
#include <xcb/render.h>
#include <xcb/shm.h>
#include <xcb/xcb_renderutil.h>
#include <xcb/xfixes.h>
#include <xcb/xinput.h>
#include <wlr/backend/interface.h>
#include <wlr/backend/x11.h>
#include <wlr/interfaces/wlr_keyboard.h>
#include <wlr/interfaces/wlr_pointer.h>
#include <wlr/util/log.h>
#include "backend/x11.h"
#include "render/drm_format_set.h"
// See dri2_format_for_depth in mesa
const struct wlr_x11_format formats[] = {
{ .drm = DRM_FORMAT_XRGB8888, .depth = 24, .bpp = 32 },
{ .drm = DRM_FORMAT_ARGB8888, .depth = 32, .bpp = 32 },
};
static const struct wlr_x11_format *x11_format_from_depth(uint8_t depth) {
for (size_t i = 0; i < sizeof(formats) / sizeof(formats[0]); i++) {
if (formats[i].depth == depth) {
return &formats[i];
}
}
return NULL;
}
struct wlr_x11_output *get_x11_output_from_window_id(
struct wlr_x11_backend *x11, xcb_window_t window) {
struct wlr_x11_output *output;
wl_list_for_each(output, &x11->outputs, link) {
if (output->win == window) {
return output;
}
}
return NULL;
}
static void handle_x11_error(struct wlr_x11_backend *x11, xcb_value_error_t *ev);
static void handle_x11_unknown_event(struct wlr_x11_backend *x11,
xcb_generic_event_t *ev);
static void handle_x11_event(struct wlr_x11_backend *x11,
xcb_generic_event_t *event) {
switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) {
case XCB_EXPOSE: {
xcb_expose_event_t *ev = (xcb_expose_event_t *)event;
struct wlr_x11_output *output =
get_x11_output_from_window_id(x11, ev->window);
if (output != NULL) {
pixman_region32_union_rect(
&output->exposed, &output->exposed,
ev->x, ev->y, ev->width, ev->height);
wlr_output_update_needs_frame(&output->wlr_output);
}
break;
}
case XCB_CONFIGURE_NOTIFY: {
xcb_configure_notify_event_t *ev =
(xcb_configure_notify_event_t *)event;
struct wlr_x11_output *output =
get_x11_output_from_window_id(x11, ev->window);
if (output != NULL) {
handle_x11_configure_notify(output, ev);
}
break;
}
case XCB_CLIENT_MESSAGE: {
xcb_client_message_event_t *ev = (xcb_client_message_event_t *)event;
if (ev->data.data32[0] == x11->atoms.wm_delete_window) {
struct wlr_x11_output *output =
get_x11_output_from_window_id(x11, ev->window);
if (output != NULL) {
wlr_output_destroy(&output->wlr_output);
}
} else {
wlr_log(WLR_DEBUG, "Unhandled client message %"PRIu32,
ev->data.data32[0]);
}
break;
}
case XCB_GE_GENERIC: {
xcb_ge_generic_event_t *ev = (xcb_ge_generic_event_t *)event;
if (ev->extension == x11->xinput_opcode) {
handle_x11_xinput_event(x11, ev);
} else if (ev->extension == x11->present_opcode) {
handle_x11_present_event(x11, ev);
} else {
handle_x11_unknown_event(x11, event);
}
break;
}
case 0: {
xcb_value_error_t *ev = (xcb_value_error_t *)event;
handle_x11_error(x11, ev);
break;
}
case XCB_UNMAP_NOTIFY:
case XCB_MAP_NOTIFY:
break;
default:
handle_x11_unknown_event(x11, event);
break;
}
}
static int x11_event(int fd, uint32_t mask, void *data) {
struct wlr_x11_backend *x11 = data;
if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) {
if (mask & WL_EVENT_ERROR) {
wlr_log(WLR_ERROR, "Failed to read from X11 server");
}
wlr_backend_destroy(&x11->backend);
return 0;
}
xcb_generic_event_t *e;
while ((e = xcb_poll_for_event(x11->xcb))) {
handle_x11_event(x11, e);
free(e);
}
int ret = xcb_connection_has_error(x11->xcb);
if (ret != 0) {
wlr_log(WLR_ERROR, "X11 connection error (%d)", ret);
wlr_backend_destroy(&x11->backend);
}
return 0;
}
struct wlr_x11_backend *get_x11_backend_from_backend(
struct wlr_backend *wlr_backend) {
assert(wlr_backend_is_x11(wlr_backend));
struct wlr_x11_backend *backend = wl_container_of(wlr_backend, backend, backend);
return backend;
}
static bool backend_start(struct wlr_backend *backend) {
struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend);
x11->started = true;
wlr_log(WLR_INFO, "Starting X11 backend");
wl_signal_emit_mutable(&x11->backend.events.new_input, &x11->keyboard.base);
for (size_t i = 0; i < x11->requested_outputs; ++i) {
wlr_x11_output_create(&x11->backend);
}
return true;
}
static void backend_destroy(struct wlr_backend *backend) {
if (!backend) {
return;
}
struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend);
struct wlr_x11_output *output, *tmp;
wl_list_for_each_safe(output, tmp, &x11->outputs, link) {
wlr_output_destroy(&output->wlr_output);
}
wlr_keyboard_finish(&x11->keyboard);
wlr_backend_finish(backend);
if (x11->event_source) {
wl_event_source_remove(x11->event_source);
}
wl_list_remove(&x11->event_loop_destroy.link);
wlr_drm_format_set_finish(&x11->primary_dri3_formats);
wlr_drm_format_set_finish(&x11->primary_shm_formats);
wlr_drm_format_set_finish(&x11->dri3_formats);
wlr_drm_format_set_finish(&x11->shm_formats);
#if HAVE_XCB_ERRORS
xcb_errors_context_free(x11->errors_context);
#endif
close(x11->drm_fd);
xcb_disconnect(x11->xcb);
free(x11);
}
static int backend_get_drm_fd(struct wlr_backend *backend) {
struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend);
return x11->drm_fd;
}
static uint32_t get_buffer_caps(struct wlr_backend *backend) {
struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend);
return (x11->have_dri3 ? WLR_BUFFER_CAP_DMABUF : 0)
| (x11->have_shm ? WLR_BUFFER_CAP_SHM : 0);
}
static const struct wlr_backend_impl backend_impl = {
.start = backend_start,
.destroy = backend_destroy,
.get_drm_fd = backend_get_drm_fd,
.get_buffer_caps = get_buffer_caps,
};
bool wlr_backend_is_x11(struct wlr_backend *backend) {
return backend->impl == &backend_impl;
}
static void handle_event_loop_destroy(struct wl_listener *listener, void *data) {
struct wlr_x11_backend *x11 = wl_container_of(listener, x11, event_loop_destroy);
backend_destroy(&x11->backend);
}
static xcb_depth_t *get_depth(xcb_screen_t *screen, uint8_t depth) {
xcb_depth_iterator_t iter = xcb_screen_allowed_depths_iterator(screen);
while (iter.rem > 0) {
if (iter.data->depth == depth) {
return iter.data;
}
xcb_depth_next(&iter);
}
return NULL;
}
static xcb_visualid_t pick_visualid(xcb_depth_t *depth) {
xcb_visualtype_t *visuals = xcb_depth_visuals(depth);
for (int i = 0; i < xcb_depth_visuals_length(depth); i++) {
if (visuals[i]._class == XCB_VISUAL_CLASS_TRUE_COLOR) {
return visuals[i].visual_id;
}
}
return 0;
}
static int query_dri3_drm_fd(struct wlr_x11_backend *x11) {
xcb_dri3_open_cookie_t open_cookie =
xcb_dri3_open(x11->xcb, x11->screen->root, 0);
xcb_dri3_open_reply_t *open_reply =
xcb_dri3_open_reply(x11->xcb, open_cookie, NULL);
if (open_reply == NULL) {
wlr_log(WLR_ERROR, "Failed to open DRI3");
return -1;
}
int *open_fds = xcb_dri3_open_reply_fds(x11->xcb, open_reply);
if (open_fds == NULL) {
wlr_log(WLR_ERROR, "xcb_dri3_open_reply_fds() failed");
free(open_reply);
return -1;
}
assert(open_reply->nfd == 1);
int drm_fd = open_fds[0];
free(open_reply);
int flags = fcntl(drm_fd, F_GETFD);
if (flags < 0) {
wlr_log_errno(WLR_ERROR, "Failed to get DRM FD flags");
close(drm_fd);
return -1;
}
if (fcntl(drm_fd, F_SETFD, flags | FD_CLOEXEC) < 0) {
wlr_log_errno(WLR_ERROR, "Failed to set DRM FD flags");
close(drm_fd);
return -1;
}
if (drmGetNodeTypeFromFd(drm_fd) != DRM_NODE_RENDER) {
char *render_name = drmGetRenderDeviceNameFromFd(drm_fd);
if (render_name == NULL) {
wlr_log(WLR_ERROR, "Failed to get DRM render node name from DRM FD");
close(drm_fd);
return -1;
}
close(drm_fd);
drm_fd = open(render_name, O_RDWR | O_CLOEXEC);
if (drm_fd < 0) {
wlr_log_errno(WLR_ERROR, "Failed to open DRM render node '%s'", render_name);
free(render_name);
return -1;
}
free(render_name);
}
return drm_fd;
}
static bool query_dri3_modifiers(struct wlr_x11_backend *x11,
const struct wlr_x11_format *format) {
if (x11->dri3_major_version == 1 && x11->dri3_minor_version < 2) {
return true; // GetSupportedModifiers requires DRI3 1.2
}
// Query the root window's supported modifiers, because we only care about
// screen_modifiers for now
xcb_dri3_get_supported_modifiers_cookie_t modifiers_cookie =
xcb_dri3_get_supported_modifiers(x11->xcb, x11->screen->root,
format->depth, format->bpp);
xcb_dri3_get_supported_modifiers_reply_t *modifiers_reply =
xcb_dri3_get_supported_modifiers_reply(x11->xcb, modifiers_cookie,
NULL);
if (!modifiers_reply) {
wlr_log(WLR_ERROR, "Failed to get DMA-BUF modifiers supported by "
"the X11 server for the format 0x%"PRIX32, format->drm);
return false;
}
// If modifiers aren't supported, DRI3 will return an empty list
const uint64_t *modifiers =
xcb_dri3_get_supported_modifiers_screen_modifiers(modifiers_reply);
int modifiers_len =
xcb_dri3_get_supported_modifiers_screen_modifiers_length(modifiers_reply);
for (int i = 0; i < modifiers_len; i++) {
wlr_drm_format_set_add(&x11->dri3_formats, format->drm, modifiers[i]);
}
free(modifiers_reply);
return true;
}
static bool query_formats(struct wlr_x11_backend *x11) {
xcb_depth_iterator_t iter = xcb_screen_allowed_depths_iterator(x11->screen);
while (iter.rem > 0) {
uint8_t depth = iter.data->depth;
const struct wlr_x11_format *format = x11_format_from_depth(depth);
if (format != NULL) {
if (x11->have_shm) {
wlr_drm_format_set_add(&x11->shm_formats, format->drm,
DRM_FORMAT_MOD_INVALID);
}
if (x11->have_dri3) {
// X11 always supports implicit modifiers
wlr_drm_format_set_add(&x11->dri3_formats, format->drm,
DRM_FORMAT_MOD_INVALID);
if (!query_dri3_modifiers(x11, format)) {
return false;
}
}
}
xcb_depth_next(&iter);
}
return true;
}
static void x11_get_argb32(struct wlr_x11_backend *x11) {
xcb_render_query_pict_formats_cookie_t cookie =
xcb_render_query_pict_formats(x11->xcb);
xcb_render_query_pict_formats_reply_t *reply =
xcb_render_query_pict_formats_reply(x11->xcb, cookie, NULL);
if (!reply) {
wlr_log(WLR_ERROR, "Did not get any reply from xcb_render_query_pict_formats");
return;
}
xcb_render_pictforminfo_t *format =
xcb_render_util_find_standard_format(reply, XCB_PICT_STANDARD_ARGB_32);
if (format == NULL) {
wlr_log(WLR_DEBUG, "No ARGB_32 render format");
free(reply);
return;
}
x11->argb32 = format->id;
free(reply);
}
struct wlr_backend *wlr_x11_backend_create(struct wl_event_loop *loop,
const char *x11_display) {
wlr_log(WLR_INFO, "Creating X11 backend");
struct wlr_x11_backend *x11 = calloc(1, sizeof(*x11));
if (!x11) {
return NULL;
}
wlr_backend_init(&x11->backend, &backend_impl);
x11->event_loop = loop;
wl_list_init(&x11->outputs);
x11->xcb = xcb_connect(x11_display, NULL);
if (!x11->xcb || xcb_connection_has_error(x11->xcb)) {
wlr_log(WLR_ERROR, "Failed to open xcb connection");
goto error_x11;
}
struct {
const char *name;
xcb_intern_atom_cookie_t cookie;
xcb_atom_t *atom;
} atom[] = {
{ .name = "WM_PROTOCOLS", .atom = &x11->atoms.wm_protocols },
{ .name = "WM_DELETE_WINDOW", .atom = &x11->atoms.wm_delete_window },
{ .name = "_NET_WM_NAME", .atom = &x11->atoms.net_wm_name },
{ .name = "UTF8_STRING", .atom = &x11->atoms.utf8_string },
{ .name = "_VARIABLE_REFRESH", .atom = &x11->atoms.variable_refresh },
};
for (size_t i = 0; i < sizeof(atom) / sizeof(atom[0]); ++i) {
atom[i].cookie = xcb_intern_atom(x11->xcb,
true, strlen(atom[i].name), atom[i].name);
}
for (size_t i = 0; i < sizeof(atom) / sizeof(atom[0]); ++i) {
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(
x11->xcb, atom[i].cookie, NULL);
if (reply) {
*atom[i].atom = reply->atom;
free(reply);
} else {
*atom[i].atom = XCB_ATOM_NONE;
}
}
const xcb_query_extension_reply_t *ext;
// DRI3 extension
ext = xcb_get_extension_data(x11->xcb, &xcb_dri3_id);
if (ext && ext->present) {
xcb_dri3_query_version_cookie_t dri3_cookie =
xcb_dri3_query_version(x11->xcb, 1, 2);
xcb_dri3_query_version_reply_t *dri3_reply =
xcb_dri3_query_version_reply(x11->xcb, dri3_cookie, NULL);
if (dri3_reply) {
if (dri3_reply->major_version >= 1) {
x11->have_dri3 = true;
x11->dri3_major_version = dri3_reply->major_version;
x11->dri3_minor_version = dri3_reply->minor_version;
} else {
wlr_log(WLR_INFO, "X11 does not support required DRI3 version "
"(has %"PRIu32".%"PRIu32", want 1.0)",
dri3_reply->major_version, dri3_reply->minor_version);
}
free(dri3_reply);
} else {
wlr_log(WLR_INFO, "X11 does not support required DRi3 version");
}
} else {
wlr_log(WLR_INFO, "X11 does not support DRI3 extension");
}
// SHM extension
ext = xcb_get_extension_data(x11->xcb, &xcb_shm_id);
if (ext && ext->present) {
xcb_shm_query_version_cookie_t shm_cookie =
xcb_shm_query_version(x11->xcb);
xcb_shm_query_version_reply_t *shm_reply =
xcb_shm_query_version_reply(x11->xcb, shm_cookie, NULL);
if (shm_reply) {
if (shm_reply->major_version >= 1 || shm_reply->minor_version >= 2) {
if (shm_reply->shared_pixmaps) {
x11->have_shm = true;
} else {
wlr_log(WLR_INFO, "X11 does not support shared pixmaps");
}
} else {
wlr_log(WLR_INFO, "X11 does not support required SHM version "
"(has %"PRIu32".%"PRIu32", want 1.2)",
shm_reply->major_version, shm_reply->minor_version);
}
} else {
wlr_log(WLR_INFO, "X11 does not support required SHM version");
}
free(shm_reply);
} else {
wlr_log(WLR_INFO, "X11 does not support SHM extension");
}
// Present extension
ext = xcb_get_extension_data(x11->xcb, &xcb_present_id);
if (!ext || !ext->present) {
wlr_log(WLR_ERROR, "X11 does not support Present extension");
goto error_display;
}
x11->present_opcode = ext->major_opcode;
xcb_present_query_version_cookie_t present_cookie =
xcb_present_query_version(x11->xcb, 1, 2);
xcb_present_query_version_reply_t *present_reply =
xcb_present_query_version_reply(x11->xcb, present_cookie, NULL);
if (!present_reply || present_reply->major_version < 1) {
wlr_log(WLR_ERROR, "X11 does not support required Present version "
"(has %"PRIu32".%"PRIu32", want 1.0)",
present_reply->major_version, present_reply->minor_version);
free(present_reply);
goto error_display;
}
free(present_reply);
// Xfixes extension
ext = xcb_get_extension_data(x11->xcb, &xcb_xfixes_id);
if (!ext || !ext->present) {
wlr_log(WLR_ERROR, "X11 does not support Xfixes extension");
goto error_display;
}
xcb_xfixes_query_version_cookie_t fixes_cookie =
xcb_xfixes_query_version(x11->xcb, 4, 0);
xcb_xfixes_query_version_reply_t *fixes_reply =
xcb_xfixes_query_version_reply(x11->xcb, fixes_cookie, NULL);
if (!fixes_reply || fixes_reply->major_version < 4) {
wlr_log(WLR_ERROR, "X11 does not support required Xfixes version "
"(has %"PRIu32".%"PRIu32", want 4.0)",
fixes_reply->major_version, fixes_reply->minor_version);
free(fixes_reply);
goto error_display;
}
free(fixes_reply);
// Xinput extension
ext = xcb_get_extension_data(x11->xcb, &xcb_input_id);
if (!ext || !ext->present) {
wlr_log(WLR_ERROR, "X11 does not support Xinput extension");
goto error_display;
}
x11->xinput_opcode = ext->major_opcode;
xcb_input_xi_query_version_cookie_t xi_cookie =
xcb_input_xi_query_version(x11->xcb, 2, 0);
xcb_input_xi_query_version_reply_t *xi_reply =
xcb_input_xi_query_version_reply(x11->xcb, xi_cookie, NULL);
if (!xi_reply || xi_reply->major_version < 2) {
wlr_log(WLR_ERROR, "X11 does not support required Xinput version "
"(has %"PRIu32".%"PRIu32", want 2.0)",
xi_reply->major_version, xi_reply->minor_version);
free(xi_reply);
goto error_display;
}
free(xi_reply);
int fd = xcb_get_file_descriptor(x11->xcb);
uint32_t events = WL_EVENT_READABLE | WL_EVENT_ERROR | WL_EVENT_HANGUP;
x11->event_source = wl_event_loop_add_fd(loop, fd, events, x11_event, x11);
if (!x11->event_source) {
wlr_log(WLR_ERROR, "Could not create event source");
goto error_display;
}
wl_event_source_check(x11->event_source);
x11->screen = xcb_setup_roots_iterator(xcb_get_setup(x11->xcb)).data;
if (!x11->screen) {
wlr_log(WLR_ERROR, "Failed to get X11 screen");
goto error_event;
}
x11->depth = get_depth(x11->screen, 24);
if (!x11->depth) {
wlr_log(WLR_ERROR, "Failed to get 24-bit depth for X11 screen");
goto error_event;
}
x11->visualid = pick_visualid(x11->depth);
if (!x11->visualid) {
wlr_log(WLR_ERROR, "Failed to pick X11 visual");
goto error_event;
}
x11->x11_format = x11_format_from_depth(x11->depth->depth);
if (!x11->x11_format) {
wlr_log(WLR_ERROR, "Unsupported depth %"PRIu8, x11->depth->depth);
goto error_event;
}
x11->colormap = xcb_generate_id(x11->xcb);
xcb_create_colormap(x11->xcb, XCB_COLORMAP_ALLOC_NONE, x11->colormap,
x11->screen->root, x11->visualid);
if (!query_formats(x11)) {
wlr_log(WLR_ERROR, "Failed to query supported DRM formats");
goto error_event;
}
x11->drm_fd = -1;
if (x11->have_dri3) {
// DRI3 may return a render node (Xwayland) or an authenticated primary
// node (plain Glamor).
x11->drm_fd = query_dri3_drm_fd(x11);
if (x11->drm_fd < 0) {
wlr_log(WLR_ERROR, "Failed to query DRI3 DRM FD");
goto error_event;
}
}
// Windows can only display buffers with the depth they were created with
// TODO: look into changing the window's depth at runtime
const struct wlr_drm_format *dri3_format =
wlr_drm_format_set_get(&x11->dri3_formats, x11->x11_format->drm);
if (x11->have_dri3 && dri3_format != NULL) {
wlr_drm_format_set_add(&x11->primary_dri3_formats,
dri3_format->format, DRM_FORMAT_MOD_INVALID);
for (size_t i = 0; i < dri3_format->len; i++) {
wlr_drm_format_set_add(&x11->primary_dri3_formats,
dri3_format->format, dri3_format->modifiers[i]);
}
}
const struct wlr_drm_format *shm_format =
wlr_drm_format_set_get(&x11->shm_formats, x11->x11_format->drm);
if (x11->have_shm && shm_format != NULL) {
wlr_drm_format_set_add(&x11->primary_shm_formats,
shm_format->format, DRM_FORMAT_MOD_INVALID);
}
#if HAVE_XCB_ERRORS
if (xcb_errors_context_new(x11->xcb, &x11->errors_context) != 0) {
wlr_log(WLR_ERROR, "Failed to create error context");
goto error_event;
}
#endif
wlr_keyboard_init(&x11->keyboard, &x11_keyboard_impl,
x11_keyboard_impl.name);
x11->event_loop_destroy.notify = handle_event_loop_destroy;
wl_event_loop_add_destroy_listener(loop, &x11->event_loop_destroy);
// Create an empty pixmap to be used as the cursor. The
// default GC foreground is 0, and that is what it will be
// filled with.
xcb_pixmap_t blank = xcb_generate_id(x11->xcb);
xcb_create_pixmap(x11->xcb, 1, blank, x11->screen->root, 1, 1);
xcb_gcontext_t gc = xcb_generate_id(x11->xcb);
xcb_create_gc(x11->xcb, gc, blank, 0, NULL);
xcb_rectangle_t rect = { .x = 0, .y = 0, .width = 1, .height = 1 };
xcb_poly_fill_rectangle(x11->xcb, blank, gc, 1, &rect);
x11->transparent_cursor = xcb_generate_id(x11->xcb);
xcb_create_cursor(x11->xcb, x11->transparent_cursor, blank, blank,
0, 0, 0, 0, 0, 0, 0, 0);
xcb_free_gc(x11->xcb, gc);
xcb_free_pixmap(x11->xcb, blank);
x11_get_argb32(x11);
return &x11->backend;
error_event:
wl_event_source_remove(x11->event_source);
error_display:
xcb_disconnect(x11->xcb);
error_x11:
free(x11);
return NULL;
}
static void handle_x11_error(struct wlr_x11_backend *x11, xcb_value_error_t *ev) {
#if HAVE_XCB_ERRORS
const char *major_name = xcb_errors_get_name_for_major_code(
x11->errors_context, ev->major_opcode);
if (!major_name) {
wlr_log(WLR_DEBUG, "X11 error happened, but could not get major name");
goto log_raw;
}
const char *minor_name = xcb_errors_get_name_for_minor_code(
x11->errors_context, ev->major_opcode, ev->minor_opcode);
const char *extension;
const char *error_name = xcb_errors_get_name_for_error(x11->errors_context,
ev->error_code, &extension);
if (!error_name) {
wlr_log(WLR_DEBUG, "X11 error happened, but could not get error name");
goto log_raw;
}
wlr_log(WLR_ERROR, "X11 error: op %s (%s), code %s (%s), "
"sequence %"PRIu16", value %"PRIu32,
major_name, minor_name ? minor_name : "no minor",
error_name, extension ? extension : "no extension",
ev->sequence, ev->bad_value);
return;
log_raw:
#endif
wlr_log(WLR_ERROR, "X11 error: op %"PRIu8":%"PRIu16", code %"PRIu8", "
"sequence %"PRIu16", value %"PRIu32,
ev->major_opcode, ev->minor_opcode, ev->error_code,
ev->sequence, ev->bad_value);
}
static void handle_x11_unknown_event(struct wlr_x11_backend *x11,
xcb_generic_event_t *ev) {
#if HAVE_XCB_ERRORS
const char *extension;
const char *event_name = xcb_errors_get_name_for_xcb_event(
x11->errors_context, ev, &extension);
if (!event_name) {
wlr_log(WLR_DEBUG, "No name for unhandled event: %u",
ev->response_type);
return;
}
wlr_log(WLR_DEBUG, "Unhandled X11 event: %s (%u)", event_name, ev->response_type);
#else
wlr_log(WLR_DEBUG, "Unhandled X11 event: %u", ev->response_type);
#endif
}

331
backend/x11/input_device.c Normal file
View file

@ -0,0 +1,331 @@
#include <stdlib.h>
#include <wlr/config.h>
#include <linux/input-event-codes.h>
#include <wayland-server-protocol.h>
#include <xcb/xcb.h>
#include <xcb/xfixes.h>
#include <xcb/xinput.h>
#include <wlr/interfaces/wlr_keyboard.h>
#include <wlr/interfaces/wlr_pointer.h>
#include <wlr/interfaces/wlr_touch.h>
#include <wlr/util/log.h>
#include "backend/x11.h"
static void send_key_event(struct wlr_x11_backend *x11, uint32_t key,
enum wl_keyboard_key_state st, xcb_timestamp_t time) {
struct wlr_keyboard_key_event ev = {
.time_msec = time,
.keycode = key,
.state = st,
.update_state = true,
};
wlr_keyboard_notify_key(&x11->keyboard, &ev);
}
static void send_button_event(struct wlr_x11_output *output, uint32_t key,
enum wl_pointer_button_state st, xcb_timestamp_t time) {
struct wlr_pointer_button_event ev = {
.pointer = &output->pointer,
.time_msec = time,
.button = key,
.state = st,
};
wl_signal_emit_mutable(&output->pointer.events.button, &ev);
wl_signal_emit_mutable(&output->pointer.events.frame, &output->pointer);
}
static void send_axis_event(struct wlr_x11_output *output, int32_t delta,
xcb_timestamp_t time) {
struct wlr_pointer_axis_event ev = {
.pointer = &output->pointer,
.time_msec = time,
.source = WL_POINTER_AXIS_SOURCE_WHEEL,
.orientation = WL_POINTER_AXIS_VERTICAL_SCROLL,
// Most mice use a 15 degree angle per scroll click
.delta = delta * 15,
.delta_discrete = delta * WLR_POINTER_AXIS_DISCRETE_STEP,
};
wl_signal_emit_mutable(&output->pointer.events.axis, &ev);
wl_signal_emit_mutable(&output->pointer.events.frame, &output->pointer);
}
static void send_pointer_position_event(struct wlr_x11_output *output,
int16_t x, int16_t y, xcb_timestamp_t time) {
struct wlr_pointer_motion_absolute_event ev = {
.pointer = &output->pointer,
.time_msec = time,
.x = (double)x / output->wlr_output.width,
.y = (double)y / output->wlr_output.height,
};
wl_signal_emit_mutable(&output->pointer.events.motion_absolute, &ev);
wl_signal_emit_mutable(&output->pointer.events.frame, &output->pointer);
}
static void send_touch_down_event(struct wlr_x11_output *output,
int16_t x, int16_t y, int32_t touch_id, xcb_timestamp_t time) {
struct wlr_touch_down_event ev = {
.touch = &output->touch,
.time_msec = time,
.x = (double)x / output->wlr_output.width,
.y = (double)y / output->wlr_output.height,
.touch_id = touch_id,
};
wl_signal_emit_mutable(&output->touch.events.down, &ev);
wl_signal_emit_mutable(&output->touch.events.frame, NULL);
}
static void send_touch_motion_event(struct wlr_x11_output *output,
int16_t x, int16_t y, int32_t touch_id, xcb_timestamp_t time) {
struct wlr_touch_motion_event ev = {
.touch = &output->touch,
.time_msec = time,
.x = (double)x / output->wlr_output.width,
.y = (double)y / output->wlr_output.height,
.touch_id = touch_id,
};
wl_signal_emit_mutable(&output->touch.events.motion, &ev);
wl_signal_emit_mutable(&output->touch.events.frame, NULL);
}
static void send_touch_up_event(struct wlr_x11_output *output,
int32_t touch_id, xcb_timestamp_t time) {
struct wlr_touch_up_event ev = {
.touch = &output->touch,
.time_msec = time,
.touch_id = touch_id,
};
wl_signal_emit_mutable(&output->touch.events.up, &ev);
wl_signal_emit_mutable(&output->touch.events.frame, NULL);
}
static struct wlr_x11_touchpoint *get_touchpoint_from_x11_touch_id(
struct wlr_x11_output *output, uint32_t id) {
struct wlr_x11_touchpoint *touchpoint;
wl_list_for_each(touchpoint, &output->touchpoints, link) {
if (touchpoint->x11_id == id) {
return touchpoint;
}
}
return NULL;
}
void handle_x11_xinput_event(struct wlr_x11_backend *x11,
xcb_ge_generic_event_t *event) {
struct wlr_x11_output *output;
switch (event->event_type) {
case XCB_INPUT_KEY_PRESS: {
xcb_input_key_press_event_t *ev =
(xcb_input_key_press_event_t *)event;
if (ev->flags & XCB_INPUT_KEY_EVENT_FLAGS_KEY_REPEAT) {
return;
}
wlr_keyboard_notify_modifiers(&x11->keyboard, ev->mods.base,
ev->mods.latched, ev->mods.locked, ev->mods.effective);
send_key_event(x11, ev->detail - 8, WL_KEYBOARD_KEY_STATE_PRESSED, ev->time);
x11->time = ev->time;
break;
}
case XCB_INPUT_KEY_RELEASE: {
xcb_input_key_release_event_t *ev =
(xcb_input_key_release_event_t *)event;
wlr_keyboard_notify_modifiers(&x11->keyboard, ev->mods.base,
ev->mods.latched, ev->mods.locked, ev->mods.effective);
send_key_event(x11, ev->detail - 8, WL_KEYBOARD_KEY_STATE_RELEASED, ev->time);
x11->time = ev->time;
break;
}
case XCB_INPUT_BUTTON_PRESS: {
xcb_input_button_press_event_t *ev =
(xcb_input_button_press_event_t *)event;
output = get_x11_output_from_window_id(x11, ev->event);
if (!output) {
return;
}
switch (ev->detail) {
case XCB_BUTTON_INDEX_1:
send_button_event(output, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED,
ev->time);
break;
case XCB_BUTTON_INDEX_2:
send_button_event(output, BTN_MIDDLE, WL_POINTER_BUTTON_STATE_PRESSED,
ev->time);
break;
case XCB_BUTTON_INDEX_3:
send_button_event(output, BTN_RIGHT, WL_POINTER_BUTTON_STATE_PRESSED,
ev->time);
break;
case XCB_BUTTON_INDEX_4:
send_axis_event(output, -1, ev->time);
break;
case XCB_BUTTON_INDEX_5:
send_axis_event(output, 1, ev->time);
break;
}
x11->time = ev->time;
break;
}
case XCB_INPUT_BUTTON_RELEASE: {
xcb_input_button_release_event_t *ev =
(xcb_input_button_release_event_t *)event;
output = get_x11_output_from_window_id(x11, ev->event);
if (!output) {
return;
}
switch (ev->detail) {
case XCB_BUTTON_INDEX_1:
send_button_event(output, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED,
ev->time);
break;
case XCB_BUTTON_INDEX_2:
send_button_event(output, BTN_MIDDLE, WL_POINTER_BUTTON_STATE_RELEASED,
ev->time);
break;
case XCB_BUTTON_INDEX_3:
send_button_event(output, BTN_RIGHT, WL_POINTER_BUTTON_STATE_RELEASED,
ev->time);
break;
}
x11->time = ev->time;
break;
}
case XCB_INPUT_MOTION: {
xcb_input_motion_event_t *ev = (xcb_input_motion_event_t *)event;
output = get_x11_output_from_window_id(x11, ev->event);
if (!output) {
return;
}
send_pointer_position_event(output, ev->event_x >> 16,
ev->event_y >> 16, ev->time);
x11->time = ev->time;
break;
}
case XCB_INPUT_TOUCH_BEGIN: {
xcb_input_touch_begin_event_t *ev = (xcb_input_touch_begin_event_t *)event;
output = get_x11_output_from_window_id(x11, ev->event);
if (!output) {
return;
}
int32_t id = 0;
if (!wl_list_empty(&output->touchpoints)) {
struct wlr_x11_touchpoint *last_touchpoint = wl_container_of(
output->touchpoints.next, last_touchpoint, link);
id = last_touchpoint->wayland_id + 1;
}
struct wlr_x11_touchpoint *touchpoint = calloc(1, sizeof(*touchpoint));
if (!touchpoint) {
return;
}
touchpoint->x11_id = ev->detail;
touchpoint->wayland_id = id;
wl_list_init(&touchpoint->link);
wl_list_insert(&output->touchpoints, &touchpoint->link);
send_touch_down_event(output, ev->event_x >> 16,
ev->event_y >> 16, touchpoint->wayland_id, ev->time);
x11->time = ev->time;
break;
}
case XCB_INPUT_TOUCH_END: {
xcb_input_touch_end_event_t *ev = (xcb_input_touch_end_event_t *)event;
output = get_x11_output_from_window_id(x11, ev->event);
if (!output) {
return;
}
struct wlr_x11_touchpoint *touchpoint = get_touchpoint_from_x11_touch_id(output, ev->detail);
if (!touchpoint) {
return;
}
send_touch_up_event(output, touchpoint->wayland_id, ev->time);
x11->time = ev->time;
wl_list_remove(&touchpoint->link);
free(touchpoint);
break;
}
case XCB_INPUT_TOUCH_UPDATE: {
xcb_input_touch_update_event_t *ev = (xcb_input_touch_update_event_t *)event;
output = get_x11_output_from_window_id(x11, ev->event);
if (!output) {
return;
}
struct wlr_x11_touchpoint *touchpoint = get_touchpoint_from_x11_touch_id(output, ev->detail);
if (!touchpoint) {
return;
}
send_touch_motion_event(output, ev->event_x >> 16,
ev->event_y >> 16, touchpoint->wayland_id, ev->time);
x11->time = ev->time;
break;
}
}
}
const struct wlr_keyboard_impl x11_keyboard_impl = {
.name = "x11-keyboard",
};
const struct wlr_pointer_impl x11_pointer_impl = {
.name = "x11-pointer",
};
const struct wlr_touch_impl x11_touch_impl = {
.name = "x11-touch",
};
void update_x11_pointer_position(struct wlr_x11_output *output,
xcb_timestamp_t time) {
struct wlr_x11_backend *x11 = output->x11;
xcb_query_pointer_cookie_t cookie =
xcb_query_pointer(x11->xcb, output->win);
xcb_query_pointer_reply_t *reply =
xcb_query_pointer_reply(x11->xcb, cookie, NULL);
if (!reply) {
return;
}
send_pointer_position_event(output, reply->win_x, reply->win_y, time);
free(reply);
}
bool wlr_input_device_is_x11(struct wlr_input_device *wlr_dev) {
switch (wlr_dev->type) {
case WLR_INPUT_DEVICE_KEYBOARD:
return wlr_keyboard_from_input_device(wlr_dev)->impl == &x11_keyboard_impl;
case WLR_INPUT_DEVICE_POINTER:
return wlr_pointer_from_input_device(wlr_dev)->impl == &x11_pointer_impl;
case WLR_INPUT_DEVICE_TOUCH:
return wlr_touch_from_input_device(wlr_dev)->impl == &x11_touch_impl;
default:
return false;
}
}

36
backend/x11/meson.build Normal file
View file

@ -0,0 +1,36 @@
x11_libs = []
x11_required = [
'xcb',
'xcb-dri3',
'xcb-present',
'xcb-render',
'xcb-renderutil',
'xcb-shm',
'xcb-xfixes',
'xcb-xinput',
]
msg = ['Required for X11 backend support.']
if 'x11' in backends
msg += 'Install "@0@" or disable the X11 backend.'
endif
foreach lib : x11_required
dep = dependency(lib,
required: 'x11' in backends,
not_found_message: '\n'.join(msg).format(lib),
)
if not dep.found()
subdir_done()
endif
x11_libs += dep
endforeach
wlr_files += files(
'backend.c',
'input_device.c',
'output.c',
)
wlr_deps += x11_libs
features += { 'x11-backend': true }

774
backend/x11/output.c Normal file
View file

@ -0,0 +1,774 @@
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <drm_fourcc.h>
#include <xcb/dri3.h>
#include <xcb/present.h>
#include <xcb/render.h>
#include <xcb/shm.h>
#include <xcb/xcb.h>
#include <xcb/xinput.h>
#include <wlr/interfaces/wlr_output.h>
#include <wlr/interfaces/wlr_pointer.h>
#include <wlr/interfaces/wlr_touch.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/util/log.h>
#include "backend/x11.h"
#include "util/time.h"
#include "types/wlr_output.h"
static const uint32_t SUPPORTED_OUTPUT_STATE =
WLR_OUTPUT_STATE_BACKEND_OPTIONAL |
WLR_OUTPUT_STATE_BUFFER |
WLR_OUTPUT_STATE_ENABLED |
WLR_OUTPUT_STATE_MODE |
WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED;
static size_t last_output_num = 0;
static void parse_xcb_setup(struct wlr_output *output,
xcb_connection_t *xcb) {
const xcb_setup_t *xcb_setup = xcb_get_setup(xcb);
output->make = calloc(1, xcb_setup_vendor_length(xcb_setup) + 1);
if (output->make == NULL) {
wlr_log_errno(WLR_ERROR, "Allocation failed");
return;
}
memcpy(output->make, xcb_setup_vendor(xcb_setup),
xcb_setup_vendor_length(xcb_setup));
char model[64];
snprintf(model, sizeof(model), "%"PRIu16".%"PRIu16,
xcb_setup->protocol_major_version,
xcb_setup->protocol_minor_version);
output->model = strdup(model);
}
static struct wlr_x11_output *get_x11_output_from_output(
struct wlr_output *wlr_output) {
assert(wlr_output_is_x11(wlr_output));
struct wlr_x11_output *output = wl_container_of(wlr_output, output, wlr_output);
return output;
}
static bool output_set_custom_mode(struct wlr_output *wlr_output,
int32_t width, int32_t height, int32_t refresh) {
struct wlr_x11_output *output = get_x11_output_from_output(wlr_output);
struct wlr_x11_backend *x11 = output->x11;
if (width == output->win_width && height == output->win_height) {
return true;
}
const uint32_t values[] = { width, height };
xcb_void_cookie_t cookie = xcb_configure_window_checked(
x11->xcb, output->win,
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values);
xcb_generic_error_t *error;
if ((error = xcb_request_check(x11->xcb, cookie))) {
wlr_log(WLR_ERROR, "Could not set window size to %dx%d\n",
width, height);
free(error);
return false;
}
output->win_width = width;
output->win_height = height;
// Move the pointer to its new location
update_x11_pointer_position(output, output->x11->time);
return true;
}
static void destroy_x11_buffer(struct wlr_x11_buffer *buffer);
static void output_destroy(struct wlr_output *wlr_output) {
struct wlr_x11_output *output = get_x11_output_from_output(wlr_output);
struct wlr_x11_backend *x11 = output->x11;
pixman_region32_fini(&output->exposed);
wlr_pointer_finish(&output->pointer);
wlr_touch_finish(&output->touch);
struct wlr_x11_buffer *buffer, *buffer_tmp;
wl_list_for_each_safe(buffer, buffer_tmp, &output->buffers, link) {
destroy_x11_buffer(buffer);
}
wl_list_remove(&output->link);
if (output->cursor.pic != XCB_NONE) {
xcb_render_free_picture(x11->xcb, output->cursor.pic);
}
// A zero event mask deletes the event context
xcb_present_select_input(x11->xcb, output->present_event_id, output->win, 0);
xcb_destroy_window(x11->xcb, output->win);
xcb_flush(x11->xcb);
free(output);
}
static bool output_test(struct wlr_output *wlr_output,
const struct wlr_output_state *state) {
struct wlr_x11_output *output = get_x11_output_from_output(wlr_output);
struct wlr_x11_backend *x11 = output->x11;
uint32_t unsupported = state->committed & ~SUPPORTED_OUTPUT_STATE;
if (unsupported != 0) {
wlr_log(WLR_DEBUG, "Unsupported output state fields: 0x%"PRIx32,
unsupported);
return false;
}
// All we can do to influence adaptive sync on the X11 backend is set the
// _VARIABLE_REFRESH window property like mesa automatically does. We don't
// have any control beyond that, so we set the state to enabled on creating
// the output and never allow changing it (just like the Wayland backend).
assert(wlr_output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED);
if (state->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) {
if (!state->adaptive_sync_enabled) {
wlr_log(WLR_DEBUG, "Disabling adaptive sync is not supported");
return false;
}
}
if (state->committed & WLR_OUTPUT_STATE_BUFFER) {
struct wlr_buffer *buffer = state->buffer;
struct wlr_dmabuf_attributes dmabuf_attrs;
struct wlr_shm_attributes shm_attrs;
uint32_t format = DRM_FORMAT_INVALID;
if (wlr_buffer_get_dmabuf(buffer, &dmabuf_attrs)) {
format = dmabuf_attrs.format;
} else if (wlr_buffer_get_shm(buffer, &shm_attrs)) {
format = shm_attrs.format;
}
if (format != x11->x11_format->drm) {
wlr_log(WLR_DEBUG, "Unsupported buffer format");
return false;
}
}
if (state->committed & WLR_OUTPUT_STATE_MODE) {
assert(state->mode_type == WLR_OUTPUT_STATE_MODE_CUSTOM);
if (state->custom_mode.refresh != 0) {
wlr_log(WLR_DEBUG, "Refresh rates are not supported");
return false;
}
}
return true;
}
static void destroy_x11_buffer(struct wlr_x11_buffer *buffer) {
if (!buffer) {
return;
}
wl_list_remove(&buffer->buffer_destroy.link);
wl_list_remove(&buffer->link);
xcb_free_pixmap(buffer->x11->xcb, buffer->pixmap);
for (size_t i = 0; i < buffer->n_busy; i++) {
wlr_buffer_unlock(buffer->buffer);
}
free(buffer);
}
static void buffer_handle_buffer_destroy(struct wl_listener *listener,
void *data) {
struct wlr_x11_buffer *buffer =
wl_container_of(listener, buffer, buffer_destroy);
destroy_x11_buffer(buffer);
}
static xcb_pixmap_t import_dmabuf(struct wlr_x11_output *output,
struct wlr_dmabuf_attributes *dmabuf) {
struct wlr_x11_backend *x11 = output->x11;
if (dmabuf->format != x11->x11_format->drm) {
// The pixmap's depth must match the window's depth, otherwise Present
// will throw a Match error
return XCB_PIXMAP_NONE;
}
// xcb closes the FDs after sending them, so we need to dup them here
struct wlr_dmabuf_attributes dup_attrs = {0};
if (!wlr_dmabuf_attributes_copy(&dup_attrs, dmabuf)) {
return XCB_PIXMAP_NONE;
}
const struct wlr_x11_format *x11_fmt = x11->x11_format;
xcb_pixmap_t pixmap = xcb_generate_id(x11->xcb);
if (x11->dri3_major_version > 1 || x11->dri3_minor_version >= 2) {
if (dmabuf->n_planes > 4) {
wlr_dmabuf_attributes_finish(&dup_attrs);
return XCB_PIXMAP_NONE;
}
xcb_dri3_pixmap_from_buffers(x11->xcb, pixmap, output->win,
dmabuf->n_planes, dmabuf->width, dmabuf->height, dmabuf->stride[0],
dmabuf->offset[0], dmabuf->stride[1], dmabuf->offset[1],
dmabuf->stride[2], dmabuf->offset[2], dmabuf->stride[3],
dmabuf->offset[3], x11_fmt->depth, x11_fmt->bpp, dmabuf->modifier,
dup_attrs.fd);
} else {
// PixmapFromBuffers requires DRI3 1.2
if (dmabuf->n_planes != 1
|| dmabuf->modifier != DRM_FORMAT_MOD_INVALID) {
wlr_dmabuf_attributes_finish(&dup_attrs);
return XCB_PIXMAP_NONE;
}
xcb_dri3_pixmap_from_buffer(x11->xcb, pixmap, output->win,
dmabuf->height * dmabuf->stride[0], dmabuf->width, dmabuf->height,
dmabuf->stride[0], x11_fmt->depth, x11_fmt->bpp, dup_attrs.fd[0]);
}
return pixmap;
}
static xcb_pixmap_t import_shm(struct wlr_x11_output *output,
struct wlr_shm_attributes *shm) {
struct wlr_x11_backend *x11 = output->x11;
if (shm->format != x11->x11_format->drm) {
// The pixmap's depth must match the window's depth, otherwise Present
// will throw a Match error
return XCB_PIXMAP_NONE;
}
// xcb closes the FD after sending it
int fd = fcntl(shm->fd, F_DUPFD_CLOEXEC, 0);
if (fd < 0) {
wlr_log_errno(WLR_ERROR, "fcntl(F_DUPFD_CLOEXEC) failed");
return XCB_PIXMAP_NONE;
}
xcb_shm_seg_t seg = xcb_generate_id(x11->xcb);
xcb_shm_attach_fd(x11->xcb, seg, fd, false);
xcb_pixmap_t pixmap = xcb_generate_id(x11->xcb);
xcb_shm_create_pixmap(x11->xcb, pixmap, output->win, shm->width,
shm->height, x11->x11_format->depth, seg, shm->offset);
xcb_shm_detach(x11->xcb, seg);
return pixmap;
}
static struct wlr_x11_buffer *create_x11_buffer(struct wlr_x11_output *output,
struct wlr_buffer *wlr_buffer) {
struct wlr_x11_backend *x11 = output->x11;
xcb_pixmap_t pixmap = XCB_PIXMAP_NONE;
struct wlr_dmabuf_attributes dmabuf_attrs;
struct wlr_shm_attributes shm_attrs;
if (wlr_buffer_get_dmabuf(wlr_buffer, &dmabuf_attrs)) {
pixmap = import_dmabuf(output, &dmabuf_attrs);
} else if (wlr_buffer_get_shm(wlr_buffer, &shm_attrs)) {
pixmap = import_shm(output, &shm_attrs);
}
if (pixmap == XCB_PIXMAP_NONE) {
return NULL;
}
struct wlr_x11_buffer *buffer = calloc(1, sizeof(*buffer));
if (!buffer) {
xcb_free_pixmap(x11->xcb, pixmap);
return NULL;
}
buffer->buffer = wlr_buffer_lock(wlr_buffer);
buffer->n_busy = 1;
buffer->pixmap = pixmap;
buffer->x11 = x11;
wl_list_insert(&output->buffers, &buffer->link);
buffer->buffer_destroy.notify = buffer_handle_buffer_destroy;
wl_signal_add(&wlr_buffer->events.destroy, &buffer->buffer_destroy);
return buffer;
}
static struct wlr_x11_buffer *get_or_create_x11_buffer(
struct wlr_x11_output *output, struct wlr_buffer *wlr_buffer) {
struct wlr_x11_buffer *buffer;
wl_list_for_each(buffer, &output->buffers, link) {
if (buffer->buffer == wlr_buffer) {
wlr_buffer_lock(buffer->buffer);
buffer->n_busy++;
return buffer;
}
}
return create_x11_buffer(output, wlr_buffer);
}
static bool output_commit_buffer(struct wlr_x11_output *output,
const struct wlr_output_state *state) {
struct wlr_x11_backend *x11 = output->x11;
struct wlr_buffer *buffer = state->buffer;
struct wlr_x11_buffer *x11_buffer =
get_or_create_x11_buffer(output, buffer);
if (!x11_buffer) {
goto error;
}
xcb_xfixes_region_t region = XCB_NONE;
if (state->committed & WLR_OUTPUT_STATE_DAMAGE) {
pixman_region32_union(&output->exposed, &output->exposed, &state->damage);
int rects_len = 0;
const pixman_box32_t *rects = pixman_region32_rectangles(&output->exposed, &rects_len);
xcb_rectangle_t *xcb_rects = calloc(rects_len, sizeof(xcb_rectangle_t));
if (!xcb_rects) {
goto error;
}
for (int i = 0; i < rects_len; i++) {
const pixman_box32_t *box = &rects[i];
xcb_rects[i] = (struct xcb_rectangle_t){
.x = box->x1,
.y = box->y1,
.width = box->x2 - box->x1,
.height = box->y2 - box->y1,
};
}
region = xcb_generate_id(x11->xcb);
xcb_xfixes_create_region(x11->xcb, region, rects_len, xcb_rects);
free(xcb_rects);
}
pixman_region32_clear(&output->exposed);
uint32_t serial = output->wlr_output.commit_seq;
uint32_t options = 0;
uint64_t target_msc = output->last_msc ? output->last_msc + 1 : 0;
xcb_present_pixmap(x11->xcb, output->win, x11_buffer->pixmap, serial,
0, region, 0, 0, XCB_NONE, XCB_NONE, XCB_NONE, options, target_msc,
0, 0, 0, NULL);
if (region != XCB_NONE) {
xcb_xfixes_destroy_region(x11->xcb, region);
}
return true;
error:
destroy_x11_buffer(x11_buffer);
return false;
}
static bool output_commit(struct wlr_output *wlr_output,
const struct wlr_output_state *state) {
struct wlr_x11_output *output = get_x11_output_from_output(wlr_output);
struct wlr_x11_backend *x11 = output->x11;
if (!output_test(wlr_output, state)) {
return false;
}
if (state->committed & WLR_OUTPUT_STATE_ENABLED) {
if (state->enabled) {
xcb_map_window(x11->xcb, output->win);
} else {
xcb_unmap_window(x11->xcb, output->win);
}
}
if (state->committed & WLR_OUTPUT_STATE_MODE) {
if (!output_set_custom_mode(wlr_output,
state->custom_mode.width,
state->custom_mode.height,
state->custom_mode.refresh)) {
return false;
}
}
if (state->committed & WLR_OUTPUT_STATE_BUFFER) {
if (!output_commit_buffer(output, state)) {
return false;
}
} else if (output_pending_enabled(wlr_output, state)) {
uint32_t serial = output->wlr_output.commit_seq;
uint64_t target_msc = output->last_msc ? output->last_msc + 1 : 0;
xcb_present_notify_msc(x11->xcb, output->win, serial, target_msc, 0, 0);
}
xcb_flush(x11->xcb);
return true;
}
static void update_x11_output_cursor(struct wlr_x11_output *output,
int32_t hotspot_x, int32_t hotspot_y) {
struct wlr_x11_backend *x11 = output->x11;
xcb_cursor_t cursor = x11->transparent_cursor;
if (output->cursor.pic != XCB_NONE) {
cursor = xcb_generate_id(x11->xcb);
xcb_render_create_cursor(x11->xcb, cursor, output->cursor.pic,
hotspot_x, hotspot_y);
}
uint32_t values[] = {cursor};
xcb_change_window_attributes(x11->xcb, output->win,
XCB_CW_CURSOR, values);
xcb_flush(x11->xcb);
if (cursor != x11->transparent_cursor) {
xcb_free_cursor(x11->xcb, cursor);
}
}
static bool output_cursor_to_picture(struct wlr_x11_output *output,
struct wlr_buffer *buffer) {
struct wlr_x11_backend *x11 = output->x11;
struct wlr_renderer *renderer = output->wlr_output.renderer;
if (output->cursor.pic != XCB_NONE) {
xcb_render_free_picture(x11->xcb, output->cursor.pic);
}
output->cursor.pic = XCB_NONE;
if (buffer == NULL) {
return true;
}
struct wlr_texture *texture = wlr_texture_from_buffer(renderer, buffer);
if (!texture) {
return false;
}
int depth = 32;
int stride = texture->width * 4;
uint8_t *data = malloc(texture->height * stride);
if (data == NULL) {
wlr_texture_destroy(texture);
return false;
}
bool result = wlr_texture_read_pixels(texture, &(struct wlr_texture_read_pixels_options) {
.format = DRM_FORMAT_ARGB8888,
.stride = stride,
.data = data,
});
wlr_texture_destroy(texture);
if (!result) {
free(data);
return false;
}
xcb_pixmap_t pix = xcb_generate_id(x11->xcb);
xcb_create_pixmap(x11->xcb, depth, pix, output->win,
buffer->width, buffer->height);
output->cursor.pic = xcb_generate_id(x11->xcb);
xcb_render_create_picture(x11->xcb, output->cursor.pic,
pix, x11->argb32, 0, 0);
xcb_gcontext_t gc = xcb_generate_id(x11->xcb);
xcb_create_gc(x11->xcb, gc, pix, 0, NULL);
xcb_put_image(x11->xcb, XCB_IMAGE_FORMAT_Z_PIXMAP,
pix, gc, buffer->width, buffer->height, 0, 0, 0, depth,
stride * buffer->height * sizeof(uint8_t), data);
free(data);
xcb_free_gc(x11->xcb, gc);
xcb_free_pixmap(x11->xcb, pix);
return true;
}
static bool output_set_cursor(struct wlr_output *wlr_output,
struct wlr_buffer *buffer, int32_t hotspot_x, int32_t hotspot_y) {
struct wlr_x11_output *output = get_x11_output_from_output(wlr_output);
struct wlr_x11_backend *x11 = output->x11;
if (x11->argb32 == XCB_NONE) {
return false;
}
if (buffer != NULL) {
if (hotspot_x < 0) {
hotspot_x = 0;
}
if (hotspot_x > buffer->width) {
hotspot_x = buffer->width;
}
if (hotspot_y < 0) {
hotspot_y = 0;
}
if (hotspot_y > buffer->height) {
hotspot_y = buffer->height;
}
}
bool success = output_cursor_to_picture(output, buffer);
update_x11_output_cursor(output, hotspot_x, hotspot_y);
return success;
}
static bool output_move_cursor(struct wlr_output *_output, int x, int y) {
// TODO: only return true if x == current x and y == current y
return true;
}
static const struct wlr_drm_format_set *output_get_primary_formats(
struct wlr_output *wlr_output, uint32_t buffer_caps) {
struct wlr_x11_output *output = get_x11_output_from_output(wlr_output);
struct wlr_x11_backend *x11 = output->x11;
if (x11->have_dri3 && (buffer_caps & WLR_BUFFER_CAP_DMABUF)) {
return &output->x11->primary_dri3_formats;
} else if (x11->have_shm && (buffer_caps & WLR_BUFFER_CAP_SHM)) {
return &output->x11->primary_shm_formats;
}
return NULL;
}
static const struct wlr_output_impl output_impl = {
.destroy = output_destroy,
.test = output_test,
.commit = output_commit,
.set_cursor = output_set_cursor,
.move_cursor = output_move_cursor,
.get_primary_formats = output_get_primary_formats,
};
struct wlr_output *wlr_x11_output_create(struct wlr_backend *backend) {
struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend);
if (!x11->started) {
++x11->requested_outputs;
return NULL;
}
struct wlr_x11_output *output = calloc(1, sizeof(*output));
if (output == NULL) {
return NULL;
}
output->x11 = x11;
wl_list_init(&output->buffers);
pixman_region32_init(&output->exposed);
struct wlr_output *wlr_output = &output->wlr_output;
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_custom_mode(&state, 1024, 768, 0);
wlr_output_init(wlr_output, &x11->backend, &output_impl, x11->event_loop, &state);
wlr_output_state_finish(&state);
size_t output_num = ++last_output_num;
char name[64];
snprintf(name, sizeof(name), "X11-%zu", output_num);
wlr_output_set_name(wlr_output, name);
parse_xcb_setup(wlr_output, x11->xcb);
char description[128];
snprintf(description, sizeof(description), "X11 output %zu", output_num);
wlr_output_set_description(wlr_output, description);
// The X11 protocol requires us to set a colormap and border pixel if the
// depth doesn't match the root window's
uint32_t mask = XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK |
XCB_CW_COLORMAP | XCB_CW_CURSOR;
uint32_t values[] = {
0,
XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY,
x11->colormap,
x11->transparent_cursor,
};
output->win = xcb_generate_id(x11->xcb);
xcb_create_window(x11->xcb, x11->depth->depth, output->win,
x11->screen->root, 0, 0, wlr_output->width, wlr_output->height, 0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, x11->visualid, mask, values);
output->win_width = wlr_output->width;
output->win_height = wlr_output->height;
struct {
xcb_input_event_mask_t head;
xcb_input_xi_event_mask_t mask;
} xinput_mask = {
.head = { .deviceid = XCB_INPUT_DEVICE_ALL_MASTER, .mask_len = 1 },
.mask = XCB_INPUT_XI_EVENT_MASK_KEY_PRESS |
XCB_INPUT_XI_EVENT_MASK_KEY_RELEASE |
XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS |
XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE |
XCB_INPUT_XI_EVENT_MASK_MOTION |
XCB_INPUT_XI_EVENT_MASK_TOUCH_BEGIN |
XCB_INPUT_XI_EVENT_MASK_TOUCH_END |
XCB_INPUT_XI_EVENT_MASK_TOUCH_UPDATE,
};
xcb_input_xi_select_events(x11->xcb, output->win, 1, &xinput_mask.head);
uint32_t present_mask = XCB_PRESENT_EVENT_MASK_IDLE_NOTIFY |
XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY;
output->present_event_id = xcb_generate_id(x11->xcb);
xcb_present_select_input(x11->xcb, output->present_event_id, output->win,
present_mask);
xcb_change_property(x11->xcb, XCB_PROP_MODE_REPLACE, output->win,
x11->atoms.wm_protocols, XCB_ATOM_ATOM, 32, 1,
&x11->atoms.wm_delete_window);
uint32_t enabled = 1;
xcb_change_property(x11->xcb, XCB_PROP_MODE_REPLACE, output->win,
x11->atoms.variable_refresh, XCB_ATOM_CARDINAL, 32, 1,
&enabled);
wlr_output->adaptive_sync_status = WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED;
wlr_x11_output_set_title(wlr_output, NULL);
xcb_flush(x11->xcb);
wl_list_insert(&x11->outputs, &output->link);
wlr_pointer_init(&output->pointer, &x11_pointer_impl, "x11-pointer");
output->pointer.output_name = strdup(wlr_output->name);
wlr_touch_init(&output->touch, &x11_touch_impl, "x11-touch");
output->touch.output_name = strdup(wlr_output->name);
wl_list_init(&output->touchpoints);
wl_signal_emit_mutable(&x11->backend.events.new_output, wlr_output);
wl_signal_emit_mutable(&x11->backend.events.new_input, &output->pointer.base);
wl_signal_emit_mutable(&x11->backend.events.new_input, &output->touch.base);
return wlr_output;
}
void handle_x11_configure_notify(struct wlr_x11_output *output,
xcb_configure_notify_event_t *ev) {
if (ev->width == 0 || ev->height == 0) {
wlr_log(WLR_DEBUG,
"Ignoring X11 configure event for height=%d, width=%d",
ev->width, ev->height);
return;
}
output->win_width = ev->width;
output->win_height = ev->height;
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_custom_mode(&state, ev->width, ev->height, 0);
wlr_output_send_request_state(&output->wlr_output, &state);
wlr_output_state_finish(&state);
}
bool wlr_output_is_x11(struct wlr_output *wlr_output) {
return wlr_output->impl == &output_impl;
}
void wlr_x11_output_set_title(struct wlr_output *output, const char *title) {
struct wlr_x11_output *x11_output = get_x11_output_from_output(output);
char wl_title[32];
if (title == NULL) {
if (snprintf(wl_title, sizeof(wl_title), "wlroots - %s", output->name) <= 0) {
return;
}
title = wl_title;
}
xcb_change_property(x11_output->x11->xcb, XCB_PROP_MODE_REPLACE, x11_output->win,
x11_output->x11->atoms.net_wm_name, x11_output->x11->atoms.utf8_string, 8,
strlen(title), title);
}
static struct wlr_x11_buffer *get_x11_buffer(struct wlr_x11_output *output,
xcb_pixmap_t pixmap) {
struct wlr_x11_buffer *buffer;
wl_list_for_each(buffer, &output->buffers, link) {
if (buffer->pixmap == pixmap) {
return buffer;
}
}
return NULL;
}
void handle_x11_present_event(struct wlr_x11_backend *x11,
xcb_ge_generic_event_t *event) {
struct wlr_x11_output *output;
switch (event->event_type) {
case XCB_PRESENT_EVENT_IDLE_NOTIFY:;
xcb_present_idle_notify_event_t *idle_notify =
(xcb_present_idle_notify_event_t *)event;
output = get_x11_output_from_window_id(x11, idle_notify->window);
if (!output) {
wlr_log(WLR_DEBUG, "Got PresentIdleNotify event for unknown window");
return;
}
struct wlr_x11_buffer *buffer =
get_x11_buffer(output, idle_notify->pixmap);
if (!buffer) {
wlr_log(WLR_DEBUG, "Got PresentIdleNotify event for unknown buffer");
return;
}
assert(buffer->n_busy > 0);
buffer->n_busy--;
wlr_buffer_unlock(buffer->buffer); // may destroy buffer
break;
case XCB_PRESENT_COMPLETE_NOTIFY:;
xcb_present_complete_notify_event_t *complete_notify =
(xcb_present_complete_notify_event_t *)event;
output = get_x11_output_from_window_id(x11, complete_notify->window);
if (!output) {
wlr_log(WLR_DEBUG, "Got PresentCompleteNotify event for unknown window");
return;
}
output->last_msc = complete_notify->msc;
struct timespec t;
timespec_from_nsec(&t, complete_notify->ust * 1000);
uint32_t flags = 0;
if (complete_notify->mode == XCB_PRESENT_COMPLETE_MODE_FLIP) {
flags |= WLR_OUTPUT_PRESENT_ZERO_COPY;
}
bool presented = complete_notify->mode != XCB_PRESENT_COMPLETE_MODE_SKIP;
struct wlr_output_event_present present_event = {
.output = &output->wlr_output,
.commit_seq = complete_notify->serial,
.presented = presented,
.when = &t,
.seq = complete_notify->msc,
.flags = flags,
};
wlr_output_send_present(&output->wlr_output, &present_event);
wlr_output_send_frame(&output->wlr_output);
break;
default:
wlr_log(WLR_DEBUG, "Unhandled Present event %"PRIu16, event->event_type);
}
}

86
docs/architecture.md Normal file
View file

@ -0,0 +1,86 @@
# Architecture
This document describes the high-level design of wlroots. wlroots is modular:
each module can be used mostly independently from the rest of wlroots. For
instance, some wlroots-based compositors only use its backends, some only use
its protocol implementations.
## Backends
Backends are responsible for exposing input devices and output devices.
wlroots provides DRM and libinput backends to directly drive physical
devices, Wayland and X11 backends to run nested inside another compositor,
and a headless backend. A special "multi" backend is used to combine together
multiple backends, for instance DRM and libinput. Compositors can also
implement their own custom backends if they have special needs.
Input devices such as pointers, keyboards, touch screens, tablets, switches
are supported. They emit input events (e.g. a keyboard key is pressed) which
compositors can handle and forward to Wayland clients.
Output devices are tasked with presenting buffers to the user. They also
provide feedback, for instance presentation timestamps. Some backends support
more advanced functionality, such as displaying multiple buffers (e.g. for the
cursor image) or basic 2D transformations (e.g. rotation, clipping, scaling).
## Renderers
Renderers provide utilities to import buffers submitted by Wayland clients,
and a basic 2D drawing API suitable for simple compositors. wlroots provides
renderer implementations based on OpenGL ES 2, Vulkan and Pixman. Just like
backends, compositors can implement their own renderers, or use the graphics
APIs directly.
To draw an image onto a buffer, compositors will first need to create a
texture, representing a source of pixels the renderer can sample from. This can
be done either by uploading pixels from CPU memory, or by importing already
existing GPU memory via DMA-BUFs. Compositors can then create a render pass
and submit drawing operations. Once they are done drawing, compositors can
submit the rendered buffer to an output.
## Protocol implementations
A number of Wayland interface implementations are provided.
### Plumbing protocols
wlroots ships unopinionated implementations of core plumbing interfaces, for
instance:
- `wl_compositor` and `wl_surface`
- `wl_seat` and all input-related interfaces
- Buffer factories such as `wl_shm` and linux-dmabuf
- Additional protocols such as viewporter and presentation-time
### Shells
Shells give a meaning to surfaces. There are many kinds of surfaces:
application windows, tooltips, right-click menus, desktop panels, wallpapers,
lock screens, on-screen keyboards, and so on. Each of these use-cases is
fulfilled with a shell. wlroots supports xdg-shell for regular windows and
popups, Xwayland for interoperability with X11 applications, layer-shell for
desktop UI elements, and more.
### Other protocols
Many other protocol implementations are included, for instance:
- xdg-activation for raising application windows
- idle-inhibit for preventing the screen from blanking when the user is
watching a video
- ext-idle-notify for notifying when the user is idle
## Helpers
wlroots provides additional helpers which can make it easier for compositors to
tie everything together:
- `wlr_output_layout` organises output devices in the physical space
- `wlr_cursor` stores the current position and image of the cursor
- `wlr_scene` provides a declarative way to display surfaces
## tinywl
tinywl is a minimal wlroots compositor. It implements basic stacking window
management and only supports xdg-shell. It's extensively commented and is a
good learning resource for developers new to wlroots.

68
docs/env_vars.md Normal file
View file

@ -0,0 +1,68 @@
wlroots reads these environment variables
# wlroots specific
* *WLR_BACKENDS*: comma-separated list of backends to use (available backends:
libinput, drm, wayland, x11, headless)
* *WLR_NO_HARDWARE_CURSORS*: set to 1 to use software cursors instead of
hardware cursors
* *WLR_XWAYLAND*: specifies the path to an Xwayland binary to be used (instead
of following shell search semantics for "Xwayland")
* *WLR_RENDERER*: forces the creation of a specified renderer (available
renderers: gles2, pixman, vulkan)
* *WLR_RENDER_DRM_DEVICE*: specifies the DRM node to use for
hardware-accelerated renderers.
* *WLR_EGL_NO_MODIFIERS*: set to 1 to disable format modifiers in EGL, this can
be used to understand and work around driver bugs.
## DRM backend
* *WLR_DRM_DEVICES*: specifies the DRM devices (as a colon separated list)
instead of auto probing them. The first existing device in this list is
considered the primary DRM device.
* *WLR_DRM_NO_ATOMIC*: set to 1 to use legacy DRM interface instead of atomic
mode setting
* *WLR_DRM_NO_MODIFIERS*: set to 1 to always allocate planes without modifiers,
this can fix certain modeset failures because of bandwidth restrictions.
* *WLR_DRM_FORCE_LIBLIFTOFF*: set to 1 to force libliftoff (by default,
libliftoff is never used)
## Headless backend
* *WLR_HEADLESS_OUTPUTS*: when using the headless backend specifies the number
of outputs
## libinput backend
* *WLR_LIBINPUT_NO_DEVICES*: set to 1 to not fail without any input devices
## Wayland backend
* *WLR_WL_OUTPUTS*: when using the wayland backend specifies the number of outputs
## X11 backend
* *WLR_X11_OUTPUTS*: when using the X11 backend specifies the number of outputs
## gles2 renderer
* *WLR_RENDERER_ALLOW_SOFTWARE*: allows the gles2 renderer to use software
rendering
## scenes
* *WLR_SCENE_DEBUG_DAMAGE*: specifies debug options for screen damage related
tasks for compositors that use scenes (available options: none, rerender,
highlight)
* *WLR_SCENE_DISABLE_DIRECT_SCANOUT*: disables direct scan-out for debugging.
* *WLR_SCENE_DISABLE_VISIBILITY*: If set to 1, the visibility of all scene nodes
will be considered to be the full node. Intelligent visibility canculations will
be disabled.
# Generic
* *DISPLAY*: if set probe X11 backend in `wlr_backend_autocreate`
* *WAYLAND_DISPLAY*, *WAYLAND_SOCKET*: if set probe Wayland backend in
`wlr_backend_autocreate`
* *XCURSOR_PATH*: directory where xcursors are located
* *XDG_SESSION_ID*: if set, session ID used by the logind session

1
examples/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/compositor/protocols

193
examples/cairo-buffer.c Normal file
View file

@ -0,0 +1,193 @@
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#include <cairo.h>
#include <drm_fourcc.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/util/log.h>
#include <wlr/interfaces/wlr_buffer.h>
/* Simple scene-graph example with a custom buffer drawn by Cairo.
*
* Input is unimplemented. Surfaces are unimplemented. */
struct cairo_buffer {
struct wlr_buffer base;
cairo_surface_t *surface;
};
static void cairo_buffer_destroy(struct wlr_buffer *wlr_buffer) {
struct cairo_buffer *buffer = wl_container_of(wlr_buffer, buffer, base);
cairo_surface_destroy(buffer->surface);
free(buffer);
}
static bool cairo_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer,
uint32_t flags, void **data, uint32_t *format, size_t *stride) {
struct cairo_buffer *buffer = wl_container_of(wlr_buffer, buffer, base);
if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) {
return false;
}
*format = DRM_FORMAT_ARGB8888;
*data = cairo_image_surface_get_data(buffer->surface);
*stride = cairo_image_surface_get_stride(buffer->surface);
return true;
}
static void cairo_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) {
}
static const struct wlr_buffer_impl cairo_buffer_impl = {
.destroy = cairo_buffer_destroy,
.begin_data_ptr_access = cairo_buffer_begin_data_ptr_access,
.end_data_ptr_access = cairo_buffer_end_data_ptr_access
};
static struct cairo_buffer *create_cairo_buffer(int width, int height) {
struct cairo_buffer *buffer = calloc(1, sizeof(*buffer));
if (!buffer) {
return NULL;
}
wlr_buffer_init(&buffer->base, &cairo_buffer_impl, width, height);
buffer->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
width, height);
if (cairo_surface_status(buffer->surface) != CAIRO_STATUS_SUCCESS) {
free(buffer);
return NULL;
}
return buffer;
}
struct server {
struct wl_display *display;
struct wlr_backend *backend;
struct wlr_renderer *renderer;
struct wlr_allocator *allocator;
struct wlr_scene *scene;
struct wl_listener new_output;
};
struct output {
struct wl_list link;
struct server *server;
struct wlr_output *wlr;
struct wlr_scene_output *scene_output;
struct wl_listener frame;
};
static void output_handle_frame(struct wl_listener *listener, void *data) {
struct output *output = wl_container_of(listener, output, frame);
wlr_scene_output_commit(output->scene_output, NULL);
}
static void server_handle_new_output(struct wl_listener *listener, void *data) {
struct server *server = wl_container_of(listener, server, new_output);
struct wlr_output *wlr_output = data;
wlr_output_init_render(wlr_output, server->allocator, server->renderer);
struct output *output = calloc(1, sizeof(*output));
output->wlr = wlr_output;
output->server = server;
output->frame.notify = output_handle_frame;
wl_signal_add(&wlr_output->events.frame, &output->frame);
output->scene_output = wlr_scene_output_create(server->scene, wlr_output);
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_enabled(&state, true);
struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output);
if (mode != NULL) {
wlr_output_state_set_mode(&state, mode);
}
wlr_output_commit_state(wlr_output, &state);
wlr_output_state_finish(&state);
}
int main(void) {
wlr_log_init(WLR_DEBUG, NULL);
struct server server = {0};
server.display = wl_display_create();
server.backend = wlr_backend_autocreate(wl_display_get_event_loop(server.display), NULL);
server.scene = wlr_scene_create();
server.renderer = wlr_renderer_autocreate(server.backend);
wlr_renderer_init_wl_display(server.renderer, server.display);
server.allocator = wlr_allocator_autocreate(server.backend,
server.renderer);
server.new_output.notify = server_handle_new_output;
wl_signal_add(&server.backend->events.new_output, &server.new_output);
if (!wlr_backend_start(server.backend)) {
wl_display_destroy(server.display);
return EXIT_FAILURE;
}
struct cairo_buffer *buffer = create_cairo_buffer(256, 256);
if (!buffer) {
wl_display_destroy(server.display);
return EXIT_FAILURE;
}
/* Begin drawing
* From cairo samples at https://www.cairographics.org/samples/ */
cairo_t *cr = cairo_create(buffer->surface);
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_paint(cr);
cairo_set_source_rgb(cr, 0, 0, 0);
double x = 25.6, y = 128.0;
double x1 = 102.4, y1 = 230.4,
x2 = 153.6, y2 = 25.6,
x3 = 230.4, y3 = 128.0;
cairo_move_to(cr, x, y);
cairo_curve_to(cr, x1, y1, x2, y2, x3, y3);
cairo_set_line_width(cr, 10.0);
cairo_stroke(cr);
cairo_set_source_rgba(cr, 1, 0.2, 0.2, 0.6);
cairo_set_line_width(cr, 6.0);
cairo_move_to(cr, x, y);
cairo_line_to(cr, x1, y1);
cairo_move_to(cr, x2, y2);
cairo_line_to(cr, x3, y3);
cairo_stroke(cr);
cairo_destroy(cr);
/* End drawing */
struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_create(
&server.scene->tree, &buffer->base);
if (!scene_buffer) {
wl_display_destroy(server.display);
return EXIT_FAILURE;
}
wlr_scene_node_set_position(&scene_buffer->node, 50, 50);
wlr_buffer_drop(&buffer->base);
wl_display_run(server.display);
wl_display_destroy(server.display);
return EXIT_SUCCESS;
}

2641
examples/cat.c Normal file

File diff suppressed because it is too large Load diff

13
examples/cat.h Normal file
View file

@ -0,0 +1,13 @@
#ifndef _CAT_H
#define _CAT_H
struct gimp_texture {
unsigned int width;
unsigned int height;
unsigned int bytes_per_pixel; /* 2:RGB16, 3:RGB, 4:RGBA */
unsigned char pixel_data[128 * 128 * 4 + 1];
};
extern const struct gimp_texture cat_tex;
#endif

212
examples/embedded.c Normal file
View file

@ -0,0 +1,212 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-client.h>
#include <wayland-egl.h>
#include <wayland-server.h>
#include <wlr/backend/wayland.h>
#include <wlr/render/allocator.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>
#include "xdg-shell-client-protocol.h"
static struct wl_display *remote_display = NULL;
static struct wl_compositor *compositor = NULL;
static struct wl_subcompositor *subcompositor = NULL;
static struct xdg_wm_base *wm_base = NULL;
static struct wl_egl_window *egl_window = NULL;
static struct wlr_egl_surface *egl_surface = NULL;
static struct wl_surface *main_surface = NULL;
static struct wl_callback *frame_callback = NULL;
static struct wlr_scene *scene = NULL;
static struct wlr_scene_output *scene_output = NULL;
static struct wl_listener new_surface = {0};
static struct wl_listener output_frame = {0};
static EGLDisplay egl_display;
static EGLConfig egl_config;
static EGLContext egl_context;
static int width = 500;
static int height = 500;
static void draw_main_surface(void);
static void frame_handle_done(void *data, struct wl_callback *callback, uint32_t t) {
wl_callback_destroy(callback);
frame_callback = NULL;
draw_main_surface();
}
static const struct wl_callback_listener frame_listener = {
.done = frame_handle_done,
};
static void draw_main_surface(void) {
frame_callback = wl_surface_frame(main_surface);
wl_callback_add_listener(frame_callback, &frame_listener, NULL);
eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
eglSwapInterval(egl_display, 0);
glViewport(0, 0, width, height);
glClearColor(1, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(egl_display, egl_surface);
wl_display_flush(remote_display);
}
static void xdg_surface_handle_configure(void *data,
struct xdg_surface *xdg_surface, uint32_t serial) {
xdg_surface_ack_configure(xdg_surface, serial);
if (frame_callback == NULL) {
draw_main_surface();
}
}
static const struct xdg_surface_listener xdg_surface_listener = {
.configure = xdg_surface_handle_configure,
};
static void xdg_toplevel_handle_configure(void *data,
struct xdg_toplevel *xdg_toplevel, int32_t w, int32_t h,
struct wl_array *states) {
if (w != 0 && h != 0) {
width = w;
height = h;
}
}
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
.configure = xdg_toplevel_handle_configure,
};
static void handle_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version) {
if (strcmp(interface, wl_compositor_interface.name) == 0) {
compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4);
} else if (strcmp(interface, wl_subcompositor_interface.name) == 0) {
subcompositor = wl_registry_bind(registry, name, &wl_subcompositor_interface, 1);
} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
}
}
static void handle_global_remove(void *data, struct wl_registry *registry,
uint32_t name) {
// Who cares?
}
static const struct wl_registry_listener registry_listener = {
.global = handle_global,
.global_remove = handle_global_remove,
};
static void output_handle_frame(struct wl_listener *listener, void *data) {
wlr_scene_output_commit(scene_output, NULL);
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
wlr_scene_output_send_frame_done(scene_output, &now);
}
static void handle_new_surface(struct wl_listener *listener, void *data) {
struct wlr_surface *wlr_surface = data;
wlr_scene_surface_create(&scene->tree, wlr_surface);
}
static void init_egl(struct wl_display *display) {
egl_display = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, display, NULL);
eglInitialize(egl_display, NULL, NULL);
EGLint matched = 0;
const EGLint config_attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_NONE,
};
eglChooseConfig(egl_display, config_attribs, &egl_config, 1, &matched);
const EGLint context_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE,
};
egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, context_attribs);
}
int main(int argc, char *argv[]) {
wlr_log_init(WLR_DEBUG, NULL);
remote_display = wl_display_connect(NULL);
struct wl_registry *registry = wl_display_get_registry(remote_display);
wl_registry_add_listener(registry, &registry_listener, NULL);
wl_display_roundtrip(remote_display);
init_egl(remote_display);
struct wl_display *local_display = wl_display_create();
struct wl_event_loop *event_loop = wl_display_get_event_loop(local_display);
struct wlr_backend *backend = wlr_wl_backend_create(event_loop, remote_display);
struct wlr_renderer *renderer = wlr_renderer_autocreate(backend);
wlr_renderer_init_wl_display(renderer, local_display);
struct wlr_allocator *allocator = wlr_allocator_autocreate(backend, renderer);
scene = wlr_scene_create();
struct wlr_compositor *wlr_compositor = wlr_compositor_create(local_display, 5, renderer);
wlr_xdg_shell_create(local_display, 2);
new_surface.notify = handle_new_surface;
wl_signal_add(&wlr_compositor->events.new_surface, &new_surface);
wlr_backend_start(backend);
main_surface = wl_compositor_create_surface(compositor);
struct xdg_surface *xdg_surface = xdg_wm_base_get_xdg_surface(wm_base, main_surface);
struct xdg_toplevel *xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL);
egl_window = wl_egl_window_create(main_surface, width, height);
egl_surface = eglCreatePlatformWindowSurface(egl_display,
egl_config, egl_window, NULL);
struct wl_surface *child_surface = wl_compositor_create_surface(compositor);
struct wl_subsurface *subsurface = wl_subcompositor_get_subsurface(subcompositor, child_surface, main_surface);
wl_subsurface_set_position(subsurface, 20, 20);
struct wlr_output *output = wlr_wl_output_create_from_surface(backend, child_surface);
wlr_output_init_render(output, allocator, renderer);
scene_output = wlr_scene_output_create(scene, output);
output_frame.notify = output_handle_frame;
wl_signal_add(&output->events.frame, &output_frame);
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_enabled(&state, true);
wlr_output_commit_state(output, &state);
wlr_output_state_finish(&state);
wl_surface_commit(main_surface);
wl_display_flush(remote_display);
const char *socket = wl_display_add_socket_auto(local_display);
setenv("WAYLAND_DISPLAY", socket, true);
wlr_log(WLR_INFO, "Running embedded Wayland compositor on WAYLAND_DISPLAY=%s", socket);
wl_display_run(local_display);
return 0;
}

258
examples/fullscreen-shell.c Normal file
View file

@ -0,0 +1,258 @@
#include <getopt.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_fullscreen_shell_v1.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_output.h>
#include <wlr/util/box.h>
#include <wlr/util/log.h>
#include <wlr/util/transform.h>
/**
* A minimal fullscreen-shell server. It only supports rendering.
*/
struct fullscreen_server {
struct wl_display *wl_display;
struct wlr_backend *backend;
struct wlr_renderer *renderer;
struct wlr_allocator *allocator;
struct wlr_fullscreen_shell_v1 *fullscreen_shell;
struct wl_listener present_surface;
struct wlr_output_layout *output_layout;
struct wl_list outputs;
struct wl_listener new_output;
};
struct fullscreen_output {
struct wl_list link;
struct fullscreen_server *server;
struct wlr_output *wlr_output;
struct wlr_surface *surface;
struct wl_listener surface_destroy;
struct wl_listener frame;
};
struct render_data {
struct wlr_output *output;
struct wlr_render_pass *render_pass;
struct timespec *when;
};
static void render_surface(struct wlr_surface *surface,
int sx, int sy, void *data) {
struct render_data *rdata = data;
struct wlr_output *output = rdata->output;
struct wlr_texture *texture = wlr_surface_get_texture(surface);
if (texture == NULL) {
return;
}
struct wlr_box box = {
.x = sx * output->scale,
.y = sy * output->scale,
.width = surface->current.width * output->scale,
.height = surface->current.height * output->scale,
};
enum wl_output_transform transform = wlr_output_transform_invert(surface->current.transform);
transform = wlr_output_transform_compose(transform, output->transform);
wlr_render_pass_add_texture(rdata->render_pass, &(struct wlr_render_texture_options){
.texture = texture,
.dst_box = box,
.transform = transform,
});
wlr_surface_send_frame_done(surface, rdata->when);
}
static void output_handle_frame(struct wl_listener *listener, void *data) {
struct fullscreen_output *output =
wl_container_of(listener, output, frame);
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
int width, height;
wlr_output_effective_resolution(output->wlr_output, &width, &height);
struct wlr_output_state state;
wlr_output_state_init(&state);
struct wlr_render_pass *pass = wlr_output_begin_render_pass(output->wlr_output, &state, NULL,
NULL);
if (pass == NULL) {
return;
}
wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){
.color = { 0.3, 0.3, 0.3, 1.0 },
.box = { .width = width, .height = height },
});
if (output->surface != NULL) {
struct render_data rdata = {
.output = output->wlr_output,
.render_pass = pass,
.when = &now,
};
wlr_surface_for_each_surface(output->surface, render_surface, &rdata);
}
wlr_render_pass_submit(pass);
wlr_output_commit_state(output->wlr_output, &state);
wlr_output_state_finish(&state);
}
static void output_set_surface(struct fullscreen_output *output,
struct wlr_surface *surface);
static void output_handle_surface_destroy(struct wl_listener *listener,
void *data) {
struct fullscreen_output *output =
wl_container_of(listener, output, surface_destroy);
output_set_surface(output, NULL);
}
static void output_set_surface(struct fullscreen_output *output,
struct wlr_surface *surface) {
if (output->surface == surface) {
return;
}
if (output->surface != NULL) {
wl_list_remove(&output->surface_destroy.link);
output->surface = NULL;
}
if (surface != NULL) {
output->surface_destroy.notify = output_handle_surface_destroy;
wl_signal_add(&surface->events.destroy, &output->surface_destroy);
output->surface = surface;
}
wlr_log(WLR_DEBUG, "Presenting surface %p on output %s",
surface, output->wlr_output->name);
}
static void server_handle_new_output(struct wl_listener *listener, void *data) {
struct fullscreen_server *server =
wl_container_of(listener, server, new_output);
struct wlr_output *wlr_output = data;
wlr_output_init_render(wlr_output, server->allocator, server->renderer);
struct fullscreen_output *output = calloc(1, sizeof(*output));
output->wlr_output = wlr_output;
output->server = server;
output->frame.notify = output_handle_frame;
wl_signal_add(&wlr_output->events.frame, &output->frame);
wl_list_insert(&server->outputs, &output->link);
wlr_output_layout_add_auto(server->output_layout, wlr_output);
wlr_output_create_global(wlr_output, server->wl_display);
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_enabled(&state, true);
struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output);
if (mode != NULL) {
wlr_output_state_set_mode(&state, mode);
}
wlr_output_commit_state(wlr_output, &state);
wlr_output_state_finish(&state);
}
static void server_handle_present_surface(struct wl_listener *listener,
void *data) {
struct fullscreen_server *server =
wl_container_of(listener, server, present_surface);
struct wlr_fullscreen_shell_v1_present_surface_event *event = data;
struct fullscreen_output *output;
wl_list_for_each(output, &server->outputs, link) {
if (event->output == NULL || event->output == output->wlr_output) {
output_set_surface(output, event->surface);
}
}
}
int main(int argc, char *argv[]) {
wlr_log_init(WLR_DEBUG, NULL);
char *startup_cmd = NULL;
int c;
while ((c = getopt(argc, argv, "s:")) != -1) {
switch (c) {
case 's':
startup_cmd = optarg;
break;
default:
printf("usage: %s [-s startup-command]\n", argv[0]);
return EXIT_FAILURE;
}
}
if (optind < argc) {
printf("usage: %s [-s startup-command]\n", argv[0]);
return EXIT_FAILURE;
}
struct fullscreen_server server = {0};
server.wl_display = wl_display_create();
server.backend = wlr_backend_autocreate(wl_display_get_event_loop(server.wl_display), NULL);
server.renderer = wlr_renderer_autocreate(server.backend);
wlr_renderer_init_wl_display(server.renderer, server.wl_display);
server.allocator = wlr_allocator_autocreate(server.backend,
server.renderer);
wlr_compositor_create(server.wl_display, 5, server.renderer);
server.output_layout = wlr_output_layout_create(server.wl_display);
wl_list_init(&server.outputs);
server.new_output.notify = server_handle_new_output;
wl_signal_add(&server.backend->events.new_output, &server.new_output);
server.fullscreen_shell = wlr_fullscreen_shell_v1_create(server.wl_display);
server.present_surface.notify = server_handle_present_surface;
wl_signal_add(&server.fullscreen_shell->events.present_surface,
&server.present_surface);
const char *socket = wl_display_add_socket_auto(server.wl_display);
if (!socket) {
wl_display_destroy(server.wl_display);
return EXIT_FAILURE;
}
if (!wlr_backend_start(server.backend)) {
wl_display_destroy(server.wl_display);
return EXIT_FAILURE;
}
setenv("WAYLAND_DISPLAY", socket, true);
if (startup_cmd != NULL) {
if (fork() == 0) {
execl("/bin/sh", "/bin/sh", "-c", startup_cmd, (void *)NULL);
}
}
wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s",
socket);
wl_display_run(server.wl_display);
wl_display_destroy_clients(server.wl_display);
wl_display_destroy(server.wl_display);
return 0;
}

68
examples/meson.build Normal file
View file

@ -0,0 +1,68 @@
cairo = dependency('cairo', required: false, disabler: true)
# Only needed for drm_fourcc.h
libdrm_header = dependency('libdrm').partial_dependency(compile_args: true, includes: true)
wayland_client = dependency('wayland-client', required: false, disabler: true)
wayland_egl = dependency('wayland-egl', required: false, disabler: true)
egl = dependency('egl', version: '>= 1.5', required: false, disabler: true)
glesv2 = dependency('glesv2', required: false, disabler: true)
compositors = {
'simple': {
'src': 'simple.c',
},
'pointer': {
'src': 'pointer.c',
},
'touch': {
'src': ['touch.c', 'cat.c'],
},
'tablet': {
'src': 'tablet.c',
},
'rotation': {
'src': ['rotation.c', 'cat.c'],
},
'output-layout': {
'src': ['output-layout.c', 'cat.c'],
},
'fullscreen-shell': {
'src': 'fullscreen-shell.c',
'proto': ['fullscreen-shell-unstable-v1'],
},
'scene-graph': {
'src': 'scene-graph.c',
'proto': ['xdg-shell'],
},
'output-layers': {
'src': 'output-layers.c',
'proto': [
'xdg-shell',
],
},
'cairo-buffer': {
'src': 'cairo-buffer.c',
'dep': cairo,
},
'embedded': {
'src': [
'embedded.c',
protocols_code['xdg-shell'],
protocols_client_header['xdg-shell'],
],
'dep': [wayland_client, wayland_egl, egl, glesv2],
},
}
foreach name, info : compositors
extra_src = []
foreach p : info.get('proto', [])
extra_src += protocols_server_header[p]
endforeach
executable(
name,
[info.get('src'), extra_src],
dependencies: [wlroots, libdrm_header, info.get('dep', [])],
build_by_default: get_option('examples'),
)
endforeach

340
examples/output-layers.c Normal file
View file

@ -0,0 +1,340 @@
#include <getopt.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_drm.h>
#include <wlr/types/wlr_linux_dmabuf_v1.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_layer.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>
/* Simple compositor making use of the output layers API. The compositor will
* attempt to display client surfaces with output layers. Input is
* unimplemented.
*
* New surfaces are stacked on top of the existing ones as they appear.
* Surfaces that don't make it into an output layer are rendered as usual. */
struct server {
struct wl_display *wl_display;
struct wlr_backend *backend;
struct wlr_renderer *renderer;
struct wlr_allocator *allocator;
struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1;
struct wl_list outputs;
struct wl_listener new_output;
struct wl_listener new_surface;
};
struct output_surface {
struct wlr_surface *wlr_surface;
struct wlr_output_layer *layer;
struct server *server;
struct wl_list link;
int x, y;
struct wlr_buffer *buffer;
bool first_commit, layer_accepted, prev_layer_accepted;
struct wl_listener destroy;
struct wl_listener commit;
struct wl_listener layer_feedback;
};
struct output {
struct wl_list link;
struct server *server;
struct wlr_output *wlr_output;
struct wl_list surfaces;
struct wl_listener frame;
};
static void output_handle_frame(struct wl_listener *listener, void *data) {
struct output *output = wl_container_of(listener, output, frame);
struct wl_array layers_arr = {0};
struct output_surface *output_surface;
wl_list_for_each(output_surface, &output->surfaces, link) {
struct wlr_output_layer_state *layer_state =
wl_array_add(&layers_arr, sizeof(*layer_state));
*layer_state = (struct wlr_output_layer_state){
.layer = output_surface->layer,
.buffer = output_surface->buffer,
.dst_box = {
.x = output_surface->x,
.y = output_surface->y,
.width = output_surface->wlr_surface->current.width,
.height = output_surface->wlr_surface->current.height,
},
};
}
struct wlr_output_state output_state;
wlr_output_state_init(&output_state);
wlr_output_state_set_layers(&output_state, layers_arr.data,
layers_arr.size / sizeof(struct wlr_output_layer_state));
if (!wlr_output_test_state(output->wlr_output, &output_state)) {
wlr_log(WLR_ERROR, "wlr_output_test() failed");
return;
}
int width, height;
wlr_output_effective_resolution(output->wlr_output, &width, &height);
struct wlr_render_pass *pass = wlr_output_begin_render_pass(output->wlr_output, &output_state,
NULL, NULL);
wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){
.box = { .width = width, .height = height },
.color = { 0.3, 0.3, 0.3, 1 },
});
size_t i = 0;
struct wlr_output_layer_state *layers = layers_arr.data;
wl_list_for_each(output_surface, &output->surfaces, link) {
struct wlr_surface *wlr_surface = output_surface->wlr_surface;
output_surface->layer_accepted = layers[i].accepted;
i++;
if (wlr_surface->buffer == NULL || output_surface->layer_accepted) {
continue;
}
struct wlr_texture *texture = wlr_surface_get_texture(wlr_surface);
if (texture == NULL) {
continue;
}
wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options){
.texture = texture,
.dst_box = { .x = output_surface->x, .y = output_surface->y },
});
}
wlr_render_pass_submit(pass);
wlr_output_commit_state(output->wlr_output, &output_state);
wlr_output_state_finish(&output_state);
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
wl_list_for_each(output_surface, &output->surfaces, link) {
wlr_surface_send_frame_done(output_surface->wlr_surface, &now);
if (output_surface->wlr_surface->buffer == NULL) {
continue;
}
if ((output_surface->first_commit ||
!output_surface->prev_layer_accepted) &&
output_surface->layer_accepted) {
wlr_log(WLR_INFO, "Scanning out wlr_surface %p on output '%s'",
output_surface->wlr_surface, output->wlr_output->name);
}
if ((output_surface->first_commit ||
output_surface->prev_layer_accepted) &&
!output_surface->layer_accepted) {
wlr_log(WLR_INFO, "Cannot scan out wlr_surface %p on output '%s'",
output_surface->wlr_surface, output->wlr_output->name);
}
output_surface->prev_layer_accepted = output_surface->layer_accepted;
output_surface->first_commit = false;
}
wl_array_release(&layers_arr);
}
static void server_handle_new_output(struct wl_listener *listener, void *data) {
struct server *server = wl_container_of(listener, server, new_output);
struct wlr_output *wlr_output = data;
wlr_output_init_render(wlr_output, server->allocator, server->renderer);
struct output *output = calloc(1, sizeof(*output));
output->wlr_output = wlr_output;
output->server = server;
wl_list_init(&output->surfaces);
output->frame.notify = output_handle_frame;
wl_signal_add(&wlr_output->events.frame, &output->frame);
wl_list_insert(&server->outputs, &output->link);
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_enabled(&state, true);
struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output);
if (mode != NULL) {
wlr_output_state_set_mode(&state, mode);
}
wlr_output_commit_state(wlr_output, &state);
wlr_output_state_finish(&state);
wlr_output_create_global(wlr_output, server->wl_display);
}
static void output_surface_handle_destroy(struct wl_listener *listener,
void *data) {
struct output_surface *output_surface =
wl_container_of(listener, output_surface, destroy);
wlr_buffer_unlock(output_surface->buffer);
wl_list_remove(&output_surface->destroy.link);
wl_list_remove(&output_surface->commit.link);
wl_list_remove(&output_surface->layer_feedback.link);
wl_list_remove(&output_surface->link);
wlr_output_layer_destroy(output_surface->layer);
free(output_surface);
}
static void output_surface_handle_commit(struct wl_listener *listener,
void *data) {
struct output_surface *output_surface =
wl_container_of(listener, output_surface, commit);
struct wlr_buffer *buffer = NULL;
if (output_surface->wlr_surface->buffer != NULL) {
buffer = wlr_buffer_lock(&output_surface->wlr_surface->buffer->base);
}
wlr_buffer_unlock(output_surface->buffer);
output_surface->buffer = buffer;
}
static void output_surface_handle_layer_feedback(struct wl_listener *listener,
void *data) {
struct output_surface *output_surface =
wl_container_of(listener, output_surface, layer_feedback);
const struct wlr_output_layer_feedback_event *event = data;
wlr_log(WLR_DEBUG, "Sending linux-dmabuf feedback to surface %p",
output_surface->wlr_surface);
struct wlr_linux_dmabuf_feedback_v1 feedback = {0};
const struct wlr_linux_dmabuf_feedback_v1_init_options options = {
.main_renderer = output_surface->server->renderer,
.output_layer_feedback_event = event,
};
wlr_linux_dmabuf_feedback_v1_init_with_options(&feedback, &options);
wlr_linux_dmabuf_v1_set_surface_feedback(output_surface->server->linux_dmabuf_v1,
output_surface->wlr_surface, &feedback);
wlr_linux_dmabuf_feedback_v1_finish(&feedback);
}
static void server_handle_new_surface(struct wl_listener *listener,
void *data) {
struct server *server = wl_container_of(listener, server, new_surface);
struct wlr_surface *wlr_surface = data;
struct output *output;
wl_list_for_each(output, &server->outputs, link) {
struct output_surface *output_surface = calloc(1, sizeof(*output_surface));
output_surface->wlr_surface = wlr_surface;
output_surface->server = server;
output_surface->destroy.notify = output_surface_handle_destroy;
wl_signal_add(&wlr_surface->events.destroy, &output_surface->destroy);
output_surface->commit.notify = output_surface_handle_commit;
wl_signal_add(&wlr_surface->events.commit, &output_surface->commit);
output_surface->layer = wlr_output_layer_create(output->wlr_output);
output_surface->layer_feedback.notify = output_surface_handle_layer_feedback;
wl_signal_add(&output_surface->layer->events.feedback,
&output_surface->layer_feedback);
int pos = 50 * wl_list_length(&output->surfaces);
output_surface->x = output_surface->y = pos;
output_surface->first_commit = true;
wl_list_insert(output->surfaces.prev, &output_surface->link);
}
}
int main(int argc, char *argv[]) {
wlr_log_init(WLR_DEBUG, NULL);
char *startup_cmd = NULL;
int c;
while ((c = getopt(argc, argv, "s:")) != -1) {
switch (c) {
case 's':
startup_cmd = optarg;
break;
default:
printf("usage: %s [-s startup-command]\n", argv[0]);
return EXIT_FAILURE;
}
}
if (optind < argc) {
printf("usage: %s [-s startup-command]\n", argv[0]);
return EXIT_FAILURE;
}
struct server server = {0};
server.wl_display = wl_display_create();
server.backend = wlr_backend_autocreate(wl_display_get_event_loop(server.wl_display), NULL);
server.renderer = wlr_renderer_autocreate(server.backend);
wlr_renderer_init_wl_shm(server.renderer, server.wl_display);
if (wlr_renderer_get_dmabuf_texture_formats(server.renderer) != NULL) {
wlr_drm_create(server.wl_display, server.renderer);
server.linux_dmabuf_v1 = wlr_linux_dmabuf_v1_create_with_renderer(
server.wl_display, 4, server.renderer);
}
server.allocator = wlr_allocator_autocreate(server.backend,
server.renderer);
struct wlr_compositor *compositor =
wlr_compositor_create(server.wl_display, 5, server.renderer);
wlr_xdg_shell_create(server.wl_display, 1);
wl_list_init(&server.outputs);
server.new_output.notify = server_handle_new_output;
wl_signal_add(&server.backend->events.new_output, &server.new_output);
server.new_surface.notify = server_handle_new_surface;
wl_signal_add(&compositor->events.new_surface, &server.new_surface);
const char *socket = wl_display_add_socket_auto(server.wl_display);
if (!socket) {
wl_display_destroy(server.wl_display);
return EXIT_FAILURE;
}
if (!wlr_backend_start(server.backend)) {
wl_display_destroy(server.wl_display);
return EXIT_FAILURE;
}
setenv("WAYLAND_DISPLAY", socket, true);
if (startup_cmd != NULL) {
if (fork() == 0) {
execlp("sh", "sh", "-c", startup_cmd, (char *)NULL);
}
}
wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s",
socket);
wl_display_run(server.wl_display);
wl_display_destroy_clients(server.wl_display);
wl_display_destroy(server.wl_display);
return EXIT_SUCCESS;
}

304
examples/output-layout.c Normal file
View file

@ -0,0 +1,304 @@
#include <drm_fourcc.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/backend/session.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_output.h>
#include <wlr/util/box.h>
#include <wlr/util/log.h>
#include <xkbcommon/xkbcommon.h>
#include "cat.h"
struct sample_state {
struct wl_display *display;
struct wl_listener new_output;
struct wl_listener new_input;
struct wlr_renderer *renderer;
struct wlr_allocator *allocator;
struct wlr_texture *cat_texture;
struct wlr_output_layout *layout;
float x_offs, y_offs;
float x_vel, y_vel;
struct timespec ts_last;
};
struct sample_output {
struct sample_state *sample;
struct wlr_output *output;
struct wl_listener frame;
struct wl_listener destroy;
};
struct sample_keyboard {
struct sample_state *sample;
struct wlr_keyboard *wlr_keyboard;
struct wl_listener key;
struct wl_listener destroy;
};
static void animate_cat(struct sample_state *sample,
struct wlr_output *output) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
long ms = (ts.tv_sec - sample->ts_last.tv_sec) * 1000 +
(ts.tv_nsec - sample->ts_last.tv_nsec) / 1000000;
// how many seconds have passed since the last time we animated
float seconds = ms / 1000.0f;
if (seconds > 0.1f) {
// XXX when we switch vt, the rendering loop stops so try to detect
// that and pause when it happens.
seconds = 0.0f;
}
// check for collisions and bounce
bool ur_collision = !wlr_output_layout_output_at(sample->layout,
sample->x_offs + 128, sample->y_offs);
bool ul_collision = !wlr_output_layout_output_at(sample->layout,
sample->x_offs, sample->y_offs);
bool ll_collision = !wlr_output_layout_output_at(sample->layout,
sample->x_offs, sample->y_offs + 128);
bool lr_collision = !wlr_output_layout_output_at(sample->layout,
sample->x_offs + 128, sample->y_offs + 128);
if (ur_collision && ul_collision && ll_collision && lr_collision) {
// oops we went off the screen somehow
struct wlr_output_layout_output *l_output =
wlr_output_layout_get(sample->layout, output);
sample->x_offs = l_output->x + 20;
sample->y_offs = l_output->y + 20;
} else if (ur_collision && ul_collision) {
sample->y_vel = fabs(sample->y_vel);
} else if (lr_collision && ll_collision) {
sample->y_vel = -fabs(sample->y_vel);
} else if (ll_collision && ul_collision) {
sample->x_vel = fabs(sample->x_vel);
} else if (ur_collision && lr_collision) {
sample->x_vel = -fabs(sample->x_vel);
} else {
if (ur_collision || lr_collision) {
sample->x_vel = -fabs(sample->x_vel);
}
if (ul_collision || ll_collision) {
sample->x_vel = fabs(sample->x_vel);
}
if (ul_collision || ur_collision) {
sample->y_vel = fabs(sample->y_vel);
}
if (ll_collision || lr_collision) {
sample->y_vel = -fabs(sample->y_vel);
}
}
sample->x_offs += sample->x_vel * seconds;
sample->y_offs += sample->y_vel * seconds;
sample->ts_last = ts;
}
static void output_frame_notify(struct wl_listener *listener, void *data) {
struct sample_output *output = wl_container_of(listener, output, frame);
struct sample_state *sample = output->sample;
struct wlr_output *wlr_output = output->output;
struct wlr_output_state output_state;
wlr_output_state_init(&output_state);
struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL, NULL);
wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){
.box = { .width = wlr_output->width, .height = wlr_output->height },
.color = { 0.25, 0.25, 0.25, 1 },
});
animate_cat(sample, output->output);
struct wlr_box box = {
.x = sample->x_offs, .y = sample->y_offs,
.width = 128, .height = 128,
};
if (wlr_output_layout_intersects(sample->layout, output->output, &box)) {
// transform global coordinates to local coordinates
double local_x = sample->x_offs;
double local_y = sample->y_offs;
wlr_output_layout_output_coords(sample->layout, output->output,
&local_x, &local_y);
wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options){
.texture = sample->cat_texture,
.dst_box = box,
});
}
wlr_render_pass_submit(pass);
wlr_output_commit_state(wlr_output, &output_state);
wlr_output_state_finish(&output_state);
}
static void update_velocities(struct sample_state *sample,
float x_diff, float y_diff) {
sample->x_vel += x_diff;
sample->y_vel += y_diff;
}
static void output_remove_notify(struct wl_listener *listener, void *data) {
struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy);
struct sample_state *sample = sample_output->sample;
wlr_output_layout_remove(sample->layout, sample_output->output);
wl_list_remove(&sample_output->frame.link);
wl_list_remove(&sample_output->destroy.link);
free(sample_output);
}
static void new_output_notify(struct wl_listener *listener, void *data) {
struct wlr_output *output = data;
struct sample_state *sample = wl_container_of(listener, sample, new_output);
wlr_output_init_render(output, sample->allocator, sample->renderer);
struct sample_output *sample_output = calloc(1, sizeof(*sample_output));
wlr_output_layout_add_auto(sample->layout, output);
sample_output->output = output;
sample_output->sample = sample;
wl_signal_add(&output->events.frame, &sample_output->frame);
sample_output->frame.notify = output_frame_notify;
wl_signal_add(&output->events.destroy, &sample_output->destroy);
sample_output->destroy.notify = output_remove_notify;
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_enabled(&state, true);
struct wlr_output_mode *mode = wlr_output_preferred_mode(output);
if (mode != NULL) {
wlr_output_state_set_mode(&state, mode);
}
wlr_output_commit_state(output, &state);
wlr_output_state_finish(&state);
}
static void keyboard_key_notify(struct wl_listener *listener, void *data) {
struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, key);
struct sample_state *sample = keyboard->sample;
struct wlr_keyboard_key_event *event = data;
uint32_t keycode = event->keycode + 8;
const xkb_keysym_t *syms;
int nsyms = xkb_state_key_get_syms(keyboard->wlr_keyboard->xkb_state,
keycode, &syms);
for (int i = 0; i < nsyms; i++) {
xkb_keysym_t sym = syms[i];
if (sym == XKB_KEY_Escape) {
wl_display_terminate(sample->display);
}
// NOTE: It may be better to simply refer to our key state during each frame
// and make this change in pixels/sec^2
// Also, key repeat
int delta = 75;
if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
switch (sym) {
case XKB_KEY_Left:
update_velocities(sample, -delta, 0);
break;
case XKB_KEY_Right:
update_velocities(sample, delta, 0);
break;
case XKB_KEY_Up:
update_velocities(sample, 0, -delta);
break;
case XKB_KEY_Down:
update_velocities(sample, 0, delta);
break;
}
}
}
}
static void keyboard_destroy_notify(struct wl_listener *listener, void *data) {
struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, destroy);
wl_list_remove(&keyboard->destroy.link);
wl_list_remove(&keyboard->key.link);
free(keyboard);
}
static void new_input_notify(struct wl_listener *listener, void *data) {
struct wlr_input_device *device = data;
struct sample_state *sample = wl_container_of(listener, sample, new_input);
switch (device->type) {
case WLR_INPUT_DEVICE_KEYBOARD:;
struct sample_keyboard *keyboard = calloc(1, sizeof(*keyboard));
keyboard->wlr_keyboard = wlr_keyboard_from_input_device(device);
keyboard->sample = sample;
wl_signal_add(&device->events.destroy, &keyboard->destroy);
keyboard->destroy.notify = keyboard_destroy_notify;
wl_signal_add(&keyboard->wlr_keyboard->events.key, &keyboard->key);
keyboard->key.notify = keyboard_key_notify;
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!context) {
wlr_log(WLR_ERROR, "Failed to create XKB context");
exit(1);
}
struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL,
XKB_KEYMAP_COMPILE_NO_FLAGS);
if (!keymap) {
wlr_log(WLR_ERROR, "Failed to create XKB keymap");
exit(1);
}
wlr_keyboard_set_keymap(keyboard->wlr_keyboard, keymap);
xkb_keymap_unref(keymap);
xkb_context_unref(context);
break;
default:
break;
}
}
int main(int argc, char *argv[]) {
wlr_log_init(WLR_DEBUG, NULL);
struct wl_display *display = wl_display_create();
struct sample_state state = {
.x_vel = 500,
.y_vel = 500,
.display = display,
};
state.layout = wlr_output_layout_create(display);
clock_gettime(CLOCK_MONOTONIC, &state.ts_last);
struct wlr_backend *wlr = wlr_backend_autocreate(wl_display_get_event_loop(display), NULL);
if (!wlr) {
exit(1);
}
wl_signal_add(&wlr->events.new_output, &state.new_output);
state.new_output.notify = new_output_notify;
wl_signal_add(&wlr->events.new_input, &state.new_input);
state.new_input.notify = new_input_notify;
state.renderer = wlr_renderer_autocreate(wlr);
state.cat_texture = wlr_texture_from_pixels(state.renderer,
DRM_FORMAT_ABGR8888, cat_tex.width * 4, cat_tex.width, cat_tex.height,
cat_tex.pixel_data);
state.allocator = wlr_allocator_autocreate(wlr, state.renderer);
if (!wlr_backend_start(wlr)) {
wlr_log(WLR_ERROR, "Failed to start backend");
wlr_backend_destroy(wlr);
exit(1);
}
wl_display_run(display);
wlr_texture_destroy(state.cat_texture);
wl_display_destroy(state.display);
}

416
examples/pointer.c Normal file
View file

@ -0,0 +1,416 @@
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/backend/session.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_pointer.h>
#include <wlr/types/wlr_tablet_tool.h>
#include <wlr/types/wlr_touch.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/util/log.h>
#include <xkbcommon/xkbcommon.h>
struct sample_state {
struct wl_display *display;
struct compositor_state *compositor;
struct wlr_renderer *renderer;
struct wlr_allocator *allocator;
struct wlr_xcursor_manager *xcursor_manager;
struct wlr_cursor *cursor;
double cur_x, cur_y;
float default_color[4];
float clear_color[4];
struct wlr_output_layout *layout;
struct wl_list devices;
struct timespec last_frame;
struct wl_listener new_output;
struct wl_listener new_input;
struct wl_listener cursor_motion;
struct wl_listener cursor_motion_absolute;
struct wl_listener cursor_button;
struct wl_listener cursor_axis;
struct wl_listener touch_motion;
struct wl_listener touch_up;
struct wl_listener touch_down;
struct wl_listener touch_cancel;
struct wl_list touch_points;
struct wl_listener tablet_tool_axis;
struct wl_listener tablet_tool_proxmity;
struct wl_listener tablet_tool_tip;
struct wl_listener tablet_tool_button;
};
struct touch_point {
int32_t touch_id;
double x, y;
struct wl_list link;
};
struct sample_output {
struct sample_state *state;
struct wlr_output *output;
struct wl_listener frame;
struct wl_listener destroy;
};
struct sample_keyboard {
struct sample_state *state;
struct wlr_keyboard *wlr_keyboard;
struct wl_listener key;
struct wl_listener destroy;
};
static void warp_to_touch(struct sample_state *state,
struct wlr_input_device *dev) {
if (wl_list_empty(&state->touch_points)) {
return;
}
double x = 0, y = 0;
size_t n = 0;
struct touch_point *point;
wl_list_for_each(point, &state->touch_points, link) {
x += point->x;
y += point->y;
n++;
}
x /= n;
y /= n;
wlr_cursor_warp_absolute(state->cursor, dev, x, y);
}
static void output_frame_notify(struct wl_listener *listener, void *data) {
struct sample_output *sample_output = wl_container_of(listener, sample_output, frame);
struct sample_state *state = sample_output->state;
struct wlr_output *wlr_output = sample_output->output;
struct wlr_renderer *renderer = state->renderer;
assert(renderer);
struct wlr_output_state output_state;
wlr_output_state_init(&output_state);
struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL, NULL);
wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){
.box = { .width = wlr_output->width, .height = wlr_output->height },
.color = {
state->clear_color[0],
state->clear_color[1],
state->clear_color[2],
state->clear_color[3],
},
});
wlr_output_add_software_cursors_to_render_pass(wlr_output, pass, NULL);
wlr_render_pass_submit(pass);
wlr_output_commit_state(wlr_output, &output_state);
wlr_output_state_finish(&output_state);
}
static void handle_cursor_motion(struct wl_listener *listener, void *data) {
struct sample_state *sample =
wl_container_of(listener, sample, cursor_motion);
struct wlr_pointer_motion_event *event = data;
wlr_cursor_move(sample->cursor, &event->pointer->base, event->delta_x,
event->delta_y);
}
static void handle_cursor_motion_absolute(struct wl_listener *listener,
void *data) {
struct sample_state *sample =
wl_container_of(listener, sample, cursor_motion_absolute);
struct wlr_pointer_motion_absolute_event *event = data;
sample->cur_x = event->x;
sample->cur_y = event->y;
wlr_cursor_warp_absolute(sample->cursor, &event->pointer->base,
sample->cur_x, sample->cur_y);
}
static void handle_cursor_button(struct wl_listener *listener, void *data) {
struct sample_state *sample =
wl_container_of(listener, sample, cursor_button);
struct wlr_pointer_button_event *event = data;
float (*color)[4];
if (event->state == WL_POINTER_BUTTON_STATE_RELEASED) {
color = &sample->default_color;
memcpy(&sample->clear_color, color, sizeof(*color));
} else {
float red[4] = { 0.25f, 0.25f, 0.25f, 1 };
red[event->button % 3] = 1;
color = &red;
memcpy(&sample->clear_color, color, sizeof(*color));
}
}
static void handle_cursor_axis(struct wl_listener *listener, void *data) {
struct sample_state *sample =
wl_container_of(listener, sample, cursor_axis);
struct wlr_pointer_axis_event *event = data;
for (size_t i = 0; i < 3; ++i) {
sample->default_color[i] += event->delta > 0 ? -0.05f : 0.05f;
if (sample->default_color[i] > 1.0f) {
sample->default_color[i] = 1.0f;
}
if (sample->default_color[i] < 0.0f) {
sample->default_color[i] = 0.0f;
}
}
memcpy(&sample->clear_color, &sample->default_color,
sizeof(sample->clear_color));
}
static void handle_touch_up(struct wl_listener *listener, void *data) {
struct sample_state *sample = wl_container_of(listener, sample, touch_up);
struct wlr_touch_up_event *event = data;
struct touch_point *point, *tmp;
wl_list_for_each_safe(point, tmp, &sample->touch_points, link) {
if (point->touch_id == event->touch_id) {
wl_list_remove(&point->link);
break;
}
}
warp_to_touch(sample, &event->touch->base);
}
static void handle_touch_down(struct wl_listener *listener, void *data) {
struct sample_state *sample = wl_container_of(listener, sample, touch_down);
struct wlr_touch_down_event *event = data;
struct touch_point *point = calloc(1, sizeof(*point));
point->touch_id = event->touch_id;
point->x = event->x;
point->y = event->y;
wl_list_insert(&sample->touch_points, &point->link);
warp_to_touch(sample, &event->touch->base);
}
static void handle_touch_motion(struct wl_listener *listener, void *data) {
struct sample_state *sample =
wl_container_of(listener, sample, touch_motion);
struct wlr_touch_motion_event *event = data;
struct touch_point *point;
wl_list_for_each(point, &sample->touch_points, link) {
if (point->touch_id == event->touch_id) {
point->x = event->x;
point->y = event->y;
break;
}
}
warp_to_touch(sample, &event->touch->base);
}
static void handle_touch_cancel(struct wl_listener *listener, void *data) {
wlr_log(WLR_DEBUG, "TODO: touch cancel");
}
static void handle_tablet_tool_axis(struct wl_listener *listener, void *data) {
struct sample_state *sample =
wl_container_of(listener, sample, tablet_tool_axis);
struct wlr_tablet_tool_axis_event *event = data;
if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X) &&
(event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) {
wlr_cursor_warp_absolute(sample->cursor, &event->tablet->base,
event->x, event->y);
}
}
static void keyboard_key_notify(struct wl_listener *listener, void *data) {
struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, key);
struct sample_state *sample = keyboard->state;
struct wlr_keyboard_key_event *event = data;
uint32_t keycode = event->keycode + 8;
const xkb_keysym_t *syms;
int nsyms = xkb_state_key_get_syms(keyboard->wlr_keyboard->xkb_state,
keycode, &syms);
for (int i = 0; i < nsyms; i++) {
xkb_keysym_t sym = syms[i];
if (sym == XKB_KEY_Escape) {
wl_display_terminate(sample->display);
}
}
}
static void output_remove_notify(struct wl_listener *listener, void *data) {
struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy);
struct sample_state *sample = sample_output->state;
wlr_output_layout_remove(sample->layout, sample_output->output);
wl_list_remove(&sample_output->frame.link);
wl_list_remove(&sample_output->destroy.link);
free(sample_output);
}
static void new_output_notify(struct wl_listener *listener, void *data) {
struct wlr_output *output = data;
struct sample_state *sample = wl_container_of(listener, sample, new_output);
wlr_output_init_render(output, sample->allocator, sample->renderer);
struct sample_output *sample_output = calloc(1, sizeof(*sample_output));
sample_output->output = output;
sample_output->state = sample;
wl_signal_add(&output->events.frame, &sample_output->frame);
sample_output->frame.notify = output_frame_notify;
wl_signal_add(&output->events.destroy, &sample_output->destroy);
sample_output->destroy.notify = output_remove_notify;
wlr_output_layout_add_auto(sample->layout, sample_output->output);
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_enabled(&state, true);
struct wlr_output_mode *mode = wlr_output_preferred_mode(output);
if (mode != NULL) {
wlr_output_state_set_mode(&state, mode);
}
wlr_output_commit_state(output, &state);
wlr_output_state_finish(&state);
}
static void keyboard_destroy_notify(struct wl_listener *listener, void *data) {
struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, destroy);
wl_list_remove(&keyboard->destroy.link);
wl_list_remove(&keyboard->key.link);
free(keyboard);
}
static void new_input_notify(struct wl_listener *listener, void *data) {
struct wlr_input_device *device = data;
struct sample_state *state = wl_container_of(listener, state, new_input);
switch (device->type) {
case WLR_INPUT_DEVICE_POINTER:
case WLR_INPUT_DEVICE_TOUCH:
case WLR_INPUT_DEVICE_TABLET:
wlr_cursor_attach_input_device(state->cursor, device);
break;
case WLR_INPUT_DEVICE_KEYBOARD:;
struct sample_keyboard *keyboard = calloc(1, sizeof(*keyboard));
keyboard->wlr_keyboard = wlr_keyboard_from_input_device(device);
keyboard->state = state;
wl_signal_add(&device->events.destroy, &keyboard->destroy);
keyboard->destroy.notify = keyboard_destroy_notify;
wl_signal_add(&keyboard->wlr_keyboard->events.key, &keyboard->key);
keyboard->key.notify = keyboard_key_notify;
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!context) {
wlr_log(WLR_ERROR, "Failed to create XKB context");
exit(1);
}
struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL,
XKB_KEYMAP_COMPILE_NO_FLAGS);
if (!keymap) {
wlr_log(WLR_ERROR, "Failed to create XKB keymap");
exit(1);
}
wlr_keyboard_set_keymap(keyboard->wlr_keyboard, keymap);
xkb_keymap_unref(keymap);
xkb_context_unref(context);
break;
default:
break;
}
}
int main(int argc, char *argv[]) {
wlr_log_init(WLR_DEBUG, NULL);
struct wl_display *display = wl_display_create();
struct sample_state state = {
.default_color = { 0.25f, 0.25f, 0.25f, 1 },
.clear_color = { 0.25f, 0.25f, 0.25f, 1 },
.display = display
};
struct wlr_backend *wlr = wlr_backend_autocreate(wl_display_get_event_loop(display), NULL);
if (!wlr) {
exit(1);
}
state.renderer = wlr_renderer_autocreate(wlr);
state.allocator = wlr_allocator_autocreate(wlr, state.renderer);
state.cursor = wlr_cursor_create();
state.layout = wlr_output_layout_create(display);
wlr_cursor_attach_output_layout(state.cursor, state.layout);
//wlr_cursor_map_to_region(state.cursor, state.config->cursor.mapped_box);
wl_list_init(&state.devices);
wl_list_init(&state.touch_points);
// pointer events
wl_signal_add(&state.cursor->events.motion, &state.cursor_motion);
state.cursor_motion.notify = handle_cursor_motion;
wl_signal_add(&state.cursor->events.motion_absolute,
&state.cursor_motion_absolute);
state.cursor_motion_absolute.notify = handle_cursor_motion_absolute;
wl_signal_add(&state.cursor->events.button, &state.cursor_button);
state.cursor_button.notify = handle_cursor_button;
wl_signal_add(&state.cursor->events.axis, &state.cursor_axis);
state.cursor_axis.notify = handle_cursor_axis;
// touch events
wl_signal_add(&state.cursor->events.touch_up, &state.touch_up);
state.touch_up.notify = handle_touch_up;
wl_signal_add(&state.cursor->events.touch_down, &state.touch_down);
state.touch_down.notify = handle_touch_down;
wl_signal_add(&state.cursor->events.touch_motion, &state.touch_motion);
state.touch_motion.notify = handle_touch_motion;
wl_signal_add(&state.cursor->events.touch_cancel, &state.touch_cancel);
state.touch_cancel.notify = handle_touch_cancel;
wl_signal_add(&wlr->events.new_input, &state.new_input);
state.new_input.notify = new_input_notify;
wl_signal_add(&wlr->events.new_output, &state.new_output);
state.new_output.notify = new_output_notify;
// tool events
wl_signal_add(&state.cursor->events.tablet_tool_axis,
&state.tablet_tool_axis);
state.tablet_tool_axis.notify = handle_tablet_tool_axis;
state.xcursor_manager = wlr_xcursor_manager_create(NULL, 24);
if (!state.xcursor_manager) {
wlr_log(WLR_ERROR, "Failed to load default cursor");
return 1;
}
wlr_cursor_set_xcursor(state.cursor, state.xcursor_manager, "default");
clock_gettime(CLOCK_MONOTONIC, &state.last_frame);
if (!wlr_backend_start(wlr)) {
wlr_log(WLR_ERROR, "Failed to start backend");
wlr_backend_destroy(wlr);
exit(1);
}
wl_display_run(display);
wl_display_destroy(display);
wlr_xcursor_manager_destroy(state.xcursor_manager);
wlr_cursor_destroy(state.cursor);
}

288
examples/rotation.c Normal file
View file

@ -0,0 +1,288 @@
#include <drm_fourcc.h>
#include <getopt.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_input_device.h>
#include <wlr/util/log.h>
#include <xkbcommon/xkbcommon.h>
#include "cat.h"
struct sample_state {
struct wl_display *display;
struct wl_listener new_output;
struct wl_listener new_input;
struct timespec last_frame;
struct wlr_renderer *renderer;
struct wlr_allocator *allocator;
struct wlr_texture *cat_texture;
struct wl_list outputs;
enum wl_output_transform transform;
};
struct sample_output {
struct sample_state *sample;
struct wlr_output *output;
struct wl_listener frame;
struct wl_listener destroy;
float x_offs, y_offs;
float x_vel, y_vel;
struct wl_list link;
};
struct sample_keyboard {
struct sample_state *sample;
struct wlr_keyboard *wlr_keyboard;
struct wl_listener key;
struct wl_listener destroy;
};
static void output_frame_notify(struct wl_listener *listener, void *data) {
struct sample_output *sample_output = wl_container_of(listener, sample_output, frame);
struct sample_state *sample = sample_output->sample;
struct wlr_output *wlr_output = sample_output->output;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
int32_t width, height;
wlr_output_effective_resolution(wlr_output, &width, &height);
struct wlr_output_state output_state;
wlr_output_state_init(&output_state);
struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL, NULL);
wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){
.box = { .width = wlr_output->width, .height = wlr_output->height },
.color = { 0.25, 0.25, 0.25, 1 },
});
for (int y = -128 + (int)sample_output->y_offs; y < height; y += 128) {
for (int x = -128 + (int)sample_output->x_offs; x < width; x += 128) {
wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options){
.texture = sample->cat_texture,
.dst_box = { .x = x, .y = y },
.transform = wlr_output->transform,
});
}
}
wlr_render_pass_submit(pass);
wlr_output_commit_state(wlr_output, &output_state);
wlr_output_state_finish(&output_state);
long ms = (now.tv_sec - sample->last_frame.tv_sec) * 1000 +
(now.tv_nsec - sample->last_frame.tv_nsec) / 1000000;
float seconds = ms / 1000.0f;
sample_output->x_offs += sample_output->x_vel * seconds;
sample_output->y_offs += sample_output->y_vel * seconds;
if (sample_output->x_offs > 128) {
sample_output->x_offs = 0;
}
if (sample_output->y_offs > 128) {
sample_output->y_offs = 0;
}
sample->last_frame = now;
}
static void update_velocities(struct sample_state *sample,
float x_diff, float y_diff) {
struct sample_output *sample_output;
wl_list_for_each(sample_output, &sample->outputs, link) {
sample_output->x_vel += x_diff;
sample_output->y_vel += y_diff;
}
}
static void output_remove_notify(struct wl_listener *listener, void *data) {
struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy);
wl_list_remove(&sample_output->frame.link);
wl_list_remove(&sample_output->destroy.link);
free(sample_output);
}
static void new_output_notify(struct wl_listener *listener, void *data) {
struct wlr_output *output = data;
struct sample_state *sample = wl_container_of(listener, sample, new_output);
wlr_output_init_render(output, sample->allocator, sample->renderer);
struct sample_output *sample_output = calloc(1, sizeof(*sample_output));
sample_output->x_offs = sample_output->y_offs = 0;
sample_output->x_vel = sample_output->y_vel = 128;
sample_output->output = output;
sample_output->sample = sample;
wl_signal_add(&output->events.frame, &sample_output->frame);
sample_output->frame.notify = output_frame_notify;
wl_signal_add(&output->events.destroy, &sample_output->destroy);
sample_output->destroy.notify = output_remove_notify;
wl_list_insert(&sample->outputs, &sample_output->link);
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_transform(&state, sample->transform);
wlr_output_state_set_enabled(&state, true);
struct wlr_output_mode *mode = wlr_output_preferred_mode(output);
if (mode != NULL) {
wlr_output_state_set_mode(&state, mode);
}
wlr_output_commit_state(output, &state);
wlr_output_state_finish(&state);
}
static void keyboard_key_notify(struct wl_listener *listener, void *data) {
struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, key);
struct sample_state *sample = keyboard->sample;
struct wlr_keyboard_key_event *event = data;
uint32_t keycode = event->keycode + 8;
const xkb_keysym_t *syms;
int nsyms = xkb_state_key_get_syms(keyboard->wlr_keyboard->xkb_state,
keycode, &syms);
for (int i = 0; i < nsyms; i++) {
xkb_keysym_t sym = syms[i];
if (sym == XKB_KEY_Escape) {
wl_display_terminate(sample->display);
}
if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
switch (sym) {
case XKB_KEY_Left:
update_velocities(sample, -16, 0);
break;
case XKB_KEY_Right:
update_velocities(sample, 16, 0);
break;
case XKB_KEY_Up:
update_velocities(sample, 0, -16);
break;
case XKB_KEY_Down:
update_velocities(sample, 0, 16);
break;
}
}
}
}
static void keyboard_destroy_notify(struct wl_listener *listener, void *data) {
struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, destroy);
wl_list_remove(&keyboard->destroy.link);
wl_list_remove(&keyboard->key.link);
free(keyboard);
}
static void new_input_notify(struct wl_listener *listener, void *data) {
struct wlr_input_device *device = data;
struct sample_state *sample = wl_container_of(listener, sample, new_input);
switch (device->type) {
case WLR_INPUT_DEVICE_KEYBOARD:;
struct sample_keyboard *keyboard = calloc(1, sizeof(*keyboard));
keyboard->wlr_keyboard = wlr_keyboard_from_input_device(device);
keyboard->sample = sample;
wl_signal_add(&device->events.destroy, &keyboard->destroy);
keyboard->destroy.notify = keyboard_destroy_notify;
wl_signal_add(&keyboard->wlr_keyboard->events.key, &keyboard->key);
keyboard->key.notify = keyboard_key_notify;
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!context) {
wlr_log(WLR_ERROR, "Failed to create XKB context");
exit(1);
}
struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL,
XKB_KEYMAP_COMPILE_NO_FLAGS);
if (!keymap) {
wlr_log(WLR_ERROR, "Failed to create XKB keymap");
exit(1);
}
wlr_keyboard_set_keymap(keyboard->wlr_keyboard, keymap);
xkb_keymap_unref(keymap);
xkb_context_unref(context);
break;
default:
break;
}
}
int main(int argc, char *argv[]) {
int c;
enum wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
while ((c = getopt(argc, argv, "r:")) != -1) {
switch (c) {
case 'r':
if (strcmp(optarg, "90") == 0) {
transform = WL_OUTPUT_TRANSFORM_90;
} else if (strcmp(optarg, "180") == 0) {
transform = WL_OUTPUT_TRANSFORM_180;
} else if (strcmp(optarg, "270") == 0) {
transform = WL_OUTPUT_TRANSFORM_270;
} else if (strcmp(optarg, "flipped") == 0) {
transform = WL_OUTPUT_TRANSFORM_FLIPPED;
} else if (strcmp(optarg, "flipped-90") == 0) {
transform = WL_OUTPUT_TRANSFORM_FLIPPED_90;
} else if (strcmp(optarg, "flipped-180") == 0) {
transform = WL_OUTPUT_TRANSFORM_FLIPPED_180;
} else if (strcmp(optarg, "flipped-270") == 0) {
transform = WL_OUTPUT_TRANSFORM_FLIPPED_270;
} else {
wlr_log(WLR_ERROR, "got unknown transform value: %s", optarg);
}
break;
default:
return 1;
}
}
wlr_log_init(WLR_DEBUG, NULL);
struct wl_display *display = wl_display_create();
struct sample_state state = {
.display = display,
.transform = transform
};
wl_list_init(&state.outputs);
struct wlr_backend *wlr = wlr_backend_autocreate(wl_display_get_event_loop(display), NULL);
if (!wlr) {
exit(1);
}
wl_signal_add(&wlr->events.new_output, &state.new_output);
state.new_output.notify = new_output_notify;
wl_signal_add(&wlr->events.new_input, &state.new_input);
state.new_input.notify = new_input_notify;
clock_gettime(CLOCK_MONOTONIC, &state.last_frame);
state.renderer = wlr_renderer_autocreate(wlr);
if (!state.renderer) {
wlr_log(WLR_ERROR, "Could not start compositor, OOM");
wlr_backend_destroy(wlr);
exit(EXIT_FAILURE);
}
state.cat_texture = wlr_texture_from_pixels(state.renderer,
DRM_FORMAT_ABGR8888, cat_tex.width * 4, cat_tex.width, cat_tex.height,
cat_tex.pixel_data);
if (!state.cat_texture) {
wlr_log(WLR_ERROR, "Could not start compositor, OOM");
exit(EXIT_FAILURE);
}
state.allocator = wlr_allocator_autocreate(wlr, state.renderer);
if (!wlr_backend_start(wlr)) {
wlr_log(WLR_ERROR, "Failed to start backend");
wlr_backend_destroy(wlr);
exit(1);
}
wl_display_run(display);
wlr_texture_destroy(state.cat_texture);
wl_display_destroy(display);
}

207
examples/scene-graph.c Normal file
View file

@ -0,0 +1,207 @@
#include <assert.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>
/* Simple compositor making use of the scene-graph API. Input is unimplemented.
*
* New surfaces are stacked on top of the existing ones as they appear. */
static const int border_width = 3;
struct server {
struct wl_display *display;
struct wlr_backend *backend;
struct wlr_renderer *renderer;
struct wlr_allocator *allocator;
struct wlr_scene *scene;
uint32_t surface_offset;
struct wl_listener new_output;
struct wl_listener new_surface;
};
struct surface {
struct wlr_surface *wlr;
struct wlr_scene_surface *scene_surface;
struct wlr_scene_rect *border;
struct wl_list link;
struct wl_listener commit;
struct wl_listener destroy;
};
struct output {
struct wl_list link;
struct server *server;
struct wlr_output *wlr;
struct wlr_scene_output *scene_output;
struct wl_listener frame;
};
static void output_handle_frame(struct wl_listener *listener, void *data) {
struct output *output = wl_container_of(listener, output, frame);
if (!wlr_scene_output_commit(output->scene_output, NULL)) {
return;
}
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
wlr_scene_output_send_frame_done(output->scene_output, &now);
}
static void server_handle_new_output(struct wl_listener *listener, void *data) {
struct server *server = wl_container_of(listener, server, new_output);
struct wlr_output *wlr_output = data;
wlr_output_init_render(wlr_output, server->allocator, server->renderer);
struct output *output = calloc(1, sizeof(*output));
output->wlr = wlr_output;
output->server = server;
output->frame.notify = output_handle_frame;
wl_signal_add(&wlr_output->events.frame, &output->frame);
output->scene_output = wlr_scene_output_create(server->scene, wlr_output);
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_enabled(&state, true);
struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output);
if (mode != NULL) {
wlr_output_state_set_mode(&state, mode);
}
wlr_output_commit_state(wlr_output, &state);
wlr_output_state_finish(&state);
wlr_output_create_global(wlr_output, server->display);
}
static void surface_handle_commit(struct wl_listener *listener, void *data) {
struct surface *surface = wl_container_of(listener, surface, commit);
wlr_scene_rect_set_size(surface->border,
surface->wlr->current.width + 2 * border_width,
surface->wlr->current.height + 2 * border_width);
}
static void surface_handle_destroy(struct wl_listener *listener, void *data) {
struct surface *surface = wl_container_of(listener, surface, destroy);
wlr_scene_node_destroy(&surface->scene_surface->buffer->node);
wlr_scene_node_destroy(&surface->border->node);
wl_list_remove(&surface->destroy.link);
wl_list_remove(&surface->link);
free(surface);
}
static void server_handle_new_surface(struct wl_listener *listener,
void *data) {
struct server *server = wl_container_of(listener, server, new_surface);
struct wlr_surface *wlr_surface = data;
int pos = server->surface_offset;
server->surface_offset += 50;
struct surface *surface = calloc(1, sizeof(*surface));
surface->wlr = wlr_surface;
surface->commit.notify = surface_handle_commit;
wl_signal_add(&wlr_surface->events.commit, &surface->commit);
surface->destroy.notify = surface_handle_destroy;
wl_signal_add(&wlr_surface->events.destroy, &surface->destroy);
/* Border dimensions will be set in surface.commit handler */
surface->border = wlr_scene_rect_create(&server->scene->tree,
0, 0, (float[4]){ 0.5f, 0.5f, 0.5f, 1 });
wlr_scene_node_set_position(&surface->border->node, pos, pos);
surface->scene_surface =
wlr_scene_surface_create(&server->scene->tree, wlr_surface);
wlr_scene_node_set_position(&surface->scene_surface->buffer->node,
pos + border_width, pos + border_width);
}
int main(int argc, char *argv[]) {
wlr_log_init(WLR_DEBUG, NULL);
char *startup_cmd = NULL;
int c;
while ((c = getopt(argc, argv, "s:")) != -1) {
switch (c) {
case 's':
startup_cmd = optarg;
break;
default:
printf("usage: %s [-s startup-command]\n", argv[0]);
return EXIT_FAILURE;
}
}
if (optind < argc) {
printf("usage: %s [-s startup-command]\n", argv[0]);
return EXIT_FAILURE;
}
struct server server = {0};
server.surface_offset = 0;
server.display = wl_display_create();
server.backend = wlr_backend_autocreate(wl_display_get_event_loop(server.display), NULL);
server.scene = wlr_scene_create();
server.renderer = wlr_renderer_autocreate(server.backend);
wlr_renderer_init_wl_display(server.renderer, server.display);
server.allocator = wlr_allocator_autocreate(server.backend,
server.renderer);
struct wlr_compositor *compositor =
wlr_compositor_create(server.display, 5, server.renderer);
wlr_xdg_shell_create(server.display, 2);
server.new_output.notify = server_handle_new_output;
wl_signal_add(&server.backend->events.new_output, &server.new_output);
server.new_surface.notify = server_handle_new_surface;
wl_signal_add(&compositor->events.new_surface, &server.new_surface);
const char *socket = wl_display_add_socket_auto(server.display);
if (!socket) {
wl_display_destroy(server.display);
return EXIT_FAILURE;
}
if (!wlr_backend_start(server.backend)) {
wl_display_destroy(server.display);
return EXIT_FAILURE;
}
setenv("WAYLAND_DISPLAY", socket, true);
if (startup_cmd != NULL) {
if (fork() == 0) {
execl("/bin/sh", "/bin/sh", "-c", startup_cmd, (void *)NULL);
}
}
wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s",
socket);
wl_display_run(server.display);
wl_display_destroy_clients(server.display);
wl_display_destroy(server.display);
return EXIT_SUCCESS;
}

204
examples/simple.c Normal file
View file

@ -0,0 +1,204 @@
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/backend/session.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_keyboard.h>
#include <wlr/util/log.h>
#include <xkbcommon/xkbcommon.h>
struct sample_state {
struct wl_display *display;
struct wl_listener new_output;
struct wl_listener new_input;
struct wlr_renderer *renderer;
struct wlr_allocator *allocator;
struct timespec last_frame;
float color[4];
int dec;
};
struct sample_output {
struct sample_state *sample;
struct wlr_output *output;
struct wl_listener frame;
struct wl_listener destroy;
};
struct sample_keyboard {
struct sample_state *sample;
struct wlr_keyboard *wlr_keyboard;
struct wl_listener key;
struct wl_listener destroy;
};
static void output_frame_notify(struct wl_listener *listener, void *data) {
struct sample_output *sample_output =
wl_container_of(listener, sample_output, frame);
struct sample_state *sample = sample_output->sample;
struct wlr_output *wlr_output = sample_output->output;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
long ms = (now.tv_sec - sample->last_frame.tv_sec) * 1000 +
(now.tv_nsec - sample->last_frame.tv_nsec) / 1000000;
int inc = (sample->dec + 1) % 3;
sample->color[inc] += ms / 2000.0f;
sample->color[sample->dec] -= ms / 2000.0f;
if (sample->color[sample->dec] < 0.0f) {
sample->color[inc] = 1.0f;
sample->color[sample->dec] = 0.0f;
sample->dec = inc;
}
struct wlr_output_state state;
wlr_output_state_init(&state);
struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &state, NULL, NULL);
wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){
.box = { .width = wlr_output->width, .height = wlr_output->height },
.color = {
.r = sample->color[0],
.g = sample->color[1],
.b = sample->color[2],
.a = sample->color[3],
},
});
wlr_render_pass_submit(pass);
wlr_output_commit_state(wlr_output, &state);
wlr_output_state_finish(&state);
sample->last_frame = now;
}
static void output_remove_notify(struct wl_listener *listener, void *data) {
struct sample_output *sample_output =
wl_container_of(listener, sample_output, destroy);
wlr_log(WLR_DEBUG, "Output removed");
wl_list_remove(&sample_output->frame.link);
wl_list_remove(&sample_output->destroy.link);
free(sample_output);
}
static void new_output_notify(struct wl_listener *listener, void *data) {
struct wlr_output *output = data;
struct sample_state *sample =
wl_container_of(listener, sample, new_output);
wlr_output_init_render(output, sample->allocator, sample->renderer);
struct sample_output *sample_output = calloc(1, sizeof(*sample_output));
sample_output->output = output;
sample_output->sample = sample;
wl_signal_add(&output->events.frame, &sample_output->frame);
sample_output->frame.notify = output_frame_notify;
wl_signal_add(&output->events.destroy, &sample_output->destroy);
sample_output->destroy.notify = output_remove_notify;
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_enabled(&state, true);
struct wlr_output_mode *mode = wlr_output_preferred_mode(output);
if (mode != NULL) {
wlr_output_state_set_mode(&state, mode);
}
wlr_output_commit_state(output, &state);
wlr_output_state_finish(&state);
}
static void keyboard_key_notify(struct wl_listener *listener, void *data) {
struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, key);
struct sample_state *sample = keyboard->sample;
struct wlr_keyboard_key_event *event = data;
uint32_t keycode = event->keycode + 8;
const xkb_keysym_t *syms;
int nsyms = xkb_state_key_get_syms(keyboard->wlr_keyboard->xkb_state,
keycode, &syms);
for (int i = 0; i < nsyms; i++) {
xkb_keysym_t sym = syms[i];
if (sym == XKB_KEY_Escape) {
wl_display_terminate(sample->display);
}
}
}
static void keyboard_destroy_notify(struct wl_listener *listener, void *data) {
struct sample_keyboard *keyboard =
wl_container_of(listener, keyboard, destroy);
wl_list_remove(&keyboard->destroy.link);
wl_list_remove(&keyboard->key.link);
free(keyboard);
}
static void new_input_notify(struct wl_listener *listener, void *data) {
struct wlr_input_device *device = data;
struct sample_state *sample = wl_container_of(listener, sample, new_input);
switch (device->type) {
case WLR_INPUT_DEVICE_KEYBOARD:;
struct sample_keyboard *keyboard = calloc(1, sizeof(*keyboard));
keyboard->wlr_keyboard = wlr_keyboard_from_input_device(device);
keyboard->sample = sample;
wl_signal_add(&device->events.destroy, &keyboard->destroy);
keyboard->destroy.notify = keyboard_destroy_notify;
wl_signal_add(&keyboard->wlr_keyboard->events.key, &keyboard->key);
keyboard->key.notify = keyboard_key_notify;
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!context) {
wlr_log(WLR_ERROR, "Failed to create XKB context");
exit(1);
}
struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL,
XKB_KEYMAP_COMPILE_NO_FLAGS);
if (!keymap) {
wlr_log(WLR_ERROR, "Failed to create XKB keymap");
exit(1);
}
wlr_keyboard_set_keymap(keyboard->wlr_keyboard, keymap);
xkb_keymap_unref(keymap);
xkb_context_unref(context);
break;
default:
break;
}
}
int main(void) {
wlr_log_init(WLR_DEBUG, NULL);
struct wl_display *display = wl_display_create();
struct sample_state state = {
.color = { 1.0, 0.0, 0.0, 1.0 },
.dec = 0,
.last_frame = { 0 },
.display = display
};
struct wlr_backend *backend = wlr_backend_autocreate(wl_display_get_event_loop(display), NULL);
if (!backend) {
exit(1);
}
state.renderer = wlr_renderer_autocreate(backend);
state.allocator = wlr_allocator_autocreate(backend, state.renderer);
wl_signal_add(&backend->events.new_output, &state.new_output);
state.new_output.notify = new_output_notify;
wl_signal_add(&backend->events.new_input, &state.new_input);
state.new_input.notify = new_input_notify;
clock_gettime(CLOCK_MONOTONIC, &state.last_frame);
if (!wlr_backend_start(backend)) {
wlr_log(WLR_ERROR, "Failed to start backend");
wlr_backend_destroy(backend);
exit(1);
}
wl_display_run(display);
wl_display_destroy(display);
}

416
examples/tablet.c Normal file
View file

@ -0,0 +1,416 @@
#undef _POSIX_C_SOURCE
#define _XOPEN_SOURCE 600 // for M_PI
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/backend/session.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_tablet_pad.h>
#include <wlr/types/wlr_tablet_tool.h>
#include <wlr/util/box.h>
#include <wlr/util/log.h>
#include <xkbcommon/xkbcommon.h>
struct sample_state {
struct wl_display *display;
struct wlr_renderer *renderer;
struct wlr_allocator *allocator;
bool proximity, tap, button;
double distance;
double pressure;
double x, y;
double x_tilt, y_tilt;
double width_mm, height_mm;
double ring;
struct wl_list link;
float tool_color[4];
float pad_color[4];
struct timespec last_frame;
struct wl_listener new_output;
struct wl_listener new_input;
struct wl_list tablet_tools;
struct wl_list tablet_pads;
};
struct tablet_tool_state {
struct sample_state *sample;
struct wlr_tablet *wlr_tablet;
struct wl_listener destroy;
struct wl_listener axis;
struct wl_listener proximity;
struct wl_listener tip;
struct wl_listener button;
struct wl_list link;
void *data;
};
struct tablet_pad_state {
struct sample_state *sample;
struct wlr_tablet_pad *wlr_tablet_pad;
struct wl_listener destroy;
struct wl_listener button;
struct wl_listener ring;
struct wl_list link;
void *data;
};
struct sample_output {
struct sample_state *sample;
struct wlr_output *output;
struct wl_listener frame;
struct wl_listener destroy;
};
struct sample_keyboard {
struct sample_state *sample;
struct wlr_keyboard *wlr_keyboard;
struct wl_listener key;
struct wl_listener destroy;
};
static void output_frame_notify(struct wl_listener *listener, void *data) {
struct sample_output *sample_output = wl_container_of(listener, sample_output, frame);
struct sample_state *sample = sample_output->sample;
struct wlr_output *wlr_output = sample_output->output;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
int32_t width, height;
wlr_output_effective_resolution(wlr_output, &width, &height);
struct wlr_output_state output_state;
wlr_output_state_init(&output_state);
struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL, NULL);
wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){
.box = { .width = wlr_output->width, .height = wlr_output->height },
.color = { 0.25, 0.25, 0.25, 1 },
});
float distance = 0.8f * (1 - sample->distance);
float tool_color[4] = { distance, distance, distance, 1 };
for (size_t i = 0; sample->button && i < 4; ++i) {
tool_color[i] = sample->tool_color[i];
}
float scale = 4;
float pad_width = sample->width_mm * scale;
float pad_height = sample->height_mm * scale;
float left = width / 2.0f - pad_width / 2.0f;
float top = height / 2.0f - pad_height / 2.0f;
wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){
.box = {
.x = left,
.y = top,
.width = pad_width,
.height = pad_height,
},
.color = {
sample->pad_color[0],
sample->pad_color[1],
sample->pad_color[2],
sample->pad_color[3],
},
});
if (sample->proximity) {
struct wlr_box box = {
.x = (sample->x * pad_width) - 8 * (sample->pressure + 1) + left,
.y = (sample->y * pad_height) - 8 * (sample->pressure + 1) + top,
.width = 16 * (sample->pressure + 1),
.height = 16 * (sample->pressure + 1),
};
// TODO: use sample->ring
wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){
.box = box,
.color = {
tool_color[0],
tool_color[1],
tool_color[2],
tool_color[3],
},
});
box.x += sample->x_tilt;
box.y += sample->y_tilt;
box.width /= 2;
box.height /= 2;
wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){
.box = box,
.color = {
tool_color[0],
tool_color[1],
tool_color[2],
tool_color[3],
},
});
}
wlr_render_pass_submit(pass);
wlr_output_commit_state(wlr_output, &output_state);
wlr_output_state_finish(&output_state);
sample->last_frame = now;
}
static void tablet_tool_axis_notify(struct wl_listener *listener, void *data) {
struct tablet_tool_state *tstate = wl_container_of(listener, tstate, axis);
struct wlr_tablet_tool_axis_event *event = data;
struct sample_state *sample = tstate->sample;
if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X)) {
sample->x = event->x;
}
if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) {
sample->y = event->y;
}
if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE)) {
sample->distance = event->distance;
}
if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE)) {
sample->pressure = event->pressure;
}
if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X)) {
sample->x_tilt = event->tilt_x;
}
if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y)) {
sample->y_tilt = event->tilt_y;
}
}
static void tablet_tool_proximity_notify(struct wl_listener *listener, void *data) {
struct tablet_tool_state *tstate = wl_container_of(listener, tstate, proximity);
struct wlr_tablet_tool_proximity_event *event = data;
struct sample_state *sample = tstate->sample;
sample->proximity = event->state == WLR_TABLET_TOOL_PROXIMITY_IN;
}
static void tablet_tool_button_notify(struct wl_listener *listener, void *data) {
struct tablet_tool_state *tstate = wl_container_of(listener, tstate, button);
struct wlr_tablet_tool_button_event *event = data;
struct sample_state *sample = tstate->sample;
if (event->state == WLR_BUTTON_RELEASED) {
sample->button = false;
} else {
sample->button = true;
for (size_t i = 0; i < 3; ++i) {
if (event->button % 3 == i) {
sample->tool_color[i] = 0;
} else {
sample->tool_color[i] = 1;
}
}
}
}
static void tablet_pad_button_notify(struct wl_listener *listener, void *data) {
struct tablet_pad_state *pstate = wl_container_of(listener, pstate, button);
struct wlr_tablet_pad_button_event *event = data;
struct sample_state *sample = pstate->sample;
float default_color[4] = { 0.5, 0.5, 0.5, 1.0 };
if (event->state == WLR_BUTTON_RELEASED) {
memcpy(sample->pad_color, default_color, sizeof(default_color));
} else {
for (size_t i = 0; i < 3; ++i) {
if (event->button % 3 == i) {
sample->pad_color[i] = 0;
} else {
sample->pad_color[i] = 1;
}
}
}
}
static void tablet_pad_ring_notify(struct wl_listener *listener, void *data) {
struct tablet_pad_state *pstate = wl_container_of(listener, pstate, ring);
struct wlr_tablet_pad_ring_event *event = data;
struct sample_state *sample = pstate->sample;
if (event->position != -1) {
sample->ring = -(event->position * (M_PI / 180.0));
}
}
static void tablet_tool_destroy_notify(struct wl_listener *listener, void *data) {
struct tablet_tool_state *tstate = wl_container_of(listener, tstate, destroy);
wl_list_remove(&tstate->link);
wl_list_remove(&tstate->destroy.link);
wl_list_remove(&tstate->axis.link);
wl_list_remove(&tstate->proximity.link);
wl_list_remove(&tstate->button.link);
free(tstate);
}
static void tablet_pad_destroy_notify(struct wl_listener *listener, void *data) {
struct tablet_pad_state *pstate = wl_container_of(listener, pstate, destroy);
wl_list_remove(&pstate->link);
wl_list_remove(&pstate->destroy.link);
wl_list_remove(&pstate->ring.link);
wl_list_remove(&pstate->button.link);
free(pstate);
}
static void output_remove_notify(struct wl_listener *listener, void *data) {
struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy);
wl_list_remove(&sample_output->frame.link);
wl_list_remove(&sample_output->destroy.link);
free(sample_output);
}
static void new_output_notify(struct wl_listener *listener, void *data) {
struct wlr_output *output = data;
struct sample_state *sample = wl_container_of(listener, sample, new_output);
wlr_output_init_render(output, sample->allocator, sample->renderer);
struct sample_output *sample_output = calloc(1, sizeof(*sample_output));
sample_output->output = output;
sample_output->sample = sample;
wl_signal_add(&output->events.frame, &sample_output->frame);
sample_output->frame.notify = output_frame_notify;
wl_signal_add(&output->events.destroy, &sample_output->destroy);
sample_output->destroy.notify = output_remove_notify;
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_enabled(&state, true);
struct wlr_output_mode *mode = wlr_output_preferred_mode(output);
if (mode != NULL) {
wlr_output_state_set_mode(&state, mode);
}
wlr_output_commit_state(output, &state);
wlr_output_state_finish(&state);
}
static void keyboard_key_notify(struct wl_listener *listener, void *data) {
struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, key);
struct sample_state *sample = keyboard->sample;
struct wlr_keyboard_key_event *event = data;
uint32_t keycode = event->keycode + 8;
const xkb_keysym_t *syms;
int nsyms = xkb_state_key_get_syms(keyboard->wlr_keyboard->xkb_state,
keycode, &syms);
for (int i = 0; i < nsyms; i++) {
xkb_keysym_t sym = syms[i];
if (sym == XKB_KEY_Escape) {
wl_display_terminate(sample->display);
}
}
}
static void keyboard_destroy_notify(struct wl_listener *listener, void *data) {
struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, destroy);
wl_list_remove(&keyboard->destroy.link);
wl_list_remove(&keyboard->key.link);
free(keyboard);
}
static void new_input_notify(struct wl_listener *listener, void *data) {
struct wlr_input_device *device = data;
struct sample_state *sample = wl_container_of(listener, sample, new_input);
switch (device->type) {
case WLR_INPUT_DEVICE_KEYBOARD:;
struct sample_keyboard *keyboard = calloc(1, sizeof(*keyboard));
keyboard->wlr_keyboard = wlr_keyboard_from_input_device(device);
keyboard->sample = sample;
wl_signal_add(&device->events.destroy, &keyboard->destroy);
keyboard->destroy.notify = keyboard_destroy_notify;
wl_signal_add(&keyboard->wlr_keyboard->events.key, &keyboard->key);
keyboard->key.notify = keyboard_key_notify;
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!context) {
wlr_log(WLR_ERROR, "Failed to create XKB context");
exit(1);
}
struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL,
XKB_KEYMAP_COMPILE_NO_FLAGS);
if (!keymap) {
wlr_log(WLR_ERROR, "Failed to create XKB keymap");
exit(1);
}
wlr_keyboard_set_keymap(keyboard->wlr_keyboard, keymap);
xkb_keymap_unref(keymap);
xkb_context_unref(context);
break;
case WLR_INPUT_DEVICE_TABLET_PAD:;
struct tablet_pad_state *pstate = calloc(1, sizeof(*pstate));
pstate->wlr_tablet_pad = wlr_tablet_pad_from_input_device(device);
pstate->sample = sample;
pstate->destroy.notify = tablet_pad_destroy_notify;
wl_signal_add(&device->events.destroy, &pstate->destroy);
pstate->button.notify = tablet_pad_button_notify;
wl_signal_add(&pstate->wlr_tablet_pad->events.button, &pstate->button);
pstate->ring.notify = tablet_pad_ring_notify;
wl_signal_add(&pstate->wlr_tablet_pad->events.ring, &pstate->ring);
wl_list_insert(&sample->tablet_pads, &pstate->link);
break;
case WLR_INPUT_DEVICE_TABLET:;
struct wlr_tablet *tablet = wlr_tablet_from_input_device(device);
sample->width_mm = tablet->width_mm == 0 ?
20 : tablet->width_mm;
sample->height_mm = tablet->height_mm == 0 ?
10 : tablet->height_mm;
struct tablet_tool_state *tstate = calloc(1, sizeof(*tstate));
tstate->wlr_tablet = tablet;
tstate->sample = sample;
tstate->destroy.notify = tablet_tool_destroy_notify;
wl_signal_add(&device->events.destroy, &tstate->destroy);
tstate->axis.notify = tablet_tool_axis_notify;
wl_signal_add(&tablet->events.axis, &tstate->axis);
tstate->proximity.notify = tablet_tool_proximity_notify;
wl_signal_add(&tablet->events.proximity, &tstate->proximity);
tstate->button.notify = tablet_tool_button_notify;
wl_signal_add(&tablet->events.button, &tstate->button);
wl_list_insert(&sample->tablet_tools, &tstate->link);
break;
default:
break;
}
}
int main(int argc, char *argv[]) {
wlr_log_init(WLR_DEBUG, NULL);
struct wl_display *display = wl_display_create();
struct sample_state state = {
.display = display,
.tool_color = { 1, 1, 1, 1 },
.pad_color = { 0.5, 0.5, 0.5, 1.0 }
};
wl_list_init(&state.tablet_pads);
wl_list_init(&state.tablet_tools);
struct wlr_backend *wlr = wlr_backend_autocreate(wl_display_get_event_loop(display), NULL);
if (!wlr) {
exit(1);
}
wl_signal_add(&wlr->events.new_output, &state.new_output);
state.new_output.notify = new_output_notify;
wl_signal_add(&wlr->events.new_input, &state.new_input);
state.new_input.notify = new_input_notify;
clock_gettime(CLOCK_MONOTONIC, &state.last_frame);
state.renderer = wlr_renderer_autocreate(wlr);
if (!state.renderer) {
wlr_log(WLR_ERROR, "Could not start compositor, OOM");
exit(EXIT_FAILURE);
}
state.allocator = wlr_allocator_autocreate(wlr, state.renderer);
if (!wlr_backend_start(wlr)) {
wlr_log(WLR_ERROR, "Failed to start backend");
wlr_backend_destroy(wlr);
exit(1);
}
wl_display_run(display);
wl_display_destroy(display);
}

311
examples/touch.c Normal file
View file

@ -0,0 +1,311 @@
#include <drm_fourcc.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/backend/session.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_touch.h>
#include <wlr/util/log.h>
#include <xkbcommon/xkbcommon.h>
#include "cat.h"
struct sample_state {
struct wl_display *display;
struct wlr_renderer *renderer;
struct wlr_allocator *allocator;
struct wlr_texture *cat_texture;
struct wl_list touch_points;
struct timespec last_frame;
struct wl_listener new_output;
struct wl_listener new_input;
struct wl_list touch;
};
struct touch_point {
int32_t touch_id;
double x, y;
struct wl_list link;
};
struct touch_state {
struct sample_state *sample;
struct wlr_touch *wlr_touch;
struct wl_listener destroy;
struct wl_listener down;
struct wl_listener up;
struct wl_listener motion;
struct wl_listener cancel;
struct wl_list link;
void *data;
};
struct sample_output {
struct sample_state *sample;
struct wlr_output *output;
struct wl_listener frame;
struct wl_listener destroy;
};
struct sample_keyboard {
struct sample_state *sample;
struct wlr_keyboard *wlr_keyboard;
struct wl_listener key;
struct wl_listener destroy;
};
static void output_frame_notify(struct wl_listener *listener, void *data) {
struct sample_output *sample_output = wl_container_of(listener, sample_output, frame);
struct sample_state *sample = sample_output->sample;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
struct wlr_output *wlr_output = sample_output->output;
int32_t width, height;
wlr_output_effective_resolution(wlr_output, &width, &height);
struct wlr_output_state output_state;
wlr_output_state_init(&output_state);
struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL, NULL);
wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){
.box = { .width = width, .height = height },
.color = { 0.25, 0.25, 0.25, 1 },
});
struct touch_point *p;
wl_list_for_each(p, &sample->touch_points, link) {
int x = (int)(p->x * width) - sample->cat_texture->width / 2;
int y = (int)(p->y * height) - sample->cat_texture->height / 2;
wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options){
.texture = sample->cat_texture,
.dst_box = { .x = x, .y = y },
});
}
wlr_render_pass_submit(pass);
wlr_output_commit_state(wlr_output, &output_state);
wlr_output_state_finish(&output_state);
sample->last_frame = now;
}
static void touch_down_notify(struct wl_listener *listener, void *data) {
struct wlr_touch_down_event *event = data;
struct touch_state *tstate = wl_container_of(listener, tstate, down);
struct sample_state *sample = tstate->sample;
struct touch_point *point = calloc(1, sizeof(*point));
point->touch_id = event->touch_id;
point->x = event->x;
point->y = event->y;
wl_list_insert(&sample->touch_points, &point->link);
}
static void touch_up_notify(struct wl_listener *listener, void *data ) {
struct wlr_touch_up_event *event = data;
struct touch_state *tstate = wl_container_of(listener, tstate, up);
struct sample_state *sample = tstate->sample;
struct touch_point *point, *tmp;
wl_list_for_each_safe(point, tmp, &sample->touch_points, link) {
if (point->touch_id == event->touch_id) {
wl_list_remove(&point->link);
break;
}
}
}
static void touch_motion_notify(struct wl_listener *listener, void *data) {
struct wlr_touch_motion_event *event = data;
struct touch_state *tstate = wl_container_of(listener, tstate, motion);
struct sample_state *sample = tstate->sample;
struct touch_point *point;
wl_list_for_each(point, &sample->touch_points, link) {
if (point->touch_id == event->touch_id) {
point->x = event->x;
point->y = event->y;
break;
}
}
}
static void touch_cancel_notify(struct wl_listener *listener, void *data) {
struct wlr_touch_cancel_event *event = data;
struct touch_state *tstate = wl_container_of(listener, tstate, cancel);
struct sample_state *sample = tstate->sample;
struct touch_point *point, *tmp;
wl_list_for_each_safe(point, tmp, &sample->touch_points, link) {
if (point->touch_id == event->touch_id) {
wl_list_remove(&point->link);
break;
}
}
}
static void touch_destroy_notify(struct wl_listener *listener, void *data) {
struct touch_state *tstate = wl_container_of(listener, tstate, destroy);
wl_list_remove(&tstate->link);
wl_list_remove(&tstate->destroy.link);
wl_list_remove(&tstate->down.link);
wl_list_remove(&tstate->up.link);
wl_list_remove(&tstate->motion.link);
wl_list_remove(&tstate->cancel.link);
free(tstate);
}
static void output_remove_notify(struct wl_listener *listener, void *data) {
struct sample_output *sample_output = wl_container_of(listener, sample_output, destroy);
wl_list_remove(&sample_output->frame.link);
wl_list_remove(&sample_output->destroy.link);
free(sample_output);
}
static void new_output_notify(struct wl_listener *listener, void *data) {
struct wlr_output *output = data;
struct sample_state *sample = wl_container_of(listener, sample, new_output);
wlr_output_init_render(output, sample->allocator, sample->renderer);
struct sample_output *sample_output = calloc(1, sizeof(*sample_output));
sample_output->output = output;
sample_output->sample = sample;
wl_signal_add(&output->events.frame, &sample_output->frame);
sample_output->frame.notify = output_frame_notify;
wl_signal_add(&output->events.destroy, &sample_output->destroy);
sample_output->destroy.notify = output_remove_notify;
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_enabled(&state, true);
struct wlr_output_mode *mode = wlr_output_preferred_mode(output);
if (mode != NULL) {
wlr_output_state_set_mode(&state, mode);
}
wlr_output_commit_state(output, &state);
wlr_output_state_finish(&state);
}
static void keyboard_key_notify(struct wl_listener *listener, void *data) {
struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, key);
struct sample_state *sample = keyboard->sample;
struct wlr_keyboard_key_event *event = data;
uint32_t keycode = event->keycode + 8;
const xkb_keysym_t *syms;
int nsyms = xkb_state_key_get_syms(keyboard->wlr_keyboard->xkb_state,
keycode, &syms);
for (int i = 0; i < nsyms; i++) {
xkb_keysym_t sym = syms[i];
if (sym == XKB_KEY_Escape) {
wl_display_terminate(sample->display);
}
}
}
static void keyboard_destroy_notify(struct wl_listener *listener, void *data) {
struct sample_keyboard *keyboard = wl_container_of(listener, keyboard, destroy);
wl_list_remove(&keyboard->destroy.link);
wl_list_remove(&keyboard->key.link);
free(keyboard);
}
static void new_input_notify(struct wl_listener *listener, void *data) {
struct wlr_input_device *device = data;
struct sample_state *sample = wl_container_of(listener, sample, new_input);
switch (device->type) {
case WLR_INPUT_DEVICE_KEYBOARD:;
struct sample_keyboard *keyboard = calloc(1, sizeof(*keyboard));
keyboard->wlr_keyboard = wlr_keyboard_from_input_device(device);
keyboard->sample = sample;
wl_signal_add(&device->events.destroy, &keyboard->destroy);
keyboard->destroy.notify = keyboard_destroy_notify;
wl_signal_add(&keyboard->wlr_keyboard->events.key, &keyboard->key);
keyboard->key.notify = keyboard_key_notify;
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!context) {
wlr_log(WLR_ERROR, "Failed to create XKB context");
exit(1);
}
struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, NULL,
XKB_KEYMAP_COMPILE_NO_FLAGS);
if (!keymap) {
wlr_log(WLR_ERROR, "Failed to create XKB keymap");
exit(1);
}
wlr_keyboard_set_keymap(keyboard->wlr_keyboard, keymap);
xkb_keymap_unref(keymap);
xkb_context_unref(context);
break;
case WLR_INPUT_DEVICE_TOUCH:;
struct touch_state *tstate = calloc(1, sizeof(*tstate));
tstate->wlr_touch = wlr_touch_from_input_device(device);
tstate->sample = sample;
tstate->destroy.notify = touch_destroy_notify;
wl_signal_add(&device->events.destroy, &tstate->destroy);
tstate->down.notify = touch_down_notify;
wl_signal_add(&tstate->wlr_touch->events.down, &tstate->down);
tstate->motion.notify = touch_motion_notify;
wl_signal_add(&tstate->wlr_touch->events.motion, &tstate->motion);
tstate->up.notify = touch_up_notify;
wl_signal_add(&tstate->wlr_touch->events.up, &tstate->up);
tstate->cancel.notify = touch_cancel_notify;
wl_signal_add(&tstate->wlr_touch->events.cancel, &tstate->cancel);
wl_list_insert(&sample->touch, &tstate->link);
break;
default:
break;
}
}
int main(int argc, char *argv[]) {
wlr_log_init(WLR_DEBUG, NULL);
struct wl_display *display = wl_display_create();
struct sample_state state = {
.display = display
};
wl_list_init(&state.touch_points);
wl_list_init(&state.touch);
struct wlr_backend *wlr = wlr_backend_autocreate(wl_display_get_event_loop(display), NULL);
if (!wlr) {
exit(1);
}
wl_signal_add(&wlr->events.new_output, &state.new_output);
state.new_output.notify = new_output_notify;
wl_signal_add(&wlr->events.new_input, &state.new_input);
state.new_input.notify = new_input_notify;
clock_gettime(CLOCK_MONOTONIC, &state.last_frame);
state.renderer = wlr_renderer_autocreate(wlr);
if (!state.renderer) {
wlr_log(WLR_ERROR, "Could not start compositor, OOM");
exit(EXIT_FAILURE);
}
state.cat_texture = wlr_texture_from_pixels(state.renderer,
DRM_FORMAT_ARGB8888, cat_tex.width * 4, cat_tex.width, cat_tex.height,
cat_tex.pixel_data);
if (!state.cat_texture) {
wlr_log(WLR_ERROR, "Could not start compositor, OOM");
exit(EXIT_FAILURE);
}
state.allocator = wlr_allocator_autocreate(wlr, state.renderer);
if (!wlr_backend_start(wlr)) {
wlr_log(WLR_ERROR, "Failed to start backend");
wlr_backend_destroy(wlr);
exit(1);
}
wl_display_run(display);
wlr_texture_destroy(state.cat_texture);
wl_display_destroy(display);
}

13
include/backend/backend.h Normal file
View file

@ -0,0 +1,13 @@
#ifndef BACKEND_WLR_BACKEND_H
#define BACKEND_WLR_BACKEND_H
#include <wlr/backend.h>
/**
* Get the supported buffer capabilities.
*
* This functions returns a bitfield of supported wlr_buffer_cap.
*/
uint32_t backend_get_buffer_caps(struct wlr_backend *backend);
#endif

208
include/backend/drm/drm.h Normal file
View file

@ -0,0 +1,208 @@
#ifndef BACKEND_DRM_DRM_H
#define BACKEND_DRM_DRM_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <wayland-server-core.h>
#include <wayland-util.h>
#include <wlr/backend/drm.h>
#include <wlr/backend/session.h>
#include <wlr/render/drm_format_set.h>
#include <wlr/types/wlr_output_layer.h>
#include <xf86drmMode.h>
#include "backend/drm/iface.h"
#include "backend/drm/properties.h"
#include "backend/drm/renderer.h"
struct wlr_drm_plane {
uint32_t type;
uint32_t id;
/* Only initialized on multi-GPU setups */
struct wlr_drm_surface mgpu_surf;
/* Buffer submitted to the kernel, will be presented on next vblank */
struct wlr_drm_fb *queued_fb;
/* Buffer currently displayed on screen */
struct wlr_drm_fb *current_fb;
struct wlr_drm_format_set formats;
union wlr_drm_plane_props props;
uint32_t initial_crtc_id;
struct liftoff_plane *liftoff;
struct liftoff_layer *liftoff_layer;
};
struct wlr_drm_layer {
struct wlr_output_layer *wlr;
struct liftoff_layer *liftoff;
struct wlr_addon addon; // wlr_output_layer.addons
struct wl_list link; // wlr_drm_crtc.layers
/* Buffer to be submitted to the kernel on the next page-flip */
struct wlr_drm_fb *pending_fb;
/* Buffer submitted to the kernel, will be presented on next vblank */
struct wlr_drm_fb *queued_fb;
/* Buffer currently displayed on screen */
struct wlr_drm_fb *current_fb;
// One entry per wlr_drm_backend.planes
bool *candidate_planes;
};
struct wlr_drm_crtc {
uint32_t id;
struct wlr_drm_lease *lease;
struct liftoff_output *liftoff;
struct liftoff_layer *liftoff_composition_layer;
struct wl_list layers; // wlr_drm_layer.link
// Atomic modesetting only
bool own_mode_id;
uint32_t mode_id;
uint32_t gamma_lut;
// Legacy only
int legacy_gamma_size;
struct wlr_drm_plane *primary;
struct wlr_drm_plane *cursor;
union wlr_drm_crtc_props props;
};
struct wlr_drm_backend {
struct wlr_backend backend;
struct wlr_drm_backend *parent;
const struct wlr_drm_interface *iface;
bool addfb2_modifiers;
int fd;
char *name;
struct wlr_device *dev;
struct liftoff_device *liftoff;
size_t num_crtcs;
struct wlr_drm_crtc *crtcs;
size_t num_planes;
struct wlr_drm_plane *planes;
struct wl_event_source *drm_event;
struct wl_listener session_destroy;
struct wl_listener session_active;
struct wl_listener parent_destroy;
struct wl_listener dev_change;
struct wl_listener dev_remove;
struct wl_list fbs; // wlr_drm_fb.link
struct wl_list connectors; // wlr_drm_connector.link
struct wl_list page_flips; // wlr_drm_page_flip.link
/* Only initialized on multi-GPU setups */
struct wlr_drm_renderer mgpu_renderer;
struct wlr_session *session;
uint64_t cursor_width, cursor_height;
struct wlr_drm_format_set mgpu_formats;
bool supports_tearing_page_flips;
};
struct wlr_drm_mode {
struct wlr_output_mode wlr_mode;
drmModeModeInfo drm_mode;
};
struct wlr_drm_connector_state {
const struct wlr_output_state *base;
bool modeset;
bool nonblock;
bool active;
drmModeModeInfo mode;
struct wlr_drm_fb *primary_fb;
struct wlr_drm_fb *cursor_fb;
};
/**
* Per-page-flip tracking struct.
*
* We've asked for a state change in the kernel, and yet to receive a
* notification for its completion. Currently, the kernel only has a queue
* length of 1, and no way to modify your submissions after they're sent.
*
* However, we might have multiple in-flight page-flip events, for instance
* when performing a non-blocking commit followed by a blocking commit. In
* that case, conn will be set to NULL on the non-blocking commit to indicate
* that it's been superseded.
*/
struct wlr_drm_page_flip {
struct wl_list link; // wlr_drm_connector.page_flips
struct wlr_drm_connector *conn;
};
struct wlr_drm_connector {
struct wlr_output output; // only valid if status != DISCONNECTED
struct wlr_drm_backend *backend;
char name[24];
drmModeConnection status;
uint32_t id;
uint64_t max_bpc_bounds[2];
struct wlr_drm_lease *lease;
struct wlr_drm_crtc *crtc;
uint32_t possible_crtcs;
union wlr_drm_connector_props props;
bool cursor_enabled;
int cursor_x, cursor_y;
int cursor_width, cursor_height;
int cursor_hotspot_x, cursor_hotspot_y;
/* Buffer to be submitted to the kernel on the next page-flip */
struct wlr_drm_fb *cursor_pending_fb;
struct wl_list link; // wlr_drm_backend.connectors
// Last committed page-flip
struct wlr_drm_page_flip *pending_page_flip;
int32_t refresh;
};
struct wlr_drm_backend *get_drm_backend_from_backend(
struct wlr_backend *wlr_backend);
bool check_drm_features(struct wlr_drm_backend *drm);
bool init_drm_resources(struct wlr_drm_backend *drm);
void finish_drm_resources(struct wlr_drm_backend *drm);
void scan_drm_connectors(struct wlr_drm_backend *state,
struct wlr_device_hotplug_event *event);
void scan_drm_leases(struct wlr_drm_backend *drm);
void restore_drm_device(struct wlr_drm_backend *drm);
int handle_drm_event(int fd, uint32_t mask, void *data);
void destroy_drm_connector(struct wlr_drm_connector *conn);
bool drm_connector_is_cursor_visible(struct wlr_drm_connector *conn);
bool drm_connector_supports_vrr(struct wlr_drm_connector *conn);
size_t drm_crtc_get_gamma_lut_size(struct wlr_drm_backend *drm,
struct wlr_drm_crtc *crtc);
void drm_lease_destroy(struct wlr_drm_lease *lease);
void drm_page_flip_destroy(struct wlr_drm_page_flip *page_flip);
struct wlr_drm_layer *get_drm_layer(struct wlr_drm_backend *drm,
struct wlr_output_layer *layer);
#define wlr_drm_conn_log(conn, verb, fmt, ...) \
wlr_log(verb, "connector %s: " fmt, conn->name, ##__VA_ARGS__)
#define wlr_drm_conn_log_errno(conn, verb, fmt, ...) \
wlr_log_errno(verb, "connector %s: " fmt, conn->name, ##__VA_ARGS__)
#endif

24
include/backend/drm/fb.h Normal file
View file

@ -0,0 +1,24 @@
#ifndef BACKEND_DRM_FB_H
#define BACKEND_DRM_FB_H
#include <stdbool.h>
struct wlr_drm_fb {
struct wlr_buffer *wlr_buf;
struct wlr_addon addon;
struct wlr_drm_backend *backend;
struct wl_list link; // wlr_drm_backend.fbs
uint32_t id;
};
bool drm_fb_import(struct wlr_drm_fb **fb, struct wlr_drm_backend *drm,
struct wlr_buffer *buf, const struct wlr_drm_format_set *formats);
void drm_fb_destroy(struct wlr_drm_fb *fb);
void drm_fb_clear(struct wlr_drm_fb **fb);
void drm_fb_copy(struct wlr_drm_fb **new, struct wlr_drm_fb *old);
void drm_fb_move(struct wlr_drm_fb **new, struct wlr_drm_fb **old);
struct wlr_drm_fb *drm_fb_lock(struct wlr_drm_fb *fb);
#endif

View file

@ -0,0 +1,44 @@
#ifndef BACKEND_DRM_IFACE_H
#define BACKEND_DRM_IFACE_H
#include <stdbool.h>
#include <stdint.h>
#include <pixman.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
struct wlr_drm_backend;
struct wlr_drm_connector;
struct wlr_drm_crtc;
struct wlr_drm_connector_state;
struct wlr_drm_fb;
struct wlr_drm_page_flip;
// Used to provide atomic or legacy DRM functions
struct wlr_drm_interface {
bool (*init)(struct wlr_drm_backend *drm);
void (*finish)(struct wlr_drm_backend *drm);
// Commit all pending changes on a CRTC.
bool (*crtc_commit)(struct wlr_drm_connector *conn,
const struct wlr_drm_connector_state *state,
struct wlr_drm_page_flip *page_flip, uint32_t flags, bool test_only);
// Turn off everything
bool (*reset)(struct wlr_drm_backend *drm);
};
extern const struct wlr_drm_interface atomic_iface;
extern const struct wlr_drm_interface legacy_iface;
extern const struct wlr_drm_interface liftoff_iface;
bool drm_legacy_crtc_set_gamma(struct wlr_drm_backend *drm,
struct wlr_drm_crtc *crtc, size_t size, uint16_t *lut);
bool create_mode_blob(struct wlr_drm_connector *conn,
const struct wlr_drm_connector_state *state, uint32_t *blob_id);
bool create_gamma_lut_blob(struct wlr_drm_backend *drm,
size_t size, const uint16_t *lut, uint32_t *blob_id);
bool create_fb_damage_clips_blob(struct wlr_drm_backend *drm,
int width, int height, const pixman_region32_t *damage, uint32_t *blob_id);
bool drm_atomic_reset(struct wlr_drm_backend *drm);
#endif

View file

@ -0,0 +1,24 @@
#ifndef BACKEND_DRM_MONITOR_H
#define BACKEND_DRM_MONITOR_H
#include <wlr/backend/drm.h>
/**
* Helper to create new DRM sub-backends on GPU hotplug.
*/
struct wlr_drm_backend_monitor {
struct wlr_backend *multi;
struct wlr_backend *primary_drm;
struct wlr_session *session;
struct wl_listener multi_destroy;
struct wl_listener primary_drm_destroy;
struct wl_listener session_destroy;
struct wl_listener session_add_drm_card;
};
struct wlr_drm_backend_monitor *drm_backend_monitor_create(
struct wlr_backend *multi, struct wlr_backend *primary_drm,
struct wlr_session *session);
#endif

View file

@ -0,0 +1,84 @@
#ifndef BACKEND_DRM_PROPERTIES_H
#define BACKEND_DRM_PROPERTIES_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/*
* These types contain the property ids for several DRM objects.
* For more details, see:
* https://dri.freedesktop.org/docs/drm/gpu/drm-kms.html#kms-properties
*/
union wlr_drm_connector_props {
struct {
uint32_t edid;
uint32_t dpms;
uint32_t link_status; // not guaranteed to exist
uint32_t path;
uint32_t vrr_capable; // not guaranteed to exist
uint32_t subconnector; // not guaranteed to exist
uint32_t non_desktop;
uint32_t panel_orientation; // not guaranteed to exist
uint32_t content_type; // not guaranteed to exist
uint32_t max_bpc; // not guaranteed to exist
// atomic-modesetting only
uint32_t crtc_id;
};
uint32_t props[4];
};
union wlr_drm_crtc_props {
struct {
// Neither of these are guaranteed to exist
uint32_t vrr_enabled;
uint32_t gamma_lut;
uint32_t gamma_lut_size;
// atomic-modesetting only
uint32_t active;
uint32_t mode_id;
};
uint32_t props[6];
};
union wlr_drm_plane_props {
struct {
uint32_t type;
uint32_t rotation; // Not guaranteed to exist
uint32_t in_formats; // Not guaranteed to exist
// atomic-modesetting only
uint32_t src_x;
uint32_t src_y;
uint32_t src_w;
uint32_t src_h;
uint32_t crtc_x;
uint32_t crtc_y;
uint32_t crtc_w;
uint32_t crtc_h;
uint32_t fb_id;
uint32_t crtc_id;
uint32_t fb_damage_clips;
};
uint32_t props[14];
};
bool get_drm_connector_props(int fd, uint32_t id,
union wlr_drm_connector_props *out);
bool get_drm_crtc_props(int fd, uint32_t id, union wlr_drm_crtc_props *out);
bool get_drm_plane_props(int fd, uint32_t id, union wlr_drm_plane_props *out);
bool get_drm_prop(int fd, uint32_t obj, uint32_t prop, uint64_t *ret);
void *get_drm_prop_blob(int fd, uint32_t obj, uint32_t prop, size_t *ret_len);
char *get_drm_prop_enum(int fd, uint32_t obj, uint32_t prop);
bool introspect_drm_prop_range(int fd, uint32_t prop_id,
uint64_t *min, uint64_t *max);
#endif

View file

@ -0,0 +1,40 @@
#ifndef BACKEND_DRM_RENDERER_H
#define BACKEND_DRM_RENDERER_H
#include <stdbool.h>
#include <stdint.h>
#include <wlr/backend.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/util/addon.h>
struct wlr_drm_backend;
struct wlr_drm_format;
struct wlr_drm_plane;
struct wlr_buffer;
struct wlr_drm_renderer {
struct wlr_renderer *wlr_rend;
struct wlr_allocator *allocator;
};
struct wlr_drm_surface {
struct wlr_drm_renderer *renderer;
struct wlr_swapchain *swapchain;
};
bool init_drm_renderer(struct wlr_drm_backend *drm,
struct wlr_drm_renderer *renderer);
void finish_drm_renderer(struct wlr_drm_renderer *renderer);
bool init_drm_surface(struct wlr_drm_surface *surf,
struct wlr_drm_renderer *renderer, int width, int height,
const struct wlr_drm_format *drm_format);
void finish_drm_surface(struct wlr_drm_surface *surf);
struct wlr_buffer *drm_surface_blit(struct wlr_drm_surface *surf,
struct wlr_buffer *buffer);
bool drm_plane_pick_render_format(struct wlr_drm_plane *plane,
struct wlr_drm_format *fmt, struct wlr_drm_renderer *renderer);
#endif

View file

@ -0,0 +1,43 @@
#ifndef BACKEND_DRM_UTIL_H
#define BACKEND_DRM_UTIL_H
#include <stdint.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
struct wlr_drm_connector;
// Calculates a more accurate refresh rate (mHz) than what mode itself provides
int32_t calculate_refresh_rate(const drmModeModeInfo *mode);
enum wlr_output_mode_aspect_ratio get_picture_aspect_ratio(const drmModeModeInfo *mode);
// Returns manufacturer based on pnp id
const char *get_pnp_manufacturer(const char code[static 3]);
// Populates the make/model/phys_{width,height} of output from the edid data
void parse_edid(struct wlr_drm_connector *conn, size_t len, const uint8_t *data);
const char *drm_connector_status_str(drmModeConnection status);
void generate_cvt_mode(drmModeModeInfo *mode, int hdisplay, int vdisplay,
float vrefresh);
// Part of match_obj
enum {
UNMATCHED = (uint32_t)-1,
SKIP = (uint32_t)-2,
};
/*
* Tries to match some DRM objects with some other DRM resource.
* e.g. Match CRTCs with Encoders, CRTCs with Planes.
*
* objs contains a bit array which resources it can be matched with.
* e.g. Bit 0 set means can be matched with res[0]
*
* res contains an index of which objs it is matched with or UNMATCHED.
*
* This solution is left in out.
* Returns the total number of matched solutions.
*/
size_t match_obj(size_t num_objs, const uint32_t objs[static restrict num_objs],
size_t num_res, const uint32_t res[static restrict num_res],
uint32_t out[static restrict num_res]);
#endif

View file

@ -0,0 +1,30 @@
#ifndef BACKEND_HEADLESS_H
#define BACKEND_HEADLESS_H
#include <wlr/backend/headless.h>
#include <wlr/backend/interface.h>
#define HEADLESS_DEFAULT_REFRESH (60 * 1000) // 60 Hz
struct wlr_headless_backend {
struct wlr_backend backend;
struct wl_event_loop *event_loop;
struct wl_list outputs;
struct wl_listener event_loop_destroy;
bool started;
};
struct wlr_headless_output {
struct wlr_output wlr_output;
struct wlr_headless_backend *backend;
struct wl_list link;
struct wl_event_source *frame_timer;
int frame_delay; // ms
};
struct wlr_headless_backend *headless_backend_from_backend(
struct wlr_backend *wlr_backend);
#endif

139
include/backend/libinput.h Normal file
View file

@ -0,0 +1,139 @@
#ifndef BACKEND_LIBINPUT_H
#define BACKEND_LIBINPUT_H
#include <libinput.h>
#include <wayland-server-core.h>
#include <wlr/backend/interface.h>
#include <wlr/backend/libinput.h>
#include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_pointer.h>
#include <wlr/types/wlr_switch.h>
#include <wlr/types/wlr_tablet_pad.h>
#include <wlr/types/wlr_tablet_tool.h>
#include <wlr/types/wlr_touch.h>
#include "config.h"
struct wlr_libinput_backend {
struct wlr_backend backend;
struct wlr_session *session;
struct libinput *libinput_context;
struct wl_event_source *input_event;
struct wl_listener session_destroy;
struct wl_listener session_signal;
struct wl_list devices; // wlr_libinput_device.link
};
struct wlr_libinput_input_device {
struct libinput_device *handle;
struct wlr_keyboard keyboard;
struct wlr_pointer pointer;
struct wlr_switch switch_device;
struct wlr_touch touch;
struct wlr_tablet tablet;
struct wl_list tablet_tools; // see backend/libinput/tablet_tool.c
struct wlr_tablet_pad tablet_pad;
struct wl_list link;
};
uint32_t usec_to_msec(uint64_t usec);
void handle_libinput_event(struct wlr_libinput_backend *state,
struct libinput_event *event);
void destroy_libinput_input_device(struct wlr_libinput_input_device *dev);
const char *get_libinput_device_name(struct libinput_device *device);
extern const struct wlr_keyboard_impl libinput_keyboard_impl;
extern const struct wlr_pointer_impl libinput_pointer_impl;
extern const struct wlr_switch_impl libinput_switch_impl;
extern const struct wlr_tablet_impl libinput_tablet_impl;
extern const struct wlr_tablet_pad_impl libinput_tablet_pad_impl;
extern const struct wlr_touch_impl libinput_touch_impl;
void init_device_keyboard(struct wlr_libinput_input_device *dev);
struct wlr_libinput_input_device *device_from_keyboard(struct wlr_keyboard *kb);
void handle_keyboard_key(struct libinput_event *event, struct wlr_keyboard *kb);
void init_device_pointer(struct wlr_libinput_input_device *dev);
struct wlr_libinput_input_device *device_from_pointer(struct wlr_pointer *kb);
void handle_pointer_motion(struct libinput_event *event,
struct wlr_pointer *pointer);
void handle_pointer_motion_abs(struct libinput_event *event,
struct wlr_pointer *pointer);
void handle_pointer_button(struct libinput_event *event,
struct wlr_pointer *pointer);
void handle_pointer_axis(struct libinput_event *event,
struct wlr_pointer *pointer);
#if HAVE_LIBINPUT_SCROLL_VALUE120
void handle_pointer_axis_value120(struct libinput_event *event,
struct wlr_pointer *pointer, enum wl_pointer_axis_source source);
#endif
void handle_pointer_swipe_begin(struct libinput_event *event,
struct wlr_pointer *pointer);
void handle_pointer_swipe_update(struct libinput_event *event,
struct wlr_pointer *pointer);
void handle_pointer_swipe_end(struct libinput_event *event,
struct wlr_pointer *pointer);
void handle_pointer_pinch_begin(struct libinput_event *event,
struct wlr_pointer *pointer);
void handle_pointer_pinch_update(struct libinput_event *event,
struct wlr_pointer *pointer);
void handle_pointer_pinch_end(struct libinput_event *event,
struct wlr_pointer *pointer);
void handle_pointer_hold_begin(struct libinput_event *event,
struct wlr_pointer *pointer);
void handle_pointer_hold_end(struct libinput_event *event,
struct wlr_pointer *pointer);
void init_device_switch(struct wlr_libinput_input_device *dev);
struct wlr_libinput_input_device *device_from_switch(
struct wlr_switch *switch_device);
void handle_switch_toggle(struct libinput_event *event,
struct wlr_switch *switch_device);
void init_device_touch(struct wlr_libinput_input_device *dev);
struct wlr_libinput_input_device *device_from_touch(
struct wlr_touch *touch);
void handle_touch_down(struct libinput_event *event,
struct wlr_touch *touch);
void handle_touch_up(struct libinput_event *event,
struct wlr_touch *touch);
void handle_touch_motion(struct libinput_event *event,
struct wlr_touch *touch);
void handle_touch_cancel(struct libinput_event *event,
struct wlr_touch *touch);
void handle_touch_frame(struct libinput_event *event,
struct wlr_touch *touch);
void init_device_tablet(struct wlr_libinput_input_device *dev);
void finish_device_tablet(struct wlr_libinput_input_device *dev);
struct wlr_libinput_input_device *device_from_tablet(
struct wlr_tablet *tablet);
void handle_tablet_tool_axis(struct libinput_event *event,
struct wlr_tablet *tablet);
void handle_tablet_tool_proximity(struct libinput_event *event,
struct wlr_tablet *tablet);
void handle_tablet_tool_tip(struct libinput_event *event,
struct wlr_tablet *tablet);
void handle_tablet_tool_button(struct libinput_event *event,
struct wlr_tablet *tablet);
void init_device_tablet_pad(struct wlr_libinput_input_device *dev);
void finish_device_tablet_pad(struct wlr_libinput_input_device *dev);
struct wlr_libinput_input_device *device_from_tablet_pad(
struct wlr_tablet_pad *tablet_pad);
void handle_tablet_pad_button(struct libinput_event *event,
struct wlr_tablet_pad *tablet_pad);
void handle_tablet_pad_ring(struct libinput_event *event,
struct wlr_tablet_pad *tablet_pad);
void handle_tablet_pad_strip(struct libinput_event *event,
struct wlr_tablet_pad *tablet_pad);
#endif

21
include/backend/multi.h Normal file
View file

@ -0,0 +1,21 @@
#ifndef BACKEND_MULTI_H
#define BACKEND_MULTI_H
#include <wayland-util.h>
#include <wlr/backend/interface.h>
#include <wlr/backend/multi.h>
struct wlr_multi_backend {
struct wlr_backend backend;
struct wl_list backends;
struct wl_listener event_loop_destroy;
struct {
struct wl_signal backend_add;
struct wl_signal backend_remove;
} events;
};
#endif

View file

@ -0,0 +1,20 @@
#ifndef BACKEND_SESSION_SESSION_H
#define BACKEND_SESSION_SESSION_H
#include <stdbool.h>
struct wl_display;
struct wlr_session;
struct wlr_session *libseat_session_create(struct wl_display *disp);
void libseat_session_destroy(struct wlr_session *base);
int libseat_session_open_device(struct wlr_session *base, const char *path);
void libseat_session_close_device(struct wlr_session *base, int fd);
bool libseat_change_vt(struct wlr_session *base, unsigned vt);
void session_init(struct wlr_session *session);
struct wlr_device *session_open_if_kms(struct wlr_session *restrict session,
const char *restrict path);
#endif

183
include/backend/wayland.h Normal file
View file

@ -0,0 +1,183 @@
#ifndef BACKEND_WAYLAND_H
#define BACKEND_WAYLAND_H
#include <stdbool.h>
#include <wayland-client.h>
#include <wayland-server-core.h>
#include <wlr/backend/wayland.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_pointer.h>
#include <wlr/types/wlr_tablet_pad.h>
#include <wlr/types/wlr_tablet_tool.h>
#include <wlr/types/wlr_touch.h>
#include <wlr/render/drm_format_set.h>
struct wlr_wl_backend {
struct wlr_backend backend;
/* local state */
bool started;
struct wl_event_loop *event_loop;
struct wl_list outputs;
int drm_fd;
struct wl_list buffers; // wlr_wl_buffer.link
size_t requested_outputs;
struct wl_listener event_loop_destroy;
char *activation_token;
/* remote state */
struct wl_display *remote_display;
bool own_remote_display;
struct wl_event_source *remote_display_src;
struct wl_registry *registry;
struct wl_compositor *compositor;
struct xdg_wm_base *xdg_wm_base;
struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1;
struct zwp_pointer_gestures_v1 *zwp_pointer_gestures_v1;
struct wp_presentation *presentation;
struct wl_shm *shm;
struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1;
struct zwp_relative_pointer_manager_v1 *zwp_relative_pointer_manager_v1;
struct wl_list seats; // wlr_wl_seat.link
struct zwp_tablet_manager_v2 *tablet_manager;
struct wlr_drm_format_set shm_formats;
struct wlr_drm_format_set linux_dmabuf_v1_formats;
struct wl_drm *legacy_drm;
struct xdg_activation_v1 *activation_v1;
struct wl_subcompositor *subcompositor;
struct wp_viewporter *viewporter;
char *drm_render_name;
};
struct wlr_wl_buffer {
struct wlr_buffer *buffer;
struct wl_buffer *wl_buffer;
bool released;
struct wl_list link; // wlr_wl_backend.buffers
struct wl_listener buffer_destroy;
};
struct wlr_wl_presentation_feedback {
struct wlr_wl_output *output;
struct wl_list link;
struct wp_presentation_feedback *feedback;
uint32_t commit_seq;
};
struct wlr_wl_output_layer {
struct wlr_addon addon;
struct wl_surface *surface;
struct wl_subsurface *subsurface;
struct wp_viewport *viewport;
bool mapped;
};
struct wlr_wl_output {
struct wlr_output wlr_output;
struct wlr_wl_backend *backend;
struct wl_list link;
struct wl_surface *surface;
bool own_surface;
struct wl_callback *frame_callback;
struct xdg_surface *xdg_surface;
struct xdg_toplevel *xdg_toplevel;
struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1;
struct wl_list presentation_feedbacks;
bool configured;
uint32_t enter_serial;
struct {
struct wlr_wl_pointer *pointer;
struct wl_surface *surface;
int32_t hotspot_x, hotspot_y;
} cursor;
};
struct wlr_wl_pointer {
struct wlr_pointer wlr_pointer;
struct wlr_wl_seat *seat;
struct wlr_wl_output *output;
enum wl_pointer_axis_source axis_source;
int32_t axis_discrete;
uint32_t fingers; // trackpad gesture
enum wl_pointer_axis_relative_direction axis_relative_direction;
struct wl_listener output_destroy;
struct wl_list link;
};
struct wlr_wl_touch_points {
int32_t ids[64];
size_t len;
};
struct wlr_wl_seat {
char *name;
struct wl_seat *wl_seat;
uint32_t global_name;
struct wlr_wl_backend *backend;
struct wl_keyboard *wl_keyboard;
struct wlr_keyboard wlr_keyboard;
struct wl_pointer *wl_pointer;
struct wlr_wl_pointer *active_pointer;
struct wl_list pointers; // wlr_wl_pointer.link
struct zwp_pointer_gesture_swipe_v1 *gesture_swipe;
struct zwp_pointer_gesture_pinch_v1 *gesture_pinch;
struct zwp_pointer_gesture_hold_v1 *gesture_hold;
struct zwp_relative_pointer_v1 *relative_pointer;
struct wl_touch *wl_touch;
struct wlr_touch wlr_touch;
struct wlr_wl_touch_points touch_points;
struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2;
struct zwp_tablet_v2 *zwp_tablet_v2;
struct wlr_tablet wlr_tablet;
struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2;
struct wlr_tablet_tool wlr_tablet_tool;
struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2;
struct wlr_tablet_pad wlr_tablet_pad;
struct wl_list link; // wlr_wl_backend.seats
};
struct wlr_wl_backend *get_wl_backend_from_backend(struct wlr_backend *backend);
struct wlr_wl_output *get_wl_output_from_surface(struct wlr_wl_backend *wl,
struct wl_surface *surface);
void update_wl_output_cursor(struct wlr_wl_output *output);
void init_seat_keyboard(struct wlr_wl_seat *seat);
void init_seat_pointer(struct wlr_wl_seat *seat);
void finish_seat_pointer(struct wlr_wl_seat *seat);
void create_pointer(struct wlr_wl_seat *seat, struct wlr_wl_output *output);
void init_seat_touch(struct wlr_wl_seat *seat);
void init_seat_tablet(struct wlr_wl_seat *seat);
void finish_seat_tablet(struct wlr_wl_seat *seat);
bool create_wl_seat(struct wl_seat *wl_seat, struct wlr_wl_backend *wl,
uint32_t global_name);
void destroy_wl_seat(struct wlr_wl_seat *seat);
void destroy_wl_buffer(struct wlr_wl_buffer *buffer);
extern const struct wlr_pointer_impl wl_pointer_impl;
extern const struct wlr_tablet_pad_impl wl_tablet_pad_impl;
extern const struct wlr_tablet_impl wl_tablet_impl;
#endif

147
include/backend/x11.h Normal file
View file

@ -0,0 +1,147 @@
#ifndef BACKEND_X11_H
#define BACKEND_X11_H
#include <wlr/config.h>
#include <stdbool.h>
#include <wayland-server-core.h>
#include <xcb/xcb.h>
#include <xcb/present.h>
#include <pixman.h>
#include <wlr/backend/x11.h>
#include <wlr/interfaces/wlr_keyboard.h>
#include <wlr/interfaces/wlr_output.h>
#include <wlr/interfaces/wlr_touch.h>
#include <wlr/types/wlr_pointer.h>
#include <wlr/render/drm_format_set.h>
#include "config.h"
#if HAVE_XCB_ERRORS
#include <xcb/xcb_errors.h>
#endif
#define XCB_EVENT_RESPONSE_TYPE_MASK 0x7f
struct wlr_x11_backend;
struct wlr_x11_output {
struct wlr_output wlr_output;
struct wlr_x11_backend *x11;
struct wl_list link; // wlr_x11_backend.outputs
xcb_window_t win;
xcb_present_event_t present_event_id;
int32_t win_width, win_height;
struct wlr_pointer pointer;
struct wlr_touch touch;
struct wl_list touchpoints; // wlr_x11_touchpoint.link
struct wl_list buffers; // wlr_x11_buffer.link
pixman_region32_t exposed;
uint64_t last_msc;
struct {
struct wlr_swapchain *swapchain;
xcb_render_picture_t pic;
} cursor;
};
struct wlr_x11_touchpoint {
uint32_t x11_id;
int wayland_id;
struct wl_list link; // wlr_x11_output.touch_points
};
struct wlr_x11_backend {
struct wlr_backend backend;
struct wl_event_loop *event_loop;
bool started;
xcb_connection_t *xcb;
xcb_screen_t *screen;
xcb_depth_t *depth;
xcb_visualid_t visualid;
xcb_colormap_t colormap;
xcb_cursor_t transparent_cursor;
xcb_render_pictformat_t argb32;
bool have_shm;
bool have_dri3;
uint32_t dri3_major_version, dri3_minor_version;
size_t requested_outputs;
struct wl_list outputs; // wlr_x11_output.link
struct wlr_keyboard keyboard;
int drm_fd;
struct wlr_drm_format_set dri3_formats;
struct wlr_drm_format_set shm_formats;
const struct wlr_x11_format *x11_format;
struct wlr_drm_format_set primary_dri3_formats;
struct wlr_drm_format_set primary_shm_formats;
struct wl_event_source *event_source;
struct {
xcb_atom_t wm_protocols;
xcb_atom_t wm_delete_window;
xcb_atom_t net_wm_name;
xcb_atom_t utf8_string;
xcb_atom_t variable_refresh;
} atoms;
// The time we last received an event
xcb_timestamp_t time;
#if HAVE_XCB_ERRORS
xcb_errors_context_t *errors_context;
#endif
uint8_t present_opcode;
uint8_t xinput_opcode;
struct wl_listener event_loop_destroy;
};
struct wlr_x11_buffer {
struct wlr_x11_backend *x11;
struct wlr_buffer *buffer;
xcb_pixmap_t pixmap;
struct wl_list link; // wlr_x11_output.buffers
struct wl_listener buffer_destroy;
size_t n_busy;
};
struct wlr_x11_format {
uint32_t drm;
uint8_t depth, bpp;
};
struct wlr_x11_backend *get_x11_backend_from_backend(
struct wlr_backend *wlr_backend);
struct wlr_x11_output *get_x11_output_from_window_id(
struct wlr_x11_backend *x11, xcb_window_t window);
extern const struct wlr_keyboard_impl x11_keyboard_impl;
extern const struct wlr_pointer_impl x11_pointer_impl;
extern const struct wlr_touch_impl x11_touch_impl;
void handle_x11_xinput_event(struct wlr_x11_backend *x11,
xcb_ge_generic_event_t *event);
void update_x11_pointer_position(struct wlr_x11_output *output,
xcb_timestamp_t time);
void handle_x11_configure_notify(struct wlr_x11_output *output,
xcb_configure_notify_event_t *event);
void handle_x11_present_event(struct wlr_x11_backend *x11,
xcb_ge_generic_event_t *event);
#endif

View file

@ -0,0 +1,20 @@
#ifndef INTERFACES_INPUT_DEVICE_H
#define INTERFACES_INPUT_DEVICE_H
#include <wlr/types/wlr_input_device.h>
/**
* Initializes a given wlr_input_device. Allocates memory for the name and sets
* its vendor and product id to 0.
* wlr_device must be non-NULL.
*/
void wlr_input_device_init(struct wlr_input_device *wlr_device,
enum wlr_input_device_type type, const char *name);
/**
* Cleans up all the memory owned by a given wlr_input_device and signals
* the destroy event.
*/
void wlr_input_device_finish(struct wlr_input_device *wlr_device);
#endif

38
include/meson.build Normal file
View file

@ -0,0 +1,38 @@
subdir('wlr')
exclude_files = ['meson.build', 'config.h.in', 'version.h.in']
if not features.get('drm-backend')
exclude_files += 'backend/drm.h'
exclude_files += 'types/wlr_drm_lease_v1.h'
endif
if not features.get('libinput-backend')
exclude_files += 'backend/libinput.h'
endif
if not features.get('x11-backend')
exclude_files += 'backend/x11.h'
endif
if not features.get('xwayland')
exclude_files += 'xwayland.h'
endif
if not features.get('gles2-renderer')
exclude_files += ['render/egl.h', 'render/gles2.h']
endif
if not features.get('vulkan-renderer')
exclude_files += 'render/vulkan.h'
endif
if not features.get('session')
exclude_files += 'backend/session.h'
endif
install_subdir('wlr',
install_dir: get_option('includedir'),
exclude_files: exclude_files,
)
foreach name, have : internal_features
internal_config.set10('HAVE_' + name.underscorify().to_upper(), have)
endforeach
wlr_files += configure_file(
output: 'config.h',
configuration: internal_config,
)

View file

@ -0,0 +1,9 @@
#ifndef RENDER_ALLOCATOR_ALLOCATOR_H
#define RENDER_ALLOCATOR_ALLOCATOR_H
#include <wlr/render/allocator.h>
struct wlr_allocator *allocator_autocreate_with_drm_fd(
uint32_t backend_caps, struct wlr_renderer *renderer, int drm_fd);
#endif

View file

@ -0,0 +1,37 @@
#ifndef RENDER_ALLOCATOR_DRM_DUMB_H
#define RENDER_ALLOCATOR_DRM_DUMB_H
#include <wlr/render/dmabuf.h>
#include <wlr/types/wlr_buffer.h>
#include "render/allocator/allocator.h"
struct wlr_drm_dumb_buffer {
struct wlr_buffer base;
struct wl_list link; // wlr_drm_dumb_allocator.buffers
int drm_fd; // -1 if the allocator has been destroyed
struct wlr_dmabuf_attributes dmabuf;
uint32_t format;
uint32_t handle;
uint32_t stride;
uint32_t width, height;
uint64_t size;
void *data;
};
struct wlr_drm_dumb_allocator {
struct wlr_allocator base;
struct wl_list buffers; // wlr_drm_dumb_buffer.link
int drm_fd;
};
/**
* Creates a new drm dumb allocator from a DRM FD.
*
* Does not take ownership over the FD.
*/
struct wlr_allocator *wlr_drm_dumb_allocator_create(int fd);
#endif

View file

@ -0,0 +1,34 @@
#ifndef RENDER_ALLOCATOR_GBM_H
#define RENDER_ALLOCATOR_GBM_H
#include <gbm.h>
#include <wlr/render/dmabuf.h>
#include <wlr/types/wlr_buffer.h>
#include "render/allocator/allocator.h"
struct wlr_gbm_buffer {
struct wlr_buffer base;
struct wl_list link; // wlr_gbm_allocator.buffers
struct gbm_bo *gbm_bo; // NULL if the gbm_device has been destroyed
struct wlr_dmabuf_attributes dmabuf;
};
struct wlr_gbm_allocator {
struct wlr_allocator base;
int fd;
struct gbm_device *gbm_device;
struct wl_list buffers; // wlr_gbm_buffer.link
};
/**
* Creates a new GBM allocator from a DRM FD.
*
* Takes ownership over the FD.
*/
struct wlr_allocator *wlr_gbm_allocator_create(int drm_fd);
#endif

View file

@ -0,0 +1,23 @@
#ifndef RENDER_ALLOCATOR_SHM_H
#define RENDER_ALLOCATOR_SHM_H
#include <wlr/types/wlr_buffer.h>
#include "render/allocator/allocator.h"
struct wlr_shm_buffer {
struct wlr_buffer base;
struct wlr_shm_attributes shm;
void *data;
size_t size;
};
struct wlr_shm_allocator {
struct wlr_allocator base;
};
/**
* Creates a new shared memory allocator.
*/
struct wlr_allocator *wlr_shm_allocator_create(void);
#endif

35
include/render/dmabuf.h Normal file
View file

@ -0,0 +1,35 @@
#ifndef RENDER_DMABUF_H
#define RENDER_DMABUF_H
#include <stdbool.h>
#include <stdint.h>
// Copied from <linux/dma-buf.h> to avoid #ifdef soup
#define DMA_BUF_SYNC_READ (1 << 0)
#define DMA_BUF_SYNC_WRITE (2 << 0)
#define DMA_BUF_SYNC_RW (DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE)
/**
* Check whether DMA-BUF import/export from/to sync_file is available.
*
* If this function returns true, dmabuf_import_sync_file() is supported.
*/
bool dmabuf_check_sync_file_import_export(void);
/**
* Import a sync_file into a DMA-BUF with DMA_BUF_IOCTL_IMPORT_SYNC_FILE.
*
* This can be used to make explicit sync interoperate with implicit sync.
*/
bool dmabuf_import_sync_file(int dmabuf_fd, uint32_t flags, int sync_file_fd);
/**
* Export a sync_file from a DMA-BUF with DMA_BUF_IOCTL_EXPORT_SYNC_FILE.
*
* The sync_file FD is returned on success, -1 is returned on error.
*
* This can be used to make explicit sync interoperate with implicit sync.
*/
int dmabuf_export_sync_file(int dmabuf_fd, uint32_t flags);
#endif

View file

@ -0,0 +1,23 @@
#ifndef RENDER_DRM_FORMAT_SET_H
#define RENDER_DRM_FORMAT_SET_H
#include <wlr/render/drm_format_set.h>
void wlr_drm_format_init(struct wlr_drm_format *fmt, uint32_t format);
bool wlr_drm_format_has(const struct wlr_drm_format *fmt, uint64_t modifier);
bool wlr_drm_format_add(struct wlr_drm_format *fmt, uint64_t modifier);
bool wlr_drm_format_copy(struct wlr_drm_format *dst, const struct wlr_drm_format *src);
/**
* Intersect modifiers for two DRM formats. The `dst` must be zeroed or initialized
* with other state being replaced.
*
* Both arguments must have the same format field. If the formats aren't
* compatible, NULL is returned. If either format doesn't support any modifier,
* a format that doesn't support any modifier is returned.
*/
bool wlr_drm_format_intersect(struct wlr_drm_format *dst,
const struct wlr_drm_format *a, const struct wlr_drm_format *b);
bool wlr_drm_format_set_copy(struct wlr_drm_format_set *dst, const struct wlr_drm_format_set *src);
#endif

108
include/render/egl.h Normal file
View file

@ -0,0 +1,108 @@
#ifndef RENDER_EGL_H
#define RENDER_EGL_H
#include <wlr/render/egl.h>
struct wlr_egl {
EGLDisplay display;
EGLContext context;
EGLDeviceEXT device; // may be EGL_NO_DEVICE_EXT
struct gbm_device *gbm_device;
struct {
// Display extensions
bool KHR_image_base;
bool EXT_image_dma_buf_import;
bool EXT_image_dma_buf_import_modifiers;
bool IMG_context_priority;
bool EXT_create_context_robustness;
// Device extensions
bool EXT_device_drm;
bool EXT_device_drm_render_node;
// Client extensions
bool EXT_device_query;
bool KHR_platform_gbm;
bool EXT_platform_device;
bool KHR_display_reference;
} exts;
struct {
PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT;
PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;
PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;
PFNEGLQUERYDMABUFFORMATSEXTPROC eglQueryDmaBufFormatsEXT;
PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT;
PFNEGLDEBUGMESSAGECONTROLKHRPROC eglDebugMessageControlKHR;
PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT;
PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT;
PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT;
} procs;
bool has_modifiers;
struct wlr_drm_format_set dmabuf_texture_formats;
struct wlr_drm_format_set dmabuf_render_formats;
};
struct wlr_egl_context {
EGLDisplay display;
EGLContext context;
EGLSurface draw_surface;
EGLSurface read_surface;
};
/**
* Initializes an EGL context for the given DRM FD.
*
* Will attempt to load all possibly required API functions.
*/
struct wlr_egl *wlr_egl_create_with_drm_fd(int drm_fd);
/**
* Frees all related EGL resources, makes the context not-current and
* unbinds a bound wayland display.
*/
void wlr_egl_destroy(struct wlr_egl *egl);
/**
* Creates an EGL image from the given dmabuf attributes. Check usability
* of the dmabuf with wlr_egl_check_import_dmabuf once first.
*/
EGLImageKHR wlr_egl_create_image_from_dmabuf(struct wlr_egl *egl,
struct wlr_dmabuf_attributes *attributes, bool *external_only);
/**
* Get DMA-BUF formats suitable for sampling usage.
*/
const struct wlr_drm_format_set *wlr_egl_get_dmabuf_texture_formats(
struct wlr_egl *egl);
/**
* Get DMA-BUF formats suitable for rendering usage.
*/
const struct wlr_drm_format_set *wlr_egl_get_dmabuf_render_formats(
struct wlr_egl *egl);
/**
* Destroys an EGL image created with the given wlr_egl.
*/
bool wlr_egl_destroy_image(struct wlr_egl *egl, EGLImageKHR image);
int wlr_egl_dup_drm_fd(struct wlr_egl *egl);
/**
* Restore EGL context that was previously saved using wlr_egl_save_current().
*/
bool wlr_egl_restore_context(struct wlr_egl_context *context);
/**
* Make the EGL context current.
*
* The old EGL context is saved. Callers are expected to clear the current
* context when they are done by calling wlr_egl_restore_context().
*/
bool wlr_egl_make_current(struct wlr_egl *egl, struct wlr_egl_context *save_context);
bool wlr_egl_unset_current(struct wlr_egl *egl);
#endif

172
include/render/gles2.h Normal file
View file

@ -0,0 +1,172 @@
#ifndef RENDER_GLES2_H
#define RENDER_GLES2_H
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <wlr/render/egl.h>
#include <wlr/render/gles2.h>
#include <wlr/render/interface.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/render/wlr_texture.h>
#include <wlr/util/addon.h>
#include <wlr/util/log.h>
#include "render/egl.h"
// mesa ships old GL headers that don't include this type, so for distros that use headers from
// mesa we need to def it ourselves until they update.
// https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/23144
typedef void (GL_APIENTRYP PFNGLGETINTEGER64VEXTPROC) (GLenum pname, GLint64 *data);
struct wlr_gles2_pixel_format {
uint32_t drm_format;
// optional field, if empty then internalformat = format
GLint gl_internalformat;
GLint gl_format, gl_type;
};
struct wlr_gles2_tex_shader {
GLuint program;
GLint proj;
GLint tex_proj;
GLint tex;
GLint alpha;
GLint pos_attrib;
};
struct wlr_gles2_renderer {
struct wlr_renderer wlr_renderer;
struct wlr_egl *egl;
int drm_fd;
const char *exts_str;
struct {
bool EXT_read_format_bgra;
bool KHR_debug;
bool OES_egl_image_external;
bool OES_egl_image;
bool EXT_texture_type_2_10_10_10_REV;
bool OES_texture_half_float_linear;
bool EXT_texture_norm16;
bool EXT_disjoint_timer_query;
} exts;
struct {
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
PFNGLDEBUGMESSAGECALLBACKKHRPROC glDebugMessageCallbackKHR;
PFNGLDEBUGMESSAGECONTROLKHRPROC glDebugMessageControlKHR;
PFNGLPOPDEBUGGROUPKHRPROC glPopDebugGroupKHR;
PFNGLPUSHDEBUGGROUPKHRPROC glPushDebugGroupKHR;
PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES;
PFNGLGETGRAPHICSRESETSTATUSKHRPROC glGetGraphicsResetStatusKHR;
PFNGLGENQUERIESEXTPROC glGenQueriesEXT;
PFNGLDELETEQUERIESEXTPROC glDeleteQueriesEXT;
PFNGLQUERYCOUNTEREXTPROC glQueryCounterEXT;
PFNGLGETQUERYOBJECTIVEXTPROC glGetQueryObjectivEXT;
PFNGLGETQUERYOBJECTUI64VEXTPROC glGetQueryObjectui64vEXT;
PFNGLGETINTEGER64VEXTPROC glGetInteger64vEXT;
} procs;
struct {
struct {
GLuint program;
GLint proj;
GLint color;
GLint pos_attrib;
} quad;
struct wlr_gles2_tex_shader tex_rgba;
struct wlr_gles2_tex_shader tex_rgbx;
struct wlr_gles2_tex_shader tex_ext;
} shaders;
struct wl_list buffers; // wlr_gles2_buffer.link
struct wl_list textures; // wlr_gles2_texture.link
};
struct wlr_gles2_render_timer {
struct wlr_render_timer base;
struct wlr_gles2_renderer *renderer;
struct timespec cpu_start;
struct timespec cpu_end;
GLuint id;
GLint64 gl_cpu_end;
};
struct wlr_gles2_buffer {
struct wlr_buffer *buffer;
struct wlr_gles2_renderer *renderer;
struct wl_list link; // wlr_gles2_renderer.buffers
bool external_only;
EGLImageKHR image;
GLuint rbo;
GLuint fbo;
GLuint tex;
struct wlr_addon addon;
};
struct wlr_gles2_texture {
struct wlr_texture wlr_texture;
struct wlr_gles2_renderer *renderer;
struct wl_list link; // wlr_gles2_renderer.textures
GLenum target;
// If this texture is imported from a buffer, the texture is does not own
// these states. These cannot be destroyed along with the texture in this
// case.
GLuint tex;
GLuint fbo;
bool has_alpha;
uint32_t drm_format; // for mutable textures only, used to interpret upload data
struct wlr_gles2_buffer *buffer; // for DMA-BUF imports only
};
struct wlr_gles2_render_pass {
struct wlr_render_pass base;
struct wlr_gles2_buffer *buffer;
float projection_matrix[9];
struct wlr_egl_context prev_ctx;
struct wlr_gles2_render_timer *timer;
};
bool is_gles2_pixel_format_supported(const struct wlr_gles2_renderer *renderer,
const struct wlr_gles2_pixel_format *format);
const struct wlr_gles2_pixel_format *get_gles2_format_from_drm(uint32_t fmt);
const struct wlr_gles2_pixel_format *get_gles2_format_from_gl(
GLint gl_format, GLint gl_type, bool alpha);
const uint32_t *get_gles2_shm_formats(const struct wlr_gles2_renderer *renderer,
size_t *len);
GLuint gles2_buffer_get_fbo(struct wlr_gles2_buffer *buffer);
struct wlr_gles2_renderer *gles2_get_renderer(
struct wlr_renderer *wlr_renderer);
struct wlr_gles2_render_timer *gles2_get_render_timer(
struct wlr_render_timer *timer);
struct wlr_gles2_texture *gles2_get_texture(
struct wlr_texture *wlr_texture);
struct wlr_gles2_buffer *gles2_buffer_get_or_create(struct wlr_gles2_renderer *renderer,
struct wlr_buffer *wlr_buffer);
struct wlr_texture *gles2_texture_from_buffer(struct wlr_renderer *wlr_renderer,
struct wlr_buffer *buffer);
void gles2_texture_destroy(struct wlr_gles2_texture *texture);
void push_gles2_debug_(struct wlr_gles2_renderer *renderer,
const char *file, const char *func);
#define push_gles2_debug(renderer) push_gles2_debug_(renderer, _WLR_FILENAME, __func__)
void pop_gles2_debug(struct wlr_gles2_renderer *renderer);
struct wlr_gles2_render_pass *begin_gles2_buffer_pass(struct wlr_gles2_buffer *buffer,
struct wlr_egl_context *prev_ctx, struct wlr_gles2_render_timer *timer);
#endif

View file

@ -0,0 +1,66 @@
#ifndef RENDER_PIXEL_FORMAT_H
#define RENDER_PIXEL_FORMAT_H
#include <wayland-server-protocol.h>
/**
* Information about a pixel format.
*
* A pixel format is identified via its DRM four character code (see <drm_fourcc.h>).
*
* Simple formats have a block size of 1×1 pixels and bytes_per_block contains
* the number of bytes per pixel (including padding).
*
* Tiled formats (e.g. sub-sampled YCbCr) are described with a block size
* greater than 1×1 pixels. A block is a rectangle of pixels which are stored
* next to each other in a byte-aligned memory region.
*/
struct wlr_pixel_format_info {
uint32_t drm_format;
/* Equivalent of the format if it has an alpha channel,
* DRM_FORMAT_INVALID (0) if NA
*/
uint32_t opaque_substitute;
/* Bytes per block (including padding) */
uint32_t bytes_per_block;
/* Size of a block in pixels (zero for 1×1) */
uint32_t block_width, block_height;
};
/**
* Get pixel format information from a DRM FourCC.
*
* NULL is returned if the pixel format is unknown.
*/
const struct wlr_pixel_format_info *drm_get_pixel_format_info(uint32_t fmt);
/**
* Get the number of pixels per block for a pixel format.
*/
uint32_t pixel_format_info_pixels_per_block(const struct wlr_pixel_format_info *info);
/**
* Get the minimum stride for a given pixel format and width.
*/
int32_t pixel_format_info_min_stride(const struct wlr_pixel_format_info *info, int32_t width);
/**
* Check whether a stride is large enough for a given pixel format and width.
*/
bool pixel_format_info_check_stride(const struct wlr_pixel_format_info *info,
int32_t stride, int32_t width);
/**
* Convert an enum wl_shm_format to a DRM FourCC.
*/
uint32_t convert_wl_shm_format_to_drm(enum wl_shm_format fmt);
/**
* Convert a DRM FourCC to an enum wl_shm_format.
*/
enum wl_shm_format convert_drm_format_to_wl_shm(uint32_t fmt);
/**
* Return true if the DRM FourCC fmt has an alpha channel, false otherwise.
*/
bool pixel_format_has_alpha(uint32_t fmt);
#endif

64
include/render/pixman.h Normal file
View file

@ -0,0 +1,64 @@
#ifndef RENDER_PIXMAN_H
#define RENDER_PIXMAN_H
#include <wlr/render/drm_format_set.h>
#include <wlr/render/interface.h>
#include <wlr/render/pixman.h>
#include <wlr/render/wlr_renderer.h>
#include "render/pixel_format.h"
struct wlr_pixman_pixel_format {
uint32_t drm_format;
pixman_format_code_t pixman_format;
};
struct wlr_pixman_buffer;
struct wlr_pixman_renderer {
struct wlr_renderer wlr_renderer;
struct wl_list buffers; // wlr_pixman_buffer.link
struct wl_list textures; // wlr_pixman_texture.link
struct wlr_drm_format_set drm_formats;
};
struct wlr_pixman_buffer {
struct wlr_buffer *buffer;
struct wlr_pixman_renderer *renderer;
pixman_image_t *image;
struct wl_listener buffer_destroy;
struct wl_list link; // wlr_pixman_renderer.buffers
};
struct wlr_pixman_texture {
struct wlr_texture wlr_texture;
struct wlr_pixman_renderer *renderer;
struct wl_list link; // wlr_pixman_renderer.textures
pixman_image_t *image;
pixman_format_code_t format;
const struct wlr_pixel_format_info *format_info;
void *data; // if created via texture_from_pixels
struct wlr_buffer *buffer; // if created via texture_from_buffer
};
struct wlr_pixman_render_pass {
struct wlr_render_pass base;
struct wlr_pixman_buffer *buffer;
};
pixman_format_code_t get_pixman_format_from_drm(uint32_t fmt);
uint32_t get_drm_format_from_pixman(pixman_format_code_t fmt);
const uint32_t *get_pixman_drm_formats(size_t *len);
bool begin_pixman_data_ptr_access(struct wlr_buffer *buffer, pixman_image_t **image_ptr,
uint32_t flags);
struct wlr_pixman_render_pass *begin_pixman_render_pass(
struct wlr_pixman_buffer *buffer);
#endif

456
include/render/vulkan.h Normal file
View file

@ -0,0 +1,456 @@
#ifndef RENDER_VULKAN_H
#define RENDER_VULKAN_H
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <vulkan/vulkan.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/render/wlr_texture.h>
#include <wlr/render/drm_format_set.h>
#include <wlr/render/interface.h>
#include <wlr/util/addon.h>
#include "util/rect_union.h"
struct wlr_vk_descriptor_pool;
struct wlr_vk_texture;
struct wlr_vk_instance {
VkInstance instance;
VkDebugUtilsMessengerEXT messenger;
struct {
PFN_vkCreateDebugUtilsMessengerEXT createDebugUtilsMessengerEXT;
PFN_vkDestroyDebugUtilsMessengerEXT destroyDebugUtilsMessengerEXT;
} api;
};
// Creates and initializes a vulkan instance.
// The debug parameter determines if validation layers are enabled and a
// debug messenger created.
struct wlr_vk_instance *vulkan_instance_create(bool debug);
void vulkan_instance_destroy(struct wlr_vk_instance *ini);
// Logical vulkan device state.
struct wlr_vk_device {
struct wlr_vk_instance *instance;
VkPhysicalDevice phdev;
VkDevice dev;
int drm_fd;
bool implicit_sync_interop;
bool sampler_ycbcr_conversion;
// we only ever need one queue for rendering and transfer commands
uint32_t queue_family;
VkQueue queue;
struct {
PFN_vkGetMemoryFdPropertiesKHR vkGetMemoryFdPropertiesKHR;
PFN_vkWaitSemaphoresKHR vkWaitSemaphoresKHR;
PFN_vkGetSemaphoreCounterValueKHR vkGetSemaphoreCounterValueKHR;
PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR;
PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR;
PFN_vkQueueSubmit2KHR vkQueueSubmit2KHR;
} api;
uint32_t format_prop_count;
struct wlr_vk_format_props *format_props;
struct wlr_drm_format_set dmabuf_render_formats;
struct wlr_drm_format_set dmabuf_texture_formats;
// supported formats for textures (contains only those formats
// that support everything we need for textures)
uint32_t shm_format_count;
uint32_t *shm_formats; // to implement vulkan_get_shm_texture_formats
};
// Tries to find the VkPhysicalDevice for the given drm fd.
// Might find none and return VK_NULL_HANDLE.
VkPhysicalDevice vulkan_find_drm_phdev(struct wlr_vk_instance *ini, int drm_fd);
int vulkan_open_phdev_drm_fd(VkPhysicalDevice phdev);
// Creates a device for the given instance and physical device.
struct wlr_vk_device *vulkan_device_create(struct wlr_vk_instance *ini,
VkPhysicalDevice phdev);
void vulkan_device_destroy(struct wlr_vk_device *dev);
// Tries to find any memory bit for the given vulkan device that
// supports the given flags and is set in req_bits (e.g. if memory
// type 2 is ok, (req_bits & (1 << 2)) must not be 0.
// Set req_bits to 0xFFFFFFFF to allow all types.
int vulkan_find_mem_type(struct wlr_vk_device *device,
VkMemoryPropertyFlags flags, uint32_t req_bits);
struct wlr_vk_format {
uint32_t drm;
VkFormat vk;
VkFormat vk_srgb; // sRGB version of the format, or 0 if nonexistent
bool is_ycbcr;
};
extern const VkImageUsageFlags vulkan_render_usage, vulkan_shm_tex_usage, vulkan_dma_tex_usage;
// Returns all known format mappings.
// Might not be supported for gpu/usecase.
const struct wlr_vk_format *vulkan_get_format_list(size_t *len);
const struct wlr_vk_format *vulkan_get_format_from_drm(uint32_t drm_format);
struct wlr_vk_format_modifier_props {
VkDrmFormatModifierPropertiesEXT props;
VkExtent2D max_extent;
bool has_mutable_srgb;
};
struct wlr_vk_format_props {
struct wlr_vk_format format;
struct {
VkExtent2D max_extent;
VkFormatFeatureFlags features;
bool has_mutable_srgb;
} shm;
struct {
uint32_t render_mod_count;
struct wlr_vk_format_modifier_props *render_mods;
uint32_t texture_mod_count;
struct wlr_vk_format_modifier_props *texture_mods;
} dmabuf;
};
void vulkan_format_props_query(struct wlr_vk_device *dev,
const struct wlr_vk_format *format);
const struct wlr_vk_format_modifier_props *vulkan_format_props_find_modifier(
const struct wlr_vk_format_props *props, uint64_t mod, bool render);
void vulkan_format_props_finish(struct wlr_vk_format_props *props);
struct wlr_vk_pipeline_layout_key {
const struct wlr_vk_format *ycbcr_format;
enum wlr_scale_filter_mode filter_mode;
};
struct wlr_vk_pipeline_layout {
struct wlr_vk_pipeline_layout_key key;
VkPipelineLayout vk;
VkDescriptorSetLayout ds;
VkSampler sampler;
// for YCbCr pipelines only
struct {
VkSamplerYcbcrConversion conversion;
VkFormat format;
} ycbcr;
struct wl_list link; // struct wlr_vk_renderer.pipeline_layouts
};
// Constants used to pick the color transform for the texture drawing
// fragment shader. Must match those in shaders/texture.frag
enum wlr_vk_texture_transform {
WLR_VK_TEXTURE_TRANSFORM_IDENTITY = 0,
WLR_VK_TEXTURE_TRANSFORM_SRGB = 1,
};
enum wlr_vk_shader_source {
WLR_VK_SHADER_SOURCE_TEXTURE,
WLR_VK_SHADER_SOURCE_SINGLE_COLOR,
};
struct wlr_vk_pipeline_key {
struct wlr_vk_pipeline_layout_key layout;
enum wlr_vk_shader_source source;
enum wlr_render_blend_mode blend_mode;
// only used if source is texture
enum wlr_vk_texture_transform texture_transform;
};
struct wlr_vk_pipeline {
struct wlr_vk_pipeline_key key;
VkPipeline vk;
const struct wlr_vk_pipeline_layout *layout;
struct wlr_vk_render_format_setup *setup;
struct wl_list link; // struct wlr_vk_render_format_setup
};
// For each format we want to render, we need a separate renderpass
// and therefore also separate pipelines.
struct wlr_vk_render_format_setup {
struct wl_list link; // wlr_vk_renderer.render_format_setups
const struct wlr_vk_format *render_format; // used in renderpass
VkRenderPass render_pass;
VkPipeline output_pipe;
struct wlr_vk_renderer *renderer;
struct wl_list pipelines; // struct wlr_vk_pipeline.link
};
// Renderer-internal represenation of an wlr_buffer imported for rendering.
struct wlr_vk_render_buffer {
struct wlr_buffer *wlr_buffer;
struct wlr_addon addon;
struct wlr_vk_renderer *renderer;
struct wlr_vk_render_format_setup *render_setup;
struct wl_list link; // wlr_vk_renderer.buffers
VkImage image;
VkImageView image_view;
VkFramebuffer framebuffer;
uint32_t mem_count;
VkDeviceMemory memories[WLR_DMABUF_MAX_PLANES];
bool transitioned;
VkImage blend_image;
VkImageView blend_image_view;
VkDeviceMemory blend_memory;
VkDescriptorSet blend_descriptor_set;
struct wlr_vk_descriptor_pool *blend_attachment_pool;
bool blend_transitioned;
};
struct wlr_vk_command_buffer {
VkCommandBuffer vk;
bool recording;
uint64_t timeline_point;
// Textures to destroy after the command buffer completes
struct wl_list destroy_textures; // wlr_vk_texture.destroy_link
// Staging shared buffers to release after the command buffer completes
struct wl_list stage_buffers; // wlr_vk_shared_buffer.link
// For DMA-BUF implicit sync interop, may be NULL
VkSemaphore binary_semaphore;
};
#define VULKAN_COMMAND_BUFFERS_CAP 64
// Vulkan wlr_renderer implementation on top of a wlr_vk_device.
struct wlr_vk_renderer {
struct wlr_renderer wlr_renderer;
struct wlr_backend *backend;
struct wlr_vk_device *dev;
VkCommandPool command_pool;
VkShaderModule vert_module;
VkShaderModule tex_frag_module;
VkShaderModule quad_frag_module;
VkShaderModule output_module;
struct wl_list pipeline_layouts; // struct wlr_vk_pipeline_layout.link
// for blend->output subpass
VkPipelineLayout output_pipe_layout;
VkDescriptorSetLayout output_ds_layout;
size_t last_output_pool_size;
struct wl_list output_descriptor_pools; // wlr_vk_descriptor_pool.link
VkSemaphore timeline_semaphore;
uint64_t timeline_point;
size_t last_pool_size;
struct wl_list descriptor_pools; // wlr_vk_descriptor_pool.link
struct wl_list render_format_setups; // wlr_vk_render_format_setup.link
struct wl_list textures; // wlr_vk_texture.link
// Textures to return to foreign queue
struct wl_list foreign_textures; // wlr_vk_texture.foreign_link
struct wl_list render_buffers; // wlr_vk_render_buffer.link
// Pool of command buffers
struct wlr_vk_command_buffer command_buffers[VULKAN_COMMAND_BUFFERS_CAP];
struct {
struct wlr_vk_command_buffer *cb;
uint64_t last_timeline_point;
struct wl_list buffers; // wlr_vk_shared_buffer.link
} stage;
struct {
bool initialized;
uint32_t drm_format;
uint32_t width, height;
VkImage dst_image;
VkDeviceMemory dst_img_memory;
} read_pixels_cache;
};
// vertex shader push constant range data
struct wlr_vk_vert_pcr_data {
float mat4[4][4];
float uv_off[2];
float uv_size[2];
};
struct wlr_vk_texture_view {
struct wl_list link; // struct wlr_vk_texture.views
const struct wlr_vk_pipeline_layout *layout;
VkDescriptorSet ds;
VkImageView image_view;
struct wlr_vk_descriptor_pool *ds_pool;
};
struct wlr_vk_pipeline *setup_get_or_create_pipeline(
struct wlr_vk_render_format_setup *setup,
const struct wlr_vk_pipeline_key *key);
struct wlr_vk_pipeline_layout *get_or_create_pipeline_layout(
struct wlr_vk_renderer *renderer,
const struct wlr_vk_pipeline_layout_key *key);
struct wlr_vk_texture_view *vulkan_texture_get_or_create_view(
struct wlr_vk_texture *texture,
const struct wlr_vk_pipeline_layout *layout);
// Creates a vulkan renderer for the given device.
struct wlr_renderer *vulkan_renderer_create_for_device(struct wlr_vk_device *dev);
// stage utility - for uploading/retrieving data
// Gets an command buffer in recording state which is guaranteed to be
// executed before the next frame.
VkCommandBuffer vulkan_record_stage_cb(struct wlr_vk_renderer *renderer);
// Submits the current stage command buffer and waits until it has
// finished execution.
bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer);
struct wlr_vk_render_pass {
struct wlr_render_pass base;
struct wlr_vk_renderer *renderer;
struct wlr_vk_render_buffer *render_buffer;
struct wlr_vk_command_buffer *command_buffer;
struct rect_union updated_region;
VkPipeline bound_pipeline;
float projection[9];
bool failed;
};
struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *renderer,
struct wlr_vk_render_buffer *buffer);
// Suballocates a buffer span with the given size that can be mapped
// and used as staging buffer. The allocation is implicitly released when the
// stage cb has finished execution. The start of the span will be a multiple
// of the given alignment.
struct wlr_vk_buffer_span vulkan_get_stage_span(
struct wlr_vk_renderer *renderer, VkDeviceSize size,
VkDeviceSize alignment);
// Tries to allocate a texture descriptor set. Will additionally
// return the pool it was allocated from when successful (for freeing it later).
struct wlr_vk_descriptor_pool *vulkan_alloc_texture_ds(
struct wlr_vk_renderer *renderer, VkDescriptorSetLayout ds_layout,
VkDescriptorSet *ds);
// Tries to allocate a descriptor set for the blending image. Will
// additionally return the pool it was allocated from when successful
// (for freeing it later).
struct wlr_vk_descriptor_pool *vulkan_alloc_blend_ds(
struct wlr_vk_renderer *renderer, VkDescriptorSet *ds);
// Frees the given descriptor set from the pool its pool.
void vulkan_free_ds(struct wlr_vk_renderer *renderer,
struct wlr_vk_descriptor_pool *pool, VkDescriptorSet ds);
struct wlr_vk_format_props *vulkan_format_props_from_drm(
struct wlr_vk_device *dev, uint32_t drm_format);
struct wlr_vk_renderer *vulkan_get_renderer(struct wlr_renderer *r);
struct wlr_vk_command_buffer *vulkan_acquire_command_buffer(
struct wlr_vk_renderer *renderer);
uint64_t vulkan_end_command_buffer(struct wlr_vk_command_buffer *cb,
struct wlr_vk_renderer *renderer);
void vulkan_reset_command_buffer(struct wlr_vk_command_buffer *cb);
bool vulkan_wait_command_buffer(struct wlr_vk_command_buffer *cb,
struct wlr_vk_renderer *renderer);
bool vulkan_sync_render_buffer(struct wlr_vk_renderer *renderer,
struct wlr_vk_render_buffer *render_buffer, struct wlr_vk_command_buffer *cb);
bool vulkan_sync_foreign_texture(struct wlr_vk_texture *texture);
bool vulkan_read_pixels(struct wlr_vk_renderer *vk_renderer,
VkFormat src_format, VkImage src_image,
uint32_t drm_format, uint32_t stride,
uint32_t width, uint32_t height, uint32_t src_x, uint32_t src_y,
uint32_t dst_x, uint32_t dst_y, void *data);
// State (e.g. image texture) associated with a surface.
struct wlr_vk_texture {
struct wlr_texture wlr_texture;
struct wlr_vk_renderer *renderer;
uint32_t mem_count;
VkDeviceMemory memories[WLR_DMABUF_MAX_PLANES];
VkImage image;
const struct wlr_vk_format *format;
enum wlr_vk_texture_transform transform;
struct wlr_vk_command_buffer *last_used_cb; // to track when it can be destroyed
bool dmabuf_imported;
bool owned; // if dmabuf_imported: whether we have ownership of the image
bool transitioned; // if dma_imported: whether we transitioned it away from preinit
bool has_alpha; // whether the image is has alpha channel
bool using_mutable_srgb; // is this accessed through _SRGB format view
struct wl_list foreign_link; // wlr_vk_renderer.foreign_textures
struct wl_list destroy_link; // wlr_vk_command_buffer.destroy_textures
struct wl_list link; // wlr_vk_renderer.textures
// If imported from a wlr_buffer
struct wlr_buffer *buffer;
struct wlr_addon buffer_addon;
// For DMA-BUF implicit sync interop
VkSemaphore foreign_semaphores[WLR_DMABUF_MAX_PLANES];
struct wl_list views; // struct wlr_vk_texture_ds.link
};
struct wlr_vk_texture *vulkan_get_texture(struct wlr_texture *wlr_texture);
VkImage vulkan_import_dmabuf(struct wlr_vk_renderer *renderer,
const struct wlr_dmabuf_attributes *attribs,
VkDeviceMemory mems[static WLR_DMABUF_MAX_PLANES], uint32_t *n_mems,
bool for_render, bool *using_mutable_srgb);
struct wlr_texture *vulkan_texture_from_buffer(
struct wlr_renderer *wlr_renderer, struct wlr_buffer *buffer);
void vulkan_texture_destroy(struct wlr_vk_texture *texture);
struct wlr_vk_descriptor_pool {
VkDescriptorPool pool;
uint32_t free; // number of textures that can be allocated
struct wl_list link; // wlr_vk_renderer.descriptor_pools
};
struct wlr_vk_allocation {
VkDeviceSize start;
VkDeviceSize size;
};
// List of suballocated staging buffers.
// Used to upload to/read from device local images.
struct wlr_vk_shared_buffer {
struct wl_list link; // wlr_vk_renderer.stage.buffers or wlr_vk_command_buffer.stage_buffers
VkBuffer buffer;
VkDeviceMemory memory;
VkDeviceSize buf_size;
struct wl_array allocs; // struct wlr_vk_allocation
};
// Suballocated range on a buffer.
struct wlr_vk_buffer_span {
struct wlr_vk_shared_buffer *buffer;
struct wlr_vk_allocation alloc;
};
// util
const char *vulkan_strerror(VkResult err);
void vulkan_change_layout(VkCommandBuffer cb, VkImage img,
VkImageLayout ol, VkPipelineStageFlags srcs, VkAccessFlags srca,
VkImageLayout nl, VkPipelineStageFlags dsts, VkAccessFlags dsta);
#define wlr_vk_error(fmt, res, ...) wlr_log(WLR_ERROR, fmt ": %s (%d)", \
vulkan_strerror(res), res, ##__VA_ARGS__)
#endif // RENDER_VULKAN_H

View file

@ -0,0 +1,23 @@
#ifndef RENDER_WLR_RENDERER_H
#define RENDER_WLR_RENDERER_H
#include <wlr/render/wlr_renderer.h>
/**
* Automatically select and create a renderer suitable for the DRM FD.
*/
struct wlr_renderer *renderer_autocreate_with_drm_fd(int drm_fd);
/**
* Get the supported render formats. Buffers allocated with a format from this
* list may be attached via wlr_renderer_begin_with_buffer.
*/
const struct wlr_drm_format_set *wlr_renderer_get_render_formats(
struct wlr_renderer *renderer);
/**
* Get the supported buffer capabilities.
*
* This functions returns a bitfield of supported wlr_buffer_cap.
*/
uint32_t renderer_get_render_buffer_caps(struct wlr_renderer *renderer);
#endif

View file

@ -0,0 +1,77 @@
#ifndef TYPES_WLR_BUFFER
#define TYPES_WLR_BUFFER
#include <wlr/types/wlr_buffer.h>
/**
* A read-only buffer that holds a data pointer.
*
* This is suitable for passing raw pixel data to a function that accepts a
* wlr_buffer.
*/
struct wlr_readonly_data_buffer {
struct wlr_buffer base;
const void *data;
uint32_t format;
size_t stride;
void *saved_data;
};
/**
* Wraps a read-only data pointer into a wlr_buffer. The data pointer may be
* accessed until readonly_data_buffer_drop() is called.
*/
struct wlr_readonly_data_buffer *readonly_data_buffer_create(uint32_t format,
size_t stride, uint32_t width, uint32_t height, const void *data);
/**
* Drops ownership of the buffer (see wlr_buffer_drop() for more details) and
* perform a copy of the data pointer if a consumer still has the buffer locked.
*/
bool readonly_data_buffer_drop(struct wlr_readonly_data_buffer *buffer);
struct wlr_dmabuf_buffer {
struct wlr_buffer base;
struct wlr_dmabuf_attributes dmabuf;
bool saved;
};
/**
* Wraps a DMA-BUF into a wlr_buffer. The DMA-BUF may be accessed until
* dmabuf_buffer_drop() is called.
*/
struct wlr_dmabuf_buffer *dmabuf_buffer_create(
struct wlr_dmabuf_attributes *dmabuf);
/**
* Drops ownership of the buffer (see wlr_buffer_drop() for more details) and
* takes a reference to the DMA-BUF (by dup'ing its file descriptors) if a
* consumer still has the buffer locked.
*/
bool dmabuf_buffer_drop(struct wlr_dmabuf_buffer *buffer);
/**
* Check whether a buffer is fully opaque.
*
* When true is returned, the buffer is guaranteed to be fully opaque, but the
* reverse is not true: false may be returned in cases where the buffer is fully
* opaque.
*/
bool buffer_is_opaque(struct wlr_buffer *buffer);
/**
* Creates a struct wlr_client_buffer from a given struct wlr_buffer by creating
* a texture from it, and copying its struct wl_resource.
*/
struct wlr_client_buffer *wlr_client_buffer_create(struct wlr_buffer *buffer,
struct wlr_renderer *renderer);
/**
* Try to update the buffer's content.
*
* Fails if there's more than one reference to the buffer or if the texture
* isn't mutable.
*/
bool wlr_client_buffer_apply_damage(struct wlr_client_buffer *client_buffer,
struct wlr_buffer *next, const pixman_region32_t *damage);
#endif

View file

@ -0,0 +1,43 @@
#ifndef TYPES_WLR_DATA_DEVICE_H
#define TYPES_WLR_DATA_DEVICE_H
#include <wayland-server-core.h>
#include <wlr/types/wlr_data_device.h>
#define DATA_DEVICE_ALL_ACTIONS (WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | \
WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | \
WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)
struct wlr_client_data_source {
struct wlr_data_source source;
struct wlr_data_source_impl impl;
struct wl_resource *resource;
bool finalized;
};
extern const struct wlr_surface_role drag_icon_surface_role;
struct wlr_data_offer *data_offer_create(struct wl_resource *device_resource,
struct wlr_data_source *source, enum wlr_data_offer_type type);
void data_offer_update_action(struct wlr_data_offer *offer);
void data_offer_destroy(struct wlr_data_offer *offer);
struct wlr_client_data_source *client_data_source_create(
struct wl_client *client, uint32_t version, uint32_t id,
struct wl_list *resource_list);
struct wlr_client_data_source *client_data_source_from_resource(
struct wl_resource *resource);
void data_source_notify_finish(struct wlr_data_source *source);
struct wlr_seat_client *seat_client_from_data_device_resource(
struct wl_resource *resource);
/**
* Creates a new wl_data_offer if there is a wl_data_source currently set as
* the seat selection and sends it to the seat client, followed by the
* wl_data_device.selection() event. If there is no current selection, the
* wl_data_device.selection() event will carry a NULL wl_data_offer. If the
* client does not have a wl_data_device for the seat nothing will be done.
*/
void seat_client_send_selection(struct wlr_seat_client *seat_client);
#endif

View file

@ -0,0 +1,8 @@
#include <wlr/types/wlr_keyboard.h>
void keyboard_key_update(struct wlr_keyboard *keyboard,
struct wlr_keyboard_key_event *event);
bool keyboard_modifier_update(struct wlr_keyboard *keyboard);
void keyboard_led_update(struct wlr_keyboard *keyboard);

View file

@ -0,0 +1,15 @@
#ifndef TYPES_WLR_MATRIX_H
#define TYPES_WLR_MATRIX_H
#include <wlr/types/wlr_matrix.h>
/**
* Writes a 2D orthographic projection matrix to mat of (width, height) with a
* specified wl_output_transform.
*
* Equivalent to glOrtho(0, width, 0, height, 1, -1) with the transform applied.
*/
void matrix_projection(float mat[static 9], int width, int height,
enum wl_output_transform transform);
#endif

Some files were not shown because too many files have changed in this diff Show more