update: to hyprland version
This commit is contained in:
commit
538219d389
421 changed files with 93083 additions and 0 deletions
38
.builds/alpine.yml
Normal file
38
.builds/alpine.yml
Normal 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
48
.builds/archlinux.yml
Normal 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
37
.builds/freebsd.yml
Normal 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
14
.editorconfig
Normal 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
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/subprojects/
|
7
.gitlab-ci.yml
Normal file
7
.gitlab-ci.yml
Normal 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
|
15
.gitlab/issue_templates/Default.md
Normal file
15
.gitlab/issue_templates/Default.md
Normal 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
1
.mailmap
Normal file
|
@ -0,0 +1 @@
|
|||
Isaac Freund <mail@isaacfreund.com> <ifreund@ifreund.xyz>
|
449
CONTRIBUTING.md
Normal file
449
CONTRIBUTING.md
Normal 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
21
LICENSE
Normal 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
87
README.md
Normal 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
447
backend/backend.c
Normal 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
434
backend/drm/atomic.c
Normal 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
262
backend/drm/backend.c
Normal 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
2069
backend/drm/drm.c
Normal file
File diff suppressed because it is too large
Load diff
258
backend/drm/fb.c
Normal file
258
backend/drm/fb.c
Normal 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
26
backend/drm/gen_pnpids.sh
Executable 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
246
backend/drm/legacy.c
Normal 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
513
backend/drm/libliftoff.c
Normal 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
56
backend/drm/meson.build
Normal 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
91
backend/drm/monitor.c
Normal 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
209
backend/drm/properties.c
Normal 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
172
backend/drm/renderer.c
Normal 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
279
backend/drm/util.c
Normal 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);
|
||||
}
|
88
backend/headless/backend.c
Normal file
88
backend/headless/backend.c
Normal 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;
|
||||
}
|
4
backend/headless/meson.build
Normal file
4
backend/headless/meson.build
Normal file
|
@ -0,0 +1,4 @@
|
|||
wlr_files += files(
|
||||
'backend.c',
|
||||
'output.c',
|
||||
)
|
146
backend/headless/output.c
Normal file
146
backend/headless/output.c
Normal 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
245
backend/libinput/backend.c
Normal 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
263
backend/libinput/events.c
Normal 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;
|
||||
}
|
||||
}
|
51
backend/libinput/keyboard.c
Normal file
51
backend/libinput/keyboard.c
Normal 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);
|
||||
}
|
34
backend/libinput/meson.build
Normal file
34
backend/libinput/meson.build
Normal 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
286
backend/libinput/pointer.c
Normal 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
49
backend/libinput/switch.c
Normal 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);
|
||||
}
|
201
backend/libinput/tablet_pad.c
Normal file
201
backend/libinput/tablet_pad.c
Normal 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);
|
||||
}
|
277
backend/libinput/tablet_tool.c
Normal file
277
backend/libinput/tablet_tool.c
Normal 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
83
backend/libinput/touch.c
Normal 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
29
backend/meson.build
Normal 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
227
backend/multi/backend.c
Normal 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);
|
||||
}
|
||||
}
|
1
backend/multi/meson.build
Normal file
1
backend/multi/meson.build
Normal file
|
@ -0,0 +1 @@
|
|||
wlr_files += files('backend.c')
|
17
backend/session/meson.build
Normal file
17
backend/session/meson.build
Normal 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
551
backend/session/session.c
Normal 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
701
backend/wayland/backend.c
Normal 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, ®istry_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;
|
||||
}
|
30
backend/wayland/meson.build
Normal file
30
backend/wayland/meson.build
Normal 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
932
backend/wayland/output.c
Normal 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
557
backend/wayland/pointer.c
Normal 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
387
backend/wayland/seat.c
Normal 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
929
backend/wayland/tablet_v2.c
Normal 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
736
backend/x11/backend.c
Normal 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
331
backend/x11/input_device.c
Normal 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
36
backend/x11/meson.build
Normal 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
774
backend/x11/output.c
Normal 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
86
docs/architecture.md
Normal 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
68
docs/env_vars.md
Normal 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
1
examples/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/compositor/protocols
|
193
examples/cairo-buffer.c
Normal file
193
examples/cairo-buffer.c
Normal 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
2641
examples/cat.c
Normal file
File diff suppressed because it is too large
Load diff
13
examples/cat.h
Normal file
13
examples/cat.h
Normal 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
212
examples/embedded.c
Normal 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, ®istry_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
258
examples/fullscreen-shell.c
Normal 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
68
examples/meson.build
Normal 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
340
examples/output-layers.c
Normal 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
304
examples/output-layout.c
Normal 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
416
examples/pointer.c
Normal 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
288
examples/rotation.c
Normal 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
207
examples/scene-graph.c
Normal 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
204
examples/simple.c
Normal 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
416
examples/tablet.c
Normal 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
311
examples/touch.c
Normal 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
13
include/backend/backend.h
Normal 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
208
include/backend/drm/drm.h
Normal 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
24
include/backend/drm/fb.h
Normal 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
|
44
include/backend/drm/iface.h
Normal file
44
include/backend/drm/iface.h
Normal 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
|
24
include/backend/drm/monitor.h
Normal file
24
include/backend/drm/monitor.h
Normal 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
|
84
include/backend/drm/properties.h
Normal file
84
include/backend/drm/properties.h
Normal 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
|
40
include/backend/drm/renderer.h
Normal file
40
include/backend/drm/renderer.h
Normal 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
|
43
include/backend/drm/util.h
Normal file
43
include/backend/drm/util.h
Normal 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
|
30
include/backend/headless.h
Normal file
30
include/backend/headless.h
Normal 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
139
include/backend/libinput.h
Normal 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
21
include/backend/multi.h
Normal 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
|
20
include/backend/session/session.h
Normal file
20
include/backend/session/session.h
Normal 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
183
include/backend/wayland.h
Normal 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
147
include/backend/x11.h
Normal 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
|
20
include/interfaces/wlr_input_device.h
Normal file
20
include/interfaces/wlr_input_device.h
Normal 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
38
include/meson.build
Normal 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,
|
||||
)
|
9
include/render/allocator/allocator.h
Normal file
9
include/render/allocator/allocator.h
Normal 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
|
37
include/render/allocator/drm_dumb.h
Normal file
37
include/render/allocator/drm_dumb.h
Normal 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
|
34
include/render/allocator/gbm.h
Normal file
34
include/render/allocator/gbm.h
Normal 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
|
23
include/render/allocator/shm.h
Normal file
23
include/render/allocator/shm.h
Normal 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
35
include/render/dmabuf.h
Normal 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
|
23
include/render/drm_format_set.h
Normal file
23
include/render/drm_format_set.h
Normal 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
108
include/render/egl.h
Normal 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
172
include/render/gles2.h
Normal 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
|
66
include/render/pixel_format.h
Normal file
66
include/render/pixel_format.h
Normal 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
64
include/render/pixman.h
Normal 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
456
include/render/vulkan.h
Normal 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
|
23
include/render/wlr_renderer.h
Normal file
23
include/render/wlr_renderer.h
Normal 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
|
77
include/types/wlr_buffer.h
Normal file
77
include/types/wlr_buffer.h
Normal 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
|
43
include/types/wlr_data_device.h
Normal file
43
include/types/wlr_data_device.h
Normal 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
|
8
include/types/wlr_keyboard.h
Normal file
8
include/types/wlr_keyboard.h
Normal 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);
|
15
include/types/wlr_matrix.h
Normal file
15
include/types/wlr_matrix.h
Normal 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
Loading…
Add table
Reference in a new issue