diff options
323 files changed, 69556 insertions, 0 deletions
diff --git a/.builds/alpine.yml b/.builds/alpine.yml new file mode 100644 index 00000000..9751dc8a --- /dev/null +++ b/.builds/alpine.yml @@ -0,0 +1,22 @@ +image: alpine/edge +packages: + - eudev-dev + - ffmpeg-dev + - libcap-dev + - libinput-dev + - libxkbcommon-dev + - mesa-dev + - meson + - pixman-dev + - wayland-dev + - wayland-protocols + - xcb-util-image-dev +sources: + - https://github.com/swaywm/wlroots +tasks: + - setup: | + cd wlroots + meson build + - build: | + cd wlroots + ninja -C build diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml new file mode 100644 index 00000000..2a4b5fc0 --- /dev/null +++ b/.builds/archlinux.yml @@ -0,0 +1,26 @@ +image: archlinux +packages: + - clang + - ffmpeg + - libcap + - libinput + - libxkbcommon + - mesa + - meson + - pixman + - wayland + - wayland-protocols + - xcb-util-image +sources: + - https://github.com/swaywm/wlroots +tasks: + - setup: | + cd wlroots + CC=gcc meson build-gcc + CC=clang meson build-clang + - gcc: | + cd wlroots/build-gcc + ninja + - clang: | + cd wlroots/build-clang + ninja diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml new file mode 100644 index 00000000..7199af85 --- /dev/null +++ b/.builds/freebsd.yml @@ -0,0 +1,79 @@ +image: freebsd +packages: +- devel/automake +- multimedia/ffmpeg +- devel/gmake +- devel/json-c +- devel/libtool +- x11/libxkbcommon +- textproc/libxslt +- x11-toolkits/pango +- devel/pkgconf +- print/texinfo +- x11/xcb-util-image +- x11/xcb-util-wm +- python36 +- py36-setuptools +sources: +- https://github.com/swaywm/wlroots +tasks: +- setup: | + # Don't build unnecessary stuff + echo "OPTIONS_UNSET+= NLS DOCS EXAMPLES LIBWACOM" | sudo tee -a /etc/make.conf + # Note: this could probably be set in the FreeBSD base image + echo "BATCH=yes" | sudo tee -a /etc/make.conf +- ports_tree: | + # This is ugly, but fetching and extracting the whole ports tree takes a + # really-really long time... + # First we need a clean tree, and renaming is faster than deleting. + sudo mv /usr/ports /usr/ports.orig + sudo mkdir /usr/ports + # Fetch only needed ports + sudo svnlite export --force https://svn.FreeBSD.org/ports/head/GIDs /usr/ports/GIDs + sudo svnlite export --force https://svn.FreeBSD.org/ports/head/Keywords /usr/ports/Keywords + sudo svnlite export --force https://svn.FreeBSD.org/ports/head/Makefile /usr/ports/Makefile + sudo svnlite export --force https://svn.FreeBSD.org/ports/head/Mk /usr/ports/Mk + sudo svnlite export --force https://svn.FreeBSD.org/ports/head/Templates /usr/ports/Templates + sudo svnlite export --force https://svn.FreeBSD.org/ports/head/Tools /usr/ports/Tools + sudo svnlite export --force https://svn.FreeBSD.org/ports/head/UIDs /usr/ports/UIDs + sudo svnlite export --force https://svn.FreeBSD.org/ports/head/devel/libevdev /usr/ports/devel/libevdev + sudo svnlite export --force https://svn.FreeBSD.org/ports/head/devel/libmtdev /usr/ports/devel/libmtdev + sudo svnlite export --force https://svn.FreeBSD.org/ports/head/devel/libudev-devd /usr/ports/devel/libudev-devd + sudo svnlite export --force https://svn.FreeBSD.org/ports/head/devel/meson /usr/ports/devel/meson + sudo svnlite export --force https://svn.FreeBSD.org/ports/head/devel/ninja /usr/ports/devel/ninja + sudo svnlite export --force https://svn.FreeBSD.org/ports/head/devel/py-evdev /usr/ports/devel/py-evdev + sudo svnlite export --force https://svn.FreeBSD.org/ports/head/devel/py-six /usr/ports/devel/py-six + sudo svnlite export --force https://svn.FreeBSD.org/ports/head/graphics/wayland-protocols /usr/ports/graphics/wayland-protocols + sudo svnlite export --force https://svn.FreeBSD.org/ports/head/graphics/wayland /usr/ports/graphics/wayland + sudo svnlite export --force https://svn.FreeBSD.org/ports/head/lang/python27 /usr/ports/lang/python27 + sudo svnlite export --force https://svn.FreeBSD.org/ports/head/lang/python36 /usr/ports/lang/python36 +- fixup_libinput: | + sudo svnlite export https://github.com/FreeBSDDesktop/freebsd-ports/branches/feature/input/devel/libepoll-shim /usr/ports/devel/libepoll-shim + sudo svnlite export https://github.com/FreeBSDDesktop/freebsd-ports/branches/feature/input/x11/libinput /usr/ports/x11/libinput + sudo svnlite export https://github.com/FreeBSDDesktop/freebsd-ports/branches/feature/input/devel/evdev-proto /usr/ports/devel/evdev-proto + sudo svnlite export https://github.com/FreeBSDDesktop/freebsd-ports/branches/feature/input/devel/py-pyudev /usr/ports/devel/py-pyudev +- ports_build: | + # v4l_compat is a dependency of libinput, but the version in the ports tree + # conflicts with the new evdev-proto. It can be safely removed though. + sudo pkg remove -fy v4l_compat + cd /usr/ports/devel/evdev-proto && sudo make install clean + cd /usr/ports/graphics/wayland-protocols/ && sudo make install + cd /usr/ports/x11/libinput/ && sudo make install clean +- fixup_epoll: | + cat << 'EOF' | sudo tee /usr/local/libdata/pkgconfig/epoll-shim.pc + prefix=/usr/local + exec_prefix=\$\{\$prefix\} + libdir=${prefix}/lib + sharedlibdir=${prefix}/lib + includedir=${prefix}/include/libepoll-shim + Name: epoll-shim + Description: epoll shim implemented using kevent + Version: 0 + Requires: + Libs: -L${libdir} -L${sharedlibdir} -lepoll-shim -lthr + Cflags: -I${includedir} + EOF +- wlroots: | + cd wlroots + meson build + ninja -C build diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..c74fecda --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = tab +trim_trailing_whitespace = true + +[*.xml] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..be30a5fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.clang_complete +*.o +*.a +bin/ +test/ +build/ +wayland-*-protocol.* +wlr-example.ini +rootston.ini diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..10e3ac22 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,356 @@ +# Contributing to wlroots + +Contributing just involves sending a pull request. You will probably be more +successful with your contribution if you visit +[#sway-devel](https://webchat.freenode.net/?channels=sway-devel) on +irc.freenode.net 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. + +## Pull Requests + +If you already have your own pull 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 https://github.com/username/wlroots && cd wlroots` +3. `git remote add upstream https://github.com/swaywm/wlroots` + +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 pull request from your feature branch + +When you submit your pull 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 pull 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 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 *"Fix #742"* 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 Github +issues](https://help.github.com/articles/closing-issues-via-commit-messages/), +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 pull 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 pull request +description on Github is probably fair game for going into the extended commit +message as well. + +See [here](https://chris.beams.io/posts/git-commit/) 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 pull 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 GitHub 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 pull request when all reviewers approve. +5. **File** follow-up tickets if appropriate. + +## Style Reference + +wlroots is written in C with a style similar to the [kernel +style](https://www.kernel.org/doc/Documentation/process/coding-style.rst), 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 80 columns, but you can go up to 100 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 + +For functions that are responsible for constructing and destructing an object, +they should be written as a pair of one of two forms: +* `init`/`finish`: These initialize/deinitialize a type, but are **NOT** +responsible for allocating it. They should accept a pointer to some +pre-allocated memory (e.g. a member of a struct). +* `create`/`destroy`: These also initialize/deinitialize, but will return a +pointer to a `malloc`ed chunk of memory, and will `free` it in `destroy`. + +A destruction function should always be able to accept a NULL pointer or a +zeroed value and exit cleanly; this simplifies error handling a lot. + +### Error Codes + +For functions not returning a value, they should return a (stdbool.h) bool to +indicated if 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. + +### 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 list of resources clients +created by binding to the global, a destroy signal and a `wl_display` destroy +listener. Example: + +```c +struct wlr_compositor { + struct wl_global *global; + struct wl_list resources; + … + + 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`, destroy all bound resources +and then destroy the struct. + +### 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); +} +``` + +### 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`). +Destructor requests should only call `wl_resource_destroy`. + +The compositor should not destroy resources on its own. + +### 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); +} +``` diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..b75a8c40 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2017, 2018 Drew DeVault +Copyright (c) 2014 Jari Vetoniemi + +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. diff --git a/README.md b/README.md new file mode 100644 index 00000000..2ebef443 --- /dev/null +++ b/README.md @@ -0,0 +1,98 @@ +# wlroots + +Pluggable, composable, unopinionated modules for building a +[Wayland](http://wayland.freedesktop.org/) compositor; or about 50,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](https://github.com/swaywm/wlroots/wiki/Getting-started) to +get started with wlroots. + +wlroots is developed under the direction of the +[sway](https://github.com/swaywm/sway) project. A variety of wrapper libraries +[are available](https://github.com/swaywm) for using it with your favorite +programming language. + +## Building + +Install dependencies: + +* meson +* wayland +* wayland-protocols +* EGL +* GLESv2 +* libdrm +* GBM +* libinput +* xkbcommon +* udev +* pixman +* systemd (optional, for logind support) +* elogind (optional, for logind support on systems without systemd) +* libcap (optional, for capability support) + +If you choose to enable X11 support: + +* xcb +* xcb-composite +* xcb-xfixes +* xcb-xinput +* xcb-image +* xcb-render +* x11-xcb +* xcb-errors (optional, for improved error reporting) +* x11-icccm (optional, for improved Xwayland introspection) + +Run these commands: + + meson build + ninja -C build + +Install like so: + + sudo ninja -C build install + +## Running the test compositor + +wlroots comes with a test compositor called rootston, which demonstrates the +features of the library and is used as a testbed for the development of the +library. It may also be useful as a reference for understanding how to use +various wlroots features, but it's not considered a production-quality codebase +and is not designed for daily use. + +If you followed the build instructions above the rootston executable can be +found at `./build/rootston/rootston`. To use it, refer to the example config at +[./rootston/rootston.ini.example](https://github.com/swaywm/wlroots/blob/master/rootston/rootston.ini.example) +and place a config file of your own at `rootston.ini` in the working directory +(or in an arbitrary location via `rootston -C`). Other options are available, +refer to `rootston -h`. + +## Contributing + +See [CONTRIBUTING.md](https://github.com/swaywm/wlroots/blob/master/CONTRIBUTING.md). diff --git a/backend/backend.c b/backend/backend.c new file mode 100644 index 00000000..b369a135 --- /dev/null +++ b/backend/backend.c @@ -0,0 +1,291 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <errno.h> +#include <libinput.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/backend/drm.h> +#include <wlr/backend/headless.h> +#include <wlr/backend/interface.h> +#include <wlr/backend/libinput.h> +#include <wlr/backend/multi.h> +#include <wlr/backend/session.h> +#include <wlr/backend/wayland.h> +#include <wlr/config.h> +#include <wlr/util/log.h> +#include "backend/multi.h" + +#if WLR_HAS_X11_BACKEND +#include <wlr/backend/x11.h> +#endif + +void wlr_backend_init(struct wlr_backend *backend, + const struct wlr_backend_impl *impl) { + assert(backend); + backend->impl = impl; + wl_signal_init(&backend->events.destroy); + wl_signal_init(&backend->events.new_input); + wl_signal_init(&backend->events.new_output); +} + +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); + } +} + +struct wlr_renderer *wlr_backend_get_renderer(struct wlr_backend *backend) { + if (backend->impl->get_renderer) { + return backend->impl->get_renderer(backend); + } + return NULL; +} + +struct wlr_session *wlr_backend_get_session(struct wlr_backend *backend) { + if (backend->impl->get_session) { + return backend->impl->get_session(backend); + } + return NULL; +} + +clockid_t wlr_backend_get_presentation_clock(struct wlr_backend *backend) { + if (backend->impl->get_presentation_clock) { + return backend->impl->get_presentation_clock(backend); + } + return CLOCK_MONOTONIC; +} + +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; +} + +static struct wlr_backend *attempt_wl_backend(struct wl_display *display, + wlr_renderer_create_func_t create_renderer_func) { + struct wlr_backend *backend = wlr_wl_backend_create(display, NULL, create_renderer_func); + 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; +} + +#if WLR_HAS_X11_BACKEND +static struct wlr_backend *attempt_x11_backend(struct wl_display *display, + const char *x11_display, wlr_renderer_create_func_t create_renderer_func) { + struct wlr_backend *backend = wlr_x11_backend_create(display, x11_display, create_renderer_func); + 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; +} +#endif + +static struct wlr_backend *attempt_headless_backend( + struct wl_display *display, wlr_renderer_create_func_t create_renderer_func) { + struct wlr_backend *backend = wlr_headless_backend_create(display, create_renderer_func); + 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 wl_display *display, + struct wlr_backend *backend, struct wlr_session *session, + wlr_renderer_create_func_t create_renderer_func) { + int gpus[8]; + size_t num_gpus = wlr_session_find_gpus(session, 8, gpus); + struct wlr_backend *primary_drm = NULL; + wlr_log(WLR_INFO, "Found %zu GPUs", num_gpus); + + for (size_t i = 0; i < num_gpus; ++i) { + struct wlr_backend *drm = wlr_drm_backend_create(display, session, + gpus[i], primary_drm, create_renderer_func); + if (!drm) { + wlr_log(WLR_ERROR, "Failed to open DRM device %d", gpus[i]); + continue; + } + + if (!primary_drm) { + primary_drm = drm; + } + + wlr_multi_backend_add(backend, drm); + } + + return primary_drm; +} + +static struct wlr_backend *attempt_backend_by_name(struct wl_display *display, + struct wlr_backend *backend, struct wlr_session **session, + const char *name, wlr_renderer_create_func_t create_renderer_func) { + if (strcmp(name, "wayland") == 0) { + return attempt_wl_backend(display, create_renderer_func); +#if WLR_HAS_X11_BACKEND + } else if (strcmp(name, "x11") == 0) { + return attempt_x11_backend(display, NULL, create_renderer_func); +#endif + } else if (strcmp(name, "headless") == 0) { + return attempt_headless_backend(display, create_renderer_func); + } else if (strcmp(name, "drm") == 0 || strcmp(name, "libinput") == 0) { + // DRM and libinput need a session + if (!*session) { + *session = wlr_session_create(display); + if (!*session) { + wlr_log(WLR_ERROR, "failed to start a session"); + return NULL; + } + } + + if (strcmp(name, "libinput") == 0) { + return wlr_libinput_backend_create(display, *session); + } else { + return attempt_drm_backend(display, backend, *session, create_renderer_func); + } + } + + wlr_log(WLR_ERROR, "unrecognized backend '%s'", name); + return NULL; +} + +struct wlr_backend *wlr_backend_autocreate(struct wl_display *display, + wlr_renderer_create_func_t create_renderer_func) { + struct wlr_backend *backend = wlr_multi_backend_create(display); + struct wlr_multi_backend *multi = (struct wlr_multi_backend *)backend; + if (!backend) { + wlr_log(WLR_ERROR, "could not allocate multibackend"); + return NULL; + } + + char *names = getenv("WLR_BACKENDS"); + if (names) { + names = strdup(names); + if (names == NULL) { + wlr_log(WLR_ERROR, "allocation failed"); + wlr_backend_destroy(backend); + return NULL; + } + + char *saveptr; + char *name = strtok_r(names, ",", &saveptr); + while (name != NULL) { + struct wlr_backend *subbackend = attempt_backend_by_name(display, + backend, &multi->session, name, create_renderer_func); + if (subbackend == NULL) { + wlr_log(WLR_ERROR, "failed to start backend '%s'", name); + wlr_session_destroy(multi->session); + wlr_backend_destroy(backend); + free(names); + return NULL; + } + + if (!wlr_multi_backend_add(backend, subbackend)) { + wlr_log(WLR_ERROR, "failed to add backend '%s'", name); + wlr_session_destroy(multi->session); + wlr_backend_destroy(backend); + free(names); + return NULL; + } + + name = strtok_r(NULL, ",", &saveptr); + } + + free(names); + return backend; + } + + if (getenv("WAYLAND_DISPLAY") || getenv("_WAYLAND_DISPLAY") || + getenv("WAYLAND_SOCKET")) { + struct wlr_backend *wl_backend = attempt_wl_backend(display, + create_renderer_func); + if (wl_backend) { + wlr_multi_backend_add(backend, wl_backend); + return backend; + } + } + +#if WLR_HAS_X11_BACKEND + const char *x11_display = getenv("DISPLAY"); + if (x11_display) { + struct wlr_backend *x11_backend = + attempt_x11_backend(display, x11_display, create_renderer_func); + if (x11_backend) { + wlr_multi_backend_add(backend, x11_backend); + return backend; + } + } +#endif + + // Attempt DRM+libinput + multi->session = wlr_session_create(display); + if (!multi->session) { + wlr_log(WLR_ERROR, "Failed to start a DRM session"); + wlr_backend_destroy(backend); + return NULL; + } + + struct wlr_backend *libinput = wlr_libinput_backend_create(display, + multi->session); + if (!libinput) { + wlr_log(WLR_ERROR, "Failed to start libinput backend"); + wlr_session_destroy(multi->session); + wlr_backend_destroy(backend); + return NULL; + } + wlr_multi_backend_add(backend, libinput); + + struct wlr_backend *primary_drm = attempt_drm_backend(display, backend, + multi->session, create_renderer_func); + if (!primary_drm) { + wlr_log(WLR_ERROR, "Failed to open any DRM device"); + wlr_backend_destroy(libinput); + wlr_session_destroy(multi->session); + wlr_backend_destroy(backend); + return NULL; + } + + return backend; +} diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c new file mode 100644 index 00000000..fc649d68 --- /dev/null +++ b/backend/drm/atomic.c @@ -0,0 +1,273 @@ +#include <gbm.h> +#include <stdlib.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include "backend/drm/drm.h" +#include "backend/drm/iface.h" +#include "backend/drm/util.h" + +struct atomic { + drmModeAtomicReq *req; + int cursor; + bool failed; +}; + +static void atomic_begin(struct wlr_drm_crtc *crtc, struct atomic *atom) { + if (!crtc->atomic) { + crtc->atomic = drmModeAtomicAlloc(); + if (!crtc->atomic) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + atom->failed = true; + return; + } + } + + atom->req = crtc->atomic; + atom->cursor = drmModeAtomicGetCursor(atom->req); + atom->failed = false; +} + +static bool atomic_end(int drm_fd, struct atomic *atom) { + if (atom->failed) { + return false; + } + + uint32_t flags = DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_NONBLOCK; + if (drmModeAtomicCommit(drm_fd, atom->req, flags, NULL)) { + wlr_log_errno(WLR_ERROR, "Atomic test failed"); + drmModeAtomicSetCursor(atom->req, atom->cursor); + return false; + } + + return true; +} + +static bool atomic_commit(int drm_fd, struct atomic *atom, + struct wlr_drm_connector *conn, uint32_t flags, bool modeset) { + if (atom->failed) { + return false; + } + + int ret = drmModeAtomicCommit(drm_fd, atom->req, flags, conn); + if (ret) { + wlr_log_errno(WLR_ERROR, "%s: Atomic commit failed (%s)", + conn->output.name, modeset ? "modeset" : "pageflip"); + + // Try to commit without new changes + drmModeAtomicSetCursor(atom->req, atom->cursor); + if (drmModeAtomicCommit(drm_fd, atom->req, flags, conn)) { + wlr_log_errno(WLR_ERROR, + "%s: Atomic commit without new changes failed (%s)", + conn->output.name, modeset ? "modeset" : "pageflip"); + } + } + + drmModeAtomicSetCursor(atom->req, 0); + + return !ret; +} + +static inline 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; + } +} + +static void set_plane_props(struct atomic *atom, struct wlr_drm_plane *plane, + uint32_t crtc_id, uint32_t fb_id, bool set_crtc_xy) { + uint32_t id = plane->id; + const union wlr_drm_plane_props *props = &plane->props; + + // 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)plane->surf.width << 16); + atomic_add(atom, id, props->src_h, (uint64_t)plane->surf.height << 16); + atomic_add(atom, id, props->crtc_w, plane->surf.width); + atomic_add(atom, id, props->crtc_h, plane->surf.height); + atomic_add(atom, id, props->fb_id, fb_id); + atomic_add(atom, id, props->crtc_id, crtc_id); + if (set_crtc_xy) { + atomic_add(atom, id, props->crtc_x, 0); + atomic_add(atom, id, props->crtc_y, 0); + } +} + +static bool atomic_crtc_pageflip(struct wlr_drm_backend *drm, + struct wlr_drm_connector *conn, + struct wlr_drm_crtc *crtc, + uint32_t fb_id, drmModeModeInfo *mode) { + if (mode != NULL) { + if (crtc->mode_id != 0) { + drmModeDestroyPropertyBlob(drm->fd, crtc->mode_id); + } + + if (drmModeCreatePropertyBlob(drm->fd, mode, sizeof(*mode), + &crtc->mode_id)) { + wlr_log_errno(WLR_ERROR, "Unable to create property blob"); + return false; + } + } + + uint32_t flags = DRM_MODE_PAGE_FLIP_EVENT; + if (mode != NULL) { + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } else { + flags |= DRM_MODE_ATOMIC_NONBLOCK; + } + + struct atomic atom; + atomic_begin(crtc, &atom); + atomic_add(&atom, conn->id, conn->props.crtc_id, crtc->id); + if (mode != NULL && conn->props.link_status != 0) { + atomic_add(&atom, conn->id, conn->props.link_status, + DRM_MODE_LINK_STATUS_GOOD); + } + atomic_add(&atom, crtc->id, crtc->props.mode_id, crtc->mode_id); + atomic_add(&atom, crtc->id, crtc->props.active, 1); + set_plane_props(&atom, crtc->primary, crtc->id, fb_id, true); + return atomic_commit(drm->fd, &atom, conn, flags, mode); +} + +static bool atomic_conn_enable(struct wlr_drm_backend *drm, + struct wlr_drm_connector *conn, bool enable) { + struct wlr_drm_crtc *crtc = conn->crtc; + if (crtc == NULL) { + return !enable; + } + + struct atomic atom; + atomic_begin(crtc, &atom); + atomic_add(&atom, crtc->id, crtc->props.active, enable); + if (enable) { + atomic_add(&atom, conn->id, conn->props.crtc_id, crtc->id); + atomic_add(&atom, crtc->id, crtc->props.mode_id, crtc->mode_id); + } else { + atomic_add(&atom, conn->id, conn->props.crtc_id, 0); + atomic_add(&atom, crtc->id, crtc->props.mode_id, 0); + } + return atomic_commit(drm->fd, &atom, conn, DRM_MODE_ATOMIC_ALLOW_MODESET, + true); +} + +bool legacy_crtc_set_cursor(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, struct gbm_bo *bo); + +static bool atomic_crtc_set_cursor(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, struct gbm_bo *bo) { + if (!crtc || !crtc->cursor) { + return true; + } + + struct wlr_drm_plane *plane = crtc->cursor; + // We can't use atomic operations on fake planes + if (plane->id == 0) { + return legacy_crtc_set_cursor(drm, crtc, bo); + } + + struct atomic atom; + + atomic_begin(crtc, &atom); + + if (bo) { + set_plane_props(&atom, plane, crtc->id, get_fb_for_bo(bo), false); + } else { + atomic_add(&atom, plane->id, plane->props.fb_id, 0); + atomic_add(&atom, plane->id, plane->props.crtc_id, 0); + } + + return atomic_end(drm->fd, &atom); +} + +bool legacy_crtc_move_cursor(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, int x, int y); + +static bool atomic_crtc_move_cursor(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, int x, int y) { + if (!crtc || !crtc->cursor) { + return true; + } + + struct wlr_drm_plane *plane = crtc->cursor; + // We can't use atomic operations on fake planes + if (plane->id == 0) { + return legacy_crtc_move_cursor(drm, crtc, x, y); + } + + struct atomic atom; + + atomic_begin(crtc, &atom); + atomic_add(&atom, plane->id, plane->props.crtc_x, x); + atomic_add(&atom, plane->id, plane->props.crtc_y, y); + return atomic_end(drm->fd, &atom); +} + +static bool atomic_crtc_set_gamma(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, size_t size, + uint16_t *r, uint16_t *g, uint16_t *b) { + // Fallback to legacy gamma interface when gamma properties are not available + // (can happen on older Intel GPUs that support gamma but not degamma). + // TEMP: This is broken on AMDGPU. Provide a fallback to legacy until they + // get it fixed. Ref https://bugs.freedesktop.org/show_bug.cgi?id=107459 + const char *no_atomic_str = getenv("WLR_DRM_NO_ATOMIC_GAMMA"); + bool no_atomic = no_atomic_str != NULL && strcmp(no_atomic_str, "1") == 0; + if (crtc->props.gamma_lut == 0 || no_atomic) { + return legacy_iface.crtc_set_gamma(drm, crtc, size, r, g, b); + } + + struct drm_color_lut *gamma = malloc(size * sizeof(struct drm_color_lut)); + if (gamma == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate gamma table"); + return false; + } + + for (size_t i = 0; i < size; i++) { + gamma[i].red = r[i]; + gamma[i].green = g[i]; + gamma[i].blue = b[i]; + } + + if (crtc->gamma_lut != 0) { + drmModeDestroyPropertyBlob(drm->fd, crtc->gamma_lut); + } + + if (drmModeCreatePropertyBlob(drm->fd, gamma, + size * sizeof(struct drm_color_lut), &crtc->gamma_lut)) { + free(gamma); + wlr_log_errno(WLR_ERROR, "Unable to create property blob"); + return false; + } + free(gamma); + + struct atomic atom; + atomic_begin(crtc, &atom); + atomic_add(&atom, crtc->id, crtc->props.gamma_lut, crtc->gamma_lut); + return atomic_end(drm->fd, &atom); +} + +static size_t atomic_crtc_get_gamma_size(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc) { + if (crtc->props.gamma_lut_size == 0) { + return legacy_iface.crtc_get_gamma_size(drm, crtc); + } + + uint64_t gamma_lut_size; + if (!get_drm_prop(drm->fd, crtc->id, crtc->props.gamma_lut_size, + &gamma_lut_size)) { + wlr_log(WLR_ERROR, "Unable to get gamma lut size"); + return 0; + } + + return (size_t)gamma_lut_size; +} + +const struct wlr_drm_interface atomic_iface = { + .conn_enable = atomic_conn_enable, + .crtc_pageflip = atomic_crtc_pageflip, + .crtc_set_cursor = atomic_crtc_set_cursor, + .crtc_move_cursor = atomic_crtc_move_cursor, + .crtc_set_gamma = atomic_crtc_set_gamma, + .crtc_get_gamma_size = atomic_crtc_get_gamma_size, +}; diff --git a/backend/drm/backend.c b/backend/drm/backend.c new file mode 100644 index 00000000..a9082077 --- /dev/null +++ b/backend/drm/backend.c @@ -0,0 +1,211 @@ +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/backend/interface.h> +#include <wlr/backend/session.h> +#include <wlr/interfaces/wlr_output.h> +#include <wlr/render/egl.h> +#include <wlr/types/wlr_list.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include "backend/drm/drm.h" +#include "util/signal.h" + +struct wlr_drm_backend *get_drm_backend_from_backend( + struct wlr_backend *wlr_backend) { + assert(wlr_backend_is_drm(wlr_backend)); + return (struct wlr_drm_backend *)wlr_backend; +} + +static bool backend_start(struct wlr_backend *backend) { + struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); + scan_drm_connectors(drm); + return true; +} + +static void backend_destroy(struct wlr_backend *backend) { + if (!backend) { + return; + } + + struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); + + restore_drm_outputs(drm); + + struct wlr_drm_connector *conn, *next; + wl_list_for_each_safe(conn, next, &drm->outputs, link) { + wlr_output_destroy(&conn->output); + } + + wlr_signal_emit_safe(&backend->events.destroy, backend); + + wl_list_remove(&drm->display_destroy.link); + wl_list_remove(&drm->session_signal.link); + wl_list_remove(&drm->drm_invalidated.link); + + finish_drm_resources(drm); + finish_drm_renderer(&drm->renderer); + wlr_session_close_file(drm->session, drm->fd); + wl_event_source_remove(drm->drm_event); + free(drm); +} + +static struct wlr_renderer *backend_get_renderer( + struct wlr_backend *backend) { + struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); + + if (drm->parent) { + return drm->parent->renderer.wlr_rend; + } else { + return drm->renderer.wlr_rend; + } +} + +static clockid_t backend_get_presentation_clock(struct wlr_backend *backend) { + struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); + return drm->clock; +} + +static struct wlr_backend_impl backend_impl = { + .start = backend_start, + .destroy = backend_destroy, + .get_renderer = backend_get_renderer, + .get_presentation_clock = backend_get_presentation_clock, +}; + +bool wlr_backend_is_drm(struct wlr_backend *b) { + return b->impl == &backend_impl; +} + +static void session_signal(struct wl_listener *listener, void *data) { + struct wlr_drm_backend *drm = + wl_container_of(listener, drm, session_signal); + struct wlr_session *session = data; + + if (session->active) { + wlr_log(WLR_INFO, "DRM fd resumed"); + scan_drm_connectors(drm); + + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link){ + if (conn->output.enabled) { + wlr_output_set_mode(&conn->output, conn->output.current_mode); + } else { + enable_drm_connector(&conn->output, false); + } + + if (!conn->crtc) { + continue; + } + + struct wlr_drm_plane *plane = conn->crtc->cursor; + drm->iface->crtc_set_cursor(drm, conn->crtc, + (plane && plane->cursor_enabled) ? plane->cursor_bo : NULL); + drm->iface->crtc_move_cursor(drm, conn->crtc, conn->cursor_x, + conn->cursor_y); + + if (conn->crtc->gamma_table != NULL) { + size_t size = conn->crtc->gamma_table_size; + uint16_t *r = conn->crtc->gamma_table; + uint16_t *g = conn->crtc->gamma_table + size; + uint16_t *b = conn->crtc->gamma_table + 2 * size; + drm->iface->crtc_set_gamma(drm, conn->crtc, size, r, g, b); + } else { + set_drm_connector_gamma(&conn->output, 0, NULL, NULL, NULL); + } + } + } else { + wlr_log(WLR_INFO, "DRM fd paused"); + } +} + +static void drm_invalidated(struct wl_listener *listener, void *data) { + struct wlr_drm_backend *drm = + wl_container_of(listener, drm, drm_invalidated); + + char *name = drmGetDeviceNameFromFd2(drm->fd); + wlr_log(WLR_DEBUG, "%s invalidated", name); + free(name); + + scan_drm_connectors(drm); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_drm_backend *drm = + wl_container_of(listener, drm, display_destroy); + backend_destroy(&drm->backend); +} + +struct wlr_backend *wlr_drm_backend_create(struct wl_display *display, + struct wlr_session *session, int gpu_fd, struct wlr_backend *parent, + wlr_renderer_create_func_t create_renderer_func) { + assert(display && session && gpu_fd >= 0); + assert(!parent || wlr_backend_is_drm(parent)); + + char *name = drmGetDeviceNameFromFd2(gpu_fd); + drmVersion *version = drmGetVersion(gpu_fd); + wlr_log(WLR_INFO, "Initializing DRM backend for %s (%s)", name, version->name); + free(name); + drmFreeVersion(version); + + struct wlr_drm_backend *drm = calloc(1, sizeof(struct wlr_drm_backend)); + 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->outputs); + + drm->fd = gpu_fd; + if (parent != NULL) { + drm->parent = get_drm_backend_from_backend(parent); + } + + drm->drm_invalidated.notify = drm_invalidated; + wlr_session_signal_add(session, gpu_fd, &drm->drm_invalidated); + + drm->display = display; + struct wl_event_loop *event_loop = wl_display_get_event_loop(display); + + drm->drm_event = wl_event_loop_add_fd(event_loop, drm->fd, + WL_EVENT_READABLE, handle_drm_event, NULL); + if (!drm->drm_event) { + wlr_log(WLR_ERROR, "Failed to create DRM event source"); + goto error_fd; + } + + drm->session_signal.notify = session_signal; + wl_signal_add(&session->session_signal, &drm->session_signal); + + if (!check_drm_features(drm)) { + goto error_event; + } + + if (!init_drm_resources(drm)) { + goto error_event; + } + + if (!init_drm_renderer(drm, &drm->renderer, create_renderer_func)) { + wlr_log(WLR_ERROR, "Failed to initialize renderer"); + goto error_event; + } + + drm->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &drm->display_destroy); + + return &drm->backend; + +error_event: + wl_list_remove(&drm->session_signal.link); + wl_event_source_remove(drm->drm_event); +error_fd: + wlr_session_close_file(drm->session, drm->fd); + free(drm); + return NULL; +} diff --git a/backend/drm/drm.c b/backend/drm/drm.c new file mode 100644 index 00000000..735b7c29 --- /dev/null +++ b/backend/drm/drm.c @@ -0,0 +1,1418 @@ +#define _POSIX_C_SOURCE 200112L +#include <assert.h> +#include <drm_mode.h> +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <errno.h> +#include <gbm.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <wayland-server.h> +#include <wayland-util.h> +#include <wlr/backend/interface.h> +#include <wlr/interfaces/wlr_output.h> +#include <wlr/render/gles2.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include "backend/drm/drm.h" +#include "backend/drm/iface.h" +#include "backend/drm/util.h" +#include "util/signal.h" + +bool check_drm_features(struct wlr_drm_backend *drm) { + uint64_t cap; + if (drm->parent) { + if (drmGetCap(drm->fd, DRM_CAP_PRIME, &cap) || + !(cap & DRM_PRIME_CAP_IMPORT)) { + wlr_log(WLR_ERROR, + "PRIME import not supported on secondary GPU"); + return false; + } + + if (drmGetCap(drm->parent->fd, DRM_CAP_PRIME, &cap) || + !(cap & DRM_PRIME_CAP_EXPORT)) { + wlr_log(WLR_ERROR, + "PRIME export not supported on primary GPU"); + return false; + } + } + + if (drmSetClientCap(drm->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) { + wlr_log(WLR_ERROR, "DRM universal planes unsupported"); + return false; + } + + const char *no_atomic = getenv("WLR_DRM_NO_ATOMIC"); + if (no_atomic && strcmp(no_atomic, "1") == 0) { + wlr_log(WLR_DEBUG, + "WLR_DRM_NO_ATOMIC set, forcing legacy DRM interface"); + drm->iface = &legacy_iface; + } else if (drmSetClientCap(drm->fd, DRM_CLIENT_CAP_ATOMIC, 1)) { + wlr_log(WLR_DEBUG, + "Atomic modesetting unsupported, using legacy DRM interface"); + drm->iface = &legacy_iface; + } else { + wlr_log(WLR_DEBUG, "Using atomic DRM interface"); + drm->iface = &atomic_iface; + } + + int ret = drmGetCap(drm->fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap); + drm->clock = (ret == 0 && cap == 1) ? CLOCK_MONOTONIC : CLOCK_REALTIME; + + return true; +} + +static int cmp_plane(const void *arg1, const void *arg2) { + const struct wlr_drm_plane *a = arg1; + const struct wlr_drm_plane *b = arg2; + + return (int)a->type - (int)b->type; +} + +static bool init_planes(struct wlr_drm_backend *drm) { + drmModePlaneRes *plane_res = drmModeGetPlaneResources(drm->fd); + if (!plane_res) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM plane resources"); + return false; + } + + wlr_log(WLR_INFO, "Found %"PRIu32" DRM planes", plane_res->count_planes); + + if (plane_res->count_planes == 0) { + drmModeFreePlaneResources(plane_res); + return true; + } + + drm->num_planes = plane_res->count_planes; + drm->planes = calloc(drm->num_planes, sizeof(*drm->planes)); + if (!drm->planes) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto error_res; + } + + for (size_t i = 0; i < drm->num_planes; ++i) { + struct wlr_drm_plane *p = &drm->planes[i]; + + drmModePlane *plane = drmModeGetPlane(drm->fd, plane_res->planes[i]); + if (!plane) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM plane"); + goto error_planes; + } + + p->id = plane->plane_id; + p->possible_crtcs = plane->possible_crtcs; + uint64_t type; + + if (!get_drm_plane_props(drm->fd, p->id, &p->props) || + !get_drm_prop(drm->fd, p->id, p->props.type, &type)) { + drmModeFreePlane(plane); + goto error_planes; + } + + p->type = type; + drm->num_type_planes[type]++; + + drmModeFreePlane(plane); + } + + wlr_log(WLR_INFO, "(%zu overlay, %zu primary, %zu cursor)", + drm->num_overlay_planes, + drm->num_primary_planes, + drm->num_cursor_planes); + + qsort(drm->planes, drm->num_planes, sizeof(*drm->planes), cmp_plane); + + drm->overlay_planes = drm->planes; + drm->primary_planes = drm->overlay_planes + + drm->num_overlay_planes; + drm->cursor_planes = drm->primary_planes + + drm->num_primary_planes; + + drmModeFreePlaneResources(plane_res); + return true; + +error_planes: + free(drm->planes); +error_res: + drmModeFreePlaneResources(plane_res); + return false; +} + +bool init_drm_resources(struct wlr_drm_backend *drm) { + drmModeRes *res = drmModeGetResources(drm->fd); + if (!res) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM resources"); + return false; + } + + wlr_log(WLR_INFO, "Found %d DRM CRTCs", res->count_crtcs); + + drm->num_crtcs = res->count_crtcs; + if (drm->num_crtcs == 0) { + drmModeFreeResources(res); + return true; + } + + drm->crtcs = calloc(drm->num_crtcs, sizeof(drm->crtcs[0])); + if (!drm->crtcs) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto error_res; + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + struct wlr_drm_crtc *crtc = &drm->crtcs[i]; + crtc->id = res->crtcs[i]; + crtc->legacy_crtc = drmModeGetCrtc(drm->fd, crtc->id); + get_drm_crtc_props(drm->fd, crtc->id, &crtc->props); + } + + if (!init_planes(drm)) { + goto error_crtcs; + } + + drmModeFreeResources(res); + + return true; + +error_crtcs: + free(drm->crtcs); +error_res: + drmModeFreeResources(res); + return false; +} + +void finish_drm_resources(struct wlr_drm_backend *drm) { + if (!drm) { + return; + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + struct wlr_drm_crtc *crtc = &drm->crtcs[i]; + drmModeAtomicFree(crtc->atomic); + drmModeFreeCrtc(crtc->legacy_crtc); + if (crtc->mode_id) { + drmModeDestroyPropertyBlob(drm->fd, crtc->mode_id); + } + if (crtc->gamma_lut) { + drmModeDestroyPropertyBlob(drm->fd, crtc->gamma_lut); + } + free(crtc->gamma_table); + } + for (size_t i = 0; i < drm->num_planes; ++i) { + struct wlr_drm_plane *plane = &drm->planes[i]; + if (plane->cursor_bo) { + gbm_bo_destroy(plane->cursor_bo); + } + } + + free(drm->crtcs); + free(drm->planes); +} + +static struct wlr_drm_connector *get_drm_connector_from_output( + struct wlr_output *wlr_output) { + assert(wlr_output_is_drm(wlr_output)); + return (struct wlr_drm_connector *)wlr_output; +} + +static bool drm_connector_make_current(struct wlr_output *output, + int *buffer_age) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + return make_drm_surface_current(&conn->crtc->primary->surf, buffer_age); +} + +static bool drm_connector_swap_buffers(struct wlr_output *output, + pixman_region32_t *damage) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + if (!drm->session->active) { + return false; + } + + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc) { + return false; + } + struct wlr_drm_plane *plane = crtc->primary; + + struct gbm_bo *bo = swap_drm_surface_buffers(&plane->surf, damage); + if (drm->parent) { + bo = copy_drm_surface_mgpu(&plane->mgpu_surf, bo); + } + uint32_t fb_id = get_fb_for_bo(bo); + + if (conn->pageflip_pending) { + wlr_log(WLR_ERROR, "Skipping pageflip on output '%s'", conn->output.name); + return false; + } + + if (!drm->iface->crtc_pageflip(drm, conn, crtc, fb_id, NULL)) { + return false; + } + + conn->pageflip_pending = true; + wlr_output_update_enabled(output, true); + return true; +} + +static void fill_empty_gamma_table(size_t size, + uint16_t *r, uint16_t *g, uint16_t *b) { + for (uint32_t i = 0; i < size; ++i) { + uint16_t val = (uint32_t)0xffff * i / (size - 1); + r[i] = g[i] = b[i] = val; + } +} + +static size_t drm_connector_get_gamma_size(struct wlr_output *output) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + + if (conn->crtc) { + return drm->iface->crtc_get_gamma_size(drm, conn->crtc); + } + + return 0; +} + +bool set_drm_connector_gamma(struct wlr_output *output, size_t size, + const uint16_t *r, const uint16_t *g, const uint16_t *b) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + + if (!conn->crtc) { + return false; + } + + bool reset = false; + if (size == 0) { + reset = true; + size = drm_connector_get_gamma_size(output); + if (size == 0) { + return false; + } + } + + uint16_t *gamma_table = malloc(3 * size * sizeof(uint16_t)); + if (gamma_table == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate gamma table"); + return false; + } + uint16_t *_r = gamma_table; + uint16_t *_g = gamma_table + size; + uint16_t *_b = gamma_table + 2 * size; + + if (reset) { + fill_empty_gamma_table(size, _r, _g, _b); + } else { + memcpy(_r, r, size * sizeof(uint16_t)); + memcpy(_g, g, size * sizeof(uint16_t)); + memcpy(_b, b, size * sizeof(uint16_t)); + } + + bool ok = drm->iface->crtc_set_gamma(drm, conn->crtc, size, _r, _g, _b); + if (ok) { + wlr_output_update_needs_swap(output); + + free(conn->crtc->gamma_table); + conn->crtc->gamma_table = gamma_table; + conn->crtc->gamma_table_size = size; + } else { + free(gamma_table); + } + return ok; +} + +static bool drm_connector_export_dmabuf(struct wlr_output *output, + struct wlr_dmabuf_attributes *attribs) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + + if (!drm->session->active) { + return false; + } + + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc) { + return false; + } + struct wlr_drm_plane *plane = crtc->primary; + struct wlr_drm_surface *surf = &plane->surf; + + return export_drm_bo(surf->back, attribs); +} + +static void drm_connector_start_renderer(struct wlr_drm_connector *conn) { + if (conn->state != WLR_DRM_CONN_CONNECTED) { + return; + } + + wlr_log(WLR_DEBUG, "Starting renderer on output '%s'", conn->output.name); + + struct wlr_drm_backend *drm = + get_drm_backend_from_backend(conn->output.backend); + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc) { + return; + } + struct wlr_drm_plane *plane = crtc->primary; + + struct gbm_bo *bo = get_drm_surface_front( + drm->parent ? &plane->mgpu_surf : &plane->surf); + uint32_t fb_id = get_fb_for_bo(bo); + + struct wlr_drm_mode *mode = (struct wlr_drm_mode *)conn->output.current_mode; + if (drm->iface->crtc_pageflip(drm, conn, crtc, fb_id, &mode->drm_mode)) { + conn->pageflip_pending = true; + wlr_output_update_enabled(&conn->output, true); + } else { + wl_event_source_timer_update(conn->retry_pageflip, + 1000000.0f / conn->output.current_mode->refresh); + } +} + +static bool drm_connector_set_mode(struct wlr_output *output, + struct wlr_output_mode *mode); + +static void realloc_crtcs(struct wlr_drm_backend *drm, bool *changed_outputs); + +static void attempt_enable_needs_modeset(struct wlr_drm_backend *drm) { + // Try to modeset any output that has a desired mode and a CRTC (ie. was + // lacking a CRTC on last modeset) + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link) { + if (conn->state == WLR_DRM_CONN_NEEDS_MODESET && + conn->crtc != NULL && conn->desired_mode != NULL && + conn->desired_enabled) { + drm_connector_set_mode(&conn->output, conn->desired_mode); + } + } +} + +bool enable_drm_connector(struct wlr_output *output, bool enable) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + if (conn->state != WLR_DRM_CONN_CONNECTED + && conn->state != WLR_DRM_CONN_NEEDS_MODESET) { + return false; + } + + conn->desired_enabled = enable; + + if (enable && conn->crtc == NULL) { + // Maybe we can steal a CRTC from a disabled output + realloc_crtcs(drm, NULL); + } + + bool ok = drm->iface->conn_enable(drm, conn, enable); + if (!ok) { + return false; + } + + if (enable) { + drm_connector_start_renderer(conn); + } else { + realloc_crtcs(drm, NULL); + + attempt_enable_needs_modeset(drm); + } + + wlr_output_update_enabled(&conn->output, enable); + return true; +} + +static ssize_t connector_index_from_crtc(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc) { + size_t i = 0; + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link) { + if (conn->crtc == crtc) { + return i; + } + ++i; + } + return -1; +} + +static void realloc_planes(struct wlr_drm_backend *drm, const uint32_t *crtc_in, + bool *changed_outputs) { + wlr_log(WLR_DEBUG, "Reallocating planes"); + + // overlay, primary, cursor + for (size_t type = 0; type < 3; ++type) { + if (drm->num_type_planes[type] == 0) { + continue; + } + + uint32_t possible[drm->num_type_planes[type] + 1]; + uint32_t crtc[drm->num_crtcs + 1]; + uint32_t crtc_res[drm->num_crtcs + 1]; + + for (size_t i = 0; i < drm->num_type_planes[type]; ++i) { + possible[i] = drm->type_planes[type][i].possible_crtcs; + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (crtc_in[i] == UNMATCHED) { + crtc[i] = SKIP; + } else if (drm->crtcs[i].planes[type]) { + crtc[i] = drm->crtcs[i].planes[type] + - drm->type_planes[type]; + } else { + crtc[i] = UNMATCHED; + } + } + + match_obj(drm->num_type_planes[type], possible, + drm->num_crtcs, crtc, crtc_res); + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (crtc_res[i] == UNMATCHED || crtc_res[i] == SKIP) { + continue; + } + + struct wlr_drm_crtc *c = &drm->crtcs[i]; + struct wlr_drm_plane **old = &c->planes[type]; + struct wlr_drm_plane *new = &drm->type_planes[type][crtc_res[i]]; + + if (*old != new) { + wlr_log(WLR_DEBUG, + "Assigning plane %d -> %d (type %zu) to CRTC %d", + *old ? (int)(*old)->id : -1, + new ? (int)new->id : -1, + type, + c->id); + + ssize_t conn_idx = connector_index_from_crtc(drm, c); + if (conn_idx >= 0) { + changed_outputs[conn_idx] = true; + } + if (*old) { + finish_drm_surface(&(*old)->surf); + } + finish_drm_surface(&new->surf); + *old = new; + } + } + } +} + +static void drm_connector_cleanup(struct wlr_drm_connector *conn); + +static bool drm_connector_set_mode(struct wlr_output *output, + struct wlr_output_mode *mode) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + if (conn->crtc == NULL) { + // Maybe we can steal a CRTC from a disabled output + realloc_crtcs(drm, NULL); + } + if (conn->crtc == NULL) { + wlr_log(WLR_ERROR, "Cannot modeset '%s': no CRTC for this connector", + conn->output.name); + // Save the desired mode for later, when we'll get a proper CRTC + conn->desired_mode = mode; + return false; + } + + wlr_log(WLR_INFO, "Modesetting '%s' with '%ux%u@%u mHz'", + conn->output.name, mode->width, mode->height, mode->refresh); + + if (!init_drm_plane_surfaces(conn->crtc->primary, drm, + mode->width, mode->height, GBM_FORMAT_XRGB8888)) { + wlr_log(WLR_ERROR, "Failed to initialize renderer for plane"); + return false; + } + + conn->state = WLR_DRM_CONN_CONNECTED; + conn->desired_mode = NULL; + wlr_output_update_mode(&conn->output, mode); + wlr_output_update_enabled(&conn->output, true); + conn->desired_enabled = true; + + drm_connector_start_renderer(conn); + + // When switching VTs, the mode is not updated but the buffers become + // invalid, so we need to manually damage the output here + wlr_output_damage_whole(&conn->output); + + return true; +} + +bool wlr_drm_connector_add_mode(struct wlr_output *output, + const drmModeModeInfo *modeinfo) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + + if (modeinfo->type != DRM_MODE_TYPE_USERDEF) { + return false; + } + + struct wlr_output_mode *wlr_mode; + wl_list_for_each(wlr_mode, &conn->output.modes, link) { + struct wlr_drm_mode *mode = (struct wlr_drm_mode *)wlr_mode; + if (memcmp(&mode->drm_mode, modeinfo, sizeof(*modeinfo)) == 0) { + return true; + } + } + + struct wlr_drm_mode *mode = calloc(1, sizeof(*mode)); + if (!mode) { + return false; + } + memcpy(&mode->drm_mode, modeinfo, sizeof(*modeinfo)); + + mode->wlr_mode.width = mode->drm_mode.hdisplay; + mode->wlr_mode.height = mode->drm_mode.vdisplay; + mode->wlr_mode.refresh = calculate_refresh_rate(modeinfo); + + wlr_log(WLR_INFO, "Registered custom mode " + "%"PRId32"x%"PRId32"@%"PRId32, + mode->wlr_mode.width, mode->wlr_mode.height, + mode->wlr_mode.refresh); + wl_list_insert(&conn->output.modes, &mode->wlr_mode.link); + return true; +} + +static void drm_connector_transform(struct wlr_output *output, + enum wl_output_transform transform) { + output->transform = transform; +} + +static bool drm_connector_set_cursor(struct wlr_output *output, + struct wlr_texture *texture, int32_t scale, + enum wl_output_transform transform, + int32_t hotspot_x, int32_t hotspot_y, bool update_texture) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc) { + return false; + } + + struct wlr_drm_plane *plane = crtc->cursor; + if (!plane) { + // We don't have a real cursor plane, so we make a fake one + plane = calloc(1, sizeof(*plane)); + if (!plane) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return false; + } + crtc->cursor = plane; + } + + if (!plane->surf.gbm) { + int ret; + uint64_t w, h; + ret = drmGetCap(drm->fd, DRM_CAP_CURSOR_WIDTH, &w); + w = ret ? 64 : w; + ret = drmGetCap(drm->fd, DRM_CAP_CURSOR_HEIGHT, &h); + h = ret ? 64 : h; + + struct wlr_drm_renderer *renderer = + drm->parent ? &drm->parent->renderer : &drm->renderer; + + if (!init_drm_surface(&plane->surf, renderer, w, h, + GBM_FORMAT_ARGB8888, 0)) { + wlr_log(WLR_ERROR, "Cannot allocate cursor resources"); + return false; + } + + plane->cursor_bo = gbm_bo_create(drm->renderer.gbm, w, h, + GBM_FORMAT_ARGB8888, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE); + if (!plane->cursor_bo) { + wlr_log_errno(WLR_ERROR, "Failed to create cursor bo"); + return false; + } + } + + wlr_matrix_projection(plane->matrix, plane->surf.width, + plane->surf.height, output->transform); + + struct wlr_box hotspot = { .x = hotspot_x, .y = hotspot_y }; + wlr_box_transform(&hotspot, &hotspot, + wlr_output_transform_invert(output->transform), + plane->surf.width, plane->surf.height); + + if (plane->cursor_hotspot_x != hotspot.x || + plane->cursor_hotspot_y != hotspot.y) { + // Update cursor hotspot + conn->cursor_x -= hotspot.x - plane->cursor_hotspot_x; + conn->cursor_y -= hotspot.y - plane->cursor_hotspot_y; + plane->cursor_hotspot_x = hotspot.x; + plane->cursor_hotspot_y = hotspot.y; + + if (!drm->iface->crtc_move_cursor(drm, conn->crtc, conn->cursor_x, + conn->cursor_y)) { + return false; + } + + wlr_output_update_needs_swap(output); + } + + if (!update_texture) { + // Don't update cursor image + return true; + } + + plane->cursor_enabled = false; + if (texture != NULL) { + int width, height; + wlr_texture_get_size(texture, &width, &height); + width = width * output->scale / scale; + height = height * output->scale / scale; + + if (width > (int)plane->surf.width || height > (int)plane->surf.height) { + wlr_log(WLR_ERROR, "Cursor too large (max %dx%d)", + (int)plane->surf.width, (int)plane->surf.height); + return false; + } + + uint32_t bo_width = gbm_bo_get_width(plane->cursor_bo); + uint32_t bo_height = gbm_bo_get_height(plane->cursor_bo); + + uint32_t bo_stride; + void *bo_data; + if (!gbm_bo_map(plane->cursor_bo, 0, 0, bo_width, bo_height, + GBM_BO_TRANSFER_WRITE, &bo_stride, &bo_data)) { + wlr_log_errno(WLR_ERROR, "Unable to map buffer"); + return false; + } + + make_drm_surface_current(&plane->surf, NULL); + + struct wlr_renderer *rend = plane->surf.renderer->wlr_rend; + + struct wlr_box cursor_box = { .width = width, .height = height }; + + float matrix[9]; + wlr_matrix_project_box(matrix, &cursor_box, transform, 0, plane->matrix); + + wlr_renderer_begin(rend, plane->surf.width, plane->surf.height); + wlr_renderer_clear(rend, (float[]){ 0.0, 0.0, 0.0, 0.0 }); + wlr_render_texture_with_matrix(rend, texture, matrix, 1.0); + wlr_renderer_end(rend); + + wlr_renderer_read_pixels(rend, WL_SHM_FORMAT_ARGB8888, NULL, bo_stride, + plane->surf.width, plane->surf.height, 0, 0, 0, 0, bo_data); + + swap_drm_surface_buffers(&plane->surf, NULL); + + gbm_bo_unmap(plane->cursor_bo, bo_data); + + plane->cursor_enabled = true; + } + + if (!drm->session->active) { + return true; // will be committed when session is resumed + } + + struct gbm_bo *bo = plane->cursor_enabled ? plane->cursor_bo : NULL; + bool ok = drm->iface->crtc_set_cursor(drm, crtc, bo); + if (ok) { + wlr_output_update_needs_swap(output); + } + return ok; +} + +static bool drm_connector_move_cursor(struct wlr_output *output, + int x, int y) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + if (!conn->crtc) { + return false; + } + struct wlr_drm_plane *plane = conn->crtc->cursor; + + struct wlr_box box = { .x = x, .y = y }; + + int width, height; + wlr_output_transformed_resolution(output, &width, &height); + + enum wl_output_transform transform = + wlr_output_transform_invert(output->transform); + wlr_box_transform(&box, &box, transform, width, height); + + if (plane != NULL) { + box.x -= plane->cursor_hotspot_x; + box.y -= plane->cursor_hotspot_y; + } + + conn->cursor_x = box.x; + conn->cursor_y = box.y; + + if (!drm->session->active) { + return true; // will be committed when session is resumed + } + + bool ok = drm->iface->crtc_move_cursor(drm, conn->crtc, box.x, box.y); + if (ok) { + wlr_output_update_needs_swap(output); + } + return ok; +} + +static bool drm_connector_schedule_frame(struct wlr_output *output) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + if (!drm->session->active) { + return false; + } + + // We need to figure out where we are in the vblank cycle + // TODO: try using drmWaitVBlank and fallback to pageflipping + + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc) { + return false; + } + struct wlr_drm_plane *plane = crtc->primary; + struct gbm_bo *bo = plane->surf.back; + if (!bo) { + // We haven't swapped buffers yet -- can't do a pageflip + wlr_output_send_frame(output); + return true; + } + if (drm->parent) { + bo = copy_drm_surface_mgpu(&plane->mgpu_surf, bo); + } + uint32_t fb_id = get_fb_for_bo(bo); + + if (conn->pageflip_pending) { + wlr_log(WLR_ERROR, "Skipping pageflip on output '%s'", + conn->output.name); + return true; + } + + if (!drm->iface->crtc_pageflip(drm, conn, crtc, fb_id, NULL)) { + return false; + } + + conn->pageflip_pending = true; + wlr_output_update_enabled(output, true); + return true; +} + +static void drm_connector_destroy(struct wlr_output *output) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + drm_connector_cleanup(conn); + drmModeFreeCrtc(conn->old_crtc); + wl_event_source_remove(conn->retry_pageflip); + wl_list_remove(&conn->link); + free(conn); +} + +static const struct wlr_output_impl output_impl = { + .enable = enable_drm_connector, + .set_mode = drm_connector_set_mode, + .transform = drm_connector_transform, + .set_cursor = drm_connector_set_cursor, + .move_cursor = drm_connector_move_cursor, + .destroy = drm_connector_destroy, + .make_current = drm_connector_make_current, + .swap_buffers = drm_connector_swap_buffers, + .set_gamma = set_drm_connector_gamma, + .get_gamma_size = drm_connector_get_gamma_size, + .export_dmabuf = drm_connector_export_dmabuf, + .schedule_frame = drm_connector_schedule_frame, +}; + +bool wlr_output_is_drm(struct wlr_output *output) { + return output->impl == &output_impl; +} + +static int retry_pageflip(void *data) { + struct wlr_drm_connector *conn = data; + wlr_log(WLR_INFO, "%s: Retrying pageflip", conn->output.name); + drm_connector_start_renderer(conn); + return 0; +} + +static const int32_t subpixel_map[] = { + [DRM_MODE_SUBPIXEL_UNKNOWN] = WL_OUTPUT_SUBPIXEL_UNKNOWN, + [DRM_MODE_SUBPIXEL_HORIZONTAL_RGB] = WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB, + [DRM_MODE_SUBPIXEL_HORIZONTAL_BGR] = WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR, + [DRM_MODE_SUBPIXEL_VERTICAL_RGB] = WL_OUTPUT_SUBPIXEL_VERTICAL_RGB, + [DRM_MODE_SUBPIXEL_VERTICAL_BGR] = WL_OUTPUT_SUBPIXEL_VERTICAL_BGR, + [DRM_MODE_SUBPIXEL_NONE] = WL_OUTPUT_SUBPIXEL_NONE, +}; + +static void dealloc_crtc(struct wlr_drm_connector *conn) { + struct wlr_drm_backend *drm = + get_drm_backend_from_backend(conn->output.backend); + if (conn->crtc == NULL) { + return; + } + + wlr_log(WLR_DEBUG, "De-allocating CRTC %zu for output '%s'", + conn->crtc - drm->crtcs, conn->output.name); + + set_drm_connector_gamma(&conn->output, 0, NULL, NULL, NULL); + + for (size_t type = 0; type < 3; ++type) { + struct wlr_drm_plane *plane = conn->crtc->planes[type]; + if (plane == NULL) { + continue; + } + + finish_drm_surface(&plane->surf); + conn->crtc->planes[type] = NULL; + } + + drm->iface->conn_enable(drm, conn, false); + + conn->crtc = NULL; +} + +static void realloc_crtcs(struct wlr_drm_backend *drm, bool *changed_outputs) { + size_t num_outputs = wl_list_length(&drm->outputs); + + if (changed_outputs == NULL) { + changed_outputs = calloc(num_outputs, sizeof(bool)); + if (changed_outputs == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + return; + } + } + + wlr_log(WLR_DEBUG, "Reallocating CRTCs"); + + uint32_t crtc[drm->num_crtcs + 1]; + for (size_t i = 0; i < drm->num_crtcs; ++i) { + crtc[i] = UNMATCHED; + } + + struct wlr_drm_connector *connectors[num_outputs + 1]; + + uint32_t possible_crtc[num_outputs + 1]; + memset(possible_crtc, 0, sizeof(possible_crtc)); + + wlr_log(WLR_DEBUG, "State before reallocation:"); + ssize_t i = -1; + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link) { + i++; + connectors[i] = conn; + + wlr_log(WLR_DEBUG, " '%s' crtc=%d state=%d desired_enabled=%d", + conn->output.name, + conn->crtc ? (int)(conn->crtc - drm->crtcs) : -1, + conn->state, conn->desired_enabled); + + if (conn->crtc) { + crtc[conn->crtc - drm->crtcs] = i; + } + + // Only search CRTCs for user-enabled outputs (that are already + // connected or in need of a modeset) + if ((conn->state == WLR_DRM_CONN_CONNECTED || + conn->state == WLR_DRM_CONN_NEEDS_MODESET) && + conn->desired_enabled) { + possible_crtc[i] = conn->possible_crtc; + } + } + + uint32_t crtc_res[drm->num_crtcs + 1]; + match_obj(wl_list_length(&drm->outputs), possible_crtc, + drm->num_crtcs, crtc, crtc_res); + + bool matched[num_outputs + 1]; + memset(matched, false, sizeof(matched)); + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (crtc_res[i] != UNMATCHED) { + matched[crtc_res[i]] = true; + } + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + // We don't want any of the current monitors to be deactivated + if (crtc[i] != UNMATCHED && !matched[crtc[i]] && + connectors[crtc[i]]->desired_enabled) { + wlr_log(WLR_DEBUG, "Could not match a CRTC for connected output %d", + crtc[i]); + return; + } + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (crtc_res[i] == crtc[i]) { + continue; + } + + // De-allocate this CRTC on previous output + if (crtc[i] != UNMATCHED) { + changed_outputs[crtc[i]] = true; + dealloc_crtc(connectors[crtc[i]]); + } + + // Assign this CRTC to next output + if (crtc_res[i] != UNMATCHED) { + changed_outputs[crtc_res[i]] = true; + + struct wlr_drm_connector *conn = connectors[crtc_res[i]]; + dealloc_crtc(conn); + conn->crtc = &drm->crtcs[i]; + + wlr_log(WLR_DEBUG, "Assigning CRTC %zu to output %d -> %d '%s'", + i, crtc[i], crtc_res[i], conn->output.name); + } + } + + wlr_log(WLR_DEBUG, "State after reallocation:"); + wl_list_for_each(conn, &drm->outputs, link) { + wlr_log(WLR_DEBUG, " '%s' crtc=%d state=%d desired_enabled=%d", + conn->output.name, + conn->crtc ? (int)(conn->crtc - drm->crtcs) : -1, + conn->state, conn->desired_enabled); + } + + realloc_planes(drm, crtc_res, changed_outputs); + + // We need to reinitialize any plane that has changed + i = -1; + wl_list_for_each(conn, &drm->outputs, link) { + i++; + struct wlr_output_mode *mode = conn->output.current_mode; + + if (conn->state != WLR_DRM_CONN_CONNECTED || !changed_outputs[i] + || conn->crtc == NULL) { + continue; + } + + if (!init_drm_plane_surfaces(conn->crtc->primary, drm, + mode->width, mode->height, GBM_FORMAT_XRGB8888)) { + wlr_log(WLR_ERROR, "Failed to initialize renderer for plane"); + drm_connector_cleanup(conn); + break; + } + + drm_connector_start_renderer(conn); + + wlr_output_damage_whole(&conn->output); + } +} + +static uint32_t get_possible_crtcs(int fd, drmModeRes *res, + drmModeConnector *conn, bool is_mst) { + uint32_t ret = 0; + + for (int i = 0; i < conn->count_encoders; ++i) { + drmModeEncoder *enc = drmModeGetEncoder(fd, conn->encoders[i]); + if (!enc) { + continue; + } + + ret |= enc->possible_crtcs; + + drmModeFreeEncoder(enc); + } + + // Sometimes DP MST connectors report no encoders, so we'll loop though + // all of the encoders of the MST type instead. + // TODO: See if there is a better solution. + + if (!is_mst || ret) { + return ret; + } + + for (int i = 0; i < res->count_encoders; ++i) { + drmModeEncoder *enc = drmModeGetEncoder(fd, res->encoders[i]); + if (!enc) { + continue; + } + + if (enc->encoder_type == DRM_MODE_ENCODER_DPMST) { + ret |= enc->possible_crtcs; + } + + drmModeFreeEncoder(enc); + } + + return ret; +} + +void scan_drm_connectors(struct wlr_drm_backend *drm) { + wlr_log(WLR_INFO, "Scanning DRM connectors"); + + drmModeRes *res = drmModeGetResources(drm->fd); + if (!res) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM resources"); + return; + } + + size_t seen_len = wl_list_length(&drm->outputs); + // +1 so length can never be 0, which is undefined behaviour. + // Last element isn't used. + bool seen[seen_len + 1]; + memset(seen, false, sizeof(seen)); + size_t new_outputs_len = 0; + struct wlr_drm_connector *new_outputs[res->count_connectors + 1]; + + for (int i = 0; i < res->count_connectors; ++i) { + drmModeConnector *drm_conn = drmModeGetConnector(drm->fd, + res->connectors[i]); + if (!drm_conn) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM connector"); + continue; + } + drmModeEncoder *curr_enc = drmModeGetEncoder(drm->fd, + drm_conn->encoder_id); + + ssize_t index = -1; + struct wlr_drm_connector *c, *wlr_conn = NULL; + wl_list_for_each(c, &drm->outputs, link) { + index++; + if (c->id == drm_conn->connector_id) { + wlr_conn = c; + break; + } + } + + if (!wlr_conn) { + wlr_conn = calloc(1, sizeof(*wlr_conn)); + if (!wlr_conn) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + drmModeFreeEncoder(curr_enc); + drmModeFreeConnector(drm_conn); + continue; + } + wlr_output_init(&wlr_conn->output, &drm->backend, &output_impl, + drm->display); + + struct wl_event_loop *ev = wl_display_get_event_loop(drm->display); + wlr_conn->retry_pageflip = wl_event_loop_add_timer(ev, retry_pageflip, + wlr_conn); + + wlr_conn->state = WLR_DRM_CONN_DISCONNECTED; + wlr_conn->id = drm_conn->connector_id; + + snprintf(wlr_conn->output.name, sizeof(wlr_conn->output.name), + "%s-%"PRIu32, conn_get_name(drm_conn->connector_type), + drm_conn->connector_type_id); + + if (curr_enc) { + wlr_conn->old_crtc = drmModeGetCrtc(drm->fd, curr_enc->crtc_id); + } + + wl_list_insert(drm->outputs.prev, &wlr_conn->link); + wlr_log(WLR_INFO, "Found connector '%s'", wlr_conn->output.name); + } else { + seen[index] = true; + } + + if (curr_enc) { + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (drm->crtcs[i].id == curr_enc->crtc_id) { + wlr_conn->crtc = &drm->crtcs[i]; + break; + } + } + } else { + wlr_conn->crtc = NULL; + } + + // This can only happen *after* hotplug, since we haven't read the + // connector properties yet + if (wlr_conn->props.link_status != 0) { + uint64_t link_status; + if (!get_drm_prop(drm->fd, wlr_conn->id, + wlr_conn->props.link_status, &link_status)) { + wlr_log(WLR_ERROR, "Failed to get link status for '%s'", + wlr_conn->output.name); + continue; + } + + if (link_status == DRM_MODE_LINK_STATUS_BAD) { + // We need to reload our list of modes and force a modeset + wlr_log(WLR_INFO, "Bad link for '%s'", wlr_conn->output.name); + drm_connector_cleanup(wlr_conn); + } + } + + if (wlr_conn->state == WLR_DRM_CONN_DISCONNECTED && + drm_conn->connection == DRM_MODE_CONNECTED) { + wlr_log(WLR_INFO, "'%s' connected", wlr_conn->output.name); + wlr_log(WLR_DEBUG, "Current CRTC: %d", + wlr_conn->crtc ? (int)wlr_conn->crtc->id : -1); + + wlr_conn->output.phys_width = drm_conn->mmWidth; + wlr_conn->output.phys_height = drm_conn->mmHeight; + wlr_log(WLR_INFO, "Physical size: %"PRId32"x%"PRId32, + wlr_conn->output.phys_width, wlr_conn->output.phys_height); + wlr_conn->output.subpixel = subpixel_map[drm_conn->subpixel]; + + get_drm_connector_props(drm->fd, wlr_conn->id, &wlr_conn->props); + + size_t edid_len = 0; + uint8_t *edid = get_drm_prop_blob(drm->fd, + wlr_conn->id, wlr_conn->props.edid, &edid_len); + parse_edid(&wlr_conn->output, edid_len, edid); + free(edid); + + wlr_log(WLR_INFO, "Detected modes:"); + + for (int i = 0; i < drm_conn->count_modes; ++i) { + struct wlr_drm_mode *mode = calloc(1, sizeof(*mode)); + if (!mode) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + continue; + } + + if (drm_conn->modes[i].flags & DRM_MODE_FLAG_INTERLACE) { + free(mode); + continue; + } + + mode->drm_mode = drm_conn->modes[i]; + mode->wlr_mode.width = mode->drm_mode.hdisplay; + mode->wlr_mode.height = mode->drm_mode.vdisplay; + mode->wlr_mode.refresh = calculate_refresh_rate(&mode->drm_mode); + + wlr_log(WLR_INFO, " %"PRId32"x%"PRId32"@%"PRId32, + mode->wlr_mode.width, mode->wlr_mode.height, + mode->wlr_mode.refresh); + + wl_list_insert(&wlr_conn->output.modes, &mode->wlr_mode.link); + } + + wlr_conn->possible_crtc = get_possible_crtcs(drm->fd, res, drm_conn, + wlr_conn->props.path != 0); + if (wlr_conn->possible_crtc == 0) { + wlr_log(WLR_ERROR, "No CRTC possible for connector '%s'", + wlr_conn->output.name); + } + + wlr_output_update_enabled(&wlr_conn->output, wlr_conn->crtc != NULL); + wlr_conn->desired_enabled = true; + + wlr_conn->state = WLR_DRM_CONN_NEEDS_MODESET; + new_outputs[new_outputs_len++] = wlr_conn; + } else if ((wlr_conn->state == WLR_DRM_CONN_CONNECTED || + wlr_conn->state == WLR_DRM_CONN_NEEDS_MODESET) && + drm_conn->connection != DRM_MODE_CONNECTED) { + wlr_log(WLR_INFO, "'%s' disconnected", wlr_conn->output.name); + + drm_connector_cleanup(wlr_conn); + } + + drmModeFreeEncoder(curr_enc); + drmModeFreeConnector(drm_conn); + } + + drmModeFreeResources(res); + + // Iterate in reverse order because we'll remove items from the list and + // still want indices to remain correct. + struct wlr_drm_connector *conn, *tmp_conn; + size_t index = wl_list_length(&drm->outputs); + wl_list_for_each_reverse_safe(conn, tmp_conn, &drm->outputs, link) { + index--; + if (index >= seen_len || seen[index]) { + continue; + } + + wlr_log(WLR_INFO, "'%s' disappeared", conn->output.name); + drm_connector_cleanup(conn); + + if (conn->pageflip_pending) { + conn->state = WLR_DRM_CONN_DISAPPEARED; + } else { + wlr_output_destroy(&conn->output); + } + } + + bool changed_outputs[wl_list_length(&drm->outputs) + 1]; + memset(changed_outputs, false, sizeof(changed_outputs)); + for (size_t i = 0; i < new_outputs_len; ++i) { + struct wlr_drm_connector *conn = new_outputs[i]; + + ssize_t pos = -1; + struct wlr_drm_connector *c; + wl_list_for_each(c, &drm->outputs, link) { + ++pos; + if (c == conn) { + break; + } + } + assert(pos >= 0); + + changed_outputs[pos] = true; + } + + realloc_crtcs(drm, changed_outputs); + + for (size_t i = 0; i < new_outputs_len; ++i) { + struct wlr_drm_connector *conn = new_outputs[i]; + + wlr_log(WLR_INFO, "Requesting modeset for '%s'", + conn->output.name); + wlr_signal_emit_safe(&drm->backend.events.new_output, + &conn->output); + } + + attempt_enable_needs_modeset(drm); +} + +static int mhz_to_nsec(int mhz) { + return 1000000000000LL / mhz; +} + +static void page_flip_handler(int fd, unsigned seq, + unsigned tv_sec, unsigned tv_usec, void *data) { + struct wlr_drm_connector *conn = data; + struct wlr_drm_backend *drm = + get_drm_backend_from_backend(conn->output.backend); + + conn->pageflip_pending = false; + + if (conn->state == WLR_DRM_CONN_DISAPPEARED) { + wlr_output_destroy(&conn->output); + return; + } + + if (conn->state != WLR_DRM_CONN_CONNECTED || conn->crtc == NULL) { + return; + } + + post_drm_surface(&conn->crtc->primary->surf); + if (drm->parent) { + post_drm_surface(&conn->crtc->primary->mgpu_surf); + } + + struct timespec present_time = { + .tv_sec = tv_sec, + .tv_nsec = tv_usec * 1000, + }; + struct wlr_output_event_present present_event = { + .when = &present_time, + .seq = seq, + .refresh = mhz_to_nsec(conn->output.refresh), + .flags = WLR_OUTPUT_PRESENT_VSYNC | WLR_OUTPUT_PRESENT_HW_CLOCK | + WLR_OUTPUT_PRESENT_HW_COMPLETION, + }; + wlr_output_send_present(&conn->output, &present_event); + + if (drm->session->active) { + wlr_output_send_frame(&conn->output); + } +} + +int handle_drm_event(int fd, uint32_t mask, void *data) { + drmEventContext event = { + .version = 2, + .page_flip_handler = page_flip_handler, + }; + + drmHandleEvent(fd, &event); + return 1; +} + +void restore_drm_outputs(struct wlr_drm_backend *drm) { + uint64_t to_close = (1L << wl_list_length(&drm->outputs)) - 1; + + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link) { + if (conn->state == WLR_DRM_CONN_CONNECTED) { + conn->state = WLR_DRM_CONN_CLEANUP; + } + } + + time_t timeout = time(NULL) + 5; + + while (to_close && time(NULL) < timeout) { + handle_drm_event(drm->fd, 0, NULL); + size_t i = 0; + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link) { + if (conn->state != WLR_DRM_CONN_CLEANUP || !conn->pageflip_pending) { + to_close &= ~(1 << i); + } + i++; + } + } + + if (to_close) { + wlr_log(WLR_ERROR, "Timed out stopping output renderers"); + } + + wl_list_for_each(conn, &drm->outputs, link) { + drmModeCrtc *crtc = conn->old_crtc; + if (!crtc) { + continue; + } + + drmModeSetCrtc(drm->fd, crtc->crtc_id, crtc->buffer_id, crtc->x, crtc->y, + &conn->id, 1, &crtc->mode); + } +} + +static void drm_connector_cleanup(struct wlr_drm_connector *conn) { + if (!conn) { + return; + } + + switch (conn->state) { + case WLR_DRM_CONN_CONNECTED: + case WLR_DRM_CONN_CLEANUP:; + struct wlr_drm_crtc *crtc = conn->crtc; + if (crtc != NULL) { + for (int i = 0; i < 3; ++i) { + if (!crtc->planes[i]) { + continue; + } + + finish_drm_surface(&crtc->planes[i]->surf); + finish_drm_surface(&crtc->planes[i]->mgpu_surf); + if (crtc->planes[i]->id == 0) { + free(crtc->planes[i]); + crtc->planes[i] = NULL; + } + } + } + + conn->output.current_mode = NULL; + conn->desired_mode = NULL; + struct wlr_drm_mode *mode, *tmp; + wl_list_for_each_safe(mode, tmp, &conn->output.modes, wlr_mode.link) { + wl_list_remove(&mode->wlr_mode.link); + free(mode); + } + + conn->output.enabled = false; + conn->output.width = conn->output.height = conn->output.refresh = 0; + + memset(&conn->output.make, 0, sizeof(conn->output.make)); + memset(&conn->output.model, 0, sizeof(conn->output.model)); + memset(&conn->output.serial, 0, sizeof(conn->output.serial)); + + if (conn->output.idle_frame != NULL) { + wl_event_source_remove(conn->output.idle_frame); + conn->output.idle_frame = NULL; + } + conn->output.needs_swap = false; + conn->output.frame_pending = false; + + /* Fallthrough */ + case WLR_DRM_CONN_NEEDS_MODESET: + wlr_log(WLR_INFO, "Emitting destruction signal for '%s'", + conn->output.name); + dealloc_crtc(conn); + conn->possible_crtc = 0; + conn->desired_mode = NULL; + wlr_signal_emit_safe(&conn->output.events.destroy, &conn->output); + break; + case WLR_DRM_CONN_DISCONNECTED: + break; + case WLR_DRM_CONN_DISAPPEARED: + return; // don't change state + } + + conn->state = WLR_DRM_CONN_DISCONNECTED; +} diff --git a/backend/drm/legacy.c b/backend/drm/legacy.c new file mode 100644 index 00000000..182c7a95 --- /dev/null +++ b/backend/drm/legacy.c @@ -0,0 +1,83 @@ +#include <gbm.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include "backend/drm/drm.h" +#include "backend/drm/iface.h" +#include "backend/drm/util.h" + +static bool legacy_crtc_pageflip(struct wlr_drm_backend *drm, + struct wlr_drm_connector *conn, struct wlr_drm_crtc *crtc, + uint32_t fb_id, drmModeModeInfo *mode) { + if (mode) { + if (drmModeSetCrtc(drm->fd, crtc->id, fb_id, 0, 0, + &conn->id, 1, mode)) { + wlr_log_errno(WLR_ERROR, "%s: Failed to set CRTC", conn->output.name); + return false; + } + } + + if (drmModePageFlip(drm->fd, crtc->id, fb_id, DRM_MODE_PAGE_FLIP_EVENT, conn)) { + wlr_log_errno(WLR_ERROR, "%s: Failed to page flip", conn->output.name); + return false; + } + + return true; +} + +static bool legacy_conn_enable(struct wlr_drm_backend *drm, + struct wlr_drm_connector *conn, bool enable) { + int ret = drmModeConnectorSetProperty(drm->fd, conn->id, conn->props.dpms, + enable ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF); + return ret >= 0; +} + +bool legacy_crtc_set_cursor(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, struct gbm_bo *bo) { + if (!crtc || !crtc->cursor) { + return true; + } + + if (!bo) { + if (drmModeSetCursor(drm->fd, crtc->id, 0, 0, 0)) { + wlr_log_errno(WLR_DEBUG, "Failed to clear hardware cursor"); + return false; + } + return true; + } + + struct wlr_drm_plane *plane = crtc->cursor; + + if (drmModeSetCursor(drm->fd, crtc->id, gbm_bo_get_handle(bo).u32, + plane->surf.width, plane->surf.height)) { + wlr_log_errno(WLR_DEBUG, "Failed to set hardware cursor"); + return false; + } + + return true; +} + +bool legacy_crtc_move_cursor(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, int x, int y) { + return !drmModeMoveCursor(drm->fd, crtc->id, x, y); +} + +bool legacy_crtc_set_gamma(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, size_t size, + uint16_t *r, uint16_t *g, uint16_t *b) { + return !drmModeCrtcSetGamma(drm->fd, crtc->id, (uint32_t)size, r, g, b); +} + +size_t legacy_crtc_get_gamma_size(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc) { + return (size_t)crtc->legacy_crtc->gamma_size; +} + +const struct wlr_drm_interface legacy_iface = { + .conn_enable = legacy_conn_enable, + .crtc_pageflip = legacy_crtc_pageflip, + .crtc_set_cursor = legacy_crtc_set_cursor, + .crtc_move_cursor = legacy_crtc_move_cursor, + .crtc_set_gamma = legacy_crtc_set_gamma, + .crtc_get_gamma_size = legacy_crtc_get_gamma_size, +}; diff --git a/backend/drm/properties.c b/backend/drm/properties.c new file mode 100644 index 00000000..5541d1be --- /dev/null +++ b/backend/drm/properties.c @@ -0,0 +1,151 @@ +#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) }, + { "link-status", INDEX(link_status) }, +#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) }, + { "rotation", INDEX(rotation) }, + { "scaling mode", INDEX(scaling_mode) }, +#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_ID", INDEX(fb_id) }, + { "SRC_H", INDEX(src_h) }, + { "SRC_W", INDEX(src_w) }, + { "SRC_X", INDEX(src_x) }, + { "SRC_Y", INDEX(src_y) }, + { "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; +} diff --git a/backend/drm/renderer.c b/backend/drm/renderer.c new file mode 100644 index 00000000..70b1bcbe --- /dev/null +++ b/backend/drm/renderer.c @@ -0,0 +1,264 @@ +#include <assert.h> +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <gbm.h> +#include <stdbool.h> +#include <stdlib.h> +#include <unistd.h> +#include <wayland-util.h> +#include <wlr/render/egl.h> +#include <wlr/render/gles2.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/util/log.h> +#include "backend/drm/drm.h" +#include "glapi.h" + +#ifndef DRM_FORMAT_MOD_LINEAR +#define DRM_FORMAT_MOD_LINEAR 0 +#endif + +bool init_drm_renderer(struct wlr_drm_backend *drm, + struct wlr_drm_renderer *renderer, wlr_renderer_create_func_t create_renderer_func) { + renderer->gbm = gbm_create_device(drm->fd); + if (!renderer->gbm) { + wlr_log(WLR_ERROR, "Failed to create GBM device"); + return false; + } + + if (!create_renderer_func) { + create_renderer_func = wlr_renderer_autocreate; + } + + static EGLint config_attribs[] = { + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_ALPHA_SIZE, 1, + EGL_NONE, + }; + + renderer->wlr_rend = create_renderer_func(&renderer->egl, + EGL_PLATFORM_GBM_MESA, renderer->gbm, + config_attribs, GBM_FORMAT_ARGB8888); + + if (!renderer->wlr_rend) { + wlr_log(WLR_ERROR, "Failed to create EGL/WLR renderer"); + goto error_gbm; + } + + renderer->fd = drm->fd; + return true; + +error_gbm: + gbm_device_destroy(renderer->gbm); + return false; +} + +void finish_drm_renderer(struct wlr_drm_renderer *renderer) { + if (!renderer) { + return; + } + + wlr_renderer_destroy(renderer->wlr_rend); + wlr_egl_finish(&renderer->egl); + gbm_device_destroy(renderer->gbm); +} + +bool init_drm_surface(struct wlr_drm_surface *surf, + struct wlr_drm_renderer *renderer, uint32_t width, uint32_t height, + uint32_t format, uint32_t flags) { + if (surf->width == width && surf->height == height) { + return true; + } + + surf->renderer = renderer; + surf->width = width; + surf->height = height; + + if (surf->gbm) { + if (surf->front) { + gbm_surface_release_buffer(surf->gbm, surf->front); + surf->front = NULL; + } + if (surf->back) { + gbm_surface_release_buffer(surf->gbm, surf->back); + surf->back = NULL; + } + gbm_surface_destroy(surf->gbm); + } + wlr_egl_destroy_surface(&surf->renderer->egl, surf->egl); + + surf->gbm = gbm_surface_create(renderer->gbm, width, height, + format, GBM_BO_USE_RENDERING | flags); + if (!surf->gbm) { + wlr_log_errno(WLR_ERROR, "Failed to create GBM surface"); + goto error_zero; + } + + surf->egl = wlr_egl_create_surface(&renderer->egl, surf->gbm); + if (surf->egl == EGL_NO_SURFACE) { + wlr_log(WLR_ERROR, "Failed to create EGL surface"); + goto error_gbm; + } + + return true; + +error_gbm: + gbm_surface_destroy(surf->gbm); +error_zero: + memset(surf, 0, sizeof(*surf)); + return false; +} + +void finish_drm_surface(struct wlr_drm_surface *surf) { + if (!surf || !surf->renderer) { + return; + } + + if (surf->front) { + gbm_surface_release_buffer(surf->gbm, surf->front); + } + if (surf->back) { + gbm_surface_release_buffer(surf->gbm, surf->back); + } + + wlr_egl_destroy_surface(&surf->renderer->egl, surf->egl); + if (surf->gbm) { + gbm_surface_destroy(surf->gbm); + } + + memset(surf, 0, sizeof(*surf)); +} + +bool make_drm_surface_current(struct wlr_drm_surface *surf, + int *buffer_damage) { + return wlr_egl_make_current(&surf->renderer->egl, surf->egl, buffer_damage); +} + +struct gbm_bo *swap_drm_surface_buffers(struct wlr_drm_surface *surf, + pixman_region32_t *damage) { + if (surf->front) { + gbm_surface_release_buffer(surf->gbm, surf->front); + } + + wlr_egl_swap_buffers(&surf->renderer->egl, surf->egl, damage); + + surf->front = surf->back; + surf->back = gbm_surface_lock_front_buffer(surf->gbm); + return surf->back; +} + +struct gbm_bo *get_drm_surface_front(struct wlr_drm_surface *surf) { + if (surf->front) { + return surf->front; + } + + make_drm_surface_current(surf, NULL); + struct wlr_renderer *renderer = surf->renderer->wlr_rend; + wlr_renderer_begin(renderer, surf->width, surf->height); + wlr_renderer_clear(renderer, (float[]){ 0.0, 0.0, 0.0, 1.0 }); + wlr_renderer_end(renderer); + return swap_drm_surface_buffers(surf, NULL); +} + +void post_drm_surface(struct wlr_drm_surface *surf) { + if (surf->front) { + gbm_surface_release_buffer(surf->gbm, surf->front); + surf->front = NULL; + } +} + +bool export_drm_bo(struct gbm_bo *bo, struct wlr_dmabuf_attributes *attribs) { + memset(attribs, 0, sizeof(struct wlr_dmabuf_attributes)); + + attribs->n_planes = gbm_bo_get_plane_count(bo); + if (attribs->n_planes > WLR_DMABUF_MAX_PLANES) { + return false; + } + + attribs->width = gbm_bo_get_width(bo); + attribs->height = gbm_bo_get_height(bo); + attribs->format = gbm_bo_get_format(bo); + attribs->modifier = gbm_bo_get_modifier(bo); + + for (int i = 0; i < attribs->n_planes; ++i) { + attribs->offset[i] = gbm_bo_get_offset(bo, i); + attribs->stride[i] = gbm_bo_get_stride_for_plane(bo, i); + attribs->fd[i] = gbm_bo_get_fd(bo); + if (attribs->fd[i] < 0) { + for (int j = 0; j < i; ++j) { + close(attribs->fd[j]); + } + return false; + } + } + + return true; +} + +static void free_tex(struct gbm_bo *bo, void *data) { + struct wlr_texture *tex = data; + wlr_texture_destroy(tex); +} + +static struct wlr_texture *get_tex_for_bo(struct wlr_drm_renderer *renderer, + struct gbm_bo *bo) { + struct wlr_texture *tex = gbm_bo_get_user_data(bo); + if (tex) { + return tex; + } + + struct wlr_dmabuf_attributes attribs; + if (!export_drm_bo(bo, &attribs)) { + return NULL; + } + + tex = wlr_texture_from_dmabuf(renderer->wlr_rend, &attribs); + if (tex) { + gbm_bo_set_user_data(bo, tex, free_tex); + } + + return tex; +} + +struct gbm_bo *copy_drm_surface_mgpu(struct wlr_drm_surface *dest, + struct gbm_bo *src) { + make_drm_surface_current(dest, NULL); + + struct wlr_texture *tex = get_tex_for_bo(dest->renderer, src); + assert(tex); + + float mat[9]; + wlr_matrix_projection(mat, 1, 1, WL_OUTPUT_TRANSFORM_NORMAL); + + struct wlr_renderer *renderer = dest->renderer->wlr_rend; + wlr_renderer_begin(renderer, dest->width, dest->height); + wlr_renderer_clear(renderer, (float[]){ 0.0, 0.0, 0.0, 1.0 }); + wlr_render_texture_with_matrix(renderer, tex, mat, 1.0f); + wlr_renderer_end(renderer); + + return swap_drm_surface_buffers(dest, NULL); +} + +bool init_drm_plane_surfaces(struct wlr_drm_plane *plane, + struct wlr_drm_backend *drm, int32_t width, uint32_t height, + uint32_t format) { + if (!drm->parent) { + return init_drm_surface(&plane->surf, &drm->renderer, width, height, + format, GBM_BO_USE_SCANOUT); + } + + if (!init_drm_surface(&plane->surf, &drm->parent->renderer, + width, height, format, GBM_BO_USE_LINEAR)) { + return false; + } + + if (!init_drm_surface(&plane->mgpu_surf, &drm->renderer, + width, height, format, GBM_BO_USE_SCANOUT)) { + finish_drm_surface(&plane->surf); + return false; + } + + return true; +} diff --git a/backend/drm/util.c b/backend/drm/util.c new file mode 100644 index 00000000..6f2dd5be --- /dev/null +++ b/backend/drm/util.c @@ -0,0 +1,348 @@ +#include <drm_mode.h> +#include <drm.h> +#include <gbm.h> +#include <stdio.h> +#include <string.h> +#include <wlr/util/log.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; +} + +// Constructed from http://edid.tv/manufacturer +static const char *get_manufacturer(uint16_t id) { +#define ID(a, b, c) ((a & 0x1f) << 10) | ((b & 0x1f) << 5) | (c & 0x1f) + switch (id) { + case ID('A', 'A', 'A'): return "Avolites Ltd"; + case ID('A', 'C', 'I'): return "Ancor Communications Inc"; + case ID('A', 'C', 'R'): return "Acer Technologies"; + case ID('A', 'D', 'A'): return "Addi-Data GmbH"; + case ID('A', 'P', 'P'): return "Apple Computer Inc"; + case ID('A', 'S', 'K'): return "Ask A/S"; + case ID('A', 'V', 'T'): return "Avtek (Electronics) Pty Ltd"; + case ID('B', 'N', 'O'): return "Bang & Olufsen"; + case ID('C', 'M', 'N'): return "Chimei Innolux Corporation"; + case ID('C', 'M', 'O'): return "Chi Mei Optoelectronics corp."; + case ID('C', 'R', 'O'): return "Extraordinary Technologies PTY Limited"; + case ID('D', 'E', 'L'): return "Dell Inc."; + case ID('D', 'G', 'C'): return "Data General Corporation"; + case ID('D', 'O', 'N'): return "DENON, Ltd."; + case ID('E', 'N', 'C'): return "Eizo Nanao Corporation"; + case ID('E', 'P', 'H'): return "Epiphan Systems Inc."; + case ID('E', 'X', 'P'): return "Data Export Corporation"; + case ID('F', 'N', 'I'): return "Funai Electric Co., Ltd."; + case ID('F', 'U', 'S'): return "Fujitsu Siemens Computers GmbH"; + case ID('G', 'S', 'M'): return "Goldstar Company Ltd"; + case ID('H', 'I', 'Q'): return "Kaohsiung Opto Electronics Americas, Inc."; + case ID('H', 'S', 'D'): return "HannStar Display Corp"; + case ID('H', 'T', 'C'): return "Hitachi Ltd"; + case ID('H', 'W', 'P'): return "Hewlett Packard"; + case ID('I', 'N', 'T'): return "Interphase Corporation"; + case ID('I', 'N', 'X'): return "Communications Supply Corporation (A division of WESCO)"; + case ID('I', 'T', 'E'): return "Integrated Tech Express Inc"; + case ID('I', 'V', 'M'): return "Iiyama North America"; + case ID('L', 'E', 'N'): return "Lenovo Group Limited"; + case ID('M', 'A', 'X'): return "Rogen Tech Distribution Inc"; + case ID('M', 'E', 'G'): return "Abeam Tech Ltd"; + case ID('M', 'E', 'I'): return "Panasonic Industry Company"; + case ID('M', 'T', 'C'): return "Mars-Tech Corporation"; + case ID('M', 'T', 'X'): return "Matrox"; + case ID('N', 'E', 'C'): return "NEC Corporation"; + case ID('N', 'E', 'X'): return "Nexgen Mediatech Inc."; + case ID('O', 'N', 'K'): return "ONKYO Corporation"; + case ID('O', 'R', 'N'): return "ORION ELECTRIC CO., LTD."; + case ID('O', 'T', 'M'): return "Optoma Corporation"; + case ID('O', 'V', 'R'): return "Oculus VR, Inc."; + case ID('P', 'H', 'L'): return "Philips Consumer Electronics Company"; + case ID('P', 'I', 'O'): return "Pioneer Electronic Corporation"; + case ID('P', 'N', 'R'): return "Planar Systems, Inc."; + case ID('Q', 'D', 'S'): return "Quanta Display Inc."; + case ID('R', 'A', 'T'): return "Rent-A-Tech"; + case ID('R', 'E', 'N'): return "Renesas Technology Corp."; + case ID('S', 'A', 'M'): return "Samsung Electric Company"; + case ID('S', 'A', 'N'): return "Sanyo Electric Co., Ltd."; + case ID('S', 'E', 'C'): return "Seiko Epson Corporation"; + case ID('S', 'H', 'P'): return "Sharp Corporation"; + case ID('S', 'I', 'I'): return "Silicon Image, Inc."; + case ID('S', 'N', 'Y'): return "Sony"; + case ID('S', 'T', 'D'): return "STD Computer Inc"; + case ID('S', 'V', 'S'): return "SVSI"; + case ID('S', 'Y', 'N'): return "Synaptics Inc"; + case ID('T', 'C', 'L'): return "Technical Concepts Ltd"; + case ID('T', 'O', 'P'): return "Orion Communications Co., Ltd."; + case ID('T', 'S', 'B'): return "Toshiba America Info Systems Inc"; + case ID('T', 'S', 'T'): return "Transtream Inc"; + case ID('U', 'N', 'K'): return "Unknown"; + case ID('V', 'E', 'S'): return "Vestel Elektronik Sanayi ve Ticaret A. S."; + case ID('V', 'I', 'T'): return "Visitech AS"; + case ID('V', 'I', 'Z'): return "VIZIO, Inc"; + case ID('V', 'S', 'C'): return "ViewSonic Corporation"; + case ID('Y', 'M', 'H'): return "Yamaha Corporation"; + default: return "Unknown"; + } +#undef ID +} + +/* See https://en.wikipedia.org/wiki/Extended_Display_Identification_Data for layout of EDID data. + * We don't parse the EDID properly. We just expect to receive valid data. + */ +void parse_edid(struct wlr_output *restrict output, size_t len, const uint8_t *data) { + if (!data || len < 128) { + snprintf(output->make, sizeof(output->make), "<Unknown>"); + snprintf(output->model, sizeof(output->model), "<Unknown>"); + return; + } + + uint16_t id = (data[8] << 8) | data[9]; + snprintf(output->make, sizeof(output->make), "%s", get_manufacturer(id)); + + uint16_t model = data[10] | (data[11] << 8); + snprintf(output->model, sizeof(output->model), "0x%04X", model); + + uint32_t serial = data[12] | (data[13] << 8) | (data[14] << 8) | (data[15] << 8); + snprintf(output->serial, sizeof(output->serial), "0x%08X", serial); + + for (size_t i = 72; i <= 108; i += 18) { + uint16_t flag = (data[i] << 8) | data[i + 1]; + if (flag == 0 && data[i + 3] == 0xFC) { + sprintf(output->model, "%.13s", &data[i + 5]); + + // Monitor names are terminated by newline if they're too short + char *nl = strchr(output->model, '\n'); + if (nl) { + *nl = '\0'; + } + } else if (flag == 0 && data[i + 3] == 0xFF) { + sprintf(output->serial, "%.13s", &data[i + 5]); + + // Monitor serial numbers are terminated by newline if they're too + // short + char *nl = strchr(output->serial, '\n'); + if (nl) { + *nl = '\0'; + } + } + } +} + +const char *conn_get_name(uint32_t type_id) { + switch (type_id) { + case DRM_MODE_CONNECTOR_Unknown: return "Unknown"; + case DRM_MODE_CONNECTOR_VGA: return "VGA"; + case DRM_MODE_CONNECTOR_DVII: return "DVI-I"; + case DRM_MODE_CONNECTOR_DVID: return "DVI-D"; + case DRM_MODE_CONNECTOR_DVIA: return "DVI-A"; + case DRM_MODE_CONNECTOR_Composite: return "Composite"; + case DRM_MODE_CONNECTOR_SVIDEO: return "SVIDEO"; + case DRM_MODE_CONNECTOR_LVDS: return "LVDS"; + case DRM_MODE_CONNECTOR_Component: return "Component"; + case DRM_MODE_CONNECTOR_9PinDIN: return "DIN"; + case DRM_MODE_CONNECTOR_DisplayPort: return "DP"; + case DRM_MODE_CONNECTOR_HDMIA: return "HDMI-A"; + case DRM_MODE_CONNECTOR_HDMIB: return "HDMI-B"; + case DRM_MODE_CONNECTOR_TV: return "TV"; + case DRM_MODE_CONNECTOR_eDP: return "eDP"; + case DRM_MODE_CONNECTOR_VIRTUAL: return "Virtual"; + case DRM_MODE_CONNECTOR_DSI: return "DSI"; +#ifdef DRM_MODE_CONNECTOR_DPI + case DRM_MODE_CONNECTOR_DPI: return "DPI"; +#endif + default: return "Unknown"; + } +} + +static void free_fb(struct gbm_bo *bo, void *data) { + uint32_t id = (uintptr_t)data; + + if (id) { + struct gbm_device *gbm = gbm_bo_get_device(bo); + drmModeRmFB(gbm_device_get_fd(gbm), id); + } +} + +uint32_t get_fb_for_bo(struct gbm_bo *bo) { + uint32_t id = (uintptr_t)gbm_bo_get_user_data(bo); + if (id) { + return id; + } + + struct gbm_device *gbm = gbm_bo_get_device(bo); + + int fd = gbm_device_get_fd(gbm); + uint32_t width = gbm_bo_get_width(bo); + uint32_t height = gbm_bo_get_height(bo); + uint32_t handles[4] = {gbm_bo_get_handle(bo).u32}; + uint32_t pitches[4] = {gbm_bo_get_stride(bo)}; + uint32_t offsets[4] = {gbm_bo_get_offset(bo, 0)}; + uint32_t format = gbm_bo_get_format(bo); + + if (drmModeAddFB2(fd, width, height, format, handles, pitches, offsets, &id, 0)) { + wlr_log_errno(WLR_ERROR, "Unable to add DRM framebuffer"); + } + + gbm_bo_set_user_data(bo, (void *)(uintptr_t)id, free_fb); + + return id; +} + +static inline 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; +} diff --git a/backend/headless/backend.c b/backend/headless/backend.c new file mode 100644 index 00000000..c0fc6022 --- /dev/null +++ b/backend/headless/backend.c @@ -0,0 +1,132 @@ +#include <assert.h> +#include <stdlib.h> +#include <wlr/interfaces/wlr_input_device.h> +#include <wlr/interfaces/wlr_output.h> +#include <wlr/render/egl.h> +#include <wlr/render/gles2.h> +#include <wlr/util/log.h> +#include "backend/headless.h" +#include "glapi.h" +#include "util/signal.h" + +struct wlr_headless_backend *headless_backend_from_backend( + struct wlr_backend *wlr_backend) { + assert(wlr_backend_is_headless(wlr_backend)); + return (struct wlr_headless_backend *)wlr_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_event_source_timer_update(output->frame_timer, output->frame_delay); + wlr_output_update_enabled(&output->wlr_output, true); + wlr_signal_emit_safe(&backend->backend.events.new_output, + &output->wlr_output); + } + + struct wlr_headless_input_device *input_device; + wl_list_for_each(input_device, &backend->input_devices, + wlr_input_device.link) { + wlr_signal_emit_safe(&backend->backend.events.new_input, + &input_device->wlr_input_device); + } + + 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; + } + + wl_list_remove(&backend->display_destroy.link); + + struct wlr_headless_output *output, *output_tmp; + wl_list_for_each_safe(output, output_tmp, &backend->outputs, link) { + wlr_output_destroy(&output->wlr_output); + } + + struct wlr_headless_input_device *input_device, *input_device_tmp; + wl_list_for_each_safe(input_device, input_device_tmp, + &backend->input_devices, wlr_input_device.link) { + wlr_input_device_destroy(&input_device->wlr_input_device); + } + + wlr_signal_emit_safe(&wlr_backend->events.destroy, backend); + + wlr_renderer_destroy(backend->renderer); + wlr_egl_finish(&backend->egl); + free(backend); +} + +static struct wlr_renderer *backend_get_renderer( + struct wlr_backend *wlr_backend) { + struct wlr_headless_backend *backend = + headless_backend_from_backend(wlr_backend); + return backend->renderer; +} + +static const struct wlr_backend_impl backend_impl = { + .start = backend_start, + .destroy = backend_destroy, + .get_renderer = backend_get_renderer, +}; + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_headless_backend *backend = + wl_container_of(listener, backend, display_destroy); + backend_destroy(&backend->backend); +} + +struct wlr_backend *wlr_headless_backend_create(struct wl_display *display, + wlr_renderer_create_func_t create_renderer_func) { + wlr_log(WLR_INFO, "Creating headless backend"); + + struct wlr_headless_backend *backend = + calloc(1, sizeof(struct wlr_headless_backend)); + if (!backend) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_headless_backend"); + return NULL; + } + wlr_backend_init(&backend->backend, &backend_impl); + backend->display = display; + wl_list_init(&backend->outputs); + wl_list_init(&backend->input_devices); + + static const EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, + EGL_ALPHA_SIZE, 0, + EGL_BLUE_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_RED_SIZE, 1, + EGL_NONE, + }; + + if (!create_renderer_func) { + create_renderer_func = wlr_renderer_autocreate; + } + + backend->renderer = create_renderer_func(&backend->egl, + EGL_PLATFORM_SURFACELESS_MESA, NULL, (EGLint*)config_attribs, 0); + if (!backend->renderer) { + wlr_log(WLR_ERROR, "Failed to create renderer"); + free(backend); + return NULL; + } + + backend->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &backend->display_destroy); + + return &backend->backend; +} + +bool wlr_backend_is_headless(struct wlr_backend *backend) { + return backend->impl == &backend_impl; +} diff --git a/backend/headless/input_device.c b/backend/headless/input_device.c new file mode 100644 index 00000000..827c6ee4 --- /dev/null +++ b/backend/headless/input_device.c @@ -0,0 +1,99 @@ +#include <assert.h> +#include <stdlib.h> +#include <wlr/interfaces/wlr_input_device.h> +#include <wlr/interfaces/wlr_keyboard.h> +#include <wlr/interfaces/wlr_pointer.h> +#include <wlr/interfaces/wlr_tablet_pad.h> +#include <wlr/interfaces/wlr_tablet_tool.h> +#include <wlr/interfaces/wlr_touch.h> +#include <wlr/interfaces/wlr_switch.h> +#include <wlr/util/log.h> +#include "backend/headless.h" +#include "util/signal.h" + +static const struct wlr_input_device_impl input_device_impl = { 0 }; + +bool wlr_input_device_is_headless(struct wlr_input_device *wlr_dev) { + return wlr_dev->impl == &input_device_impl; +} + +struct wlr_input_device *wlr_headless_add_input_device( + struct wlr_backend *wlr_backend, enum wlr_input_device_type type) { + struct wlr_headless_backend *backend = + headless_backend_from_backend(wlr_backend); + + struct wlr_headless_input_device *device = + calloc(1, sizeof(struct wlr_headless_input_device)); + if (device == NULL) { + return NULL; + } + device->backend = backend; + + int vendor = 0; + int product = 0; + const char *name = "headless"; + struct wlr_input_device *wlr_device = &device->wlr_input_device; + wlr_input_device_init(wlr_device, type, &input_device_impl, name, vendor, + product); + + switch (type) { + case WLR_INPUT_DEVICE_KEYBOARD: + wlr_device->keyboard = calloc(1, sizeof(struct wlr_keyboard)); + if (wlr_device->keyboard == NULL) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_keyboard"); + goto error; + } + wlr_keyboard_init(wlr_device->keyboard, NULL); + break; + case WLR_INPUT_DEVICE_POINTER: + wlr_device->pointer = calloc(1, sizeof(struct wlr_pointer)); + if (wlr_device->pointer == NULL) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_pointer"); + goto error; + } + wlr_pointer_init(wlr_device->pointer, NULL); + break; + case WLR_INPUT_DEVICE_TOUCH: + wlr_device->touch = calloc(1, sizeof(struct wlr_touch)); + if (wlr_device->touch == NULL) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_touch"); + goto error; + } + wlr_touch_init(wlr_device->touch, NULL); + break; + case WLR_INPUT_DEVICE_TABLET_TOOL: + wlr_device->tablet = calloc(1, sizeof(struct wlr_tablet)); + if (wlr_device->tablet == NULL) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_tablet"); + goto error; + } + wlr_tablet_init(wlr_device->tablet, NULL); + break; + case WLR_INPUT_DEVICE_TABLET_PAD: + wlr_device->tablet_pad = calloc(1, sizeof(struct wlr_tablet_pad)); + if (wlr_device->tablet_pad == NULL) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_tablet_pad"); + goto error; + } + wlr_tablet_pad_init(wlr_device->tablet_pad, NULL); + break; + case WLR_INPUT_DEVICE_SWITCH: + wlr_device->lid_switch = calloc(1, sizeof(struct wlr_switch)); + if (wlr_device->lid_switch == NULL) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_switch"); + goto error; + } + wlr_switch_init(wlr_device->lid_switch, NULL); + } + + wl_list_insert(&backend->input_devices, &wlr_device->link); + + if (backend->started) { + wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_device); + } + + return wlr_device; +error: + free(device); + return NULL; +} diff --git a/backend/headless/output.c b/backend/headless/output.c new file mode 100644 index 00000000..3cb35dce --- /dev/null +++ b/backend/headless/output.c @@ -0,0 +1,159 @@ +#include <assert.h> +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <stdlib.h> +#include <wlr/interfaces/wlr_output.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/util/log.h> +#include "backend/headless.h" +#include "util/signal.h" + +static struct wlr_headless_output *headless_output_from_output( + struct wlr_output *wlr_output) { + assert(wlr_output_is_headless(wlr_output)); + return (struct wlr_headless_output *)wlr_output; +} + +static EGLSurface egl_create_surface(struct wlr_egl *egl, unsigned int width, + unsigned int height) { + EGLint attribs[] = {EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE}; + + EGLSurface surf = eglCreatePbufferSurface(egl->display, egl->config, attribs); + if (surf == EGL_NO_SURFACE) { + wlr_log(WLR_ERROR, "Failed to create EGL surface"); + return EGL_NO_SURFACE; + } + return surf; +} + +static bool output_set_custom_mode(struct wlr_output *wlr_output, int32_t width, + int32_t height, int32_t refresh) { + struct wlr_headless_output *output = + headless_output_from_output(wlr_output); + struct wlr_headless_backend *backend = output->backend; + + if (refresh <= 0) { + refresh = HEADLESS_DEFAULT_REFRESH; + } + + wlr_egl_destroy_surface(&backend->egl, output->egl_surface); + + output->egl_surface = egl_create_surface(&backend->egl, width, height); + if (output->egl_surface == EGL_NO_SURFACE) { + wlr_log(WLR_ERROR, "Failed to recreate EGL surface"); + wlr_output_destroy(wlr_output); + return false; + } + + output->frame_delay = 1000000 / refresh; + + wlr_output_update_custom_mode(&output->wlr_output, width, height, refresh); + return true; +} + +static void output_transform(struct wlr_output *wlr_output, + enum wl_output_transform transform) { + struct wlr_headless_output *output = + headless_output_from_output(wlr_output); + output->wlr_output.transform = transform; +} + +static bool output_make_current(struct wlr_output *wlr_output, int *buffer_age) { + struct wlr_headless_output *output = + headless_output_from_output(wlr_output); + return wlr_egl_make_current(&output->backend->egl, output->egl_surface, + buffer_age); +} + +static bool output_swap_buffers(struct wlr_output *wlr_output, + pixman_region32_t *damage) { + // Nothing needs to be done for pbuffers + wlr_output_send_present(wlr_output, NULL); + 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); + + wlr_egl_destroy_surface(&output->backend->egl, output->egl_surface); + free(output); +} + +static const struct wlr_output_impl output_impl = { + .set_custom_mode = output_set_custom_mode, + .transform = output_transform, + .destroy = output_destroy, + .make_current = output_make_current, + .swap_buffers = output_swap_buffers, +}; + +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); + wl_event_source_timer_update(output->frame_timer, output->frame_delay); + 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(struct wlr_headless_output)); + if (output == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_headless_output"); + return NULL; + } + output->backend = backend; + wlr_output_init(&output->wlr_output, &backend->backend, &output_impl, + backend->display); + struct wlr_output *wlr_output = &output->wlr_output; + + output->egl_surface = egl_create_surface(&backend->egl, width, height); + if (output->egl_surface == EGL_NO_SURFACE) { + wlr_log(WLR_ERROR, "Failed to create EGL surface"); + goto error; + } + + output_set_custom_mode(wlr_output, width, height, 0); + strncpy(wlr_output->make, "headless", sizeof(wlr_output->make)); + strncpy(wlr_output->model, "headless", sizeof(wlr_output->model)); + snprintf(wlr_output->name, sizeof(wlr_output->name), "HEADLESS-%d", + wl_list_length(&backend->outputs) + 1); + + if (!wlr_egl_make_current(&output->backend->egl, output->egl_surface, + NULL)) { + goto error; + } + + wlr_renderer_begin(backend->renderer, wlr_output->width, wlr_output->height); + wlr_renderer_clear(backend->renderer, (float[]){ 1.0, 1.0, 1.0, 1.0 }); + wlr_renderer_end(backend->renderer); + + struct wl_event_loop *ev = wl_display_get_event_loop(backend->display); + output->frame_timer = wl_event_loop_add_timer(ev, signal_frame, output); + + wl_list_insert(&backend->outputs, &output->link); + + if (backend->started) { + wl_event_source_timer_update(output->frame_timer, output->frame_delay); + wlr_output_update_enabled(wlr_output, true); + wlr_signal_emit_safe(&backend->backend.events.new_output, wlr_output); + } + + return wlr_output; + +error: + wlr_output_destroy(&output->wlr_output); + return NULL; +} diff --git a/backend/libinput/backend.c b/backend/libinput/backend.c new file mode 100644 index 00000000..8106af00 --- /dev/null +++ b/backend/libinput/backend.c @@ -0,0 +1,204 @@ +#include <assert.h> +#include <libinput.h> +#include <stdlib.h> +#include <wlr/backend/interface.h> +#include <wlr/backend/session.h> +#include <wlr/util/log.h> +#include "backend/libinput.h" +#include "util/signal.h" + +static struct wlr_libinput_backend *get_libinput_backend_from_backend( + struct wlr_backend *wlr_backend) { + assert(wlr_backend_is_libinput(wlr_backend)); + return (struct wlr_libinput_backend *)wlr_backend; +} + +static int libinput_open_restricted(const char *path, + int flags, void *_backend) { + struct wlr_libinput_backend *backend = _backend; + return wlr_session_open_file(backend->session, path); +} + +static void libinput_close_restricted(int fd, void *_backend) { + struct wlr_libinput_backend *backend = _backend; + wlr_session_close_file(backend->session, fd); +} + +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; + if (libinput_dispatch(backend->libinput_context) != 0) { + wlr_log(WLR_ERROR, "Failed to dispatch libinput"); + // TODO: some kind of abort? + 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 void log_libinput(struct libinput *libinput_context, + enum libinput_log_priority priority, const char *fmt, va_list args) { + _wlr_vlog(WLR_ERROR, 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, "Initializing libinput"); + + 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); + char *no_devs = getenv("WLR_LIBINPUT_NO_DEVICES"); + if (no_devs) { + if (strcmp(no_devs, "1") != 0) { + no_devs = NULL; + } + } + if (!no_devs && backend->wlr_device_lists.length == 0) { + handle_libinput_readable(libinput_fd, WL_EVENT_READABLE, backend); + if (backend->wlr_device_lists.length == 0) { + 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; + } + } + + struct wl_event_loop *event_loop = + wl_display_get_event_loop(backend->display); + if (backend->input_event) { + wl_event_source_remove(backend->input_event); + } + backend->input_event = wl_event_loop_add_fd(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); + + for (size_t i = 0; i < backend->wlr_device_lists.length; i++) { + struct wl_list *wlr_devices = backend->wlr_device_lists.items[i]; + struct wlr_input_device *wlr_dev, *next; + wl_list_for_each_safe(wlr_dev, next, wlr_devices, link) { + wlr_input_device_destroy(wlr_dev); + } + free(wlr_devices); + } + + wlr_signal_emit_safe(&wlr_backend->events.destroy, wlr_backend); + + wl_list_remove(&backend->display_destroy.link); + wl_list_remove(&backend->session_signal.link); + + wlr_list_finish(&backend->wlr_device_lists); + 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 = data; + + if (!backend->libinput_context) { + return; + } + + if (session->active) { + libinput_resume(backend->libinput_context); + } else { + libinput_suspend(backend->libinput_context); + } +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_libinput_backend *backend = + wl_container_of(listener, backend, display_destroy); + backend_destroy(&backend->backend); +} + +struct wlr_backend *wlr_libinput_backend_create(struct wl_display *display, + struct wlr_session *session) { + struct wlr_libinput_backend *backend = + calloc(1, sizeof(struct wlr_libinput_backend)); + if (!backend) { + wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno)); + return NULL; + } + wlr_backend_init(&backend->backend, &backend_impl); + + if (!wlr_list_init(&backend->wlr_device_lists)) { + wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno)); + goto error_backend; + } + + backend->session = session; + backend->display = display; + + backend->session_signal.notify = session_signal; + wl_signal_add(&session->session_signal, &backend->session_signal); + + backend->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &backend->display_destroy); + + return &backend->backend; +error_backend: + free(backend); + return NULL; +} + +struct libinput_device *wlr_libinput_get_device_handle( + struct wlr_input_device *wlr_dev) { + struct wlr_libinput_input_device *dev = + (struct wlr_libinput_input_device *)wlr_dev; + return dev->handle; +} + +uint32_t usec_to_msec(uint64_t usec) { + return (uint32_t)(usec / 1000); +} diff --git a/backend/libinput/events.c b/backend/libinput/events.c new file mode 100644 index 00000000..a7a6c114 --- /dev/null +++ b/backend/libinput/events.c @@ -0,0 +1,294 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <libinput.h> +#include <stdlib.h> +#include <wayland-util.h> +#include <wlr/backend/session.h> +#include <wlr/interfaces/wlr_input_device.h> +#include <wlr/util/log.h> +#include "backend/libinput.h" +#include "util/signal.h" + +struct wlr_libinput_input_device *get_libinput_device_from_device( + struct wlr_input_device *wlr_dev) { + assert(wlr_input_device_is_libinput(wlr_dev)); + return (struct wlr_libinput_input_device *)wlr_dev; +} + +struct wlr_input_device *get_appropriate_device( + enum wlr_input_device_type desired_type, + struct libinput_device *libinput_dev) { + struct wl_list *wlr_devices = libinput_device_get_user_data(libinput_dev); + if (!wlr_devices) { + return NULL; + } + struct wlr_input_device *dev; + wl_list_for_each(dev, wlr_devices, link) { + if (dev->type == desired_type) { + return dev; + } + } + return NULL; +} + +static void input_device_destroy(struct wlr_input_device *wlr_dev) { + struct wlr_libinput_input_device *dev = + get_libinput_device_from_device(wlr_dev); + libinput_device_unref(dev->handle); + wl_list_remove(&dev->wlr_input_device.link); + free(dev); +} + +static const struct wlr_input_device_impl input_device_impl = { + .destroy = input_device_destroy, +}; + +static struct wlr_input_device *allocate_device( + struct wlr_libinput_backend *backend, + struct libinput_device *libinput_dev, struct wl_list *wlr_devices, + enum wlr_input_device_type type) { + 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); + struct wlr_libinput_input_device *dev = + calloc(1, sizeof(struct wlr_libinput_input_device)); + if (dev == NULL) { + return NULL; + } + struct wlr_input_device *wlr_dev = &dev->wlr_input_device; + libinput_device_get_size(libinput_dev, + &wlr_dev->width_mm, &wlr_dev->height_mm); + const char *output_name = libinput_device_get_output_name(libinput_dev); + if (output_name != NULL) { + wlr_dev->output_name = strdup(output_name); + } + wl_list_insert(wlr_devices, &wlr_dev->link); + dev->handle = libinput_dev; + libinput_device_ref(libinput_dev); + wlr_input_device_init(wlr_dev, type, &input_device_impl, + name, vendor, product); + return wlr_dev; +} + +bool wlr_input_device_is_libinput(struct wlr_input_device *wlr_dev) { + return wlr_dev->impl == &input_device_impl; +} + +static void handle_device_added(struct wlr_libinput_backend *backend, + struct libinput_device *libinput_dev) { + /* + * Note: the wlr API exposes only devices with a single capability, because + * that meshes better with how Wayland does things and is a bit simpler. + * However, libinput devices often have multiple capabilities - in such + * cases we have to create several devices. + */ + 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); + struct wl_list *wlr_devices = calloc(1, sizeof(struct wl_list)); + if (!wlr_devices) { + wlr_log(WLR_ERROR, "Allocation failed"); + return; + } + wl_list_init(wlr_devices); + wlr_log(WLR_DEBUG, "Added %s [%d:%d]", name, vendor, product); + + if (libinput_device_has_capability( + libinput_dev, LIBINPUT_DEVICE_CAP_KEYBOARD)) { + struct wlr_input_device *wlr_dev = allocate_device(backend, + libinput_dev, wlr_devices, WLR_INPUT_DEVICE_KEYBOARD); + if (!wlr_dev) { + goto fail; + } + wlr_dev->keyboard = create_libinput_keyboard(libinput_dev); + if (!wlr_dev->keyboard) { + free(wlr_dev); + goto fail; + } + wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev); + } + if (libinput_device_has_capability( + libinput_dev, LIBINPUT_DEVICE_CAP_POINTER)) { + struct wlr_input_device *wlr_dev = allocate_device(backend, + libinput_dev, wlr_devices, WLR_INPUT_DEVICE_POINTER); + if (!wlr_dev) { + goto fail; + } + wlr_dev->pointer = create_libinput_pointer(libinput_dev); + if (!wlr_dev->pointer) { + free(wlr_dev); + goto fail; + } + wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev); + } + if (libinput_device_has_capability( + libinput_dev, LIBINPUT_DEVICE_CAP_TOUCH)) { + struct wlr_input_device *wlr_dev = allocate_device(backend, + libinput_dev, wlr_devices, WLR_INPUT_DEVICE_TOUCH); + if (!wlr_dev) { + goto fail; + } + wlr_dev->touch = create_libinput_touch(libinput_dev); + if (!wlr_dev->touch) { + free(wlr_dev); + goto fail; + } + wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev); + } + if (libinput_device_has_capability(libinput_dev, + LIBINPUT_DEVICE_CAP_TABLET_TOOL)) { + struct wlr_input_device *wlr_dev = allocate_device(backend, + libinput_dev, wlr_devices, WLR_INPUT_DEVICE_TABLET_TOOL); + if (!wlr_dev) { + goto fail; + } + wlr_dev->tablet = create_libinput_tablet(libinput_dev); + if (!wlr_dev->tablet) { + free(wlr_dev); + goto fail; + } + wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev); + } + if (libinput_device_has_capability( + libinput_dev, LIBINPUT_DEVICE_CAP_TABLET_PAD)) { + struct wlr_input_device *wlr_dev = allocate_device(backend, + libinput_dev, wlr_devices, WLR_INPUT_DEVICE_TABLET_PAD); + if (!wlr_dev) { + goto fail; + } + wlr_dev->tablet_pad = create_libinput_tablet_pad(libinput_dev); + if (!wlr_dev->tablet_pad) { + free(wlr_dev); + goto fail; + } + wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev); + } + if (libinput_device_has_capability( + libinput_dev, LIBINPUT_DEVICE_CAP_GESTURE)) { + // TODO + } + if (libinput_device_has_capability( + libinput_dev, LIBINPUT_DEVICE_CAP_SWITCH)) { + struct wlr_input_device *wlr_dev = allocate_device(backend, + libinput_dev, wlr_devices, WLR_INPUT_DEVICE_SWITCH); + if (!wlr_dev) { + goto fail; + } + wlr_dev->lid_switch = create_libinput_switch(libinput_dev); + if (!wlr_dev->lid_switch) { + free(wlr_dev); + goto fail; + } + wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev); + } + + if (!wl_list_empty(wlr_devices)) { + libinput_device_set_user_data(libinput_dev, wlr_devices); + wlr_list_push(&backend->wlr_device_lists, wlr_devices); + } else { + free(wlr_devices); + } + return; + +fail: + wlr_log(WLR_ERROR, "Could not allocate new device"); + struct wlr_input_device *dev, *tmp_dev; + wl_list_for_each_safe(dev, tmp_dev, wlr_devices, link) { + free(dev); + } + free(wlr_devices); +} + +static void handle_device_removed(struct wlr_libinput_backend *backend, + struct libinput_device *libinput_dev) { + struct wl_list *wlr_devices = libinput_device_get_user_data(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, "Removing %s [%d:%d]", name, vendor, product); + if (!wlr_devices) { + return; + } + struct wlr_input_device *dev, *tmp_dev; + wl_list_for_each_safe(dev, tmp_dev, wlr_devices, link) { + wlr_input_device_destroy(dev); + } + for (size_t i = 0; i < backend->wlr_device_lists.length; i++) { + if (backend->wlr_device_lists.items[i] == wlr_devices) { + wlr_list_del(&backend->wlr_device_lists, i); + break; + } + } + free(wlr_devices); +} + +void handle_libinput_event(struct wlr_libinput_backend *backend, + struct libinput_event *event) { + struct libinput_device *libinput_dev = libinput_event_get_device(event); + enum libinput_event_type event_type = libinput_event_get_type(event); + switch (event_type) { + case LIBINPUT_EVENT_DEVICE_ADDED: + handle_device_added(backend, libinput_dev); + break; + case LIBINPUT_EVENT_DEVICE_REMOVED: + handle_device_removed(backend, libinput_dev); + break; + case LIBINPUT_EVENT_KEYBOARD_KEY: + handle_keyboard_key(event, libinput_dev); + break; + case LIBINPUT_EVENT_POINTER_MOTION: + handle_pointer_motion(event, libinput_dev); + break; + case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: + handle_pointer_motion_abs(event, libinput_dev); + break; + case LIBINPUT_EVENT_POINTER_BUTTON: + handle_pointer_button(event, libinput_dev); + break; + case LIBINPUT_EVENT_POINTER_AXIS: + handle_pointer_axis(event, libinput_dev); + break; + case LIBINPUT_EVENT_TOUCH_DOWN: + handle_touch_down(event, libinput_dev); + break; + case LIBINPUT_EVENT_TOUCH_UP: + handle_touch_up(event, libinput_dev); + break; + case LIBINPUT_EVENT_TOUCH_MOTION: + handle_touch_motion(event, libinput_dev); + break; + case LIBINPUT_EVENT_TOUCH_CANCEL: + handle_touch_cancel(event, libinput_dev); + break; + case LIBINPUT_EVENT_TOUCH_FRAME: + // no-op (at least for now) + break; + case LIBINPUT_EVENT_TABLET_TOOL_AXIS: + handle_tablet_tool_axis(event, libinput_dev); + break; + case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: + handle_tablet_tool_proximity(event, libinput_dev); + break; + case LIBINPUT_EVENT_TABLET_TOOL_TIP: + handle_tablet_tool_tip(event, libinput_dev); + break; + case LIBINPUT_EVENT_TABLET_TOOL_BUTTON: + handle_tablet_tool_button(event, libinput_dev); + break; + case LIBINPUT_EVENT_TABLET_PAD_BUTTON: + handle_tablet_pad_button(event, libinput_dev); + break; + case LIBINPUT_EVENT_TABLET_PAD_RING: + handle_tablet_pad_ring(event, libinput_dev); + break; + case LIBINPUT_EVENT_TABLET_PAD_STRIP: + handle_tablet_pad_strip(event, libinput_dev); + break; + case LIBINPUT_EVENT_SWITCH_TOGGLE: + handle_switch_toggle(event, libinput_dev); + break; + default: + wlr_log(WLR_DEBUG, "Unknown libinput event %d", event_type); + break; + } +} diff --git a/backend/libinput/keyboard.c b/backend/libinput/keyboard.c new file mode 100644 index 00000000..93605e77 --- /dev/null +++ b/backend/libinput/keyboard.c @@ -0,0 +1,83 @@ +#include <assert.h> +#include <libinput.h> +#include <stdlib.h> +#include <wlr/backend/session.h> +#include <wlr/interfaces/wlr_keyboard.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/util/log.h> +#include "backend/libinput.h" + +struct wlr_libinput_keyboard { + struct wlr_keyboard wlr_keyboard; + struct libinput_device *libinput_dev; +}; + +static const struct wlr_keyboard_impl impl; + +static struct wlr_libinput_keyboard *get_libinput_keyboard_from_keyboard( + struct wlr_keyboard *wlr_kb) { + assert(wlr_kb->impl == &impl); + return (struct wlr_libinput_keyboard *)wlr_kb; +} + +static void keyboard_set_leds(struct wlr_keyboard *wlr_kb, uint32_t leds) { + struct wlr_libinput_keyboard *kb = + get_libinput_keyboard_from_keyboard(wlr_kb); + libinput_device_led_update(kb->libinput_dev, leds); +} + +static void keyboard_destroy(struct wlr_keyboard *wlr_kb) { + struct wlr_libinput_keyboard *kb = + get_libinput_keyboard_from_keyboard(wlr_kb); + libinput_device_unref(kb->libinput_dev); + free(kb); +} + +static const struct wlr_keyboard_impl impl = { + .destroy = keyboard_destroy, + .led_update = keyboard_set_leds +}; + +struct wlr_keyboard *create_libinput_keyboard( + struct libinput_device *libinput_dev) { + struct wlr_libinput_keyboard *kb = + calloc(1, sizeof(struct wlr_libinput_keyboard)); + if (kb == NULL) { + return NULL; + } + kb->libinput_dev = libinput_dev; + libinput_device_ref(libinput_dev); + libinput_device_led_update(libinput_dev, 0); + struct wlr_keyboard *wlr_kb = &kb->wlr_keyboard; + wlr_keyboard_init(wlr_kb, &impl); + return wlr_kb; +} + +void handle_keyboard_key(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_KEYBOARD, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, + "Got a keyboard event for a device with no keyboards?"); + return; + } + struct libinput_event_keyboard *kbevent = + libinput_event_get_keyboard_event(event); + struct wlr_event_keyboard_key wlr_event = { 0 }; + wlr_event.time_msec = + usec_to_msec(libinput_event_keyboard_get_time_usec(kbevent)); + wlr_event.keycode = libinput_event_keyboard_get_key(kbevent); + enum libinput_key_state state = + libinput_event_keyboard_get_key_state(kbevent); + switch (state) { + case LIBINPUT_KEY_STATE_RELEASED: + wlr_event.state = WLR_KEY_RELEASED; + break; + case LIBINPUT_KEY_STATE_PRESSED: + wlr_event.state = WLR_KEY_PRESSED; + break; + } + wlr_event.update_state = true; + wlr_keyboard_notify_key(wlr_dev->keyboard, &wlr_event); +} diff --git a/backend/libinput/pointer.c b/backend/libinput/pointer.c new file mode 100644 index 00000000..fb85cddd --- /dev/null +++ b/backend/libinput/pointer.c @@ -0,0 +1,136 @@ +#include <assert.h> +#include <libinput.h> +#include <stdlib.h> +#include <wlr/backend/session.h> +#include <wlr/interfaces/wlr_pointer.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/util/log.h> +#include "backend/libinput.h" +#include "util/signal.h" + +struct wlr_pointer *create_libinput_pointer( + struct libinput_device *libinput_dev) { + assert(libinput_dev); + struct wlr_pointer *wlr_pointer = calloc(1, sizeof(struct wlr_pointer)); + if (!wlr_pointer) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_pointer"); + return NULL; + } + wlr_pointer_init(wlr_pointer, NULL); + return wlr_pointer; +} + +void handle_pointer_motion(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_POINTER, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, "Got a pointer event for a device with no pointers?"); + return; + } + struct libinput_event_pointer *pevent = + libinput_event_get_pointer_event(event); + struct wlr_event_pointer_motion wlr_event = { 0 }; + wlr_event.device = wlr_dev; + wlr_event.time_msec = + usec_to_msec(libinput_event_pointer_get_time_usec(pevent)); + wlr_event.delta_x = libinput_event_pointer_get_dx(pevent); + wlr_event.delta_y = libinput_event_pointer_get_dy(pevent); + wlr_signal_emit_safe(&wlr_dev->pointer->events.motion, &wlr_event); +} + +void handle_pointer_motion_abs(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_POINTER, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, "Got a pointer event for a device with no pointers?"); + return; + } + struct libinput_event_pointer *pevent = + libinput_event_get_pointer_event(event); + struct wlr_event_pointer_motion_absolute wlr_event = { 0 }; + wlr_event.device = wlr_dev; + wlr_event.time_msec = + usec_to_msec(libinput_event_pointer_get_time_usec(pevent)); + wlr_event.x = libinput_event_pointer_get_absolute_x_transformed(pevent, 1); + wlr_event.y = libinput_event_pointer_get_absolute_y_transformed(pevent, 1); + wlr_signal_emit_safe(&wlr_dev->pointer->events.motion_absolute, &wlr_event); +} + +void handle_pointer_button(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_POINTER, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, "Got a pointer event for a device with no pointers?"); + return; + } + struct libinput_event_pointer *pevent = + libinput_event_get_pointer_event(event); + struct wlr_event_pointer_button wlr_event = { 0 }; + wlr_event.device = wlr_dev; + wlr_event.time_msec = + usec_to_msec(libinput_event_pointer_get_time_usec(pevent)); + wlr_event.button = libinput_event_pointer_get_button(pevent); + switch (libinput_event_pointer_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; + } + wlr_signal_emit_safe(&wlr_dev->pointer->events.button, &wlr_event); +} + +void handle_pointer_axis(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_POINTER, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, "Got a pointer event for a device with no pointers?"); + return; + } + struct libinput_event_pointer *pevent = + libinput_event_get_pointer_event(event); + struct wlr_event_pointer_axis wlr_event = { 0 }; + wlr_event.device = wlr_dev; + wlr_event.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 = WLR_AXIS_SOURCE_WHEEL; + break; + case LIBINPUT_POINTER_AXIS_SOURCE_FINGER: + wlr_event.source = WLR_AXIS_SOURCE_FINGER; + break; + case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS: + wlr_event.source = WLR_AXIS_SOURCE_CONTINUOUS; + break; + case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT: + wlr_event.source = WLR_AXIS_SOURCE_WHEEL_TILT; + break; + } + enum libinput_pointer_axis axies[] = { + LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, + LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, + }; + for (size_t i = 0; i < sizeof(axies) / sizeof(axies[0]); ++i) { + if (libinput_event_pointer_has_axis(pevent, axies[i])) { + switch (axies[i]) { + case LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL: + wlr_event.orientation = WLR_AXIS_ORIENTATION_VERTICAL; + break; + case LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL: + wlr_event.orientation = WLR_AXIS_ORIENTATION_HORIZONTAL; + break; + } + wlr_event.delta = + libinput_event_pointer_get_axis_value(pevent, axies[i]); + wlr_event.delta_discrete = + libinput_event_pointer_get_axis_value_discrete(pevent, axies[i]); + wlr_signal_emit_safe(&wlr_dev->pointer->events.axis, &wlr_event); + } + } +} diff --git a/backend/libinput/switch.c b/backend/libinput/switch.c new file mode 100644 index 00000000..393460b0 --- /dev/null +++ b/backend/libinput/switch.c @@ -0,0 +1,55 @@ +#include <assert.h> +#include <libinput.h> +#include <stdlib.h> +#include <wlr/backend/session.h> +#include <wlr/interfaces/wlr_switch.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/util/log.h> +#include "backend/libinput.h" +#include "util/signal.h" + +struct wlr_switch *create_libinput_switch( + struct libinput_device *libinput_dev) { + assert(libinput_dev); + struct wlr_switch *wlr_switch = calloc(1, sizeof(struct wlr_switch)); + if (!wlr_switch) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_switch"); + return NULL; + } + wlr_switch_init(wlr_switch, NULL); + wlr_log(WLR_DEBUG, "Created switch for device %s", libinput_device_get_name(libinput_dev)); + return wlr_switch; +} + +void handle_switch_toggle(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_SWITCH, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, "Got a switch event for a device with no switch?"); + return; + } + struct libinput_event_switch *sevent = + libinput_event_get_switch_event (event); + struct wlr_event_switch_toggle wlr_event = { 0 }; + wlr_event.device = wlr_dev; + 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; + } + wlr_event.time_msec = + usec_to_msec(libinput_event_switch_get_time_usec(sevent)); + wlr_signal_emit_safe(&wlr_dev->lid_switch->events.toggle, &wlr_event); +} diff --git a/backend/libinput/tablet_pad.c b/backend/libinput/tablet_pad.c new file mode 100644 index 00000000..b053b9a0 --- /dev/null +++ b/backend/libinput/tablet_pad.c @@ -0,0 +1,183 @@ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#include <assert.h> +#include <string.h> +#include <libinput.h> +#include <stdlib.h> +#include <wlr/backend/session.h> +#include <wlr/interfaces/wlr_tablet_pad.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/util/log.h> +#include "backend/libinput.h" +#include "util/signal.h" + +// FIXME: Decide on how to alloc/count here +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(struct wlr_tablet_pad_group)); + if (!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(sizeof(unsigned int), group->ring_count); + 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(sizeof(unsigned int), group->strip_count); + 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(sizeof(unsigned int), group->button_count); + 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); + wl_list_insert(&pad->groups, &group->link); +} + +struct wlr_tablet_pad *create_libinput_tablet_pad( + struct libinput_device *libinput_dev) { + assert(libinput_dev); + struct wlr_tablet_pad *wlr_tablet_pad = + calloc(1, sizeof(struct wlr_tablet_pad)); + if (!wlr_tablet_pad) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_tablet_pad"); + return NULL; + } + + wlr_tablet_pad->button_count = + libinput_device_tablet_pad_get_num_buttons(libinput_dev); + wlr_tablet_pad->ring_count = + libinput_device_tablet_pad_get_num_rings(libinput_dev); + wlr_tablet_pad->strip_count = + libinput_device_tablet_pad_get_num_strips(libinput_dev); + + wlr_list_init(&wlr_tablet_pad->paths); + struct udev_device *udev = libinput_device_get_udev_device(libinput_dev); + wlr_list_push(&wlr_tablet_pad->paths, strdup(udev_device_get_syspath(udev))); + + wl_list_init(&wlr_tablet_pad->groups); + int groups = libinput_device_tablet_pad_get_num_mode_groups(libinput_dev); + for (int i = 0; i < groups; ++i) { + add_pad_group_from_libinput(wlr_tablet_pad, libinput_dev, i); + } + + wlr_tablet_pad_init(wlr_tablet_pad, NULL); + return wlr_tablet_pad; +} + +void handle_tablet_pad_button(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TABLET_PAD, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, + "Got a tablet pad event for a device with no tablet pad?"); + return; + } + struct libinput_event_tablet_pad *pevent = + libinput_event_get_tablet_pad_event(event); + struct wlr_event_tablet_pad_button wlr_event = { 0 }; + wlr_event.time_msec = + usec_to_msec(libinput_event_tablet_pad_get_time_usec(pevent)); + wlr_event.button = libinput_event_tablet_pad_get_button_number(pevent); + wlr_event.mode = libinput_event_tablet_pad_get_mode(pevent); + wlr_event.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; + } + wlr_signal_emit_safe(&wlr_dev->tablet_pad->events.button, &wlr_event); +} + +void handle_tablet_pad_ring(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TABLET_PAD, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, + "Got a tablet pad event for a device with no tablet pad?"); + return; + } + struct libinput_event_tablet_pad *pevent = + libinput_event_get_tablet_pad_event(event); + struct wlr_event_tablet_pad_ring wlr_event = { 0 }; + wlr_event.time_msec = + usec_to_msec(libinput_event_tablet_pad_get_time_usec(pevent)); + wlr_event.ring = libinput_event_tablet_pad_get_ring_number(pevent); + wlr_event.position = libinput_event_tablet_pad_get_ring_position(pevent); + wlr_event.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; + } + wlr_signal_emit_safe(&wlr_dev->tablet_pad->events.ring, &wlr_event); +} + +void handle_tablet_pad_strip(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TABLET_PAD, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, + "Got a tablet pad event for a device with no tablet pad?"); + return; + } + struct libinput_event_tablet_pad *pevent = + libinput_event_get_tablet_pad_event(event); + struct wlr_event_tablet_pad_strip wlr_event = { 0 }; + wlr_event.time_msec = + usec_to_msec(libinput_event_tablet_pad_get_time_usec(pevent)); + wlr_event.strip = libinput_event_tablet_pad_get_strip_number(pevent); + wlr_event.position = libinput_event_tablet_pad_get_strip_position(pevent); + wlr_event.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; + } + wlr_signal_emit_safe(&wlr_dev->tablet_pad->events.strip, &wlr_event); +} diff --git a/backend/libinput/tablet_tool.c b/backend/libinput/tablet_tool.c new file mode 100644 index 00000000..4e87b700 --- /dev/null +++ b/backend/libinput/tablet_tool.c @@ -0,0 +1,371 @@ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#include <string.h> +#include <assert.h> +#include <libinput.h> +#include <stdlib.h> +#include <wayland-util.h> +#include <wlr/backend/session.h> +#include <wlr/interfaces/wlr_tablet_tool.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/util/log.h> +#include "backend/libinput.h" +#include "util/signal.h" + +static struct wlr_tablet_impl tablet_impl; + +static bool tablet_is_libinput(struct wlr_tablet *tablet) { + return tablet->impl == &tablet_impl; +} + +struct wlr_libinput_tablet_tool { + struct wlr_tablet_tool wlr_tool; + + struct libinput_tablet_tool *libinput_tool; + + bool unique; + // Refcount for destroy + release + size_t pad_refs; +}; + +// TODO: Maybe this should be a wlr_list? Do we keep it, or want to get rid of +// it? +struct tablet_tool_list_elem { + struct wl_list link; + + struct wlr_libinput_tablet_tool *tool; +}; + +struct wlr_libinput_tablet { + struct wlr_tablet wlr_tablet; + + struct wl_list tools; // tablet_tool_list_elem::link +}; + +static void destroy_tool(struct wlr_libinput_tablet_tool *tool) { + wlr_signal_emit_safe(&tool->wlr_tool.events.destroy, &tool->wlr_tool); + libinput_tablet_tool_ref(tool->libinput_tool); + libinput_tablet_tool_set_user_data(tool->libinput_tool, NULL); + free(tool); +} + + +static void destroy_tablet(struct wlr_tablet *wlr_tablet) { + assert(tablet_is_libinput(wlr_tablet)); + struct wlr_libinput_tablet *tablet = + wl_container_of(wlr_tablet, tablet, wlr_tablet); + + struct tablet_tool_list_elem *pos; + struct tablet_tool_list_elem *tmp; + wl_list_for_each_safe(pos, tmp, &tablet->tools, link) { + struct wlr_libinput_tablet_tool *tool = pos->tool; + wl_list_remove(&pos->link); + free(pos); + + if (--tool->pad_refs == 0) { + destroy_tool(tool); + } + } + + free(tablet); +} + +static struct wlr_tablet_impl tablet_impl = { + .destroy = destroy_tablet, +}; + +struct wlr_tablet *create_libinput_tablet( + struct libinput_device *libinput_dev) { + assert(libinput_dev); + struct wlr_libinput_tablet *libinput_tablet = + calloc(1, sizeof(struct wlr_libinput_tablet)); + if (!libinput_tablet) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_tablet_tool"); + return NULL; + } + struct wlr_tablet *wlr_tablet = &libinput_tablet->wlr_tablet; + + wlr_list_init(&wlr_tablet->paths); + struct udev_device *udev = libinput_device_get_udev_device(libinput_dev); + wlr_list_push(&wlr_tablet->paths, strdup(udev_device_get_syspath(udev))); + wlr_tablet->name = strdup(libinput_device_get_name(libinput_dev)); + wl_list_init(&libinput_tablet->tools); + + wlr_tablet_init(wlr_tablet, &tablet_impl); + return wlr_tablet; +} + +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; + } + + assert(false && "UNREACHABLE"); +} + +static struct wlr_libinput_tablet_tool *get_wlr_tablet_tool( + struct libinput_tablet_tool *tool) { + struct wlr_libinput_tablet_tool *ret = + libinput_tablet_tool_get_user_data(tool); + + if (ret) { + return ret; + } + + ret = calloc(1, sizeof(struct wlr_libinput_tablet_tool)); + if (!ret) { + return NULL; + } + + ret->libinput_tool = libinput_tablet_tool_ref(tool); + ret->wlr_tool.pressure = libinput_tablet_tool_has_pressure(tool); + ret->wlr_tool.distance = libinput_tablet_tool_has_distance(tool); + ret->wlr_tool.tilt = libinput_tablet_tool_has_tilt(tool); + ret->wlr_tool.rotation = libinput_tablet_tool_has_rotation(tool); + ret->wlr_tool.slider = libinput_tablet_tool_has_slider(tool); + ret->wlr_tool.wheel = libinput_tablet_tool_has_wheel(tool); + + ret->wlr_tool.hardware_serial = libinput_tablet_tool_get_serial(tool); + ret->wlr_tool.hardware_wacom = libinput_tablet_tool_get_tool_id(tool); + ret->wlr_tool.type = wlr_type_from_libinput_type( + libinput_tablet_tool_get_type(tool)); + + ret->unique = libinput_tablet_tool_is_unique(tool); + + wl_signal_init(&ret->wlr_tool.events.destroy); + + libinput_tablet_tool_set_user_data(tool, ret); + return ret; +} + +static void ensure_tool_reference(struct wlr_libinput_tablet_tool *tool, + struct wlr_tablet *wlr_dev) { + assert(tablet_is_libinput(wlr_dev)); + struct wlr_libinput_tablet *tablet = + wl_container_of(wlr_dev, tablet, wlr_tablet); + + struct tablet_tool_list_elem *pos; + wl_list_for_each(pos, &tablet->tools, link) { + if (pos->tool == tool) { // We already have a ref + // XXX: We *could* optimize the tool to the front of + // the list here, since we will probably get the next + // couple of events from the same tool. + // BUT the list should always be rather short (probably + // single digit amount of tools) so it might be more + // work than it saves + return; + } + } + + struct tablet_tool_list_elem *new = + calloc(1, sizeof(struct tablet_tool_list_elem)); + if (!new) { + wlr_log(WLR_ERROR, "Failed to allocate memory for tracking tablet tool"); + return; + } + + new->tool = tool; + wl_list_insert(&tablet->tools, &new->link); + ++tool->pad_refs; +} + +void handle_tablet_tool_axis(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TABLET_TOOL, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, + "Got a tablet tool event for a device with no tablet tools?"); + return; + } + struct libinput_event_tablet_tool *tevent = + libinput_event_get_tablet_tool_event(event); + struct wlr_event_tablet_tool_axis wlr_event = { 0 }; + struct wlr_libinput_tablet_tool *tool = get_wlr_tablet_tool( + libinput_event_tablet_tool_get_tool(tevent)); + ensure_tool_reference(tool, wlr_dev->tablet); + + wlr_event.device = wlr_dev; + wlr_event.tool = &tool->wlr_tool; + wlr_event.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); + } + wlr_signal_emit_safe(&wlr_dev->tablet->events.axis, &wlr_event); +} + +void handle_tablet_tool_proximity(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TABLET_TOOL, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, + "Got a tablet tool event for a device with no tablet tools?"); + return; + } + struct libinput_event_tablet_tool *tevent = + libinput_event_get_tablet_tool_event(event); + struct wlr_event_tablet_tool_proximity wlr_event = { 0 }; + struct wlr_libinput_tablet_tool *tool = get_wlr_tablet_tool( + libinput_event_tablet_tool_get_tool(tevent)); + ensure_tool_reference(tool, wlr_dev->tablet); + + wlr_event.tool = &tool->wlr_tool; + wlr_event.device = wlr_dev; + wlr_event.time_msec = + usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)); + 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; + } + wlr_signal_emit_safe(&wlr_dev->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, libinput_dev); + } + + // If the tool is not unique, libinput will not find it again after the + // proximity out, so we should destroy it + if (!tool->unique && + 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 + assert(tool->pad_refs == 1); + assert(tablet_is_libinput(wlr_dev->tablet)); + struct wlr_libinput_tablet *tablet = + wl_container_of(wlr_dev->tablet, tablet, wlr_tablet); + struct tablet_tool_list_elem *pos; + struct tablet_tool_list_elem *tmp; + + wl_list_for_each_safe(pos, tmp, &tablet->tools, link) { + if (pos->tool == tool) { + wl_list_remove(&pos->link); + free(pos); + break; + } + } + + destroy_tool(tool); + } +} + +void handle_tablet_tool_tip(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TABLET_TOOL, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, + "Got a tablet tool event for a device with no tablet tools?"); + return; + } + handle_tablet_tool_axis(event, libinput_dev); + struct libinput_event_tablet_tool *tevent = + libinput_event_get_tablet_tool_event(event); + struct wlr_event_tablet_tool_tip wlr_event = { 0 }; + struct wlr_libinput_tablet_tool *tool = get_wlr_tablet_tool( + libinput_event_tablet_tool_get_tool(tevent)); + ensure_tool_reference(tool, wlr_dev->tablet); + + wlr_event.device = wlr_dev; + wlr_event.tool = &tool->wlr_tool; + wlr_event.time_msec = + usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)); + 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; + } + wlr_signal_emit_safe(&wlr_dev->tablet->events.tip, &wlr_event); +} + +void handle_tablet_tool_button(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TABLET_TOOL, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, + "Got a tablet tool event for a device with no tablet tools?"); + return; + } + handle_tablet_tool_axis(event, libinput_dev); + struct libinput_event_tablet_tool *tevent = + libinput_event_get_tablet_tool_event(event); + struct wlr_event_tablet_tool_button wlr_event = { 0 }; + struct wlr_libinput_tablet_tool *tool = get_wlr_tablet_tool( + libinput_event_tablet_tool_get_tool(tevent)); + ensure_tool_reference(tool, wlr_dev->tablet); + + wlr_event.device = wlr_dev; + wlr_event.tool = &tool->wlr_tool; + wlr_event.time_msec = + usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)); + wlr_event.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; + } + wlr_signal_emit_safe(&wlr_dev->tablet->events.button, &wlr_event); +} diff --git a/backend/libinput/touch.c b/backend/libinput/touch.c new file mode 100644 index 00000000..cb9b0e36 --- /dev/null +++ b/backend/libinput/touch.c @@ -0,0 +1,97 @@ +#include <assert.h> +#include <libinput.h> +#include <stdlib.h> +#include <wlr/backend/session.h> +#include <wlr/interfaces/wlr_touch.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/util/log.h> +#include "backend/libinput.h" +#include "util/signal.h" + +struct wlr_touch *create_libinput_touch( + struct libinput_device *libinput_dev) { + assert(libinput_dev); + struct wlr_touch *wlr_touch = calloc(1, sizeof(struct wlr_touch)); + if (!wlr_touch) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_touch"); + return NULL; + } + wlr_touch_init(wlr_touch, NULL); + return wlr_touch; +} + +void handle_touch_down(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TOUCH, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, "Got a touch event for a device with no touch?"); + return; + } + struct libinput_event_touch *tevent = + libinput_event_get_touch_event(event); + struct wlr_event_touch_down wlr_event = { 0 }; + wlr_event.device = wlr_dev; + wlr_event.time_msec = + usec_to_msec(libinput_event_touch_get_time_usec(tevent)); + wlr_event.touch_id = libinput_event_touch_get_slot(tevent); + wlr_event.x = libinput_event_touch_get_x_transformed(tevent, 1); + wlr_event.y = libinput_event_touch_get_y_transformed(tevent, 1); + wlr_signal_emit_safe(&wlr_dev->touch->events.down, &wlr_event); +} + +void handle_touch_up(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TOUCH, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, "Got a touch event for a device with no touch?"); + return; + } + struct libinput_event_touch *tevent = + libinput_event_get_touch_event(event); + struct wlr_event_touch_up wlr_event = { 0 }; + wlr_event.device = wlr_dev; + wlr_event.time_msec = + usec_to_msec(libinput_event_touch_get_time_usec(tevent)); + wlr_event.touch_id = libinput_event_touch_get_slot(tevent); + wlr_signal_emit_safe(&wlr_dev->touch->events.up, &wlr_event); +} + +void handle_touch_motion(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TOUCH, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, "Got a touch event for a device with no touch?"); + return; + } + struct libinput_event_touch *tevent = + libinput_event_get_touch_event(event); + struct wlr_event_touch_motion wlr_event = { 0 }; + wlr_event.device = wlr_dev; + wlr_event.time_msec = + usec_to_msec(libinput_event_touch_get_time_usec(tevent)); + wlr_event.touch_id = libinput_event_touch_get_slot(tevent); + wlr_event.x = libinput_event_touch_get_x_transformed(tevent, 1); + wlr_event.y = libinput_event_touch_get_y_transformed(tevent, 1); + wlr_signal_emit_safe(&wlr_dev->touch->events.motion, &wlr_event); +} + +void handle_touch_cancel(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TOUCH, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, "Got a touch event for a device with no touch?"); + return; + } + struct libinput_event_touch *tevent = + libinput_event_get_touch_event(event); + struct wlr_event_touch_cancel wlr_event = { 0 }; + wlr_event.device = wlr_dev; + wlr_event.time_msec = + usec_to_msec(libinput_event_touch_get_time_usec(tevent)); + wlr_event.touch_id = libinput_event_touch_get_slot(tevent); + wlr_signal_emit_safe(&wlr_dev->touch->events.cancel, &wlr_event); +} diff --git a/backend/meson.build b/backend/meson.build new file mode 100644 index 00000000..bf9b4f83 --- /dev/null +++ b/backend/meson.build @@ -0,0 +1,61 @@ +backend_parts = [] +backend_files = files( + 'backend.c', + 'drm/atomic.c', + 'drm/backend.c', + 'drm/drm.c', + 'drm/legacy.c', + 'drm/properties.c', + 'drm/renderer.c', + 'drm/util.c', + 'headless/backend.c', + 'headless/input_device.c', + 'headless/output.c', + 'libinput/backend.c', + 'libinput/events.c', + 'libinput/keyboard.c', + 'libinput/pointer.c', + 'libinput/switch.c', + 'libinput/tablet_pad.c', + 'libinput/tablet_tool.c', + 'libinput/touch.c', + 'multi/backend.c', + 'session/direct-ipc.c', + 'session/session.c', + 'wayland/backend.c', + 'wayland/output.c', + 'wayland/wl_seat.c', +) + +backend_deps = [ + drm, + egl, + gbm, + libinput, + pixman, + xkbcommon, + wayland_server, + wlr_protos, + wlr_render, +] + +if host_machine.system().startswith('freebsd') + backend_files += files('session/direct-freebsd.c') +else + backend_files += files('session/direct.c') +endif + +if logind.found() + backend_files += files('session/logind.c') + backend_deps += logind +endif + +subdir('x11') + +lib_wlr_backend = static_library( + 'wlr_backend', + backend_files, + include_directories: wlr_inc, + link_whole: backend_parts, + dependencies: backend_deps, +) diff --git a/backend/multi/backend.c b/backend/multi/backend.c new file mode 100644 index 00000000..50851109 --- /dev/null +++ b/backend/multi/backend.c @@ -0,0 +1,231 @@ +#define _POSIX_C_SOURCE 200112L +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <time.h> +#include <wlr/backend/interface.h> +#include <wlr/backend/session.h> +#include <wlr/util/log.h> +#include "backend/multi.h" +#include "util/signal.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)); + return (struct wlr_multi_backend *)wlr_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->display_destroy.link); + + struct subbackend_state *sub, *next; + wl_list_for_each_safe(sub, next, &backend->backends, link) { + wlr_backend_destroy(sub->backend); + } + + // Destroy this backend only after removing all sub-backends + wlr_signal_emit_safe(&wlr_backend->events.destroy, backend); + free(backend); +} + +static struct wlr_renderer *multi_backend_get_renderer( + 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) { + struct wlr_renderer *rend = wlr_backend_get_renderer(sub->backend); + if (rend != NULL) { + return rend; + } + } + return NULL; +} + +static struct wlr_session *multi_backend_get_session( + struct wlr_backend *_backend) { + struct wlr_multi_backend *backend = multi_backend_from_backend(_backend); + return backend->session; +} + +static clockid_t multi_backend_get_presentation_clock( + 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_presentation_clock) { + return wlr_backend_get_presentation_clock(sub->backend); + } + } + + return CLOCK_MONOTONIC; +} + +struct wlr_backend_impl backend_impl = { + .start = multi_backend_start, + .destroy = multi_backend_destroy, + .get_renderer = multi_backend_get_renderer, + .get_session = multi_backend_get_session, + .get_presentation_clock = multi_backend_get_presentation_clock, +}; + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_multi_backend *backend = + wl_container_of(listener, backend, display_destroy); + multi_backend_destroy((struct wlr_backend*)backend); +} + +struct wlr_backend *wlr_multi_backend_create(struct wl_display *display) { + struct wlr_multi_backend *backend = + calloc(1, sizeof(struct wlr_multi_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->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &backend->display_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); + wlr_signal_emit_safe(&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); + wlr_signal_emit_safe(&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) { + struct wlr_multi_backend *multi = multi_backend_from_backend(_multi); + + if (multi_backend_get_subbackend(multi, backend)) { + // already added + return true; + } + + struct wlr_renderer *multi_renderer = + multi_backend_get_renderer(&multi->backend); + struct wlr_renderer *backend_renderer = wlr_backend_get_renderer(backend); + if (multi_renderer != NULL && backend_renderer != NULL && multi_renderer != backend_renderer) { + wlr_log(WLR_ERROR, "Could not add backend: multiple renderers at the " + "same time aren't supported"); + return false; + } + + struct subbackend_state *sub = calloc(1, sizeof(struct subbackend_state)); + if (sub == NULL) { + wlr_log(WLR_ERROR, "Could not add backend: allocation failed"); + return false; + } + wl_list_insert(&multi->backends, &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; + + wlr_signal_emit_safe(&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) { + wlr_signal_emit_safe(&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); + } +} diff --git a/backend/session/direct-freebsd.c b/backend/session/direct-freebsd.c new file mode 100644 index 00000000..342d0d4e --- /dev/null +++ b/backend/session/direct-freebsd.c @@ -0,0 +1,268 @@ +#include <assert.h> +#include <dev/evdev/input.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/consio.h> +#include <sys/ioctl.h> +#include <sys/kbio.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <termios.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/backend/session/interface.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include "backend/session/direct-ipc.h" +#include "util/signal.h" + +const struct session_impl session_direct; + +struct direct_session { + struct wlr_session base; + int tty_fd; + int old_tty; + int old_kbmode; + int sock; + pid_t child; + + struct wl_event_source *vt_source; +}; + +static struct direct_session *direct_session_from_session( + struct wlr_session *base) { + assert(base->impl == &session_direct); + return (struct direct_session *)base; +} + +static int direct_session_open(struct wlr_session *base, const char *path) { + struct direct_session *session = direct_session_from_session(base); + + int fd = direct_ipc_open(session->sock, path); + if (fd < 0) { + wlr_log(WLR_ERROR, "Failed to open %s: %s%s", path, strerror(-fd), + fd == -EINVAL ? "; is another display server running?" : ""); + return fd; + } + + return fd; +} + +static void direct_session_close(struct wlr_session *base, int fd) { + struct direct_session *session = direct_session_from_session(base); + + int ev; + struct drm_version dv = {0}; + if (ioctl(fd, DRM_IOCTL_VERSION, &dv) == 0) { + direct_ipc_dropmaster(session->sock, fd); + } else if (ioctl(fd, EVIOCGVERSION, &ev) == 0) { + ioctl(fd, EVIOCREVOKE, 0); + } + + close(fd); +} + +static bool direct_change_vt(struct wlr_session *base, unsigned vt) { + struct direct_session *session = direct_session_from_session(base); + + // Only seat0 has VTs associated with it + if (strcmp(session->base.seat, "seat0") != 0) { + return true; + } + + return ioctl(session->tty_fd, VT_ACTIVATE, (int)vt) == 0; +} + +static void direct_session_destroy(struct wlr_session *base) { + struct direct_session *session = direct_session_from_session(base); + + if (strcmp(session->base.seat, "seat0") == 0) { + struct vt_mode mode = { + .mode = VT_AUTO, + }; + + errno = 0; + + ioctl(session->tty_fd, KDSKBMODE, session->old_kbmode); + ioctl(session->tty_fd, KDSETMODE, KD_TEXT); + ioctl(session->tty_fd, VT_SETMODE, &mode); + + ioctl(session->tty_fd, VT_ACTIVATE, session->old_tty); + + if (errno) { + wlr_log(WLR_ERROR, "Failed to restore tty"); + } + + wl_event_source_remove(session->vt_source); + close(session->tty_fd); + } + + direct_ipc_finish(session->sock, session->child); + close(session->sock); + + free(session); +} + +static int vt_handler(int signo, void *data) { + struct direct_session *session = data; + struct drm_version dv = {0}; + struct wlr_device *dev; + + if (session->base.active) { + session->base.active = false; + wlr_signal_emit_safe(&session->base.session_signal, session); + + wl_list_for_each(dev, &session->base.devices, link) { + if (ioctl(dev->fd, DRM_IOCTL_VERSION, &dv) == 0) { + direct_ipc_dropmaster(session->sock, dev->fd); + } + } + + ioctl(session->tty_fd, VT_RELDISP, 1); + } else { + ioctl(session->tty_fd, VT_RELDISP, VT_ACKACQ); + + wl_list_for_each(dev, &session->base.devices, link) { + if (ioctl(dev->fd, DRM_IOCTL_VERSION, &dv) == 0) { + direct_ipc_setmaster(session->sock, dev->fd); + } + } + + session->base.active = true; + wlr_signal_emit_safe(&session->base.session_signal, session); + } + + return 1; +} + +static bool setup_tty(struct direct_session *session, struct wl_display *display) { + int fd = -1, tty = -1, tty0_fd = -1, old_tty = 1; + if ((tty0_fd = open("/dev/ttyv0", O_RDWR | O_CLOEXEC)) < 0) { + wlr_log_errno(WLR_ERROR, "Could not open /dev/ttyv0 to find a free vt"); + goto error; + } + if (ioctl(tty0_fd, VT_GETACTIVE, &old_tty) != 0) { + wlr_log_errno(WLR_ERROR, "Could not get active vt"); + goto error; + } + if (ioctl(tty0_fd, VT_OPENQRY, &tty) != 0) { + wlr_log_errno(WLR_ERROR, "Could not find a free vt"); + goto error; + } + close(tty0_fd); + char tty_path[64]; + snprintf(tty_path, sizeof(tty_path), "/dev/ttyv%d", tty - 1); + wlr_log(WLR_INFO, "Using tty %s", tty_path); + fd = open(tty_path, O_RDWR | O_NOCTTY | O_CLOEXEC); + + if (fd == -1) { + wlr_log_errno(WLR_ERROR, "Cannot open tty"); + return false; + } + + ioctl(fd, VT_ACTIVATE, tty); + ioctl(fd, VT_WAITACTIVE, tty); + + int old_kbmode; + if (ioctl(fd, KDGKBMODE, &old_kbmode)) { + wlr_log_errno(WLR_ERROR, "Failed to read tty %d keyboard mode", tty); + goto error; + } + + if (ioctl(fd, KDSKBMODE, K_CODE)) { + wlr_log_errno(WLR_ERROR, + "Failed to set keyboard mode K_CODE on tty %d", tty); + goto error; + } + + if (ioctl(fd, KDSETMODE, KD_GRAPHICS)) { + wlr_log_errno(WLR_ERROR, "Failed to set graphics mode on tty %d", tty); + goto error; + } + + struct vt_mode mode = { + .mode = VT_PROCESS, + .relsig = SIGUSR2, + .acqsig = SIGUSR2, + .frsig = SIGIO, // has to be set + }; + + if (ioctl(fd, VT_SETMODE, &mode) < 0) { + wlr_log(WLR_ERROR, "Failed to take control of tty %d", tty); + goto error; + } + + struct wl_event_loop *loop = wl_display_get_event_loop(display); + session->vt_source = wl_event_loop_add_signal(loop, SIGUSR2, + vt_handler, session); + if (!session->vt_source) { + goto error; + } + + session->base.vtnr = tty; + session->tty_fd = fd; + session->old_tty = old_tty; + session->old_kbmode = old_kbmode; + + return true; + +error: + // In case we could not get the last active one, drop back to tty 1, + // better than hanging in a useless blank console. Otherwise activate the + // last active. + ioctl(fd, VT_ACTIVATE, old_tty); + close(fd); + return false; +} + +static struct wlr_session *direct_session_create(struct wl_display *disp) { + struct direct_session *session = calloc(1, sizeof(*session)); + if (!session) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + + session->sock = direct_ipc_init(&session->child); + if (session->sock == -1) { + goto error_session; + } + + const char *seat = getenv("XDG_SEAT"); + if (!seat) { + seat = "seat0"; + } + + if (strcmp(seat, "seat0") == 0) { + if (!setup_tty(session, disp)) { + goto error_ipc; + } + } else { + session->base.vtnr = 0; + session->tty_fd = -1; + } + + wlr_log(WLR_INFO, "Successfully loaded direct session"); + + snprintf(session->base.seat, sizeof(session->base.seat), "%s", seat); + session->base.impl = &session_direct; + return &session->base; + +error_ipc: + direct_ipc_finish(session->sock, session->child); + close(session->sock); +error_session: + free(session); + return NULL; +} + +const struct session_impl session_direct = { + .create = direct_session_create, + .destroy = direct_session_destroy, + .open = direct_session_open, + .close = direct_session_close, + .change_vt = direct_change_vt, +}; diff --git a/backend/session/direct-ipc.c b/backend/session/direct-ipc.c new file mode 100644 index 00000000..2b9634da --- /dev/null +++ b/backend/session/direct-ipc.c @@ -0,0 +1,269 @@ +#define _POSIX_C_SOURCE 200809L +#ifdef __FreeBSD__ +#define __BSD_VISIBLE 1 +#include <dev/evdev/input.h> +#endif +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <wlr/config.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#ifdef __linux__ +#include <sys/sysmacros.h> +#include <linux/major.h> +#endif +#include "backend/session/direct-ipc.h" + +enum { DRM_MAJOR = 226 }; + +#if WLR_HAS_LIBCAP +#include <sys/capability.h> + +static bool have_permissions(void) { + cap_t cap = cap_get_proc(); + cap_flag_value_t val; + + if (!cap || cap_get_flag(cap, CAP_SYS_ADMIN, CAP_PERMITTED, &val) || val != CAP_SET) { + wlr_log(WLR_ERROR, "Do not have CAP_SYS_ADMIN; cannot become DRM master"); + cap_free(cap); + return false; + } + + cap_free(cap); + return true; +} +#else +static bool have_permissions(void) { +#ifdef __linux__ + if (geteuid() != 0) { + wlr_log(WLR_ERROR, "Do not have root privileges; cannot become DRM master"); + return false; + } +#endif + return true; +} +#endif + +static void send_msg(int sock, int fd, void *buf, size_t buf_len) { + char control[CMSG_SPACE(sizeof(fd))] = {0}; + struct iovec iovec = { .iov_base = buf, .iov_len = buf_len }; + struct msghdr msghdr = {0}; + + if (buf) { + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + } + + if (fd >= 0) { + msghdr.msg_control = &control; + msghdr.msg_controllen = sizeof(control); + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msghdr); + *cmsg = (struct cmsghdr) { + .cmsg_level = SOL_SOCKET, + .cmsg_type = SCM_RIGHTS, + .cmsg_len = CMSG_LEN(sizeof(fd)), + }; + memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd)); + } + + ssize_t ret; + do { + ret = sendmsg(sock, &msghdr, 0); + } while (ret < 0 && errno == EINTR); +} + +static ssize_t recv_msg(int sock, int *fd_out, void *buf, size_t buf_len) { + char control[CMSG_SPACE(sizeof(*fd_out))] = {0}; + struct iovec iovec = { .iov_base = buf, .iov_len = buf_len }; + struct msghdr msghdr = {0}; + + if (buf) { + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + } + + if (fd_out) { + msghdr.msg_control = &control; + msghdr.msg_controllen = sizeof(control); + } + + ssize_t ret; + do { + ret = recvmsg(sock, &msghdr, MSG_CMSG_CLOEXEC); + } while (ret < 0 && errno == EINTR); + + if (fd_out) { + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msghdr); + if (cmsg) { + memcpy(fd_out, CMSG_DATA(cmsg), sizeof(*fd_out)); + } else { + *fd_out = -1; + } + } + + return ret; +} + +enum msg_type { + MSG_OPEN, + MSG_SETMASTER, + MSG_DROPMASTER, + MSG_END, +}; + +struct msg { + enum msg_type type; + char path[256]; +}; + +static void communicate(int sock) { + struct msg msg; + int drm_fd = -1; + bool running = true; + + while (running && recv_msg(sock, &drm_fd, &msg, sizeof(msg)) > 0) { + switch (msg.type) { + case MSG_OPEN: + errno = 0; + + // These are the same flags that logind opens files with + int fd = open(msg.path, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + int ret = errno; + if (fd == -1) { + goto error; + } + +#ifndef __FreeBSD__ + struct stat st; + if (fstat(fd, &st) < 0) { + ret = errno; + goto error; + } + + uint32_t maj = major(st.st_rdev); + if (maj != INPUT_MAJOR && maj != DRM_MAJOR) { + ret = ENOTSUP; + goto error; + } + + if (maj == DRM_MAJOR && drmSetMaster(fd)) { + ret = errno; + } +#else + int ev; + struct drm_version dv = {0}; + if (ioctl(fd, EVIOCGVERSION, &ev) == -1 && + ioctl(fd, DRM_IOCTL_VERSION, &dv) == -1) { + ret = ENOTSUP; + goto error; + } + + if (dv.version_major != 0 && drmSetMaster(fd)) { + ret = errno; + } +#endif + +error: + send_msg(sock, ret ? -1 : fd, &ret, sizeof(ret)); + if (fd >= 0) { + close(fd); + } + + break; + + case MSG_SETMASTER: + drmSetMaster(drm_fd); + close(drm_fd); + send_msg(sock, -1, NULL, 0); + break; + + case MSG_DROPMASTER: + drmDropMaster(drm_fd); + close(drm_fd); + send_msg(sock, -1, NULL, 0); + break; + + case MSG_END: + running = false; + send_msg(sock, -1, NULL, 0); + break; + } + } + + close(sock); +} + +int direct_ipc_open(int sock, const char *path) { + struct msg msg = { .type = MSG_OPEN }; + snprintf(msg.path, sizeof(msg.path), "%s", path); + + send_msg(sock, -1, &msg, sizeof(msg)); + + int fd, err, ret; + int retry = 0; + do { + ret = recv_msg(sock, &fd, &err, sizeof(err)); + } while (ret == 0 && retry++ < 3); + + return err ? -err : fd; +} + +void direct_ipc_setmaster(int sock, int fd) { + struct msg msg = { .type = MSG_SETMASTER }; + + send_msg(sock, fd, &msg, sizeof(msg)); + recv_msg(sock, NULL, NULL, 0); +} + +void direct_ipc_dropmaster(int sock, int fd) { + struct msg msg = { .type = MSG_DROPMASTER }; + + send_msg(sock, fd, &msg, sizeof(msg)); + recv_msg(sock, NULL, NULL, 0); +} + +void direct_ipc_finish(int sock, pid_t pid) { + struct msg msg = { .type = MSG_END }; + + send_msg(sock, -1, &msg, sizeof(msg)); + recv_msg(sock, NULL, NULL, 0); + + waitpid(pid, NULL, 0); +} + +int direct_ipc_init(pid_t *pid_out) { + if (!have_permissions()) { + return -1; + } + + int sock[2]; + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sock) < 0) { + wlr_log_errno(WLR_ERROR, "Failed to create socket pair"); + return -1; + } + + pid_t pid = fork(); + if (pid < 0) { + wlr_log_errno(WLR_ERROR, "Fork failed"); + close(sock[0]); + close(sock[1]); + return -1; + } else if (pid == 0) { + close(sock[0]); + communicate(sock[1]); + _Exit(0); + } + + close(sock[1]); + *pid_out = pid; + return sock[0]; +} diff --git a/backend/session/direct.c b/backend/session/direct.c new file mode 100644 index 00000000..0912cd58 --- /dev/null +++ b/backend/session/direct.c @@ -0,0 +1,278 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/input.h> +#include <linux/kd.h> +#include <linux/major.h> +#include <linux/vt.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/backend/session/interface.h> +#include <wlr/util/log.h> +#include "backend/session/direct-ipc.h" +#include "util/signal.h" + +enum { DRM_MAJOR = 226 }; + +const struct session_impl session_direct; + +struct direct_session { + struct wlr_session base; + int tty_fd; + int old_kbmode; + int sock; + pid_t child; + + struct wl_event_source *vt_source; +}; + +static struct direct_session *direct_session_from_session( + struct wlr_session *base) { + assert(base->impl == &session_direct); + return (struct direct_session *)base; +} + +static int direct_session_open(struct wlr_session *base, const char *path) { + struct direct_session *session = direct_session_from_session(base); + + int fd = direct_ipc_open(session->sock, path); + if (fd < 0) { + wlr_log(WLR_ERROR, "Failed to open %s: %s%s", path, strerror(-fd), + fd == -EINVAL ? "; is another display server running?" : ""); + return fd; + } + + struct stat st; + if (fstat(fd, &st) < 0) { + close(fd); + return -errno; + } + + if (major(st.st_rdev) == DRM_MAJOR) { + direct_ipc_setmaster(session->sock, fd); + } + + return fd; +} + +static void direct_session_close(struct wlr_session *base, int fd) { + struct direct_session *session = direct_session_from_session(base); + + struct stat st; + if (fstat(fd, &st) < 0) { + wlr_log_errno(WLR_ERROR, "Stat failed"); + close(fd); + return; + } + + if (major(st.st_rdev) == DRM_MAJOR) { + direct_ipc_dropmaster(session->sock, fd); + } else if (major(st.st_rdev) == INPUT_MAJOR) { + ioctl(fd, EVIOCREVOKE, 0); + } + + close(fd); +} + +static bool direct_change_vt(struct wlr_session *base, unsigned vt) { + struct direct_session *session = direct_session_from_session(base); + + // Only seat0 has VTs associated with it + if (strcmp(session->base.seat, "seat0") != 0) { + return true; + } + + return ioctl(session->tty_fd, VT_ACTIVATE, (int)vt) == 0; +} + +static void direct_session_destroy(struct wlr_session *base) { + struct direct_session *session = direct_session_from_session(base); + + if (strcmp(session->base.seat, "seat0") == 0) { + struct vt_mode mode = { + .mode = VT_AUTO, + }; + errno = 0; + + ioctl(session->tty_fd, KDSKBMODE, session->old_kbmode); + ioctl(session->tty_fd, KDSETMODE, KD_TEXT); + ioctl(session->tty_fd, VT_SETMODE, &mode); + + if (errno) { + wlr_log(WLR_ERROR, "Failed to restore tty"); + } + + wl_event_source_remove(session->vt_source); + close(session->tty_fd); + } + + direct_ipc_finish(session->sock, session->child); + close(session->sock); + + free(session); +} + +static int vt_handler(int signo, void *data) { + struct direct_session *session = data; + + if (session->base.active) { + session->base.active = false; + wlr_signal_emit_safe(&session->base.session_signal, session); + + struct wlr_device *dev; + wl_list_for_each(dev, &session->base.devices, link) { + if (major(dev->dev) == DRM_MAJOR) { + direct_ipc_dropmaster(session->sock, + dev->fd); + } + } + + ioctl(session->tty_fd, VT_RELDISP, 1); + } else { + ioctl(session->tty_fd, VT_RELDISP, VT_ACKACQ); + + struct wlr_device *dev; + wl_list_for_each(dev, &session->base.devices, link) { + if (major(dev->dev) == DRM_MAJOR) { + direct_ipc_setmaster(session->sock, + dev->fd); + } + } + + session->base.active = true; + wlr_signal_emit_safe(&session->base.session_signal, session); + } + + return 1; +} + +static bool setup_tty(struct direct_session *session, struct wl_display *display) { + int fd = open("/dev/tty", O_RDWR); + if (fd == -1) { + wlr_log_errno(WLR_ERROR, "Cannot open /dev/tty"); + return false; + } + + struct vt_stat vt_stat; + if (ioctl(fd, VT_GETSTATE, &vt_stat)) { + wlr_log_errno(WLR_ERROR, "Could not get current tty number"); + goto error; + } + + int tty = vt_stat.v_active; + int ret, kd_mode, old_kbmode; + + ret = ioctl(fd, KDGETMODE, &kd_mode); + if (ret) { + wlr_log_errno(WLR_ERROR, "Failed to get tty mode"); + goto error; + } + + if (kd_mode != KD_TEXT) { + wlr_log(WLR_ERROR, + "tty already in graphics mode; is another display server running?"); + goto error; + } + + ioctl(fd, VT_ACTIVATE, tty); + ioctl(fd, VT_WAITACTIVE, tty); + + if (ioctl(fd, KDGKBMODE, &old_kbmode)) { + wlr_log_errno(WLR_ERROR, "Failed to read keyboard mode"); + goto error; + } + + if (ioctl(fd, KDSKBMODE, K_OFF)) { + wlr_log_errno(WLR_ERROR, "Failed to set keyboard mode"); + goto error; + } + + if (ioctl(fd, KDSETMODE, KD_GRAPHICS)) { + wlr_log_errno(WLR_ERROR, "Failed to set graphics mode on tty"); + goto error; + } + + struct vt_mode mode = { + .mode = VT_PROCESS, + .relsig = SIGUSR2, + .acqsig = SIGUSR2, + }; + + if (ioctl(fd, VT_SETMODE, &mode) < 0) { + wlr_log(WLR_ERROR, "Failed to take control of tty"); + goto error; + } + + struct wl_event_loop *loop = wl_display_get_event_loop(display); + session->vt_source = wl_event_loop_add_signal(loop, SIGUSR2, + vt_handler, session); + if (!session->vt_source) { + goto error; + } + + session->base.vtnr = tty; + session->tty_fd = fd; + session->old_kbmode = old_kbmode; + + return true; + +error: + close(fd); + return false; +} + +static struct wlr_session *direct_session_create(struct wl_display *disp) { + struct direct_session *session = calloc(1, sizeof(*session)); + if (!session) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + + session->sock = direct_ipc_init(&session->child); + if (session->sock == -1) { + goto error_session; + } + + const char *seat = getenv("XDG_SEAT"); + if (!seat) { + seat = "seat0"; + } + + if (strcmp(seat, "seat0") == 0) { + if (!setup_tty(session, disp)) { + goto error_ipc; + } + } else { + session->base.vtnr = 0; + session->tty_fd = -1; + } + + snprintf(session->base.seat, sizeof(session->base.seat), "%s", seat); + session->base.impl = &session_direct; + + wlr_log(WLR_INFO, "Successfully loaded direct session"); + return &session->base; + +error_ipc: + direct_ipc_finish(session->sock, session->child); + close(session->sock); +error_session: + free(session); + return NULL; +} + +const struct session_impl session_direct = { + .create = direct_session_create, + .destroy = direct_session_destroy, + .open = direct_session_open, + .close = direct_session_close, + .change_vt = direct_change_vt, +}; diff --git a/backend/session/logind.c b/backend/session/logind.c new file mode 100644 index 00000000..9a1383ce --- /dev/null +++ b/backend/session/logind.c @@ -0,0 +1,562 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/backend/session/interface.h> +#include <wlr/config.h> +#include <wlr/util/log.h> +#include "util/signal.h" + +#if WLR_HAS_SYSTEMD + #include <systemd/sd-bus.h> + #include <systemd/sd-login.h> +#elif WLR_HAS_ELOGIND + #include <elogind/sd-bus.h> + #include <elogind/sd-login.h> +#endif + +enum { DRM_MAJOR = 226 }; + +const struct session_impl session_logind; + +struct logind_session { + struct wlr_session base; + + sd_bus *bus; + struct wl_event_source *event; + + char *id; + char *path; + + // specifies whether a drm device was taken + // if so, the session will be (de)activated with the drm fd, + // otherwise with the dbus PropertiesChanged on "active" signal + bool has_drm; +}; + +static struct logind_session *logind_session_from_session( + struct wlr_session *base) { + assert(base->impl == &session_logind); + return (struct logind_session *)base; +} + +static int logind_take_device(struct wlr_session *base, const char *path) { + struct logind_session *session = logind_session_from_session(base); + + int fd = -1; + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + struct stat st; + if (stat(path, &st) < 0) { + wlr_log(WLR_ERROR, "Failed to stat '%s'", path); + return -1; + } + + if (major(st.st_rdev) == DRM_MAJOR) { + session->has_drm = true; + } + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + session->path, "org.freedesktop.login1.Session", "TakeDevice", + &error, &msg, "uu", major(st.st_rdev), minor(st.st_rdev)); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to take device '%s': %s", path, + error.message); + goto out; + } + + int paused = 0; + ret = sd_bus_message_read(msg, "hb", &fd, &paused); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse D-Bus response for '%s': %s", + path, strerror(-ret)); + goto out; + } + + // The original fd seems to be closed when the message is freed + // so we just clone it. + fd = fcntl(fd, F_DUPFD_CLOEXEC, 0); + if (fd < 0) { + wlr_log(WLR_ERROR, "Failed to clone file descriptor for '%s': %s", + path, strerror(errno)); + goto out; + } + +out: + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return fd; +} + +static void logind_release_device(struct wlr_session *base, int fd) { + struct logind_session *session = logind_session_from_session(base); + + struct stat st; + if (fstat(fd, &st) < 0) { + wlr_log(WLR_ERROR, "Failed to stat device '%d': %s", fd, + strerror(errno)); + return; + } + + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + int ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + session->path, "org.freedesktop.login1.Session", "ReleaseDevice", + &error, &msg, "uu", major(st.st_rdev), minor(st.st_rdev)); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to release device '%d': %s", fd, + error.message); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + close(fd); +} + +static bool logind_change_vt(struct wlr_session *base, unsigned vt) { + struct logind_session *session = logind_session_from_session(base); + + // Only seat0 has VTs associated with it + if (strcmp(session->base.seat, "seat0") != 0) { + return true; + } + + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + "/org/freedesktop/login1/seat/self", "org.freedesktop.login1.Seat", "SwitchTo", + &error, &msg, "u", (uint32_t)vt); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to change to vt '%d'", vt); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return ret >= 0; +} + +static bool find_session_path(struct logind_session *session) { + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + "/org/freedesktop/login1", "org.freedesktop.login1.Manager", + "GetSession", &error, &msg, "s", session->id); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get session path: %s", error.message); + goto out; + } + + const char *path; + ret = sd_bus_message_read(msg, "o", &path); + if (ret < 0) { + wlr_log(WLR_ERROR, "Could not parse session path: %s", error.message); + goto out; + } + session->path = strdup(path); + +out: + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + + return ret >= 0; +} + +static bool session_activate(struct logind_session *session) { + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + session->path, "org.freedesktop.login1.Session", "Activate", + &error, &msg, ""); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to activate session: %s", error.message); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return ret >= 0; +} + +static bool take_control(struct logind_session *session) { + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + session->path, "org.freedesktop.login1.Session", "TakeControl", + &error, &msg, "b", false); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to take control of session: %s", + error.message); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return ret >= 0; +} + +static void release_control(struct logind_session *session) { + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + session->path, "org.freedesktop.login1.Session", "ReleaseControl", + &error, &msg, ""); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to release control of session: %s", + error.message); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); +} + +static void logind_session_destroy(struct wlr_session *base) { + struct logind_session *session = logind_session_from_session(base); + + release_control(session); + + wl_event_source_remove(session->event); + sd_bus_unref(session->bus); + free(session->id); + free(session->path); + free(session); +} + +static int session_removed(sd_bus_message *msg, void *userdata, + sd_bus_error *ret_error) { + wlr_log(WLR_INFO, "SessionRemoved signal received"); + return 0; +} + +static struct wlr_device *find_device(struct wlr_session *session, + dev_t devnum) { + struct wlr_device *dev; + + wl_list_for_each(dev, &session->devices, link) { + if (dev->dev == devnum) { + return dev; + } + } + + wlr_log(WLR_ERROR, "Tried to use dev_t %lu not opened by session", + (unsigned long)devnum); + assert(0); +} + +static int pause_device(sd_bus_message *msg, void *userdata, + sd_bus_error *ret_error) { + struct logind_session *session = userdata; + int ret; + + uint32_t major, minor; + const char *type; + ret = sd_bus_message_read(msg, "uus", &major, &minor, &type); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse D-Bus response for PauseDevice: %s", + strerror(-ret)); + goto error; + } + + if (major == DRM_MAJOR) { + assert(session->has_drm); + session->base.active = false; + wlr_signal_emit_safe(&session->base.session_signal, session); + } + + if (strcmp(type, "pause") == 0) { + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + session->path, "org.freedesktop.login1.Session", "PauseDeviceComplete", + ret_error, &msg, "uu", major, minor); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to send PauseDeviceComplete signal: %s", + strerror(-ret)); + } + } + +error: + return 0; +} + +static int resume_device(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { + struct logind_session *session = userdata; + int ret; + + int fd; + uint32_t major, minor; + ret = sd_bus_message_read(msg, "uuh", &major, &minor, &fd); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse D-Bus response for ResumeDevice: %s", + strerror(-ret)); + goto error; + } + + if (major == DRM_MAJOR) { + struct wlr_device *dev = find_device(&session->base, makedev(major, minor)); + dup2(fd, dev->fd); + + if (!session->base.active) { + session->base.active = true; + wlr_signal_emit_safe(&session->base.session_signal, session); + } + } + +error: + return 0; +} + +static int properties_changed(sd_bus_message *msg, void *userdata, + sd_bus_error *ret_error) { + struct logind_session *session = userdata; + int ret = 0; + + // if we have a drm fd we don't depend on this + if (session->has_drm) { + return 0; + } + + // PropertiesChanged arg 1: interface + const char *interface; + ret = sd_bus_message_read_basic(msg, 's', &interface); // skip path + if (ret < 0) { + goto error; + } + + if (strcmp(interface, "org.freedesktop.login1.Session") != 0) { + // not interesting for us; ignore + wlr_log(WLR_DEBUG, "ignoring PropertiesChanged from %s", interface); + return 0; + } + + // PropertiesChanged arg 2: changed properties with values + ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); + if (ret < 0) { + goto error; + } + + const char *s; + while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { + ret = sd_bus_message_read_basic(msg, 's', &s); + if (ret < 0) { + goto error; + } + + if (strcmp(s, "Active") == 0) { + int ret; + ret = sd_bus_message_enter_container(msg, 'v', "b"); + if (ret < 0) { + goto error; + } + + bool active; + ret = sd_bus_message_read_basic(msg, 'b', &active); + if (ret < 0) { + goto error; + } + + if (session->base.active != active) { + session->base.active = active; + wlr_signal_emit_safe(&session->base.session_signal, session); + } + return 0; + } else { + sd_bus_message_skip(msg, "{sv}"); + } + + ret = sd_bus_message_exit_container(msg); + if (ret < 0) { + goto error; + } + } + + if (ret < 0) { + goto error; + } + + ret = sd_bus_message_exit_container(msg); + if (ret < 0) { + goto error; + } + + // PropertiesChanged arg 3: changed properties without values + sd_bus_message_enter_container(msg, 'a', "s"); + while ((ret = sd_bus_message_read_basic(msg, 's', &s)) > 0) { + if (strcmp(s, "Active") == 0) { + sd_bus_error error = SD_BUS_ERROR_NULL; + bool active; + ret = sd_bus_get_property_trivial(session->bus, + "org.freedesktop.login1", session->path, + "org.freedesktop.login1.Session", "Active", &error, + 'b', &active); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get 'Active' property: %s", + error.message); + return 0; + } + + if (session->base.active != active) { + session->base.active = active; + wlr_signal_emit_safe(&session->base.session_signal, session); + } + return 0; + } + } + + if (ret < 0) { + goto error; + } + + return 0; + +error: + wlr_log(WLR_ERROR, "Failed to parse D-Bus PropertiesChanged: %s", + strerror(-ret)); + return 0; +} + +static bool add_signal_matches(struct logind_session *session) { + int ret; + + char str[256]; + const char *fmt = "type='signal'," + "sender='org.freedesktop.login1'," + "interface='org.freedesktop.login1.%s'," + "member='%s'," + "path='%s'"; + + snprintf(str, sizeof(str), fmt, "Manager", "SessionRemoved", + "/org/freedesktop/login1"); + ret = sd_bus_add_match(session->bus, NULL, str, session_removed, session); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); + return false; + } + + snprintf(str, sizeof(str), fmt, "Session", "PauseDevice", session->path); + ret = sd_bus_add_match(session->bus, NULL, str, pause_device, session); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); + return false; + } + + snprintf(str, sizeof(str), fmt, "Session", "ResumeDevice", session->path); + ret = sd_bus_add_match(session->bus, NULL, str, resume_device, session); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); + return false; + } + + ret = sd_bus_match_signal(session->bus, NULL, "org.freedesktop.login1", + session->path, "org.freedesktop.DBus.Properties", "PropertiesChanged", + properties_changed, session); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); + return false; + } + + return true; +} + +static int dbus_event(int fd, uint32_t mask, void *data) { + sd_bus *bus = data; + while (sd_bus_process(bus, NULL) > 0) { + // Do nothing. + } + return 1; +} + +static struct wlr_session *logind_session_create(struct wl_display *disp) { + int ret; + struct logind_session *session = calloc(1, sizeof(*session)); + if (!session) { + wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno)); + return NULL; + } + + ret = sd_pid_get_session(getpid(), &session->id); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get session id: %s", strerror(-ret)); + goto error; + } + + char *seat; + ret = sd_session_get_seat(session->id, &seat); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get seat id: %s", strerror(-ret)); + goto error; + } + snprintf(session->base.seat, sizeof(session->base.seat), "%s", seat); + + if (strcmp(seat, "seat0") == 0) { + ret = sd_session_get_vt(session->id, &session->base.vtnr); + if (ret < 0) { + wlr_log(WLR_ERROR, "Session not running in virtual terminal"); + goto error; + } + } + free(seat); + + ret = sd_bus_default_system(&session->bus); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to open D-Bus connection: %s", strerror(-ret)); + goto error; + } + + if (!find_session_path(session)) { + sd_bus_unref(session->bus); + goto error; + } + + if (!add_signal_matches(session)) { + goto error_bus; + } + + struct wl_event_loop *event_loop = wl_display_get_event_loop(disp); + session->event = wl_event_loop_add_fd(event_loop, sd_bus_get_fd(session->bus), + WL_EVENT_READABLE, dbus_event, session->bus); + + if (!session_activate(session)) { + goto error_bus; + } + + if (!take_control(session)) { + goto error_bus; + } + + wlr_log(WLR_INFO, "Successfully loaded logind session"); + + session->base.impl = &session_logind; + return &session->base; + +error_bus: + sd_bus_unref(session->bus); + free(session->path); + +error: + free(session->id); + return NULL; +} + +const struct session_impl session_logind = { + .create = logind_session_create, + .destroy = logind_session_destroy, + .open = logind_take_device, + .close = logind_release_device, + .change_vt = logind_change_vt, +}; diff --git a/backend/session/session.c b/backend/session/session.c new file mode 100644 index 00000000..96cde65c --- /dev/null +++ b/backend/session/session.c @@ -0,0 +1,359 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <libudev.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <wayland-server.h> +#include <wlr/backend/session.h> +#include <wlr/backend/session/interface.h> +#include <wlr/config.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include "util/signal.h" + +extern const struct session_impl session_logind; +extern const struct session_impl session_direct; + +static const struct session_impl *impls[] = { +#if WLR_HAS_SYSTEMD || WLR_HAS_ELOGIND + &session_logind, +#endif + &session_direct, + NULL, +}; + +static int 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 *action = udev_device_get_action(udev_dev); + + wlr_log(WLR_DEBUG, "udev event for %s (%s)", + udev_device_get_sysname(udev_dev), action); + + if (!action || strcmp(action, "change") != 0) { + goto out; + } + + 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) { + wlr_signal_emit_safe(&dev->signal, session); + break; + } + } + +out: + udev_device_unref(udev_dev); + return 1; +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_session *session = + wl_container_of(listener, session, display_destroy); + wlr_session_destroy(session); +} + +struct wlr_session *wlr_session_create(struct wl_display *disp) { + struct wlr_session *session = NULL; + + const char *env_wlr_session = getenv("WLR_SESSION"); + if (env_wlr_session) { + if (!strcmp(env_wlr_session, "logind") || !strcmp(env_wlr_session, "systemd")) { + #if WLR_HAS_SYSTEMD || WLR_HAS_ELOGIND + session = session_logind.create(disp); + #else + wlr_log(WLR_ERROR, "wlroots is not compiled with logind support"); + #endif + } else if (!strcmp(env_wlr_session, "direct")) { + session = session_direct.create(disp); + } else { + wlr_log(WLR_ERROR, "WLR_SESSION has an invalid value: %s", env_wlr_session); + } + } else { + const struct session_impl **iter; + for (iter = impls; !session && *iter; ++iter) { + session = (*iter)->create(disp); + } + } + + if (!session) { + wlr_log(WLR_ERROR, "Failed to load session backend"); + return NULL; + } + + session->active = true; + wl_signal_init(&session->session_signal); + wl_signal_init(&session->events.destroy); + wl_list_init(&session->devices); + + 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); + + struct wl_event_loop *event_loop = wl_display_get_event_loop(disp); + int fd = udev_monitor_get_fd(session->mon); + + session->udev_event = wl_event_loop_add_fd(event_loop, fd, + WL_EVENT_READABLE, udev_event, session); + if (!session->udev_event) { + wlr_log_errno(WLR_ERROR, "Failed to create udev event source"); + goto error_mon; + } + + session->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(disp, &session->display_destroy); + + return session; + +error_mon: + udev_monitor_unref(session->mon); +error_udev: + udev_unref(session->udev); +error_session: + session->impl->destroy(session); + return NULL; +} + +void wlr_session_destroy(struct wlr_session *session) { + if (!session) { + return; + } + + wlr_signal_emit_safe(&session->events.destroy, session); + wl_list_remove(&session->display_destroy.link); + + wl_event_source_remove(session->udev_event); + udev_monitor_unref(session->mon); + udev_unref(session->udev); + + session->impl->destroy(session); +} + +int wlr_session_open_file(struct wlr_session *session, const char *path) { + int fd = session->impl->open(session, path); + if (fd < 0) { + return fd; + } + + 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; + wl_signal_init(&dev->signal); + wl_list_insert(&session->devices, &dev->link); + + return fd; + +error: + free(dev); + return fd; +} + +static struct wlr_device *find_device(struct wlr_session *session, int fd) { + struct wlr_device *dev; + + wl_list_for_each(dev, &session->devices, link) { + if (dev->fd == fd) { + return dev; + } + } + + wlr_log(WLR_ERROR, "Tried to use fd %d not opened by session", fd); + assert(0); +} + +void wlr_session_close_file(struct wlr_session *session, int fd) { + struct wlr_device *dev = find_device(session, fd); + + session->impl->close(session, fd); + wl_list_remove(&dev->link); + free(dev); +} + +void wlr_session_signal_add(struct wlr_session *session, int fd, + struct wl_listener *listener) { + struct wlr_device *dev = find_device(session, fd); + + wl_signal_add(&dev->signal, listener); +} + +bool wlr_session_change_vt(struct wlr_session *session, unsigned vt) { + if (!session) { + return false; + } + + return session->impl->change_vt(session, vt); +} + +/* Tests if 'path' is KMS compatible by trying to open it. + * It leaves the open device in *fd_out it it succeeds. + */ +static int open_if_kms(struct wlr_session *restrict session, const char *restrict path) { + int fd; + + if (!path) { + return -1; + } + + fd = wlr_session_open_file(session, path); + if (fd < 0) { + return -1; + } + + drmModeRes *res = drmModeGetResources(fd); + if (!res) { + goto out_fd; + } + + drmModeFreeResources(res); + return fd; + +out_fd: + wlr_session_close_file(session, fd); + return -1; +} + +static size_t explicit_find_gpus(struct wlr_session *session, + size_t ret_len, int ret[static ret_len], const char *str) { + char *gpus = strdup(str); + if (!gpus) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return 0; + } + + size_t i = 0; + char *save; + char *ptr = strtok_r(gpus, ":", &save); + do { + if (i >= ret_len) { + break; + } + + ret[i] = open_if_kms(session, ptr); + if (ret[i] < 0) { + wlr_log(WLR_ERROR, "Unable to open %s as DRM device", ptr); + } else { + ++i; + } + } while ((ptr = strtok_r(NULL, ":", &save))); + + free(gpus); + return i; +} + +/* Tries to find the primary GPU by checking for the "boot_vga" attribute. + * If it's not found, it returns the first valid GPU it finds. + */ +size_t wlr_session_find_gpus(struct wlr_session *session, + size_t ret_len, int *ret) { + const char *explicit = getenv("WLR_DRM_DEVICES"); + if (explicit) { + return explicit_find_gpus(session, ret_len, ret, explicit); + } + +#ifdef __FreeBSD__ + // XXX: libudev-devd does not return any GPUs (yet?) + return explicit_find_gpus(session, ret_len, ret, "/dev/drm/0"); +#else + + struct udev_enumerate *en = udev_enumerate_new(session->udev); + if (!en) { + wlr_log(WLR_ERROR, "Failed to create udev enumeration"); + return -1; + } + + udev_enumerate_add_match_subsystem(en, "drm"); + udev_enumerate_add_match_sysname(en, "card[0-9]*"); + udev_enumerate_scan_devices(en); + + 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; + } + } + + int fd = open_if_kms(session, udev_device_get_devnode(dev)); + if (fd < 0) { + udev_device_unref(dev); + continue; + } + + udev_device_unref(dev); + + ret[i] = fd; + if (is_boot_vga) { + int tmp = ret[0]; + ret[0] = ret[i]; + ret[i] = tmp; + } + + ++i; + } + + udev_enumerate_unref(en); + + return i; +#endif +} diff --git a/backend/wayland/backend.c b/backend/wayland/backend.c new file mode 100644 index 00000000..df1bf431 --- /dev/null +++ b/backend/wayland/backend.c @@ -0,0 +1,279 @@ +#include <assert.h> +#include <limits.h> +#include <stdint.h> +#include <stdlib.h> + +#include <wlr/config.h> + +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <wayland-server.h> + +#include <wlr/backend/interface.h> +#include <wlr/interfaces/wlr_input_device.h> +#include <wlr/interfaces/wlr_output.h> +#include <wlr/render/egl.h> +#include <wlr/render/gles2.h> +#include <wlr/util/log.h> + +#include "backend/wayland.h" +#include "util/signal.h" +#include "xdg-shell-unstable-v6-client-protocol.h" + +struct wlr_wl_backend *get_wl_backend_from_backend(struct wlr_backend *backend) { + assert(wlr_backend_is_wl(backend)); + return (struct wlr_wl_backend *)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)) { + wl_display_terminate(wl->local_display); + return 0; + } + + if (mask & WL_EVENT_READABLE) { + return wl_display_dispatch(wl->remote_display); + } + if (mask & WL_EVENT_WRITABLE) { + wl_display_flush(wl->remote_display); + return 0; + } + if (mask == 0) { + int count = wl_display_dispatch_pending(wl->remote_display); + wl_display_flush(wl->remote_display); + return count; + } + + return 0; +} + +static void xdg_shell_handle_ping(void *data, struct zxdg_shell_v6 *shell, + uint32_t serial) { + zxdg_shell_v6_pong(shell, serial); +} + +static const struct zxdg_shell_v6_listener xdg_shell_listener = { + xdg_shell_handle_ping, +}; + +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%d", 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) { + wl->seat = wl_registry_bind(registry, name, + &wl_seat_interface, 2); + wl_seat_add_listener(wl->seat, &seat_listener, wl); + + } else if (strcmp(iface, wl_shm_interface.name) == 0) { + wl->shm = wl_registry_bind(registry, name, + &wl_shm_interface, 1); + + } else if (strcmp(iface, zxdg_shell_v6_interface.name) == 0) { + wl->shell = wl_registry_bind(registry, name, + &zxdg_shell_v6_interface, 1); + zxdg_shell_v6_add_listener(wl->shell, &xdg_shell_listener, NULL); + } +} + +static void registry_global_remove(void *data, struct wl_registry *registry, + uint32_t name) { + // TODO +} + +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, "Initializating wayland backend"); + + wl->started = true; + + if (wl->keyboard) { + create_wl_keyboard(wl->keyboard, wl); + } + + 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); + } + + struct wlr_input_device *input_device, *tmp_input_device; + wl_list_for_each_safe(input_device, tmp_input_device, &wl->devices, link) { + wlr_input_device_destroy(input_device); + } + + wlr_signal_emit_safe(&wl->backend.events.destroy, &wl->backend); + + wl_list_remove(&wl->local_display_destroy.link); + + free(wl->seat_name); + + wl_event_source_remove(wl->remote_display_src); + + wlr_renderer_destroy(wl->renderer); + wlr_egl_finish(&wl->egl); + + if (wl->pointer) { + wl_pointer_destroy(wl->pointer); + } + if (wl->seat) { + wl_seat_destroy(wl->seat); + } + if (wl->shm) { + wl_shm_destroy(wl->shm); + } + zxdg_shell_v6_destroy(wl->shell); + wl_compositor_destroy(wl->compositor); + wl_registry_destroy(wl->registry); + wl_display_disconnect(wl->remote_display); + free(wl); +} + +static struct wlr_renderer *backend_get_renderer(struct wlr_backend *backend) { + struct wlr_wl_backend *wl = get_wl_backend_from_backend(backend); + return wl->renderer; +} + +static struct wlr_backend_impl backend_impl = { + .start = backend_start, + .destroy = backend_destroy, + .get_renderer = backend_get_renderer, +}; + +bool wlr_backend_is_wl(struct wlr_backend *b) { + return b->impl == &backend_impl; +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_wl_backend *wl = + wl_container_of(listener, wl, local_display_destroy); + backend_destroy(&wl->backend); +} + +struct wlr_backend *wlr_wl_backend_create(struct wl_display *display, + const char *remote, wlr_renderer_create_func_t create_renderer_func) { + 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->local_display = display; + wl_list_init(&wl->devices); + wl_list_init(&wl->outputs); + + wl->remote_display = wl_display_connect(remote); + if (!wl->remote_display) { + wlr_log_errno(WLR_ERROR, "Could not connect to remote display"); + goto error_wl; + } + + 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_dispatch(wl->remote_display); + wl_display_roundtrip(wl->remote_display); + + if (!wl->compositor) { + wlr_log(WLR_ERROR, + "Remote Wayland compositor does not support wl_compositor"); + goto error_registry; + } + if (!wl->shell) { + wlr_log(WLR_ERROR, + "Remote Wayland compositor does not support zxdg_shell_v6"); + goto error_registry; + } + + struct wl_event_loop *loop = wl_display_get_event_loop(wl->local_display); + int fd = wl_display_get_fd(wl->remote_display); + int events = WL_EVENT_READABLE | WL_EVENT_ERROR | WL_EVENT_HANGUP; + wl->remote_display_src = wl_event_loop_add_fd(loop, fd, events, + 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); + + static EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_ALPHA_SIZE, 1, + EGL_NONE, + }; + + if (!create_renderer_func) { + create_renderer_func = wlr_renderer_autocreate; + } + + wl->renderer = create_renderer_func(&wl->egl, EGL_PLATFORM_WAYLAND_EXT, + wl->remote_display, config_attribs, WL_SHM_FORMAT_ARGB8888); + + if (!wl->renderer) { + wlr_log(WLR_ERROR, "Could not create renderer"); + goto error_event; + } + + wl->local_display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &wl->local_display_destroy); + + return &wl->backend; + +error_event: + wl_event_source_remove(wl->remote_display_src); +error_registry: + if (wl->compositor) { + wl_compositor_destroy(wl->compositor); + } + if (wl->shell) { + zxdg_shell_v6_destroy(wl->shell); + } + wl_registry_destroy(wl->registry); +error_display: + wl_display_disconnect(wl->remote_display); +error_wl: + free(wl); + return NULL; +} diff --git a/backend/wayland/output.c b/backend/wayland/output.c new file mode 100644 index 00000000..89d5b5c9 --- /dev/null +++ b/backend/wayland/output.c @@ -0,0 +1,367 @@ +#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 <wayland-client.h> + +#include <wlr/interfaces/wlr_output.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/util/log.h> + +#include "backend/wayland.h" +#include "util/signal.h" +#include "xdg-shell-unstable-v6-client-protocol.h" + +static struct wlr_wl_output *get_wl_output_from_output( + struct wlr_output *wlr_output) { + assert(wlr_output_is_wl(wlr_output)); + return (struct wlr_wl_output *)wlr_output; +} + +static struct wl_callback_listener frame_listener; + +static void surface_frame_callback(void *data, struct wl_callback *cb, + uint32_t time) { + struct wlr_wl_output *output = data; + assert(output); + wl_callback_destroy(cb); + output->frame_callback = NULL; + + wlr_output_send_frame(&output->wlr_output); +} + +static struct wl_callback_listener frame_listener = { + .done = surface_frame_callback +}; + +static bool output_set_custom_mode(struct wlr_output *wlr_output, + int32_t width, int32_t height, int32_t refresh) { + struct wlr_wl_output *output = get_wl_output_from_output(wlr_output); + wl_egl_window_resize(output->egl_window, width, height, 0, 0); + wlr_output_update_custom_mode(&output->wlr_output, width, height, 0); + return true; +} + +static bool output_make_current(struct wlr_output *wlr_output, + int *buffer_age) { + struct wlr_wl_output *output = + get_wl_output_from_output(wlr_output); + return wlr_egl_make_current(&output->backend->egl, output->egl_surface, + buffer_age); +} + +static bool output_swap_buffers(struct wlr_output *wlr_output, + pixman_region32_t *damage) { + struct wlr_wl_output *output = + get_wl_output_from_output(wlr_output); + + if (output->frame_callback != NULL) { + wlr_log(WLR_ERROR, "Skipping buffer swap"); + return false; + } + + output->frame_callback = wl_surface_frame(output->surface); + wl_callback_add_listener(output->frame_callback, &frame_listener, output); + + if (!wlr_egl_swap_buffers(&output->backend->egl, + output->egl_surface, damage)) { + return false; + } + + // TODO: if available, use the presentation-time protocol + wlr_output_send_present(wlr_output, NULL); + return true; +} + +static void output_transform(struct wlr_output *wlr_output, + enum wl_output_transform transform) { + struct wlr_wl_output *output = get_wl_output_from_output(wlr_output); + output->wlr_output.transform = transform; +} + +static bool output_set_cursor(struct wlr_output *wlr_output, + struct wlr_texture *texture, int32_t scale, + enum wl_output_transform transform, + int32_t hotspot_x, int32_t hotspot_y, bool update_texture) { + struct wlr_wl_output *output = get_wl_output_from_output(wlr_output); + struct wlr_wl_backend *backend = output->backend; + + struct wlr_box hotspot = { .x = hotspot_x, .y = hotspot_y }; + wlr_box_transform(&hotspot, &hotspot, + wlr_output_transform_invert(wlr_output->transform), + output->cursor.width, output->cursor.height); + + // TODO: use output->wlr_output.transform to transform pixels and hotpot + output->cursor.hotspot_x = hotspot.x; + output->cursor.hotspot_y = hotspot.y; + + if (!update_texture) { + // Update hotspot without changing cursor image + update_wl_output_cursor(output); + return true; + } + + if (output->cursor.surface == NULL) { + output->cursor.surface = + wl_compositor_create_surface(backend->compositor); + } + struct wl_surface *surface = output->cursor.surface; + + if (texture != NULL) { + int width, height; + wlr_texture_get_size(texture, &width, &height); + width = width * wlr_output->scale / scale; + height = height * wlr_output->scale / scale; + + output->cursor.width = width; + output->cursor.height = height; + + if (output->cursor.egl_window == NULL) { + output->cursor.egl_window = + wl_egl_window_create(surface, width, height); + } + wl_egl_window_resize(output->cursor.egl_window, width, height, 0, 0); + + EGLSurface egl_surface = + wlr_egl_create_surface(&backend->egl, output->cursor.egl_window); + + wlr_egl_make_current(&backend->egl, egl_surface, NULL); + + struct wlr_box cursor_box = { + .width = width, + .height = height, + }; + + float projection[9]; + wlr_matrix_projection(projection, width, height, wlr_output->transform); + + float matrix[9]; + wlr_matrix_project_box(matrix, &cursor_box, transform, 0, projection); + + wlr_renderer_begin(backend->renderer, width, height); + wlr_renderer_clear(backend->renderer, (float[]){ 0.0, 0.0, 0.0, 0.0 }); + wlr_render_texture_with_matrix(backend->renderer, texture, matrix, 1.0); + wlr_renderer_end(backend->renderer); + + wlr_egl_swap_buffers(&backend->egl, egl_surface, NULL); + wlr_egl_destroy_surface(&backend->egl, egl_surface); + } else { + wl_surface_attach(surface, NULL, 0, 0); + wl_surface_commit(surface); + } + + update_wl_output_cursor(output); + return true; +} + +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.egl_window != NULL) { + wl_egl_window_destroy(output->cursor.egl_window); + } + if (output->cursor.surface) { + wl_surface_destroy(output->cursor.surface); + } + + if (output->frame_callback) { + wl_callback_destroy(output->frame_callback); + } + + wlr_egl_destroy_surface(&output->backend->egl, output->egl_surface); + wl_egl_window_destroy(output->egl_window); + zxdg_toplevel_v6_destroy(output->xdg_toplevel); + zxdg_surface_v6_destroy(output->xdg_surface); + wl_surface_destroy(output->surface); + free(output); +} + +void update_wl_output_cursor(struct wlr_wl_output *output) { + if (output->backend->pointer && output->enter_serial) { + wl_pointer_set_cursor(output->backend->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 bool output_schedule_frame(struct wlr_output *wlr_output) { + struct wlr_wl_output *output = get_wl_output_from_output(wlr_output); + + if (output->frame_callback != NULL) { + wlr_log(WLR_ERROR, "Skipping frame scheduling"); + return true; + } + + output->frame_callback = wl_surface_frame(output->surface); + wl_callback_add_listener(output->frame_callback, &frame_listener, output); + wl_surface_commit(output->surface); + return true; +} + +static const struct wlr_output_impl output_impl = { + .set_custom_mode = output_set_custom_mode, + .transform = output_transform, + .destroy = output_destroy, + .make_current = output_make_current, + .swap_buffers = output_swap_buffers, + .set_cursor = output_set_cursor, + .move_cursor = output_move_cursor, + .schedule_frame = output_schedule_frame, +}; + +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 zxdg_surface_v6 *xdg_surface, + uint32_t serial) { + struct wlr_wl_output *output = data; + assert(output && output->xdg_surface == xdg_surface); + + zxdg_surface_v6_ack_configure(xdg_surface, serial); + + // nothing else? +} + +static struct zxdg_surface_v6_listener xdg_surface_listener = { + .configure = xdg_surface_handle_configure, +}; + +static void xdg_toplevel_handle_configure(void *data, + struct zxdg_toplevel_v6 *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; + } + // loop over states for maximized etc? + wl_egl_window_resize(output->egl_window, width, height, 0, 0); + wlr_output_update_custom_mode(&output->wlr_output, width, height, 0); +} + +static void xdg_toplevel_handle_close(void *data, + struct zxdg_toplevel_v6 *xdg_toplevel) { + struct wlr_wl_output *output = data; + assert(output && output->xdg_toplevel == xdg_toplevel); + + wlr_output_destroy((struct wlr_output *)output); +} + +static struct zxdg_toplevel_v6_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_handle_configure, + .close = xdg_toplevel_handle_close, +}; + +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 wlr_wl_output *output; + if (!(output = calloc(sizeof(struct wlr_wl_output), 1))) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_wl_output"); + return NULL; + } + wlr_output_init(&output->wlr_output, &backend->backend, &output_impl, + backend->local_display); + struct wlr_output *wlr_output = &output->wlr_output; + + wlr_output_update_custom_mode(wlr_output, 1280, 720, 0); + strncpy(wlr_output->make, "wayland", sizeof(wlr_output->make)); + strncpy(wlr_output->model, "wayland", sizeof(wlr_output->model)); + snprintf(wlr_output->name, sizeof(wlr_output->name), "WL-%d", + wl_list_length(&backend->outputs) + 1); + + output->backend = backend; + + output->surface = wl_compositor_create_surface(backend->compositor); + if (!output->surface) { + wlr_log_errno(WLR_ERROR, "Could not create output surface"); + goto error; + } + wl_surface_set_user_data(output->surface, output); + output->xdg_surface = + zxdg_shell_v6_get_xdg_surface(backend->shell, output->surface); + if (!output->xdg_surface) { + wlr_log_errno(WLR_ERROR, "Could not get xdg surface"); + goto error; + } + output->xdg_toplevel = + zxdg_surface_v6_get_toplevel(output->xdg_surface); + if (!output->xdg_toplevel) { + wlr_log_errno(WLR_ERROR, "Could not get xdg toplevel"); + goto error; + } + + char title[32]; + if (snprintf(title, sizeof(title), "wlroots - %s", wlr_output->name)) { + zxdg_toplevel_v6_set_title(output->xdg_toplevel, title); + } + + zxdg_toplevel_v6_set_app_id(output->xdg_toplevel, "wlroots"); + zxdg_surface_v6_add_listener(output->xdg_surface, + &xdg_surface_listener, output); + zxdg_toplevel_v6_add_listener(output->xdg_toplevel, + &xdg_toplevel_listener, output); + wl_surface_commit(output->surface); + + output->egl_window = wl_egl_window_create(output->surface, + wlr_output->width, wlr_output->height); + output->egl_surface = wlr_egl_create_surface(&backend->egl, + output->egl_window); + + wl_display_roundtrip(output->backend->remote_display); + + // start rendering loop per callbacks by rendering first frame + if (!wlr_egl_make_current(&output->backend->egl, output->egl_surface, + NULL)) { + goto error; + } + + wlr_renderer_begin(backend->renderer, wlr_output->width, wlr_output->height); + wlr_renderer_clear(backend->renderer, (float[]){ 1.0, 1.0, 1.0, 1.0 }); + wlr_renderer_end(backend->renderer); + + output->frame_callback = wl_surface_frame(output->surface); + wl_callback_add_listener(output->frame_callback, &frame_listener, output); + + if (!wlr_egl_swap_buffers(&output->backend->egl, output->egl_surface, + NULL)) { + goto error; + } + + wl_list_insert(&backend->outputs, &output->link); + wlr_output_update_enabled(wlr_output, true); + + wlr_signal_emit_safe(&backend->backend.events.new_output, wlr_output); + + if (backend->pointer != NULL) { + create_wl_pointer(backend->pointer, output); + } + + return wlr_output; + +error: + wlr_output_destroy(&output->wlr_output); + return NULL; +} diff --git a/backend/wayland/wl_seat.c b/backend/wayland/wl_seat.c new file mode 100644 index 00000000..b654197a --- /dev/null +++ b/backend/wayland/wl_seat.c @@ -0,0 +1,434 @@ +#define _POSIX_C_SOURCE 200809L + +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include <wayland-client.h> + +#include <wlr/interfaces/wlr_input_device.h> +#include <wlr/interfaces/wlr_keyboard.h> +#include <wlr/interfaces/wlr_output.h> +#include <wlr/interfaces/wlr_pointer.h> +#include <wlr/interfaces/wlr_touch.h> +#include <wlr/util/log.h> + +#include "backend/wayland.h" +#include "util/signal.h" + +static struct wlr_wl_pointer *output_get_pointer(struct wlr_wl_output *output) { + struct wlr_input_device *wlr_dev; + wl_list_for_each(wlr_dev, &output->backend->devices, link) { + if (wlr_dev->type != WLR_INPUT_DEVICE_POINTER) { + continue; + } + struct wlr_wl_pointer *pointer = pointer_get_wl(wlr_dev->pointer); + 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_backend *backend = data; + if (surface == NULL) { + return; + } + + struct wlr_wl_output *output = wl_surface_get_user_data(surface); + assert(output); + struct wlr_wl_pointer *pointer = output_get_pointer(output); + + output->enter_serial = serial; + backend->current_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_backend *backend = data; + if (surface == NULL) { + return; + } + + struct wlr_wl_output *output = wl_surface_get_user_data(surface); + assert(output); + output->enter_serial = 0; + + if (backend->current_pointer == NULL || + backend->current_pointer->output != output) { + return; + } + + backend->current_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_backend *backend = data; + struct wlr_wl_pointer *pointer = backend->current_pointer; + if (pointer == NULL) { + return; + } + + struct wlr_output *wlr_output = &pointer->output->wlr_output; + struct wlr_event_pointer_motion_absolute event = { + .device = &pointer->input_device->wlr_input_device, + .time_msec = time, + .x = wl_fixed_to_double(sx) / wlr_output->width, + .y = wl_fixed_to_double(sy) / wlr_output->height, + }; + wlr_signal_emit_safe(&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_backend *backend = data; + struct wlr_wl_pointer *pointer = backend->current_pointer; + if (pointer == NULL) { + return; + } + + struct wlr_event_pointer_button event = { + .device = &pointer->input_device->wlr_input_device, + .button = button, + .state = state, + .time_msec = time, + }; + wlr_signal_emit_safe(&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_backend *backend = data; + struct wlr_wl_pointer *pointer = backend->current_pointer; + if (pointer == NULL) { + return; + } + + struct wlr_event_pointer_axis event = { + .device = &pointer->input_device->wlr_input_device, + .delta = wl_fixed_to_double(value), + .delta_discrete = pointer->axis_discrete, + .orientation = axis, + .time_msec = time, + .source = pointer->axis_source, + }; + wlr_signal_emit_safe(&pointer->wlr_pointer.events.axis, &event); + + pointer->axis_discrete = 0; +} + +static void pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) { + // This space is intentionally left blank +} + +static void pointer_handle_axis_source(void *data, + struct wl_pointer *wl_pointer, uint32_t axis_source) { + struct wlr_wl_backend *backend = data; + struct wlr_wl_pointer *pointer = backend->current_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) { + // This space is intentionally left blank +} + +static void pointer_handle_axis_discrete(void *data, + struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) { + struct wlr_wl_backend *backend = data; + struct wlr_wl_pointer *pointer = backend->current_pointer; + if (pointer == NULL) { + return; + } + + pointer->axis_discrete = discrete; +} + +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, +}; + +static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int32_t fd, uint32_t size) { + // TODO: set keymap +} + +static uint32_t get_current_time_msec() { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return now.tv_nsec / 1000; +} + +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_input_device *dev = data; + + uint32_t time = get_current_time_msec(); + + uint32_t *keycode_ptr; + wl_array_for_each(keycode_ptr, keys) { + struct wlr_event_keyboard_key event = { + .keycode = *keycode_ptr, + .state = WLR_KEY_PRESSED, + .time_msec = time, + .update_state = false, + }; + wlr_keyboard_notify_key(dev->keyboard, &event); + } +} + +static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, struct wl_surface *surface) { + struct wlr_input_device *dev = data; + + uint32_t time = get_current_time_msec(); + + uint32_t pressed[dev->keyboard->num_keycodes + 1]; + memcpy(pressed, dev->keyboard->keycodes, + dev->keyboard->num_keycodes * sizeof(uint32_t)); + + for (size_t i = 0; i < sizeof(pressed)/sizeof(pressed[0]); ++i) { + uint32_t keycode = pressed[i]; + + struct wlr_event_keyboard_key event = { + .keycode = keycode, + .state = WLR_KEY_RELEASED, + .time_msec = time, + .update_state = false, + }; + wlr_keyboard_notify_key(dev->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_input_device *dev = data; + assert(dev && dev->keyboard); + + struct wlr_event_keyboard_key wlr_event = { + .keycode = key, + .state = state, + .time_msec = time, + .update_state = false, + }; + wlr_keyboard_notify_key(dev->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_input_device *dev = data; + assert(dev && dev->keyboard); + wlr_keyboard_notify_modifiers(dev->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 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 struct wlr_wl_input_device *get_wl_input_device_from_input_device( + struct wlr_input_device *wlr_dev) { + assert(wlr_input_device_is_wl(wlr_dev)); + return (struct wlr_wl_input_device *)wlr_dev; +} + +static void input_device_destroy(struct wlr_input_device *wlr_dev) { + struct wlr_wl_input_device *dev = + get_wl_input_device_from_input_device(wlr_dev); + if (dev->resource) { + wl_proxy_destroy(dev->resource); + } + wl_list_remove(&dev->wlr_input_device.link); + free(dev); +} + +static struct wlr_input_device_impl input_device_impl = { + .destroy = input_device_destroy, +}; + +bool wlr_input_device_is_wl(struct wlr_input_device *dev) { + return dev->impl == &input_device_impl; +} + +static struct wlr_wl_input_device *create_wl_input_device( + struct wlr_wl_backend *backend, enum wlr_input_device_type type) { + struct wlr_wl_input_device *dev = + calloc(1, sizeof(struct wlr_wl_input_device)); + if (dev == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + dev->backend = backend; + + struct wlr_input_device *wlr_dev = &dev->wlr_input_device; + + unsigned int vendor = 0, product = 0; + const char *name = "wayland"; + wlr_input_device_init(wlr_dev, type, &input_device_impl, name, vendor, + product); + wl_list_insert(&backend->devices, &wlr_dev->link); + return dev; +} + +static struct wlr_pointer_impl pointer_impl; + +struct wlr_wl_pointer *pointer_get_wl(struct wlr_pointer *wlr_pointer) { + assert(wlr_pointer->impl == &pointer_impl); + return (struct wlr_wl_pointer *)wlr_pointer; +} + +static void pointer_destroy(struct wlr_pointer *wlr_pointer) { + struct wlr_wl_pointer *pointer = pointer_get_wl(wlr_pointer); + wl_list_remove(&pointer->output_destroy.link); + free(pointer); +} + +static struct wlr_pointer_impl pointer_impl = { + .destroy = pointer_destroy, +}; + +static void pointer_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_wl_pointer *pointer = + wl_container_of(listener, pointer, output_destroy); + wlr_input_device_destroy(&pointer->input_device->wlr_input_device); +} + +void create_wl_pointer(struct wl_pointer *wl_pointer, struct wlr_wl_output *output) { + struct wlr_wl_backend *backend = output->backend; + + struct wlr_input_device *wlr_dev; + wl_list_for_each(wlr_dev, &output->backend->devices, link) { + if (wlr_dev->type != WLR_INPUT_DEVICE_POINTER) { + continue; + } + struct wlr_wl_pointer *pointer = pointer_get_wl(wlr_dev->pointer); + if (pointer->output == output) { + return; + } + } + + struct wlr_wl_pointer *pointer = calloc(1, sizeof(struct wlr_wl_pointer)); + if (pointer == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + return; + } + pointer->wl_pointer = wl_pointer; + pointer->output = output; + + wl_signal_add(&output->wlr_output.events.destroy, &pointer->output_destroy); + pointer->output_destroy.notify = pointer_handle_output_destroy; + + struct wlr_wl_input_device *dev = + create_wl_input_device(backend, WLR_INPUT_DEVICE_POINTER); + if (dev == NULL) { + free(pointer); + wlr_log(WLR_ERROR, "Allocation failed"); + return; + } + pointer->input_device = dev; + + wlr_dev = &dev->wlr_input_device; + wlr_dev->pointer = &pointer->wlr_pointer; + wlr_dev->output_name = strdup(output->wlr_output.name); + wlr_pointer_init(wlr_dev->pointer, &pointer_impl); + + wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev); +} + +void create_wl_keyboard(struct wl_keyboard *wl_keyboard, struct wlr_wl_backend *wl) { + struct wlr_wl_input_device *dev = + create_wl_input_device(wl, WLR_INPUT_DEVICE_KEYBOARD); + if (!dev) { + return; + } + + struct wlr_input_device *wlr_dev = &dev->wlr_input_device; + + wlr_dev->keyboard = calloc(1, sizeof(*wlr_dev->keyboard)); + if (!wlr_dev->keyboard) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + free(dev); + return; + } + wlr_keyboard_init(wlr_dev->keyboard, NULL); + + wl_keyboard_add_listener(wl_keyboard, &keyboard_listener, wlr_dev); + dev->resource = wl_keyboard; + wlr_signal_emit_safe(&wl->backend.events.new_input, wlr_dev); +} + +static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, + enum wl_seat_capability caps) { + struct wlr_wl_backend *backend = data; + assert(backend->seat == wl_seat); + + if ((caps & WL_SEAT_CAPABILITY_POINTER)) { + wlr_log(WLR_DEBUG, "seat %p offered pointer", (void*) wl_seat); + + struct wl_pointer *wl_pointer = wl_seat_get_pointer(wl_seat); + backend->pointer = wl_pointer; + + struct wlr_wl_output *output; + wl_list_for_each(output, &backend->outputs, link) { + create_wl_pointer(wl_pointer, output); + } + + wl_pointer_add_listener(wl_pointer, &pointer_listener, backend); + } + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD)) { + wlr_log(WLR_DEBUG, "seat %p offered keyboard", (void*) wl_seat); + + struct wl_keyboard *wl_keyboard = wl_seat_get_keyboard(wl_seat); + backend->keyboard = wl_keyboard; + + if (backend->started) { + create_wl_keyboard(wl_keyboard, backend); + } + } +} + +static void seat_handle_name(void *data, struct wl_seat *wl_seat, + const char *name) { + struct wlr_wl_backend *backend = data; + assert(backend->seat == wl_seat); + // Do we need to check if seatName was previously set for name change? + free(backend->seat_name); + backend->seat_name = strdup(name); +} + +const struct wl_seat_listener seat_listener = { + .capabilities = seat_handle_capabilities, + .name = seat_handle_name, +}; diff --git a/backend/x11/backend.c b/backend/x11/backend.c new file mode 100644 index 00000000..38715631 --- /dev/null +++ b/backend/x11/backend.c @@ -0,0 +1,314 @@ +#define _POSIX_C_SOURCE 200112L + +#include <assert.h> +#include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + +#include <wlr/config.h> + +#include <X11/Xlib-xcb.h> +#include <wayland-server.h> +#include <xcb/xcb.h> +#include <xcb/xfixes.h> +#include <xcb/xinput.h> + +#include <wlr/backend/interface.h> +#include <wlr/backend/x11.h> +#include <wlr/interfaces/wlr_input_device.h> +#include <wlr/interfaces/wlr_keyboard.h> +#include <wlr/interfaces/wlr_pointer.h> +#include <wlr/render/egl.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/util/log.h> + +#include "backend/x11.h" +#include "util/signal.h" + +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_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) { + wlr_output_update_needs_swap(&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); + } + } + 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); + } + } + } +} + +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)) { + wl_display_terminate(x11->wl_display); + return 0; + } + + xcb_generic_event_t *e; + while ((e = xcb_poll_for_event(x11->xcb))) { + handle_x11_event(x11, e); + free(e); + } + + return 0; +} + +struct wlr_x11_backend *get_x11_backend_from_backend( + struct wlr_backend *wlr_backend) { + assert(wlr_backend_is_x11(wlr_backend)); + return (struct wlr_x11_backend *)wlr_backend; +} + +static bool backend_start(struct wlr_backend *backend) { + struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend); + x11->started = true; + + wlr_signal_emit_safe(&x11->backend.events.new_input, &x11->keyboard_dev); + + 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_input_device_destroy(&x11->keyboard_dev); + + wlr_signal_emit_safe(&backend->events.destroy, backend); + + if (x11->event_source) { + wl_event_source_remove(x11->event_source); + } + wl_list_remove(&x11->display_destroy.link); + + wlr_renderer_destroy(x11->renderer); + wlr_egl_finish(&x11->egl); + + if (x11->xlib_conn) { + XCloseDisplay(x11->xlib_conn); + } + free(x11); +} + +static struct wlr_renderer *backend_get_renderer( + struct wlr_backend *backend) { + struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend); + return x11->renderer; +} + +static const struct wlr_backend_impl backend_impl = { + .start = backend_start, + .destroy = backend_destroy, + .get_renderer = backend_get_renderer, +}; + +bool wlr_backend_is_x11(struct wlr_backend *backend) { + return backend->impl == &backend_impl; +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_x11_backend *x11 = + wl_container_of(listener, x11, display_destroy); + backend_destroy(&x11->backend); +} + +struct wlr_backend *wlr_x11_backend_create(struct wl_display *display, + const char *x11_display, + wlr_renderer_create_func_t create_renderer_func) { + struct wlr_x11_backend *x11 = calloc(1, sizeof(*x11)); + if (!x11) { + return NULL; + } + + wlr_backend_init(&x11->backend, &backend_impl); + x11->wl_display = display; + wl_list_init(&x11->outputs); + + x11->xlib_conn = XOpenDisplay(x11_display); + if (!x11->xlib_conn) { + wlr_log(WLR_ERROR, "Failed to open X connection"); + goto error_x11; + } + + x11->xcb = XGetXCBConnection(x11->xlib_conn); + if (!x11->xcb || xcb_connection_has_error(x11->xcb)) { + wlr_log(WLR_ERROR, "Failed to open xcb connection"); + goto error_display; + } + + XSetEventQueueOwner(x11->xlib_conn, XCBOwnsEventQueue); + + 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 }, + }; + + 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; + + 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"); + free(fixes_reply); + goto error_display; + } + free(fixes_reply); + + 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"); + free(xi_reply); + goto error_display; + } + free(xi_reply); + + int fd = xcb_get_file_descriptor(x11->xcb); + struct wl_event_loop *ev = wl_display_get_event_loop(display); + uint32_t events = WL_EVENT_READABLE | WL_EVENT_ERROR | WL_EVENT_HANGUP; + x11->event_source = wl_event_loop_add_fd(ev, 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 (!create_renderer_func) { + create_renderer_func = wlr_renderer_autocreate; + } + + static EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_ALPHA_SIZE, 0, + EGL_NONE, + }; + + x11->renderer = create_renderer_func(&x11->egl, EGL_PLATFORM_X11_KHR, + x11->xlib_conn, config_attribs, x11->screen->root_visual); + + if (x11->renderer == NULL) { + wlr_log(WLR_ERROR, "Failed to create renderer"); + goto error_event; + } + + wlr_input_device_init(&x11->keyboard_dev, WLR_INPUT_DEVICE_KEYBOARD, + &input_device_impl, "X11 keyboard", 0, 0); + wlr_keyboard_init(&x11->keyboard, &keyboard_impl); + x11->keyboard_dev.keyboard = &x11->keyboard; + + x11->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &x11->display_destroy); + + return &x11->backend; + +error_event: + wl_event_source_remove(x11->event_source); +error_display: + XCloseDisplay(x11->xlib_conn); +error_x11: + free(x11); + return NULL; +} diff --git a/backend/x11/input_device.c b/backend/x11/input_device.c new file mode 100644 index 00000000..a50f478a --- /dev/null +++ b/backend/x11/input_device.c @@ -0,0 +1,244 @@ +#include <stdlib.h> + +#include <wlr/config.h> + +#ifdef __linux__ +#include <linux/input-event-codes.h> +#elif __FreeBSD__ +#include <dev/evdev/input-event-codes.h> +#endif + +#include <xcb/xcb.h> +#include <xcb/xfixes.h> +#include <xcb/xinput.h> + +#include <wlr/interfaces/wlr_input_device.h> +#include <wlr/interfaces/wlr_keyboard.h> +#include <wlr/interfaces/wlr_pointer.h> +#include <wlr/util/log.h> + +#include "backend/x11.h" +#include "util/signal.h" + +static void send_key_event(struct wlr_x11_backend *x11, uint32_t key, + enum wlr_key_state st, xcb_timestamp_t time) { + struct wlr_event_keyboard_key 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 wlr_button_state st, xcb_timestamp_t time) { + struct wlr_event_pointer_button ev = { + .device = &output->pointer_dev, + .time_msec = time, + .button = key, + .state = st, + }; + wlr_signal_emit_safe(&output->pointer.events.button, &ev); +} + +static void send_axis_event(struct wlr_x11_output *output, int32_t delta, + xcb_timestamp_t time) { + struct wlr_event_pointer_axis ev = { + .device = &output->pointer_dev, + .time_msec = time, + .source = WLR_AXIS_SOURCE_WHEEL, + .orientation = WLR_AXIS_ORIENTATION_VERTICAL, + // 15 is a typical value libinput sends for one scroll + .delta = delta * 15, + .delta_discrete = delta, + }; + wlr_signal_emit_safe(&output->pointer.events.axis, &ev); +} + +static void send_pointer_position_event(struct wlr_x11_output *output, + int16_t x, int16_t y, xcb_timestamp_t time) { + struct wlr_event_pointer_motion_absolute ev = { + .device = &output->pointer_dev, + .time_msec = time, + .x = (double)x / output->wlr_output.width, + .y = (double)y / output->wlr_output.height, + }; + wlr_signal_emit_safe(&output->pointer.events.motion_absolute, &ev); +} + +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; + + 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, WLR_KEY_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, WLR_KEY_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, WLR_BUTTON_PRESSED, + ev->time); + break; + case XCB_BUTTON_INDEX_2: + send_button_event(output, BTN_MIDDLE, WLR_BUTTON_PRESSED, + ev->time); + break; + case XCB_BUTTON_INDEX_3: + send_button_event(output, BTN_RIGHT, WLR_BUTTON_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, WLR_BUTTON_RELEASED, + ev->time); + break; + case XCB_BUTTON_INDEX_2: + send_button_event(output, BTN_MIDDLE, WLR_BUTTON_RELEASED, + ev->time); + break; + case XCB_BUTTON_INDEX_3: + send_button_event(output, BTN_RIGHT, WLR_BUTTON_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_ENTER: { + xcb_input_enter_event_t *ev = (xcb_input_enter_event_t *)event; + + output = get_x11_output_from_window_id(x11, ev->event); + if (!output) { + return; + } + + if (!output->cursor_hidden) { + xcb_xfixes_hide_cursor(x11->xcb, output->win); + xcb_flush(x11->xcb); + output->cursor_hidden = true; + } + break; + } + case XCB_INPUT_LEAVE: { + xcb_input_leave_event_t *ev = (xcb_input_leave_event_t *)event; + + output = get_x11_output_from_window_id(x11, ev->event); + if (!output) { + return; + } + + if (output->cursor_hidden) { + xcb_xfixes_show_cursor(x11->xcb, output->win); + xcb_flush(x11->xcb); + output->cursor_hidden = false; + } + break; + } + } +} + +static void input_device_destroy(struct wlr_input_device *wlr_device) { + // Don't free the input device, it's on the stack +} + +const struct wlr_input_device_impl input_device_impl = { + .destroy = input_device_destroy, +}; + +static void keyboard_destroy(struct wlr_keyboard *wlr_keyboard) { + // Don't free the keyboard, it's on the stack +} + +const struct wlr_keyboard_impl keyboard_impl = { + .destroy = keyboard_destroy, +}; + +static void pointer_destroy(struct wlr_pointer *wlr_pointer) { + // Don't free the pointer, it's on the stack +} + +const struct wlr_pointer_impl pointer_impl = { + .destroy = pointer_destroy, +}; + +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) { + return wlr_dev->impl == &input_device_impl; +} diff --git a/backend/x11/meson.build b/backend/x11/meson.build new file mode 100644 index 00000000..19e873ab --- /dev/null +++ b/backend/x11/meson.build @@ -0,0 +1,35 @@ +x11_libs = [] +x11_required = [ + 'x11-xcb', + 'xcb', + 'xcb-xinput', + 'xcb-xfixes', +] + +foreach lib : x11_required + dep = dependency(lib, required: get_option('x11-backend')) + if not dep.found() + subdir_done() + endif + + x11_libs += dep +endforeach + +lib_wlr_backend_x11 = static_library( + 'wlr_backend_x11', + files( + 'backend.c', + 'input_device.c', + 'output.c', + ), + include_directories: wlr_inc, + dependencies: [ + wayland_server, + pixman, + xkbcommon, + x11_libs, + ], +) + +backend_parts += lib_wlr_backend_x11 +conf_data.set10('WLR_HAS_X11_BACKEND', true) diff --git a/backend/x11/output.c b/backend/x11/output.c new file mode 100644 index 00000000..6f98c590 --- /dev/null +++ b/backend/x11/output.c @@ -0,0 +1,236 @@ +#define _POSIX_C_SOURCE 200809L + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include <xcb/xcb.h> +#include <xcb/xinput.h> + +#include <wlr/interfaces/wlr_output.h> +#include <wlr/interfaces/wlr_pointer.h> +#include <wlr/util/log.h> + +#include "backend/x11.h" +#include "util/signal.h" + +static int signal_frame(void *data) { + struct wlr_x11_output *output = data; + wlr_output_send_frame(&output->wlr_output); + wl_event_source_timer_update(output->frame_timer, output->frame_delay); + return 0; +} + +static void parse_xcb_setup(struct wlr_output *output, + xcb_connection_t *xcb) { + const xcb_setup_t *xcb_setup = xcb_get_setup(xcb); + + snprintf(output->make, sizeof(output->make), "%.*s", + xcb_setup_vendor_length(xcb_setup), + xcb_setup_vendor(xcb_setup)); + snprintf(output->model, sizeof(output->model), "%"PRIu16".%"PRIu16, + xcb_setup->protocol_major_version, + xcb_setup->protocol_minor_version); +} + +static struct wlr_x11_output *get_x11_output_from_output( + struct wlr_output *wlr_output) { + assert(wlr_output_is_x11(wlr_output)); + return (struct wlr_x11_output *)wlr_output; +} + +static void output_set_refresh(struct wlr_output *wlr_output, int32_t refresh) { + struct wlr_x11_output *output = get_x11_output_from_output(wlr_output); + + if (refresh <= 0) { + refresh = X11_DEFAULT_REFRESH; + } + + wlr_output_update_custom_mode(&output->wlr_output, wlr_output->width, + wlr_output->height, refresh); + + output->frame_delay = 1000000 / refresh; +} + +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; + + output_set_refresh(&output->wlr_output, refresh); + + 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; + } + + return true; +} + +static void output_transform(struct wlr_output *wlr_output, + enum wl_output_transform transform) { + struct wlr_x11_output *output = get_x11_output_from_output(wlr_output); + output->wlr_output.transform = transform; +} + +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; + + wlr_input_device_destroy(&output->pointer_dev); + + wl_list_remove(&output->link); + wl_event_source_remove(output->frame_timer); + wlr_egl_destroy_surface(&x11->egl, output->surf); + xcb_destroy_window(x11->xcb, output->win); + xcb_flush(x11->xcb); + free(output); +} + +static bool output_make_current(struct wlr_output *wlr_output, + int *buffer_age) { + struct wlr_x11_output *output = get_x11_output_from_output(wlr_output); + struct wlr_x11_backend *x11 = output->x11; + + return wlr_egl_make_current(&x11->egl, output->surf, buffer_age); +} + +static bool output_swap_buffers(struct wlr_output *wlr_output, + pixman_region32_t *damage) { + struct wlr_x11_output *output = (struct wlr_x11_output *)wlr_output; + struct wlr_x11_backend *x11 = output->x11; + + if (!wlr_egl_swap_buffers(&x11->egl, output->surf, damage)) { + return false; + } + + wlr_output_send_present(wlr_output, NULL); + return true; +} + +static const struct wlr_output_impl output_impl = { + .set_custom_mode = output_set_custom_mode, + .transform = output_transform, + .destroy = output_destroy, + .make_current = output_make_current, + .swap_buffers = output_swap_buffers, +}; + +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(struct wlr_x11_output)); + if (output == NULL) { + return NULL; + } + output->x11 = x11; + + struct wlr_output *wlr_output = &output->wlr_output; + wlr_output_init(wlr_output, &x11->backend, &output_impl, x11->wl_display); + + wlr_output->width = 1024; + wlr_output->height = 768; + + output_set_refresh(&output->wlr_output, 0); + + snprintf(wlr_output->name, sizeof(wlr_output->name), "X11-%d", + wl_list_length(&x11->outputs) + 1); + parse_xcb_setup(wlr_output, x11->xcb); + + uint32_t mask = XCB_CW_EVENT_MASK; + uint32_t values[] = { + XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY + }; + output->win = xcb_generate_id(x11->xcb); + xcb_create_window(x11->xcb, XCB_COPY_FROM_PARENT, output->win, + x11->screen->root, 0, 0, wlr_output->width, wlr_output->height, 1, + XCB_WINDOW_CLASS_INPUT_OUTPUT, x11->screen->root_visual, mask, values); + + 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_ENTER | + XCB_INPUT_XI_EVENT_MASK_LEAVE, + }; + xcb_input_xi_select_events(x11->xcb, output->win, 1, &xinput_mask.head); + + output->surf = wlr_egl_create_surface(&x11->egl, &output->win); + if (!output->surf) { + wlr_log(WLR_ERROR, "Failed to create EGL surface"); + free(output); + return NULL; + } + + 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); + + char title[32]; + if (snprintf(title, sizeof(title), "wlroots - %s", wlr_output->name)) { + xcb_change_property(x11->xcb, XCB_PROP_MODE_REPLACE, output->win, + x11->atoms.net_wm_name, x11->atoms.utf8_string, 8, + strlen(title), title); + } + + xcb_map_window(x11->xcb, output->win); + xcb_flush(x11->xcb); + + struct wl_event_loop *ev = wl_display_get_event_loop(x11->wl_display); + output->frame_timer = wl_event_loop_add_timer(ev, signal_frame, output); + + wl_list_insert(&x11->outputs, &output->link); + + wl_event_source_timer_update(output->frame_timer, output->frame_delay); + wlr_output_update_enabled(wlr_output, true); + + wlr_input_device_init(&output->pointer_dev, WLR_INPUT_DEVICE_POINTER, + &input_device_impl, "X11 pointer", 0, 0); + wlr_pointer_init(&output->pointer, &pointer_impl); + output->pointer_dev.pointer = &output->pointer; + output->pointer_dev.output_name = strdup(wlr_output->name); + + wlr_signal_emit_safe(&x11->backend.events.new_output, wlr_output); + wlr_signal_emit_safe(&x11->backend.events.new_input, &output->pointer_dev); + + return wlr_output; +} + +void handle_x11_configure_notify(struct wlr_x11_output *output, + xcb_configure_notify_event_t *ev) { + // ignore events that set an invalid size: + if (ev->width > 0 && ev->height > 0) { + wlr_output_update_custom_mode(&output->wlr_output, ev->width, + ev->height, output->wlr_output.refresh); + + // Move the pointer to its new location + update_x11_pointer_position(output, output->x11->time); + } else { + wlr_log(WLR_DEBUG, + "Ignoring X11 configure event for height=%d, width=%d", + ev->width, ev->height); + } +} + +bool wlr_output_is_x11(struct wlr_output *wlr_output) { + return wlr_output->impl == &output_impl; +} diff --git a/docs/env_vars.md b/docs/env_vars.md new file mode 100644 index 00000000..b67d1652 --- /dev/null +++ b/docs/env_vars.md @@ -0,0 +1,34 @@ +wlroots reads these environment variables + +wlroots specific +---------------- +* *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_ATOMIC_GAMMA*: set to 1 to use legacy DRM interface for gamma + control instead of the atomic interface +* *WLR_LIBINPUT_NO_DEVICES*: set to 1 to not fail without any input devices +* *WLR_BACKENDS*: comma-separated list of backends to use (available backends: + wayland, x11, headless) +* *WLR_WL_OUTPUTS*: when using the wayland backend specifies the number of outputs +* *WLR_X11_OUTPUTS*: when using the X11 backend specifies the number of outputs +* *WLR_HEADLESS_OUTPUTS*: when using the headless backend specifies the number + of outputs +* *WLR_NO_HARDWARE_CURSORS*: set to 1 to use software cursors instead of + hardware cursors +* *WLR_SESSION*: specifies the wlr\_session to be used (available sessions: + logind/systemd, direct) + +rootston specific +------------------ +* *XKB_DEFAULT_RULES*, *XKB_DEFAULT_MODEL*, *XKB_DEFAULT_LAYOUT*, + *XKB_DEFAULT_VARIANT*, *XKB_DEFAULT_OPTIONS*: xkb setup + +generic +------- +* *DISPLAY*: if set probe X11 backend in *wlr_backend_autocreate* +* *WAYLAND_DISPLAY*, *_WAYLAND_DISPLAY*, *WAYLAND_SOCKET*: if set probe Wayland + backend in *wlr_backend_autocreate* +* *XCURSOR_PATH*: directory where xcursors are located diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 00000000..38582982 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1 @@ +/compositor/protocols diff --git a/examples/cat.c b/examples/cat.c new file mode 100644 index 00000000..cc1403d8 --- /dev/null +++ b/examples/cat.c @@ -0,0 +1,2641 @@ +/* GIMP RGBA C-Source image dump (cat.c) */ + +#include "cat.h" + +const struct gimp_texture cat_tex = { + 128, 128, 4, + "[\227\017\377L\206\001\377M\212\002\377T\227\011\377V\231\010\377W\224\001\377[\222" + "\001\377T\212\001\377P\211\001\377M\203\001\377P\212\001\377Q\217\001\377K\210\001\377" + "K\210\001\377N\214\001\377Q\217\002\377W\226\001\377X\224\001\377U\214\001\377U\216\001" + "\377[\225\003\377Y\221\002\377W\217\001\377^\230\005\377^\230\006\377`\231\011\377Z" + "\225\003\377X\221\002\377Y\221\003\377X\220\003\377^\227\007\377Z\225\004\377Z\226\002" + "\377W\217\001\377Y\224\002\377X\222\002\377W\220\001\377[\227\001\377c\241\004\377a\237" + "\003\377Z\230\001\377Y\222\001\377Z\220\001\377X\216\001\377[\231\005\377j\246\025\377" + "_\232\010\377U\214\000\377o\242\026\377k\234\013\377g\231\001\377c\222\001\377X\213" + "\005\377_\221\023\377V\214\010\377T\216\002\377U\222\002\377Q\211\001\377J|\001\377W" + "\214\003\377j\235\006\377o\243\004\377g\235\002\377X\217\002\377X\226\002\377U\221\001" + "\377T\210\002\377V\212\001\377V\216\002\377Z\226\002\377Y\221\001\377[\221\002\377g\234" + "\014\377d\232\016\377g\240\032\377[\226\022\377\\\224\020\377g\235\026\377\\\225" + "\005\377V\221\002\377U\212\002\377Y\217\001\377Z\220\001\377Y\221\001\377Y\226\002\377" + "W\227\001\377P\217\001\377O\217\002\377V\231\004\377X\230\003\377[\231\004\377X\217\001" + "\377Z\215\002\377Y\212\002\377[\214\001\377\\\215\002\377]\220\001\377]\227\002\377]" + "\233\001\377X\220\001\377X\217\001\377X\215\001\377^\234\001\377]\237\002\377U\221\001" + "\377R\214\001\377S\217\001\377T\220\001\377P\217\001\377P\224\001\377O\216\001\377M\210" + "\001\377V\225\001\377S\216\001\377S\216\001\377S\224\001\377N\220\001\377Q\226\004\377" + "S\227\013\377Y\235\023\377a\241\025\377`\240\015\377]\234\005\377V\227\002\377Y\233" + "\010\377W\231\011\377P\222\002\377R\217\001\377Y\237\021\377S\232\010\377K\224\002" + "\377L\222\001\377P\214\002\377]\222\001\377]\224\002\377U\217\001\377L\212\001\377K\206" + "\001\377Q\213\001\377T\222\001\377U\220\001\377V\224\001\377V\223\001\377Y\223\001\377" + "[\223\001\377Z\221\001\377Y\222\001\377X\225\001\377V\222\002\377U\210\002\377^\225\001" + "\377i\240\013\377g\236\020\377h\240\023\377T\223\002\377R\216\001\377V\215\001\377" + "[\225\001\377Y\226\001\377[\227\001\377Z\227\001\377Z\226\002\377^\226\005\377e\232\014" + "\377\\\225\004\377a\233\005\377_\231\002\377m\246\013\377a\232\003\377_\226\002\377" + "\\\223\001\377X\224\001\377]\235\012\377c\241\016\377^\231\002\377_\222\002\377f\235" + "\006\377s\252\012\377j\242\001\377b\233\002\377a\235\006\377_\232\011\377V\220\002\377" + "T\210\002\377T\216\001\377S\216\001\377U\220\002\377e\234\005\377k\241\004\377n\244\004" + "\377s\252\014\377a\234\003\377c\235\010\377W\217\001\377U\204\001\377X\212\001\377" + "[\217\001\377]\225\001\377[\224\001\377W\216\002\377V\216\003\377T\214\002\377R\212\002" + "\377T\214\002\377U\213\001\377\\\223\005\377e\235\014\377W\221\002\377W\220\001\377" + "Y\220\001\377W\213\001\377V\214\001\377V\217\001\377W\227\002\377V\227\001\377U\227\001" + "\377V\231\003\377N\211\001\377S\215\001\377W\216\001\377T\210\002\377Q\204\002\377W\213" + "\001\377Z\217\001\377Z\216\000\377[\225\002\377b\243\013\377V\220\001\377T\207\001\377" + "U\214\001\377[\231\002\377\\\236\002\377U\220\001\377M\207\002\377N\207\001\377Q\215" + "\001\377S\216\001\377V\226\002\377U\216\000\377V\212\002\377c\233\001\377]\221\002\377" + "V\215\001\377X\230\001\377S\226\001\377L\215\001\377I\211\001\377L\217\001\377T\225\004" + "\377j\244\014\377l\246\013\377X\226\001\377T\225\004\377\\\235\016\377c\243\025\377" + "^\236\012\377K\223\002\377M\230\007\377S\235\016\377N\227\007\377P\215\001\377^\224" + "\001\377`\226\001\377Z\225\001\377Q\216\001\377M\210\001\377V\216\002\377]\227\001\377" + "_\232\001\377]\226\001\377[\223\001\377Z\222\001\377Y\216\001\377Y\216\001\377X\225\001" + "\377W\226\001\377V\216\001\377T\210\001\377]\225\001\377o\246\014\377e\235\012\377" + "b\236\014\377S\222\001\377T\213\001\377W\220\001\377W\223\001\377W\216\002\377\\\231" + "\002\377c\235\011\377l\242\027\377e\233\022\377d\234\020\377Y\223\003\377b\235\007" + "\377b\233\003\377f\237\004\377b\233\001\377b\232\002\377`\232\001\377]\235\001\377^\237" + "\005\377Y\227\002\377U\211\001\377]\220\002\377_\225\001\377c\240\001\377l\250\005\377" + "b\237\002\377^\233\001\377W\223\002\377W\221\001\377T\214\001\377T\217\001\377Z\225\005" + "\377]\230\002\377a\233\002\377b\234\001\377k\245\011\377l\247\014\377\\\227\003\377" + "g\240\015\377V\213\001\377P\202\001\377T\212\001\377Z\224\001\377Z\226\003\377\\\231" + "\010\377T\220\014\377L\207\004\377L\200\002\377Q\203\001\377^\223\003\377Y\216\001\377" + "^\224\004\377p\247\026\377]\227\005\377Y\222\001\377X\222\001\377V\213\001\377W\213" + "\001\377X\220\001\377[\233\001\377X\231\001\377O\212\002\377S\220\001\377S\211\001\377" + "V\216\001\377W\214\002\377O\204\002\377U\213\001\377Z\217\002\377\\\225\001\377Z\223" + "\001\377U\221\003\377g\247\027\377S\224\002\377O\213\001\377U\223\001\377Y\230\001\377" + "\\\234\001\377Y\226\001\377Q\215\001\377O\207\001\377S\214\001\377V\217\001\377Y\225" + "\001\377X\214\001\377_\223\001\377i\237\001\377_\223\001\377[\222\001\377Y\227\002\377" + "V\226\001\377K\206\001\377H\203\002\377J\211\001\377T\224\003\377j\244\014\377i\244" + "\010\377]\234\003\377T\222\001\377U\226\002\377\\\236\012\377a\242\012\377H\211\001" + "\377E\214\001\377I\225\010\377V\240\021\377Q\232\002\377Y\224\001\377\\\222\002\377" + "X\224\001\377Q\215\001\377P\212\001\377W\216\001\377_\226\001\377a\226\001\377^\224\001" + "\377Z\221\001\377X\217\002\377S\210\001\377U\214\001\377[\227\003\377X\227\001\377U\221" + "\001\377W\216\001\377^\227\001\377h\243\002\377`\231\001\377Z\226\002\377U\214\001\377" + "W\217\001\377Y\222\001\377X\221\001\377X\226\002\377W\227\002\377X\227\004\377U\223\005" + "\377U\220\002\377V\222\001\377V\222\001\377\\\235\004\377]\236\003\377X\226\002\377Z" + "\225\002\377`\237\001\377`\241\002\377]\235\001\377Y\232\002\377X\224\001\377U\212\001" + "\377W\214\002\377Z\221\002\377Y\216\001\377d\240\005\377`\233\003\377Z\226\001\377^\232" + "\003\377Z\227\002\377X\222\001\377c\234\010\377i\242\014\377\\\227\003\377]\235\003\377" + "[\234\002\377d\244\015\377e\244\017\377Z\230\004\377j\246\017\377W\215\001\377S\205" + "\002\377X\223\003\377j\244\037\377l\246/\377~\261P\377\224\301r\377j\236\063\377" + "M\201\001\377S\207\001\377[\222\002\377T\214\000\377`\233\013\377i\243\024\377`\233" + "\014\377h\241\027\377e\240\023\377U\217\002\377V\213\001\377[\230\002\377Z\231\001\377" + "N\210\002\377H\177\002\377W\225\001\377Y\220\001\377Z\217\001\377V\214\001\377R\210\001" + "\377]\234\001\377_\231\002\377Z\222\001\377Y\223\001\377V\223\001\377c\244\020\377Y" + "\233\007\377R\223\001\377U\224\001\377Z\232\001\377^\235\001\377]\234\002\377Y\232\002" + "\377R\222\001\377T\223\001\377W\230\001\377W\230\002\377W\213\001\377h\240\001\377h\236" + "\001\377_\222\002\377\\\221\002\377[\231\002\377X\231\001\377O\220\001\377K\213\001\377" + "O\217\002\377b\236\015\377_\233\007\377Z\227\002\377_\240\004\377U\217\002\377T\221" + "\001\377S\222\001\377U\221\001\377K\203\002\377F\202\001\377D\212\001\377K\225\004\377" + "O\232\003\377R\230\002\377U\225\001\377T\223\001\377P\211\001\377K\204\001\377R\211\001" + "\377X\223\001\377[\231\001\377[\233\001\377X\222\001\377S\212\001\377M\200\002\377U\214" + "\003\377^\226\012\377X\225\005\377Y\232\006\377V\222\001\377\\\225\001\377d\240\001\377" + "_\226\001\377Z\220\001\377X\221\001\377Y\222\001\377[\216\001\377\\\217\002\377^\233" + "\002\377V\225\001\377R\221\001\377T\215\001\377V\220\001\377Z\224\001\377X\222\002\377" + "W\225\001\377^\240\007\377V\224\001\377V\216\001\377^\236\002\377b\243\004\377[\233\003" + "\377X\230\004\377\\\234\011\377V\222\003\377U\216\001\377X\215\001\377[\217\001\377" + "h\240\005\377i\245\005\377]\227\001\377^\230\002\377Z\217\001\377[\221\001\377k\243\011" + "\377e\236\007\377W\223\001\377c\243\014\377[\234\005\377f\244\017\377f\241\015\377" + "b\236\012\377n\251\024\377W\217\001\377V\216\005\377e\237\040\377t\254B\377|\260" + "T\377\230\305y\377\250\317\222\377o\242<\377P\210\002\377^\224\006\377\\\226" + "\006\377Q\221\003\377b\241\024\377a\241\020\377S\224\005\377e\240\030\377l\245\033" + "\377Z\222\001\377]\223\002\377`\232\001\377[\226\002\377P\204\001\377T\211\001\377_\231" + "\001\377^\224\002\377^\224\001\377[\224\002\377Y\217\001\377Z\225\001\377Y\220\001\377" + "Z\221\001\377X\224\001\377R\215\001\377X\231\005\377`\241\012\377S\221\001\377R\214" + "\001\377W\226\001\377[\227\001\377_\237\002\377`\242\002\377W\226\001\377Y\232\002\377" + "g\246\011\377a\237\003\377Z\220\002\377e\236\002\377_\226\001\377X\220\001\377X\223" + "\001\377[\233\001\377Z\233\002\377V\230\002\377Q\220\001\377b\235\015\377m\247\025\377" + "Z\232\004\377U\224\001\377[\234\004\377R\220\001\377R\220\001\377R\225\001\377V\221\001" + "\377M\200\001\377M\205\002\377L\207\001\377N\217\002\377N\217\001\377J\217\002\377P\230" + "\004\377O\227\003\377O\215\001\377E\201\001\377N\205\002\377Q\223\002\377`\242\021\377" + "R\225\002\377T\223\001\377T\215\001\377T\205\001\377^\222\010\377b\226\015\377]\231" + "\014\377]\235\011\377V\223\001\377Z\221\001\377c\233\001\377b\231\002\377`\226\001\377" + "]\225\001\377Y\216\002\377[\213\002\377^\222\001\377b\235\003\377[\223\002\377Y\225\001" + "\377Z\223\002\377]\223\002\377]\222\002\377]\227\002\377[\230\002\377a\243\010\377W" + "\230\002\377R\217\001\377Z\234\003\377h\251\017\377e\245\022\377^\233\017\377`\234" + "\025\377]\230\017\377i\243\032\377d\235\016\377]\224\001\377i\241\002\377i\243\002" + "\377b\233\001\377_\233\002\377X\222\001\377W\223\001\377c\237\017\377h\243\026\377" + "\\\234\014\377`\240\015\377Y\233\003\377^\235\004\377]\231\004\377c\235\007\377f\242" + "\016\377]\232\016\377n\250/\377q\253<\377w\256H\377\177\263Q\377\234\307z\377" + "\244\315\211\377g\241+\377O\216\002\377U\222\003\377S\222\003\377N\217\001\377_\241" + "\020\377Z\235\011\377Q\220\002\377W\226\003\377\\\231\003\377d\236\003\377k\237\001\377" + "f\233\001\377_\225\001\377X\213\002\377]\225\001\377f\236\002\377c\231\001\377`\234\002" + "\377]\234\001\377W\217\001\377V\214\001\377V\223\001\377T\223\001\377T\224\002\377K\203" + "\002\377N\212\001\377_\240\004\377X\222\001\377W\222\001\377Y\225\002\377W\220\001\377" + "Z\227\001\377`\242\003\377^\241\003\377`\241\004\377h\247\007\377c\242\003\377]\231\001" + "\377^\234\001\377S\220\001\377R\216\001\377U\225\001\377Z\231\002\377`\233\002\377[\225" + "\003\377W\213\002\377h\235\016\377e\240\015\377[\233\003\377X\232\003\377\\\235\005\377" + "V\225\002\377S\222\002\377Z\234\001\377b\235\001\377L~\002\377P\205\001\377O\206\002\377" + "Z\223\001\377Y\226\001\377N\210\001\377I\212\001\377R\231\004\377J\213\001\377C}\001\377" + "L\211\001\377V\232\015\377a\242\040\377L\212\001\377N\212\001\377T\217\001\377^\232" + "\003\377a\233\003\377Y\223\001\377T\224\002\377W\232\003\377W\225\001\377\\\225\002\377" + "b\230\001\377e\234\002\377a\226\001\377\\\217\001\377X\214\002\377\\\220\001\377a\232" + "\003\377b\233\002\377[\222\001\377_\233\002\377_\225\001\377`\225\001\377a\225\001\377" + "b\231\001\377\\\227\001\377a\243\005\377`\244\007\377U\224\002\377]\237\003\377b\242" + "\007\377f\246\020\377Z\231\007\377X\225\006\377b\235\022\377w\253.\377\200\263:\377" + "m\244\033\377j\245\002\377g\236\001\377b\232\002\377]\230\001\377X\224\001\377T\220" + "\004\377f\241\034\377b\240\031\377i\250\035\377d\244\021\377Y\230\001\377^\231\001" + "\377]\223\002\377b\231\003\377]\231\005\377^\233\024\377r\253\067\377x\262A\377{" + "\261@\377\202\265I\377\230\305k\377\227\302t\377_\234\026\377P\213\002\377R" + "\213\001\377V\220\002\377W\224\002\377a\241\010\377W\230\003\377N\213\001\377R\221\001" + "\377[\231\001\377c\232\001\377i\240\002\377j\240\001\377[\221\001\377O\202\001\377]\225" + "\001\377c\235\001\377a\230\001\377b\237\003\377_\240\003\377W\216\002\377\\\232\002\377" + "Z\227\001\377U\222\001\377P\216\001\377M\210\002\377Q\207\002\377\\\232\001\377_\233" + "\002\377^\236\001\377Z\224\001\377W\213\002\377[\231\001\377h\246\010\377f\247\014\377" + "W\231\002\377]\237\003\377\\\234\001\377^\233\001\377Z\227\002\377V\225\001\377U\230" + "\001\377Z\233\001\377\\\233\002\377c\240\002\377d\232\004\377^\223\005\377^\226\007\377" + "[\231\004\377]\233\003\377`\237\005\377^\235\003\377^\236\002\377\\\235\002\377_\240" + "\001\377`\230\002\377Ey\002\377H\201\001\377N\210\001\377R\216\001\377Z\231\001\377U\221" + "\001\377N\211\001\377N\221\000\377J\212\001\377G\202\001\377G\212\001\377R\231\012\377" + "X\233\021\377I\202\001\377M\212\001\377O\221\001\377W\227\001\377_\240\001\377]\223" + "\001\377X\223\001\377W\227\001\377X\226\002\377^\231\001\377d\237\001\377i\247\002\377" + "\\\231\001\377W\216\001\377X\220\001\377Z\227\002\377f\246\011\377\\\231\002\377Y\224" + "\002\377]\227\001\377^\222\002\377c\225\001\377e\230\001\377b\227\001\377^\227\001\377" + "b\243\003\377h\252\012\377]\236\001\377^\240\001\377Z\233\001\377]\235\002\377c\244" + "\012\377g\244\020\377b\236\022\377i\243\037\377u\253\060\377}\262\060\377o\251" + "\013\377f\234\002\377`\223\001\377e\235\006\377e\233\021\377q\247\063\377y\261F\377" + "l\245\062\377o\253*\377f\246\024\377Z\231\002\377c\235\001\377a\221\003\377n\243" + "\013\377s\247\034\377g\237\037\377s\253\064\377~\264?\377x\254\065\377\177\263" + ";\377\204\265C\377q\246)\377V\224\000\377W\223\002\377T\216\001\377V\217\001\377" + "\\\227\001\377e\241\005\377W\221\001\377M\200\002\377S\220\002\377]\233\001\377b\226" + "\002\377j\234\002\377i\240\002\377\\\226\001\377R\207\001\377]\230\001\377b\233\002\377" + "_\224\002\377f\240\002\377`\233\002\377[\222\001\377c\232\002\377e\240\001\377V\213\002" + "\377R\212\001\377S\215\001\377[\226\001\377`\237\001\377^\235\001\377Y\233\001\377X\230" + "\001\377U\212\001\377[\227\001\377j\250\007\377b\242\004\377S\215\002\377Y\226\001\377" + "a\242\002\377a\240\001\377X\222\001\377Y\227\001\377^\242\001\377]\234\001\377_\234\002" + "\377c\237\001\377f\237\004\377b\227\005\377a\230\005\377_\232\003\377f\240\004\377m\246" + "\006\377g\242\001\377p\251\005\377l\246\003\377c\232\002\377a\232\001\377<p\001\377J\204" + "\001\377I\206\001\377I\206\002\377Q\222\001\377V\233\001\377Q\223\001\377J\224\002\377" + "L\225\005\377G\220\001\377C\210\001\377P\227\005\377R\223\004\377N\207\002\377O\216\002" + "\377P\221\001\377T\224\001\377^\236\001\377\\\224\001\377X\221\001\377V\223\001\377X" + "\230\001\377_\233\001\377m\250\001\377t\257\004\377b\234\001\377Z\225\001\377X\222\001" + "\377[\230\002\377k\251\014\377Z\227\002\377[\222\001\377[\217\002\377`\225\002\377d" + "\234\001\377c\231\002\377^\223\001\377]\223\001\377^\233\002\377e\246\004\377f\245\002" + "\377i\245\001\377e\237\001\377_\227\002\377_\240\003\377\\\234\005\377d\243\024\377" + "b\237\024\377m\246\035\377i\246\017\377g\243\003\377h\240\001\377d\230\001\377n\245" + "\017\377u\252*\377x\257E\377\201\271V\377t\257;\377a\242\023\377c\244\012\377" + "Z\227\002\377c\236\001\377j\240\004\377x\256\033\377}\262\062\377n\244.\377\201\263" + "D\377~\263?\377l\245$\377|\265/\377k\246\030\377[\226\002\377Y\225\001\377[\227" + "\001\377Y\226\002\377Y\220\001\377d\235\002\377h\241\001\377]\222\001\377Z\222\002\377" + "]\232\002\377^\225\001\377j\240\001\377p\247\003\377f\242\002\377Z\231\001\377Z\231\002" + "\377\\\232\001\377_\227\002\377d\235\001\377l\246\001\377d\231\001\377a\227\002\377]" + "\220\001\377e\234\001\377Z\220\001\377U\214\001\377X\223\001\377]\233\002\377\\\232\002" + "\377b\244\010\377N\214\000\377V\230\001\377[\231\001\377[\231\001\377g\247\004\377a" + "\240\002\377Y\220\001\377b\237\002\377k\250\002\377g\243\002\377^\224\002\377`\236\001" + "\377a\240\001\377d\242\001\377d\235\002\377f\237\001\377h\236\003\377\\\222\001\377h" + "\236\005\377m\243\005\377y\257\021\377s\251\015\377l\243\005\377q\252\010\377g\240" + "\003\377_\223\001\377h\241\002\377>p\001\377M\207\001\377H\204\002\377D}\001\377I\210\001" + "\377S\226\001\377N\224\002\377K\222\002\377\\\241\025\377H\217\003\377D\210\001\377" + "U\233\004\377S\223\001\377X\223\001\377W\216\001\377S\216\001\377X\227\002\377\\\233" + "\002\377Z\222\001\377Y\227\002\377V\227\002\377^\237\010\377]\234\002\377l\245\002\377" + "u\256\003\377d\231\002\377\\\217\002\377Y\216\001\377c\237\004\377{\262\026\377j\240" + "\003\377f\225\001\377e\222\002\377i\234\002\377h\240\001\377a\231\002\377]\226\001\377" + "\\\221\001\377\\\223\001\377_\236\002\377c\237\001\377h\244\001\377d\235\002\377^\226" + "\001\377_\234\001\377Z\230\002\377c\243\016\377a\235\017\377o\251\027\377g\241\005\377" + "g\242\001\377l\246\002\377f\235\000\377j\242\013\377p\246'\377v\253A\377\217\300" + "d\377w\261;\377Y\233\001\377e\250\005\377]\236\001\377_\237\003\377p\254\026\377x" + "\263(\377p\255)\377b\236\036\377|\262<\377s\252-\377\\\233\010\377m\253\023" + "\377d\241\005\377\\\231\002\377[\230\001\377Y\231\001\377\\\227\001\377c\231\001\377" + "k\237\001\377p\247\002\377k\243\002\377e\241\002\377^\231\001\377i\243\001\377p\252\002" + "\377o\246\005\377a\233\002\377_\232\001\377b\236\002\377d\236\002\377b\233\001\377s\255" + "\013\377x\261\013\377j\241\002\377a\225\002\377Z\214\002\377_\225\002\377d\236\001\377" + "b\232\001\377b\240\001\377]\232\001\377X\225\002\377c\245\016\377S\223\002\377[\233" + "\002\377[\226\002\377c\237\004\377n\254\010\377a\242\001\377`\226\001\377h\241\001\377" + "v\256\002\377r\247\001\377j\241\001\377e\236\001\377c\234\002\377f\242\001\377f\236\001" + "\377f\234\002\377d\233\001\377a\225\002\377i\240\003\377k\242\003\377z\261\031\377~" + "\263\037\377h\240\003\377j\244\002\377d\235\002\377`\220\001\377i\243\001\377Cu\002\377" + "I\201\001\377F\201\001\377E{\002\377K\206\001\377U\225\001\377R\226\001\377J\221\001\377" + "R\231\011\377H\220\004\377E\212\002\377R\230\002\377V\226\001\377Z\221\001\377O|\001\377" + "P\203\001\377[\223\001\377`\232\001\377^\226\001\377Z\233\002\377R\226\001\377\\\235" + "\014\377c\243\014\377c\240\001\377l\251\001\377c\235\001\377Z\223\001\377X\223\001\377" + "i\246\010\377x\260\017\377j\235\001\377h\224\002\377h\230\001\377h\236\001\377c\231" + "\001\377Z\223\002\377Z\231\001\377[\226\001\377^\230\001\377a\242\003\377]\237\001\377" + "Y\230\001\377Z\225\001\377_\236\002\377c\240\001\377d\240\002\377d\242\007\377\\\231" + "\006\377d\237\012\377q\255\015\377i\243\002\377k\250\002\377f\243\003\377a\236\010\377" + "f\240\037\377s\254>\377\203\271Q\377s\256\062\377\\\235\003\377h\252\012\377a" + "\242\005\377^\236\003\377n\253\027\377\200\273\060\377j\251\027\377\\\235\006\377" + "]\235\010\377]\235\006\377_\231\002\377e\234\001\377i\240\002\377g\235\001\377a\230" + "\002\377\\\221\001\377d\233\001\377n\246\002\377o\243\001\377p\246\002\377h\241\002\377" + "a\232\002\377k\247\003\377r\255\002\377q\251\001\377j\235\002\377k\243\002\377k\242\001" + "\377j\240\001\377h\234\001\377d\234\002\377x\261\015\377w\261\007\377j\240\002\377_" + "\220\002\377Y\212\002\377V\210\002\377e\236\001\377m\250\002\377l\250\001\377f\242\002" + "\377_\236\005\377b\242\011\377\\\235\003\377]\232\002\377Y\220\001\377n\247\013\377" + "r\254\010\377i\245\001\377k\242\001\377l\241\001\377w\254\001\377x\252\001\377v\256" + "\001\377m\243\002\377d\234\002\377e\236\002\377e\234\002\377c\233\002\377`\227\002\377" + "g\236\002\377p\247\003\377h\234\001\377p\250\015\377z\261\032\377d\240\001\377d\236" + "\001\377e\236\002\377a\224\002\377h\242\001\377G|\002\377K\206\004\377K\211\006\377\071" + "m\001\377N\212\002\377]\237\001\377V\235\001\377M\222\001\377O\226\007\377N\226\007\377" + "I\217\001\377V\233\003\377V\221\001\377N\177\001\377Hu\001\377M}\001\377Y\216\001\377h" + "\241\001\377c\233\001\377[\231\002\377U\227\002\377M\215\002\377Y\230\003\377_\235\001" + "\377g\243\001\377b\235\002\377\\\230\001\377X\224\001\377l\251\012\377n\247\005\377" + "j\233\002\377i\227\002\377k\235\002\377g\236\001\377[\216\001\377U\211\001\377[\230\001" + "\377_\233\001\377`\232\002\377a\243\002\377\\\236\002\377Z\237\001\377Z\232\001\377c" + "\243\002\377o\255\004\377i\245\003\377_\235\001\377\\\233\002\377a\241\002\377i\247\004" + "\377d\241\001\377l\250\010\377e\242\011\377a\237\015\377o\251%\377x\261\066\377" + "v\260\066\377x\262.\377^\236\006\377\\\235\002\377c\245\006\377`\240\003\377q\256" + "\030\377w\263\036\377e\244\007\377b\243\002\377Z\226\001\377]\231\002\377c\236\001\377" + "j\242\002\377m\241\001\377k\241\002\377j\236\002\377f\231\001\377h\235\001\377i\240\001" + "\377h\240\002\377g\244\002\377e\244\002\377j\246\005\377n\251\004\377q\251\001\377o\241" + "\002\377p\241\001\377r\253\002\377p\244\001\377p\243\001\377j\235\002\377h\236\002\377" + "t\257\002\377p\250\001\377j\235\001\377d\223\001\377h\227\003\377]\213\002\377Z\212\001" + "\377i\241\002\377m\247\001\377n\246\003\377o\252\011\377`\241\004\377Z\231\001\377^" + "\231\002\377^\231\001\377p\254\015\377g\245\004\377j\244\001\377p\252\001\377r\245\001" + "\377w\254\001\377{\255\001\377y\262\001\377o\256\001\377a\235\002\377b\234\002\377b\236" + "\001\377`\231\001\377`\227\001\377i\240\002\377m\244\002\377h\236\001\377p\254\016\377" + "r\257\021\377`\237\001\377a\231\002\377c\232\002\377d\227\001\377g\240\001\377J\210" + "\001\377]\233\026\377h\243)\377:j\000\377R\207\002\377b\243\002\377_\242\004\377M\222" + "\001\377N\226\004\377Q\230\005\377Q\227\003\377U\232\003\377R\215\001\377K~\002\377M\200" + "\002\377T\213\001\377a\231\002\377g\236\002\377`\224\002\377]\231\001\377Z\232\001\377" + "Q\215\001\377Q\216\001\377\\\231\001\377f\242\001\377b\231\001\377X\217\002\377Y\225" + "\000\377o\252\012\377f\235\003\377d\227\001\377h\233\002\377h\236\001\377d\240\002\377" + "\\\227\001\377Y\225\001\377^\235\001\377a\234\001\377c\235\002\377c\240\002\377^\234" + "\001\377Z\231\001\377\\\237\001\377a\242\001\377n\252\010\377i\245\010\377\\\235\002" + "\377a\242\001\377d\242\001\377a\233\001\377]\232\001\377i\251\017\377a\241\014\377" + "[\234\010\377l\250\030\377{\264+\377v\260&\377t\261\035\377^\236\003\377\\\230" + "\002\377j\254\012\377c\245\006\377j\251\015\377k\253\014\377c\243\004\377g\251\003\377" + "[\230\001\377Z\227\001\377d\245\001\377h\243\001\377j\235\001\377j\240\001\377m\244\002" + "\377j\243\001\377f\237\002\377h\240\002\377k\245\002\377l\251\003\377f\247\001\377d\242" + "\002\377i\242\001\377n\247\001\377i\234\002\377o\243\002\377x\252\002\377r\241\002\377" + "t\247\001\377n\243\001\377l\240\001\377o\253\002\377j\243\001\377f\236\002\377b\231\002" + "\377\177\255\033\377b\222\006\377U\204\001\377d\233\001\377p\252\002\377j\244\001\377" + "l\253\002\377b\240\001\377a\234\002\377d\233\002\377g\241\003\377m\252\010\377_\237" + "\001\377b\237\001\377h\240\002\377r\246\002\377x\247\002\377\206\267\002\377\200\265" + "\001\377r\254\001\377k\251\002\377e\236\001\377a\232\001\377^\227\001\377a\231\001\377" + "j\241\003\377w\255\020\377z\262\025\377r\256\017\377l\254\014\377\\\236\001\377\\" + "\226\001\377b\233\001\377h\237\001\377l\245\001\377J\210\002\377S\225\016\377o\254-" + "\377K\204\004\377S\217\002\377Z\232\001\377^\241\002\377Y\234\001\377X\236\003\377[\242" + "\011\377\\\242\015\377P\230\003\377P\222\001\377P\212\001\377R\214\001\377[\223\001\377" + "g\236\002\377i\235\001\377^\222\003\377]\231\001\377Y\230\001\377U\226\002\377Q\217\002" + "\377U\224\001\377`\236\002\377c\233\001\377]\221\001\377b\241\003\377q\255\021\377_" + "\233\002\377a\230\001\377e\233\002\377d\234\002\377b\240\002\377c\246\002\377d\247\004" + "\377\\\234\001\377^\235\001\377`\235\002\377a\240\002\377^\233\002\377[\230\002\377a" + "\242\001\377^\237\000\377d\243\012\377j\247\025\377j\250\017\377d\244\003\377e\247" + "\001\377^\232\002\377W\223\001\377\\\240\006\377[\236\003\377Z\234\002\377a\241\006\377" + "s\260\026\377y\266\035\377f\245\006\377a\234\001\377a\235\002\377n\255\007\377j\250" + "\010\377j\247\011\377r\260\021\377f\246\005\377a\243\002\377Z\232\001\377f\246\012" + "\377n\253\011\377h\233\002\377l\241\001\377e\235\001\377d\240\001\377c\241\001\377b" + "\234\002\377j\245\001\377j\246\002\377g\243\002\377i\250\002\377k\245\002\377p\251\002" + "\377o\247\001\377m\241\001\377j\235\002\377q\244\002\377r\242\002\377s\246\002\377n\243" + "\001\377h\237\001\377h\243\001\377e\237\001\377`\233\002\377]\231\002\377j\246\016\377" + "`\230\003\377a\227\001\377g\240\001\377m\252\002\377i\246\001\377i\244\001\377e\235\002" + "\377a\227\002\377i\242\001\377q\253\003\377l\251\003\377b\237\001\377d\242\001\377g\240" + "\001\377m\242\001\377r\244\001\377}\257\001\377\207\271\001\377~\256\001\377t\251\001\377" + "k\242\001\377f\236\002\377b\232\001\377f\237\002\377m\245\003\377o\246\011\377z\263" + "\026\377q\256\020\377q\260\022\377_\241\002\377^\234\002\377e\235\002\377m\245\001\377" + "n\241\001\377V\230\010\377]\236\017\377w\261-\377f\244\026\377S\220\001\377V\222" + "\001\377Z\227\001\377`\234\001\377_\235\002\377[\235\002\377`\245\014\377V\232\003\377" + "W\233\002\377\\\237\001\377Y\233\001\377]\230\001\377d\232\002\377f\232\001\377b\230" + "\001\377^\230\001\377[\231\001\377W\226\002\377X\227\001\377Y\221\001\377e\240\002\377" + "g\236\002\377d\237\001\377m\253\014\377l\253\021\377[\233\001\377b\231\001\377l\237" + "\001\377h\236\002\377c\237\001\377g\250\007\377c\245\005\377\\\236\002\377^\241\001\377" + "\\\234\001\377[\232\001\377[\231\001\377^\232\001\377a\243\002\377Z\235\001\377\\\240" + "\011\377U\231\006\377`\241\012\377f\250\007\377d\247\002\377`\242\002\377V\227\002\377" + "U\232\003\377b\246\011\377\\\236\001\377^\236\001\377i\252\005\377i\251\003\377c\243" + "\002\377b\243\001\377d\245\001\377g\250\002\377a\243\002\377`\241\002\377h\251\005\377" + "e\247\002\377_\235\002\377_\237\002\377d\245\005\377e\236\003\377s\251\001\377w\254\002" + "\377j\241\001\377h\247\002\377a\235\001\377c\234\001\377j\246\002\377n\252\004\377j\247" + "\002\377k\245\001\377l\243\002\377s\255\001\377o\241\002\377k\237\002\377l\236\001\377" + "p\245\002\377m\244\001\377l\244\001\377l\243\001\377i\241\001\377h\237\001\377a\225\002" + "\377_\226\002\377]\224\001\377c\234\002\377j\237\001\377l\235\001\377l\240\001\377p\253" + "\001\377k\242\001\377e\227\002\377f\234\001\377b\226\002\377n\247\002\377s\256\002\377" + "m\241\001\377j\240\001\377j\243\002\377i\240\001\377l\242\001\377q\252\001\377t\253\001" + "\377\210\267\001\377\216\274\001\377{\257\002\377l\245\002\377j\246\001\377j\243\002" + "\377n\244\001\377t\251\005\377u\252\005\377l\245\002\377m\252\006\377r\257\021\377h" + "\246\010\377d\241\002\377f\235\001\377o\245\002\377s\244\001\377O\212\003\377W\227\004" + "\377f\243\022\377r\254\036\377V\223\001\377X\223\001\377Z\230\002\377^\223\001\377" + "a\230\001\377\\\232\001\377Z\237\004\377M\221\001\377P\223\001\377^\243\003\377\\\237" + "\001\377\\\233\001\377b\233\001\377d\241\001\377a\237\001\377\\\234\001\377\\\234\001\377" + "[\224\002\377_\233\001\377_\230\001\377k\246\003\377d\242\004\377]\236\002\377g\250\015" + "\377e\247\015\377[\233\002\377c\237\002\377f\233\002\377f\234\002\377d\242\001\377o" + "\253\017\377f\246\012\377]\236\001\377_\240\002\377Y\225\001\377[\231\001\377`\234" + "\001\377c\237\001\377c\240\001\377[\232\001\377X\232\002\377\\\237\003\377`\242\003\377" + "b\243\004\377d\245\005\377f\250\012\377Z\237\003\377W\231\002\377Y\235\002\377[\235" + "\001\377`\240\002\377b\243\001\377c\242\001\377d\244\001\377c\240\001\377g\246\002\377" + "f\247\001\377`\240\001\377^\241\002\377c\245\001\377h\251\002\377n\253\012\377j\252" + "\012\377b\241\002\377g\242\001\377t\261\002\377q\253\002\377l\247\002\377k\251\002\377" + "b\236\002\377b\236\002\377l\253\012\377}\270\034\377k\250\004\377k\246\001\377m\247" + "\002\377q\256\001\377m\241\001\377n\242\001\377o\242\001\377r\245\001\377q\247\002\377" + "n\246\001\377p\252\001\377l\242\001\377g\235\002\377c\234\001\377`\226\001\377c\227\002" + "\377l\234\001\377m\236\001\377i\231\001\377i\236\001\377i\242\001\377i\241\001\377d\230" + "\001\377h\226\002\377l\233\002\377q\244\001\377t\253\001\377o\241\001\377k\241\002\377" + "j\242\001\377j\243\001\377m\251\002\377m\247\002\377n\244\001\377{\256\001\377\203\266" + "\001\377w\254\001\377m\246\002\377o\246\001\377t\247\002\377y\250\001\377\225\266\023" + "\377\235\274\032\377u\252\003\377j\243\001\377w\264\017\377s\261\013\377g\237\002" + "\377l\242\001\377s\251\001\377s\246\001\377R\212\001\377[\227\002\377c\241\010\377l" + "\247\020\377`\235\002\377Z\225\002\377\\\234\001\377[\222\001\377\\\220\001\377`\235" + "\002\377Z\240\002\377J\223\002\377R\227\003\377V\233\002\377X\226\001\377^\223\002\377" + "a\232\001\377`\231\002\377X\222\002\377X\227\001\377[\233\001\377]\235\001\377\\\233" + "\001\377`\235\001\377k\252\003\377b\234\003\377\\\231\001\377_\243\002\377[\234\001\377" + "Y\224\002\377b\234\002\377b\232\001\377a\231\001\377a\236\002\377_\240\004\377d\247\010" + "\377\\\236\001\377_\236\002\377a\237\001\377c\244\001\377c\240\001\377_\233\001\377\\" + "\234\002\377[\236\001\377a\244\005\377b\244\004\377`\232\002\377d\237\000\377p\254\015" + "\377r\255\031\377[\234\004\377[\234\001\377]\235\001\377`\236\001\377`\235\001\377c" + "\240\001\377c\231\001\377f\246\001\377e\243\001\377g\243\001\377j\245\001\377e\240\002" + "\377_\237\002\377d\245\006\377r\262\022\377w\267\032\377c\244\007\377`\240\002\377" + "d\242\002\377p\257\001\377m\247\001\377h\246\001\377h\247\001\377d\242\002\377`\235\001" + "\377m\255\021\377z\272#\377e\247\003\377i\247\001\377j\237\001\377n\250\002\377m\241" + "\001\377l\237\002\377q\243\002\377s\246\001\377s\246\001\377p\242\001\377n\244\001\377" + "g\236\001\377g\243\001\377e\237\001\377b\227\001\377h\234\001\377n\243\001\377m\244\002" + "\377h\234\001\377g\240\002\377g\245\001\377n\251\002\377o\245\001\377o\236\002\377q\240" + "\001\377r\243\002\377u\254\002\377n\241\002\377m\246\001\377r\255\002\377o\252\002\377" + "j\237\001\377j\237\001\377m\242\001\377t\254\001\377z\257\001\377y\256\002\377t\251\001" + "\377o\241\001\377q\241\001\377v\246\001\377~\255\005\377\206\264\014\377k\242\002\377" + "i\243\001\377w\264\013\377p\256\006\377h\235\002\377n\244\002\377s\250\002\377r\244" + "\002\377e\241\002\377\\\232\002\377\\\235\002\377_\240\003\377]\235\001\377]\226\001\377" + "_\234\002\377\\\222\002\377X\215\001\377a\233\002\377^\240\002\377W\230\001\377Y\233" + "\002\377Y\232\001\377\\\227\001\377^\222\002\377d\233\002\377^\225\001\377X\224\002\377" + "X\224\001\377^\241\004\377i\252\014\377Z\233\002\377^\237\001\377k\254\006\377b\236" + "\002\377^\227\001\377`\242\002\377Y\226\001\377\\\221\001\377d\237\001\377f\241\001\377" + "b\231\002\377^\227\001\377Y\227\001\377Y\233\001\377X\225\001\377\\\233\001\377`\236" + "\001\377c\242\001\377a\237\002\377`\235\001\377^\240\001\377Z\233\001\377\\\237\001\377" + "[\231\001\377_\225\002\377a\234\001\377a\236\003\377a\237\005\377Y\232\001\377\\\234" + "\001\377b\240\001\377a\237\001\377f\244\010\377|\265\034\377i\242\002\377g\242\001\377" + "d\234\001\377i\244\001\377g\237\002\377b\240\001\377\\\236\001\377t\262\033\377\206" + "\301*\377l\254\020\377Z\236\002\377Z\235\001\377a\233\002\377k\243\002\377j\241\001" + "\377g\241\001\377c\241\001\377f\247\001\377a\241\002\377j\254\006\377f\251\010\377i" + "\254\004\377k\247\002\377i\235\001\377l\243\002\377h\234\002\377f\232\002\377g\232\002" + "\377g\232\002\377o\244\001\377n\242\001\377k\245\001\377d\237\001\377c\241\002\377b\231" + "\001\377`\224\002\377b\227\001\377k\244\002\377p\253\002\377j\237\002\377j\243\002\377" + "j\241\001\377p\250\001\377u\254\001\377s\246\002\377j\234\002\377h\233\001\377p\250\002" + "\377o\251\001\377m\244\001\377z\265\004\377r\251\001\377n\242\001\377l\241\001\377i\240" + "\001\377l\243\001\377l\237\001\377w\253\001\377w\253\001\377m\237\001\377o\241\001\377" + "o\241\002\377o\246\001\377n\252\001\377j\240\001\377l\244\001\377y\264\005\377y\262\005" + "\377l\242\001\377k\240\001\377n\245\002\377j\235\001\377a\240\001\377`\233\002\377_\237" + "\001\377[\235\001\377X\227\001\377X\222\001\377[\231\001\377[\225\002\377W\213\001\377" + "\\\223\001\377`\236\002\377\\\230\002\377\\\227\001\377Y\227\001\377\\\231\001\377[\226" + "\001\377a\237\001\377a\237\002\377Z\231\001\377X\226\001\377_\236\002\377m\254\015\377" + "^\240\002\377\\\235\001\377k\254\007\377c\240\003\377_\230\000\377b\243\001\377_\237" + "\001\377Z\222\002\377`\227\002\377d\236\002\377e\241\001\377c\235\002\377a\237\001\377" + "_\234\001\377\\\224\001\377_\231\001\377c\241\001\377b\236\002\377`\235\002\377_\237" + "\003\377d\244\017\377_\236\016\377X\231\002\377X\223\001\377\\\231\002\377a\240\001\377" + "[\230\001\377Y\230\001\377\\\234\001\377a\242\001\377b\242\001\377\\\233\002\377j\251" + "\016\377y\261\035\377j\235\000\377o\243\002\377]\221\002\377f\236\002\377g\244\001\377" + "_\237\001\377e\247\012\377\177\275)\377w\266\040\377b\244\011\377Z\236\004\377X" + "\232\002\377^\231\001\377f\236\002\377f\234\001\377f\241\001\377c\242\001\377j\254\004" + "\377h\250\003\377f\247\002\377c\241\001\377g\241\001\377n\250\001\377o\244\002\377r\246" + "\002\377m\237\002\377c\226\001\377c\226\001\377g\236\001\377o\246\001\377o\243\001\377" + "o\241\002\377m\237\002\377t\252\002\377f\230\001\377b\231\001\377a\225\001\377h\240\001" + "\377j\244\002\377q\254\001\377k\244\001\377i\235\002\377q\250\001\377u\257\002\377o\246" + "\001\377c\230\001\377e\233\002\377p\252\001\377n\247\002\377l\244\001\377t\261\002\377" + "p\253\001\377n\251\002\377l\251\003\377e\236\002\377k\244\001\377j\240\001\377u\254\002" + "\377z\256\001\377t\243\002\377m\235\001\377m\241\002\377s\256\001\377o\255\001\377h\235" + "\002\377n\244\001\377u\252\001\377\177\265\003\377q\245\001\377j\235\002\377i\241\001\377" + "f\236\002\377a\240\001\377`\231\001\377b\241\002\377`\240\001\377X\222\002\377W\220\001" + "\377\\\232\001\377[\225\001\377Y\220\001\377]\226\001\377a\232\001\377c\230\001\377a" + "\226\001\377^\222\002\377b\232\002\377`\234\004\377b\241\006\377e\245\007\377Z\226\001" + "\377[\223\001\377_\237\002\377g\247\003\377f\245\001\377a\235\001\377e\246\002\377a\240" + "\002\377a\231\001\377c\245\001\377a\243\002\377R\214\001\377Y\223\002\377`\234\002\377" + "c\244\001\377b\243\003\377_\240\002\377Z\226\002\377`\230\001\377d\231\001\377n\253\003" + "\377m\255\004\377a\243\004\377V\222\007\377Z\203)\377\213\256]\377g\235\"\377O" + "\220\000\377X\224\001\377\\\233\001\377Z\231\001\377Y\226\001\377Z\226\001\377^\234\002" + "\377a\243\001\377j\253\014\377h\247\007\377`\227\001\377m\243\001\377p\243\001\377Y" + "\217\002\377h\236\001\377h\247\002\377]\235\002\377o\257\032\377x\266\040\377j\253" + "\017\377Z\237\004\377X\236\004\377W\231\002\377\\\234\002\377j\250\007\377c\240\001\377" + "g\246\002\377j\252\004\377n\256\007\377l\254\007\377`\237\002\377c\241\001\377i\240\001" + "\377r\252\001\377w\253\001\377t\243\002\377l\234\001\377e\227\002\377a\225\001\377h\246" + "\001\377k\250\001\377l\241\002\377q\243\002\377t\251\002\377o\242\002\377h\234\001\377" + "c\232\002\377^\226\001\377f\245\001\377e\236\001\377s\260\006\377h\243\003\377g\235\002" + "\377o\247\001\377r\254\001\377j\236\002\377a\226\002\377g\236\001\377n\251\001\377l\246" + "\002\377j\247\001\377o\260\003\377i\251\001\377m\254\005\377z\267\020\377g\246\001\377" + "g\243\001\377j\246\001\377n\244\001\377t\247\002\377{\262\001\377u\245\001\377r\245\002" + "\377r\251\001\377m\243\001\377i\240\001\377n\250\001\377r\253\001\377|\264\002\377u\256" + "\002\377m\241\001\377g\235\002\377i\245\001\377X\227\001\377Y\221\002\377`\237\001\377" + "b\240\002\377\\\222\001\377^\230\000\377`\235\001\377^\225\001\377Y\217\002\377_\226" + "\002\377_\230\002\377`\234\003\377a\230\001\377d\233\002\377g\237\004\377`\233\004\377" + "g\245\020\377b\243\011\377[\232\002\377[\221\001\377_\232\001\377j\250\004\377k\253" + "\004\377b\240\001\377b\241\001\377c\241\002\377d\234\002\377m\256\003\377e\244\002\377" + "W\215\001\377[\222\001\377b\235\002\377c\241\001\377f\251\004\377f\246\005\377`\233\001" + "\377f\235\002\377m\243\001\377w\263\001\377n\251\002\377Z\223\001\377a\220\032\377\335" + "\317\305\377\377\362\365\377\373\370\361\377\225\273W\377S\220\000\377V\222" + "\001\377W\225\002\377Y\225\002\377X\217\002\377Z\232\002\377\\\235\007\377{\272(\377" + "d\240\010\377`\226\002\377l\245\002\377p\251\001\377j\236\002\377n\246\001\377j\251" + "\001\377f\245\007\377i\251\022\377h\250\014\377[\233\001\377^\240\001\377`\242\002\377" + "_\236\002\377i\251\010\377m\254\015\377Z\232\001\377]\235\002\377^\236\001\377_\240" + "\001\377i\254\004\377`\241\001\377a\234\002\377g\234\002\377r\250\002\377o\240\002\377" + "o\240\002\377k\234\001\377g\235\002\377h\246\002\377i\253\003\377h\243\001\377n\245\001" + "\377p\246\002\377q\253\001\377j\235\002\377f\236\002\377c\227\002\377b\231\001\377i\245" + "\001\377g\241\001\377n\256\005\377d\244\004\377f\240\002\377n\243\001\377s\246\001\377" + "m\242\001\377f\230\002\377h\233\002\377m\246\001\377m\245\001\377p\256\001\377m\251\002" + "\377m\247\001\377l\250\003\377v\267\014\377e\247\002\377a\243\001\377`\241\002\377c" + "\233\002\377l\244\001\377s\253\001\377r\252\001\377v\254\002\377o\244\001\377j\234\001" + "\377h\241\002\377o\256\001\377t\264\003\377t\261\002\377q\253\002\377l\246\001\377f\235" + "\002\377q\254\010\377[\230\001\377X\220\001\377[\227\001\377^\230\001\377_\225\001\377" + "a\231\001\377c\242\001\377_\232\002\377]\224\001\377\\\225\000\377e\243\010\377g\250" + "\010\377^\224\001\377e\236\002\377a\232\001\377e\237\004\377l\247\012\377c\236\002\377" + "e\235\001\377f\235\002\377i\242\002\377j\246\002\377k\253\003\377f\246\001\377k\252\002" + "\377j\244\001\377m\243\001\377r\255\002\377d\233\002\377Z\217\003\377W\214\001\377n\242" + "\001\377o\251\001\377g\245\001\377d\240\001\377g\234\002\377o\252\002\377t\255\002\377" + "v\256\001\377n\243\001\377Z\224\000\377\243\264}\377\344\326\333\377\366\352\356" + "\377\373\363\366\377\373\362\363\377\257\311\177\377N\210\000\377Y\225\002\377" + "[\227\001\377Y\226\001\377Z\235\001\377f\250\014\377x\264\034\377r\257\017\377d\237" + "\002\377q\254\002\377s\255\002\377k\236\002\377f\233\002\377d\240\001\377f\250\005\377" + "\\\235\002\377`\241\006\377Y\226\001\377_\237\001\377c\240\002\377d\236\001\377h\250" + "\003\377a\241\002\377]\237\003\377\\\236\002\377W\225\001\377_\233\002\377g\247\002\377" + "d\243\001\377[\222\001\377b\227\002\377i\236\002\377c\227\001\377e\232\000\377f\237\000" + "\377p\253\010\377g\250\003\377g\244\001\377l\243\001\377l\243\002\377o\254\001\377k" + "\251\001\377e\235\002\377d\241\001\377b\240\002\377a\234\002\377j\246\001\377i\237\001" + "\377m\250\001\377h\243\001\377j\243\002\377n\242\001\377p\244\001\377s\251\001\377l\237" + "\001\377m\241\001\377p\245\002\377r\247\001\377w\263\001\377s\250\001\377q\247\002\377" + "o\246\001\377v\267\006\377f\247\003\377]\240\001\377[\241\002\377\\\233\001\377l\246" + "\001\377s\253\002\377s\255\002\377q\254\003\377j\246\003\377c\235\002\377d\242\001\377" + "g\250\002\377k\251\003\377k\245\003\377t\255\011\377s\253\014\377l\247\013\377u\260" + "\021\377b\234\001\377`\234\001\377^\227\002\377a\231\002\377f\241\001\377f\237\001\377" + "i\243\001\377a\227\001\377X\217\000\377e\243\021\377~\271/\377a\233\001\377^\222\002" + "\377a\231\001\377g\235\003\377q\244\007\377m\241\002\377k\236\002\377m\232\001\377n\236" + "\001\377i\233\002\377_\222\001\377_\232\002\377d\246\002\377o\255\004\377r\252\001\377" + "}\261\001\377y\254\002\377^\223\002\377X\216\002\377T\212\002\377u\246\002\377w\255\002" + "\377e\237\001\377e\240\002\377i\237\002\377p\252\001\377h\234\002\377o\244\002\377l\242" + "\001\377V\222\000\377\331\320\317\377\324\303\313\377\341\317\331\377\363\345" + "\354\377\353\332\332\377\364\355\351\377y\251\061\377V\231\000\377W\221\001\377" + "_\236\004\377n\257\023\377p\257\023\377l\254\014\377p\260\013\377j\251\002\377p\252" + "\002\377s\245\001\377l\237\001\377b\226\001\377f\243\001\377j\254\003\377\\\236\001\377" + "Z\234\002\377Y\232\001\377]\234\001\377f\242\001\377e\234\001\377d\244\002\377`\243\002" + "\377_\243\005\377V\234\001\377T\220\002\377f\243\001\377k\250\001\377g\247\001\377]\224" + "\001\377_\227\000\377t\246\027\377\223\272G\377\242\277b\377u\215\071\377^\234" + "\011\377^\237\001\377f\240\002\377m\250\001\377m\252\001\377h\247\002\377d\245\001\377" + "c\243\001\377b\244\001\377]\234\001\377b\244\001\377e\244\002\377g\235\002\377p\251\001" + "\377m\244\001\377l\246\001\377j\237\002\377o\244\001\377t\255\001\377q\250\001\377q\252" + "\001\377p\244\002\377t\253\002\377y\263\002\377y\263\001\377u\253\001\377r\245\002\377" + "r\255\001\377h\240\001\377c\242\001\377e\247\004\377a\234\002\377q\252\002\377v\245\002" + "\377z\260\001\377n\254\004\377m\257\011\377d\243\001\377g\247\001\377g\245\001\377i" + "\245\001\377j\242\004\377~\263\032\377\203\263\040\377\202\264\037\377s\254\013\377" + "g\232\002\377n\245\007\377`\226\002\377`\224\002\377f\242\001\377f\240\001\377g\243\002" + "\377f\236\001\377f\240\003\377\205\274\071\377\200\270\066\377d\233\002\377b\230" + "\002\377`\226\002\377d\233\001\377z\260\015\377m\241\003\377g\226\001\377k\231\002\377" + "n\234\001\377j\234\001\377e\231\001\377`\225\001\377c\240\002\377e\243\002\377h\237\002" + "\377\210\267\001\377y\251\001\377_\225\002\377^\225\001\377X\214\001\377v\247\002\377" + "y\256\001\377e\232\002\377f\233\002\377m\240\002\377l\237\001\377]\221\002\377t\251\001" + "\377t\257\000\377k\233\034\377\326\301\315\377\313\267\301\377\347\331\343\377" + "\367\356\364\377\362\345\352\377\354\332\332\377\357\354\326\377W\221\011" + "\377Q\221\000\377c\244\011\377q\260\030\377e\246\006\377_\240\002\377[\225\002\377" + "d\244\001\377n\247\002\377u\251\001\377u\247\002\377k\236\002\377n\252\002\377l\251\002" + "\377Z\231\002\377T\225\002\377[\234\004\377a\236\003\377f\244\001\377f\240\001\377e\247" + "\002\377^\237\002\377Y\230\001\377U\221\001\377Y\225\002\377d\243\002\377d\241\002\377" + "b\240\002\377X\225\000\377\253\306s\377\363\357\336\377\353\335\332\377\345\326" + "\322\377\311\273\261\377e\212\"\377^\237\000\377d\240\001\377m\250\001\377l\246" + "\001\377b\232\002\377^\226\001\377^\236\001\377k\255\010\377o\257\007\377d\245\000\377" + "c\240\002\377g\234\002\377r\254\002\377k\243\002\377i\243\001\377e\234\001\377l\244\001" + "\377t\260\001\377v\257\001\377v\253\002\377o\240\002\377u\247\002\377z\262\001\377|\262" + "\001\377z\255\002\377r\243\003\377t\250\001\377n\243\002\377j\243\002\377p\254\003\377" + "l\245\001\377v\257\004\377q\245\001\377u\255\002\377t\263\007\377i\252\002\377e\245\002" + "\377o\254\001\377q\252\001\377o\247\002\377l\241\002\377{\257\014\377\204\263\027\377" + "\201\261\022\377r\252\004\377m\245\001\377f\236\002\377b\236\002\377d\236\002\377j\246" + "\002\377i\242\001\377i\242\001\377e\237\002\377t\255\030\377\220\304E\377u\254\032" + "\377g\233\001\377f\226\001\377f\225\001\377f\226\001\377p\252\001\377m\247\002\377c\230" + "\002\377d\232\001\377j\241\002\377g\233\002\377e\236\002\377d\236\001\377e\241\001\377" + "f\241\001\377d\227\002\377\201\261\002\377y\252\002\377g\233\001\377b\232\002\377c\230" + "\001\377u\250\002\377|\260\001\377j\236\002\377g\233\002\377i\235\002\377k\236\001\377" + "e\231\002\377{\256\001\377\203\273\014\377\230\262`\377\317\266\304\377\271\236" + "\252\377\350\332\344\377\360\342\353\377\341\316\325\377\335\307\307\377" + "\334\304\303\377\326\340\261\377`\236\020\377n\255\035\377x\267$\377e\244\004" + "\377a\233\001\377[\220\002\377e\235\001\377n\244\001\377q\245\002\377r\243\001\377t\254" + "\002\377s\253\001\377s\247\001\377d\234\001\377P\221\002\377m\251\003\377l\244\002\377" + "d\234\001\377e\244\003\377f\247\003\377`\240\002\377\\\222\002\377\\\225\002\377^\234" + "\002\377_\241\003\377X\227\000\377s\242\026\377\336\343\246\377\352\326\322\377" + "\335\306\276\377\330\303\273\377\314\271\260\377\330\311\306\377\236\245" + "r\377[\234\000\377g\250\002\377e\243\001\377d\234\001\377b\231\001\377_\230\001\377^" + "\224\001\377b\232\001\377l\251\004\377n\255\003\377h\242\001\377n\243\001\377w\261\001" + "\377m\241\002\377k\242\002\377e\233\001\377m\251\002\377u\260\002\377v\254\002\377u\250" + "\001\377q\243\002\377w\250\002\377}\264\002\377~\262\001\377z\255\001\377n\240\001\377" + "o\247\002\377n\245\002\377j\242\002\377n\252\002\377s\257\007\377n\250\003\377l\244\002" + "\377p\253\001\377s\260\003\377n\256\001\377l\246\001\377n\251\002\377t\256\001\377p\251" + "\001\377o\246\002\377|\262\014\377w\255\010\377m\246\001\377k\245\001\377h\243\002\377" + "e\243\001\377c\235\001\377f\236\001\377x\263\006\377n\242\002\377l\240\001\377d\234\001" + "\377d\231\014\377\212\301B\377l\253\020\377h\244\004\377i\240\002\377l\241\002\377" + "g\230\001\377r\253\001\377l\246\001\377h\247\002\377e\241\001\377o\250\004\377e\234\002" + "\377o\251\011\377s\256\017\377k\251\003\377h\240\001\377k\234\001\377\202\262\002\377" + "s\242\002\377h\234\001\377g\245\002\377j\250\002\377p\252\001\377x\260\002\377r\246\001" + "\377]\225\002\377g\235\003\377f\232\003\377q\244\002\377q\246\001\377i\245\000\377\245" + "\263z\377\321\275\307\377\323\277\313\377\360\345\357\377\362\347\360\377" + "\357\345\352\377\342\314\320\377\341\314\314\377\346\315\303\377\231\272" + "T\377i\246\014\377g\245\005\377_\224\002\377`\227\001\377d\230\001\377i\234\002\377" + "p\243\002\377o\241\002\377o\243\002\377s\255\001\377r\251\001\377q\245\002\377j\236\002" + "\377T\223\002\377v\255\002\377j\242\001\377e\241\002\377i\246\004\377f\242\001\377f\236" + "\002\377d\227\001\377g\235\001\377d\241\002\377]\234\002\377{\254\060\377\365\362\337" + "\377\332\305\277\377\317\265\260\377\330\302\275\377\322\274\272\377\311" + "\262\255\377\301\257\251\377\276\264\242\377U\223\002\377]\236\002\377a\237\002" + "\377c\234\002\377a\234\002\377a\236\002\377`\225\002\377c\237\001\377c\236\001\377g\244" + "\001\377l\243\002\377s\251\001\377{\263\001\377n\240\002\377m\241\001\377j\241\001\377" + "k\242\002\377o\251\001\377o\247\001\377k\243\002\377j\240\001\377p\251\001\377y\262\001" + "\377x\262\001\377x\262\001\377k\237\002\377j\242\001\377j\240\001\377j\243\002\377j\243" + "\002\377m\247\001\377k\242\002\377o\242\001\377s\252\002\377t\244\002\377t\255\002\377" + "u\257\001\377k\242\001\377m\247\001\377i\242\001\377l\245\001\377s\255\005\377f\241\001" + "\377e\243\001\377i\247\001\377g\243\001\377e\243\002\377d\232\001\377o\247\005\377u\261" + "\011\377n\250\001\377n\244\001\377l\242\001\377a\226\002\377u\260\021\377d\245\005\377" + "_\241\003\377k\252\022\377n\252\021\377k\243\005\377o\246\001\377t\252\002\377p\251" + "\001\377k\243\001\377m\244\002\377k\233\002\377j\235\002\377i\235\002\377q\253\003\377" + "y\257\001\377\177\255\001\377\207\264\001\377o\235\002\377e\231\002\377_\222\001\377" + "b\234\001\377m\252\001\377u\257\002\377j\234\002\377_\223\001\377n\242\001\377j\237\001" + "\377k\242\001\377c\227\002\377_\234\000\377\262\272\213\377\303\254\266\377\324" + "\277\312\377\336\316\333\377\372\365\371\377\355\341\351\377\344\321\331" + "\377\335\304\310\377\327\277\277\377\347\324\276\377t\247\025\377[\220\001\377" + "W\216\000\377a\233\000\377e\234\000\377k\241\000\377p\246\000\377n\243\001\377n\250\001" + "\377l\246\002\377n\246\002\377h\235\002\377d\236\001\377_\233\002\377z\260\001\377h\241" + "\002\377k\251\006\377x\261\031\377i\245\006\377i\246\003\377b\231\001\377c\235\002\377" + "^\233\000\377\217\265?\377\356\344\327\377\323\273\265\377\313\257\246\377" + "\341\315\315\377\334\313\315\377\307\263\265\377\266\240\235\377\272\244" + "\236\377\313\273\261\377Y\226\011\377_\237\003\377a\232\002\377_\226\001\377]\225" + "\002\377b\241\002\377c\237\001\377e\243\001\377c\234\002\377i\240\001\377k\236\002\377" + "{\255\002\377\201\263\001\377q\243\002\377q\250\002\377m\245\001\377l\240\001\377n\245" + "\001\377p\250\001\377h\235\002\377i\240\002\377q\254\002\377|\264\002\377}\263\002\377" + "z\263\001\377n\246\001\377m\245\001\377l\250\000\377h\245\000\377o\247\011\377\203\266" + "\035\377|\260\023\377n\241\001\377v\254\001\377x\247\002\377}\256\001\377|\257\001\377" + "s\246\002\377t\256\001\377n\245\001\377m\243\001\377j\243\001\377e\234\002\377e\237\002" + "\377j\243\001\377q\251\002\377c\236\001\377c\237\001\377k\251\004\377d\242\002\377o\255" + "\005\377o\250\002\377p\245\001\377o\243\003\377x\263\012\377g\241\000\377e\246\006\377" + "_\240\023\377d\243\024\377g\241\007\377w\255\005\377{\256\001\377z\261\001\377v\257" + "\003\377s\254\006\377o\243\005\377k\237\002\377h\234\001\377t\253\003\377\200\265\002\377" + "\211\264\001\377\214\263\002\377w\245\001\377k\242\002\377c\227\001\377_\227\001\377" + "i\247\001\377p\254\001\377d\225\002\377f\230\002\377o\241\002\377p\247\001\377h\235\002" + "\377a\230\001\377U\216\000\377\275\301\232\377\305\247\252\377\303\242\243\377" + "\347\325\335\377\364\352\362\377\364\347\357\377\346\323\334\377\312\257" + "\264\377\340\313\315\377\316\265\261\377\316\316\230\377Z\214\022\377a\225" + "\035\377`\222\"\377`\217&\377m\235\060\377p\242%\377e\235\016\377f\240\012\377" + "h\243\010\377c\234\004\377a\223\001\377d\237\001\377e\237\003\377x\261\005\377f\234" + "\000\377y\261\040\377r\257\040\377o\257\025\377f\247\005\377e\242\001\377e\235\001\377" + "\226\273G\377\354\336\316\377\304\251\245\377\342\311\310\377\327\301\277" + "\377\333\310\312\377\327\310\316\377\313\275\302\377\277\256\257\377\271" + "\246\242\377\320\276\270\377c\232\013\377d\241\001\377c\227\001\377c\226\002\377" + "h\241\001\377e\236\001\377o\256\010\377c\240\001\377a\231\001\377g\242\002\377j\235" + "\002\377}\257\001\377\201\263\001\377w\252\002\377q\246\001\377n\246\002\377l\240\002\377" + "r\247\001\377r\252\001\377f\232\002\377g\241\002\377v\260\001\377~\261\002\377{\252\001" + "\377|\257\001\377u\252\001\377v\263\010\377t\256\014\377x\263\031\377\211\300\063" + "\377\205\273+\377~\266\032\377r\254\002\377w\261\001\377|\262\002\377{\255\002\377" + "v\250\002\377p\237\001\377y\253\002\377z\252\002\377n\236\001\377l\237\001\377j\241\001" + "\377l\243\001\377n\241\002\377p\250\002\377g\235\002\377j\244\004\377m\250\006\377`\232" + "\001\377l\251\006\377r\251\001\377{\256\001\377p\241\001\377t\253\004\377o\241\001\377" + "m\246\003\377g\245\030\377Y\230\013\377l\246\027\377\211\273%\377\204\267\015\377" + "}\262\002\377}\266\012\377z\264\024\377v\260\031\377t\255\026\377\205\274'\377" + "\205\275\035\377t\246\001\377\201\255\001\377\221\271\001\377o\237\002\377f\231\002" + "\377i\242\001\377d\240\002\377d\243\002\377n\254\002\377j\235\001\377j\231\002\377r\241" + "\002\377s\245\001\377i\235\001\377a\230\002\377S\214\000\377\321\316\265\377\316\260" + "\263\377\332\277\306\377\366\352\362\377\373\367\372\377\365\355\362\377" + "\354\337\345\377\337\311\315\377\317\266\267\377\302\252\244\377\324\271" + "\253\377\270\266\232\377emd\377aff\377^bg\377_ej\377gpo\377o\200l\377s\216" + "b\377n\217M\377w\240G\377f\226(\377`\225\030\377b\225\021\377v\254\017\377k" + "\240\012\377o\246\031\377k\247\030\377h\244\017\377a\233\001\377i\244\000\377\272" + "\321u\377\331\304\264\377\301\241\227\377\302\242\232\377\337\313\310\377" + "\336\314\316\377\333\313\321\377\341\324\335\377\312\275\310\377\273\254" + "\265\377\274\251\253\377\310\266\262\377^\227\014\377c\236\002\377b\226\001\377" + "l\242\002\377r\253\001\377l\246\001\377j\252\004\377c\246\002\377`\230\001\377i\243\002" + "\377m\237\001\377\200\262\001\377w\247\002\377r\246\002\377p\247\001\377o\244\001\377" + "o\241\002\377v\256\002\377x\261\001\377e\233\002\377g\236\001\377u\251\002\377\200\263" + "\002\377z\252\001\377\206\272\016\377\203\271\031\377\211\301-\377\200\274-\377" + "o\256\027\377k\250\012\377p\255\004\377o\246\003\377m\241\001\377p\245\001\377q\246" + "\002\377r\253\002\377p\253\002\377m\236\001\377~\260\001\377|\251\001\377p\234\001\377" + "u\250\001\377s\250\001\377q\245\001\377u\252\000\377f\232\001\377r\251\006\377o\247\004" + "\377j\241\002\377g\237\001\377i\242\003\377q\250\002\377\205\265\001\377|\256\001\377" + "o\246\002\377i\237\001\377i\243\002\377m\252\035\377d\242\040\377o\254*\377l\250" + "\027\377\203\265\017\377\204\265\005\377z\255\002\377s\253\006\377v\261\027\377|\266" + "!\377~\267\040\377|\267\025\377s\250\000\377x\246\001\377\206\262\002\377z\252\002" + "\377i\234\001\377q\253\001\377l\246\001\377k\245\002\377o\252\001\377r\253\001\377o\240" + "\001\377u\250\001\377n\240\001\377j\234\002\377e\235\002\377Y\212\000\377\321\311\261" + "\377\321\266\271\377\327\277\307\377\347\325\337\377\345\323\336\377\336" + "\314\326\377\341\317\327\377\314\271\274\377\271\241\240\377\301\251\250" + "\377\212gf\377\306\250\226\377xei\377~ow\377XI\\\377XKa\377[Ne\377XLd\377" + "i`r\377tn}\377}{\206\377\201\206\210\377z\205x\377x\212d\377p\222C\377n\226" + "?\377f\225,\377l\236.\377d\227\034\377d\226\016\377\271\314q\377\301\254\237" + "\377\251\215\213\377\313\261\254\377\330\304\300\377\341\320\320\377\362" + "\350\353\377\324\306\316\377\346\332\343\377\311\274\306\377\275\251\262" + "\377\271\243\243\377\330\302\274\377i\237\022\377i\240\001\377o\241\002\377y\257" + "\001\377n\241\002\377a\227\001\377c\244\001\377g\251\003\377e\235\002\377p\246\001\377" + "t\245\002\377\202\263\001\377c\227\002\377f\235\001\377j\245\001\377m\245\002\377o\244" + "\001\377z\265\006\377x\262\003\377p\247\002\377p\243\001\377w\252\001\377|\264\000\377" + "\202\273\015\377\202\271\027\377v\261\016\377w\261\015\377]\225\000\377c\240\001" + "\377i\240\001\377q\244\001\377r\242\002\377x\251\002\377q\240\002\377p\241\001\377l\242" + "\002\377n\244\001\377y\253\001\377\201\262\001\377r\240\001\377s\241\002\377v\247\001\377" + "q\244\002\377q\246\002\377\201\271\012\377_\222\002\377l\246\004\377l\245\001\377m\241" + "\001\377q\251\001\377h\232\001\377s\250\002\377\177\260\001\377\201\263\004\377z\256" + "\020\377n\244\014\377g\237\003\377l\246\022\377p\254$\377i\247\034\377c\242\005\377" + "n\247\002\377z\253\002\377\200\260\002\377z\255\002\377u\257\006\377y\262\021\377\202" + "\273\036\377t\257\013\377u\254\002\377z\252\001\377z\247\002\377\202\261\001\377y\251" + "\002\377v\247\001\377t\250\001\377v\243\002\377\177\257\001\377v\251\001\377u\257\001\377" + "l\245\001\377h\235\002\377c\226\002\377d\231\001\377[\216\000\377\332\325\272\377\342" + "\307\311\377\335\302\307\377\361\345\354\377\375\375\374\377\367\354\363" + "\377\357\336\347\377\333\306\312\377\312\260\261\377\262\225\223\377\312" + "\260\252\377\317\264\254\377\307\256\254\377\332\306\306\377\321\277\304" + "\377\324\312\321\377\260\237\257\377wf\177\377\200m\211\377\233\215\242\377" + "\212|\222\377\207v\215\377\201r\206\377\226\207\220\377zoz\377e`h\377ggh" + "\377t|r\377\216\227\200\377\313\320\253\377\330\300\271\377\254\214\210\377" + "\312\260\254\377\330\303\277\377\323\272\272\377\357\337\343\377\332\311" + "\321\377\360\344\355\377\343\330\345\377\311\274\311\377\302\257\267\377" + "\305\260\257\377\320\277\264\377^\232\002\377m\243\001\377x\253\002\377z\252\002" + "\377m\235\002\377h\240\001\377f\246\001\377d\241\002\377j\250\001\377s\253\001\377x\252" + "\001\377~\255\002\377`\223\002\377a\225\001\377d\244\001\377l\252\001\377p\252\001\377" + "t\257\002\377q\247\001\377v\256\002\377v\250\001\377\200\267\002\377\201\270\002\377" + "\201\271\006\377s\251\002\377q\251\002\377\200\271\013\377e\232\001\377f\233\001\377" + "l\243\001\377i\231\002\377o\240\002\377|\255\002\377y\246\002\377r\237\002\377n\236\001" + "\377u\250\001\377{\260\002\377z\250\001\377w\241\002\377{\251\001\377w\250\001\377s\247" + "\001\377y\260\001\377\202\271\005\377_\223\002\377i\243\002\377p\251\003\377n\237\001\377" + "p\247\001\377l\236\001\377r\244\002\377x\252\001\377\177\261\003\377\210\267\027\377" + "\221\276\060\377m\243\006\377j\243\003\377w\260\030\377d\243\015\377l\253\017\377" + "s\255\006\377r\243\002\377v\253\001\377}\266\014\377k\247\005\377s\255\023\377\204" + "\274(\377l\240\012\377q\240\002\377~\255\001\377{\250\002\377x\245\001\377\177\261" + "\001\377y\250\002\377x\245\002\377|\247\002\377\204\260\001\377t\243\002\377p\247\002\377" + "l\247\001\377d\236\002\377Y\215\001\377]\216\001\377X\217\000\377\326\320\262\377\343" + "\303\302\377\354\321\325\377\341\321\333\377\363\353\360\377\353\335\344" + "\377\360\335\337\377\340\312\307\377\337\310\304\377\347\317\312\377\353" + "\326\323\377\345\323\321\377\360\341\346\377\361\347\354\377\365\360\363" + "\377\366\356\363\377\344\327\341\377\340\322\334\377\346\332\343\377\370" + "\361\365\377\360\351\355\377\336\322\333\377\303\256\267\377\322\276\275" + "\377\312\262\255\377\271\233\220\377\260\223\214\377\232\202\177\377\263" + "\235\226\377\301\241\220\377\215oh\377\272\233\226\377\307\256\252\377\333" + "\310\310\377\347\327\333\377\364\352\360\377\327\307\321\377\337\320\332" + "\377\325\310\326\377\303\263\300\377\277\251\260\377\305\253\252\377\310" + "\274\253\377_\240\000\377h\241\001\377k\232\002\377z\251\002\377t\245\001\377n\244" + "\001\377g\241\001\377f\236\001\377m\246\001\377u\254\002\377|\254\002\377w\247\001\377" + "[\216\002\377^\223\001\377k\250\003\377u\264\004\377r\250\001\377t\252\001\377t\251\001" + "\377u\256\001\377u\252\001\377{\260\001\377~\257\001\377\204\264\001\377\203\263\002" + "\377\200\263\002\377|\264\003\377a\223\002\377j\242\002\377k\245\002\377m\244\001\377" + "w\255\001\377t\245\002\377u\242\002\377w\246\002\377w\247\001\377v\254\001\377o\242\001" + "\377r\244\002\377\216\271\016\377\210\264\011\377{\255\002\377\177\267\002\377\201" + "\270\002\377~\261\002\377b\237\001\377c\233\002\377h\243\002\377o\252\004\377p\250\001" + "\377o\246\002\377q\251\003\377t\245\001\377~\256\001\377\207\265\020\377\226\277." + "\377l\240\003\377o\246\003\377t\255\007\377f\244\005\377r\257\022\377m\243\003\377|" + "\257\002\377w\255\001\377m\247\003\377\213\270\064\377p\251\033\377\200\271)\377" + "d\231\003\377k\235\002\377r\251\002\377r\251\002\377o\243\001\377u\253\000\377x\257\001" + "\377v\255\001\377w\256\001\377u\254\002\377m\241\001\377n\237\001\377r\255\002\377`\226" + "\002\377Y\214\002\377_\220\002\377[\220\000\377\305\307\223\377\344\277\273\377\352" + "\313\312\377\337\312\322\377\356\341\351\377\354\336\340\377\362\337\334" + "\377\362\343\337\377\367\356\355\377\342\316\317\377\277\245\247\377\344" + "\320\324\377\365\356\360\377\271\244\260\377\364\355\360\377\324\300\314" + "\377\342\322\334\377\360\346\354\377\357\345\352\377\305\263\272\377\352" + "\340\344\377\353\336\341\377\314\262\262\377\334\305\301\377\347\323\315" + "\377\342\313\302\377\334\300\263\377\343\303\257\377\333\274\243\377\303" + "\242\212\377\312\254\232\377\306\251\237\377\277\244\236\377\354\331\332" + "\377\332\305\314\377\356\342\352\377\332\311\323\377\320\276\311\377\306" + "\266\302\377\276\254\265\377\302\252\256\377\321\270\263\377\267\270\214" + "\377b\244\000\377i\243\001\377j\232\001\377|\255\002\377w\253\002\377l\235\001\377h\231" + "\002\377i\235\001\377l\237\001\377u\251\002\377{\256\001\377k\233\002\377Z\216\001\377" + "m\250\010\377x\266\014\377p\252\002\377r\246\002\377s\247\002\377v\260\002\377}\270" + "\013\377u\253\002\377{\256\001\377|\255\002\377z\246\002\377\203\261\002\377\216\275" + "\002\377u\260\002\377c\225\002\377l\245\001\377q\255\002\377o\247\001\377x\264\002\377" + "j\240\001\377k\235\002\377m\236\002\377t\251\001\377s\254\002\377n\245\001\377o\243\001" + "\377\221\302\036\377\203\266\011\377\202\267\003\377\202\272\004\377|\260\001\377" + "|\254\002\377g\243\002\377h\241\002\377e\236\001\377\200\270\016\377w\257\004\377o\247" + "\002\377q\250\002\377r\246\001\377|\253\002\377\211\267\016\377\177\255\013\377p\242" + "\003\377k\242\001\377i\243\002\377k\251\005\377j\250\005\377k\243\003\377u\254\001\377" + "r\246\001\377\204\256\022\377\260\277]\377q\250\"\377\202\271/\377n\246\006\377" + "o\245\001\377o\245\001\377u\254\012\377z\261\025\377r\254\015\377l\251\006\377k\247" + "\002\377u\262\011\377o\255\006\377i\244\001\377l\245\001\377l\245\002\377g\240\001\377" + "c\227\001\377^\217\002\377V\215\000\377\255\271x\377\340\270\262\377\337\275\266" + "\377\360\334\334\377\360\340\342\377\361\342\341\377\340\313\315\377\355" + "\340\341\377\370\360\360\377\272\230\234\377\311\255\261\377\367\355\356" + "\377\334\310\312\377\344\323\331\377\345\330\336\377\320\276\311\377\352" + "\334\345\377\330\307\315\377\261\233\243\377\317\276\276\377\375\373\373" + "\377\336\314\316\377\221wx\377\317\272\264\377\347\333\327\377\353\333\327" + "\377\353\326\314\377\336\303\261\377\344\304\254\377\351\314\262\377\357" + "\327\302\377\325\273\254\377\332\304\274\377\346\324\325\377\326\302\312" + "\377\353\335\350\377\350\334\345\377\275\254\270\377\276\255\273\377\274" + "\250\263\377\317\267\271\377\340\304\303\377\235\261_\377a\237\000\377f\233" + "\002\377u\251\002\377\204\265\002\377o\237\001\377l\236\001\377i\232\001\377k\241\002\377" + "o\244\001\377p\246\001\377p\246\001\377i\231\001\377l\242\006\377y\264\022\377p\254" + "\003\377q\255\001\377o\245\001\377q\253\001\377u\262\004\377m\246\002\377n\245\001\377" + "z\257\001\377w\246\002\377i\223\002\377|\255\001\377\206\272\003\377\200\272\023\377" + "h\234\000\377k\242\002\377w\262\002\377\201\271\001\377o\252\002\377q\260\014\377i\241" + "\002\377o\243\001\377s\247\001\377t\254\001\377p\243\001\377v\246\001\377\212\275\004\377" + "\206\272\003\377\177\264\001\377|\257\001\377w\245\002\377u\247\001\377b\235\002\377" + "e\237\002\377d\231\002\377\200\267\022\377z\260\011\377i\237\001\377k\244\002\377s" + "\253\003\377u\251\001\377~\261\005\377q\247\001\377g\235\001\377h\235\002\377e\231\001" + "\377j\246\004\377g\245\005\377f\245\010\377g\244\005\377c\231\001\377\241\263,\377" + "z\244\036\377e\241\016\377u\260\027\377w\254\010\377w\244\001\377\204\264\021\377" + "\213\272%\377\203\265\032\377i\242\005\377Z\224\001\377b\233\001\377r\253\001\377" + "s\247\001\377l\240\001\377h\243\001\377d\234\002\377f\241\001\377f\236\005\377\\\216" + "\006\377W\205\023\377\224\243p\377\352\310\312\377\312\245\250\377\356\330\330" + "\377\355\334\332\377\332\312\307\377\307\263\261\377\370\362\362\377\320" + "\272\266\377\247zr\377\365\341\335\377\344\315\313\377\335\311\311\377\343" + "\326\334\377\345\327\340\377\351\333\343\377\360\346\354\377\355\343\350" + "\377\336\317\320\377\374\361\357\377\374\367\365\377\334\307\277\377\207" + "g[\377\303\246\233\377\367\360\355\377\333\312\305\377\311\262\254\377\331" + "\301\267\377\342\310\267\377\316\261\236\377\341\305\261\377\363\346\327" + "\377\356\340\327\377\356\341\336\377\345\325\330\377\342\323\334\377\325" + "\306\324\377\267\245\265\377\274\251\271\377\277\255\272\377\337\306\304" + "\377\347\315\306\377p\236\026\377e\234\000\377j\241\001\377{\262\001\377\200\264" + "\001\377t\245\002\377o\242\002\377k\243\001\377q\256\002\377p\256\001\377m\247\001\377" + "p\251\001\377q\246\002\377\202\264\011\377m\241\004\377m\246\001\377m\252\002\377g\242" + "\002\377k\247\002\377k\250\002\377d\234\002\377t\253\003\377\177\264\002\377w\247\001\377" + "p\240\001\377z\257\002\377y\256\001\377\177\270\010\377r\244\002\377|\256\001\377\206" + "\273\002\377\203\263\000\377q\246\002\377u\261\011\377k\247\001\377p\251\001\377t\247" + "\001\377u\254\001\377z\253\001\377\205\271\001\377\205\272\002\377~\263\001\377|\260" + "\001\377}\260\002\377r\243\001\377s\247\002\377_\236\004\377c\232\001\377l\243\001\377" + "u\254\006\377~\265\024\377b\232\002\377e\237\001\377m\253\007\377r\255\005\377y\263" + "\011\377i\242\002\377e\235\003\377d\232\002\377i\235\001\377q\252\006\377k\246\004\377" + "b\236\002\377k\245\013\377h\234\002\377\213\247\015\377e\227\000\377c\232\002\377h" + "\241\003\377v\255\007\377u\252\002\377x\256\011\377k\240\002\377~\261\001\377t\247\001" + "\377]\226\001\377b\232\001\377m\247\001\377u\251\001\377v\253\001\377n\245\001\377c\235" + "\002\377g\244\024\377i\234*\377b\220\063\377r\212b\377\200\203~\377\340\303\304" + "\377\347\325\324\377\371\363\363\377\366\356\357\377\352\331\336\377\365" + "\353\354\377\376\373\372\377\344\323\313\377\252\200i\377\354\322\310\377" + "\340\310\305\377\341\315\317\377\353\336\345\377\341\322\334\377\350\333" + "\343\377\346\330\340\377\332\311\322\377\323\277\301\377\332\302\277\377" + "\361\335\321\377\341\307\265\377\204V;\377\330\275\252\377\375\375\375\377" + "\354\340\334\377\270\234\233\377\302\246\243\377\317\270\261\377\351\323" + "\304\377\340\305\257\377\347\314\272\377\365\352\341\377\353\340\332\377" + "\373\364\363\377\360\350\353\377\303\255\274\377\263\235\255\377\267\242" + "\261\377\315\262\270\377\336\275\257\377\316\277\230\377h\237\005\377i\237" + "\001\377u\261\006\377s\252\002\377|\263\001\377x\253\002\377v\262\002\377q\254\002\377" + "u\256\004\377|\261\003\377v\252\001\377r\252\001\377r\253\002\377n\242\001\377i\232\002" + "\377n\245\002\377p\252\001\377l\247\002\377k\250\001\377l\251\001\377l\245\002\377u\254" + "\003\377\200\265\004\377s\242\001\377r\241\001\377v\256\001\377u\255\004\377\216\304" + "&\377x\255\016\377\224\303\034\377\204\265\000\377\177\260\001\377{\255\001\377y" + "\257\002\377n\247\002\377s\253\002\377x\254\002\377\177\263\001\377\207\273\001\377\207" + "\274\002\377\203\271\001\377}\260\001\377\203\272\003\377{\256\001\377o\242\001\377t" + "\253\001\377r\254!\377f\232\000\377q\246\001\377q\247\001\377\202\271\031\377m\252" + "\007\377c\241\003\377`\233\002\377i\247\003\377v\262\014\377n\246\002\377e\235\002\377" + "l\244\010\377o\247\007\377r\252\005\377i\241\002\377\\\225\001\377p\247\027\377p\241" + "\014\377u\243\011\377j\230\001\377k\241\002\377r\252\012\377o\251\007\377j\236\001\377" + "n\240\002\377x\250\001\377\200\255\002\377\203\262\001\377Y\224\001\377Y\225\001\377" + "d\241\001\377j\237\001\377o\241\001\377q\246\001\377i\242\002\377h\242\030\377r\222" + "g\377{\214\200\377\177\201\210\377\254\235\246\377\346\321\315\377\374\367" + "\365\377\370\361\361\377\277\250\250\377v[\\\377}db\377\272\241\225\377\363" + "\325\307\377\345\301\253\377\364\332\313\377\343\306\275\377\344\313\311" + "\377\336\311\316\377\334\313\323\377\344\322\332\377\316\271\301\377\326" + "\302\305\377\315\266\264\377\323\273\260\377\353\320\301\377\316\255\226" + "\377\216dD\377\372\357\346\377\365\354\347\377\344\330\326\377\331\307\307" + "\377\352\332\327\377\320\267\261\377\340\311\274\377\331\277\250\377\335" + "\301\255\377\361\341\323\377\346\322\311\377\361\341\337\377\346\324\327" + "\377\317\273\306\377\263\233\250\377\274\240\246\377\340\276\263\377\352" + "\306\275\377\231\261I\377m\247\002\377i\237\001\377n\250\001\377q\243\001\377\177" + "\265\001\377z\256\002\377r\253\001\377o\237\001\377t\243\002\377\201\257\004\377\205" + "\270\012\377y\261\010\377p\252\007\377c\234\002\377d\234\002\377s\255\001\377v\252" + "\002\377z\261\001\377s\253\001\377p\250\002\377r\251\002\377}\263\007\377~\263\002\377" + "|\255\003\377z\247\001\377\201\262\001\377z\257\002\377\200\271\037\377\210\276\063" + "\377\230\310>\377z\256\000\377{\254\001\377r\240\002\377r\246\001\377r\255\001\377" + "u\261\002\377v\256\003\377\205\271\001\377\213\276\001\377~\254\001\377\201\263\002\377" + "\202\265\002\377\207\275\006\377{\256\001\377s\252\001\377n\247\001\377|\265\061\377" + "m\250\013\377f\234\001\377o\250\006\377\204\272\040\377\177\266\033\377r\253\017" + "\377l\245\012\377q\251\014\377z\261\016\377r\255\010\377d\237\001\377j\245\010\377" + "{\260\024\377|\257\022\377s\246\020\377U\221\005\377h\235\020\377x\247\023\377r" + "\243\014\377f\234\002\377o\247\001\377p\251\001\377l\241\001\377q\245\001\377t\242\002" + "\377\200\262\003\377s\241\002\377x\252\003\377k\240\006\377c\230\001\377d\232\002\377" + "j\244\001\377j\241\002\377g\236\002\377j\244\010\377s\240\060\377|\204\205\377||" + "\207\377\212\177\220\377\326\307\313\377\370\353\351\377\372\361\354\377" + "\310\244\224\377O/$\377#\011\003\377#\014\005\377\024\000\000\377D%\035\377\330\261\233" + "\377\377\366\347\377\342\314\300\377\333\277\272\377\333\305\305\377\330" + "\276\302\377\276\247\255\377\276\250\251\377\275\243\240\377\337\307\274" + "\377\360\334\316\377\355\323\277\377\325\260\221\377\343\303\251\377\306" + "\257\237\377\251\221\210\377\266\241\235\377\274\250\251\377\343\326\327" + "\377\350\330\327\377\337\312\303\377\334\301\264\377\323\272\246\377\324" + "\271\247\377\372\362\354\377\357\337\340\377\315\266\303\377\266\233\254" + "\377\257\220\234\377\323\253\236\377\341\270\250\377\333\306\255\377f\235" + "\000\377m\247\002\377l\244\001\377q\254\001\377v\256\001\377x\262\002\377o\247\001\377" + "j\243\001\377o\242\001\377q\237\001\377u\243\001\377v\254\002\377\200\271\033\377~\267" + "-\377f\237\004\377e\233\001\377s\252\002\377|\257\001\377~\260\001\377|\256\001\377y" + "\254\001\377}\257\001\377\203\270\005\377}\260\001\377}\256\002\377\202\263\001\377\177" + "\256\002\377t\247\006\377\207\300/\377\205\276\066\377\210\302\062\377r\252\000\377" + "s\254\001\377s\255\003\377m\250\001\377p\255\004\377n\247\002\377\203\266\001\377\211" + "\275\002\377\205\267\001\377|\252\002\377\201\264\001\377\204\267\006\377|\257\001\377" + "{\260\002\377v\262\002\377q\251\001\377~\267!\377u\260\015\377`\225\001\377c\234\003" + "\377\201\266\031\377\214\276'\377\200\265\034\377\177\264\036\377\200\265\035" + "\377\200\266\030\377t\256\011\377c\235\002\377_\230\001\377d\241\002\377w\254\034" + "\377w\251)\377V\217\016\377p\241\030\377\205\255\034\377\217\265,\377o\245\020" + "\377t\256\012\377q\254\002\377m\244\002\377w\253\006\377~\257\004\377x\255\003\377m" + "\246\010\377m\245\015\377\202\263\"\377\203\262$\377d\226\002\377g\240\003\377" + "X\215\001\377f\235\024\377d\226'\377\177\217w\377\200|\207\377\210}\216\377" + "\241\225\237\377\362\342\333\377\350\321\313\377\355\323\306\377\236n[\377" + "\071\025\020\377bA+\377\320\307\317\377\244\241\267\377bLB\377M(\032\377\376" + "\354\330\377\355\317\271\377\373\352\337\377\326\254\225\377\347\315\277" + "\377\323\270\260\377\340\305\272\377\325\270\254\377\372\365\357\377\375" + "\372\364\377\365\336\307\377\300\241\207\377;&\035\377\034\006\014\377\"\012\017" + "\377\040\010\010\377<&#\377\234\204}\377\346\326\324\377\350\332\326\377\310" + "\256\241\377\352\322\302\377\334\303\256\377\363\344\327\377\371\363\360" + "\377\340\317\325\377\267\234\254\377\323\262\264\377\345\277\264\377\354" + "\317\304\377pdJ\377s\255\000\377q\246\001\377w\255\003\377z\266\005\377s\256\001\377" + "u\261\005\377i\243\002\377l\241\002\377x\253\001\377{\251\002\377~\253\002\377z\253\001" + "\377n\246\002\377n\252\034\377\207\275\071\377j\240\002\377{\257\001\377\207\267" + "\002\377\177\255\001\377z\255\002\377|\261\002\377~\260\001\377\212\276\010\377|\257" + "\002\377{\254\001\377\203\270\004\377y\257\007\377t\256\016\377\217\306?\377\207\300" + ";\377x\265\036\377r\261\001\377p\254\001\377r\260\007\377m\254\007\377i\243\001\377" + "~\260\001\377\230\303\002\377\177\256\001\377u\246\001\377t\252\004\377\212\301\036" + "\377\211\300\034\377y\260\003\377{\257\001\377z\261\001\377s\250\001\377x\257\013\377" + "|\262\005\377e\236\002\377c\235\003\377w\255\011\377\206\270\030\377}\262\017\377" + "s\253\011\377g\237\002\377z\260\022\377~\264\025\377e\240\006\377^\234\003\377b\237" + "\004\377h\240\011\377e\236\021\377R\217\006\377a\230\016\377\221\264\063\377\204" + "\252*\377{\253\"\377v\255\031\377c\240\006\377m\251\022\377\203\266\"\377\210" + "\267\026\377w\250\002\377m\242\002\377r\251\013\377\203\266)\377\206\265\063\377" + "Y\211\001\377h\236\025\377h\232\034\377w\243<\377x\222\\\377\201{\206\377\205" + "w\210\377\226\203\223\377\301\260\265\377\267\237\245\377\372\362\357\377" + "\371\361\355\377\241\200}\377nP:\377\273\253|\377\307\305\260\377\247\251" + "\276\377\322\322\324\377\204dN\377\374\342\313\377\366\327\271\377\366\323" + "\260\377\366\320\252\377\370\334\276\377\366\335\306\377\357\317\275\377" + "\367\342\321\377\375\377\375\377\372\352\330\377\225t\\\377\065\034\015\377" + "B/'\377\201u{\377ymy\377^H@\377/\023\014\377\067\033\025\377\251\210u\377\333" + "\311\300\377\350\327\314\377\315\260\242\377\332\301\263\377\354\336\321" + "\377\340\317\310\377\351\331\331\377\322\261\262\377\353\303\261\377\373" + "\343\326\377\254zc\377\201tC\377y\261\011\377w\250\001\377{\257\003\377z\263\004" + "\377v\260\002\377t\257\001\377q\253\001\377o\246\002\377w\253\001\377\177\260\000\377" + "\177\255\001\377w\247\001\377n\245\001\377b\233\001\377\202\271)\377w\256\007\377~" + "\261\001\377\204\265\001\377\177\260\001\377{\254\001\377\200\262\001\377\200\262\001" + "\377\211\300\010\377u\250\002\377t\245\001\377\202\271\001\377}\267\015\377\204\276" + "\071\377\244\325`\377\217\307C\377x\265\024\377t\262\003\377y\265\006\377|\266" + "\015\377p\247\002\377s\251\001\377\206\270\002\377\204\264\002\377x\253\000\377{\262" + "\023\377\222\306=\377~\272\025\377q\252\001\377u\246\001\377~\257\001\377}\263\001" + "\377v\253\001\377p\247\007\377\202\265\007\377g\242\012\377c\240\007\377r\253\007\377" + "y\260\012\377\202\265\022\377p\247\006\377d\235\003\377v\255\025\377\200\266\036" + "\377t\254\017\377q\251\016\377b\231\001\377c\231\002\377^\233\007\377J\214\003\377" + "X\225\017\377\204\257\061\377e\226\024\377l\237\035\377v\252(\377`\233\016\377" + "t\257'\377|\262\"\377}\260\025\377r\243\002\377m\237\002\377r\247\005\377\203\270" + "\040\377w\255\031\377Y\222\005\377k\240#\377l\235/\377v\225^\377\200\200\211" + "\377~v\203\377\213|\215\377\234\210\225\377\271\241\246\377\321\272\270\377" + "\344\316\314\377\363\354\354\377\336\322\321\377\231\210\201\377\334\330" + "\273\377\346\340\303\377\355\347\314\377\333\321\303\377\265\225\215\377" + "\362\320\274\377\366\331\276\377\364\321\262\377\363\310\250\377\362\313" + "\250\377\362\316\261\377\365\326\303\377\341\271\251\377\357\320\265\377" + "tTJ\377)\031\034\377kZ\070\377\275\265\237\377\315\316\341\377\265\266\322\377" + "\325\327\333\377\213mK\377\067\031\024\377tL?\377\313\252\231\377\307\251\224" + "\377\262\217{\377\320\263\242\377\343\315\301\377\346\330\322\377\332\302" + "\303\377\306\225\203\377\330\246\211\377\371\343\323\377\303\242\223\377" + "\225\224y\377x\246-\377u\246\012\377\177\263\013\377{\257\004\377z\255\002\377" + "z\263\002\377x\263\003\377p\251\001\377s\253\001\377z\254\001\377}\255\001\377x\247\001" + "\377q\242\001\377u\247\001\377{\260\003\377~\263\003\377\177\261\001\377\203\264\001" + "\377\202\263\001\377~\250\002\377\203\263\002\377\201\264\001\377\205\273\004\377u" + "\250\001\377z\253\002\377\202\265\002\377\202\270\007\377\214\304K\377\227\314W\377" + "{\272$\377r\261\006\377r\255\001\377u\260\003\377t\254\002\377\177\257\001\377\212" + "\275\002\377\177\262\001\377v\256\001\377w\262\012\377\224\310D\377\213\300\064\377" + "u\260\000\377u\254\001\377z\256\001\377~\255\001\377\201\266\001\377v\250\002\377v\244" + "\002\377\207\267\022\377o\250!\377W\225\003\377h\245\016\377l\247\014\377|\263\037" + "\377m\247\024\377l\247\033\377j\246\026\377\200\266%\377n\243\006\377\203\264" + "\037\377l\241\013\377b\233\007\377^\233\023\377P\220\004\377Z\231\005\377n\243\017" + "\377y\253\036\377n\237\025\377x\253,\377d\234\034\377\205\274;\377{\262\"\377" + "x\256\032\377`\226\003\377[\213\001\377V\210\001\377y\254\060\377\177\264!\377\177" + "\263(\377{\253=\377}\247V\377\177\213\177\377\203~\213\377\207{\214\377\214" + "|\215\377\305\257\260\377\256\221\221\377\301\243\231\377\275\235\220\377" + "\303\247\236\377\325\300\273\377\353\334\332\377\370\364\360\377\374\373" + "\365\377\364\346\330\377\326\254\235\377\310\241\242\377\332\262\261\377" + "\341\273\274\377\333\265\267\377\314\247\254\377\266\216\217\377\323\240" + "\177\377\341\301\262\377\277\232\225\377\220b_\377oCF\377\213cP\377\264\241" + "\202\377\333\324\262\377\330\327\312\377\341\342\331\377\343\345\333\377" + "\276\270\235\377\202hW\377\347\332\321\377\364\351\340\377\312\262\250\377" + "\245\215\203\377\317\265\247\377\324\274\261\377\344\322\313\377\347\324" + "\320\377\305\236\216\377\331\271\247\377\345\311\267\377\264\201l\377\223" + "\220\221\377\207\237|\377v\242\067\377t\251\017\377r\250\002\377p\246\001\377r" + "\257\002\377m\251\004\377q\256\002\377x\256\001\377y\245\002\377}\251\001\377|\252\001" + "\377{\247\002\377{\252\002\377{\253\001\377\177\266\003\377\207\301\015\377\207\273" + "\005\377\203\262\000\377\201\254\001\377\200\257\002\377~\261\001\377\177\270\003\377" + "t\247\001\377{\254\001\377\201\265\002\377\227\311/\377\203\273\063\377\210\301" + "E\377v\265\037\377q\255\003\377s\255\001\377u\261\002\377}\265\001\377\214\276\001\377" + "\203\264\001\377w\254\001\377y\267\016\377\233\320R\377\203\273)\377s\257\005\377" + "r\256\001\377w\261\001\377v\245\001\377~\256\001\377~\257\001\377{\252\002\377\221\263" + "\001\377\200\256\012\377\203\267D\377V\225\016\377Y\230\014\377_\236\016\377s\256" + "&\377y\256\032\377\206\270\040\377q\246\006\377}\263\033\377r\250\022\377~\262" + ".\377\210\272G\377\214\275U\377\204\270N\377Z\226\007\377Z\223\001\377a\231\001" + "\377t\247\030\377y\247\036\377y\252'\377m\242!\377\177\266.\377i\240\011\377" + "v\254#\377`\227\020\377U\206\001\377Q\177\004\377\210\264X\377r\245\023\377\205" + "\266$\377~\254\071\377{\226n\377{v\204\377\214\177\216\377\214{\213\377\270" + "\242\251\377\260\224\225\377\260\217\216\377\303\241\230\377\324\265\253" + "\377\323\265\260\377\326\272\266\377\340\310\304\377\354\326\320\377\360" + "\340\330\377\357\334\314\377\366\335\302\377\302|]\377\304\177a\377\362\253" + "\227\377\365\263\241\377\314\222\202\377\231f`\377\227fW\377\310\240\211" + "\377\261\220\206\377\265\224\215\377\246gY\377\376\354\315\377\373\367\346" + "\377\356\352\330\377\357\350\323\377\360\350\325\377\347\340\321\377\324" + "\316\307\377\366\361\360\377\351\335\335\377\343\321\317\377\353\332\327" + "\377\240\214\214\377t]_\377\326\301\274\377\343\323\315\377\330\305\277\377" + "\257\214\201\377\265\224\202\377\241xa\377\216jW\377\201y\201\377\235\240" + "\247\377\224\251\207\377{\253\063\377n\244\015\377r\253\010\377q\255\007\377f" + "\236\005\377l\242\001\377{\256\002\377y\245\001\377\201\255\001\377~\250\001\377|\251" + "\001\377z\250\001\377|\254\001\377\203\274\013\377\207\300\032\377\200\270\013\377" + "\203\272\013\377\200\262\002\377x\250\001\377x\255\001\377q\245\001\377p\240\001\377" + "\202\264\017\377\233\312\062\377z\257\005\377{\263\026\377g\245\034\377\177\272" + "\067\377s\260\016\377u\260\002\377y\263\002\377\200\266\002\377\206\270\002\377|\254" + "\002\377t\251\002\377w\263\013\377\211\277(\377\217\302)\377|\263\012\377x\262" + "\001\377u\254\002\377u\252\002\377z\257\002\377}\262\001\377\177\261\002\377\234\277" + "\002\377\177\261\027\377\210\274K\377a\234'\377m\244\061\377~\264\065\377\210" + "\272\013\377\222\276\003\377\210\263\001\377\177\256\017\377\203\264\033\377u\251" + "\011\377p\246\011\377x\257\022\377\202\266\034\377v\253\012\377h\236\001\377g\236" + "\002\377h\243\002\377r\251\013\377v\254\017\377s\252\021\377~\265)\377{\263'\377" + "^\227\004\377n\243\035\377\177\264\070\377[\213\003\377Nx\007\377t\246\070\377n\240" + ")\377\177\253\064\377\203\245R\377\215\224\221\377\202v\202\377\217\200\217" + "\377\251\227\237\377\317\273\274\377\246\221\226\377\302\252\251\377\340" + "\306\277\377\355\327\317\377\324\271\261\377\306\250\244\377\340\313\305" + "\377\354\331\316\377\361\341\324\377\375\374\366\377\372\365\357\377\215" + "fX\377X%\033\377\310eE\377\260aF\377\033\005\001\377@\033\013\377B\034\027\377\316" + "\237~\377\361\323\265\377\366\347\324\377\326\265\253\377\354\332\316\377" + "\366\351\343\377\361\344\336\377\356\337\330\377\341\320\311\377\326\301" + "\271\377\311\260\251\377\243\204\200\377\211lg\377\252\217\210\377\301\252" + "\236\377\301\255\242\377ubd\377\262\234\226\377\336\313\304\377\343\317\311" + "\377\242\206\204\377mUK\377Q\065$\377`J=\377\203v\203\377\237\230\246\377" + "\246\245\255\377\237\254\227\377\206\250R\377r\242\021\377~\260\021\377n\240" + "\003\377q\241\001\377z\257\001\377x\246\002\377~\257\001\377t\242\001\377s\246\001\377" + "|\261\002\377\204\267\004\377\205\270\004\377\216\300\027\377{\257\006\377\201\267" + "\017\377~\262\001\377}\257\001\377{\255\002\377s\243\000\377~\264\023\377\242\317a" + "\377\212\270\036\377|\256\000\377m\243\002\377\\\226\000\377w\264\040\377\200\273" + "%\377}\265\006\377\203\271\001\377\203\265\001\377|\255\001\377w\250\002\377u\253\001" + "\377s\255\001\377{\266\005\377~\264\005\377}\260\001\377\177\263\001\377v\247\002\377" + "s\247\002\377z\253\001\377\202\261\000\377}\254\002\377\231\276\001\377\200\267\062" + "\377q\251#\377\205\272/\377\225\305!\377\210\272\011\377x\255\002\377r\246\001" + "\377}\257\013\377\207\270%\377\204\265\030\377\201\263\013\377\215\274\032\377" + "\233\307\065\377\227\304/\377\217\300\"\377\202\264\014\377r\251\000\377q\255" + "\014\377{\265(\377k\245\025\377e\235\021\377\206\272<\377\213\276G\377i\241" + "\035\377l\240\033\377\225\303V\377o\240\037\377i\223%\377z\243K\377\201\247" + "Y\377\201\244a\377\212\230\210\377\212\204\217\377\237\222\236\377\212y\204" + "\377\266\243\245\377\313\267\264\377\253\216\221\377\272\240\233\377\266" + "\234\224\377\265\235\225\377\271\241\235\377\306\255\250\377\364\346\333" + "\377\336\276\261\377\361\330\305\377\371\365\361\377\375\375\376\377\377" + "\377\377\377\240ru\377\230J\067\377\232VD\377uZc\377\320\300\276\377\375\365" + "\353\377\377\377\370\377\372\363\340\377\370\347\322\377\360\334\313\377" + "\351\323\312\377\351\324\317\377\300\244\245\377\271\235\235\377\317\265" + "\261\377\320\265\257\377\301\245\240\377\306\253\242\377\260\222\206\377" + "\236\202u\377\233\201u\377\245\214\177\377\226~y\377yab\377\307\262\256\377" + "\310\262\257\377\315\270\263\377\231\203}\377_HB\377eOE\377\224\203\210\377" + "\214\177\215\377\232\223\242\377\237\237\250\377\223\236\225\377\206\247" + "T\377\205\265%\377z\253\002\377{\251\002\377\177\261\001\377~\261\001\377{\261\002" + "\377q\245\001\377s\253\003\377|\264\003\377\204\266\003\377\204\262\002\377\205\265" + "\005\377\177\262\005\377|\262\012\377~\270\017\377{\256\002\377z\252\002\377w\256\000" + "\377\177\267!\377\220\302<\377z\246\000\377|\256\001\377e\226\001\377j\237\001\377" + "|\267\012\377\201\271\024\377\204\274\013\377\204\266\001\377}\261\001\377t\252" + "\001\377q\246\001\377v\257\001\377x\261\001\377x\255\001\377|\254\001\377\204\270\001\377" + "\201\262\001\377y\250\001\377r\242\001\377\216\275\030\377\217\275\015\377t\246\001" + "\377\221\274\001\377t\255\035\377\214\300\060\377\202\266\032\377o\250)\377\\" + "\227\033\377Y\225\024\377k\243\022\377r\251\011\377y\253\002\377\202\263\002\377" + "\216\275\021\377\210\272&\377\207\266\067\377\204\263\066\377\210\267:\377~" + "\257\037\377|\256\000\377~\261$\377t\246)\377w\246\063\377u\243/\377\202\256" + "\062\377\230\301T\377\210\264>\377\205\266=\377\213\274H\377s\250\064\377h" + "\232,\377w\231\\\377\203\232z\377\213\226\213\377\217\212\225\377\227\212" + "\232\377\232\214\230\377ucq\377\276\254\251\377\271\243\244\377\264\236\240" + "\377\321\271\254\377\257\227\222\377\256\225\222\377\244\214\211\377\370" + "\360\355\377\374\367\356\377\367\344\321\377\342\303\261\377\371\364\363" + "\377\375\375\375\377\376\376\376\377\304\266\300\377T/<\377\202cr\377\363" + "\360\363\377\376\376\376\377\375\375\375\377\376\375\373\377\336\303\257" + "\377\323\250\217\377\301\235\222\377\331\275\264\377\371\356\350\377\337" + "\313\311\377\267\240\242\377\302\247\245\377\274\242\237\377\322\274\272" + "\377\310\262\260\377\264\232\224\377\241\204}\377\273\247\227\377\212qi\377" + "\220uq\377nXX\377\262\236\226\377\260\233\226\377\325\300\275\377\331\305" + "\303\377\241\211\212\377\202jg\377\217{z\377\217\200\213\377\220\205\226" + "\377\220\212\232\377\222\222\234\377\221\235\225\377\207\256T\377z\252\017" + "\377v\244\001\377\177\260\003\377{\253\001\377y\256\001\377t\245\001\377w\254\001\377" + "|\254\002\377\203\263\001\377\201\260\001\377~\255\001\377\210\271\012\377\222\303" + "-\377}\264\032\377z\261\016\377~\256\001\377{\257\001\377\202\273\017\377}\257\011" + "\377|\253\001\377z\251\002\377Z\222\001\377q\251\001\377\200\267\002\377\200\265\001" + "\377~\261\001\377y\254\002\377v\257\001\377r\251\001\377q\243\002\377|\254\002\377\201" + "\262\001\377~\253\002\377\202\263\001\377\202\264\001\377\177\264\001\377z\260\001\377" + "t\251\001\377\200\271\024\377z\262\007\377t\247\002\377\215\273\007\377|\267)\377" + "b\236\022\377j\242,\377u\253@\377a\231*\377q\246\071\377\205\266+\377v\250" + "\017\377f\227\001\377\201\257\001\377\214\275\003\377{\260\015\377_\230\014\377m\244" + "!\377k\241\032\377Y\217\004\377\211\267\000\377\210\270\070\377\203\262?\377\234" + "\310\\\377\221\275@\377\201\260,\377\213\270?\377\204\263B\377q\244\064\377" + "p\244\060\377l\237*\377p\235?\377p\206m\377\205\214\216\377\222\217\235\377" + "\221\206\227\377\233\217\236\377\207{\207\377\224\206\215\377\312\270\263" + "\377\264\243\240\377\314\263\250\377\320\261\235\377zYN\377yc^\377\352\342" + "\341\377\344\316\303\377\337\300\261\377\345\310\274\377\367\351\344\377" + "\375\375\374\377\373\367\370\377\374\371\373\377\344\334\347\377\200g|\377" + "\314\276\312\377\360\353\355\377\374\371\372\377\373\371\372\377\363\346" + "\342\377\324\261\233\377\354\314\261\377\312\245\221\377\325\271\247\377" + "\321\261\237\377\353\326\316\377\325\306\307\377\232\205\204\377\264\237" + "\234\377\251\224\222\377\250\221\213\377\265\234\224\377\266\233\216\377" + "\246\213~\377\234\202y\377\225~y\377kWV\377\231\207\200\377\256\233\227\377" + "\321\277\274\377\316\274\272\377\262\237\240\377\243\216\220\377\247\224" + "\226\377\241\217\230\377\212}\215\377\201x\211\377\210\203\223\377\213\213" + "\230\377\225\244\233\377x\243J\377n\245\036\377\204\271/\377t\251\002\377x\247" + "\002\377w\246\002\377\177\262\001\377\201\260\001\377\201\263\001\377{\257\002\377u\253" + "\002\377\213\276\037\377\222\301*\377n\245\001\377t\260\010\377\200\266\001\377\201" + "\256\001\377\205\265\002\377\177\253\001\377\200\253\001\377x\246\000\377\202\256\026" + "\377\200\260\006\377\204\271\001\377\201\265\001\377}\261\003\377v\255\006\377p\252" + "\001\377w\254\001\377\207\270\002\377~\254\002\377\201\261\001\377~\254\002\377\201\262" + "\001\377\201\265\002\377y\252\001\377{\261\002\377w\260\002\377u\261\000\377u\255\001\377" + "s\246\002\377\206\276\062\377p\256\"\377p\254)\377l\246'\377o\247-\377s\252" + ".\377c\233\016\377g\236\006\377Z\220\003\377]\222\001\377u\250\001\377\207\272\007\377" + "z\260\007\377n\244\023\377f\231\022\377]\221\004\377W\215\007\377\211\264\002\377q" + "\246\030\377\225\304_\377\214\275G\377y\252\027\377y\251\027\377|\253%\377\223" + "\277G\377]\222\032\377o\241+\377r\241+\377\203\243a\377\243\244\252\377\260" + "\253\270\377\251\237\257\377\241\230\250\377\220\207\226\377wiz\377\215y" + "\206\377\270\244\242\377\272\241\237\377\341\307\271\377\277\232\204\377" + "\257\214x\377\344\321\311\377\375\371\366\377\372\370\365\377\367\350\341" + "\377\352\315\307\377\364\334\333\377\367\356\360\377\374\366\370\377\371" + "\364\370\377\275\256\274\377\204v\204\377\265\243\261\377\321\273\302\377" + "\335\307\311\377\362\344\346\377\372\363\363\377\371\356\352\377\261\207" + "r\377\266\210k\377\326\262\225\377\376\366\354\377\373\370\365\377\373\364" + "\363\377\266\241\237\377\231\205\200\377\307\265\251\377\276\252\227\377" + "\275\250\225\377\314\265\250\377\264\234\217\377\276\252\240\377\247\223" + "\216\377}eh\377\212tq\377\247\217\213\377\266\241\235\377\307\265\260\377" + "\305\262\256\377\235\205\207\377\245\216\222\377\263\244\253\377\232\214" + "\232\377\200u\206\377\200x\213\377\212\205\226\377\212\213\227\377\206\226" + "\211\377~\251V\377{\257\062\377x\253\000\377\200\260\001\377v\245\002\377}\255\001" + "\377\200\263\001\377}\264\001\377w\257\002\377r\254\007\377\222\306F\377\200\264" + "\032\377q\247\001\377q\251\003\377w\262\003\377\200\260\001\377\203\256\001\377\177" + "\250\001\377~\251\002\377x\246\001\377\207\267\010\377\207\267\004\377\205\267\002\377" + "}\257\001\377\204\266\013\377\227\302&\377u\250\000\377|\255\001\377\215\300\001\377" + "\204\262\001\377\207\264\001\377\205\260\001\377\210\267\001\377\203\262\001\377x\246" + "\002\377\177\257\001\377w\246\002\377z\257\001\377~\263\001\377s\243\001\377`\241\021" + "\377N\214\000\377c\240\011\377y\262\037\377d\240\005\377b\237\006\377p\253\017\377" + "l\243\002\377g\225\003\377w\246\014\377q\241\000\377\177\263\004\377u\255\005\377~\263" + "\"\377\\\204\000\377e\216\007\377d\217\012\377}\253\003\377\216\303\064\377\177\262" + "@\377g\236\026\377[\216\005\377h\231\006\377~\254!\377\220\274A\377u\241)\377" + "\206\255S\377\232\260\204\377\243\250\243\377\234\230\244\377\210\200\225" + "\377\222\211\233\377\217\204\226\377\224\204\225\377\215\200\216\377\242" + "\224\231\377\240\217\223\377\320\274\271\377\336\311\302\377\262\226\217" + "\377\267\233\231\377\363\357\360\377\373\370\365\377\351\324\312\377\351" + "\317\311\377\366\346\346\377\365\353\355\377\360\352\356\377\361\347\356" + "\377\322\306\322\377}i|\377P\070H\377\216\200\215\377\313\275\306\377\340" + "\322\330\377\364\347\353\377\345\316\313\377\347\320\312\377\327\265\250" + "\377\336\304\265\377\320\262\242\377\311\252\233\377\332\306\275\377\352" + "\342\342\377\331\314\316\377\216\200|\377`TR\377ygb\377iXT\377\231\200t\377" + "\314\270\255\377\301\254\247\377\262\237\236\377\234\212\211\377\202on\377" + "\266\243\237\377\234\206\205\377\267\242\240\377\302\256\256\377\253\226" + "\233\377\237\211\221\377\315\300\306\377\322\306\313\377\241\225\242\377" + "\210~\220\377\214\205\227\377\214\207\226\377\211\211\224\377\210\233\204" + "\377x\250\066\377l\232\000\377\203\257\002\377\205\265\001\377\202\265\002\377\203" + "\267\002\377|\257\002\377x\255\001\377w\262\007\377w\261\022\377n\237\001\377q\245\001" + "\377r\251\001\377t\256\002\377\202\266\004\377\204\262\002\377\205\257\002\377~\251" + "\001\377{\247\002\377\204\265\001\377\206\265\002\377\204\266\001\377~\257\001\377\205" + "\261\002\377\222\274\002\377\206\264\001\377y\251\002\377\177\261\001\377\205\271\002" + "\377\201\256\001\377\205\261\001\377\212\272\002\377\206\265\001\377{\252\002\377}" + "\253\002\377y\250\001\377}\260\001\377{\255\001\377r\241\002\377d\236\002\377[\223\002" + "\377l\250\025\377|\264\031\377t\255\011\377^\225\001\377l\246\004\377w\252\003\377" + "b\215\001\377^\215\002\377q\242\001\377\205\266\005\377v\253\007\377\216\305+\377p" + "\236\003\377f\214\001\377Ry\001\377t\254\020\377z\264$\377Q\205\010\377~\260\040\377" + "T\206\003\377_\217\005\377\177\253\040\377\260\317t\377q\230B\377k\222E\377\213" + "\230\206\377\252\250\262\377\254\245\263\377\232\216\242\377rdx\377\201m" + "}\377\205q|\377\220|\201\377\221}|\377\202ge\377\334\300\254\377\340\274" + "\242\377\255~d\377\353\333\324\377\372\367\367\377\344\325\315\377\356\337" + "\325\377\351\324\312\377\275\245\242\377\307\265\267\377\342\331\337\377" + "\307\301\310\377zq~\377\070\036+\377N\067D\377UBN\377\232\217\233\377\325\312" + "\323\377\312\273\302\377\331\310\306\377\343\320\315\377\343\313\304\377" + "\277\236\220\377\310\237\203\377\370\355\345\377\357\341\331\377\332\312" + "\306\377\327\320\315\377\274\253\246\377\273\246\235\377\271\242\232\377" + "\225}w\377mOB\377\271\233\210\377\335\312\301\377\251\216\207\377\250\220" + "\212\377nVS\377\253\227\216\377\302\257\251\377\247\220\217\377\261\234\235" + "\377\254\232\237\377\240\213\227\377\260\237\252\377\314\277\305\377\323" + "\306\314\377\235\221\241\377\214\203\226\377\205~\220\377\202\177\215\377" + "\211\217\224\377v\233U\377i\234\010\377\200\261\001\377\203\262\002\377\202\262" + "\001\377\201\262\001\377}\253\001\377}\257\002\377~\262\001\377v\247\002\377x\246\001\377" + "x\247\002\377s\241\002\377x\260\003\377y\254\001\377\177\256\001\377\202\263\001\377" + "\202\256\002\377\200\254\002\377\206\264\001\377\207\264\002\377\211\271\002\377\204" + "\255\001\377\223\267\001\377\245\307\001\377\212\263\002\377{\256\001\377s\245\002\377" + "~\260\001\377}\254\002\377\203\262\002\377\207\267\001\377\211\271\001\377\177\256" + "\001\377~\256\002\377\177\263\002\377}\257\001\377u\244\001\377z\252\002\377g\236\001\377" + "f\236\001\377s\256\026\377\210\276.\377z\262\020\377t\256\011\377i\235\001\377s" + "\246\002\377p\240\001\377o\235\001\377w\242\000\377\223\301\071\377w\254\040\377\214" + "\301/\377y\256\013\377m\240\002\377a\222\000\377z\262\025\377k\242\012\377h\226" + "\020\377x\250\024\377Gy\000\377d\214\022\377\212\246\064\377\262\316\213\377\204" + "\245l\377\207\224\211\377\220\216\234\377\207\177\222\377\205{\220\377\204" + "w\212\377l_r\377\211w\203\377\234\206\217\377\266\243\250\377\254\233\235" + "\377\277\256\255\377\367\347\340\377\326\271\243\377\314\262\244\377\336" + "\324\322\377\354\342\336\377\367\356\347\377\324\300\262\377\236\214\207" + "\377\261\247\242\377\271\262\264\377\267\260\266\377l^g\377XIP\377ven\377" + "cNZ\377eS\\\377aNY\377\210z\210\377\277\271\301\377\316\301\306\377\252\220" + "\215\377\273\240\231\377\337\306\277\377\314\270\255\377\320\267\243\377" + "\336\314\276\377\363\353\346\377\327\313\312\377\335\317\322\377\355\341" + "\342\377\364\355\355\377\341\323\315\377\234\205z\377\243\213\201\377\316" + "\272\262\377\305\256\242\377\270\241\225\377\207sm\377zd`\377\261\237\225" + "\377\254\231\225\377\262\240\235\377\262\243\247\377\227\205\222\377\221" + "~\216\377\304\266\276\377\326\311\316\377\315\300\310\377\241\226\246\377" + "\213\205\227\377\205\201\221\377\216\215\233\377z\216x\377\223\273S\377\205" + "\265\016\377\210\266\005\377\213\271\002\377\205\261\001\377~\257\001\377y\257\001\377" + "s\245\001\377z\254\001\377\200\257\002\377}\254\002\377}\253\010\377}\261\013\377y" + "\254\004\377\177\257\002\377}\253\002\377~\253\001\377v\246\002\377u\245\002\377\200" + "\260\001\377\205\265\002\377\203\253\001\377\225\275\001\377\223\267\001\377\200\253" + "\003\377\200\262\001\377{\261\002\377t\246\002\377x\251\002\377\203\264\001\377\212\267" + "\001\377\221\301\001\377\206\262\001\377\200\257\001\377\201\263\001\377z\251\002\377" + "w\246\001\377\204\264\001\377\226\275\002\377q\247\001\377m\246\002\377u\256\011\377" + "o\247\001\377y\255\001\377~\260\002\377~\256\002\377z\247\001\377}\253\001\377\220\271" + "#\377\227\302a\377Y\227\003\377\205\272\060\377}\260\026\377o\242\002\377s\253" + "\004\377v\254\003\377y\252\003\377z\247\010\377\210\263I\377}\243F\377\201\242\066" + "\377\232\264g\377\242\276\202\377z\215u\377|}\205\377\203|\214\377\204x\211" + "\377\213~\220\377~p\201\377|n{\377\265\245\252\377\327\313\317\377\354\344" + "\351\377\326\320\326\377\343\326\327\377\342\315\277\377\310\265\251\377" + "\341\324\317\377\344\326\323\377\350\335\334\377\352\343\340\377\262\243" + "\217\377\234\207l\377wjX\377\204\201v\377smk\377iZ^\377\212x\177\377\235" + "\212\226\377\221~\213\377\204nx\377\214x\200\377eV\\\377kci\377\244\237\242" + "\377\267\252\252\377\240\217\215\377\212tf\377\301\255\235\377\333\311\275" + "\377\331\316\307\377\333\312\277\377\365\357\355\377\351\336\334\377\335" + "\317\310\377\303\250\224\377\301\250\226\377\326\310\301\377\223\203\177" + "\377\270\250\242\377\333\311\303\377\264\237\221\377\220zv\377`LN\377\221" + "~z\377\236\215\206\377\226\203\204\377\247\227\233\377\234\213\230\377\232" + "\214\234\377\255\235\252\377\311\273\303\377\323\310\317\377\314\303\312" + "\377\243\233\252\377\205\177\220\377\215\212\230\377\202\207\213\377v\233" + "?\377\214\272&\377\210\264\001\377\213\257\001\377\215\264\001\377\201\260\001\377" + "t\250\001\377q\243\001\377u\244\002\377z\245\002\377z\250\001\377z\255\010\377\200\265" + "!\377\202\271'\377x\253\001\377\206\263\003\377\200\260\002\377w\247\002\377u\245" + "\001\377\201\260\002\377\207\265\002\377\206\260\001\377\214\267\001\377\214\273\004" + "\377\201\255\003\377}\262\001\377p\255\002\377k\242\002\377h\234\001\377}\257\002\377" + "\202\256\002\377\217\274\002\377\221\276\001\377\200\257\002\377{\256\002\377w\247" + "\001\377~\261\001\377\202\265\001\377\215\265\001\377\202\264\003\377x\253\006\377i\227" + "\001\377y\256\001\377w\251\001\377d\224\001\377g\226\002\377\200\255\002\377\205\262" + "\000\377\204\266\"\377f\232\020\377Z\231\001\377p\250\034\377\203\264%\377}\261" + "\035\377v\255\013\377r\251\003\377{\254\001\377\201\257\001\377\211\260i\377\216" + "\265q\377\234\276i\377|\246C\377n\225J\377\201\211\205\377\203\177\213\377" + "\203y\213\377\204w\212\377\204w\211\377\242\231\244\377\253\242\252\377\307" + "\275\306\377\342\325\336\377\313\277\306\377\277\265\276\377\323\310\314" + "\377\312\261\247\377\306\247\217\377\327\301\255\377\317\274\265\377\325" + "\307\277\377\312\266\242\377\272\242\202\377\234\202a\377oaM\377ph\\\377" + "tol\377\247\234\237\377\275\261\271\377\252\233\247\377\226\204\220\377\245" + "\225\240\377\246\230\241\377\242\230\234\377pfl\377ife\377\213\211\201\377" + "|rh\377\242\232\222\377\204t_\377\257\237\214\377\321\304\264\377\344\336" + "\331\377\365\363\364\377\343\335\330\377\317\275\257\377\250\205e\377\260" + "\217r\377\247\205j\377\230xb\377\223vi\377\332\314\311\377\316\301\277\377" + "\277\254\245\377^FD\377}ib\377\225\200|\377\222\177~\377\230\206\213\377" + "\227\210\224\377\260\244\261\377\252\232\247\377\262\243\256\377\312\274" + "\306\377\313\277\307\377\307\273\305\377\233\221\242\377\211\200\222\377" + "\203~\215\377u\203h\377u\246\022\377~\257\007\377\177\247\002\377\225\264\001\377" + "\202\253\002\377j\231\002\377z\251\002\377\201\252\001\377\201\254\001\377r\234\001\377" + "~\257\002\377}\260\007\377w\255\010\377w\251\003\377\223\277\031\377\207\270\011\377" + "\200\261\013\377x\245\001\377\206\262\001\377\213\267\001\377\206\257\002\377\212" + "\262\001\377\220\276\016\377\214\276\026\377{\264\002\377m\250\001\377n\251\001\377" + "h\235\001\377|\262\001\377}\255\001\377\210\264\001\377\221\275\002\377\203\263\002\377" + "q\245\001\377u\252\001\377|\267\003\377~\270\001\377\207\260\001\377\206\256\001\377" + "\211\266\007\377r\236\002\377y\251\002\377j\234\001\377P\202\002\377\\\216\002\377x\245" + "\001\377\210\267\026\377\206\266\021\377|\250\002\377g\237\012\377c\232#\377\200" + "\264\060\377t\253\034\377l\247\017\377m\244\014\377\206\264\032\377\227\274\070" + "\377w\240\037\377z\246\064\377\216\267X\377}\252S\377q\217`\377\212\207\217" + "\377\215\205\222\377\230\214\234\377\230\215\235\377\241\223\242\377\210" + "z\210\377\302\263\275\377\335\321\331\377\353\340\347\377\325\314\324\377" + "\266\245\255\377\247\217\214\377\243\213\200\377\330\312\302\377\326\310" + "\300\377\303\255\241\377\302\245\210\377\301\246\210\377\263\227z\377\221" + "v\\\377`M@\377pgb\377\252\247\251\377\312\304\310\377\305\276\305\377\313" + "\306\314\377\260\250\262\377\275\264\275\377\274\263\274\377\254\245\254" + "\377\255\250\256\377xss\377ysl\377ri`\377uja\377\242\232\212\377\265\257" + "\231\377\302\275\252\377\317\310\271\377\331\325\320\377\340\332\332\377" + "\325\305\276\377\303\252\224\377\322\272\246\377\277\244\212\377\237}a\377" + "tS@\377\275\243\216\377\333\313\277\377\343\331\326\377\214{}\377xd`\377" + "ve\\\377\204rq\377\210w\200\377\224\204\221\377\251\232\247\377\274\256\270" + "\377\273\253\266\377\265\242\256\377\313\275\306\377\330\313\323\377\321" + "\305\314\377\217\201\225\377\211\200\221\377x~y\377o\234<\377w\253.\377p" + "\245\001\377\201\256\001\377\177\250\001\377r\236\002\377\177\254\002\377\206\261\002" + "\377\201\253\001\377{\245\001\377\177\250\001\377~\247\001\377\202\261\001\377r\242" + "\002\377|\253\001\377\202\262\002\377|\254\003\377|\252\001\377\211\267\001\377\206\262" + "\001\377\203\256\001\377\206\257\001\377\202\253\001\377\201\264\000\377\201\272\006" + "\377n\244\001\377q\252\001\377l\240\002\377~\261\004\377\206\266\012\377\215\266\002" + "\377\213\264\001\377\211\265\001\377p\251\001\377s\254\001\377{\257\001\377\202\265" + "\001\377e\221\001\377\203\255\002\377\216\270\001\377\207\261\001\377j\232\001\377_\217" + "\002\377X\210\002\377s\241\002\377z\254\001\377\214\274A\377g\230\020\377g\223\002\377" + "y\251\010\377\203\265'\377\215\277@\377y\256\063\377\233\303i\377\236\302a" + "\377\177\254\015\377{\254\003\377l\236\011\377n\236#\377o\235\071\377p\230L\377" + "}\205\202\377\217\210\226\377\242\234\247\377\221\210\230\377\214\177\220" + "\377\226\206\224\377\256\234\245\377\320\303\311\377\334\322\332\377\311" + "\271\302\377\277\257\270\377\252\226\241\377\245\226\227\377\177j_\377\320" + "\300\264\377\320\276\257\377\320\275\252\377\312\271\244\377\301\257\234" + "\377\274\254\232\377\274\256\241\377\236\224\216\377\250\244\243\377\302" + "\300\304\377\277\275\301\377\311\307\314\377\317\315\322\377\300\270\301" + "\377\303\300\306\377\311\307\314\377\266\263\270\377\315\314\320\377\271" + "\271\274\377\235\234\230\377~ys\377TFB\377YOS\377\215}e\377\277\264\237\377" + "\266\253\223\377\273\253\220\377\300\264\243\377\336\325\323\377\337\324" + "\322\377\330\311\301\377\321\275\255\377\273\240\206\377vVA\377\225sZ\377" + "\336\316\300\377\350\336\327\377\260\244\250\377\227\210\221\377\224\207" + "\205\377ucd\377\206x\202\377\223\205\222\377\243\225\242\377\262\242\256" + "\377\300\261\272\377\273\252\263\377\301\261\271\377\312\273\301\377\317" + "\300\306\377\321\306\312\377\233\222\240\377z{\203\377w\221o\377\211\264" + "c\377p\247\004\377t\250\002\377y\247\002\377}\253\001\377~\251\001\377\201\257\001\377" + "{\247\001\377z\244\002\377v\237\002\377\203\252\001\377\204\261\002\377w\245\002\377" + "s\243\001\377z\253\001\377}\261\001\377\201\262\002\377\203\255\002\377\207\263\002\377" + "\203\256\002\377\201\254\002\377\204\255\001\377\206\267\002\377z\257\002\377s\251" + "\001\377t\254\002\377j\233\001\377n\237\002\377\220\300\024\377\212\270\000\377\211" + "\264\001\377{\251\002\377{\260\002\377v\252\002\377\177\257\001\377\202\263\001\377b" + "\221\002\377g\225\001\377\213\267\001\377\224\276\012\377\205\261\005\377l\232\001\377" + "d\221\002\377w\246\002\377{\253\001\377r\247\000\377T\211\000\377P\207\002\377O\210\002" + "\377{\255#\377\227\303K\377\231\304Z\377|\256\067\377z\251\037\377\204\260" + "\002\377\210\261\007\377\202\253*\377q\237.\377o\233>\377q\217[\377\231\227\241" + "\377\226\216\236\377\200u\211\377\207}\221\377\200t\206\377\235\215\232\377" + "\303\267\273\377\306\267\276\377\251\221\233\377\266\240\253\377\315\276" + "\306\377\311\274\304\377\241\216\217\377\264\237\226\377\275\243\214\377" + "\272\241\206\377\264\235\177\377\242\210j\377\254\225}\377\304\264\253\377" + "\331\323\322\377\316\313\315\377\300\277\300\377\300\300\300\377\271\271" + "\272\377\311\311\315\377\312\311\316\377\302\301\306\377\304\303\311\377" + "\310\307\315\377\312\311\316\377\273\272\276\377\320\320\324\377\276\301" + "\277\377\266\266\263\377\233\232\223\377\210\177\204\377jY]\377\212pV\377" + "\305\273\247\377\276\263\235\377\301\262\232\377\301\265\241\377\327\317" + "\310\377\316\277\261\377\301\261\237\377\304\261\237\377\261\231~\377|\\" + "C\377\312\263\232\377\342\325\312\377\300\262\257\377l`g\377\200t~\377\243" + "\232\241\377\215\201\213\377\230\213\227\377\217\201\221\377\243\225\244" + "\377\271\252\264\377\310\271\277\377\261\240\251\377\274\255\264\377\276" + "\255\265\377\323\306\311\377\315\303\311\377\205\202\216\377m|q\377\234\272" + "\220\377z\261\034\377l\241\000\377r\247\003\377\177\256\015\377s\246\001\377|\262" + "\012\377{\256\006\377v\244\001\377x\246\001\377\177\256\001\377s\241\002\377p\236\001" + "\377z\251\002\377z\250\001\377w\251\001\377y\260\001\377|\256\002\377\201\262\001\377" + "\177\252\001\377{\245\002\377\206\261\001\377\207\271\002\377o\242\001\377r\255\006\377" + "u\256\005\377p\240\001\377j\233\002\377\204\270\025\377\231\304.\377\207\270\004\377" + "x\252\002\377k\240\001\377|\254\001\377~\255\001\377~\261\001\377k\235\001\377m\235\002" + "\377{\252\003\377\223\277\031\377}\253\002\377}\254\001\377~\256\002\377|\261\003\377" + "m\236\002\377p\245\001\377i\245\004\377u\257&\377_\232\037\377\214\275f\377\215" + "\277T\377z\255\013\377m\237\002\377p\230\002\377\211\262\001\377\213\262\004\377u" + "\237\003\377]\214\000\377h\224\062\377\213\225\214\377\222\213\227\377~t\204\377" + "xp\200\377}u\207\377\265\254\271\377\276\260\264\377\271\244\250\377\267" + "\240\244\377\270\243\253\377\303\264\276\377\277\256\266\377\273\254\264" + "\377\241\217\223\377\227{p\377\276\241\213\377\267\235\210\377\272\246\223" + "\377\275\253\227\377\271\246\222\377\314\276\267\377\340\333\334\377\333" + "\331\335\377\307\307\307\377\275\276\271\377\261\262\254\377\275\302\276" + "\377\276\301\300\377\271\272\272\377\272\273\274\377\303\304\307\377\301" + "\303\304\377\257\262\255\377\274\300\273\377\307\314\312\377\311\313\312" + "\377\302\302\303\377\274\273\276\377\264\257\260\377\256\241\234\377\326" + "\321\312\377\265\256\234\377\305\274\253\377\266\246\223\377\314\300\261" + "\377\301\270\261\377\320\306\272\377\306\272\254\377\304\253\226\377\216" + "pS\377\242\207l\377\312\271\243\377\256\231\216\377yjs\377tdj\377rek\377" + "\245\234\246\377\264\251\261\377\223\204\223\377\237\215\235\377\256\236" + "\250\377\301\262\271\377\277\256\267\377\265\242\254\377\271\250\260\377" + "\273\253\257\377\343\327\327\377\236\225\242\377sux\377\215\243\203\377\177" + "\260)\377p\243\000\377n\236\002\377r\244\003\377j\231\003\377|\262\016\377p\244\001" + "\377u\253\003\377\200\264\021\377t\247\002\377k\232\002\377t\242\002\377y\246\001\377" + "\204\262\002\377u\244\002\377t\250\001\377m\237\001\377x\257\002\377|\255\001\377~\251" + "\002\377\213\273\007\377\201\264\002\377q\246\003\377u\260\011\377n\240\001\377s\243" + "\001\377y\250\002\377\201\266\011\377\243\310B\377\201\261\006\377j\237\002\377a\227" + "\002\377\217\274\002\377\206\260\001\377\201\262\001\377h\227\002\377u\246\001\377l\234" + "\002\377p\243\017\377{\256\030\377e\234\006\377Y\220\003\377v\257\024\377l\242\004\377" + "p\246\002\377u\255\010\377\211\277:\377\177\263:\377r\250\065\377\200\264B\377" + "`\227\022\377[\216\001\377|\253\001\377\215\266\002\377\210\257\006\377\227\265\065" + "\377\201\246@\377w\230a\377\211\216\210\377\214\207\217\377\217\204\222\377" + "\226\217\236\377\253\241\256\377\233\207\222\377\264\240\244\377\303\261" + "\263\377\270\244\252\377\270\246\260\377\246\223\236\377\275\256\267\377" + "\315\300\311\377\244\217\225\377\206ig\377\250\216{\377\265\236\213\377\252" + "\223\203\377\261\232\206\377\261\235\214\377\317\306\301\377\327\322\322" + "\377\327\325\326\377\313\314\311\377\263\266\251\377\243\251\231\377\254" + "\261\244\377\261\266\253\377\263\270\256\377\265\272\262\377\273\301\272" + "\377\271\300\271\377\265\272\262\377\263\271\261\377\301\306\300\377\305" + "\312\310\377\316\320\322\377\317\317\323\377\316\315\321\377\326\326\330" + "\377\315\312\305\377\302\274\263\377\273\261\243\377\270\256\233\377\255" + "\237\211\377\277\265\244\377\315\305\273\377\307\275\257\377\301\263\241" + "\377\227~h\377gK\063\377\245\215u\377\211xk\377XLL\377h`c\377\204z\177\377" + "\204u\200\377\255\242\252\377\261\242\254\377\236\214\232\377\252\231\244" + "\377\273\253\264\377\277\260\266\377\271\250\256\377\267\246\255\377\272" + "\251\254\377\325\307\310\377\304\270\300\377\177y\203\377\214\232{\377\211" + "\265\067\377r\250\002\377k\233\001\377q\246\003\377s\247\004\377r\247\004\377j\237\002" + "\377^\217\001\377z\246\004\377~\253\007\377r\234\001\377\177\253\000\377\203\257\002" + "\377~\253\002\377z\251\002\377p\245\002\377k\235\001\377n\237\001\377|\257\004\377\201" + "\263\011\377{\256\003\377\202\267\014\377\201\270\023\377o\246\000\377o\241\001\377" + "o\240\002\377~\254\001\377y\251\000\377\212\267\016\377w\246\002\377h\235\002\377Y\221" + "\002\377|\260\001\377\217\272\002\377\212\265\000\377z\246\002\377v\245\001\377f\225" + "\002\377_\215\001\377v\251\014\377\205\266$\377e\233\003\377_\224\001\377w\254\032" + "\377\204\267+\377\207\273)\377~\262\020\377z\254\000\377{\255\014\377\205\263" + "\067\377\205\265\063\377U\216\003\377f\234\005\377\207\262\013\377\211\260\030\377" + "\301\301\247\377\212\217x\377{\204y\377\204\204\210\377\221\207\223\377\242" + "\227\247\377\233\222\240\377\220~\216\377\232\207\221\377\302\260\267\377" + "\311\272\301\377\271\250\260\377\254\227\242\377\265\243\255\377\276\257" + "\271\377\301\264\274\377\274\256\265\377\222\200}\377p_R\377\256\223~\377" + "\250\215r\377\253\216s\377\306\262\244\377\277\261\253\377\322\314\311\377" + "\324\324\323\377\305\307\301\377\266\271\252\377\247\255\224\377\237\251" + "\212\377\242\253\215\377\242\256\220\377\240\256\221\377\252\266\237\377" + "\255\272\245\377\255\271\243\377\255\270\245\377\266\277\262\377\303\310" + "\302\377\313\316\316\377\316\322\325\377\330\331\335\377\332\332\334\377" + "\317\314\307\377\301\271\255\377\275\261\240\377\273\257\241\377\217zc\377" + "~iP\377\214{c\377\233\213x\377\311\273\261\377\241\211w\377kVH\377q\\J\377" + "aNB\377UF?\377bUU\377xlt\377\212~\212\377\225\207\221\377\245\227\240\377" + "\232\213\230\377\260\242\255\377\257\236\252\377\312\272\302\377\300\261" + "\267\377\260\236\250\377\301\261\267\377\321\302\305\377\312\275\301\377" + "\200x\207\377\213\225l\377\217\262=\377i\241\003\377r\250\012\377t\247\004\377" + "o\234\001\377g\224\002\377[\213\001\377m\231\002\377\206\253\002\377\206\261\002\377" + "~\250\005\377\232\301\020\377\203\256\001\377|\250\002\377w\247\002\377s\250\001\377" + "k\233\002\377n\236\002\377s\246\000\377\216\275/\377{\260\005\377x\254\002\377{\263" + "\006\377v\254\004\377r\244\002\377v\245\002\377\202\256\002\377n\235\002\377l\235\001\377" + "s\244\001\377j\237\001\377c\234\001\377\202\273\031\377\210\270\001\377\207\262\002" + "\377\206\260\001\377i\226\002\377U\206\001\377_\215\002\377i\232\001\377e\227\006\377" + "y\257\026\377z\255\035\377\220\277\066\377\206\265\037\377m\237\002\377p\240\000" + "\377\177\256\000\377\221\270\000\377\214\267\061\377v\241G\377^\177\070\377awE" + "\377q{c\377c_h\377G>N\377TLY\377olt\377\227\223\235\377\241\231\247\377\224" + "\210\230\377\203t\206\377\222\200\216\377\256\236\245\377\310\273\300\377" + "\243\217\231\377\266\243\253\377\231\204\220\377\300\262\271\377\300\262" + "\273\377\246\225\234\377\261\240\242\377\265\246\243\377\211so\377rQF\377" + "nK>\377\227xe\377\252\212{\377\276\253\241\377\306\272\264\377\273\263\253" + "\377\265\262\244\377\251\251\222\377\245\251\207\377\233\244y\377\227\241" + "q\377\221\235i\377\214\231g\377\222\236p\377\222\240u\377\225\243|\377\244" + "\261\222\377\250\265\235\377\267\301\262\377\306\315\306\377\306\313\311" + "\377\323\327\327\377\327\330\332\377\323\322\322\377\311\304\277\377\304" + "\274\266\377\271\254\243\377\302\272\264\377\271\256\245\377\252\232\214" + "\377\252\223{\377\270\245\217\377\264\237\217\377\203ie\377t`\\\377[EC\377" + "wfd\377vkm\377znu\377\202t}\377\217\201\212\377\231\211\224\377\232\207\222" + "\377\222\200\216\377\255\236\252\377\274\255\264\377\304\265\273\377\252" + "\230\242\377\233\207\222\377\257\233\237\377\320\303\305\377\271\254\267" + "\377\240\236{\377\225\260H\377\202\261\064\377\200\260\036\377x\251\003\377u" + "\243\003\377l\226\001\377j\223\002\377{\242\001\377\205\256\007\377\206\264\016\377" + "\206\260\031\377\255\315;\377\201\263\002\377\177\250\002\377\177\247\002\377{\244" + "\002\377y\242\002\377w\237\000\377\215\273$\377\227\304\067\377z\252\003\377z\247" + "\001\377\200\257\002\377~\255\004\377o\236\002\377\203\256\002\377\204\255\003\377q\236" + "\002\377m\235\002\377y\252\001\377u\247\002\377k\237\003\377t\252\003\377y\247\002\377" + "\206\263\002\377z\246\002\377o\237\002\377Hw\002\377\\\213\001\377d\225\003\377c\226" + "\014\377\224\306<\377\214\276\065\377\210\264\026\377\201\247\003\377q\241\002\377" + "p\237\003\377c\204,\377Yo\064\377alI\377KDN\377=\064?\377G=J\377RET\377@\060=" + "\377-\033(\377NAL\377\216\207\225\377\232\221\236\377\217\205\223\377\213" + "~\217\377\203u\205\377\221\200\216\377\312\273\301\377\272\251\254\377\272" + "\247\250\377\254\230\235\377\236\212\224\377\276\257\265\377\262\241\251" + "\377\252\227\240\377\257\235\236\377\242\224\222\377\266\250\237\377\213" + "uh\377qWM\377pTC\377\206gO\377\207jV\377\216q`\377\241\215|\377\234\215z" + "\377\230\222v\377\230\227t\377\211\211`\377}}N\377oo=\377tvE\377\203\211" + "W\377\205\216\\\377\210\220d\377\232\245~\377\250\261\224\377\251\262\233" + "\377\266\274\255\377\276\304\273\377\303\306\300\377\307\307\306\377\326" + "\326\326\377\316\312\311\377\310\301\275\377\273\256\246\377\257\235\217" + "\377\264\246\234\377\247\222{\377\275\250\235\377\242\205e\377\231}d\377" + "\215wo\377\202pk\377\203wt\377\201xu\377shj\377\211z\200\377\207w|\377\212" + "x~\377\235\213\222\377\256\240\250\377\217\200\212\377\252\232\244\377\261" + "\243\254\377\261\241\251\377\253\232\242\377\237\216\226\377\240\216\226" + "\377\263\242\250\377\324\310\314\377\257\245\216\377\236\257[\377r\243(\377" + "\226\303N\377s\246\010\377o\235\001\377n\233\002\377e\220\002\377\177\242\002\377" + "\221\270\034\377\207\266#\377\247\311J\377\233\277'\377|\252\000\377\211\263" + "\001\377\207\256\000\377\230\265\024\377\231\266\034\377|\244\000\377\177\253\011" + "\377}\254\002\377~\250\002\377z\245\001\377\204\264\003\377\201\261\003\377t\241\002" + "\377y\243\002\377z\245\002\377o\234\002\377w\250\001\377z\254\001\377}\260\003\377r\241" + "\001\377u\250\001\377n\236\002\377\204\261\001\377x\245\001\377k\232\002\377k\232\001\377" + "t\244\005\377\214\274\"\377\220\300&\377s\252\016\377b\235\005\377\215\272\017" + "\377x\252\005\377k\240\020\377\177\226V\377\200\201\203\377\237\234\242\377" + "\220\207\217\377ymt\377XHT\377=+\071\377;)\063\377cT]\377qfn\377gZe\377{p\200" + "\377\203w\207\377\207y\212\377\214~\217\377\213}\215\377\246\227\241\377" + "\305\265\264\377\254\230\232\377\241\215\217\377\220}\206\377\270\250\254" + "\377\274\254\261\377\254\233\242\377\266\243\251\377\262\242\242\377\236" + "\216\217\377\214\177~\377\245\227\221\377\227\203t\377jL\063\377iO\067\377" + "ubO\377\\H;\377dND\377^K=\377XF\064\377P@)\377aV\070\377bV\064\377uoI\377\207" + "\207\\\377\212\214b\377\221\224k\377\214\216i\377\224\227w\377\244\247\217" + "\377\235\237\212\377\245\247\224\377\237\234\214\377\265\262\251\377\271" + "\263\257\377\304\276\276\377\273\256\250\377\265\241\220\377\255\227\203" + "\377\240\203h\377\241\207v\377\222va\377\231|a\377\214wh\377nS<\377n[M\377" + "zfb\377\244\226\223\377}qs\377{po\377\212{|\377\223\204\207\377\221\203\206" + "\377\220~\201\377\220~\203\377\242\222\234\377\240\220\232\377\232\210\222" + "\377\267\253\260\377\266\251\257\377\226\205\217\377\241\220\227\377\255" + "\232\236\377\313\300\300\377\274\267\252\377\227\253i\377i\234\027\377t\246" + "\032\377y\253\034\377n\241\003\377o\241\002\377g\221\001\377y\240\001\377\226\274\064" + "\377t\243\012\377\262\320M\377\205\256\010\377\200\255\003\377}\254\001\377{\250" + "\002\377\201\251\002\377\212\255\004\377~\245\002\377i\230\001\377s\243\001\377\200\254" + "\002\377u\243\001\377y\250\002\377w\252\001\377r\242\002\377w\250\002\377w\246\002\377" + "u\244\001\377u\250\002\377{\257\012\377w\246\013\377{\253\007\377v\247\002\377w\251" + "\003\377v\243\001\377y\251\001\377_\214\001\377v\251\012\377\224\306%\377\206\267" + "\022\377{\253\001\377]\221\001\377W\215\001\377_\227\004\377d\200>\377LJR\377\063#" + "\065\377\067!\065\377_KZ\377\226\214\224\377\270\261\267\377\206|\203\377E\066" + ":\377/\033'\377P<K\377|kx\377l^m\377qgu\377\200v\206\377\203z\213\377\215" + "\200\221\377\226\210\227\377\270\251\256\377\310\267\267\377\255\232\232" + "\377\232\206\214\377\207q}\377\256\235\245\377\262\242\250\377\253\227\240" + "\377\256\233\236\377\243\214\213\377\232\204\202\377\211xw\377\217\203\201" + "\377\262\247\234\377\244\226\206\377\252\241\223\377\264\257\245\377\253" + "\245\232\377\242\231\212\377\217\207p\377wpQ\377ohD\377lg?\377\214\207^\377" + "\225\223n\377\227\230r\377\231\232w\377\230\232y\377\232\232}\377\216\212" + "n\377\214\207p\377\201zj\377xpd\377l_O\377\203q]\377\221|j\377\236\210~\377" + "{aV\377mO?\377x[J\377\206jY\377qP;\377\216vp\377\\A.\377_F\060\377iYN\377" + "UE\064\377~sh\377\212\202y\377nef\377{je\377zig\377\205tu\377\202nr\377\211" + "uv\377\225\201\200\377\240\217\223\377\234\215\226\377\227\210\223\377\235" + "\217\227\377\257\244\253\377\236\220\233\377\203q}\377\245\222\224\377\302" + "\267\266\377\266\264\257\377\205\240j\377u\241.\377n\237\016\377v\247\021\377" + "n\233\001\377t\243\003\377l\231\002\377c\221\001\377\202\254\030\377\215\256\030\377" + "\245\303+\377\200\254\004\377~\253\012\377t\250\004\377n\240\001\377}\246\002\377" + "\212\260\001\377\206\256\001\377q\235\002\377r\242\002\377|\252\001\377s\242\002\377" + "w\244\002\377v\247\001\377r\250\002\377n\243\001\377m\240\002\377o\245\001\377n\237\001" + "\377q\244\001\377s\246\003\377n\235\003\377}\255\016\377w\252\004\377y\251\003\377w" + "\250\002\377\215\276\036\377\205\273\033\377t\245\007\377}\253\001\377\177\256\001" + "\377b\230\002\377M\177\015\377V\177(\377@F=\377*$*\377\040\023\031\377&\031\032\377" + "\027\011\014\377!\030\033\377+&*\377\013\010\012\377\025\016\024\377\071.\071\377\060\037" + "-\377\230\211\233\377tjx\377pfr\377|r\200\377\204y\210\377\222\206\225\377" + "\230\214\231\377\306\267\273\377\313\274\275\377\245\222\223\377\213u~\377" + "\200jw\377\260\235\246\377\271\252\260\377\250\225\235\377\267\245\242\377" + "\221|}\377\232\205\206\377\241\217\220\377zmn\377}qk\377\251\243\232\377" + "\274\266\260\377\261\255\250\377\256\254\241\377\223\223{\377\225\226}\377" + "\234\235\201\377\207\204\\\377\216\216e\377\227\230p\377\226\227q\377\231" + "\232w\377\225\231w\377\233\237\202\377\240\242\207\377\232\230|\377\212\204" + "j\377bS;\377aQ:\377`M\065\377V=\"\377H-\024\377bH\071\377U<,\377=$\021\377A'" + "\024\377nZL\377^C\062\377mUI\377ue\\\377R>,\377j_O\377uog\377pjf\377aZZ\377" + "i^Z\377\217~y\377\204qj\377\211ys\377\220\200|\377\233\213\207\377\220}z" + "\377\264\245\242\377\231\214\225\377\206w\202\377\243\227\236\377\260\246" + "\252\377\232\213\224\377\226\204\216\377\246\221\225\377\310\273\274\377" + "\273\265\265\377~\232j\377t\234:\377o\236\021\377r\241\007\377v\245\003\377u\245" + "\001\377r\242\013\377~\260\071\377l\234\000\377\253\303\062\377\206\252\010\377s" + "\240\001\377s\241\002\377p\242\001\377q\236\001\377~\250\002\377\213\261\002\377\211" + "\255\001\377\202\251\002\377v\242\002\377z\244\002\377~\252\011\377y\247\004\377t\243" + "\001\377u\250\001\377q\243\001\377j\235\002\377o\250\002\377u\250\001\377s\242\001\377" + "g\230\002\377\\\220\001\377\200\257$\377r\244\013\377\206\265\021\377\222\302\035" + "\377\203\265\024\377[\213\000\377x\242\001\377\205\260\001\377t\241\002\377p\243\010" + "\377Ts\071\377?V\061\377$%\031\377\017\013\006\377\015\011\003\377\007\006\002\377\000\000\001\377" + "!\037!\377\000\000\000\377\000\000\000\377%!$\377\012\005\011\377$\040&\377'\024#\377i[h\377" + "zk{\377\203u\204\377\203w\204\377\223\206\225\377\246\232\242\377\317\302" + "\305\377\313\274\275\377\230\200\206\377yem\377\212v\200\377\264\244\253" + "\377\250\227\236\377\253\225\233\377\272\244\241\377\232\203\200\377\241" + "\212\205\377\226\203}\377uc_\377M\071\061\377o^V\377\250\242\232\377\265\260" + "\247\377\210~h\377{oO\377ta?\377[@\027\377U<\022\377dR-\377aN*\377bN*\377l" + "`\070\377\204\202Z\377\206\206_\377\217\217m\377\233\234\177\377\227\227z" + "\377\213\206h\377\202yY\377\221\204c\377\201pQ\377udA\377\212y\\\377~nN\377" + "^I-\377W>#\377u]M\377gJ\060\377v`G\377\245\234\224\377\231\221\201\377\204" + "zi\377mbS\377cXQ\377i`\\\377od^\377~lg\377|jg\377\204sr\377\225\205\203\377" + "\203qp\377\206qo\377\254\235\233\377\203u\201\377\221\206\220\377\231\213" + "\223\377\252\235\241\377\234\215\224\377zju\377\215|\204\377\270\253\256" + "\377\302\272\276\377\177\225n\377~\240A\377}\245\026\377o\234\001\377r\240\001" + "\377z\255\001\377u\244\010\377z\255\026\377{\250\007\377\201\250\004\377z\241\001\377" + "\214\261\033\377r\236\003\377w\247\002\377w\243\002\377\213\264\001\377\215\261\002" + "\377\212\255\002\377\207\256\002\377\202\252\001\377p\233\002\377x\242\014\377\212" + "\265,\377|\253\014\377y\251\001\377|\257\001\377s\240\001\377z\251\003\377x\245\001" + "\377q\236\001\377g\227\002\377i\233\006\377\211\265%\377\213\271\060\377~\261\026" + "\377h\233\005\377i\231\001\377p\234\001\377\201\253\002\377\220\271\003\377s\237\035" + "\377Zz+\377?S(\377\061@\036\377\012\012\003\377\000\000\000\377\000\000\000\377\000\000\000\377\015" + "\015\016\377\004\004\004\377\000\000\000\377\001\001\001\377\001\000\001\377\020\015\020\377\002\002\003\377" + "\001\000\000\377;-\071\377xgv\377zlz\377\217~\221\377\217}\216\377\243\222\235\377" + "\324\312\312\377\264\240\241\377\220{{\377lZb\377\212z\201\377\266\246\254" + "\377\261\240\243\377\303\263\261\377\301\255\250\377\266\235\221\377\231" + "~s\377\226|q\377}fa\377l[U\377g]T\377k`M\377{nU\377\177pT\377\215~b\377\210" + "y^\377q_?\377]G!\377\\D\032\377aK\037\377iV&\377m^,\377\200xI\377|tC\377wp" + "B\377\177xQ\377~xS\377\214\205`\377\206~X\377xkC\377\201uN\377\203zW\377" + "\205}^\377\233\226~\377\236\231\206\377\224\212w\377\235\223\200\377\232" + "\214u\377\237\224~\377\227\213x\377yl[\377bSA\377^UC\377PC/\377XJ;\377cV" + "L\377xmg\377\203tr\377wdd\377vef\377\204qs\377\224\200\201\377\256\240\240" + "\377\204x\201\377\204y\203\377\233\216\226\377\240\221\232\377\232\212\224" + "\377veq\377\223\201\207\377\263\247\247\377\310\304\305\377\221\241\201\377" + "m\227)\377n\231\010\377s\236\003\377t\232\003\377\213\267\032\377x\250\003\377{\251" + "\001\377k\237\002\377h\233\001\377w\247\000\377\260\277\061\377\216\246\024\377q\232" + "\001\377\177\252\002\377\203\261\002\377}\242\002\377\220\264\002\377\213\261\001\377" + "{\244\002\377u\240\003\377z\245\010\377x\246\012\377k\234\002\377z\257\002\377y\250" + "\002\377s\236\002\377\177\253\003\377z\246\001\377i\230\002\377j\231\002\377w\241\003\377" + "y\244\002\377u\246\004\377v\256\006\377e\220\002\377q\230\001\377\205\253\001\377\214" + "\261\000\377\211\265\017\377f\215\037\377Vi\063\377+>\015\377\040\062\012\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377!\027\036\377h[g\377\200q\177\377" + "\237\223\241\377\204v\206\377\251\233\243\377\303\267\270\377\244\216\221" + "\377\204pv\377mYe\377\224\204\211\377\270\251\254\377\264\242\246\377\263" + "\233\232\377\271\242\231\377\267\237\224\377\237\203|\377\235\204\177\377" + "\200h`\377aH>\377ZA\064\377n_E\377sfG\377\204zX\377\242\237\204\377\243\241" + "\210\377\227\224w\377\200uT\377\203vS\377\210|U\377\203vL\377\213\204Z\377" + "\212\205Z\377\213\205]\377\213\204_\377\177vO\377xk?\377m]/\377iY.\377pb" + "\067\377skB\377}zZ\377\226\227\204\377\223\216|\377\237\235\213\377\250\246" + "\224\377\240\235\210\377\213\200k\377Q<'\377VD\066\377eVO\377O>'\377XH+\377" + "^O/\377]J,\377eT:\377\217\200x\377\210xx\377\177nl\377\202pn\377\214|x\377" + "\210sq\377\236\215\212\377~pz\377\215\201\212\377\246\231\241\377\221\201" + "\213\377\237\222\231\377rdn\377\215}\207\377\256\241\244\377\274\266\267" + "\377\226\243\205\377g\223\033\377e\224\003\377j\222\002\377p\225\004\377\224\272" + "\061\377\204\256\020\377~\251\002\377i\237\001\377b\230\000\377n\237\001\377\210\247" + "\021\377\276\311P\377|\241\004\377x\243\002\377{\253\002\377v\240\001\377\215\264" + "\002\377\202\250\002\377\200\246\002\377\204\254\002\377s\232\001\377y\245\001\377t\241" + "\001\377r\240\002\377o\234\002\377o\237\002\377y\250\002\377v\242\002\377s\237\002\377" + "l\232\002\377i\224\002\377u\242\001\377q\236\001\377r\244\002\377m\233\002\377\203\257" + "\003\377\211\262\003\377\222\272\021\377\213\263E\377\221\266V\377w\215d\377a" + "\202E\377\065M\"\377\034.\020\377\016\032\002\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\"" + "\035!\377b[c\377sds\377\230\212\235\377\204u\211\377\246\227\241\377\302\267" + "\266\377\232\210\210\377\213y\200\377s`m\377|jv\377\247\225\233\377\216z" + "\203\377\260\234\230\377\254\225\216\377\262\232\215\377\224zo\377\207le" + "\377oVO\377s\\S\377fRE\377\\N\066\377h^=\377ld@\377}wW\377\215\211k\377zu" + "Z\377}x\\\377\206|^\377|mF\377\215\202]\377\215\203\\\377|qI\377\212\200" + "\\\377\224\215n\377\203uP\377{kA\377m\\.\377hW/\377gW\061\377ujI\377\203|" + "b\377\203}c\377\202y]\377|rO\377xkH\377YF)\377maG\377K<\034\377RE'\377zqf" + "\377SC&\377P@\036\377VA!\377P:\031\377aO\063\377\204tc\377\226\213\213\377u" + "cb\377\213zw\377\220\177y\377\216xt\377\230\205\203\377wep\377\206u\201\377" + "\246\234\241\377\220\206\216\377\227\214\223\377k^h\377qcm\377\244\227\232" + "\377\274\270\272\377\212\227w\377e\222\035\377`\216\001\377p\233\002\377r\231" + "\003\377\225\273-\377|\243\001\377\201\257\003\377r\244\007\377r\247\017\377g\230" + "\001\377t\231\002\377\270\306S\377\214\256\033\377y\245\001\377o\234\002\377r\237" + "\001\377{\246\001\377\201\250\001\377\201\246\002\377\206\250\002\377}\240\003\377\211" + "\257\002\377\177\247\001\377w\241\001\377q\240\001\377w\252\002\377f\232\002\377w\244" + "\001\377~\244\001\377j\223\002\377q\227\002\377t\243\002\377v\246\002\377y\251\005\377" + "g\227\001\377s\243\002\377|\252\002\377u\241\002\377l\232\010\377c\215+\377=]$\377" + "Ms,\377Jb>\377WlL\377[mX\377XlW\377NcM\377N`H\377AR?\377-\066,\377\012\016\011" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377'#'\377JAI\377ses\377\225\207" + "\230\377\205v\210\377\270\255\262\377\271\254\254\377\245\224\224\377}kt" + "\377k[g\377\201t}\377\253\232\236\377\240\215\220\377\251\222\216\377\255" + "\222\213\377\274\242\225\377\220ra\377\225xj\377\220ti\377x^R\377iRA\377" + "N\071\036\377=,\013\377dW\064\377{rO\377\221\213m\377\214\204j\377}qW\377|lP" + "\377\203sU\377\210wZ\377\201pO\377\212yX\377\204rW\377\234\224\200\377\231" + "\221{\377\211|b\377\221\207m\377\211\201h\377\207\201h\377\212\206m\377\203" + "ze\377\204{e\377\200xZ\377YO$\377_R(\377reF\377gZ\064\377[M%\377Q@\030\377" + "rdP\377@/\013\377VC\040\377ZD#\377\\E(\377yfP\377wdT\377~oj\377\210yy\377~" + "je\377\206pi\377\224\200y\377\227\203\201\377wfr\377\215~\207\377\252\236" + "\244\377\235\220\227\377\235\217\227\377l^j\377\203u}\377\230\213\215\377" + "\265\256\264\377u\211X\377_\212\022\377_\214\001\377x\244\022\377u\243\011\377" + "|\247\013\377y\244\001\377u\245\000\377\203\265)\377\201\265\063\377g\236\017\377" + "}\250\004\377\207\252\014\377\240\270\035\377\214\256\002\377\201\245\002\377|\243" + "\001\377k\231\002\377x\243\002\377}\243\002\377\217\261\002\377\215\255\002\377p\230" + "\002\377u\236\002\377~\252\001\377~\254\001\377u\245\002\377Y\216\002\377l\230\002\377" + "v\235\001\377q\230\002\377u\234\002\377z\252\003\377p\231\003\377x\240\001\377g\236\003" + "\377t\247\001\377z\246\002\377u\234\002\377x\240\002\377W\201\003\377;a\003\377#H\002\377" + "\036\064\004\377\013\013\006\377\004\004\003\377\001\001\001\377\001\000\001\377\000\001\001\377\003\011\003\377" + "(\067%\377b{^\377w\222y\377AQ?\377\015\023\011\377\035\037\023\377==\066\377<:<\377" + "kbn\377\215\177\220\377\216\177\217\377\264\251\255\377\270\254\253\377\240" + "\216\217\377\211w~\377m\\g\377\202v|\377\267\252\254\377\234\206\216\377" + "\244\215\214\377\226yq\377\256\225\211\377\237\201p\377\221qa\377~bO\377" + "rWB\377fN\066\377UB\"\377C\064\020\377?\061\017\377J\066\026\377kV\070\377n\\B\377" + "\213\205v\377\230\224\205\377\211\200m\377\215\200k\377\231\214w\377\241" + "\226\201\377\232\216w\377\215\201i\377\241\234\210\377\243\241\215\377\235" + "\234\207\377\223\220y\377\224\216t\377\205}[\377ykC\377kZ\061\377sd<\377]" + "H\033\377Q\071\014\377sb=\377l^\064\377VC\033\377G\063\020\377]L\070\377G.\024\377" + "K\063\025\377M\066\027\377q^D\377zfT\377r]Q\377\202oi\377zgb\377\230\207|\377" + "\223\200u\377\205qn\377\226\207\210\377udp\377\200q|\377\223\210\216\377" + "\232\220\224\377\220\205\214\377fYe\377o`k\377\240\224\231\377\261\254\260" + "\377s\211X\377a\212\037\377f\222\027\377i\230\010\377x\250\010\377v\244\003\377" + "v\241\001\377{\246\007\377\200\261\032\377t\250\013\377l\243\006\377e\234\002\377q" + "\237\002\377\200\247\011\377\230\267\013\377\231\267\002\377\225\265\002\377m\232" + "\002\377u\241\002\377\205\260\002\377\223\270\002\377\212\254\001\377i\225\002\377x\242" + "\002\377\206\256\001\377\202\254\002\377u\242\002\377l\232\002\377c\222\001\377y\245" + "\003\377u\237\001\377p\233\002\377t\237\002\377w\233\001\377}\241\003\377g\240\006\377" + "u\250\001\377y\245\002\377}\247\001\377z\242\002\377]\211\001\377Ak\004\377%@\002\377." + "\063#\377!%\026\377\016\016\013\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\011\015\011\377:J#\377`l;\377@G*\377;<\064\377l" + "gn\377\224\210\224\377\207}\210\377\255\244\250\377\272\257\260\377\233\214" + "\216\377xjo\377^PZ\377{ms\377\260\244\245\377\246\225\230\377\243\220\213" + "\377\212ql\377\272\245\235\377\240\202q\377~XB\377yT?\377V\063&\377Z=,\377" + "s^E\377mW\070\377S<\033\377G/\013\377K\064\023\377xhR\377\202ub\377wiV\377\225" + "\217}\377\220\211v\377\230\221\200\377\235\226\204\377\250\241\216\377\243" + "\236\211\377\223\214q\377\221\212m\377\215\204d\377ykE\377xgB\377vb\064\377" + "lV%\377gP#\377\\D\031\377aK!\377ziB\377\177qF\377fP&\377B*\013\377\060\027\003" + "\377ZA\063\377W=,\377pYC\377\202nX\377\213xj\377fMC\377pYP\377nUN\377\206" + "oj\377\237\212\202\377\202ka\377\231\206}\377\235\220\214\377~v{\377\212" + "\201\210\377\235\221\230\377\241\226\234\377\205x\202\377`S`\377l_j\377\241" + "\232\240\377\244\243\246\377i~\071\377`\202\025\377x\244@\377`\216\002\377|\254" + "\005\377v\243\001\377y\247\002\377z\250\006\377w\246\004\377t\241\000\377}\256\005\377" + "i\235\001\377k\225\001\377|\242\001\377\210\251\002\377\214\255\001\377\214\257\001\377" + "y\241\001\377v\240\001\377\201\250\002\377\203\247\002\377\205\250\002\377s\233\001\377" + "~\247\002\377\210\264\002\377}\250\001\377x\242\002\377x\245\001\377m\232\001\377{\245" + "\004\377}\250\020\377x\245\002\377s\232\002\377l\217\002\377\207\246\002\377n\251\003" + "\377k\237\002\377k\232\002\377k\233\001\377g\231\003\377]\221\001\377Hx\003\377\031+\005" + "\377'\060\030\377MOE\377,**\377\012\007\010\377\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\001\002\000\377\011\027\002\377,D\010\377\060I\010\377\062E\020\377=" + "H)\377rtq\377\232\221\231\377\221\206\216\377\221\205\214\377\261\245\244" + "\377\211y\177\377hX`\377UHP\377\221\206\212\377\252\240\243\377\243\224\227" + "\377\235\212\204\377\215yl\377\253\227\212\377\257\224\206\377\203]I\377" + "\222o^\377\205h\\\377t]R\377nWA\377xaD\377u`;\377hR*\377aI#\377eN+\377Y:" + "\030\377wbK\377xiS\377\177pZ\377\215\177h\377\177pT\377\230\216t\377\226\212" + "n\377\204vR\377\205wU\377\222\210i\377\216\206f\377\214\203e\377\221\206" + "h\377\223\204f\377\202pW\377\224\203o\377\215}a\377xdB\377hM-\377;\040\006\377" + "@'\015\377A'\015\377{fR\377s\\@\377jR<\377oZK\377yf[\377aKG\377eOI\377oWQ\377" + "\241\213\203\377\210lg\377\203bZ\377\232\203\200\377\233\221\223\377\205" + "\200\205\377\230\224\230\377\252\242\245\377\252\237\244\377\216\200\211" + "\377VGR\377gXe\377\230\216\226\377\236\233\235\377{\210S\377g\201\060\377" + "{\251F\377f\234\006\377p\236\002\377\177\252\002\377z\245\003\377y\244\002\377s\241" + "\003\377t\237\002\377\200\250\001\377|\252\004\377j\226\002\377w\240\001\377}\241\003\377" + "\177\243\001\377\200\244\002\377\177\245\001\377\177\246\003\377\201\251\002\377s\232" + "\002\377\177\243\001\377\200\244\002\377\207\260\002\377|\251\002\377w\246\001\377r\234" + "\001\377v\244\000\377\212\256\016\377l\222\002\377|\252\027\377r\240\002\377w\236\002" + "\377|\237\002\377\177\241\002\377p\253\011\377U\200\000\377Z\204\002\377Y\206\002\377" + "[\220\006\377\\\232\006\377S\211\004\377\020\034\003\377\000\000\000\377\023\022\016\377\"\034" + "\025\377\013\012\006\377\000\000\000\377\000\000\000\377\000\000\000\377\001\001\001\377\007\014\001\377\027" + ")\001\377&E\003\377\066^\011\377;e\020\377Mr&\377.A\030\377_[Z\377\220\203\215\377" + "\214\200\211\377\231\216\224\377\276\263\262\377\220\200\203\377g[d\377O" + "@K\377QDH\377\236\223\222\377\247\227\226\377\243\217\213\377\200kd\377\231" + "\204y\377\262\235\221\377\237\201t\377\201aS\377mOA\377I*\036\377hO>\377r" + "X?\377|gI\377v`;\377lW-\377r\\/\377oV)\377~iA\377u_\071\377~jH\377\210yY\377" + "\231\217q\377\235\224x\377\241\231}\377\216\202b\377\215\200a\377\205z]\377" + "\211~d\377\200s\\\377\177r`\377\213\177p\377\222\206z\377~oZ\377|jJ\377Q" + "\071\024\377:!\005\377F.\020\377\\H#\377nX\066\377\231\210s\377{gK\377nYE\377v" + "cQ\377]L>\377aQB\377bL>\377zaT\377\220{l\377|bT\377\200fW\377\273\262\252" + "\377\222\205\210\377\200pz\377\210}\202\377\227\216\220\377\216\205\212\377" + "i^d\377L?K\377d[d\377\226\214\225\377\207\206|\377l|M\377k\225>\377t\251" + "M\377i\240\021\377i\227\001\377t\237\002\377x\241\002\377w\237\000\377\200\254\016" + "\377v\236\003\377\206\250\002\377\230\274\026\377|\250\010\377t\240\002\377~\250" + "\002\377y\237\001\377\220\256\002\377~\240\002\377\205\254\001\377\203\253\002\377\200" + "\247\001\377\202\247\002\377}\243\001\377\204\255\002\377m\233\002\377z\256\016\377" + "{\254\012\377m\226\002\377\207\251\011\377\210\254\015\377\177\254\017\377k\227" + "\002\377\205\252\006\377r\226\002\377{\245\004\377b\243\013\377`\230\015\377Z\206\004" + "\377_\215\001\377Y\212\002\377]\224\000\377[\231\012\377/O\006\377\022\035\002\377\001\002" + "\001\377\000\000\000\377\000\000\002\377\002\003\001\377\006\011\002\377\005\007\002\377\012\030\002\377\031" + ".\002\377;X\006\377:_\003\377\066_\003\377=j\016\377=_\032\377\071D*\377a[^\377\213\177" + "\211\377\225\213\224\377\214\200\211\377\253\237\242\377\231\211\214\377" + "pdk\377ZOW\377d^`\377\203{z\377\231\215\216\377\273\254\246\377\237\210\201" + "\377\177rh\377\261\242\226\377\263\242\224\377\215r^\377v\\H\377|bT\377i" + "O?\377s^O\377xdN\377\206sU\377\205pK\377\222\177W\377\200k?\377oU&\377\204" + "oK\377zfF\377\205rV\377\201nU\377\236\224~\377\205tX\377\216\200`\377\222" + "\204c\377\215|Y\377\230\210h\377\206wU\377\203pU\377t`G\377fS>\377\\K\066" + "\377=*\026\377\061\035\004\377N\070\021\377eR%\377ub\066\377\215{U\377\217\177h\377" + "T<\036\377zeN\377bJ\066\377bJ<\377}f\\\377xaW\377\247\223\216\377w]V\377`F" + ";\377\204rj\377\274\264\254\377\212}\200\377\217\204\211\377\237\226\232" + "\377\230\217\225\377\211\201\210\377`Xa\377F?J\377`Ya\377\243\236\240\377" + "sx`\377l\216>\377\\\217\027\377u\252>\377j\235\006\377o\233\002\377k\225\001\377" + "~\245\002\377z\241\002\377v\235\000\377w\237\002\377\217\264\021\377\212\255\006\377" + "\207\256\010\377t\240\001\377y\244\002\377w\237\002\377\214\254\002\377\211\251\001" + "\377\207\250\002\377\201\246\002\377y\240\002\377}\245\001\377w\236\002\377|\250\002" + "\377b\222\002\377x\252\011\377t\234\001\377~\240\002\377\206\252\007\377}\242\005\377" + "\200\252\014\377m\225\002\377x\236\001\377y\235\003\377\202\253\025\377U\223\001\377" + "c\237\020\377a\232\012\377_\222\001\377c\223\001\377d\227\002\377Y\206\002\377\\\220" + "\011\377R\177\014\377#\067\002\377\023$\002\377\013\031\002\377\034'\002\377?F\013\377;H" + "\013\377\012*\002\377!E\003\377\067^\002\377Ly\003\377Nz\012\377t\246H\377\067R\021\377" + "?O+\377]`Y\377\200z\177\377\201w}\377|px\377\247\233\234\377\233\215\216" + "\377rfk\377SEQ\377LCK\377\214\205\204\377\252\236\234\377\273\256\246\377" + "\250\215}\377pZJ\377\217|p\377\302\266\255\377\234\205r\377z[B\377rS<\377" + "~`I\377oQ:\377mO?\377|cO\377\213wY\377\230\205d\377\222\200^\377\210uR\377" + "\206qO\377\202jF\377|b<\377\212rQ\377\206oL\377\206oI\377\202jC\377za\070" + "\377tZ\060\377\207uX\377\213|_\377vfL\377\\N\064\377\065\"\012\377\065\040\010\377" + "\066\040\004\377Q:\026\377r_\070\377\214zW\377\223\202a\377\206rU\377\201kV\377" + "kT;\377\\A*\377fK\067\377sZK\377|d[\377\233\213\204\377\232\213\205\377}h" + "_\377hRI\377\240\221\207\377\263\253\250\377|ou\377\214\202\206\377\211\177" + "\205\377\243\233\240\377\203\177\205\377UNX\377JAL\377h`g\377z|o\377kzV\377" + "g\231\064\377c\225#\377\202\261A\377n\232\004\377n\232\002\377p\222\002\377\224" + "\251\002\377\215\251\001\377x\231\001\377}\252\006\377{\241\003\377\205\253\002\377\205" + "\255\002\377|\246\001\377y\242\002\377z\242\002\377\201\244\001\377~\236\001\377\207" + "\252\002\377\202\247\002\377\177\244\001\377\202\251\002\377\215\270\003\377x\244\002" + "\377`\217\002\377o\237\002\377r\231\002\377\207\254\002\377p\225\003\377n\223\002\377" + "y\245\010\377c\215\002\377s\234\002\377\204\256\007\377x\237\002\377d\230\010\377c" + "\227\006\377\\\214\001\377Q}\002\377]\211\001\377^\206\001\377`\206\002\377Z~\001\377Y" + "\210\005\377=a\003\377%G\002\377\060J\002\377/B\003\377/?\002\377GV\005\377-F\002\377*O\003" + "\377\065\\\002\377Q|\002\377`\220\015\377X\205\031\377;c\006\377Ih\"\377m{_\377\205" + "\206~\377\204~}\377\201vz\377\246\236\236\377\232\217\217\377pbf\377I:D\377" + "<-\067\377WJM\377\201rq\377\241\223\216\377\266\243\225\377\201fV\377dNG\377" + "\236\213~\377\250\221|\377\211lP\377pL/\377}]@\377\205hN\377\177dJ\377mL" + "\061\377qR\066\377yaF\377\221\202g\377\226\213p\377\236\225z\377\247\236\204" + "\377\232\215t\377\231\211n\377\215|_\377\222\201d\377\230\210l\377\210vV" + "\377~jK\377vbG\377iS\070\377aJ/\377S>\036\377V@\033\377mY\067\377hP.\377[@\040" + "\377\231\205e\377\243\223u\377zdM\377v^D\377\203nS\377w[B\377^@)\377\210" + "pZ\377\237\211y\377\214uh\377\242\222\211\377]LF\377G\065\062\377\204rm\377" + "\304\275\271\377\222\210\207\377\204y~\377\226\214\217\377\237\226\231\377" + "\235\227\235\377oms\377@:E\377@\071F\377pmo\377ip\\\377m\206Y\377d\222\067" + "\377a\224(\377m\244\036\377`\220\000\377~\252)\377y\232\006\377\232\260\004\377" + "\215\251\002\377\200\245\002\377z\244\004\377n\224\002\377\200\245\002\377\205\251" + "\002\377\215\263\002\377t\235\001\377t\235\002\377~\242\002\377\204\251\002\377z\237" + "\002\377\207\256\001\377\177\244\002\377\211\260\001\377\204\251\001\377~\246\002\377" + "q\236\002\377a\216\001\377s\234\001\377w\235\002\377f\214\002\377l\221\000\377v\237\003" + "\377l\223\002\377r\232\002\377\201\247\002\377{\241\000\377\204\260N\377}\246\060" + "\377a\212\002\377Do\002\377Y|\002\377X{\002\377Rx\001\377\\\203\001\377Mv\002\377Jw\003\377" + "Ky\004\377Cd\002\377\064O\003\377\035\067\001\377)=\001\377:M\002\377>V\002\377\062O\002\377" + "<a\002\377a\220\022\377[\210\004\377Iq\001\377Hj\030\377N\\@\377ytw\377{pv\377zo" + "v\377\233\221\223\377\227\215\213\377ymq\377WKT\377?\065>\377e[b\377\204z" + "y\377\210xw\377\307\267\256\377\221wj\377^H>\377\201i_\377\215uh\377\227" + "{g\377\242\206j\377\234}[\377\207gE\377sS\066\377\201bH\377a@\"\377jL-\377" + "\177jK\377\201pT\377\221\210u\377\243\235\212\377\234\226\202\377\245\236" + "\216\377\240\233\214\377\243\233\214\377\235\223\204\377\223\206v\377\213" + "|l\377\224\205w\377\206sa\377\200nV\377\206tX\377\207oR\377z_;\377\203hH" + "\377\231\204b\377\251\234y\377}gP\377eI\065\377\216yb\377~iP\377fI-\377}a" + "G\377\241\213q\377\250\227\204\377\226\205s\377\217\202u\377A,$\377\\J?\377" + "\244\230\217\377\303\276\271\377\203\177\202\377\202{\200\377mck\377\213" + "\203\207\377\203y\200\377NDN\377JDL\377MIP\377ppm\377ahR\377{\235j\377Y\207" + "-\377d\225+\377a\226\014\377a\220\003\377p\232\012\377n\221\002\377{\244\002\377" + "y\241\002\377z\243\003\377k\223\002\377o\230\002\377}\250\002\377\203\254\002\377\207" + "\263\002\377p\241\003\377j\226\002\377\177\246\001\377\210\256\001\377\205\253\001\377" + "\202\252\002\377~\246\002\377\206\255\001\377w\234\002\377\206\254\006\377u\233\002\377" + "x\241\003\377w\240\002\377l\221\001\377m\224\001\377|\252\017\377\204\265\030\377|" + "\256\016\377\211\272\035\377\224\300)\377\215\270\036\377^\222\005\377p\233\012" + "\377r\226\007\377n\215\007\377`}\001\377_\177\001\377]\177\001\377Z\203\002\377Kt\002\377" + "It\002\377J\177\003\377Cj\002\377\060O\001\377@X\004\377\032\070\002\377\037<\002\377,I\000\377" + "\063W\001\377Mv\004\377\\\206\037\377Jj\002\377Yx\005\377]x\036\377Xe?\377poi\377\205" + "{\201\377\205z\201\377\225\213\215\377\232\217\217\377~tz\377_S[\377A\071" + ">\377\071\063\064\377]YU\377\177xv\377\272\255\245\377\253\227\203\377lP@\377" + "T\066-\377\203m`\377\264\240\215\377\253\226y\377\252\224v\377\231\177c\377" + "\207mS\377tU;\377fE$\377nP/\377bH%\377\201oP\377\215\177d\377\204uZ\377\210" + "}f\377\224\217\201\377\222\216\200\377\212\201r\377\232\221\204\377\243\235" + "\215\377\226\216z\377\227\211t\377\215|f\377\203pS\377q[\067\377\206nJ\377" + "\240\214k\377\257\242\205\377\242\222t\377{dK\377^A.\377w^I\377zcK\377nR" + "\070\377\200eM\377\237\213x\377\261\245\223\377\236\217|\377\220\201q\377" + "S?\062\377P<\061\377\214zj\377\271\262\244\377\244\233\223\377\205|{\377\220" + "\207\207\377\220\207\213\377\206{\200\377obl\377OEP\377F?G\377VRU\377qrg" + "\377brQ\377r\241S\377\\\216-\377b\227%\377T\204\005\377f\222\010\377g\215\001" + "\377k\223\001\377v\244\003\377\177\255\013\377y\246\004\377g\222\002\377x\243\002\377" + "\177\254\002\377\201\255\002\377t\243\001\377m\243\002\377l\232\001\377|\244\001\377" + "\210\256\001\377\201\247\002\377}\245\002\377|\247\002\377\177\247\001\377{\237\002\377" + "\210\254\002\377{\237\002\377\206\262\005\377z\244\005\377\204\257\016\377\224\275" + "$\377\225\277#\377\206\263\017\377\205\264\013\377y\243\004\377\207\265\033\377" + "\214\270#\377V\213\003\377\\\215\002\377Z}\001\377x\222\021\377]v\003\377^x\001\377" + "Zv\001\377^}\003\377Wu\002\377A`\002\377\"K\002\377<^\002\377Rk\003\377<L\002\377AW\002\377" + "Xw\007\377\217\235,\377`y\023\377d\215*\377Fg\002\377Hc\002\377@Z\002\377Je\007\377" + "`s\067\377nq`\377\214\215\207\377odk\377vkq\377\217\205\206\377\217\206\207" + "\377f]^\377L@B\377J@A\377\201zv\377\224\212\201\377\201vi\377\263\251\232" + "\377\212wd\377Z@\060\377dM=\377\204ra\377\242\223|\377\230\203i\377\216tZ" + "\377\236\210k\377\226~_\377oM)\377jJ\037\377v^\065\377kU-\377oY\062\377ygB\377" + "udA\377\215\200f\377~qV\377yiP\377\212\177l\377xjT\377kZA\377vcG\377gQ\060" + "\377lS-\377\237\217j\377\250\232y\377\216\177b\377\177lV\377sWA\377tW@\377" + "rV;\377v]D\377jL\061\377\222{b\377\245\221~\377\261\241\220\377\242\222\202" + "\377\235\217\201\377uka\377L\066+\377nUA\377\261\250\224\377\254\244\226\377" + "\200vt\377|sp\377rjh\377sjk\377ohk\377e_e\377XU[\377KIO\377jhi\377~\204w" + "\377p\213`\377p\231Q\377j\225\066\377b\226\024\377V\202\002\377^\215\002\377c\215" + "\002\377\205\260\011\377y\246\003\377u\240\003\377{\247\002\377e\221\002\377z\245\001" + "\377\216\261\002\377\222\264\002\377p\232\001\377o\235\001\377x\246\003\377~\243\001" + "\377\212\256\001\377\202\244\002\377|\242\001\377\200\247\002\377\205\254\002\377\203" + "\246\002\377\215\253\002\377\207\252\001\377}\241\002\377\203\247\001\377\204\242\003" + "\377\212\242\002\377\220\254\002\377\200\240\002\377\205\257\005\377\204\252\005\377" + "\204\252\006\377\210\256\004\377h\231\024\377Z\210\002\377Ou\001\377p\224\011\377]" + "x\001\377_x\001\377`y\002\377_z\002\377Tn\001\377<W\002\377)I\002\377\065S\002\377Eg\001\377" + "Rx\003\377Rw\000\377f\227\004\377Gp\006\377Js\026\377Oy\027\377Bb\000\377Hf\002\377AY\001" + "\377B]\003\377Tl#\377Yb@\377{\200p\377wpo\377vlm\377\206{|\377\234\217\217" + "\377shl\377[PW\377aW_\377g__\377pkg\377}xs\377\226\216\202\377\251\235\212" + "\377q[F\377`E\071\377_J<\377\230\210y\377\222{i\377{^O\377\240\213y\377\274" + "\254\233\377\250\221v\377\216rM\377\206lE\377\224~[\377\204pE\377\200nD\377" + "}lE\377\211|Z\377\205wZ\377\214\201f\377\217\204g\377\203wX\377\215\203e" + "\377\214~\\\377\227\206b\377\210uN\377\236\217l\377\220\177_\377oV@\377j" + "Q=\377cH/\377\200eH\377iK.\377mO\062\377\217vZ\377\253\232\204\377\260\240" + "\216\377\253\235\214\377\235\212y\377\216\177q\377ZKC\377th[\377\240\223" + "\203\377\266\260\237\377\217\207~\377\200vu\377\201zv\377wqo\377khh\377X" + "WY\377HGL\377QOU\377[Z_\377vvu\377luc\377c\220K\377]\215\064\377V\212\023\377" + "^\225\020\377T\207\001\377l\234\010\377z\245\015\377\202\255\005\377u\235\002\377" + "p\226\002\377k\227\001\377t\242\003\377}\252\001\377\214\261\002\377\205\253\001\377" + "p\234\002\377s\244\005\377}\257\007\377\207\253\007\377\204\244\002\377y\232\001\377" + "\205\241\010\377\210\253\002\377\221\265\003\377\210\253\002\377\226\260\006\377\202" + "\244\001\377\200\244\002\377\201\241\002\377\204\235\004\377\215\243\016\377\224\263" + "\016\377\205\255\004\377x\236\003\377x\233\003\377\202\255\002\377\203\252\001\377_" + "\211\001\377S\201\002\377Z\205\002\377j\223\002\377c}\002\377f\201\001\377g\200\001\377" + "g\206\002\377Yx\001\377Lm\001\377Jp\003\377Ks\002\377R}\004\377:_\001\377c\221\033\377W" + "\203\027\377Pu\020\377q\232<\377?m\010\377\066\\\004\377Nq\017\377Lj\007\377So\015" + "\377k\202\065\377i|I\377x{l\377pnj\377xrq\377\207\177}\377\236\223\221\377" + "ukl\377^SZ\377ZQX\377kfj\377e^a\377\177xs\377\203yp\377\261\251\234\377\244" + "\227\201\377mXA\377}k^\377|jb\377\217~s\377nXJ\377\213yi\377\262\244\220" + "\377\257\237\204\377\235\206d\377\213qN\377\216sM\377\240\216j\377\227\205" + "a\377\224\204`\377\202sO\377\204tS\377\204vT\377\225\210h\377\211|\\\377" + "\223\211j\377\223\212n\377\233\215s\377\207vZ\377\206rQ\377\216zY\377v`?" + "\377hR\060\377cJ*\377u[;\377\210oQ\377\227\200e\377\251\230\202\377\256\240" + "\216\377\247\231\207\377\230\207v\377\222\203v\377S@\062\377I\064\"\377\202" + "q\\\377\247\235\213\377\215\203v\377phc\377voj\377nfd\377]VU\377ZUY\377h" + "fl\377c`g\377ROX\377a`e\377xwx\377XdI\377c\236H\377I|\015\377Q\211\007\377U" + "\217\011\377S\211\001\377n\237\003\377o\234\002\377t\235\001\377x\236\002\377r\231\002" + "\377g\222\002\377m\230\003\377\204\254\001\377\201\247\002\377|\245\001\377r\242\002" + "\377k\237\016\377p\253%\377\201\250\025\377\201\244\001\377p\224\002\377\201\231" + "\007\377\225\257\004\377\226\260\003\377\217\263\001\377\232\267\012\377w\230\001\377" + "z\235\002\377\203\246\002\377\203\241\003\377\230\263\027\377\225\261\015\377\212" + "\254\001\377y\232\002\377|\242\002\377\177\254\002\377}\250\002\377X\204\002\377Hy\001" + "\377T\202\003\377Jp\004\377`\177\002\377m\222\003\377e\215\002\377b\212\002\377X~\002\377" + "Sz\003\377Ry\001\377Fc\002\377\062J\002\377\064J\002\377>Z\003\377o\237I\377j\223>\377" + "_\210\030\377Hm\003\377Or\007\377Tv\006\377g\207!\377g\205!\377b\177!\377u\206U" + "\377_^O\377pmg\377vsn\377tqj\377\221\211\200\377\210\177x\377h^^\377g\\_" + "\377dYa\377ZRY\377GAD\377]XP\377\200zl\377\270\261\227\377\214\177a\377G" + "\060!\377V?.\377{fR\377\232\211{\377\212yh\377}jU\377\240\217y\377\227\204" + "i\377\205iH\377\201e?\377\210oI\377\206pJ\377\217\200]\377\177oK\377\211" + "{X\377\213\203^\377\200wU\377\215\206d\377\212\202a\377\222\215o\377\220" + "\207n\377\226\212p\377\200oK\377\177hC\377~e=\377\207tO\377\210uT\377\232" + "\211k\377\244\224z\377\257\242\214\377\267\253\232\377\252\235\213\377\231" + "\210v\377\203sf\377scZ\377K\066(\377fN\067\377\246\231\204\377\252\244\223" + "\377\203wk\377ynd\377ka[\377\177{v\377\213\211\207\377xtx\377d^g\377`Zc\377" + "igk\377ddc\377w|t\377bwS\377]\216\066\377My\002\377R\203\005\377Y\215\011\377f" + "\230\005\377|\244\004\377w\235\004\377u\234\002\377p\226\002\377r\231\002\377r\232\001" + "\377j\226\002\377\202\252\002\377\201\252\002\377|\246\001\377u\243\007\377t\246\031" + "\377\211\277U\377\201\255\021\377~\246\002\377n\230\002\377\177\251\004\377\207" + "\251\002\377\250\275\025\377\230\263\016\377\205\240\002\377\221\260\023\377\210" + "\257\010\377\204\251\020\377}\232\006\377\216\250\002\377\254\271\035\377\222\256" + "\003\377\201\242\001\377\206\257\001\377\200\251\001\377z\246\001\377T\203\002\377V\206" + "\006\377[\214\006\377@e\001\377[\207\005\377a\221\004\377Z\203\001\377`\207\006\377S~\002" + "\377Z\202\001\377Op\004\377Vs\002\377Gf\002\377\063S\002\377Dd\002\377Ec\007\377h\243\060" + "\377S\205\000\377Lq\003\377C`\001\377Tq\003\377Lg\001\377o\211'\377{\230F\377drC\377" + "VYE\377yvn\377yvo\377||s\377rph\377\225\221\211\377~vu\377WLS\377ZNW\377" + "nhm\377^[[\377_\\U\377\\XL\377\213\205r\377\250\241\207\377o_J\377M:\"\377" + ">)\023\377{pe\377\222\212z\377kXC\377uaL\377\231\211r\377\243\223y\377\226" + "\201b\377\202jB\377\207pI\377\210wN\377\212|T\377\221\206_\377~tN\377\207" + "\200^\377\203~Z\377}uT\377\215\205j\377\204uX\377\217\200_\377\204qJ\377" + "\210tL\377\204tK\377\242\224t\377\250\232~\377\264\252\226\377\234\215u\377" + "\264\250\225\377\237\222\200\377\221\202n\377\226\210w\377{na\377G\061+\377" + "T?\071\377\236\217}\377\265\253\235\377\233\220\207\377\177rl\377ped\377~" + "wu\377}xy\377c^e\377D;I\377H?L\377c`e\377||z\377lri\377s\201n\377f\213U\377" + "X\177!\377Z\213\010\377X\211\003\377`\216\003\377t\240\002\377\206\253\017\377r\232" + "\002\377x\244\002\377f\215\002\377\202\246\007\377\201\243\002\377|\236\003\377\205\257" + "\002\377\177\245\002\377\200\250\001\377p\237\007\377n\236\015\377\225\304n\377}\246" + "\024\377x\240\002\377y\245\002\377~\247\002\377\201\247\002\377\246\300\022\377\262" + "\265/\377~\230\004\377\236\277&\377}\253\006\377{\246\015\377\201\242\003\377\221" + "\252\001\377\242\250\025\377\226\257\011\377\206\251\002\377\204\257\001\377\203" + "\253\000\377\177\245\000\377a\222\001\377q\242\023\377V\207\002\377M~\002\377L|\003\377" + "Y\216\002\377W\214\002\377Y\217\016\377Y\213\026\377T~\023\377X|\006\377Kf\001\377b" + "\223\015\377En\001\377Z\201\012\377f\223*\377V\213\010\377W\215\003\377Hn\001\377" + "Pp\002\377Qp\004\377Oo\007\377t\223H\377[|\060\377Le%\377HT.\377Y[J\377kl_\377d" + "dZ\377{ws\377\207~|\377vlm\377ZMV\377f[c\377i^f\377\\RT\377kea\377}yn\377" + "xth\377\224\217z\377\251\244\210\377{mR\377cSF\377P>\065\377via\377\206ug" + "\377\202o_\377\207ue\377\245\226\202\377\250\234\201\377\224\204c\377\220" + "}Z\377\205sL\377\212|U\377\212\201[\377\211\201\\\377\212\177]\377\216\205" + "b\377\203vT\377\220\204f\377\206wW\377\223\205b\377\215}Z\377\235\216l\377" + "\237\220m\377\232\215k\377\245\230|\377\243\227\200\377\222\202j\377\221" + "\201j\377~l[\377\214~m\377uf]\377Q:\070\377bN@\377\227\211o\377\246\232\205" + "\377\237\226\205\377\200yn\377\200}v\377vtr\377xtu\377khi\377okn\377a^e\377" + "KIO\377\\]^\377pzo\377n}j\377bwZ\377e\234K\377Hz\004\377V\211\003\377b\226\005" + "\377g\231\002\377k\232\001\377t\244\005\377q\240\002\377t\245\004\377j\227\001\377|\251" + "\003\377|\234\000\377|\237\002\377v\236\002\377z\246\002\377n\232\002\377p\235\001\377" + "y\250\062\377\240\313\213\377z\250%\377\177\252\000\377m\232\002\377\200\246\002" + "\377\213\260\002\377\224\265\003\377\243\267!\377\227\265\036\377w\237\000\377u" + "\236\001\377z\247\002\377\201\252\002\377\204\247\002\377\216\255\016\377\224\266" + "\012\377\211\262\002\377|\244\002\377\235\273\026\377\212\246\001\377b\232\006\377" + "g\233\020\377]\215\002\377X\214\002\377Y\217\004\377^\222\004\377X\211\001\377N|\003\377" + "S\200\007\377a\216\020\377p\221\"\377Ru\002\377Py\002\377e\230\004\377k\226\024\377" + "m\236\035\377e\225\002\377\\\215\001\377V\212\001\377Hr\002\377Jq\003\377`\204,\377" + "Y\177)\377Qq!\377Og#\377EU\040\377bfJ\377ij[\377xvl\377}tp\377\210~|\377n" + "ad\377eW]\377pdl\377S@N\377WJO\377j`^\377rog\377||r\377ywk\377\224\216v\377" + "\236\225v\377{qY\377L<\060\377D\060-\377mZV\377\230\215z\377\216{k\377\215" + "{h\377\235\216z\377\241\224{\377\240\222u\377\224\206e\377\222\203d\377\205" + "vY\377\217\203g\377\217\204g\377\223\210k\377\207y\\\377\224\214q\377\213" + "\203e\377\234\222u\377\215\177]\377\243\225s\377\225\206b\377\212xR\377\204" + "pK\377\205qO\377\222\201i\377\235\220|\377\217\203w\377vi]\377O\071\061\377" + "M/(\377\235\216|\377\256\243\222\377\225\210y\377vk`\377xpk\377spn\377zz" + "z\377utw\377^Y\\\377B;C\377TNU\377baf\377pss\377qys\377m\201l\377_\203R\377" + "X\215\"\377T\205\001\377\\\217\004\377Y\211\003\377b\231\007\377c\227\006\377u\236" + "\020\377q\236\002\377t\242\003\377s\240\002\377p\233\001\377\207\260#\377v\237\002\377" + "i\224\001\377s\241\002\377c\223\002\377p\237\002\377e\226\002\377\222\302z\377\200" + "\264\065\377y\243\000\377\204\250\001\377\220\262\003\377\221\270\001\377\207\260" + "\002\377\207\253\006\377\256\302C\377\204\245\014\377w\233\001\377\204\254\002\377" + "\212\257\002\377\217\255\012\377\211\245\004\377\217\262\001\377|\241\002\377\177" + "\244\004\377\215\242\016\377\214\245\001\377_\227\004\377a\230\004\377]\223\002\377" + "R\206\001\377U\211\002\377]\221\003\377W\204\001\377Ot\002\377Y\200\002\377Z}\002\377U" + "w\001\377Nr\001\377Qw\002\377Ow\000\377a\224\007\377\\\216\002\377s\241\002\377e\224\002" + "\377`\222\002\377X\220\004\377l\233=\377Y\177'\377X}\032\377b\206*\377Zx&\377" + "Xo,\377aoC\377tzd\377vvl\377xso\377|sq\377znn\377|tt\377zps\377jad\377ul" + "g\377H>?\377\\XT\377lja\377oh^\377_TB\377\205}`\377~sR\377\177pV\377n\\L" + "\377YIE\377D\062*\377rdV\377ygV\377\177kX\377\235\220\177\377\236\224|\377" + "\235\220u\377\236\220p\377\206wW\377\207vY\377\207x\\\377\211y\\\377\177" + "lO\377\221\203e\377\223\207f\377\212|[\377\214|Z\377\210wR\377xe:\377wb\066" + "\377\217\177\\\377\230\210l\377\222\207q\377\231\220\200\377i^Y\377QCA\377" + "H\067\061\377\217\177b\377\261\246\221\377\235\221\202\377\215\203x\377\216" + "\205~\377jaa\377zyy\377wxx\377]^a\377C>D\377*\040*\377RQQ\377]_[\377rxv\377" + "{\206|\377s\216l\377b\230G\377Jz\000\377b\230\006\377^\223\003\377\\\221\001\377" + "e\234\016\377h\233\021\377u\241!\377n\233\002\377\177\253\004\377w\246\003\377[\204" + "\000\377v\247\034\377m\232\005\377l\227\004\377p\236\001\377v\250\020\377{\252\002\377" + "o\233\001\377n\241\014\377y\255\022\377u\236\001\377\211\251\002\377\231\263\010\377" + "\244\300\010\377\204\246\000\377\221\260\026\377\215\250\"\377\270\315n\377\201" + "\243\006\377\202\246\002\377\210\250\000\377\232\252\017\377\213\235\001\377\233\273" + "\001\377\211\254\001\377\204\252\002\377v\231\001\377\206\250\002\377e\234\014\377c" + "\231\012\377^\222\002\377Z\214\002\377a\221\003\377b\216\003\377e\212\003\377_\200\002" + "\377Su\002\377[y\002\377Vr\001\377[{\002\377`\210\002\377i\223#\377]\213\016\377Nu\005" + "\377v\241\005\377T\202\002\377\067d\000\377^\212'\377o\234\064\377S\200\000\377v\241" + "\020\377\223\265\022\377\237\301(\377\214\260Q\377g\202:\377`tB\377~\200v\377" + "\201\200|\377\205\202\177\377\210\205\200\377\204\204~\377\214\222\210\377" + "fa`\377?\060\066\377<\061\064\377PIK\377d[[\377phc\377uo`\377rjQ\377\201zZ\377" + "\247\237\204\377\227\211l\377fVC\377SB\065\377\067$\036\377QA;\377dSG\377uh" + "[\377\217\202o\377\214zf\377\240\220w\377\215|[\377\210y[\377\200sX\377}" + "oS\377\200pQ\377weB\377\203sO\377\212{W\377vbC\377\202oQ\377\207uZ\377\232" + "\212r\377\224\204p\377\225\207v\377ti`\377bVT\377L=?\377jZN\377\231\211n" + "\377\261\246\213\377\237\221w\377\215\201k\377\202{j\377ytk\377\204\205\200" + "\377{\177|\377rtt\377YZZ\377QTQ\377\\dU\377bp[\377\\gX\377mzk\377o\210m\377" + "Eu%\377P\207\036\377^\220\001\377[\221\002\377V\213\003\377c\227\002\377c\224\013\377" + "g\227\010\377Z\201\000\377h\220\002\377\202\252\001\377~\251\020\377\201\262/\377" + "\203\261\063\377\202\260\063\377\203\254\025\377\204\261\040\377v\244\021\377" + "y\247\002\377h\227\002\377o\237\003\377|\264;\377}\257\026\377\214\264\002\377\204" + "\246\002\377\225\261\001\377\246\267\002\377\212\245\013\377\233\267<\377\244\302" + "U\377\223\267*\377z\237\000\377\216\257\005\377\246\263$\377\247\274/\377\231" + "\266\000\377\216\254\002\377\203\246\003\377\203\251\003\377\217\263\002\377_\220\001" + "\377Y\212\000\377M~\000\377U\204\000\377V\205\000\377a\217\003\377c\214\002\377_\203" + "\000\377_\204\002\377b\212\003\377X\177\002\377Y\204\003\377d\223\023\377Oy\033\377C" + "o\000\377h\212\036\377~\246\024\377O\200\000\377Z\211)\377Kp&\377U\202\001\377f\225" + "\003\377x\241\003\377\211\254\002\377o\220\010\377\216\260R\377u\226;\377Yv,\377" + "\205\213\203\377\227\236\225\377\221\227\217\377\221\224\217\377\201{\200" + "\377\217\207\211\377cW`\377B\062<\377NAH\377i^e\377\202v~\377\223\214\217" + "\377\206\204z\377yq^\377rkP\377\233\224x\377\246\240\203\377\234\221s\377" + "\213yb\377cQC\377\070&\040\377=+(\377K=:\377RD>\377]J@\377vgX\377m^O\377qe" + "W\377wiX\377\177p[\377\210wa\377\225\206l\377\220\201i\377\227\212t\377\206" + "xf\377{l^\377\211~q\377\207|s\377i[Q\377S?:\377I\066\065\377]LG\377\205vi\377" + "\240\223~\377\255\242\216\377\245\231\204\377\203qX\377\202t_\377vnd\377" + "\203~|\377\221\221\221\377~~\202\377NOT\377MVL\377Q^N\377BP@\377\070D\064\377" + "VfO\377bzU\377Sz?\377H~\032\377P\204\012\377V\213\003\377P\202\003\377W\206\002\377" + "i\225\002\377g\222\012\377a\217\001\377p\232\003\377l\226\002\377p\235\002\377k\225" + "\002\377z\247\007\377p\236\006\377h\235\010\377\207\262\067\377r\244\020\377j\234" + "\003\377\202\261\013\377m\236\003\377s\243\006\377\204\266:\377s\255\061\377\202" + "\253\001\377\201\242\002\377\216\256\002\377\212\250\002\377\256\267\007\377\244\300" + "M\377\215\260\031\377\210\260\040\377~\247\007\377\206\253\013\377\242\273,\377" + "\230\271\037\377\222\264\002\377\211\251\002\377\216\262\004\377\205\250\001\377\207" + "\251\001\377u\235\065\377\236\277v\377\302\333\270\377t\244\066\377\220\266M" + "\377m\225\025\377_\216\006\377\\\217\024\377S\205\005\377\\\221\004\377_\225\003\377" + "Y\211\001\377e\227\031\377En\000\377Ks\001\377U~\002\377n\241\003\377Y\211\030\377O|" + "\017\377W\211\005\377g\231\015\377V\203\001\377]\210\000\377V\177\000\377]\207\033\377" + "y\244<\377h\224-\377c\211\066\377o\212U\377~\205x\377\231\231\227\377\235" + "\233\233\377\233\230\230\377\223\216\214\377qim\377\\QV\377h`b\377ztw\377" + "\211\203\210\377\233\226\224\377\210\202x\377\212\203s\377\200u_\377\207" + "}b\377\233\223t\377\246\240\203\377\242\231{\377\240\221u\377|iX\377eOH\377" + "`MJ\377A*(\377G\060.\377L;\070\377A/*\377WG>\377M\071,\377^L>\377{jZ\377\220" + "\201r\377\207zj\377\202wj\377pe[\377UIB\377WHC\377^LB\377wfW\377q`Q\377}" + "j]\377\243\232\205\377\260\251\226\377\260\250\224\377\235\222~\377\201s" + "]\377\201s`\377\202ym\377\177vo\377\204~{\377\250\247\246\377\221\225\222" + "\377q}n\377PUO\377AEE\377ENE\377:L/\377<W!\377Ck\035\377J~\034\377h\230\060" + "\377Iv\002\377X\215\006\377V\207\006\377c\222\001\377z\234\010\377i\226\013\377\225" + "\271\066\377x\236\000\377`\216\002\377o\240\001\377k\232\010\377o\237\002\377o\236" + "\003\377y\253-\377|\246\023\377\200\260\033\377j\231\001\377}\252\005\377p\241\001" + "\377u\242\010\377\221\273R\377g\235(\377\246\303`\377\215\246\002\377\221\256" + "\001\377u\231\002\377\213\251\005\377\264\315W\377\210\253\002\377y\234\000\377\221" + "\276@\377\205\256\013\377\222\272\016\377\226\274\022\377\230\276\025\377\220" + "\257\001\377\224\261\003\377\217\252\002\377\243\276\030\377\324\344\321\377x\244" + "\066\377d\226\021\377X\212\004\377Q\202\003\377\\\220\004\377e\222\014\377_\221\003" + "\377]\216\001\377J|\002\377O\200\002\377`\225\007\377R{\004\377Ej\001\377U|\003\377[\207" + "\005\377f\233\002\377P}\013\377V~\004\377\\\204\006\377Kl\001\377Y\204\002\377u\235\025" + "\377w\235\027\377X\207\013\377S\204\010\377^\215\027\377E`\033\377Rh\064\377my" + "_\377\224\224\215\377\237\237\232\377\225\225\220\377\203|x\377}vv\377wr" + "t\377gaf\377ojl\377\224\215\217\377\263\260\254\377\227\223\213\377\207\205" + "x\377\223\215{\377\177x]\377zoM\377\226\217m\377\225\215m\377\251\237\202" + "\377\250\237\204\377\262\252\224\377\230\213r\377\177oY\377p]O\377jWN\377" + "YC\067\377kXL\377T?\063\377eSG\377jXH\377{h[\377veW\377l\\M\377i\\M\377k\\" + "J\377zhT\377\235\214y\377\260\241\215\377\254\235\211\377\266\251\225\377" + "\257\244\220\377\260\250\224\377\214}d\377\214~c\377\221\204n\377xlY\377" + "\226\222\207\377\230\227\220\377\222\225\212\377\213\231\200\377\177\227" + "r\377etX\377>>?\377>A=\377UjH\377W\177:\377N\203&\377F\210\016\377T\223\025" + "\377t\231\023\377c\216\004\377Q\201\001\377`\214\002\377_\211\002\377c\210\001\377o" + "\233\026\377\201\253\064\377\205\245\012\377e\222\002\377n\235\002\377j\236\014\377" + "l\243\002\377c\225\010\377^\221\005\377r\244\005\377x\247\012\377q\235\001\377w\253" + "\003\377t\251\030\377x\237\013\377\226\270W\377u\235\040\377|\251E\377\217\255" + "\005\377\223\262\003\377\204\244\002\377\203\244\003\377\253\312J\377\222\266\023" + "\377~\244\000\377\211\266\034\377\213\271#\377\207\262\010\377\215\270\007\377" + "\231\274\017\377\233\266\003\377\247\275\011\377\223\253\001\377\233\270\002\377" + "o\257\061\377[\221\000\377d\230\001\377Q\203\002\377Y\223\017\377O\205\002\377Ix\001" + "\377[\214\002\377W\213\002\377V\212\003\377T\205\004\377\\\225\013\377\\\217\016\377" + "S}\003\377Tz\001\377^\216\007\377_\221\005\377U\200\005\377Z\201\020\377o\236\016\377" + "h\227\005\377\207\241\017\377e\206\005\377f\210\002\377p\227\002\377Z}\001\377Y|\012" + "\377\067R\000\377Fb\030\377izN\377t}f\377\231\232\224\377\227\227\217\377\220" + "\217\207\377\202{x\377h]d\377lfl\377lgm\377\231\220\225\377\262\253\247\377" + "\252\244\234\377\221\216\201\377\220\213y\377\211\202j\377|tS\377\216\202" + "`\377~qI\377\213{Y\377\253\243\211\377\263\255\230\377\252\243\211\377\256" + "\244\216\377\241\222}\377\233\212w\377\237\216{\377\250\232\207\377\231\213" + "x\377\225\207u\377\212{m\377\240\222\203\377\247\232\213\377\234\215}\377" + "\245\227\200\377\254\234\202\377\251\230~\377\255\236\206\377\273\255\233" + "\377\277\266\244\377\262\252\226\377\235\225\201\377\224\212u\377\211}e\377" + "\227\216x\377\220\210u\377\224\217\201\377\211\207}\377\217\216\207\377\247" + "\250\242\377\235\252\224\377gw`\377bkT\377ScN\377bv`\377Hc\065\377\030=\000\377" + "+Z\005\377E\203\013\377B}\007\377\\\222\003\377\227\240\011\377X\204\002\377c\215\002" + "\377_\207\002\377a\211\001\377e\224\014\377R\200\003\377\206\256(\377c\214\001\377" + "s\237\001\377k\233\002\377l\243\023\377g\231\010\377d\223\003\377q\242\003\377p\237" + "\002\377\177\260\005\377k\233\002\377k\235\000\377\201\250\006\377w\246\034\377\203" + "\253\066\377\251\273W\377\215\253\037\377\250\267\040\377\215\251\004\377\210" + "\252\002\377\212\263\010\377\214\263\016\377\177\242\001\377\246\273\005\377\217" + "\274B\377\213\271\040\377\214\271\013\377\205\251\001\377\243\276\006\377\230\260" + "\002\377\220\252\003\377\231\263\002\377Z\220\001\377g\233\002\377W\211\002\377Y\216" + "\005\377_\224\003\377b\223\002\377V\210\002\377_\222\003\377^\220\003\377b\235\002\377" + "d\235\003\377]\227\002\377^\230\003\377R\210\003\377[\221\003\377d\231\012\377b\227" + "\006\377g\226\023\377e\227\007\377l\232\011\377\215\253&\377_z\002\377Z}\004\377q\213" + "\003\377p\212\002\377Ts\003\377\065W\002\377Cg\004\377-Q\001\377X\202\004\377_tD\377t|n" + "\377y{r\377\212\211\201\377\222\220\213\377\217\214\210\377XQY\377b[c\377" + "~w\177\377\230\223\223\377\270\263\254\377\237\232\217\377\217\210w\377\224" + "\215x\377\220\212q\377\215\202b\377\215}Z\377\221\203_\377\217\202a\377\227" + "\214p\377\245\234\202\377\253\244\215\377\267\255\230\377\260\244\217\377" + "\264\250\225\377\264\252\230\377\262\247\226\377\253\240\217\377\254\241" + "\222\377\251\235\216\377\232\213z\377\262\245\223\377\262\244\215\377\255" + "\235\205\377\264\242\211\377\247\223z\377\267\250\225\377\272\255\233\377" + "\232\215v\377\221\207r\377\231\222~\377\215\206p\377\223\215y\377\231\223" + "\204\377\221\220\205\377\213\213\203\377\211\211\200\377\241\246\226\377" + "\201\217u\377|\211d\377\201\177U\377Q[I\377)E\035\377.T\040\377p\227Z\377J" + "\200\021\377=p\002\377P\210\003\377[\222\003\377o\212\016\377c\203\003\377b\210\002\377" + "h\224\002\377e\222\001\377R\202\001\377M\200\002\377\204\254/\377b\212\000\377q\237" + "\002\377i\236\013\377p\247\023\377j\235\005\377s\241\011\377u\243\001\377t\246\002\377" + "q\242\003\377h\232\002\377m\236\004\377|\247\003\377n\245\024\377\210\266G\377\260" + "\313P\377o\234\010\377\234\263\022\377\250\265!\377\222\256\017\377\212\261" + "\024\377\221\271'\377\203\245\001\377\236\243\003\377\221\271\060\377\216\272:" + "\377\223\300\017\377\222\272\004\377\220\256\002\377\213\247\002\377\221\255\002\377" + "\222\257\002\377_\223\002\377m\235\001\377g\230\010\377p\230\011\377n\221\012\377" + "z\234\033\377_\213\005\377W\206\003\377\\\220\002\377^\225\002\377c\234\011\377Z\210" + "\001\377j\226\026\377s\244'\377^\220\026\377V\204\014\377_\217\002\377]\206\001\377" + "o\227\002\377{\230\023\377\205\243\030\377Mp\001\377Ln\002\377f\202\002\377m\206\002" + "\377Uo\002\377\065J\001\377;T\002\377Ff\003\377?c\001\377If\040\377Sa@\377ei^\377\177" + "~v\377\220\221\204\377\201\201w\377gfc\377cac\377a]a\377\233\227\225\377" + "\252\245\235\377\223\214\201\377\224\220\203\377\215\213{\377\231\226\205" + "\377\212\205j\377\206\200]\377\216\205c\377\217\205g\377\222\206j\377\204" + "uU\377\217\201a\377\241\225x\377\260\244\211\377\250\235\202\377\235\220" + "w\377\252\240\207\377\237\224\177\377\260\247\224\377\227\211u\377\224\203" + "o\377\247\233\206\377\266\252\223\377\250\231\201\377\260\243\214\377\254" + "\237\210\377\247\232\204\377\250\233\205\377\230\213u\377\221\205r\377\212" + "\201o\377\235\227\207\377\234\233\216\377\232\232\221\377\202\202|\377\207" + "\207\177\377\177\202t\377\216\241w\377\177\217d\377r\177^\377eoc\377G\\>" + "\377&K\015\377)V\003\377Fy\021\377n\232H\377J\201\014\377d\235\002\377[\217\020\377" + "x\241=\377o\217\012\377o\220\005\377d\222\003\377_\217\001\377Z\215\003\377Y\212\002" + "\377t\241#\377p\235\017\377p\243\010\377k\234\015\377o\243\010\377f\223\005\377" + "p\236\005\377w\245\002\377}\254\002\377{\255\004\377n\237\002\377\\\217\000\377\205\256" + "\000\377f\241\017\377\210\270G\377\224\273(\377x\243\002\377u\245\013\377\202\244" + "\000\377\225\257#\377\224\263#\377\223\271\063\377\203\250\001\377\222\246\004\377" + "\200\246\006\377\206\256+\377\207\262\011\377\204\251\001\377\217\264\002\377\214" + "\256\002\377\214\264\007\377\215\261\006\377a\225\003\377e\230\002\377Y\220\004\377U" + "\213\002\377U\205\001\377`\215\001\377Sq\001\377]\211\002\377^\216\003\377]\222\003\377" + "_\220\030\377\212\264T\377k\234\025\377`\222\001\377U\203\001\377Fp\001\377b\217" + "\002\377S|\003\377[\210\005\377\223\257>\377f\217\006\377W\201\002\377Z~\005\377i\223" + "\002\377Yw\003\377Xi\001\377do\007\377Vg\004\377Rm\006\377Km\011\377:\\\000\377Qr\016\377" + "\067H\040\377]kK\377|\206l\377\214\216\200\377\227\230\217\377kih\377ead\377" + "\221\213\213\377\205{w\377\216\204{\377\214\203y\377\206\177t\377\217\214" + "|\377\217\214u\377\222\215o\377\200zX\377\203~]\377\226\215k\377\204vP\377" + "\216~Z\377\216\200]\377\230\213h\377\230\215j\377\241\230u\377\234\222t\377" + "\242\227}\377\245\233\207\377\222\205s\377\226\212t\377\250\236\211\377\256" + "\241\213\377\253\235\205\377\253\235\205\377\227\213u\377\240\224\202\377" + "\207\177m\377\214\200o\377\215\205t\377\245\241\220\377\227\226\211\377\216" + "\217\205\377\213\213\203\377\177~y\377}}t\377\220\236x\377y\221V\377^qK\377" + "Q^K\377L]D\377.N\024\377+Q\004\377@m\005\377M\203\011\377?s\001\377\202\260R\377" + "a\232\001\377Bm\025\377\203\255O\377k\217\035\377X\177\006\377O\177\001\377_\216" + "\001\377c\225\002\377V\202\002\377V\202\000\377\231\302J\377j\231\010\377i\230\002\377" + "t\246\002\377w\245\002\377t\243\003\377l\232\002\377\200\261\003\377l\236\003\377l\241" + "\001\377W\217\017\377\222\270&\377r\246\024\377\215\276Y\377\224\273,\377\201" + "\246\000\377}\247\002\377\200\255\002\377\177\245\002\377\200\246\005\377\242\302G" + "\377\177\251\006\377w\235\001\377\220\261#\377\220\256.\377\206\261.\377v\237" + "\000\377\212\265\001\377\205\253\002\377\224\271\005\377\243\305#\377O\204\001\377" + "Z\217\003\377Z\221\003\377P\210\002\377Z\224\004\377W\213\003\377\\\215\000\377e\226" + "\007\377d\226\005\377Vx\005\377t\222\024\377c\217\006\377\202\240>\377a\205\010\377" + "_\207\004\377U~\002\377f\227\006\377t\251\036\377\204\261#\377\202\252'\377q\235" + "\010\377^\214\001\377a\214\015\377\\\212\000\377T~\001\377]\205\003\377_\206\002\377" + "[|\002\377Xw\004\377Ss\004\377Db\001\377;Z\001\377\063P\003\377p\224D\377`fS\377tvh\377" + "~\177t\377d^]\377tps\377uoq\377\240\230\223\377\254\245\234\377\243\236\222" + "\377\230\222\205\377{yk\377\204\203r\377\215\213r\377\217\212k\377\211\202" + "`\377\207\200[\377\212\203]\377\227\217j\377\230\220l\377\242\232{\377\223" + "\211j\377\232\224u\377\247\240\204\377\223\215s\377\234\224\177\377\206y" + "f\377\220\203p\377\226\212t\377\244\230\177\377\254\240\207\377\247\234\206" + "\377\222\207u\377~ua\377\226\221\200\377\234\232\213\377\230\230\212\377" + "\230\231\216\377\206\206}\377\226\225\217\377\212\212\204\377\177~y\377\201" + "\201z\377\215\244h\377k\177Q\377ERA\377/B&\377\062M\031\377,N\007\377Af\007\377" + "Fr\002\377V\214\007\377F~\003\377_\237\002\377d\230,\377[\204\023\377s\235D\377Mv" + "\000\377p\216-\377Ft\000\377]\213\002\377Y\211\001\377W\207\003\377`\222\002\377\226" + "\300H\377c\215\007\377h\227\001\377i\232\003\377u\244\002\377g\230\002\377i\230\002\377" + "x\254\002\377m\235\015\377b\231\000\377{\257<\377\234\306h\377\213\266\063\377" + "\217\272\061\377\206\260\022\377\212\257\031\377\244\300=\377~\251\035\377\216" + "\261\006\377\201\244\000\377\255\310N\377\317\326\217\377n\222\000\377\220\244" + "'\377\246\276M\377r\240\006\377\211\256\023\377\223\264\002\377\206\246\002\377" + "\214\257\001\377\227\273\030\377h\236\013\377P\206\001\377W\215\003\377_\226\003\377" + "T\212\002\377^\226\000\377q\243\034\377q\245\037\377\\\216\001\377}\223\027\377\257" + "\257F\377Z\203\001\377\\\212\017\377Rz\002\377d\217\015\377c\215\007\377s\244\003\377" + "q\234\002\377\233\272\066\377h\206\001\377l\220\003\377V}\001\377o\233\004\377m\232" + "\016\377h\216\013\377b\203\001\377]|\002\377Zw\002\377Vr\002\377Ee\002\377?a\002\377Cb" + "\002\377[~\004\377Lk\004\377!\063\006\377?K,\377prd\377\214\213\204\377\212\213\204" + "\377\210\203\201\377\232\223\224\377\225\222\220\377\235\234\226\377\203" + "\201x\377|{r\377ccZ\377\214\214|\377\221\216y\377\220\214q\377\224\217q\377" + "\206~\\\377\214\204`\377\222\214j\377\251\245\214\377\223\220t\377\222\220" + "s\377\217\214p\377\222\213n\377\225\211n\377\210y`\377\203r[\377\216~h\377" + "\245\232\200\377\240\230\177\377\232\223\177\377\230\221\200\377\240\233" + "\212\377\243\243\227\377\224\224\211\377\214\215\203\377yyr\377lkh\377on" + "o\377xxw\377YYY\377x\177h\377p\213N\377DND\377ES?\377)C\030\377+M\012\377?" + "k\017\377/Z\002\377=l\002\377M\202\010\377G\201\002\377\\\226\004\377Bl\022\377{\253" + "F\377y\244H\377Cn\000\377\245\274m\377d\222\017\377b\225\003\377^\222\012\377^" + "\226\020\377\\\222\002\377n\232\026\377\201\255'\377_\221\001\377l\236\002\377d\227" + "\003\377]\220\002\377o\242\002\377o\242\006\377\214\257G\377\223\277]\377i\235\014" + "\377\\\216\003\377~\262\030\377p\230\004\377\204\263\035\377\212\273L\377\227\305" + "h\377\231\304r\377\265\327\215\377\240\307F\377\221\267\065\377\245\272\071" + "\377\223\251,\377\250\272I\377\271\313`\377\276\320`\377\233\271/\377\211" + "\245\001\377x\226\002\377\212\254\001\377\242\301)\377V\210\003\377V\211\003\377c\223" + "\004\377\\\225\004\377]\223\005\377Y\217\000\377`\222\004\377e\224\001\377d\216\004\377" + "n\223\003\377h\214\002\377n\232\002\377h\231\003\377u\247\040\377\216\274J\377\201" + "\256\015\377}\244\002\377\224\264\022\377\224\261A\377Z\201\000\377a\215\002\377" + "p\232\012\377u\227\004\377w\222\003\377l\201\002\377l\200\002\377_p\002\377`p\001\377" + "J_\002\377So\003\377+C\000\377Jp\013\377z\237\036\377<]\002\377\031\065\000\377Oi%\377" + "dv?\377np_\377kjb\377}wz\377xmu\377ndn\377qfn\377^SZ\377>\070<\377>\071=\377" + "efc\377]^V\377\207\210t\377\227\225z\377\206\202`\377\213\210d\377\216\211" + "g\377\230\227|\377\177\207]\377\236\235\177\377\226\224v\377\234\226y\377" + "\201uX\377\207z\\\377\222\207j\377\237\230}\377\230\222{\377\233\225\201" + "\377\235\232\206\377\231\230\205\377\231\231\212\377\226\227\213\377\231" + "\232\220\377wwr\377^]]\377>==\377B?A\377JFI\377B>B\377\231\245v\377]bQ\377" + "-<\"\377(A\025\377(I\011\377\062Y\017\377*S\001\377\063d\006\377\067k\005\377Cx\002\377" + "U\224\005\377Cl\002\377Nt\000\377o\233+\377y\245M\377Q\203\000\377Y\212\024\377\214" + "\266Q\377S\205\002\377R\202\002\377S\201\002\377b\222\002\377e\226\015\377{\252&\377" + "j\236\001\377k\235\001\377`\217\003\377i\227\000\377v\246\001\377s\250\026\377s\247" + "\062\377f\233\015\377p\237\003\377m\232\005\377y\255\040\377v\232\003\377t\245\012" + "\377o\236\000\377\225\273?\377\300\341\224\377\215\271<\377\204\260\027\377" + "\204\263)\377\212\261(\377\254\306]\377\247\264?\377\214\257\030\377\207\256" + "\023\377\232\303B\377\217\267(\377u\217\001\377\202\236\002\377\236\301/\377`" + "\221\003\377\\\221\003\377\\\227\004\377`\231\005\377n\237\012\377\200\245\035\377" + "g\227\001\377Y\215\002\377X\212\001\377q\231\004\377p\227\002\377f\224\002\377g\231\002" + "\377u\251\"\377f\224\005\377m\230\001\377u\236\001\377s\233\006\377b\221\004\377a\221" + "\004\377a\212\003\377m\217\006\377k\212\002\377l\210\001\377x\225\003\377j\212\002\377" + "a\203\002\377a\201\003\377`\201\007\377h\216\007\377\\\203\011\377o\244\026\377Lk\012" + "\377Hf\020\377Xy\034\377<T\006\377:M\013\377S\\\070\377UVF\377GC?\377VJQ\377xk" + "s\377ujp\377]RZ\377#\027\035\377\062*.\377;\065\067\377ONJ\377ute\377\217\213" + "q\377\214\210h\377\233\231x\377\207\210a\377\204\207[\377\200\205W\377\234" + "\232w\377\220\214i\377\227\220n\377\203zV\377\226\221q\377\231\224x\377\226" + "\221y\377\243\240\214\377\236\237\212\377\224\225\202\377\217\217\177\377" + "\223\224\206\377\230\231\217\377\221\220\213\377lfe\377JCE\377\060)+\377>" + "\071;\377\063.\060\377zx[\377\210\230h\377XdV\377\064K*\377\036@\010\377-V\016\377" + "\037L\002\377,\\\005\377:m\007\377F{\004\377X\204\012\377`\215\021\377Lu\010\377`\207" + "\013\377U\202\024\377~\261C\377U\210\000\377U\204\000\377\210\270G\377Z\213\004\377" + "Q\204\001\377c\226\003\377W\205\002\377`\215\012\377|\252\032\377l\242\002\377[\215" + "\002\377g\232\007\377\207\264\064\377|\254\012\377h\237\014\377`\227\020\377r\245" + "\004\377}\246\001\377z\252\037\377p\244(\377\202\235\001\377\204\264\060\377\242" + "\312^\377\260\323k\377\200\243\020\377v\244\004\377\214\275@\377\230\303T\377" + "\255\320g\377\204\261.\377\240\274\040\377\210\255\020\377\235\253\040\377\224" + "\273@\377\230\274G\377\225\264\021\377\217\260\002\377\227\302\070\377V\206\003" + "\377Y\223\011\377O\216\002\377W\222\002\377U\220\003\377Q\212\003\377U\214\002\377Z" + "\220\001\377\\\221\002\377^\222\001\377n\236\006\377t\247\024\377[\211\001\377i\233" + "\015\377g\224\002\377h\217\002\377b\206\000\377}\240\022\377\224\267\011\377r\237" + "\001\377j\226\002\377r\237\003\377i\221\002\377l\227\002\377q\225\002\377k\203\003\377" + "`l\002\377eo\002\377{\230\002\377u\235\002\377Sl\010\377l\217\034\377o\213,\377Ei\005" + "\377?`\001\377Np\022\377Pc\037\377!\060\006\377FN\071\377Z[S\377XUS\377skk\377\211" + "\203\200\377ka]\377H\071\061\377TF>\377L?\071\377_VO\377e^R\377uo\\\377\211" + "\210k\377\211\215g\377{\204S\377\210\212a\377~|V\377\222\222m\377\224\221" + "m\377\177|T\377\203\177W\377\233\232x\377\213\211k\377\220\220v\377\221\222" + "~\377\222\227\200\377\227\240\204\377\210\213x\377\226\226\211\377\233\234" + "\223\377\216\214\205\377zri\377tk_\377zqe\377~yn\377kh]\377\224\245r\377" + "ViB\377\067O\060\377>Y,\377\064T\024\377\032C\002\377\037Q\001\377;s\020\377A{\006\377" + "X\213\020\377p\231#\377Nz\031\377M\204\015\377I{\000\377b\226\031\377v\253\062\377" + "_\220\002\377W\201\001\377t\245\023\377^\220\004\377`\224\005\377K}\003\377U\203\002\377" + "m\235\011\377\203\262%\377c\224\000\377[\214\000\377i\233\006\377j\227\004\377t\247" + "\002\377i\240\012\377c\226\002\377\206\260\004\377~\246\012\377l\236\017\377i\242" + "/\377\226\276K\377\221\272I\377\201\256&\377\212\237\020\377\211\241\005\377" + "o\230\005\377\235\275O\377\221\257:\377\206\252!\377}\252\022\377\214\242\006" + "\377\203\236\004\377\213\254\011\377\325\336q\377\272\311>\377\224\260\005\377" + "\221\260\002\377\225\300=\377Bx\002\377T\217\004\377S\214\002\377U\215\002\377Y\221" + "\003\377]\223\002\377X\213\002\377p\236\027\377p\233\010\377f\225\003\377b\221\007\377" + "a\220\002\377\\\212\002\377W\205\001\377f\220\002\377X\202\002\377_\211\002\377\210\256" + "\005\377\221\263\002\377l\222\002\377r\232\003\377v\236\001\377z\245\002\377q\216\003\377" + "k\200\002\377hz\004\377jw\002\377x\206\005\377{\225\001\377g\210\011\377m\230\024\377" + "q\226\040\377Zl\016\377Tl\012\377Uy\012\377Oi\034\377\040.\000\377\061<\025\377js[" + "\377\202\204u\377{zn\377qh_\377}tm\377\230\220\200\377\225\210p\377\230\210" + "m\377\230\211l\377\213|c\377\202u]\377|w\\\377tuO\377v\200N\377\206\214b" + "\377\216\220n\377~}W\377\203\201Z\377\213\212b\377\203\201R\377\212\210^" + "\377\224\223q\377\214\214l\377\213\213n\377\201\201j\377\223\226\200\377" + "\227\236\201\377\224\232\200\377\225\226\211\377\230\231\220\377\211\206" + "|\377\216\206y\377\226\216|\377\235\227\206\377\214\211|\377\201\205o\377" + "\212\262j\377BY\064\377&?\031\377+K\021\377$H\010\377\"J\002\377<m\022\377O\206" + "\031\377M\205\012\377N\203\003\377R\202\030\377c\222%\377P\177\000\377P\177\002\377" + "^\217\025\377c\224!\377f\226\003\377Ov\002\377h\234\003\377R\206\002\377e\231\016\377" + "R\200\004\377q\236\011\377\202\260'\377\226\277\070\377\212\270C\377l\234\036" + "\377n\236\025\377t\241\003\377q\242\013\377y\253\011\377\211\265\004\377m\237\022" + "\377\206\264D\377p\244\021\377\203\267E\377\225\276?\377\201\256#\377t\245" + "\007\377\246\257\062\377\232\260,\377\232\272\066\377\177\243\"\377}\235\017\377" + "\232\300H\377n\234\001\377|\237\005\377\207\243\000\377\266\306/\377\231\275-\377" + "\205\256\026\377x\233\004\377\206\253\006\377\215\271\063\377S\213\003\377O\213\003" + "\377G}\002\377Z\210\001\377U\204\002\377Z\215\003\377]\217\013\377b\217\004\377l\231" + "\007\377c\217\003\377\\\215\003\377c\224\017\377a\220\010\377i\230\014\377b\222\000" + "\377b\222\002\377j\231\001\377\230\274\011\377p\222\002\377p\223\002\377v\230\002\377" + "{\233\005\377e}\002\377fx\002\377\215\241\006\377~\222\006\377z\245\004\377n\225\002\377" + "s\214\031\377\206\257:\377_\205\002\377^}\004\377q\216\035\377s\215\032\377g\177" + "\014\377>J\001\377<I\001\377Wg!\377JW&\377nqQ\377\207\205s\377zsg\377\215\205" + "}\377\205}s\377\237\222\201\377\254\237\210\377\244\226y\377\224\204g\377" + "xhJ\377vqF\377pq?\377}{S\377}{Z\377\210\207f\377\211\212g\377\207\206`\377" + "\206\204Z\377\206\204X\377\226\224m\377\213\212d\377\204\202c\377\211\212" + "l\377\206\206m\377\221\223}\377\220\225~\377\230\242\213\377\227\231\217" + "\377\201~x\377\214\207~\377\214\205x\377~wl\377}yo\377mji\377\203\234f\377" + "y\242X\377\062L%\377\034<\015\377Fn!\377&F\003\377\071`\010\377[\217!\377K\205\007" + "\377Fz\002\377_\223\015\377f\235!\377L\200\000\377P\202\002\377W\207\002\377Z\211" + "\022\377f\225\025\377f\220\003\377R\177\002\377j\234\003\377^\215\002\377b\222\016\377" + "{\244\005\377\206\262=\377g\236\007\377\206\265\061\377a\225\016\377Z\212\002\377" + "g\223\003\377{\250\002\377y\246\017\377\222\271\007\377\200\253\006\377j\230\020\377" + "\206\263\066\377\257\321n\377e\230\014\377i\233\002\377q\236\003\377i\236\007\377" + "p\235\002\377\215\262\004\377x\234\003\377q\227\000\377\253\274H\377\215\263\062\377" + "u\246\024\377\201\245\005\377\246\270\030\377\237\257\003\377\240\273\026\377\214" + "\265\033\377\202\245\010\377\236\301\033\377\205\266(\377Y\227\002\377K\207\003" + "\377Y\223\003\377f\220\012\377\\\207\002\377^\214\002\377]\217\002\377`\221\004\377" + "[\215\002\377Y\213\002\377p\234\022\377l\225\024\377c\213\007\377g\221\005\377a\215" + "\002\377c\217\002\377g\222\002\377\210\256\003\377s\232\001\377s\227\002\377t\223\002\377" + "t\220\004\377\202\225\005\377j}\002\377\211\227\004\377\177\224\007\377j\203\005\377" + "q\215)\377y\242*\377i\236\003\377i\226\007\377d\213\006\377a\205\005\377Qq\001\377" + "Ni\001\377`v\006\377z\212\031\377dn\006\377mo\016\377]`\037\377ggB\377po_\377\177" + "|t\377voi\377\220\206z\377\222\204q\377\202rX\377\207yW\377|tA\377\203\177" + "D\377\200uG\377xmG\377rkH\377}y\\\377\213\212k\377\214\212h\377}|S\377\212" + "\210`\377\231\230r\377\212\210c\377\213\207g\377\220\217r\377\215\215u\377" + "\220\222}\377\223\226\202\377\232\242\214\377\221\221\211\377vqi\377ysh\377" + "\205~q\377wti\377TSN\377^eS\377c\217E\377r\235R\377\065U\033\377Nv\060\377&" + "K\001\377:e\006\377Gy\014\377>x\005\377;p\001\377e\230\027\377Z\217\003\377Q\204\001\377" + "Hz\002\377I{\001\377Z\212\002\377a\224\022\377l\226\021\377n\225\004\377`\212\002\377" + "l\227\003\377p\230\004\377i\231\007\377j\227\014\377\205\265H\377o\240\031\377\211" + "\265/\377c\227\014\377Z\211\002\377m\232\004\377l\231\002\377\220\257\003\377z\251" + "\002\377u\236\026\377q\240\035\377\212\271F\377s\242\032\377h\230\001\377q\240\001" + "\377\251\315\065\377n\237\016\377u\244\006\377k\230\003\377\204\251\001\377\230\262" + "\017\377u\242\016\377\225\270\066\377v\247+\377\230\271*\377\246\276#\377\245" + "\270\013\377\240\252\001\377\241\274\014\377\215\264\007\377\207\260\000\377\202" + "\257\003\377\\\233\003\377T\221\004\377O\206\003\377T\200\003\377Ao\001\377c\225\004\377" + "c\226\004\377d\221\005\377d\224\002\377X\205\002\377O|\001\377s\232\033\377g\221\012" + "\377a\211\001\377^\212\002\377`\212\001\377i\223\002\377}\247\004\377\205\244\004\377" + "z\224\003\377p\214\003\377h\205\001\377\221\234\023\377v\212\004\377z\231\002\377s\234" + "\005\377}\234B\377[x\011\377e\225\006\377Z\205\003\377[\210\007\377h\220\020\377_\203" + "\005\377Qo\002\377Sn\001\377^{\005\377H_\002\377:M\001\377FU\003\377\066F\006\377aj@\377z" + "{k\377\177\201t\377{zr\377gdW\377\201xe\377\223\214i\377\234\232e\377\237" + "\234_\377\236\217a\377\226\201[\377\203qL\377q`@\377wlO\377\202|_\377\205" + "\201b\377|yQ\377\201\200Y\377\220\220j\377\217\216i\377\221\220r\377\211" + "\211m\377\217\221|\377\221\222\204\377\224\226\211\377\222\236|\377ztn\377" + "qj_\377nf[\377WNI\377JFF\377B@B\377ivU\377^\205D\377}\253R\377Iv#\377\062" + "\\\016\377Kx\030\377\065f\005\377/e\004\377\066l\002\377N\201\006\377]\220\003\377P\203" + "\002\377Q\201\003\377[\216\002\377\\\220\002\377F{\001\377]\225\023\377_\221\012\377" + "_\217\004\377Fv\001\377t\235\003\377]\206\002\377^\221\015\377\216\274R\377n\235\004" + "\377T\200\002\377\212\271\067\377a\227\010\377m\227\010\377l\227\002\377\240\264" + "\014\377r\231\002\377p\243\012\377`\225\011\377t\247\040\377p\231\014\377\177\251" + "\002\377n\234\003\377e\226\001\377\230\300\040\377\221\271\064\377k\230\004\377k\230" + "\003\377\177\244\002\377\240\270!\377\200\247\016\377\226\272'\377a\224\004\377" + "\202\262\023\377\223\273\033\377\245\300\"\377\233\257\007\377\221\252\001\377" + "\213\260\013\377\210\265\022\377\272\320H\377L\210\001\377T\217\003\377L\205\002" + "\377R\213\005\377[\224\004\377b\223\006\377`\205\002\377o\222\004\377g\215\000\377X\203" + "\000\377]\207\010\377[\177\001\377e\215\005\377f\217\016\377i\223\012\377W\202\001\377" + "]\205\001\377x\241\013\377g\215\004\377j\212\003\377o\213\002\377n\215\002\377i\212" + "\004\377~\234\010\377\204\250\013\377|\241\"\377x\222\027\377]\205\003\377g\225" + "\002\377\\\201\001\377Ot\003\377^\213\023\377^\214\012\377^\204\010\377f\206\015\377" + "Xr\002\377Pi\003\377Ga\002\377G_\001\377?W\003\377F[\021\377fnH\377rsb\377ii]\377kk" + "`\377[[I\377s\200E\377\224\221e\377\241\224o\377\203pP\377nY\070\377ub<\377" + "ziC\377yjH\377{pS\377yqT\377{vV\377|yU\377\202\177]\377\214\214k\377\215" + "\216o\377\214\215t\377\225\226\204\377\213\213\200\377~}t\377\202\220f\377" + "pmZ\377icZ\377]ZT\377JGF\377KJJ\377RRO\377r\212U\377ZoE\377t\244O\377Gv\036" + "\377W\214&\377?u\024\377\067p\005\377M\212\004\377Q\210\004\377T\212\002\377W\212\002" + "\377Dw\002\377O|\002\377Y\212\003\377V\211\003\377J~\002\377X\224\020\377N\202\007\377" + "L}\003\377o\230\005\377o\233\004\377e\222\004\377s\242'\377m\230\016\377t\236\004\377" + "h\220\012\377\207\265\062\377Y\205\000\377o\222\005\377\226\255\007\377o\217\003\377" + "l\236\005\377s\247\063\377f\232\030\377d\230\007\377v\236\001\377z\240\003\377u\240" + "\005\377s\241\003\377\212\270\013\377\200\254\026\377\\\212\002\377q\234\001\377\177" + "\246\002\377\214\261\005\377\177\250\006\377l\234\005\377h\233\002\377}\257\015\377" + "\223\274\"\377\246\273$\377\273\311R\377\236\264\023\377\216\261\011\377\203" + "\257\027\377\216\261,\377H~\002\377M\205\004\377L~\003\377G\200\002\377j\246\015\377" + "`\231\015\377h\241\023\377|\254*\377\213\267A\377\205\265/\377\202\253\065\377" + "f\220\004\377j\224\004\377O|\002\377[\211\013\377X\207\001\377e\204\020\377\202\256" + "\004\377Y\200\002\377Y~\002\377_\204\003\377V{\002\377_\202\003\377\200\245\004\377t\234" + "\022\377~\222\032\377w\225\014\377e\216\003\377_\215\004\377\\\214\013\377S\202\003" + "\377Jo\001\377W\203\017\377o\240'\377^\202\002\377]|\003\377_\202\002\377Uz\003\377" + "X\177\005\377Oo\002\377Da\003\377DZ\016\377S[\060\377__H\377fiQ\377Yt\061\377ZXG\377" + "tlY\377iYD\377qcE\377{lG\377\221\201U\377\233\211^\377\202oG\377\177mL\377" + "m_B\377kbE\377\200z_\377{uX\377\202~a\377\222\220y\377\213\212v\377\207\207" + "y\377{yn\377eaX\377ekP\377x\205X\377\\UN\377eb[\377ec_\377`^W\377`bQ\377" + "q\215T\377w\233Y\377s\247C\377Ar\031\377\067i\020\377\066m\007\377D\201\005\377I" + "\205\002\377Bz\002\377C}\001\377Av\002\377\067l\004\377a\227\010\377L\202\003\377I}\001\377" + "K\201\001\377]\232\016\377>u\005\377k\230\005\377^\211\002\377T\205\003\377c\226\022" + "\377x\243\061\377r\232\006\377\207\250\003\377j\213\004\377\220\267\066\377b\215" + "\000\377\200\235\004\377\252\270\005\377f\224\002\377n\244\031\377\\\220\025\377v\253" + "\060\377u\241\001\377\201\245\005\377{\235\002\377z\242\003\377z\254\006\377\205\262" + "\002\377k\232\002\377`\222\003\377q\237\005\377\200\252\003\377\201\251\005\377}\252" + "\005\377i\235\006\377i\237\004\377w\242\004\377\255\311V\377\311\322a\377\256\304" + "@\377\234\271!\377\220\262\032\377\202\261$\377t\237\023\377v\244\030\377J~" + "\004\377Av\002\377;v\004\377\\\217\015\377v\252\"\377\232\307h\377\234\307k\377" + "\241\314v\377k\230.\377N|\000\377t\237\022\377f\226\007\377e\226\003\377b\230\003" + "\377]\222\002\377`\223\002\377\231\275\004\377r\222\004\377h\206\002\377l\211\002\377" + "s\221\003\377~\237\004\377o\231\017\377\205\247\037\377d\220\007\377g\224\014\377" + "h\225\004\377]\215\002\377[\211\016\377\\\211\013\377Rw\002\377Qy\005\377\201\253N" + "\377b\213\027\377f\210\024\377a\204\013\377Xv\001\377[y\004\377Ul\003\377GZ\001\377" + "HZ\007\377GV\022\377w\203H\377~\210Q\377qpX\377ZVI\377`YJ\377nfL\377\221\207" + "^\377\237\222e\377\222|P\377\222yN\377\217uM\377\217wS\377}jL\377aR<\377" + "rkX\377wr[\377\204~i\377\213\206u\377\205\200t\377yri\377[QJ\377KC\070\377" + "GD\066\377\205\227O\377wo_\377yrc\377yuh\377oma\377x\207^\377a\211=\377\204" + "\256[\377Ai\025\377\065\\\023\377.\\\003\377P\210\003\377Dv\002\377F{\010\377Av\005\377" + "N\202\002\377\061j\002\377L\201\003\377O\211\003\377O\210\002\377N\202\003\377K\201\002" + "\377Y\221\007\377t\240\006\377g\221\002\377X\204\001\377^\220\002\377q\251/\377e\232" + "\021\377x\236\004\377q\216\002\377l\207\014\377\221\272A\377c\215\000\377\253\267" + "\011\377~\232\002\377q\241\000\377x\252,\377y\252\031\377{\255\"\377\224\270\010" + "\377y\236\011\377\224\257\006\377\177\246\006\377\210\265\002\377\200\253\002\377" + "m\233\010\377_\222\002\377b\222\004\377\177\247\013\377\222\271'\377\206\256\036" + "\377q\236\003\377\243\270\027\377\232\260!\377\215\260.\377\200\244\022\377\233" + "\272&\377\216\256\025\377\212\253\024\377\243\304Y\377\203\257\"\377[\217\002" + "\377N\177\002\377I|\002\377Q\204\004\377L{\002\377o\235!\377x\250\061\377\234\304" + "i\377p\226-\377Z\206\002\377\\\205\005\377^\210\005\377m\211\004\377X{\003\377^\212" + "\005\377O\200\002\377X\221\014\377\242\305\002\377\202\240\026\377q\217\001\377z\231" + "\002\377u\230\003\377h\221\003\377\\\211\004\377s\246\033\377d\233\016\377`\226\011" + "\377v\244\035\377a\225\021\377e\222\036\377d\213!\377c\205\020\377]|\003\377k\217" + "\037\377s\227\065\377n\223/\377n\227&\377a\204\017\377^\200\014\377Yr\021\377" + "du\031\377@N\004\377Md\007\377r}\071\377\224\212h\377\205~b\377zx`\377xx^\377s" + "pW\377zsW\377qbF\377w_?\377\231\200^\377\242\211f\377\234\202a\377\216v[" + "\377XH;\377G<\061\377geX\377spd\377zsi\377|si\377wka\377fYK\377dXG\377woT" + "\377\203\235+\377vzS\377qsT\377so_\377xtf\377\213\242m\377W\204%\377w\251" + "K\377Ds\030\377Ar\016\377Z\217\004\377M|\001\377\070g\011\377Y\213\033\377L\177\002" + "\377I\177\002\377M\201\003\377F}\001\377W\215\003\377P\210\002\377_\220\002\377Z\204" + "\002\377s\240\005\377b\213\003\377f\216\003\377a\215\003\377[\215\002\377\\\231\025\377" + "e\234\017\377\222\260\011\377j\222\014\377c\214\003\377|\255\060\377\224\252\007" + "\377~\245\004\377m\233\002\377f\225\000\377\203\264\061\377|\253\013\377|\251\005\377" + "\202\252\002\377\204\245\002\377\241\270\005\377v\247\003\377\201\255\003\377\203\256" + "\002\377x\234\005\377i\224\007\377|\247\015\377}\244\003\377\205\255\000\377\210\261" + "\000\377\216\257\013\377z\236\004\377v\245\040\377\203\252\012\377\203\247\007\377" + "\252\300\031\377\215\252\004\377\230\263&\377\271\320w\377r\237\000\377`\205\014" + "\377_\205\003\377\\\200\003\377Z|\004\377Rw\003\377V|\003\377t\221\005\377]\201\007\377" + "e\212\004\377m\223\002\377e\215\010\377[\207\002\377e\213\006\377j\214\005\377r\232" + "\003\377W\203\003\377Y\202\002\377\246\310\007\377\202\255(\377r\236\016\377k\227" + "\002\377f\227\000\377k\233\012\377`\224\003\377`\221\014\377`\216\005\377i\225\010\377" + "`\213\006\377u\231\015\377\\\205\014\377l\240\067\377j\245E\377b\230=\377^\223" + "-\377\\\215\027\377Q\177\020\377Y\214\016\377X\177\020\377=V\002\377@P\001\377We" + "\002\377e\210\010\377fx\030\377qp\070\377\202}U\377\225\220k\377\220\213i\377" + "\204~c\377xk[\377|n]\377\212yb\377\244\217n\377\253\224r\377\242\213h\377" + "\240\207h\377\222zc\377PH=\377\027\022\020\377<\067\070\377RLE\377maU\377\203" + "vc\377vdO\377mWD\377]I:\377TB\066\377}\214=\377qrP\377am\071\377o\177E\377" + "h}H\377d\221>\377Bf\023\377\204\263R\377P\207\026\377;g\002\377J~\005\377<k\004\377" + "@r\014\377>o\001\377K\177\001\377J\200\004\377H~\003\377F~\001\377M\203\003\377]\220\003" + "\377c\222\003\377}\251\002\377g\233\031\377R\201\006\377U\200\003\377i\221\003\377t" + "\247\001\377l\232'\377n\240\023\377\226\267\004\377f\237\022\377P\207\007\377s\242" + "\017\377\241\254\013\377u\240\004\377n\232\002\377i\225\005\377\177\262\016\377w\251" + "\011\377w\247\004\377l\226\003\377\224\264\003\377\201\250\003\377w\246\003\377u\244" + "\003\377|\251\004\377d\216\007\377i\223\007\377\207\255\002\377\225\275\002\377\200\260" + "\013\377v\252\017\377k\237\032\377k\236\003\377{\255;\377\203\243\000\377\202\254" + "\021\377\211\255\006\377\221\261\013\377\246\303N\377n\222\005\377\207\250\012\377" + "q\212&\377Hh\002\377Zz\003\377Hr\003\377Uy\002\377Xy\003\377Gc\002\377i\215\003\377a\207" + "\003\377_}\003\377U\200\002\377U\202\006\377i\220\003\377k\212\002\377\202\245\002\377" + "j\211\004\377j\223\010\377\230\276\011\377j\224\010\377p\241\001\377e\227\007\377" + "w\250\065\377s\236\067\377k\225\033\377d\220\012\377c\205\003\377i\212\002\377q\222" + "\006\377f\217\003\377U~\003\377[\212\010\377Z\215\021\377c\230!\377b\235$\377Y\226" + "\030\377W\222\030\377N\206\012\377Y\220\020\377d\224\016\377f\212\016\377h\214" + "\020\377cz\024\377Wj\013\377Zk\026\377\233\234m\377\236\227x\377\225\207s\377" + "\241\220\201\377\236\215~\377\236\216}\377\226\205o\377\215y_\377\231\202" + "e\377\251\224s\377\245\220r\377\224}g\377OJC\377\011\007\005\377'\035\032\377\\" + "K?\377nXG\377x_J\377u[G\377zeU\377wh\\\377WJ?\377}\210T\377PY/\377DR$\377" + "Zt<\377X\213\062\377S\212+\377Z\204\063\377n\240\062\377Ct\007\377Y\221\022\377" + "<l\004\377:j\010\377\063e\001\377O\205\002\377F~\002\377N\205\003\377J~\004\377L\202\003" + "\377Z\222\002\377Z\216\003\377l\233\006\377u\232\022\377O\177\000\377O\201\003\377U" + "\203\002\377\206\257\007\377j\231\010\377d\234\036\377r\243\023\377\216\265\004\377" + "X\205\001\377a\222\031\377\217\261\026\377\177\240\016\377}\243\001\377n\227\003\377" + "w\243\010\377\205\255\001\377x\242\002\377y\246\005\377o\237\003\377l\233\002\377v\242" + "\005\377k\232\004\377r\241\002\377|\251\004\377n\231\005\377\212\264\012\377\235\271" + "\016\377\200\243\012\377\177\247\016\377z\244\027\377\215\272/\377u\251\014\377" + "\211\277H\377\212\274?\377\211\273A\377}\260$\377\213\270-\377v\234\015\377" + "~\235\014\377\207\247\006\377Pz\002\377\\\201\010\377]\210\003\377L\177\004\377X\216" + "\006\377Q}\002\377^\206\007\377\207\231\025\377d\214\002\377X|\004\377S}\005\377Ao\001\377" + "R\202\004\377b\216\003\377y\235\002\377`\203\001\377b\212\003\377\210\264\011\377o\232" + "\013\377x\243\007\377d\225\023\377\244\314\207\377\245\316\177\377\256\324{\377" + "\250\313c\377\205\244'\377\232\256'\377w\234\040\377s\235\037\377h\221\016\377" + "w\243\017\377d\220\003\377x\247\027\377\213\265@\377\177\256\063\377w\255*\377" + "\\\231\015\377\\\225\011\377]\215\007\377Sv\007\377Vx\006\377i\212\022\377Y|\011\377" + "ct+\377\300\265\244\377\253\235\217\377\234\213\204\377\237\215\205\377\236" + "\214\203\377\220~s\377o]T\377fVD\377\200qW\377\242\221t\377\245\223y\377" + "\232\207q\377SLD\377\025\022\007\377N=\062\377kTH\377iO;\377oVC\377iO=\377L\064" + "(\377?\060'\377\063+!\377oyR\377M[\061\377L\\/\377Y}\060\377Jq\"\377Q\211\040" + "\377W\217*\377c\234*\377I\204\013\377@q\001\377Ao\011\377\063W\001\377M\203\003\377" + "N\210\002\377S\220\004\377Q\215\005\377Fz\005\377P\205\003\377S\210\002\377u\241\006\377" + "b\211\002\377d\212\007\377P\202\012\377R\205\004\377Y\212\003\377h\226\004\377g\223" + "\015\377G\201\006\377x\253\020\377~\255\003\377a\226\012\377u\250\013\377y\247\031" + "\377t\234\003\377v\234\001\377j\230\020\377\177\257\005\377\177\242\013\377\215\262" + "\004\377q\242\013\377p\247\015\377n\234\002\377\221\257\004\377\202\252\011\377\177" + "\250\001\377\232\274\003\377\211\251\003\377n\236\012\377\227\274\060\377\214\257" + "\061\377\210\251\003\377~\233\001\377s\224\003\377\202\254\033\377\204\264/\377\201" + "\251\002\377|\240\006\377\201\261\010\377t\243\003\377r\237\001\377\206\243\023\377" + "\217\247\011\377Mp\002\377\\{\003\377Y~\002\377Hl\001\377Vw\003\377Ow\002\377Y\205\003\377" + "k\221\002\377h\227\004\377T\200\003\377o\220\030\377Hs\002\377R\177\003\377c\224\003\377" + "m\236\010\377Y\207\003\377e\224\004\377\214\266\002\377\204\257\004\377p\241\024\377" + "t\237'\377\203\237\026\377\230\247\034\377\233\260(\377\202\255K\377\255\325" + "\204\377\241\315w\377t\242\066\377g\232!\377v\247\063\377d\233\027\377m\245" + "!\377m\235\"\377j\233\026\377a\222\016\377Z\214\004\377d\230\017\377`\232\007\377" + "X\216\002\377Ry\003\377f\203\014\377No\007\377Sn\013\377m\201(\377\261\247\233\377" + "\264\247\237\377\245\226\223\377\224\204\200\377\230\210\200\377\223\206" + "q\377\206\201]\377lgF\377kaH\377\224\203n\377\254\235\206\377\246\224\202" + "\377\\QL\377'\040\020\377dPE\377v]P\377|bP\377tT@\377iI\067\377cF\067\377WB\063" + "\377F\064$\377iqD\377F^\035\377Qs!\377Qz\022\377Hx\011\377=l\006\377c\222\061\377" + "c\234'\377J{\013\377Q\204\017\377M\204\011\377S\213\005\377W\221\002\377N\211\002" + "\377X\224\002\377S\213\001\377H}\002\377Y\223\003\377`\227\003\377m\237\011\377a\225" + "\031\377V\213\016\377y\253:\377x\253\031\377e\231\005\377v\246\015\377n\241\"\377" + "w\246'\377v\247\006\377{\254\005\377\\\214\003\377\211\257\004\377y\244\011\377\215" + "\261\003\377\202\251\006\377p\230\003\377{\251\004\377s\237\006\377\217\265\016\377" + "p\233\002\377a\230\014\377x\241\005\377\234\267\004\377\223\262\002\377}\247\004\377" + "\214\257\003\377\177\243\020\377g\222\001\377v\231\000\377\222\251\003\377\217\262" + "\023\377\226\271\064\377u\233\012\377p\233\012\377~\256$\377\242\310P\377\202" + "\242\002\377\203\246\005\377\211\246\032\377m\226\001\377z\243\001\377\215\253\004\377" + "Ul\003\377^|\003\377P{\003\377U~\004\377j\216\005\377c\212\006\377f\215\005\377h\227\006" + "\377o\246\004\377_\217\005\377`\213\006\377Js\001\377X\207\002\377b\224\003\377b\220" + "\003\377X\213\002\377j\233\004\377\214\265\004\377p\233\003\377h\220\006\377~\231\012" + "\377b\202\012\377j\220\007\377h\227\035\377n\245-\377f\235\025\377a\234\016\377" + "\213\302O\377\213\276R\377n\237&\377Z\204\011\377e\210%\377s\236\061\377p\231" + "\036\377p\243\031\377o\247\024\377`\235\011\377]\234\014\377K\205\003\377T\212\002" + "\377a\221\002\377b\202\005\377c\204\004\377Y~\023\377\234\242\202\377\235\224\216" + "\377\222\206\204\377\220\210{\377\220\230l\377\210\220a\377\230\235s\377" + "\231\233s\377\233\234s\377\247\246\200\377\252\251\203\377\237\243|\377r" + "\177Y\377Vj\067\377s\202G\377|\206G\377{\177=\377rs\061\377`]\030\377[W\034\377" + "`Z+\377cc\066\377h|\070\377a\213&\377K\177\006\377I~\005\377Dz\003\377H~\010\377\207" + "\263<\377T\203\021\377Q\207\023\377Z\232\023\377b\233\015\377[\220\002\377P\211" + "\004\377:p\002\377R\212\004\377V\214\005\377b\236\023\377Z\217\002\377p\243\004\377X\222" + "\011\377Z\222\013\377P\204\000\377\245\310^\377m\237\022\377m\233\011\377u\240" + "\005\377{\246\027\377y\252\004\377v\253\010\377{\250\005\377\200\250\004\377Uu\002\377" + "u\243\027\377~\250\003\377{\237\015\377y\247\002\377q\237\007\377\206\250\006\377\203" + "\255$\377x\250\023\377s\244\024\377\262\307\006\377\225\271\015\377\201\251#\377" + "u\236\000\377\177\245\004\377n\230\004\377i\226\004\377q\245\017\377\177\256\035\377" + "y\243\005\377~\255\006\377\204\256,\377\201\247$\377s\227\010\377\253\324u\377" + "\203\251\014\377\206\255#\377\214\255\030\377z\237\002\377\205\246\002\377\206" + "\244\002\377bs\005\377i\216\003\377Dt\004\377V|\004\377Yh\002\377V_\002\377hv\003\377Pf\002" + "\377m\241\003\377X\220\004\377e\227\005\377\210\263\007\377t\245\016\377q\245\002\377" + "g\235\002\377Y\220\004\377\177\255\004\377}\245\030\377s\237.\377g\221\007\377r\231" + "$\377^\216\013\377V\205\005\377u\232\014\377f\232\010\377W\214\001\377V\206\000\377" + "Z\217\000\377u\253\061\377z\257\071\377[\224\006\377_\224\011\377q\242\035\377q\243" + "\026\377\\\225\001\377\\\233\005\377X\224\006\377_\232\015\377\\\231\010\377S\214" + "\004\377\\\226\006\377b\231\011\377Z\216\006\377Y\217\005\377t\226.\377t\220-\377" + "t\220+\377\202\217]\377kwJ\377stX\377ieK\377iaJ\377\200r\\\377\231\205n\377" + "\242\216t\377\222za\377S?\066\377]H<\377\220tZ\377\211nQ\377|a>\377w`<\377" + "n]\061\377hc*\377n{\065\377^w!\377u\224)\377W\202\020\377Jz\004\377T\216\010\377" + "V\215\024\377Z\227\033\377i\243\062\377f\242+\377`\230\034\377V\215\010\377W\214" + "\001\377M\204\005\377H\200\006\377O\203\003\377L\177\002\377Y\210\006\377U|\003\377u\243" + "\036\377S\213\003\377Q\204\002\377Q~\006\377y\242.\377\211\265D\377[\221\006\377n" + "\224\005\377\204\251\013\377\202\256)\377g\230\002\377q\245\005\377\201\250\004\377" + "\210\253\002\377`\217\002\377p\245\012\377\202\255\002\377z\246\001\377o\236\003\377" + "`\221\002\377\254\270$\377\201\240(\377\241\274\017\377\231\262\004\377\205\250" + "\003\377r\233\004\377f\215\001\377i\221\003\377{\242\006\377s\232\005\377t\233\002\377" + "}\245\013\377{\242\022\377p\233\014\377|\250\002\377o\230\000\377x\236!\377|\233" + "\010\377\213\254\023\377\224\276D\377\200\246\000\377\231\301Z\377\200\256\061" + "\377\227\270\025\377\203\237\004\377Pk\002\377V|\003\377=c\001\377Sp\002\377Zk\002\377" + "Nc\002\377]z\002\377j\202\003\377z\243\003\377o\224\005\377Z\220\012\377O\212\005\377" + "{\253\003\377t\246\002\377_\217\004\377]\213\002\377\232\277\002\377u\240\025\377[\211" + "\023\377e\227\013\377e\225\034\377t\243\037\377e\235\017\377m\237\"\377\206\260" + "N\377~\254C\377c\226\021\377m\230\025\377e\230\002\377_\220\003\377a\224\004\377" + "n\237\010\377m\231\022\377\221\262\067\377\260\303\064\377\254\275=\377c\231" + "\021\377Z\225\024\377a\236\026\377Z\223\007\377_\230\012\377W\220\001\377V\216\002" + "\377U\211\002\377b\217\011\377q\230\026\377g\221\015\377_\212\017\377b\205\034\377" + "i\202)\377|\214J\377\212\220c\377\223\225o\377\213\213c\377\213\201a\377" + "\215\177c\377W@\070\377zdR\377\250\225q\377}nC\377\207yL\377\205zG\377zx\071" + "\377gn\037\377j\203+\377Qt\015\377[~\026\377Hl\013\377Is\003\377m\245,\377k\243" + ".\377}\260J\377Q\210\040\377u\253A\377V\212\004\377Z\215\003\377M\177\002\377I~" + "\004\377<m\002\377J~\004\377U\211\002\377u\236.\377Rp\005\377e\234\025\377\214\272d" + "\377N\177\000\377\227\261\061\377\274\317\215\377a\222\016\377q\237\005\377h\223" + "\002\377\216\271\034\377b\222\015\377V\214\014\377m\241\007\377\200\244\003\377\204" + "\251\003\377o\235\003\377v\242\004\377p\231\003\377v\241\002\377c\221\003\377\207\254" + "\006\377\231\254\006\377\277\306\017\377~\242\006\377Uw\002\377{\245\004\377d\221\004" + "\377k\223\003\377o\225\002\377k\224\004\377i\224\004\377{\237\002\377\203\244\001\377" + "\204\246\003\377q\230\004\377w\242\002\377p\223\003\377\177\236\003\377\222\252\002\377" + "\205\240\001\377\201\255\022\377\212\263\030\377\201\251\035\377\204\256(\377" + "p\222\002\377z\224\001\377S{\003\377S\206\002\377U\205\004\377Y\211\006\377[\213\005\377" + "`\220\003\377\\}\003\377`\200\003\377p\235\003\377}\242\015\377R\204\010\377T\216\004" + "\377]\222\004\377o\237\002\377n\221\015\377\201\251\012\377\224\273\006\377c\220" + "\012\377]\213\002\377q\235\014\377^\217\004\377b\223\004\377]\221\003\377U\210\002\377" + "`\220\010\377U{\000\377f\201\005\377}\226\024\377n\231\004\377e\224\007\377Y\215\016" + "\377b\225\023\377z\246\032\377Z\210\005\377\\\215\013\377f\220\022\377i\225\034" + "\377^\215\020\377^\220\016\377\\\215\006\377]\221\005\377X\214\003\377L|\002\377S\204" + "\002\377V\206\006\377g\221#\377Lx\015\377[\210\037\377s\232\071\377w\220@\377o\215" + "\065\377X\177\032\377]\213\040\377_\216!\377b\215\"\377h\216)\377j\206\062\377" + "\232\251s\377\224\233k\377\204rB\377\223uJ\377\224sG\377\253\217@\377z~\035" + "\377^|\021\377Ej\000\377i\214\061\377R{\023\377[\215\004\377\213\271N\377Y\216\026" + "\377f\237*\377`\234)\377J\202\003\377T\211\016\377a\225\006\377i\227\004\377f\223" + "\003\377Op\001\377U\214\002\377g\230\032\377\211\251<\377k\234\006\377P\203\006\377" + "b\216\040\377n\236.\377\216\266\070\377\231\271@\377\177\256\004\377P{\020\377" + "^\223\010\377\212\265\033\377_\221\001\377T\215\016\377y\245\014\377\261\302\004" + "\377x\243\005\377u\240\003\377~\252\005\377h\222\004\377c\222\003\377\200\241\003\377" + "\256\275\006\377\202\240\004\377\216\245\007\377i\220\002\377f\215\002\377v\240\004\377" + "f\214\004\377k\221\003\377m\230\003\377f\220\001\377m\226\005\377\177\243\004\377\206" + "\246\006\377\177\243\002\377v\235\004\377n\225\002\377y\240\004\377\223\254\002\377\207" + "\235\004\377i\217\001\377~\243\024\377\233\273D\377\201\247\026\377{\244\005\377" + "\200\252\025\377\206\226\017\377Fp\002\377Dq\002\377K|\003\377N|\002\377b\204\005\377" + "Y{\012\377Sv\007\377W|\005\377s\240\026\377{\237\004\377a\201\005\377H}\004\377P\207" + "\002\377b\227\003\377o\243\004\377]\220\003\377p\235\004\377M~\002\377X\211\004\377S\203" + "\003\377h\226\004\377\\\217\003\377J\200\003\377X\214\003\377R\204\002\377d\220\003\377" + "b\211\002\377l\221\002\377l\231\005\377K\177\002\377Z\213\002\377\210\252\013\377i\220" + "\020\377`\220\021\377d\227$\377_\230\034\377d\235\017\377W\213\004\377V\205\003\377" + "U\206\005\377Lx\003\377i\224\007\377p\227\004\377\216\247#\377\245\262<\377\231\256" + ",\377\214\247\033\377\203\247\023\377\201\251\030\377\204\255\035\377\223\265" + "\064\377\217\256\071\377\205\241\066\377v\223(\377|\233,\377j\217\017\377`\213" + "\011\377g\215!\377d\207\"\377i\210#\377h\207!\377g\211!\377q\220\015\377y\233" + "\007\377Iq\011\377\\|\037\377}\253\066\377j\232\025\377l\233\010\377\177\255C\377" + "]\227\020\377\\\224\016\377Fu\001\377Cv\004\377g\232\016\377U\203\003\377S\202\003\377" + "^\214\004\377t\245\007\377_\233\010\377k\240\024\377g\235\021\377^\222\004\377Z\205" + "\004\377W\202\003\377\201\261?\377t\236\025\377\203\254\023\377o\236\040\377`\224" + "\007\377U\211\011\377\217\273\013\377a\223\001\377b\240\034\377t\234\005\377\224\256" + "\004\377p\234\002\377\207\253\006\377\202\251\004\377m\231\003\377\220\257\005\377\246" + "\274\005\377b\214\002\377d\221\003\377\210\243\011\377}\233\002\377\240\277\"\377" + "e\216\001\377\210\253\006\377o\224\002\377f\223\002\377~\251\017\377n\223\011\377{" + "\240\001\377\230\262\027\377\177\242\004\377v\227\004\377}\237\005\377\212\252\006\377" + "r\221\003\377s\226\002\377r\235\002\377\236\270<\377\216\236:\377\257\313j\377" + "t\235\000\377|\245\017\377\222\276\037\377T\205\003\377R\204\002\377K\177\002\377D" + "r\001\377[v\021\377d}\017\377\210\202\031\377x\233\060\377m\234\001\377M\202\002\377" + "W\214\004\377W\215\022\377J\201\003\377g\236\003\377c\230\003\377z\252\003\377^\221" + "\003\377V\214\003\377M\202\002\377Qw\002\377^\207\003\377c\217\003\377`\220\003\377X\214" + "\003\377_\224\000\377b\217\005\377c\213\002\377f\226\002\377P\204\003\377]\217\002\377" + "p\235\003\377\223\255\004\377~\236\006\377f\235\010\377m\241\035\377d\226\007\377[" + "\210\002\377T{\006\377U\177\006\377Z\207\004\377]\216\002\377^\216\003\377R~\003\377X\202" + "\010\377\200\235.\377\250\260e\377\230\245a\377\216\246a\377\220\250a\377" + "\240\250r\377\231\224b\377\206wC\377\230\210R\377\222\217L\377\227\214X\377" + "\206\201L\377^m\061\377as\071\377\211\226M\377\226\240P\377l\204&\377T~\027" + "\377[\214\016\377r\235\002\377x\234\021\377\220\256@\377\202\254M\377\\\221\023" + "\377s\243\020\377f\232(\377_\226\022\377Dn\003\377[\204\000\377u\244'\377S|\001\377" + "Kq\002\377?e\002\377J\204\004\377J\210\006\377T\214\011\377\177\260\031\377u\245\003" + "\377Qz\004\377Hs\002\377V\203\006\377k\232\023\377z\247\024\377f\231\016\377Y\213" + "\010\377W\207\004\377P\202\004\377~\256\006\377[\220\006\377^\227\026\377\230\265\003" + "\377j\222\004\377u\242\002\377\200\240\014\377\212\260\004\377\221\265\004\377s\231" + "\004\377\201\245\004\377Y\206\002\377q\227\005\377\207\251\003\377}\232\004\377\224\261" + "\003\377\213\254\005\377y\236\002\377g\221\002\377s\241\005\377\201\250\004\377d\215" + "\002\377q\233\004\377\221\260\016\377x\226\003\377z\225\006\377~\241\006\377x\243\003" + "\377h\227\002\377{\250\025\377\200\251\021\377\203\257\027\377~\240\032\377{\231" + "\035\377\206\253&\377z\241\001\377x\250\017\377U\210\004\377Y\207\003\377Z\204\002" + "\377bg\004\377\202t\005\377{\200\004\377h\210&\377S\210\005\377T\216\003\377T\212\005" + "\377V\207\003\377X\205\004\377S\204\005\377g\241\002\377]\226\003\377e\232\003\377i\231" + "\002\377K\177\003\377V\215\013\377Z\201\012\377^\213\006\377`\220\007\377\\\217\003\377" + "[\223\003\377~\250?\377b\222\010\377h\230\005\377R\210\003\377]\224\003\377d\234\002" + "\377r\251\004\377k\237\001\377d\233\004\377c\232\015\377_\224\005\377T\206\002\377[" + "\204\004\377Zz\014\377_\201\020\377h\223\026\377Lz\004\377@b\003\377\070Y\003\377(I\002" + "\377lw!\377}\203\060\377\223\226L\377\273\265|\377y\206%\377Sm\026\377Wq.\377" + "d|>\377j\206;\377l\207\061\377y\214;\377w\211\071\377Rf!\377?S\016\377`t\025" + "\377j\203\020\377o\213\011\377m\212\004\377~\225\004\377q\232\003\377\200\220\003\377" + "{\230\061\377\201\252L\377j\227\004\377l\235\023\377\203\250\061\377]\222\031\377" + "]\215\003\377m\232\030\377[{\034\377Fj\002\377@g\003\377By\005\377F}\012\377]\230'\377" + "l\252\060\377r\250\014\377]\214\003\377l\234\011\377a\215\007\377b\226\006\377~\252" + "\023\377n\234\014\377O~\002\377d\226\013\377W\207\025\377j\227\014\377\211\262\003" + "\377\\\214\011\377b\235\025\377\226\260\003\377k\225\007\377z\247\002\377\234\261" + "\025\377q\240\003\377f\221\005\377|\241\004\377x\236\002\377\\\206\002\377w\233\004\377" + "\206\245\002\377\210\244\004\377\223\255\010\377y\232\003\377|\242\013\377s\235\004" + "\377x\242\003\377\215\254\005\377e\217\002\377m\237\014\377u\233\000\377\242\264." + "\377\204\223\016\377\221\261\003\377\201\255\002\377t\252\026\377t\247\023\377\207" + "\270\060\377\205\260\037\377\211\267\021\377s\235\004\377\211\261\"\377\205\256" + "\024\377q\231\002\377ct\004\377u\230\005\377p\241\004\377j\225\005\377Z\210\004\377l\235" + ")\377v\237'\377_\202\005\377g\231\004\377L\201\001\377l\230\016\377Q\177\003\377N" + "\202\002\377b\233\002\377O\212\002\377_\226\002\377p\250\003\377O\202\001\377X\211\005" + "\377l\235\030\377p\236\063\377T\202\020\377T\203\007\377m\233*\377Z\224\007\377" + "L\203\002\377b\227\003\377T\207\002\377`\231\002\377d\235\014\377S\213\003\377P\204" + "\004\377J~\003\377V\213\003\377[\214\004\377`\212\002\377b\212\004\377w\236\012\377{\250" + "\016\377a\217\024\377Nu\003\377Nf\011\377IW\007\377\\o\010\377z\211#\377\220\241" + "I\377\207\252=\377\243\274X\377\201\222-\377x\213\036\377Qe\010\377`x\031\377" + "n\220\064\377d\204\032\377]\205\012\377\\\213\012\377U\204\002\377Gp\001\377Pu\000" + "\377R}\010\377q\232\023\377\223\265\061\377Rt\012\377`\214\000\377r\243-\377w\241" + "\071\377h\237\017\377X\211\014\377\177\233%\377f\215&\377M\177\002\377:j\000\377" + "Z\207!\377N|\012\377Cm\001\377H{\004\377<m\000\377\177\254R\377x\252S\377c\217\020" + "\377a\223\005\377Et\002\377f\210\021\377p\231\007\377r\246\003\377\203\256\014\377" + "X\205\003\377U\205\002\377c\226\016\377Co\004\377h\216\004\377\205\256\004\377_\217" + "\004\377\202\264\032\377~\244\005\377a\217\006\377\203\251\007\377\224\252\015\377" + "u\245\002\377^\215\003\377\204\253\003\377o\231\012\377\\\210\002\377\202\245\005\377" + "}\241\005\377\222\263\004\377\210\243\003\377n\220\002\377|\240\006\377x\224\004\377" + "u\231\004\377\217\255\010\377q\226\002\377{\242\010\377}\240\000\377\217\256\001\377" + "\202\234\010\377\215\267\005\377\207\262\004\377r\231\010\377p\230\002\377o\232\007" + "\377z\252\024\377\202\255\035\377\202\256\005\377\206\263\021\377\203\254\005\377" + "\177\243\002\377l|\005\377Q\200\003\377X\177\005\377_\205\004\377g\234\006\377^\230\006" + "\377S\212\001\377i\234\004\377d\226\003\377_u\006\377i\220\017\377O\201\004\377[\222" + "\003\377^\224\003\377B{\002\377h\236\003\377Lz\001\377d\226\011\377Y\203\003\377[\215" + "\000\377P\201\001\377{\245\061\377{\237\071\377Sy\010\377Gw\002\377a\230\025\377c\231" + "\002\377X\215\003\377\\\223\005\377^\226\015\377T\213\001\377b\230\020\377\\\220\005" + "\377V\212\010\377e\234\011\377b\231\015\377n\236\031\377c\220\032\377Y\214\017" + "\377a\227\012\377]\216\002\377v\246\017\377i\225\020\377e\212\025\377d\202\031\377" + "\236\271c\377\207\261I\377r\231%\377a\177\024\377v\215!\377dy\000\377m\214\022" + "\377X\202\007\377V\206\004\377\\\220\011\377M~\004\377Gv\004\377Jv\007\377U\206\023\377" + "y\251&\377\205\252\063\377Oe\013\377Ni\016\377n\224\003\377t\236\"\377x\243D\377" + "c\230\001\377L\200\013\377y\244\040\377G{\007\377Bp\003\377Q~\034\377b\223\061\377" + "\063W\002\377Ap\017\377J\203\016\377x\252H\377h\237;\377L}\014\377u\230\011\377" + "Uu\002\377\\{\005\377g\220\010\377t\240\001\377\233\271;\377\223\261\"\377a\230" + "\005\377g\226\025\377R|\004\377S}\002\377l\224\004\377\205\257\003\377h\227\023\377\224" + "\267\012\377z\244\010\377t\237\006\377\236\265\005\377o\233\002\377v\244\011\377i" + "\230\010\377|\243\002\377h\223\004\377f\221\003\377v\236\003\377s\233\002\377\213\260" + "\003\377p\225\004\377u\234\003\377}\244\002\377w\233\003\377d\215\003\377n\230\001\377" + "o\230\003\377v\240\020\377w\241\007\377\203\257\026\377\200\254\031\377\177\250" + "\006\377\221\266\003\377z\217\004\377v\224\002\377s\226\001\377w\241\017\377\200\253" + "\026\377u\244\004\377s\244\004\377x\244\003\377u\232\002\377X\210\003\377`\212\007\377" + "}\210\023\377d{\003\377\\\206\003\377Gv\002\377C}\002\377V\214\003\377m\235\006\377v\222" + "\011\377Z\210\003\377O\206\004\377k\242\006\377Z\221\002\377T\206\004\377`\223\003\377" + "Jy\002\377Pz\002\377_\213\003\377a\222\003\377Z\212\004\377{\233\030\377T|\010\377t\241" + "\067\377L\200\001\377W\220\014\377g\237\007\377\\\221\002\377a\213\011\377V\211\002" + "\377a\226\013\377\204\260/\377q\240\020\377s\243\022\377v\244)\377{\254@\377" + "d\232#\377K\200\032\377Ev\037\377a\213\060\377j\227*\377\\\214\026\377Q\201\010" + "\377M}\004\377p\246/\377\224\277[\377u\245)\377g\230\016\377e\225\011\377\177" + "\250*\377j\231\013\377^\216\003\377`\223\003\377W\207\004\377Do\004\377Q\200\005\377" + "Ku\002\377h\230\027\377\225\275F\377\246\311_\377\203\237B\377j\212!\377d\201" + "\021\377w\241\017\377l\237\022\377\226\267N\377[\213\000\377U\204\014\377m\240" + "\021\377@j\005\377Ov\011\377a\211\034\377Y\200\040\377>a\001\377n\235@\377\223\304" + "o\377s\252K\377Dk\013\377Is\004\377m\232\010\377`\212\002\377c\216\004\377m\223\004" + "\377\224\265(\377\202\243\033\377\204\247\015\377d\237\026\377P\203\004\377V\206" + "\011\377X\206\012\377u\236\010\377\206\256\002\377\210\262\031\377\214\264\007\377" + "\214\240\015\377\211\252\005\377\237\267\034\377~\253\024\377\207\257\023\377m" + "\224\004\377z\237\004\377r\227\011\377i\223\011\377u\242\004\377\212\257\003\377\203" + "\247\003\377u\234\004\377z\242\002\377q\235\003\377u\236\002\377u\226\015\377u\235\006" + "\377f\225\003\377o\236\003\377{\245\006\377\220\264\010\377\214\252\004\377\206\252" + "\003\377\215\261\004\377\204\246\003\377[z\001\377\212\241\020\377\206\232\004\377\224" + "\246\005\377\226\263\024\377x\247\034\377\200\255\036\377s\236\000\377i\233\006\377" + "g\230\007\377_\227\005\377\\\214\004\377Fn\000\377V\206\003\377Aw\004\377R\207\003\377" + "f\232\005\377Y\204\002\377\\\215\003\377V\220\002\377f\234\003\377^\223\004\377_\216" + "\004\377p\237\004\377P{\003\377W\177\013\377b\221\004\377v\252\002\377P\204\003\377V\204" + "\004\377a\213\010\377P\201\000\377k\236\037\377b\217\027\377o\242+\377U\213\000\377" + "_\221\006\377Z\216\007\377X\223\007\377m\242\024\377o\241\013\377k\240\016\377z\254" + ";\377y\254?\377a\230\027\377]\220\007\377f\222\031\377Wx\025\377Nk\002\377q\225" + "\067\377o\241\065\377q\245\"\377z\254'\377Z\212\006\377]~\007\377]\210\022\377c" + "\221\025\377c\226\017\377Y\177\022\377_\220\006\377_\223\003\377O}\007\377c\221\030" + "\377v\243/\377\206\271/\377~\255'\377\223\266K\377\220\261K\377\227\276]" + "\377\237\311o\377\230\306k\377m\236\020\377h\230\016\377\210\257-\377\\\226" + "\012\377Y\216\023\377d\226\006\377Hs\000\377n\222/\377_\177\035\377Rl\004\377\202" + "\255G\377|\256P\377}\256=\377Kt\015\377a\217$\377t\244\011\377X\212\006\377S" + "\203\010\377a\222\012\377y\243\002\377\240\275\070\377Q\207\012\377j\237\013\377" + "h\227\021\377Kx\003\377m\243\021\377Y\211\006\377w\241\003\377\203\262\011\377\203" + "\260\005\377\177\246\014\377y\231\005\377\216\256\002\377o\222\003\377v\240\005\377" + "g\221\002\377\200\251\004\377k\227\006\377m\240\013\377h\231\004\377y\244\005\377\231" + "\300\010\377s\240\011\377n\230\004\377w\237\002\377e\221\002\377}\234\003\377\250\254" + "\024\377l\224\003\377k\232\003\377e\226\002\377\202\247\030\377\204\246\002\377\212" + "\253\003\377\217\262\004\377\227\270\003\377\221\262\003\377d\211\002\377k\223\003\377" + "~\240\004\377\227\245\006\377\220\237\006\377\222\255\015\377\214\257\015\377y\250" + "\007\377c\222\005\377[\213\004\377r\245\021\377q\253\030\377k\246\030\377Bv\001\377" + "@s\002\377J}\002\377S\203\003\377U|\011\377U\211\003\377c\232\004\377f\225\002\377W\210" + "\003\377h\230\002\377m\233\002\377Z\210\005\377c\222\023\377L\214\006\377p\244\003\377" + "l\236\003\377R\201\004\377M\177\002\377_\217\002\377^\215\002\377a\216\024\377o\243" + "=\377a\240\011\377h\241\034\377\205\270N\377b\236\026\377T\216\010\377i\233\016" + "\377_\222\003\377x\254/\377\200\265;\377d\234\014\377g\240\005\377e\230\001\377" + "X\207\013\377h\224\065\377f\222\026\377~\250\060\377~\255=\377}\255/\377q\234" + "\021\377t\240\023\377e\231\005\377\\\216\003\377a\225\004\377p\243\016\377o\243\016" + "\377}\265\061\377j\236\066\377\226\302v\377\220\276J\377q\243\035\377c\214\023" + "\377Vt\014\377u\235\061\377\210\264\\\377V\204\037\377Z\221\031\377\201\266;" + "\377z\257,\377w\250\025\377^\213\004\377V}\016\377l\223\012\377i\221#\377e\212" + "%\377]\200\007\377\177\252O\377_\222-\377v\244(\377Rx\022\377m\237\061\377W\204" + "\027\377m\232\003\377h\227\005\377W\203\006\377q\233\003\377\225\267\062\377s\227\020" + "\377n\233\034\377h\234\014\377i\233\011\377f\233\015\377g\225\026\377j\231\013" + "\377\205\266\003\377m\252\025\377s\245\010\377a\217\004\377o\237\004\377~\254\002\377" + "m\234\004\377\211\256\007\377z\240\010\377i\224\004\377y\242\007\377r\236\020\377n" + "\235\003\377\203\257\004\377t\244\007\377`\214\005\377s\236\003\377n\231\003\377l\226" + "\005\377\225\247\002\377{\236\024\377k\223\002\377t\233\007\377g\217\002\377~\253\005" + "\377p\233\004\377s\232\003\377\211\261\002\377\216\263\002\377\200\247\002\377\212" + "\260\004\377p\227\002\377\204\244\003\377}\224\003\377\243\257\002\377\242\271'\377" + "}\241\002\377t\241\006\377R\206\002\377Z\222\005\377O\205\006\377`\226\027\377w\247" + "-\377v\246\062\377[\216\"\377M\202\000\377>r\001\377W\205\005\377N\204\003\377W\212" + "\004\377o\240\010\377Kz\003\377d\230\004\377Y\213\004\377[\214\012\377Z\216\004\377F" + "~\002\377O\205\003\377{\254\002\377i\226\003\377R{\003\377X\214\003\377]\217\004\377Y\212" + "\013\377q\236'\377|\265;\377q\251-\377o\241+\377`\224\016\377L\202\003\377M\200" + "\003\377M~\010\377V\205\003\377d\213\023\377y\231\030\377p\235\035\377d\231\026\377" + "s\247)\377\\\220\021\377{\257<\377\201\260P\377]\224\010\377d\231\007\377a\231" + "\004\377h\242\022\377^\220\011\377t\241\024\377\212\264'\377k\225\013\377o\227" + "\024\377\207\252A\377\225\273P\377|\256\061\377k\235\035\377k\230\035\377}\245" + "/\377\201\260E\377r\247>\377e\231!\377^\220\022\377m\234!\377[\215\016\377" + "V\213\020\377b\227\026\377y\244\033\377~\247\064\377o\222\015\377u\243\066\377" + "Z\207\037\377|\255H\377W\177\037\377s\252*\377Rz\005\377f\223\"\377T\204\015\377" + "Gp\001\377[\204\002\377w\245+\377x\242\003\377v\236\023\377\204\250\024\377k\217" + "\026\377h\230\006\377l\236\031\377o\236\007\377q\234\030\377p\240\007\377\206\266" + "\010\377r\244\011\377h\246\036\377y\250\000\377i\226\002\377r\240\003\377\200\261" + "\003\377q\235\003\377\201\242\020\377\215\261\032\377q\236\003\377Zs\002\377l\230\003" + "\377r\231\003\377\214\263\004\377u\242\005\377n\230\006\377\203\245\013\377q\224\020" + "\377p\234\011\377\201\247\036\377u\241\013\377v\236\006\377y\243\001\377s\241\001" + "\377m\233\003\377]\212\004\377t\237\004\377{\245\003\377\222\267\004\377n\227\002\377" + "\204\246\006\377\204\246\005\377\227\266\013\377\220\252\004\377\231\257\005\377\270" + "\274@\377|\243\016\377p\233\001\377`\225\003\377a\225\002\377a\223\003\377T\206\004" + "\377X\216\003\377Y\220\001\377b\220\031\377\225\275`\377c\211\030\377T\204\003\377" + "H~\002\377V\212\003\377g\240\002\377M\205\003\377a\227\004\377Q\202\002\377Ky\002\377^" + "\212\014\377S\204\002\377U\207\003\377m\236\003\377^\210\002\377h\235\002\377a\227\003" + "\377L\205\003\377a\232\011\377h\240\021\377j\242\033\377}\260A\377\232\305h\377" + "g\237\023\377Fw\002\377Z\215\003\377V\213\006\377V\206\003\377f\235\012\377V\216\004" + "\377]\222\002\377U\213\002\377V\217\002\377^\226\003\377[\224\005\377R\213\005\377o\240" + "\033\377r\241\016\377a\214\015\377f\232\"\377b\226\011\377i\236\013\377o\236\037" + "\377{\250?\377e\221\027\377\225\267\065\377\200\244\031\377^\205\004\377t\241" + "'\377i\237\037\377k\240\"\377l\232%\377Fg\006\377^\216\024\377z\241\063\377\212" + "\235<\377b\220\031\377L~\003\377V\211\004\377p\235\016\377g}\035\377\207\264\065" + "\377z\246*\377}\264B\377^\215'\377l\221\062\377d\214!\377k\222\033\377Mu\010" + "\377@f\002\377Z\205\004\377v\244\"\377w\247\022\377m\224\001\377\241\272*\377h\213" + "\017\377^\214\011\377j\230\005\377\216\257#\377k\227\004\377\207\260\005\377\204" + "\255\007\377h\224\005\377f\235\021\377j\244\024\377|\252\002\377q\240\004\377\206\256" + "\004\377u\241\002\377h\224\002\377r\217\007\377\211\251\013\377j\227\003\377]\201\003" + "\377i\220\002\377\227\270\003\377p\227\002\377z\243\020\377u\242\001\377\177\255\031" + "\377g\221\003\377g\217\010\377g\215\012\377n\230\005\377\177\252\007\377y\242\014" + "\377{\243\025\377l\225\003\377f\221\004\377\210\261\004\377s\234\003\377\200\246\003" + "\377}\240\003\377\207\247\003\377\214\250\003\377\200\241\006\377\220\267\021\377" + "\202\245\003\377\242\261\031\377\226\260-\377u\236\002\377]\227\023\377Y\210\001" + "\377Nv\000\377_\202\017\377U\210\005\377]\231\010\377a\236\010\377Y\216\000\377_\223" + "\027\377W\214\006\377G\202\003\377b\226\023\377d\232\001\377[\223\007\377f\225\002\377" + "Q}\005\377Hs\002\377`\212\013\377S\200\004\377W\212\003\377X\212\002\377^\206\003\377" + "l\234\004\377n\245\004\377J\177\003\377i\235\007\377t\250\026\377|\260+\377u\253\065" + "\377\207\266L\377\225\300T\377\207\264B\377r\225\066\377^\201\023\377`\220" + "\021\377Iu\003\377L~\006\377R\205\004\377S\201\003\377]\221\015\377`\233\005\377M\201" + "\000\377_\224\005\377d\225\013\377`\220\011\377h\231\021\377\\\214\004\377h\241\017" + "\377b\227\012\377v\246\036\377k\231\035\377j\232\014\377u\245\022\377~\241\033" + "\377r\230\030\377\204\260Q\377\200\255J\377i\210+\377}\227A\377}\251A\377" + "\200\257I\377b\226)\377]\222\036\377\\\222\026\377F{\005\377\\\222\007\377v\245" + "\005\377j\225\011\377t\254\024\377^\214\020\377p\245\035\377Y\213\014\377u\246(" + "\377Z\212!\377R~\016\377F_\004\377JX\006\377h\216\007\377u\250#\377\214\261\001\377" + "\207\244\012\377\177\251\026\377l\224\024\377i\232\002\377t\251\035\377h\231\011" + "\377\204\260\005\377x\244\010\377\200\251\005\377`\216\011\377k\245.\377j\240\013" + "\377w\246\002\377\205\260\004\377w\235\004\377i\225\002\377v\235\003\377l\221\005\377" + "}\247\002\377u\245\006\377q\235\007\377r\232\003\377\224\265\010\377v\237$\377l\232" + "\012\377l\231\004\377~\254\002\377w\247\030\377r\236\015\377w\236\007\377{\243\004\377" + "u\232\004\377\212\261\032\377`\214\003\377\222\267)\377v\243\004\377\200\253\005\377" + "p\225\000\377\210\255\004\377\203\246\005\377\207\247\007\377\215\245\001\377l\212" + "\003\377~\225\004\377\204\242\010\377\222\261\010\377\211\247\024\377\220\262\016" + "\377=u\005\377[\216\"\377\213\271G\377Fw\005\377Dp\002\377:j\001\377b\231\013\377" + "Y\217\002\377Q\210\005\377o\250\005\377X\212\001\377p\244\030\377\\\225\001\377]\225" + "\007\377a\223\003\377Ls\005\377V\203\005\377U\202\005\377Xv\010\377c\223\004\377N\201" + "\003\377Y\213\005\377a\220\006\377a\215\005\377\\\213\003\377V\206\001\377~\251\061\377" + "\216\301F\377l\242\"\377\241\313i\377\211\267A\377\215\274L\377\262\334\214" + "\377h\215<\377h\214/\377h\240\007\377U\216\005\377W\216\004\377^\215\004\377Q\203" + "\003\377e\234\004\377s\254\034\377\202\262E\377z\244\060\377i\227\011\377e\223\013" + "\377d\225\013\377j\237\015\377c\222\017\377j\225\011\377\211\261\011\377{\245" + "\012\377\177\250\037\377\234\300c\377|\251F\377\204\251Z\377{\241G\377g\226" + "/\377R\206\015\377a\210\020\377dw\004\377\201\262\032\377\201\255\067\377x\247" + "\061\377r\251.\377i\233\017\377z\245\002\377q\243-\377l\240\020\377`\224\027\377" + "d\233\"\377d\234\030\377|\261-\377v\252(\377c\223\010\377[\205\005\377Nu\005\377" + "x\244\005\377\200\260\022\377x\234\005\377\204\245\023\377z\245\031\377m\233\005\377" + "q\233\005\377}\256&\377\204\260\005\377V\204\004\377z\247\015\377^\204\004\377g\222" + "!\377\211\265a\377\200\254\011\377}\253\003\377v\241\002\377n\230\004\377c\212\002" + "\377{\233\010\377\\\210\002\377r\243\014\377n\233\002\377q\232\002\377\201\250\005" + "\377y\231\016\377u\237\011\377\217\254\017\377\202\256\025\377\236\312Q\377\206" + "\261\061\377p\237\005\377{\245\013\377\234\274\016\377q\225\002\377\215\264\010\377" + "t\237\021\377a\211\005\377\214\271\022\377z\254\016\377}\254\034\377\231\273\016" + "\377\207\244\011\377\216\251\012\377\211\236\033\377\211\247\006\377Oc\001\377w" + "\233\003\377\211\261\017\377\205\247\013\377r\227\006\377[\207\003\377\200\246A\377" + "y\253\071\377d\221\034\377@c\002\377;a\002\377Iq\003\377W\211\005\377J\177\001\377g\231" + "\003\377Lu\004\377W\216\003\377b\242\006\377[\230\010\377P\203\000\377u\230.\377U\202" + "\005\377T\201\002\377Ir\003\377U\215\003\377I\177\003\377]\225\010\377Hi\003\377Vq\007\377" + "Or\002\377]\212\004\377;X\001\377^\207\015\377c\217\020\377w\253\032\377\203\270\060" + "\377\205\273\066\377\210\274\067\377r\244%\377Ms\015\377\177\241,\377a\213\023" + "\377l\244\025\377[\217\003\377c\234\004\377a\227\005\377j\234\005\377v\251\013\377" + "s\244\016\377x\247'\377y\245.\377q\242'\377o\237(\377g\226\036\377_\222\032" + "\377]\217\016\377`\222\017\377h\234\033\377Z\215\010\377\201\257\020\377\201\242" + "\040\377\177\253\037\377{\254%\377x\251\015\377~\246\013\377\206\254\031\377w" + "\246\020\377m\243\027\377d\231\025\377b\232\027\377W\213\011\377v\246\003\377]\205" + "\013\377e\225\013\377n\243\040\377z\253*\377X\211\020\377P~\004\377{\247=\377{" + "\245*\377Wz\006\377Qv\011\377\200\256\003\377p\240\001\377\240\272+\377r\243\027" + "\377\205\252\005\377s\240\004\377\214\264\004\377~\253\004\377^\216\020\377y\252\007" + "\377R\201\003\377h\230\027\377t\242%\377\231\300S\377\243\303!\377\234\271\"" + "\377\232\264$\377\207\240\021\377r\216\005\377\202\237\005\377h\226\003\377o\235" + "\013\377\206\254\003\377j\227\003\377\214\261\005\377y\241\006\377r\236\007\377\221" + "\262\014\377\205\255\016\377V\201\010\377}\247\015\377\216\261\013\377n\225\013" + "\377\220\265\007\377\205\260\005\377\210\256\024\377z\243\004\377x\241\003\377v\243" + "\010\377\215\267)\377\250\301.\377\252\302\033\377\222\257(\377\224\260\025" + "\377~\233\006\377\220\256\007\377\203\241\003\377\207\254\002\377y\232\002\377\212" + "\247\011\377\207\256\022\377", +}; + diff --git a/examples/cat.h b/examples/cat.h new file mode 100644 index 00000000..46dc50f6 --- /dev/null +++ b/examples/cat.h @@ -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 diff --git a/examples/dmabuf-capture.c b/examples/dmabuf-capture.c new file mode 100644 index 00000000..ebbe0a70 --- /dev/null +++ b/examples/dmabuf-capture.c @@ -0,0 +1,850 @@ +#define _POSIX_C_SOURCE 199309L +#include <libavformat/avformat.h> +#include <libavutil/display.h> +#include <libavutil/hwcontext_drm.h> +#include <libavutil/pixdesc.h> +#include <poll.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <pthread.h> +#include <stdbool.h> +#include <drm_fourcc.h> +#include "wlr-export-dmabuf-unstable-v1-client-protocol.h" + +struct wayland_output { + struct wl_list link; + uint32_t id; + struct wl_output *output; + char *make; + char *model; + int width; + int height; + AVRational framerate; +}; + +struct fifo_buffer { + AVFrame **queued_frames; + int num_queued_frames; + int max_queued_frames; + pthread_mutex_t lock; + pthread_cond_t cond; + pthread_mutex_t cond_lock; +}; + +struct capture_context { + AVClass *class; /* For pretty logging */ + struct wl_display *display; + struct wl_registry *registry; + struct zwlr_export_dmabuf_manager_v1 *export_manager; + + struct wl_list output_list; + + /* Target */ + struct wl_output *target_output; + bool with_cursor; + + /* Main frame callback */ + struct zwlr_export_dmabuf_frame_v1 *frame_callback; + + /* If something happens during capture */ + int err; + bool quit; + + /* FFmpeg specific parts */ + pthread_t vid_thread; + AVFrame *current_frame; + AVFormatContext *avf; + AVCodecContext *avctx; + AVBufferRef *drm_device_ref; + AVBufferRef *drm_frames_ref; + AVBufferRef *mapped_device_ref; + AVBufferRef *mapped_frames_ref; + + /* Sync stuff */ + struct fifo_buffer vid_frames; + + int64_t start_pts; + + /* Config */ + enum AVPixelFormat software_format; + enum AVHWDeviceType hw_device_type; + AVDictionary *encoder_opts; + int is_software_encoder; + char *hardware_device; + char *out_filename; + char *encoder_name; + float out_bitrate; +}; + +static int init_fifo(struct fifo_buffer *buf, int max_queued_frames) { + pthread_mutex_init(&buf->lock, NULL); + pthread_cond_init(&buf->cond, NULL); + pthread_mutex_init(&buf->cond_lock, NULL); + buf->num_queued_frames = 0; + buf->max_queued_frames = max_queued_frames; + buf->queued_frames = av_mallocz(buf->max_queued_frames * sizeof(AVFrame)); + return !buf->queued_frames ? AVERROR(ENOMEM) : 0; +} + +static int get_fifo_size(struct fifo_buffer *buf) { + pthread_mutex_lock(&buf->lock); + int ret = buf->num_queued_frames; + pthread_mutex_unlock(&buf->lock); + return ret; +} + +static int push_to_fifo(struct fifo_buffer *buf, AVFrame *f) { + int ret; + pthread_mutex_lock(&buf->lock); + if ((buf->num_queued_frames + 1) > buf->max_queued_frames) { + av_frame_free(&f); + ret = 1; + } else { + buf->queued_frames[buf->num_queued_frames++] = f; + ret = 0; + } + pthread_mutex_unlock(&buf->lock); + pthread_cond_signal(&buf->cond); + return ret; +} + +static AVFrame *pop_from_fifo(struct fifo_buffer *buf) { + pthread_mutex_lock(&buf->lock); + + if (!buf->num_queued_frames) { + pthread_mutex_unlock(&buf->lock); + pthread_cond_wait(&buf->cond, &buf->cond_lock); + pthread_mutex_lock(&buf->lock); + } + + AVFrame *rf = buf->queued_frames[0]; + for (int i = 1; i < buf->num_queued_frames; i++) { + buf->queued_frames[i - 1] = buf->queued_frames[i]; + } + buf->num_queued_frames--; + buf->queued_frames[buf->num_queued_frames] = NULL; + + pthread_mutex_unlock(&buf->lock); + return rf; +} + +static void free_fifo(struct fifo_buffer *buf) { + pthread_mutex_lock(&buf->lock); + if (buf->num_queued_frames) { + for (int i = 0; i < buf->num_queued_frames; i++) { + av_frame_free(&buf->queued_frames[i]); + } + } + av_freep(&buf->queued_frames); + pthread_mutex_unlock(&buf->lock); +} + +static void output_handle_geometry(void *data, struct wl_output *wl_output, + int32_t x, int32_t y, int32_t phys_width, int32_t phys_height, + int32_t subpixel, const char *make, const char *model, + int32_t transform) { + struct wayland_output *output = data; + output->make = av_strdup(make); + output->model = av_strdup(model); +} + +static void output_handle_mode(void *data, struct wl_output *wl_output, + uint32_t flags, int32_t width, int32_t height, int32_t refresh) { + if (flags & WL_OUTPUT_MODE_CURRENT) { + struct wayland_output *output = data; + output->width = width; + output->height = height; + output->framerate = (AVRational){ refresh, 1000 }; + } +} + +static void output_handle_done(void* data, struct wl_output *wl_output) { + /* Nothing to do */ +} + +static void output_handle_scale(void* data, struct wl_output *wl_output, + int32_t factor) { + /* Nothing to do */ +} + +static const struct wl_output_listener output_listener = { + .geometry = output_handle_geometry, + .mode = output_handle_mode, + .done = output_handle_done, + .scale = output_handle_scale, +}; + +static void registry_handle_add(void *data, struct wl_registry *reg, + uint32_t id, const char *interface, uint32_t ver) { + struct capture_context *ctx = data; + + if (!strcmp(interface, wl_output_interface.name)) { + struct wayland_output *output = av_mallocz(sizeof(*output)); + + output->id = id; + output->output = wl_registry_bind(reg, id, &wl_output_interface, 1); + + wl_output_add_listener(output->output, &output_listener, output); + wl_list_insert(&ctx->output_list, &output->link); + } + + if (!strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name)) { + ctx->export_manager = wl_registry_bind(reg, id, + &zwlr_export_dmabuf_manager_v1_interface, 1); + } +} + +static void remove_output(struct wayland_output *out) { + wl_list_remove(&out->link); + av_free(out->make); + av_free(out->model); + av_free(out); +} + +static struct wayland_output *find_output(struct capture_context *ctx, + struct wl_output *out, uint32_t id) { + struct wayland_output *output, *tmp; + wl_list_for_each_safe(output, tmp, &ctx->output_list, link) { + if ((output->output == out) || (output->id == id)) { + return output; + } + } + return NULL; +} + +static void registry_handle_remove(void *data, struct wl_registry *reg, + uint32_t id) { + remove_output(find_output((struct capture_context *)data, NULL, id)); +} + +static const struct wl_registry_listener registry_listener = { + .global = registry_handle_add, + .global_remove = registry_handle_remove, +}; + +static void frame_free(void *opaque, uint8_t *data) { + AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)data; + + for (int i = 0; i < desc->nb_objects; ++i) { + close(desc->objects[i].fd); + } + + zwlr_export_dmabuf_frame_v1_destroy(opaque); + + av_free(data); +} + +static void frame_start(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, + uint32_t width, uint32_t height, uint32_t offset_x, uint32_t offset_y, + uint32_t buffer_flags, uint32_t flags, uint32_t format, + uint32_t mod_high, uint32_t mod_low, uint32_t num_objects) { + struct capture_context *ctx = data; + int err = 0; + + /* Allocate DRM specific struct */ + AVDRMFrameDescriptor *desc = av_mallocz(sizeof(*desc)); + if (!desc) { + err = AVERROR(ENOMEM); + goto fail; + } + + desc->nb_objects = num_objects; + desc->objects[0].format_modifier = ((uint64_t)mod_high << 32) | mod_low; + + desc->nb_layers = 1; + desc->layers[0].format = format; + + /* Allocate a frame */ + AVFrame *f = av_frame_alloc(); + if (!f) { + err = AVERROR(ENOMEM); + goto fail; + } + + /* Set base frame properties */ + ctx->current_frame = f; + f->width = width; + f->height = height; + f->format = AV_PIX_FMT_DRM_PRIME; + + /* Set the frame data to the DRM specific struct */ + f->buf[0] = av_buffer_create((uint8_t*)desc, sizeof(*desc), + &frame_free, frame, 0); + if (!f->buf[0]) { + err = AVERROR(ENOMEM); + goto fail; + } + + f->data[0] = (uint8_t*)desc; + + return; + +fail: + ctx->err = err; + frame_free(frame, (uint8_t *)desc); +} + +static void frame_object(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, + uint32_t index, int32_t fd, uint32_t size, uint32_t offset, + uint32_t stride, uint32_t plane_index) { + struct capture_context *ctx = data; + AVFrame *f = ctx->current_frame; + AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)f->data[0]; + + desc->objects[index].fd = fd; + desc->objects[index].size = size; + + desc->layers[0].planes[plane_index].object_index = index; + desc->layers[0].planes[plane_index].offset = offset; + desc->layers[0].planes[plane_index].pitch = stride; +} + +static enum AVPixelFormat drm_fmt_to_pixfmt(uint32_t fmt) { + switch (fmt) { + case DRM_FORMAT_NV12: return AV_PIX_FMT_NV12; + case DRM_FORMAT_ARGB8888: return AV_PIX_FMT_BGRA; + case DRM_FORMAT_XRGB8888: return AV_PIX_FMT_BGR0; + case DRM_FORMAT_ABGR8888: return AV_PIX_FMT_RGBA; + case DRM_FORMAT_XBGR8888: return AV_PIX_FMT_RGB0; + case DRM_FORMAT_RGBA8888: return AV_PIX_FMT_ABGR; + case DRM_FORMAT_RGBX8888: return AV_PIX_FMT_0BGR; + case DRM_FORMAT_BGRA8888: return AV_PIX_FMT_ARGB; + case DRM_FORMAT_BGRX8888: return AV_PIX_FMT_0RGB; + default: return AV_PIX_FMT_NONE; + }; +} + +static int attach_drm_frames_ref(struct capture_context *ctx, AVFrame *f, + enum AVPixelFormat sw_format) { + int err = 0; + AVHWFramesContext *hwfc; + + if (ctx->drm_frames_ref) { + hwfc = (AVHWFramesContext*)ctx->drm_frames_ref->data; + if (hwfc->width == f->width && hwfc->height == f->height && + hwfc->sw_format == sw_format) { + goto attach; + } + av_buffer_unref(&ctx->drm_frames_ref); + } + + ctx->drm_frames_ref = av_hwframe_ctx_alloc(ctx->drm_device_ref); + if (!ctx->drm_frames_ref) { + err = AVERROR(ENOMEM); + goto fail; + } + + hwfc = (AVHWFramesContext*)ctx->drm_frames_ref->data; + + hwfc->format = f->format; + hwfc->sw_format = sw_format; + hwfc->width = f->width; + hwfc->height = f->height; + + err = av_hwframe_ctx_init(ctx->drm_frames_ref); + if (err) { + av_log(ctx, AV_LOG_ERROR, "AVHWFramesContext init failed: %s!\n", + av_err2str(err)); + goto fail; + } + +attach: + /* Set frame hardware context referencce */ + f->hw_frames_ctx = av_buffer_ref(ctx->drm_frames_ref); + if (!f->hw_frames_ctx) { + err = AVERROR(ENOMEM); + goto fail; + } + + return 0; + +fail: + av_buffer_unref(&ctx->drm_frames_ref); + return err; +} + +static void register_cb(struct capture_context *ctx); + +static void frame_ready(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { + struct capture_context *ctx = data; + AVFrame *f = ctx->current_frame; + AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)f->data[0]; + enum AVPixelFormat pix_fmt = drm_fmt_to_pixfmt(desc->layers[0].format); + int err = 0; + + /* Timestamp, nanoseconds timebase */ + f->pts = ((((uint64_t)tv_sec_hi) << 32) | tv_sec_lo) * 1000000000 + tv_nsec; + + if (!ctx->start_pts) { + ctx->start_pts = f->pts; + } + + f->pts = av_rescale_q(f->pts - ctx->start_pts, (AVRational){ 1, 1000000000 }, + ctx->avctx->time_base); + + /* Attach the hardware frame context to the frame */ + err = attach_drm_frames_ref(ctx, f, pix_fmt); + if (err) { + goto end; + } + + /* TODO: support multiplane stuff */ + desc->layers[0].nb_planes = av_pix_fmt_count_planes(pix_fmt); + + AVFrame *mapped_frame = av_frame_alloc(); + if (!mapped_frame) { + err = AVERROR(ENOMEM); + goto end; + } + + AVHWFramesContext *mapped_hwfc; + mapped_hwfc = (AVHWFramesContext *)ctx->mapped_frames_ref->data; + mapped_frame->format = mapped_hwfc->format; + mapped_frame->pts = f->pts; + + /* Set frame hardware context referencce */ + mapped_frame->hw_frames_ctx = av_buffer_ref(ctx->mapped_frames_ref); + if (!mapped_frame->hw_frames_ctx) { + err = AVERROR(ENOMEM); + goto end; + } + + err = av_hwframe_map(mapped_frame, f, 0); + if (err) { + av_log(ctx, AV_LOG_ERROR, "Error mapping: %s!\n", av_err2str(err)); + goto end; + } + + if (push_to_fifo(&ctx->vid_frames, mapped_frame)) { + av_log(ctx, AV_LOG_WARNING, "Dropped frame!\n"); + } + + if (!ctx->quit && !ctx->err) { + register_cb(ctx); + } + +end: + ctx->err = err; + av_frame_free(&ctx->current_frame); +} + +static void frame_cancel(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, + uint32_t reason) { + struct capture_context *ctx = data; + av_log(ctx, AV_LOG_WARNING, "Frame cancelled!\n"); + av_frame_free(&ctx->current_frame); + if (reason == ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERMANENT) { + av_log(ctx, AV_LOG_ERROR, "Permanent failure, exiting\n"); + ctx->err = true; + } else { + register_cb(ctx); + } +} + +static const struct zwlr_export_dmabuf_frame_v1_listener frame_listener = { + .frame = frame_start, + .object = frame_object, + .ready = frame_ready, + .cancel = frame_cancel, +}; + +static void register_cb(struct capture_context *ctx) { + ctx->frame_callback = zwlr_export_dmabuf_manager_v1_capture_output( + ctx->export_manager, ctx->with_cursor, ctx->target_output); + + zwlr_export_dmabuf_frame_v1_add_listener(ctx->frame_callback, + &frame_listener, ctx); +} + +void *vid_encode_thread(void *arg) { + int err = 0; + struct capture_context *ctx = arg; + + do { + AVFrame *f = NULL; + if (get_fifo_size(&ctx->vid_frames) || !ctx->quit) { + f = pop_from_fifo(&ctx->vid_frames); + } + + if (ctx->is_software_encoder && f) { + AVFrame *soft_frame = av_frame_alloc(); + av_hwframe_transfer_data(soft_frame, f, 0); + soft_frame->pts = f->pts; + av_frame_free(&f); + f = soft_frame; + } + + err = avcodec_send_frame(ctx->avctx, f); + + av_frame_free(&f); + + if (err) { + av_log(ctx, AV_LOG_ERROR, "Error encoding: %s!\n", av_err2str(err)); + goto end; + } + + while (1) { + AVPacket pkt; + av_init_packet(&pkt); + + int ret = avcodec_receive_packet(ctx->avctx, &pkt); + if (ret == AVERROR(EAGAIN)) { + break; + } else if (ret == AVERROR_EOF) { + av_log(ctx, AV_LOG_INFO, "Encoder flushed!\n"); + goto end; + } else if (ret) { + av_log(ctx, AV_LOG_ERROR, "Error encoding: %s!\n", + av_err2str(ret)); + err = ret; + goto end; + } + + pkt.stream_index = 0; + err = av_interleaved_write_frame(ctx->avf, &pkt); + + av_packet_unref(&pkt); + + if (err) { + av_log(ctx, AV_LOG_ERROR, "Writing packet fail: %s!\n", + av_err2str(err)); + goto end; + } + }; + + av_log(ctx, AV_LOG_INFO, "Encoded frame %i (%i in queue)\n", + ctx->avctx->frame_number, get_fifo_size(&ctx->vid_frames)); + + } while (!ctx->err); + +end: + if (!ctx->err) { + ctx->err = err; + } + return NULL; +} + +static int init_lavu_hwcontext(struct capture_context *ctx) { + /* DRM hwcontext */ + ctx->drm_device_ref = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_DRM); + if (!ctx->drm_device_ref) + return AVERROR(ENOMEM); + + AVHWDeviceContext *ref_data = (AVHWDeviceContext*)ctx->drm_device_ref->data; + AVDRMDeviceContext *hwctx = ref_data->hwctx; + + /* We don't need a device (we don't even know it and can't open it) */ + hwctx->fd = -1; + + av_hwdevice_ctx_init(ctx->drm_device_ref); + + /* Mapped hwcontext */ + int err = av_hwdevice_ctx_create(&ctx->mapped_device_ref, + ctx->hw_device_type, ctx->hardware_device, NULL, 0); + if (err < 0) { + av_log(ctx, AV_LOG_ERROR, "Failed to create a hardware device: %s\n", + av_err2str(err)); + return err; + } + + return 0; +} + +static int set_hwframe_ctx(struct capture_context *ctx, + AVBufferRef *hw_device_ctx) { + AVHWFramesContext *frames_ctx = NULL; + int err = 0; + + if (!(ctx->mapped_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx))) { + return AVERROR(ENOMEM); + } + + AVHWFramesConstraints *cst = + av_hwdevice_get_hwframe_constraints(ctx->mapped_device_ref, NULL); + if (!cst) { + av_log(ctx, AV_LOG_ERROR, "Failed to get hw device constraints!\n"); + av_buffer_unref(&ctx->mapped_frames_ref); + return AVERROR(ENOMEM); + } + + frames_ctx = (AVHWFramesContext *)(ctx->mapped_frames_ref->data); + frames_ctx->format = cst->valid_hw_formats[0]; + frames_ctx->sw_format = ctx->avctx->pix_fmt; + frames_ctx->width = ctx->avctx->width; + frames_ctx->height = ctx->avctx->height; + + av_hwframe_constraints_free(&cst); + + if ((err = av_hwframe_ctx_init(ctx->mapped_frames_ref))) { + av_log(ctx, AV_LOG_ERROR, "Failed to initialize hw frame context: %s!\n", + av_err2str(err)); + av_buffer_unref(&ctx->mapped_frames_ref); + return err; + } + + if (!ctx->is_software_encoder) { + ctx->avctx->pix_fmt = frames_ctx->format; + ctx->avctx->hw_frames_ctx = av_buffer_ref(ctx->mapped_frames_ref); + if (!ctx->avctx->hw_frames_ctx) { + av_buffer_unref(&ctx->mapped_frames_ref); + err = AVERROR(ENOMEM); + } + } + + return err; +} + +static int init_encoding(struct capture_context *ctx) { + int err; + + /* lavf init */ + err = avformat_alloc_output_context2(&ctx->avf, NULL, + NULL, ctx->out_filename); + if (err) { + av_log(ctx, AV_LOG_ERROR, "Unable to init lavf context!\n"); + return err; + } + + AVStream *st = avformat_new_stream(ctx->avf, NULL); + if (!st) { + av_log(ctx, AV_LOG_ERROR, "Unable to alloc stream!\n"); + return 1; + } + + /* Find encoder */ + AVCodec *out_codec = avcodec_find_encoder_by_name(ctx->encoder_name); + if (!out_codec) { + av_log(ctx, AV_LOG_ERROR, "Codec not found (not compiled in lavc?)!\n"); + return AVERROR(EINVAL); + } + ctx->avf->oformat->video_codec = out_codec->id; + ctx->is_software_encoder = !(out_codec->capabilities & AV_CODEC_CAP_HARDWARE); + + ctx->avctx = avcodec_alloc_context3(out_codec); + if (!ctx->avctx) + return 1; + + ctx->avctx->opaque = ctx; + ctx->avctx->bit_rate = (int)ctx->out_bitrate*1000000.0f; + ctx->avctx->pix_fmt = ctx->software_format; + ctx->avctx->time_base = (AVRational){ 1, 1000 }; + ctx->avctx->compression_level = 7; + ctx->avctx->width = find_output(ctx, ctx->target_output, 0)->width; + ctx->avctx->height = find_output(ctx, ctx->target_output, 0)->height; + + if (ctx->avf->oformat->flags & AVFMT_GLOBALHEADER) { + ctx->avctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } + + st->id = 0; + st->time_base = ctx->avctx->time_base; + st->avg_frame_rate = find_output(ctx, ctx->target_output, 0)->framerate; + + /* Init hw frames context */ + err = set_hwframe_ctx(ctx, ctx->mapped_device_ref); + if (err) { + return err; + } + + err = avcodec_open2(ctx->avctx, out_codec, &ctx->encoder_opts); + if (err) { + av_log(ctx, AV_LOG_ERROR, "Cannot open encoder: %s!\n", + av_err2str(err)); + return err; + } + + if (avcodec_parameters_from_context(st->codecpar, ctx->avctx) < 0) { + av_log(ctx, AV_LOG_ERROR, "Couldn't copy codec params: %s!\n", + av_err2str(err)); + return err; + } + + /* Debug print */ + av_dump_format(ctx->avf, 0, ctx->out_filename, 1); + + /* Open for writing */ + err = avio_open(&ctx->avf->pb, ctx->out_filename, AVIO_FLAG_WRITE); + if (err) { + av_log(ctx, AV_LOG_ERROR, "Couldn't open %s: %s!\n", ctx->out_filename, + av_err2str(err)); + return err; + } + + err = avformat_write_header(ctx->avf, NULL); + if (err) { + av_log(ctx, AV_LOG_ERROR, "Couldn't write header: %s!\n", av_err2str(err)); + return err; + } + + return err; +} + +struct capture_context *q_ctx = NULL; + +void on_quit_signal(int signo) { + printf("\r"); + av_log(q_ctx, AV_LOG_WARNING, "Quitting!\n"); + q_ctx->quit = true; +} + +static int main_loop(struct capture_context *ctx) { + int err; + + q_ctx = ctx; + + if (signal(SIGINT, on_quit_signal) == SIG_ERR) { + av_log(ctx, AV_LOG_ERROR, "Unable to install signal handler!\n"); + return AVERROR(EINVAL); + } + + err = init_lavu_hwcontext(ctx); + if (err) { + return err; + } + + err = init_encoding(ctx); + if (err) { + return err; + } + + /* Start video encoding thread */ + err = init_fifo(&ctx->vid_frames, 16); + if (err) { + return err; + } + pthread_create(&ctx->vid_thread, NULL, vid_encode_thread, ctx); + + /* Start the frame callback */ + register_cb(ctx); + + /* Run capture */ + while (wl_display_dispatch(ctx->display) != -1 && !ctx->err && !ctx->quit); + + /* Join with encoder thread */ + pthread_join(ctx->vid_thread, NULL); + + err = av_write_trailer(ctx->avf); + if (err) { + av_log(ctx, AV_LOG_ERROR, "Error writing trailer: %s!\n", + av_err2str(err)); + return err; + } + + av_log(ctx, AV_LOG_INFO, "Wrote trailer!\n"); + + return ctx->err; +} + +static int init(struct capture_context *ctx) { + ctx->display = wl_display_connect(NULL); + if (!ctx->display) { + av_log(ctx, AV_LOG_ERROR, "Failed to connect to display!\n"); + return AVERROR(EINVAL); + } + + wl_list_init(&ctx->output_list); + + ctx->registry = wl_display_get_registry(ctx->display); + wl_registry_add_listener(ctx->registry, ®istry_listener, ctx); + + wl_display_roundtrip(ctx->display); + wl_display_dispatch(ctx->display); + + if (!ctx->export_manager) { + av_log(ctx, AV_LOG_ERROR, "Compositor doesn't support %s!\n", + zwlr_export_dmabuf_manager_v1_interface.name); + return -1; + } + + return 0; +} + +static void uninit(struct capture_context *ctx); + +int main(int argc, char *argv[]) { + int err; + struct capture_context ctx = { 0 }; + ctx.class = &((AVClass) { + .class_name = "dmabuf-capture", + .item_name = av_default_item_name, + .version = LIBAVUTIL_VERSION_INT, + }); + + err = init(&ctx); + if (err) { + goto end; + } + + struct wayland_output *o, *tmp_o; + wl_list_for_each_reverse_safe(o, tmp_o, &ctx.output_list, link) { + printf("Capturable output: %s Model: %s: ID: %i\n", + o->make, o->model, o->id); + } + + if (argc != 8) { + printf("Invalid number of arguments! Usage and example:\n" + "./dmabuf-capture <source id> <hardware device type> <device> " + "<encoder name> <pixel format> <bitrate in Mbps> <file path>\n" + "./dmabuf-capture 0 vaapi /dev/dri/renderD129 libx264 nv12 12 " + "dmabuf_recording_01.mkv\n"); + return 1; + } + + const int o_id = strtol(argv[1], NULL, 10); + o = find_output(&ctx, NULL, o_id); + if (!o) { + printf("Unable to find output with ID %i!\n", o_id); + return 1; + } + + ctx.target_output = o->output; + ctx.with_cursor = true; + ctx.hw_device_type = av_hwdevice_find_type_by_name(argv[2]); + ctx.hardware_device = argv[3]; + + ctx.encoder_name = argv[4]; + ctx.software_format = av_get_pix_fmt(argv[5]); + ctx.out_bitrate = strtof(argv[6], NULL); + ctx.out_filename = argv[7]; + + av_dict_set(&ctx.encoder_opts, "preset", "veryfast", 0); + + err = main_loop(&ctx); + if (err) { + goto end; + } + +end: + uninit(&ctx); + return err; +} + +static void uninit(struct capture_context *ctx) { + struct wayland_output *output, *tmp_o; + wl_list_for_each_safe(output, tmp_o, &ctx->output_list, link) { + remove_output(output); + } + + if (ctx->export_manager) { + zwlr_export_dmabuf_manager_v1_destroy(ctx->export_manager); + } + + free_fifo(&ctx->vid_frames); + + av_buffer_unref(&ctx->drm_frames_ref); + av_buffer_unref(&ctx->drm_device_ref); + av_buffer_unref(&ctx->mapped_frames_ref); + av_buffer_unref(&ctx->mapped_device_ref); + + av_dict_free(&ctx->encoder_opts); + + avcodec_close(ctx->avctx); + if (ctx->avf) { + avio_closep(&ctx->avf->pb); + } + avformat_free_context(ctx->avf); +} diff --git a/examples/foreign-toplevel.c b/examples/foreign-toplevel.c new file mode 100644 index 00000000..40ddbb09 --- /dev/null +++ b/examples/foreign-toplevel.c @@ -0,0 +1,358 @@ +#define _POSIX_C_SOURCE 200809L +#include <getopt.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <wayland-client.h> +#include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" + +/** + * Usage: + * 1. foreign-toplevel + * Prints a list of opened toplevels + * 2. foreign-toplevel -f <id> + * Focus the toplevel with the given id + * 3. foreign-toplevel -a <id> + * Maximize the toplevel with the given id + * 4. foreign-toplevel -u <id> + * Unmaximize the toplevel with the given id + * 5. foreign-toplevel -i <id> + * Minimize the toplevel with the given id + * 6. foreign-toplevel -r <id> + * Restore(unminimize) the toplevel with the given id + * 7. foreign-toplevel -c <id> + * Close the toplevel with the given id + * 8. foreign-toplevel -m + * Continuously print changes to the list of opened toplevels. + * Can be used together with some of the previous options. + */ + +enum toplevel_state_field { + TOPLEVEL_STATE_MAXIMIZED = 1, + TOPLEVEL_STATE_MINIMIZED = 2, + TOPLEVEL_STATE_ACTIVATED = 4, + TOPLEVEL_STATE_INVALID = 8, +}; + +struct toplevel_state { + char *title; + char *app_id; + + uint32_t state; +}; + +static void copy_state(struct toplevel_state *current, + struct toplevel_state *pending) { + if (current->title && pending->title) { + free(current->title); + } + if (current->app_id && pending->app_id) { + free(current->app_id); + } + + if (pending->title) { + current->title = pending->title; + pending->title = NULL; + } + if (pending->app_id) { + current->app_id = pending->app_id; + pending->app_id = NULL; + } + + if (!(pending->state & TOPLEVEL_STATE_INVALID)) { + current->state = pending->state; + } + + pending->state = TOPLEVEL_STATE_INVALID; +} + +static uint32_t global_id = 0; +struct toplevel_v1 { + struct wl_list link; + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel; + + uint32_t id; + struct toplevel_state current, pending; +}; + +static void print_toplevel(struct toplevel_v1 *toplevel, bool print_endl) { + printf("-> %d. title=%s app_id=%s", toplevel->id, + toplevel->current.title ?: "(nil)", + toplevel->current.app_id ?: "(nil)"); + + if (print_endl) { + printf("\n"); + } +} + +static void print_toplevel_state(struct toplevel_v1 *toplevel, bool print_endl) { + if (toplevel->current.state & TOPLEVEL_STATE_MAXIMIZED) { + printf(" maximized"); + } else { + printf(" unmaximized"); + } + if (toplevel->current.state & TOPLEVEL_STATE_MINIMIZED) { + printf(" minimized"); + } else { + printf(" unminimized"); + } + if (toplevel->current.state & TOPLEVEL_STATE_ACTIVATED) { + printf(" active"); + } else { + printf(" inactive"); + } + + if (print_endl) { + printf("\n"); + } +} + +static void toplevel_handle_title(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel, + const char *title) { + struct toplevel_v1 *toplevel = data; + free(toplevel->pending.title); + toplevel->pending.title = strdup(title); +} + +static void toplevel_handle_app_id(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel, + const char *app_id) { + struct toplevel_v1 *toplevel = data; + free(toplevel->pending.app_id); + toplevel->pending.app_id = strdup(app_id); +} + +static void toplevel_handle_output_enter(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel, + struct wl_output *output) { + struct toplevel_v1 *toplevel = data; + print_toplevel(toplevel, false); + printf(" enter output %u\n", + (uint32_t)(size_t)wl_output_get_user_data(output)); +} + +static void toplevel_handle_output_leave(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel, + struct wl_output *output) { + struct toplevel_v1 *toplevel = data; + print_toplevel(toplevel, false); + printf(" leave output %u\n", + (uint32_t)(size_t)wl_output_get_user_data(output)); +} + +static uint32_t array_to_state(struct wl_array *array) { + uint32_t state = 0; + uint32_t *entry; + wl_array_for_each(entry, array) { + if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED) + state |= TOPLEVEL_STATE_MAXIMIZED; + if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED) + state |= TOPLEVEL_STATE_MINIMIZED; + if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) + state |= TOPLEVEL_STATE_ACTIVATED; + } + + return state; +} + +static void toplevel_handle_state(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel, + struct wl_array *state) { + struct toplevel_v1 *toplevel = data; + toplevel->pending.state = array_to_state(state); +} + +static void toplevel_handle_done(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel) { + struct toplevel_v1 *toplevel = data; + bool state_changed = toplevel->current.state != toplevel->pending.state; + + copy_state(&toplevel->current, &toplevel->pending); + + print_toplevel(toplevel, !state_changed); + if (state_changed) { + print_toplevel_state(toplevel, true); + } +} + +static void toplevel_handle_closed(void *data, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel) { + struct toplevel_v1 *toplevel = data; + print_toplevel(toplevel, false); + printf(" closed\n"); + + zwlr_foreign_toplevel_handle_v1_destroy(zwlr_toplevel); +} + +static const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_impl = { + .title = toplevel_handle_title, + .app_id = toplevel_handle_app_id, + .output_enter = toplevel_handle_output_enter, + .output_leave = toplevel_handle_output_leave, + .state = toplevel_handle_state, + .done = toplevel_handle_done, + .closed = toplevel_handle_closed, +}; + +static struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager = NULL; +static struct wl_list toplevel_list; + +static void toplevel_manager_handle_toplevel(void *data, + struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager, + struct zwlr_foreign_toplevel_handle_v1 *zwlr_toplevel) { + struct toplevel_v1 *toplevel = calloc(1, sizeof(struct toplevel_v1)); + if (!toplevel) { + fprintf(stderr, "Failed to allocate memory for toplevel\n"); + return; + } + + toplevel->id = global_id++; + toplevel->zwlr_toplevel = zwlr_toplevel; + wl_list_insert(&toplevel_list, &toplevel->link); + + zwlr_foreign_toplevel_handle_v1_add_listener(zwlr_toplevel, &toplevel_impl, + toplevel); +} + +static void toplevel_manager_handle_finished(void *data, + struct zwlr_foreign_toplevel_manager_v1 *toplevel_manager) { + zwlr_foreign_toplevel_manager_v1_destroy(toplevel_manager); +} + +static const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_impl = { + .toplevel = toplevel_manager_handle_toplevel, + .finished = toplevel_manager_handle_finished, +}; + +struct wl_seat *seat = NULL; +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + if (strcmp(interface, wl_output_interface.name) == 0) { + struct wl_output *output = wl_registry_bind(registry, name, + &wl_output_interface, version); + wl_output_set_user_data(output, (void*)(size_t)name); // assign some ID to the output + } else if (strcmp(interface, + zwlr_foreign_toplevel_manager_v1_interface.name) == 0) { + toplevel_manager = wl_registry_bind(registry, name, + &zwlr_foreign_toplevel_manager_v1_interface, 1); + + wl_list_init(&toplevel_list); + zwlr_foreign_toplevel_manager_v1_add_listener(toplevel_manager, + &toplevel_manager_impl, NULL); + } else if (strcmp(interface, wl_seat_interface.name) == 0 && seat == NULL) { + seat = wl_registry_bind(registry, name, &wl_seat_interface, version); + } +} + +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, +}; + +/* return NULL when id == -1 + * exit if the given ID cannot be found in the list of toplevels */ +static struct toplevel_v1 *toplevel_by_id_or_bail(int32_t id) { + if (id == -1) { + return NULL; + } + + struct toplevel_v1 *toplevel; + wl_list_for_each(toplevel, &toplevel_list, link) { + if (toplevel->id == (uint32_t)id) { + return toplevel; + } + } + + fprintf(stderr, "No toplevel with the given id: %d\n", id); + exit(EXIT_FAILURE); +} + +int main(int argc, char **argv) { + int focus_id = -1, close_id = -1; + int maximize_id = -1, unmaximize_id = -1; + int minimize_id = -1, restore_id = -1; + int one_shot = 1; + int c; + + // TODO maybe print usage with -h? + while ((c = getopt(argc, argv, "f:a:u:i:r:c:m")) != -1) { + switch (c) { + case 'f': + focus_id = atoi(optarg); + break; + case 'a': + maximize_id = atoi(optarg); + break; + case 'u': + unmaximize_id = atoi(optarg); + break; + case 'i': + minimize_id = atoi(optarg); + break; + case 'r': + restore_id = atoi(optarg); + break; + case 'c': + close_id = atoi(optarg); + break; + case 'm': + one_shot = 0; + break; + } + } + + struct wl_display *display = wl_display_connect(NULL); + if (display == NULL) { + fprintf(stderr, "Failed to create display\n"); + return EXIT_FAILURE; + } + + struct wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_display_dispatch(display); + wl_display_roundtrip(display); + + if (toplevel_manager == NULL) { + fprintf(stderr, "wlr-foreign-toplevel not available\n"); + return EXIT_FAILURE; + } + wl_display_roundtrip(display); // load list of toplevels + wl_display_roundtrip(display); // load toplevel details + + struct toplevel_v1 *toplevel; + if ((toplevel = toplevel_by_id_or_bail(focus_id))) { + zwlr_foreign_toplevel_handle_v1_activate(toplevel->zwlr_toplevel, seat); + } + if ((toplevel = toplevel_by_id_or_bail(maximize_id))) { + zwlr_foreign_toplevel_handle_v1_set_maximized(toplevel->zwlr_toplevel); + } + if ((toplevel = toplevel_by_id_or_bail(unmaximize_id))) { + zwlr_foreign_toplevel_handle_v1_unset_maximized(toplevel->zwlr_toplevel); + } + if ((toplevel = toplevel_by_id_or_bail(minimize_id))) { + zwlr_foreign_toplevel_handle_v1_set_minimized(toplevel->zwlr_toplevel); + } + if ((toplevel = toplevel_by_id_or_bail(restore_id))) { + zwlr_foreign_toplevel_handle_v1_unset_minimized(toplevel->zwlr_toplevel); + } + if ((toplevel = toplevel_by_id_or_bail(close_id))) { + zwlr_foreign_toplevel_handle_v1_close(toplevel->zwlr_toplevel); + } + + wl_display_flush(display); + + if (one_shot == 0) { + while (wl_display_dispatch(display) != -1) { + // This space intentionally left blank + } + } + + return EXIT_SUCCESS; +} diff --git a/examples/gamma-control.c b/examples/gamma-control.c new file mode 100644 index 00000000..a060b883 --- /dev/null +++ b/examples/gamma-control.c @@ -0,0 +1,195 @@ +#define _POSIX_C_SOURCE 200809L +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <unistd.h> +#include <wayland-client-protocol.h> +#include <wayland-client.h> +#include "wlr-gamma-control-unstable-v1-client-protocol.h" + +struct output { + struct wl_output *wl_output; + struct zwlr_gamma_control_v1 *gamma_control; + uint32_t ramp_size; + int table_fd; + uint16_t *table; + struct wl_list link; +}; + +static struct wl_list outputs; +static struct zwlr_gamma_control_manager_v1 *gamma_control_manager = NULL; + +static int create_anonymous_file(off_t size) { + char template[] = "/tmp/wlroots-shared-XXXXXX"; + int fd = mkstemp(template); + if (fd < 0) { + return -1; + } + + int ret; + do { + errno = 0; + ret = ftruncate(fd, size); + } while (errno == EINTR); + if (ret < 0) { + close(fd); + return -1; + } + + unlink(template); + return fd; +} + +static int create_gamma_table(uint32_t ramp_size, uint16_t **table) { + size_t table_size = ramp_size * 3 * sizeof(uint16_t); + int fd = create_anonymous_file(table_size); + if (fd < 0) { + fprintf(stderr, "failed to create anonymous file\n"); + return -1; + } + + void *data = + mmap(NULL, table_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + fprintf(stderr, "failed to mmap()\n"); + close(fd); + return -1; + } + + *table = data; + return fd; +} + +static void gamma_control_handle_gamma_size(void *data, + struct zwlr_gamma_control_v1 *gamma_control, uint32_t ramp_size) { + struct output *output = data; + output->ramp_size = ramp_size; + output->table_fd = create_gamma_table(ramp_size, &output->table); + if (output->table_fd < 0) { + exit(EXIT_FAILURE); + } +} + +static void gamma_control_handle_failed(void *data, + struct zwlr_gamma_control_v1 *gamma_control) { + fprintf(stderr, "failed to set gamma table\n"); + exit(EXIT_FAILURE); +} + +static const struct zwlr_gamma_control_v1_listener gamma_control_listener = { + .gamma_size = gamma_control_handle_gamma_size, + .failed = gamma_control_handle_failed, +}; + +static void registry_handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + if (strcmp(interface, wl_output_interface.name) == 0) { + struct output *output = calloc(1, sizeof(struct output)); + output->wl_output = wl_registry_bind(registry, name, + &wl_output_interface, 1); + wl_list_insert(&outputs, &output->link); + } else if (strcmp(interface, + zwlr_gamma_control_manager_v1_interface.name) == 0) { + gamma_control_manager = wl_registry_bind(registry, name, + &zwlr_gamma_control_manager_v1_interface, 1); + } +} + +static void registry_handle_global_remove(void *data, + struct wl_registry *registry, uint32_t name) { + // Who cares? +} + +static const struct wl_registry_listener registry_listener = { + .global = registry_handle_global, + .global_remove = registry_handle_global_remove, +}; + +static void fill_gamma_table(uint16_t *table, uint32_t ramp_size, + double contrast, double brightness, double gamma) { + uint16_t *r = table; + uint16_t *g = table + ramp_size; + uint16_t *b = table + 2 * ramp_size; + for (uint32_t i = 0; i < ramp_size; ++i) { + double val = (double)i / (ramp_size - 1); + val = contrast * pow(val, 1.0 / gamma) + (brightness - 1); + if (val > 1.0) { + val = 1.0; + } else if (val < 0.0) { + val = 0.0; + } + r[i] = g[i] = b[i] = (uint16_t)(UINT16_MAX * val); + } +} + +static const char usage[] = "usage: gamma-control [options]\n" + " -h show this help message\n" + " -c <value> set contrast (default: 1)\n" + " -b <value> set brightness (default: 1)\n" + " -g <value> set gamma (default: 1)\n"; + +int main(int argc, char *argv[]) { + wl_list_init(&outputs); + + double contrast = 1, brightness = 1, gamma = 1; + int opt; + while ((opt = getopt(argc, argv, "hc:b:g:")) != -1) { + switch (opt) { + case 'c': + contrast = strtod(optarg, NULL); + break; + case 'b': + brightness = strtod(optarg, NULL); + break; + case 'g': + gamma = strtod(optarg, NULL); + break; + case 'h': + default: + fprintf(stderr, usage); + return opt == 'h' ? EXIT_SUCCESS : EXIT_FAILURE; + } + } + + struct wl_display *display = wl_display_connect(NULL); + if (display == NULL) { + fprintf(stderr, "failed to create display\n"); + return -1; + } + + struct wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_display_dispatch(display); + wl_display_roundtrip(display); + + if (gamma_control_manager == NULL) { + fprintf(stderr, + "compositor doesn't support wlr-gamma-control-unstable-v1\n"); + return EXIT_FAILURE; + } + + struct output *output; + wl_list_for_each(output, &outputs, link) { + output->gamma_control = zwlr_gamma_control_manager_v1_get_gamma_control( + gamma_control_manager, output->wl_output); + zwlr_gamma_control_v1_add_listener(output->gamma_control, + &gamma_control_listener, output); + } + wl_display_roundtrip(display); + + wl_list_for_each(output, &outputs, link) { + fill_gamma_table(output->table, output->ramp_size, + contrast, brightness, gamma); + zwlr_gamma_control_v1_set_gamma(output->gamma_control, + output->table_fd); + } + + while (wl_display_dispatch(display) != -1) { + // This space is intentionnally left blank + } + + return EXIT_SUCCESS; +} diff --git a/examples/idle-inhibit.c b/examples/idle-inhibit.c new file mode 100644 index 00000000..48c812e8 --- /dev/null +++ b/examples/idle-inhibit.c @@ -0,0 +1,233 @@ +#include <GLES2/gl2.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <wayland-client.h> +#include <wayland-egl.h> +#include <wlr/render/egl.h> +#include "idle-inhibit-unstable-v1-client-protocol.h" +#include "xdg-shell-client-protocol.h" + +#ifdef __linux__ +#include <linux/input-event-codes.h> +#elif __FreeBSD__ +#include <dev/evdev/input-event-codes.h> +#endif + +/** + * Usage: idle-inhibit + * Creates a xdg-toplevel using the idle-inhibit protocol. + * It will be solid green, when it has an idle inhibitor, and solid yellow if + * it does not. + * Left click with a pointer will toggle this state. (Touch is not supported + * for now). + */ + +static int width = 500, height = 300; + +static struct wl_compositor *compositor = NULL; +static struct wl_seat *seat = NULL; +static struct xdg_wm_base *wm_base = NULL; +static struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = NULL; +static struct zwp_idle_inhibitor_v1 *idle_inhibitor = NULL; + +struct wlr_egl egl; +struct wl_egl_window *egl_window; +struct wlr_egl_surface *egl_surface; + +static void draw(void) { + eglMakeCurrent(egl.display, egl_surface, egl_surface, egl.context); + + float color[] = {1.0, 1.0, 0.0, 1.0}; + if (idle_inhibitor) { + color[0] = 0.0; + } + + glViewport(0, 0, width, height); + glClearColor(color[0], color[1], color[2], 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + eglSwapBuffers(egl.display, egl_surface); +} + +static void pointer_handle_button(void *data, struct wl_pointer *pointer, uint32_t serial, + uint32_t time, uint32_t button, uint32_t state_w) { + struct wl_surface *surface = data; + + if (button == BTN_LEFT && state_w == WL_POINTER_BUTTON_STATE_PRESSED) { + if (idle_inhibitor) { + zwp_idle_inhibitor_v1_destroy(idle_inhibitor); + idle_inhibitor = NULL; + } else { + idle_inhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor( + idle_inhibit_manager, surface); + } + } + + draw(); +} + +static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t surface_x, wl_fixed_t surface_y) { + // This space intentionally left blank +} + +static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface) { + // This space intentionally left blank +} + +static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, + uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { + // This space intentionally left blank +} + +static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) { + // This space intentionally left blank +} + +static void pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) { + // This space intentionally left blank +} + +static void pointer_handle_axis_source(void *data, + struct wl_pointer *wl_pointer, uint32_t axis_source) { + // This space intentionally left blank +} + +static void pointer_handle_axis_stop(void *data, + struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis) { + // This space intentionally left blank +} + +static void pointer_handle_axis_discrete(void *data, + struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) { + // This space intentionally left blank +} + +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, +}; + +static void xdg_surface_handle_configure(void *data, + struct xdg_surface *xdg_surface, uint32_t serial) { + xdg_surface_ack_configure(xdg_surface, serial); + wl_egl_window_resize(egl_window, width, height, 0, 0); + draw(); +} + +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) { + width = w; + height = h; +} + +static void xdg_toplevel_handle_close(void *data, + struct xdg_toplevel *xdg_toplevel) { + exit(EXIT_SUCCESS); +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_handle_configure, + .close = xdg_toplevel_handle_close, +}; + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + if (strcmp(interface, "wl_compositor") == 0) { + compositor = wl_registry_bind(registry, name, + &wl_compositor_interface, 1); + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); + } else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) { + idle_inhibit_manager = wl_registry_bind(registry, name, + &zwp_idle_inhibit_manager_v1_interface, 1); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + seat = wl_registry_bind(registry, name, &wl_seat_interface, version); + } +} + +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, +}; + +int main(int argc, char **argv) { + struct wl_display *display = wl_display_connect(NULL); + if (display == NULL) { + fprintf(stderr, "Failed to create display\n"); + return EXIT_FAILURE; + } + + struct wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_display_dispatch(display); + wl_display_roundtrip(display); + + if (compositor == NULL) { + fprintf(stderr, "wl-compositor not available\n"); + return EXIT_FAILURE; + } + if (wm_base == NULL) { + fprintf(stderr, "xdg-shell not available\n"); + return EXIT_FAILURE; + } + if (idle_inhibit_manager == NULL) { + fprintf(stderr, "idle-inhibit not available\n"); + return EXIT_FAILURE; + } + + wlr_egl_init(&egl, EGL_PLATFORM_WAYLAND_EXT, display, NULL, + WL_SHM_FORMAT_ARGB8888); + + struct wl_surface *surface = wl_compositor_create_surface(compositor); + struct xdg_surface *xdg_surface = + xdg_wm_base_get_xdg_surface(wm_base, surface); + struct xdg_toplevel *xdg_toplevel = xdg_surface_get_toplevel(xdg_surface); + + idle_inhibitor = + zwp_idle_inhibit_manager_v1_create_inhibitor(idle_inhibit_manager, + surface); + + struct wl_pointer *pointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(pointer, &pointer_listener, surface); + + + xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL); + xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL); + + wl_surface_commit(surface); + + egl_window = wl_egl_window_create(surface, width, height); + egl_surface = wlr_egl_create_surface(&egl, egl_window); + + wl_display_roundtrip(display); + + draw(); + + while (wl_display_dispatch(display) != -1) { + // This space intentionally left blank + } + + return EXIT_SUCCESS; +} diff --git a/examples/idle.c b/examples/idle.c new file mode 100644 index 00000000..a6a4f5c0 --- /dev/null +++ b/examples/idle.c @@ -0,0 +1,197 @@ +#include <getopt.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wayland-client-protocol.h> +#include <wayland-client.h> +#include "idle-client-protocol.h" + +static struct org_kde_kwin_idle *idle_manager = NULL; +static struct wl_seat *seat = NULL; +static uint32_t timeout = 0, simulate_activity_timeout = 0, close_timeout = 0; +static int run = 1; + +struct thread_args { + struct wl_display *display; + struct org_kde_kwin_idle_timeout *timer; +}; + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + fprintf(stdout, "interfaces found: %s\n", interface); + if (strcmp(interface, "org_kde_kwin_idle") == 0) { + idle_manager = wl_registry_bind(registry, name, &org_kde_kwin_idle_interface, 1); + } + else if (strcmp(interface, "wl_seat") == 0) { + seat = wl_registry_bind(registry, name, &wl_seat_interface, 1); + } +} + +static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { + //TODO +} + +static const struct wl_registry_listener registry_listener = { + .global = handle_global, + .global_remove = handle_global_remove, +}; + +static void handle_idle(void* data, struct org_kde_kwin_idle_timeout *timer) { + fprintf(stdout, "idle state\n"); +} + +static void handle_resume(void* data, struct org_kde_kwin_idle_timeout *timer) { + fprintf(stdout, "active state\n"); +} + +static const struct org_kde_kwin_idle_timeout_listener idle_timer_listener = { + .idle = handle_idle, + .resumed = handle_resume, +}; + +int parse_args(int argc, char *argv[]) { + int c; + while ((c = getopt(argc, argv, "c:hs:t:")) != -1) { + switch(c) + { + case 'c': + close_timeout = strtoul(optarg, NULL, 10); + break; + case 's': + simulate_activity_timeout = strtoul(optarg, NULL, 10); + break; + case 't': + timeout = strtoul(optarg, NULL, 10); + break; + case 'h': + case '?': + printf("Usage: %s [OPTIONS]\n", argv[0]); + printf(" -t seconds\t\t\tidle timeout in seconds\n"); + printf(" -s seconds optional\t\tsimulate user activity after x seconds\n"); + printf(" -c seconds optional\t\tclose program after x seconds\n"); + printf(" -h\t\t\t\tthis help menu\n"); + return 1; + default: + return 1; + } + } + return 0; +} + +void *simulate_activity(void *data) { + sleep(simulate_activity_timeout); + fprintf(stdout, "simulate user activity\n"); + struct thread_args *arg = data; + org_kde_kwin_idle_timeout_simulate_user_activity(arg->timer); + wl_display_roundtrip(arg->display); + return NULL; +} + +void *close_program(void *data) { + sleep(close_timeout); + struct thread_args *arg = data; + org_kde_kwin_idle_timeout_release(arg->timer); + wl_display_roundtrip(arg->display); + fprintf(stdout, "close program\n"); + run = 0; + return NULL; +} + +void *main_loop(void *data) { + struct wl_display *display = data; + while (wl_display_dispatch(display) != -1) { + ; + } + return NULL; +} + +int main(int argc, char *argv[]) { + if (parse_args(argc, argv) != 0) { + return -1; + } + if (timeout == 0) { + printf("idle timeout 0 is invalid\n"); + return -1; + } + + struct wl_display *display = wl_display_connect(NULL); + if (display == NULL) { + fprintf(stderr, "failed to create display\n"); + return -1; + } + + struct wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_display_dispatch(display); + wl_display_roundtrip(display); + free(registry); + + if (idle_manager == NULL) { + fprintf(stderr, "display doesn't support idle protocol\n"); + return -1; + } + if (seat== NULL) { + fprintf(stderr, "seat error\n"); + return -1; + } + struct org_kde_kwin_idle_timeout *timer = + org_kde_kwin_idle_get_idle_timeout(idle_manager, seat, timeout * 1000); + + if (timer == NULL) { + fprintf(stderr, "Could not create idle_timeout\n"); + return -1; + } + + pthread_t t1, t2, t3; + struct thread_args arg = { + .timer = timer, + .display = display, + }; + + bool create_t1 = (simulate_activity_timeout != 0) && + (simulate_activity_timeout < close_timeout); + + if (create_t1) { + if (pthread_create(&t1, NULL, &simulate_activity, (void *)&arg) != 0) { + return -1; + } + } + + bool create_t2 = (close_timeout != 0); + + if (create_t2) { + if (pthread_create(&t2, NULL, &close_program, (void *)&arg) != 0) { + if (create_t1) { + pthread_cancel(t1); + } + return -1; + } + } + + org_kde_kwin_idle_timeout_add_listener(timer, &idle_timer_listener, timer); + fprintf(stdout, "waiting\n"); + + if (pthread_create(&t3, NULL, &main_loop, (void *)display) != 0) { + if (create_t1) { + pthread_cancel(t1); + } + if (create_t2) { + pthread_cancel(t2); + } + return -1; + } + + if (create_t1) { + pthread_join(t1, NULL); + } + if (create_t2) { + pthread_join(t2, NULL); + } + pthread_cancel(t3); + + wl_display_disconnect(display); + return 0; +} diff --git a/examples/input-inhibitor.c b/examples/input-inhibitor.c new file mode 100644 index 00000000..25e46c73 --- /dev/null +++ b/examples/input-inhibitor.c @@ -0,0 +1,189 @@ +#include <GLES2/gl2.h> +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <wayland-client.h> +#include <wayland-egl.h> +#include <wlr/render/egl.h> +#include "wlr-input-inhibitor-unstable-v1-client-protocol.h" +#include "xdg-shell-client-protocol.h" + +static int width = 500, height = 300; +static int keys = 0; + +static struct wl_compositor *compositor = NULL; +static struct wl_seat *seat = NULL; +static struct xdg_wm_base *wm_base = NULL; +static struct zwlr_input_inhibit_manager_v1 *input_inhibit_manager = NULL; +static struct zwlr_input_inhibitor_v1 *input_inhibitor = NULL; + +struct wlr_egl egl; +struct wl_egl_window *egl_window; +struct wlr_egl_surface *egl_surface; + +static void render_frame(void) { + eglMakeCurrent(egl.display, egl_surface, egl_surface, egl.context); + + glViewport(0, 0, width, height); + if (keys) { + glClearColor(1.0, 1.0, 1.0, 1.0); + } else { + glClearColor(0.8, 0.4, 1.0, 1.0); + } + glClear(GL_COLOR_BUFFER_BIT); + + eglSwapBuffers(egl.display, egl_surface); +} + +static void xdg_surface_handle_configure(void *data, + struct xdg_surface *xdg_surface, uint32_t serial) { + xdg_surface_ack_configure(xdg_surface, serial); + wl_egl_window_resize(egl_window, width, height, 0, 0); + render_frame(); +} + +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) { + width = w; + height = h; +} + +static void xdg_toplevel_handle_close(void *data, + struct xdg_toplevel *xdg_toplevel) { + exit(0); +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_handle_configure, + .close = xdg_toplevel_handle_close, +}; + +static void wl_keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int32_t fd, uint32_t size) { +} +static void wl_keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { +} +static void wl_keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, struct wl_surface *surface) { +} +static void wl_keyboard_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) { +} +static void wl_keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, + int32_t rate, int32_t delay) { +} + +static void wl_keyboard_key(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + ++keys; + } else { + --keys; + } + render_frame(); +} + +static struct wl_keyboard_listener keyboard_listener = { + .keymap = wl_keyboard_keymap, + .enter = wl_keyboard_enter, + .leave = wl_keyboard_leave, + .key = wl_keyboard_key, + .modifiers = wl_keyboard_modifiers, + .repeat_info = wl_keyboard_repeat_info, +}; + +static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, + enum wl_seat_capability caps) { + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD)) { + struct wl_keyboard *keyboard = wl_seat_get_keyboard(wl_seat); + wl_keyboard_add_listener(keyboard, &keyboard_listener, NULL); + } +} + +static void seat_handle_name(void *data, struct wl_seat *wl_seat, + const char *name) { + // Who cares +} + +const struct wl_seat_listener seat_listener = { + .capabilities = seat_handle_capabilities, + .name = seat_handle_name, +}; + +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, 1); + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); + } else if (strcmp(interface, zwlr_input_inhibit_manager_v1_interface.name) == 0) { + input_inhibit_manager = wl_registry_bind(registry, name, + &zwlr_input_inhibit_manager_v1_interface, 1); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + seat = wl_registry_bind(registry, name, &wl_seat_interface, version); + wl_seat_add_listener(seat, &seat_listener, seat); + } +} + +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, +}; + +int main(int argc, char **argv) { + struct wl_display *display = wl_display_connect(NULL); + assert(display); + struct wl_registry *registry = wl_display_get_registry(display); + assert(registry); + wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_display_dispatch(display); + wl_display_roundtrip(display); + assert(compositor && seat && wm_base && input_inhibit_manager); + + input_inhibitor = zwlr_input_inhibit_manager_v1_get_inhibitor( + input_inhibit_manager); + assert(input_inhibitor); + + wlr_egl_init(&egl, EGL_PLATFORM_WAYLAND_EXT, display, NULL, + WL_SHM_FORMAT_ARGB8888); + + struct wl_surface *surface = wl_compositor_create_surface(compositor); + assert(surface); + struct xdg_surface *xdg_surface = + xdg_wm_base_get_xdg_surface(wm_base, surface); + assert(xdg_surface); + struct xdg_toplevel *xdg_toplevel = xdg_surface_get_toplevel(xdg_surface); + assert(xdg_toplevel); + + xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL); + xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL); + + wl_surface_commit(surface); + + egl_window = wl_egl_window_create(surface, width, height); + egl_surface = wlr_egl_create_surface(&egl, egl_window); + + wl_display_roundtrip(display); + + render_frame(); + + while (wl_display_dispatch(display) != -1) { + // This space intentionally left blank + } + + return 0; +} diff --git a/examples/input-method.c b/examples/input-method.c new file mode 100644 index 00000000..4e375306 --- /dev/null +++ b/examples/input-method.c @@ -0,0 +1,402 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/epoll.h> +#include <sys/timerfd.h> +#include <unistd.h> +#include <wayland-client.h> +#include <wayland-egl.h> +#include "input-method-unstable-v2-client-protocol.h" +#include "text-input-unstable-v3-client-protocol.h" +#include "xdg-shell-client-protocol.h" + +const char usage[] = "Usage: input-method [seconds]\n\ +\n\ +Creates an input method using the input-method protocol.\n\ +\n\ +Whenever a text input is activated, this program sends a few sequences of\n\ +commands and checks the validity of the responses, relying on returned\n\ +surrounding text.\n\ +\n\ +The \"seconds\" argument is optional and defines the maximum delay between\n\ +stages."; + +struct input_method_state { + enum zwp_text_input_v3_change_cause change_cause; + struct { + enum zwp_text_input_v3_content_hint hint; + enum zwp_text_input_v3_content_purpose purpose; + } content_type; + struct { + char *text; + uint32_t cursor; + uint32_t anchor; + } surrounding; +}; + +static int sleeptime = 0; + +static struct wl_display *display = NULL; +static struct wl_compositor *compositor = NULL; +static struct wl_seat *seat = NULL; +static struct zwp_input_method_manager_v2 *input_method_manager = NULL; +static struct zwp_input_method_v2 *input_method = NULL; + +struct input_method_state pending; +struct input_method_state current; + +static uint32_t serial = 0; +bool active = false; +bool pending_active = false; +bool unavailable = false; +bool running = false; + +uint32_t update_stage = 0; + +int timer_fd = 0; + +static void print_state_diff(struct input_method_state previous, + struct input_method_state future) { + if (previous.content_type.hint != future.content_type.hint) { + char *strs[] = { "COMPLETION", "SPELLCHECK", "AUTO_CAPITALIZATION", + "LOWERCASE", "UPPERCASE", "TITLECASE", "HIDDEN_TEXT", + "SENSITIVE_DATA", "LATIN", "MULTILINE"}; + printf("content_type.hint:"); + uint32_t hint = future.content_type.hint; + if (!hint) { + printf(" NONE"); + } + for (unsigned i = 0; i < sizeof(strs) / sizeof(*strs); i++) { + if (hint & 1 << i) { + printf(" %s", strs[i]); + } + } + printf("\n"); + } + if (previous.content_type.purpose != future.content_type.purpose) { + char *strs[] = { "NORMAL", "ALPHA", "DIGITS", "NUMBER", "PHONE", "URL", + "EMAIL", "NAME", "PASSWORD", "PIN", "DATE", "TIME", "DATETIME", + "TERMINAL" }; + printf("content_type.purpose: %s\n", strs[future.content_type.purpose]); + } + if (!!previous.surrounding.text != !!future.surrounding.text + || (previous.surrounding.text && future.surrounding.text + && strcmp(previous.surrounding.text, future.surrounding.text) != 0) + || previous.surrounding.anchor != future.surrounding.anchor + || previous.surrounding.cursor != future.surrounding.cursor) { + char *text = future.surrounding.text; + if (!text) { + printf("Removed surrounding text\n"); + } else { + printf("Surrounding text: %s\n", text); + uint32_t anchor = future.surrounding.anchor; + uint32_t cursor = future.surrounding.cursor; + if (cursor == anchor) { + char *temp = strndup(text, cursor); + printf("Cursor after %d: %s\n", cursor, temp); + free(temp); + } else { + if (cursor > anchor) { + uint32_t tmp = anchor; + anchor = cursor; + cursor = tmp; + } + char *temp = strndup(&text[cursor], anchor - cursor); + printf("Selection: %s\n", temp); + free(temp); + } + } + } + if (previous.change_cause != future.change_cause) { + char *strs[] = { "INPUT_METHOD", "OTHER" }; + printf("Change cause: %s\n", strs[future.change_cause]); + } +} + +static void handle_content_type(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2, + uint32_t hint, uint32_t purpose) { + pending.content_type.hint = hint; + pending.content_type.purpose = purpose; +} + +static void handle_surrounding_text(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2, + const char *text, uint32_t cursor, uint32_t anchor) { + free(pending.surrounding.text); + pending.surrounding.text = strdup(text); + pending.surrounding.cursor = cursor; + pending.surrounding.anchor = anchor; +} + +static void handle_text_change_cause(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2, + uint32_t cause) { + pending.change_cause = cause; +} + +static void handle_activate(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2) { + pending_active = true; +} + +static void handle_deactivate(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2) { + pending_active = false; +} + +static void handle_unavailable(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2) { + printf("IM disappeared\n"); + zwp_input_method_v2_destroy(zwp_input_method_v2); + input_method = NULL; + running = false; +} + +static void im_activate(void *data, + struct zwp_input_method_v2 *id) { + update_stage = 0; +} + +static void timer_arm(unsigned seconds) { + printf("Timer armed\n"); + struct itimerspec spec = { + .it_interval = {0}, + .it_value = { + .tv_sec = seconds, + .tv_nsec = 0 + } + }; + if (timerfd_settime(timer_fd, 0, &spec, NULL)) { + fprintf(stderr, "Failed to arm timer: %s\n", strerror(errno)); + } +} + +static void do_updates() { + printf("Update %d\n", update_stage); + switch (update_stage) { + case 0: + // TODO: remember initial surrounding text + zwp_input_method_v2_set_preedit_string(input_method, "Preedit", 2, 4); + zwp_input_method_v2_commit(input_method, serial); + // don't expect an answer, preedit doesn't change anything visible + timer_arm(sleeptime); + update_stage++; + return; + case 1: + zwp_input_method_v2_set_preedit_string(input_method, "Præedit2", strlen("Pr"), strlen("Præed")); + zwp_input_method_v2_commit_string(input_method, "_Commit_"); + zwp_input_method_v2_commit(input_method, serial); + update_stage++; + break; + case 2: + if (strcmp(current.surrounding.text, "_Commit_") != 0) { + return; + } + zwp_input_method_v2_commit_string(input_method, "_CommitNoPreed_"); + zwp_input_method_v2_commit(input_method, serial); + timer_arm(sleeptime); + update_stage++; + break; + case 3: + if (strcmp(current.surrounding.text, "_Commit__CommitNoPreed_") != 0) { + return; + } + zwp_input_method_v2_commit_string(input_method, "_WaitNo_"); + zwp_input_method_v2_delete_surrounding_text(input_method, strlen("_CommitNoPreed_"), 0); + zwp_input_method_v2_commit(input_method, serial); + update_stage++; + break; + case 4: + if (strcmp(current.surrounding.text, "_Commit__WaitNo_") != 0) { + return; + } + zwp_input_method_v2_set_preedit_string(input_method, "PreedWithDel", strlen("Preed"), strlen("Preed")); + zwp_input_method_v2_delete_surrounding_text(input_method, strlen("_WaitNo_"), 0); + zwp_input_method_v2_commit(input_method, serial); + update_stage++; + break; + case 5: + if (strcmp(current.surrounding.text, "_Commit_") != 0) { + return; + } + zwp_input_method_v2_delete_surrounding_text(input_method, strlen("mit_"), 0); + zwp_input_method_v2_commit(input_method, serial); + update_stage++; + break; + case 6: + if (strcmp(current.surrounding.text, "_Com") != 0) { + printf("Failed\n"); + } + update_stage++; + break; + default: + printf("Submitted everything\n"); + return; + }; +} + +static void handle_timer() { + printf("Timer dispatched at %d\n", update_stage); + do_updates(); +} + +static void im_deactivate(void *data, + struct zwp_input_method_v2 *context) { + // No special action needed +} + +static void handle_done(void *data, + struct zwp_input_method_v2 *zwp_input_method_v2) { + bool prev_active = active; + serial++; + printf("Handle serial %d\n", serial); + if (active != pending_active) { + printf("Now %s\n", pending_active ? "active" : "inactive"); + } + if (pending_active) { + print_state_diff(current, pending); + } + active = pending_active; + free(current.surrounding.text); + struct input_method_state default_state = {0}; + current = pending; + pending = default_state; + if (active && !prev_active) { + im_activate(data, zwp_input_method_v2); + } else if (!active && prev_active) { + im_deactivate(data, zwp_input_method_v2); + } + + do_updates(); +} + +static const struct zwp_input_method_v2_listener im_listener = { + .activate = handle_activate, + .deactivate = handle_deactivate, + .surrounding_text = handle_surrounding_text, + .text_change_cause = handle_text_change_cause, + .content_type = handle_content_type, + .done = handle_done, + .unavailable = handle_unavailable, +}; + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + if (strcmp(interface, "wl_compositor") == 0) { + compositor = wl_registry_bind(registry, name, + &wl_compositor_interface, 1); + } else if (strcmp(interface, zwp_input_method_manager_v2_interface.name) == 0) { + input_method_manager = wl_registry_bind(registry, name, + &zwp_input_method_manager_v2_interface, 1); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + seat = wl_registry_bind(registry, name, &wl_seat_interface, version); + } +} + +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, +}; + +int main(int argc, char **argv) { + if (argc > 1) { + if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) { + printf(usage); + return 0; + } + sleeptime = atoi(argv[1]); + } + display = wl_display_connect(NULL); + if (display == NULL) { + fprintf(stderr, "Failed to create display\n"); + return EXIT_FAILURE; + } + + struct wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_display_dispatch(display); + wl_display_roundtrip(display); + + if (compositor == NULL) { + fprintf(stderr, "wl-compositor not available\n"); + return EXIT_FAILURE; + } + if (input_method_manager == NULL) { + fprintf(stderr, "input-method not available\n"); + return EXIT_FAILURE; + } + if (seat == NULL) { + fprintf(stderr, "seat not available\n"); + return EXIT_FAILURE; + } + + input_method = zwp_input_method_manager_v2_get_input_method( + input_method_manager, seat); + running = true; + zwp_input_method_v2_add_listener(input_method, &im_listener, NULL); + + int display_fd = wl_display_get_fd(display); + timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); + if (timer_fd < 0) { + fprintf(stderr, "Failed to start timer\n"); + return EXIT_FAILURE; + } + int epoll = epoll_create1(EPOLL_CLOEXEC); + if (epoll < 0) { + fprintf(stderr, "Failed to start epoll\n"); + return EXIT_FAILURE; + } + + struct epoll_event epoll_display = { + .events = EPOLLIN | EPOLLOUT, + .data = {.fd = display_fd}, + }; + if (epoll_ctl(epoll, EPOLL_CTL_ADD, display_fd, &epoll_display)) { + fprintf(stderr, "Failed to epoll display\n"); + return EXIT_FAILURE; + } + + wl_display_roundtrip(display); // timer may be armed here + + struct epoll_event epoll_timer = { + .events = EPOLLIN, + .data = {.fd = timer_fd}, + }; + if (epoll_ctl(epoll, EPOLL_CTL_ADD, timer_fd, &epoll_timer)) { + fprintf(stderr, "Failed to epoll timer\n"); + return EXIT_FAILURE; + } + + timer_arm(2); + + struct epoll_event caught; + while (epoll_wait(epoll, &caught, 1, -1)) { + if (!running) { + printf("Exiting\n"); + return EXIT_SUCCESS; + } + if (caught.data.fd == display_fd) { + if (wl_display_dispatch(display) == -1) { + break; + } + } else if (caught.data.fd == timer_fd) { + uint64_t expirations; + ssize_t n = read(timer_fd, &expirations, sizeof(expirations)); + assert(n >= 0); + handle_timer(); + } else { + printf("Unknown source\n"); + } + } + return EXIT_SUCCESS; +} diff --git a/examples/layer-shell.c b/examples/layer-shell.c new file mode 100644 index 00000000..77b2f6c3 --- /dev/null +++ b/examples/layer-shell.c @@ -0,0 +1,653 @@ +#define _POSIX_C_SOURCE 200112L +#ifdef __linux__ +#include <linux/input-event-codes.h> +#elif __FreeBSD__ +#include <dev/evdev/input-event-codes.h> +#endif +#include <assert.h> +#include <GLES2/gl2.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <wayland-client.h> +#include <wayland-cursor.h> +#include <wayland-egl.h> +#include <wlr/render/egl.h> +#include <wlr/util/log.h> +#include "wlr-layer-shell-unstable-v1-client-protocol.h" +#include "xdg-shell-client-protocol.h" + +static struct wl_display *display; +static struct wl_compositor *compositor; +static struct wl_seat *seat; +static struct wl_shm *shm; +static struct wl_pointer *pointer; +static struct wl_keyboard *keyboard; +static struct xdg_wm_base *xdg_wm_base; +static struct zwlr_layer_shell_v1 *layer_shell; + +struct zwlr_layer_surface_v1 *layer_surface; +static struct wl_output *wl_output; + +struct wl_surface *wl_surface; +struct wlr_egl egl; +struct wl_egl_window *egl_window; +struct wlr_egl_surface *egl_surface; +struct wl_callback *frame_callback; + +static uint32_t output = UINT32_MAX; +struct xdg_popup *popup; +struct wl_surface *popup_wl_surface; +struct wl_egl_window *popup_egl_window; +static uint32_t popup_width = 256, popup_height = 256; +struct wlr_egl_surface *popup_egl_surface; +struct wl_callback *popup_frame_callback; +float popup_alpha = 1.0, popup_red = 0.5f; + +static uint32_t layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; +static uint32_t anchor = 0; +static uint32_t width = 256, height = 256; +static int32_t margin_top = 0; +static double alpha = 1.0; +static bool run_display = true; +static bool animate = false; +static bool keyboard_interactive = false; +static double frame = 0; +static int cur_x = -1, cur_y = -1; +static int buttons = 0; + +struct wl_cursor_image *cursor_image; +struct wl_cursor_image *popup_cursor_image; +struct wl_surface *cursor_surface, *input_surface; + +static struct { + struct timespec last_frame; + float color[3]; + int dec; +} demo; + +static void draw(void); +static void draw_popup(void); + +static void surface_frame_callback( + void *data, struct wl_callback *cb, uint32_t time) { + wl_callback_destroy(cb); + frame_callback = NULL; + draw(); +} + +static struct wl_callback_listener frame_listener = { + .done = surface_frame_callback +}; + +static void popup_surface_frame_callback( + void *data, struct wl_callback *cb, uint32_t time) { + wl_callback_destroy(cb); + popup_frame_callback = NULL; + if (popup) { + draw_popup(); + } +} + +static struct wl_callback_listener popup_frame_listener = { + .done = popup_surface_frame_callback +}; + +static void draw(void) { + eglMakeCurrent(egl.display, egl_surface, egl_surface, egl.context); + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + + long ms = (ts.tv_sec - demo.last_frame.tv_sec) * 1000 + + (ts.tv_nsec - demo.last_frame.tv_nsec) / 1000000; + int inc = (demo.dec + 1) % 3; + + if (!buttons) { + demo.color[inc] += ms / 2000.0f; + demo.color[demo.dec] -= ms / 2000.0f; + + if (demo.color[demo.dec] < 0.0f) { + demo.color[inc] = 1.0f; + demo.color[demo.dec] = 0.0f; + demo.dec = inc; + } + } + + if (animate) { + frame += ms / 50.0; + int32_t old_top = margin_top; + margin_top = -(20 - ((int)frame % 20)); + if (old_top != margin_top) { + zwlr_layer_surface_v1_set_margin(layer_surface, + margin_top, 0, 0, 0); + wl_surface_commit(wl_surface); + } + } + + glViewport(0, 0, width, height); + if (buttons) { + glClearColor(1, 1, 1, alpha); + } else { + glClearColor(demo.color[0], demo.color[1], demo.color[2], alpha); + } + glClear(GL_COLOR_BUFFER_BIT); + + if (cur_x != -1 && cur_y != -1) { + glEnable(GL_SCISSOR_TEST); + glScissor(cur_x, height - cur_y, 5, 5); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_SCISSOR_TEST); + } + + frame_callback = wl_surface_frame(wl_surface); + wl_callback_add_listener(frame_callback, &frame_listener, NULL); + + eglSwapBuffers(egl.display, egl_surface); + + demo.last_frame = ts; +} + +static void draw_popup(void) { + static float alpha_mod = -0.01; + + eglMakeCurrent(egl.display, popup_egl_surface, popup_egl_surface, egl.context); + glViewport(0, 0, popup_width, popup_height); + glClearColor(popup_red, 0.5f, 0.5f, popup_alpha); + popup_alpha += alpha_mod; + if (popup_alpha < 0.01 || popup_alpha >= 1.0f) { + alpha_mod *= -1.0; + } + glClear(GL_COLOR_BUFFER_BIT); + + popup_frame_callback = wl_surface_frame(popup_wl_surface); + assert(popup_frame_callback); + wl_callback_add_listener(popup_frame_callback, &popup_frame_listener, NULL); + eglSwapBuffers(egl.display, popup_egl_surface); + wl_surface_commit(popup_wl_surface); +} + +static void xdg_surface_handle_configure(void *data, + struct xdg_surface *xdg_surface, uint32_t serial) { + xdg_surface_ack_configure(xdg_surface, serial); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_handle_configure, +}; + +static void xdg_popup_configure(void *data, struct xdg_popup *xdg_popup, + int32_t x, int32_t y, int32_t width, int32_t height) { + wlr_log(WLR_DEBUG, "Popup configured %dx%d@%d,%d", + width, height, x, y); + popup_width = width; + popup_height = height; + if (popup_egl_window) { + wl_egl_window_resize(popup_egl_window, width, height, 0, 0); + } +} + +static void popup_destroy(void) { + wlr_egl_destroy_surface(&egl, popup_egl_surface); + wl_egl_window_destroy(popup_egl_window); + xdg_popup_destroy(popup); + wl_surface_destroy(popup_wl_surface); + popup_wl_surface = NULL; + popup = NULL; + popup_egl_window = NULL; +} + +static void xdg_popup_done(void *data, struct xdg_popup *xdg_popup) { + wlr_log(WLR_DEBUG, "Popup done"); + popup_destroy(); +} + +static const struct xdg_popup_listener xdg_popup_listener = { + .configure = xdg_popup_configure, + .popup_done = xdg_popup_done, +}; + +static void create_popup(uint32_t serial) { + if (popup) { + return; + } + struct wl_surface *surface = wl_compositor_create_surface(compositor); + assert(xdg_wm_base && surface); + struct xdg_surface *xdg_surface = + xdg_wm_base_get_xdg_surface(xdg_wm_base, surface); + struct xdg_positioner *xdg_positioner = + xdg_wm_base_create_positioner(xdg_wm_base); + assert(xdg_surface && xdg_positioner); + + xdg_positioner_set_size(xdg_positioner, popup_width, popup_height); + xdg_positioner_set_offset(xdg_positioner, 0, 0); + xdg_positioner_set_anchor_rect(xdg_positioner, cur_x, cur_y, 1, 1); + xdg_positioner_set_anchor(xdg_positioner, XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT); + + popup = xdg_surface_get_popup(xdg_surface, NULL, xdg_positioner); + xdg_popup_grab(popup, seat, serial); + + assert(popup); + + zwlr_layer_surface_v1_get_popup(layer_surface, popup); + + xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL); + xdg_popup_add_listener(popup, &xdg_popup_listener, NULL); + + wl_surface_commit(surface); + wl_display_roundtrip(display); + + xdg_positioner_destroy(xdg_positioner); + + popup_wl_surface = surface; + popup_egl_window = wl_egl_window_create(surface, popup_width, popup_height); + assert(popup_egl_window); + popup_egl_surface = wlr_egl_create_surface(&egl, popup_egl_window); + assert(popup_egl_surface); + draw_popup(); +} + +static void layer_surface_configure(void *data, + struct zwlr_layer_surface_v1 *surface, + uint32_t serial, uint32_t w, uint32_t h) { + width = w; + height = h; + if (egl_window) { + wl_egl_window_resize(egl_window, width, height, 0, 0); + } + zwlr_layer_surface_v1_ack_configure(surface, serial); +} + +static void layer_surface_closed(void *data, + struct zwlr_layer_surface_v1 *surface) { + wlr_egl_destroy_surface(&egl, egl_surface); + wl_egl_window_destroy(egl_window); + zwlr_layer_surface_v1_destroy(surface); + wl_surface_destroy(wl_surface); + run_display = false; +} + +struct zwlr_layer_surface_v1_listener layer_surface_listener = { + .configure = layer_surface_configure, + .closed = layer_surface_closed, +}; + +static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t surface_x, wl_fixed_t surface_y) { + struct wl_cursor_image *image; + if (surface == popup_wl_surface) { + image = popup_cursor_image; + } else { + image = cursor_image; + } + wl_surface_attach(cursor_surface, + wl_cursor_image_get_buffer(image), 0, 0); + wl_surface_damage(cursor_surface, 1, 0, + image->width, image->height); + wl_surface_commit(cursor_surface); + wl_pointer_set_cursor(wl_pointer, serial, cursor_surface, + image->hotspot_x, image->hotspot_y); + input_surface = surface; +} + +static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface) { + cur_x = cur_y = -1; + buttons = 0; +} + +static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, + uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { + cur_x = wl_fixed_to_int(surface_x); + cur_y = wl_fixed_to_int(surface_y); +} + +static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { + if (input_surface == wl_surface) { + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + if (button == BTN_RIGHT) { + if (popup_wl_surface) { + popup_destroy(); + } else { + create_popup(serial); + } + } else { + buttons++; + } + } else { + if (button != BTN_RIGHT) { + buttons--; + } + } + } else if (input_surface == popup_wl_surface) { + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + if (button == BTN_LEFT && popup_red <= 0.9f) { + popup_red += 0.1; + } else if (button == BTN_RIGHT && popup_red >= 0.1f) { + popup_red -= 0.1; + } + } + } else { + assert(false && "Unknown surface"); + } +} + +static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) { + // Who cares +} + +static void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) { + // Who cares +} + +static void wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer, + uint32_t axis_source) { + // Who cares +} + +static void wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis) { + // Who cares +} + +static void wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer, + uint32_t axis, int32_t discrete) { + // Who cares +} + +struct wl_pointer_listener pointer_listener = { + .enter = wl_pointer_enter, + .leave = wl_pointer_leave, + .motion = wl_pointer_motion, + .button = wl_pointer_button, + .axis = wl_pointer_axis, + .frame = wl_pointer_frame, + .axis_source = wl_pointer_axis_source, + .axis_stop = wl_pointer_axis_stop, + .axis_discrete = wl_pointer_axis_discrete, +}; + +static void wl_keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int32_t fd, uint32_t size) { + // Who cares +} + +static void wl_keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { + wlr_log(WLR_DEBUG, "Keyboard enter"); +} + +static void wl_keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, struct wl_surface *surface) { + wlr_log(WLR_DEBUG, "Keyboard leave"); +} + +static void wl_keyboard_key(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { + wlr_log(WLR_DEBUG, "Key event: %d %d", key, state); +} + +static void wl_keyboard_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) { + // Who cares +} + +static void wl_keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, + int32_t rate, int32_t delay) { + // Who cares +} + +static struct wl_keyboard_listener keyboard_listener = { + .keymap = wl_keyboard_keymap, + .enter = wl_keyboard_enter, + .leave = wl_keyboard_leave, + .key = wl_keyboard_key, + .modifiers = wl_keyboard_modifiers, + .repeat_info = wl_keyboard_repeat_info, +}; + +static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, + enum wl_seat_capability caps) { + if ((caps & WL_SEAT_CAPABILITY_POINTER)) { + pointer = wl_seat_get_pointer(wl_seat); + wl_pointer_add_listener(pointer, &pointer_listener, NULL); + } + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD)) { + keyboard = wl_seat_get_keyboard(wl_seat); + wl_keyboard_add_listener(keyboard, &keyboard_listener, NULL); + } +} + +static void seat_handle_name(void *data, struct wl_seat *wl_seat, + const char *name) { + // Who cares +} + +const struct wl_seat_listener seat_listener = { + .capabilities = seat_handle_capabilities, + .name = seat_handle_name, +}; + +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, 1); + } else if (strcmp(interface, wl_shm_interface.name) == 0) { + shm = wl_registry_bind(registry, name, + &wl_shm_interface, 1); + } else if (strcmp(interface, "wl_output") == 0) { + if (output != UINT32_MAX) { + if (!wl_output) { + wl_output = wl_registry_bind(registry, name, + &wl_output_interface, 1); + } else { + output--; + } + } + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + seat = wl_registry_bind(registry, name, + &wl_seat_interface, 1); + wl_seat_add_listener(seat, &seat_listener, NULL); + } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { + layer_shell = wl_registry_bind( + registry, name, &zwlr_layer_shell_v1_interface, 1); + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + xdg_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, +}; + +int main(int argc, char **argv) { + wlr_log_init(WLR_DEBUG, NULL); + char *namespace = "wlroots"; + int exclusive_zone = 0; + int32_t margin_right = 0, margin_bottom = 0, margin_left = 0; + bool found; + int c; + while ((c = getopt(argc, argv, "knw:h:o:l:a:x:m:t:")) != -1) { + switch (c) { + case 'o': + output = atoi(optarg); + break; + case 'w': + width = atoi(optarg); + break; + case 'h': + height = atoi(optarg); + break; + case 'x': + exclusive_zone = atoi(optarg); + break; + case 'l': { + struct { + char *name; + enum zwlr_layer_shell_v1_layer value; + } layers[] = { + { "background", ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND }, + { "bottom", ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM }, + { "top", ZWLR_LAYER_SHELL_V1_LAYER_TOP }, + { "overlay", ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY }, + }; + found = false; + for (size_t i = 0; i < sizeof(layers) / sizeof(layers[0]); ++i) { + if (strcmp(optarg, layers[i].name) == 0) { + layer = layers[i].value; + found = true; + break; + } + } + if (!found) { + fprintf(stderr, "invalid layer %s\n", optarg); + return 1; + } + break; + } + case 'a': { + struct { + char *name; + uint32_t value; + } anchors[] = { + { "top", ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP }, + { "bottom", ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM }, + { "left", ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT }, + { "right", ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT }, + }; + found = false; + for (size_t i = 0; i < sizeof(anchors) / sizeof(anchors[0]); ++i) { + if (strcmp(optarg, anchors[i].name) == 0) { + anchor |= anchors[i].value; + found = true; + break; + } + } + if (!found) { + fprintf(stderr, "invalid anchor %s\n", optarg); + return 1; + } + break; + } + case 't': + alpha = atof(optarg); + break; + case 'm': { + char *endptr = optarg; + margin_top = strtol(endptr, &endptr, 10); + assert(*endptr == ','); + margin_right = strtol(endptr + 1, &endptr, 10); + assert(*endptr == ','); + margin_bottom = strtol(endptr + 1, &endptr, 10); + assert(*endptr == ','); + margin_left = strtol(endptr + 1, &endptr, 10); + assert(!*endptr); + break; + } + case 'n': + animate = true; + break; + case 'k': + keyboard_interactive = true; + break; + default: + break; + } + } + + display = wl_display_connect(NULL); + if (display == NULL) { + fprintf(stderr, "Failed to create display\n"); + return 1; + } + + struct wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_display_roundtrip(display); + + if (compositor == NULL) { + fprintf(stderr, "wl_compositor not available\n"); + return 1; + } + if (shm == NULL) { + fprintf(stderr, "wl_shm not available\n"); + return 1; + } + if (layer_shell == NULL) { + fprintf(stderr, "layer_shell not available\n"); + return 1; + } + + struct wl_cursor_theme *cursor_theme = + wl_cursor_theme_load(NULL, 16, shm); + assert(cursor_theme); + struct wl_cursor *cursor = + wl_cursor_theme_get_cursor(cursor_theme, "crosshair"); + if (cursor == NULL) { + cursor = wl_cursor_theme_get_cursor(cursor_theme, "left_ptr"); + } + assert(cursor); + cursor_image = cursor->images[0]; + + cursor = wl_cursor_theme_get_cursor(cursor_theme, "tcross"); + if (cursor == NULL) { + cursor = wl_cursor_theme_get_cursor(cursor_theme, "left_ptr"); + } + assert(cursor); + popup_cursor_image = cursor->images[0]; + + cursor_surface = wl_compositor_create_surface(compositor); + assert(cursor_surface); + + EGLint attribs[] = { EGL_ALPHA_SIZE, 8, EGL_NONE }; + wlr_egl_init(&egl, EGL_PLATFORM_WAYLAND_EXT, display, + attribs, WL_SHM_FORMAT_ARGB8888); + + wl_surface = wl_compositor_create_surface(compositor); + assert(wl_surface); + + layer_surface = zwlr_layer_shell_v1_get_layer_surface(layer_shell, + wl_surface, wl_output, layer, namespace); + assert(layer_surface); + zwlr_layer_surface_v1_set_size(layer_surface, width, height); + zwlr_layer_surface_v1_set_anchor(layer_surface, anchor); + zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, exclusive_zone); + zwlr_layer_surface_v1_set_margin(layer_surface, + margin_top, margin_right, margin_bottom, margin_left); + zwlr_layer_surface_v1_set_keyboard_interactivity( + layer_surface, keyboard_interactive); + zwlr_layer_surface_v1_add_listener(layer_surface, + &layer_surface_listener, layer_surface); + wl_surface_commit(wl_surface); + wl_display_roundtrip(display); + + egl_window = wl_egl_window_create(wl_surface, width, height); + assert(egl_window); + egl_surface = wlr_egl_create_surface(&egl, egl_window); + assert(egl_surface); + + wl_display_roundtrip(display); + draw(); + + while (wl_display_dispatch(display) != -1 && run_display) { + // This space intentionally left blank + } + + wl_cursor_theme_destroy(cursor_theme); + return 0; +} diff --git a/examples/meson.build b/examples/meson.build new file mode 100644 index 00000000..589a7326 --- /dev/null +++ b/examples/meson.build @@ -0,0 +1,133 @@ +threads = dependency('threads') +wayland_cursor = dependency('wayland-cursor') +libpng = dependency('libpng', required: false) +# These versions correspond to ffmpeg 4.0 +libavutil = dependency('libavutil', version: '>=56.14.100', required: false) +libavcodec = dependency('libavcodec', version: '>=58.18.100', required: false) +libavformat = dependency('libavformat', version: '>=58.12.100', required: false) + +# epoll is a separate library in FreeBSD +if host_machine.system() == 'freebsd' + libepoll = [dependency('epoll-shim')] +else + libepoll = [] +endif + +# Small hack until https://github.com/mesonbuild/meson/pull/3386/ is merged +foreach dep : ['libpng', 'libavutil', 'libavcodec', 'libavformat'] + if not get_variable(dep).found() + set_variable(dep, disabler()) + endif +endforeach + +if not cc.has_header('libavutil/hwcontext_drm.h', dependencies: libavutil) + libavutil = disabler() +endif + +examples = { + 'simple': { + 'src': 'simple.c', + 'dep': [wlroots], + }, + 'pointer': { + 'src': 'pointer.c', + 'dep': [wlroots], + }, + 'touch': { + 'src': ['touch.c', 'cat.c'], + 'dep': [wlroots], + }, + 'tablet': { + 'src': 'tablet.c', + 'dep': [wlroots], + }, + 'rotation': { + 'src': ['rotation.c', 'cat.c'], + 'dep': [wlroots], + }, + 'multi-pointer': { + 'src': 'multi-pointer.c', + 'dep': [wlroots], + }, + 'output-layout': { + 'src': ['output-layout.c', 'cat.c'], + 'dep': [wlroots], + }, + 'screenshot': { + 'src': 'screenshot.c', + 'dep': [wayland_client, wlr_protos, rt], + }, + 'idle': { + 'src': 'idle.c', + 'dep': [wayland_client, wlr_protos, threads], + }, + 'idle-inhibit': { + 'src': 'idle-inhibit.c', + 'dep': [wayland_client, wlr_protos, wlroots], + }, + 'layer-shell': { + 'src': 'layer-shell.c', + 'dep': [wayland_client, wayland_cursor, wlr_protos, wlroots], + }, + 'input-inhibitor': { + 'src': 'input-inhibitor.c', + 'dep': [wayland_client, wayland_cursor, wlr_protos, wlroots], + }, + 'gamma-control': { + 'src': 'gamma-control.c', + 'dep': [wayland_client, wayland_cursor, wlr_protos, math], + }, + 'pointer-constraints': { + 'src': 'pointer-constraints.c', + 'dep': [wayland_client, wlr_protos, wlroots], + }, + 'dmabuf-capture': { + 'src': 'dmabuf-capture.c', + 'dep': [ + libavcodec, + libavformat, + libavutil, + drm.partial_dependency(compile_args: true), # <drm_fourcc.h> + threads, + wayland_client, + wlr_protos, + ], + }, + 'screencopy': { + 'src': 'screencopy.c', + 'dep': [libpng, wayland_client, wlr_protos, rt], + }, + 'toplevel-decoration': { + 'src': 'toplevel-decoration.c', + 'dep': [wayland_client, wlr_protos, wlroots], + }, + 'input-method': { + 'src': 'input-method.c', + 'dep': [wayland_client, wlr_protos] + libepoll, + }, + 'text-input': { + 'src': 'text-input.c', + 'dep': [wayland_cursor, wayland_client, wlr_protos, wlroots], + }, + 'foreign-toplevel': { + 'src': 'foreign-toplevel.c', + 'dep': [wayland_client, wlr_protos, wlroots], + }, +} + +foreach name, info : examples + all_dep_found = true + foreach d : info.get('dep') + all_dep_found = all_dep_found and d.found() + endforeach + if all_dep_found + executable( + name, + info.get('src'), + dependencies: info.get('dep'), + build_by_default: get_option('examples'), + ) + else + warning('Dependencies not satisfied for ' + name) + endif +endforeach diff --git a/examples/multi-pointer.c b/examples/multi-pointer.c new file mode 100644 index 00000000..49670c39 --- /dev/null +++ b/examples/multi-pointer.c @@ -0,0 +1,340 @@ +#define _POSIX_C_SOURCE 200112L +#include <assert.h> +#include <GLES2/gl2.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <wayland-server-protocol.h> +#include <wayland-server.h> +#include <wlr/backend.h> +#include <wlr/backend/session.h> +#include <wlr/render/gles2.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_cursor.h> +#include <wlr/types/wlr_keyboard.h> +#include <wlr/types/wlr_list.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/types/wlr_output_layout.h> +#include <wlr/util/log.h> +#include <wlr/xcursor.h> +#include <xkbcommon/xkbcommon.h> + +struct sample_state { + struct wl_display *display; + struct wlr_xcursor *xcursor; + float default_color[4]; + float clear_color[4]; + struct wlr_output_layout *layout; + struct wl_list cursors; // sample_cursor::link + struct wl_list pointers; // sample_pointer::link + struct wl_list outputs; // sample_output::link + struct timespec last_frame; + struct wl_listener new_output; + struct wl_listener new_input; +}; + +struct sample_cursor { + struct sample_state *sample; + struct wlr_input_device *device; + struct wlr_cursor *cursor; + struct wl_list link; + + struct wl_listener cursor_motion; + struct wl_listener cursor_motion_absolute; + struct wl_listener cursor_button; + struct wl_listener cursor_axis; + struct wl_listener destroy; +}; + +struct sample_pointer { + struct wlr_input_device *device; + struct wl_list link; +}; + +struct sample_output { + struct sample_state *sample; + struct wlr_output *output; + struct wl_listener frame; + struct wl_listener destroy; + struct wl_list link; +}; + +struct sample_keyboard { + struct sample_state *sample; + struct wlr_input_device *device; + struct wl_listener key; + struct wl_listener destroy; +}; + +void configure_cursor(struct wlr_cursor *cursor, struct wlr_input_device *device, + struct sample_state *sample) { + struct sample_output *output; + wlr_log(WLR_ERROR, "Configuring cursor %p for device %p", cursor, device); + + // reset mappings + wlr_cursor_map_to_output(cursor, NULL); + wlr_cursor_detach_input_device(cursor, device); + wlr_cursor_map_input_to_output(cursor, device, NULL); + + wlr_cursor_attach_input_device(cursor, device); + + // configure device to output mappings + wl_list_for_each(output, &sample->outputs, link) { + wlr_cursor_map_to_output(cursor, output->output); + + wlr_cursor_map_input_to_output(cursor, device, output->output); + } +} + +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; + + wlr_output_make_current(wlr_output, NULL); + + glClearColor(sample->clear_color[0], sample->clear_color[1], + sample->clear_color[2], sample->clear_color[3]); + glClear(GL_COLOR_BUFFER_BIT); + + wlr_output_swap_buffers(wlr_output, NULL, NULL); +} + +static void handle_cursor_motion(struct wl_listener *listener, void *data) { + struct sample_cursor *cursor = + wl_container_of(listener, cursor, cursor_motion); + struct wlr_event_pointer_motion *event = data; + wlr_cursor_move(cursor->cursor, event->device, event->delta_x, + event->delta_y); +} + +static void handle_cursor_motion_absolute(struct wl_listener *listener, + void *data) { + struct sample_cursor *cursor = + wl_container_of(listener, cursor, cursor_motion_absolute); + struct wlr_event_pointer_motion_absolute *event = data; + wlr_cursor_warp_absolute(cursor->cursor, event->device, event->x, event->y); +} + +static void cursor_destroy(struct sample_cursor *cursor) { + wl_list_remove(&cursor->link); + wl_list_remove(&cursor->cursor_motion.link); + wl_list_remove(&cursor->cursor_motion_absolute.link); + wlr_cursor_destroy(cursor->cursor); + free(cursor); +} + +void input_remove_notify(struct wl_listener *listener, void *data) { + struct wlr_input_device *device = data; + struct sample_cursor *sample_cursor = wl_container_of(listener, sample_cursor, destroy); + struct sample_state *sample = sample_cursor->sample; + struct sample_cursor *cursor; + wl_list_for_each(cursor, &sample->cursors, link) { + if (cursor->device == device) { + cursor_destroy(cursor); + break; + } + } + struct sample_pointer *pointer; + wl_list_for_each(pointer, &sample->pointers, link) { + if (pointer->device == device) { + free(pointer); + break; + } + } +} + +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; + wl_list_remove(&sample_output->frame.link); + wl_list_remove(&sample_output->destroy.link); + wl_list_remove(&sample_output->link); + free(sample_output); + + struct sample_cursor *cursor; + wl_list_for_each(cursor, &sample->cursors, link) { + configure_cursor(cursor->cursor, cursor->device, sample); + } +} + +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); + struct sample_output *sample_output = calloc(1, sizeof(struct sample_output)); + if (!wl_list_empty(&output->modes)) { + struct wlr_output_mode *mode = wl_container_of(output->modes.prev, mode, link); + wlr_output_set_mode(output, mode); + } + 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; + + wlr_output_layout_add_auto(sample->layout, output); + + + struct sample_cursor *cursor; + wl_list_for_each(cursor, &sample->cursors, link) { + configure_cursor(cursor->cursor, cursor->device, sample); + + struct wlr_xcursor_image *image = sample->xcursor->images[0]; + wlr_cursor_set_image(cursor->cursor, image->buffer, image->width * 4, + image->width, image->height, image->hotspot_x, image->hotspot_y, 0); + + wlr_cursor_warp(cursor->cursor, NULL, cursor->cursor->x, + cursor->cursor->y); + } + wl_list_insert(&sample->outputs, &sample_output->link); +} + +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_event_keyboard_key *event = data; + uint32_t keycode = event->keycode + 8; + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms(keyboard->device->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); + } + } +} + +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); +} + +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(struct sample_keyboard)); + keyboard->device = device; + keyboard->sample = sample; + wl_signal_add(&device->events.destroy, &keyboard->destroy); + keyboard->destroy.notify = keyboard_destroy_notify; + wl_signal_add(&device->keyboard->events.key, &keyboard->key); + keyboard->key.notify = keyboard_key_notify; + struct xkb_rule_names rules = { 0 }; + rules.rules = getenv("XKB_DEFAULT_RULES"); + rules.model = getenv("XKB_DEFAULT_MODEL"); + rules.layout = getenv("XKB_DEFAULT_LAYOUT"); + rules.variant = getenv("XKB_DEFAULT_VARIANT"); + rules.options = getenv("XKB_DEFAULT_OPTIONS"); + 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_map_new_from_names(context, &rules, + XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + wlr_log(WLR_ERROR, "Failed to create XKB keymap"); + exit(1); + } + wlr_keyboard_set_keymap(device->keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + break; + case WLR_INPUT_DEVICE_POINTER:; + struct sample_cursor *cursor = calloc(1, sizeof(struct sample_cursor)); + struct sample_pointer *pointer = calloc(1, sizeof(struct sample_pointer)); + pointer->device = device; + cursor->sample = sample; + cursor->device = device; + + cursor->cursor = wlr_cursor_create(); + + wlr_cursor_attach_output_layout(cursor->cursor, sample->layout); + + wl_signal_add(&cursor->cursor->events.motion, &cursor->cursor_motion); + cursor->cursor_motion.notify = handle_cursor_motion; + wl_signal_add(&cursor->cursor->events.motion_absolute, + &cursor->cursor_motion_absolute); + cursor->cursor_motion_absolute.notify = handle_cursor_motion_absolute; + + wlr_cursor_attach_input_device(cursor->cursor, device); + configure_cursor(cursor->cursor, device, sample); + + struct wlr_xcursor_image *image = sample->xcursor->images[0]; + wlr_cursor_set_image(cursor->cursor, image->buffer, image->width * 4, + image->width, image->height, image->hotspot_x, image->hotspot_y, 0); + + wl_list_insert(&sample->cursors, &cursor->link); + wl_list_insert(&sample->pointers, &pointer->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 = { + .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(display, NULL); + if (!wlr) { + exit(1); + } + wl_list_init(&state.cursors); + wl_list_init(&state.pointers); + wl_list_init(&state.outputs); + + state.layout = wlr_output_layout_create(); + + 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); + + struct wlr_xcursor_theme *theme = wlr_xcursor_theme_load("default", 16); + if (!theme) { + wlr_log(WLR_ERROR, "Failed to load cursor theme"); + return 1; + } + state.xcursor = wlr_xcursor_theme_get_cursor(theme, "left_ptr"); + if (!state.xcursor) { + wlr_log(WLR_ERROR, "Failed to load left_ptr cursor"); + return 1; + } + + 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); + + struct sample_cursor *cursor, *tmp_cursor; + wl_list_for_each_safe(cursor, tmp_cursor, &state.cursors, link) { + cursor_destroy(cursor); + } + + struct sample_pointer *pointer, *tmp_pointer; + wl_list_for_each_safe(pointer, tmp_pointer, &state.pointers, link) { + free(pointer); + } + + wlr_xcursor_theme_destroy(theme); + wlr_output_layout_destroy(state.layout); +} diff --git a/examples/output-layout.c b/examples/output-layout.c new file mode 100644 index 00000000..440b3188 --- /dev/null +++ b/examples/output-layout.c @@ -0,0 +1,295 @@ +#define _POSIX_C_SOURCE 200112L +#include <GLES2/gl2.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-protocol.h> +#include <wayland-server.h> +#include <wlr/backend.h> +#include <wlr/backend/session.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_keyboard.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_output_layout.h> +#include <wlr/types/wlr_output.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_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_input_device *device; + 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; +} + +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 timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + + struct wlr_output *wlr_output = output->output; + + wlr_output_make_current(wlr_output, NULL); + wlr_renderer_begin(sample->renderer, wlr_output->width, wlr_output->height); + wlr_renderer_clear(sample->renderer, (float[]){0.25f, 0.25f, 0.25f, 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_texture(sample->renderer, sample->cat_texture, + wlr_output->transform_matrix, local_x, local_y, 1.0f); + } + + wlr_renderer_end(sample->renderer); + wlr_output_swap_buffers(wlr_output, NULL, NULL); +} + +static void update_velocities(struct sample_state *sample, + float x_diff, float y_diff) { + sample->x_vel += x_diff; + sample->y_vel += y_diff; +} + +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); +} + +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); + struct sample_output *sample_output = calloc(1, sizeof(struct sample_output)); + if (!wl_list_empty(&output->modes)) { + struct wlr_output_mode *mode = wl_container_of(output->modes.prev, mode, link); + wlr_output_set_mode(output, mode); + } + 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; +} + +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_event_keyboard_key *event = data; + uint32_t keycode = event->keycode + 8; + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms(keyboard->device->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 == WLR_KEY_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; + } + } + } +} + +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); +} + +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(struct sample_keyboard)); + keyboard->device = device; + keyboard->sample = sample; + wl_signal_add(&device->events.destroy, &keyboard->destroy); + keyboard->destroy.notify = keyboard_destroy_notify; + wl_signal_add(&device->keyboard->events.key, &keyboard->key); + keyboard->key.notify = keyboard_key_notify; + struct xkb_rule_names rules = { 0 }; + rules.rules = getenv("XKB_DEFAULT_RULES"); + rules.model = getenv("XKB_DEFAULT_MODEL"); + rules.layout = getenv("XKB_DEFAULT_LAYOUT"); + rules.variant = getenv("XKB_DEFAULT_VARIANT"); + rules.options = getenv("XKB_DEFAULT_OPTIONS"); + 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_map_new_from_names(context, &rules, + XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + wlr_log(WLR_ERROR, "Failed to create XKB keymap"); + exit(1); + } + wlr_keyboard_set_keymap(device->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(); + clock_gettime(CLOCK_MONOTONIC, &state.ts_last); + + struct wlr_backend *wlr = wlr_backend_autocreate(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_backend_get_renderer(wlr); + state.cat_texture = wlr_texture_from_pixels(state.renderer, + WL_SHM_FORMAT_ABGR8888, cat_tex.width * 4, cat_tex.width, cat_tex.height, + cat_tex.pixel_data); + + 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); + wlr_output_layout_destroy(state.layout); +} diff --git a/examples/pointer-constraints.c b/examples/pointer-constraints.c new file mode 100644 index 00000000..1df9f6ce --- /dev/null +++ b/examples/pointer-constraints.c @@ -0,0 +1,260 @@ +#include <GLES2/gl2.h> +#include <linux/input-event-codes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <wayland-client.h> +#include <wayland-egl.h> +#include <wlr/render/egl.h> +#include "xdg-shell-client-protocol.h" +#include "pointer-constraints-unstable-v1-client-protocol.h" + +static int width = 512, height = 512; + +static struct wl_compositor *compositor = NULL; +static struct wl_seat *seat = NULL; +static struct xdg_wm_base *wm_base = NULL; +static struct zwp_pointer_constraints_v1 *pointer_constraints = NULL; + +struct wlr_egl egl; +struct wl_egl_window *egl_window; +struct wlr_egl_surface *egl_surface; +struct zwp_locked_pointer_v1* locked_pointer; +struct zwp_confined_pointer_v1* confined_pointer; + +enum { + REGION_TYPE_NONE, + REGION_TYPE_DISJOINT, + REGION_TYPE_JOINT, + REGION_TYPE_MAX +} region_type = REGION_TYPE_NONE; + +struct wl_region *regions[3]; + +static void draw(void) { + eglMakeCurrent(egl.display, egl_surface, egl_surface, egl.context); + + float color[] = {1.0, 1.0, 0.0, 1.0}; + + glViewport(0, 0, width, height); + glClearColor(color[0], color[1], color[2], 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + eglSwapBuffers(egl.display, egl_surface); +} + +static void pointer_handle_button(void *data, struct wl_pointer *pointer, + uint32_t serial, uint32_t time, uint32_t button, uint32_t state_w) { + struct wl_surface *surface = data; + + if (button == BTN_LEFT && state_w == WL_POINTER_BUTTON_STATE_PRESSED) { + region_type = (region_type + 1) % REGION_TYPE_MAX; + + if (locked_pointer) { + zwp_locked_pointer_v1_set_region(locked_pointer, + regions[region_type]); + } else if (confined_pointer) { + zwp_confined_pointer_v1_set_region(confined_pointer, + regions[region_type]); + } + + wl_surface_commit(surface); + } +} + +static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t surface_x, wl_fixed_t surface_y) { + // This space intentionally left blank +} + +static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface) { + // This space intentionally left blank +} + +static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, + uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { + // This space intentionally left blank +} + +static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) { + // This space intentionally left blank +} + +static void pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) { + // This space intentionally left blank +} + +static void pointer_handle_axis_source(void *data, + struct wl_pointer *wl_pointer, uint32_t axis_source) { + // This space intentionally left blank +} + +static void pointer_handle_axis_stop(void *data, + struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis) { + // This space intentionally left blank +} + +static void pointer_handle_axis_discrete(void *data, + struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) { + // This space intentionally left blank +} + +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, +}; + +static void xdg_surface_handle_configure(void *data, + struct xdg_surface *xdg_surface, uint32_t serial) { + xdg_surface_ack_configure(xdg_surface, serial); + wl_egl_window_resize(egl_window, width, height, 0, 0); + draw(); +} + +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) { + width = w; + height = h; +} + +static void xdg_toplevel_handle_close(void *data, + struct xdg_toplevel *xdg_toplevel) { + exit(EXIT_SUCCESS); +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_handle_configure, + .close = xdg_toplevel_handle_close, +}; + +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, 1); + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + seat = wl_registry_bind(registry, name, &wl_seat_interface, version); + } else if (strcmp(interface, + zwp_pointer_constraints_v1_interface.name) == 0) { + pointer_constraints = wl_registry_bind(registry, name, + &zwp_pointer_constraints_v1_interface, version); + } +} + +static const struct wl_registry_listener registry_listener = { + .global = handle_global, + .global_remove = NULL, +}; + +int main(int argc, char **argv) { + if (argc != 4) { + goto invalid_args; + } + + bool lock; + if (strcmp(argv[1], "lock") == 0) { + lock = true; + } else if (strcmp(argv[1], "confine") == 0) { + lock = false; + } else { + goto invalid_args; + } + + enum zwp_pointer_constraints_v1_lifetime lifetime; + if (strcmp(argv[2], "oneshot") == 0) { + lifetime = ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT; + } else if (strcmp(argv[2], "persistent") == 0) { + lifetime = ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT; + } else { + goto invalid_args; + } + + if (strcmp(argv[3], "no-region") == 0) { + region_type = REGION_TYPE_NONE; + } else if (strcmp(argv[3], "disjoint-region") == 0) { + region_type = REGION_TYPE_DISJOINT; + } else if (strcmp(argv[3], "joint-region") == 0) { + region_type = REGION_TYPE_JOINT; + } + + struct wl_display *display = wl_display_connect(NULL); + + struct wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_display_dispatch(display); + wl_display_roundtrip(display); + + struct wl_region *disjoint_region = wl_compositor_create_region(compositor); + wl_region_add(disjoint_region, 0, 0, 255, 256); + wl_region_add(disjoint_region, 257, 0, 255, 256); + regions[REGION_TYPE_DISJOINT] = disjoint_region; + + struct wl_region *joint_region = wl_compositor_create_region(compositor); + wl_region_add(joint_region, 0, 0, 256, 256); + wl_region_add(joint_region, 256, 0, 256, 256); + wl_region_add(joint_region, 256, 256, 256, 256); + regions[REGION_TYPE_JOINT] = joint_region; + + wlr_egl_init(&egl, EGL_PLATFORM_WAYLAND_EXT, display, NULL, + WL_SHM_FORMAT_ARGB8888); + + struct wl_surface *surface = wl_compositor_create_surface(compositor); + struct xdg_surface *xdg_surface = + xdg_wm_base_get_xdg_surface(wm_base, 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); + + struct wl_pointer *pointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(pointer, &pointer_listener, surface); + + if (lock) { + locked_pointer = zwp_pointer_constraints_v1_lock_pointer( + pointer_constraints, surface, pointer, + regions[region_type], lifetime); + + zwp_locked_pointer_v1_set_cursor_position_hint(locked_pointer, + wl_fixed_from_int(128), wl_fixed_from_int(128)); + } else { + confined_pointer = zwp_pointer_constraints_v1_confine_pointer( + pointer_constraints, surface, pointer, + regions[region_type], lifetime); + } + + wl_surface_commit(surface); + + egl_window = wl_egl_window_create(surface, width, height); + egl_surface = wlr_egl_create_surface(&egl, egl_window); + + wl_display_roundtrip(display); + + draw(); + + while (wl_display_dispatch(display) != -1) {} + + return EXIT_SUCCESS; + +invalid_args: + fprintf(stderr, "pointer-constraints <lock | confine> " + "<oneshot | persistent> " + "<no-region | disjoint-rejoin | joint-region>\n"); + return EXIT_FAILURE; +} diff --git a/examples/pointer.c b/examples/pointer.c new file mode 100644 index 00000000..cc58c223 --- /dev/null +++ b/examples/pointer.c @@ -0,0 +1,403 @@ +#define _POSIX_C_SOURCE 200112L +#include <assert.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <wayland-server-protocol.h> +#include <wayland-server.h> +#include <wlr/backend.h> +#include <wlr/backend/session.h> +#include <wlr/render/gles2.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_cursor.h> +#include <wlr/types/wlr_keyboard.h> +#include <wlr/types/wlr_list.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/types/wlr_output_layout.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_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_input_device *device; + 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); +} + +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 = wlr_backend_get_renderer(wlr_output->backend); + assert(renderer); + + wlr_output_make_current(wlr_output, NULL); + wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height); + wlr_renderer_clear(renderer, state->clear_color); + wlr_output_swap_buffers(wlr_output, NULL, NULL); + wlr_renderer_end(renderer); +} + +static void handle_cursor_motion(struct wl_listener *listener, void *data) { + struct sample_state *sample = + wl_container_of(listener, sample, cursor_motion); + struct wlr_event_pointer_motion *event = data; + wlr_cursor_move(sample->cursor, event->device, 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_event_pointer_motion_absolute *event = data; + + sample->cur_x = event->x; + sample->cur_y = event->y; + + wlr_cursor_warp_absolute(sample->cursor, event->device, 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_event_pointer_button *event = data; + + float (*color)[4]; + if (event->state == WLR_BUTTON_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_event_pointer_axis *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_event_touch_up *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->device); +} + +static void handle_touch_down(struct wl_listener *listener, void *data) { + struct sample_state *sample = wl_container_of(listener, sample, touch_down); + struct wlr_event_touch_down *event = data; + struct touch_point *point = calloc(1, sizeof(struct touch_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->device); +} + +static void handle_touch_motion(struct wl_listener *listener, void *data) { + struct sample_state *sample = + wl_container_of(listener, sample, touch_motion); + struct wlr_event_touch_motion *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->device); +} + +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_event_tablet_tool_axis *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->device, event->x, event->y); + } +} + +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_event_keyboard_key *event = data; + uint32_t keycode = event->keycode + 8; + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms(keyboard->device->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); + } + } +} + +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); +} + +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); + struct sample_output *sample_output = calloc(1, sizeof(struct sample_output)); + if (!wl_list_empty(&output->modes)) { + struct wlr_output_mode *mode = wl_container_of(output->modes.prev, mode, link); + wlr_output_set_mode(output, mode); + } + 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); + + wlr_xcursor_manager_load(sample->xcursor_manager, output->scale); + wlr_xcursor_manager_set_cursor_image(sample->xcursor_manager, "left_ptr", + sample->cursor); +} + + +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); +} + +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_TOOL: + wlr_cursor_attach_input_device(state->cursor, device); + break; + + case WLR_INPUT_DEVICE_KEYBOARD:; + struct sample_keyboard *keyboard = calloc(1, sizeof(struct sample_keyboard)); + keyboard->device = device; + keyboard->state = state; + wl_signal_add(&device->events.destroy, &keyboard->destroy); + keyboard->destroy.notify = keyboard_destroy_notify; + wl_signal_add(&device->keyboard->events.key, &keyboard->key); + keyboard->key.notify = keyboard_key_notify; + struct xkb_rule_names rules = { 0 }; + rules.rules = getenv("XKB_DEFAULT_RULES"); + rules.model = getenv("XKB_DEFAULT_MODEL"); + rules.layout = getenv("XKB_DEFAULT_LAYOUT"); + rules.variant = getenv("XKB_DEFAULT_VARIANT"); + rules.options = getenv("XKB_DEFAULT_OPTIONS"); + 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_map_new_from_names(context, &rules, + XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + wlr_log(WLR_ERROR, "Failed to create XKB keymap"); + exit(1); + } + wlr_keyboard_set_keymap(device->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(display, NULL); + if (!wlr) { + exit(1); + } + state.cursor = wlr_cursor_create(); + state.layout = wlr_output_layout_create(); + 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("default", 24); + if (!state.xcursor_manager) { + wlr_log(WLR_ERROR, "Failed to load left_ptr cursor"); + return 1; + } + + wlr_xcursor_manager_set_cursor_image(state.xcursor_manager, "left_ptr", + state.cursor); + + 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); + wlr_output_layout_destroy(state.layout); +} diff --git a/examples/rotation.c b/examples/rotation.c new file mode 100644 index 00000000..7cf5727b --- /dev/null +++ b/examples/rotation.c @@ -0,0 +1,277 @@ +#define _POSIX_C_SOURCE 200112L +#include <GLES2/gl2.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-protocol.h> +#include <wayland-server.h> +#include <wlr/backend.h> +#include <wlr/backend/session.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_keyboard.h> +#include <wlr/types/wlr_output.h> +#include <wlr/types/wlr_output_layout.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_matrix.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_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_input_device *device; + 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); + + wlr_output_make_current(wlr_output, NULL); + wlr_renderer_begin(sample->renderer, wlr_output->width, wlr_output->height); + wlr_renderer_clear(sample->renderer, (float[]){0.25f, 0.25f, 0.25f, 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_texture(sample->renderer, sample->cat_texture, + wlr_output->transform_matrix, x, y, 1.0f); + } + } + + wlr_renderer_end(sample->renderer); + wlr_output_swap_buffers(wlr_output, NULL, NULL); + + 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; + } +} + +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); +} + +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); + struct sample_output *sample_output = calloc(1, sizeof(struct sample_output)); + if (!wl_list_empty(&output->modes)) { + struct wlr_output_mode *mode = wl_container_of(output->modes.prev, mode, link); + wlr_output_set_mode(output, mode); + } + sample_output->x_offs = sample_output->y_offs = 0; + sample_output->x_vel = sample_output->y_vel = 128; + + wlr_output_set_transform(output, sample->transform); + 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); +} + +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_event_keyboard_key *event = data; + uint32_t keycode = event->keycode + 8; + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms(keyboard->device->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 == WLR_KEY_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; + } + } + } +} + +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); +} + +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(struct sample_keyboard)); + keyboard->device = device; + keyboard->sample = sample; + wl_signal_add(&device->events.destroy, &keyboard->destroy); + keyboard->destroy.notify = keyboard_destroy_notify; + wl_signal_add(&device->keyboard->events.key, &keyboard->key); + keyboard->key.notify = keyboard_key_notify; + struct xkb_rule_names rules = { 0 }; + rules.rules = getenv("XKB_DEFAULT_RULES"); + rules.model = getenv("XKB_DEFAULT_MODEL"); + rules.layout = getenv("XKB_DEFAULT_LAYOUT"); + rules.variant = getenv("XKB_DEFAULT_VARIANT"); + rules.options = getenv("XKB_DEFAULT_OPTIONS"); + 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_map_new_from_names(context, &rules, + XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + wlr_log(WLR_ERROR, "Failed to create XKB keymap"); + exit(1); + } + wlr_keyboard_set_keymap(device->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: + break; + } + } + 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(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_backend_get_renderer(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, + WL_SHM_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); + } + + 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); +} diff --git a/examples/screencopy.c b/examples/screencopy.c new file mode 100644 index 00000000..82edcb9c --- /dev/null +++ b/examples/screencopy.c @@ -0,0 +1,261 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * 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 (including the next + * paragraph) 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. + */ + +#define _POSIX_C_SOURCE 200112L +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <png.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/param.h> +#include <sys/wait.h> +#include <unistd.h> +#include <wayland-client-protocol.h> +#include "wlr-screencopy-unstable-v1-client-protocol.h" + +struct format { + enum wl_shm_format wl_format; + bool is_bgr; +}; + +static struct wl_shm *shm = NULL; +static struct zwlr_screencopy_manager_v1 *screencopy_manager = NULL; +static struct wl_output *output = NULL; + +static struct { + struct wl_buffer *wl_buffer; + void *data; + enum wl_shm_format format; + int width, height, stride; + bool y_invert; +} buffer; +bool buffer_copy_done = false; + +// wl_shm_format describes little-endian formats, libpng uses big-endian +// formats (so Wayland's ABGR is libpng's RGBA). +static const struct format formats[] = { + {WL_SHM_FORMAT_XRGB8888, true}, + {WL_SHM_FORMAT_ARGB8888, true}, + {WL_SHM_FORMAT_XBGR8888, false}, + {WL_SHM_FORMAT_ABGR8888, false}, +}; + +static struct wl_buffer *create_shm_buffer(enum wl_shm_format fmt, + int width, int height, int stride, void **data_out) { + int size = stride * height; + + const char shm_name[] = "/wlroots-screencopy"; + int fd = shm_open(shm_name, O_RDWR | O_CREAT | O_EXCL, 0); + if (fd < 0) { + fprintf(stderr, "shm_open failed\n"); + return NULL; + } + shm_unlink(shm_name); + + int ret; + while ((ret = ftruncate(fd, size)) == EINTR) { + // No-op + } + if (ret < 0) { + close(fd); + fprintf(stderr, "ftruncate failed\n"); + return NULL; + } + + void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %m\n"); + close(fd); + return NULL; + } + + struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); + close(fd); + struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height, + stride, fmt); + wl_shm_pool_destroy(pool); + + *data_out = data; + return buffer; +} + +static void frame_handle_buffer(void *data, + struct zwlr_screencopy_frame_v1 *frame, uint32_t format, + uint32_t width, uint32_t height, uint32_t stride) { + buffer.format = format; + buffer.width = width; + buffer.height = height; + buffer.stride = stride; + buffer.wl_buffer = + create_shm_buffer(format, width, height, stride, &buffer.data); + if (buffer.wl_buffer == NULL) { + fprintf(stderr, "failed to create buffer\n"); + exit(EXIT_FAILURE); + } + + zwlr_screencopy_frame_v1_copy(frame, buffer.wl_buffer); +} + +static void frame_handle_flags(void *data, + struct zwlr_screencopy_frame_v1 *frame, uint32_t flags) { + buffer.y_invert = flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT; +} + +static void frame_handle_ready(void *data, + struct zwlr_screencopy_frame_v1 *frame, uint32_t tv_sec_hi, + uint32_t tv_sec_lo, uint32_t tv_nsec) { + buffer_copy_done = true; +} + +static void frame_handle_failed(void *data, + struct zwlr_screencopy_frame_v1 *frame) { + fprintf(stderr, "failed to copy frame\n"); + exit(EXIT_FAILURE); +} + +static const struct zwlr_screencopy_frame_v1_listener frame_listener = { + .buffer = frame_handle_buffer, + .flags = frame_handle_flags, + .ready = frame_handle_ready, + .failed = frame_handle_failed, +}; + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + if (strcmp(interface, wl_output_interface.name) == 0 && output == NULL) { + output = wl_registry_bind(registry, name, &wl_output_interface, 1); + } else if (strcmp(interface, wl_shm_interface.name) == 0) { + shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + } else if (strcmp(interface, + zwlr_screencopy_manager_v1_interface.name) == 0) { + screencopy_manager = wl_registry_bind(registry, name, + &zwlr_screencopy_manager_v1_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 write_image(char *filename, enum wl_shm_format wl_fmt, int width, + int height, int stride, bool y_invert, png_bytep data) { + const struct format *fmt = NULL; + for (size_t i = 0; i < sizeof(formats) / sizeof(formats[0]); ++i) { + if (formats[i].wl_format == wl_fmt) { + fmt = &formats[i]; + break; + } + } + if (fmt == NULL) { + fprintf(stderr, "unsupported format %"PRIu32"\n", wl_fmt); + exit(EXIT_FAILURE); + } + + FILE *f = fopen(filename, "wb"); + if (f == NULL) { + fprintf(stderr, "failed to open output file\n"); + exit(EXIT_FAILURE); + } + + png_structp png = + png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + png_infop info = png_create_info_struct(png); + + png_init_io(png, f); + + png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGBA, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + if (fmt->is_bgr) { + png_set_bgr(png); + } + + png_write_info(png, info); + + for (size_t i = 0; i < (size_t)height; ++i) { + png_bytep row; + if (y_invert) { + row = data + (height - i - 1) * stride; + } else { + row = data + i * stride; + } + png_write_row(png, row); + } + + png_write_end(png, NULL); + + png_destroy_write_struct(&png, &info); + + fclose(f); +} + +int main(int argc, char *argv[]) { + struct wl_display * display = wl_display_connect(NULL); + if (display == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return EXIT_FAILURE; + } + + struct wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_display_dispatch(display); + wl_display_roundtrip(display); + + if (shm == NULL) { + fprintf(stderr, "compositor is missing wl_shm\n"); + return EXIT_FAILURE; + } + if (screencopy_manager == NULL) { + fprintf(stderr, "compositor doesn't support wlr-screencopy-unstable-v1\n"); + return EXIT_FAILURE; + } + if (output == NULL) { + fprintf(stderr, "no output available\n"); + return EXIT_FAILURE; + } + + struct zwlr_screencopy_frame_v1 *frame = + zwlr_screencopy_manager_v1_capture_output(screencopy_manager, 0, output); + zwlr_screencopy_frame_v1_add_listener(frame, &frame_listener, NULL); + + while (!buffer_copy_done && wl_display_dispatch(display) != -1) { + // This space is intentionally left blank + } + + write_image("wayland-screenshot.png", buffer.format, buffer.width, + buffer.height, buffer.stride, buffer.y_invert, buffer.data); + wl_buffer_destroy(buffer.wl_buffer); + + return EXIT_SUCCESS; +} diff --git a/examples/screenshot.c b/examples/screenshot.c new file mode 100644 index 00000000..914f3994 --- /dev/null +++ b/examples/screenshot.c @@ -0,0 +1,237 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * 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 (including the next + * paragraph) 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. + */ + +#define _POSIX_C_SOURCE 200112L +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/param.h> +#include <sys/wait.h> +#include <unistd.h> +#include <wayland-client.h> +#include "screenshooter-client-protocol.h" + +static struct wl_shm *shm = NULL; +static struct orbital_screenshooter *screenshooter = NULL; +static struct wl_list output_list; +static bool buffer_copy_done; + +struct screenshooter_output { + struct wl_output *output; + int width, height; + struct wl_list link; +}; + +static void output_handle_geometry(void *data, struct wl_output *wl_output, + int x, int y, int physical_width, int physical_height, int subpixel, + const char *make, const char *model, int transform) { + // No-op +} + +static void output_handle_mode(void *data, struct wl_output *wl_output, + uint32_t flags, int width, int height, int refresh) { + struct screenshooter_output *output = wl_output_get_user_data(wl_output); + + if (wl_output == output->output && (flags & WL_OUTPUT_MODE_CURRENT)) { + output->width = width; + output->height = height; + } +} + +static void output_handle_done(void *data, struct wl_output *wl_output) { + // No-op +} + +static const struct wl_output_listener output_listener = { + .geometry = output_handle_geometry, + .mode = output_handle_mode, + .done = output_handle_done, +}; + +static void screenshot_done(void *data, struct orbital_screenshot *screenshot) { + buffer_copy_done = true; +} + +static const struct orbital_screenshot_listener screenshot_listener = { + .done = screenshot_done, +}; + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + static struct screenshooter_output *output; + + if (strcmp(interface, "wl_output") == 0) { + output = calloc(1, sizeof(*output)); + output->output = wl_registry_bind(registry, name, &wl_output_interface, + 1); + wl_list_insert(&output_list, &output->link); + wl_output_add_listener(output->output, &output_listener, output); + } else if (strcmp(interface, "wl_shm") == 0) { + shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + } else if (strcmp(interface, "orbital_screenshooter") == 0) { + screenshooter = wl_registry_bind(registry, name, + &orbital_screenshooter_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 struct wl_buffer *create_shm_buffer(int width, int height, + void **data_out) { + int stride = width * 4; + int size = stride * height; + + const char shm_name[] = "/wlroots-screenshot"; + int fd = shm_open(shm_name, O_RDWR | O_CREAT | O_EXCL, 0); + if (fd < 0) { + fprintf(stderr, "shm_open failed\n"); + return NULL; + } + shm_unlink(shm_name); + + int ret; + while ((ret = ftruncate(fd, size)) == EINTR) { + // No-op + } + if (ret < 0) { + close(fd); + fprintf(stderr, "ftruncate failed\n"); + return NULL; + } + + void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %m\n"); + close(fd); + return NULL; + } + + struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); + close(fd); + struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height, + stride, WL_SHM_FORMAT_XRGB8888); + wl_shm_pool_destroy(pool); + + *data_out = data; + + return buffer; +} + +static void write_image(const char *filename, int width, int height, + void *data) { + char size[10 + 1 + 10 + 2 + 1]; // int32_t are max 10 digits + sprintf(size, "%dx%d+0", width, height); + + int fd[2]; + if (pipe(fd) != 0) { + fprintf(stderr, "cannot create pipe: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + pid_t child = fork(); + if (child < 0) { + fprintf(stderr, "fork() failed\n"); + exit(EXIT_FAILURE); + } else if (child != 0) { + close(fd[0]); + if (write(fd[1], data, 4 * width * height) < 0) { + fprintf(stderr, "write() failed: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + close(fd[1]); + waitpid(child, NULL, 0); + } else { + close(fd[1]); + if (dup2(fd[0], 0) != 0) { + fprintf(stderr, "cannot dup the pipe\n"); + exit(EXIT_FAILURE); + } + close(fd[0]); + // We requested WL_SHM_FORMAT_XRGB8888 in little endian, so that's BGRA + // in big endian. + execlp("convert", "convert", "-depth", "8", "-size", size, "bgra:-", + "-alpha", "opaque", filename, NULL); + fprintf(stderr, "cannot execute convert\n"); + exit(EXIT_FAILURE); + } +} + +int main(int argc, char *argv[]) { + struct wl_display * display = wl_display_connect(NULL); + if (display == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + wl_list_init(&output_list); + struct wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_display_dispatch(display); + wl_display_roundtrip(display); + + if (screenshooter == NULL) { + fprintf(stderr, "display doesn't support screenshooter\n"); + return -1; + } + + int i = 0; + struct screenshooter_output *output; + wl_list_for_each(output, &output_list, link) { + void *data = NULL; + struct wl_buffer *buffer = + create_shm_buffer(output->width, output->height, &data); + if (buffer == NULL) { + return -1; + } + struct orbital_screenshot *screenshot = orbital_screenshooter_shoot( + screenshooter, output->output, buffer); + orbital_screenshot_add_listener(screenshot, &screenshot_listener, + screenshot); + buffer_copy_done = false; + while (!buffer_copy_done) { + wl_display_roundtrip(display); + } + + char filename[24 + 10]; // int32_t are max 10 digits + snprintf(filename, sizeof(filename), "wayland-screenshot-%d.png", i); + + write_image(filename, output->width, output->height, data); + wl_buffer_destroy(buffer); + ++i; + } + + return EXIT_SUCCESS; +} diff --git a/examples/simple.c b/examples/simple.c new file mode 100644 index 00000000..e1c10906 --- /dev/null +++ b/examples/simple.c @@ -0,0 +1,185 @@ +#define _POSIX_C_SOURCE 200112L +#include <GLES2/gl2.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <wayland-server.h> +#include <wlr/backend.h> +#include <wlr/backend/session.h> +#include <wlr/types/wlr_output.h> +#include <wlr/types/wlr_input_device.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 timespec last_frame; + float color[3]; + 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_input_device *device; + struct wl_listener key; + struct wl_listener destroy; +}; + +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); + + 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; + } + + wlr_output_make_current(sample_output->output, NULL); + + glClearColor(sample->color[0], sample->color[1], sample->color[2], 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + wlr_output_swap_buffers(sample_output->output, NULL, NULL); + sample->last_frame = now; +} + +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); +} + +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); + struct sample_output *sample_output = + calloc(1, sizeof(struct sample_output)); + if (!wl_list_empty(&output->modes)) { + struct wlr_output_mode *mode = + wl_container_of(output->modes.prev, mode, link); + wlr_output_set_mode(output, mode); + } + 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; +} + +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_event_keyboard_key *event = data; + uint32_t keycode = event->keycode + 8; + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms(keyboard->device->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); + } + } +} + +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); +} + +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(struct sample_keyboard)); + keyboard->device = device; + keyboard->sample = sample; + wl_signal_add(&device->events.destroy, &keyboard->destroy); + keyboard->destroy.notify = keyboard_destroy_notify; + wl_signal_add(&device->keyboard->events.key, &keyboard->key); + keyboard->key.notify = keyboard_key_notify; + struct xkb_rule_names rules = { 0 }; + rules.rules = getenv("XKB_DEFAULT_RULES"); + rules.model = getenv("XKB_DEFAULT_MODEL"); + rules.layout = getenv("XKB_DEFAULT_LAYOUT"); + rules.variant = getenv("XKB_DEFAULT_VARIANT"); + rules.options = getenv("XKB_DEFAULT_OPTIONS"); + 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_map_new_from_names(context, &rules, + XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + wlr_log(WLR_ERROR, "Failed to create XKB keymap"); + exit(1); + } + wlr_keyboard_set_keymap(device->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 }, + .dec = 0, + .last_frame = { 0 }, + .display = display + }; + struct wlr_backend *backend = wlr_backend_autocreate(display, NULL); + if (!backend) { + exit(1); + } + 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); +} diff --git a/examples/tablet.c b/examples/tablet.c new file mode 100644 index 00000000..fad30d52 --- /dev/null +++ b/examples/tablet.c @@ -0,0 +1,381 @@ +#define _XOPEN_SOURCE 600 +#include <GLES2/gl2.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <wayland-server-protocol.h> +#include <wayland-server.h> +#include <wlr/backend.h> +#include <wlr/backend/session.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/types/wlr_output.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_tablet_pad.h> +#include <wlr/types/wlr_tablet_tool.h> +#include <wlr/util/log.h> +#include <xkbcommon/xkbcommon.h> + +struct sample_state { + struct wl_display *display; + struct wlr_renderer *renderer; + 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_input_device *device; + 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_input_device *device; + 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_input_device *device; + 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); + + wlr_output_make_current(wlr_output, NULL); + wlr_renderer_begin(sample->renderer, wlr_output->width, wlr_output->height); + wlr_renderer_clear(sample->renderer, (float[]){0.25f, 0.25f, 0.25f, 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; + const struct wlr_box box = { + .x = left, .y = top, + .width = pad_width, .height = pad_height, + }; + wlr_render_rect(sample->renderer, &box, sample->pad_color, + wlr_output->transform_matrix); + + 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), + }; + float matrix[9]; + wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, + sample->ring, wlr_output->transform_matrix); + wlr_render_quad_with_matrix(sample->renderer, tool_color, matrix); + + box.x += sample->x_tilt; + box.y += sample->y_tilt; + box.width /= 2; + box.height /= 2; + wlr_render_rect(sample->renderer, &box, tool_color, + wlr_output->transform_matrix); + } + + wlr_renderer_end(sample->renderer); + wlr_output_swap_buffers(wlr_output, NULL, NULL); + 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_event_tablet_tool_axis *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_event_tablet_tool_proximity *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_event_tablet_tool_button *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_event_tablet_pad_button *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_event_tablet_pad_ring *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); +} + +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); +} + +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); + struct sample_output *sample_output = calloc(1, sizeof(struct sample_output)); + if (!wl_list_empty(&output->modes)) { + struct wlr_output_mode *mode = wl_container_of(output->modes.prev, mode, link); + wlr_output_set_mode(output, mode); + } + 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; +} + +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_event_keyboard_key *event = data; + uint32_t keycode = event->keycode + 8; + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms(keyboard->device->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); + } + } +} + +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); +} + +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(struct sample_keyboard)); + keyboard->device = device; + keyboard->sample = sample; + wl_signal_add(&device->events.destroy, &keyboard->destroy); + keyboard->destroy.notify = keyboard_destroy_notify; + wl_signal_add(&device->keyboard->events.key, &keyboard->key); + keyboard->key.notify = keyboard_key_notify; + struct xkb_rule_names rules = { 0 }; + rules.rules = getenv("XKB_DEFAULT_RULES"); + rules.model = getenv("XKB_DEFAULT_MODEL"); + rules.layout = getenv("XKB_DEFAULT_LAYOUT"); + rules.variant = getenv("XKB_DEFAULT_VARIANT"); + rules.options = getenv("XKB_DEFAULT_OPTIONS"); + 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_map_new_from_names(context, &rules, + XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + wlr_log(WLR_ERROR, "Failed to create XKB keymap"); + exit(1); + } + wlr_keyboard_set_keymap(device->keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + break; + case WLR_INPUT_DEVICE_TABLET_PAD:; + struct tablet_pad_state *pstate = calloc(sizeof(struct tablet_pad_state), 1); + pstate->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(&device->tablet_pad->events.button, &pstate->button); + pstate->ring.notify = tablet_pad_ring_notify; + wl_signal_add(&device->tablet_pad->events.ring, &pstate->ring); + wl_list_insert(&sample->tablet_pads, &pstate->link); + break; + case WLR_INPUT_DEVICE_TABLET_TOOL: + sample->width_mm = device->width_mm == 0 ? + 20 : device->width_mm; + sample->height_mm = device->height_mm == 0 ? + 10 : device->height_mm; + + struct tablet_tool_state *tstate = calloc(sizeof(struct tablet_tool_state), 1); + tstate->device = device; + 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(&device->tablet->events.axis, &tstate->axis); + tstate->proximity.notify = tablet_tool_proximity_notify; + wl_signal_add(&device->tablet->events.proximity, &tstate->proximity); + tstate->button.notify = tablet_tool_button_notify; + wl_signal_add(&device->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(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_backend_get_renderer(wlr); + if (!state.renderer) { + wlr_log(WLR_ERROR, "Could not start compositor, OOM"); + exit(EXIT_FAILURE); + } + 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); +} diff --git a/examples/text-input.c b/examples/text-input.c new file mode 100644 index 00000000..3ccd99a0 --- /dev/null +++ b/examples/text-input.c @@ -0,0 +1,394 @@ +#define _POSIX_C_SOURCE 200809L +#include <GLES2/gl2.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wayland-client.h> +#include <wayland-egl.h> +#include <wlr/render/egl.h> +#include "text-input-unstable-v3-client-protocol.h" +#include "xdg-shell-client-protocol.h" + +const char usage[] = "Usage: text-input [seconds [width height]]\n\ +\n\ +Creates a xdg-toplevel using the text-input protocol.\n\ +It will be solid black when it has no text input focus, yellow when it\n\ +has focus, and red when it was notified that the focus moved away\n\ +but still didn't give up the text input ability.\n\ +\n\ +The \"seconds\" argument is optional and defines the delay between getting\n\ +notified of lost focus and releasing text input.\n\ +\n\ +The \"width\" and \"height\" arguments define the window shape.\n\ +\n\ +The console will print the internal state of the text field:\n\ +- the text in the 1st line\n\ +- \".\" under each preedit character\n\ +- \"_\" under each selected preedit character\n\ +- \"|\" at the cursor position if there are no selected characters in the\n\ +preedit.\n\ +\n\ +The cursor positions may be inaccurate, especially in presence of zero-width\n\ +characters or non-monospaced fonts.\n"; + +struct text_input_state { + char *commit; + struct { + char *text; + int32_t cursor_begin; + int32_t cursor_end; + } preedit; + struct { + uint32_t after_length; + uint32_t before_length; + } delete_surrounding; +}; + +static struct text_input_state pending = {0}; +static struct text_input_state current = {0}; +static bool entered = false; +static uint32_t serial; +static char *buffer; // text buffer +// cursor is not present, there's no way to move it outside of preedit + +static int sleeptime = 0; +static int width = 100, height = 200; +static int enabled = 0; + +static struct wl_display *display = NULL; +static struct wl_compositor *compositor = NULL; +static struct wl_seat *seat = NULL; +static struct xdg_wm_base *wm_base = NULL; +static struct zwp_text_input_manager_v3 *text_input_manager = NULL; +static struct zwp_text_input_v3 *text_input = NULL; + +struct wlr_egl egl; +struct wl_egl_window *egl_window; +struct wlr_egl_surface *egl_surface; + +static void draw(void) { + eglMakeCurrent(egl.display, egl_surface, egl_surface, egl.context); + + float color[] = {1.0, 1.0, 0.0, 1.0}; + color[0] = enabled * 1.0; + color[1] = entered * 1.0; + + glViewport(0, 0, width, height); + glClearColor(color[0], color[1], color[2], 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + eglSwapBuffers(egl.display, egl_surface); +} + +static size_t utf8_strlen(char *str) { + size_t cp_count = 0; + for (; *str != '\0'; str++) { + if ((*str & 0xc0) != 0x80) { + cp_count++; + } + } + return cp_count; +} + +static size_t utf8_offset(char *utf8_str, size_t byte_offset) { + size_t cp_count = 0; + for (char *c = utf8_str; c < utf8_str + byte_offset; c++) { + if ((*c & 0xc0) != 0x80) { + cp_count++; + } + } + return cp_count; +} + +// TODO: would be nicer to have this text display inside the window +static void show_status() { + printf("State %d:", serial); + if (!enabled) { + printf(" disabled"); + } + + char *preedit_text = current.preedit.text; + if (!preedit_text) { + preedit_text = ""; + } + + printf("\n"); + printf("%s", buffer); + printf("%s\n", preedit_text); + + // Positioning of the cursor requires UTF8 offsets to match monospaced + // glyphs + for (unsigned i = 0; i < utf8_strlen(buffer); i++) { + printf(" "); + } + char *cursor_mark = calloc(utf8_strlen(preedit_text) + 2, sizeof(char)); + for (unsigned i = 0; i < utf8_strlen(preedit_text); i++) { + cursor_mark[i] = '.'; + } + if (current.preedit.cursor_begin == -1 + && current.preedit.cursor_end == -1) { + goto end; + } + if (current.preedit.cursor_begin == -1 + || current.preedit.cursor_end == -1) { + printf("Only one cursor side is defined: %d to %d\n", + current.preedit.cursor_begin, current.preedit.cursor_end); + goto end; + } + + if ((unsigned)current.preedit.cursor_begin > strlen(preedit_text) + || (unsigned)current.preedit.cursor_begin > strlen(preedit_text)) { + printf("Cursor out of bounds\n"); + goto end; + } + + if (current.preedit.cursor_begin == current.preedit.cursor_end) { + cursor_mark[utf8_offset(preedit_text, current.preedit.cursor_begin)] + = '|'; + goto print; + } + + if (current.preedit.cursor_begin > current.preedit.cursor_end) { + printf("End cursor is before start cursor\n"); + goto end; + } + + // negative offsets already checked before + for (unsigned i = utf8_offset(preedit_text, current.preedit.cursor_begin); + i < utf8_offset(preedit_text, current.preedit.cursor_end); i++) { + cursor_mark[i] = '_'; + } +print: + printf("%s\n", cursor_mark); +end: + free(cursor_mark); +} + +static void commit(struct zwp_text_input_v3 *text_input) { + zwp_text_input_v3_commit(text_input); + serial++; +} + +static void send_status_update(struct zwp_text_input_v3 *text_input) { + zwp_text_input_v3_set_surrounding_text(text_input, buffer, strlen(buffer), strlen(buffer)); + zwp_text_input_v3_set_text_change_cause(text_input, ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD); + commit(text_input); +} + +static void text_input_handle_enter(void *data, + struct zwp_text_input_v3 *zwp_text_input_v3, + struct wl_surface *surface) { + entered = true; + zwp_text_input_v3_enable(zwp_text_input_v3); + commit(zwp_text_input_v3); + enabled = true; + draw(); + show_status(); +} + +static void text_input_handle_leave(void *data, + struct zwp_text_input_v3 *zwp_text_input_v3, + struct wl_surface *surface) { + entered = false; + draw(); + wl_display_roundtrip(display); + sleep(sleeptime); + zwp_text_input_v3_disable(zwp_text_input_v3); + commit(zwp_text_input_v3); + enabled = false; + draw(); + show_status(); +} + +static void text_input_commit_string(void *data, + struct zwp_text_input_v3 *zwp_text_input_v3, + const char *text) { + free(pending.commit); + pending.commit = strdup(text); +} + +static void text_input_delete_surrounding_text(void *data, + struct zwp_text_input_v3 *zwp_text_input_v3, + uint32_t before_length, uint32_t after_length) { + pending.delete_surrounding.before_length = before_length; + pending.delete_surrounding.after_length = after_length; +} + +static void text_input_preedit_string(void *data, + struct zwp_text_input_v3 *zwp_text_input_v3, + const char *text, int32_t cursor_begin, int32_t cursor_end) { + free(pending.preedit.text); + pending.preedit.text = strdup(text); + pending.preedit.cursor_begin = cursor_begin; + pending.preedit.cursor_end = cursor_end; +} + +static void text_input_handle_done(void *data, + struct zwp_text_input_v3 *zwp_text_input_v3, + uint32_t incoming_serial) { + if (serial != incoming_serial) { + fprintf(stderr, "Received serial %d while expecting %d\n", incoming_serial, serial); + return; + } + free(current.preedit.text); + free(current.commit); + current = pending; + struct text_input_state empty = {0}; + pending = empty; + + if (current.delete_surrounding.after_length + current.delete_surrounding.before_length > 0) { + // cursor is always after committed text, after_length != 0 will never happen + unsigned delete_before = current.delete_surrounding.before_length; + if (delete_before > strlen(buffer)) { + delete_before = strlen(buffer); + } + buffer[strlen(buffer) - delete_before] = '\0'; + } + + char *commit_string = current.commit; + if (!commit_string) { + commit_string = ""; + } + char *old_buffer = buffer; + buffer = calloc(strlen(buffer) + strlen(commit_string) + 1, sizeof(char)); // realloc may fail anyway + strcpy(buffer, old_buffer); + free(old_buffer); + strcat(buffer, commit_string); + + send_status_update(zwp_text_input_v3); + show_status(); +} + +static const struct zwp_text_input_v3_listener text_input_listener = { + .enter = text_input_handle_enter, + .leave = text_input_handle_leave, + .commit_string = text_input_commit_string, + .delete_surrounding_text = text_input_delete_surrounding_text, + .preedit_string = text_input_preedit_string, + .done = text_input_handle_done, +}; + +static void xdg_surface_handle_configure(void *data, + struct xdg_surface *xdg_surface, uint32_t serial) { + xdg_surface_ack_configure(xdg_surface, serial); + wl_egl_window_resize(egl_window, width, height, 0, 0); + draw(); +} + +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) { + width = w; + height = h; +} + +static void xdg_toplevel_handle_close(void *data, + struct xdg_toplevel *xdg_toplevel) { + exit(EXIT_SUCCESS); +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_handle_configure, + .close = xdg_toplevel_handle_close, +}; + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) { + if (strcmp(interface, "wl_compositor") == 0) { + compositor = wl_registry_bind(registry, name, + &wl_compositor_interface, 1); + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); + } else if (strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0) { + text_input_manager = wl_registry_bind(registry, name, + &zwp_text_input_manager_v3_interface, 1); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + seat = wl_registry_bind(registry, name, &wl_seat_interface, version); + } +} + +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, +}; + +int main(int argc, char **argv) { + if (argc > 1) { + if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) { + printf(usage); + return 0; + } + sleeptime = atoi(argv[1]); + if (argc > 3) { + width = atoi(argv[2]); + height = atoi(argv[3]); + } + } + + buffer = calloc(1, sizeof(char)); + + display = wl_display_connect(NULL); + if (display == NULL) { + fprintf(stderr, "Failed to create display\n"); + return EXIT_FAILURE; + } + + struct wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_display_dispatch(display); + wl_display_roundtrip(display); + + if (compositor == NULL) { + fprintf(stderr, "wl-compositor not available\n"); + return EXIT_FAILURE; + } + if (wm_base == NULL) { + fprintf(stderr, "xdg-shell not available\n"); + return EXIT_FAILURE; + } + if (text_input_manager == NULL) { + fprintf(stderr, "text-input not available\n"); + return EXIT_FAILURE; + } + + text_input = zwp_text_input_manager_v3_get_text_input(text_input_manager, seat); + + zwp_text_input_v3_add_listener(text_input, &text_input_listener, NULL); + + + wlr_egl_init(&egl, EGL_PLATFORM_WAYLAND_EXT, display, NULL, + WL_SHM_FORMAT_ARGB8888); + + struct wl_surface *surface = wl_compositor_create_surface(compositor); + struct xdg_surface *xdg_surface = + xdg_wm_base_get_xdg_surface(wm_base, 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); + + wl_surface_commit(surface); + + egl_window = wl_egl_window_create(surface, width, height); + egl_surface = wlr_egl_create_surface(&egl, egl_window); + + wl_display_roundtrip(display); + + draw(); + + while (wl_display_dispatch(display) != -1) { + // This space intentionally left blank + } + + return EXIT_SUCCESS; +} diff --git a/examples/toplevel-decoration.c b/examples/toplevel-decoration.c new file mode 100644 index 00000000..e930c417 --- /dev/null +++ b/examples/toplevel-decoration.c @@ -0,0 +1,253 @@ +#include <GLES2/gl2.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <wayland-client.h> +#include <wayland-egl.h> +#include <wlr/render/egl.h> +#include "xdg-shell-client-protocol.h" +#include "xdg-decoration-unstable-v1-client-protocol.h" + +/** + * Usage: toplevel-decoration [mode] + * Creates an xdg-toplevel supporting decoration negotiation. If `mode` is + * specified, the client will prefer this decoration mode. + */ + +static int width = 500, height = 300; + +static struct wl_compositor *compositor = NULL; +static struct xdg_wm_base *wm_base = NULL; +static struct zxdg_decoration_manager_v1 *decoration_manager = NULL; + +struct wlr_egl egl; +struct wl_egl_window *egl_window; +struct wlr_egl_surface *egl_surface; + +struct zxdg_toplevel_decoration_v1 *decoration; +enum zxdg_toplevel_decoration_v1_mode client_preferred_mode, current_mode; + +static const char *get_mode_name(enum zxdg_toplevel_decoration_v1_mode mode) { + switch (mode) { + case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE: + return "client-side decorations"; + case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE: + return "server-side decorations"; + } + abort(); +} + +static void request_preferred_mode(void) { + enum zxdg_toplevel_decoration_v1_mode mode = client_preferred_mode; + if (mode == 0) { + printf("Requesting compositor preferred mode\n"); + zxdg_toplevel_decoration_v1_unset_mode(decoration); + return; + } + if (mode == current_mode) { + return; + } + + printf("Requesting %s\n", get_mode_name(mode)); + zxdg_toplevel_decoration_v1_set_mode(decoration, mode); +} + +static void draw(void) { + eglMakeCurrent(egl.display, egl_surface, egl_surface, egl.context); + + float color[] = {1.0, 1.0, 0.0, 1.0}; + if (current_mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE) { + color[0] = 0.0; + } + + glViewport(0, 0, width, height); + glClearColor(color[0], color[1], color[2], 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + eglSwapBuffers(egl.display, egl_surface); +} + +static void xdg_surface_handle_configure(void *data, + struct xdg_surface *xdg_surface, uint32_t serial) { + xdg_surface_ack_configure(xdg_surface, serial); + wl_egl_window_resize(egl_window, width, height, 0, 0); + draw(); +} + +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) { + width = w; + height = h; +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_handle_configure, +}; + +static void decoration_handle_configure(void *data, + struct zxdg_toplevel_decoration_v1 *decoration, + enum zxdg_toplevel_decoration_v1_mode mode) { + printf("Using %s\n", get_mode_name(mode)); + current_mode = mode; +} + +static const struct zxdg_toplevel_decoration_v1_listener decoration_listener = { + .configure = decoration_handle_configure, +}; + +static void pointer_handle_enter(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t sx, wl_fixed_t sy) { + // No-op +} + +static void pointer_handle_leave(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface) { + // No-op +} + +static void pointer_handle_motion(void *data, struct wl_pointer *pointer, + uint32_t time, wl_fixed_t sx, wl_fixed_t sy) { + // No-op +} + +static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, uint32_t button, + enum wl_pointer_button_state state) { + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + // Toggle mode + if (client_preferred_mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE) { + client_preferred_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; + } else { + client_preferred_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; + } + request_preferred_mode(); + } +} + +static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, enum wl_pointer_axis axis, wl_fixed_t value) { + // No-op +} + +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, +}; + +static void seat_handle_capabilities(void *data, struct wl_seat *seat, + enum wl_seat_capability caps) { + if (caps & WL_SEAT_CAPABILITY_POINTER) { + struct wl_pointer *pointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(pointer, &pointer_listener, NULL); + } +} + +static const struct wl_seat_listener seat_listener = { + .capabilities = seat_handle_capabilities, +}; + +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, + 1); + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); + } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { + decoration_manager = wl_registry_bind(registry, name, + &zxdg_decoration_manager_v1_interface, 1); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + struct wl_seat *seat = + wl_registry_bind(registry, name, &wl_seat_interface, 1); + wl_seat_add_listener(seat, &seat_listener, NULL); + } +} + +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, +}; + +int main(int argc, char **argv) { + if (argc == 2) { + char *mode = argv[1]; + if (strcmp(mode, "client") == 0) { + client_preferred_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; + } else if (strcmp(mode, "server") == 0) { + client_preferred_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; + } else { + fprintf(stderr, "Invalid decoration mode\n"); + return EXIT_FAILURE; + } + } + + struct wl_display *display = wl_display_connect(NULL); + if (display == NULL) { + fprintf(stderr, "Failed to create display\n"); + return EXIT_FAILURE; + } + + struct wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_display_dispatch(display); + wl_display_roundtrip(display); + + if (compositor == NULL) { + fprintf(stderr, "wl-compositor not available\n"); + return EXIT_FAILURE; + } + if (wm_base == NULL) { + fprintf(stderr, "xdg-shell not available\n"); + return EXIT_FAILURE; + } + if (decoration_manager == NULL) { + fprintf(stderr, "xdg-decoration not available\n"); + return EXIT_FAILURE; + } + + wlr_egl_init(&egl, EGL_PLATFORM_WAYLAND_EXT, display, NULL, + WL_SHM_FORMAT_ARGB8888); + + struct wl_surface *surface = wl_compositor_create_surface(compositor); + struct xdg_surface *xdg_surface = + xdg_wm_base_get_xdg_surface(wm_base, surface); + struct xdg_toplevel *xdg_toplevel = xdg_surface_get_toplevel(xdg_surface); + decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( + decoration_manager, xdg_toplevel); + + wl_display_roundtrip(display); + request_preferred_mode(); + + xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL); + xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL); + zxdg_toplevel_decoration_v1_add_listener(decoration, &decoration_listener, + NULL); + wl_surface_commit(surface); + + egl_window = wl_egl_window_create(surface, width, height); + egl_surface = wlr_egl_create_surface(&egl, egl_window); + + wl_display_roundtrip(display); + + draw(); + + while (wl_display_dispatch(display) != -1) { + // This space is intentionally left blank + } + + return EXIT_SUCCESS; +} diff --git a/examples/touch.c b/examples/touch.c new file mode 100644 index 00000000..9ed20a28 --- /dev/null +++ b/examples/touch.c @@ -0,0 +1,286 @@ +#define _POSIX_C_SOURCE 200112L +#include <GLES2/gl2.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-protocol.h> +#include <wayland-server.h> +#include <wlr/backend.h> +#include <wlr/backend/session.h> +#include <wlr/types/wlr_output.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_list.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_matrix.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_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_input_device *device; + struct wl_listener destroy; + struct wl_listener down; + struct wl_listener up; + struct wl_listener motion; + 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_input_device *device; + 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); + + wlr_output_make_current(wlr_output, NULL); + wlr_renderer_begin(sample->renderer, wlr_output->width, wlr_output->height); + wlr_renderer_clear(sample->renderer, (float[]){0.25f, 0.25f, 0.25f, 1}); + + int tex_width, tex_height; + wlr_texture_get_size(sample->cat_texture, &tex_width, &tex_height); + + struct touch_point *p; + wl_list_for_each(p, &sample->touch_points, link) { + int x = (int)(p->x * width) - tex_width / 2; + int y = (int)(p->y * height) - tex_height / 2; + wlr_render_texture(sample->renderer, sample->cat_texture, + wlr_output->transform_matrix, x, y, 1.0f); + } + + wlr_renderer_end(sample->renderer); + wlr_output_swap_buffers(wlr_output, NULL, NULL); + sample->last_frame = now; +} + +static void touch_down_notify(struct wl_listener *listener, void *data) { + struct wlr_event_touch_motion *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(struct touch_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_event_touch_up *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_event_touch_motion *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_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); + free(tstate); +} + +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); +} + +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); + struct sample_output *sample_output = calloc(1, sizeof(struct sample_output)); + if (!wl_list_empty(&output->modes)) { + struct wlr_output_mode *mode = wl_container_of(output->modes.prev, mode, link); + wlr_output_set_mode(output, mode); + } + 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; +} + +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_event_keyboard_key *event = data; + uint32_t keycode = event->keycode + 8; + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms(keyboard->device->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); + } + } +} + +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); +} + +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(struct sample_keyboard)); + keyboard->device = device; + keyboard->sample = sample; + wl_signal_add(&device->events.destroy, &keyboard->destroy); + keyboard->destroy.notify = keyboard_destroy_notify; + wl_signal_add(&device->keyboard->events.key, &keyboard->key); + keyboard->key.notify = keyboard_key_notify; + struct xkb_rule_names rules = { 0 }; + rules.rules = getenv("XKB_DEFAULT_RULES"); + rules.model = getenv("XKB_DEFAULT_MODEL"); + rules.layout = getenv("XKB_DEFAULT_LAYOUT"); + rules.variant = getenv("XKB_DEFAULT_VARIANT"); + rules.options = getenv("XKB_DEFAULT_OPTIONS"); + 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_map_new_from_names(context, &rules, + XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + wlr_log(WLR_ERROR, "Failed to create XKB keymap"); + exit(1); + } + wlr_keyboard_set_keymap(device->keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + break; + case WLR_INPUT_DEVICE_TOUCH:; + struct touch_state *tstate = calloc(sizeof(struct touch_state), 1); + tstate->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(&device->touch->events.down, &tstate->down); + tstate->motion.notify = touch_motion_notify; + wl_signal_add(&device->touch->events.motion, &tstate->motion); + tstate->up.notify = touch_up_notify; + wl_signal_add(&device->touch->events.up, &tstate->up); + 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(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_backend_get_renderer(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, + WL_SHM_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); + } + + 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); +} diff --git a/glgen.sh b/glgen.sh new file mode 100755 index 00000000..fb3bb3c6 --- /dev/null +++ b/glgen.sh @@ -0,0 +1,92 @@ +#!/bin/sh + +# Generates a simple GL/EGL extension function loader. +# +# The input is a .txt file, with each function to load on its own line. +# If a line starts with a -, it is optional, and will not cause the loader +# to fail if it can't load the function. You'll need to check if that function +# is NULL before using it. + +if [ $# -ne 2 ]; then + exit 1 +fi + +SPEC=$1 +OUTDIR=$2 + +BASE=$(basename "$SPEC" .txt) +INCLUDE_GUARD=$(printf %s_%s_H "$OUTDIR" "$BASE" | tr -c [:alnum:] _ | tr [:lower:] [:upper:]) + +DECL="" +DEFN="" +LOADER="" + +DECL_FMT='extern %s %s;' +DEFN_FMT='%s %s;' +LOADER_FMT='%s = (%s)eglGetProcAddress("%s");' +CHECK_FMT='if (!%s) { + wlr_log(WLR_ERROR, "Unable to load %s"); + return false; +}' + +while read -r COMMAND; do + OPTIONAL=0 + FUNC_PTR_FMT='PFN%sPROC' + + case $COMMAND in + -*) + OPTIONAL=1 + ;; + esac + + case $COMMAND in + *WL) + FUNC_PTR_FMT='PFN%s' + ;; + esac + + COMMAND=${COMMAND#-} + FUNC_PTR=$(printf "$FUNC_PTR_FMT" "$COMMAND" | tr [:lower:] [:upper:]) + + DECL="$DECL$(printf "\n$DECL_FMT" "$FUNC_PTR" "$COMMAND")" + DEFN="$DEFN$(printf "\n$DEFN_FMT" "$FUNC_PTR" "$COMMAND")" + LOADER="$LOADER$(printf "\n$LOADER_FMT" "$COMMAND" "$FUNC_PTR" "$COMMAND")" + + if [ $OPTIONAL -eq 0 ]; then + LOADER="$LOADER$(printf "\n$CHECK_FMT" "$COMMAND" "$COMMAND")" + fi +done < "$SPEC" + +cat > "$OUTDIR/$BASE.h" << EOF +#ifndef $INCLUDE_GUARD +#define $INCLUDE_GUARD + +#include <stdbool.h> + +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> + +bool load_$BASE(void); +$DECL + +#endif +EOF + +cat > "$OUTDIR/$BASE.c" << EOF +#include <wlr/util/log.h> +#include "$BASE.h" +$DEFN + +bool load_$BASE(void) { + static bool done = false; + if (done) { + return true; + } +$LOADER + + done = true; + return true; +} +EOF diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h new file mode 100644 index 00000000..de5212d3 --- /dev/null +++ b/include/backend/drm/drm.h @@ -0,0 +1,162 @@ +#ifndef BACKEND_DRM_DRM_H +#define BACKEND_DRM_DRM_H + +#include <EGL/egl.h> +#include <gbm.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <time.h> +#include <wayland-server.h> +#include <wayland-util.h> +#include <wlr/backend/drm.h> +#include <wlr/backend/session.h> +#include <wlr/render/egl.h> +#include <xf86drmMode.h> +#include "iface.h" +#include "properties.h" +#include "renderer.h" + +struct wlr_drm_plane { + uint32_t type; + uint32_t id; + + uint32_t possible_crtcs; + + struct wlr_drm_surface surf; + struct wlr_drm_surface mgpu_surf; + + // Only used by cursor + float matrix[9]; + struct gbm_bo *cursor_bo; + bool cursor_enabled; + int32_t cursor_hotspot_x, cursor_hotspot_y; + + union wlr_drm_plane_props props; +}; + +struct wlr_drm_crtc { + uint32_t id; + + // Atomic modesetting only + uint32_t mode_id; + uint32_t gamma_lut; + drmModeAtomicReq *atomic; + + // Legacy only + drmModeCrtc *legacy_crtc; + + union { + struct { + struct wlr_drm_plane *overlay; + struct wlr_drm_plane *primary; + struct wlr_drm_plane *cursor; + }; + struct wlr_drm_plane *planes[3]; + }; + + union wlr_drm_crtc_props props; + + struct wl_list connectors; + + uint16_t *gamma_table; + size_t gamma_table_size; +}; + +struct wlr_drm_backend { + struct wlr_backend backend; + + struct wlr_drm_backend *parent; + const struct wlr_drm_interface *iface; + clockid_t clock; + + int fd; + + size_t num_crtcs; + struct wlr_drm_crtc *crtcs; + size_t num_planes; + struct wlr_drm_plane *planes; + + union { + struct { + size_t num_overlay_planes; + size_t num_primary_planes; + size_t num_cursor_planes; + }; + size_t num_type_planes[3]; + }; + + union { + struct { + struct wlr_drm_plane *overlay_planes; + struct wlr_drm_plane *primary_planes; + struct wlr_drm_plane *cursor_planes; + }; + struct wlr_drm_plane *type_planes[3]; + }; + + struct wl_display *display; + struct wl_event_source *drm_event; + + struct wl_listener display_destroy; + struct wl_listener session_signal; + struct wl_listener drm_invalidated; + + struct wl_list outputs; + + struct wlr_drm_renderer renderer; + struct wlr_session *session; +}; + +enum wlr_drm_connector_state { + // Connector is available but no output is plugged in + WLR_DRM_CONN_DISCONNECTED, + // An output just has been plugged in and is waiting for a modeset + WLR_DRM_CONN_NEEDS_MODESET, + WLR_DRM_CONN_CLEANUP, + WLR_DRM_CONN_CONNECTED, + // Connector disappeared, waiting for being destroyed on next page-flip + WLR_DRM_CONN_DISAPPEARED, +}; + +struct wlr_drm_mode { + struct wlr_output_mode wlr_mode; + drmModeModeInfo drm_mode; +}; + +struct wlr_drm_connector { + struct wlr_output output; + + enum wlr_drm_connector_state state; + struct wlr_output_mode *desired_mode; + bool desired_enabled; + uint32_t id; + + struct wlr_drm_crtc *crtc; + uint32_t possible_crtc; + + union wlr_drm_connector_props props; + + uint32_t width, height; + int32_t cursor_x, cursor_y; + + drmModeCrtc *old_crtc; + + bool pageflip_pending; + struct wl_event_source *retry_pageflip; + struct wl_list link; +}; + +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 restore_drm_outputs(struct wlr_drm_backend *drm); +void scan_drm_connectors(struct wlr_drm_backend *state); +int handle_drm_event(int fd, uint32_t mask, void *data); +bool enable_drm_connector(struct wlr_output *output, bool enable); +bool set_drm_connector_gamma(struct wlr_output *output, size_t size, + const uint16_t *r, const uint16_t *g, const uint16_t *b); + +#endif diff --git a/include/backend/drm/iface.h b/include/backend/drm/iface.h new file mode 100644 index 00000000..5308b136 --- /dev/null +++ b/include/backend/drm/iface.h @@ -0,0 +1,41 @@ +#ifndef BACKEND_DRM_IFACE_H +#define BACKEND_DRM_IFACE_H + +#include <gbm.h> +#include <stdbool.h> +#include <stdint.h> +#include <xf86drm.h> +#include <xf86drmMode.h> + +struct wlr_drm_backend; +struct wlr_drm_connector; +struct wlr_drm_crtc; + +// Used to provide atomic or legacy DRM functions +struct wlr_drm_interface { + // Enable or disable DPMS for connector + bool (*conn_enable)(struct wlr_drm_backend *drm, + struct wlr_drm_connector *conn, bool enable); + // Pageflip on crtc. If mode is non-NULL perform a full modeset using it. + bool (*crtc_pageflip)(struct wlr_drm_backend *drm, + struct wlr_drm_connector *conn, struct wlr_drm_crtc *crtc, + uint32_t fb_id, drmModeModeInfo *mode); + // Enable the cursor buffer on crtc. Set bo to NULL to disable + bool (*crtc_set_cursor)(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, struct gbm_bo *bo); + // Move the cursor on crtc + bool (*crtc_move_cursor)(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, int x, int y); + // Set the gamma lut on crtc + bool (*crtc_set_gamma)(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, size_t size, + uint16_t *r, uint16_t *g, uint16_t *b); + // Get the gamma lut size of a crtc + size_t (*crtc_get_gamma_size)(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc); +}; + +extern const struct wlr_drm_interface atomic_iface; +extern const struct wlr_drm_interface legacy_iface; + +#endif diff --git a/include/backend/drm/properties.h b/include/backend/drm/properties.h new file mode 100644 index 00000000..b4d43bdd --- /dev/null +++ b/include/backend/drm/properties.h @@ -0,0 +1,72 @@ +#ifndef BACKEND_DRM_PROPERTIES_H +#define BACKEND_DRM_PROPERTIES_H + +#include <stdbool.h> +#include <stdint.h> + +/* + * These types contain the property ids for several DRM objects. + * See https://01.org/linuxgraphics/gfx-docs/drm/gpu/drm-kms.html#kms-properties + * for more details. + */ + +union wlr_drm_connector_props { + struct { + uint32_t edid; + uint32_t dpms; + uint32_t link_status; // not guaranteed to exist + uint32_t path; + + // 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 rotation; + uint32_t scaling_mode; + + // atomic-modesetting only + + uint32_t active; + uint32_t mode_id; + uint32_t gamma_lut; + uint32_t gamma_lut_size; + }; + uint32_t props[6]; +}; + +union wlr_drm_plane_props { + struct { + uint32_t type; + uint32_t rotation; // 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 props[12]; +}; + +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); + +#endif diff --git a/include/backend/drm/renderer.h b/include/backend/drm/renderer.h new file mode 100644 index 00000000..575758de --- /dev/null +++ b/include/backend/drm/renderer.h @@ -0,0 +1,57 @@ +#ifndef BACKEND_DRM_RENDERER_H +#define BACKEND_DRM_RENDERER_H + +#include <EGL/egl.h> +#include <gbm.h> +#include <stdbool.h> +#include <stdint.h> +#include <wlr/backend.h> +#include <wlr/render/wlr_renderer.h> + +struct wlr_drm_backend; +struct wlr_drm_plane; + +struct wlr_drm_renderer { + int fd; + struct gbm_device *gbm; + struct wlr_egl egl; + + struct wlr_renderer *wlr_rend; +}; + +struct wlr_drm_surface { + struct wlr_drm_renderer *renderer; + + uint32_t width; + uint32_t height; + + struct gbm_surface *gbm; + EGLSurface egl; + + struct gbm_bo *front; + struct gbm_bo *back; +}; + +bool init_drm_renderer(struct wlr_drm_backend *drm, + struct wlr_drm_renderer *renderer, wlr_renderer_create_func_t create_render); +void finish_drm_renderer(struct wlr_drm_renderer *renderer); + +bool init_drm_surface(struct wlr_drm_surface *surf, + struct wlr_drm_renderer *renderer, uint32_t width, uint32_t height, + uint32_t format, uint32_t flags); + +bool init_drm_plane_surfaces(struct wlr_drm_plane *plane, + struct wlr_drm_backend *drm, int32_t width, uint32_t height, + uint32_t format); + +void finish_drm_surface(struct wlr_drm_surface *surf); +bool make_drm_surface_current(struct wlr_drm_surface *surf, int *buffer_age); +struct gbm_bo *swap_drm_surface_buffers(struct wlr_drm_surface *surf, + pixman_region32_t *damage); +struct gbm_bo *get_drm_surface_front(struct wlr_drm_surface *surf); +void post_drm_surface(struct wlr_drm_surface *surf); +struct gbm_bo *copy_drm_surface_mgpu(struct wlr_drm_surface *dest, + struct gbm_bo *src); +bool export_drm_bo(struct gbm_bo *bo, struct wlr_dmabuf_attributes *attribs); + +#endif diff --git a/include/backend/drm/util.h b/include/backend/drm/util.h new file mode 100644 index 00000000..e159d716 --- /dev/null +++ b/include/backend/drm/util.h @@ -0,0 +1,41 @@ +#ifndef BACKEND_DRM_UTIL_H +#define BACKEND_DRM_UTIL_H + +#include <stdint.h> +#include <wlr/types/wlr_output.h> +#include <xf86drm.h> +#include <xf86drmMode.h> + +// Calculates a more accurate refresh rate (mHz) than what mode itself provides +int32_t calculate_refresh_rate(const drmModeModeInfo *mode); +// Populates the make/model/phys_{width,height} of output from the edid data +void parse_edid(struct wlr_output *restrict output, size_t len, + const uint8_t *data); +// Returns the string representation of a DRM output type +const char *conn_get_name(uint32_t type_id); +// Returns the DRM framebuffer id for a gbm_bo +uint32_t get_fb_for_bo(struct gbm_bo *bo); + +// 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 diff --git a/include/backend/headless.h b/include/backend/headless.h new file mode 100644 index 00000000..35280862 --- /dev/null +++ b/include/backend/headless.h @@ -0,0 +1,40 @@ +#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 wlr_egl egl; + struct wlr_renderer *renderer; + struct wl_display *display; + struct wl_list outputs; + struct wl_list input_devices; + struct wl_listener display_destroy; + bool started; +}; + +struct wlr_headless_output { + struct wlr_output wlr_output; + + struct wlr_headless_backend *backend; + struct wl_list link; + + void *egl_surface; + struct wl_event_source *frame_timer; + int frame_delay; // ms +}; + +struct wlr_headless_input_device { + struct wlr_input_device wlr_input_device; + + struct wlr_headless_backend *backend; +}; + +struct wlr_headless_backend *headless_backend_from_backend( + struct wlr_backend *wlr_backend); + +#endif diff --git a/include/backend/libinput.h b/include/backend/libinput.h new file mode 100644 index 00000000..f4886956 --- /dev/null +++ b/include/backend/libinput.h @@ -0,0 +1,94 @@ +#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/interfaces/wlr_input_device.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_list.h> + +struct wlr_libinput_backend { + struct wlr_backend backend; + + struct wlr_session *session; + struct wl_display *display; + + struct libinput *libinput_context; + struct wl_event_source *input_event; + + struct wl_listener display_destroy; + struct wl_listener session_signal; + + struct wlr_list wlr_device_lists; // list of struct wl_list +}; + +struct wlr_libinput_input_device { + struct wlr_input_device wlr_input_device; + + struct libinput_device *handle; +}; + +uint32_t usec_to_msec(uint64_t usec); + +void handle_libinput_event(struct wlr_libinput_backend *state, + struct libinput_event *event); + +struct wlr_input_device *get_appropriate_device( + enum wlr_input_device_type desired_type, + struct libinput_device *device); + +struct wlr_keyboard *create_libinput_keyboard( + struct libinput_device *device); +void handle_keyboard_key(struct libinput_event *event, + struct libinput_device *device); + +struct wlr_pointer *create_libinput_pointer( + struct libinput_device *device); +void handle_pointer_motion(struct libinput_event *event, + struct libinput_device *device); +void handle_pointer_motion_abs(struct libinput_event *event, + struct libinput_device *device); +void handle_pointer_button(struct libinput_event *event, + struct libinput_device *device); +void handle_pointer_axis(struct libinput_event *event, + struct libinput_device *device); + +struct wlr_switch *create_libinput_switch( + struct libinput_device *device); +void handle_switch_toggle(struct libinput_event *event, + struct libinput_device *device); + +struct wlr_touch *create_libinput_touch( + struct libinput_device *device); +void handle_touch_down(struct libinput_event *event, + struct libinput_device *device); +void handle_touch_up(struct libinput_event *event, + struct libinput_device *device); +void handle_touch_motion(struct libinput_event *event, + struct libinput_device *device); +void handle_touch_cancel(struct libinput_event *event, + struct libinput_device *device); + +struct wlr_tablet *create_libinput_tablet( + struct libinput_device *device); +void handle_tablet_tool_axis(struct libinput_event *event, + struct libinput_device *device); +void handle_tablet_tool_proximity(struct libinput_event *event, + struct libinput_device *device); +void handle_tablet_tool_tip(struct libinput_event *event, + struct libinput_device *device); +void handle_tablet_tool_button(struct libinput_event *event, + struct libinput_device *device); + +struct wlr_tablet_pad *create_libinput_tablet_pad( + struct libinput_device *device); +void handle_tablet_pad_button(struct libinput_event *event, + struct libinput_device *device); +void handle_tablet_pad_ring(struct libinput_event *event, + struct libinput_device *device); +void handle_tablet_pad_strip(struct libinput_event *event, + struct libinput_device *device); + +#endif diff --git a/include/backend/multi.h b/include/backend/multi.h new file mode 100644 index 00000000..2f5f1bd4 --- /dev/null +++ b/include/backend/multi.h @@ -0,0 +1,23 @@ +#ifndef BACKEND_MULTI_H +#define BACKEND_MULTI_H + +#include <wayland-util.h> +#include <wlr/backend/interface.h> +#include <wlr/backend/multi.h> +#include <wlr/backend/session.h> + +struct wlr_multi_backend { + struct wlr_backend backend; + struct wlr_session *session; + + struct wl_list backends; + + struct wl_listener display_destroy; + + struct { + struct wl_signal backend_add; + struct wl_signal backend_remove; + } events; +}; + +#endif diff --git a/include/backend/session/direct-ipc.h b/include/backend/session/direct-ipc.h new file mode 100644 index 00000000..c660b58f --- /dev/null +++ b/include/backend/session/direct-ipc.h @@ -0,0 +1,12 @@ +#ifndef BACKEND_SESSION_DIRECT_IPC_H +#define BACKEND_SESSION_DIRECT_IPC_H + +#include <sys/types.h> + +int direct_ipc_open(int sock, const char *path); +void direct_ipc_setmaster(int sock, int fd); +void direct_ipc_dropmaster(int sock, int fd); +void direct_ipc_finish(int sock, pid_t pid); +int direct_ipc_init(pid_t *pid_out); + +#endif diff --git a/include/backend/wayland.h b/include/backend/wayland.h new file mode 100644 index 00000000..dbc309ca --- /dev/null +++ b/include/backend/wayland.h @@ -0,0 +1,92 @@ +#ifndef BACKEND_WAYLAND_H +#define BACKEND_WAYLAND_H + +#include <stdbool.h> + +#include <wayland-client.h> +#include <wayland-egl.h> +#include <wayland-server.h> +#include <wayland-util.h> + +#include <wlr/backend/wayland.h> +#include <wlr/render/egl.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_box.h> + +struct wlr_wl_backend { + struct wlr_backend backend; + + /* local state */ + bool started; + struct wl_display *local_display; + struct wl_list devices; + struct wl_list outputs; + struct wlr_egl egl; + struct wlr_renderer *renderer; + size_t requested_outputs; + struct wl_listener local_display_destroy; + /* remote state */ + struct wl_display *remote_display; + struct wl_event_source *remote_display_src; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct zxdg_shell_v6 *shell; + struct wl_shm *shm; + struct wl_seat *seat; + struct wl_pointer *pointer; + struct wl_keyboard *keyboard; + struct wlr_wl_pointer *current_pointer; + char *seat_name; +}; + +struct wlr_wl_output { + struct wlr_output wlr_output; + + struct wlr_wl_backend *backend; + struct wl_list link; + + struct wl_surface *surface; + struct wl_callback *frame_callback; + struct zxdg_surface_v6 *xdg_surface; + struct zxdg_toplevel_v6 *xdg_toplevel; + struct wl_egl_window *egl_window; + EGLSurface egl_surface; + + uint32_t enter_serial; + + struct { + struct wl_surface *surface; + struct wl_egl_window *egl_window; + int32_t hotspot_x, hotspot_y; + int32_t width, height; + } cursor; +}; + +struct wlr_wl_input_device { + struct wlr_input_device wlr_input_device; + + struct wlr_wl_backend *backend; + void *resource; +}; + +struct wlr_wl_pointer { + struct wlr_pointer wlr_pointer; + + struct wlr_wl_input_device *input_device; + struct wl_pointer *wl_pointer; + enum wlr_axis_source axis_source; + int32_t axis_discrete; + struct wlr_wl_output *output; + + struct wl_listener output_destroy; +}; + +struct wlr_wl_backend *get_wl_backend_from_backend(struct wlr_backend *backend); +void update_wl_output_cursor(struct wlr_wl_output *output); +struct wlr_wl_pointer *pointer_get_wl(struct wlr_pointer *wlr_pointer); +void create_wl_pointer(struct wl_pointer *wl_pointer, struct wlr_wl_output *output); +void create_wl_keyboard(struct wl_keyboard *wl_keyboard, struct wlr_wl_backend *wl); + +extern const struct wl_seat_listener seat_listener; + +#endif diff --git a/include/backend/x11.h b/include/backend/x11.h new file mode 100644 index 00000000..1a8341f6 --- /dev/null +++ b/include/backend/x11.h @@ -0,0 +1,91 @@ +#ifndef BACKEND_X11_H +#define BACKEND_X11_H + +#include <stdbool.h> + +#include <X11/Xlib-xcb.h> +#include <wayland-server.h> +#include <xcb/xcb.h> + +#include <wlr/backend/x11.h> +#include <wlr/config.h> +#include <wlr/interfaces/wlr_input_device.h> +#include <wlr/interfaces/wlr_output.h> +#include <wlr/render/egl.h> +#include <wlr/render/wlr_renderer.h> + +#define XCB_EVENT_RESPONSE_TYPE_MASK 0x7f + +#define X11_DEFAULT_REFRESH (60 * 1000) // 60 Hz + +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; + EGLSurface surf; + + struct wlr_pointer pointer; + struct wlr_input_device pointer_dev; + + struct wl_event_source *frame_timer; + int frame_delay; + + bool cursor_hidden; +}; + +struct wlr_x11_backend { + struct wlr_backend backend; + struct wl_display *wl_display; + bool started; + + Display *xlib_conn; + xcb_connection_t *xcb; + xcb_screen_t *screen; + + size_t requested_outputs; + struct wl_list outputs; // wlr_x11_output::link + + struct wlr_keyboard keyboard; + struct wlr_input_device keyboard_dev; + + struct wlr_egl egl; + struct wlr_renderer *renderer; + 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; + } atoms; + + // The time we last received an event + xcb_timestamp_t time; + + uint8_t xinput_opcode; + + struct wl_listener display_destroy; +}; + +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 keyboard_impl; +extern const struct wlr_pointer_impl pointer_impl; +extern const struct wlr_input_device_impl input_device_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); + +#endif diff --git a/include/meson.build b/include/meson.build new file mode 100644 index 00000000..d16d6ef4 --- /dev/null +++ b/include/meson.build @@ -0,0 +1 @@ +subdir('wlr') diff --git a/include/render/gles2.h b/include/render/gles2.h new file mode 100644 index 00000000..1857383f --- /dev/null +++ b/include/render/gles2.h @@ -0,0 +1,108 @@ +#ifndef RENDER_GLES2_H +#define RENDER_GLES2_H + +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <wlr/backend.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/log.h> + +extern PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; + +struct wlr_gles2_pixel_format { + enum wl_shm_format wl_format; + GLint gl_format, gl_type; + int depth, bpp; + bool has_alpha; +}; + +struct wlr_gles2_tex_shader { + GLuint program; + GLint proj; + GLint invert_y; + GLint tex; + GLint alpha; +}; + +struct wlr_gles2_renderer { + struct wlr_renderer wlr_renderer; + + struct wlr_egl *egl; + const char *exts_str; + + struct { + bool read_format_bgra_ext; + bool debug_khr; + bool egl_image_external_oes; + } exts; + + struct { + struct { + GLuint program; + GLint proj; + GLint color; + } quad; + struct { + GLuint program; + GLint proj; + GLint color; + } ellipse; + struct wlr_gles2_tex_shader tex_rgba; + struct wlr_gles2_tex_shader tex_rgbx; + struct wlr_gles2_tex_shader tex_ext; + } shaders; + + uint32_t viewport_width, viewport_height; +}; + +enum wlr_gles2_texture_type { + WLR_GLES2_TEXTURE_GLTEX, + WLR_GLES2_TEXTURE_WL_DRM_GL, + WLR_GLES2_TEXTURE_WL_DRM_EXT, + WLR_GLES2_TEXTURE_DMABUF, +}; + +struct wlr_gles2_texture { + struct wlr_texture wlr_texture; + + struct wlr_egl *egl; + enum wlr_gles2_texture_type type; + int width, height; + bool has_alpha; + enum wl_shm_format wl_format; // used to interpret upload data + bool inverted_y; + + // Not set if WLR_GLES2_TEXTURE_GLTEX + EGLImageKHR image; + GLuint image_tex; + + union { + GLuint gl_tex; + struct wl_resource *wl_drm; + }; +}; + +const struct wlr_gles2_pixel_format *get_gles2_format_from_wl( + enum wl_shm_format fmt); +const struct wlr_gles2_pixel_format *get_gles2_format_from_gl( + GLint gl_format, GLint gl_type, bool alpha); +const enum wl_shm_format *get_gles2_wl_formats(size_t *len); + +struct wlr_gles2_texture *gles2_get_texture( + struct wlr_texture *wlr_texture); + +void push_gles2_marker(const char *file, const char *func); +void pop_gles2_marker(void); +#define PUSH_GLES2_DEBUG push_gles2_marker(_wlr_strip_path(__FILE__), __func__) +#define POP_GLES2_DEBUG pop_gles2_marker() + +#endif diff --git a/include/rootston/bindings.h b/include/rootston/bindings.h new file mode 100644 index 00000000..db38130b --- /dev/null +++ b/include/rootston/bindings.h @@ -0,0 +1,9 @@ +#ifndef ROOTSTON_BINDINGS_H +#define ROOTSTON_BINDINGS_H + +#include "rootston/seat.h" +#include "rootston/input.h" + +void execute_binding_command(struct roots_seat *seat, struct roots_input *input, const char *command); + +#endif //ROOTSTON_BINDINGS_H diff --git a/include/rootston/config.h b/include/rootston/config.h new file mode 100644 index 00000000..f8132269 --- /dev/null +++ b/include/rootston/config.h @@ -0,0 +1,133 @@ +#ifndef ROOTSTON_CONFIG_H +#define ROOTSTON_CONFIG_H + +#include <xf86drmMode.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_switch.h> +#include <wlr/types/wlr_output_layout.h> + +#define ROOTS_CONFIG_DEFAULT_SEAT_NAME "seat0" + +struct roots_output_mode_config { + drmModeModeInfo info; + struct wl_list link; +}; + +struct roots_output_config { + char *name; + bool enable; + enum wl_output_transform transform; + int x, y; + float scale; + struct wl_list link; + struct { + int width, height; + float refresh_rate; + } mode; + struct wl_list modes; +}; + +struct roots_device_config { + char *name; + char *seat; + char *mapped_output; + bool tap_enabled; + struct wlr_box *mapped_box; + struct wl_list link; +}; + +struct roots_binding_config { + uint32_t modifiers; + xkb_keysym_t *keysyms; + size_t keysyms_len; + char *command; + struct wl_list link; +}; + +struct roots_keyboard_config { + char *name; + char *seat; + uint32_t meta_key; + char *rules; + char *model; + char *layout; + char *variant; + char *options; + int repeat_rate, repeat_delay; + struct wl_list link; +}; + +struct roots_cursor_config { + char *seat; + char *mapped_output; + struct wlr_box *mapped_box; + char *theme; + char *default_image; + struct wl_list link; +}; + +struct roots_switch_config { + char *name; + enum wlr_switch_type switch_type; + enum wlr_switch_state switch_state; + char *command; + struct wl_list link; +}; + +struct roots_config { + bool xwayland; + bool xwayland_lazy; + + struct wl_list outputs; + struct wl_list devices; + struct wl_list bindings; + struct wl_list keyboards; + struct wl_list cursors; + struct wl_list switches; + + char *config_path; + char *startup_cmd; + bool debug_damage_tracking; +}; + +/** + * Create a roots config from the given command line arguments. Command line + * arguments can specify the location of the config file. If it is not + * specified, the default location will be used. + */ +struct roots_config *roots_config_create_from_args(int argc, char *argv[]); + +/** + * Destroy the config and free its resources. + */ +void roots_config_destroy(struct roots_config *config); + +/** + * Get configuration for the output. If the output is not configured, returns + * NULL. + */ +struct roots_output_config *roots_config_get_output(struct roots_config *config, + struct wlr_output *output); + +/** + * Get configuration for the device. If the device is not configured, returns + * NULL. + */ +struct roots_device_config *roots_config_get_device(struct roots_config *config, + struct wlr_input_device *device); + +/** + * Get configuration for the keyboard. If the keyboard is not configured, + * returns NULL. A NULL device returns the default config for keyboards. + */ +struct roots_keyboard_config *roots_config_get_keyboard( + struct roots_config *config, struct wlr_input_device *device); + +/** + * Get configuration for the cursor. If the cursor is not configured, returns + * NULL. A NULL seat_name returns the default config for cursors. + */ +struct roots_cursor_config *roots_config_get_cursor(struct roots_config *config, + const char *seat_name); + +#endif diff --git a/include/rootston/cursor.h b/include/rootston/cursor.h new file mode 100644 index 00000000..b5bb682f --- /dev/null +++ b/include/rootston/cursor.h @@ -0,0 +1,105 @@ +#ifndef ROOTSTON_CURSOR_H +#define ROOTSTON_CURSOR_H + +#include <wlr/types/wlr_pointer_constraints_v1.h> +#include "rootston/seat.h" + +enum roots_cursor_mode { + ROOTS_CURSOR_PASSTHROUGH = 0, + ROOTS_CURSOR_MOVE = 1, + ROOTS_CURSOR_RESIZE = 2, + ROOTS_CURSOR_ROTATE = 3, +}; + +struct roots_cursor { + struct roots_seat *seat; + struct wlr_cursor *cursor; + + struct wlr_pointer_constraint_v1 *active_constraint; + pixman_region32_t confine; // invalid if active_constraint == NULL + + const char *default_xcursor; + + enum roots_cursor_mode mode; + + // state from input (review if this is necessary) + struct wlr_xcursor_manager *xcursor_manager; + struct wlr_seat *wl_seat; + struct wl_client *cursor_client; + int offs_x, offs_y; + int view_x, view_y, view_width, view_height; + float view_rotation; + uint32_t resize_edges; + + struct roots_seat_view *pointer_view; + struct wlr_surface *wlr_surface; + + struct wl_listener motion; + struct wl_listener motion_absolute; + struct wl_listener button; + struct wl_listener axis; + + struct wl_listener touch_down; + struct wl_listener touch_up; + struct wl_listener touch_motion; + + struct wl_listener tool_axis; + struct wl_listener tool_tip; + struct wl_listener tool_proximity; + struct wl_listener tool_button; + + struct wl_listener request_set_cursor; + + struct wl_listener focus_change; + + struct wl_listener constraint_commit; +}; + +struct roots_cursor *roots_cursor_create(struct roots_seat *seat); + +void roots_cursor_destroy(struct roots_cursor *cursor); + +void roots_cursor_handle_motion(struct roots_cursor *cursor, + struct wlr_event_pointer_motion *event); + +void roots_cursor_handle_motion_absolute(struct roots_cursor *cursor, + struct wlr_event_pointer_motion_absolute *event); + +void roots_cursor_handle_button(struct roots_cursor *cursor, + struct wlr_event_pointer_button *event); + +void roots_cursor_handle_axis(struct roots_cursor *cursor, + struct wlr_event_pointer_axis *event); + +void roots_cursor_handle_touch_down(struct roots_cursor *cursor, + struct wlr_event_touch_down *event); + +void roots_cursor_handle_touch_up(struct roots_cursor *cursor, + struct wlr_event_touch_up *event); + +void roots_cursor_handle_touch_motion(struct roots_cursor *cursor, + struct wlr_event_touch_motion *event); + +void roots_cursor_handle_tool_axis(struct roots_cursor *cursor, + struct wlr_event_tablet_tool_axis *event); + +void roots_cursor_handle_tool_tip(struct roots_cursor *cursor, + struct wlr_event_tablet_tool_tip *event); + +void roots_cursor_handle_request_set_cursor(struct roots_cursor *cursor, + struct wlr_seat_pointer_request_set_cursor_event *event); + +void roots_cursor_handle_focus_change(struct roots_cursor *cursor, + struct wlr_seat_pointer_focus_change_event *event); + +void roots_cursor_handle_constraint_commit(struct roots_cursor *cursor); + +void roots_cursor_update_position(struct roots_cursor *cursor, + uint32_t time); + +void roots_cursor_update_focus(struct roots_cursor *cursor); + +void roots_cursor_constrain(struct roots_cursor *cursor, + struct wlr_pointer_constraint_v1 *constraint, double sx, double sy); + +#endif diff --git a/include/rootston/desktop.h b/include/rootston/desktop.h new file mode 100644 index 00000000..b1fcaca0 --- /dev/null +++ b/include/rootston/desktop.h @@ -0,0 +1,119 @@ +#ifndef ROOTSTON_DESKTOP_H +#define ROOTSTON_DESKTOP_H +#include <time.h> +#include <wayland-server.h> +#include <wlr/config.h> +#include <wlr/types/wlr_compositor.h> +#include <wlr/types/wlr_foreign_toplevel_management_v1.h> +#include <wlr/types/wlr_gamma_control_v1.h> +#include <wlr/types/wlr_gamma_control.h> +#include <wlr/types/wlr_idle_inhibit_v1.h> +#include <wlr/types/wlr_idle.h> +#include <wlr/types/wlr_input_inhibitor.h> +#include <wlr/types/wlr_input_method_v2.h> +#include <wlr/types/wlr_layer_shell_v1.h> +#include <wlr/types/wlr_list.h> +#include <wlr/types/wlr_output_layout.h> +#include <wlr/types/wlr_output.h> +#include <wlr/types/wlr_presentation_time.h> +#include <wlr/types/wlr_gtk_primary_selection.h> +#include <wlr/types/wlr_screencopy_v1.h> +#include <wlr/types/wlr_screenshooter.h> +#include <wlr/types/wlr_text_input_v3.h> +#include <wlr/types/wlr_virtual_keyboard_v1.h> +#include <wlr/types/wlr_wl_shell.h> +#include <wlr/types/wlr_xcursor_manager.h> +#include <wlr/types/wlr_xdg_decoration_v1.h> +#include <wlr/types/wlr_xdg_shell_v6.h> +#include <wlr/types/wlr_xdg_shell.h> +#include "rootston/config.h" +#include "rootston/output.h" +#include "rootston/view.h" + +struct roots_desktop { + struct wl_list views; // roots_view::link + + struct wl_list outputs; // roots_output::link + struct timespec last_frame; + + struct roots_server *server; + struct roots_config *config; + + struct wlr_output_layout *layout; + struct wlr_xcursor_manager *xcursor_manager; + + struct wlr_compositor *compositor; + struct wlr_wl_shell *wl_shell; + struct wlr_xdg_shell_v6 *xdg_shell_v6; + struct wlr_xdg_shell *xdg_shell; + struct wlr_gamma_control_manager *gamma_control_manager; + struct wlr_gamma_control_manager_v1 *gamma_control_manager_v1; + struct wlr_screenshooter *screenshooter; + struct wlr_export_dmabuf_manager_v1 *export_dmabuf_manager_v1; + struct wlr_server_decoration_manager *server_decoration_manager; + struct wlr_xdg_decoration_manager_v1 *xdg_decoration_manager; + struct wlr_gtk_primary_selection_device_manager *primary_selection_device_manager; + struct wlr_idle *idle; + struct wlr_idle_inhibit_manager_v1 *idle_inhibit; + struct wlr_input_inhibit_manager *input_inhibit; + struct wlr_layer_shell_v1 *layer_shell; + struct wlr_input_method_manager_v2 *input_method; + struct wlr_text_input_manager_v3 *text_input; + struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard; + struct wlr_screencopy_manager_v1 *screencopy; + struct wlr_tablet_manager_v2 *tablet_v2; + struct wlr_pointer_constraints_v1 *pointer_constraints; + struct wlr_presentation *presentation; + struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager_v1; + + struct wl_listener new_output; + struct wl_listener layout_change; + struct wl_listener xdg_shell_v6_surface; + struct wl_listener xdg_shell_surface; + struct wl_listener wl_shell_surface; + struct wl_listener layer_shell_surface; + struct wl_listener xdg_toplevel_decoration; + struct wl_listener input_inhibit_activate; + struct wl_listener input_inhibit_deactivate; + struct wl_listener virtual_keyboard_new; + struct wl_listener pointer_constraint; + +#if WLR_HAS_XWAYLAND + struct wlr_xwayland *xwayland; + struct wl_listener xwayland_surface; +#endif +}; + +struct roots_server; + +struct roots_desktop *desktop_create(struct roots_server *server, + struct roots_config *config); +void desktop_destroy(struct roots_desktop *desktop); +struct roots_output *desktop_output_from_wlr_output( + struct roots_desktop *desktop, struct wlr_output *output); + +struct wlr_surface *desktop_surface_at(struct roots_desktop *desktop, + double lx, double ly, double *sx, double *sy, + struct roots_view **view); + +struct roots_view *view_create(struct roots_desktop *desktop); +void view_destroy(struct roots_view *view); +void view_activate(struct roots_view *view, bool activate); +void view_apply_damage(struct roots_view *view); +void view_damage_whole(struct roots_view *view); +void view_update_position(struct roots_view *view, int x, int y); +void view_update_size(struct roots_view *view, int width, int height); +void view_update_decorated(struct roots_view *view, bool decorated); +void view_initial_focus(struct roots_view *view); +void view_map(struct roots_view *view, struct wlr_surface *surface); +void view_unmap(struct roots_view *view); +void view_arrange_maximized(struct roots_view *view); + +void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data); +void handle_xdg_shell_surface(struct wl_listener *listener, void *data); +void handle_xdg_toplevel_decoration(struct wl_listener *listener, void *data); +void handle_wl_shell_surface(struct wl_listener *listener, void *data); +void handle_layer_shell_surface(struct wl_listener *listener, void *data); +void handle_xwayland_surface(struct wl_listener *listener, void *data); + +#endif diff --git a/include/rootston/ini.h b/include/rootston/ini.h new file mode 100644 index 00000000..2804255b --- /dev/null +++ b/include/rootston/ini.h @@ -0,0 +1,93 @@ +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#ifndef __INI_H__ +#define __INI_H__ + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdio.h> + +/* Typedef for prototype of handler function. */ +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +int ini_parse(const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See http://code.google.com/p/inih/issues/detail?id=21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +/* Maximum line length for any line in INI file. */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 2000 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __INI_H__ */ diff --git a/include/rootston/input.h b/include/rootston/input.h new file mode 100644 index 00000000..31810b4d --- /dev/null +++ b/include/rootston/input.h @@ -0,0 +1,37 @@ +#ifndef ROOTSTON_INPUT_H +#define ROOTSTON_INPUT_H + +#include <wayland-server.h> +#include <wlr/types/wlr_cursor.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_seat.h> +#include "rootston/config.h" +#include "rootston/cursor.h" +#include "rootston/server.h" +#include "rootston/view.h" + +struct roots_input { + struct roots_config *config; + struct roots_server *server; + + struct wl_listener new_input; + + struct wl_list seats; // roots_seat::link +}; + +struct roots_input *input_create(struct roots_server *server, + struct roots_config *config); +void input_destroy(struct roots_input *input); + +struct roots_seat *input_seat_from_wlr_seat(struct roots_input *input, + struct wlr_seat *seat); + +bool input_view_has_focus(struct roots_input *input, struct roots_view *view); + +struct roots_seat *input_get_seat(struct roots_input *input, char *name); + +struct roots_seat *input_last_active_seat(struct roots_input *input); + +void input_update_cursor_focus(struct roots_input *input); + +#endif diff --git a/include/rootston/keyboard.h b/include/rootston/keyboard.h new file mode 100644 index 00000000..0140389a --- /dev/null +++ b/include/rootston/keyboard.h @@ -0,0 +1,34 @@ +#ifndef ROOTSTON_KEYBOARD_H +#define ROOTSTON_KEYBOARD_H + +#include <xkbcommon/xkbcommon.h> +#include "rootston/input.h" + +#define ROOTS_KEYBOARD_PRESSED_KEYSYMS_CAP 32 + +struct roots_keyboard { + struct roots_input *input; + struct roots_seat *seat; + struct wlr_input_device *device; + struct roots_keyboard_config *config; + struct wl_list link; + + struct wl_listener device_destroy; + struct wl_listener keyboard_key; + struct wl_listener keyboard_modifiers; + + xkb_keysym_t pressed_keysyms_translated[ROOTS_KEYBOARD_PRESSED_KEYSYMS_CAP]; + xkb_keysym_t pressed_keysyms_raw[ROOTS_KEYBOARD_PRESSED_KEYSYMS_CAP]; +}; + +struct roots_keyboard *roots_keyboard_create(struct wlr_input_device *device, + struct roots_input *input); + +void roots_keyboard_destroy(struct roots_keyboard *keyboard); + +void roots_keyboard_handle_key(struct roots_keyboard *keyboard, + struct wlr_event_keyboard_key *event); + +void roots_keyboard_handle_modifiers(struct roots_keyboard *r_keyboard); + +#endif diff --git a/include/rootston/layers.h b/include/rootston/layers.h new file mode 100644 index 00000000..0dacf20f --- /dev/null +++ b/include/rootston/layers.h @@ -0,0 +1,35 @@ +#ifndef ROOTSTON_LAYERS_H +#define ROOTSTON_LAYERS_H +#include <stdbool.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_surface.h> +#include <wlr/types/wlr_layer_shell_v1.h> + +struct roots_layer_surface { + struct wlr_layer_surface_v1 *layer_surface; + struct wl_list link; + + struct wl_listener destroy; + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener surface_commit; + struct wl_listener output_destroy; + struct wl_listener new_popup; + + bool configured; + struct wlr_box geo; +}; + +struct roots_layer_popup { + struct roots_layer_surface *parent; + struct wlr_xdg_popup *wlr_popup; + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener destroy; + struct wl_listener commit; +}; + +struct roots_output; +void arrange_layers(struct roots_output *output); + +#endif diff --git a/include/rootston/output.h b/include/rootston/output.h new file mode 100644 index 00000000..3f07ab6f --- /dev/null +++ b/include/rootston/output.h @@ -0,0 +1,52 @@ +#ifndef ROOTSTON_OUTPUT_H +#define ROOTSTON_OUTPUT_H +#include <pixman.h> +#include <time.h> +#include <wayland-server.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_output_damage.h> + +struct roots_desktop; + +struct roots_output { + struct roots_desktop *desktop; + struct wlr_output *wlr_output; + struct wl_list link; // roots_desktop:outputs + + struct roots_view *fullscreen_view; + struct wl_list layers[4]; // layer_surface::link + + struct timespec last_frame; + struct wlr_output_damage *damage; + + struct wlr_box usable_area; + + struct wl_listener destroy; + struct wl_listener mode; + struct wl_listener transform; + struct wl_listener present; + struct wl_listener damage_frame; + struct wl_listener damage_destroy; +}; + +void rotate_child_position(double *sx, double *sy, double sw, double sh, + double pw, double ph, float rotation); + +void handle_new_output(struct wl_listener *listener, void *data); + +struct roots_view; +struct roots_drag_icon; + +void output_damage_whole(struct roots_output *output); +void output_damage_whole_view(struct roots_output *output, + struct roots_view *view); +void output_damage_from_view(struct roots_output *output, + struct roots_view *view); +void output_damage_whole_drag_icon(struct roots_output *output, + struct roots_drag_icon *icon); +void output_damage_from_local_surface(struct roots_output *output, + struct wlr_surface *surface, double ox, double oy, float rotation); +void output_damage_whole_local_surface(struct roots_output *output, + struct wlr_surface *surface, double ox, double oy, float rotation); + +#endif diff --git a/include/rootston/seat.h b/include/rootston/seat.h new file mode 100644 index 00000000..105ba3aa --- /dev/null +++ b/include/rootston/seat.h @@ -0,0 +1,181 @@ +#ifndef ROOTSTON_SEAT_H +#define ROOTSTON_SEAT_H + +#include <wayland-server.h> +#include "rootston/input.h" +#include "rootston/keyboard.h" +#include "rootston/layers.h" +#include "rootston/switch.h" +#include "rootston/text_input.h" + +struct roots_seat { + struct roots_input *input; + struct wlr_seat *seat; + struct roots_cursor *cursor; + struct wl_list link; // roots_input::seats + + // coordinates of the first touch point if it exists + int32_t touch_id; + double touch_x, touch_y; + + // If the focused layer is set, views cannot receive keyboard focus + struct wlr_layer_surface_v1 *focused_layer; + + struct roots_input_method_relay im_relay; + + // If non-null, only this client can receive input events + struct wl_client *exclusive_client; + + struct wl_list views; // roots_seat_view::link + bool has_focus; + + struct wl_list drag_icons; // roots_drag_icon::link + + struct wl_list keyboards; + struct wl_list pointers; + struct wl_list switches; + struct wl_list touch; + struct wl_list tablets; + struct wl_list tablet_pads; + + struct wl_listener new_drag_icon; + struct wl_listener destroy; +}; + +struct roots_seat_view { + struct roots_seat *seat; + struct roots_view *view; + + bool has_button_grab; + double grab_sx; + double grab_sy; + + struct wl_list link; // roots_seat::views + + struct wl_listener view_unmap; + struct wl_listener view_destroy; +}; + +struct roots_drag_icon { + struct roots_seat *seat; + struct wlr_drag_icon *wlr_drag_icon; + struct wl_list link; + + double x, y; + + struct wl_listener surface_commit; + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener destroy; +}; + +struct roots_pointer { + struct roots_seat *seat; + struct wlr_input_device *device; + struct wl_listener device_destroy; + struct wl_list link; +}; + +struct roots_touch { + struct roots_seat *seat; + struct wlr_input_device *device; + struct wl_listener device_destroy; + struct wl_list link; +}; + +struct roots_tablet { + struct roots_seat *seat; + struct wlr_input_device *device; + struct wlr_tablet_v2_tablet *tablet_v2; + + struct wl_listener device_destroy; + struct wl_listener axis; + struct wl_listener proximity; + struct wl_listener tip; + struct wl_listener button; + struct wl_list link; +}; + +struct roots_tablet_pad { + struct wl_list link; + struct wlr_tablet_v2_tablet_pad *tablet_v2_pad; + + struct roots_seat *seat; + struct wlr_input_device *device; + + struct wl_listener device_destroy; + struct wl_listener attach; + struct wl_listener button; + struct wl_listener ring; + struct wl_listener strip; + + struct roots_tablet *tablet; + struct wl_listener tablet_destroy; +}; + +struct roots_tablet_tool { + struct wl_list link; + struct wl_list tool_link; + struct wlr_tablet_v2_tablet_tool *tablet_v2_tool; + + struct roots_seat *seat; + double tilt_x, tilt_y; + + struct wl_listener set_cursor; + struct wl_listener tool_destroy; + + struct roots_tablet *current_tablet; + struct wl_listener tablet_destroy; +}; + +struct roots_pointer_constraint { + struct wlr_pointer_constraint_v1 *constraint; + + struct wl_listener destroy; +}; + +struct roots_seat *roots_seat_create(struct roots_input *input, char *name); + +void roots_seat_destroy(struct roots_seat *seat); + +void roots_seat_add_device(struct roots_seat *seat, + struct wlr_input_device *device); + +void roots_seat_configure_cursor(struct roots_seat *seat); + +void roots_seat_configure_xcursor(struct roots_seat *seat); + +bool roots_seat_has_meta_pressed(struct roots_seat *seat); + +struct roots_view *roots_seat_get_focus(struct roots_seat *seat); + +void roots_seat_set_focus(struct roots_seat *seat, struct roots_view *view); + +void roots_seat_set_focus_layer(struct roots_seat *seat, + struct wlr_layer_surface_v1 *layer); + +void roots_seat_cycle_focus(struct roots_seat *seat); + +void roots_seat_begin_move(struct roots_seat *seat, struct roots_view *view); + +void roots_seat_begin_resize(struct roots_seat *seat, struct roots_view *view, + uint32_t edges); + +void roots_seat_begin_rotate(struct roots_seat *seat, struct roots_view *view); + +void roots_seat_end_compositor_grab(struct roots_seat *seat); + +struct roots_seat_view *roots_seat_view_from_view( struct roots_seat *seat, + struct roots_view *view); + +void roots_drag_icon_update_position(struct roots_drag_icon *icon); + +void roots_drag_icon_damage_whole(struct roots_drag_icon *icon); + +void roots_seat_set_exclusive_client(struct roots_seat *seat, + struct wl_client *client); + +bool roots_seat_allow_input(struct roots_seat *seat, + struct wl_resource *resource); + +#endif diff --git a/include/rootston/server.h b/include/rootston/server.h new file mode 100644 index 00000000..1836a374 --- /dev/null +++ b/include/rootston/server.h @@ -0,0 +1,37 @@ +#ifndef _ROOTSTON_SERVER_H +#define _ROOTSTON_SERVER_H + +#include <wayland-server.h> +#include <wlr/backend.h> +#include <wlr/backend/session.h> +#include <wlr/config.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_data_device.h> +#if WLR_HAS_XWAYLAND +#include <wlr/xwayland.h> +#endif +#include "rootston/config.h" +#include "rootston/desktop.h" +#include "rootston/input.h" + +struct roots_server { + /* Rootston resources */ + struct roots_config *config; + struct roots_desktop *desktop; + struct roots_input *input; + + /* Wayland resources */ + struct wl_display *wl_display; + struct wl_event_loop *wl_event_loop; + + /* WLR tools */ + struct wlr_backend *backend; + struct wlr_renderer *renderer; + + /* Global resources */ + struct wlr_data_device_manager *data_device_manager; +}; + +extern struct roots_server server; + +#endif diff --git a/include/rootston/switch.h b/include/rootston/switch.h new file mode 100644 index 00000000..28197774 --- /dev/null +++ b/include/rootston/switch.h @@ -0,0 +1,18 @@ +#ifndef ROOTSTON_SWITCH_H +#define ROOTSTON_SWITCH_H + +#include "rootston/input.h" + +struct roots_switch { + struct roots_seat *seat; + struct wlr_input_device *device; + struct wl_listener device_destroy; + + struct wl_listener toggle; + struct wl_list link; +}; + +void roots_switch_handle_toggle(struct roots_switch *lid_switch, + struct wlr_event_switch_toggle *event); + +#endif // ROOTSTON_SWITCH_H diff --git a/include/rootston/text_input.h b/include/rootston/text_input.h new file mode 100644 index 00000000..82e45e3e --- /dev/null +++ b/include/rootston/text_input.h @@ -0,0 +1,63 @@ +#ifndef ROOTSTON_TEXT_INPUT_H +#define ROOTSTON_TEXT_INPUT_H + +#include <wlr/types/wlr_text_input_v3.h> +#include <wlr/types/wlr_input_method_v2.h> +#include <wlr/types/wlr_surface.h> +#include "rootston/seat.h" + +/** + * The relay structure manages the relationship between text-input and + * input_method interfaces on a given seat. Multiple text-input interfaces may + * be bound to a relay, but at most one will be focused (reveiving events) at + * a time. At most one input-method interface may be bound to the seat. The + * relay manages life cycle of both sides. When both sides are present and + * focused, the relay passes messages between them. + * + * Text input focus is a subset of keyboard focus - if the text-input is + * in the focused state, wl_keyboard sent an enter as well. However, having + * wl_keyboard focused doesn't mean that text-input will be focused. + */ +struct roots_input_method_relay { + struct roots_seat *seat; + + struct wl_list text_inputs; // roots_text_input::link + struct wlr_input_method_v2 *input_method; // doesn't have to be present + + struct wl_listener text_input_new; + struct wl_listener text_input_enable; + struct wl_listener text_input_commit; + struct wl_listener text_input_disable; + struct wl_listener text_input_destroy; + + struct wl_listener input_method_new; + struct wl_listener input_method_commit; + struct wl_listener input_method_destroy; +}; + +struct roots_text_input { + struct roots_input_method_relay *relay; + + struct wlr_text_input_v3 *input; + // The surface getting seat's focus. Stored for when text-input cannot + // be sent an enter event immediately after getting focus, e.g. when + // there's no input method available. Cleared once text-input is entered. + struct wlr_surface *pending_focused_surface; + + struct wl_list link; + + struct wl_listener pending_focused_surface_destroy; +}; + +void roots_input_method_relay_init(struct roots_seat *seat, + struct roots_input_method_relay *relay); + +// Updates currently focused surface. Surface must belong to the same seat. +void roots_input_method_relay_set_focus(struct roots_input_method_relay *relay, + struct wlr_surface *surface); + +struct roots_text_input *roots_text_input_create( + struct roots_input_method_relay *relay, + struct wlr_text_input_v3 *text_input); + +#endif diff --git a/include/rootston/view.h b/include/rootston/view.h new file mode 100644 index 00000000..b1feb5ce --- /dev/null +++ b/include/rootston/view.h @@ -0,0 +1,260 @@ +#ifndef ROOTSTON_VIEW_H +#define ROOTSTON_VIEW_H +#include <stdbool.h> +#include <wlr/config.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_foreign_toplevel_management_v1.h> +#include <wlr/types/wlr_surface.h> +#include <wlr/types/wlr_xdg_decoration_v1.h> +#include <wlr/types/wlr_xdg_shell_v6.h> +#include <wlr/types/wlr_xdg_shell.h> + +struct roots_wl_shell_surface { + struct roots_view *view; + + struct wl_listener destroy; + struct wl_listener new_popup; + struct wl_listener request_move; + struct wl_listener request_resize; + struct wl_listener request_maximize; + struct wl_listener request_fullscreen; + struct wl_listener set_state; + struct wl_listener set_title; + struct wl_listener set_class; + + struct wl_listener surface_commit; +}; + +struct roots_xdg_surface_v6 { + struct roots_view *view; + + struct wl_listener destroy; + struct wl_listener new_popup; + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener request_move; + struct wl_listener request_resize; + struct wl_listener request_maximize; + struct wl_listener request_fullscreen; + struct wl_listener set_title; + struct wl_listener set_app_id; + + struct wl_listener surface_commit; + + uint32_t pending_move_resize_configure_serial; +}; + +struct roots_xdg_toplevel_decoration; + +struct roots_xdg_surface { + struct roots_view *view; + + struct wl_listener destroy; + struct wl_listener new_popup; + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener request_move; + struct wl_listener request_resize; + struct wl_listener request_maximize; + struct wl_listener request_fullscreen; + struct wl_listener set_title; + struct wl_listener set_app_id; + + + struct wl_listener surface_commit; + + uint32_t pending_move_resize_configure_serial; + + struct roots_xdg_toplevel_decoration *xdg_toplevel_decoration; +}; + +struct roots_xwayland_surface { + struct roots_view *view; + + struct wl_listener destroy; + struct wl_listener request_configure; + struct wl_listener request_move; + struct wl_listener request_resize; + struct wl_listener request_maximize; + struct wl_listener request_fullscreen; + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener set_title; + struct wl_listener set_class; + + struct wl_listener surface_commit; +}; + +enum roots_view_type { + ROOTS_WL_SHELL_VIEW, + ROOTS_XDG_SHELL_V6_VIEW, + ROOTS_XDG_SHELL_VIEW, +#if WLR_HAS_XWAYLAND + ROOTS_XWAYLAND_VIEW, +#endif +}; + +struct roots_view { + struct roots_desktop *desktop; + struct wl_list link; // roots_desktop::views + + struct wlr_box box; + float rotation; + float alpha; + + bool decorated; + int border_width; + int titlebar_height; + + bool maximized; + struct roots_output *fullscreen_output; + struct { + double x, y; + uint32_t width, height; + float rotation; + } saved; + + struct { + bool update_x, update_y; + double x, y; + uint32_t width, height; + } pending_move_resize; + + // TODO: Something for roots-enforced width/height + enum roots_view_type type; + union { + struct wlr_wl_shell_surface *wl_shell_surface; + struct wlr_xdg_surface_v6 *xdg_surface_v6; + struct wlr_xdg_surface *xdg_surface; +#if WLR_HAS_XWAYLAND + struct wlr_xwayland_surface *xwayland_surface; +#endif + }; + union { + struct roots_wl_shell_surface *roots_wl_shell_surface; + struct roots_xdg_surface_v6 *roots_xdg_surface_v6; + struct roots_xdg_surface *roots_xdg_surface; +#if WLR_HAS_XWAYLAND + struct roots_xwayland_surface *roots_xwayland_surface; +#endif + }; + + struct wlr_surface *wlr_surface; + struct wl_list children; // roots_view_child::link + + struct wlr_foreign_toplevel_handle_v1 *toplevel_handle; + struct wl_listener toplevel_handle_request_maximize; + struct wl_listener toplevel_handle_request_activate; + struct wl_listener toplevel_handle_request_close; + + struct wl_listener new_subsurface; + + struct { + struct wl_signal unmap; + struct wl_signal destroy; + } events; + + // TODO: this should follow the typical type/impl pattern we use elsewhere + void (*activate)(struct roots_view *view, bool active); + void (*move)(struct roots_view *view, double x, double y); + void (*resize)(struct roots_view *view, uint32_t width, uint32_t height); + void (*move_resize)(struct roots_view *view, double x, double y, + uint32_t width, uint32_t height); + void (*maximize)(struct roots_view *view, bool maximized); + void (*set_fullscreen)(struct roots_view *view, bool fullscreen); + void (*close)(struct roots_view *view); + void (*destroy)(struct roots_view *view); +}; + +struct roots_view_child { + struct roots_view *view; + struct wlr_surface *wlr_surface; + struct wl_list link; + + struct wl_listener commit; + struct wl_listener new_subsurface; + + void (*destroy)(struct roots_view_child *child); +}; + +struct roots_subsurface { + struct roots_view_child view_child; + struct wlr_subsurface *wlr_subsurface; + struct wl_listener destroy; +}; + +struct roots_wl_shell_popup { + struct roots_view_child view_child; + struct wlr_wl_shell_surface *wlr_wl_shell_surface; + struct wl_listener destroy; + struct wl_listener set_state; + struct wl_listener new_popup; +}; + +struct roots_xdg_popup_v6 { + struct roots_view_child view_child; + struct wlr_xdg_popup_v6 *wlr_popup; + struct wl_listener destroy; + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener new_popup; +}; + +struct roots_xdg_popup { + struct roots_view_child view_child; + struct wlr_xdg_popup *wlr_popup; + struct wl_listener destroy; + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener new_popup; +}; + +struct roots_xdg_toplevel_decoration { + struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration; + struct roots_xdg_surface *surface; + struct wl_listener destroy; + struct wl_listener request_mode; + struct wl_listener surface_commit; +}; + +void view_get_box(const struct roots_view *view, struct wlr_box *box); +void view_activate(struct roots_view *view, bool active); +void view_move(struct roots_view *view, double x, double y); +void view_resize(struct roots_view *view, uint32_t width, uint32_t height); +void view_move_resize(struct roots_view *view, double x, double y, + uint32_t width, uint32_t height); +void view_maximize(struct roots_view *view, bool maximized); +void view_set_fullscreen(struct roots_view *view, bool fullscreen, + struct wlr_output *output); +void view_rotate(struct roots_view *view, float rotation); +void view_cycle_alpha(struct roots_view *view); +void view_close(struct roots_view *view); +bool view_center(struct roots_view *view); +void view_setup(struct roots_view *view); +void view_teardown(struct roots_view *view); + +void view_set_title(struct roots_view *view, const char *title); +void view_set_app_id(struct roots_view *view, const char *app_id); +void view_create_foreign_toplevel_handle(struct roots_view *view); + +void view_get_deco_box(const struct roots_view *view, struct wlr_box *box); + +enum roots_deco_part { + ROOTS_DECO_PART_NONE = 0, + ROOTS_DECO_PART_TOP_BORDER = (1 << 0), + ROOTS_DECO_PART_BOTTOM_BORDER = (1 << 1), + ROOTS_DECO_PART_LEFT_BORDER = (1 << 2), + ROOTS_DECO_PART_RIGHT_BORDER = (1 << 3), + ROOTS_DECO_PART_TITLEBAR = (1 << 4), +}; + +enum roots_deco_part view_get_deco_part(struct roots_view *view, double sx, double sy); + +void view_child_init(struct roots_view_child *child, struct roots_view *view, + struct wlr_surface *wlr_surface); +void view_child_finish(struct roots_view_child *child); + +struct roots_subsurface *subsurface_create(struct roots_view *view, + struct wlr_subsurface *wlr_subsurface); + +#endif diff --git a/include/rootston/virtual_keyboard.h b/include/rootston/virtual_keyboard.h new file mode 100644 index 00000000..613e5595 --- /dev/null +++ b/include/rootston/virtual_keyboard.h @@ -0,0 +1,7 @@ +#ifndef ROOTSTON_VIRTUAL_KEYBOARD_H +#define ROOTSTON_VIRTUAL_KEYBOARD_H + +#include <wayland-server-core.h> + +void handle_virtual_keyboard(struct wl_listener *listener, void *data); +#endif diff --git a/include/rootston/xcursor.h b/include/rootston/xcursor.h new file mode 100644 index 00000000..f78489a4 --- /dev/null +++ b/include/rootston/xcursor.h @@ -0,0 +1,12 @@ +#ifndef ROOTSTON_XCURSOR_H +#define ROOTSTON_XCURSOR_H + +#include <stdint.h> + +#define ROOTS_XCURSOR_SIZE 24 + +#define ROOTS_XCURSOR_DEFAULT "left_ptr" +#define ROOTS_XCURSOR_MOVE "grabbing" +#define ROOTS_XCURSOR_ROTATE "grabbing" + +#endif diff --git a/include/types/wlr_data_device.h b/include/types/wlr_data_device.h new file mode 100644 index 00000000..376c5f09 --- /dev/null +++ b/include/types/wlr_data_device.h @@ -0,0 +1,37 @@ +#ifndef TYPES_WLR_DATA_DEVICE_H +#define TYPES_WLR_DATA_DEVICE_H + +#include <wayland-server.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_client *client, + struct wlr_data_source *source, uint32_t version); +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); +struct wlr_data_offer *data_source_send_offer(struct wlr_data_source *source, + struct wl_resource *device_resource); +void data_source_notify_finish(struct wlr_data_source *source); + +bool seat_client_start_drag(struct wlr_seat_client *client, + struct wlr_data_source *source, struct wlr_surface *icon_surface, + struct wlr_surface *origin, uint32_t serial); + +#endif diff --git a/include/types/wlr_seat.h b/include/types/wlr_seat.h new file mode 100644 index 00000000..15f1dc38 --- /dev/null +++ b/include/types/wlr_seat.h @@ -0,0 +1,23 @@ +#ifndef TYPES_WLR_SEAT_H +#define TYPES_WLR_SEAT_H + +#include <wayland-server.h> +#include <wlr/types/wlr_seat.h> + +const struct wlr_pointer_grab_interface default_pointer_grab_impl; +const struct wlr_keyboard_grab_interface default_keyboard_grab_impl; +const struct wlr_touch_grab_interface default_touch_grab_impl; + +void seat_client_create_pointer(struct wlr_seat_client *seat_client, + uint32_t version, uint32_t id); +void seat_client_destroy_pointer(struct wl_resource *resource); + +void seat_client_create_keyboard(struct wlr_seat_client *seat_client, + uint32_t version, uint32_t id); +void seat_client_destroy_keyboard(struct wl_resource *resource); + +void seat_client_create_touch(struct wlr_seat_client *seat_client, + uint32_t version, uint32_t id); +void seat_client_destroy_touch(struct wl_resource *resource); + +#endif diff --git a/include/types/wlr_tablet_v2.h b/include/types/wlr_tablet_v2.h new file mode 100644 index 00000000..becde596 --- /dev/null +++ b/include/types/wlr_tablet_v2.h @@ -0,0 +1,93 @@ +#ifndef TYPES_WLR_TABLET_V2_H +#define TYPES_WLR_TABLET_V2_H + +#include "tablet-unstable-v2-protocol.h" +#include <wayland-server.h> +#include <wlr/types/wlr_tablet_v2.h> + +struct wlr_tablet_seat_v2 { + struct wl_list link; // wlr_tablet_manager_v2::seats + struct wlr_seat *wlr_seat; + struct wlr_tablet_manager_v2 *manager; + + struct wl_list tablets; // wlr_tablet_v2_tablet::link + struct wl_list tools; + struct wl_list pads; + + struct wl_list clients; // wlr_tablet_seat_v2_client::link + + struct wl_listener seat_destroy; +}; + +struct wlr_tablet_seat_client_v2 { + struct wl_list seat_link; + struct wl_list client_link; + struct wl_client *wl_client; + struct wl_resource *resource; + + struct wlr_tablet_manager_client_v2 *client; + struct wlr_seat_client *seat_client; + + struct wl_listener seat_client_destroy; + + struct wl_list tools; //wlr_tablet_tool_client_v2::link + struct wl_list tablets; //wlr_tablet_client_v2::link + struct wl_list pads; //wlr_tablet_pad_client_v2::link +}; + +struct wlr_tablet_client_v2 { + struct wl_list seat_link; // wlr_tablet_seat_client_v2::tablet + struct wl_list tablet_link; // wlr_tablet_v2_tablet::clients + struct wl_client *client; + struct wl_resource *resource; +}; + +struct wlr_tablet_pad_client_v2 { + struct wl_list seat_link; + struct wl_list pad_link; + struct wl_client *client; + struct wl_resource *resource; + struct wlr_tablet_v2_tablet_pad *pad; + + size_t button_count; + + size_t group_count; + struct wl_resource **groups; + + size_t ring_count; + struct wl_resource **rings; + + size_t strip_count; + struct wl_resource **strips; +}; + +struct wlr_tablet_tool_client_v2 { + struct wl_list seat_link; + struct wl_list tool_link; + struct wl_client *client; + struct wl_resource *resource; + struct wlr_tablet_v2_tablet_tool *tool; + struct wlr_tablet_seat_client_v2 *seat; + + struct wl_event_source *frame_source; +}; + +struct wlr_tablet_client_v2 *tablet_client_from_resource(struct wl_resource *resource); +void destroy_tablet_v2(struct wl_resource *resource); +void add_tablet_client(struct wlr_tablet_seat_client_v2 *seat, struct wlr_tablet_v2_tablet *tablet); + +void destroy_tablet_pad_v2(struct wl_resource *resource); +struct wlr_tablet_pad_client_v2 *tablet_pad_client_from_resource(struct wl_resource *resource); +void add_tablet_pad_client(struct wlr_tablet_seat_client_v2 *seat, struct wlr_tablet_v2_tablet_pad *pad); + +void destroy_tablet_tool_v2(struct wl_resource *resource); +struct wlr_tablet_tool_client_v2 *tablet_tool_client_from_resource(struct wl_resource *resource); +void add_tablet_tool_client(struct wlr_tablet_seat_client_v2 *seat, struct wlr_tablet_v2_tablet_tool *tool); + +struct wlr_tablet_seat_client_v2 *tablet_seat_client_from_resource(struct wl_resource *resource); +void tablet_seat_client_v2_destroy(struct wl_resource *resource); +struct wlr_tablet_seat_v2 *get_or_create_tablet_seat( + struct wlr_tablet_manager_v2 *manager, + struct wlr_seat *wlr_seat); + +#endif /* TYPES_WLR_TABLET_V2_H */ diff --git a/include/types/wlr_xdg_shell.h b/include/types/wlr_xdg_shell.h new file mode 100644 index 00000000..08a691bd --- /dev/null +++ b/include/types/wlr_xdg_shell.h @@ -0,0 +1,48 @@ +#ifndef TYPES_WLR_XDG_SHELL_H +#define TYPES_WLR_XDG_SHELL_H + +#include <wayland-server.h> +#include <wlr/types/wlr_xdg_shell.h> +#include "xdg-shell-protocol.h" + +struct wlr_xdg_positioner_resource { + struct wl_resource *resource; + struct wlr_xdg_positioner attrs; +}; + +extern const struct wlr_surface_role xdg_toplevel_surface_role; +extern const struct wlr_surface_role xdg_popup_surface_role; + +uint32_t schedule_xdg_surface_configure(struct wlr_xdg_surface *surface); +struct wlr_xdg_surface *create_xdg_surface( + struct wlr_xdg_client *client, struct wlr_surface *surface, + uint32_t id); +void unmap_xdg_surface(struct wlr_xdg_surface *surface); +void reset_xdg_surface(struct wlr_xdg_surface *xdg_surface); +void destroy_xdg_surface(struct wlr_xdg_surface *surface); +void handle_xdg_surface_commit(struct wlr_surface *wlr_surface); +void handle_xdg_surface_precommit(struct wlr_surface *wlr_surface); + +void create_xdg_positioner(struct wlr_xdg_client *client, uint32_t id); +struct wlr_xdg_positioner_resource *get_xdg_positioner_from_resource( + struct wl_resource *resource); + +void create_xdg_popup(struct wlr_xdg_surface *xdg_surface, + struct wlr_xdg_surface *parent, + struct wlr_xdg_positioner_resource *positioner, int32_t id); +void handle_xdg_surface_popup_committed(struct wlr_xdg_surface *surface); +struct wlr_xdg_popup_grab *get_xdg_shell_popup_grab_from_seat( + struct wlr_xdg_shell *shell, struct wlr_seat *seat); +void destroy_xdg_popup(struct wlr_xdg_surface *surface); + +void create_xdg_toplevel(struct wlr_xdg_surface *xdg_surface, + uint32_t id); +void handle_xdg_surface_toplevel_committed(struct wlr_xdg_surface *surface); +void send_xdg_toplevel_configure(struct wlr_xdg_surface *surface, + struct wlr_xdg_surface_configure *configure); +void handle_xdg_toplevel_ack_configure(struct wlr_xdg_surface *surface, + struct wlr_xdg_surface_configure *configure); +bool compare_xdg_surface_toplevel_state(struct wlr_xdg_toplevel *state); +void destroy_xdg_toplevel(struct wlr_xdg_surface *surface); + +#endif diff --git a/include/types/wlr_xdg_shell_v6.h b/include/types/wlr_xdg_shell_v6.h new file mode 100644 index 00000000..59fca667 --- /dev/null +++ b/include/types/wlr_xdg_shell_v6.h @@ -0,0 +1,47 @@ +#ifndef TYPES_WLR_XDG_SHELL_V6_H +#define TYPES_WLR_XDG_SHELL_V6_H + +#include <wayland-server.h> +#include <wlr/types/wlr_xdg_shell_v6.h> +#include "xdg-shell-unstable-v6-protocol.h" + +struct wlr_xdg_positioner_v6_resource { + struct wl_resource *resource; + struct wlr_xdg_positioner_v6 attrs; +}; + +extern const struct wlr_surface_role xdg_toplevel_v6_surface_role; +extern const struct wlr_surface_role xdg_popup_v6_surface_role; + +uint32_t schedule_xdg_surface_v6_configure(struct wlr_xdg_surface_v6 *surface); +struct wlr_xdg_surface_v6 *create_xdg_surface_v6( + struct wlr_xdg_client_v6 *client, struct wlr_surface *surface, + uint32_t id); +void unmap_xdg_surface_v6(struct wlr_xdg_surface_v6 *surface); +void destroy_xdg_surface_v6(struct wlr_xdg_surface_v6 *surface); +void handle_xdg_surface_v6_commit(struct wlr_surface *wlr_surface); +void handle_xdg_surface_v6_precommit(struct wlr_surface *wlr_surface); + +void create_xdg_positioner_v6(struct wlr_xdg_client_v6 *client, uint32_t id); +struct wlr_xdg_positioner_v6_resource *get_xdg_positioner_v6_from_resource( + struct wl_resource *resource); + +void create_xdg_popup_v6(struct wlr_xdg_surface_v6 *xdg_surface, + struct wlr_xdg_surface_v6 *parent, + struct wlr_xdg_positioner_v6_resource *positioner, int32_t id); +void handle_xdg_surface_v6_popup_committed(struct wlr_xdg_surface_v6 *surface); +struct wlr_xdg_popup_grab_v6 *get_xdg_shell_v6_popup_grab_from_seat( + struct wlr_xdg_shell_v6 *shell, struct wlr_seat *seat); +void destroy_xdg_popup_v6(struct wlr_xdg_surface_v6 *surface); + +void create_xdg_toplevel_v6(struct wlr_xdg_surface_v6 *xdg_surface, + uint32_t id); +void handle_xdg_surface_v6_toplevel_committed(struct wlr_xdg_surface_v6 *surface); +void send_xdg_toplevel_v6_configure(struct wlr_xdg_surface_v6 *surface, + struct wlr_xdg_surface_v6_configure *configure); +void handle_xdg_toplevel_v6_ack_configure(struct wlr_xdg_surface_v6 *surface, + struct wlr_xdg_surface_v6_configure *configure); +bool compare_xdg_surface_v6_toplevel_state(struct wlr_xdg_toplevel_v6 *state); +void destroy_xdg_toplevel_v6(struct wlr_xdg_surface_v6 *surface); + +#endif diff --git a/include/util/array.h b/include/util/array.h new file mode 100644 index 00000000..1c046e1d --- /dev/null +++ b/include/util/array.h @@ -0,0 +1,9 @@ +#ifndef UTIL_ARRAY_H +#define UTIL_ARRAY_H + +#include <stdint.h> +#include <stdlib.h> + +size_t push_zeroes_to_end(uint32_t arr[], size_t n); + +#endif diff --git a/include/util/shm.h b/include/util/shm.h new file mode 100644 index 00000000..fb67b711 --- /dev/null +++ b/include/util/shm.h @@ -0,0 +1,7 @@ +#ifndef UTIL_SHM_H +#define UTIL_SHM_H + +int create_shm_file(void); +int allocate_shm_file(size_t size); + +#endif diff --git a/include/util/signal.h b/include/util/signal.h new file mode 100644 index 00000000..cc6cb525 --- /dev/null +++ b/include/util/signal.h @@ -0,0 +1,8 @@ +#ifndef UTIL_SIGNAL_H +#define UTIL_SIGNAL_H + +#include <wayland-server.h> + +void wlr_signal_emit_safe(struct wl_signal *signal, void *data); + +#endif diff --git a/include/wlr/backend.h b/include/wlr/backend.h new file mode 100644 index 00000000..54f2b5e8 --- /dev/null +++ b/include/wlr/backend.h @@ -0,0 +1,70 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_BACKEND_H +#define WLR_BACKEND_H + +#include <wayland-server.h> +#include <wlr/backend/session.h> +#include <wlr/render/egl.h> + +struct wlr_backend_impl; + +struct wlr_backend { + const struct wlr_backend_impl *impl; + + struct { + /** Raised when destroyed, passed the wlr_backend reference */ + struct wl_signal destroy; + /** Raised when new inputs are added, passed the wlr_input_device */ + struct wl_signal new_input; + /** Raised when new outputs are added, passed the wlr_output */ + struct wl_signal new_output; + } events; +}; + +typedef struct wlr_renderer *(*wlr_renderer_create_func_t)(struct wlr_egl *egl, EGLenum platform, + void *remote_display, EGLint *config_attribs, EGLint visual_id); +/** + * Automatically initializes the most suitable backend given the environment. + * Will always return a multibackend. The backend is created but not started. + * Returns NULL on failure. + * + * The compositor can request to initialize the backend's renderer by setting + * the create_render_func. The callback must initialize the given wlr_egl and + * return a valid wlr_renderer, or NULL if it has failed to initiaze it. + * Pass NULL as create_renderer_func to use the backend's default renderer. + */ +struct wlr_backend *wlr_backend_autocreate(struct wl_display *display, + wlr_renderer_create_func_t create_renderer_func); +/** + * Start the backend. This may signal new_input or new_output immediately, but + * may also wait until the display's event loop begins. Returns false on + * failure. + */ +bool wlr_backend_start(struct wlr_backend *backend); +/** + * Destroy the backend and clean up all of its resources. Normally called + * automatically when the wl_display is destroyed. + */ +void wlr_backend_destroy(struct wlr_backend *backend); +/** + * Obtains the wlr_renderer reference this backend is using. + */ +struct wlr_renderer *wlr_backend_get_renderer(struct wlr_backend *backend); +/** + * Obtains the wlr_session reference from this backend if there is any. + * Might return NULL for backends that don't use a session. + */ +struct wlr_session *wlr_backend_get_session(struct wlr_backend *backend); +/** + * Returns the clock used by the backend for presentation feedback. + */ +clockid_t wlr_backend_get_presentation_clock(struct wlr_backend *backend); + +#endif diff --git a/include/wlr/backend/drm.h b/include/wlr/backend/drm.h new file mode 100644 index 00000000..3724adfb --- /dev/null +++ b/include/wlr/backend/drm.h @@ -0,0 +1,37 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_BACKEND_DRM_H +#define WLR_BACKEND_DRM_H + +#include <wayland-server.h> +#include <wlr/backend.h> +#include <wlr/backend/session.h> +#include <wlr/types/wlr_output.h> + +/** + * Creates a DRM backend using the specified GPU file descriptor (typically from + * a device node in /dev/dri). + * + * To slave this to another DRM backend, pass it as the parent (which _must_ be + * a DRM backend, other kinds of backends raise SIGABRT). + */ +struct wlr_backend *wlr_drm_backend_create(struct wl_display *display, + struct wlr_session *session, int gpu_fd, struct wlr_backend *parent, + wlr_renderer_create_func_t create_renderer_func); + +bool wlr_backend_is_drm(struct wlr_backend *backend); +bool wlr_output_is_drm(struct wlr_output *output); + +/** + * Add mode to the list of available modes + */ +typedef struct _drmModeModeInfo drmModeModeInfo; +bool wlr_drm_connector_add_mode(struct wlr_output *output, const drmModeModeInfo *mode); + +#endif diff --git a/include/wlr/backend/headless.h b/include/wlr/backend/headless.h new file mode 100644 index 00000000..eab102e2 --- /dev/null +++ b/include/wlr/backend/headless.h @@ -0,0 +1,40 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_BACKEND_HEADLESS_H +#define WLR_BACKEND_HEADLESS_H + +#include <wlr/backend.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_output.h> + +/** + * Creates a headless backend. A headless backend has no outputs or inputs by + * default. + */ +struct wlr_backend *wlr_headless_backend_create(struct wl_display *display, + wlr_renderer_create_func_t create_renderer_func); +/** + * Create a new headless output backed by an in-memory EGL framebuffer. You can + * read pixels from this framebuffer via wlr_renderer_read_pixels but it is + * otherwise not displayed. + */ +struct wlr_output *wlr_headless_add_output(struct wlr_backend *backend, + unsigned int width, unsigned int height); +/** + * Creates a new input device. The caller is responsible for manually raising + * any event signals on the new input device if it wants to simulate input + * events. + */ +struct wlr_input_device *wlr_headless_add_input_device( + struct wlr_backend *backend, enum wlr_input_device_type type); +bool wlr_backend_is_headless(struct wlr_backend *backend); +bool wlr_input_device_is_headless(struct wlr_input_device *device); +bool wlr_output_is_headless(struct wlr_output *output); + +#endif diff --git a/include/wlr/backend/interface.h b/include/wlr/backend/interface.h new file mode 100644 index 00000000..4a6a5cbb --- /dev/null +++ b/include/wlr/backend/interface.h @@ -0,0 +1,32 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_BACKEND_INTERFACE_H +#define WLR_BACKEND_INTERFACE_H + +#include <stdbool.h> +#include <time.h> +#include <wlr/backend.h> +#include <wlr/render/egl.h> + +struct wlr_backend_impl { + bool (*start)(struct wlr_backend *backend); + void (*destroy)(struct wlr_backend *backend); + struct wlr_renderer *(*get_renderer)(struct wlr_backend *backend); + struct wlr_session *(*get_session)(struct wlr_backend *backend); + clockid_t (*get_presentation_clock)(struct wlr_backend *backend); +}; + +/** + * Initializes common state on a wlr_backend and sets the implementation to the + * provided wlr_backend_impl reference. + */ +void wlr_backend_init(struct wlr_backend *backend, + const struct wlr_backend_impl *impl); + +#endif diff --git a/include/wlr/backend/libinput.h b/include/wlr/backend/libinput.h new file mode 100644 index 00000000..1a2ab294 --- /dev/null +++ b/include/wlr/backend/libinput.h @@ -0,0 +1,27 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_BACKEND_LIBINPUT_H +#define WLR_BACKEND_LIBINPUT_H + +#include <libinput.h> +#include <wayland-server.h> +#include <wlr/backend.h> +#include <wlr/backend/session.h> +#include <wlr/types/wlr_input_device.h> + +struct wlr_backend *wlr_libinput_backend_create(struct wl_display *display, + struct wlr_session *session); +/** Gets the underlying libinput_device handle for the given wlr_input_device */ +struct libinput_device *wlr_libinput_get_device_handle( + struct wlr_input_device *dev); + +bool wlr_backend_is_libinput(struct wlr_backend *backend); +bool wlr_input_device_is_libinput(struct wlr_input_device *device); + +#endif diff --git a/include/wlr/backend/meson.build b/include/wlr/backend/meson.build new file mode 100644 index 00000000..3d6f0e40 --- /dev/null +++ b/include/wlr/backend/meson.build @@ -0,0 +1,16 @@ +install_headers( + 'drm.h', + 'headless.h', + 'interface.h', + 'libinput.h', + 'multi.h', + 'session.h', + 'wayland.h', + subdir: 'wlr/backend', +) + +if conf_data.get('WLR_HAS_X11_BACKEND', 0) == 1 + install_headers('x11.h', subdir: 'wlr/backend') +endif + +subdir('session') diff --git a/include/wlr/backend/multi.h b/include/wlr/backend/multi.h new file mode 100644 index 00000000..0687f4b6 --- /dev/null +++ b/include/wlr/backend/multi.h @@ -0,0 +1,36 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_BACKEND_MULTI_H +#define WLR_BACKEND_MULTI_H + +#include <wlr/backend.h> +#include <wlr/backend/session.h> + +/** + * Creates a multi-backend. Multi-backends wrap an arbitrary number of backends + * and aggregate their new_output/new_input signals. + */ +struct wlr_backend *wlr_multi_backend_create(struct wl_display *display); +/** + * Adds the given backend to the multi backend. This should be done before the + * new backend is started. + */ +bool wlr_multi_backend_add(struct wlr_backend *multi, + struct wlr_backend *backend); + +void wlr_multi_backend_remove(struct wlr_backend *multi, + struct wlr_backend *backend); + +bool wlr_backend_is_multi(struct wlr_backend *backend); +bool wlr_multi_is_empty(struct wlr_backend *backend); + +void wlr_multi_for_each_backend(struct wlr_backend *backend, + void (*callback)(struct wlr_backend *backend, void *data), void *data); + +#endif diff --git a/include/wlr/backend/session.h b/include/wlr/backend/session.h new file mode 100644 index 00000000..7b26f34c --- /dev/null +++ b/include/wlr/backend/session.h @@ -0,0 +1,94 @@ +#ifndef WLR_BACKEND_SESSION_H +#define WLR_BACKEND_SESSION_H + +#include <libudev.h> +#include <stdbool.h> +#include <sys/types.h> +#include <wayland-server.h> + +struct session_impl; + +struct wlr_device { + int fd; + dev_t dev; + struct wl_signal signal; + + struct wl_list link; +}; + +struct wlr_session { + const struct session_impl *impl; + /* + * Signal for when the session becomes active/inactive. + * It's called when we swap virtual terminal. + */ + struct wl_signal session_signal; + bool active; + + /* + * 0 if virtual terminals are not supported + * i.e. seat != "seat0" + */ + unsigned vtnr; + char seat[256]; + + struct udev *udev; + struct udev_monitor *mon; + struct wl_event_source *udev_event; + + struct wl_list devices; + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; +}; + +/* + * Opens a session, taking control of the current virtual terminal. + * This should not be called if another program is already in control + * of the terminal (Xorg, another Wayland compositor, etc.). + * + * If logind support is not enabled, you must have CAP_SYS_ADMIN or be root. + * It is safe to drop privileges after this is called. + * + * Returns NULL on error. + */ +struct wlr_session *wlr_session_create(struct wl_display *disp); + +/* + * Closes a previously opened session and restores the virtual terminal. + * You should call wlr_session_close_file on each files you opened + * with wlr_session_open_file before you call this. + */ +void wlr_session_destroy(struct wlr_session *session); + +/* + * Opens the file at path. + * This can only be used to open DRM or evdev (input) devices. + * + * When the session becomes inactive: + * - DRM files lose their DRM master status + * - evdev files become invalid and should be closed + * + * Returns -errno on error. + */ +int wlr_session_open_file(struct wlr_session *session, const char *path); + +/* + * Closes a file previously opened with wlr_session_open_file. + */ +void wlr_session_close_file(struct wlr_session *session, int fd); + +void wlr_session_signal_add(struct wlr_session *session, int fd, + struct wl_listener *listener); +/* + * Changes the virtual terminal. + */ +bool wlr_session_change_vt(struct wlr_session *session, unsigned vt); + +size_t wlr_session_find_gpus(struct wlr_session *session, + size_t ret_len, int *ret); + +#endif diff --git a/include/wlr/backend/session/interface.h b/include/wlr/backend/session/interface.h new file mode 100644 index 00000000..5ccf9c8a --- /dev/null +++ b/include/wlr/backend/session/interface.h @@ -0,0 +1,22 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_BACKEND_SESSION_INTERFACE_H +#define WLR_BACKEND_SESSION_INTERFACE_H + +#include <wlr/backend/session.h> + +struct session_impl { + struct wlr_session *(*create)(struct wl_display *disp); + void (*destroy)(struct wlr_session *session); + int (*open)(struct wlr_session *session, const char *path); + void (*close)(struct wlr_session *session, int fd); + bool (*change_vt)(struct wlr_session *session, unsigned vt); +}; + +#endif diff --git a/include/wlr/backend/session/meson.build b/include/wlr/backend/session/meson.build new file mode 100644 index 00000000..21b5a96b --- /dev/null +++ b/include/wlr/backend/session/meson.build @@ -0,0 +1 @@ +install_headers('interface.h', subdir: 'wlr/backend/session') diff --git a/include/wlr/backend/wayland.h b/include/wlr/backend/wayland.h new file mode 100644 index 00000000..119ea247 --- /dev/null +++ b/include/wlr/backend/wayland.h @@ -0,0 +1,45 @@ +#ifndef WLR_BACKEND_WAYLAND_H +#define WLR_BACKEND_WAYLAND_H + +#include <stdbool.h> +#include <wayland-client.h> +#include <wayland-server.h> +#include <wlr/backend.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_output.h> + +/** + * Creates a new wlr_wl_backend. This backend will be created with no outputs; + * you must use wlr_wl_output_create to add them. + * + * The `remote` argument is the name of the host compositor wayland socket. Set + * to NULL for the default behaviour (WAYLAND_DISPLAY env variable or wayland-0 + * default) + */ +struct wlr_backend *wlr_wl_backend_create(struct wl_display *display, const char *remote, + wlr_renderer_create_func_t create_renderer_func); + +/** + * Adds a new output to this backend. You may remove outputs by destroying them. + * Note that if called before initializing the backend, this will return NULL + * and your outputs will be created during initialization (and given to you via + * the output_add signal). + */ +struct wlr_output *wlr_wl_output_create(struct wlr_backend *backend); + +/** + * True if the given backend is a wlr_wl_backend. + */ +bool wlr_backend_is_wl(struct wlr_backend *backend); + +/** + * True if the given input device is a wlr_wl_input_device. + */ +bool wlr_input_device_is_wl(struct wlr_input_device *device); + +/** + * True if the given output is a wlr_wl_output. + */ +bool wlr_output_is_wl(struct wlr_output *output); + +#endif diff --git a/include/wlr/backend/x11.h b/include/wlr/backend/x11.h new file mode 100644 index 00000000..5793a3b9 --- /dev/null +++ b/include/wlr/backend/x11.h @@ -0,0 +1,20 @@ +#ifndef WLR_BACKEND_X11_H +#define WLR_BACKEND_X11_H + +#include <stdbool.h> + +#include <wayland-server.h> + +#include <wlr/backend.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_output.h> + +struct wlr_backend *wlr_x11_backend_create(struct wl_display *display, + const char *x11_display, wlr_renderer_create_func_t create_renderer_func); +struct wlr_output *wlr_x11_output_create(struct wlr_backend *backend); + +bool wlr_backend_is_x11(struct wlr_backend *backend); +bool wlr_input_device_is_x11(struct wlr_input_device *device); +bool wlr_output_is_x11(struct wlr_output *output); + +#endif diff --git a/include/wlr/config.h.in b/include/wlr/config.h.in new file mode 100644 index 00000000..94273fac --- /dev/null +++ b/include/wlr/config.h.in @@ -0,0 +1,16 @@ +#ifndef WLR_CONFIG_H +#define WLR_CONFIG_H + +#mesondefine WLR_HAS_LIBCAP + +#mesondefine WLR_HAS_SYSTEMD +#mesondefine WLR_HAS_ELOGIND + +#mesondefine WLR_HAS_X11_BACKEND + +#mesondefine WLR_HAS_XWAYLAND + +#mesondefine WLR_HAS_XCB_ERRORS +#mesondefine WLR_HAS_XCB_ICCCM + +#endif diff --git a/include/wlr/interfaces/meson.build b/include/wlr/interfaces/meson.build new file mode 100644 index 00000000..7d4d811d --- /dev/null +++ b/include/wlr/interfaces/meson.build @@ -0,0 +1,11 @@ +install_headers( + 'wlr_input_device.h', + 'wlr_keyboard.h', + 'wlr_output.h', + 'wlr_pointer.h', + 'wlr_switch.h', + 'wlr_tablet_pad.h', + 'wlr_tablet_tool.h', + 'wlr_touch.h', + subdir: 'wlr/interfaces', +) diff --git a/include/wlr/interfaces/wlr_input_device.h b/include/wlr/interfaces/wlr_input_device.h new file mode 100644 index 00000000..05248bf6 --- /dev/null +++ b/include/wlr/interfaces/wlr_input_device.h @@ -0,0 +1,25 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_INTERFACES_WLR_INPUT_DEVICE_H +#define WLR_INTERFACES_WLR_INPUT_DEVICE_H + +#include <wlr/types/wlr_input_device.h> + +struct wlr_input_device_impl { + void (*destroy)(struct wlr_input_device *wlr_device); +}; + +void wlr_input_device_init( + struct wlr_input_device *wlr_device, + enum wlr_input_device_type type, + const struct wlr_input_device_impl *impl, + const char *name, int vendor, int product); +void wlr_input_device_destroy(struct wlr_input_device *dev); + +#endif diff --git a/include/wlr/interfaces/wlr_keyboard.h b/include/wlr/interfaces/wlr_keyboard.h new file mode 100644 index 00000000..5d537827 --- /dev/null +++ b/include/wlr/interfaces/wlr_keyboard.h @@ -0,0 +1,29 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_INTERFACES_WLR_KEYBOARD_H +#define WLR_INTERFACES_WLR_KEYBOARD_H + +#include <stdint.h> +#include <wlr/types/wlr_keyboard.h> + +struct wlr_keyboard_impl { + void (*destroy)(struct wlr_keyboard *keyboard); + void (*led_update)(struct wlr_keyboard *keyboard, uint32_t leds); +}; + +void wlr_keyboard_init(struct wlr_keyboard *keyboard, + const struct wlr_keyboard_impl *impl); +void wlr_keyboard_destroy(struct wlr_keyboard *keyboard); +void wlr_keyboard_notify_key(struct wlr_keyboard *keyboard, + struct wlr_event_keyboard_key *event); +void wlr_keyboard_notify_modifiers(struct wlr_keyboard *keyboard, + uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, + uint32_t group); + +#endif diff --git a/include/wlr/interfaces/wlr_output.h b/include/wlr/interfaces/wlr_output.h new file mode 100644 index 00000000..f7ffe3b4 --- /dev/null +++ b/include/wlr/interfaces/wlr_output.h @@ -0,0 +1,52 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_INTERFACES_WLR_OUTPUT_H +#define WLR_INTERFACES_WLR_OUTPUT_H + +#include <stdbool.h> +#include <wlr/backend.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_output.h> + +struct wlr_output_impl { + bool (*enable)(struct wlr_output *output, bool enable); + bool (*set_mode)(struct wlr_output *output, struct wlr_output_mode *mode); + bool (*set_custom_mode)(struct wlr_output *output, int32_t width, + int32_t height, int32_t refresh); + void (*transform)(struct wlr_output *output, + enum wl_output_transform transform); + bool (*set_cursor)(struct wlr_output *output, struct wlr_texture *texture, + int32_t scale, enum wl_output_transform transform, + int32_t hotspot_x, int32_t hotspot_y, bool update_texture); + bool (*move_cursor)(struct wlr_output *output, int x, int y); + void (*destroy)(struct wlr_output *output); + bool (*make_current)(struct wlr_output *output, int *buffer_age); + bool (*swap_buffers)(struct wlr_output *output, pixman_region32_t *damage); + bool (*set_gamma)(struct wlr_output *output, size_t size, + const uint16_t *r, const uint16_t *g, const uint16_t *b); + size_t (*get_gamma_size)(struct wlr_output *output); + bool (*export_dmabuf)(struct wlr_output *output, + struct wlr_dmabuf_attributes *attribs); + bool (*schedule_frame)(struct wlr_output *output); +}; + +void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend, + const struct wlr_output_impl *impl, struct wl_display *display); +void wlr_output_update_mode(struct wlr_output *output, + struct wlr_output_mode *mode); +void wlr_output_update_custom_mode(struct wlr_output *output, int32_t width, + int32_t height, int32_t refresh); +void wlr_output_update_enabled(struct wlr_output *output, bool enabled); +void wlr_output_update_needs_swap(struct wlr_output *output); +void wlr_output_damage_whole(struct wlr_output *output); +void wlr_output_send_frame(struct wlr_output *output); +void wlr_output_send_present(struct wlr_output *output, + struct wlr_output_event_present *event); + +#endif diff --git a/include/wlr/interfaces/wlr_pointer.h b/include/wlr/interfaces/wlr_pointer.h new file mode 100644 index 00000000..fd3ab102 --- /dev/null +++ b/include/wlr/interfaces/wlr_pointer.h @@ -0,0 +1,22 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_INTERFACES_WLR_POINTER_H +#define WLR_INTERFACES_WLR_POINTER_H + +#include <wlr/types/wlr_pointer.h> + +struct wlr_pointer_impl { + void (*destroy)(struct wlr_pointer *pointer); +}; + +void wlr_pointer_init(struct wlr_pointer *pointer, + const struct wlr_pointer_impl *impl); +void wlr_pointer_destroy(struct wlr_pointer *pointer); + +#endif diff --git a/include/wlr/interfaces/wlr_switch.h b/include/wlr/interfaces/wlr_switch.h new file mode 100644 index 00000000..0b0454f5 --- /dev/null +++ b/include/wlr/interfaces/wlr_switch.h @@ -0,0 +1,22 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_INTERFACES_WLR_SWITCH_H +#define WLR_INTERFACES_WLR_SWITCH_H + +#include <wlr/types/wlr_switch.h> + +struct wlr_switch_impl { + void (*destroy)(struct wlr_switch *lid_switch); +}; + +void wlr_switch_init(struct wlr_switch *lid_switch, + struct wlr_switch_impl *impl); +void wlr_switch_destroy(struct wlr_switch *lid_switch); + +#endif diff --git a/include/wlr/interfaces/wlr_tablet_pad.h b/include/wlr/interfaces/wlr_tablet_pad.h new file mode 100644 index 00000000..86bbe9c3 --- /dev/null +++ b/include/wlr/interfaces/wlr_tablet_pad.h @@ -0,0 +1,22 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_INTERFACES_WLR_TABLET_PAD_H +#define WLR_INTERFACES_WLR_TABLET_PAD_H + +#include <wlr/types/wlr_tablet_pad.h> + +struct wlr_tablet_pad_impl { + void (*destroy)(struct wlr_tablet_pad *pad); +}; + +void wlr_tablet_pad_init(struct wlr_tablet_pad *pad, + struct wlr_tablet_pad_impl *impl); +void wlr_tablet_pad_destroy(struct wlr_tablet_pad *pad); + +#endif diff --git a/include/wlr/interfaces/wlr_tablet_tool.h b/include/wlr/interfaces/wlr_tablet_tool.h new file mode 100644 index 00000000..9cfc3ca0 --- /dev/null +++ b/include/wlr/interfaces/wlr_tablet_tool.h @@ -0,0 +1,22 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_INTERFACES_WLR_TABLET_TOOL_H +#define WLR_INTERFACES_WLR_TABLET_TOOL_H + +#include <wlr/types/wlr_tablet_tool.h> + +struct wlr_tablet_impl { + void (*destroy)(struct wlr_tablet *tablet); +}; + +void wlr_tablet_init(struct wlr_tablet *tablet, + struct wlr_tablet_impl *impl); +void wlr_tablet_destroy(struct wlr_tablet *tablet); + +#endif diff --git a/include/wlr/interfaces/wlr_touch.h b/include/wlr/interfaces/wlr_touch.h new file mode 100644 index 00000000..cc426332 --- /dev/null +++ b/include/wlr/interfaces/wlr_touch.h @@ -0,0 +1,22 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_INTERFACES_WLR_TOUCH_H +#define WLR_INTERFACES_WLR_TOUCH_H + +#include <wlr/types/wlr_touch.h> + +struct wlr_touch_impl { + void (*destroy)(struct wlr_touch *touch); +}; + +void wlr_touch_init(struct wlr_touch *touch, + struct wlr_touch_impl *impl); +void wlr_touch_destroy(struct wlr_touch *touch); + +#endif diff --git a/include/wlr/meson.build b/include/wlr/meson.build new file mode 100644 index 00000000..8874dbc7 --- /dev/null +++ b/include/wlr/meson.build @@ -0,0 +1,26 @@ +version_array = meson.project_version().split('.') +version_data = configuration_data() +version_data.set_quoted('WLR_VERSION_STR', meson.project_version()) +version_data.set('WLR_VERSION_MAJOR', version_array[0]) +version_data.set('WLR_VERSION_MINOR', version_array[1]) +version_data.set('WLR_VERSION_MICRO', version_array[2]) +version_data.set('WLR_VERSION_API_CURRENT', so_version[0]) +version_data.set('WLR_VERSION_API_REVISION', so_version[1]) +version_data.set('WLR_VERSION_API_AGE', so_version[2]) + +install_headers( + configure_file(input: 'config.h.in', output: 'config.h',configuration: conf_data), + configure_file(input: 'version.h.in', output: 'version.h', configuration: version_data), + 'backend.h', + 'xcursor.h', + subdir: 'wlr' +) +if conf_data.get('WLR_HAS_XWAYLAND', 0) == 1 + install_headers('xwayland.h', subdir: 'wlr') +endif + +subdir('backend') +subdir('interfaces') +subdir('render') +subdir('types') +subdir('util') diff --git a/include/wlr/render/dmabuf.h b/include/wlr/render/dmabuf.h new file mode 100644 index 00000000..32cfe874 --- /dev/null +++ b/include/wlr/render/dmabuf.h @@ -0,0 +1,48 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_RENDER_DMABUF_H +#define WLR_RENDER_DMABUF_H + +#include <stdint.h> + +// So we don't have to pull in linux specific drm headers +#ifndef DRM_FORMAT_MOD_INVALID +#define DRM_FORMAT_MOD_INVALID ((1ULL<<56) - 1) +#endif + +#ifndef DRM_FORMAT_MOD_LINEAR +#define DRM_FORMAT_MOD_LINEAR 0 +#endif + +#define WLR_DMABUF_MAX_PLANES 4 + +enum wlr_dmabuf_attributes_flags { + WLR_DMABUF_ATTRIBUTES_FLAGS_Y_INVERT = 1, + WLR_DMABUF_ATTRIBUTES_FLAGS_INTERLACED = 2, + WLR_DMABUF_ATTRIBUTES_FLAGS_BOTTOM_FIRST = 4, +}; + +struct wlr_dmabuf_attributes { + int32_t width, height; + uint32_t format; + uint32_t flags; // enum wlr_dmabuf_attributes_flags + uint64_t modifier; + + int n_planes; + uint32_t offset[WLR_DMABUF_MAX_PLANES]; + uint32_t stride[WLR_DMABUF_MAX_PLANES]; + int fd[WLR_DMABUF_MAX_PLANES]; +}; + +/** + * Closes all file descriptors in the DMA-BUF attributes. + */ +void wlr_dmabuf_attributes_finish(struct wlr_dmabuf_attributes *attribs); + +#endif diff --git a/include/wlr/render/egl.h b/include/wlr/render/egl.h new file mode 100644 index 00000000..269af7e2 --- /dev/null +++ b/include/wlr/render/egl.h @@ -0,0 +1,118 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_RENDER_EGL_H +#define WLR_RENDER_EGL_H + +#include <wlr/config.h> + +#if !WLR_HAS_X11_BACKEND && !WLR_HAS_XWAYLAND && !defined MESA_EGL_NO_X11_HEADERS +#define MESA_EGL_NO_X11_HEADERS +#endif + +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <pixman.h> +#include <stdbool.h> +#include <wayland-server.h> +#include <wlr/render/dmabuf.h> + +struct wlr_egl { + EGLenum platform; + EGLDisplay display; + EGLConfig config; + EGLContext context; + + const char *exts_str; + + struct { + bool bind_wayland_display_wl; + bool buffer_age_ext; + bool image_base_khr; + bool image_dma_buf_export_mesa; + bool image_dmabuf_import_ext; + bool image_dmabuf_import_modifiers_ext; + bool swap_buffers_with_damage_ext; + bool swap_buffers_with_damage_khr; + } exts; + + struct wl_display *wl_display; +}; + +// TODO: Allocate and return a wlr_egl +/** + * Initializes an EGL context for the given platform and remote display. + * Will attempt to load all possibly required api functions. + */ +bool wlr_egl_init(struct wlr_egl *egl, EGLenum platform, void *remote_display, + EGLint *config_attribs, EGLint visual_id); + +/** + * Frees all related EGL resources, makes the context not-current and + * unbinds a bound wayland display. + */ +void wlr_egl_finish(struct wlr_egl *egl); + +/** + * Binds the given display to the EGL instance. + * This will allow clients to create EGL surfaces from wayland ones and render + * to it. + */ +bool wlr_egl_bind_display(struct wlr_egl *egl, struct wl_display *local_display); + +/** + * Returns a surface for the given native window + * The window must match the remote display the wlr_egl was created with. + */ +EGLSurface wlr_egl_create_surface(struct wlr_egl *egl, void *window); + +/** + * Creates an EGL image from the given wl_drm buffer resource. + */ +EGLImageKHR wlr_egl_create_image_from_wl_drm(struct wlr_egl *egl, + struct wl_resource *data, EGLint *fmt, int *width, int *height, + bool *inverted_y); + +/** + * 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); + +/** + * Get the available dmabuf formats + */ +int wlr_egl_get_dmabuf_formats(struct wlr_egl *egl, int **formats); + +/** + * Get the available dmabuf modifiers for a given format + */ +int wlr_egl_get_dmabuf_modifiers(struct wlr_egl *egl, int format, + uint64_t **modifiers); + +bool wlr_egl_export_image_to_dmabuf(struct wlr_egl *egl, EGLImageKHR image, + int32_t width, int32_t height, uint32_t flags, + struct wlr_dmabuf_attributes *attribs); + +/** + * Destroys an EGL image created with the given wlr_egl. + */ +bool wlr_egl_destroy_image(struct wlr_egl *egl, EGLImageKHR image); + +bool wlr_egl_make_current(struct wlr_egl *egl, EGLSurface surface, + int *buffer_age); + +bool wlr_egl_is_current(struct wlr_egl *egl); + +bool wlr_egl_swap_buffers(struct wlr_egl *egl, EGLSurface surface, + pixman_region32_t *damage); + +bool wlr_egl_destroy_surface(struct wlr_egl *egl, EGLSurface surface); + +#endif diff --git a/include/wlr/render/gles2.h b/include/wlr/render/gles2.h new file mode 100644 index 00000000..fca11ab8 --- /dev/null +++ b/include/wlr/render/gles2.h @@ -0,0 +1,27 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_RENDER_GLES2_H +#define WLR_RENDER_GLES2_H + +#include <wlr/backend.h> +#include <wlr/render/wlr_renderer.h> + +struct wlr_egl; + +struct wlr_renderer *wlr_gles2_renderer_create(struct wlr_egl *egl); + +struct wlr_texture *wlr_gles2_texture_from_pixels(struct wlr_egl *egl, + enum wl_shm_format wl_fmt, uint32_t stride, uint32_t width, uint32_t height, + const void *data); +struct wlr_texture *wlr_gles2_texture_from_wl_drm(struct wlr_egl *egl, + struct wl_resource *data); +struct wlr_texture *wlr_gles2_texture_from_dmabuf(struct wlr_egl *egl, + struct wlr_dmabuf_attributes *attribs); + +#endif diff --git a/include/wlr/render/interface.h b/include/wlr/render/interface.h new file mode 100644 index 00000000..c98a7cda --- /dev/null +++ b/include/wlr/render/interface.h @@ -0,0 +1,87 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_RENDER_INTERFACE_H +#define WLR_RENDER_INTERFACE_H + +#include <wlr/config.h> + +#if !WLR_HAS_X11_BACKEND && !WLR_HAS_XWAYLAND && !defined MESA_EGL_NO_X11_HEADERS +#define MESA_EGL_NO_X11_HEADERS +#endif + +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <stdbool.h> +#include <wayland-server-protocol.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/render/wlr_texture.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_output.h> +#include <wlr/render/dmabuf.h> + +struct wlr_renderer_impl { + void (*begin)(struct wlr_renderer *renderer, uint32_t width, + uint32_t height); + void (*end)(struct wlr_renderer *renderer); + void (*clear)(struct wlr_renderer *renderer, const float color[static 4]); + void (*scissor)(struct wlr_renderer *renderer, struct wlr_box *box); + bool (*render_texture_with_matrix)(struct wlr_renderer *renderer, + struct wlr_texture *texture, const float matrix[static 9], + float alpha); + void (*render_quad_with_matrix)(struct wlr_renderer *renderer, + const float color[static 4], const float matrix[static 9]); + void (*render_ellipse_with_matrix)(struct wlr_renderer *renderer, + const float color[static 4], const float matrix[static 9]); + const enum wl_shm_format *(*formats)( + struct wlr_renderer *renderer, size_t *len); + bool (*format_supported)(struct wlr_renderer *renderer, + enum wl_shm_format fmt); + bool (*resource_is_wl_drm_buffer)(struct wlr_renderer *renderer, + struct wl_resource *resource); + void (*wl_drm_buffer_get_size)(struct wlr_renderer *renderer, + struct wl_resource *buffer, int *width, int *height); + int (*get_dmabuf_formats)(struct wlr_renderer *renderer, int **formats); + int (*get_dmabuf_modifiers)(struct wlr_renderer *renderer, int format, + uint64_t **modifiers); + enum wl_shm_format (*preferred_read_format)(struct wlr_renderer *renderer); + bool (*read_pixels)(struct wlr_renderer *renderer, enum wl_shm_format fmt, + uint32_t *flags, 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); + struct wlr_texture *(*texture_from_pixels)(struct wlr_renderer *renderer, + enum wl_shm_format fmt, uint32_t stride, uint32_t width, + uint32_t height, const void *data); + struct wlr_texture *(*texture_from_wl_drm)(struct wlr_renderer *renderer, + struct wl_resource *data); + struct wlr_texture *(*texture_from_dmabuf)(struct wlr_renderer *renderer, + struct wlr_dmabuf_attributes *attribs); + void (*destroy)(struct wlr_renderer *renderer); + void (*init_wl_display)(struct wlr_renderer *renderer, + struct wl_display *wl_display); +}; + +void wlr_renderer_init(struct wlr_renderer *renderer, + const struct wlr_renderer_impl *impl); + +struct wlr_texture_impl { + void (*get_size)(struct wlr_texture *texture, int *width, int *height); + bool (*is_opaque)(struct wlr_texture *texture); + bool (*write_pixels)(struct wlr_texture *texture, + 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, + const void *data); + bool (*to_dmabuf)(struct wlr_texture *texture, + struct wlr_dmabuf_attributes *attribs); + void (*destroy)(struct wlr_texture *texture); +}; + +void wlr_texture_init(struct wlr_texture *texture, + const struct wlr_texture_impl *impl); + +#endif diff --git a/include/wlr/render/meson.build b/include/wlr/render/meson.build new file mode 100644 index 00000000..05127bb7 --- /dev/null +++ b/include/wlr/render/meson.build @@ -0,0 +1,9 @@ +install_headers( + 'dmabuf.h', + 'egl.h', + 'gles2.h', + 'interface.h', + 'wlr_renderer.h', + 'wlr_texture.h', + subdir: 'wlr/render' +) diff --git a/include/wlr/render/wlr_renderer.h b/include/wlr/render/wlr_renderer.h new file mode 100644 index 00000000..9c031b7f --- /dev/null +++ b/include/wlr/render/wlr_renderer.h @@ -0,0 +1,121 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_RENDER_WLR_RENDERER_H +#define WLR_RENDER_WLR_RENDERER_H + +#include <stdint.h> +#include <wayland-server-protocol.h> +#include <wlr/render/egl.h> +#include <wlr/render/wlr_texture.h> +#include <wlr/types/wlr_box.h> + +enum wlr_renderer_read_pixels_flags { + WLR_RENDERER_READ_PIXELS_Y_INVERT = 1, +}; + +struct wlr_renderer_impl; + +struct wlr_renderer { + const struct wlr_renderer_impl *impl; + + struct { + struct wl_signal destroy; + } events; +}; + +struct wlr_renderer *wlr_renderer_autocreate(struct wlr_egl *egl, EGLenum platform, + void *remote_display, EGLint *config_attribs, EGLint visual_id); + +void wlr_renderer_begin(struct wlr_renderer *r, int width, int height); +void wlr_renderer_end(struct wlr_renderer *r); +void wlr_renderer_clear(struct wlr_renderer *r, const float color[static 4]); +/** + * Defines a scissor box. Only pixels that lie within the scissor box can be + * modified by drawing functions. Providing a NULL `box` disables the scissor + * box. + */ +void wlr_renderer_scissor(struct wlr_renderer *r, struct wlr_box *box); +/** + * Renders the requested texture. + */ +bool wlr_render_texture(struct wlr_renderer *r, struct wlr_texture *texture, + const float projection[static 9], int x, int y, float alpha); +/** + * Renders the requested texture using the provided matrix. + */ +bool wlr_render_texture_with_matrix(struct wlr_renderer *r, + struct wlr_texture *texture, const float matrix[static 9], float alpha); +/** + * Renders a solid rectangle in the specified color. + */ +void wlr_render_rect(struct wlr_renderer *r, const struct wlr_box *box, + const float color[static 4], const float projection[static 9]); +/** + * Renders a solid quadrangle in the specified color with the specified matrix. + */ +void wlr_render_quad_with_matrix(struct wlr_renderer *r, + const float color[static 4], const float matrix[static 9]); +/** + * Renders a solid ellipse in the specified color. + */ +void wlr_render_ellipse(struct wlr_renderer *r, const struct wlr_box *box, + const float color[static 4], const float projection[static 9]); +/** + * Renders a solid ellipse in the specified color with the specified matrix. + */ +void wlr_render_ellipse_with_matrix(struct wlr_renderer *r, + const float color[static 4], const float matrix[static 9]); +/** + * Returns a list of pixel formats supported by this renderer. + */ +const enum wl_shm_format *wlr_renderer_get_formats(struct wlr_renderer *r, + size_t *len); +/** + * Returns true if this wl_buffer is a wl_drm buffer. + */ +bool wlr_renderer_resource_is_wl_drm_buffer(struct wlr_renderer *renderer, + struct wl_resource *buffer); +/** + * Gets the width and height of a wl_drm buffer. + */ +void wlr_renderer_wl_drm_buffer_get_size(struct wlr_renderer *renderer, + struct wl_resource *buffer, int *width, int *height); +/** + * Get the available dmabuf formats + */ +int wlr_renderer_get_dmabuf_formats(struct wlr_renderer *renderer, + int **formats); +/** + * Get the available dmabuf modifiers for a given format + */ +int wlr_renderer_get_dmabuf_modifiers(struct wlr_renderer *renderer, int format, + uint64_t **modifiers); +/** + * Reads out of pixels of the currently bound surface into data. `stride` is in + * bytes. + * + * If `flags` is not NULl, the caller indicates that it accepts frame flags + * defined in `enum wlr_renderer_read_pixels_flags`. + */ +bool wlr_renderer_read_pixels(struct wlr_renderer *r, enum wl_shm_format fmt, + uint32_t *flags, 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); +/** + * Checks if a format is supported. + */ +bool wlr_renderer_format_supported(struct wlr_renderer *r, + enum wl_shm_format fmt); +void wlr_renderer_init_wl_display(struct wlr_renderer *r, + struct wl_display *wl_display); +/** + * Destroys this wlr_renderer. Textures must be destroyed separately. + */ +void wlr_renderer_destroy(struct wlr_renderer *renderer); + +#endif diff --git a/include/wlr/render/wlr_texture.h b/include/wlr/render/wlr_texture.h new file mode 100644 index 00000000..f210717a --- /dev/null +++ b/include/wlr/render/wlr_texture.h @@ -0,0 +1,73 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_RENDER_WLR_TEXTURE_H +#define WLR_RENDER_WLR_TEXTURE_H + +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <stdint.h> +#include <wayland-server-protocol.h> +#include <wlr/render/dmabuf.h> + +struct wlr_renderer; +struct wlr_texture_impl; + +struct wlr_texture { + const struct wlr_texture_impl *impl; +}; + +/** + * Create a new texture from raw pixel data. `stride` is in bytes. The returned + * texture is mutable. + */ +struct wlr_texture *wlr_texture_from_pixels(struct wlr_renderer *renderer, + enum wl_shm_format wl_fmt, uint32_t stride, uint32_t width, uint32_t height, + const void *data); + +/** + * Create a new texture from a wl_drm resource. The returned texture is + * immutable. + */ +struct wlr_texture *wlr_texture_from_wl_drm(struct wlr_renderer *renderer, + struct wl_resource *data); + +/** + * Create a new texture from a DMA-BUF. The returned texture is immutable. + */ +struct wlr_texture *wlr_texture_from_dmabuf(struct wlr_renderer *renderer, + struct wlr_dmabuf_attributes *attribs); + +/** + * Get the texture width and height. + */ +void wlr_texture_get_size(struct wlr_texture *texture, int *width, int *height); + +/** + * Returns true if this texture is using a fully opaque format. + */ +bool wlr_texture_is_opaque(struct wlr_texture *texture); + +/** + * Update a texture with raw pixels. The texture must be mutable, and the input + * data must have the same pixel format that the texture was created with. + */ +bool wlr_texture_write_pixels(struct wlr_texture *texture, + 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, + const void *data); + +bool wlr_texture_to_dmabuf(struct wlr_texture *texture, + struct wlr_dmabuf_attributes *attribs); + +/** + * Destroys this wlr_texture. + */ +void wlr_texture_destroy(struct wlr_texture *texture); + +#endif diff --git a/include/wlr/types/meson.build b/include/wlr/types/meson.build new file mode 100644 index 00000000..752c0dea --- /dev/null +++ b/include/wlr/types/meson.build @@ -0,0 +1,49 @@ +install_headers( + 'wlr_box.h', + 'wlr_buffer.h', + 'wlr_compositor.h', + 'wlr_cursor.h', + 'wlr_data_device.h', + 'wlr_export_dmabuf_v1.h', + 'wlr_foreign_toplevel_management_v1.h', + 'wlr_gamma_control_v1.h', + 'wlr_gamma_control.h', + 'wlr_gtk_primary_selection.h', + 'wlr_idle_inhibit_v1.h', + 'wlr_idle.h', + 'wlr_input_device.h', + 'wlr_input_inhibitor.h', + 'wlr_input_method_v2.h', + 'wlr_keyboard.h', + 'wlr_layer_shell_v1.h', + 'wlr_linux_dmabuf_v1.h', + 'wlr_list.h', + 'wlr_matrix.h', + 'wlr_output_damage.h', + 'wlr_output_layout.h', + 'wlr_output.h', + 'wlr_pointer.h', + 'wlr_pointer_constraints_v1.h', + 'wlr_presentation_time.h', + 'wlr_primary_selection.h', + 'wlr_region.h', + 'wlr_screencopy_v1.h', + 'wlr_screenshooter.h', + 'wlr_seat.h', + 'wlr_server_decoration.h', + 'wlr_surface.h', + 'wlr_switch.h', + 'wlr_tablet_pad.h', + 'wlr_tablet_tool.h', + 'wlr_tablet_v2.h', + 'wlr_text_input_v3.h', + 'wlr_touch.h', + 'wlr_virtual_keyboard_v1.h', + 'wlr_wl_shell.h', + 'wlr_xcursor_manager.h', + 'wlr_xdg_decoration_v1.h', + 'wlr_xdg_output_v1.h', + 'wlr_xdg_shell_v6.h', + 'wlr_xdg_shell.h', + subdir: 'wlr/types', +) diff --git a/include/wlr/types/wlr_box.h b/include/wlr/types/wlr_box.h new file mode 100644 index 00000000..40fd69bd --- /dev/null +++ b/include/wlr/types/wlr_box.h @@ -0,0 +1,44 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_BOX_H +#define WLR_TYPES_WLR_BOX_H + +#include <pixman.h> +#include <stdbool.h> +#include <wayland-server.h> + +struct wlr_box { + int x, y; + int width, height; +}; + +void wlr_box_closest_point(const struct wlr_box *box, double x, double y, + double *dest_x, double *dest_y); + +bool wlr_box_intersection(struct wlr_box *dest, const struct wlr_box *box_a, + const struct wlr_box *box_b); + +bool wlr_box_contains_point(const struct wlr_box *box, double x, double y); + +bool wlr_box_empty(const struct wlr_box *box); + +/** + * Transforms a box inside a `width` x `height` box. + */ +void wlr_box_transform(struct wlr_box *dest, const struct wlr_box *box, + enum wl_output_transform transform, int width, int height); + +/** + * Creates the smallest box that contains the box rotated about its center. + */ +void wlr_box_rotated_bounds(struct wlr_box *dest, const struct wlr_box *box, float rotation); + +void wlr_box_from_pixman_box32(struct wlr_box *dest, const pixman_box32_t box); + +#endif diff --git a/include/wlr/types/wlr_buffer.h b/include/wlr/types/wlr_buffer.h new file mode 100644 index 00000000..0c987b17 --- /dev/null +++ b/include/wlr/types/wlr_buffer.h @@ -0,0 +1,71 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_BUFFER_H +#define WLR_TYPES_WLR_BUFFER_H + +#include <pixman.h> +#include <wayland-server.h> + +/** + * A client buffer. + */ +struct wlr_buffer { + /** + * The buffer resource, if any. Will be NULL if the client destroys it. + */ + struct wl_resource *resource; + /** + * The buffer's texture, if any. A buffer will not have a texture if the + * client destroys the buffer before it has been released. + */ + struct wlr_texture *texture; + bool released; + size_t n_refs; + + struct wl_listener resource_destroy; +}; + +struct wlr_renderer; + +/** + * Check if a resource is a wl_buffer resource. + */ +bool wlr_resource_is_buffer(struct wl_resource *resource); +/** + * Get the size of a wl_buffer resource. + */ +bool wlr_buffer_get_resource_size(struct wl_resource *resource, + struct wlr_renderer *renderer, int *width, int *height); + +/** + * Upload a buffer to the GPU and reference it. + */ +struct wlr_buffer *wlr_buffer_create(struct wlr_renderer *renderer, + struct wl_resource *resource); +/** + * Reference the buffer. + */ +struct wlr_buffer *wlr_buffer_ref(struct wlr_buffer *buffer); +/** + * Unreference the buffer. After this call, `buffer` may not be accessed + * anymore. + */ +void wlr_buffer_unref(struct wlr_buffer *buffer); +/** + * Try to update the buffer's content. On success, returns the updated buffer + * and destroys the provided `buffer`. On error, `buffer` is intact and NULL is + * returned. + * + * Fails if there's more than one reference to the buffer or if the texture + * isn't mutable. + */ +struct wlr_buffer *wlr_buffer_apply_damage(struct wlr_buffer *buffer, + struct wl_resource *resource, pixman_region32_t *damage); + +#endif diff --git a/include/wlr/types/wlr_compositor.h b/include/wlr/types/wlr_compositor.h new file mode 100644 index 00000000..36b9e83f --- /dev/null +++ b/include/wlr/types/wlr_compositor.h @@ -0,0 +1,53 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_COMPOSITOR_H +#define WLR_TYPES_WLR_COMPOSITOR_H + +#include <wayland-server.h> +#include <wlr/render/wlr_renderer.h> + +struct wlr_surface; + +struct wlr_subcompositor { + struct wl_global *global; + struct wl_list resources; + struct wl_list subsurface_resources; +}; + +struct wlr_compositor { + struct wl_global *global; + struct wl_list resources; + struct wlr_renderer *renderer; + struct wl_list surface_resources; + struct wl_list region_resources; + + struct wlr_subcompositor subcompositor; + + struct wl_listener display_destroy; + + struct { + struct wl_signal new_surface; + struct wl_signal destroy; + } events; +}; + +void wlr_compositor_destroy(struct wlr_compositor *wlr_compositor); +struct wlr_compositor *wlr_compositor_create(struct wl_display *display, + struct wlr_renderer *renderer); + +bool wlr_surface_is_subsurface(struct wlr_surface *surface); + +/** + * Get a subsurface from a surface. Can return NULL if the subsurface has been + * destroyed. + */ +struct wlr_subsurface *wlr_subsurface_from_wlr_surface( + struct wlr_surface *surface); + +#endif diff --git a/include/wlr/types/wlr_cursor.h b/include/wlr/types/wlr_cursor.h new file mode 100644 index 00000000..44ced1f0 --- /dev/null +++ b/include/wlr/types/wlr_cursor.h @@ -0,0 +1,194 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_CURSOR_H +#define WLR_TYPES_WLR_CURSOR_H + +#include <wayland-server.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_output_layout.h> +#include <wlr/types/wlr_output.h> + +/** + * wlr_cursor implements the behavior of the "cursor", that is, the image on the + * screen typically moved about with a mouse or so. It provides tracking for + * this in global coordinates, and integrates with wlr_output, + * wlr_output_layout, and wlr_input_device. You can use it to abstract multiple + * input devices over a single cursor, constrain cursor movement to the usable + * area of a wlr_output_layout and communicate position updates to the hardware + * cursor, constrain specific input devices to specific outputs or regions of + * the screen, and so on. + */ + +struct wlr_cursor_state; + +struct wlr_cursor { + struct wlr_cursor_state *state; + double x, y; + + /** + * The interpretation of these signals is the responsibility of the + * compositor, but some helpers are provided for your benefit. If you + * receive a relative motion event, for example, you may want to call + * wlr_cursor_move. If you receive an absolute event, call + * wlr_cursor_warp_absolute. If you pass an input device into these + * functions, it will apply the region/output constraints associated with + * that device to the resulting cursor motion. If an output layout is + * attached, these functions will constrain the resulting cursor motion to + * within the usable space of the output layout. + * + * Re-broadcasting these signals to, for example, a wlr_seat, is also your + * responsibility. + */ + struct { + struct wl_signal motion; + struct wl_signal motion_absolute; + struct wl_signal button; + struct wl_signal axis; + + struct wl_signal touch_up; + struct wl_signal touch_down; + struct wl_signal touch_motion; + struct wl_signal touch_cancel; + + struct wl_signal tablet_tool_axis; + struct wl_signal tablet_tool_proximity; + struct wl_signal tablet_tool_tip; + struct wl_signal tablet_tool_button; + } events; + + void *data; +}; + +struct wlr_cursor *wlr_cursor_create(); + +void wlr_cursor_destroy(struct wlr_cursor *cur); + +/** + * Warp the cursor to the given x and y in layout coordinates. If x and y are + * out of the layout boundaries or constraints, no warp will happen. + * + * `dev` may be passed to respect device mapping constraints. If `dev` is NULL, + * device mapping constraints will be ignored. + * + * Returns true when the cursor warp was successful. + */ +bool wlr_cursor_warp(struct wlr_cursor *cur, struct wlr_input_device *dev, + double lx, double ly); + +/** + * Convert absolute 0..1 coordinates to layout coordinates. + * + * `dev` may be passed to respect device mapping constraints. If `dev` is NULL, + * device mapping constraints will be ignored. + */ +void wlr_cursor_absolute_to_layout_coords(struct wlr_cursor *cur, + struct wlr_input_device *dev, double x, double y, double *lx, double *ly); + + +/** + * Warp the cursor to the given x and y coordinates. If the given point is out + * of the layout boundaries or constraints, the closest point will be used. + * If one coordinate is NAN, it will be ignored. + * + * `dev` may be passed to respect device mapping constraints. If `dev` is NULL, + * device mapping constraints will be ignored. + */ +void wlr_cursor_warp_closest(struct wlr_cursor *cur, + struct wlr_input_device *dev, double x, double y); + +/** + * Warp the cursor to the given x and y in absolute 0..1 coordinates. If the + * given point is out of the layout boundaries or constraints, the closest point + * will be used. If one coordinate is NAN, it will be ignored. + * + * `dev` may be passed to respect device mapping constraints. If `dev` is NULL, + * device mapping constraints will be ignored. + */ +void wlr_cursor_warp_absolute(struct wlr_cursor *cur, + struct wlr_input_device *dev, double x, double y); + +/** + * Move the cursor in the direction of the given x and y layout coordinates. If + * one coordinate is NAN, it will be ignored. + * + * `dev` may be passed to respect device mapping constraints. If `dev` is NULL, + * device mapping constraints will be ignored. + */ +void wlr_cursor_move(struct wlr_cursor *cur, struct wlr_input_device *dev, + double delta_x, double delta_y); + +/** + * Set the cursor image. stride is given in bytes. If pixels is NULL, hides the + * cursor. + * + * If scale isn't zero, the image is only set on outputs having the provided + * scale. + */ +void wlr_cursor_set_image(struct wlr_cursor *cur, const uint8_t *pixels, + int32_t stride, uint32_t width, uint32_t height, int32_t hotspot_x, + int32_t hotspot_y, float scale); + +/** + * Set the cursor surface. The surface can be committed to update the cursor + * image. The surface position is subtracted from the hotspot. A NULL surface + * commit hides the cursor. + */ +void wlr_cursor_set_surface(struct wlr_cursor *cur, struct wlr_surface *surface, + int32_t hotspot_x, int32_t hotspot_y); + +/** + * Attaches this input device to this cursor. The input device must be one of: + * + * - WLR_INPUT_DEVICE_POINTER + * - WLR_INPUT_DEVICE_TOUCH + * - WLR_INPUT_DEVICE_TABLET_TOOL + */ +void wlr_cursor_attach_input_device(struct wlr_cursor *cur, + struct wlr_input_device *dev); + +void wlr_cursor_detach_input_device(struct wlr_cursor *cur, + struct wlr_input_device *dev); +/** + * Uses the given layout to establish the boundaries and movement semantics of + * this cursor. Cursors without an output layout allow infinite movement in any + * direction and do not support absolute input events. + */ +void wlr_cursor_attach_output_layout(struct wlr_cursor *cur, + struct wlr_output_layout *l); + +/** + * Attaches this cursor to the given output, which must be among the outputs in + * the current output_layout for this cursor. This call is invalid for a cursor + * without an associated output layout. + */ +void wlr_cursor_map_to_output(struct wlr_cursor *cur, + struct wlr_output *output); + +/** + * Maps all input from a specific input device to a given output. The input + * device must be attached to this cursor and the output must be among the + * outputs in the attached output layout. + */ +void wlr_cursor_map_input_to_output(struct wlr_cursor *cur, + struct wlr_input_device *dev, struct wlr_output *output); + +/** + * Maps this cursor to an arbitrary region on the associated wlr_output_layout. + */ +void wlr_cursor_map_to_region(struct wlr_cursor *cur, struct wlr_box *box); + +/** + * Maps inputs from this input device to an arbitrary region on the associated + * wlr_output_layout. + */ +void wlr_cursor_map_input_to_region(struct wlr_cursor *cur, + struct wlr_input_device *dev, struct wlr_box *box); + +#endif diff --git a/include/wlr/types/wlr_data_device.h b/include/wlr/types/wlr_data_device.h new file mode 100644 index 00000000..9c4ce995 --- /dev/null +++ b/include/wlr/types/wlr_data_device.h @@ -0,0 +1,231 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_DATA_DEVICE_H +#define WLR_TYPES_WLR_DATA_DEVICE_H + +#include <wayland-server.h> +#include <wlr/types/wlr_seat.h> + +extern const struct wlr_pointer_grab_interface + wlr_data_device_pointer_drag_interface; + +extern const struct wlr_keyboard_grab_interface + wlr_data_device_keyboard_drag_interface; + +extern const struct wlr_touch_grab_interface + wlr_data_device_touch_drag_interface; + +struct wlr_data_device_manager { + struct wl_global *global; + struct wl_list resources; + struct wl_list data_sources; + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_data_offer { + struct wl_resource *resource; + struct wlr_data_source *source; + + uint32_t actions; + enum wl_data_device_manager_dnd_action preferred_action; + bool in_ask; + + struct wl_listener source_destroy; +}; + +/** + * A data source implementation. Only the `send` function is mandatory. Refer to + * the matching wl_data_source_* functions documentation to know what they do. + */ +struct wlr_data_source_impl { + void (*send)(struct wlr_data_source *source, const char *mime_type, + int32_t fd); + void (*accept)(struct wlr_data_source *source, uint32_t serial, + const char *mime_type); + void (*cancel)(struct wlr_data_source *source); + + void (*dnd_drop)(struct wlr_data_source *source); + void (*dnd_finish)(struct wlr_data_source *source); + void (*dnd_action)(struct wlr_data_source *source, + enum wl_data_device_manager_dnd_action action); +}; + +struct wlr_data_source { + const struct wlr_data_source_impl *impl; + + // source metadata + struct wl_array mime_types; + int32_t actions; + + // source status + bool accepted; + + // drag'n'drop status + enum wl_data_device_manager_dnd_action current_dnd_action; + uint32_t compositor_action; + + struct { + struct wl_signal destroy; + } events; +}; + +struct wlr_drag_icon { + struct wlr_surface *surface; + struct wlr_seat_client *client; + struct wl_list link; // wlr_seat::drag_icons + bool mapped; + + bool is_pointer; + int32_t touch_id; + + struct { + struct wl_signal map; + struct wl_signal unmap; + struct wl_signal destroy; + } events; + + struct wl_listener surface_destroy; + struct wl_listener seat_client_destroy; + + void *data; +}; + +struct wlr_drag { + struct wlr_seat_pointer_grab pointer_grab; + struct wlr_seat_keyboard_grab keyboard_grab; + struct wlr_seat_touch_grab touch_grab; + + struct wlr_seat *seat; + struct wlr_seat_client *seat_client; + struct wlr_seat_client *focus_client; + + bool is_pointer_grab; + + struct wlr_drag_icon *icon; + struct wlr_surface *focus; + struct wlr_data_source *source; + + bool cancelling; + int32_t grab_touch_id; + + struct wl_listener point_destroy; + struct wl_listener source_destroy; + struct wl_listener seat_client_destroy; + struct wl_listener icon_destroy; + + struct { + struct wl_signal focus; + struct wl_signal motion; + struct wl_signal drop; + struct wl_signal destroy; + } events; +}; + +struct wlr_drag_motion_event { + struct wlr_drag *drag; + uint32_t time; + double sx, sy; +}; + +struct wlr_drag_drop_event { + struct wlr_drag *drag; + uint32_t time; +}; + +/** + * Create a wl data device manager global for this display. + */ +struct wlr_data_device_manager *wlr_data_device_manager_create( + struct wl_display *display); + +/** + * Destroys a wlr_data_device_manager and removes its wl_data_device_manager global. + */ +void wlr_data_device_manager_destroy(struct wlr_data_device_manager *manager); + +/** + * 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 wlr_seat_client_send_selection(struct wlr_seat_client *seat_client); + +/** + * Sets the current selection for the seat. This removes the previous one if + * there was any. + */ +void wlr_seat_set_selection(struct wlr_seat *seat, + struct wlr_data_source *source, uint32_t serial); + +/** + * Initializes the data source with the provided implementation. + */ +void wlr_data_source_init(struct wlr_data_source *source, + const struct wlr_data_source_impl *impl); + +/** + * Finishes the data source. + */ +void wlr_data_source_finish(struct wlr_data_source *source); + +/** + * Sends the data as the specified MIME type over the passed file descriptor, + * then close it. + */ +void wlr_data_source_send(struct wlr_data_source *source, const char *mime_type, + int32_t fd); + +/** + * Notifies the data source that a target accepts one of the offered MIME types. + * If a target doesn't accept any of the offered types, `mime_type` is NULL. + */ +void wlr_data_source_accept(struct wlr_data_source *source, uint32_t serial, + const char *mime_type); + +/** + * Notifies the data source it is no longer valid and should be destroyed. That + * potentially destroys immediately the data source. + */ +void wlr_data_source_cancel(struct wlr_data_source *source); + +/** + * Notifies the data source that the drop operation was performed. This does not + * indicate acceptance. + * + * The data source may still be used in the future and should not be destroyed + * here. + */ +void wlr_data_source_dnd_drop(struct wlr_data_source *source); + +/** + * Notifies the data source that the drag-and-drop operation concluded. That + * potentially destroys immediately the data source. + */ +void wlr_data_source_dnd_finish(struct wlr_data_source *source); + +/** + * Notifies the data source that a target accepts the drag with the specified + * action. + * + * This shouldn't be called after `wlr_data_source_dnd_drop` unless the + * drag-and-drop operation ended in an "ask" action. + */ +void wlr_data_source_dnd_action(struct wlr_data_source *source, + enum wl_data_device_manager_dnd_action action); + +#endif diff --git a/include/wlr/types/wlr_export_dmabuf_v1.h b/include/wlr/types/wlr_export_dmabuf_v1.h new file mode 100644 index 00000000..204da985 --- /dev/null +++ b/include/wlr/types/wlr_export_dmabuf_v1.h @@ -0,0 +1,46 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_EXPORT_DMABUF_V1_H +#define WLR_TYPES_WLR_EXPORT_DMABUF_V1_H + +#include <stdbool.h> +#include <wayland-server.h> +#include <wlr/render/dmabuf.h> + +struct wlr_export_dmabuf_manager_v1 { + struct wl_global *global; + struct wl_list resources; // wl_resource_get_link + struct wl_list frames; // wlr_export_dmabuf_frame_v1::link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; +}; + +struct wlr_export_dmabuf_frame_v1 { + struct wl_resource *resource; + struct wlr_export_dmabuf_manager_v1 *manager; + struct wl_list link; // wlr_export_dmabuf_manager_v1::frames + + struct wlr_dmabuf_attributes attribs; + struct wlr_output *output; + + bool cursor_locked; + + struct wl_listener output_swap_buffers; +}; + +struct wlr_export_dmabuf_manager_v1 *wlr_export_dmabuf_manager_v1_create( + struct wl_display *display); +void wlr_export_dmabuf_manager_v1_destroy( + struct wlr_export_dmabuf_manager_v1 *manager); + +#endif diff --git a/include/wlr/types/wlr_foreign_toplevel_management_v1.h b/include/wlr/types/wlr_foreign_toplevel_management_v1.h new file mode 100644 index 00000000..75ae0e64 --- /dev/null +++ b/include/wlr/types/wlr_foreign_toplevel_management_v1.h @@ -0,0 +1,120 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_FOREIGN_TOPLEVEL_MANAGEMENT_V1_H +#define WLR_TYPES_WLR_FOREIGN_TOPLEVEL_MANAGEMENT_V1_H + +#include <wayland-server.h> +#include <wlr/types/wlr_output.h> + +struct wlr_foreign_toplevel_manager_v1 { + struct wl_event_loop *event_loop; + struct wl_global *global; + struct wl_list resources; + struct wl_list toplevels; // wlr_foreign_toplevel_handle_v1::link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +enum wlr_foreign_toplevel_handle_v1_state { + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED = 1, + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED = 2, + WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED = 4, +}; + +struct wlr_foreign_toplevel_handle_v1_output { + struct wl_list link; // wlr_foreign_toplevel_handle_v1::outputs + struct wl_listener output_destroy; + struct wlr_output *output; + + struct wlr_foreign_toplevel_handle_v1 *toplevel; +}; + +struct wlr_foreign_toplevel_handle_v1 { + struct wlr_foreign_toplevel_manager_v1 *manager; + struct wl_list resources; + struct wl_list link; + struct wl_event_source *idle_source; + + char *title; + char *app_id; + struct wl_list outputs; // wlr_foreign_toplevel_v1_output + uint32_t state; // wlr_foreign_toplevel_v1_state + + struct { + // wlr_foreign_toplevel_handle_v1_maximized_event + struct wl_signal request_maximize; + //wlr_foreign_toplevel_handle_v1_minimized_event + struct wl_signal request_minimize; + //wlr_foreign_toplevel_handle_v1_activated_event + struct wl_signal request_activate; + struct wl_signal request_close; + + //wlr_foreign_toplevel_handle_v1_set_rectangle_event + struct wl_signal set_rectangle; + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_foreign_toplevel_handle_v1_maximized_event { + struct wlr_foreign_toplevel_handle_v1 *toplevel; + bool maximized; +}; + +struct wlr_foreign_toplevel_handle_v1_minimized_event { + struct wlr_foreign_toplevel_handle_v1 *toplevel; + bool minimized; +}; + +struct wlr_foreign_toplevel_handle_v1_activated_event { + struct wlr_foreign_toplevel_handle_v1 *toplevel; + struct wlr_seat *seat; +}; + +struct wlr_foreign_toplevel_handle_v1_set_rectangle_event { + struct wlr_foreign_toplevel_handle_v1 *toplevel; + struct wlr_surface *surface; + int32_t x, y, width, height; +}; + +struct wlr_foreign_toplevel_manager_v1 *wlr_foreign_toplevel_manager_v1_create( + struct wl_display *display); +void wlr_foreign_toplevel_manager_v1_destroy( + struct wlr_foreign_toplevel_manager_v1 *manager); + +struct wlr_foreign_toplevel_handle_v1 *wlr_foreign_toplevel_handle_v1_create( + struct wlr_foreign_toplevel_manager_v1 *manager); +void wlr_foreign_toplevel_handle_v1_destroy( + struct wlr_foreign_toplevel_handle_v1 *toplevel); + +void wlr_foreign_toplevel_handle_v1_set_title( + struct wlr_foreign_toplevel_handle_v1 *toplevel, const char *title); +void wlr_foreign_toplevel_handle_v1_set_app_id( + struct wlr_foreign_toplevel_handle_v1 *toplevel, const char *app_id); + +void wlr_foreign_toplevel_handle_v1_output_enter( + struct wlr_foreign_toplevel_handle_v1 *toplevel, struct wlr_output *output); +void wlr_foreign_toplevel_handle_v1_output_leave( + struct wlr_foreign_toplevel_handle_v1 *toplevel, struct wlr_output *output); + +void wlr_foreign_toplevel_handle_v1_set_maximized( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool maximized); +void wlr_foreign_toplevel_handle_v1_set_minimized( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool minimized); +void wlr_foreign_toplevel_handle_v1_set_activated( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool activated); + +#endif diff --git a/include/wlr/types/wlr_gamma_control.h b/include/wlr/types/wlr_gamma_control.h new file mode 100644 index 00000000..912a413c --- /dev/null +++ b/include/wlr/types/wlr_gamma_control.h @@ -0,0 +1,46 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_GAMMA_CONTROL_H +#define WLR_TYPES_WLR_GAMMA_CONTROL_H + +#include <wayland-server.h> + +struct wlr_gamma_control_manager { + struct wl_global *global; + struct wl_list controls; // wlr_gamma_control::link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_gamma_control { + struct wl_resource *resource; + struct wlr_output *output; + struct wl_list link; + + struct wl_listener output_destroy_listener; + + struct { + struct wl_signal destroy; + } events; + + void* data; +}; + +struct wlr_gamma_control_manager *wlr_gamma_control_manager_create( + struct wl_display *display); +void wlr_gamma_control_manager_destroy( + struct wlr_gamma_control_manager *gamma_control_manager); + +#endif diff --git a/include/wlr/types/wlr_gamma_control_v1.h b/include/wlr/types/wlr_gamma_control_v1.h new file mode 100644 index 00000000..f186aa81 --- /dev/null +++ b/include/wlr/types/wlr_gamma_control_v1.h @@ -0,0 +1,35 @@ +#ifndef WLR_TYPES_WLR_GAMMA_CONTROL_V1_H +#define WLR_TYPES_WLR_GAMMA_CONTROL_V1_H + +#include <wayland-server.h> + +struct wlr_gamma_control_manager_v1 { + struct wl_global *global; + struct wl_list resources; + struct wl_list controls; // wlr_gamma_control_v1::link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_gamma_control_v1 { + struct wl_resource *resource; + struct wlr_output *output; + struct wl_list link; + + struct wl_listener output_destroy_listener; + + void *data; +}; + +struct wlr_gamma_control_manager_v1 *wlr_gamma_control_manager_v1_create( + struct wl_display *display); +void wlr_gamma_control_manager_v1_destroy( + struct wlr_gamma_control_manager_v1 *manager); + +#endif diff --git a/include/wlr/types/wlr_gtk_primary_selection.h b/include/wlr/types/wlr_gtk_primary_selection.h new file mode 100644 index 00000000..436a50d2 --- /dev/null +++ b/include/wlr/types/wlr_gtk_primary_selection.h @@ -0,0 +1,53 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_GTK_PRIMARY_SELECTION_H +#define WLR_TYPES_WLR_GTK_PRIMARY_SELECTION_H + +#include <wayland-server.h> +#include <wlr/types/wlr_seat.h> + +struct wlr_gtk_primary_selection_device_manager { + struct wl_global *global; + struct wl_list resources; // wl_resource_get_link + struct wl_list devices; // wlr_gtk_primary_selection_device::link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +/** + * A device is a per-seat object used to set and get the current selection. + */ +struct wlr_gtk_primary_selection_device { + struct wlr_gtk_primary_selection_device_manager *manager; + struct wlr_seat *seat; + struct wl_list link; // wlr_gtk_primary_selection_device_manager::devices + struct wl_list resources; // wl_resource_get_link + + struct wl_list offers; // wl_resource_get_link + uint32_t selection_serial; + + struct wl_listener seat_destroy; + struct wl_listener seat_focus_change; + struct wl_listener seat_primary_selection; + + void *data; +}; + +struct wlr_gtk_primary_selection_device_manager * + wlr_gtk_primary_selection_device_manager_create(struct wl_display *display); +void wlr_gtk_primary_selection_device_manager_destroy( + struct wlr_gtk_primary_selection_device_manager *manager); + +#endif diff --git a/include/wlr/types/wlr_idle.h b/include/wlr/types/wlr_idle.h new file mode 100644 index 00000000..d8c81a60 --- /dev/null +++ b/include/wlr/types/wlr_idle.h @@ -0,0 +1,71 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_IDLE_H +#define WLR_TYPES_WLR_IDLE_H + +#include <wayland-server.h> +#include <wlr/types/wlr_seat.h> + +/** + * Idle protocol is used to create timers which will notify the client when the + * compositor does not receive any input for a given time(in milliseconds). Also + * the client will be notified when the timer receives an activity notify and already + * was in idle state. Besides this, the client is able to simulate user activity + * which will reset the timers and at any time can destroy the timer. + */ + + +struct wlr_idle { + struct wl_global *global; + struct wl_list idle_timers; // wlr_idle_timeout::link + struct wl_event_loop *event_loop; + bool enabled; + + struct wl_listener display_destroy; + struct { + struct wl_signal activity_notify; + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_idle_timeout { + struct wl_resource *resource; + struct wl_list link; + struct wlr_seat *seat; + + struct wl_event_source *idle_source; + bool idle_state; + bool enabled; + uint32_t timeout; // milliseconds + + struct wl_listener input_listener; + struct wl_listener seat_destroy; + + void *data; +}; + +struct wlr_idle *wlr_idle_create(struct wl_display *display); + +void wlr_idle_destroy(struct wlr_idle *idle); + +/** + * Send notification to restart all timers for the given seat. Called by + * compositor when there is an user activity event on that seat. + */ +void wlr_idle_notify_activity(struct wlr_idle *idle, struct wlr_seat *seat); + +/** + * Enable or disable timers for a given idle resource by seat. + * Passing a NULL seat means update timers for all seats. + */ +void wlr_idle_set_enabled(struct wlr_idle *idle, struct wlr_seat *seat, + bool enabled); +#endif diff --git a/include/wlr/types/wlr_idle_inhibit_v1.h b/include/wlr/types/wlr_idle_inhibit_v1.h new file mode 100644 index 00000000..2093eafe --- /dev/null +++ b/include/wlr/types/wlr_idle_inhibit_v1.h @@ -0,0 +1,58 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_IDLE_INHIBIT_V1_H +#define WLR_TYPES_WLR_IDLE_INHIBIT_V1_H + +#include <wayland-server.h> + +/* This interface permits clients to inhibit the idle behavior such as + * screenblanking, locking, and screensaving. + * + * This allows clients to ensure they stay visible instead of being hidden by + * power-saving. + * + * Inhibitors are created for surfaces. They should only be in effect, while + * this surface is visible. + * The effect could also be limited to outputs it is displayed on (e.g. + * dimm/dpms off outputs, except the one a video is displayed on). + */ + +struct wlr_idle_inhibit_manager_v1 { + struct wl_list resources; // wl_resource_get_link + struct wl_list inhibitors; // wlr_idle_inhibit_inhibitor_v1::link + struct wl_global *global; + + struct wl_listener display_destroy; + + struct { + struct wl_signal new_inhibitor; + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_idle_inhibitor_v1 { + struct wlr_surface *surface; + struct wl_resource *resource; + struct wl_listener surface_destroy; + + struct wl_list link; // wlr_idle_inhibit_manager_v1::inhibitors; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_idle_inhibit_manager_v1 *wlr_idle_inhibit_v1_create(struct wl_display *display); +void wlr_idle_inhibit_v1_destroy(struct wlr_idle_inhibit_manager_v1 *idle_inhibit); + +#endif diff --git a/include/wlr/types/wlr_input_device.h b/include/wlr/types/wlr_input_device.h new file mode 100644 index 00000000..f948d55b --- /dev/null +++ b/include/wlr/types/wlr_input_device.h @@ -0,0 +1,66 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_INPUT_DEVICE_H +#define WLR_TYPES_WLR_INPUT_DEVICE_H + +enum wlr_button_state { + WLR_BUTTON_RELEASED, + WLR_BUTTON_PRESSED, +}; + +enum wlr_input_device_type { + WLR_INPUT_DEVICE_KEYBOARD, + WLR_INPUT_DEVICE_POINTER, + WLR_INPUT_DEVICE_TOUCH, + WLR_INPUT_DEVICE_TABLET_TOOL, + WLR_INPUT_DEVICE_TABLET_PAD, + WLR_INPUT_DEVICE_SWITCH, +}; + +/* Note: these are circular dependencies */ +#include <wlr/types/wlr_keyboard.h> +#include <wlr/types/wlr_pointer.h> +#include <wlr/types/wlr_touch.h> +#include <wlr/types/wlr_tablet_tool.h> +#include <wlr/types/wlr_tablet_pad.h> +#include <wlr/types/wlr_switch.h> + +struct wlr_input_device_impl; + +struct wlr_input_device { + const struct wlr_input_device_impl *impl; + + enum wlr_input_device_type type; + unsigned int vendor, product; + char *name; + // Or 0 if not applicable to this device + double width_mm, height_mm; + char *output_name; + + /* wlr_input_device.type determines which of these is valid */ + union { + void *_device; + struct wlr_keyboard *keyboard; + struct wlr_pointer *pointer; + struct wlr_switch *lid_switch; + struct wlr_touch *touch; + struct wlr_tablet *tablet; + struct wlr_tablet_pad *tablet_pad; + }; + + struct { + struct wl_signal destroy; + } events; + + void *data; + + struct wl_list link; +}; + +#endif diff --git a/include/wlr/types/wlr_input_inhibitor.h b/include/wlr/types/wlr_input_inhibitor.h new file mode 100644 index 00000000..f3187540 --- /dev/null +++ b/include/wlr/types/wlr_input_inhibitor.h @@ -0,0 +1,34 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_INPUT_INHIBITOR_H +#define WLR_TYPES_INPUT_INHIBITOR_H +#include <wayland-server.h> + +struct wlr_input_inhibit_manager { + struct wl_global *global; + struct wl_client *active_client; + struct wl_resource *active_inhibitor; + + struct wl_listener display_destroy; + + struct { + struct wl_signal activate; // struct wlr_input_inhibit_manager * + struct wl_signal deactivate; // struct wlr_input_inhibit_manager * + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_input_inhibit_manager *wlr_input_inhibit_manager_create( + struct wl_display *display); +void wlr_input_inhibit_manager_destroy( + struct wlr_input_inhibit_manager *manager); + +#endif diff --git a/include/wlr/types/wlr_input_method_v2.h b/include/wlr/types/wlr_input_method_v2.h new file mode 100644 index 00000000..d22d54d1 --- /dev/null +++ b/include/wlr/types/wlr_input_method_v2.h @@ -0,0 +1,87 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_INPUT_METHOD_V2_H +#define WLR_TYPES_WLR_INPUT_METHOD_V2_H +#include <stdint.h> +#include <stdlib.h> +#include <wayland-server.h> +#include <wlr/types/wlr_seat.h> + +struct wlr_input_method_v2_preedit_string { + char *text; + int32_t cursor_begin; + int32_t cursor_end; +}; + +struct wlr_input_method_v2_delete_surrounding_text { + uint32_t before_length; + uint32_t after_length; +}; + +struct wlr_input_method_v2_state { + struct wlr_input_method_v2_preedit_string preedit; + char *commit_text; + struct wlr_input_method_v2_delete_surrounding_text delete; +}; + +struct wlr_input_method_v2 { + struct wl_resource *resource; + + struct wlr_seat *seat; + + struct wlr_input_method_v2_state pending; + struct wlr_input_method_v2_state current; + bool active; // pending compositor-side state + bool client_active; // state known to the client + uint32_t current_serial; // received in last commit call + + struct wl_list link; + + struct wl_listener seat_destroy; + + struct { + struct wl_signal commit; // (struct wlr_input_method_v2*) + struct wl_signal destroy; // (struct wlr_input_method_v2*) + } events; +}; + +struct wlr_input_method_manager_v2 { + struct wl_global *global; + struct wl_list bound_resources; // struct wl_resource*::link + struct wl_list input_methods; // struct wlr_input_method_v2*::link + + struct wl_listener display_destroy; + + struct { + struct wl_signal input_method; // (struct wlr_input_method_v2*) + struct wl_signal destroy; // (struct wlr_input_method_manager_v2*) + } events; +}; + +struct wlr_input_method_manager_v2 *wlr_input_method_manager_v2_create( + struct wl_display *display); +void wlr_input_method_manager_v2_destroy( + struct wlr_input_method_manager_v2 *manager); + +void wlr_input_method_v2_send_activate( + struct wlr_input_method_v2 *input_method); +void wlr_input_method_v2_send_deactivate( + struct wlr_input_method_v2 *input_method); +void wlr_input_method_v2_send_surrounding_text( + struct wlr_input_method_v2 *input_method, const char *text, + uint32_t cursor, uint32_t anchor); +void wlr_input_method_v2_send_content_type( + struct wlr_input_method_v2 *input_method, uint32_t hint, + uint32_t purpose); +void wlr_input_method_v2_send_text_change_cause( + struct wlr_input_method_v2 *input_method, uint32_t cause); +void wlr_input_method_v2_send_done(struct wlr_input_method_v2 *input_method); +void wlr_input_method_v2_send_unavailable( + struct wlr_input_method_v2 *input_method); +#endif diff --git a/include/wlr/types/wlr_keyboard.h b/include/wlr/types/wlr_keyboard.h new file mode 100644 index 00000000..ae279541 --- /dev/null +++ b/include/wlr/types/wlr_keyboard.h @@ -0,0 +1,115 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_KEYBOARD_H +#define WLR_TYPES_WLR_KEYBOARD_H + +#include <stdbool.h> +#include <stdint.h> +#include <wayland-server.h> +#include <wayland-server.h> +#include <xkbcommon/xkbcommon.h> + +#define WLR_LED_COUNT 3 + +enum wlr_keyboard_led { + WLR_LED_NUM_LOCK = 1, + WLR_LED_CAPS_LOCK = 2, + WLR_LED_SCROLL_LOCK = 4, +}; + +#define WLR_MODIFIER_COUNT 8 + +enum wlr_keyboard_modifier { + WLR_MODIFIER_SHIFT = 1, + WLR_MODIFIER_CAPS = 2, + WLR_MODIFIER_CTRL = 4, + WLR_MODIFIER_ALT = 8, + WLR_MODIFIER_MOD2 = 16, + WLR_MODIFIER_MOD3 = 32, + WLR_MODIFIER_LOGO = 64, + WLR_MODIFIER_MOD5 = 128, +}; + +#define WLR_KEYBOARD_KEYS_CAP 32 + +struct wlr_keyboard_impl; + +struct wlr_keyboard_modifiers { + xkb_mod_mask_t depressed; + xkb_mod_mask_t latched; + xkb_mod_mask_t locked; + xkb_mod_mask_t group; +}; + +struct wlr_keyboard { + const struct wlr_keyboard_impl *impl; + + char *keymap_string; + size_t keymap_size; + struct xkb_keymap *keymap; + struct xkb_state *xkb_state; + xkb_led_index_t led_indexes[WLR_LED_COUNT]; + xkb_mod_index_t mod_indexes[WLR_MODIFIER_COUNT]; + + uint32_t keycodes[WLR_KEYBOARD_KEYS_CAP]; + size_t num_keycodes; + struct wlr_keyboard_modifiers modifiers; + + struct { + int32_t rate; + int32_t delay; + } repeat_info; + + struct { + /** + * The `key` event signals with a `wlr_event_keyboard_key` event that a + * key has been pressed or released on the keyboard. This event is + * emitted before the xkb state of the keyboard has been updated + * (including modifiers). + */ + struct wl_signal key; + + /** + * The `modifiers` event signals that the modifier state of the + * `wlr_keyboard` has been updated. At this time, you can read the + * modifier state of the `wlr_keyboard` and handle the updated state by + * sending it to clients. + */ + struct wl_signal modifiers; + struct wl_signal keymap; + struct wl_signal repeat_info; + } events; + + void *data; +}; + +enum wlr_key_state { + WLR_KEY_RELEASED, + WLR_KEY_PRESSED, +}; + +struct wlr_event_keyboard_key { + uint32_t time_msec; + uint32_t keycode; + bool update_state; // if backend doesn't update modifiers on its own + enum wlr_key_state state; +}; + +void wlr_keyboard_set_keymap(struct wlr_keyboard *kb, + struct xkb_keymap *keymap); +/** + * Sets the keyboard repeat info. `rate` is in key repeats/second and delay is + * in milliseconds. + */ +void wlr_keyboard_set_repeat_info(struct wlr_keyboard *kb, int32_t rate, + int32_t delay); +void wlr_keyboard_led_update(struct wlr_keyboard *keyboard, uint32_t leds); +uint32_t wlr_keyboard_get_modifiers(struct wlr_keyboard *keyboard); + +#endif diff --git a/include/wlr/types/wlr_layer_shell_v1.h b/include/wlr/types/wlr_layer_shell_v1.h new file mode 100644 index 00000000..838b2e83 --- /dev/null +++ b/include/wlr/types/wlr_layer_shell_v1.h @@ -0,0 +1,134 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_LAYER_SHELL_V1_H +#define WLR_TYPES_WLR_LAYER_SHELL_V1_H +#include <stdbool.h> +#include <stdint.h> +#include <wayland-server.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_surface.h> +#include "wlr-layer-shell-unstable-v1-protocol.h" + +/** + * wlr_layer_shell_v1 allows clients to arrange themselves in "layers" on the + * desktop in accordance with the wlr-layer-shell protocol. When a client is + * added, the new_surface signal will be raised and passed a reference to our + * wlr_layer_surface_v1. At this time, the client will have configured the + * surface as it desires, including information like desired anchors and + * margins. The compositor should use this information to decide how to arrange + * the layer on-screen, then determine the dimensions of the layer and call + * wlr_layer_surface_v1_configure. The client will then attach a buffer and + * commit the surface, at which point the wlr_layer_surface_v1 map signal is + * raised and the compositor should begin rendering the surface. + */ +struct wlr_layer_shell_v1 { + struct wl_global *global; + struct wl_list resources; // wl_resource + struct wl_list surfaces; // wl_layer_surface + + struct wl_listener display_destroy; + + struct { + // struct wlr_layer_surface_v1 * + // Note: the output may be NULL. In this case, it is your + // responsibility to assign an output before returning. + struct wl_signal new_surface; + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_layer_surface_v1_state { + uint32_t anchor; + int32_t exclusive_zone; + struct { + uint32_t top, right, bottom, left; + } margin; + bool keyboard_interactive; + uint32_t desired_width, desired_height; + uint32_t actual_width, actual_height; +}; + +struct wlr_layer_surface_v1_configure { + struct wl_list link; // wlr_layer_surface_v1::configure_list + uint32_t serial; + struct wlr_layer_surface_v1_state state; +}; + +struct wlr_layer_surface_v1 { + struct wl_list link; // wlr_layer_shell_v1::surfaces + struct wlr_surface *surface; + struct wlr_output *output; + struct wl_resource *resource; + struct wlr_layer_shell_v1 *shell; + struct wl_list popups; // wlr_xdg_popup::link + + char *namespace; + enum zwlr_layer_shell_v1_layer layer; + + bool added, configured, mapped, closed; + uint32_t configure_serial; + struct wl_event_source *configure_idle; + uint32_t configure_next_serial; + struct wl_list configure_list; + + struct wlr_layer_surface_v1_configure *acked_configure; + + struct wlr_layer_surface_v1_state client_pending; + struct wlr_layer_surface_v1_state server_pending; + struct wlr_layer_surface_v1_state current; + + struct wl_listener surface_destroy; + + struct { + struct wl_signal destroy; + struct wl_signal map; + struct wl_signal unmap; + struct wl_signal new_popup; + } events; + + void *data; +}; + +struct wlr_layer_shell_v1 *wlr_layer_shell_v1_create(struct wl_display *display); +void wlr_layer_shell_v1_destroy(struct wlr_layer_shell_v1 *layer_shell); + +/** + * Notifies the layer surface to configure itself with this width/height. The + * layer_surface will signal its map event when the surface is ready to assume + * this size. + */ +void wlr_layer_surface_v1_configure(struct wlr_layer_surface_v1 *surface, + uint32_t width, uint32_t height); + +/** + * Unmaps this layer surface and notifies the client that it has been closed. + */ +void wlr_layer_surface_v1_close(struct wlr_layer_surface_v1 *surface); + +bool wlr_surface_is_layer_surface(struct wlr_surface *surface); + +struct wlr_layer_surface_v1 *wlr_layer_surface_v1_from_wlr_surface( + struct wlr_surface *surface); + +/* Calls the iterator function for each sub-surface and popup of this surface */ +void wlr_layer_surface_v1_for_each_surface(struct wlr_layer_surface_v1 *surface, + wlr_surface_iterator_func_t iterator, void *user_data); + +/** + * Find a surface within this layer-surface tree at the given surface-local + * coordinates. Returns the surface and coordinates in the leaf surface + * coordinate system or NULL if no surface is found at that location. + */ +struct wlr_surface *wlr_layer_surface_v1_surface_at( + struct wlr_layer_surface_v1 *surface, double sx, double sy, + double *sub_x, double *sub_y); + +#endif diff --git a/include/wlr/types/wlr_linux_dmabuf_v1.h b/include/wlr/types/wlr_linux_dmabuf_v1.h new file mode 100644 index 00000000..f21b0b3a --- /dev/null +++ b/include/wlr/types/wlr_linux_dmabuf_v1.h @@ -0,0 +1,75 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_LINUX_DMABUF_H +#define WLR_TYPES_WLR_LINUX_DMABUF_H + +#include <stdint.h> +#include <wayland-server-protocol.h> +#include <wlr/render/dmabuf.h> + +struct wlr_dmabuf_v1_buffer { + struct wlr_renderer *renderer; + struct wl_resource *buffer_resource; + struct wl_resource *params_resource; + struct wlr_dmabuf_attributes attributes; + bool has_modifier; +}; + +/** + * Returns true if the given resource was created via the linux-dmabuf + * buffer protocol, false otherwise + */ +bool wlr_dmabuf_v1_resource_is_buffer(struct wl_resource *buffer_resource); + +/** + * Returns the wlr_dmabuf_buffer if the given resource was created + * via the linux-dmabuf buffer protocol + */ +struct wlr_dmabuf_v1_buffer *wlr_dmabuf_v1_buffer_from_buffer_resource( + struct wl_resource *buffer_resource); + +/** + * Returns the wlr_dmabuf_buffer if the given resource was created + * via the linux-dmabuf params protocol + */ +struct wlr_dmabuf_v1_buffer *wlr_dmabuf_v1_buffer_from_params_resource( + struct wl_resource *params_resource); + +/* the protocol interface */ +struct wlr_linux_dmabuf_v1 { + struct wl_global *global; + struct wlr_renderer *renderer; + struct wl_list resources; + + struct { + struct wl_signal destroy; + } events; + + struct wl_listener display_destroy; + struct wl_listener renderer_destroy; +}; + +/** + * Create linux-dmabuf interface + */ +struct wlr_linux_dmabuf_v1 *wlr_linux_dmabuf_v1_create(struct wl_display *display, + struct wlr_renderer *renderer); +/** + * Destroy the linux-dmabuf interface + */ +void wlr_linux_dmabuf_v1_destroy(struct wlr_linux_dmabuf_v1 *linux_dmabuf); + +/** + * Returns the wlr_linux_dmabuf if the given resource was created + * via the linux_dmabuf protocol + */ +struct wlr_linux_dmabuf_v1 *wlr_linux_dmabuf_v1_from_resource( + struct wl_resource *resource); + +#endif diff --git a/include/wlr/types/wlr_list.h b/include/wlr/types/wlr_list.h new file mode 100644 index 00000000..60f388f9 --- /dev/null +++ b/include/wlr/types/wlr_list.h @@ -0,0 +1,83 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_LIST_H +#define WLR_TYPES_WLR_LIST_H + +#include <stdbool.h> +#include <stddef.h> + +struct wlr_list { + size_t capacity; + size_t length; + void **items; +}; + +/** + * Initialize a list. Returns true on success, false on failure. + */ +bool wlr_list_init(struct wlr_list *list); + +/** + * Deinitialize a list. + */ +void wlr_list_finish(struct wlr_list *list); + +/** + * Executes `callback` on each element in the list. + */ +void wlr_list_for_each(struct wlr_list *list, void (*callback)(void *item)); + +/** + * Add `item` to the end of a list. + * Returns: new list length or `-1` on failure. + */ +ssize_t wlr_list_push(struct wlr_list *list, void *item); + +/** + * Place `item` into index `index` in the list. + * Returns: new list length or `-1` on failure. + */ +ssize_t wlr_list_insert(struct wlr_list *list, size_t index, void *item); + +/** + * Remove an item from the list. + */ +void wlr_list_del(struct wlr_list *list, size_t index); + +/** + * Remove and return an item from the end of the list. + */ +void *wlr_list_pop(struct wlr_list *list); + +/** + * Get a reference to the last item of a list without removal. + */ +void *wlr_list_peek(struct wlr_list *list); + +/** + * Append each item in `source` to `list`. + * Does not modify `source`. + * Returns: new list length or `-1` on failure. + */ +ssize_t wlr_list_cat(struct wlr_list *list, const struct wlr_list *source); + +/** + * Sort a list using `qsort`. + */ +void wlr_list_qsort(struct wlr_list *list, + int compare(const void *left, const void *right)); + +/** + * Return the index of the first item in the list that returns 0 for the given + * `compare` function, or -1 if none matches. + */ +ssize_t wlr_list_find(struct wlr_list *list, + int compare(const void *item, const void *cmp_to), const void *cmp_to); + +#endif diff --git a/include/wlr/types/wlr_matrix.h b/include/wlr/types/wlr_matrix.h new file mode 100644 index 00000000..a3961c35 --- /dev/null +++ b/include/wlr/types/wlr_matrix.h @@ -0,0 +1,59 @@ +/* + * This is a stable interface of wlroots. Future changes will be limited to: + * + * - New functions + * - New struct members + * - New enum members + * + * Note that wlroots does not make an ABI compatibility promise - in the future, + * the layout and size of structs used by wlroots may change, requiring code + * depending on this header to be recompiled (but not edited). + * + * Breaking changes are announced by email and follow a 1-year deprecation + * schedule. Send an email to ~sircmpwn/wlroots-announce+subscribe@lists.sr.ht + * to receive these announcements. + */ + +#ifndef WLR_TYPES_WLR_MATRIX_H +#define WLR_TYPES_WLR_MATRIX_H + +#include <wayland-server.h> +#include <wlr/types/wlr_box.h> + +/** Writes the identity matrix into mat */ +void wlr_matrix_identity(float mat[static 9]); + +/** mat ← a × b */ +void wlr_matrix_multiply(float mat[static 9], const float a[static 9], + const float b[static 9]); + +void wlr_matrix_transpose(float mat[static 9], const float a[static 9]); + +/** Writes a 2D translation matrix to mat of magnitude (x, y) */ +void wlr_matrix_translate(float mat[static 9], float x, float y); + +/** Writes a 2D scale matrix to mat of magnitude (x, y) */ +void wlr_matrix_scale(float mat[static 9], float x, float y); + +/** Writes a 2D rotation matrix to mat at an angle of rad radians */ +void wlr_matrix_rotate(float mat[static 9], float rad); + +/** Writes a transformation matrix which applies the specified + * wl_output_transform to mat */ +void wlr_matrix_transform(float mat[static 9], + enum wl_output_transform transform); + +/** Writes a 2D orthographic projection matrix to mat of (width, height) with a + * specified wl_output_transform*/ +void wlr_matrix_projection(float mat[static 9], int width, int height, + enum wl_output_transform transform); + +/** Shortcut for the various matrix operations involved in projecting the + * specified wlr_box onto a given orthographic projection with a given + * rotation. The result is written to mat, which can be applied to each + * coordinate of the box to get a new coordinate from [-1,1]. */ +void wlr_matrix_project_box(float mat[static 9], const struct wlr_box *box, + enum wl_output_transform transform, float rotation, + const float projection[static 9]); + +#endif diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h new file mode 100644 index 00000000..22822b11 --- /dev/null +++ b/include/wlr/types/wlr_output.h @@ -0,0 +1,278 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_OUTPUT_H +#define WLR_TYPES_WLR_OUTPUT_H + +#include <pixman.h> +#include <stdbool.h> +#include <time.h> +#include <wayland-server.h> +#include <wayland-util.h> +#include <wlr/render/dmabuf.h> + +struct wlr_output_mode { + uint32_t flags; // enum wl_output_mode + int32_t width, height; + int32_t refresh; // mHz + struct wl_list link; +}; + +struct wlr_output_cursor { + struct wlr_output *output; + double x, y; + bool enabled; + bool visible; + uint32_t width, height; + int32_t hotspot_x, hotspot_y; + struct wl_list link; + + // only when using a software cursor without a surface + struct wlr_texture *texture; + + // only when using a cursor surface + struct wlr_surface *surface; + struct wl_listener surface_commit; + struct wl_listener surface_destroy; + + struct { + struct wl_signal destroy; + } events; +}; + +struct wlr_output_impl; + +/** + * A compositor output region. This typically corresponds to a monitor that + * displays part of the compositor space. + * + * Compositors should listen to the `frame` event to render an output. They + * should call `wlr_output_make_current`, render and then call + * `wlr_output_swap_buffers`. No rendering should happen outside a `frame` event + * handler. + */ +struct wlr_output { + const struct wlr_output_impl *impl; + struct wlr_backend *backend; + struct wl_display *display; + + struct wl_global *global; + struct wl_list resources; + + char name[24]; + char make[56]; + char model[16]; + char serial[16]; + int32_t phys_width, phys_height; // mm + + // Note: some backends may have zero modes + struct wl_list modes; + struct wlr_output_mode *current_mode; + int32_t width, height; + int32_t refresh; // mHz, may be zero + + bool enabled; + float scale; + enum wl_output_subpixel subpixel; + enum wl_output_transform transform; + + bool needs_swap; + // damage for cursors and fullscreen surface, in output-local coordinates + pixman_region32_t damage; + bool frame_pending; + float transform_matrix[9]; + + struct { + // Request to render a frame + struct wl_signal frame; + // Emitted when buffers need to be swapped (because software cursors or + // fullscreen damage or because of backend-specific logic) + struct wl_signal needs_swap; + // Emitted right before buffer swap + struct wl_signal swap_buffers; // wlr_output_event_swap_buffers + // Emitted right after the buffer has been presented to the user + struct wl_signal present; // wlr_output_event_present + struct wl_signal enable; + struct wl_signal mode; + struct wl_signal scale; + struct wl_signal transform; + struct wl_signal destroy; + } events; + + struct wl_event_source *idle_frame; + + struct wl_list cursors; // wlr_output_cursor::link + struct wlr_output_cursor *hardware_cursor; + int software_cursor_locks; // number of locks forcing software cursors + + // the output position in layout space reported to clients + int32_t lx, ly; + + struct wl_listener display_destroy; + + void *data; +}; + +struct wlr_output_event_swap_buffers { + struct wlr_output *output; + struct timespec *when; + pixman_region32_t *damage; // output-buffer-local coordinates +}; + +enum wlr_output_present_flag { + // The presentation was synchronized to the "vertical retrace" by the + // display hardware such that tearing does not happen. + WLR_OUTPUT_PRESENT_VSYNC = 0x1, + // The display hardware provided measurements that the hardware driver + // converted into a presentation timestamp. + WLR_OUTPUT_PRESENT_HW_CLOCK = 0x2, + // The display hardware signalled that it started using the new image + // content. + WLR_OUTPUT_PRESENT_HW_COMPLETION = 0x4, + // The presentation of this update was done zero-copy. + WLR_OUTPUT_PRESENT_ZERO_COPY = 0x8, +}; + +struct wlr_output_event_present { + struct wlr_output *output; + // Time when the content update turned into light the first time. + struct timespec *when; + // Vertical retrace counter. Zero if unavailable. + unsigned seq; + // Prediction of how many nanoseconds after `when` the very next output + // refresh may occur. Zero if unknown. + int refresh; // nsec + uint32_t flags; // enum wlr_output_present_flag +}; + +struct wlr_surface; + +/** + * Enables or disables the output. A disabled output is turned off and doesn't + * emit `frame` events. + */ +bool wlr_output_enable(struct wlr_output *output, bool enable); +void wlr_output_create_global(struct wlr_output *output); +void wlr_output_destroy_global(struct wlr_output *output); +/** + * Sets the output mode. Enables the output if it's currently disabled. + */ +bool wlr_output_set_mode(struct wlr_output *output, + struct wlr_output_mode *mode); +/** + * Sets a custom mode on the output. If modes are available, they are preferred. + * Setting `refresh` to zero lets the backend pick a preferred value. + */ +bool wlr_output_set_custom_mode(struct wlr_output *output, int32_t width, + int32_t height, int32_t refresh); +void wlr_output_set_transform(struct wlr_output *output, + enum wl_output_transform transform); +void wlr_output_set_position(struct wlr_output *output, int32_t lx, int32_t ly); +void wlr_output_set_scale(struct wlr_output *output, float scale); +void wlr_output_destroy(struct wlr_output *output); +/** + * Computes the transformed output resolution. + */ +void wlr_output_transformed_resolution(struct wlr_output *output, + int *width, int *height); +/** + * Computes the transformed and scaled output resolution. + */ +void wlr_output_effective_resolution(struct wlr_output *output, + int *width, int *height); +/** + * Makes the output rendering context current. + * + * `buffer_age` is set to the drawing buffer age in number of frames or -1 if + * unknown. This is useful for damage tracking. + */ +bool wlr_output_make_current(struct wlr_output *output, int *buffer_age); +/** + * Get the preferred format for reading pixels. + * This function might change the current rendering context. + */ +bool wlr_output_preferred_read_format(struct wlr_output *output, + enum wl_shm_format *fmt); +/** + * Swaps the output buffers. If the time of the frame isn't known, set `when` to + * NULL. If the compositor doesn't support damage tracking, set `damage` to + * NULL. + * + * Damage is given in output-buffer-local coordinates (ie. scaled and + * transformed). + * + * Swapping buffers schedules a `frame` event. + */ +bool wlr_output_swap_buffers(struct wlr_output *output, struct timespec *when, + pixman_region32_t *damage); +/** + * Manually schedules a `frame` event. If a `frame` event is already pending, + * it is a no-op. + */ +void wlr_output_schedule_frame(struct wlr_output *output); +/** + * Returns the maximum length of each gamma ramp, or 0 if unsupported. + */ +size_t wlr_output_get_gamma_size(struct wlr_output *output); +/** + * Sets the gamma table for this output. `r`, `g` and `b` are gamma ramps for + * red, green and blue. `size` is the length of the ramps and must not exceed + * the value returned by `wlr_output_get_gamma_size`. + * + * Providing zero-sized ramps resets the gamma table. + */ +bool wlr_output_set_gamma(struct wlr_output *output, size_t size, + const uint16_t *r, const uint16_t *g, const uint16_t *b); +bool wlr_output_export_dmabuf(struct wlr_output *output, + struct wlr_dmabuf_attributes *attribs); +struct wlr_output *wlr_output_from_resource(struct wl_resource *resource); +/** + * Locks the output to only use software cursors instead of hardware cursors. + * This is useful if hardware cursors need to be temporarily disabled (e.g. + * during screen capture). There must be as many unlocks as there have been + * locks to restore the original state. There should never be an unlock before + * a lock. + */ +void wlr_output_lock_software_cursors(struct wlr_output *output, bool lock); +/** + * Renders software cursors. This is a utility function that can be called when + * compositors render. + */ +void wlr_output_render_software_cursors(struct wlr_output *output, + pixman_region32_t *damage); + + +struct wlr_output_cursor *wlr_output_cursor_create(struct wlr_output *output); +/** + * Sets the cursor image. The image must be already scaled for the output. + */ +bool wlr_output_cursor_set_image(struct wlr_output_cursor *cursor, + const uint8_t *pixels, int32_t stride, uint32_t width, uint32_t height, + int32_t hotspot_x, int32_t hotspot_y); +void wlr_output_cursor_set_surface(struct wlr_output_cursor *cursor, + struct wlr_surface *surface, int32_t hotspot_x, int32_t hotspot_y); +bool wlr_output_cursor_move(struct wlr_output_cursor *cursor, + double x, double y); +void wlr_output_cursor_destroy(struct wlr_output_cursor *cursor); + + +/** + * Returns the transform that, when composed with `tr`, gives + * `WL_OUTPUT_TRANSFORM_NORMAL`. + */ +enum wl_output_transform wlr_output_transform_invert( + enum wl_output_transform tr); + +/** + * Returns a transform that, when applied, has the same effect as applying + * sequentially `tr_a` and `tr_b`. + */ +enum wl_output_transform wlr_output_transform_compose( + enum wl_output_transform tr_a, enum wl_output_transform tr_b); + +#endif diff --git a/include/wlr/types/wlr_output_damage.h b/include/wlr/types/wlr_output_damage.h new file mode 100644 index 00000000..d614e6d6 --- /dev/null +++ b/include/wlr/types/wlr_output_damage.h @@ -0,0 +1,87 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_OUTPUT_DAMAGE_H +#define WLR_TYPES_WLR_OUTPUT_DAMAGE_H + +#include <pixman.h> +#include <time.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_output.h> + +/** + * Damage tracking requires to keep track of previous frames' damage. To allow + * damage tracking to work with triple buffering, a history of two frames is + * required. + */ +#define WLR_OUTPUT_DAMAGE_PREVIOUS_LEN 2 + +/** + * Tracks damage for an output. + * + * When a `frame` event is emitted, `wlr_output_damage_make_current` should be + * called. If necessary, the output should be repainted and + * `wlr_output_damage_swap_buffers` should be called. No rendering should happen + * outside a `frame` event handler. + */ +struct wlr_output_damage { + struct wlr_output *output; + int max_rects; // max number of damaged rectangles + + pixman_region32_t current; // in output-local coordinates + + // circular queue for previous damage + pixman_region32_t previous[WLR_OUTPUT_DAMAGE_PREVIOUS_LEN]; + size_t previous_idx; + + struct { + struct wl_signal frame; + struct wl_signal destroy; + } events; + + struct wl_listener output_destroy; + struct wl_listener output_mode; + struct wl_listener output_transform; + struct wl_listener output_scale; + struct wl_listener output_needs_swap; + struct wl_listener output_frame; +}; + +struct wlr_output_damage *wlr_output_damage_create(struct wlr_output *output); +void wlr_output_damage_destroy(struct wlr_output_damage *output_damage); +/** + * Makes the output rendering context current. `needs_swap` is set to true if + * `wlr_output_damage_swap_buffers` needs to be called. The region of the output + * that needs to be repainted is added to `damage`. + */ +bool wlr_output_damage_make_current(struct wlr_output_damage *output_damage, + bool *needs_swap, pixman_region32_t *damage); +/** + * Swaps the output buffers. If the time of the frame isn't known, set `when` to + * NULL. + * + * Swapping buffers schedules a `frame` event. + */ +bool wlr_output_damage_swap_buffers(struct wlr_output_damage *output_damage, + struct timespec *when, pixman_region32_t *damage); +/** + * Accumulates damage and schedules a `frame` event. + */ +void wlr_output_damage_add(struct wlr_output_damage *output_damage, + pixman_region32_t *damage); +/** + * Damages the whole output and schedules a `frame` event. + */ +void wlr_output_damage_add_whole(struct wlr_output_damage *output_damage); +/** + * Accumulates damage from a box and schedules a `frame` event. + */ +void wlr_output_damage_add_box(struct wlr_output_damage *output_damage, + struct wlr_box *box); + +#endif diff --git a/include/wlr/types/wlr_output_layout.h b/include/wlr/types/wlr_output_layout.h new file mode 100644 index 00000000..cc9d2328 --- /dev/null +++ b/include/wlr/types/wlr_output_layout.h @@ -0,0 +1,133 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_OUTPUT_LAYOUT_H +#define WLR_TYPES_WLR_OUTPUT_LAYOUT_H + +#include <stdbool.h> +#include <wayland-util.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_output.h> + +struct wlr_output_layout_state; + +struct wlr_output_layout { + struct wl_list outputs; + struct wlr_output_layout_state *state; + + struct { + struct wl_signal add; + struct wl_signal change; + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_output_layout_output_state; + +struct wlr_output_layout_output { + struct wlr_output *output; + int x, y; + struct wl_list link; + struct wlr_output_layout_output_state *state; + + struct { + struct wl_signal destroy; + } events; +}; + +/** + * Creates a wlr_output_layout, which can be used to describing outputs in + * physical space relative to one another, and perform various useful operations + * on that state. + */ +struct wlr_output_layout *wlr_output_layout_create(); + +void wlr_output_layout_destroy(struct wlr_output_layout *layout); + +struct wlr_output_layout_output *wlr_output_layout_get( + struct wlr_output_layout *layout, struct wlr_output *reference); + +struct wlr_output *wlr_output_layout_output_at(struct wlr_output_layout *layout, + double lx, double ly); + +void wlr_output_layout_add(struct wlr_output_layout *layout, + struct wlr_output *output, int lx, int ly); + +void wlr_output_layout_move(struct wlr_output_layout *layout, + struct wlr_output *output, int lx, int ly); + +void wlr_output_layout_remove(struct wlr_output_layout *layout, + struct wlr_output *output); + +/** + * Given x and y in layout coordinates, adjusts them to local output + * coordinates relative to the given reference output. + */ +void wlr_output_layout_output_coords(struct wlr_output_layout *layout, + struct wlr_output *reference, double *lx, double *ly); + +bool wlr_output_layout_contains_point(struct wlr_output_layout *layout, + struct wlr_output *reference, int lx, int ly); + +bool wlr_output_layout_intersects(struct wlr_output_layout *layout, + struct wlr_output *reference, const struct wlr_box *target_lbox); + +/** + * Get the closest point on this layout from the given point from the reference + * output. If reference is NULL, gets the closest point from the entire layout. + */ +void wlr_output_layout_closest_point(struct wlr_output_layout *layout, + struct wlr_output *reference, double lx, double ly, double *dest_lx, + double *dest_ly); + +/** + * Get the box of the layout for the given reference output in layout + * coordinates. If `reference` is NULL, the box will be for the extents of the + * entire layout. + */ +struct wlr_box *wlr_output_layout_get_box( + struct wlr_output_layout *layout, struct wlr_output *reference); + +/** +* Add an auto configured output to the layout. This will place the output in a +* sensible location in the layout. The coordinates of the output in the layout +* may adjust dynamically when the layout changes. If the output is already in +* the layout, it will become auto configured. If the position of the output is +* set such as with `wlr_output_layout_move()`, the output will become manually +* configured. +*/ +void wlr_output_layout_add_auto(struct wlr_output_layout *layout, + struct wlr_output *output); + +/** + * Get the output closest to the center of the layout extents. + */ +struct wlr_output *wlr_output_layout_get_center_output( + struct wlr_output_layout *layout); + +enum wlr_direction { + WLR_DIRECTION_UP = 1, + WLR_DIRECTION_DOWN = 2, + WLR_DIRECTION_LEFT = 4, + WLR_DIRECTION_RIGHT = 8, +}; + +/** + * Get the closest adjacent output to the reference output from the reference + * point in the given direction. + */ +struct wlr_output *wlr_output_layout_adjacent_output( + struct wlr_output_layout *layout, enum wlr_direction direction, + struct wlr_output *reference, double ref_lx, double ref_ly); +struct wlr_output *wlr_output_layout_farthest_output( + struct wlr_output_layout *layout, enum wlr_direction direction, + struct wlr_output *reference, double ref_lx, double ref_ly); + +#endif diff --git a/include/wlr/types/wlr_pointer.h b/include/wlr/types/wlr_pointer.h new file mode 100644 index 00000000..7dc643ae --- /dev/null +++ b/include/wlr/types/wlr_pointer.h @@ -0,0 +1,72 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_POINTER_H +#define WLR_TYPES_WLR_POINTER_H + +#include <stdint.h> +#include <wayland-server.h> +#include <wlr/types/wlr_input_device.h> + +struct wlr_pointer_impl; + +struct wlr_pointer { + const struct wlr_pointer_impl *impl; + + struct { + struct wl_signal motion; + struct wl_signal motion_absolute; + struct wl_signal button; + struct wl_signal axis; + } events; + + void *data; +}; + +struct wlr_event_pointer_motion { + struct wlr_input_device *device; + uint32_t time_msec; + double delta_x, delta_y; +}; + +struct wlr_event_pointer_motion_absolute { + struct wlr_input_device *device; + uint32_t time_msec; + // From 0..1 + double x, y; +}; + +struct wlr_event_pointer_button { + struct wlr_input_device *device; + uint32_t time_msec; + uint32_t button; + enum wlr_button_state state; +}; + +enum wlr_axis_source { + WLR_AXIS_SOURCE_WHEEL, + WLR_AXIS_SOURCE_FINGER, + WLR_AXIS_SOURCE_CONTINUOUS, + WLR_AXIS_SOURCE_WHEEL_TILT, +}; + +enum wlr_axis_orientation { + WLR_AXIS_ORIENTATION_VERTICAL, + WLR_AXIS_ORIENTATION_HORIZONTAL, +}; + +struct wlr_event_pointer_axis { + struct wlr_input_device *device; + uint32_t time_msec; + enum wlr_axis_source source; + enum wlr_axis_orientation orientation; + double delta; + int32_t delta_discrete; +}; + +#endif diff --git a/include/wlr/types/wlr_pointer_constraints_v1.h b/include/wlr/types/wlr_pointer_constraints_v1.h new file mode 100644 index 00000000..fef8c2e9 --- /dev/null +++ b/include/wlr/types/wlr_pointer_constraints_v1.h @@ -0,0 +1,102 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_POINTER_CONSTRAINTS_V1_H +#define WLR_TYPES_WLR_POINTER_CONSTRAINTS_V1_H + +#include <stdint.h> +#include <wayland-server.h> +#include <pixman.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_seat.h> +#include "pointer-constraints-unstable-v1-protocol.h" + +struct wlr_seat; + +enum wlr_pointer_constraint_v1_type { + WLR_POINTER_CONSTRAINT_V1_LOCKED, + WLR_POINTER_CONSTRAINT_V1_CONFINED, +}; + +enum wlr_pointer_constraint_v1_state_field { + WLR_POINTER_CONSTRAINT_V1_STATE_REGION = 1 << 0, + WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT = 1 << 1, +}; + +struct wlr_pointer_constraint_v1_state { + uint32_t committed; // enum wlr_pointer_constraint_v1_state_field + pixman_region32_t region; + + // only valid for locked_pointer + struct { + double x, y; + } cursor_hint; +}; + +struct wlr_pointer_constraint_v1 { + struct wlr_pointer_constraints_v1 *pointer_constraints; + + struct wl_resource *resource; + struct wlr_surface *surface; + struct wlr_seat *seat; + enum zwp_pointer_constraints_v1_lifetime lifetime; + enum wlr_pointer_constraint_v1_type type; + pixman_region32_t region; + + struct wlr_pointer_constraint_v1_state current, pending; + + struct wl_listener surface_commit; + struct wl_listener surface_destroy; + struct wl_listener seat_destroy; + + struct wl_list link; // wlr_pointer_constraints_v1::constraints + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_pointer_constraints_v1 { + struct wl_list resources; // wl_resource_get_link + struct wl_global *global; + + struct { + /** + * Called when a new pointer constraint is created. + * + * data: struct wlr_pointer_constraint_v1 * + */ + struct wl_signal new_constraint; + } events; + + struct wl_list constraints; // wlr_pointer_constraint_v1::link + + void *data; +}; + +struct wlr_pointer_constraints_v1 *wlr_pointer_constraints_v1_create( + struct wl_display *display); +void wlr_pointer_constraints_v1_destroy( + struct wlr_pointer_constraints_v1 *pointer_constraints); + +struct wlr_pointer_constraint_v1 * + wlr_pointer_constraints_v1_constraint_for_surface( + struct wlr_pointer_constraints_v1 *pointer_constraints, + struct wlr_surface *surface, struct wlr_seat *seat); + +void wlr_pointer_constraint_v1_send_activated( + struct wlr_pointer_constraint_v1 *constraint); +/** + * Deactivate the constraint. May destroy the constraint. + */ +void wlr_pointer_constraint_v1_send_deactivated( + struct wlr_pointer_constraint_v1 *constraint); + +#endif diff --git a/include/wlr/types/wlr_presentation_time.h b/include/wlr/types/wlr_presentation_time.h new file mode 100644 index 00000000..9f9f0e87 --- /dev/null +++ b/include/wlr/types/wlr_presentation_time.h @@ -0,0 +1,59 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_PRESENTATION_TIME_H +#define WLR_TYPES_WLR_PRESENTATION_TIME_H + +#include <stdbool.h> +#include <stddef.h> +#include <time.h> +#include <wayland-server.h> + +struct wlr_presentation { + struct wl_global *global; + struct wl_list resources; // wl_resource_get_link + struct wl_list feedbacks; // wlr_presentation_feedback::link + clockid_t clock; + + struct { + struct wl_signal destroy; + } events; + + struct wl_listener display_destroy; +}; + +struct wlr_presentation_feedback { + struct wl_resource *resource; + struct wlr_presentation *presentation; + struct wlr_surface *surface; + bool committed; + struct wl_list link; // wlr_presentation::feedbacks + + struct wl_listener surface_commit; + struct wl_listener surface_destroy; +}; + +struct wlr_presentation_event { + struct wlr_output *output; + uint64_t tv_sec; + uint32_t tv_nsec; + uint32_t refresh; + uint64_t seq; + uint32_t flags; // wp_presentation_feedback_kind +}; + +struct wlr_backend; + +struct wlr_presentation *wlr_presentation_create(struct wl_display *display, + struct wlr_backend *backend); +void wlr_presentation_destroy(struct wlr_presentation *presentation); +void wlr_presentation_send_surface_presented( + struct wlr_presentation *presentation, struct wlr_surface *surface, + struct wlr_presentation_event *event); + +#endif diff --git a/include/wlr/types/wlr_primary_selection.h b/include/wlr/types/wlr_primary_selection.h new file mode 100644 index 00000000..9be61acc --- /dev/null +++ b/include/wlr/types/wlr_primary_selection.h @@ -0,0 +1,54 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_PRIMARY_SELECTION_H +#define WLR_TYPES_WLR_PRIMARY_SELECTION_H + +#include <wayland-server.h> +#include <wlr/types/wlr_seat.h> + +struct wlr_primary_selection_source; + +/** + * A data source implementation. Only the `send` function is mandatory. + */ +struct wlr_primary_selection_source_impl { + void (*send)(struct wlr_primary_selection_source *source, + const char *mime_type, int fd); + void (*destroy)(struct wlr_primary_selection_source *source); +}; + +/** + * A source is the sending side of a selection. + */ +struct wlr_primary_selection_source { + const struct wlr_primary_selection_source_impl *impl; + + // source metadata + struct wl_array mime_types; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +void wlr_primary_selection_source_init( + struct wlr_primary_selection_source *source, + const struct wlr_primary_selection_source_impl *impl); +void wlr_primary_selection_source_destroy( + struct wlr_primary_selection_source *source); +void wlr_primary_selection_source_send( + struct wlr_primary_selection_source *source, const char *mime_type, + int fd); + +void wlr_seat_set_primary_selection(struct wlr_seat *seat, + struct wlr_primary_selection_source *source); + +#endif diff --git a/include/wlr/types/wlr_region.h b/include/wlr/types/wlr_region.h new file mode 100644 index 00000000..ec7f73aa --- /dev/null +++ b/include/wlr/types/wlr_region.h @@ -0,0 +1,24 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_REGION_H +#define WLR_TYPES_WLR_REGION_H + +#include <pixman.h> +#include <wayland-server-protocol.h> + +/* + * Creates a new region resource with the provided new ID. If `resource_list` is + * non-NULL, adds the region's resource to the list. + */ +struct wl_resource *wlr_region_create(struct wl_client *client, + uint32_t version, uint32_t id, struct wl_list *resource_list); + +pixman_region32_t *wlr_region_from_resource(struct wl_resource *resource); + +#endif diff --git a/include/wlr/types/wlr_screencopy_v1.h b/include/wlr/types/wlr_screencopy_v1.h new file mode 100644 index 00000000..c7197bab --- /dev/null +++ b/include/wlr/types/wlr_screencopy_v1.h @@ -0,0 +1,55 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_SCREENCOPY_V1_H +#define WLR_TYPES_WLR_SCREENCOPY_V1_H + +#include <stdbool.h> +#include <wayland-server.h> +#include <wlr/types/wlr_box.h> + +struct wlr_screencopy_manager_v1 { + struct wl_global *global; + struct wl_list resources; // wl_resource + struct wl_list frames; // wlr_screencopy_frame_v1::link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_screencopy_frame_v1 { + struct wl_resource *resource; + struct wlr_screencopy_manager_v1 *manager; + struct wl_list link; + + enum wl_shm_format format; + struct wlr_box box; + int stride; + + bool overlay_cursor, cursor_locked; + + struct wl_shm_buffer *buffer; + struct wl_listener buffer_destroy; + + struct wlr_output *output; + struct wl_listener output_swap_buffers; + + void *data; +}; + +struct wlr_screencopy_manager_v1 *wlr_screencopy_manager_v1_create( + struct wl_display *display); +void wlr_screencopy_manager_v1_destroy( + struct wlr_screencopy_manager_v1 *screencopy); + +#endif diff --git a/include/wlr/types/wlr_screenshooter.h b/include/wlr/types/wlr_screenshooter.h new file mode 100644 index 00000000..b7b87b39 --- /dev/null +++ b/include/wlr/types/wlr_screenshooter.h @@ -0,0 +1,41 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_SCREENSHOOTER_H +#define WLR_TYPES_WLR_SCREENSHOOTER_H + +#include <wayland-server.h> + +struct wlr_screenshooter { + struct wl_global *global; + struct wl_list screenshots; // wlr_screenshot::link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_screenshot { + struct wl_resource *resource; + struct wl_resource *output_resource; + struct wl_list link; + + struct wlr_output *output; + struct wlr_screenshooter *screenshooter; + + void* data; +}; + +struct wlr_screenshooter *wlr_screenshooter_create(struct wl_display *display); +void wlr_screenshooter_destroy(struct wlr_screenshooter *screenshooter); + +#endif diff --git a/include/wlr/types/wlr_seat.h b/include/wlr/types/wlr_seat.h new file mode 100644 index 00000000..942a3420 --- /dev/null +++ b/include/wlr/types/wlr_seat.h @@ -0,0 +1,573 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_SEAT_H +#define WLR_TYPES_WLR_SEAT_H + +#include <time.h> +#include <wayland-server.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_keyboard.h> +#include <wlr/types/wlr_surface.h> + +/** + * Contains state for a single client's bound wl_seat resource and can be used + * to issue input events to that client. The lifetime of these objects is + * managed by wlr_seat; some may be NULL. + */ +struct wlr_seat_client { + struct wl_client *client; + struct wlr_seat *seat; + + // lists of wl_resource + struct wl_list resources; + struct wl_list pointers; + struct wl_list keyboards; + struct wl_list touches; + struct wl_list data_devices; + + struct { + struct wl_signal destroy; + } events; + + struct wl_list link; +}; + +struct wlr_touch_point { + int32_t touch_id; + struct wlr_surface *surface; + struct wlr_seat_client *client; + + struct wlr_surface *focus_surface; + struct wlr_seat_client *focus_client; + double sx, sy; + + struct wl_listener surface_destroy; + struct wl_listener focus_surface_destroy; + + struct { + struct wl_signal destroy; + } events; + + struct wl_list link; +}; + +struct wlr_seat_pointer_grab; + +struct wlr_pointer_grab_interface { + void (*enter)(struct wlr_seat_pointer_grab *grab, + struct wlr_surface *surface, double sx, double sy); + void (*motion)(struct wlr_seat_pointer_grab *grab, uint32_t time, + double sx, double sy); + uint32_t (*button)(struct wlr_seat_pointer_grab *grab, uint32_t time, + uint32_t button, uint32_t state); + void (*axis)(struct wlr_seat_pointer_grab *grab, uint32_t time, + enum wlr_axis_orientation orientation, double value, + int32_t value_discrete, enum wlr_axis_source source); + void (*cancel)(struct wlr_seat_pointer_grab *grab); +}; + +struct wlr_seat_keyboard_grab; + +struct wlr_keyboard_grab_interface { + void (*enter)(struct wlr_seat_keyboard_grab *grab, + struct wlr_surface *surface, uint32_t keycodes[], + size_t num_keycodes, struct wlr_keyboard_modifiers *modifiers); + void (*key)(struct wlr_seat_keyboard_grab *grab, uint32_t time, + uint32_t key, uint32_t state); + void (*modifiers)(struct wlr_seat_keyboard_grab *grab, + struct wlr_keyboard_modifiers *modifiers); + void (*cancel)(struct wlr_seat_keyboard_grab *grab); +}; + +struct wlr_seat_touch_grab; + +struct wlr_touch_grab_interface { + uint32_t (*down)(struct wlr_seat_touch_grab *grab, uint32_t time, + struct wlr_touch_point *point); + void (*up)(struct wlr_seat_touch_grab *grab, uint32_t time, + struct wlr_touch_point *point); + void (*motion)(struct wlr_seat_touch_grab *grab, uint32_t time, + struct wlr_touch_point *point); + void (*enter)(struct wlr_seat_touch_grab *grab, uint32_t time, + struct wlr_touch_point *point); + // XXX this will conflict with the actual touch cancel which is different so + // we need to rename this + void (*cancel)(struct wlr_seat_touch_grab *grab); +}; + +/** + * Passed to `wlr_seat_touch_start_grab()` to start a grab of the touch device. + * The grabber is responsible for handling touch events for the seat. + */ +struct wlr_seat_touch_grab { + const struct wlr_touch_grab_interface *interface; + struct wlr_seat *seat; + void *data; +}; + +/** + * Passed to `wlr_seat_keyboard_start_grab()` to start a grab of the keyboard. + * The grabber is responsible for handling keyboard events for the seat. + */ +struct wlr_seat_keyboard_grab { + const struct wlr_keyboard_grab_interface *interface; + struct wlr_seat *seat; + void *data; +}; + +/** + * Passed to `wlr_seat_pointer_start_grab()` to start a grab of the pointer. The + * grabber is responsible for handling pointer events for the seat. + */ +struct wlr_seat_pointer_grab { + const struct wlr_pointer_grab_interface *interface; + struct wlr_seat *seat; + void *data; +}; + +struct wlr_seat_pointer_state { + struct wlr_seat *seat; + struct wlr_seat_client *focused_client; + struct wlr_surface *focused_surface; + + struct wlr_seat_pointer_grab *grab; + struct wlr_seat_pointer_grab *default_grab; + + uint32_t button_count; + uint32_t grab_button; + uint32_t grab_serial; + uint32_t grab_time; + + struct wl_listener surface_destroy; + + struct { + struct wl_signal focus_change; // wlr_seat_pointer_focus_change_event + } events; +}; + +// TODO: May be useful to be able to simulate keyboard input events +struct wlr_seat_keyboard_state { + struct wlr_seat *seat; + struct wlr_keyboard *keyboard; + + struct wlr_seat_client *focused_client; + struct wlr_surface *focused_surface; + + struct wl_listener keyboard_destroy; + struct wl_listener keyboard_keymap; + struct wl_listener keyboard_repeat_info; + + struct wl_listener surface_destroy; + + struct wlr_seat_keyboard_grab *grab; + struct wlr_seat_keyboard_grab *default_grab; + + struct { + struct wl_signal focus_change; // wlr_seat_keyboard_focus_change_event + } events; +}; + +struct wlr_seat_touch_state { + struct wlr_seat *seat; + struct wl_list touch_points; // wlr_touch_point::link + + uint32_t grab_serial; + uint32_t grab_id; + + struct wlr_seat_touch_grab *grab; + struct wlr_seat_touch_grab *default_grab; +}; + +struct wlr_primary_selection_source; + +struct wlr_seat { + struct wl_global *global; + struct wl_display *display; + struct wl_list clients; + struct wl_list drag_icons; // wlr_drag_icon::link + + char *name; + uint32_t capabilities; + struct timespec last_event; + + struct wlr_data_source *selection_source; + uint32_t selection_serial; + + struct wlr_primary_selection_source *primary_selection_source; + + // `drag` goes away before `drag_source`, when the implicit grab ends + struct wlr_drag *drag; + struct wlr_data_source *drag_source; + uint32_t drag_serial; + + struct wlr_seat_pointer_state pointer_state; + struct wlr_seat_keyboard_state keyboard_state; + struct wlr_seat_touch_state touch_state; + + struct wl_listener display_destroy; + struct wl_listener selection_source_destroy; + struct wl_listener primary_selection_source_destroy; + struct wl_listener drag_source_destroy; + + struct { + struct wl_signal pointer_grab_begin; + struct wl_signal pointer_grab_end; + + struct wl_signal keyboard_grab_begin; + struct wl_signal keyboard_grab_end; + + struct wl_signal touch_grab_begin; + struct wl_signal touch_grab_end; + + struct wl_signal request_set_cursor; + + struct wl_signal selection; + struct wl_signal primary_selection; + + struct wl_signal start_drag; + struct wl_signal new_drag_icon; + + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_seat_pointer_request_set_cursor_event { + struct wlr_seat_client *seat_client; + struct wlr_surface *surface; + uint32_t serial; + int32_t hotspot_x, hotspot_y; +}; + +struct wlr_seat_pointer_focus_change_event { + struct wlr_seat *seat; + struct wlr_surface *old_surface, *new_surface; + double sx, sy; +}; + +struct wlr_seat_keyboard_focus_change_event { + struct wlr_seat *seat; + struct wlr_surface *old_surface, *new_surface; +}; + +/** + * Allocates a new wlr_seat and adds a wl_seat global to the display. + */ +struct wlr_seat *wlr_seat_create(struct wl_display *display, const char *name); +/** + * Destroys a wlr_seat and removes its wl_seat global. + */ +void wlr_seat_destroy(struct wlr_seat *wlr_seat); +/** + * Gets a wlr_seat_client for the specified client, or returns NULL if no + * client is bound for that client. + */ +struct wlr_seat_client *wlr_seat_client_for_wl_client(struct wlr_seat *wlr_seat, + struct wl_client *wl_client); +/** + * Updates the capabilities available on this seat. + * Will automatically send them to all clients. + */ +void wlr_seat_set_capabilities(struct wlr_seat *wlr_seat, + uint32_t capabilities); +/** + * Updates the name of this seat. + * Will automatically send it to all clients. + */ +void wlr_seat_set_name(struct wlr_seat *wlr_seat, const char *name); + +/** + * Whether or not the surface has pointer focus + */ +bool wlr_seat_pointer_surface_has_focus(struct wlr_seat *wlr_seat, + struct wlr_surface *surface); + +/** + * Send a pointer enter event to the given surface and consider it to be the + * focused surface for the pointer. This will send a leave event to the last + * surface that was entered. Coordinates for the enter event are surface-local. + * Compositor should use `wlr_seat_pointer_notify_enter()` to change pointer + * focus to respect pointer grabs. + */ +void wlr_seat_pointer_enter(struct wlr_seat *wlr_seat, + struct wlr_surface *surface, double sx, double sy); + +/** + * Clear the focused surface for the pointer and leave all entered surfaces. + */ +void wlr_seat_pointer_clear_focus(struct wlr_seat *wlr_seat); + +/** + * Send a motion event to the surface with pointer focus. Coordinates for the + * motion event are surface-local. Compositors should use + * `wlr_seat_pointer_notify_motion()` to send motion events to respect pointer + * grabs. + */ +void wlr_seat_pointer_send_motion(struct wlr_seat *wlr_seat, uint32_t time, + double sx, double sy); + +/** + * Send a button event to the surface with pointer focus. Coordinates for the + * button event are surface-local. Returns the serial. Compositors should use + * `wlr_seat_pointer_notify_button()` to send button events to respect pointer + * grabs. + */ +uint32_t wlr_seat_pointer_send_button(struct wlr_seat *wlr_seat, uint32_t time, + uint32_t button, uint32_t state); + +/** + * Send an axis event to the surface with pointer focus. Compositors should use + * `wlr_seat_pointer_notify_axis()` to send axis events to respect pointer + * grabs. + **/ +void wlr_seat_pointer_send_axis(struct wlr_seat *wlr_seat, uint32_t time, + enum wlr_axis_orientation orientation, double value, + int32_t value_discrete, enum wlr_axis_source source); + +/** + * Start a grab of the pointer of this seat. The grabber is responsible for + * handling all pointer events until the grab ends. + */ +void wlr_seat_pointer_start_grab(struct wlr_seat *wlr_seat, + struct wlr_seat_pointer_grab *grab); + +/** + * End the grab of the pointer of this seat. This reverts the grab back to the + * default grab for the pointer. + */ +void wlr_seat_pointer_end_grab(struct wlr_seat *wlr_seat); + +/** + * Notify the seat of a pointer enter event to the given surface and request it + * to be the focused surface for the pointer. Pass surface-local coordinates + * where the enter occurred. + */ +void wlr_seat_pointer_notify_enter(struct wlr_seat *wlr_seat, + struct wlr_surface *surface, double sx, double sy); + +/** + * Notify the seat of motion over the given surface. Pass surface-local + * coordinates where the pointer motion occurred. + */ +void wlr_seat_pointer_notify_motion(struct wlr_seat *wlr_seat, uint32_t time, + double sx, double sy); + +/** + * Notify the seat that a button has been pressed. Returns the serial of the + * button press or zero if no button press was sent. + */ +uint32_t wlr_seat_pointer_notify_button(struct wlr_seat *wlr_seat, + uint32_t time, uint32_t button, uint32_t state); + +/** + * Notify the seat of an axis event. + */ +void wlr_seat_pointer_notify_axis(struct wlr_seat *wlr_seat, uint32_t time, + enum wlr_axis_orientation orientation, double value, + int32_t value_discrete, enum wlr_axis_source source); + +/** + * Whether or not the pointer has a grab other than the default grab. + */ +bool wlr_seat_pointer_has_grab(struct wlr_seat *seat); + +/** + * Set this keyboard as the active keyboard for the seat. + */ +void wlr_seat_set_keyboard(struct wlr_seat *seat, struct wlr_input_device *dev); + +/** + * Get the active keyboard for the seat. + */ +struct wlr_keyboard *wlr_seat_get_keyboard(struct wlr_seat *seat); + +/** + * Start a grab of the keyboard of this seat. The grabber is responsible for + * handling all keyboard events until the grab ends. + */ +void wlr_seat_keyboard_start_grab(struct wlr_seat *wlr_seat, + struct wlr_seat_keyboard_grab *grab); + +/** + * End the grab of the keyboard of this seat. This reverts the grab back to the + * default grab for the keyboard. + */ +void wlr_seat_keyboard_end_grab(struct wlr_seat *wlr_seat); + +/** + * Send the keyboard key to focused keyboard resources. Compositors should use + * `wlr_seat_notify_key()` to respect keyboard grabs. + */ +void wlr_seat_keyboard_send_key(struct wlr_seat *seat, uint32_t time, + uint32_t key, uint32_t state); + +/** + * Notify the seat that a key has been pressed on the keyboard. Defers to any + * keyboard grabs. + */ +void wlr_seat_keyboard_notify_key(struct wlr_seat *seat, uint32_t time, + uint32_t key, uint32_t state); + +/** + * Send the modifier state to focused keyboard resources. Compositors should use + * `wlr_seat_keyboard_notify_modifiers()` to respect any keyboard grabs. + */ +void wlr_seat_keyboard_send_modifiers(struct wlr_seat *seat, + struct wlr_keyboard_modifiers *modifiers); + +/** + * Notify the seat that the modifiers for the keyboard have changed. Defers to + * any keyboard grabs. + */ +void wlr_seat_keyboard_notify_modifiers(struct wlr_seat *seat, + struct wlr_keyboard_modifiers *modifiers); + +/** + * Notify the seat that the keyboard focus has changed and request it to be the + * focused surface for this keyboard. Defers to any current grab of the seat's + * keyboard. + */ +void wlr_seat_keyboard_notify_enter(struct wlr_seat *seat, + struct wlr_surface *surface, uint32_t keycodes[], size_t num_keycodes, + struct wlr_keyboard_modifiers *modifiers); + +/** + * Send a keyboard enter event to the given surface and consider it to be the + * focused surface for the keyboard. This will send a leave event to the last + * surface that was entered. Compositors should use + * `wlr_seat_keyboard_notify_enter()` to change keyboard focus to respect + * keyboard grabs. + */ +void wlr_seat_keyboard_enter(struct wlr_seat *seat, + struct wlr_surface *surface, uint32_t keycodes[], size_t num_keycodes, + struct wlr_keyboard_modifiers *modifiers); + +/** + * Clear the focused surface for the keyboard and leave all entered surfaces. + */ +void wlr_seat_keyboard_clear_focus(struct wlr_seat *wlr_seat); + +/** + * Whether or not the keyboard has a grab other than the default grab + */ +bool wlr_seat_keyboard_has_grab(struct wlr_seat *seat); + +/** + * Start a grab of the touch device of this seat. The grabber is responsible for + * handling all touch events until the grab ends. + */ +void wlr_seat_touch_start_grab(struct wlr_seat *wlr_seat, + struct wlr_seat_touch_grab *grab); + +/** + * End the grab of the touch device of this seat. This reverts the grab back to + * the default grab for the touch device. + */ +void wlr_seat_touch_end_grab(struct wlr_seat *wlr_seat); + +/** + * Get the active touch point with the given `touch_id`. If the touch point does + * not exist or is no longer active, returns NULL. + */ +struct wlr_touch_point *wlr_seat_touch_get_point(struct wlr_seat *seat, + int32_t touch_id); + +/** + * Notify the seat of a touch down on the given surface. Defers to any grab of + * the touch device. + */ +uint32_t wlr_seat_touch_notify_down(struct wlr_seat *seat, + struct wlr_surface *surface, uint32_t time, int32_t touch_id, double sx, + double sy); + +/** + * Notify the seat that the touch point given by `touch_id` is up. Defers to any + * grab of the touch device. + */ +void wlr_seat_touch_notify_up(struct wlr_seat *seat, uint32_t time, + int32_t touch_id); + +/** + * Notify the seat that the touch point given by `touch_id` has moved. Defers to + * any grab of the touch device. The seat should be notified of touch motion + * even if the surface is not the owner of the touch point for processing by + * grabs. + */ +void wlr_seat_touch_notify_motion(struct wlr_seat *seat, uint32_t time, + int32_t touch_id, double sx, double sy); + +/** + * Notify the seat that the touch point given by `touch_id` has entered a new + * surface. The surface is required. To clear focus, use + * `wlr_seat_touch_point_clear_focus()`. + */ +void wlr_seat_touch_point_focus(struct wlr_seat *seat, + struct wlr_surface *surface, uint32_t time, int32_t touch_id, double sx, + double sy); + +/** + * Clear the focused surface for the touch point given by `touch_id`. + */ +void wlr_seat_touch_point_clear_focus(struct wlr_seat *seat, uint32_t time, + int32_t touch_id); + +/** + * Send a touch down event to the client of the given surface. All future touch + * events for this point will go to this surface. If the touch down is valid, + * this will add a new touch point with the given `touch_id`. The touch down may + * not be valid if the surface seat client does not accept touch input. + * Coordinates are surface-local. Compositors should use + * `wlr_seat_touch_notify_down()` to respect any grabs of the touch device. + */ +uint32_t wlr_seat_touch_send_down(struct wlr_seat *seat, + struct wlr_surface *surface, uint32_t time, int32_t touch_id, double sx, + double sy); + +/** + * Send a touch up event for the touch point given by the `touch_id`. The event + * will go to the client for the surface given in the cooresponding touch down + * event. This will remove the touch point. Compositors should use + * `wlr_seat_touch_notify_up()` to respect any grabs of the touch device. + */ +void wlr_seat_touch_send_up(struct wlr_seat *seat, uint32_t time, + int32_t touch_id); + +/** + * Send a touch motion event for the touch point given by the `touch_id`. The + * event will go to the client for the surface given in the corresponding touch + * down event. Compositors should use `wlr_seat_touch_notify_motion()` to + * respect any grabs of the touch device. + */ +void wlr_seat_touch_send_motion(struct wlr_seat *seat, uint32_t time, + int32_t touch_id, double sx, double sy); + +/** + * How many touch points are currently down for the seat. + */ +int wlr_seat_touch_num_points(struct wlr_seat *seat); + +/** + * Whether or not the seat has a touch grab other than the default grab. + */ +bool wlr_seat_touch_has_grab(struct wlr_seat *seat); + +/** + * Check whether this serial is valid to start a grab action such as an + * interactive move or resize. + */ +bool wlr_seat_validate_grab_serial(struct wlr_seat *seat, uint32_t serial); + +struct wlr_seat_client *wlr_seat_client_from_resource( + struct wl_resource *resource); + +struct wlr_seat_client *wlr_seat_client_from_pointer_resource( + struct wl_resource *resource); + +#endif diff --git a/include/wlr/types/wlr_server_decoration.h b/include/wlr/types/wlr_server_decoration.h new file mode 100644 index 00000000..ff8d1369 --- /dev/null +++ b/include/wlr/types/wlr_server_decoration.h @@ -0,0 +1,78 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_SERVER_DECORATION_H +#define WLR_TYPES_WLR_SERVER_DECORATION_H + +#include <wayland-server.h> + +/** + * Possible values to use in request_mode and the event mode. Same as + * org_kde_kwin_server_decoration_manager_mode. + */ +enum wlr_server_decoration_manager_mode { + /** + * Undecorated: The surface is not decorated at all, neither server nor + * client-side. An example is a popup surface which should not be + * decorated. + */ + WLR_SERVER_DECORATION_MANAGER_MODE_NONE = 0, + /** + * Client-side decoration: The decoration is part of the surface and the + * client. + */ + WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT = 1, + /** + * Server-side decoration: The server embeds the surface into a decoration + * frame. + */ + WLR_SERVER_DECORATION_MANAGER_MODE_SERVER = 2, +}; + +struct wlr_server_decoration_manager { + struct wl_global *global; + struct wl_list resources; + struct wl_list decorations; // wlr_server_decoration::link + + uint32_t default_mode; // enum wlr_server_decoration_manager_mode + + struct wl_listener display_destroy; + + struct { + struct wl_signal new_decoration; + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_server_decoration { + struct wl_resource *resource; + struct wlr_surface *surface; + struct wl_list link; + + uint32_t mode; // enum wlr_server_decoration_manager_mode + + struct { + struct wl_signal destroy; + struct wl_signal mode; + } events; + + struct wl_listener surface_destroy_listener; + + void *data; +}; + +struct wlr_server_decoration_manager *wlr_server_decoration_manager_create( + struct wl_display *display); +void wlr_server_decoration_manager_set_default_mode( + struct wlr_server_decoration_manager *manager, uint32_t default_mode); +void wlr_server_decoration_manager_destroy( + struct wlr_server_decoration_manager *manager); + +#endif diff --git a/include/wlr/types/wlr_surface.h b/include/wlr/types/wlr_surface.h new file mode 100644 index 00000000..b8f8c02a --- /dev/null +++ b/include/wlr/types/wlr_surface.h @@ -0,0 +1,246 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_SURFACE_H +#define WLR_TYPES_WLR_SURFACE_H + +#include <pixman.h> +#include <stdbool.h> +#include <stdint.h> +#include <time.h> +#include <wayland-server.h> +#include <wlr/types/wlr_output.h> + +enum wlr_surface_state_field { + WLR_SURFACE_STATE_BUFFER = 1 << 0, + WLR_SURFACE_STATE_SURFACE_DAMAGE = 1 << 1, + WLR_SURFACE_STATE_BUFFER_DAMAGE = 1 << 2, + WLR_SURFACE_STATE_OPAQUE_REGION = 1 << 3, + WLR_SURFACE_STATE_INPUT_REGION = 1 << 4, + WLR_SURFACE_STATE_TRANSFORM = 1 << 5, + WLR_SURFACE_STATE_SCALE = 1 << 6, + WLR_SURFACE_STATE_FRAME_CALLBACK_LIST = 1 << 7, +}; + +struct wlr_surface_state { + uint32_t committed; // enum wlr_surface_state_field + + struct wl_resource *buffer_resource; + int32_t dx, dy; // relative to previous position + pixman_region32_t surface_damage, buffer_damage; // clipped to bounds + pixman_region32_t opaque, input; + enum wl_output_transform transform; + int32_t scale; + struct wl_list frame_callback_list; // wl_resource + + int width, height; // in surface-local coordinates + int buffer_width, buffer_height; + + struct wl_listener buffer_destroy; +}; + +struct wlr_surface_role { + const char *name; + void (*commit)(struct wlr_surface *surface); + void (*precommit)(struct wlr_surface *surface); +}; + +struct wlr_surface { + struct wl_resource *resource; + struct wlr_renderer *renderer; + /** + * The surface's buffer, if any. A surface has an attached buffer when it + * commits with a non-null buffer in its pending state. A surface will not + * have a buffer if it has never committed one, has committed a null buffer, + * or something went wrong with uploading the buffer. + */ + struct wlr_buffer *buffer; + /** + * The buffer position, in surface-local units. + */ + int sx, sy; + /** + * The last commit's buffer damage, in buffer-local coordinates. This + * contains both the damage accumulated by the client via + * `wlr_surface_state.surface_damage` and `wlr_surface_state.buffer_damage`. + * If the buffer has been resized, the whole buffer is damaged. + * + * This region needs to be scaled and transformed into output coordinates, + * just like the buffer's texture. In addition, if the buffer has shrunk the + * old size needs to be damaged and if the buffer has moved the old and new + * positions need to be damaged. + */ + pixman_region32_t buffer_damage; + /** + * The current opaque region, in surface-local coordinates. It is clipped to + * the surface bounds. If the surface's buffer is using a fully opaque + * format, this is set to the whole surface. + */ + pixman_region32_t opaque_region; + /** + * The current input region, in surface-local coordinates. It is clipped to + * the surface bounds. + */ + pixman_region32_t input_region; + /** + * `current` contains the current, committed surface state. `pending` + * accumulates state changes from the client between commits and shouldn't + * be accessed by the compositor directly. `previous` contains the state of + * the previous commit. + */ + struct wlr_surface_state current, pending, previous; + + const struct wlr_surface_role *role; // the lifetime-bound role or NULL + void *role_data; // role-specific data + + struct { + struct wl_signal commit; + struct wl_signal new_subsurface; + struct wl_signal destroy; + } events; + + struct wl_list subsurfaces; // wlr_subsurface::parent_link + + // wlr_subsurface::parent_pending_link + struct wl_list subsurface_pending_list; + + struct wl_listener renderer_destroy; + + void *data; +}; + +struct wlr_subsurface_state { + int32_t x, y; +}; + +struct wlr_subsurface { + struct wl_resource *resource; + struct wlr_surface *surface; + struct wlr_surface *parent; + + struct wlr_subsurface_state current, pending; + + struct wlr_surface_state cached; + bool has_cache; + + bool synchronized; + bool reordered; + + struct wl_list parent_link; + struct wl_list parent_pending_link; + + struct wl_listener surface_destroy; + struct wl_listener parent_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +typedef void (*wlr_surface_iterator_func_t)(struct wlr_surface *surface, + int sx, int sy, void *data); + +struct wlr_renderer; + +/** + * Create a new surface resource with the provided new ID. If `resource_list` + * is non-NULL, adds the surface's resource to the list. + */ +struct wlr_surface *wlr_surface_create(struct wl_client *client, + uint32_t version, uint32_t id, struct wlr_renderer *renderer, + struct wl_list *resource_list); + +/** + * Set the lifetime role for this surface. Returns 0 on success or -1 if the + * role cannot be set. + */ +bool wlr_surface_set_role(struct wlr_surface *surface, + const struct wlr_surface_role *role, void *role_data, + struct wl_resource *error_resource, uint32_t error_code); + +/** + * Whether or not this surface currently has an attached buffer. A surface has + * an attached buffer when it commits with a non-null buffer in its pending + * state. A surface will not have a buffer if it has never committed one, has + * committed a null buffer, or something went wrong with uploading the buffer. + */ +bool wlr_surface_has_buffer(struct wlr_surface *surface); + +/** + * Get the texture of the buffer currently attached to this surface. Returns + * NULL if no buffer is currently attached or if something went wrong with + * uploading the buffer. + */ +struct wlr_texture *wlr_surface_get_texture(struct wlr_surface *surface); + +/** + * Create a new subsurface resource with the provided new ID. If `resource_list` + * is non-NULL, adds the subsurface's resource to the list. + */ +struct wlr_subsurface *wlr_subsurface_create(struct wlr_surface *surface, + struct wlr_surface *parent, uint32_t version, uint32_t id, + struct wl_list *resource_list); + +/** + * Get the root of the subsurface tree for this surface. + */ +struct wlr_surface *wlr_surface_get_root_surface(struct wlr_surface *surface); + +/** + * Check if the surface accepts input events at the given surface-local + * coordinates. Does not check the surface's subsurfaces. + */ +bool wlr_surface_point_accepts_input(struct wlr_surface *surface, + double sx, double sy); + +/** + * Find a surface in this surface's tree that accepts input events at the given + * surface-local coordinates. Returns the surface and coordinates in the leaf + * surface coordinate system or NULL if no surface is found at that location. + */ +struct wlr_surface *wlr_surface_surface_at(struct wlr_surface *surface, + double sx, double sy, double *sub_x, double *sub_y); + +void wlr_surface_send_enter(struct wlr_surface *surface, + struct wlr_output *output); + +void wlr_surface_send_leave(struct wlr_surface *surface, + struct wlr_output *output); + +void wlr_surface_send_frame_done(struct wlr_surface *surface, + const struct timespec *when); + +struct wlr_box; +/** + * Get the bounding box that contains the surface and all subsurfaces in + * surface coordinates. + * X and y may be negative, if there are subsurfaces with negative position. + */ +void wlr_surface_get_extends(struct wlr_surface *surface, struct wlr_box *box); + +struct wlr_surface *wlr_surface_from_resource(struct wl_resource *resource); + +/** + * Call `iterator` on each surface in the surface tree, with the surface's + * position relative to the root surface. The function is called from root to + * leaves (in rendering order). + */ +void wlr_surface_for_each_surface(struct wlr_surface *surface, + wlr_surface_iterator_func_t iterator, void *user_data); + +/** + * Get the effective damage to the surface in terms of surface local + * coordinates. This includes damage induced by resizing and moving the + * surface. The damage is not expected to be bounded by the surface itself. + */ +void wlr_surface_get_effective_damage(struct wlr_surface *surface, + pixman_region32_t *damage); + +#endif diff --git a/include/wlr/types/wlr_switch.h b/include/wlr/types/wlr_switch.h new file mode 100644 index 00000000..df1c8579 --- /dev/null +++ b/include/wlr/types/wlr_switch.h @@ -0,0 +1,47 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_SWITCH_H +#define WLR_TYPES_WLR_SWITCH_H + +#include <stdint.h> +#include <wayland-server.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_list.h> + +struct wlr_switch_impl; + +struct wlr_switch { + struct wlr_switch_impl *impl; + + struct { + struct wl_signal toggle; + } events; + + void *data; +}; + +enum wlr_switch_type { + WLR_SWITCH_TYPE_LID = 1, + WLR_SWITCH_TYPE_TABLET_MODE, +}; + +enum wlr_switch_state { + WLR_SWITCH_STATE_OFF = 0, + WLR_SWITCH_STATE_ON, + WLR_SWITCH_STATE_TOGGLE +}; + +struct wlr_event_switch_toggle { + struct wlr_input_device *device; + uint32_t time_msec; + enum wlr_switch_type switch_type; + enum wlr_switch_state switch_state; +}; + +#endif diff --git a/include/wlr/types/wlr_tablet_pad.h b/include/wlr/types/wlr_tablet_pad.h new file mode 100644 index 00000000..d9bb284f --- /dev/null +++ b/include/wlr/types/wlr_tablet_pad.h @@ -0,0 +1,94 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_TABLET_PAD_H +#define WLR_TYPES_WLR_TABLET_PAD_H + +#include <stdint.h> +#include <wayland-server.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_list.h> + +/* + * NOTE: the wlr tablet pad implementation does not currently support tablets + * with more than one mode. I don't own any such hardware so I cannot test it + * and it is too complicated to make a meaningful implementation of blindly. + */ + +struct wlr_tablet_pad_impl; + +struct wlr_tablet_pad { + struct wlr_tablet_pad_impl *impl; + + struct { + struct wl_signal button; + struct wl_signal ring; + struct wl_signal strip; + struct wl_signal attach_tablet; //struct wlr_tablet_tool + } events; + + size_t button_count; + size_t ring_count; + size_t strip_count; + + struct wl_list groups; // wlr_tablet_pad_group::link + struct wlr_list paths; // char * + + void *data; +}; + +struct wlr_tablet_pad_group { + struct wl_list link; + + size_t button_count; + unsigned int *buttons; + + size_t strip_count; + unsigned int *strips; + + size_t ring_count; + unsigned int *rings; + + unsigned int mode_count; +}; + +struct wlr_event_tablet_pad_button { + uint32_t time_msec; + uint32_t button; + enum wlr_button_state state; + unsigned int mode; + unsigned int group; +}; + +enum wlr_tablet_pad_ring_source { + WLR_TABLET_PAD_RING_SOURCE_UNKNOWN, + WLR_TABLET_PAD_RING_SOURCE_FINGER, +}; + +struct wlr_event_tablet_pad_ring { + uint32_t time_msec; + enum wlr_tablet_pad_ring_source source; + uint32_t ring; + double position; + unsigned int mode; +}; + +enum wlr_tablet_pad_strip_source { + WLR_TABLET_PAD_STRIP_SOURCE_UNKNOWN, + WLR_TABLET_PAD_STRIP_SOURCE_FINGER, +}; + +struct wlr_event_tablet_pad_strip { + uint32_t time_msec; + enum wlr_tablet_pad_strip_source source; + uint32_t strip; + double position; + unsigned int mode; +}; + +#endif diff --git a/include/wlr/types/wlr_tablet_tool.h b/include/wlr/types/wlr_tablet_tool.h new file mode 100644 index 00000000..cb516ed9 --- /dev/null +++ b/include/wlr/types/wlr_tablet_tool.h @@ -0,0 +1,136 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_TABLET_TOOL_H +#define WLR_TYPES_TABLET_TOOL_H + +#include <stdint.h> +#include <wayland-server.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_list.h> + +/* + * Copy+Paste from libinput, but this should neither use libinput, nor + * tablet-unstable-v2 headers, so we can't include them + */ +enum wlr_tablet_tool_type { + WLR_TABLET_TOOL_TYPE_PEN = 1, /**< A generic pen */ + WLR_TABLET_TOOL_TYPE_ERASER, /**< Eraser */ + WLR_TABLET_TOOL_TYPE_BRUSH, /**< A paintbrush-like tool */ + WLR_TABLET_TOOL_TYPE_PENCIL, /**< Physical drawing tool, e.g. + Wacom Inking Pen */ + WLR_TABLET_TOOL_TYPE_AIRBRUSH, /**< An airbrush-like tool */ + WLR_TABLET_TOOL_TYPE_MOUSE, /**< A mouse bound to the tablet */ + WLR_TABLET_TOOL_TYPE_LENS, /**< A mouse tool with a lens */ +}; + +struct wlr_tablet_tool { + enum wlr_tablet_tool_type type; + uint64_t hardware_serial; + uint64_t hardware_wacom; + + // Capabilities + bool tilt; + bool pressure; + bool distance; + bool rotation; + bool slider; + bool wheel; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_tablet_impl; + +struct wlr_tablet { + struct wlr_tablet_impl *impl; + + struct { + struct wl_signal axis; + struct wl_signal proximity; + struct wl_signal tip; + struct wl_signal button; + } events; + + const char *name; + struct wlr_list paths; // char * + + void *data; +}; + +enum wlr_tablet_tool_axes { + WLR_TABLET_TOOL_AXIS_X = 1, + WLR_TABLET_TOOL_AXIS_Y = 2, + WLR_TABLET_TOOL_AXIS_DISTANCE = 4, + WLR_TABLET_TOOL_AXIS_PRESSURE = 8, + WLR_TABLET_TOOL_AXIS_TILT_X = 16, + WLR_TABLET_TOOL_AXIS_TILT_Y = 32, + WLR_TABLET_TOOL_AXIS_ROTATION = 64, + WLR_TABLET_TOOL_AXIS_SLIDER = 128, + WLR_TABLET_TOOL_AXIS_WHEEL = 256, +}; + +struct wlr_event_tablet_tool_axis { + struct wlr_input_device *device; + struct wlr_tablet_tool *tool; + + uint32_t time_msec; + uint32_t updated_axes; + // From 0..1 + double x, y; + // Relative to last event + double dx, dy; + double pressure; + double distance; + double tilt_x, tilt_y; + double rotation; + double slider; + double wheel_delta; +}; + +enum wlr_tablet_tool_proximity_state { + WLR_TABLET_TOOL_PROXIMITY_OUT, + WLR_TABLET_TOOL_PROXIMITY_IN, +}; + +struct wlr_event_tablet_tool_proximity { + struct wlr_input_device *device; + struct wlr_tablet_tool *tool; + uint32_t time_msec; + // From 0..1 + double x, y; + enum wlr_tablet_tool_proximity_state state; +}; + +enum wlr_tablet_tool_tip_state { + WLR_TABLET_TOOL_TIP_UP, + WLR_TABLET_TOOL_TIP_DOWN, +}; + +struct wlr_event_tablet_tool_tip { + struct wlr_input_device *device; + struct wlr_tablet_tool *tool; + uint32_t time_msec; + // From 0..1 + double x, y; + enum wlr_tablet_tool_tip_state state; +}; + +struct wlr_event_tablet_tool_button { + struct wlr_input_device *device; + struct wlr_tablet_tool *tool; + uint32_t time_msec; + uint32_t button; + enum wlr_button_state state; +}; + +#endif diff --git a/include/wlr/types/wlr_tablet_v2.h b/include/wlr/types/wlr_tablet_v2.h new file mode 100644 index 00000000..d6fd646d --- /dev/null +++ b/include/wlr/types/wlr_tablet_v2.h @@ -0,0 +1,330 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_TABLET_V2_H +#define WLR_TYPES_WLR_TABLET_V2_H + +#include <wayland-server.h> +#include <wlr/types/wlr_seat.h> +#include <wlr/types/wlr_input_device.h> + +#include "tablet-unstable-v2-protocol.h" + +/* This can probably be even lower,the tools don't have a lot of buttons */ +#define WLR_TABLET_V2_TOOL_BUTTONS_CAP 16 + +struct wlr_tablet_pad_v2_grab_interface; + +struct wlr_tablet_pad_v2_grab { + const struct wlr_tablet_pad_v2_grab_interface *interface; + struct wlr_tablet_v2_tablet_pad *pad; + void *data; +}; + +struct wlr_tablet_tool_v2_grab_interface; + +struct wlr_tablet_tool_v2_grab { + const struct wlr_tablet_tool_v2_grab_interface *interface; + struct wlr_tablet_v2_tablet_tool *tool; + void *data; +}; + +struct wlr_tablet_client_v2; +struct wlr_tablet_tool_client_v2; +struct wlr_tablet_pad_client_v2; + +struct wlr_tablet_manager_v2 { + struct wl_global *wl_global; + struct wl_list clients; // wlr_tablet_manager_client_v2::link + struct wl_list seats; // wlr_tablet_seat_v2::link + + struct wl_listener display_destroy; + + struct { + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_tablet_v2_tablet { + struct wl_list link; // wlr_tablet_seat_v2::tablets + struct wlr_tablet *wlr_tablet; + struct wlr_input_device *wlr_device; + struct wl_list clients; // wlr_tablet_client_v2::tablet_link + + struct wl_listener tool_destroy; + + struct wlr_tablet_client_v2 *current_client; +}; + +struct wlr_tablet_v2_tablet_tool { + struct wl_list link; // wlr_tablet_seat_v2::tablets + struct wlr_tablet_tool *wlr_tool; + struct wl_list clients; // wlr_tablet_tool_client_v2::tool_link + + struct wl_listener tool_destroy; + + struct wlr_tablet_tool_client_v2 *current_client; + struct wlr_surface *focused_surface; + struct wl_listener surface_destroy; + + struct wlr_tablet_tool_v2_grab *grab; + struct wlr_tablet_tool_v2_grab default_grab; + + uint32_t proximity_serial; + bool is_down; + uint32_t down_serial; + size_t num_buttons; + uint32_t pressed_buttons[WLR_TABLET_V2_TOOL_BUTTONS_CAP]; + uint32_t pressed_serials[WLR_TABLET_V2_TOOL_BUTTONS_CAP]; + + struct { + struct wl_signal set_cursor; // struct wlr_tablet_v2_event_cursor + } events; +}; + +struct wlr_tablet_v2_tablet_pad { + struct wl_list link; // wlr_tablet_seat_v2::pads + struct wlr_tablet_pad *wlr_pad; + struct wlr_input_device *wlr_device; + struct wl_list clients; // wlr_tablet_pad_client_v2::pad_link + + size_t group_count; + uint32_t *groups; + + struct wl_listener pad_destroy; + + struct wlr_tablet_pad_client_v2 *current_client; + struct wlr_tablet_pad_v2_grab *grab; + struct wlr_tablet_pad_v2_grab default_grab; + + struct { + struct wl_signal button_feedback; // struct wlr_tablet_v2_event_feedback + struct wl_signal strip_feedback; // struct wlr_tablet_v2_event_feedback + struct wl_signal ring_feedback; // struct wlr_tablet_v2_event_feedback + } events; +}; + +struct wlr_tablet_v2_event_cursor { + struct wlr_surface *surface; + uint32_t serial; + int32_t hotspot_x; + int32_t hotspot_y; + struct wlr_seat_client *seat_client; +}; + +struct wlr_tablet_v2_event_feedback { + const char *description; + size_t index; + uint32_t serial; +}; + +struct wlr_tablet_v2_tablet *wlr_tablet_create( + struct wlr_tablet_manager_v2 *manager, + struct wlr_seat *wlr_seat, + struct wlr_input_device *wlr_device); + +struct wlr_tablet_v2_tablet_pad *wlr_tablet_pad_create( + struct wlr_tablet_manager_v2 *manager, + struct wlr_seat *wlr_seat, + struct wlr_input_device *wlr_device); + +struct wlr_tablet_v2_tablet_tool *wlr_tablet_tool_create( + struct wlr_tablet_manager_v2 *manager, + struct wlr_seat *wlr_seat, + struct wlr_tablet_tool *wlr_tool); + +struct wlr_tablet_manager_v2 *wlr_tablet_v2_create(struct wl_display *display); +void wlr_tablet_v2_destroy(struct wlr_tablet_manager_v2 *manager); + +void wlr_send_tablet_v2_tablet_tool_proximity_in( + struct wlr_tablet_v2_tablet_tool *tool, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface); + +void wlr_send_tablet_v2_tablet_tool_down(struct wlr_tablet_v2_tablet_tool *tool); +void wlr_send_tablet_v2_tablet_tool_up(struct wlr_tablet_v2_tablet_tool *tool); + +void wlr_send_tablet_v2_tablet_tool_motion( + struct wlr_tablet_v2_tablet_tool *tool, double x, double y); + +void wlr_send_tablet_v2_tablet_tool_pressure( + struct wlr_tablet_v2_tablet_tool *tool, double pressure); + +void wlr_send_tablet_v2_tablet_tool_distance( + struct wlr_tablet_v2_tablet_tool *tool, double distance); + +void wlr_send_tablet_v2_tablet_tool_tilt( + struct wlr_tablet_v2_tablet_tool *tool, double x, double y); + +void wlr_send_tablet_v2_tablet_tool_rotation( + struct wlr_tablet_v2_tablet_tool *tool, double degrees); + +void wlr_send_tablet_v2_tablet_tool_slider( + struct wlr_tablet_v2_tablet_tool *tool, double position); + +void wlr_send_tablet_v2_tablet_tool_wheel( + struct wlr_tablet_v2_tablet_tool *tool, double degrees, int32_t clicks); + +void wlr_send_tablet_v2_tablet_tool_proximity_out( + struct wlr_tablet_v2_tablet_tool *tool); + +void wlr_send_tablet_v2_tablet_tool_button( + struct wlr_tablet_v2_tablet_tool *tool, uint32_t button, + enum zwp_tablet_pad_v2_button_state state); + + + +void wlr_tablet_v2_tablet_tool_notify_proximity_in( + struct wlr_tablet_v2_tablet_tool *tool, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface); + +void wlr_tablet_v2_tablet_tool_notify_down(struct wlr_tablet_v2_tablet_tool *tool); +void wlr_tablet_v2_tablet_tool_notify_up(struct wlr_tablet_v2_tablet_tool *tool); + +void wlr_tablet_v2_tablet_tool_notify_motion( + struct wlr_tablet_v2_tablet_tool *tool, double x, double y); + +void wlr_tablet_v2_tablet_tool_notify_pressure( + struct wlr_tablet_v2_tablet_tool *tool, double pressure); + +void wlr_tablet_v2_tablet_tool_notify_distance( + struct wlr_tablet_v2_tablet_tool *tool, double distance); + +void wlr_tablet_v2_tablet_tool_notify_tilt( + struct wlr_tablet_v2_tablet_tool *tool, double x, double y); + +void wlr_tablet_v2_tablet_tool_notify_rotation( + struct wlr_tablet_v2_tablet_tool *tool, double degrees); + +void wlr_tablet_v2_tablet_tool_notify_slider( + struct wlr_tablet_v2_tablet_tool *tool, double position); + +void wlr_tablet_v2_tablet_tool_notify_wheel( + struct wlr_tablet_v2_tablet_tool *tool, double degrees, int32_t clicks); + +void wlr_tablet_v2_tablet_tool_notify_proximity_out( + struct wlr_tablet_v2_tablet_tool *tool); + +void wlr_tablet_v2_tablet_tool_notify_button( + struct wlr_tablet_v2_tablet_tool *tool, uint32_t button, + enum zwp_tablet_pad_v2_button_state state); + + +struct wlr_tablet_tool_v2_grab_interface { + void (*proximity_in)( + struct wlr_tablet_tool_v2_grab *grab, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface); + + void (*down)(struct wlr_tablet_tool_v2_grab *grab); + void (*up)(struct wlr_tablet_tool_v2_grab *grab); + + void (*motion)(struct wlr_tablet_tool_v2_grab *grab, double x, double y); + + void (*pressure)(struct wlr_tablet_tool_v2_grab *grab, double pressure); + + void (*distance)(struct wlr_tablet_tool_v2_grab *grab, double distance); + + void (*tilt)(struct wlr_tablet_tool_v2_grab *grab, double x, double y); + + void (*rotation)(struct wlr_tablet_tool_v2_grab *grab, double degrees); + + void (*slider)(struct wlr_tablet_tool_v2_grab *grab, double position); + + void (*wheel)(struct wlr_tablet_tool_v2_grab *grab, double degrees, int32_t clicks); + + void (*proximity_out)(struct wlr_tablet_tool_v2_grab *grab); + + void (*button)( + struct wlr_tablet_tool_v2_grab *grab, uint32_t button, + enum zwp_tablet_pad_v2_button_state state); + void (*cancel)(struct wlr_tablet_tool_v2_grab *grab); +}; + +void wlr_tablet_tool_v2_start_grab(struct wlr_tablet_v2_tablet_tool *tool, struct wlr_tablet_tool_v2_grab *grab); +void wlr_tablet_tool_v2_end_grab(struct wlr_tablet_v2_tablet_tool *tool); + +void wlr_tablet_tool_v2_start_implicit_grab(struct wlr_tablet_v2_tablet_tool *tool); + + +uint32_t wlr_send_tablet_v2_tablet_pad_enter( + struct wlr_tablet_v2_tablet_pad *pad, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface); + +void wlr_send_tablet_v2_tablet_pad_button( + struct wlr_tablet_v2_tablet_pad *pad, size_t button, + uint32_t time, enum zwp_tablet_pad_v2_button_state state); + +void wlr_send_tablet_v2_tablet_pad_strip(struct wlr_tablet_v2_tablet_pad *pad, + uint32_t strip, double position, bool finger, uint32_t time); +void wlr_send_tablet_v2_tablet_pad_ring(struct wlr_tablet_v2_tablet_pad *pad, + uint32_t ring, double position, bool finger, uint32_t time); + +uint32_t wlr_send_tablet_v2_tablet_pad_leave(struct wlr_tablet_v2_tablet_pad *pad, + struct wlr_surface *surface); + +uint32_t wlr_send_tablet_v2_tablet_pad_mode(struct wlr_tablet_v2_tablet_pad *pad, + size_t group, uint32_t mode, uint32_t time); + + +uint32_t wlr_tablet_v2_tablet_pad_notify_enter( + struct wlr_tablet_v2_tablet_pad *pad, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface); + +void wlr_tablet_v2_tablet_pad_notify_button( + struct wlr_tablet_v2_tablet_pad *pad, size_t button, + uint32_t time, enum zwp_tablet_pad_v2_button_state state); + +void wlr_tablet_v2_tablet_pad_notify_strip( + struct wlr_tablet_v2_tablet_pad *pad, + uint32_t strip, double position, bool finger, uint32_t time); +void wlr_tablet_v2_tablet_pad_notify_ring( + struct wlr_tablet_v2_tablet_pad *pad, + uint32_t ring, double position, bool finger, uint32_t time); + +uint32_t wlr_tablet_v2_tablet_pad_notify_leave( + struct wlr_tablet_v2_tablet_pad *pad, struct wlr_surface *surface); + +uint32_t wlr_tablet_v2_tablet_pad_notify_mode( + struct wlr_tablet_v2_tablet_pad *pad, + size_t group, uint32_t mode, uint32_t time); + +struct wlr_tablet_pad_v2_grab_interface { + uint32_t (*enter)( + struct wlr_tablet_pad_v2_grab *grab, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface); + + void (*button)(struct wlr_tablet_pad_v2_grab *grab,size_t button, + uint32_t time, enum zwp_tablet_pad_v2_button_state state); + + void (*strip)(struct wlr_tablet_pad_v2_grab *grab, + uint32_t strip, double position, bool finger, uint32_t time); + void (*ring)(struct wlr_tablet_pad_v2_grab *grab, + uint32_t ring, double position, bool finger, uint32_t time); + + uint32_t (*leave)(struct wlr_tablet_pad_v2_grab *grab, + struct wlr_surface *surface); + + uint32_t (*mode)(struct wlr_tablet_pad_v2_grab *grab, + size_t group, uint32_t mode, uint32_t time); + + void (*cancel)(struct wlr_tablet_pad_v2_grab *grab); +}; + +void wlr_tablet_v2_end_grab(struct wlr_tablet_v2_tablet_pad *pad); +void wlr_tablet_v2_start_grab(struct wlr_tablet_v2_tablet_pad *pad, struct wlr_tablet_pad_v2_grab *grab); + +bool wlr_surface_accepts_tablet_v2(struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface); +#endif /* WLR_TYPES_WLR_TABLET_V2_H */ diff --git a/include/wlr/types/wlr_text_input_v3.h b/include/wlr/types/wlr_text_input_v3.h new file mode 100644 index 00000000..0db0cf47 --- /dev/null +++ b/include/wlr/types/wlr_text_input_v3.h @@ -0,0 +1,93 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_TEXT_INPUT_V3_H +#define WLR_TYPES_WLR_TEXT_INPUT_V3_H + +#include <wayland-server.h> +#include <wlr/types/wlr_seat.h> +#include <wlr/types/wlr_surface.h> + +struct wlr_text_input_v3_state { + struct { + char *text; // NULL is allowed and equivalent to empty string + uint32_t cursor; + uint32_t anchor; + } surrounding; + + uint32_t text_change_cause; + + struct { + uint32_t hint; + uint32_t purpose; + } content_type; + + struct { + int32_t x; + int32_t y; + int32_t width; + int32_t height; + } cursor_rectangle; +}; + +struct wlr_text_input_v3 { + struct wlr_seat *seat; // becomes null when seat destroyed + struct wl_resource *resource; + struct wlr_surface *focused_surface; + struct wlr_text_input_v3_state pending; + struct wlr_text_input_v3_state current; + uint32_t current_serial; // next in line to send + bool pending_enabled; + bool current_enabled; + + struct wl_list link; + + struct wl_listener surface_destroy; + struct wl_listener seat_destroy; + + struct { + struct wl_signal enable; // (struct wlr_text_input_v3*) + struct wl_signal commit; // (struct wlr_text_input_v3*) + struct wl_signal disable; // (struct wlr_text_input_v3*) + struct wl_signal destroy; // (struct wlr_text_input_v3*) + } events; +}; + +struct wlr_text_input_manager_v3 { + struct wl_global *global; + + struct wl_list bound_resources; // struct wl_resource*::link + struct wl_list text_inputs; // struct wlr_text_input_v3::resource::link + + struct wl_listener display_destroy; + + struct { + struct wl_signal text_input; // (struct wlr_text_input_v3*) + struct wl_signal destroy; // (struct wlr_input_method_manager_v3*) + } events; +}; + +struct wlr_text_input_manager_v3 *wlr_text_input_manager_v3_create( + struct wl_display *wl_display); +void wlr_text_input_manager_v3_destroy( + struct wlr_text_input_manager_v3 *manager); + +// Sends enter to the surface and saves it +void wlr_text_input_v3_send_enter(struct wlr_text_input_v3 *text_input, + struct wlr_surface *wlr_surface); +// Sends leave to the currently focused surface and clears it +void wlr_text_input_v3_send_leave(struct wlr_text_input_v3 *text_input); +void wlr_text_input_v3_send_preedit_string(struct wlr_text_input_v3 *text_input, + const char *text, uint32_t cursor_begin, uint32_t cursor_end); +void wlr_text_input_v3_send_commit_string(struct wlr_text_input_v3 *text_input, + const char *text); +void wlr_text_input_v3_send_delete_surrounding_text( + struct wlr_text_input_v3 *text_input, uint32_t before_length, + uint32_t after_length); +void wlr_text_input_v3_send_done(struct wlr_text_input_v3 *text_input); +#endif diff --git a/include/wlr/types/wlr_touch.h b/include/wlr/types/wlr_touch.h new file mode 100644 index 00000000..99316ae0 --- /dev/null +++ b/include/wlr/types/wlr_touch.h @@ -0,0 +1,58 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_TOUCH_H +#define WLR_TYPES_WLR_TOUCH_H + +#include <stdint.h> +#include <wayland-server.h> + +struct wlr_touch_impl; + +struct wlr_touch { + struct wlr_touch_impl *impl; + + struct { + struct wl_signal down; + struct wl_signal up; + struct wl_signal motion; + struct wl_signal cancel; + } events; + + void *data; +}; + +struct wlr_event_touch_down { + struct wlr_input_device *device; + uint32_t time_msec; + int32_t touch_id; + // From 0..1 + double x, y; +}; + +struct wlr_event_touch_up { + struct wlr_input_device *device; + uint32_t time_msec; + int32_t touch_id; +}; + +struct wlr_event_touch_motion { + struct wlr_input_device *device; + uint32_t time_msec; + int32_t touch_id; + // From 0..1 + double x, y; +}; + +struct wlr_event_touch_cancel { + struct wlr_input_device *device; + uint32_t time_msec; + int32_t touch_id; +}; + +#endif diff --git a/include/wlr/types/wlr_virtual_keyboard_v1.h b/include/wlr/types/wlr_virtual_keyboard_v1.h new file mode 100644 index 00000000..e75ed8ec --- /dev/null +++ b/include/wlr/types/wlr_virtual_keyboard_v1.h @@ -0,0 +1,46 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_VIRTUAL_KEYBOARD_V1_H +#define WLR_TYPES_WLR_VIRTUAL_KEYBOARD_V1_H + +#include <wayland-server.h> +#include <wlr/interfaces/wlr_input_device.h> +#include <wlr/interfaces/wlr_keyboard.h> + +struct wlr_virtual_keyboard_manager_v1 { + struct wl_global *global; + struct wl_list resources; // struct wl_resource* + struct wl_list virtual_keyboards; // struct wlr_virtual_keyboard_v1* + + struct wl_listener display_destroy; + + struct { + struct wl_signal new_virtual_keyboard; // struct wlr_virtual_keyboard_v1* + struct wl_signal destroy; + } events; +}; + +struct wlr_virtual_keyboard_v1 { + struct wl_resource *resource; + struct wlr_input_device input_device; + struct wlr_seat *seat; + + struct wl_list link; + + struct { + struct wl_signal destroy; // struct wlr_virtual_keyboard_v1* + } events; +}; + +struct wlr_virtual_keyboard_manager_v1* wlr_virtual_keyboard_manager_v1_create( + struct wl_display *display); +void wlr_virtual_keyboard_manager_v1_destroy( + struct wlr_virtual_keyboard_manager_v1 *manager); + +#endif diff --git a/include/wlr/types/wlr_wl_shell.h b/include/wlr/types/wlr_wl_shell.h new file mode 100644 index 00000000..dffbb4d7 --- /dev/null +++ b/include/wlr/types/wlr_wl_shell.h @@ -0,0 +1,175 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_WL_SHELL_H +#define WLR_TYPES_WLR_WL_SHELL_H + +#include <stdbool.h> +#include <wayland-server.h> +#include <wlr/types/wlr_seat.h> + +struct wlr_wl_shell { + struct wl_global *global; + struct wl_list resources; + struct wl_list surfaces; + struct wl_list popup_grabs; + uint32_t ping_timeout; + + struct wl_listener display_destroy; + + struct { + struct wl_signal new_surface; + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_wl_shell_surface_transient_state { + int32_t x; + int32_t y; + enum wl_shell_surface_transient flags; +}; + +struct wlr_wl_shell_surface_popup_state { + struct wlr_seat *seat; + uint32_t serial; +}; + +// each seat gets a popup grab +struct wlr_wl_shell_popup_grab { + struct wl_client *client; + struct wlr_seat_pointer_grab pointer_grab; + struct wlr_seat *seat; + struct wl_list popups; + struct wl_list link; // wlr_wl_shell::popup_grabs +}; + +enum wlr_wl_shell_surface_state { + WLR_WL_SHELL_SURFACE_STATE_NONE, + WLR_WL_SHELL_SURFACE_STATE_TOPLEVEL, + WLR_WL_SHELL_SURFACE_STATE_MAXIMIZED, + WLR_WL_SHELL_SURFACE_STATE_FULLSCREEN, + WLR_WL_SHELL_SURFACE_STATE_TRANSIENT, + WLR_WL_SHELL_SURFACE_STATE_POPUP, +}; + +struct wlr_wl_shell_surface { + struct wlr_wl_shell *shell; + struct wl_client *client; + struct wl_resource *resource; + struct wlr_surface *surface; + bool configured; + struct wl_list link; // wlr_wl_shell::surfaces + + uint32_t ping_serial; + struct wl_event_source *ping_timer; + + enum wlr_wl_shell_surface_state state; + struct wlr_wl_shell_surface_transient_state *transient_state; + struct wlr_wl_shell_surface_popup_state *popup_state; + struct wl_list grab_link; // wlr_wl_shell_popup_grab::popups + + char *title; + char *class; + + struct wl_listener surface_destroy; + + struct wlr_wl_shell_surface *parent; + struct wl_list popup_link; + struct wl_list popups; + bool popup_mapped; + + struct { + struct wl_signal destroy; + struct wl_signal ping_timeout; + struct wl_signal new_popup; + + struct wl_signal request_move; + struct wl_signal request_resize; + struct wl_signal request_fullscreen; + struct wl_signal request_maximize; + + struct wl_signal set_state; + struct wl_signal set_title; + struct wl_signal set_class; + } events; + + void *data; +}; + +struct wlr_wl_shell_surface_move_event { + struct wlr_wl_shell_surface *surface; + struct wlr_seat_client *seat; + uint32_t serial; +}; + +struct wlr_wl_shell_surface_resize_event { + struct wlr_wl_shell_surface *surface; + struct wlr_seat_client *seat; + uint32_t serial; + enum wl_shell_surface_resize edges; +}; + +struct wlr_wl_shell_surface_set_fullscreen_event { + struct wlr_wl_shell_surface *surface; + enum wl_shell_surface_fullscreen_method method; + uint32_t framerate; + struct wlr_output *output; +}; + +struct wlr_wl_shell_surface_maximize_event { + struct wlr_wl_shell_surface *surface; + struct wlr_output *output; +}; + +/** + * Create a wl_shell for this display. + */ +struct wlr_wl_shell *wlr_wl_shell_create(struct wl_display *display); + +/** + * Destroy this surface. + */ +void wlr_wl_shell_destroy(struct wlr_wl_shell *wlr_wl_shell); + +/** + * Send a ping to the surface. If the surface does not respond with a pong + * within a reasonable amount of time, the ping timeout event will be emitted. + */ +void wlr_wl_shell_surface_ping(struct wlr_wl_shell_surface *surface); + +/** + * Request that the surface configure itself to be the given size. + */ +void wlr_wl_shell_surface_configure(struct wlr_wl_shell_surface *surface, + enum wl_shell_surface_resize edges, int32_t width, int32_t height); + +/** + * Find a surface within this wl-shell surface tree at the given surface-local + * coordinates. Returns the surface and coordinates in the leaf surface + * coordinate system or NULL if no surface is found at that location. + */ +struct wlr_surface *wlr_wl_shell_surface_surface_at( + struct wlr_wl_shell_surface *surface, double sx, double sy, + double *sub_sx, double *sub_sy); + +bool wlr_surface_is_wl_shell_surface(struct wlr_surface *surface); + +struct wlr_wl_shell_surface *wlr_wl_shell_surface_from_wlr_surface( + struct wlr_surface *surface); + +/** + * Call `iterator` on each surface in the shell surface tree, with the surface's + * position relative to the root xdg-surface. The function is called from root to + * leaves (in rendering order). + */ +void wlr_wl_shell_surface_for_each_surface(struct wlr_wl_shell_surface *surface, + wlr_surface_iterator_func_t iterator, void *user_data); + +#endif diff --git a/include/wlr/types/wlr_xcursor_manager.h b/include/wlr/types/wlr_xcursor_manager.h new file mode 100644 index 00000000..285006bf --- /dev/null +++ b/include/wlr/types/wlr_xcursor_manager.h @@ -0,0 +1,69 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_XCURSOR_MANAGER_H +#define WLR_TYPES_WLR_XCURSOR_MANAGER_H + +#include <wayland-server.h> +#include <wlr/types/wlr_cursor.h> +#include <wlr/xcursor.h> + +/** + * An XCursor theme at a particular scale factor of the base size. + */ +struct wlr_xcursor_manager_theme { + float scale; + struct wlr_xcursor_theme *theme; + struct wl_list link; +}; + +/** + * wlr_xcursor_manager dynamically loads xcursor themes at sizes necessary for + * use on outputs at arbitrary scale factors. You should call + * wlr_xcursor_manager_load for each output you will show your cursor on, with + * the scale factor parameter set to that output's scale factor. + */ +struct wlr_xcursor_manager { + char *name; + uint32_t size; + struct wl_list scaled_themes; // wlr_xcursor_manager_theme::link +}; + +/** + * Creates a new XCursor manager with the given xcursor theme name and base size + * (for use when scale=1). + */ +struct wlr_xcursor_manager *wlr_xcursor_manager_create(const char *name, + uint32_t size); + +void wlr_xcursor_manager_destroy(struct wlr_xcursor_manager *manager); + +/** + * Ensures an xcursor theme at the given scale factor is loaded in the manager. + */ +int wlr_xcursor_manager_load(struct wlr_xcursor_manager *manager, + float scale); + +/** + * Retrieves a wlr_xcursor reference for the given cursor name at the given + * scale factor, or NULL if this wlr_xcursor_manager has not loaded a cursor + * theme at the requested scale. + */ +struct wlr_xcursor *wlr_xcursor_manager_get_xcursor( + struct wlr_xcursor_manager *manager, const char *name, float scale); + +/** + * Set a wlr_cursor's cursor image to the specified cursor name for all scale + * factors. wlr_cursor will take over from this point and ensure the correct + * cursor is used on each output, assuming a wlr_output_layout is attached to + * it. + */ +void wlr_xcursor_manager_set_cursor_image(struct wlr_xcursor_manager *manager, + const char *name, struct wlr_cursor *cursor); + +#endif diff --git a/include/wlr/types/wlr_xdg_decoration_v1.h b/include/wlr/types/wlr_xdg_decoration_v1.h new file mode 100644 index 00000000..ba1ad84b --- /dev/null +++ b/include/wlr/types/wlr_xdg_decoration_v1.h @@ -0,0 +1,69 @@ +#ifndef WLR_TYPES_WLR_XDG_DECORATION_V1 +#define WLR_TYPES_WLR_XDG_DECORATION_V1 + +#include <wayland-server.h> +#include <wlr/types/wlr_xdg_shell.h> + +enum wlr_xdg_toplevel_decoration_v1_mode { + WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE = 0, + WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE = 1, + WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE = 2, +}; + +struct wlr_xdg_decoration_manager_v1 { + struct wl_global *global; + struct wl_list resources; + struct wl_list decorations; // wlr_xdg_toplevel_decoration::link + + struct wl_listener display_destroy; + + struct { + struct wl_signal new_toplevel_decoration; // struct wlr_xdg_toplevel_decoration * + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_xdg_toplevel_decoration_v1_configure { + struct wl_list link; // wlr_xdg_toplevel_decoration::configure_list + struct wlr_xdg_surface_configure *surface_configure; + enum wlr_xdg_toplevel_decoration_v1_mode mode; +}; + +struct wlr_xdg_toplevel_decoration_v1 { + struct wl_resource *resource; + struct wlr_xdg_surface *surface; + struct wlr_xdg_decoration_manager_v1 *manager; + struct wl_list link; // wlr_xdg_decoration_manager_v1::link + + bool added; + enum wlr_xdg_toplevel_decoration_v1_mode current_mode; + enum wlr_xdg_toplevel_decoration_v1_mode client_pending_mode; + enum wlr_xdg_toplevel_decoration_v1_mode server_pending_mode; + + struct wl_list configure_list; // wlr_xdg_toplevel_decoration_v1_configure::link + + struct { + struct wl_signal destroy; + struct wl_signal request_mode; + } events; + + struct wl_listener surface_destroy; + struct wl_listener surface_configure; + struct wl_listener surface_ack_configure; + struct wl_listener surface_commit; + + void *data; +}; + +struct wlr_xdg_decoration_manager_v1 * + wlr_xdg_decoration_manager_v1_create(struct wl_display *display); +void wlr_xdg_decoration_manager_v1_destroy( + struct wlr_xdg_decoration_manager_v1 *manager); + +uint32_t wlr_xdg_toplevel_decoration_v1_set_mode( + struct wlr_xdg_toplevel_decoration_v1 *decoration, + enum wlr_xdg_toplevel_decoration_v1_mode mode); + +#endif diff --git a/include/wlr/types/wlr_xdg_output_v1.h b/include/wlr/types/wlr_xdg_output_v1.h new file mode 100644 index 00000000..d4279fb9 --- /dev/null +++ b/include/wlr/types/wlr_xdg_output_v1.h @@ -0,0 +1,47 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_XDG_OUTPUT_V1_H +#define WLR_TYPES_WLR_XDG_OUTPUT_V1_H +#include <wayland-server.h> +#include <wlr/types/wlr_output_layout.h> + +struct wlr_xdg_output_v1 { + struct wlr_xdg_output_manager_v1 *manager; + struct wl_list resources; + struct wl_list link; + + struct wlr_output_layout_output *layout_output; + + int32_t x, y; + int32_t width, height; + + struct wl_listener destroy; +}; + +struct wlr_xdg_output_manager_v1 { + struct wl_global *global; + struct wl_list resources; + struct wlr_output_layout *layout; + + struct wl_list outputs; + + struct wl_listener layout_add; + struct wl_listener layout_change; + struct wl_listener layout_destroy; + + struct { + struct wl_signal destroy; + } events; +}; + +struct wlr_xdg_output_manager_v1 *wlr_xdg_output_manager_v1_create( + struct wl_display *display, struct wlr_output_layout *layout); +void wlr_xdg_output_manager_v1_destroy(struct wlr_xdg_output_manager_v1 *manager); + +#endif diff --git a/include/wlr/types/wlr_xdg_shell.h b/include/wlr/types/wlr_xdg_shell.h new file mode 100644 index 00000000..1bca9ef3 --- /dev/null +++ b/include/wlr/types/wlr_xdg_shell.h @@ -0,0 +1,389 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_XDG_SHELL_H +#define WLR_TYPES_WLR_XDG_SHELL_H +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_seat.h> +#include <wayland-server.h> +#include "xdg-shell-protocol.h" + +struct wlr_xdg_shell { + struct wl_global *global; + struct wl_list clients; + struct wl_list popup_grabs; + uint32_t ping_timeout; + + struct wl_listener display_destroy; + + struct { + /** + * The `new_surface` event signals that a client has requested to + * create a new shell surface. At this point, the surface is ready to + * be configured but is not mapped or ready receive input events. The + * surface will be ready to be managed on the `map` event. + */ + struct wl_signal new_surface; + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_xdg_client { + struct wlr_xdg_shell *shell; + struct wl_resource *resource; + struct wl_client *client; + struct wl_list surfaces; + + struct wl_list link; // wlr_xdg_shell::clients + + uint32_t ping_serial; + struct wl_event_source *ping_timer; +}; + +struct wlr_xdg_positioner { + struct wl_resource *resource; + + struct wlr_box anchor_rect; + enum xdg_positioner_anchor anchor; + enum xdg_positioner_gravity gravity; + enum xdg_positioner_constraint_adjustment constraint_adjustment; + + struct { + int32_t width, height; + } size; + + struct { + int32_t x, y; + } offset; +}; + +struct wlr_xdg_popup { + struct wlr_xdg_surface *base; + struct wl_list link; + + struct wl_resource *resource; + bool committed; + struct wlr_surface *parent; + struct wlr_seat *seat; + + // Position of the popup relative to the upper left corner of the window + // geometry of the parent surface + struct wlr_box geometry; + + struct wlr_xdg_positioner positioner; + + struct wl_list grab_link; // wlr_xdg_popup_grab::popups +}; + +// each seat gets a popup grab +struct wlr_xdg_popup_grab { + struct wl_client *client; + struct wlr_seat_pointer_grab pointer_grab; + struct wlr_seat_keyboard_grab keyboard_grab; + struct wlr_seat *seat; + struct wl_list popups; + struct wl_list link; // wlr_xdg_shell::popup_grabs + struct wl_listener seat_destroy; +}; + +enum wlr_xdg_surface_role { + WLR_XDG_SURFACE_ROLE_NONE, + WLR_XDG_SURFACE_ROLE_TOPLEVEL, + WLR_XDG_SURFACE_ROLE_POPUP, +}; + +struct wlr_xdg_toplevel_state { + bool maximized, fullscreen, resizing, activated; + uint32_t tiled; // enum wlr_edges + uint32_t width, height; + uint32_t max_width, max_height; + uint32_t min_width, min_height; +}; + +struct wlr_xdg_toplevel { + struct wl_resource *resource; + struct wlr_xdg_surface *base; + struct wlr_xdg_surface *parent; + bool added; + + struct wlr_xdg_toplevel_state client_pending; + struct wlr_xdg_toplevel_state server_pending; + struct wlr_xdg_toplevel_state current; + + char *title; + char *app_id; + + struct { + struct wl_signal request_maximize; + struct wl_signal request_fullscreen; + struct wl_signal request_minimize; + struct wl_signal request_move; + struct wl_signal request_resize; + struct wl_signal request_show_window_menu; + struct wl_signal set_parent; + struct wl_signal set_title; + struct wl_signal set_app_id; + } events; +}; + +struct wlr_xdg_surface_configure { + struct wlr_xdg_surface *surface; + struct wl_list link; // wlr_xdg_surface::configure_list + uint32_t serial; + + struct wlr_xdg_toplevel_state *toplevel_state; +}; + +/** + * An xdg-surface is a user interface element requiring management by the + * compositor. An xdg-surface alone isn't useful, a role should be assigned to + * it in order to map it. + * + * When a surface has a role and is ready to be displayed, the `map` event is + * emitted. When a surface should no longer be displayed, the `unmap` event is + * emitted. The `unmap` event is guaranteed to be emitted before the `destroy` + * event if the view is destroyed when mapped. + */ +struct wlr_xdg_surface { + struct wlr_xdg_client *client; + struct wl_resource *resource; + struct wlr_surface *surface; + struct wl_list link; // wlr_xdg_client::surfaces + enum wlr_xdg_surface_role role; + + union { + struct wlr_xdg_toplevel *toplevel; + struct wlr_xdg_popup *popup; + }; + + struct wl_list popups; // wlr_xdg_popup::link + + bool added, configured, mapped; + uint32_t configure_serial; + struct wl_event_source *configure_idle; + uint32_t configure_next_serial; + struct wl_list configure_list; + + bool has_next_geometry; + struct wlr_box next_geometry; + struct wlr_box geometry; + + struct wl_listener surface_destroy; + struct wl_listener surface_commit; + + struct { + struct wl_signal destroy; + struct wl_signal ping_timeout; + struct wl_signal new_popup; + /** + * The `map` event signals that the shell surface is ready to be + * managed by the compositor and rendered on the screen. At this point, + * the surface has configured its properties, has had the opportunity + * to bind to the seat to receive input events, and has a buffer that + * is ready to be rendered. You can now safely add this surface to a + * list of views. + */ + struct wl_signal map; + /** + * The `unmap` event signals that the surface is no longer in a state + * where it should be shown on the screen. This might happen if the + * surface no longer has a displayable buffer because either the + * surface has been hidden or is about to be destroyed. + */ + struct wl_signal unmap; + + // for protocol extensions + struct wl_signal configure; // wlr_xdg_surface_configure + struct wl_signal ack_configure; // wlr_xdg_surface_configure + } events; + + void *data; +}; + +struct wlr_xdg_toplevel_move_event { + struct wlr_xdg_surface *surface; + struct wlr_seat_client *seat; + uint32_t serial; +}; + +struct wlr_xdg_toplevel_resize_event { + struct wlr_xdg_surface *surface; + struct wlr_seat_client *seat; + uint32_t serial; + uint32_t edges; +}; + +struct wlr_xdg_toplevel_set_fullscreen_event { + struct wlr_xdg_surface *surface; + bool fullscreen; + struct wlr_output *output; +}; + +struct wlr_xdg_toplevel_show_window_menu_event { + struct wlr_xdg_surface *surface; + struct wlr_seat_client *seat; + uint32_t serial; + uint32_t x, y; +}; + +struct wlr_xdg_shell *wlr_xdg_shell_create(struct wl_display *display); +void wlr_xdg_shell_destroy(struct wlr_xdg_shell *xdg_shell); + +struct wlr_xdg_surface *wlr_xdg_surface_from_resource( + struct wl_resource *resource); +struct wlr_xdg_surface *wlr_xdg_surface_from_popup_resource( + struct wl_resource *resource); +struct wlr_xdg_surface *wlr_xdg_surface_from_toplevel_resource( + struct wl_resource *resource); + +struct wlr_box wlr_xdg_positioner_get_geometry( + struct wlr_xdg_positioner *positioner); + +/** + * Send a ping to the surface. If the surface does not respond in a reasonable + * amount of time, the ping_timeout event will be emitted. + */ +void wlr_xdg_surface_ping(struct wlr_xdg_surface *surface); + +/** + * Request that this toplevel surface be the given size. Returns the associated + * configure serial. + */ +uint32_t wlr_xdg_toplevel_set_size(struct wlr_xdg_surface *surface, + uint32_t width, uint32_t height); + +/** + * Request that this toplevel surface show itself in an activated or deactivated + * state. Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_set_activated(struct wlr_xdg_surface *surface, + bool activated); + +/** + * Request that this toplevel surface consider itself maximized or not + * maximized. Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_set_maximized(struct wlr_xdg_surface *surface, + bool maximized); + +/** + * Request that this toplevel surface consider itself fullscreen or not + * fullscreen. Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_set_fullscreen(struct wlr_xdg_surface *surface, + bool fullscreen); + +/** + * Request that this toplevel surface consider itself to be resizing or not + * resizing. Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_set_resizing(struct wlr_xdg_surface *surface, + bool resizing); + +/** + * Request that this toplevel surface consider itself in a tiled layout and some + * edges are adjacent to another part of the tiling grid. `tiled_edges` is a + * bitfield of `enum wlr_edges`. Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_set_tiled(struct wlr_xdg_surface *surface, + uint32_t tiled_edges); + +/** + * Request that this xdg surface closes. + */ +void wlr_xdg_surface_send_close(struct wlr_xdg_surface *surface); + +/** + * Get the geometry for this positioner based on the anchor rect, gravity, and + * size of this positioner. + */ +struct wlr_box wlr_xdg_positioner_get_geometry( + struct wlr_xdg_positioner *positioner); + +/** + * Get the anchor point for this popup in the toplevel parent's coordinate system. + */ +void wlr_xdg_popup_get_anchor_point(struct wlr_xdg_popup *popup, + int *toplevel_sx, int *toplevel_sy); + +/** + * Convert the given coordinates in the popup coordinate system to the toplevel + * surface coordinate system. + */ +void wlr_xdg_popup_get_toplevel_coords(struct wlr_xdg_popup *popup, + int popup_sx, int popup_sy, int *toplevel_sx, int *toplevel_sy); + +/** + * Set the geometry of this popup to unconstrain it according to its + * xdg-positioner rules. The box should be in the popup's root toplevel parent + * surface coordinate system. + */ +void wlr_xdg_popup_unconstrain_from_box(struct wlr_xdg_popup *popup, + struct wlr_box *toplevel_sx_box); + +/** + Invert the right/left anchor and gravity for this positioner. This can be + used to "flip" the positioner around the anchor rect in the x direction. + */ +void wlr_positioner_invert_x(struct wlr_xdg_positioner *positioner); + +/** + Invert the top/bottom anchor and gravity for this positioner. This can be + used to "flip" the positioner around the anchor rect in the y direction. + */ +void wlr_positioner_invert_y(struct wlr_xdg_positioner *positioner); + +/** + * Find a surface within this xdg-surface tree at the given surface-local + * coordinates. Returns the surface and coordinates in the leaf surface + * coordinate system or NULL if no surface is found at that location. + */ +struct wlr_surface *wlr_xdg_surface_surface_at( + struct wlr_xdg_surface *surface, double sx, double sy, + double *sub_x, double *sub_y); + +bool wlr_surface_is_xdg_surface(struct wlr_surface *surface); + +struct wlr_xdg_surface *wlr_xdg_surface_from_wlr_surface( + struct wlr_surface *surface); + +/** + * Get the surface geometry. + * This is either the geometry as set by the client, or defaulted to the bounds + * of the surface + the subsurfaces (as specified by the protocol). + * + * The x and y value can be <0 + */ +void wlr_xdg_surface_get_geometry(struct wlr_xdg_surface *surface, + struct wlr_box *box); + +/** + * Call `iterator` on each surface and popup in the xdg-surface tree, with the + * surface's position relative to the root xdg-surface. The function is called + * from root to leaves (in rendering order). + */ +void wlr_xdg_surface_for_each_surface(struct wlr_xdg_surface *surface, + wlr_surface_iterator_func_t iterator, void *user_data); + +/** + * Schedule a surface configuration. This should only be called by protocols + * extending the shell. + */ +uint32_t wlr_xdg_surface_schedule_configure(struct wlr_xdg_surface *surface); + +/** + * Call `iterator` on each popup in the xdg-surface tree, with the popup's + * position relative to the root xdg-surface. The function is called from root + * to leaves (in rendering order). + */ +void wlr_xdg_surface_for_each_popup(struct wlr_xdg_surface *surface, + wlr_surface_iterator_func_t iterator, void *user_data); + +#endif diff --git a/include/wlr/types/wlr_xdg_shell_v6.h b/include/wlr/types/wlr_xdg_shell_v6.h new file mode 100644 index 00000000..a69e488f --- /dev/null +++ b/include/wlr/types/wlr_xdg_shell_v6.h @@ -0,0 +1,359 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_XDG_SHELL_V6_H +#define WLR_TYPES_WLR_XDG_SHELL_V6_H + +#include <wayland-server.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_seat.h> +#include "xdg-shell-unstable-v6-protocol.h" + +struct wlr_xdg_shell_v6 { + struct wl_global *global; + struct wl_list clients; + struct wl_list popup_grabs; + uint32_t ping_timeout; + + struct wl_listener display_destroy; + + struct { + /** + * The `new_surface` event signals that a client has requested to + * create a new shell surface. At this point, the surface is ready to + * be configured but is not mapped or ready receive input events. The + * surface will be ready to be managed on the `map` event. + */ + struct wl_signal new_surface; + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_xdg_client_v6 { + struct wlr_xdg_shell_v6 *shell; + struct wl_resource *resource; + struct wl_client *client; + struct wl_list surfaces; + + struct wl_list link; // wlr_xdg_shell_v6::clients + + uint32_t ping_serial; + struct wl_event_source *ping_timer; +}; + +struct wlr_xdg_positioner_v6 { + struct wlr_box anchor_rect; + enum zxdg_positioner_v6_anchor anchor; + enum zxdg_positioner_v6_gravity gravity; + enum zxdg_positioner_v6_constraint_adjustment constraint_adjustment; + + struct { + int32_t width, height; + } size; + + struct { + int32_t x, y; + } offset; +}; + +struct wlr_xdg_popup_v6 { + struct wlr_xdg_surface_v6 *base; + struct wl_list link; + + struct wl_resource *resource; + bool committed; + struct wlr_xdg_surface_v6 *parent; + struct wlr_seat *seat; + + // Position of the popup relative to the upper left corner of the window + // geometry of the parent surface + struct wlr_box geometry; + + struct wlr_xdg_positioner_v6 positioner; + + struct wl_list grab_link; // wlr_xdg_popup_grab_v6::popups +}; + +// each seat gets a popup grab +struct wlr_xdg_popup_grab_v6 { + struct wl_client *client; + struct wlr_seat_pointer_grab pointer_grab; + struct wlr_seat_keyboard_grab keyboard_grab; + struct wlr_seat *seat; + struct wl_list popups; + struct wl_list link; // wlr_xdg_shell_v6::popup_grabs + struct wl_listener seat_destroy; +}; + +enum wlr_xdg_surface_v6_role { + WLR_XDG_SURFACE_V6_ROLE_NONE, + WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL, + WLR_XDG_SURFACE_V6_ROLE_POPUP, +}; + +struct wlr_xdg_toplevel_v6_state { + bool maximized, fullscreen, resizing, activated; + uint32_t width, height; + uint32_t max_width, max_height; + uint32_t min_width, min_height; +}; + +/** + * An xdg-surface is a user interface element requiring management by the + * compositor. An xdg-surface alone isn't useful, a role should be assigned to + * it in order to map it. + * + * When a surface has a role and is ready to be displayed, the `map` event is + * emitted. When a surface should no longer be displayed, the `unmap` event is + * emitted. The `unmap` event is guaranteed to be emitted before the `destroy` + * event if the view is destroyed when mapped. + */ +struct wlr_xdg_toplevel_v6 { + struct wl_resource *resource; + struct wlr_xdg_surface_v6 *base; + struct wlr_xdg_surface_v6 *parent; + bool added; + + struct wlr_xdg_toplevel_v6_state client_pending; + struct wlr_xdg_toplevel_v6_state server_pending; + struct wlr_xdg_toplevel_v6_state current; + + char *title; + char *app_id; + + struct { + struct wl_signal request_maximize; + struct wl_signal request_fullscreen; + struct wl_signal request_minimize; + struct wl_signal request_move; + struct wl_signal request_resize; + struct wl_signal request_show_window_menu; + struct wl_signal set_parent; + struct wl_signal set_title; + struct wl_signal set_app_id; + } events; +}; + +struct wlr_xdg_surface_v6_configure { + struct wl_list link; // wlr_xdg_surface_v6::configure_list + uint32_t serial; + + struct wlr_xdg_toplevel_v6_state *toplevel_state; +}; + +struct wlr_xdg_surface_v6 { + struct wlr_xdg_client_v6 *client; + struct wl_resource *resource; + struct wlr_surface *surface; + struct wl_list link; // wlr_xdg_client_v6::surfaces + enum wlr_xdg_surface_v6_role role; + + union { + struct wlr_xdg_toplevel_v6 *toplevel; + struct wlr_xdg_popup_v6 *popup; + }; + + struct wl_list popups; // wlr_xdg_popup_v6::link + + bool added, configured, mapped; + uint32_t configure_serial; + struct wl_event_source *configure_idle; + uint32_t configure_next_serial; + struct wl_list configure_list; + + bool has_next_geometry; + struct wlr_box next_geometry; + struct wlr_box geometry; + + struct wl_listener surface_destroy; + struct wl_listener surface_commit; + + struct { + struct wl_signal destroy; + struct wl_signal ping_timeout; + struct wl_signal new_popup; + /** + * The `map` event signals that the shell surface is ready to be + * managed by the compositor and rendered on the screen. At this point, + * the surface has configured its properties, has had the opportunity + * to bind to the seat to receive input events, and has a buffer that + * is ready to be rendered. You can now safely add this surface to a + * list of views. + */ + struct wl_signal map; + /** + * The `unmap` event signals that the surface is no longer in a state + * where it should be shown on the screen. This might happen if the + * surface no longer has a displayable buffer because either the + * surface has been hidden or is about to be destroyed. + */ + struct wl_signal unmap; + } events; + + void *data; +}; + +struct wlr_xdg_toplevel_v6_move_event { + struct wlr_xdg_surface_v6 *surface; + struct wlr_seat_client *seat; + uint32_t serial; +}; + +struct wlr_xdg_toplevel_v6_resize_event { + struct wlr_xdg_surface_v6 *surface; + struct wlr_seat_client *seat; + uint32_t serial; + uint32_t edges; +}; + +struct wlr_xdg_toplevel_v6_set_fullscreen_event { + struct wlr_xdg_surface_v6 *surface; + bool fullscreen; + struct wlr_output *output; +}; + +struct wlr_xdg_toplevel_v6_show_window_menu_event { + struct wlr_xdg_surface_v6 *surface; + struct wlr_seat_client *seat; + uint32_t serial; + uint32_t x, y; +}; + +struct wlr_xdg_shell_v6 *wlr_xdg_shell_v6_create(struct wl_display *display); +void wlr_xdg_shell_v6_destroy(struct wlr_xdg_shell_v6 *xdg_shell); + +/** + * Send a ping to the surface. If the surface does not respond in a reasonable + * amount of time, the ping_timeout event will be emitted. + */ +void wlr_xdg_surface_v6_ping(struct wlr_xdg_surface_v6 *surface); + +/** + * Request that this toplevel surface be the given size. Returns the associated + * configure serial. + */ +uint32_t wlr_xdg_toplevel_v6_set_size(struct wlr_xdg_surface_v6 *surface, + uint32_t width, uint32_t height); + +/** + * Request that this toplevel surface show itself in an activated or deactivated + * state. Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_v6_set_activated(struct wlr_xdg_surface_v6 *surface, + bool activated); + +/** + * Request that this toplevel surface consider itself maximized or not + * maximized. Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_v6_set_maximized(struct wlr_xdg_surface_v6 *surface, + bool maximized); + +/** + * Request that this toplevel surface consider itself fullscreen or not + * fullscreen. Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_v6_set_fullscreen(struct wlr_xdg_surface_v6 *surface, + bool fullscreen); + +/** + * Request that this toplevel surface consider itself to be resizing or not + * resizing. Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_v6_set_resizing(struct wlr_xdg_surface_v6 *surface, + bool resizing); + +/** + * Request that this xdg surface closes. + */ +void wlr_xdg_surface_v6_send_close(struct wlr_xdg_surface_v6 *surface); + +/** + * Find a surface within this xdg-surface tree at the given surface-local + * coordinates. Returns the surface and coordinates in the leaf surface + * coordinate system or NULL if no surface is found at that location. + */ +struct wlr_surface *wlr_xdg_surface_v6_surface_at( + struct wlr_xdg_surface_v6 *surface, double sx, double sy, + double *sub_x, double *sub_y); + +/** + * Get the geometry for this positioner based on the anchor rect, gravity, and + * size of this positioner. + */ +struct wlr_box wlr_xdg_positioner_v6_get_geometry( + struct wlr_xdg_positioner_v6 *positioner); + +/** + * Get the anchor point for this popup in the toplevel parent's coordinate system. + */ +void wlr_xdg_popup_v6_get_anchor_point(struct wlr_xdg_popup_v6 *popup, + int *toplevel_sx, int *toplevel_sy); + +/** + * Convert the given coordinates in the popup coordinate system to the toplevel + * surface coordinate system. + */ +void wlr_xdg_popup_v6_get_toplevel_coords(struct wlr_xdg_popup_v6 *popup, + int popup_sx, int popup_sy, int *toplevel_sx, int *toplevel_sy); + +/** + * Set the geometry of this popup to unconstrain it according to its + * xdg-positioner rules. The box should be in the popup's root toplevel parent + * surface coordinate system. + */ +void wlr_xdg_popup_v6_unconstrain_from_box(struct wlr_xdg_popup_v6 *popup, + struct wlr_box *toplevel_sx_box); + +/** + Invert the right/left anchor and gravity for this positioner. This can be + used to "flip" the positioner around the anchor rect in the x direction. + */ +void wlr_positioner_v6_invert_x( + struct wlr_xdg_positioner_v6 *positioner); + +/** + Invert the top/bottom anchor and gravity for this positioner. This can be + used to "flip" the positioner around the anchor rect in the y direction. + */ +void wlr_positioner_v6_invert_y( + struct wlr_xdg_positioner_v6 *positioner); + +bool wlr_surface_is_xdg_surface_v6(struct wlr_surface *surface); + +struct wlr_xdg_surface_v6 *wlr_xdg_surface_v6_from_wlr_surface( + struct wlr_surface *surface); + +/** + * Get the surface geometry. + * This is either the geometry as set by the client, or defaulted to the bounds + * of the surface + the subsurfaces (as specified by the protocol). + * + * The x and y value can be <0 + */ +void wlr_xdg_surface_v6_get_geometry(struct wlr_xdg_surface_v6 *surface, struct wlr_box *box); + +/** + * Call `iterator` on each surface and popup in the xdg-surface tree, with the + * surface's position relative to the root xdg-surface. The function is called + * from root to leaves (in rendering order). + */ +void wlr_xdg_surface_v6_for_each_surface(struct wlr_xdg_surface_v6 *surface, + wlr_surface_iterator_func_t iterator, void *user_data); + +/** + * Call `iterator` on each popup in the xdg-surface tree, with the popup's + * position relative to the root xdg-surface. The function is called from root + * to leaves (in rendering order). + */ +void wlr_xdg_surface_v6_for_each_popup(struct wlr_xdg_surface_v6 *surface, + wlr_surface_iterator_func_t iterator, void *user_data); + +#endif diff --git a/include/wlr/util/edges.h b/include/wlr/util/edges.h new file mode 100644 index 00000000..bf1eb1e7 --- /dev/null +++ b/include/wlr/util/edges.h @@ -0,0 +1,28 @@ +/* + * This is a stable interface of wlroots. Future changes will be limited to: + * + * - New functions + * - New struct members + * - New enum members + * + * Note that wlroots does not make an ABI compatibility promise - in the future, + * the layout and size of structs used by wlroots may change, requiring code + * depending on this header to be recompiled (but not edited). + * + * Breaking changes are announced by email and follow a 1-year deprecation + * schedule. Send an email to ~sircmpwn/wlroots-announce+subscribe@lists.sr.ht + * to receive these announcements. + */ + +#ifndef WLR_UTIL_EDGES_H +#define WLR_UTIL_EDGES_H + +enum wlr_edges { + WLR_EDGE_NONE = 0, + WLR_EDGE_TOP = 1, + WLR_EDGE_BOTTOM = 2, + WLR_EDGE_LEFT = 4, + WLR_EDGE_RIGHT = 8, +}; + +#endif diff --git a/include/wlr/util/log.h b/include/wlr/util/log.h new file mode 100644 index 00000000..2c441180 --- /dev/null +++ b/include/wlr/util/log.h @@ -0,0 +1,64 @@ +/* + * This is a stable interface of wlroots. Future changes will be limited to: + * + * - New functions + * - New struct members + * - New enum members + * + * Note that wlroots does not make an ABI compatibility promise - in the future, + * the layout and size of structs used by wlroots may change, requiring code + * depending on this header to be recompiled (but not edited). + * + * Breaking changes are announced by email and follow a 1-year deprecation + * schedule. Send an email to ~sircmpwn/wlroots-announce+subscribe@lists.sr.ht + * to receive these announcements. + */ + +#ifndef WLR_UTIL_LOG_H +#define WLR_UTIL_LOG_H + +#include <stdbool.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> + +enum wlr_log_importance { + WLR_SILENT = 0, + WLR_ERROR = 1, + WLR_INFO = 2, + WLR_DEBUG = 3, + WLR_LOG_IMPORTANCE_LAST, +}; + +typedef void (*wlr_log_func_t)(enum wlr_log_importance importance, + const char *fmt, va_list args); + +// Will log all messages less than or equal to `verbosity` +// If `callback` is NULL, wlr will use its default logger. +// The function can be called multiple times to update the verbosity or +// callback function. +void wlr_log_init(enum wlr_log_importance verbosity, wlr_log_func_t callback); + +// Returns the log verbosity provided to wlr_log_init +enum wlr_log_importance wlr_log_get_verbosity(void); + +#ifdef __GNUC__ +#define _WLR_ATTRIB_PRINTF(start, end) __attribute__((format(printf, start, end))) +#else +#define _WLR_ATTRIB_PRINTF(start, end) +#endif + +void _wlr_log(enum wlr_log_importance verbosity, const char *format, ...) _WLR_ATTRIB_PRINTF(2, 3); +void _wlr_vlog(enum wlr_log_importance verbosity, const char *format, va_list args) _WLR_ATTRIB_PRINTF(2, 0); +const char *_wlr_strip_path(const char *filepath); + +#define wlr_log(verb, fmt, ...) \ + _wlr_log(verb, "[%s:%d] " fmt, _wlr_strip_path(__FILE__), __LINE__, ##__VA_ARGS__) + +#define wlr_vlog(verb, fmt, args) \ + _wlr_vlog(verb, "[%s:%d] " fmt, _wlr_strip_path(__FILE__), __LINE__, args) + +#define wlr_log_errno(verb, fmt, ...) \ + wlr_log(verb, fmt ": %s", ##__VA_ARGS__, strerror(errno)) + +#endif diff --git a/include/wlr/util/meson.build b/include/wlr/util/meson.build new file mode 100644 index 00000000..ee72cbd6 --- /dev/null +++ b/include/wlr/util/meson.build @@ -0,0 +1,6 @@ +install_headers( + 'edges.h', + 'log.h', + 'region.h', + subdir: 'wlr/util', +) diff --git a/include/wlr/util/region.h b/include/wlr/util/region.h new file mode 100644 index 00000000..4aca07e1 --- /dev/null +++ b/include/wlr/util/region.h @@ -0,0 +1,56 @@ +/* + * This is a stable interface of wlroots. Future changes will be limited to: + * + * - New functions + * - New struct members + * - New enum members + * + * Note that wlroots does not make an ABI compatibility promise - in the future, + * the layout and size of structs used by wlroots may change, requiring code + * depending on this header to be recompiled (but not edited). + * + * Breaking changes are announced by email and follow a 1-year deprecation + * schedule. Send an email to ~sircmpwn/wlroots-announce+subscribe@lists.sr.ht + * to receive these announcements. + */ + +#ifndef WLR_UTIL_REGION_H +#define WLR_UTIL_REGION_H + +#include <stdbool.h> +#include <pixman.h> +#include <wayland-server.h> + +/** + * Scales a region, ie. multiplies all its coordinates by `scale`. + * + * The resulting coordinates are rounded up or down so that the new region is + * at least as big as the original one. + */ +void wlr_region_scale(pixman_region32_t *dst, pixman_region32_t *src, + float scale); + +/** + * Applies a transform to a region inside a box of size `width` x `height`. + */ +void wlr_region_transform(pixman_region32_t *dst, pixman_region32_t *src, + enum wl_output_transform transform, int width, int height); + +/** + * Expands the region of `distance`. If `distance` is negative, it shrinks the + * region. + */ +void wlr_region_expand(pixman_region32_t *dst, pixman_region32_t *src, + int distance); + +/* + * Builds the smallest possible region that contains the region rotated about + * the point (ox, oy). + */ +void wlr_region_rotated_bounds(pixman_region32_t *dst, pixman_region32_t *src, + float rotation, int ox, int oy); + +bool wlr_region_confine(pixman_region32_t *region, double x1, double y1, double x2, + double y2, double *x2_out, double *y2_out); + +#endif diff --git a/include/wlr/version.h.in b/include/wlr/version.h.in new file mode 100644 index 00000000..cdc0fd75 --- /dev/null +++ b/include/wlr/version.h.in @@ -0,0 +1,16 @@ +#ifndef WLR_VERSION_H +#define WLR_VERSION_H + +#mesondefine WLR_VERSION_STR + +#mesondefine WLR_VERSION_MAJOR +#mesondefine WLR_VERSION_MINOR +#mesondefine WLR_VERSION_MICRO + +#define WLR_VERSION_NUM ((WLR_VERSION_MAJOR << 16) | (WLR_VERSION_MINOR << 8) | WLR_VERSION_MICRO) + +#mesondefine WLR_VERSION_API_CURRENT +#mesondefine WLR_VERSION_API_REVISION +#mesondefine WLR_VERSION_API_AGE + +#endif diff --git a/include/wlr/xcursor.h b/include/wlr/xcursor.h new file mode 100644 index 00000000..39874f39 --- /dev/null +++ b/include/wlr/xcursor.h @@ -0,0 +1,102 @@ +/* + * Copyright © 2012 Intel Corporation + * + * 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 (including the + * next paragraph) 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. + */ + +/* + * This is a stable interface of wlroots. Future changes will be limited to: + * + * - New functions + * - New struct members + * - New enum members + * + * Note that wlroots does not make an ABI compatibility promise - in the future, + * the layout and size of structs used by wlroots may change, requiring code + * depending on this header to be recompiled (but not edited). + * + * Breaking changes are announced by email and follow a 1-year deprecation + * schedule. Send an email to ~sircmpwn/wlroots-announce+subscribe@lists.sr.ht + * to receive these announcements. + */ + +#ifndef WLR_XCURSOR_H +#define WLR_XCURSOR_H + +#include <stdint.h> +#include <wlr/util/edges.h> + +struct wlr_xcursor_image { + uint32_t width; /* actual width */ + uint32_t height; /* actual height */ + uint32_t hotspot_x; /* hot spot x (must be inside image) */ + uint32_t hotspot_y; /* hot spot y (must be inside image) */ + uint32_t delay; /* animation delay to next frame (ms) */ + uint8_t *buffer; +}; + +struct wlr_xcursor { + unsigned int image_count; + struct wlr_xcursor_image **images; + char *name; + uint32_t total_delay; /* length of the animation in ms */ +}; + +/** + * Container for an Xcursor theme. + */ +struct wlr_xcursor_theme { + unsigned int cursor_count; + struct wlr_xcursor **cursors; + char *name; + int size; +}; + +/** + * Loads the named xcursor theme at the given cursor size (in pixels). This is + * useful if you need cursor images for your compositor to use when a + * client-side cursors is not available or you wish to override client-side + * cursors for a particular UI interaction (such as using a grab cursor when + * moving a window around). + */ +struct wlr_xcursor_theme *wlr_xcursor_theme_load(const char *name, int size); + +void wlr_xcursor_theme_destroy(struct wlr_xcursor_theme *theme); + +/** + * Obtains a wlr_xcursor image for the specified cursor name (e.g. "left_ptr"). + */ +struct wlr_xcursor *wlr_xcursor_theme_get_cursor( + struct wlr_xcursor_theme *theme, const char *name); + +/** + * Returns the current frame number for an animated cursor give a monotonic time + * reference. + */ +int wlr_xcursor_frame(struct wlr_xcursor *cursor, uint32_t time); + +/** + * Get the name of the resize cursor image for the given edges. + */ +const char *wlr_xcursor_get_resize_name(enum wlr_edges edges); + +#endif diff --git a/include/wlr/xwayland.h b/include/wlr/xwayland.h new file mode 100644 index 00000000..40cc8848 --- /dev/null +++ b/include/wlr/xwayland.h @@ -0,0 +1,260 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_XWAYLAND_H +#define WLR_XWAYLAND_H + +#include <stdbool.h> +#include <time.h> +#include <wlr/config.h> +#include <wlr/types/wlr_compositor.h> +#include <wlr/types/wlr_seat.h> +#include <xcb/xcb.h> + +struct wlr_xwm; +struct wlr_xwayland_cursor; +struct wlr_gtk_primary_selection_device_manager; + +struct wlr_xwayland { + pid_t pid; + struct wl_client *client; + struct wl_event_source *sigusr1_source; + struct wl_listener client_destroy; + struct wlr_xwm *xwm; + struct wlr_xwayland_cursor *cursor; + int wm_fd[2], wl_fd[2]; + + time_t server_start; + + /* Anything above display is reset on Xwayland restart, rest is conserved */ + + int display; + int x_fd[2]; + struct wl_event_source *x_fd_read_event[2]; + struct wl_listener display_destroy; + + bool lazy; + + struct wl_display *wl_display; + struct wlr_compositor *compositor; + struct wlr_seat *seat; + + struct { + struct wl_signal ready; + struct wl_signal new_surface; + } events; + + struct wl_listener seat_destroy; + + /** + * Add a custom event handler to xwayland. Return 1 if the event was + * handled or 0 to use the default wlr-xwayland handler. wlr-xwayland will + * free the event. + */ + int (*user_event_handler)(struct wlr_xwm *xwm, xcb_generic_event_t *event); + + void *data; +}; + +enum wlr_xwayland_surface_decorations { + WLR_XWAYLAND_SURFACE_DECORATIONS_ALL = 0, + WLR_XWAYLAND_SURFACE_DECORATIONS_NO_BORDER = 1, + WLR_XWAYLAND_SURFACE_DECORATIONS_NO_TITLE = 2, +}; + +struct wlr_xwayland_surface_hints { + uint32_t flags; + uint32_t input; + int32_t initial_state; + xcb_pixmap_t icon_pixmap; + xcb_window_t icon_window; + int32_t icon_x, icon_y; + xcb_pixmap_t icon_mask; + xcb_window_t window_group; +}; + +struct wlr_xwayland_surface_size_hints { + uint32_t flags; + int32_t x, y; + int32_t width, height; + int32_t min_width, min_height; + int32_t max_width, max_height; + int32_t width_inc, height_inc; + int32_t base_width, base_height; + int32_t min_aspect_num, min_aspect_den; + int32_t max_aspect_num, max_aspect_den; + uint32_t win_gravity; +}; + +/** + * An Xwayland user interface component. It has an absolute position in + * layout-local coordinates. + * + * When a surface is ready to be displayed, the `map` event is emitted. When a + * surface should no longer be displayed, the `unmap` event is emitted. The + * `unmap` event is guaranteed to be emitted before the `destroy` event if the + * view is destroyed when mapped. + */ +struct wlr_xwayland_surface { + xcb_window_t window_id; + struct wlr_xwm *xwm; + uint32_t surface_id; + + struct wl_list link; + struct wl_list unpaired_link; + + struct wlr_surface *surface; + int16_t x, y; + uint16_t width, height; + uint16_t saved_width, saved_height; + bool override_redirect; + bool mapped; + + char *title; + char *class; + char *instance; + char *role; + pid_t pid; + bool has_utf8_title; + + struct wl_list children; // wlr_xwayland_surface::parent_link + struct wlr_xwayland_surface *parent; + struct wl_list parent_link; // wlr_xwayland_surface::children + + xcb_atom_t *window_type; + size_t window_type_len; + + xcb_atom_t *protocols; + size_t protocols_len; + + uint32_t decorations; + struct wlr_xwayland_surface_hints *hints; + uint32_t hints_urgency; + struct wlr_xwayland_surface_size_hints *size_hints; + + bool pinging; + struct wl_event_source *ping_timer; + + // _NET_WM_STATE + bool modal; + bool fullscreen; + bool maximized_vert, maximized_horz; + + bool has_alpha; + + struct { + struct wl_signal destroy; + struct wl_signal request_configure; + struct wl_signal request_move; + struct wl_signal request_resize; + struct wl_signal request_maximize; + struct wl_signal request_fullscreen; + struct wl_signal request_activate; + + struct wl_signal map; + struct wl_signal unmap; + struct wl_signal set_title; + struct wl_signal set_class; + struct wl_signal set_role; + struct wl_signal set_parent; + struct wl_signal set_pid; + struct wl_signal set_window_type; + struct wl_signal set_hints; + struct wl_signal set_decorations; + struct wl_signal set_override_redirect; + struct wl_signal ping_timeout; + } events; + + struct wl_listener surface_destroy; + + void *data; +}; + +struct wlr_xwayland_surface_configure_event { + struct wlr_xwayland_surface *surface; + int16_t x, y; + uint16_t width, height; +}; + +// TODO: maybe add a seat to these +struct wlr_xwayland_move_event { + struct wlr_xwayland_surface *surface; +}; + +struct wlr_xwayland_resize_event { + struct wlr_xwayland_surface *surface; + uint32_t edges; +}; + +/** Create an Xwayland server. + * + * The server supports a lazy mode in which Xwayland is only started when a + * client tries to connect. + * + * Note: wlr_xwayland will setup a global SIGUSR1 handler on the compositor + * process. + */ +struct wlr_xwayland *wlr_xwayland_create(struct wl_display *wl_display, + struct wlr_compositor *compositor, bool lazy); + +void wlr_xwayland_destroy(struct wlr_xwayland *wlr_xwayland); + +void wlr_xwayland_set_cursor(struct wlr_xwayland *wlr_xwayland, + uint8_t *pixels, uint32_t stride, uint32_t width, uint32_t height, + int32_t hotspot_x, int32_t hotspot_y); + +void wlr_xwayland_surface_activate(struct wlr_xwayland_surface *surface, + bool activated); + +void wlr_xwayland_surface_configure(struct wlr_xwayland_surface *surface, + int16_t x, int16_t y, uint16_t width, uint16_t height); + +void wlr_xwayland_surface_close(struct wlr_xwayland_surface *surface); + +void wlr_xwayland_surface_set_maximized(struct wlr_xwayland_surface *surface, + bool maximized); + +void wlr_xwayland_surface_set_fullscreen(struct wlr_xwayland_surface *surface, + bool fullscreen); + +void wlr_xwayland_set_seat(struct wlr_xwayland *xwayland, + struct wlr_seat *seat); + +bool wlr_surface_is_xwayland_surface(struct wlr_surface *surface); + +struct wlr_xwayland_surface *wlr_xwayland_surface_from_wlr_surface( + struct wlr_surface *surface); + +void wlr_xwayland_surface_ping(struct wlr_xwayland_surface *surface); + +/** Metric to guess if an OR window should "receive" focus + * + * In the pure X setups, window managers usually straight up ignore override + * redirect windows, and never touch them. (we have to handle them for mapping) + * + * When such a window wants to receive keyboard input (e.g. rofi/dzen) it will + * use mechanics we don't support (sniffing/grabbing input). + * [Sadly this is unrelated to xwayland-keyboard-grab] + * + * To still support these windows, while keeping general OR semantics as is, we + * need to hand a subset of windows focus. + * The dirty truth is, we need to hand focus to any Xwayland window, though + * pretending this window has focus makes it easier to handle unmap. + * + * This function provides a handy metric based on the window type to guess if + * the OR window wants focus. + * It's probably not perfect, nor exactly intended but works in practice. + * + * Returns: true if the window should receive focus + * false if it should be ignored + */ +bool wlr_xwayland_or_surface_wants_focus( + const struct wlr_xwayland_surface *surface); + + +#endif diff --git a/include/xcursor/cursor_data.h b/include/xcursor/cursor_data.h new file mode 100644 index 00000000..dd7a80af --- /dev/null +++ b/include/xcursor/cursor_data.h @@ -0,0 +1,554 @@ +/* +* Copyright 1999 SuSE, Inc. +* +* 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 (including the +* next paragraph) 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. +* +* Author: Keith Packard, SuSE, Inc. +*/ + +#include <stdint.h> + +static uint32_t cursor_data[] = { + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0xff000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0x00000000, + 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, + 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0xff000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xff000000, + 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, + 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, + 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0xffffffff, 0xff000000, 0xffffffff, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0x00000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0xff000000, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, 0xff000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, + 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, + 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, + 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, +}; + +static struct cursor_metadata { + char *name; + int width, height; + int hotspot_x, hotspot_y; + size_t offset; +} cursor_metadata[] = { + { "bottom_left_corner", 16, 16, 1, 14, 0 }, + { "bottom_right_corner", 16, 16, 14, 14, 256 }, + { "bottom_side", 15, 16, 7, 14, 512 }, + { "grabbing", 16, 16, 8, 8, 752 }, + { "left_ptr", 10, 16, 1, 1, 1008 }, + { "left_side", 16, 15, 1, 7, 1168 }, + { "right_side", 16, 15, 14, 7, 1408 }, + { "top_left_corner", 16, 16, 1, 1, 1648 }, + { "top_right_corner", 16, 16, 14, 1, 1904 }, + { "top_side", 15, 16, 7, 1, 2160 }, + { "xterm", 9, 16, 4, 8, 2400 }, + { "hand1", 13, 16, 12, 0, 2544 }, + { "watch", 16, 16, 15, 9, 2752 }, +}; diff --git a/include/xcursor/xcursor.h b/include/xcursor/xcursor.h new file mode 100644 index 00000000..62e23220 --- /dev/null +++ b/include/xcursor/xcursor.h @@ -0,0 +1,65 @@ +/* + * Copyright © 2002 Keith Packard + * + * 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 (including the + * next paragraph) 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. + */ + +#ifndef XCURSOR_H +#define XCURSOR_H + +typedef int XcursorBool; +typedef unsigned int XcursorUInt; + +typedef XcursorUInt XcursorDim; +typedef XcursorUInt XcursorPixel; + +typedef struct _XcursorImage { + XcursorUInt version; /* version of the image data */ + XcursorDim size; /* nominal size for matching */ + XcursorDim width; /* actual width */ + XcursorDim height; /* actual height */ + XcursorDim xhot; /* hot spot x (must be inside image) */ + XcursorDim yhot; /* hot spot y (must be inside image) */ + XcursorUInt delay; /* animation delay to next frame (ms) */ + XcursorPixel *pixels; /* pointer to pixels */ +} XcursorImage; + +/* + * Other data structures exposed by the library API + */ +typedef struct _XcursorImages { + int nimage; /* number of images */ + XcursorImage **images; /* array of XcursorImage pointers */ + char *name; /* name used to load images */ +} XcursorImages; + +XcursorImages * +XcursorLibraryLoadImages (const char *file, const char *theme, int size); + +void +XcursorImagesDestroy (XcursorImages *images); + +void +xcursor_load_theme(const char *theme, int size, + void (*load_callback)(XcursorImages *, void *), + void *user_data); +#endif diff --git a/include/xwayland/selection.h b/include/xwayland/selection.h new file mode 100644 index 00000000..85201461 --- /dev/null +++ b/include/xwayland/selection.h @@ -0,0 +1,74 @@ +#ifndef XWAYLAND_SELECTION_H +#define XWAYLAND_SELECTION_H + +#include <xcb/xfixes.h> + +#define INCR_CHUNK_SIZE (64 * 1024) + +#define XDND_VERSION 5 + +struct wlr_primary_selection_source; + +struct wlr_xwm_selection; + +struct wlr_xwm_selection_transfer { + struct wlr_xwm_selection *selection; + + bool incr; + bool flush_property_on_delete; + bool property_set; + struct wl_array source_data; + int source_fd; + struct wl_event_source *source; + + // when sending to x11 + xcb_selection_request_event_t request; + struct wl_list outgoing_link; + + // when receiving from x11 + int property_start; + xcb_get_property_reply_t *property_reply; +}; + +struct wlr_xwm_selection { + struct wlr_xwm *xwm; + xcb_atom_t atom; + xcb_window_t window; + xcb_window_t owner; + xcb_timestamp_t timestamp; + + struct wlr_xwm_selection_transfer incoming; + struct wl_list outgoing; +}; + +void xwm_selection_transfer_remove_source( + struct wlr_xwm_selection_transfer *transfer); +void xwm_selection_transfer_close_source_fd( + struct wlr_xwm_selection_transfer *transfer); +void xwm_selection_transfer_destroy_property_reply( + struct wlr_xwm_selection_transfer *transfer); + +xcb_atom_t xwm_mime_type_to_atom(struct wlr_xwm *xwm, char *mime_type); +char *xwm_mime_type_from_atom(struct wlr_xwm *xwm, xcb_atom_t atom); +struct wlr_xwm_selection *xwm_get_selection(struct wlr_xwm *xwm, + xcb_atom_t selection_atom); + +void xwm_send_incr_chunk(struct wlr_xwm_selection_transfer *transfer); +void xwm_handle_selection_request(struct wlr_xwm *xwm, + xcb_selection_request_event_t *req); + +void xwm_get_incr_chunk(struct wlr_xwm_selection_transfer *transfer); +void xwm_handle_selection_notify(struct wlr_xwm *xwm, + xcb_selection_notify_event_t *event); +int xwm_handle_xfixes_selection_notify(struct wlr_xwm *xwm, + xcb_xfixes_selection_notify_event_t *event); +bool data_source_is_xwayland(struct wlr_data_source *wlr_source); +bool primary_selection_source_is_xwayland( + struct wlr_primary_selection_source *wlr_source); + +void xwm_seat_handle_start_drag(struct wlr_xwm *xwm, struct wlr_drag *drag); + +void xwm_selection_init(struct wlr_xwm *xwm); +void xwm_selection_finish(struct wlr_xwm *xwm); + +#endif diff --git a/include/xwayland/xwm.h b/include/xwayland/xwm.h new file mode 100644 index 00000000..c1be572b --- /dev/null +++ b/include/xwayland/xwm.h @@ -0,0 +1,158 @@ +#ifndef XWAYLAND_XWM_H +#define XWAYLAND_XWM_H + +#include <wayland-server-core.h> +#include <wlr/config.h> +#include <wlr/xwayland.h> +#include <xcb/render.h> +#if WLR_HAS_XCB_ICCCM +#include <xcb/xcb_icccm.h> +#endif +#if WLR_HAS_XCB_ERRORS +#include <xcb/xcb_errors.h> +#endif +#include "xwayland/selection.h" + +/* This is in xcb/xcb_event.h, but pulling xcb-util just for a constant + * others redefine anyway is meh + */ +#define XCB_EVENT_RESPONSE_TYPE_MASK (0x7f) + +enum atom_name { + WL_SURFACE_ID, + WM_DELETE_WINDOW, + WM_PROTOCOLS, + WM_HINTS, + WM_NORMAL_HINTS, + WM_SIZE_HINTS, + WM_WINDOW_ROLE, + MOTIF_WM_HINTS, + UTF8_STRING, + WM_S0, + NET_SUPPORTED, + NET_WM_CM_S0, + NET_WM_PID, + NET_WM_NAME, + NET_WM_STATE, + NET_WM_WINDOW_TYPE, + WM_TAKE_FOCUS, + WINDOW, + _NET_ACTIVE_WINDOW, + _NET_WM_MOVERESIZE, + _NET_WM_NAME, + _NET_SUPPORTING_WM_CHECK, + _NET_WM_STATE_MODAL, + _NET_WM_STATE_FULLSCREEN, + _NET_WM_STATE_MAXIMIZED_VERT, + _NET_WM_STATE_MAXIMIZED_HORZ, + _NET_WM_PING, + WM_STATE, + CLIPBOARD, + PRIMARY, + WL_SELECTION, + TARGETS, + CLIPBOARD_MANAGER, + INCR, + TEXT, + TIMESTAMP, + DELETE, + NET_WM_WINDOW_TYPE_NORMAL, + NET_WM_WINDOW_TYPE_UTILITY, + NET_WM_WINDOW_TYPE_TOOLTIP, + NET_WM_WINDOW_TYPE_DND, + NET_WM_WINDOW_TYPE_DROPDOWN_MENU, + NET_WM_WINDOW_TYPE_POPUP_MENU, + NET_WM_WINDOW_TYPE_COMBO, + NET_WM_WINDOW_TYPE_MENU, + NET_WM_WINDOW_TYPE_NOTIFICATION, + NET_WM_WINDOW_TYPE_SPLASH, + DND_SELECTION, + DND_AWARE, + DND_STATUS, + DND_POSITION, + DND_ENTER, + DND_LEAVE, + DND_DROP, + DND_FINISHED, + DND_PROXY, + DND_TYPE_LIST, + DND_ACTION_MOVE, + DND_ACTION_COPY, + DND_ACTION_ASK, + DND_ACTION_PRIVATE, + ATOM_LAST, +}; + +extern const char *atom_map[ATOM_LAST]; + +enum net_wm_state_action { + NET_WM_STATE_REMOVE = 0, + NET_WM_STATE_ADD = 1, + NET_WM_STATE_TOGGLE = 2, +}; + +struct wlr_xwm { + struct wlr_xwayland *xwayland; + struct wl_event_source *event_source; + struct wlr_seat *seat; + uint32_t ping_timeout; + + xcb_atom_t atoms[ATOM_LAST]; + xcb_connection_t *xcb_conn; + xcb_screen_t *screen; + xcb_window_t window; + xcb_visualid_t visual_id; + xcb_colormap_t colormap; + xcb_render_pictformat_t render_format_id; + xcb_cursor_t cursor; + + xcb_window_t selection_window; + struct wlr_xwm_selection clipboard_selection; + struct wlr_xwm_selection primary_selection; + + xcb_window_t dnd_window; + struct wlr_xwm_selection dnd_selection; + + struct wlr_xwayland_surface *focus_surface; + + struct wl_list surfaces; // wlr_xwayland_surface::link + struct wl_list unpaired_surfaces; // wlr_xwayland_surface::unpaired_link + + struct wlr_drag *drag; + struct wlr_xwayland_surface *drag_focus; + + const xcb_query_extension_reply_t *xfixes; +#if WLR_HAS_XCB_ERRORS + xcb_errors_context_t *errors_context; +#endif + + struct wl_listener compositor_new_surface; + struct wl_listener compositor_destroy; + struct wl_listener seat_selection; + struct wl_listener seat_primary_selection; + struct wl_listener seat_start_drag; + struct wl_listener seat_drag_focus; + struct wl_listener seat_drag_motion; + struct wl_listener seat_drag_drop; + struct wl_listener seat_drag_destroy; + struct wl_listener seat_drag_source_destroy; +}; + +struct wlr_xwm *xwm_create(struct wlr_xwayland *wlr_xwayland); + +void xwm_destroy(struct wlr_xwm *xwm); + +void xwm_set_cursor(struct wlr_xwm *xwm, const uint8_t *pixels, uint32_t stride, + uint32_t width, uint32_t height, int32_t hotspot_x, int32_t hotspot_y); + +int xwm_handle_selection_event(struct wlr_xwm *xwm, xcb_generic_event_t *event); +int xwm_handle_selection_client_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *ev); + +void xwm_set_seat(struct wlr_xwm *xwm, struct wlr_seat *seat); + +char *xwm_get_atom_name(struct wlr_xwm *xwm, xcb_atom_t atom); +bool xwm_atoms_contains(struct wlr_xwm *xwm, xcb_atom_t *atoms, + size_t num_atoms, enum atom_name needle); + +#endif diff --git a/meson.build b/meson.build new file mode 100644 index 00000000..df1dbb84 --- /dev/null +++ b/meson.build @@ -0,0 +1,205 @@ +project( + 'wlroots', + 'c', + version: '0.2.0', + license: 'MIT', + meson_version: '>=0.48.0', + default_options: [ + 'c_std=c11', + 'warning_level=2', + 'werror=true', + ], +) + +# Format of so_version is CURRENT, REVISION, AGE. +# See: https://autotools.io/libtool/version.html +# for a reference about clean library versioning. +so_version = ['0', '0', '0'] + +add_project_arguments( + [ + '-DWLR_SRC_DIR="@0@"'.format(meson.current_source_dir()), + '-DWLR_USE_UNSTABLE', + + '-Wno-unused-parameter', + '-Wundef', + ], + language: 'c', +) + +conf_data = configuration_data() +conf_data.set10('WLR_HAS_LIBCAP', false) +conf_data.set10('WLR_HAS_SYSTEMD', false) +conf_data.set10('WLR_HAS_ELOGIND', false) +conf_data.set10('WLR_HAS_X11_BACKEND', false) +conf_data.set10('WLR_HAS_XWAYLAND', false) +conf_data.set10('WLR_HAS_XCB_ERRORS', false) +conf_data.set10('WLR_HAS_XCB_ICCCM', false) + +wlr_inc = include_directories('.', 'include') + +cc = meson.get_compiler('c') + +# Clang complains about some zeroed initializer lists (= {0}), even though they +# are valid +if cc.get_id() == 'clang' + add_project_arguments('-Wno-missing-field-initializers', language: 'c') + add_project_arguments('-Wno-missing-braces', language: 'c') +endif + +# Avoid wl_buffer deprecation warnings +add_project_arguments('-DWL_HIDE_DEPRECATED', language: 'c') + +wayland_server = dependency('wayland-server', version: '>=1.16') +wayland_client = dependency('wayland-client') +wayland_egl = dependency('wayland-egl') +wayland_protos = dependency('wayland-protocols', version: '>=1.15') +egl = dependency('egl') +glesv2 = dependency('glesv2') +drm = dependency('libdrm') +gbm = dependency('gbm', version: '>=17.1.0') +libinput = dependency('libinput', version: '>=1.7.0') +xkbcommon = dependency('xkbcommon') +udev = dependency('libudev') +pixman = dependency('pixman-1') +libcap = dependency('libcap', required: get_option('libcap')) +logind = dependency('lib' + get_option('logind-provider'), required: get_option('logind'), version: '>=237') +math = cc.find_library('m') +rt = cc.find_library('rt') + +wlr_parts = [] +wlr_deps = [] + +if libcap.found() + conf_data.set10('WLR_HAS_LIBCAP', true) + wlr_deps += libcap +endif + +if logind.found() + conf_data.set10('WLR_HAS_' + get_option('logind-provider').to_upper(), true) + wlr_deps += logind +endif + +subdir('protocol') +subdir('render') + +subdir('backend') +subdir('types') +subdir('util') +subdir('xcursor') +subdir('xwayland') + +subdir('include') + +wlr_parts += [ + lib_wl_protos, + lib_wlr_backend, + lib_wlr_render, + lib_wlr_types, + lib_wlr_util, + lib_wlr_xcursor, +] + +wlr_deps += [ + wayland_server, + wayland_client, + wayland_egl, + wayland_protos, + egl, + glesv2, + drm, + gbm, + libinput, + xkbcommon, + udev, + pixman, + math, +] + +if host_machine.system() == 'freebsd' + override_options = ['b_lundef=false'] +else + override_options = [] +endif + +symbols_file = 'wlroots.syms' +symbols_flag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), symbols_file) +lib_wlr = library( + meson.project_name(), + version: '.'.join(so_version), + link_whole: wlr_parts, + dependencies: wlr_deps, + include_directories: wlr_inc, + install: true, + link_args : symbols_flag, + link_depends: symbols_file, + override_options: override_options, +) + +wlroots = declare_dependency( + link_with: lib_wlr, + dependencies: wlr_deps, + include_directories: wlr_inc, +) + +summary = [ + '', + '----------------', + 'wlroots @0@'.format(meson.project_version()), + '', + ' libcap: @0@'.format(conf_data.get('WLR_HAS_LIBCAP', false)), + ' systemd: @0@'.format(conf_data.get('WLR_HAS_SYSTEMD', false)), + ' elogind: @0@'.format(conf_data.get('WLR_HAS_ELOGIND', false)), + ' xwayland: @0@'.format(conf_data.get('WLR_HAS_XWAYLAND', false)), + ' x11_backend: @0@'.format(conf_data.get('WLR_HAS_X11_BACKEND', false)), + ' xcb-icccm: @0@'.format(conf_data.get('WLR_HAS_XCB_ICCCM', false)), + ' xcb-errors: @0@'.format(conf_data.get('WLR_HAS_XCB_ERRORS', false)), + '----------------', + '' +] +message('\n'.join(summary)) + +subdir('examples') +subdir('rootston') + +pkgconfig = import('pkgconfig') +pkgconfig.generate( + libraries: lib_wlr, + version: meson.project_version(), + filebase: meson.project_name(), + name: meson.project_name(), + description: 'Wayland compositor library', +) + +git = find_program('git', required: false) +if git.found() + all_files = run_command( + git, + '--git-dir=@0@/.git'.format(meson.current_source_dir()), + 'ls-files', + ':/*.[ch]', + ) + all_files = files(all_files.stdout().split()) + + etags = find_program('etags', required: false) + if etags.found() and all_files.length() > 0 + custom_target( + 'etags', + build_by_default: true, + input: all_files, + output: 'TAGS', + command: [etags, '-o', '@OUTPUT@', '@INPUT@'], + ) + endif + + ctags = find_program('ctags', required: false) + if ctags.found() and all_files.length() > 0 + custom_target( + 'ctags', + build_by_default: true, + input: all_files, + output: 'tags', + command: [ctags, '-f', '@OUTPUT@', '@INPUT@'], + ) + endif +endif diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 00000000..19a6cad7 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,9 @@ +option('libcap', type: 'feature', value: 'auto', description: 'Enable support for rootless session via capabilities (cap_sys_admin)') +option('logind', type: 'feature', value: 'auto', description: 'Enable support for rootless session via logind') +option('logind-provider', type: 'combo', choices: ['systemd', 'elogind'], value: 'systemd', description: 'Provider of logind support library') +option('xcb-errors', type: 'feature', value: 'auto', description: 'Use xcb-errors util library') +option('xcb-icccm', type: 'feature', value: 'auto', description: 'Use xcb-icccm util library') +option('xwayland', type: 'feature', value: 'auto', description: 'Enable support for X11 applications') +option('x11-backend', type: 'feature', value: 'auto', description: 'Enable X11 backend') +option('rootston', type: 'boolean', value: true, description: 'Build the rootston example compositor') +option('examples', type: 'boolean', value: true, description: 'Build example applications') diff --git a/protocol/gamma-control.xml b/protocol/gamma-control.xml new file mode 100644 index 00000000..e6e33265 --- /dev/null +++ b/protocol/gamma-control.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="gamma_control"> + + <copyright> + Copyright © 2015 Giulio camuffo + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + </copyright> + + <interface name="gamma_control_manager" version="1"> + <request name="destroy" type="destructor"/> + + <request name="get_gamma_control"> + <arg name="id" type="new_id" interface="gamma_control"/> + <arg name="output" type="object" interface="wl_output"/> + </request> + </interface> + + <interface name="gamma_control" version="1"> + <enum name="error"> + <entry name="invalid_gamma" value="0"/> + </enum> + + <request name="destroy" type="destructor"/> + + <request name="set_gamma"> + <arg name="red" type="array"/> + <arg name="green" type="array"/> + <arg name="blue" type="array"/> + </request> + + <request name="reset_gamma"/> + + <event name="gamma_size"> + <arg name="size" type="uint"/> + </event> + </interface> +</protocol> diff --git a/protocol/gtk-primary-selection.xml b/protocol/gtk-primary-selection.xml new file mode 100644 index 00000000..02cab94f --- /dev/null +++ b/protocol/gtk-primary-selection.xml @@ -0,0 +1,225 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="gtk_primary_selection"> + <copyright> + Copyright © 2015, 2016 Red Hat + + 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 (including the next + paragraph) 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. + </copyright> + + <description summary="Primary selection protocol"> + This protocol provides the ability to have a primary selection device to + match that of the X server. This primary selection is a shortcut to the + common clipboard selection, where text just needs to be selected in order + to allow copying it elsewhere. The de facto way to perform this action + is the middle mouse button, although it is not limited to this one. + + Clients wishing to honor primary selection should create a primary + selection source and set it as the selection through + wp_primary_selection_device.set_selection whenever the text selection + changes. In order to minimize calls in pointer-driven text selection, + it should happen only once after the operation finished. Similarly, + a NULL source should be set when text is unselected. + + wp_primary_selection_offer objects are first announced through the + wp_primary_selection_device.data_offer event. Immediately after this event, + the primary data offer will emit wp_primary_selection_offer.offer events + to let know of the mime types being offered. + + When the primary selection changes, the client with the keyboard focus + will receive wp_primary_selection_device.selection events. Only the client + with the keyboard focus will receive such events with a non-NULL + wp_primary_selection_offer. Across keyboard focus changes, previously + focused clients will receive wp_primary_selection_device.events with a + NULL wp_primary_selection_offer. + + In order to request the primary selection data, the client must pass + a recent serial pertaining to the press event that is triggering the + operation, if the compositor deems the serial valid and recent, the + wp_primary_selection_source.send event will happen in the other end + to let the transfer begin. The client owning the primary selection + should write the requested data, and close the file descriptor + immediately. + + If the primary selection owner client disappeared during the transfer, + the client reading the data will receive a + wp_primary_selection_device.selection event with a NULL + wp_primary_selection_offer, the client should take this as a hint + to finish the reads related to the no longer existing offer. + + The primary selection owner should be checking for errors during + writes, merely cancelling the ongoing transfer if any happened. + </description> + + <interface name="gtk_primary_selection_device_manager" version="1"> + <description summary="X primary selection emulation"> + The primary selection device manager is a singleton global object that + provides access to the primary selection. It allows to create + wp_primary_selection_source objects, as well as retrieving the per-seat + wp_primary_selection_device objects. + </description> + + <request name="create_source"> + <description summary="create a new primary selection source"> + Create a new primary selection source. + </description> + <arg name="id" type="new_id" interface="gtk_primary_selection_source"/> + </request> + + <request name="get_device"> + <description summary="create a new primary selection device"> + Create a new data device for a given seat. + </description> + <arg name="id" type="new_id" interface="gtk_primary_selection_device"/> + <arg name="seat" type="object" interface="wl_seat"/> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the primary selection device manager"> + Destroy the primary selection device manager. + </description> + </request> + </interface> + + <interface name="gtk_primary_selection_device" version="1"> + <request name="set_selection"> + <description summary="set the primary selection"> + Replaces the current selection. The previous owner of the primary selection + will receive a wp_primary_selection_source.cancelled event. + + To unset the selection, set the source to NULL. + </description> + <arg name="source" type="object" interface="gtk_primary_selection_source" allow-null="true"/> + <arg name="serial" type="uint" summary="serial of the event that triggered this request"/> + </request> + + <event name="data_offer"> + <description summary="introduce a new wp_primary_selection_offer"> + Introduces a new wp_primary_selection_offer object that may be used + to receive the current primary selection. Immediately following this + event, the new wp_primary_selection_offer object will send + wp_primary_selection_offer.offer events to describe the offered mime + types. + </description> + <arg name="offer" type="new_id" interface="gtk_primary_selection_offer"/> + </event> + + <event name="selection"> + <description summary="advertise a new primary selection"> + The wp_primary_selection_device.selection event is sent to notify the + client of a new primary selection. This event is sent after the + wp_primary_selection.data_offer event introducing this object, and after + the offer has announced its mimetypes through + wp_primary_selection_offer.offer. + + The data_offer is valid until a new offer or NULL is received + or until the client loses keyboard focus. The client must destroy the + previous selection data_offer, if any, upon receiving this event. + </description> + <arg name="id" type="object" interface="gtk_primary_selection_offer" allow-null="true"/> + </event> + + <request name="destroy" type="destructor"> + <description summary="destroy the primary selection device"> + Destroy the primary selection device. + </description> + </request> + </interface> + + <interface name="gtk_primary_selection_offer" version="1"> + <description summary="offer to transfer primary selection contents"> + A wp_primary_selection_offer represents an offer to transfer the contents + of the primary selection clipboard to the client. Similar to + wl_data_offer, the offer also describes the mime types that the source + will transferthat the + data can be converted to and provides the mechanisms for transferring the + data directly to the client. + </description> + + <request name="receive"> + <description summary="request that the data is transferred"> + To transfer the contents of the primary selection clipboard, the client + issues this request and indicates the mime type that it wants to + receive. The transfer happens through the passed file descriptor + (typically created with the pipe system call). The source client writes + the data in the mime type representation requested and then closes the + file descriptor. + + The receiving client reads from the read end of the pipe until EOF and + closes its end, at which point the transfer is complete. + </description> + <arg name="mime_type" type="string"/> + <arg name="fd" type="fd"/> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the primary selection offer"> + Destroy the primary selection offer. + </description> + </request> + + <event name="offer"> + <description summary="advertise offered mime type"> + Sent immediately after creating announcing the wp_primary_selection_offer + through wp_primary_selection_device.data_offer. One event is sent per + offered mime type. + </description> + <arg name="mime_type" type="string"/> + </event> + </interface> + + <interface name="gtk_primary_selection_source" version="1"> + <description summary="offer to replace the contents of the primary selection"> + The source side of a wp_primary_selection_offer, it provides a way to + describe the offered data and respond to requests to transfer the + requested contents of the primary selection clipboard. + </description> + + <request name="offer"> + <description summary="add an offered mime type"> + This request adds a mime type to the set of mime types advertised to + targets. Can be called several times to offer multiple types. + </description> + <arg name="mime_type" type="string"/> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the primary selection source"> + Destroy the primary selection source. + </description> + </request> + + <event name="send"> + <description summary="send the primary selection contents"> + Request for the current primary selection contents from the client. + Send the specified mime type over the passed file descriptor, then + close it. + </description> + <arg name="mime_type" type="string"/> + <arg name="fd" type="fd"/> + </event> + + <event name="cancelled"> + <description summary="request for primary selection contents was canceled"> + This primary selection source is no longer valid. The client should + clean up and destroy this primary selection source. + </description> + </event> + </interface> +</protocol> diff --git a/protocol/idle.xml b/protocol/idle.xml new file mode 100644 index 00000000..92d9989c --- /dev/null +++ b/protocol/idle.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="idle"> + <copyright><![CDATA[ + Copyright (C) 2015 Martin Gräßlin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ]]></copyright> + <interface name="org_kde_kwin_idle" version="1"> + <description summary="User idle time manager"> + This interface allows to monitor user idle time on a given seat. The interface + allows to register timers which trigger after no user activity was registered + on the seat for a given interval. It notifies when user activity resumes. + + This is useful for applications wanting to perform actions when the user is not + interacting with the system, e.g. chat applications setting the user as away, power + management features to dim screen, etc.. + </description> + <request name="get_idle_timeout"> + <arg name="id" type="new_id" interface="org_kde_kwin_idle_timeout"/> + <arg name="seat" type="object" interface="wl_seat"/> + <arg name="timeout" type="uint" summary="The idle timeout in msec"/> + </request> + </interface> + <interface name="org_kde_kwin_idle_timeout" version="1"> + <request name="release" type="destructor"> + <description summary="release the timeout object"/> + </request> + <request name="simulate_user_activity"> + <description summary="Simulates user activity for this timeout, behaves just like real user activity on the seat"/> + </request> + <event name="idle"> + <description summary="Triggered when there has not been any user activity in the requested idle time interval"/> + </event> + <event name="resumed"> + <description summary="Triggered on the first user activity after an idle event"/> + </event> + </interface> +</protocol> diff --git a/protocol/input-method-unstable-v2.xml b/protocol/input-method-unstable-v2.xml new file mode 100644 index 00000000..62be9d94 --- /dev/null +++ b/protocol/input-method-unstable-v2.xml @@ -0,0 +1,490 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="input_method_unstable_v2"> + + <copyright> + Copyright © 2008-2011 Kristian Høgsberg + Copyright © 2010-2011 Intel Corporation + Copyright © 2012-2013 Collabora, Ltd. + Copyright © 2012, 2013 Intel Corporation + Copyright © 2015, 2016 Jan Arne Petersen + Copyright © 2017, 2018 Red Hat, Inc. + Copyright © 2018 Purism SPC + + 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 (including the next + paragraph) 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. + </copyright> + + <description summary="Protocol for creating input methods"> + This protocol allows applications to act as input methods for compositors. + + An input method context is used to manage the state of the input method. + + Text strings are UTF-8 encoded, their indices and lengths are in bytes. + + This document adheres to the RFC 2119 when using words like "must", + "should", "may", etc. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + </description> + + <interface name="zwp_input_method_v2" version="1"> + <description summary="input method"> + An input method object allows for clients to compose text. + + The objects connects the client to a text input in an application, and + lets the client to serve as an input method for a seat. + + The zwp_input_method_v2 object can occupy two distinct states: active and + inactive. In the active state, the object is associated to and + communicates with a text input. In the inactive state, there is no + associated text input, and the only communication is with the compositor. + Initially, the input method is in the inactive state. + + Requests issued in the inactive state must be accepted by the compositor. + Because of the serial mechanism, and the state reset on activate event, + they will not have any effect on the state of the next text input. + + There must be no more than one input method object per seat. + </description> + + <event name="activate"> + <description summary="input method has been requested"> + Notification that a text input focused on this seat requested the input + method to be activated. + + This event serves the purpose of providing the compositor with an + active input method. + + This event resets all state associated with previous enable, disable, + surrounding_text, text_change_cause, and content_type events, as well + as the state associated with set_preedit_string, commit_string, and + delete_surrounding_text requests. In addition, it marks the + zwp_input_method_v2 object as active, and makes any existing + zwp_input_popup_surface_v2 objects visible. + + The surrounding_text, and content_type events must follow before the + next done event if the text input supports the respective + functionality. + + State set with this event is double-buffered. It will get applied on + the next zwp_input_method_v2.done event, and stay valid until changed. + </description> + </event> + + <event name="deactivate"> + <description summary="deactivate event"> + Notification that no focused text input currently needs an active + input method on this seat. + + This event marks the zwp_input_method_v2 object as inactive. The + compositor must make all existing zwp_input_popup_surface_v2 objects + invisible until the next activate event. + + State set with this event is double-buffered. It will get applied on + the next zwp_input_method_v2.done event, and stay valid until changed. + </description> + </event> + + <event name="surrounding_text"> + <description summary="surrounding text event"> + Updates the surrounding plain text around the cursor, excluding the + preedit text. + + If any preedit text is present, it is replaced with the cursor for the + purpose of this event. + + The argument text is a buffer containing the preedit string, and must + include the cursor position, and the complete selection. It should + contain additional characters before and after these. There is a + maximum length of wayland messages, so text can not be longer than 4000 + bytes. + + cursor is the byte offset of the cursor within the text buffer. + + anchor is the byte offset of the selection anchor within the text + buffer. If there is no selected text, anchor must be the same as + cursor. + + If this event does not arrive before the first done event, the input + method may assume that the text input does not support this + functionality and ignore following surrounding_text events. + + Values set with this event are double-buffered. They will get applied + and set to initial values on the next zwp_input_method_v2.done + event. + + The initial state for affected fields is empty, meaning that the text + input does not support sending surrounding text. If the empty values + get applied, subsequent attempts to change them may have no effect. + </description> + <arg name="text" type="string"/> + <arg name="cursor" type="uint"/> + <arg name="anchor" type="uint"/> + </event> + + <event name="text_change_cause"> + <description summary="indicates the cause of surrounding text change"> + Tells the input method why the text surrounding the cursor changed. + + Whenever the client detects an external change in text, cursor, or + anchor position, it must issue this request to the compositor. This + request is intended to give the input method a chance to update the + preedit text in an appropriate way, e.g. by removing it when the user + starts typing with a keyboard. + + cause describes the source of the change. + + The value set with this event is double-buffered. It will get applied + and set to its initial value on the next zwp_input_method_v2.done + event. + + The initial value of cause is input_method. + </description> + <arg name="cause" type="uint" enum="zwp_text_input_v3.change_cause"/> + </event> + + <event name="content_type"> + <description summary="content purpose and hint"> + Indicates the content type and hint for the current + zwp_input_method_v2 instance. + + Values set with this event are double-buffered. They will get applied + on the next zwp_input_method_v2.done event. + + The initial value for hint is none, and the initial value for purpose + is normal. + </description> + <arg name="hint" type="uint" enum="zwp_text_input_v3.content_hint"/> + <arg name="purpose" type="uint" enum="zwp_text_input_v3.content_purpose"/> + </event> + + <event name="done"> + <description summary="apply state"> + Atomically applies state changes recently sent to the client. + + The done event establishes and updates the state of the client, and + must be issued after any changes to apply them. + + Text input state (content purpose, content hint, surrounding text, and + change cause) is conceptually double-buffered within an input method + context. + + Events modify the pending state, as opposed to the current state in use + by the input method. A done event atomically applies all pending state, + replacing the current state. After done, the new pending state is as + documented for each related request. + + Events must be applied in the order of arrival. + + Neither current nor pending state are modified unless noted otherwise. + </description> + </event> + + <request name="commit_string"> + <description summary="commit string"> + Send the commit string text for insertion to the application. + + Inserts a string at current cursor position (see commit event + sequence). The string to commit could be either just a single character + after a key press or the result of some composing. + + The argument text is a buffer containing the string to insert. There is + a maximum length of wayland messages, so text can not be longer than + 4000 bytes. + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.commit request. + + The initial value of text is an empty string. + </description> + <arg name="text" type="string"/> + </request> + + <request name="set_preedit_string"> + <description summary="pre-edit string"> + Send the pre-edit string text to the application text input. + + Place a new composing text (pre-edit) at the current cursor position. + Any previously set composing text must be removed. Any previously + existing selected text must be removed. The cursor is moved to a new + position within the preedit string. + + The argument text is a buffer containing the preedit string. There is + a maximum length of wayland messages, so text can not be longer than + 4000 bytes. + + The arguments cursor_begin and cursor_end are counted in bytes relative + to the beginning of the submitted string buffer. Cursor should be + hidden by the text input when both are equal to -1. + + cursor_begin indicates the beginning of the cursor. cursor_end + indicates the end of the cursor. It may be equal or different than + cursor_begin. + + Values set with this event are double-buffered. They must be applied on + the next zwp_input_method_v2.commit event. + + The initial value of text is an empty string. The initial value of + cursor_begin, and cursor_end are both 0. + </description> + <arg name="text" type="string"/> + <arg name="cursor_begin" type="int"/> + <arg name="cursor_end" type="int"/> + </request> + + <request name="delete_surrounding_text"> + <description summary="delete text"> + Remove the surrounding text. + + before_length and after_length are the number of bytes before and after + the current cursor index (excluding the preedit text) to delete. + + If any preedit text is present, it is replaced with the cursor for the + purpose of this event. In effect before_length is counted from the + beginning of preedit text, and after_length from its end (see commit + event sequence). + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_input_method_v2.commit request. + + The initial values of both before_length and after_length are 0. + </description> + <arg name="before_length" type="uint"/> + <arg name="after_length" type="uint"/> + </request> + + <request name="commit"> + <description summary="apply state"> + Apply state changes from commit_string, set_preedit_string and + delete_surrounding_text requests. + + The state relating to these events is double-buffered, and each one + modifies the pending state. This request replaces the current state + with the pending state. + + The connected text input is expected to proceed by evaluating the + changes in the following order: + + 1. Replace existing preedit string with the cursor. + 2. Delete requested surrounding text. + 3. Insert commit string with the cursor at its end. + 4. Calculate surrounding text to send. + 5. Insert new preedit text in cursor position. + 6. Place cursor inside preedit text. + + The serial number reflects the last state of the zwp_input_method_v2 + object known to the client. The value of the serial argument must be + equal to the number of done events already issued by that object. When + the compositor receives a commit request with a serial different than + the number of past done events, it must proceed as normal, except it + should not change the current state of the zwp_input_method_v2 object. + </description> + <arg name="serial" type="uint"/> + </request> + + <request name="get_input_popup_surface"> + <description summary="create popup surface"> + Creates a new zwp_input_popup_surface_v2 object wrapping a given + surface. + + The surface gets assigned the "input_popup" role. If the surface + already has an assigned role, the compositor must issue a protocol + error. + </description> + <arg name="id" type="new_id" interface="zwp_input_popup_surface_v2"/> + <arg name="surface" type="object" interface="wl_surface"/> + </request> + + <request name="grab_keyboard"> + <description summary="grab hardware keyboard"> + Allow an input method to receive hardware keyboard input and process + key events to generate text events (with pre-edit) over the wire. This + allows input methods which compose multiple key events for inputting + text like it is done for CJK languages. + + The compositor should send all keyboard events on the seat to the grab + holder via the returned wl_keyboard object. Nevertheless, the + compositor may decide not to forward any particular event. The + compositor must not further process any event after it has been + forwarded to the grab holder. + + Releasing the resulting wl_keyboard object releases the grab. + </description> + <arg name="keyboard" type="new_id" + interface="zwp_input_method_keyboard_grab_v2"/> + </request> + + <event name="unavailable"> + <description summary="input method unavailable"> + The input method ceased to be available. + + The compositor must issue this event as the only event on the object if + there was another input_method object associated with the same seat at + the time of its creation. + + The compositor must issue this request when the object is no longer + useable, e.g. due to seat removal. + + The input method context becomes inert and should be destroyed after + deactivation is handled. Any further requests and events except for the + destroy request must be ignored. + </description> + </event> + + <request name="destroy" type="destructor"> + <description summary="destroy the text input"> + Destroys the zwp_text_input_v2 object and any associated child + objects, i.e. zwp_input_popup_surface_v2 and + zwp_input_method_keyboard_grab_v2. + </description> + </request> + </interface> + + <interface name="zwp_input_popup_surface_v2" version="1"> + <description summary="popup surface"> + This interface marks a surface as a popup for interacting with an input + method. + + The compositor should place it near the active text input area. It must + be visible if and only if the input method is in the active state. + + The client must not destroy the underlying wl_surface while the + zwp_input_popup_surface_v2 object exists. + </description> + + <event name="text_input_rectangle"> + <description summary="set text input area position"> + Notify about the position of the area of the text input expressed as a + rectangle in surface local coordinates. + + This is a hint to the input method telling it the relative position of + the text being entered. + </description> + <arg name="x" type="int"/> + <arg name="y" type="int"/> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + </event> + + <request name="destroy" type="destructor"/> + </interface> + + <interface name="zwp_input_method_keyboard_grab_v2" version="1"> + <!-- Closely follows wl_keyboard version 6 --> + <description summary="keyboard grab"> + The zwp_input_method_keyboard_grab_v2 interface represents an exclusive + grab of the wl_keyboard interface associated with the seat. + </description> + + <event name="keymap"> + <description summary="keyboard mapping"> + This event provides a file descriptor to the client which can be + memory-mapped to provide a keyboard mapping description. + </description> + <arg name="format" type="uint" enum="wl_keyboard.keymap_format" + summary="keymap format"/> + <arg name="fd" type="fd" summary="keymap file descriptor"/> + <arg name="size" type="uint" summary="keymap size, in bytes"/> + </event> + + <event name="key"> + <description summary="key event"> + A key was pressed or released. + The time argument is a timestamp with millisecond granularity, with an + undefined base. + </description> + <arg name="serial" type="uint" summary="serial number of the key event"/> + <arg name="time" type="uint" summary="timestamp with millisecond granularity"/> + <arg name="key" type="uint" summary="key that produced the event"/> + <arg name="state" type="uint" enum="wl_keyboard.key_state" + summary="physical state of the key"/> + </event> + + <event name="modifiers"> + <description summary="modifier and group state"> + Notifies clients that the modifier and/or group state has changed, and + it should update its local state. + </description> + <arg name="serial" type="uint" summary="serial number of the modifiers event"/> + <arg name="mods_depressed" type="uint" summary="depressed modifiers"/> + <arg name="mods_latched" type="uint" summary="latched modifiers"/> + <arg name="mods_locked" type="uint" summary="locked modifiers"/> + <arg name="group" type="uint" summary="keyboard layout"/> + </event> + + <request name="release" type="destructor"> + <description summary="release the grab object"/> + </request> + + <event name="repeat_info"> + <description summary="repeat rate and delay"> + Informs the client about the keyboard's repeat rate and delay. + + This event is sent as soon as the zwp_input_method_keyboard_grab_v2 + object has been created, and is guaranteed to be received by the + client before any key press event. + + Negative values for either rate or delay are illegal. A rate of zero + will disable any repeating (regardless of the value of delay). + + This event can be sent later on as well with a new value if necessary, + so clients should continue listening for the event past the creation + of zwp_input_method_keyboard_grab_v2. + </description> + <arg name="rate" type="int" + summary="the rate of repeating keys in characters per second"/> + <arg name="delay" type="int" + summary="delay in milliseconds since key down until repeating starts"/> + </event> + </interface> + + <interface name="zwp_input_method_manager_v2" version="1"> + <description summary="input method manager"> + The input method manager allows the client to become the input method on + a chosen seat. + + No more than one input method must be associated with any seat at any + given time. + </description> + + <request name="get_input_method"> + <description summary="request an input method object"> + Request a new input zwp_input_method_v2 object associated with a given + seat. + </description> + <arg name="seat" type="object" interface="wl_seat"/> + <arg name="input_method" type="new_id" interface="zwp_input_method_v2"/> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the input method manager"> + Destroys the zwp_input_method_manager_v2 object. + + The zwp_input_method_v2 objects originating from it remain valid. + </description> + </request> + </interface> +</protocol> diff --git a/protocol/meson.build b/protocol/meson.build new file mode 100644 index 00000000..58f57046 --- /dev/null +++ b/protocol/meson.build @@ -0,0 +1,95 @@ +wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') + +wayland_scanner_dep = dependency('wayland-scanner', required: false, native: true) +if wayland_scanner_dep.found() + wayland_scanner = find_program( + wayland_scanner_dep.get_pkgconfig_variable('wayland_scanner'), + native: true, + ) +else + wayland_scanner = find_program('wayland-scanner', native: true) +endif + +protocols = [ + [wl_protocol_dir, 'stable/presentation-time/presentation-time.xml'], + [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], + [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], + [wl_protocol_dir, 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml'], + [wl_protocol_dir, 'unstable/tablet/tablet-unstable-v2.xml'], + [wl_protocol_dir, 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml'], + [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], + [wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'], + [wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'], + 'gamma-control.xml', + 'gtk-primary-selection.xml', + 'idle.xml', + 'input-method-unstable-v2.xml', + 'screenshooter.xml', + 'server-decoration.xml', + 'text-input-unstable-v3.xml', + 'virtual-keyboard-unstable-v1.xml', + 'wlr-export-dmabuf-unstable-v1.xml', + 'wlr-gamma-control-unstable-v1.xml', + 'wlr-foreign-toplevel-management-unstable-v1.xml', + 'wlr-input-inhibitor-unstable-v1.xml', + 'wlr-layer-shell-unstable-v1.xml', + 'wlr-screencopy-unstable-v1.xml', +] + +client_protocols = [ + [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], + [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], + [wl_protocol_dir, 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml'], + [wl_protocol_dir, 'unstable/xdg-shell/xdg-shell-unstable-v6.xml'], + [wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'], + 'idle.xml', + 'input-method-unstable-v2.xml', + 'screenshooter.xml', + 'text-input-unstable-v3.xml', + 'wlr-export-dmabuf-unstable-v1.xml', + 'wlr-gamma-control-unstable-v1.xml', + 'wlr-foreign-toplevel-management-unstable-v1.xml', + 'wlr-input-inhibitor-unstable-v1.xml', + 'wlr-layer-shell-unstable-v1.xml', + 'wlr-screencopy-unstable-v1.xml', +] + +wl_protos_src = [] +wl_protos_headers = [] + +foreach p : protocols + xml = join_paths(p) + wl_protos_src += custom_target( + xml.underscorify() + '_server_c', + input: xml, + output: '@BASENAME@-protocol.c', + command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'], + ) + wl_protos_headers += custom_target( + xml.underscorify() + '_server_h', + input: xml, + output: '@BASENAME@-protocol.h', + command: [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'], + ) +endforeach + +foreach p : client_protocols + xml = join_paths(p) + wl_protos_headers += custom_target( + xml.underscorify() + '_client_h', + input: xml, + output: '@BASENAME@-client-protocol.h', + command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'], + ) +endforeach + +lib_wl_protos = static_library( + 'wl_protos', + wl_protos_src + wl_protos_headers, + dependencies: wayland_client.partial_dependency(compile_args: true), +) + +wlr_protos = declare_dependency( + link_with: lib_wl_protos, + sources: wl_protos_headers, +) diff --git a/protocol/screenshooter.xml b/protocol/screenshooter.xml new file mode 100644 index 00000000..fc48eac9 --- /dev/null +++ b/protocol/screenshooter.xml @@ -0,0 +1,16 @@ +<protocol name="orbital_screenshooter"> + + <interface name="orbital_screenshooter" version="1"> + <request name="shoot"> + <arg name="id" type="new_id" interface="orbital_screenshot"/> + <arg name="output" type="object" interface="wl_output"/> + <arg name="buffer" type="object" interface="wl_buffer"/> + </request> + </interface> + + <interface name="orbital_screenshot" version="1"> + <event name="done"> + </event> + </interface> + +</protocol> diff --git a/protocol/server-decoration.xml b/protocol/server-decoration.xml new file mode 100644 index 00000000..45f11284 --- /dev/null +++ b/protocol/server-decoration.xml @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="server_decoration"> + <copyright><![CDATA[ + Copyright (C) 2015 Martin Gräßlin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + ]]></copyright> + <interface name="org_kde_kwin_server_decoration_manager" version="1"> + <description summary="Server side window decoration manager"> + This interface allows to coordinate whether the server should create + a server-side window decoration around a wl_surface representing a + shell surface (wl_shell_surface or similar). By announcing support + for this interface the server indicates that it supports server + side decorations. + </description> + <request name="create"> + <description summary="Create a server-side decoration object for a given surface"> + When a client creates a server-side decoration object it indicates + that it supports the protocol. The client is supposed to tell the + server whether it wants server-side decorations or will provide + client-side decorations. + + If the client does not create a server-side decoration object for + a surface the server interprets this as lack of support for this + protocol and considers it as client-side decorated. Nevertheless a + client-side decorated surface should use this protocol to indicate + to the server that it does not want a server-side deco. + </description> + <arg name="id" type="new_id" interface="org_kde_kwin_server_decoration"/> + <arg name="surface" type="object" interface="wl_surface"/> + </request> + <enum name="mode"> + <description summary="Possible values to use in request_mode and the event mode."/> + <entry name="None" value="0" summary="Undecorated: The surface is not decorated at all, neither server nor client-side. An example is a popup surface which should not be decorated."/> + <entry name="Client" value="1" summary="Client-side decoration: The decoration is part of the surface and the client."/> + <entry name="Server" value="2" summary="Server-side decoration: The server embeds the surface into a decoration frame."/> + </enum> + <event name="default_mode"> + <description summary="The default mode used on the server"> + This event is emitted directly after binding the interface. It contains + the default mode for the decoration. When a new server decoration object + is created this new object will be in the default mode until the first + request_mode is requested. + + The server may change the default mode at any time. + </description> + <arg name="mode" type="uint" summary="The default decoration mode applied to newly created server decorations."/> + </event> + </interface> + <interface name="org_kde_kwin_server_decoration" version="1"> + <request name="release" type="destructor"> + <description summary="release the server decoration object"/> + </request> + <enum name="mode"> + <description summary="Possible values to use in request_mode and the event mode."/> + <entry name="None" value="0" summary="Undecorated: The surface is not decorated at all, neither server nor client-side. An example is a popup surface which should not be decorated."/> + <entry name="Client" value="1" summary="Client-side decoration: The decoration is part of the surface and the client."/> + <entry name="Server" value="2" summary="Server-side decoration: The server embeds the surface into a decoration frame."/> + </enum> + <request name="request_mode"> + <description summary="The decoration mode the surface wants to use."/> + <arg name="mode" type="uint" summary="The mode this surface wants to use."/> + </request> + <event name="mode"> + <description summary="The new decoration mode applied by the server"> + This event is emitted directly after the decoration is created and + represents the base decoration policy by the server. E.g. a server + which wants all surfaces to be client-side decorated will send Client, + a server which wants server-side decoration will send Server. + + The client can request a different mode through the decoration request. + The server will acknowledge this by another event with the same mode. So + even if a server prefers server-side decoration it's possible to force a + client-side decoration. + + The server may emit this event at any time. In this case the client can + again request a different mode. It's the responsibility of the server to + prevent a feedback loop. + </description> + <arg name="mode" type="uint" summary="The decoration mode applied to the surface by the server."/> + </event> + </interface> +</protocol> diff --git a/protocol/text-input-unstable-v3.xml b/protocol/text-input-unstable-v3.xml new file mode 100644 index 00000000..8b710fd6 --- /dev/null +++ b/protocol/text-input-unstable-v3.xml @@ -0,0 +1,441 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<protocol name="text_input_unstable_v3"> + <copyright> + Copyright © 2012, 2013 Intel Corporation + Copyright © 2015, 2016 Jan Arne Petersen + Copyright © 2017, 2018 Red Hat, Inc. + Copyright © 2018 Purism SPC + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + </copyright> + + <description summary="Protocol for composing text"> + This protocol allows compositors to act as input methods and to send text + to applications. A text input object is used to manage state of what are + typically text entry fields in the application. + + This document adheres to the RFC 2119 when using words like "must", + "should", "may", etc. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + </description> + + <interface name="zwp_text_input_v3" version="1"> + <description summary="text input"> + The zwp_text_input_v3 interface represents text input and input methods + associated with a seat. It provides enter/leave events to follow the + text input focus for a seat. + + Requests are used to enable/disable the text-input object and set + state information like surrounding and selected text or the content type. + The information about the entered text is sent to the text-input object + via the preedit_string and commit_string events. + + Text is valid UTF-8 encoded, indices and lengths are in bytes. Indices + must not point to middle bytes inside a code point: they must either + point to the first byte of a code point or to the end of the buffer. + Lengths must be measured between two valid indices. + + Focus moving throughout surfaces will result in the emission of + zwp_text_input_v3.enter and zwp_text_input_v3.leave events. The focused + surface must commit zwp_text_input_v3.enable and + zwp_text_input_v3.disable requests as the keyboard focus moves across + editable and non-editable elements of the UI. Those two requests are not + expected to be paired with each other, the compositor must be able to + handle consecutive series of the same request. + + State is sent by the state requests (set_surrounding_text, + set_content_type and set_cursor_rectangle) and a commit request. After an + enter event or disable request all state information is invalidated and + needs to be resent by the client. + </description> + + <request name="destroy" type="destructor"> + <description summary="Destroy the wp_text_input"> + Destroy the wp_text_input object. Also disables all surfaces enabled + through this wp_text_input object. + </description> + </request> + + <request name="enable"> + <description summary="Request text input to be enabled"> + Requests text input on the surface previously obtained from the enter + event. + + This request must be issued every time the active text input changes + to a new one, including within the current surface. Use + zwp_text_input_v3.disable when there is no longer any input focus on + the current surface. + + This request resets all state associated with previous enable, disable, + set_surrounding_text, set_text_change_cause, set_content_type, and + set_cursor_rectangle requests, as well as the state associated with + preedit_string, commit_string, and delete_surrounding_text events. + + The set_surrounding_text, set_content_type and set_cursor_rectangle + requests must follow if the text input supports the necessary + functionality. + + State set with this request is double-buffered. It will get applied on + the next zwp_text_input_v3.commit request, and stay valid until the + next committed enable or disable request. + + The changes must be applied by the compositor after issuing a + zwp_text_input_v3.commit request. + </description> + </request> + + <request name="disable"> + <description summary="Disable text input on a surface"> + Explicitly disable text input on the current surface (typically when + there is no focus on any text entry inside the surface). + + State set with this request is double-buffered. It will get applied on + the next zwp_text_input_v3.commit request. + </description> + </request> + + <request name="set_surrounding_text"> + <description summary="sets the surrounding text"> + Sets the surrounding plain text around the input, excluding the preedit + text. + + The client should notify the compositor of any changes in any of the + values carried with this request, including changes caused by handling + incoming text-input events as well as changes caused by other + mechanisms like keyboard typing. + + If the client is unaware of the text around the cursor, it should not + issue this request, to signify lack of support to the compositor. + + Text is UTF-8 encoded, and should include the cursor position, the + complete selection and additional characters before and after them. + There is a maximum length of wayland messages, so text can not be + longer than 4000 bytes. + + Cursor is the byte offset of the cursor within text buffer. + + Anchor is the byte offset of the selection anchor within text buffer. + If there is no selected text, anchor is the same as cursor. + + If any preedit text is present, it is replaced with a cursor for the + purpose of this event. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v3.commit request, and stay valid until the + next committed enable or disable request. + + The initial state for affected fields is empty, meaning that the text + input does not support sending surrounding text. If the empty values + get applied, subsequent attempts to change them may have no effect. + </description> + <arg name="text" type="string"/> + <arg name="cursor" type="int"/> + <arg name="anchor" type="int"/> + </request> + + <enum name="change_cause"> + <description summary="text change reason"> + Reason for the change of surrounding text or cursor posision. + </description> + <entry name="input_method" value="0" summary="input method caused the change"/> + <entry name="other" value="1" summary="something else than the input method caused the change"/> + </enum> + + <request name="set_text_change_cause"> + <description summary="indicates the cause of surrounding text change"> + Tells the compositor why the text surrounding the cursor changed. + + Whenever the client detects an external change in text, cursor, or + anchor posision, it must issue this request to the compositor. This + request is intended to give the input method a chance to update the + preedit text in an appropriate way, e.g. by removing it when the user + starts typing with a keyboard. + + cause describes the source of the change. + + The value set with this request is double-buffered. It must be applied + and reset to initial at the next zwp_text_input_v3.commit request. + + The initial value of cause is input_method. + </description> + <arg name="cause" type="uint" enum="change_cause"/> + </request> + + <enum name="content_hint" bitfield="true"> + <description summary="content hint"> + Content hint is a bitmask to allow to modify the behavior of the text + input. + </description> + <entry name="none" value="0x0" summary="no special behavior"/> + <entry name="completion" value="0x1" summary="suggest word completions"/> + <entry name="spellcheck" value="0x2" summary="suggest word corrections"/> + <entry name="auto_capitalization" value="0x4" summary="switch to uppercase letters at the start of a sentence"/> + <entry name="lowercase" value="0x8" summary="prefer lowercase letters"/> + <entry name="uppercase" value="0x10" summary="prefer uppercase letters"/> + <entry name="titlecase" value="0x20" summary="prefer casing for titles and headings (can be language dependent)"/> + <entry name="hidden_text" value="0x40" summary="characters should be hidden"/> + <entry name="sensitive_data" value="0x80" summary="typed text should not be stored"/> + <entry name="latin" value="0x100" summary="just Latin characters should be entered"/> + <entry name="multiline" value="0x200" summary="the text input is multiline"/> + </enum> + + <enum name="content_purpose"> + <description summary="content purpose"> + The content purpose allows to specify the primary purpose of a text + input. + + This allows an input method to show special purpose input panels with + extra characters or to disallow some characters. + </description> + <entry name="normal" value="0" summary="default input, allowing all characters"/> + <entry name="alpha" value="1" summary="allow only alphabetic characters"/> + <entry name="digits" value="2" summary="allow only digits"/> + <entry name="number" value="3" summary="input a number (including decimal separator and sign)"/> + <entry name="phone" value="4" summary="input a phone number"/> + <entry name="url" value="5" summary="input an URL"/> + <entry name="email" value="6" summary="input an email address"/> + <entry name="name" value="7" summary="input a name of a person"/> + <entry name="password" value="8" summary="input a password (combine with sensitive_data hint)"/> + <entry name="pin" value="9" summary="input is a numeric password (combine with sensitive_data hint)"/> + <entry name="date" value="10" summary="input a date"/> + <entry name="time" value="11" summary="input a time"/> + <entry name="datetime" value="12" summary="input a date and time"/> + <entry name="terminal" value="13" summary="input for a terminal"/> + </enum> + + <request name="set_content_type"> + <description summary="set content purpose and hint"> + Sets the content purpose and content hint. While the purpose is the + basic purpose of an input field, the hint flags allow to modify some of + the behavior. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v3.commit request. + Subsequent attempts to update them may have no effect. The values + remain valid until the next committed enable or disable request. + + The initial value for hint is none, and the initial value for purpose + is normal. + </description> + <arg name="hint" type="uint" enum="content_hint"/> + <arg name="purpose" type="uint" enum="content_purpose"/> + </request> + + <request name="set_cursor_rectangle"> + <description summary="set cursor position"> + Marks an area around the cursor as a x, y, width, height rectangle in + surface local coordinates. + + Allows the compositor to put a window with word suggestions near the + cursor, without obstructing the text being input. + + If the client is unaware of the position of edited text, it should not + issue this request, to signify lack of support to the compositor. + + Values set with this request are double-buffered. They will get applied + on the next zwp_text_input_v3.commit request, and stay valid until the + next committed enable or disable request. + + The initial values describing a cursor rectangle are empty. That means + the text input does not support describing the cursor area. If the + empty values get applied, subsequent attempts to change them may have + no effect. + </description> + <arg name="x" type="int"/> + <arg name="y" type="int"/> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + </request> + + <request name="commit"> + <description summary="commit state"> + Atomically applies state changes recently sent to the compositor. + + The commit request establishes and updates the state of the client, and + must be issued after any changes to apply them. + + Text input state (enabled status, content purpose, content hint, + surrounding text and change cause, cursor rectangle) is conceptually + double-buffered within the context of a text input, i.e. between a + committed enable request and the following committed enable or disable + request. + + Protocol requests modify the pending state, as opposed to the current + state in use by the input method. A commit request atomically applies + all pending state, replacing the current state. After commit, the new + pending state is as documented for each related request. + + Requests are applied in the order of arrival. + + Neither current nor pending state are modified unless noted otherwise. + + The compositor must count the number of commit requests coming from + each zwp_text_input_v3 object and use the count as the serial in done + events. + </description> + </request> + + <event name="enter"> + <description summary="enter event"> + Notification that this seat's text-input focus is on a certain surface. + + When the seat has the keyboard capability the text-input focus follows + the keyboard focus. This event sets the current surface for the + text-input object. + </description> + <arg name="surface" type="object" interface="wl_surface"/> + </event> + + <event name="leave"> + <description summary="leave event"> + Notification that this seat's text-input focus is no longer on a + certain surface. The client should reset any preedit string previously + set. + + The leave notification clears the current surface. It is sent before + the enter notification for the new focus. + + When the seat has the keyboard capability the text-input focus follows + the keyboard focus. + </description> + <arg name="surface" type="object" interface="wl_surface"/> + </event> + + <event name="preedit_string"> + <description summary="pre-edit"> + Notify when a new composing text (pre-edit) should be set at the + current cursor position. Any previously set composing text must be + removed. Any previously existing selected text must be removed. + + The argument text contains the pre-edit string buffer. + + The parameters cursor_begin and cursor_end are counted in bytes + relative to the beginning of the submitted text buffer. Cursor should + be hidden when both are equal to -1. + + They could be represented by the client as a line if both values are + the same, or as a text highlight otherwise. + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.done event. + + The initial value of text is an empty string, and cursor_begin, + cursor_end and cursor_hidden are all 0. + </description> + <arg name="text" type="string" allow-null="true"/> + <arg name="cursor_begin" type="int"/> + <arg name="cursor_end" type="int"/> + </event> + + <event name="commit_string"> + <description summary="text commit"> + Notify when text should be inserted into the editor widget. The text to + commit could be either just a single character after a key press or the + result of some composing (pre-edit). + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.done event. + + The initial value of text is an empty string. + </description> + <arg name="text" type="string" allow-null="true"/> + </event> + + <event name="delete_surrounding_text"> + <description summary="delete surrounding text"> + Notify when the text around the current cursor position should be + deleted. + + Before_length and after_length are the number of bytes before and after + the current cursor index (excluding the selection) to delete. + + If a preedit text is present, in effect before_length is counted from + the beginning of it, and after_length from its end (see done event + sequence). + + Values set with this event are double-buffered. They must be applied + and reset to initial on the next zwp_text_input_v3.done event. + + The initial values of both before_length and after_length are 0. + </description> + <arg name="before_length" type="uint" summary="length of text before current cursor position"/> + <arg name="after_length" type="uint" summary="length of text after current cursor position"/> + </event> + + <event name="done"> + <description summary="apply changes"> + Instruct the application to apply changes to state requested by the + preedit_string, commit_string and delete_surrounding_text events. The + state relating to these events is double-buffered, and each one + modifies the pending state. This event replaces the current state with + the pending state. + + The application must proceed by evaluating the changes in the following + order: + + 1. Replace existing preedit string with the cursor. + 2. Delete requested surrounding text. + 3. Insert commit string with the cursor at its end. + 4. Calculate surrounding text to send. + 5. Insert new preedit text in cursor position. + 6. Place cursor inside preedit text. + + The serial number reflects the last state of the zwp_text_input_v3 + object known to the compositor. The value of the serial argument must + be equal to the number of commit requests already issued on that object. + When the client receives a done event with a serial different than the + number of past commit requests, it must proceed as normal, except it + should not change the current state of the zwp_text_input_v3 object. + </description> + <arg name="serial" type="uint"/> + </event> + </interface> + + <interface name="zwp_text_input_manager_v3" version="1"> + <description summary="text input manager"> + A factory for text-input objects. This object is a global singleton. + </description> + + <request name="destroy" type="destructor"> + <description summary="Destroy the wp_text_input_manager"> + Destroy the wp_text_input_manager object. + </description> + </request> + + <request name="get_text_input"> + <description summary="create a new text input object"> + Creates a new text-input object for a given seat. + </description> + <arg name="id" type="new_id" interface="zwp_text_input_v3"/> + <arg name="seat" type="object" interface="wl_seat"/> + </request> + </interface> +</protocol> diff --git a/protocol/virtual-keyboard-unstable-v1.xml b/protocol/virtual-keyboard-unstable-v1.xml new file mode 100644 index 00000000..5095c91b --- /dev/null +++ b/protocol/virtual-keyboard-unstable-v1.xml @@ -0,0 +1,113 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="virtual_keyboard_unstable_v1"> + <copyright> + Copyright © 2008-2011 Kristian Høgsberg + Copyright © 2010-2013 Intel Corporation + Copyright © 2012-2013 Collabora, Ltd. + Copyright © 2018 Purism SPC + + 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 (including the next + paragraph) 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. + </copyright> + + <interface name="zwp_virtual_keyboard_v1" version="1"> + <description summary="virtual keyboard"> + The virtual keyboard provides an application with requests which emulate + the behaviour of a physical keyboard. + + This interface can be used by clients on its own to provide raw input + events, or it can accompany the input method protocol. + </description> + + <request name="keymap"> + <description summary="keyboard mapping"> + Provide a file descriptor to the compositor which can be + memory-mapped to provide a keyboard mapping description. + + Format carries a value from the keymap_format enumeration. + </description> + <arg name="format" type="uint" summary="keymap format"/> + <arg name="fd" type="fd" summary="keymap file descriptor"/> + <arg name="size" type="uint" summary="keymap size, in bytes"/> + </request> + + <enum name="error"> + <entry name="no_keymap" value="0" summary="No keymap was set"/> + </enum> + + <request name="key"> + <description summary="key event"> + A key was pressed or released. + The time argument is a timestamp with millisecond granularity, with an + undefined base. All requests regarding a single object must share the + same clock. + + Keymap must be set before issuing this request. + + State carries a value from the key_state enumeration. + </description> + <arg name="time" type="uint" summary="timestamp with millisecond granularity"/> + <arg name="key" type="uint" summary="key that produced the event"/> + <arg name="state" type="uint" summary="physical state of the key"/> + </request> + + <request name="modifiers"> + <description summary="modifier and group state"> + Notifies the compositor that the modifier and/or group state has + changed, and it should update state. + + The client should use wl_keyboard.modifiers event to synchronize its + internal state with seat state. + + Keymap must be set before issuing this request. + </description> + <arg name="mods_depressed" type="uint" summary="depressed modifiers"/> + <arg name="mods_latched" type="uint" summary="latched modifiers"/> + <arg name="mods_locked" type="uint" summary="locked modifiers"/> + <arg name="group" type="uint" summary="keyboard layout"/> + </request> + + <request name="destroy" type="destructor" since="1"> + <description summary="destroy the virtual keyboard keyboard object"/> + </request> + </interface> + + <interface name="zwp_virtual_keyboard_manager_v1" version="1"> + <description summary="virtual keyboard manager"> + A virtual keyboard manager allows an application to provide keyboard + input events as if they came from a physical keyboard. + </description> + + <enum name="error"> + <entry name="unauthorized" value="0" summary="client not authorized to use the interface"/> + </enum> + + <request name="create_virtual_keyboard"> + <description summary="Create a new virtual keyboard"> + Creates a new virtual keyboard associated to a seat. + + If the compositor enables a keyboard to perform arbitrary actions, it + should present an error when an untrusted client requests a new + keyboard. + </description> + <arg name="seat" type="object" interface="wl_seat"/> + <arg name="id" type="new_id" interface="zwp_virtual_keyboard_v1"/> + </request> + </interface> +</protocol> diff --git a/protocol/wlr-export-dmabuf-unstable-v1.xml b/protocol/wlr-export-dmabuf-unstable-v1.xml new file mode 100644 index 00000000..751f7efb --- /dev/null +++ b/protocol/wlr-export-dmabuf-unstable-v1.xml @@ -0,0 +1,203 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="wlr_export_dmabuf_unstable_v1"> + <copyright> + Copyright © 2018 Rostislav Pehlivanov + + 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 (including the next + paragraph) 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. + </copyright> + + <description summary="a protocol for low overhead screen content capturing"> + An interface to capture surfaces in an efficient way by exporting DMA-BUFs. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + </description> + + <interface name="zwlr_export_dmabuf_manager_v1" version="1"> + <description summary="manager to inform clients and begin capturing"> + This object is a manager with which to start capturing from sources. + </description> + + <request name="capture_output"> + <description summary="capture a frame from an output"> + Capture the next frame of a an entire output. + </description> + <arg name="frame" type="new_id" interface="zwlr_export_dmabuf_frame_v1"/> + <arg name="overlay_cursor" type="int" + summary="include custom client hardware cursor on top of the frame"/> + <arg name="output" type="object" interface="wl_output"/> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the manager"> + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + </description> + </request> + </interface> + + <interface name="zwlr_export_dmabuf_frame_v1" version="1"> + <description summary="a DMA-BUF frame"> + This object represents a single DMA-BUF frame. + + If the capture is successful, the compositor will first send a "frame" + event, followed by one or several "object". When the frame is available + for readout, the "ready" event is sent. + + If the capture failed, the "cancel" event is sent. This can happen anytime + before the "ready" event. + + Once either a "ready" or a "cancel" event is received, the client should + destroy the frame. Once an "object" event is received, the client is + responsible for closing the associated file descriptor. + + All frames are read-only and may not be written into or altered. + </description> + + <enum name="flags"> + <description summary="frame flags"> + Special flags that should be respected by the client. + </description> + <entry name="transient" value="0x1" + summary="clients should copy frame before processing"/> + </enum> + + <event name="frame"> + <description summary="a frame description"> + Main event supplying the client with information about the frame. If the + capture didn't fail, this event is always emitted first before any other + events. + + This event is followed by a number of "object" as specified by the + "num_objects" argument. + </description> + <arg name="width" type="uint" + summary="frame width in pixels"/> + <arg name="height" type="uint" + summary="frame height in pixels"/> + <arg name="offset_x" type="uint" + summary="crop offset for the x axis"/> + <arg name="offset_y" type="uint" + summary="crop offset for the y axis"/> + <arg name="buffer_flags" type="uint" + summary="flags which indicate properties (invert, interlacing), + has the same values as zwp_linux_buffer_params_v1:flags"/> + <arg name="flags" type="uint" enum="flags" + summary="indicates special frame features"/> + <arg name="format" type="uint" + summary="format of the frame (DRM_FORMAT_*)"/> + <arg name="mod_high" type="uint" + summary="drm format modifier, high"/> + <arg name="mod_low" type="uint" + summary="drm format modifier, low"/> + <arg name="num_objects" type="uint" + summary="indicates how many objects (FDs) the frame has (max 4)"/> + </event> + + <event name="object"> + <description summary="an object description"> + Event which serves to supply the client with the file descriptors + containing the data for each object. + + After receiving this event, the client must always close the file + descriptor as soon as they're done with it and even if the frame fails. + </description> + <arg name="index" type="uint" + summary="index of the current object"/> + <arg name="fd" type="fd" + summary="fd of the current object"/> + <arg name="size" type="uint" + summary="size in bytes for the current object"/> + <arg name="offset" type="uint" + summary="starting point for the data in the object's fd"/> + <arg name="stride" type="uint" + summary="line size in bytes"/> + <arg name="plane_index" type="uint" + summary="index of the the plane the data in the object applies to"/> + </event> + + <event name="ready"> + <description summary="indicates frame is available for reading"> + This event is sent as soon as the frame is presented, indicating it is + available for reading. This event includes the time at which + presentation happened at. + + The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, + each component being an unsigned 32-bit value. Whole seconds are in + tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, + and the additional fractional part in tv_nsec as nanoseconds. Hence, + for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part + may have an arbitrary offset at start. + + After receiving this event, the client should destroy this object. + </description> + <arg name="tv_sec_hi" type="uint" + summary="high 32 bits of the seconds part of the timestamp"/> + <arg name="tv_sec_lo" type="uint" + summary="low 32 bits of the seconds part of the timestamp"/> + <arg name="tv_nsec" type="uint" + summary="nanoseconds part of the timestamp"/> + </event> + + <enum name="cancel_reason"> + <description summary="cancel reason"> + Indicates reason for cancelling the frame. + </description> + <entry name="temporary" value="0" + summary="temporary error, source will produce more frames"/> + <entry name="permanent" value="1" + summary="fatal error, source will not produce frames"/> + <entry name="resizing" value="2" + summary="temporary error, source will produce more frames"/> + </enum> + + <event name="cancel"> + <description summary="indicates the frame is no longer valid"> + If the capture failed or if the frame is no longer valid after the + "frame" event has been emitted, this event will be used to inform the + client to scrap the frame. + + If the failure is temporary, the client may capture again the same + source. If the failure is permanent, any further attempts to capture the + same source will fail again. + + After receiving this event, the client should destroy this object. + </description> + <arg name="reason" type="uint" enum="cancel_reason" + summary="indicates a reason for cancelling this frame capture"/> + </event> + + <request name="destroy" type="destructor"> + <description summary="delete this object, used or not"> + Unreferences the frame. This request must be called as soon as its no + longer used. + + It can be called at any time by the client. The client will still have + to close any FDs it has been given. + </description> + </request> + </interface> +</protocol> diff --git a/protocol/wlr-foreign-toplevel-management-unstable-v1.xml b/protocol/wlr-foreign-toplevel-management-unstable-v1.xml new file mode 100644 index 00000000..234d9535 --- /dev/null +++ b/protocol/wlr-foreign-toplevel-management-unstable-v1.xml @@ -0,0 +1,235 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="wlr_foreign_toplevel_management_unstable_v1"> + <copyright> + Copyright © 2018 Ilia Bozhinov + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + </copyright> + + <interface name="zwlr_foreign_toplevel_manager_v1" version="1"> + <description summary="list and control opened apps"> + The purpose of this protocol is to enable the creation of taskbars + and docks by providing them with a list of opened applications and + letting them request certain actions on them, like maximizing, etc. + + After a client binds the zwlr_foreign_toplevel_manager_v1, each opened + toplevel window will be sent via the toplevel event + </description> + + <event name="toplevel"> + <description summary="a toplevel has been created"> + This event is emitted whenever a new toplevel window is created. It + is emitted for all toplevels, regardless of the app that has created + them. + + All initial details of the toplevel(title, app_id, states, etc.) will + be sent immediately after this event via the corresponding events in + zwlr_foreign_toplevel_handle_v1. + </description> + <arg name="toplevel" type="new_id" interface="zwlr_foreign_toplevel_handle_v1"/> + </event> + + <request name="stop"> + <description summary="stop sending events"> + Indicates the client no longer wishes to receive events for new toplevels. + However the compositor may emit further toplevel_created events, until + the finished event is emitted. + + The client must not send any more requests after this one. + </description> + </request> + + <event name="finished"> + <description summary="the compositor has finished with the toplevel manager"> + This event indicates that the compositor is done sending events to the + zwlr_foreign_toplevel_manager_v1. The server will destroy the object + immediately after sending this request, so it will become invalid and + the client should free any resources associated with it. + </description> + </event> + </interface> + + <interface name="zwlr_foreign_toplevel_handle_v1" version="1"> + <description summary="an opened toplevel"> + A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel + window. Each app may have multiple opened toplevels. + + Each toplevel has a list of outputs it is visible on, conveyed to the + client with the output_enter and output_leave events. + </description> + + <event name="title"> + <description summary="title change"> + This event is emitted whenever the title of the toplevel changes. + </description> + <arg name="title" type="string"/> + </event> + + <event name="app_id"> + <description summary="app-id change"> + This event is emitted whenever the app-id of the toplevel changes. + </description> + <arg name="app_id" type="string"/> + </event> + + <event name="output_enter"> + <description summary="toplevel entered an output"> + This event is emitted whenever the toplevel becomes visible on + the given output. A toplevel may be visible on multiple outputs. + </description> + <arg name="output" type="object" interface="wl_output"/> + </event> + + <event name="output_leave"> + <description summary="toplevel left an output"> + This event is emitted whenever the toplevel stops being visible on + the given output. It is guaranteed that an entered-output event + with the same output has been emitted before this event. + </description> + <arg name="output" type="object" interface="wl_output"/> + </event> + + <request name="set_maximized"> + <description summary="requests that the toplevel be maximized"> + Requests that the toplevel be maximized. If the maximized state actually + changes, this will be indicated by the state event. + </description> + </request> + + <request name="unset_maximized"> + <description summary="requests that the toplevel be unmaximized"> + Requests that the toplevel be unmaximized. If the maximized state actually + changes, this will be indicated by the state event. + </description> + </request> + + <request name="set_minimized"> + <description summary="requests that the toplevel be minimized"> + Requests that the toplevel be minimized. If the minimized state actually + changes, this will be indicated by the state event. + </description> + </request> + + <request name="unset_minimized"> + <description summary="requests that the toplevel be unminimized"> + Requests that the toplevel be unminimized. If the minimized state actually + changes, this will be indicated by the state event. + </description> + </request> + + <request name="activate"> + <description summary="activate the toplevel"> + Request that this toplevel be activated on the given seat. + There is no guarantee the toplevel will be actually activated. + </description> + <arg name="seat" type="object" interface="wl_seat"/> + </request> + + <enum name="state"> + <description summary="types of states on the toplevel"> + The different states that a toplevel can have. These have the same meaning + as the states with the same names defined in xdg-toplevel + </description> + + <entry name="maximized" value="0" summary="the toplevel is maximized"/> + <entry name="minimized" value="1" summary="the toplevel is minimized"/> + <entry name="activated" value="2" summary="the toplevel is active"/> + </enum> + + <event name="state"> + <description summary="the toplevel state changed"> + This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 + is created and each time the toplevel state changes, either because of a + compositor action or because of a request in this protocol. + </description> + + <arg name="state" type="array"/> + </event> + + <event name="done"> + <description summary="all information about the toplevel has been sent"> + This event is sent after all changes in the toplevel state have been + sent. + + This allows changes to the zwlr_foreign_toplevel_handle_v1 properties + to be seen as atomic, even if they happen via multiple events. + </description> + </event> + + <request name="close"> + <description summary="request that the toplevel be closed"> + Send a request to the toplevel to close itself. The compositor would + typically use a shell-specific method to carry out this request, for + example by sending the xdg_toplevel.close event. However, this gives + no guarantees the toplevel will actually be destroyed. If and when + this happens, the zwlr_foreign_toplevel_handle_v1.closed event will + be emitted. + </description> + </request> + + <request name="set_rectangle"> + <description summary="the rectangle which represents the toplevel"> + The rectangle of the surface specified in this request corresponds to + the place where the app using this protocol represents the given toplevel. + It can be used by the compositor as a hint for some operations, e.g + minimizing. The client is however not required to set this, in which + case the compositor is free to decide some default value. + + If the client specifies more than one rectangle, only the last one is + considered. + + The dimensions are given in surface-local coordinates. + Setting width=height=0 removes the already-set rectangle. + </description> + + <arg name="surface" type="object" interface="wl_surface"/> + <arg name="x" type="int"/> + <arg name="y" type="int"/> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + </request> + + <enum name="error"> + <entry name="invalid_rectangle" value="0" + summary="the provided rectangle is invalid"/> + </enum> + + <event name="closed"> + <description summary="this toplevel has been destroyed"> + This event means the toplevel has been destroyed. It is guaranteed there + won't be any more events for this zwlr_foreign_toplevel_handle_v1. The + toplevel itself becomes inert so any requests will be ignored except the + destroy request. + </description> + </event> + + <request name="destroy" type="destructor"> + <description summary="destroy the zwlr_foreign_toplevel_handle_v1 object"> + Destroys the zwlr_foreign_toplevel_handle_v1 object. + + This request should be called either when the client does not want to + use the toplevel anymore or after the closed event to finalize the + destruction of the object. + </description> + </request> + </interface> +</protocol> diff --git a/protocol/wlr-gamma-control-unstable-v1.xml b/protocol/wlr-gamma-control-unstable-v1.xml new file mode 100644 index 00000000..f55f3fe5 --- /dev/null +++ b/protocol/wlr-gamma-control-unstable-v1.xml @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="wlr_gamma_control_unstable_v1"> + <copyright> + Copyright © 2015 Giulio camuffo + Copyright © 2018 Simon Ser + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + </copyright> + + <description summary="manage gamma tables of outputs"> + This protocol allows a privileged client to set the gamma tables for + outputs. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + </description> + + <interface name="zwlr_gamma_control_manager_v1" version="1"> + <description summary="manager to create per-output gamma controls"> + This interface is a manager that allows creating per-output gamma + controls. + </description> + + <request name="get_gamma_control"> + <description summary="get a gamma control for an output"> + Create a gamma control that can be used to adjust gamma tables for the + provided output. + </description> + <arg name="id" type="new_id" interface="zwlr_gamma_control_v1"/> + <arg name="output" type="object" interface="wl_output"/> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the manager"> + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + </description> + </request> + </interface> + + <interface name="zwlr_gamma_control_v1" version="1"> + <description summary="adjust gamma tables for an output"> + This interface allows a client to adjust gamma tables for a particular + output. + + The client will receive the gamma size, and will then be able to set gamma + tables. At any time the compositor can send a failed event indicating that + this object is no longer valid. + + There must always be at most one gamma control object per output, which + has exclusive access to this particular output. When the gamma control + object is destroyed, the gamma table is restored to its original value. + </description> + + <event name="gamma_size"> + <description summary="size of gamma ramps"> + Advertise the size of each gamma ramp. + + This event is sent immediately when the gamma control object is created. + </description> + <arg name="size" type="uint" summary="number of elements in a ramp"/> + </event> + + <enum name="error"> + <entry name="invalid_gamma" value="1" summary="invalid gamma tables"/> + </enum> + + <request name="set_gamma"> + <description summary="set the gamma table"> + Set the gamma table. The file descriptor can be memory-mapped to provide + the raw gamma table, which contains successive gamma ramps for the red, + green and blue channels. Each gamma ramp is an array of 16-byte unsigned + integers which has the same length as the gamma size. + + The file descriptor data must have the same length as three times the + gamma size. + </description> + <arg name="fd" type="fd" summary="gamma table file descriptor"/> + </request> + + <event name="failed"> + <description summary="object no longer valid"> + This event indicates that the gamma control is no longer valid. This + can happen for a number of reasons, including: + - The output doesn't support gamma tables + - Setting the gamma tables failed + - Another client already has exclusive gamma control for this output + - The compositor has transfered gamma control to another client + + Upon receiving this event, the client should destroy this object. + </description> + </event> + + <request name="destroy" type="destructor"> + <description summary="destroy this control"> + Destroys the gamma control object. If the object is still valid, this + restores the original gamma tables. + </description> + </request> + </interface> +</protocol> diff --git a/protocol/wlr-input-inhibitor-unstable-v1.xml b/protocol/wlr-input-inhibitor-unstable-v1.xml new file mode 100644 index 00000000..b62d1bb4 --- /dev/null +++ b/protocol/wlr-input-inhibitor-unstable-v1.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="wlr_input_inhibit_unstable_v1"> + <copyright> + Copyright © 2018 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + </copyright> + + <interface name="zwlr_input_inhibit_manager_v1" version="1"> + <description summary="inhibits input events to other clients"> + Clients can use this interface to prevent input events from being sent to + any surfaces but its own, which is useful for example in lock screen + software. It is assumed that access to this interface will be locked down + to whitelisted clients by the compositor. + </description> + + <request name="get_inhibitor"> + <description summary="inhibit input to other clients"> + Activates the input inhibitor. As long as the inhibitor is active, the + compositor will not send input events to other clients. + </description> + <arg name="id" type="new_id" interface="zwlr_input_inhibitor_v1"/> + </request> + + <enum name="error"> + <entry name="already_inhibited" value="0" summary="an input inhibitor is already in use on the compositor"/> + </enum> + </interface> + + <interface name="zwlr_input_inhibitor_v1" version="1"> + <description summary="inhibits input to other clients"> + While this resource exists, input to clients other than the owner of the + inhibitor resource will not receive input events. The client that owns + this resource will receive all input events normally. The compositor will + also disable all of its own input processing (such as keyboard shortcuts) + while the inhibitor is active. + + The compositor may continue to send input events to selected clients, + such as an on-screen keyboard (via the input-method protocol). + </description> + + <request name="destroy" type="destructor"> + <description summary="destroy the input inhibitor object"> + Destroy the inhibitor and allow other clients to receive input. + </description> + </request> + </interface> +</protocol> diff --git a/protocol/wlr-layer-shell-unstable-v1.xml b/protocol/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 00000000..216e0d9f --- /dev/null +++ b/protocol/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,285 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="wlr_layer_shell_unstable_v1"> + <copyright> + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + </copyright> + + <interface name="zwlr_layer_shell_v1" version="1"> + <description summary="create surfaces that are layers of the desktop"> + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + </description> + + <request name="get_layer_surface"> + <description summary="create a layer_surface from a surface"> + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + </description> + <arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/> + <arg name="surface" type="object" interface="wl_surface"/> + <arg name="output" type="object" interface="wl_output" allow-null="true"/> + <arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/> + <arg name="namespace" type="string" summary="namespace for the layer surface"/> + </request> + + <enum name="error"> + <entry name="role" value="0" summary="wl_surface has another role"/> + <entry name="invalid_layer" value="1" summary="layer value is invalid"/> + <entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/> + </enum> + + <enum name="layer"> + <description summary="available layers for surfaces"> + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + </description> + + <entry name="background" value="0"/> + <entry name="bottom" value="1"/> + <entry name="top" value="2"/> + <entry name="overlay" value="3"/> + </enum> + </interface> + + <interface name="zwlr_layer_surface_v1" version="1"> + <description summary="layer metadata interface"> + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (size, anchor, exclusive zone, margin, interactivity) + is double-buffered, and will be applied at the time wl_surface.commit of + the corresponding wl_surface is called. + </description> + + <request name="set_size"> + <description summary="sets the size of the surface"> + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + </description> + <arg name="width" type="uint"/> + <arg name="height" type="uint"/> + </request> + + <request name="set_anchor"> + <description summary="configures the anchor point of the surface"> + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthoginal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + </description> + <arg name="anchor" type="uint" enum="anchor"/> + </request> + + <request name="set_exclusive_zone"> + <description summary="configures the exclusive geometry of this surface"> + Requests that the compositor avoids occluding an area of the surface + with other surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to an + edge, rather than a corner. The zone is the number of surface-local + coordinates from the edge that are considered exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive excluzive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + </description> + <arg name="zone" type="int"/> + </request> + + <request name="set_margin"> + <description summary="sets a margin from the anchor point"> + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + </description> + <arg name="top" type="int"/> + <arg name="right" type="int"/> + <arg name="bottom" type="int"/> + <arg name="left" type="int"/> + </request> + + <request name="set_keyboard_interactivity"> + <description summary="requests keyboard events"> + Set to 1 to request that the seat send keyboard events to this layer + surface. For layers below the shell surface layer, the seat will use + normal focus semantics. For layers above the shell surface layers, the + seat will always give exclusive keyboard focus to the top-most layer + which has keyboard interactivity set to true. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Events is double-buffered, see wl_surface.commit. + </description> + <arg name="keyboard_interactivity" type="uint"/> + </request> + + <request name="get_popup"> + <description summary="assign this layer_surface as an xdg_popup parent"> + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + </description> + <arg name="popup" type="object" interface="xdg_popup"/> + </request> + + <request name="ack_configure"> + <description summary="ack a configure event"> + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + </description> + <arg name="serial" type="uint" summary="the serial from the configure event"/> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the layer_surface"> + This request destroys the layer surface. + </description> + </request> + + <event name="configure"> + <description summary="suggest a surface change"> + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + </description> + <arg name="serial" type="uint"/> + <arg name="width" type="uint"/> + <arg name="height" type="uint"/> + </event> + + <event name="closed"> + <description summary="surface should be closed"> + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + </description> + </event> + + <enum name="error"> + <entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/> + <entry name="invalid_size" value="1" summary="size is invalid"/> + <entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/> + </enum> + + <enum name="anchor" bitfield="true"> + <entry name="top" value="1" summary="the top edge of the anchor rectangle"/> + <entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/> + <entry name="left" value="4" summary="the left edge of the anchor rectangle"/> + <entry name="right" value="8" summary="the right edge of the anchor rectangle"/> + </enum> + </interface> +</protocol> diff --git a/protocol/wlr-screencopy-unstable-v1.xml b/protocol/wlr-screencopy-unstable-v1.xml new file mode 100644 index 00000000..a7a2d172 --- /dev/null +++ b/protocol/wlr-screencopy-unstable-v1.xml @@ -0,0 +1,179 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="wlr_screencopy_unstable_v1"> + <copyright> + Copyright © 2018 Simon Ser + + 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 (including the next + paragraph) 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. + </copyright> + + <description summary="screen content capturing on client buffers"> + This protocol allows clients to ask the compositor to copy part of the + screen content to a client buffer. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + </description> + + <interface name="zwlr_screencopy_manager_v1" version="1"> + <description summary="manager to inform clients and begin capturing"> + This object is a manager which offers requests to start capturing from a + source. + </description> + + <request name="capture_output"> + <description summary="capture an output"> + Capture the next frame of an entire output. + </description> + <arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/> + <arg name="overlay_cursor" type="int" + summary="composite cursor onto the frame"/> + <arg name="output" type="object" interface="wl_output"/> + </request> + + <request name="capture_output_region"> + <description summary="capture an output's region"> + Capture the next frame of an output's region. + + The region is given in output logical coordinates, see + xdg_output.logical_size. The region will be clipped to the output's + extents. + </description> + <arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/> + <arg name="overlay_cursor" type="int" + summary="composite cursor onto the frame"/> + <arg name="output" type="object" interface="wl_output"/> + <arg name="x" type="int"/> + <arg name="y" type="int"/> + <arg name="width" type="int"/> + <arg name="height" type="int"/> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the manager"> + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + </description> + </request> + </interface> + + <interface name="zwlr_screencopy_frame_v1" version="1"> + <description summary="a frame ready for copy"> + This object represents a single frame. + + When created, a "buffer" event will be sent. The client will then be able + to send a "copy" request. If the capture is successful, the compositor + will send a "flags" followed by a "ready" event. + + If the capture failed, the "failed" event is sent. This can happen anytime + before the "ready" event. + + Once either a "ready" or a "failed" event is received, the client should + destroy the frame. + </description> + + <event name="buffer"> + <description summary="buffer information"> + Provides information about the frame's buffer. This event is sent once + as soon as the frame is created. + + The client should then create a buffer with the provided attributes, and + send a "copy" request. + </description> + <arg name="format" type="uint" summary="buffer format"/> + <arg name="width" type="uint" summary="buffer width"/> + <arg name="height" type="uint" summary="buffer height"/> + <arg name="stride" type="uint" summary="buffer stride"/> + </event> + + <request name="copy"> + <description summary="copy the frame"> + Copy the frame to the supplied buffer. The buffer must have a the + correct size, see zwlr_screencopy_frame_v1.buffer. The buffer needs to + have a supported format. + + If the frame is successfully copied, a "flags" and a "ready" events are + sent. Otherwise, a "failed" event is sent. + </description> + <arg name="buffer" type="object" interface="wl_buffer"/> + </request> + + <enum name="error"> + <entry name="already_used" value="0" + summary="the object has already been used to copy a wl_buffer"/> + <entry name="invalid_buffer" value="1" + summary="buffer attributes are invalid"/> + </enum> + + <enum name="flags" bitfield="true"> + <entry name="y_invert" value="1" summary="contents are y-inverted"/> + </enum> + + <event name="flags"> + <description summary="frame flags"> + Provides flags about the frame. This event is sent once before the + "ready" event. + </description> + <arg name="flags" type="uint" enum="flags" summary="frame flags"/> + </event> + + <event name="ready"> + <description summary="indicates frame is available for reading"> + Called as soon as the frame is copied, indicating it is available + for reading. This event includes the time at which presentation happened + at. + + The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, + each component being an unsigned 32-bit value. Whole seconds are in + tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, + and the additional fractional part in tv_nsec as nanoseconds. Hence, + for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part + may have an arbitrary offset at start. + + After receiving this event, the client should destroy the object. + </description> + <arg name="tv_sec_hi" type="uint" + summary="high 32 bits of the seconds part of the timestamp"/> + <arg name="tv_sec_lo" type="uint" + summary="low 32 bits of the seconds part of the timestamp"/> + <arg name="tv_nsec" type="uint" + summary="nanoseconds part of the timestamp"/> + </event> + + <event name="failed"> + <description summary="frame copy failed"> + This event indicates that the attempted frame copy has failed. + + After receiving this event, the client should destroy the object. + </description> + </event> + + <request name="destroy" type="destructor"> + <description summary="delete this object, used or not"> + Destroys the frame. This request can be sent at any time by the client. + </description> + </request> + </interface> +</protocol> diff --git a/render/dmabuf.c b/render/dmabuf.c new file mode 100644 index 00000000..6b500748 --- /dev/null +++ b/render/dmabuf.c @@ -0,0 +1,10 @@ +#include <unistd.h> +#include <wlr/render/dmabuf.h> + +void wlr_dmabuf_attributes_finish( struct wlr_dmabuf_attributes *attribs) { + for (int i = 0; i < attribs->n_planes; ++i) { + close(attribs->fd[i]); + attribs->fd[i] = -1; + } + attribs->n_planes = 0; +} diff --git a/render/egl.c b/render/egl.c new file mode 100644 index 00000000..cc00dece --- /dev/null +++ b/render/egl.c @@ -0,0 +1,609 @@ +#include <assert.h> +#include <drm_fourcc.h> +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <stdio.h> +#include <stdlib.h> +#include <wlr/render/egl.h> +#include <wlr/util/log.h> +#include <wlr/util/region.h> +#include "glapi.h" + +static bool egl_get_config(EGLDisplay disp, EGLint *attribs, EGLConfig *out, + EGLint visual_id) { + EGLint count = 0, matched = 0, ret; + + ret = eglGetConfigs(disp, NULL, 0, &count); + if (ret == EGL_FALSE || count == 0) { + wlr_log(WLR_ERROR, "eglGetConfigs returned no configs"); + return false; + } + + EGLConfig configs[count]; + + ret = eglChooseConfig(disp, attribs, configs, count, &matched); + if (ret == EGL_FALSE) { + wlr_log(WLR_ERROR, "eglChooseConfig failed"); + return false; + } + + for (int i = 0; i < matched; ++i) { + EGLint visual; + if (!eglGetConfigAttrib(disp, configs[i], + EGL_NATIVE_VISUAL_ID, &visual)) { + continue; + } + + if (!visual_id || visual == visual_id) { + *out = configs[i]; + return true; + } + } + + wlr_log(WLR_ERROR, "no valid egl config found"); + return false; +} + +static enum wlr_log_importance egl_log_importance_to_wlr(EGLint type) { + switch (type) { + case EGL_DEBUG_MSG_CRITICAL_KHR: return WLR_ERROR; + case EGL_DEBUG_MSG_ERROR_KHR: return WLR_ERROR; + case EGL_DEBUG_MSG_WARN_KHR: return WLR_ERROR; + case EGL_DEBUG_MSG_INFO_KHR: return WLR_INFO; + default: return WLR_INFO; + } +} + +static void egl_log(EGLenum error, const char *command, EGLint msg_type, + EGLLabelKHR thread, EGLLabelKHR obj, const char *msg) { + _wlr_log(egl_log_importance_to_wlr(msg_type), "[EGL] %s: %s", command, msg); +} + +static bool check_egl_ext(const char *exts, const char *ext) { + size_t extlen = strlen(ext); + const char *end = exts + strlen(exts); + + while (exts < end) { + if (*exts == ' ') { + exts++; + continue; + } + size_t n = strcspn(exts, " "); + if (n == extlen && strncmp(ext, exts, n) == 0) { + return true; + } + exts += n; + } + return false; +} + +static void print_dmabuf_formats(struct wlr_egl *egl) { + /* Avoid log msg if extension is not present */ + if (!egl->exts.image_dmabuf_import_modifiers_ext) { + return; + } + + int *formats; + int num = wlr_egl_get_dmabuf_formats(egl, &formats); + if (num < 0) { + return; + } + + char str_formats[num * 5 + 1]; + for (int i = 0; i < num; i++) { + snprintf(&str_formats[i*5], (num - i) * 5 + 1, "%.4s ", + (char*)&formats[i]); + } + wlr_log(WLR_DEBUG, "Supported dmabuf buffer formats: %s", str_formats); + free(formats); +} + +bool wlr_egl_init(struct wlr_egl *egl, EGLenum platform, void *remote_display, + EGLint *config_attribs, EGLint visual_id) { + if (!load_glapi()) { + return false; + } + + if (eglDebugMessageControlKHR) { + static const EGLAttrib debug_attribs[] = { + EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE, + EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE, + EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE, + EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE, + EGL_NONE, + }; + eglDebugMessageControlKHR(egl_log, debug_attribs); + } + + if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) { + wlr_log(WLR_ERROR, "Failed to bind to the OpenGL ES API"); + goto error; + } + + if (platform == EGL_PLATFORM_SURFACELESS_MESA) { + assert(remote_display == NULL); + egl->display = eglGetPlatformDisplayEXT(platform, EGL_DEFAULT_DISPLAY, NULL); + } else { + egl->display = eglGetPlatformDisplayEXT(platform, remote_display, NULL); + } + if (egl->display == EGL_NO_DISPLAY) { + wlr_log(WLR_ERROR, "Failed to create EGL display"); + goto error; + } + + egl->platform = platform; + + EGLint major, minor; + if (eglInitialize(egl->display, &major, &minor) == EGL_FALSE) { + wlr_log(WLR_ERROR, "Failed to initialize EGL"); + goto error; + } + + if (!egl_get_config(egl->display, config_attribs, &egl->config, visual_id)) { + wlr_log(WLR_ERROR, "Failed to get EGL config"); + goto error; + } + + egl->exts_str = eglQueryString(egl->display, EGL_EXTENSIONS); + + wlr_log(WLR_INFO, "Using EGL %d.%d", (int)major, (int)minor); + wlr_log(WLR_INFO, "Supported EGL extensions: %s", egl->exts_str); + wlr_log(WLR_INFO, "EGL vendor: %s", eglQueryString(egl->display, EGL_VENDOR)); + + egl->exts.image_base_khr = + check_egl_ext(egl->exts_str, "EGL_KHR_image_base") + && eglCreateImageKHR && eglDestroyImageKHR; + + egl->exts.buffer_age_ext = + check_egl_ext(egl->exts_str, "EGL_EXT_buffer_age"); + egl->exts.swap_buffers_with_damage_ext = + (check_egl_ext(egl->exts_str, "EGL_EXT_swap_buffers_with_damage") && + eglSwapBuffersWithDamageEXT); + egl->exts.swap_buffers_with_damage_khr = + (check_egl_ext(egl->exts_str, "EGL_KHR_swap_buffers_with_damage") && + eglSwapBuffersWithDamageKHR); + + egl->exts.image_dmabuf_import_ext = + check_egl_ext(egl->exts_str, "EGL_EXT_image_dma_buf_import"); + egl->exts.image_dmabuf_import_modifiers_ext = + check_egl_ext(egl->exts_str, "EGL_EXT_image_dma_buf_import_modifiers") + && eglQueryDmaBufFormatsEXT && eglQueryDmaBufModifiersEXT; + + egl->exts.image_dma_buf_export_mesa = + check_egl_ext(egl->exts_str, "EGL_MESA_image_dma_buf_export") && + eglExportDMABUFImageQueryMESA && eglExportDMABUFImageMESA; + + print_dmabuf_formats(egl); + + egl->exts.bind_wayland_display_wl = + check_egl_ext(egl->exts_str, "EGL_WL_bind_wayland_display") + && eglBindWaylandDisplayWL && eglUnbindWaylandDisplayWL + && eglQueryWaylandBufferWL; + + bool ext_context_priority = + check_egl_ext(egl->exts_str, "EGL_IMG_context_priority"); + + size_t atti = 0; + EGLint attribs[5]; + attribs[atti++] = EGL_CONTEXT_CLIENT_VERSION; + attribs[atti++] = 2; + + // On DRM, request a high priority context if possible + bool request_high_priority = ext_context_priority && + platform == EGL_PLATFORM_GBM_MESA; + + // Try to reschedule all of our rendering to be completed first. If it + // fails, it will fallback to the default priority (MEDIUM). + if (request_high_priority) { + attribs[atti++] = EGL_CONTEXT_PRIORITY_LEVEL_IMG; + attribs[atti++] = EGL_CONTEXT_PRIORITY_HIGH_IMG; + } + + attribs[atti++] = EGL_NONE; + assert(atti <= sizeof(attribs)/sizeof(attribs[0])); + + egl->context = eglCreateContext(egl->display, egl->config, + EGL_NO_CONTEXT, attribs); + if (egl->context == EGL_NO_CONTEXT) { + wlr_log(WLR_ERROR, "Failed to create EGL context"); + goto error; + } + + if (request_high_priority) { + EGLint priority = EGL_CONTEXT_PRIORITY_MEDIUM_IMG; + eglQueryContext(egl->display, egl->context, + EGL_CONTEXT_PRIORITY_LEVEL_IMG, &priority); + if (priority != EGL_CONTEXT_PRIORITY_HIGH_IMG) { + wlr_log(WLR_INFO, "Failed to obtain a high priority context"); + } else { + wlr_log(WLR_DEBUG, "Obtained high priority context"); + } + } + + if (!eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, + egl->context)) { + wlr_log(WLR_ERROR, "Failed to make EGL context current"); + goto error; + } + + return true; + +error: + eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (egl->display) { + eglTerminate(egl->display); + } + eglReleaseThread(); + return false; +} + +void wlr_egl_finish(struct wlr_egl *egl) { + if (egl == NULL) { + return; + } + + eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (egl->wl_display) { + assert(egl->exts.bind_wayland_display_wl); + eglUnbindWaylandDisplayWL(egl->display, egl->wl_display); + } + + eglDestroyContext(egl->display, egl->context); + eglTerminate(egl->display); + eglReleaseThread(); +} + +bool wlr_egl_bind_display(struct wlr_egl *egl, struct wl_display *local_display) { + if (!egl->exts.bind_wayland_display_wl) { + return false; + } + + if (eglBindWaylandDisplayWL(egl->display, local_display)) { + egl->wl_display = local_display; + return true; + } + + return false; +} + +bool wlr_egl_destroy_image(struct wlr_egl *egl, EGLImage image) { + if (!egl->exts.image_base_khr) { + return false; + } + if (!image) { + return true; + } + return eglDestroyImageKHR(egl->display, image); +} + +EGLSurface wlr_egl_create_surface(struct wlr_egl *egl, void *window) { + assert(eglCreatePlatformWindowSurfaceEXT); + EGLSurface surf = eglCreatePlatformWindowSurfaceEXT(egl->display, + egl->config, window, NULL); + if (surf == EGL_NO_SURFACE) { + wlr_log(WLR_ERROR, "Failed to create EGL surface"); + return EGL_NO_SURFACE; + } + return surf; +} + +static int egl_get_buffer_age(struct wlr_egl *egl, EGLSurface surface) { + if (!egl->exts.buffer_age_ext) { + return -1; + } + + EGLint buffer_age; + EGLBoolean ok = eglQuerySurface(egl->display, surface, + EGL_BUFFER_AGE_EXT, &buffer_age); + if (!ok) { + wlr_log(WLR_ERROR, "Failed to get EGL surface buffer age"); + return -1; + } + + return buffer_age; +} + +bool wlr_egl_make_current(struct wlr_egl *egl, EGLSurface surface, + int *buffer_age) { + if (!eglMakeCurrent(egl->display, surface, surface, egl->context)) { + wlr_log(WLR_ERROR, "eglMakeCurrent failed"); + return false; + } + + if (buffer_age != NULL) { + *buffer_age = egl_get_buffer_age(egl, surface); + } + return true; +} + +bool wlr_egl_is_current(struct wlr_egl *egl) { + return eglGetCurrentContext() == egl->context; +} + +bool wlr_egl_swap_buffers(struct wlr_egl *egl, EGLSurface surface, + pixman_region32_t *damage) { + // Never block when swapping buffers on Wayland + if (egl->platform == EGL_PLATFORM_WAYLAND_EXT) { + eglSwapInterval(egl->display, 0); + } + + EGLBoolean ret; + if (damage != NULL && (egl->exts.swap_buffers_with_damage_ext || + egl->exts.swap_buffers_with_damage_khr)) { + EGLint width = 0, height = 0; + eglQuerySurface(egl->display, surface, EGL_WIDTH, &width); + eglQuerySurface(egl->display, surface, EGL_HEIGHT, &height); + + pixman_region32_t flipped_damage; + pixman_region32_init(&flipped_damage); + wlr_region_transform(&flipped_damage, damage, + WL_OUTPUT_TRANSFORM_FLIPPED_180, width, height); + + int nrects; + pixman_box32_t *rects = + pixman_region32_rectangles(&flipped_damage, &nrects); + EGLint egl_damage[4 * nrects]; + for (int i = 0; i < nrects; ++i) { + egl_damage[4*i] = rects[i].x1; + egl_damage[4*i + 1] = rects[i].y1; + egl_damage[4*i + 2] = rects[i].x2 - rects[i].x1; + egl_damage[4*i + 3] = rects[i].y2 - rects[i].y1; + } + + pixman_region32_fini(&flipped_damage); + + if (egl->exts.swap_buffers_with_damage_ext) { + ret = eglSwapBuffersWithDamageEXT(egl->display, surface, egl_damage, + nrects); + } else { + ret = eglSwapBuffersWithDamageKHR(egl->display, surface, egl_damage, + nrects); + } + } else { + ret = eglSwapBuffers(egl->display, surface); + } + + if (!ret) { + wlr_log(WLR_ERROR, "eglSwapBuffers failed"); + return false; + } + return true; +} + +EGLImageKHR wlr_egl_create_image_from_wl_drm(struct wlr_egl *egl, + struct wl_resource *data, EGLint *fmt, int *width, int *height, + bool *inverted_y) { + if (!egl->exts.bind_wayland_display_wl || !egl->exts.image_base_khr) { + return NULL; + } + + if (!eglQueryWaylandBufferWL(egl->display, data, EGL_TEXTURE_FORMAT, fmt)) { + return NULL; + } + + eglQueryWaylandBufferWL(egl->display, data, EGL_WIDTH, width); + eglQueryWaylandBufferWL(egl->display, data, EGL_HEIGHT, height); + + EGLint _inverted_y; + if (eglQueryWaylandBufferWL(egl->display, data, EGL_WAYLAND_Y_INVERTED_WL, + &_inverted_y)) { + *inverted_y = !!_inverted_y; + } else { + *inverted_y = false; + } + + const EGLint attribs[] = { + EGL_WAYLAND_PLANE_WL, 0, + EGL_NONE, + }; + return eglCreateImageKHR(egl->display, egl->context, EGL_WAYLAND_BUFFER_WL, + data, attribs); +} + +EGLImageKHR wlr_egl_create_image_from_dmabuf(struct wlr_egl *egl, + struct wlr_dmabuf_attributes *attributes) { + if (!egl->exts.image_base_khr || !egl->exts.image_dmabuf_import_ext) { + wlr_log(WLR_ERROR, "dmabuf import extension not present"); + return NULL; + } + + bool has_modifier = false; + + // we assume the same way we assumed formats without the import_modifiers + // extension that mod_linear is supported. The special mod mod_invalid + // is sometimes used to signal modifier unawareness which is what we + // have here + if (attributes->modifier != DRM_FORMAT_MOD_INVALID && + attributes->modifier != DRM_FORMAT_MOD_LINEAR) { + if (!egl->exts.image_dmabuf_import_modifiers_ext) { + wlr_log(WLR_ERROR, "dmabuf modifiers extension not present"); + return NULL; + } + has_modifier = true; + } + + unsigned int atti = 0; + EGLint attribs[50]; + attribs[atti++] = EGL_WIDTH; + attribs[atti++] = attributes->width; + attribs[atti++] = EGL_HEIGHT; + attribs[atti++] = attributes->height; + attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT; + attribs[atti++] = attributes->format; + + struct { + EGLint fd; + EGLint offset; + EGLint pitch; + EGLint mod_lo; + EGLint mod_hi; + } attr_names[WLR_DMABUF_MAX_PLANES] = { + { + EGL_DMA_BUF_PLANE0_FD_EXT, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, + EGL_DMA_BUF_PLANE0_PITCH_EXT, + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT + }, { + EGL_DMA_BUF_PLANE1_FD_EXT, + EGL_DMA_BUF_PLANE1_OFFSET_EXT, + EGL_DMA_BUF_PLANE1_PITCH_EXT, + EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT + }, { + EGL_DMA_BUF_PLANE2_FD_EXT, + EGL_DMA_BUF_PLANE2_OFFSET_EXT, + EGL_DMA_BUF_PLANE2_PITCH_EXT, + EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT + }, { + EGL_DMA_BUF_PLANE3_FD_EXT, + EGL_DMA_BUF_PLANE3_OFFSET_EXT, + EGL_DMA_BUF_PLANE3_PITCH_EXT, + EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT + } + }; + + for (int i=0; i < attributes->n_planes; i++) { + attribs[atti++] = attr_names[i].fd; + attribs[atti++] = attributes->fd[i]; + attribs[atti++] = attr_names[i].offset; + attribs[atti++] = attributes->offset[i]; + attribs[atti++] = attr_names[i].pitch; + attribs[atti++] = attributes->stride[i]; + if (has_modifier) { + attribs[atti++] = attr_names[i].mod_lo; + attribs[atti++] = attributes->modifier & 0xFFFFFFFF; + attribs[atti++] = attr_names[i].mod_hi; + attribs[atti++] = attributes->modifier >> 32; + } + } + attribs[atti++] = EGL_NONE; + assert(atti < sizeof(attribs)/sizeof(attribs[0])); + + return eglCreateImageKHR(egl->display, EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, NULL, attribs); +} + +int wlr_egl_get_dmabuf_formats(struct wlr_egl *egl, + int **formats) { + if (!egl->exts.image_dmabuf_import_ext) { + wlr_log(WLR_DEBUG, "dmabuf import extension not present"); + return -1; + } + + // when we only have the image_dmabuf_import extension we can't query + // which formats are supported. These two are on almost always + // supported; it's the intended way to just try to create buffers. + // Just a guess but better than not supporting dmabufs at all, + // given that the modifiers extension isn't supported everywhere. + if (!egl->exts.image_dmabuf_import_modifiers_ext) { + static const int fallback_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + }; + static unsigned num = sizeof(fallback_formats) / + sizeof(fallback_formats[0]); + + *formats = calloc(num, sizeof(int)); + if (!*formats) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return -1; + } + + memcpy(*formats, fallback_formats, num * sizeof(**formats)); + return num; + } + + EGLint num; + if (!eglQueryDmaBufFormatsEXT(egl->display, 0, NULL, &num)) { + wlr_log(WLR_ERROR, "failed to query number of dmabuf formats"); + return -1; + } + + *formats = calloc(num, sizeof(int)); + if (*formats == NULL) { + wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno)); + return -1; + } + + if (!eglQueryDmaBufFormatsEXT(egl->display, num, *formats, &num)) { + wlr_log(WLR_ERROR, "failed to query dmabuf format"); + free(*formats); + return -1; + } + return num; +} + +int wlr_egl_get_dmabuf_modifiers(struct wlr_egl *egl, + int format, uint64_t **modifiers) { + if (!egl->exts.image_dmabuf_import_ext) { + wlr_log(WLR_DEBUG, "dmabuf extension not present"); + return -1; + } + + if(!egl->exts.image_dmabuf_import_modifiers_ext) { + *modifiers = NULL; + return 0; + } + + EGLint num; + if (!eglQueryDmaBufModifiersEXT(egl->display, format, 0, + NULL, NULL, &num)) { + wlr_log(WLR_ERROR, "failed to query dmabuf number of modifiers"); + return -1; + } + + *modifiers = calloc(num, sizeof(uint64_t)); + if (*modifiers == NULL) { + wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno)); + return -1; + } + + if (!eglQueryDmaBufModifiersEXT(egl->display, format, num, + *modifiers, NULL, &num)) { + wlr_log(WLR_ERROR, "failed to query dmabuf modifiers"); + free(*modifiers); + return -1; + } + return num; +} + +bool wlr_egl_export_image_to_dmabuf(struct wlr_egl *egl, EGLImageKHR image, + int32_t width, int32_t height, uint32_t flags, + struct wlr_dmabuf_attributes *attribs) { + memset(attribs, 0, sizeof(struct wlr_dmabuf_attributes)); + + if (!egl->exts.image_dma_buf_export_mesa) { + return false; + } + + // Only one set of modifiers is returned for all planes + if (!eglExportDMABUFImageQueryMESA(egl->display, image, + (int *)&attribs->format, &attribs->n_planes, &attribs->modifier)) { + return false; + } + if (attribs->n_planes > WLR_DMABUF_MAX_PLANES) { + wlr_log(WLR_ERROR, "EGL returned %d planes, but only %d are supported", + attribs->n_planes, WLR_DMABUF_MAX_PLANES); + return false; + } + + if (!eglExportDMABUFImageMESA(egl->display, image, attribs->fd, + (EGLint *)attribs->stride, (EGLint *)attribs->offset)) { + return false; + } + + attribs->width = width; + attribs->height = height; + attribs->flags = flags; + return true; +} + +bool wlr_egl_destroy_surface(struct wlr_egl *egl, EGLSurface surface) { + if (!surface) { + return true; + } + return eglDestroySurface(egl->display, surface); +} diff --git a/render/glapi.txt b/render/glapi.txt new file mode 100644 index 00000000..b1166f27 --- /dev/null +++ b/render/glapi.txt @@ -0,0 +1,19 @@ +eglGetPlatformDisplayEXT +eglCreatePlatformWindowSurfaceEXT +-eglCreateImageKHR +-eglDestroyImageKHR +-eglQueryWaylandBufferWL +-eglBindWaylandDisplayWL +-eglUnbindWaylandDisplayWL +-glEGLImageTargetTexture2DOES +-eglSwapBuffersWithDamageEXT +-eglSwapBuffersWithDamageKHR +-eglQueryDmaBufFormatsEXT +-eglQueryDmaBufModifiersEXT +-eglExportDMABUFImageQueryMESA +-eglExportDMABUFImageMESA +-eglDebugMessageControlKHR +-glDebugMessageCallbackKHR +-glDebugMessageControlKHR +-glPopDebugGroupKHR +-glPushDebugGroupKHR diff --git a/render/gles2/pixel_format.c b/render/gles2/pixel_format.c new file mode 100644 index 00000000..a4b4c101 --- /dev/null +++ b/render/gles2/pixel_format.c @@ -0,0 +1,78 @@ +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include "render/gles2.h" + +/* + * The wayland formats are little endian while the GL formats are big endian, + * so WL_SHM_FORMAT_ARGB8888 is actually compatible with GL_BGRA_EXT. + */ +static const struct wlr_gles2_pixel_format formats[] = { + { + .wl_format = WL_SHM_FORMAT_ARGB8888, + .depth = 32, + .bpp = 32, + .gl_format = GL_BGRA_EXT, + .gl_type = GL_UNSIGNED_BYTE, + .has_alpha = true, + }, + { + .wl_format = WL_SHM_FORMAT_XRGB8888, + .depth = 24, + .bpp = 32, + .gl_format = GL_BGRA_EXT, + .gl_type = GL_UNSIGNED_BYTE, + .has_alpha = false, + }, + { + .wl_format = WL_SHM_FORMAT_XBGR8888, + .depth = 24, + .bpp = 32, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_BYTE, + .has_alpha = false, + }, + { + .wl_format = WL_SHM_FORMAT_ABGR8888, + .depth = 32, + .bpp = 32, + .gl_format = GL_RGBA, + .gl_type = GL_UNSIGNED_BYTE, + .has_alpha = true, + }, +}; + +static const enum wl_shm_format wl_formats[] = { + WL_SHM_FORMAT_ARGB8888, + WL_SHM_FORMAT_XRGB8888, + WL_SHM_FORMAT_ABGR8888, + WL_SHM_FORMAT_XBGR8888, +}; + +// TODO: more pixel formats + +const struct wlr_gles2_pixel_format *get_gles2_format_from_wl( + enum wl_shm_format fmt) { + for (size_t i = 0; i < sizeof(formats) / sizeof(*formats); ++i) { + if (formats[i].wl_format == fmt) { + return &formats[i]; + } + } + return NULL; +} + +const struct wlr_gles2_pixel_format *get_gles2_format_from_gl( + GLint gl_format, GLint gl_type, bool alpha) { + for (size_t i = 0; i < sizeof(formats) / sizeof(*formats); ++i) { + if (formats[i].gl_format == gl_format && + formats[i].gl_type == gl_type && + formats[i].has_alpha == alpha) { + return &formats[i]; + } + } + return NULL; +} + +const enum wl_shm_format *get_gles2_wl_formats(size_t *len) { + *len = sizeof(wl_formats) / sizeof(wl_formats[0]); + return wl_formats; +} diff --git a/render/gles2/renderer.c b/render/gles2/renderer.c new file mode 100644 index 00000000..50689ad4 --- /dev/null +++ b/render/gles2/renderer.c @@ -0,0 +1,655 @@ +#include <assert.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <wayland-server-protocol.h> +#include <wayland-util.h> +#include <wlr/render/egl.h> +#include <wlr/render/interface.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/util/log.h> +#include "glapi.h" +#include "render/gles2.h" + +static const struct wlr_renderer_impl renderer_impl; + +static struct wlr_gles2_renderer *gles2_get_renderer( + struct wlr_renderer *wlr_renderer) { + assert(wlr_renderer->impl == &renderer_impl); + return (struct wlr_gles2_renderer *)wlr_renderer; +} + +static struct wlr_gles2_renderer *gles2_get_renderer_in_context( + struct wlr_renderer *wlr_renderer) { + struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); + assert(wlr_egl_is_current(renderer->egl)); + return renderer; +} + +static void gles2_begin(struct wlr_renderer *wlr_renderer, uint32_t width, + uint32_t height) { + struct wlr_gles2_renderer *renderer = + gles2_get_renderer_in_context(wlr_renderer); + + PUSH_GLES2_DEBUG; + + glViewport(0, 0, width, height); + renderer->viewport_width = width; + renderer->viewport_height = height; + + // enable transparency + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + // XXX: maybe we should save output projection and remove some of the need + // for users to sling matricies themselves + + POP_GLES2_DEBUG; +} + +static void gles2_end(struct wlr_renderer *wlr_renderer) { + gles2_get_renderer_in_context(wlr_renderer); + // no-op +} + +static void gles2_clear(struct wlr_renderer *wlr_renderer, + const float color[static 4]) { + gles2_get_renderer_in_context(wlr_renderer); + + PUSH_GLES2_DEBUG; + glClearColor(color[0], color[1], color[2], color[3]); + glClear(GL_COLOR_BUFFER_BIT); + POP_GLES2_DEBUG; +} + +static void gles2_scissor(struct wlr_renderer *wlr_renderer, + struct wlr_box *box) { + struct wlr_gles2_renderer *renderer = + gles2_get_renderer_in_context(wlr_renderer); + + PUSH_GLES2_DEBUG; + if (box != NULL) { + struct wlr_box gl_box; + wlr_box_transform(&gl_box, box, WL_OUTPUT_TRANSFORM_FLIPPED_180, + renderer->viewport_width, renderer->viewport_height); + + glScissor(gl_box.x, gl_box.y, gl_box.width, gl_box.height); + glEnable(GL_SCISSOR_TEST); + } else { + glDisable(GL_SCISSOR_TEST); + } + POP_GLES2_DEBUG; +} + +static void draw_quad(void) { + GLfloat verts[] = { + 1, 0, // top right + 0, 0, // top left + 1, 1, // bottom right + 0, 1, // bottom left + }; + GLfloat texcoord[] = { + 1, 0, // top right + 0, 0, // top left + 1, 1, // bottom right + 0, 1, // bottom left + }; + + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, verts); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, texcoord); + + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); +} + +static bool gles2_render_texture_with_matrix(struct wlr_renderer *wlr_renderer, + struct wlr_texture *wlr_texture, const float matrix[static 9], + float alpha) { + struct wlr_gles2_renderer *renderer = + gles2_get_renderer_in_context(wlr_renderer); + struct wlr_gles2_texture *texture = + gles2_get_texture(wlr_texture); + + struct wlr_gles2_tex_shader *shader = NULL; + GLenum target = 0; + + switch (texture->type) { + case WLR_GLES2_TEXTURE_GLTEX: + case WLR_GLES2_TEXTURE_WL_DRM_GL: + if (texture->has_alpha) { + shader = &renderer->shaders.tex_rgba; + } else { + shader = &renderer->shaders.tex_rgbx; + } + target = GL_TEXTURE_2D; + break; + case WLR_GLES2_TEXTURE_WL_DRM_EXT: + case WLR_GLES2_TEXTURE_DMABUF: + shader = &renderer->shaders.tex_ext; + target = GL_TEXTURE_EXTERNAL_OES; + + if (!renderer->exts.egl_image_external_oes) { + wlr_log(WLR_ERROR, "Failed to render texture: " + "GL_TEXTURE_EXTERNAL_OES not supported"); + return false; + } + break; + } + + // OpenGL ES 2 requires the glUniformMatrix3fv transpose parameter to be set + // to GL_FALSE + float transposition[9]; + wlr_matrix_transpose(transposition, matrix); + + PUSH_GLES2_DEBUG; + + GLuint tex_id = texture->type == WLR_GLES2_TEXTURE_GLTEX ? + texture->gl_tex : texture->image_tex; + glActiveTexture(GL_TEXTURE0); + glBindTexture(target, tex_id); + + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glUseProgram(shader->program); + + glUniformMatrix3fv(shader->proj, 1, GL_FALSE, transposition); + glUniform1i(shader->invert_y, texture->inverted_y); + glUniform1i(shader->tex, 0); + glUniform1f(shader->alpha, alpha); + + draw_quad(); + + POP_GLES2_DEBUG; + return true; +} + + +static void gles2_render_quad_with_matrix(struct wlr_renderer *wlr_renderer, + const float color[static 4], const float matrix[static 9]) { + struct wlr_gles2_renderer *renderer = + gles2_get_renderer_in_context(wlr_renderer); + + // OpenGL ES 2 requires the glUniformMatrix3fv transpose parameter to be set + // to GL_FALSE + float transposition[9]; + wlr_matrix_transpose(transposition, matrix); + + PUSH_GLES2_DEBUG; + glUseProgram(renderer->shaders.quad.program); + + glUniformMatrix3fv(renderer->shaders.quad.proj, 1, GL_FALSE, transposition); + glUniform4f(renderer->shaders.quad.color, color[0], color[1], color[2], color[3]); + draw_quad(); + POP_GLES2_DEBUG; +} + +static void gles2_render_ellipse_with_matrix(struct wlr_renderer *wlr_renderer, + const float color[static 4], const float matrix[static 9]) { + struct wlr_gles2_renderer *renderer = + gles2_get_renderer_in_context(wlr_renderer); + + // OpenGL ES 2 requires the glUniformMatrix3fv transpose parameter to be set + // to GL_FALSE + float transposition[9]; + wlr_matrix_transpose(transposition, matrix); + + PUSH_GLES2_DEBUG; + glUseProgram(renderer->shaders.ellipse.program); + + glUniformMatrix3fv(renderer->shaders.ellipse.proj, 1, GL_FALSE, transposition); + glUniform4f(renderer->shaders.ellipse.color, color[0], color[1], color[2], color[3]); + draw_quad(); + POP_GLES2_DEBUG; +} + +static const enum wl_shm_format *gles2_renderer_formats( + struct wlr_renderer *wlr_renderer, size_t *len) { + return get_gles2_wl_formats(len); +} + +static bool gles2_format_supported(struct wlr_renderer *wlr_renderer, + enum wl_shm_format wl_fmt) { + return get_gles2_format_from_wl(wl_fmt) != NULL; +} + +static bool gles2_resource_is_wl_drm_buffer(struct wlr_renderer *wlr_renderer, + struct wl_resource *resource) { + struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); + + if (!eglQueryWaylandBufferWL) { + return false; + } + + EGLint fmt; + return eglQueryWaylandBufferWL(renderer->egl->display, resource, + EGL_TEXTURE_FORMAT, &fmt); +} + +static void gles2_wl_drm_buffer_get_size(struct wlr_renderer *wlr_renderer, + struct wl_resource *buffer, int *width, int *height) { + struct wlr_gles2_renderer *renderer = + gles2_get_renderer(wlr_renderer); + + if (!eglQueryWaylandBufferWL) { + return; + } + + eglQueryWaylandBufferWL(renderer->egl->display, buffer, EGL_WIDTH, width); + eglQueryWaylandBufferWL(renderer->egl->display, buffer, EGL_HEIGHT, height); +} + +static int gles2_get_dmabuf_formats(struct wlr_renderer *wlr_renderer, + int **formats) { + struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); + return wlr_egl_get_dmabuf_formats(renderer->egl, formats); +} + +static int gles2_get_dmabuf_modifiers(struct wlr_renderer *wlr_renderer, + int format, uint64_t **modifiers) { + struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); + return wlr_egl_get_dmabuf_modifiers(renderer->egl, format, modifiers); +} + +static enum wl_shm_format gles2_preferred_read_format( + struct wlr_renderer *wlr_renderer) { + struct wlr_gles2_renderer *renderer = + gles2_get_renderer_in_context(wlr_renderer); + + GLint gl_format = -1, gl_type = -1; + PUSH_GLES2_DEBUG; + glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &gl_format); + glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &gl_type); + POP_GLES2_DEBUG; + + EGLint alpha_size = -1; + eglGetConfigAttrib(renderer->egl->display, renderer->egl->config, + EGL_ALPHA_SIZE, &alpha_size); + + const struct wlr_gles2_pixel_format *fmt = + get_gles2_format_from_gl(gl_format, gl_type, alpha_size > 0); + if (fmt != NULL) { + return fmt->wl_format; + } + + if (renderer->exts.read_format_bgra_ext) { + return WL_SHM_FORMAT_XRGB8888; + } + return WL_SHM_FORMAT_XBGR8888; +} + +static bool gles2_read_pixels(struct wlr_renderer *wlr_renderer, + enum wl_shm_format wl_fmt, uint32_t *flags, 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) { + struct wlr_gles2_renderer *renderer = + gles2_get_renderer_in_context(wlr_renderer); + + const struct wlr_gles2_pixel_format *fmt = get_gles2_format_from_wl(wl_fmt); + if (fmt == NULL) { + wlr_log(WLR_ERROR, "Cannot read pixels: unsupported pixel format"); + return false; + } + + if (fmt->gl_format == GL_BGRA_EXT && !renderer->exts.read_format_bgra_ext) { + wlr_log(WLR_ERROR, + "Cannot read pixels: missing GL_EXT_read_format_bgra extension"); + return false; + } + + PUSH_GLES2_DEBUG; + + // Make sure any pending drawing is finished before we try to read it + glFinish(); + + glGetError(); // Clear the error flag + + unsigned char *p = data + dst_y * stride; + uint32_t pack_stride = width * fmt->bpp / 8; + if (pack_stride == stride && dst_x == 0 && flags != NULL) { + // Under these particular conditions, we can read the pixels with only + // one glReadPixels call + glReadPixels(src_x, renderer->viewport_height - height - src_y, + width, height, fmt->gl_format, fmt->gl_type, p); + *flags = WLR_RENDERER_READ_PIXELS_Y_INVERT; + } else { + // Unfortunately GLES2 doesn't support GL_PACK_*, so we have to read + // the lines out row by row + for (size_t i = src_y; i < src_y + height; ++i) { + glReadPixels(src_x, src_y + height - i - 1, width, 1, fmt->gl_format, + fmt->gl_type, p + i * stride + dst_x * fmt->bpp / 8); + } + if (flags != NULL) { + *flags = 0; + } + } + + POP_GLES2_DEBUG; + + return glGetError() == GL_NO_ERROR; +} + +static struct wlr_texture *gles2_texture_from_pixels( + struct wlr_renderer *wlr_renderer, enum wl_shm_format wl_fmt, + uint32_t stride, uint32_t width, uint32_t height, const void *data) { + struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); + return wlr_gles2_texture_from_pixels(renderer->egl, wl_fmt, stride, width, + height, data); +} + +static struct wlr_texture *gles2_texture_from_wl_drm( + struct wlr_renderer *wlr_renderer, struct wl_resource *data) { + struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); + return wlr_gles2_texture_from_wl_drm(renderer->egl, data); +} + +static struct wlr_texture *gles2_texture_from_dmabuf( + struct wlr_renderer *wlr_renderer, + struct wlr_dmabuf_attributes *attribs) { + struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); + return wlr_gles2_texture_from_dmabuf(renderer->egl, attribs); +} + +static void gles2_init_wl_display(struct wlr_renderer *wlr_renderer, + struct wl_display *wl_display) { + struct wlr_gles2_renderer *renderer = + gles2_get_renderer(wlr_renderer); + if (!wlr_egl_bind_display(renderer->egl, wl_display)) { + wlr_log(WLR_INFO, "failed to bind wl_display to EGL"); + } +} + +static void gles2_destroy(struct wlr_renderer *wlr_renderer) { + struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); + + wlr_egl_make_current(renderer->egl, EGL_NO_SURFACE, NULL); + + PUSH_GLES2_DEBUG; + glDeleteProgram(renderer->shaders.quad.program); + glDeleteProgram(renderer->shaders.ellipse.program); + glDeleteProgram(renderer->shaders.tex_rgba.program); + glDeleteProgram(renderer->shaders.tex_rgbx.program); + glDeleteProgram(renderer->shaders.tex_ext.program); + POP_GLES2_DEBUG; + + if (renderer->exts.debug_khr) { + glDisable(GL_DEBUG_OUTPUT_KHR); + glDebugMessageCallbackKHR(NULL, NULL); + } + + free(renderer); +} + +static const struct wlr_renderer_impl renderer_impl = { + .destroy = gles2_destroy, + .begin = gles2_begin, + .end = gles2_end, + .clear = gles2_clear, + .scissor = gles2_scissor, + .render_texture_with_matrix = gles2_render_texture_with_matrix, + .render_quad_with_matrix = gles2_render_quad_with_matrix, + .render_ellipse_with_matrix = gles2_render_ellipse_with_matrix, + .formats = gles2_renderer_formats, + .format_supported = gles2_format_supported, + .resource_is_wl_drm_buffer = gles2_resource_is_wl_drm_buffer, + .wl_drm_buffer_get_size = gles2_wl_drm_buffer_get_size, + .get_dmabuf_formats = gles2_get_dmabuf_formats, + .get_dmabuf_modifiers = gles2_get_dmabuf_modifiers, + .preferred_read_format = gles2_preferred_read_format, + .read_pixels = gles2_read_pixels, + .texture_from_pixels = gles2_texture_from_pixels, + .texture_from_wl_drm = gles2_texture_from_wl_drm, + .texture_from_dmabuf = gles2_texture_from_dmabuf, + .init_wl_display = gles2_init_wl_display, +}; + +void push_gles2_marker(const char *file, const char *func) { + if (!glPushDebugGroupKHR) { + return; + } + + int len = snprintf(NULL, 0, "%s:%s", file, func) + 1; + char str[len]; + snprintf(str, len, "%s:%s", file, func); + glPushDebugGroupKHR(GL_DEBUG_SOURCE_APPLICATION_KHR, 1, -1, str); +} + +void pop_gles2_marker(void) { + if (glPopDebugGroupKHR) { + glPopDebugGroupKHR(); + } +} + +static enum wlr_log_importance gles2_log_importance_to_wlr(GLenum type) { + switch (type) { + case GL_DEBUG_TYPE_ERROR_KHR: return WLR_ERROR; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR: return WLR_DEBUG; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR: return WLR_ERROR; + case GL_DEBUG_TYPE_PORTABILITY_KHR: return WLR_DEBUG; + case GL_DEBUG_TYPE_PERFORMANCE_KHR: return WLR_DEBUG; + case GL_DEBUG_TYPE_OTHER_KHR: return WLR_DEBUG; + case GL_DEBUG_TYPE_MARKER_KHR: return WLR_DEBUG; + case GL_DEBUG_TYPE_PUSH_GROUP_KHR: return WLR_DEBUG; + case GL_DEBUG_TYPE_POP_GROUP_KHR: return WLR_DEBUG; + default: return WLR_DEBUG; + } +} + +static void gles2_log(GLenum src, GLenum type, GLuint id, GLenum severity, + GLsizei len, const GLchar *msg, const void *user) { + _wlr_log(gles2_log_importance_to_wlr(type), "[GLES2] %s", msg); +} + +static GLuint compile_shader(GLuint type, const GLchar *src) { + PUSH_GLES2_DEBUG; + + GLuint shader = glCreateShader(type); + glShaderSource(shader, 1, &src, NULL); + glCompileShader(shader); + + GLint ok; + glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); + if (ok == GL_FALSE) { + glDeleteShader(shader); + shader = 0; + } + + POP_GLES2_DEBUG; + return shader; +} + +static GLuint link_program(const GLchar *vert_src, const GLchar *frag_src) { + PUSH_GLES2_DEBUG; + + GLuint vert = compile_shader(GL_VERTEX_SHADER, vert_src); + if (!vert) { + goto error; + } + + GLuint frag = compile_shader(GL_FRAGMENT_SHADER, frag_src); + if (!frag) { + glDeleteShader(vert); + goto error; + } + + GLuint prog = glCreateProgram(); + glAttachShader(prog, vert); + glAttachShader(prog, frag); + glLinkProgram(prog); + + glDetachShader(prog, vert); + glDetachShader(prog, frag); + glDeleteShader(vert); + glDeleteShader(frag); + + GLint ok; + glGetProgramiv(prog, GL_LINK_STATUS, &ok); + if (ok == GL_FALSE) { + glDeleteProgram(prog); + goto error; + } + + POP_GLES2_DEBUG; + return prog; + +error: + POP_GLES2_DEBUG; + return 0; +} + +static bool check_gl_ext(const char *exts, const char *ext) { + size_t extlen = strlen(ext); + const char *end = exts + strlen(exts); + + while (exts < end) { + if (exts[0] == ' ') { + exts++; + continue; + } + size_t n = strcspn(exts, " "); + if (n == extlen && strncmp(ext, exts, n) == 0) { + return true; + } + exts += n; + } + return false; +} + +extern const GLchar quad_vertex_src[]; +extern const GLchar quad_fragment_src[]; +extern const GLchar ellipse_fragment_src[]; +extern const GLchar tex_vertex_src[]; +extern const GLchar tex_fragment_src_rgba[]; +extern const GLchar tex_fragment_src_rgbx[]; +extern const GLchar tex_fragment_src_external[]; + +struct wlr_renderer *wlr_gles2_renderer_create(struct wlr_egl *egl) { + if (!load_glapi()) { + return NULL; + } + + struct wlr_gles2_renderer *renderer = + calloc(1, sizeof(struct wlr_gles2_renderer)); + if (renderer == NULL) { + return NULL; + } + wlr_renderer_init(&renderer->wlr_renderer, &renderer_impl); + + renderer->egl = egl; + if (!wlr_egl_make_current(renderer->egl, EGL_NO_SURFACE, NULL)) { + free(renderer); + return NULL; + } + + renderer->exts_str = (const char *)glGetString(GL_EXTENSIONS); + wlr_log(WLR_INFO, "Using %s", glGetString(GL_VERSION)); + wlr_log(WLR_INFO, "GL vendor: %s", glGetString(GL_VENDOR)); + wlr_log(WLR_INFO, "Supported GLES2 extensions: %s", renderer->exts_str); + + if (!check_gl_ext(renderer->exts_str, "GL_EXT_texture_format_BGRA8888")) { + wlr_log(WLR_ERROR, "BGRA8888 format not supported by GLES2"); + free(renderer); + return NULL; + } + + renderer->exts.read_format_bgra_ext = + check_gl_ext(renderer->exts_str, "GL_EXT_read_format_bgra"); + renderer->exts.debug_khr = + check_gl_ext(renderer->exts_str, "GL_KHR_debug") && + glDebugMessageCallbackKHR && glDebugMessageControlKHR; + renderer->exts.egl_image_external_oes = + check_gl_ext(renderer->exts_str, "GL_OES_EGL_image_external") && + glEGLImageTargetTexture2DOES; + + if (renderer->exts.debug_khr) { + glEnable(GL_DEBUG_OUTPUT_KHR); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR); + glDebugMessageCallbackKHR(gles2_log, NULL); + + // Silence unwanted message types + glDebugMessageControlKHR(GL_DONT_CARE, GL_DEBUG_TYPE_POP_GROUP_KHR, + GL_DONT_CARE, 0, NULL, GL_FALSE); + glDebugMessageControlKHR(GL_DONT_CARE, GL_DEBUG_TYPE_PUSH_GROUP_KHR, + GL_DONT_CARE, 0, NULL, GL_FALSE); + } + + PUSH_GLES2_DEBUG; + + GLuint prog; + renderer->shaders.quad.program = prog = + link_program(quad_vertex_src, quad_fragment_src); + if (!renderer->shaders.quad.program) { + goto error; + } + renderer->shaders.quad.proj = glGetUniformLocation(prog, "proj"); + renderer->shaders.quad.color = glGetUniformLocation(prog, "color"); + + renderer->shaders.ellipse.program = prog = + link_program(quad_vertex_src, ellipse_fragment_src); + if (!renderer->shaders.ellipse.program) { + goto error; + } + renderer->shaders.ellipse.proj = glGetUniformLocation(prog, "proj"); + renderer->shaders.ellipse.color = glGetUniformLocation(prog, "color"); + + renderer->shaders.tex_rgba.program = prog = + link_program(tex_vertex_src, tex_fragment_src_rgba); + if (!renderer->shaders.tex_rgba.program) { + goto error; + } + renderer->shaders.tex_rgba.proj = glGetUniformLocation(prog, "proj"); + renderer->shaders.tex_rgba.invert_y = glGetUniformLocation(prog, "invert_y"); + renderer->shaders.tex_rgba.tex = glGetUniformLocation(prog, "tex"); + renderer->shaders.tex_rgba.alpha = glGetUniformLocation(prog, "alpha"); + + renderer->shaders.tex_rgbx.program = prog = + link_program(tex_vertex_src, tex_fragment_src_rgbx); + if (!renderer->shaders.tex_rgbx.program) { + goto error; + } + renderer->shaders.tex_rgbx.proj = glGetUniformLocation(prog, "proj"); + renderer->shaders.tex_rgbx.invert_y = glGetUniformLocation(prog, "invert_y"); + renderer->shaders.tex_rgbx.tex = glGetUniformLocation(prog, "tex"); + renderer->shaders.tex_rgbx.alpha = glGetUniformLocation(prog, "alpha"); + + if (renderer->exts.egl_image_external_oes) { + renderer->shaders.tex_ext.program = prog = + link_program(tex_vertex_src, tex_fragment_src_external); + if (!renderer->shaders.tex_ext.program) { + goto error; + } + renderer->shaders.tex_ext.proj = glGetUniformLocation(prog, "proj"); + renderer->shaders.tex_ext.invert_y = glGetUniformLocation(prog, "invert_y"); + renderer->shaders.tex_ext.tex = glGetUniformLocation(prog, "tex"); + renderer->shaders.tex_ext.alpha = glGetUniformLocation(prog, "alpha"); + } + + POP_GLES2_DEBUG; + + return &renderer->wlr_renderer; + +error: + glDeleteProgram(renderer->shaders.quad.program); + glDeleteProgram(renderer->shaders.ellipse.program); + glDeleteProgram(renderer->shaders.tex_rgba.program); + glDeleteProgram(renderer->shaders.tex_rgbx.program); + glDeleteProgram(renderer->shaders.tex_ext.program); + + POP_GLES2_DEBUG; + + if (renderer->exts.debug_khr) { + glDisable(GL_DEBUG_OUTPUT_KHR); + glDebugMessageCallbackKHR(NULL, NULL); + } + + free(renderer); + return NULL; +} diff --git a/render/gles2/shaders.c b/render/gles2/shaders.c new file mode 100644 index 00000000..01410d87 --- /dev/null +++ b/render/gles2/shaders.c @@ -0,0 +1,88 @@ +#include <GLES2/gl2.h> +#include "render/gles2.h" + +// Colored quads +const GLchar quad_vertex_src[] = +"uniform mat3 proj;\n" +"uniform vec4 color;\n" +"attribute vec2 pos;\n" +"attribute vec2 texcoord;\n" +"varying vec4 v_color;\n" +"varying vec2 v_texcoord;\n" +"\n" +"void main() {\n" +" gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);\n" +" v_color = color;\n" +" v_texcoord = texcoord;\n" +"}\n"; + +const GLchar quad_fragment_src[] = +"precision mediump float;\n" +"varying vec4 v_color;\n" +"varying vec2 v_texcoord;\n" +"\n" +"void main() {\n" +" gl_FragColor = v_color;\n" +"}\n"; + +// Colored ellipses +const GLchar ellipse_fragment_src[] = +"precision mediump float;\n" +"varying vec4 v_color;\n" +"varying vec2 v_texcoord;\n" +"\n" +"void main() {\n" +" float l = length(v_texcoord - vec2(0.5, 0.5));\n" +" if (l > 0.5) {\n" +" discard;\n" +" }\n" +" gl_FragColor = v_color;\n" +"}\n"; + +// Textured quads +const GLchar tex_vertex_src[] = +"uniform mat3 proj;\n" +"uniform bool invert_y;\n" +"attribute vec2 pos;\n" +"attribute vec2 texcoord;\n" +"varying vec2 v_texcoord;\n" +"\n" +"void main() {\n" +" gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);\n" +" if (invert_y) {\n" +" v_texcoord = vec2(texcoord.s, 1.0 - texcoord.t);\n" +" } else {\n" +" v_texcoord = texcoord;\n" +" }\n" +"}\n"; + +const GLchar tex_fragment_src_rgba[] = +"precision mediump float;\n" +"varying vec2 v_texcoord;\n" +"uniform sampler2D tex;\n" +"uniform float alpha;\n" +"\n" +"void main() {\n" +" gl_FragColor = texture2D(tex, v_texcoord) * alpha;\n" +"}\n"; + +const GLchar tex_fragment_src_rgbx[] = +"precision mediump float;\n" +"varying vec2 v_texcoord;\n" +"uniform sampler2D tex;\n" +"uniform float alpha;\n" +"\n" +"void main() {\n" +" gl_FragColor = vec4(texture2D(tex, v_texcoord).rgb, 1.0) * alpha;\n" +"}\n"; + +const GLchar tex_fragment_src_external[] = +"#extension GL_OES_EGL_image_external : require\n\n" +"precision mediump float;\n" +"varying vec2 v_texcoord;\n" +"uniform samplerExternalOES texture0;\n" +"uniform float alpha;\n" +"\n" +"void main() {\n" +" gl_FragColor = texture2D(texture0, v_texcoord) * alpha;\n" +"}\n"; diff --git a/render/gles2/texture.c b/render/gles2/texture.c new file mode 100644 index 00000000..d035841e --- /dev/null +++ b/render/gles2/texture.c @@ -0,0 +1,304 @@ +#include <assert.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <stdint.h> +#include <stdlib.h> +#include <wayland-server-protocol.h> +#include <wayland-util.h> +#include <wlr/render/wlr_texture.h> +#include <wlr/render/egl.h> +#include <wlr/render/interface.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/util/log.h> +#include "glapi.h" +#include "render/gles2.h" +#include "util/signal.h" + +static const struct wlr_texture_impl texture_impl; + +struct wlr_gles2_texture *gles2_get_texture( + struct wlr_texture *wlr_texture) { + assert(wlr_texture->impl == &texture_impl); + return (struct wlr_gles2_texture *)wlr_texture; +} + +struct wlr_gles2_texture *get_gles2_texture_in_context( + struct wlr_texture *wlr_texture) { + struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture); + if (!wlr_egl_is_current(texture->egl)) { + wlr_egl_make_current(texture->egl, EGL_NO_SURFACE, NULL); + } + return texture; +} + +static void gles2_texture_get_size(struct wlr_texture *wlr_texture, int *width, + int *height) { + struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture); + *width = texture->width; + *height = texture->height; +} + +static bool gles2_texture_is_opaque(struct wlr_texture *wlr_texture) { + struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture); + return !texture->has_alpha; +} + +static bool gles2_texture_write_pixels(struct wlr_texture *wlr_texture, + 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, + const void *data) { + struct wlr_gles2_texture *texture = + get_gles2_texture_in_context(wlr_texture); + + if (texture->type != WLR_GLES2_TEXTURE_GLTEX) { + wlr_log(WLR_ERROR, "Cannot write pixels to immutable texture"); + return false; + } + + const struct wlr_gles2_pixel_format *fmt = + get_gles2_format_from_wl(texture->wl_format); + assert(fmt); + + // TODO: what if the unpack subimage extension isn't supported? + PUSH_GLES2_DEBUG; + + glBindTexture(GL_TEXTURE_2D, texture->gl_tex); + + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / (fmt->bpp / 8)); + glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, src_x); + glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, src_y); + + glTexSubImage2D(GL_TEXTURE_2D, 0, dst_x, dst_y, width, height, + fmt->gl_format, fmt->gl_type, data); + + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); + glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0); + + POP_GLES2_DEBUG; + return true; +} + +static bool gles2_texture_to_dmabuf(struct wlr_texture *wlr_texture, + struct wlr_dmabuf_attributes *attribs) { + struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture); + + if (!texture->image) { + assert(texture->type == WLR_GLES2_TEXTURE_GLTEX); + + if (!eglCreateImageKHR) { + return false; + } + + texture->image = eglCreateImageKHR(texture->egl->display, + texture->egl->context, EGL_GL_TEXTURE_2D_KHR, + (EGLClientBuffer)(uintptr_t)texture->gl_tex, NULL); + if (texture->image == EGL_NO_IMAGE_KHR) { + return false; + } + } + + uint32_t flags = 0; + if (texture->inverted_y) { + flags |= WLR_DMABUF_ATTRIBUTES_FLAGS_Y_INVERT; + } + + return wlr_egl_export_image_to_dmabuf(texture->egl, texture->image, + texture->width, texture->height, flags, attribs); +} + +static void gles2_texture_destroy(struct wlr_texture *wlr_texture) { + if (wlr_texture == NULL) { + return; + } + + struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture); + + wlr_egl_make_current(texture->egl, EGL_NO_SURFACE, NULL); + + PUSH_GLES2_DEBUG; + + if (texture->image_tex) { + glDeleteTextures(1, &texture->image_tex); + } + wlr_egl_destroy_image(texture->egl, texture->image); + + if (texture->type == WLR_GLES2_TEXTURE_GLTEX) { + glDeleteTextures(1, &texture->gl_tex); + } + + POP_GLES2_DEBUG; + + free(texture); +} + +static const struct wlr_texture_impl texture_impl = { + .get_size = gles2_texture_get_size, + .is_opaque = gles2_texture_is_opaque, + .write_pixels = gles2_texture_write_pixels, + .to_dmabuf = gles2_texture_to_dmabuf, + .destroy = gles2_texture_destroy, +}; + +struct wlr_texture *wlr_gles2_texture_from_pixels(struct wlr_egl *egl, + enum wl_shm_format wl_fmt, uint32_t stride, uint32_t width, + uint32_t height, const void *data) { + if (!wlr_egl_is_current(egl)) { + wlr_egl_make_current(egl, EGL_NO_SURFACE, NULL); + } + + const struct wlr_gles2_pixel_format *fmt = get_gles2_format_from_wl(wl_fmt); + if (fmt == NULL) { + wlr_log(WLR_ERROR, "Unsupported pixel format %"PRIu32, wl_fmt); + return NULL; + } + + struct wlr_gles2_texture *texture = + calloc(1, sizeof(struct wlr_gles2_texture)); + if (texture == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + return NULL; + } + wlr_texture_init(&texture->wlr_texture, &texture_impl); + texture->egl = egl; + texture->width = width; + texture->height = height; + texture->type = WLR_GLES2_TEXTURE_GLTEX; + texture->has_alpha = fmt->has_alpha; + texture->wl_format = fmt->wl_format; + + PUSH_GLES2_DEBUG; + + glGenTextures(1, &texture->gl_tex); + glBindTexture(GL_TEXTURE_2D, texture->gl_tex); + + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / (fmt->bpp / 8)); + glTexImage2D(GL_TEXTURE_2D, 0, fmt->gl_format, width, height, 0, + fmt->gl_format, fmt->gl_type, data); + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); + + POP_GLES2_DEBUG; + return &texture->wlr_texture; +} + +struct wlr_texture *wlr_gles2_texture_from_wl_drm(struct wlr_egl *egl, + struct wl_resource *data) { + if (!wlr_egl_is_current(egl)) { + wlr_egl_make_current(egl, EGL_NO_SURFACE, NULL); + } + + if (!glEGLImageTargetTexture2DOES) { + return NULL; + } + + struct wlr_gles2_texture *texture = + calloc(1, sizeof(struct wlr_gles2_texture)); + if (texture == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + return NULL; + } + wlr_texture_init(&texture->wlr_texture, &texture_impl); + texture->egl = egl; + texture->wl_drm = data; + + EGLint fmt; + texture->wl_format = 0xFFFFFFFF; // texture can't be written anyways + texture->image = wlr_egl_create_image_from_wl_drm(egl, data, &fmt, + &texture->width, &texture->height, &texture->inverted_y); + if (texture->image == NULL) { + free(texture); + return NULL; + } + + GLenum target; + switch (fmt) { + case EGL_TEXTURE_RGB: + case EGL_TEXTURE_RGBA: + target = GL_TEXTURE_2D; + texture->type = WLR_GLES2_TEXTURE_WL_DRM_GL; + texture->has_alpha = fmt == EGL_TEXTURE_RGBA; + break; + case EGL_TEXTURE_EXTERNAL_WL: + target = GL_TEXTURE_EXTERNAL_OES; + texture->type = WLR_GLES2_TEXTURE_WL_DRM_EXT; + texture->has_alpha = true; + break; + default: + wlr_log(WLR_ERROR, "Invalid or unsupported EGL buffer format"); + free(texture); + return NULL; + } + + PUSH_GLES2_DEBUG; + + glGenTextures(1, &texture->image_tex); + glBindTexture(target, texture->image_tex); + glEGLImageTargetTexture2DOES(target, texture->image); + + POP_GLES2_DEBUG; + return &texture->wlr_texture; +} + +#ifndef DRM_FORMAT_BIG_ENDIAN +#define DRM_FORMAT_BIG_ENDIAN 0x80000000 +#endif + +struct wlr_texture *wlr_gles2_texture_from_dmabuf(struct wlr_egl *egl, + struct wlr_dmabuf_attributes *attribs) { + if (!wlr_egl_is_current(egl)) { + wlr_egl_make_current(egl, EGL_NO_SURFACE, NULL); + } + + if (!glEGLImageTargetTexture2DOES) { + return NULL; + } + + if (!egl->exts.image_dmabuf_import_ext) { + wlr_log(WLR_ERROR, "Cannot create DMA-BUF texture: EGL extension " + "unavailable"); + return NULL; + } + + switch (attribs->format & ~DRM_FORMAT_BIG_ENDIAN) { + case WL_SHM_FORMAT_YUYV: + case WL_SHM_FORMAT_YVYU: + case WL_SHM_FORMAT_UYVY: + case WL_SHM_FORMAT_VYUY: + case WL_SHM_FORMAT_AYUV: + // TODO: YUV based formats not yet supported, require multiple images + return false; + default: + break; + } + + struct wlr_gles2_texture *texture = + calloc(1, sizeof(struct wlr_gles2_texture)); + if (texture == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + return NULL; + } + wlr_texture_init(&texture->wlr_texture, &texture_impl); + texture->egl = egl; + texture->width = attribs->width; + texture->height = attribs->height; + texture->type = WLR_GLES2_TEXTURE_DMABUF; + texture->has_alpha = true; + texture->wl_format = 0xFFFFFFFF; // texture can't be written anyways + texture->inverted_y = + (attribs->flags & WLR_DMABUF_ATTRIBUTES_FLAGS_Y_INVERT) != 0; + + texture->image = wlr_egl_create_image_from_dmabuf(egl, attribs); + if (texture->image == NULL) { + free(texture); + return NULL; + } + + PUSH_GLES2_DEBUG; + + glGenTextures(1, &texture->image_tex); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture->image_tex); + glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, texture->image); + + POP_GLES2_DEBUG; + return &texture->wlr_texture; +} diff --git a/render/gles2/util.c b/render/gles2/util.c new file mode 100644 index 00000000..3ac777ee --- /dev/null +++ b/render/gles2/util.c @@ -0,0 +1,38 @@ +#include <GLES2/gl2.h> +#include <stdbool.h> +#include <stdlib.h> +#include <wlr/util/log.h> +#include "render/gles2.h" + +const char *gles2_strerror(GLenum err) { + switch (err) { + case GL_INVALID_ENUM: + return "Invalid enum"; + case GL_INVALID_VALUE: + return "Invalid value"; + case GL_INVALID_OPERATION: + return "Invalid operation"; + case GL_OUT_OF_MEMORY: + return "Out of memory"; + case GL_INVALID_FRAMEBUFFER_OPERATION: + return "Invalid framebuffer operation"; + default: + return "Unknown error"; + } +} + +bool _gles2_flush_errors(const char *file, int line) { + GLenum err; + bool failure = false; + while ((err = glGetError()) != GL_NO_ERROR) { + failure = true; + if (err == GL_OUT_OF_MEMORY) { + // The OpenGL context is now undefined + _wlr_log(WLR_ERROR, "[%s:%d] Fatal GL error: out of memory", file, line); + exit(1); + } else { + _wlr_log(WLR_ERROR, "[%s:%d] GL error %d %s", file, line, err, gles2_strerror(err)); + } + } + return failure; +} diff --git a/render/meson.build b/render/meson.build new file mode 100644 index 00000000..e45ea90b --- /dev/null +++ b/render/meson.build @@ -0,0 +1,37 @@ +glgen = find_program('../glgen.sh') + +glapi = custom_target( + 'glapi', + input: 'glapi.txt', + output: ['@BASENAME@.c', '@BASENAME@.h'], + command: [glgen, '@INPUT@', '@OUTDIR@'], +) + +lib_wlr_render = static_library( + 'wlr_render', + files( + 'dmabuf.c', + 'egl.c', + 'gles2/pixel_format.c', + 'gles2/renderer.c', + 'gles2/shaders.c', + 'gles2/texture.c', + 'gles2/util.c', + 'wlr_renderer.c', + 'wlr_texture.c', + ), + glapi, + include_directories: wlr_inc, + dependencies: [ + egl, + drm.partial_dependency(compile_args: true), # <drm_fourcc.h> + glesv2, + pixman, + wayland_server + ], +) + +wlr_render = declare_dependency( + link_with: lib_wlr_render, + sources: glapi[1], +) diff --git a/render/wlr_renderer.c b/render/wlr_renderer.c new file mode 100644 index 00000000..58731d7f --- /dev/null +++ b/render/wlr_renderer.c @@ -0,0 +1,226 @@ +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <wlr/render/gles2.h> +#include <wlr/render/interface.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_linux_dmabuf_v1.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/util/log.h> +#include "util/signal.h" + +void wlr_renderer_init(struct wlr_renderer *renderer, + const struct wlr_renderer_impl *impl) { + assert(impl->begin); + assert(impl->clear); + assert(impl->scissor); + assert(impl->render_texture_with_matrix); + assert(impl->render_quad_with_matrix); + assert(impl->render_ellipse_with_matrix); + assert(impl->formats); + assert(impl->format_supported); + assert(impl->texture_from_pixels); + renderer->impl = impl; + + wl_signal_init(&renderer->events.destroy); +} + +void wlr_renderer_destroy(struct wlr_renderer *r) { + if (!r) { + return; + } + wlr_signal_emit_safe(&r->events.destroy, r); + + if (r->impl && r->impl->destroy) { + r->impl->destroy(r); + } else { + free(r); + } +} + +void wlr_renderer_begin(struct wlr_renderer *r, int width, int height) { + r->impl->begin(r, width, height); +} + +void wlr_renderer_end(struct wlr_renderer *r) { + if (r->impl->end) { + r->impl->end(r); + } +} + +void wlr_renderer_clear(struct wlr_renderer *r, const float color[static 4]) { + r->impl->clear(r, color); +} + +void wlr_renderer_scissor(struct wlr_renderer *r, struct wlr_box *box) { + r->impl->scissor(r, box); +} + +bool wlr_render_texture(struct wlr_renderer *r, struct wlr_texture *texture, + const float projection[static 9], int x, int y, float alpha) { + struct wlr_box box = { .x = x, .y = y }; + wlr_texture_get_size(texture, &box.width, &box.height); + + float matrix[9]; + wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0, + projection); + + return wlr_render_texture_with_matrix(r, texture, matrix, alpha); +} + +bool wlr_render_texture_with_matrix(struct wlr_renderer *r, + struct wlr_texture *texture, const float matrix[static 9], + float alpha) { + return r->impl->render_texture_with_matrix(r, texture, matrix, alpha); +} + +void wlr_render_rect(struct wlr_renderer *r, const struct wlr_box *box, + const float color[static 4], const float projection[static 9]) { + float matrix[9]; + wlr_matrix_project_box(matrix, box, WL_OUTPUT_TRANSFORM_NORMAL, 0, + projection); + + wlr_render_quad_with_matrix(r, color, matrix); +} + +void wlr_render_quad_with_matrix(struct wlr_renderer *r, + const float color[static 4], const float matrix[static 9]) { + r->impl->render_quad_with_matrix(r, color, matrix); +} + +void wlr_render_ellipse(struct wlr_renderer *r, const struct wlr_box *box, + const float color[static 4], const float projection[static 9]) { + float matrix[9]; + wlr_matrix_project_box(matrix, box, WL_OUTPUT_TRANSFORM_NORMAL, 0, + projection); + + wlr_render_ellipse_with_matrix(r, color, matrix); +} + +void wlr_render_ellipse_with_matrix(struct wlr_renderer *r, + const float color[static 4], const float matrix[static 9]) { + r->impl->render_ellipse_with_matrix(r, color, matrix); +} + +const enum wl_shm_format *wlr_renderer_get_formats( + struct wlr_renderer *r, size_t *len) { + return r->impl->formats(r, len); +} + +bool wlr_renderer_resource_is_wl_drm_buffer(struct wlr_renderer *r, + struct wl_resource *resource) { + if (!r->impl->resource_is_wl_drm_buffer) { + return false; + } + return r->impl->resource_is_wl_drm_buffer(r, resource); +} + +void wlr_renderer_wl_drm_buffer_get_size(struct wlr_renderer *r, + struct wl_resource *buffer, int *width, int *height) { + if (!r->impl->wl_drm_buffer_get_size) { + return; + } + return r->impl->wl_drm_buffer_get_size(r, buffer, width, height); +} + +int wlr_renderer_get_dmabuf_formats(struct wlr_renderer *r, + int **formats) { + if (!r->impl->get_dmabuf_formats) { + return -1; + } + return r->impl->get_dmabuf_formats(r, formats); +} + +int wlr_renderer_get_dmabuf_modifiers(struct wlr_renderer *r, int format, + uint64_t **modifiers) { + if (!r->impl->get_dmabuf_modifiers) { + return -1; + } + return r->impl->get_dmabuf_modifiers(r, format, modifiers); +} + +bool wlr_renderer_read_pixels(struct wlr_renderer *r, enum wl_shm_format fmt, + uint32_t *flags, 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) { + if (!r->impl->read_pixels) { + return false; + } + return r->impl->read_pixels(r, fmt, flags, stride, width, height, + src_x, src_y, dst_x, dst_y, data); +} + +bool wlr_renderer_format_supported(struct wlr_renderer *r, + enum wl_shm_format fmt) { + return r->impl->format_supported(r, fmt); +} + +void wlr_renderer_init_wl_display(struct wlr_renderer *r, + struct wl_display *wl_display) { + if (wl_display_init_shm(wl_display)) { + wlr_log(WLR_ERROR, "Failed to initialize shm"); + return; + } + + size_t len; + const enum wl_shm_format *formats = wlr_renderer_get_formats(r, &len); + if (formats == NULL) { + wlr_log(WLR_ERROR, "Failed to initialize shm: cannot get formats"); + return; + } + + for (size_t i = 0; i < len; ++i) { + // These formats are already added by default + if (formats[i] != WL_SHM_FORMAT_ARGB8888 && + formats[i] != WL_SHM_FORMAT_XRGB8888) { + wl_display_add_shm_format(wl_display, formats[i]); + } + } + + if (r->impl->texture_from_dmabuf) { + wlr_linux_dmabuf_v1_create(wl_display, r); + } + + if (r->impl->init_wl_display) { + r->impl->init_wl_display(r, wl_display); + } +} + +struct wlr_renderer *wlr_renderer_autocreate(struct wlr_egl *egl, + EGLenum platform, void *remote_display, EGLint *config_attribs, + EGLint visual_id) { + // Append GLES2-specific bits to the provided EGL config attributes + EGLint gles2_config_attribs[] = { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE, + }; + + size_t config_attribs_len = 0; // not including terminating EGL_NONE + while (config_attribs != NULL && + config_attribs[config_attribs_len] != EGL_NONE) { + ++config_attribs_len; + } + + size_t all_config_attribs_len = config_attribs_len + + sizeof(gles2_config_attribs) / sizeof(gles2_config_attribs[0]); + EGLint all_config_attribs[all_config_attribs_len]; + if (config_attribs_len > 0) { + memcpy(all_config_attribs, config_attribs, + config_attribs_len * sizeof(EGLint)); + } + memcpy(&all_config_attribs[config_attribs_len], gles2_config_attribs, + sizeof(gles2_config_attribs)); + + if (!wlr_egl_init(egl, platform, remote_display, all_config_attribs, + visual_id)) { + wlr_log(WLR_ERROR, "Could not initialize EGL"); + return NULL; + } + + struct wlr_renderer *renderer = wlr_gles2_renderer_create(egl); + if (!renderer) { + wlr_egl_finish(egl); + } + + return renderer; +} diff --git a/render/wlr_texture.c b/render/wlr_texture.c new file mode 100644 index 00000000..833032c9 --- /dev/null +++ b/render/wlr_texture.c @@ -0,0 +1,71 @@ +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <wlr/render/interface.h> +#include <wlr/render/wlr_texture.h> + +void wlr_texture_init(struct wlr_texture *texture, + const struct wlr_texture_impl *impl) { + assert(impl->get_size); + assert(impl->write_pixels); + texture->impl = impl; +} + +void wlr_texture_destroy(struct wlr_texture *texture) { + if (texture && texture->impl && texture->impl->destroy) { + texture->impl->destroy(texture); + } else { + free(texture); + } +} + +struct wlr_texture *wlr_texture_from_pixels(struct wlr_renderer *renderer, + enum wl_shm_format wl_fmt, uint32_t stride, uint32_t width, + uint32_t height, const void *data) { + return renderer->impl->texture_from_pixels(renderer, wl_fmt, stride, width, + height, data); +} + +struct wlr_texture *wlr_texture_from_wl_drm(struct wlr_renderer *renderer, + struct wl_resource *data) { + if (!renderer->impl->texture_from_wl_drm) { + return NULL; + } + return renderer->impl->texture_from_wl_drm(renderer, data); +} + +struct wlr_texture *wlr_texture_from_dmabuf(struct wlr_renderer *renderer, + struct wlr_dmabuf_attributes *attribs) { + if (!renderer->impl->texture_from_dmabuf) { + return NULL; + } + return renderer->impl->texture_from_dmabuf(renderer, attribs); +} + +void wlr_texture_get_size(struct wlr_texture *texture, int *width, + int *height) { + return texture->impl->get_size(texture, width, height); +} + +bool wlr_texture_is_opaque(struct wlr_texture *texture) { + if (!texture->impl->is_opaque) { + return false; + } + return texture->impl->is_opaque(texture); +} + +bool wlr_texture_write_pixels(struct wlr_texture *texture, + 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, + const void *data) { + return texture->impl->write_pixels(texture, stride, width, height, + src_x, src_y, dst_x, dst_y, data); +} + +bool wlr_texture_to_dmabuf(struct wlr_texture *texture, + struct wlr_dmabuf_attributes *attribs) { + if (!texture->impl->to_dmabuf) { + return false; + } + return texture->impl->to_dmabuf(texture, attribs); +} diff --git a/rootston/bindings.c b/rootston/bindings.c new file mode 100644 index 00000000..9fdbb33b --- /dev/null +++ b/rootston/bindings.c @@ -0,0 +1,107 @@ +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <wlr/util/log.h> +#include "rootston/bindings.h" + +static bool outputs_enabled = true; + +static const char *exec_prefix = "exec "; + +static void double_fork_shell_cmd(const char *shell_cmd) { + pid_t pid = fork(); + if (pid < 0) { + wlr_log(WLR_ERROR, "cannot execute binding command: fork() failed"); + return; + } + + if (pid == 0) { + pid = fork(); + if (pid == 0) { + execl("/bin/sh", "/bin/sh", "-c", shell_cmd, NULL); + _exit(EXIT_FAILURE); + } else { + _exit(pid == -1); + } + } + + int status; + while (waitpid(pid, &status, 0) < 0) { + if (errno == EINTR) { + continue; + } + wlr_log_errno(WLR_ERROR, "waitpid() on first child failed"); + return; + } + + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + return; + } + + wlr_log(WLR_ERROR, "first child failed to fork command"); +} + +void execute_binding_command(struct roots_seat *seat, + struct roots_input *input, const char *command) { + if (strcmp(command, "exit") == 0) { + wl_display_terminate(input->server->wl_display); + } else if (strcmp(command, "close") == 0) { + struct roots_view *focus = roots_seat_get_focus(seat); + if (focus != NULL) { + view_close(focus); + } + } else if (strcmp(command, "fullscreen") == 0) { + struct roots_view *focus = roots_seat_get_focus(seat); + if (focus != NULL) { + bool is_fullscreen = focus->fullscreen_output != NULL; + view_set_fullscreen(focus, !is_fullscreen, NULL); + } + } else if (strcmp(command, "next_window") == 0) { + roots_seat_cycle_focus(seat); + } else if (strcmp(command, "alpha") == 0) { + struct roots_view *focus = roots_seat_get_focus(seat); + if (focus != NULL) { + view_cycle_alpha(focus); + } + } else if (strncmp(exec_prefix, command, strlen(exec_prefix)) == 0) { + const char *shell_cmd = command + strlen(exec_prefix); + double_fork_shell_cmd(shell_cmd); + } else if (strcmp(command, "maximize") == 0) { + struct roots_view *focus = roots_seat_get_focus(seat); + if (focus != NULL) { + view_maximize(focus, !focus->maximized); + } + } else if (strcmp(command, "nop") == 0) { + wlr_log(WLR_DEBUG, "nop command"); + } else if (strcmp(command, "toggle_outputs") == 0) { + outputs_enabled = !outputs_enabled; + struct roots_output *output; + wl_list_for_each(output, &input->server->desktop->outputs, link) { + wlr_output_enable(output->wlr_output, outputs_enabled); + } + } else if (strcmp(command, "toggle_decoration_mode") == 0) { + struct roots_view *focus = roots_seat_get_focus(seat); + if (focus != NULL && focus->type == ROOTS_XDG_SHELL_VIEW) { + struct roots_xdg_toplevel_decoration *decoration = + focus->roots_xdg_surface->xdg_toplevel_decoration; + if (decoration != NULL) { + enum wlr_xdg_toplevel_decoration_v1_mode mode = + decoration->wlr_decoration->current_mode; + mode = mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE + ? WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE + : WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; + wlr_xdg_toplevel_decoration_v1_set_mode( + decoration->wlr_decoration, mode); + } + } + } else if (strcmp(command, "break_pointer_constraint") == 0) { + struct wl_list *list = &input->seats; + struct roots_seat *seat; + wl_list_for_each(seat, list, link) { + roots_cursor_constrain(seat->cursor, NULL, NAN, NAN); + } + } else { + wlr_log(WLR_ERROR, "unknown binding command: %s", command); + } +} diff --git a/rootston/config.c b/rootston/config.c new file mode 100644 index 00000000..198aaba6 --- /dev/null +++ b/rootston/config.c @@ -0,0 +1,670 @@ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#include <assert.h> +#include <getopt.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <sys/param.h> +#include <unistd.h> +#include <wlr/config.h> +#include <wlr/types/wlr_box.h> +#include <wlr/util/log.h> +#include "rootston/config.h" +#include "rootston/ini.h" +#include "rootston/input.h" +#include "rootston/keyboard.h" + +static void usage(const char *name, int ret) { + fprintf(stderr, + "usage: %s [-C <FILE>] [-E <COMMAND>]\n" + "\n" + " -C <FILE> Path to the configuration file\n" + " (default: rootston.ini).\n" + " See `rootston.ini.example` for config\n" + " file documentation.\n" + " -E <COMMAND> Command that will be ran at startup.\n" + " -D Enable damage tracking debugging.\n" + " -l <LEVEL> Set log verbosity, where,\n" + " 0:SILENT, 1:ERROR, 2:INFO, 3+:DEBUG\n" + " (default: DEBUG)\n", + name); + + exit(ret); +} + +static struct wlr_box *parse_geometry(const char *str) { + // format: {width}x{height}+{x}+{y} + if (strlen(str) > 255) { + wlr_log(WLR_ERROR, "cannot parse geometry string, too long"); + return NULL; + } + + char *buf = strdup(str); + struct wlr_box *box = calloc(1, sizeof(struct wlr_box)); + + bool has_width = false; + bool has_height = false; + bool has_x = false; + bool has_y = false; + + char *pch = strtok(buf, "x+"); + while (pch != NULL) { + errno = 0; + char *endptr; + long val = strtol(pch, &endptr, 0); + + if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) || + (errno != 0 && val == 0)) { + goto invalid_input; + } + + if (endptr == pch) { + goto invalid_input; + } + + if (!has_width) { + box->width = val; + has_width = true; + } else if (!has_height) { + box->height = val; + has_height = true; + } else if (!has_x) { + box->x = val; + has_x = true; + } else if (!has_y) { + box->y = val; + has_y = true; + } else { + break; + } + pch = strtok(NULL, "x+"); + } + + if (!has_width || !has_height) { + goto invalid_input; + } + + free(buf); + return box; + +invalid_input: + wlr_log(WLR_ERROR, "could not parse geometry string: %s", str); + free(buf); + free(box); + return NULL; +} + +static uint32_t parse_modifier(const char *symname) { + if (strcmp(symname, "Shift") == 0) { + return WLR_MODIFIER_SHIFT; + } else if (strcmp(symname, "Caps") == 0) { + return WLR_MODIFIER_CAPS; + } else if (strcmp(symname, "Ctrl") == 0) { + return WLR_MODIFIER_CTRL; + } else if (strcmp(symname, "Alt") == 0) { + return WLR_MODIFIER_ALT; + } else if (strcmp(symname, "Mod2") == 0) { + return WLR_MODIFIER_MOD2; + } else if (strcmp(symname, "Mod3") == 0) { + return WLR_MODIFIER_MOD3; + } else if (strcmp(symname, "Logo") == 0) { + return WLR_MODIFIER_LOGO; + } else if (strcmp(symname, "Mod5") == 0) { + return WLR_MODIFIER_MOD5; + } else { + return 0; + } +} + +static bool parse_modeline(const char *s, drmModeModeInfo *mode) { + char hsync[16]; + char vsync[16]; + float fclock; + + mode->type = DRM_MODE_TYPE_USERDEF; + + if (sscanf(s, "%f %hd %hd %hd %hd %hd %hd %hd %hd %15s %15s", + &fclock, + &mode->hdisplay, + &mode->hsync_start, + &mode->hsync_end, + &mode->htotal, + &mode->vdisplay, + &mode->vsync_start, + &mode->vsync_end, + &mode->vtotal, hsync, vsync) != 11) { + return false; + } + + mode->clock = fclock * 1000; + mode->vrefresh = mode->clock * 1000.0 * 1000.0 + / mode->htotal / mode->vtotal; + if (strcasecmp(hsync, "+hsync") == 0) { + mode->flags |= DRM_MODE_FLAG_PHSYNC; + } else if (strcasecmp(hsync, "-hsync") == 0) { + mode->flags |= DRM_MODE_FLAG_NHSYNC; + } else { + return false; + } + + if (strcasecmp(vsync, "+vsync") == 0) { + mode->flags |= DRM_MODE_FLAG_PVSYNC; + } else if (strcasecmp(vsync, "-vsync") == 0) { + mode->flags |= DRM_MODE_FLAG_NVSYNC; + } else { + return false; + } + + snprintf(mode->name, sizeof(mode->name), "%dx%d@%d", + mode->hdisplay, mode->vdisplay, mode->vrefresh / 1000); + + return true; +} + +void add_binding_config(struct wl_list *bindings, const char* combination, + const char* command) { + struct roots_binding_config *bc = + calloc(1, sizeof(struct roots_binding_config)); + + xkb_keysym_t keysyms[ROOTS_KEYBOARD_PRESSED_KEYSYMS_CAP]; + char *symnames = strdup(combination); + char *symname = strtok(symnames, "+"); + while (symname) { + uint32_t modifier = parse_modifier(symname); + if (modifier != 0) { + bc->modifiers |= modifier; + } else { + xkb_keysym_t sym = xkb_keysym_from_name(symname, + XKB_KEYSYM_NO_FLAGS); + if (sym == XKB_KEY_NoSymbol) { + wlr_log(WLR_ERROR, "got unknown key binding symbol: %s", + symname); + free(bc); + bc = NULL; + break; + } + keysyms[bc->keysyms_len] = sym; + bc->keysyms_len++; + } + symname = strtok(NULL, "+"); + } + free(symnames); + + if (bc) { + wl_list_insert(bindings, &bc->link); + bc->command = strdup(command); + bc->keysyms = malloc(bc->keysyms_len * sizeof(xkb_keysym_t)); + memcpy(bc->keysyms, keysyms, bc->keysyms_len * sizeof(xkb_keysym_t)); + } +} + +void add_switch_config(struct wl_list *switches, const char *switch_name, const char *action, + const char* command) { + struct roots_switch_config *sc = calloc(1, sizeof(struct roots_switch_config)); + + if (strcmp(switch_name, "tablet") == 0) { + sc->switch_type = WLR_SWITCH_TYPE_TABLET_MODE; + } else if (strcmp(switch_name, "lid") == 0) { + sc->switch_type = WLR_SWITCH_TYPE_LID; + } else { + sc->switch_type = -1; + sc->name = strdup(switch_name); + } + if (strcmp(action, "on") == 0) { + sc->switch_state = WLR_SWITCH_STATE_ON; + } else if (strcmp(action, "off") == 0) { + sc->switch_state = WLR_SWITCH_STATE_OFF; + } else if (strcmp(action, "toggle") == 0) { + sc->switch_state = WLR_SWITCH_STATE_TOGGLE; + } else { + wlr_log(WLR_ERROR, "Invalid switch action %s/n for switch %s:%s", + action, switch_name, action); + return; + } + sc->command = strdup(command); + wl_list_insert(switches, &sc->link); +} + +static void config_handle_cursor(struct roots_config *config, + const char *seat_name, const char *name, const char *value) { + struct roots_cursor_config *cc; + bool found = false; + wl_list_for_each(cc, &config->cursors, link) { + if (strcmp(cc->seat, seat_name) == 0) { + found = true; + break; + } + } + + if (!found) { + cc = calloc(1, sizeof(struct roots_cursor_config)); + cc->seat = strdup(seat_name); + wl_list_insert(&config->cursors, &cc->link); + } + + if (strcmp(name, "map-to-output") == 0) { + free(cc->mapped_output); + cc->mapped_output = strdup(value); + } else if (strcmp(name, "geometry") == 0) { + free(cc->mapped_box); + cc->mapped_box = parse_geometry(value); + } else if (strcmp(name, "theme") == 0) { + free(cc->theme); + cc->theme = strdup(value); + } else if (strcmp(name, "default-image") == 0) { + free(cc->default_image); + cc->default_image = strdup(value); + } else { + wlr_log(WLR_ERROR, "got unknown cursor config: %s", name); + } +} + +static void config_handle_keyboard(struct roots_config *config, + const char *device_name, const char *name, const char *value) { + struct roots_keyboard_config *kc; + bool found = false; + wl_list_for_each(kc, &config->keyboards, link) { + if (strcmp(kc->name, device_name) == 0) { + found = true; + break; + } + } + + if (!found) { + kc = calloc(1, sizeof(struct roots_keyboard_config)); + kc->name = strdup(device_name); + wl_list_insert(&config->keyboards, &kc->link); + } + + if (strcmp(name, "meta-key") == 0) { + kc->meta_key = parse_modifier(value); + if (kc->meta_key == 0) { + wlr_log(WLR_ERROR, "got unknown meta key: %s", name); + } + } else if (strcmp(name, "rules") == 0) { + kc->rules = strdup(value); + } else if (strcmp(name, "model") == 0) { + kc->model = strdup(value); + } else if (strcmp(name, "layout") == 0) { + kc->layout = strdup(value); + } else if (strcmp(name, "variant") == 0) { + kc->variant = strdup(value); + } else if (strcmp(name, "options") == 0) { + kc->options = strdup(value); + } else if (strcmp(name, "repeat-rate") == 0) { + kc->repeat_rate = strtol(value, NULL, 10); + } else if (strcmp(name, "repeat-delay") == 0) { + kc->repeat_delay = strtol(value, NULL, 10); + } else { + wlr_log(WLR_ERROR, "got unknown keyboard config: %s", name); + } +} + +static const char *output_prefix = "output:"; +static const char *device_prefix = "device:"; +static const char *keyboard_prefix = "keyboard:"; +static const char *cursor_prefix = "cursor:"; +static const char *switch_prefix = "switch:"; + +static int config_ini_handler(void *user, const char *section, const char *name, + const char *value) { + struct roots_config *config = user; + if (strcmp(section, "core") == 0) { + if (strcmp(name, "xwayland") == 0) { + if (strcasecmp(value, "true") == 0) { + config->xwayland = true; + } else if (strcasecmp(value, "immediate") == 0) { + config->xwayland = true; + config->xwayland_lazy = false; + } else if (strcasecmp(value, "false") == 0) { + config->xwayland = false; + } else { + wlr_log(WLR_ERROR, "got unknown xwayland value: %s", value); + } + } else { + wlr_log(WLR_ERROR, "got unknown core config: %s", name); + } + } else if (strncmp(output_prefix, section, strlen(output_prefix)) == 0) { + const char *output_name = section + strlen(output_prefix); + struct roots_output_config *oc; + bool found = false; + + wl_list_for_each(oc, &config->outputs, link) { + if (strcmp(oc->name, output_name) == 0) { + found = true; + break; + } + } + + if (!found) { + oc = calloc(1, sizeof(struct roots_output_config)); + oc->name = strdup(output_name); + oc->transform = WL_OUTPUT_TRANSFORM_NORMAL; + oc->scale = 1; + oc->enable = true; + wl_list_init(&oc->modes); + wl_list_insert(&config->outputs, &oc->link); + } + + if (strcmp(name, "enable") == 0) { + if (strcasecmp(value, "true") == 0) { + oc->enable = true; + } else if (strcasecmp(value, "false") == 0) { + oc->enable = false; + } else { + wlr_log(WLR_ERROR, "got invalid output enable value: %s", value); + } + } else if (strcmp(name, "x") == 0) { + oc->x = strtol(value, NULL, 10); + } else if (strcmp(name, "y") == 0) { + oc->y = strtol(value, NULL, 10); + } else if (strcmp(name, "scale") == 0) { + oc->scale = strtof(value, NULL); + assert(oc->scale > 0); + } else if (strcmp(name, "rotate") == 0) { + if (strcmp(value, "normal") == 0) { + oc->transform = WL_OUTPUT_TRANSFORM_NORMAL; + } else if (strcmp(value, "90") == 0) { + oc->transform = WL_OUTPUT_TRANSFORM_90; + } else if (strcmp(value, "180") == 0) { + oc->transform = WL_OUTPUT_TRANSFORM_180; + } else if (strcmp(value, "270") == 0) { + oc->transform = WL_OUTPUT_TRANSFORM_270; + } else if (strcmp(value, "flipped") == 0) { + oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED; + } else if (strcmp(value, "flipped-90") == 0) { + oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED_90; + } else if (strcmp(value, "flipped-180") == 0) { + oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED_180; + } else if (strcmp(value, "flipped-270") == 0) { + oc->transform = WL_OUTPUT_TRANSFORM_FLIPPED_270; + } else { + wlr_log(WLR_ERROR, "got unknown transform value: %s", value); + } + } else if (strcmp(name, "mode") == 0) { + char *end; + oc->mode.width = strtol(value, &end, 10); + assert(*end == 'x'); + ++end; + oc->mode.height = strtol(end, &end, 10); + if (*end) { + assert(*end == '@'); + ++end; + oc->mode.refresh_rate = strtof(end, &end); + assert(strcmp("Hz", end) == 0); + } + wlr_log(WLR_DEBUG, "Configured output %s with mode %dx%d@%f", + oc->name, oc->mode.width, oc->mode.height, + oc->mode.refresh_rate); + } else if (strcmp(name, "modeline") == 0) { + struct roots_output_mode_config *mode = calloc(1, sizeof(*mode)); + + if (parse_modeline(value, &mode->info)) { + wl_list_insert(&oc->modes, &mode->link); + } else { + free(mode); + wlr_log(WLR_ERROR, "Invalid modeline: %s", value); + } + } + } else if (strncmp(cursor_prefix, section, strlen(cursor_prefix)) == 0) { + const char *seat_name = section + strlen(cursor_prefix); + config_handle_cursor(config, seat_name, name, value); + } else if (strcmp(section, "cursor") == 0) { + config_handle_cursor(config, ROOTS_CONFIG_DEFAULT_SEAT_NAME, name, + value); + } else if (strncmp(device_prefix, section, strlen(device_prefix)) == 0) { + const char *device_name = section + strlen(device_prefix); + + struct roots_device_config *dc; + bool found = false; + wl_list_for_each(dc, &config->devices, link) { + if (strcmp(dc->name, device_name) == 0) { + found = true; + break; + } + } + + if (!found) { + dc = calloc(1, sizeof(struct roots_device_config)); + dc->name = strdup(device_name); + dc->seat = strdup(ROOTS_CONFIG_DEFAULT_SEAT_NAME); + wl_list_insert(&config->devices, &dc->link); + } + + if (strcmp(name, "map-to-output") == 0) { + free(dc->mapped_output); + dc->mapped_output = strdup(value); + } else if (strcmp(name, "geometry") == 0) { + free(dc->mapped_box); + dc->mapped_box = parse_geometry(value); + } else if (strcmp(name, "seat") == 0) { + free(dc->seat); + dc->seat = strdup(value); + } else if (strcmp(name, "tap_enabled") == 0) { + if (strcasecmp(value, "true") == 0) { + dc->tap_enabled = true; + } else if (strcasecmp(value, "false") == 0) { + dc->tap_enabled = false; + } else { + wlr_log(WLR_ERROR, + "got unknown tap_enabled value: %s", + value); + } + } else { + wlr_log(WLR_ERROR, "got unknown device config: %s", name); + } + } else if (strcmp(section, "keyboard") == 0) { + config_handle_keyboard(config, "", name, value); + } else if (strncmp(keyboard_prefix, + section, strlen(keyboard_prefix)) == 0) { + const char *device_name = section + strlen(keyboard_prefix); + config_handle_keyboard(config, device_name, name, value); + } else if (strcmp(section, "bindings") == 0) { + add_binding_config(&config->bindings, name, value); + } else if (strncmp(switch_prefix, section, strlen(switch_prefix)) == 0) { + const char *switch_name = section + strlen(switch_prefix); + add_switch_config(&config->switches, switch_name, name, value); + } else { + wlr_log(WLR_ERROR, "got unknown config section: %s", section); + } + + return 1; +} + +struct roots_config *roots_config_create_from_args(int argc, char *argv[]) { + struct roots_config *config = calloc(1, sizeof(struct roots_config)); + if (config == NULL) { + return NULL; + } + + config->xwayland = true; + config->xwayland_lazy = true; + wl_list_init(&config->outputs); + wl_list_init(&config->devices); + wl_list_init(&config->keyboards); + wl_list_init(&config->cursors); + wl_list_init(&config->bindings); + wl_list_init(&config->switches); + + int c; + unsigned int log_verbosity = WLR_DEBUG; + while ((c = getopt(argc, argv, "C:E:hDl:")) != -1) { + switch (c) { + case 'C': + config->config_path = strdup(optarg); + break; + case 'E': + config->startup_cmd = strdup(optarg); + break; + case 'D': + config->debug_damage_tracking = true; + break; + case 'l': + log_verbosity = strtoul(optarg, NULL, 10); + if (log_verbosity >= WLR_LOG_IMPORTANCE_LAST) { + log_verbosity = WLR_LOG_IMPORTANCE_LAST - 1; + } + break; + case 'h': + case '?': + usage(argv[0], c != 'h'); + } + } + wlr_log_init(log_verbosity, NULL); + + if (!config->config_path) { + // get the config path from the current directory + char cwd[MAXPATHLEN]; + if (getcwd(cwd, sizeof(cwd)) != NULL) { + char buf[MAXPATHLEN]; + if (snprintf(buf, MAXPATHLEN, "%s/%s", cwd, "rootston.ini") >= MAXPATHLEN) { + wlr_log(WLR_ERROR, "config path too long"); + exit(1); + } + config->config_path = strdup(buf); + } else { + wlr_log(WLR_ERROR, "could not get cwd"); + exit(1); + } + } + + int result = ini_parse(config->config_path, config_ini_handler, config); + + if (result == -1) { + wlr_log(WLR_DEBUG, "No config file found. Using sensible defaults."); + add_binding_config(&config->bindings, "Logo+Shift+E", "exit"); + add_binding_config(&config->bindings, "Ctrl+q", "close"); + add_binding_config(&config->bindings, "Alt+Tab", "next_window"); + add_binding_config(&config->bindings, "Logo+Escape", "break_pointer_constraint"); + struct roots_keyboard_config *kc = + calloc(1, sizeof(struct roots_keyboard_config)); + kc->meta_key = WLR_MODIFIER_LOGO; + kc->name = strdup(""); + wl_list_insert(&config->keyboards, &kc->link); + } else if (result == -2) { + wlr_log(WLR_ERROR, "Could not allocate memory to parse config file"); + exit(1); + } else if (result != 0) { + wlr_log(WLR_ERROR, "Could not parse config file"); + exit(1); + } + + return config; +} + +void roots_config_destroy(struct roots_config *config) { + struct roots_output_config *oc, *otmp = NULL; + wl_list_for_each_safe(oc, otmp, &config->outputs, link) { + struct roots_output_mode_config *omc, *omctmp = NULL; + wl_list_for_each_safe(omc, omctmp, &oc->modes, link) { + free(omc); + } + free(oc->name); + free(oc); + } + + struct roots_device_config *dc, *dtmp = NULL; + wl_list_for_each_safe(dc, dtmp, &config->devices, link) { + free(dc->name); + free(dc->seat); + free(dc->mapped_output); + free(dc->mapped_box); + free(dc); + } + + struct roots_keyboard_config *kc, *ktmp = NULL; + wl_list_for_each_safe(kc, ktmp, &config->keyboards, link) { + free(kc->name); + free(kc->rules); + free(kc->model); + free(kc->layout); + free(kc->variant); + free(kc->options); + free(kc); + } + + struct roots_cursor_config *cc, *ctmp = NULL; + wl_list_for_each_safe(cc, ctmp, &config->cursors, link) { + free(cc->seat); + free(cc->mapped_output); + free(cc->mapped_box); + free(cc->theme); + free(cc->default_image); + free(cc); + } + + struct roots_binding_config *bc, *btmp = NULL; + wl_list_for_each_safe(bc, btmp, &config->bindings, link) { + free(bc->keysyms); + free(bc->command); + free(bc); + } + + free(config->config_path); + free(config); +} + +struct roots_output_config *roots_config_get_output(struct roots_config *config, + struct wlr_output *output) { + char name[88]; + snprintf(name, sizeof(name), "%s %s %s", output->make, output->model, + output->serial); + + struct roots_output_config *oc; + wl_list_for_each(oc, &config->outputs, link) { + if (strcmp(oc->name, output->name) == 0 || + strcmp(oc->name, name) == 0) { + return oc; + } + } + + return NULL; +} + +struct roots_device_config *roots_config_get_device(struct roots_config *config, + struct wlr_input_device *device) { + struct roots_device_config *d_config; + wl_list_for_each(d_config, &config->devices, link) { + if (strcmp(d_config->name, device->name) == 0) { + return d_config; + } + } + + return NULL; +} + +struct roots_keyboard_config *roots_config_get_keyboard( + struct roots_config *config, struct wlr_input_device *device) { + const char *device_name = ""; + if (device != NULL) { + device_name = device->name; + } + + struct roots_keyboard_config *kc; + wl_list_for_each(kc, &config->keyboards, link) { + if (strcmp(kc->name, device_name) == 0) { + return kc; + } + } + + return NULL; +} + +struct roots_cursor_config *roots_config_get_cursor(struct roots_config *config, + const char *seat_name) { + if (seat_name == NULL) { + seat_name = ROOTS_CONFIG_DEFAULT_SEAT_NAME; + } + + struct roots_cursor_config *cc; + wl_list_for_each(cc, &config->cursors, link) { + if (strcmp(cc->seat, seat_name) == 0) { + return cc; + } + } + + return NULL; +} diff --git a/rootston/cursor.c b/rootston/cursor.c new file mode 100644 index 00000000..b9ded30e --- /dev/null +++ b/rootston/cursor.c @@ -0,0 +1,615 @@ +#define _XOPEN_SOURCE 700 +#include <assert.h> +#include <math.h> +#include <stdlib.h> +#include <wlr/types/wlr_region.h> +#include <wlr/types/wlr_xcursor_manager.h> +#include <wlr/util/edges.h> +#include <wlr/util/log.h> +#include <wlr/util/region.h> +#ifdef __linux__ +#include <linux/input-event-codes.h> +#elif __FreeBSD__ +#include <dev/evdev/input-event-codes.h> +#endif +#include "rootston/cursor.h" +#include "rootston/desktop.h" +#include "rootston/view.h" +#include "rootston/xcursor.h" + +struct roots_cursor *roots_cursor_create(struct roots_seat *seat) { + struct roots_cursor *cursor = calloc(1, sizeof(struct roots_cursor)); + if (!cursor) { + return NULL; + } + cursor->cursor = wlr_cursor_create(); + if (!cursor->cursor) { + free(cursor); + return NULL; + } + cursor->default_xcursor = ROOTS_XCURSOR_DEFAULT; + return cursor; +} + +void roots_cursor_destroy(struct roots_cursor *cursor) { + // TODO +} + +static void seat_view_deco_motion(struct roots_seat_view *view, double deco_sx, double deco_sy) { + struct roots_cursor *cursor = view->seat->cursor; + + double sx = deco_sx; + double sy = deco_sy; + if (view->has_button_grab) { + sx = view->grab_sx; + sy = view->grab_sy; + } + + enum roots_deco_part parts = view_get_deco_part(view->view, sx, sy); + + bool is_titlebar = (parts & ROOTS_DECO_PART_TITLEBAR); + uint32_t edges = 0; + if (parts & ROOTS_DECO_PART_LEFT_BORDER) { + edges |= WLR_EDGE_LEFT; + } else if (parts & ROOTS_DECO_PART_RIGHT_BORDER) { + edges |= WLR_EDGE_RIGHT; + } else if (parts & ROOTS_DECO_PART_BOTTOM_BORDER) { + edges |= WLR_EDGE_BOTTOM; + } else if (parts & ROOTS_DECO_PART_TOP_BORDER) { + edges |= WLR_EDGE_TOP; + } + + if (view->has_button_grab) { + if (is_titlebar) { + roots_seat_begin_move(view->seat, view->view); + } else if (edges) { + roots_seat_begin_resize(view->seat, view->view, edges); + } + view->has_button_grab = false; + } else { + if (is_titlebar) { + wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager, + cursor->default_xcursor, cursor->cursor); + } else if (edges) { + const char *resize_name = wlr_xcursor_get_resize_name(edges); + wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager, + resize_name, cursor->cursor); + } + } +} + +static void seat_view_deco_leave(struct roots_seat_view *view) { + struct roots_cursor *cursor = view->seat->cursor; + wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager, + cursor->default_xcursor, cursor->cursor); + view->has_button_grab = false; +} + +static void seat_view_deco_button(struct roots_seat_view *view, double sx, + double sy, uint32_t button, uint32_t state) { + if (button == BTN_LEFT && state == WLR_BUTTON_PRESSED) { + view->has_button_grab = true; + view->grab_sx = sx; + view->grab_sy = sy; + } else { + view->has_button_grab = false; + } + + enum roots_deco_part parts = view_get_deco_part(view->view, sx, sy); + if (state == WLR_BUTTON_RELEASED && (parts & ROOTS_DECO_PART_TITLEBAR)) { + struct roots_cursor *cursor = view->seat->cursor; + wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager, + cursor->default_xcursor, cursor->cursor); + } +} + +static void roots_passthrough_cursor(struct roots_cursor *cursor, + int64_t time) { + bool focus_changed; + double sx, sy; + struct roots_view *view = NULL; + struct roots_seat *seat = cursor->seat; + struct roots_desktop *desktop = seat->input->server->desktop; + struct wlr_surface *surface = desktop_surface_at(desktop, + cursor->cursor->x, cursor->cursor->y, &sx, &sy, &view); + + struct wl_client *client = NULL; + if (surface) { + client = wl_resource_get_client(surface->resource); + } + + if (surface && !roots_seat_allow_input(seat, surface->resource)) { + return; + } + + if (cursor->cursor_client != client) { + wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager, + cursor->default_xcursor, cursor->cursor); + cursor->cursor_client = client; + } + + if (view) { + struct roots_seat_view *seat_view = + roots_seat_view_from_view(seat, view); + + if (cursor->pointer_view && + !cursor->wlr_surface && (surface || seat_view != cursor->pointer_view)) { + seat_view_deco_leave(cursor->pointer_view); + } + + cursor->pointer_view = seat_view; + + if (!surface) { + seat_view_deco_motion(seat_view, sx, sy); + } + } else { + cursor->pointer_view = NULL; + } + + cursor->wlr_surface = surface; + + if (surface) { + focus_changed = (seat->seat->pointer_state.focused_surface != surface); + wlr_seat_pointer_notify_enter(seat->seat, surface, sx, sy); + if (!focus_changed && time > 0) { + wlr_seat_pointer_notify_motion(seat->seat, time, sx, sy); + } + } else { + wlr_seat_pointer_clear_focus(seat->seat); + } + + struct roots_drag_icon *drag_icon; + wl_list_for_each(drag_icon, &seat->drag_icons, link) { + roots_drag_icon_update_position(drag_icon); + } +} + +void roots_cursor_update_focus(struct roots_cursor *cursor) { + roots_passthrough_cursor(cursor, -1); +} + +void roots_cursor_update_position(struct roots_cursor *cursor, + uint32_t time) { + struct roots_seat *seat = cursor->seat; + struct roots_view *view; + switch (cursor->mode) { + case ROOTS_CURSOR_PASSTHROUGH: + roots_passthrough_cursor(cursor, time); + break; + case ROOTS_CURSOR_MOVE: + view = roots_seat_get_focus(seat); + if (view != NULL) { + double dx = cursor->cursor->x - cursor->offs_x; + double dy = cursor->cursor->y - cursor->offs_y; + view_move(view, cursor->view_x + dx, + cursor->view_y + dy); + } + break; + case ROOTS_CURSOR_RESIZE: + view = roots_seat_get_focus(seat); + if (view != NULL) { + double dx = cursor->cursor->x - cursor->offs_x; + double dy = cursor->cursor->y - cursor->offs_y; + double x = view->box.x; + double y = view->box.y; + int width = cursor->view_width; + int height = cursor->view_height; + if (cursor->resize_edges & WLR_EDGE_TOP) { + y = cursor->view_y + dy; + height -= dy; + if (height < 1) { + y += height; + } + } else if (cursor->resize_edges & WLR_EDGE_BOTTOM) { + height += dy; + } + if (cursor->resize_edges & WLR_EDGE_LEFT) { + x = cursor->view_x + dx; + width -= dx; + if (width < 1) { + x += width; + } + } else if (cursor->resize_edges & WLR_EDGE_RIGHT) { + width += dx; + } + view_move_resize(view, x, y, + width < 1 ? 1 : width, + height < 1 ? 1 : height); + } + break; + case ROOTS_CURSOR_ROTATE: + view = roots_seat_get_focus(seat); + if (view != NULL) { + int ox = view->box.x + view->wlr_surface->current.width/2, + oy = view->box.y + view->wlr_surface->current.height/2; + int ux = cursor->offs_x - ox, + uy = cursor->offs_y - oy; + int vx = cursor->cursor->x - ox, + vy = cursor->cursor->y - oy; + float angle = atan2(ux*vy - uy*vx, vx*ux + vy*uy); + int steps = 12; + angle = round(angle/M_PI*steps) / (steps/M_PI); + view_rotate(view, cursor->view_rotation + angle); + } + break; + } +} + +static void roots_cursor_press_button(struct roots_cursor *cursor, + struct wlr_input_device *device, uint32_t time, uint32_t button, + uint32_t state, double lx, double ly) { + struct roots_seat *seat = cursor->seat; + struct roots_desktop *desktop = seat->input->server->desktop; + + bool is_touch = device->type == WLR_INPUT_DEVICE_TOUCH; + + double sx, sy; + struct roots_view *view; + struct wlr_surface *surface = desktop_surface_at(desktop, + lx, ly, &sx, &sy, &view); + + if (state == WLR_BUTTON_PRESSED && view && + roots_seat_has_meta_pressed(seat)) { + roots_seat_set_focus(seat, view); + + uint32_t edges; + switch (button) { + case BTN_LEFT: + roots_seat_begin_move(seat, view); + break; + case BTN_RIGHT: + edges = 0; + if (sx < view->wlr_surface->current.width/2) { + edges |= WLR_EDGE_LEFT; + } else { + edges |= WLR_EDGE_RIGHT; + } + if (sy < view->wlr_surface->current.height/2) { + edges |= WLR_EDGE_TOP; + } else { + edges |= WLR_EDGE_BOTTOM; + } + roots_seat_begin_resize(seat, view, edges); + break; + case BTN_MIDDLE: + roots_seat_begin_rotate(seat, view); + break; + } + } else { + if (view && !surface && cursor->pointer_view) { + seat_view_deco_button(cursor->pointer_view, + sx, sy, button, state); + } + + if (state == WLR_BUTTON_RELEASED && + cursor->mode != ROOTS_CURSOR_PASSTHROUGH) { + cursor->mode = ROOTS_CURSOR_PASSTHROUGH; + } + + if (state == WLR_BUTTON_PRESSED) { + if (view) { + roots_seat_set_focus(seat, view); + } + if (surface && wlr_surface_is_layer_surface(surface)) { + struct wlr_layer_surface_v1 *layer = + wlr_layer_surface_v1_from_wlr_surface(surface); + if (layer->current.keyboard_interactive) { + roots_seat_set_focus_layer(seat, layer); + } + } + } + } + + if (!is_touch) { + wlr_seat_pointer_notify_button(seat->seat, time, button, state); + } +} + +void roots_cursor_handle_motion(struct roots_cursor *cursor, + struct wlr_event_pointer_motion *event) { + double dx = event->delta_x; + double dy = event->delta_y; + + if (cursor->active_constraint) { + struct roots_view *view = cursor->pointer_view->view; + assert(view); + + // TODO: handle rotated views + if (view->rotation == 0.0) { + double lx1 = cursor->cursor->x; + double ly1 = cursor->cursor->y; + + double lx2 = lx1 + dx; + double ly2 = ly1 + dy; + + double sx1 = lx1 - view->box.x; + double sy1 = ly1 - view->box.y; + + double sx2 = lx2 - view->box.x; + double sy2 = ly2 - view->box.y; + + double sx2_confined, sy2_confined; + if (!wlr_region_confine(&cursor->confine, sx1, sy1, sx2, sy2, + &sx2_confined, &sy2_confined)) { + return; + } + + dx = sx2_confined - sx1; + dy = sy2_confined - sy1; + } + } + + wlr_cursor_move(cursor->cursor, event->device, dx, dy); + roots_cursor_update_position(cursor, event->time_msec); +} + +void roots_cursor_handle_motion_absolute(struct roots_cursor *cursor, + struct wlr_event_pointer_motion_absolute *event) { + double lx, ly; + wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device, event->x, + event->y, &lx, &ly); + + if (cursor->pointer_view) { + struct roots_view *view = cursor->pointer_view->view; + + if (cursor->active_constraint && + !pixman_region32_contains_point(&cursor->confine, + floor(lx - view->box.x), floor(ly - view->box.y), NULL)) { + return; + } + } + + wlr_cursor_warp_closest(cursor->cursor, event->device, lx, ly); + roots_cursor_update_position(cursor, event->time_msec); +} + +void roots_cursor_handle_button(struct roots_cursor *cursor, + struct wlr_event_pointer_button *event) { + roots_cursor_press_button(cursor, event->device, event->time_msec, + event->button, event->state, cursor->cursor->x, cursor->cursor->y); +} + +void roots_cursor_handle_axis(struct roots_cursor *cursor, + struct wlr_event_pointer_axis *event) { + wlr_seat_pointer_notify_axis(cursor->seat->seat, event->time_msec, + event->orientation, event->delta, event->delta_discrete, event->source); +} + +void roots_cursor_handle_touch_down(struct roots_cursor *cursor, + struct wlr_event_touch_down *event) { + struct roots_desktop *desktop = cursor->seat->input->server->desktop; + double lx, ly; + wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device, + event->x, event->y, &lx, &ly); + + double sx, sy; + struct wlr_surface *surface = desktop_surface_at( + desktop, lx, ly, &sx, &sy, NULL); + + uint32_t serial = 0; + if (surface && roots_seat_allow_input(cursor->seat, surface->resource)) { + serial = wlr_seat_touch_notify_down(cursor->seat->seat, surface, + event->time_msec, event->touch_id, sx, sy); + } + + if (serial && wlr_seat_touch_num_points(cursor->seat->seat) == 1) { + cursor->seat->touch_id = event->touch_id; + cursor->seat->touch_x = lx; + cursor->seat->touch_y = ly; + roots_cursor_press_button(cursor, event->device, event->time_msec, + BTN_LEFT, 1, lx, ly); + } +} + +void roots_cursor_handle_touch_up(struct roots_cursor *cursor, + struct wlr_event_touch_up *event) { + struct wlr_touch_point *point = + wlr_seat_touch_get_point(cursor->seat->seat, event->touch_id); + if (!point) { + return; + } + + if (wlr_seat_touch_num_points(cursor->seat->seat) == 1) { + roots_cursor_press_button(cursor, event->device, event->time_msec, + BTN_LEFT, 0, cursor->seat->touch_x, cursor->seat->touch_y); + } + + wlr_seat_touch_notify_up(cursor->seat->seat, event->time_msec, + event->touch_id); +} + +void roots_cursor_handle_touch_motion(struct roots_cursor *cursor, + struct wlr_event_touch_motion *event) { + struct roots_desktop *desktop = cursor->seat->input->server->desktop; + struct wlr_touch_point *point = + wlr_seat_touch_get_point(cursor->seat->seat, event->touch_id); + if (!point) { + return; + } + + double lx, ly; + wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device, + event->x, event->y, &lx, &ly); + + double sx, sy; + struct wlr_surface *surface = desktop_surface_at( + desktop, lx, ly, &sx, &sy, NULL); + + if (surface && roots_seat_allow_input(cursor->seat, surface->resource)) { + wlr_seat_touch_point_focus(cursor->seat->seat, surface, + event->time_msec, event->touch_id, sx, sy); + wlr_seat_touch_notify_motion(cursor->seat->seat, event->time_msec, + event->touch_id, sx, sy); + } else { + wlr_seat_touch_point_clear_focus(cursor->seat->seat, event->time_msec, + event->touch_id); + } + + if (event->touch_id == cursor->seat->touch_id) { + cursor->seat->touch_x = lx; + cursor->seat->touch_y = ly; + } +} + +void roots_cursor_handle_tool_axis(struct roots_cursor *cursor, + struct wlr_event_tablet_tool_axis *event) { + double x = NAN, y = NAN; + if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X) && + (event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) { + x = event->x; + y = event->y; + } else if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_X)) { + x = event->x; + } else if ((event->updated_axes & WLR_TABLET_TOOL_AXIS_Y)) { + y = event->y; + } + + double lx, ly; + wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device, + x, y, &lx, &ly); + + + if (cursor->pointer_view) { + struct roots_view *view = cursor->pointer_view->view; + + if (cursor->active_constraint && + !pixman_region32_contains_point(&cursor->confine, + floor(lx - view->box.x), floor(ly - view->box.y), NULL)) { + return; + } + } + + wlr_cursor_warp_closest(cursor->cursor, event->device, lx, ly); + roots_cursor_update_position(cursor, event->time_msec); +} + +void roots_cursor_handle_tool_tip(struct roots_cursor *cursor, + struct wlr_event_tablet_tool_tip *event) { + roots_cursor_press_button(cursor, event->device, + event->time_msec, BTN_LEFT, event->state, cursor->cursor->x, + cursor->cursor->y); +} + +void roots_cursor_handle_request_set_cursor(struct roots_cursor *cursor, + struct wlr_seat_pointer_request_set_cursor_event *event) { + struct wlr_surface *focused_surface = + event->seat_client->seat->pointer_state.focused_surface; + bool has_focused = + focused_surface != NULL && focused_surface->resource != NULL; + struct wl_client *focused_client = NULL; + if (has_focused) { + focused_client = wl_resource_get_client(focused_surface->resource); + } + if (event->seat_client->client != focused_client || + cursor->mode != ROOTS_CURSOR_PASSTHROUGH) { + wlr_log(WLR_DEBUG, "Denying request to set cursor from unfocused client"); + return; + } + + wlr_cursor_set_surface(cursor->cursor, event->surface, event->hotspot_x, + event->hotspot_y); + cursor->cursor_client = event->seat_client->client; +} + +void roots_cursor_handle_focus_change(struct roots_cursor *cursor, + struct wlr_seat_pointer_focus_change_event *event) { + double sx = event->sx; + double sy = event->sy; + + double lx = cursor->cursor->x; + double ly = cursor->cursor->y; + + wlr_log(WLR_DEBUG, "entered surface %p, lx: %f, ly: %f, sx: %f, sy: %f", + event->new_surface, lx, ly, sx, sy); + + roots_cursor_constrain(cursor, + wlr_pointer_constraints_v1_constraint_for_surface( + cursor->seat->input->server->desktop->pointer_constraints, + event->new_surface, cursor->seat->seat), + sx, sy); +} + +void roots_cursor_handle_constraint_commit(struct roots_cursor *cursor) { + struct roots_desktop *desktop = cursor->seat->input->server->desktop; + + struct roots_view *view; + double sx, sy; + struct wlr_surface *surface = desktop_surface_at(desktop, + cursor->cursor->x, cursor->cursor->y, &sx, &sy, &view); + // This should never happen but views move around right when they're + // created from (0, 0) to their actual coordinates. + if (surface != cursor->active_constraint->surface) { + roots_cursor_update_focus(cursor); + } else { + roots_cursor_constrain(cursor, cursor->active_constraint, sx, sy); + } +} + +static void handle_constraint_commit(struct wl_listener *listener, + void *data) { + struct roots_cursor *cursor = + wl_container_of(listener, cursor, constraint_commit); + assert(cursor->active_constraint->surface == data); + roots_cursor_handle_constraint_commit(cursor); +} + +void roots_cursor_constrain(struct roots_cursor *cursor, + struct wlr_pointer_constraint_v1 *constraint, double sx, double sy) { + if (cursor->active_constraint == constraint) { + return; + } + + wlr_log(WLR_DEBUG, "roots_cursor_constrain(%p, %p)", + cursor, constraint); + wlr_log(WLR_DEBUG, "cursor->active_constraint: %p", + cursor->active_constraint); + + wl_list_remove(&cursor->constraint_commit.link); + wl_list_init(&cursor->constraint_commit.link); + if (cursor->active_constraint) { + wlr_pointer_constraint_v1_send_deactivated( + cursor->active_constraint); + } + + cursor->active_constraint = constraint; + + if (constraint == NULL) { + return; + } + + wlr_pointer_constraint_v1_send_activated(constraint); + + wl_list_remove(&cursor->constraint_commit.link); + wl_signal_add(&constraint->surface->events.commit, + &cursor->constraint_commit); + cursor->constraint_commit.notify = handle_constraint_commit; + + pixman_region32_clear(&cursor->confine); + + pixman_region32_t *region = &constraint->region; + + if (!pixman_region32_contains_point(region, floor(sx), floor(sy), NULL)) { + // Warp into region if possible + int nboxes; + pixman_box32_t *boxes = pixman_region32_rectangles(region, &nboxes); + if (nboxes > 0) { + struct roots_view *view = cursor->pointer_view->view; + + double sx = (boxes[0].x1 + boxes[0].x2) / 2.; + double sy = (boxes[0].y1 + boxes[0].y2) / 2.; + + rotate_child_position(&sx, &sy, 0, 0, view->box.width, view->box.height, + view->rotation); + + double lx = view->box.x + sx; + double ly = view->box.y + sy; + + wlr_cursor_warp_closest(cursor->cursor, NULL, lx, ly); + } + } + + // A locked pointer will result in an empty region, thus disallowing all movement + if (constraint->type == WLR_POINTER_CONSTRAINT_V1_CONFINED) { + pixman_region32_copy(&cursor->confine, region); + } +} diff --git a/rootston/desktop.c b/rootston/desktop.c new file mode 100644 index 00000000..48e2635c --- /dev/null +++ b/rootston/desktop.c @@ -0,0 +1,1101 @@ +#define _POSIX_C_SOURCE 200112L +#include <assert.h> +#include <math.h> +#include <stdlib.h> +#include <time.h> +#include <wlr/config.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_compositor.h> +#include <wlr/types/wlr_cursor.h> +#include <wlr/types/wlr_export_dmabuf_v1.h> +#include <wlr/types/wlr_gamma_control.h> +#include <wlr/types/wlr_gamma_control_v1.h> +#include <wlr/types/wlr_idle_inhibit_v1.h> +#include <wlr/types/wlr_idle.h> +#include <wlr/types/wlr_input_inhibitor.h> +#include <wlr/types/wlr_layer_shell_v1.h> +#include <wlr/types/wlr_output_layout.h> +#include <wlr/types/wlr_pointer_constraints_v1.h> +#include <wlr/types/wlr_gtk_primary_selection.h> +#include <wlr/types/wlr_server_decoration.h> +#include <wlr/types/wlr_wl_shell.h> +#include <wlr/types/wlr_xcursor_manager.h> +#include <wlr/types/wlr_xdg_output_v1.h> +#include <wlr/types/wlr_xdg_shell_v6.h> +#include <wlr/types/wlr_xdg_shell.h> +#include <wlr/types/wlr_xdg_output_v1.h> +#include <wlr/types/wlr_tablet_v2.h> +#include <wlr/util/log.h> +#include "rootston/layers.h" +#include "rootston/seat.h" +#include "rootston/server.h" +#include "rootston/view.h" +#include "rootston/virtual_keyboard.h" +#include "rootston/xcursor.h" +#include "wlr-layer-shell-unstable-v1-protocol.h" + +struct roots_view *view_create(struct roots_desktop *desktop) { + struct roots_view *view = calloc(1, sizeof(struct roots_view)); + if (!view) { + return NULL; + } + view->desktop = desktop; + view->alpha = 1.0f; + wl_signal_init(&view->events.unmap); + wl_signal_init(&view->events.destroy); + wl_list_init(&view->children); + return view; +} + +void view_get_box(const struct roots_view *view, struct wlr_box *box) { + box->x = view->box.x; + box->y = view->box.y; + box->width = view->box.width; + box->height = view->box.height; +} + +void view_get_deco_box(const struct roots_view *view, struct wlr_box *box) { + view_get_box(view, box); + if (!view->decorated) { + return; + } + + box->x -= view->border_width; + box->y -= (view->border_width + view->titlebar_height); + box->width += view->border_width * 2; + box->height += (view->border_width * 2 + view->titlebar_height); +} + +enum roots_deco_part view_get_deco_part(struct roots_view *view, double sx, + double sy) { + if (!view->decorated) { + return ROOTS_DECO_PART_NONE; + } + + int sw = view->wlr_surface->current.width; + int sh = view->wlr_surface->current.height; + int bw = view->border_width; + int titlebar_h = view->titlebar_height; + + if (sx > 0 && sx < sw && sy < 0 && sy > -view->titlebar_height) { + return ROOTS_DECO_PART_TITLEBAR; + } + + enum roots_deco_part parts = 0; + if (sy >= -(titlebar_h + bw) && + sy <= sh + bw) { + if (sx < 0 && sx > -bw) { + parts |= ROOTS_DECO_PART_LEFT_BORDER; + } else if (sx > sw && sx < sw + bw) { + parts |= ROOTS_DECO_PART_RIGHT_BORDER; + } + } + + if (sx >= -bw && sx <= sw + bw) { + if (sy > sh && sy <= sh + bw) { + parts |= ROOTS_DECO_PART_BOTTOM_BORDER; + } else if (sy >= -(titlebar_h + bw) && sy < 0) { + parts |= ROOTS_DECO_PART_TOP_BORDER; + } + } + + // TODO corners + + return parts; +} + +static void view_update_output(const struct roots_view *view, + const struct wlr_box *before) { + struct roots_desktop *desktop = view->desktop; + + if (view->wlr_surface == NULL) { + return; + } + + struct wlr_box box; + view_get_box(view, &box); + + struct roots_output *output; + wl_list_for_each(output, &desktop->outputs, link) { + bool intersected = before != NULL && wlr_output_layout_intersects( + desktop->layout, output->wlr_output, before); + bool intersects = wlr_output_layout_intersects(desktop->layout, + output->wlr_output, &box); + if (intersected && !intersects) { + wlr_surface_send_leave(view->wlr_surface, output->wlr_output); + if (view->toplevel_handle) { + wlr_foreign_toplevel_handle_v1_output_leave( + view->toplevel_handle, output->wlr_output); + } + } + if (!intersected && intersects) { + wlr_surface_send_enter(view->wlr_surface, output->wlr_output); + if (view->toplevel_handle) { + wlr_foreign_toplevel_handle_v1_output_enter( + view->toplevel_handle, output->wlr_output); + } + } + } +} + +void view_move(struct roots_view *view, double x, double y) { + if (view->box.x == x && view->box.y == y) { + return; + } + + struct wlr_box before; + view_get_box(view, &before); + if (view->move) { + view->move(view, x, y); + } else { + view_update_position(view, x, y); + } + view_update_output(view, &before); +} + +void view_activate(struct roots_view *view, bool activate) { + if (view->activate) { + view->activate(view, activate); + } + + if (view->toplevel_handle) { + wlr_foreign_toplevel_handle_v1_set_activated(view->toplevel_handle, + activate); + } +} + +void view_resize(struct roots_view *view, uint32_t width, uint32_t height) { + struct wlr_box before; + view_get_box(view, &before); + if (view->resize) { + view->resize(view, width, height); + } + view_update_output(view, &before); +} + +void view_move_resize(struct roots_view *view, double x, double y, + uint32_t width, uint32_t height) { + bool update_x = x != view->box.x; + bool update_y = y != view->box.y; + if (!update_x && !update_y) { + view_resize(view, width, height); + return; + } + + if (view->move_resize) { + view->move_resize(view, x, y, width, height); + return; + } + + view->pending_move_resize.update_x = update_x; + view->pending_move_resize.update_y = update_y; + view->pending_move_resize.x = x; + view->pending_move_resize.y = y; + view->pending_move_resize.width = width; + view->pending_move_resize.height = height; + + view_resize(view, width, height); +} + +static struct wlr_output *view_get_output(struct roots_view *view) { + struct wlr_box view_box; + view_get_box(view, &view_box); + + double output_x, output_y; + wlr_output_layout_closest_point(view->desktop->layout, NULL, + view->box.x + (double)view_box.width/2, + view->box.y + (double)view_box.height/2, + &output_x, &output_y); + return wlr_output_layout_output_at(view->desktop->layout, output_x, + output_y); +} + +void view_arrange_maximized(struct roots_view *view) { + struct wlr_box view_box; + view_get_box(view, &view_box); + + struct wlr_output *output = view_get_output(view); + struct roots_output *roots_output = output->data; + struct wlr_box *output_box = + wlr_output_layout_get_box(view->desktop->layout, output); + struct wlr_box usable_area; + memcpy(&usable_area, &roots_output->usable_area, + sizeof(struct wlr_box)); + usable_area.x += output_box->x; + usable_area.y += output_box->y; + + view_move_resize(view, usable_area.x, usable_area.y, + usable_area.width, usable_area.height); + view_rotate(view, 0); +} + +void view_maximize(struct roots_view *view, bool maximized) { + if (view->maximized == maximized) { + return; + } + + if (view->maximize) { + view->maximize(view, maximized); + } + + if (view->toplevel_handle) { + wlr_foreign_toplevel_handle_v1_set_maximized(view->toplevel_handle, + maximized); + } + + if (!view->maximized && maximized) { + view->maximized = true; + view->saved.x = view->box.x; + view->saved.y = view->box.y; + view->saved.rotation = view->rotation; + view->saved.width = view->box.width; + view->saved.height = view->box.height; + + view_arrange_maximized(view); + } + + if (view->maximized && !maximized) { + view->maximized = false; + + view_move_resize(view, view->saved.x, view->saved.y, view->saved.width, + view->saved.height); + view_rotate(view, view->saved.rotation); + } +} + +void view_set_fullscreen(struct roots_view *view, bool fullscreen, + struct wlr_output *output) { + bool was_fullscreen = view->fullscreen_output != NULL; + if (was_fullscreen == fullscreen) { + // TODO: support changing the output? + return; + } + + // TODO: check if client is focused? + + if (view->set_fullscreen) { + view->set_fullscreen(view, fullscreen); + } + + if (!was_fullscreen && fullscreen) { + if (output == NULL) { + output = view_get_output(view); + } + struct roots_output *roots_output = + desktop_output_from_wlr_output(view->desktop, output); + if (roots_output == NULL) { + return; + } + + struct wlr_box view_box; + view_get_box(view, &view_box); + + view->saved.x = view->box.x; + view->saved.y = view->box.y; + view->saved.rotation = view->rotation; + view->saved.width = view_box.width; + view->saved.height = view_box.height; + + struct wlr_box *output_box = + wlr_output_layout_get_box(view->desktop->layout, output); + view_move_resize(view, output_box->x, output_box->y, output_box->width, + output_box->height); + view_rotate(view, 0); + + roots_output->fullscreen_view = view; + view->fullscreen_output = roots_output; + output_damage_whole(roots_output); + } + + if (was_fullscreen && !fullscreen) { + view_move_resize(view, view->saved.x, view->saved.y, view->saved.width, + view->saved.height); + view_rotate(view, view->saved.rotation); + + output_damage_whole(view->fullscreen_output); + view->fullscreen_output->fullscreen_view = NULL; + view->fullscreen_output = NULL; + } +} + +void view_rotate(struct roots_view *view, float rotation) { + if (view->rotation == rotation) { + return; + } + + view_damage_whole(view); + view->rotation = rotation; + view_damage_whole(view); +} + +void view_cycle_alpha(struct roots_view *view) { + view->alpha -= 0.05; + /* Don't go completely transparent */ + if (view->alpha < 0.1) { + view->alpha = 1.0; + } + view_damage_whole(view); +} + +void view_close(struct roots_view *view) { + if (view->close) { + view->close(view); + } +} + +bool view_center(struct roots_view *view) { + struct wlr_box box; + view_get_box(view, &box); + + struct roots_desktop *desktop = view->desktop; + struct roots_input *input = desktop->server->input; + struct roots_seat *seat = input_last_active_seat(input); + if (!seat) { + return false; + } + + struct wlr_output *output = + wlr_output_layout_output_at(desktop->layout, + seat->cursor->cursor->x, + seat->cursor->cursor->y); + if (!output) { + // empty layout + return false; + } + + const struct wlr_output_layout_output *l_output = + wlr_output_layout_get(desktop->layout, output); + + int width, height; + wlr_output_effective_resolution(output, &width, &height); + + double view_x = (double)(width - box.width) / 2 + l_output->x; + double view_y = (double)(height - box.height) / 2 + l_output->y; + view_move(view, view_x, view_y); + + return true; +} + +void view_child_finish(struct roots_view_child *child) { + if (child == NULL) { + return; + } + view_damage_whole(child->view); + wl_list_remove(&child->link); + wl_list_remove(&child->commit.link); + wl_list_remove(&child->new_subsurface.link); +} + +static void view_child_handle_commit(struct wl_listener *listener, + void *data) { + struct roots_view_child *child = wl_container_of(listener, child, commit); + view_apply_damage(child->view); +} + +static void view_child_handle_new_subsurface(struct wl_listener *listener, + void *data) { + struct roots_view_child *child = + wl_container_of(listener, child, new_subsurface); + struct wlr_subsurface *wlr_subsurface = data; + subsurface_create(child->view, wlr_subsurface); +} + +void view_child_init(struct roots_view_child *child, struct roots_view *view, + struct wlr_surface *wlr_surface) { + assert(child->destroy); + child->view = view; + child->wlr_surface = wlr_surface; + child->commit.notify = view_child_handle_commit; + wl_signal_add(&wlr_surface->events.commit, &child->commit); + child->new_subsurface.notify = view_child_handle_new_subsurface; + wl_signal_add(&wlr_surface->events.new_subsurface, &child->new_subsurface); + wl_list_insert(&view->children, &child->link); +} + +static void subsurface_destroy(struct roots_view_child *child) { + assert(child->destroy == subsurface_destroy); + struct roots_subsurface *subsurface = (struct roots_subsurface *)child; + if (subsurface == NULL) { + return; + } + wl_list_remove(&subsurface->destroy.link); + view_child_finish(&subsurface->view_child); + free(subsurface); +} + +static void subsurface_handle_destroy(struct wl_listener *listener, + void *data) { + struct roots_subsurface *subsurface = + wl_container_of(listener, subsurface, destroy); + subsurface_destroy((struct roots_view_child *)subsurface); +} + +struct roots_subsurface *subsurface_create(struct roots_view *view, + struct wlr_subsurface *wlr_subsurface) { + struct roots_subsurface *subsurface = + calloc(1, sizeof(struct roots_subsurface)); + if (subsurface == NULL) { + return NULL; + } + subsurface->wlr_subsurface = wlr_subsurface; + subsurface->view_child.destroy = subsurface_destroy; + view_child_init(&subsurface->view_child, view, wlr_subsurface->surface); + subsurface->destroy.notify = subsurface_handle_destroy; + wl_signal_add(&wlr_subsurface->events.destroy, &subsurface->destroy); + input_update_cursor_focus(view->desktop->server->input); + return subsurface; +} + +void view_destroy(struct roots_view *view) { + if (view == NULL) { + return; + } + + wl_signal_emit(&view->events.destroy, view); + + if (view->wlr_surface != NULL) { + view_unmap(view); + } + + // Can happen if fullscreened while unmapped, and hasn't been mapped + if (view->fullscreen_output != NULL) { + view->fullscreen_output->fullscreen_view = NULL; + } + + if (view->destroy) { + view->destroy(view); + } + + free(view); +} + +static void view_handle_new_subsurface(struct wl_listener *listener, + void *data) { + struct roots_view *view = wl_container_of(listener, view, new_subsurface); + struct wlr_subsurface *wlr_subsurface = data; + subsurface_create(view, wlr_subsurface); +} + +void view_map(struct roots_view *view, struct wlr_surface *surface) { + assert(view->wlr_surface == NULL); + + view->wlr_surface = surface; + + struct wlr_subsurface *subsurface; + wl_list_for_each(subsurface, &view->wlr_surface->subsurfaces, + parent_link) { + subsurface_create(view, subsurface); + } + + view->new_subsurface.notify = view_handle_new_subsurface; + wl_signal_add(&view->wlr_surface->events.new_subsurface, + &view->new_subsurface); + + wl_list_insert(&view->desktop->views, &view->link); + view_damage_whole(view); + input_update_cursor_focus(view->desktop->server->input); +} + +void view_unmap(struct roots_view *view) { + assert(view->wlr_surface != NULL); + + wl_signal_emit(&view->events.unmap, view); + + view_damage_whole(view); + wl_list_remove(&view->link); + + wl_list_remove(&view->new_subsurface.link); + + struct roots_view_child *child, *tmp; + wl_list_for_each_safe(child, tmp, &view->children, link) { + child->destroy(child); + } + + if (view->fullscreen_output != NULL) { + output_damage_whole(view->fullscreen_output); + view->fullscreen_output->fullscreen_view = NULL; + view->fullscreen_output = NULL; + } + + view->wlr_surface = NULL; + view->box.width = view->box.height = 0; + + if (view->toplevel_handle) { + wlr_foreign_toplevel_handle_v1_destroy(view->toplevel_handle); + view->toplevel_handle = NULL; + } +} + +void view_initial_focus(struct roots_view *view) { + struct roots_input *input = view->desktop->server->input; + // TODO what seat gets focus? the one with the last input event? + struct roots_seat *seat; + wl_list_for_each(seat, &input->seats, link) { + roots_seat_set_focus(seat, view); + } +} + +void view_setup(struct roots_view *view) { + view_initial_focus(view); + + if (view->fullscreen_output == NULL && !view->maximized) { + view_center(view); + } + + view_create_foreign_toplevel_handle(view); + view_update_output(view, NULL); +} + +void view_apply_damage(struct roots_view *view) { + struct roots_output *output; + wl_list_for_each(output, &view->desktop->outputs, link) { + output_damage_from_view(output, view); + } +} + +void view_damage_whole(struct roots_view *view) { + struct roots_output *output; + wl_list_for_each(output, &view->desktop->outputs, link) { + output_damage_whole_view(output, view); + } +} + +void view_update_position(struct roots_view *view, int x, int y) { + if (view->box.x == x && view->box.y == y) { + return; + } + + view_damage_whole(view); + view->box.x = x; + view->box.y = y; + view_damage_whole(view); +} + +void view_update_size(struct roots_view *view, int width, int height) { + if (view->box.width == width && view->box.height == height) { + return; + } + + view_damage_whole(view); + view->box.width = width; + view->box.height = height; + view_damage_whole(view); +} + +void view_update_decorated(struct roots_view *view, bool decorated) { + if (view->decorated == decorated) { + return; + } + + view_damage_whole(view); + view->decorated = decorated; + if (decorated) { + view->border_width = 4; + view->titlebar_height = 12; + } else { + view->border_width = 0; + view->titlebar_height = 0; + } + view_damage_whole(view); +} + +void view_set_title(struct roots_view *view, const char *title) { + if (view->toplevel_handle) { + wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle, title); + } +} + +void view_set_app_id(struct roots_view *view, const char *app_id) { + if (view->toplevel_handle) { + wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle, app_id); + } +} + +static void handle_toplevel_handle_request_maximize(struct wl_listener *listener, + void *data) { + struct roots_view *view = wl_container_of(listener, view, + toplevel_handle_request_maximize); + struct wlr_foreign_toplevel_handle_v1_maximized_event *event = data; + view_maximize(view, event->maximized); +} + +static void handle_toplevel_handle_request_activate(struct wl_listener *listener, + void *data) { + struct roots_view *view = + wl_container_of(listener, view, toplevel_handle_request_activate); + struct wlr_foreign_toplevel_handle_v1_activated_event *event = data; + + struct roots_seat *seat; + wl_list_for_each(seat, &view->desktop->server->input->seats, link) { + if (event->seat == seat->seat) { + roots_seat_set_focus(seat, view); + } + } +} + +static void handle_toplevel_handle_request_close(struct wl_listener *listener, + void *data) { + struct roots_view *view = + wl_container_of(listener, view, toplevel_handle_request_close); + view_close(view); +} + +void view_create_foreign_toplevel_handle(struct roots_view *view) { + view->toplevel_handle = + wlr_foreign_toplevel_handle_v1_create( + view->desktop->foreign_toplevel_manager_v1); + + view->toplevel_handle_request_maximize.notify = + handle_toplevel_handle_request_maximize; + wl_signal_add(&view->toplevel_handle->events.request_maximize, + &view->toplevel_handle_request_maximize); + view->toplevel_handle_request_activate.notify = + handle_toplevel_handle_request_activate; + wl_signal_add(&view->toplevel_handle->events.request_activate, + &view->toplevel_handle_request_activate); + view->toplevel_handle_request_close.notify = + handle_toplevel_handle_request_close; + wl_signal_add(&view->toplevel_handle->events.request_close, + &view->toplevel_handle_request_close); +} + +static bool view_at(struct roots_view *view, double lx, double ly, + struct wlr_surface **surface, double *sx, double *sy) { + if (view->type == ROOTS_WL_SHELL_VIEW && + view->wl_shell_surface->state == WLR_WL_SHELL_SURFACE_STATE_POPUP) { + return false; + } + if (view->wlr_surface == NULL) { + return false; + } + + double view_sx = lx - view->box.x; + double view_sy = ly - view->box.y; + + struct wlr_surface_state *state = &view->wlr_surface->current; + struct wlr_box box = { + .x = 0, .y = 0, + .width = state->width, .height = state->height, + }; + if (view->rotation != 0.0) { + // Coordinates relative to the center of the view + double ox = view_sx - (double)box.width/2, + oy = view_sy - (double)box.height/2; + // Rotated coordinates + double rx = cos(view->rotation)*ox + sin(view->rotation)*oy, + ry = cos(view->rotation)*oy - sin(view->rotation)*ox; + view_sx = rx + (double)box.width/2; + view_sy = ry + (double)box.height/2; + } + + double _sx, _sy; + struct wlr_surface *_surface = NULL; + switch (view->type) { + case ROOTS_XDG_SHELL_V6_VIEW: + _surface = wlr_xdg_surface_v6_surface_at(view->xdg_surface_v6, + view_sx, view_sy, &_sx, &_sy); + break; + case ROOTS_XDG_SHELL_VIEW: + _surface = wlr_xdg_surface_surface_at(view->xdg_surface, + view_sx, view_sy, &_sx, &_sy); + break; + case ROOTS_WL_SHELL_VIEW: + _surface = wlr_wl_shell_surface_surface_at(view->wl_shell_surface, + view_sx, view_sy, &_sx, &_sy); + break; +#if WLR_HAS_XWAYLAND + case ROOTS_XWAYLAND_VIEW: + _surface = wlr_surface_surface_at(view->wlr_surface, + view_sx, view_sy, &_sx, &_sy); + break; +#endif + } + if (_surface != NULL) { + *sx = _sx; + *sy = _sy; + *surface = _surface; + return true; + } + + if (view_get_deco_part(view, view_sx, view_sy)) { + *sx = view_sx; + *sy = view_sy; + *surface = NULL; + return true; + } + + return false; +} + +static struct roots_view *desktop_view_at(struct roots_desktop *desktop, + double lx, double ly, struct wlr_surface **surface, + double *sx, double *sy) { + struct wlr_output *wlr_output = + wlr_output_layout_output_at(desktop->layout, lx, ly); + if (wlr_output != NULL) { + struct roots_output *output = + desktop_output_from_wlr_output(desktop, wlr_output); + if (output != NULL && output->fullscreen_view != NULL) { + if (view_at(output->fullscreen_view, lx, ly, surface, sx, sy)) { + return output->fullscreen_view; + } else { + return NULL; + } + } + } + + struct roots_view *view; + wl_list_for_each(view, &desktop->views, link) { + if (view_at(view, lx, ly, surface, sx, sy)) { + return view; + } + } + return NULL; +} + +static struct wlr_surface *layer_surface_at(struct roots_output *output, + struct wl_list *layer, double ox, double oy, double *sx, double *sy) { + struct roots_layer_surface *roots_surface; + wl_list_for_each_reverse(roots_surface, layer, link) { + double _sx = ox - roots_surface->geo.x; + double _sy = oy - roots_surface->geo.y; + + struct wlr_surface *sub = wlr_layer_surface_v1_surface_at( + roots_surface->layer_surface, _sx, _sy, sx, sy); + + if (sub) { + return sub; + } + } + + return NULL; +} + +struct wlr_surface *desktop_surface_at(struct roots_desktop *desktop, + double lx, double ly, double *sx, double *sy, + struct roots_view **view) { + struct wlr_surface *surface = NULL; + struct wlr_output *wlr_output = + wlr_output_layout_output_at(desktop->layout, lx, ly); + struct roots_output *roots_output = NULL; + double ox = lx, oy = ly; + if (view) { + *view = NULL; + } + + if (wlr_output) { + roots_output = wlr_output->data; + wlr_output_layout_output_coords(desktop->layout, wlr_output, &ox, &oy); + + if ((surface = layer_surface_at(roots_output, + &roots_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], + ox, oy, sx, sy))) { + return surface; + } + if ((surface = layer_surface_at(roots_output, + &roots_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], + ox, oy, sx, sy))) { + return surface; + } + } + + struct roots_view *_view; + if ((_view = desktop_view_at(desktop, lx, ly, &surface, sx, sy))) { + if (view) { + *view = _view; + } + return surface; + } + + if (wlr_output) { + if ((surface = layer_surface_at(roots_output, + &roots_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], + ox, oy, sx, sy))) { + return surface; + } + if ((surface = layer_surface_at(roots_output, + &roots_output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], + ox, oy, sx, sy))) { + return surface; + } + } + return NULL; +} + +static void handle_layout_change(struct wl_listener *listener, void *data) { + struct roots_desktop *desktop = + wl_container_of(listener, desktop, layout_change); + + struct wlr_output *center_output = + wlr_output_layout_get_center_output(desktop->layout); + if (center_output == NULL) { + return; + } + + struct wlr_box *center_output_box = + wlr_output_layout_get_box(desktop->layout, center_output); + double center_x = center_output_box->x + center_output_box->width/2; + double center_y = center_output_box->y + center_output_box->height/2; + + struct roots_view *view; + wl_list_for_each(view, &desktop->views, link) { + struct wlr_box box; + view_get_box(view, &box); + + if (wlr_output_layout_intersects(desktop->layout, NULL, &box)) { + continue; + } + + view_move(view, center_x - box.width/2, center_y - box.height/2); + } +} + +static void input_inhibit_activate(struct wl_listener *listener, void *data) { + struct roots_desktop *desktop = wl_container_of( + listener, desktop, input_inhibit_activate); + struct roots_seat *seat; + wl_list_for_each(seat, &desktop->server->input->seats, link) { + roots_seat_set_exclusive_client(seat, + desktop->input_inhibit->active_client); + } +} + +static void input_inhibit_deactivate(struct wl_listener *listener, void *data) { + struct roots_desktop *desktop = wl_container_of( + listener, desktop, input_inhibit_deactivate); + struct roots_seat *seat; + wl_list_for_each(seat, &desktop->server->input->seats, link) { + roots_seat_set_exclusive_client(seat, NULL); + } +} + +static void handle_constraint_destroy(struct wl_listener *listener, + void *data) { + struct roots_pointer_constraint *constraint = + wl_container_of(listener, constraint, destroy); + struct wlr_pointer_constraint_v1 *wlr_constraint = data; + struct roots_seat *seat = wlr_constraint->seat->data; + + wl_list_remove(&constraint->destroy.link); + + if (seat->cursor->active_constraint == wlr_constraint) { + wl_list_remove(&seat->cursor->constraint_commit.link); + wl_list_init(&seat->cursor->constraint_commit.link); + seat->cursor->active_constraint = NULL; + + if (wlr_constraint->current.committed & + WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT && + seat->cursor->pointer_view) { + double sx = wlr_constraint->current.cursor_hint.x; + double sy = wlr_constraint->current.cursor_hint.y; + + struct roots_view *view = seat->cursor->pointer_view->view; + rotate_child_position(&sx, &sy, 0, 0, view->box.width, view->box.height, + view->rotation); + double lx = view->box.x + sx; + double ly = view->box.y + sy; + + wlr_cursor_warp(seat->cursor->cursor, NULL, lx, ly); + } + } + + free(constraint); +} + +static void handle_pointer_constraint(struct wl_listener *listener, + void *data) { + struct wlr_pointer_constraint_v1 *wlr_constraint = data; + struct roots_seat *seat = wlr_constraint->seat->data; + + struct roots_pointer_constraint *constraint = + calloc(1, sizeof(struct roots_pointer_constraint)); + constraint->constraint = wlr_constraint; + + constraint->destroy.notify = handle_constraint_destroy; + wl_signal_add(&wlr_constraint->events.destroy, &constraint->destroy); + + double sx, sy; + struct wlr_surface *surface = desktop_surface_at( + seat->input->server->desktop, + seat->cursor->cursor->x, seat->cursor->cursor->y, &sx, &sy, NULL); + + if (surface == wlr_constraint->surface) { + assert(!seat->cursor->active_constraint); + roots_cursor_constrain(seat->cursor, wlr_constraint, sx, sy); + } +} + +struct roots_desktop *desktop_create(struct roots_server *server, + struct roots_config *config) { + wlr_log(WLR_DEBUG, "Initializing roots desktop"); + + struct roots_desktop *desktop = calloc(1, sizeof(struct roots_desktop)); + if (desktop == NULL) { + return NULL; + } + + wl_list_init(&desktop->views); + wl_list_init(&desktop->outputs); + + desktop->new_output.notify = handle_new_output; + wl_signal_add(&server->backend->events.new_output, &desktop->new_output); + + desktop->server = server; + desktop->config = config; + + desktop->layout = wlr_output_layout_create(); + wlr_xdg_output_manager_v1_create(server->wl_display, desktop->layout); + desktop->layout_change.notify = handle_layout_change; + wl_signal_add(&desktop->layout->events.change, &desktop->layout_change); + + desktop->compositor = wlr_compositor_create(server->wl_display, + server->renderer); + + desktop->xdg_shell_v6 = wlr_xdg_shell_v6_create(server->wl_display); + wl_signal_add(&desktop->xdg_shell_v6->events.new_surface, + &desktop->xdg_shell_v6_surface); + desktop->xdg_shell_v6_surface.notify = handle_xdg_shell_v6_surface; + + desktop->xdg_shell = wlr_xdg_shell_create(server->wl_display); + wl_signal_add(&desktop->xdg_shell->events.new_surface, + &desktop->xdg_shell_surface); + desktop->xdg_shell_surface.notify = handle_xdg_shell_surface; + + desktop->wl_shell = wlr_wl_shell_create(server->wl_display); + wl_signal_add(&desktop->wl_shell->events.new_surface, + &desktop->wl_shell_surface); + desktop->wl_shell_surface.notify = handle_wl_shell_surface; + + desktop->layer_shell = wlr_layer_shell_v1_create(server->wl_display); + wl_signal_add(&desktop->layer_shell->events.new_surface, + &desktop->layer_shell_surface); + desktop->layer_shell_surface.notify = handle_layer_shell_surface; + + desktop->tablet_v2 = wlr_tablet_v2_create(server->wl_display); + + const char *cursor_theme = NULL; +#if WLR_HAS_XWAYLAND + const char *cursor_default = ROOTS_XCURSOR_DEFAULT; +#endif + struct roots_cursor_config *cc = + roots_config_get_cursor(config, ROOTS_CONFIG_DEFAULT_SEAT_NAME); + if (cc != NULL) { + cursor_theme = cc->theme; +#if WLR_HAS_XWAYLAND + if (cc->default_image != NULL) { + cursor_default = cc->default_image; + } +#endif + } + + char cursor_size_fmt[16]; + snprintf(cursor_size_fmt, sizeof(cursor_size_fmt), + "%d", ROOTS_XCURSOR_SIZE); + setenv("XCURSOR_SIZE", cursor_size_fmt, 1); + if (cursor_theme != NULL) { + setenv("XCURSOR_THEME", cursor_theme, 1); + } + +#if WLR_HAS_XWAYLAND + desktop->xcursor_manager = wlr_xcursor_manager_create(cursor_theme, + ROOTS_XCURSOR_SIZE); + if (desktop->xcursor_manager == NULL) { + wlr_log(WLR_ERROR, "Cannot create XCursor manager for theme %s", + cursor_theme); + free(desktop); + return NULL; + } + + if (config->xwayland) { + desktop->xwayland = wlr_xwayland_create(server->wl_display, + desktop->compositor, config->xwayland_lazy); + wl_signal_add(&desktop->xwayland->events.new_surface, + &desktop->xwayland_surface); + desktop->xwayland_surface.notify = handle_xwayland_surface; + + if (wlr_xcursor_manager_load(desktop->xcursor_manager, 1)) { + wlr_log(WLR_ERROR, "Cannot load XWayland XCursor theme"); + } + struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor( + desktop->xcursor_manager, cursor_default, 1); + if (xcursor != NULL) { + struct wlr_xcursor_image *image = xcursor->images[0]; + wlr_xwayland_set_cursor(desktop->xwayland, image->buffer, + image->width * 4, image->width, image->height, image->hotspot_x, + image->hotspot_y); + } + } +#endif + + desktop->gamma_control_manager = wlr_gamma_control_manager_create( + server->wl_display); + desktop->gamma_control_manager_v1 = wlr_gamma_control_manager_v1_create( + server->wl_display); + desktop->screenshooter = wlr_screenshooter_create(server->wl_display); + desktop->export_dmabuf_manager_v1 = + wlr_export_dmabuf_manager_v1_create(server->wl_display); + desktop->server_decoration_manager = + wlr_server_decoration_manager_create(server->wl_display); + wlr_server_decoration_manager_set_default_mode( + desktop->server_decoration_manager, + WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT); + desktop->idle = wlr_idle_create(server->wl_display); + desktop->idle_inhibit = wlr_idle_inhibit_v1_create(server->wl_display); + desktop->primary_selection_device_manager = + wlr_gtk_primary_selection_device_manager_create(server->wl_display); + desktop->input_inhibit = + wlr_input_inhibit_manager_create(server->wl_display); + desktop->input_inhibit_activate.notify = input_inhibit_activate; + wl_signal_add(&desktop->input_inhibit->events.activate, + &desktop->input_inhibit_activate); + desktop->input_inhibit_deactivate.notify = input_inhibit_deactivate; + wl_signal_add(&desktop->input_inhibit->events.deactivate, + &desktop->input_inhibit_deactivate); + + desktop->input_method = + wlr_input_method_manager_v2_create(server->wl_display); + + desktop->text_input = wlr_text_input_manager_v3_create(server->wl_display); + + desktop->virtual_keyboard = wlr_virtual_keyboard_manager_v1_create( + server->wl_display); + wl_signal_add(&desktop->virtual_keyboard->events.new_virtual_keyboard, + &desktop->virtual_keyboard_new); + desktop->virtual_keyboard_new.notify = handle_virtual_keyboard; + + desktop->screencopy = wlr_screencopy_manager_v1_create(server->wl_display); + + desktop->xdg_decoration_manager = + wlr_xdg_decoration_manager_v1_create(server->wl_display); + wl_signal_add(&desktop->xdg_decoration_manager->events.new_toplevel_decoration, + &desktop->xdg_toplevel_decoration); + desktop->xdg_toplevel_decoration.notify = handle_xdg_toplevel_decoration; + + desktop->pointer_constraints = + wlr_pointer_constraints_v1_create(server->wl_display); + desktop->pointer_constraint.notify = handle_pointer_constraint; + wl_signal_add(&desktop->pointer_constraints->events.new_constraint, + &desktop->pointer_constraint); + + desktop->presentation = + wlr_presentation_create(server->wl_display, server->backend); + desktop->foreign_toplevel_manager_v1 = + wlr_foreign_toplevel_manager_v1_create(server->wl_display); + + return desktop; +} + +void desktop_destroy(struct roots_desktop *desktop) { + // TODO +} + +struct roots_output *desktop_output_from_wlr_output( + struct roots_desktop *desktop, struct wlr_output *wlr_output) { + struct roots_output *output; + wl_list_for_each(output, &desktop->outputs, link) { + if (output->wlr_output == wlr_output) { + return output; + } + } + return NULL; +} diff --git a/rootston/ini.c b/rootston/ini.c new file mode 100644 index 00000000..f515dd38 --- /dev/null +++ b/rootston/ini.c @@ -0,0 +1,195 @@ +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include <stdio.h> +#include <ctype.h> +#include <string.h> + +#include "rootston/ini.h" + +#if !INI_USE_STACK +#include <stdlib.h> +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to null at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +static char* find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Version of strncpy that ensures dest (size bytes) is null-terminated. */ +static char* strncpy0(char* dest, const char* src, size_t size) +{ + strncpy(dest, src, size-1); + dest[size - 1] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; +#else + char* line; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)malloc(INI_MAX_LINE); + if (!line) { + return -2; + } +#endif + + /* Scan through stream line by line */ + while (reader(line, INI_MAX_LINE, stream) != NULL) { + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = lskip(rstrip(start)); + + if (*start == ';' || *start == '#') { + /* Per Python configparser, allow both ; and # comments at the + start of a line */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!handler(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = lskip(end + 1); +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!handler(user, section, name, value) && !error) + error = lineno; + memset(value, 0, strlen(value)); + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ + error = lineno; + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} diff --git a/rootston/input.c b/rootston/input.c new file mode 100644 index 00000000..a863b919 --- /dev/null +++ b/rootston/input.c @@ -0,0 +1,145 @@ +#define _POSIX_C_SOURCE 200112L +#include <assert.h> +#include <stdlib.h> +#include <time.h> +#include <wayland-server.h> +#include <wlr/backend/libinput.h> +#include <wlr/config.h> +#include <wlr/types/wlr_cursor.h> +#include <wlr/util/log.h> +#include <wlr/xcursor.h> +#if WLR_HAS_XWAYLAND +#include <wlr/xwayland.h> +#endif +#include "rootston/config.h" +#include "rootston/input.h" +#include "rootston/keyboard.h" +#include "rootston/seat.h" +#include "rootston/server.h" + +static const char *device_type(enum wlr_input_device_type type) { + switch (type) { + case WLR_INPUT_DEVICE_KEYBOARD: + return "keyboard"; + case WLR_INPUT_DEVICE_POINTER: + return "pointer"; + case WLR_INPUT_DEVICE_SWITCH: + return "switch"; + case WLR_INPUT_DEVICE_TOUCH: + return "touch"; + case WLR_INPUT_DEVICE_TABLET_TOOL: + return "tablet tool"; + case WLR_INPUT_DEVICE_TABLET_PAD: + return "tablet pad"; + } + return NULL; +} + +struct roots_seat *input_get_seat(struct roots_input *input, char *name) { + struct roots_seat *seat = NULL; + wl_list_for_each(seat, &input->seats, link) { + if (strcmp(seat->seat->name, name) == 0) { + return seat; + } + } + + seat = roots_seat_create(input, name); + return seat; +} + +static void handle_new_input(struct wl_listener *listener, void *data) { + struct wlr_input_device *device = data; + struct roots_input *input = wl_container_of(listener, input, new_input); + + char *seat_name = ROOTS_CONFIG_DEFAULT_SEAT_NAME; + struct roots_device_config *dc = + roots_config_get_device(input->config, device); + if (dc) { + seat_name = dc->seat; + } + + struct roots_seat *seat = input_get_seat(input, seat_name); + if (!seat) { + wlr_log(WLR_ERROR, "could not create roots seat"); + return; + } + + wlr_log(WLR_DEBUG, "New input device: %s (%d:%d) %s seat:%s", device->name, + device->vendor, device->product, device_type(device->type), seat_name); + + roots_seat_add_device(seat, device); + + if (dc && wlr_input_device_is_libinput(device)) { + struct libinput_device *libinput_dev = + wlr_libinput_get_device_handle(device); + + wlr_log(WLR_DEBUG, "input has config, tap_enabled: %d\n", dc->tap_enabled); + if (dc->tap_enabled) { + libinput_device_config_tap_set_enabled(libinput_dev, + LIBINPUT_CONFIG_TAP_ENABLED); + } + } +} + +struct roots_input *input_create(struct roots_server *server, + struct roots_config *config) { + wlr_log(WLR_DEBUG, "Initializing roots input"); + assert(server->desktop); + + struct roots_input *input = calloc(1, sizeof(struct roots_input)); + if (input == NULL) { + return NULL; + } + + input->config = config; + input->server = server; + + wl_list_init(&input->seats); + + input->new_input.notify = handle_new_input; + wl_signal_add(&server->backend->events.new_input, &input->new_input); + + return input; +} + +void input_destroy(struct roots_input *input) { + // TODO +} + +struct roots_seat *input_seat_from_wlr_seat(struct roots_input *input, + struct wlr_seat *wlr_seat) { + struct roots_seat *seat = NULL; + wl_list_for_each(seat, &input->seats, link) { + if (seat->seat == wlr_seat) { + return seat; + } + } + return seat; +} + +bool input_view_has_focus(struct roots_input *input, struct roots_view *view) { + if (!view) { + return false; + } + struct roots_seat *seat; + wl_list_for_each(seat, &input->seats, link) { + if (view == roots_seat_get_focus(seat)) { + return true; + } + } + + return false; +} + +static inline int64_t timespec_to_msec(const struct timespec *a) { + return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000; +} + +void input_update_cursor_focus(struct roots_input *input) { + struct roots_seat *seat; + struct timespec now; + wl_list_for_each(seat, &input->seats, link) { + clock_gettime(CLOCK_MONOTONIC, &now); + roots_cursor_update_position(seat->cursor, timespec_to_msec(&now)); + } +} diff --git a/rootston/keyboard.c b/rootston/keyboard.c new file mode 100644 index 00000000..9d3fadf6 --- /dev/null +++ b/rootston/keyboard.c @@ -0,0 +1,349 @@ +#include <assert.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <wayland-server.h> +#include <wlr/backend/session.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_pointer_constraints_v1.h> +#include <wlr/types/wlr_pointer.h> +#include <wlr/util/log.h> +#include <xkbcommon/xkbcommon.h> +#include "rootston/bindings.h" +#include "rootston/input.h" +#include "rootston/keyboard.h" +#include "rootston/seat.h" + +static ssize_t pressed_keysyms_index(xkb_keysym_t *pressed_keysyms, + xkb_keysym_t keysym) { + for (size_t i = 0; i < ROOTS_KEYBOARD_PRESSED_KEYSYMS_CAP; ++i) { + if (pressed_keysyms[i] == keysym) { + return i; + } + } + return -1; +} + +static size_t pressed_keysyms_length(xkb_keysym_t *pressed_keysyms) { + size_t n = 0; + for (size_t i = 0; i < ROOTS_KEYBOARD_PRESSED_KEYSYMS_CAP; ++i) { + if (pressed_keysyms[i] != XKB_KEY_NoSymbol) { + ++n; + } + } + return n; +} + +static void pressed_keysyms_add(xkb_keysym_t *pressed_keysyms, + xkb_keysym_t keysym) { + ssize_t i = pressed_keysyms_index(pressed_keysyms, keysym); + if (i < 0) { + i = pressed_keysyms_index(pressed_keysyms, XKB_KEY_NoSymbol); + if (i >= 0) { + pressed_keysyms[i] = keysym; + } + } +} + +static void pressed_keysyms_remove(xkb_keysym_t *pressed_keysyms, + xkb_keysym_t keysym) { + ssize_t i = pressed_keysyms_index(pressed_keysyms, keysym); + if (i >= 0) { + pressed_keysyms[i] = XKB_KEY_NoSymbol; + } +} + +static bool keysym_is_modifier(xkb_keysym_t keysym) { + switch (keysym) { + case XKB_KEY_Shift_L: case XKB_KEY_Shift_R: + case XKB_KEY_Control_L: case XKB_KEY_Control_R: + case XKB_KEY_Caps_Lock: + case XKB_KEY_Shift_Lock: + case XKB_KEY_Meta_L: case XKB_KEY_Meta_R: + case XKB_KEY_Alt_L: case XKB_KEY_Alt_R: + case XKB_KEY_Super_L: case XKB_KEY_Super_R: + case XKB_KEY_Hyper_L: case XKB_KEY_Hyper_R: + return true; + default: + return false; + } +} + +static void pressed_keysyms_update(xkb_keysym_t *pressed_keysyms, + const xkb_keysym_t *keysyms, size_t keysyms_len, + enum wlr_key_state state) { + for (size_t i = 0; i < keysyms_len; ++i) { + if (keysym_is_modifier(keysyms[i])) { + continue; + } + if (state == WLR_KEY_PRESSED) { + pressed_keysyms_add(pressed_keysyms, keysyms[i]); + } else { // WLR_KEY_RELEASED + pressed_keysyms_remove(pressed_keysyms, keysyms[i]); + } + } +} + +static void keyboard_binding_execute(struct roots_keyboard *keyboard, + const char *command) { + execute_binding_command(keyboard->seat, keyboard->input, command); +} + +/** + * Execute a built-in, hardcoded compositor binding. These are triggered from a + * single keysym. + * + * Returns true if the keysym was handled by a binding and false if the event + * should be propagated to clients. + */ +static bool keyboard_execute_compositor_binding(struct roots_keyboard *keyboard, + xkb_keysym_t keysym) { + if (keysym >= XKB_KEY_XF86Switch_VT_1 && + keysym <= XKB_KEY_XF86Switch_VT_12) { + struct roots_server *server = keyboard->input->server; + + struct wlr_session *session = wlr_backend_get_session(server->backend); + if (session) { + unsigned vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1; + wlr_session_change_vt(session, vt); + } + + return true; + } + + if (keysym == XKB_KEY_Escape) { + wlr_seat_pointer_end_grab(keyboard->seat->seat); + wlr_seat_keyboard_end_grab(keyboard->seat->seat); + roots_seat_end_compositor_grab(keyboard->seat); + } + + return false; +} + +/** + * Execute keyboard bindings. These include compositor bindings and user-defined + * bindings. + * + * Returns true if the keysym was handled by a binding and false if the event + * should be propagated to clients. + */ +static bool keyboard_execute_binding(struct roots_keyboard *keyboard, + xkb_keysym_t *pressed_keysyms, uint32_t modifiers, + const xkb_keysym_t *keysyms, size_t keysyms_len) { + for (size_t i = 0; i < keysyms_len; ++i) { + if (keyboard_execute_compositor_binding(keyboard, keysyms[i])) { + return true; + } + } + + // User-defined bindings + size_t n = pressed_keysyms_length(pressed_keysyms); + struct wl_list *bindings = &keyboard->input->server->config->bindings; + struct roots_binding_config *bc; + wl_list_for_each(bc, bindings, link) { + if (modifiers ^ bc->modifiers || n != bc->keysyms_len) { + continue; + } + + bool ok = true; + for (size_t i = 0; i < bc->keysyms_len; i++) { + ssize_t j = pressed_keysyms_index(pressed_keysyms, bc->keysyms[i]); + if (j < 0) { + ok = false; + break; + } + } + + if (ok) { + keyboard_binding_execute(keyboard, bc->command); + return true; + } + } + + return false; +} + +/* + * Get keysyms and modifiers from the keyboard as xkb sees them. + * + * This uses the xkb keysyms translation based on pressed modifiers and clears + * the consumed modifiers from the list of modifiers passed to keybind + * detection. + * + * On US layout, pressing Alt+Shift+2 will trigger Alt+@. + */ +static size_t keyboard_keysyms_translated(struct roots_keyboard *keyboard, + xkb_keycode_t keycode, const xkb_keysym_t **keysyms, + uint32_t *modifiers) { + *modifiers = wlr_keyboard_get_modifiers(keyboard->device->keyboard); + xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods2( + keyboard->device->keyboard->xkb_state, keycode, XKB_CONSUMED_MODE_XKB); + *modifiers = *modifiers & ~consumed; + + return xkb_state_key_get_syms(keyboard->device->keyboard->xkb_state, + keycode, keysyms); +} + +/* + * Get keysyms and modifiers from the keyboard as if modifiers didn't change + * keysyms. + * + * This avoids the xkb keysym translation based on modifiers considered pressed + * in the state. + * + * This will trigger keybinds such as Alt+Shift+2. + */ +static size_t keyboard_keysyms_raw(struct roots_keyboard *keyboard, + xkb_keycode_t keycode, const xkb_keysym_t **keysyms, + uint32_t *modifiers) { + *modifiers = wlr_keyboard_get_modifiers(keyboard->device->keyboard); + + xkb_layout_index_t layout_index = xkb_state_key_get_layout( + keyboard->device->keyboard->xkb_state, keycode); + return xkb_keymap_key_get_syms_by_level(keyboard->device->keyboard->keymap, + keycode, layout_index, 0, keysyms); +} + +void roots_keyboard_handle_key(struct roots_keyboard *keyboard, + struct wlr_event_keyboard_key *event) { + xkb_keycode_t keycode = event->keycode + 8; + + bool handled = false; + uint32_t modifiers; + const xkb_keysym_t *keysyms; + size_t keysyms_len; + + // Handle translated keysyms + + keysyms_len = keyboard_keysyms_translated(keyboard, keycode, &keysyms, + &modifiers); + pressed_keysyms_update(keyboard->pressed_keysyms_translated, keysyms, + keysyms_len, event->state); + if (event->state == WLR_KEY_PRESSED) { + handled = keyboard_execute_binding(keyboard, + keyboard->pressed_keysyms_translated, modifiers, keysyms, + keysyms_len); + } + + // Handle raw keysyms + keysyms_len = keyboard_keysyms_raw(keyboard, keycode, &keysyms, &modifiers); + pressed_keysyms_update(keyboard->pressed_keysyms_raw, keysyms, keysyms_len, + event->state); + if (event->state == WLR_KEY_PRESSED && !handled) { + handled = keyboard_execute_binding(keyboard, + keyboard->pressed_keysyms_raw, modifiers, keysyms, keysyms_len); + } + + if (!handled) { + wlr_seat_set_keyboard(keyboard->seat->seat, keyboard->device); + wlr_seat_keyboard_notify_key(keyboard->seat->seat, event->time_msec, + event->keycode, event->state); + } +} + +void roots_keyboard_handle_modifiers(struct roots_keyboard *r_keyboard) { + struct wlr_seat *seat = r_keyboard->seat->seat; + wlr_seat_set_keyboard(seat, r_keyboard->device); + wlr_seat_keyboard_notify_modifiers(seat, + &r_keyboard->device->keyboard->modifiers); +} + +static void keyboard_config_merge(struct roots_keyboard_config *config, + struct roots_keyboard_config *fallback) { + if (fallback == NULL) { + return; + } + if (config->rules == NULL) { + config->rules = fallback->rules; + } + if (config->model == NULL) { + config->model = fallback->model; + } + if (config->layout == NULL) { + config->layout = fallback->layout; + } + if (config->variant == NULL) { + config->variant = fallback->variant; + } + if (config->options == NULL) { + config->options = fallback->options; + } + if (config->meta_key == 0) { + config->meta_key = fallback->meta_key; + } + if (config->name == NULL) { + config->name = fallback->name; + } + if (config->repeat_rate <= 0) { + config->repeat_rate = fallback->repeat_rate; + } + if (config->repeat_delay <= 0) { + config->repeat_delay = fallback->repeat_delay; + } +} + +struct roots_keyboard *roots_keyboard_create(struct wlr_input_device *device, + struct roots_input *input) { + struct roots_keyboard *keyboard = calloc(sizeof(struct roots_keyboard), 1); + if (keyboard == NULL) { + return NULL; + } + device->data = keyboard; + keyboard->device = device; + keyboard->input = input; + + struct roots_keyboard_config *config = + calloc(1, sizeof(struct roots_keyboard_config)); + if (config == NULL) { + free(keyboard); + return NULL; + } + keyboard_config_merge(config, roots_config_get_keyboard(input->config, device)); + keyboard_config_merge(config, roots_config_get_keyboard(input->config, NULL)); + + struct roots_keyboard_config env_config = { + .rules = getenv("XKB_DEFAULT_RULES"), + .model = getenv("XKB_DEFAULT_MODEL"), + .layout = getenv("XKB_DEFAULT_LAYOUT"), + .variant = getenv("XKB_DEFAULT_VARIANT"), + .options = getenv("XKB_DEFAULT_OPTIONS"), + }; + keyboard_config_merge(config, &env_config); + keyboard->config = config; + + struct xkb_rule_names rules = { 0 }; + rules.rules = config->rules; + rules.model = config->model; + rules.layout = config->layout; + rules.variant = config->variant; + rules.options = config->options; + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (context == NULL) { + wlr_log(WLR_ERROR, "Cannot create XKB context"); + return NULL; + } + + struct xkb_keymap *keymap = xkb_map_new_from_names(context, &rules, + XKB_KEYMAP_COMPILE_NO_FLAGS); + if (keymap == NULL) { + xkb_context_unref(context); + wlr_log(WLR_ERROR, "Cannot create XKB keymap"); + return NULL; + } + + wlr_keyboard_set_keymap(device->keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + + int repeat_rate = (config->repeat_rate > 0) ? config->repeat_rate : 25; + int repeat_delay = (config->repeat_delay > 0) ? config->repeat_delay : 600; + wlr_keyboard_set_repeat_info(device->keyboard, repeat_rate, repeat_delay); + + return keyboard; +} + +void roots_keyboard_destroy(struct roots_keyboard *keyboard) { + wl_list_remove(&keyboard->link); + free(keyboard->config); + free(keyboard); +} diff --git a/rootston/layer_shell.c b/rootston/layer_shell.c new file mode 100644 index 00000000..4daa20d1 --- /dev/null +++ b/rootston/layer_shell.c @@ -0,0 +1,506 @@ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200112L +#endif +#include <assert.h> +#include <errno.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <wayland-server.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_surface.h> +#include <wlr/types/wlr_layer_shell_v1.h> +#include <wlr/util/log.h> +#include "rootston/desktop.h" +#include "rootston/layers.h" +#include "rootston/output.h" +#include "rootston/server.h" + +static void apply_exclusive(struct wlr_box *usable_area, + uint32_t anchor, int32_t exclusive, + int32_t margin_top, int32_t margin_right, + int32_t margin_bottom, int32_t margin_left) { + if (exclusive <= 0) { + return; + } + struct { + uint32_t anchors; + int *positive_axis; + int *negative_axis; + int margin; + } edges[] = { + { + .anchors = + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, + .positive_axis = &usable_area->y, + .negative_axis = &usable_area->height, + .margin = margin_top, + }, + { + .anchors = + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, + .positive_axis = NULL, + .negative_axis = &usable_area->height, + .margin = margin_bottom, + }, + { + .anchors = + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, + .positive_axis = &usable_area->x, + .negative_axis = &usable_area->width, + .margin = margin_left, + }, + { + .anchors = + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, + .positive_axis = NULL, + .negative_axis = &usable_area->width, + .margin = margin_right, + }, + }; + for (size_t i = 0; i < sizeof(edges) / sizeof(edges[0]); ++i) { + if ((anchor & edges[i].anchors) == edges[i].anchors) { + if (edges[i].positive_axis) { + *edges[i].positive_axis += exclusive + edges[i].margin; + } + if (edges[i].negative_axis) { + *edges[i].negative_axis -= exclusive + edges[i].margin; + } + } + } +} + +static void update_cursors(struct roots_layer_surface *roots_surface, + struct wl_list *seats /* struct roots_seat */) { + struct roots_seat *seat; + wl_list_for_each(seat, seats, link) { + double sx, sy; + + struct wlr_surface *surface = desktop_surface_at( + seat->input->server->desktop, + seat->cursor->cursor->x, seat->cursor->cursor->y, &sx, &sy, NULL); + + if (surface == roots_surface->layer_surface->surface) { + struct timespec time; + if (clock_gettime(CLOCK_MONOTONIC, &time) == 0) { + roots_cursor_update_position(seat->cursor, + time.tv_sec * 1000 + time.tv_nsec / 1000000); + } else { + wlr_log(WLR_ERROR, "Failed to get time, not updating" + "position. Errno: %s\n", strerror(errno)); + } + } + } +} + +static void arrange_layer(struct wlr_output *output, + struct wl_list *seats /* struct *roots_seat */, + struct wl_list *list /* struct *roots_layer_surface */, + struct wlr_box *usable_area, bool exclusive) { + struct roots_layer_surface *roots_surface; + struct wlr_box full_area = { 0 }; + wlr_output_effective_resolution(output, + &full_area.width, &full_area.height); + wl_list_for_each_reverse(roots_surface, list, link) { + struct wlr_layer_surface_v1 *layer = roots_surface->layer_surface; + struct wlr_layer_surface_v1_state *state = &layer->current; + if (exclusive != (state->exclusive_zone > 0)) { + continue; + } + struct wlr_box bounds; + if (state->exclusive_zone == -1) { + bounds = full_area; + } else { + bounds = *usable_area; + } + struct wlr_box box = { + .width = state->desired_width, + .height = state->desired_height + }; + // Horizontal axis + const uint32_t both_horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT + | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + if ((state->anchor & both_horiz) && box.width == 0) { + box.x = bounds.x; + box.width = bounds.width; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) { + box.x = bounds.x; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) { + box.x = bounds.x + (bounds.width - box.width); + } else { + box.x = bounds.x + ((bounds.width / 2) - (box.width / 2)); + } + // Vertical axis + const uint32_t both_vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP + | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; + if ((state->anchor & both_vert) && box.height == 0) { + box.y = bounds.y; + box.height = bounds.height; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) { + box.y = bounds.y; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) { + box.y = bounds.y + (bounds.height - box.height); + } else { + box.y = bounds.y + ((bounds.height / 2) - (box.height / 2)); + } + // Margin + if ((state->anchor & both_horiz) == both_horiz) { + box.x += state->margin.left; + box.width -= state->margin.left + state->margin.right; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) { + box.x += state->margin.left; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) { + box.x -= state->margin.right; + } + if ((state->anchor & both_vert) == both_vert) { + box.y += state->margin.top; + box.height -= state->margin.top + state->margin.bottom; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) { + box.y += state->margin.top; + } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) { + box.y -= state->margin.bottom; + } + if (box.width < 0 || box.height < 0) { + // TODO: Bubble up a protocol error? + wlr_layer_surface_v1_close(layer); + continue; + } + + // Apply + struct wlr_box old_geo = roots_surface->geo; + roots_surface->geo = box; + apply_exclusive(usable_area, state->anchor, state->exclusive_zone, + state->margin.top, state->margin.right, + state->margin.bottom, state->margin.left); + wlr_layer_surface_v1_configure(layer, box.width, box.height); + + // Having a cursor newly end up over the moved layer will not + // automatically send a motion event to the surface. The event needs to + // be synthesized. + // Only update layer surfaces which kept their size (and so buffers) the + // same, because those with resized buffers will be handled separately. + + if (roots_surface->geo.x != old_geo.x + || roots_surface->geo.y != old_geo.y) { + update_cursors(roots_surface, seats); + } + } +} + +void arrange_layers(struct roots_output *output) { + struct wlr_box usable_area = { 0 }; + wlr_output_effective_resolution(output->wlr_output, + &usable_area.width, &usable_area.height); + + // Arrange exclusive surfaces from top->bottom + arrange_layer(output->wlr_output, &output->desktop->server->input->seats, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], + &usable_area, true); + arrange_layer(output->wlr_output, &output->desktop->server->input->seats, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], + &usable_area, true); + arrange_layer(output->wlr_output, &output->desktop->server->input->seats, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], + &usable_area, true); + arrange_layer(output->wlr_output, &output->desktop->server->input->seats, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], + &usable_area, true); + memcpy(&output->usable_area, &usable_area, sizeof(struct wlr_box)); + + struct roots_view *view; + wl_list_for_each(view, &output->desktop->views, link) { + if (view->maximized) { + view_arrange_maximized(view); + } + } + + // Arrange non-exlusive surfaces from top->bottom + arrange_layer(output->wlr_output, &output->desktop->server->input->seats, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], + &usable_area, false); + arrange_layer(output->wlr_output, &output->desktop->server->input->seats, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], + &usable_area, false); + arrange_layer(output->wlr_output, &output->desktop->server->input->seats, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], + &usable_area, false); + arrange_layer(output->wlr_output, &output->desktop->server->input->seats, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], + &usable_area, false); + + // Find topmost keyboard interactive layer, if such a layer exists + uint32_t layers_above_shell[] = { + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, + ZWLR_LAYER_SHELL_V1_LAYER_TOP, + }; + size_t nlayers = sizeof(layers_above_shell) / sizeof(layers_above_shell[0]); + struct roots_layer_surface *layer, *topmost = NULL; + for (size_t i = 0; i < nlayers; ++i) { + wl_list_for_each_reverse(layer, + &output->layers[layers_above_shell[i]], link) { + if (layer->layer_surface->current.keyboard_interactive) { + topmost = layer; + break; + } + } + if (topmost != NULL) { + break; + } + } + + struct roots_input *input = output->desktop->server->input; + struct roots_seat *seat; + wl_list_for_each(seat, &input->seats, link) { + roots_seat_set_focus_layer(seat, + topmost ? topmost->layer_surface : NULL); + } +} + +static void handle_output_destroy(struct wl_listener *listener, void *data) { + struct roots_layer_surface *layer = + wl_container_of(listener, layer, output_destroy); + layer->layer_surface->output = NULL; + wl_list_remove(&layer->output_destroy.link); + wlr_layer_surface_v1_close(layer->layer_surface); +} + +static void handle_surface_commit(struct wl_listener *listener, void *data) { + struct roots_layer_surface *layer = + wl_container_of(listener, layer, surface_commit); + struct wlr_layer_surface_v1 *layer_surface = layer->layer_surface; + struct wlr_output *wlr_output = layer_surface->output; + if (wlr_output != NULL) { + struct roots_output *output = wlr_output->data; + struct wlr_box old_geo = layer->geo; + arrange_layers(output); + + // Cursor changes which happen as a consequence of resizing a layer + // surface are applied in arrange_layers. Because the resize happens + // before the underlying surface changes, it will only receive a cursor + // update if the new cursor position crosses the *old* sized surface in + // the *new* layer surface. + // Another cursor move event is needed when the surface actually + // changes. + struct wlr_surface *surface = layer_surface->surface; + if (surface->previous.width != surface->current.width || + surface->previous.height != surface->current.height) { + update_cursors(layer, &output->desktop->server->input->seats); + } + + if (memcmp(&old_geo, &layer->geo, sizeof(struct wlr_box)) != 0) { + output_damage_whole_local_surface(output, layer_surface->surface, + old_geo.x, old_geo.y, 0); + output_damage_whole_local_surface(output, layer_surface->surface, + layer->geo.x, layer->geo.y, 0); + } else { + output_damage_from_local_surface(output, layer_surface->surface, + layer->geo.x, layer->geo.y, 0); + } + } +} + +static void unmap(struct wlr_layer_surface_v1 *layer_surface) { + struct roots_layer_surface *layer = layer_surface->data; + struct wlr_output *wlr_output = layer_surface->output; + if (wlr_output != NULL) { + struct roots_output *output = wlr_output->data; + output_damage_whole_local_surface(output, layer_surface->surface, + layer->geo.x, layer->geo.y, 0); + } +} + +static void handle_destroy(struct wl_listener *listener, void *data) { + struct roots_layer_surface *layer = wl_container_of( + listener, layer, destroy); + if (layer->layer_surface->mapped) { + unmap(layer->layer_surface); + } + wl_list_remove(&layer->link); + wl_list_remove(&layer->destroy.link); + wl_list_remove(&layer->map.link); + wl_list_remove(&layer->unmap.link); + wl_list_remove(&layer->surface_commit.link); + if (layer->layer_surface->output) { + wl_list_remove(&layer->output_destroy.link); + arrange_layers((struct roots_output *)layer->layer_surface->output->data); + } + free(layer); +} + +static void handle_map(struct wl_listener *listener, void *data) { + struct wlr_layer_surface_v1 *layer_surface = data; + struct roots_layer_surface *layer = layer_surface->data; + struct wlr_output *wlr_output = layer_surface->output; + struct roots_output *output = wlr_output->data; + output_damage_whole_local_surface(output, layer_surface->surface, + layer->geo.x, layer->geo.y, 0); + wlr_surface_send_enter(layer_surface->surface, wlr_output); +} + +static void handle_unmap(struct wl_listener *listener, void *data) { + struct roots_layer_surface *layer = wl_container_of( + listener, layer, unmap); + struct wlr_output *wlr_output = layer->layer_surface->output; + struct roots_output *output = wlr_output->data; + unmap(layer->layer_surface); + input_update_cursor_focus(output->desktop->server->input); +} + +static void popup_handle_map(struct wl_listener *listener, void *data) { + struct roots_layer_popup *popup = wl_container_of(listener, popup, map); + struct roots_layer_surface *layer = popup->parent; + struct wlr_output *wlr_output = layer->layer_surface->output; + struct roots_output *output = wlr_output->data; + int ox = popup->wlr_popup->geometry.x + layer->geo.x; + int oy = popup->wlr_popup->geometry.y + layer->geo.y; + output_damage_whole_local_surface(output, popup->wlr_popup->base->surface, + ox, oy, 0); + input_update_cursor_focus(output->desktop->server->input); +} + +static void popup_handle_unmap(struct wl_listener *listener, void *data) { + struct roots_layer_popup *popup = wl_container_of(listener, popup, unmap); + struct roots_layer_surface *layer = popup->parent; + struct wlr_output *wlr_output = layer->layer_surface->output; + struct roots_output *output = wlr_output->data; + int ox = popup->wlr_popup->geometry.x + layer->geo.x; + int oy = popup->wlr_popup->geometry.y + layer->geo.y; + output_damage_whole_local_surface(output, popup->wlr_popup->base->surface, + ox, oy, 0); +} + +static void popup_handle_commit(struct wl_listener *listener, void *data) { + struct roots_layer_popup *popup = wl_container_of(listener, popup, commit); + struct roots_layer_surface *layer = popup->parent; + struct wlr_output *wlr_output = layer->layer_surface->output; + struct roots_output *output = wlr_output->data; + int ox = popup->wlr_popup->geometry.x + layer->geo.x; + int oy = popup->wlr_popup->geometry.y + layer->geo.y; + output_damage_from_local_surface(output, popup->wlr_popup->base->surface, + ox, oy, 0); +} + +static void popup_handle_destroy(struct wl_listener *listener, void *data) { + struct roots_layer_popup *popup = + wl_container_of(listener, popup, destroy); + + wl_list_remove(&popup->map.link); + wl_list_remove(&popup->unmap.link); + wl_list_remove(&popup->destroy.link); + wl_list_remove(&popup->commit.link); + free(popup); +} + +static struct roots_layer_popup *popup_create(struct roots_layer_surface *parent, + struct wlr_xdg_popup *wlr_popup) { + struct roots_layer_popup *popup = + calloc(1, sizeof(struct roots_layer_popup)); + if (popup == NULL) { + return NULL; + } + popup->wlr_popup = wlr_popup; + popup->parent = parent; + popup->map.notify = popup_handle_map; + wl_signal_add(&wlr_popup->base->events.map, &popup->map); + popup->unmap.notify = popup_handle_unmap; + wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap); + popup->destroy.notify = popup_handle_destroy; + wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); + popup->commit.notify = popup_handle_commit; + wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit); + /* TODO: popups can have popups, see xdg_shell::popup_create */ + + return popup; +} + +static void handle_new_popup(struct wl_listener *listener, void *data) { + struct roots_layer_surface *roots_layer_surface = + wl_container_of(listener, roots_layer_surface, new_popup); + struct wlr_xdg_popup *wlr_popup = data; + popup_create(roots_layer_surface, wlr_popup); +} + +void handle_layer_shell_surface(struct wl_listener *listener, void *data) { + struct wlr_layer_surface_v1 *layer_surface = data; + struct roots_desktop *desktop = + wl_container_of(listener, desktop, layer_shell_surface); + wlr_log(WLR_DEBUG, "new layer surface: namespace %s layer %d anchor %d " + "size %dx%d margin %d,%d,%d,%d", + layer_surface->namespace, layer_surface->layer, layer_surface->layer, + layer_surface->client_pending.desired_width, + layer_surface->client_pending.desired_height, + layer_surface->client_pending.margin.top, + layer_surface->client_pending.margin.right, + layer_surface->client_pending.margin.bottom, + layer_surface->client_pending.margin.left); + + if (!layer_surface->output) { + struct roots_input *input = desktop->server->input; + struct roots_seat *seat = input_last_active_seat(input); + assert(seat); // Technically speaking we should handle this case + struct wlr_output *output = + wlr_output_layout_output_at(desktop->layout, + seat->cursor->cursor->x, + seat->cursor->cursor->y); + if (!output) { + wlr_log(WLR_ERROR, "Couldn't find output at (%.0f,%.0f)", + seat->cursor->cursor->x, + seat->cursor->cursor->y); + output = wlr_output_layout_get_center_output(desktop->layout); + } + if (output) { + layer_surface->output = output; + } else { + wlr_layer_surface_v1_close(layer_surface); + return; + } + } + + struct roots_layer_surface *roots_surface = + calloc(1, sizeof(struct roots_layer_surface)); + if (!roots_surface) { + return; + } + + roots_surface->surface_commit.notify = handle_surface_commit; + wl_signal_add(&layer_surface->surface->events.commit, + &roots_surface->surface_commit); + + roots_surface->output_destroy.notify = handle_output_destroy; + wl_signal_add(&layer_surface->output->events.destroy, + &roots_surface->output_destroy); + + roots_surface->destroy.notify = handle_destroy; + wl_signal_add(&layer_surface->events.destroy, &roots_surface->destroy); + roots_surface->map.notify = handle_map; + wl_signal_add(&layer_surface->events.map, &roots_surface->map); + roots_surface->unmap.notify = handle_unmap; + wl_signal_add(&layer_surface->events.unmap, &roots_surface->unmap); + roots_surface->new_popup.notify = handle_new_popup; + wl_signal_add(&layer_surface->events.new_popup, &roots_surface->new_popup); + // TODO: Listen for subsurfaces + + roots_surface->layer_surface = layer_surface; + layer_surface->data = roots_surface; + + struct roots_output *output = layer_surface->output->data; + wl_list_insert(&output->layers[layer_surface->layer], &roots_surface->link); + + // Temporarily set the layer's current state to client_pending + // So that we can easily arrange it + struct wlr_layer_surface_v1_state old_state = layer_surface->current; + layer_surface->current = layer_surface->client_pending; + + arrange_layers(output); + + layer_surface->current = old_state; +} diff --git a/rootston/main.c b/rootston/main.c new file mode 100644 index 00000000..7e25dab1 --- /dev/null +++ b/rootston/main.c @@ -0,0 +1,81 @@ +#define _POSIX_C_SOURCE 200112L +#include <assert.h> +#include <stdlib.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/backend.h> +#include <wlr/backend/headless.h> +#include <wlr/backend/multi.h> +#include <wlr/config.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/util/log.h> +#include "rootston/config.h" +#include "rootston/server.h" + +struct roots_server server = { 0 }; + +int main(int argc, char **argv) { + wlr_log_init(WLR_DEBUG, NULL); + server.config = roots_config_create_from_args(argc, argv); + server.wl_display = wl_display_create(); + server.wl_event_loop = wl_display_get_event_loop(server.wl_display); + assert(server.config && server.wl_display && server.wl_event_loop); + + server.backend = wlr_backend_autocreate(server.wl_display, NULL); + if (server.backend == NULL) { + wlr_log(WLR_ERROR, "could not start backend"); + return 1; + } + + server.renderer = wlr_backend_get_renderer(server.backend); + assert(server.renderer); + server.data_device_manager = + wlr_data_device_manager_create(server.wl_display); + wlr_renderer_init_wl_display(server.renderer, server.wl_display); + server.desktop = desktop_create(&server, server.config); + server.input = input_create(&server, server.config); + + const char *socket = wl_display_add_socket_auto(server.wl_display); + if (!socket) { + wlr_log_errno(WLR_ERROR, "Unable to open wayland socket"); + wlr_backend_destroy(server.backend); + return 1; + } + + wlr_log(WLR_INFO, "Running compositor on wayland display '%s'", socket); + setenv("_WAYLAND_DISPLAY", socket, true); + + if (!wlr_backend_start(server.backend)) { + wlr_log(WLR_ERROR, "Failed to start backend"); + wlr_backend_destroy(server.backend); + wl_display_destroy(server.wl_display); + return 1; + } + + setenv("WAYLAND_DISPLAY", socket, true); +#if WLR_HAS_XWAYLAND + if (server.desktop->xwayland != NULL) { + struct roots_seat *xwayland_seat = + input_get_seat(server.input, ROOTS_CONFIG_DEFAULT_SEAT_NAME); + wlr_xwayland_set_seat(server.desktop->xwayland, xwayland_seat->seat); + } +#endif + + if (server.config->startup_cmd != NULL) { + const char *cmd = server.config->startup_cmd; + pid_t pid = fork(); + if (pid < 0) { + wlr_log(WLR_ERROR, "cannot execute binding command: fork() failed"); + } else if (pid == 0) { + execl("/bin/sh", "/bin/sh", "-c", cmd, (void *)NULL); + } + } + + wl_display_run(server.wl_display); +#if WLR_HAS_XWAYLAND + wlr_xwayland_destroy(server.desktop->xwayland); +#endif + wl_display_destroy_clients(server.wl_display); + wl_display_destroy(server.wl_display); + return 0; +} diff --git a/rootston/meson.build b/rootston/meson.build new file mode 100644 index 00000000..db90a508 --- /dev/null +++ b/rootston/meson.build @@ -0,0 +1,30 @@ +sources = [ + 'bindings.c', + 'config.c', + 'cursor.c', + 'desktop.c', + 'ini.c', + 'input.c', + 'keyboard.c', + 'layer_shell.c', + 'main.c', + 'output.c', + 'seat.c', + 'switch.c', + 'text_input.c', + 'virtual_keyboard.c', + 'wl_shell.c', + 'xdg_shell.c', + 'xdg_shell_v6.c', +] + +if conf_data.get('WLR_HAS_XWAYLAND', 0) == 1 + sources += 'xwayland.c' +endif + +executable( + 'rootston', + sources, + dependencies: [wlroots, wlr_protos, pixman], + build_by_default: get_option('rootston'), +) diff --git a/rootston/output.c b/rootston/output.c new file mode 100644 index 00000000..f950d4dc --- /dev/null +++ b/rootston/output.c @@ -0,0 +1,918 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <time.h> +#include <wlr/backend/drm.h> +#include <wlr/config.h> +#include <wlr/types/wlr_compositor.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/types/wlr_output_layout.h> +#include <wlr/types/wlr_presentation_time.h> +#include <wlr/types/wlr_wl_shell.h> +#include <wlr/types/wlr_xdg_shell_v6.h> +#include <wlr/types/wlr_xdg_shell.h> +#include <wlr/util/log.h> +#include <wlr/util/region.h> +#include "rootston/config.h" +#include "rootston/layers.h" +#include "rootston/output.h" +#include "rootston/server.h" + +/** + * Rotate a child's position relative to a parent. The parent size is (pw, ph), + * the child position is (*sx, *sy) and its size is (sw, sh). + */ +void rotate_child_position(double *sx, double *sy, double sw, double sh, + double pw, double ph, float rotation) { + if (rotation != 0.0) { + // Coordinates relative to the center of the subsurface + double ox = *sx - pw/2 + sw/2, + oy = *sy - ph/2 + sh/2; + // Rotated coordinates + double rx = cos(rotation)*ox - sin(rotation)*oy, + ry = cos(rotation)*oy + sin(rotation)*ox; + *sx = rx + pw/2 - sw/2; + *sy = ry + ph/2 - sh/2; + } +} + +struct layout_data { + double x, y; + int width, height; + float rotation; +}; + +static void get_layout_position(struct layout_data *data, double *lx, double *ly, + const struct wlr_surface *surface, int sx, int sy) { + double _sx = sx, _sy = sy; + rotate_child_position(&_sx, &_sy, surface->current.width, + surface->current.height, data->width, data->height, data->rotation); + *lx = data->x + _sx; + *ly = data->y + _sy; +} + +static void surface_for_each_surface(struct wlr_surface *surface, + double lx, double ly, float rotation, struct layout_data *layout_data, + wlr_surface_iterator_func_t iterator, void *user_data) { + layout_data->x = lx; + layout_data->y = ly; + layout_data->width = surface->current.width; + layout_data->height = surface->current.height; + layout_data->rotation = rotation; + + wlr_surface_for_each_surface(surface, iterator, user_data); +} + +static void view_for_each_surface(struct roots_view *view, + struct layout_data *layout_data, wlr_surface_iterator_func_t iterator, + void *user_data) { + layout_data->x = view->box.x; + layout_data->y = view->box.y; + layout_data->width = view->wlr_surface->current.width; + layout_data->height = view->wlr_surface->current.height; + layout_data->rotation = view->rotation; + + switch (view->type) { + case ROOTS_XDG_SHELL_V6_VIEW: + wlr_xdg_surface_v6_for_each_surface(view->xdg_surface_v6, iterator, + user_data); + break; + case ROOTS_XDG_SHELL_VIEW: + wlr_xdg_surface_for_each_surface(view->xdg_surface, iterator, + user_data); + break; + case ROOTS_WL_SHELL_VIEW: + wlr_wl_shell_surface_for_each_surface(view->wl_shell_surface, iterator, + user_data); + break; +#if WLR_HAS_XWAYLAND + case ROOTS_XWAYLAND_VIEW: + wlr_surface_for_each_surface(view->wlr_surface, iterator, user_data); + break; +#endif + } +} + +#if WLR_HAS_XWAYLAND +static void xwayland_children_for_each_surface( + struct wlr_xwayland_surface *surface, + wlr_surface_iterator_func_t iterator, struct layout_data *layout_data, + void *user_data) { + struct wlr_xwayland_surface *child; + wl_list_for_each(child, &surface->children, parent_link) { + if (child->mapped) { + surface_for_each_surface(child->surface, child->x, child->y, 0, + layout_data, iterator, user_data); + } + xwayland_children_for_each_surface(child, iterator, layout_data, + user_data); + } +} +#endif + +static void drag_icons_for_each_surface(struct roots_input *input, + wlr_surface_iterator_func_t iterator, struct layout_data *layout_data, + void *user_data) { + struct roots_seat *seat; + wl_list_for_each(seat, &input->seats, link) { + struct roots_drag_icon *drag_icon; + wl_list_for_each(drag_icon, &seat->drag_icons, link) { + if (!drag_icon->wlr_drag_icon->mapped) { + continue; + } + surface_for_each_surface(drag_icon->wlr_drag_icon->surface, + drag_icon->x, drag_icon->y, 0, layout_data, + iterator, user_data); + } + } +} + +static void layer_for_each_surface(struct wl_list *layer, + const struct wlr_box *output_layout_box, + wlr_surface_iterator_func_t iterator, struct layout_data *layout_data, + void *user_data) { + struct roots_layer_surface *roots_surface; + wl_list_for_each(roots_surface, layer, link) { + struct wlr_layer_surface_v1 *layer = roots_surface->layer_surface; + + layout_data->x = roots_surface->geo.x + output_layout_box->x; + layout_data->y = roots_surface->geo.y + output_layout_box->y; + layout_data->width = roots_surface->geo.width; + layout_data->height = roots_surface->geo.height; + layout_data->rotation = 0; + wlr_layer_surface_v1_for_each_surface(layer, iterator, user_data); + } +} + +static void output_for_each_surface(struct roots_output *output, + wlr_surface_iterator_func_t iterator, struct layout_data *layout_data, + void *user_data) { + struct wlr_output *wlr_output = output->wlr_output; + struct roots_desktop *desktop = output->desktop; + struct roots_server *server = desktop->server; + + const struct wlr_box *output_box = + wlr_output_layout_get_box(desktop->layout, wlr_output); + + if (output->fullscreen_view != NULL) { + struct roots_view *view = output->fullscreen_view; + + view_for_each_surface(view, layout_data, iterator, user_data); + +#if WLR_HAS_XWAYLAND + if (view->type == ROOTS_XWAYLAND_VIEW) { + xwayland_children_for_each_surface(view->xwayland_surface, + iterator, layout_data, user_data); + } +#endif + } else { + struct roots_view *view; + wl_list_for_each_reverse(view, &desktop->views, link) { + view_for_each_surface(view, layout_data, iterator, user_data); + } + + drag_icons_for_each_surface(server->input, iterator, + layout_data, user_data); + } + + size_t len = sizeof(output->layers) / sizeof(output->layers[0]); + for (size_t i = 0; i < len; ++i) { + layer_for_each_surface(&output->layers[i], output_box, + iterator, layout_data, user_data); + } +} + + +struct render_data { + struct layout_data layout; + struct roots_output *output; + struct timespec *when; + pixman_region32_t *damage; + float alpha; +}; + +/** + * Checks whether a surface at (lx, ly) intersects an output. If `box` is not + * NULL, it populates it with the surface box in the output, in output-local + * coordinates. + */ +static bool surface_intersect_output(struct wlr_surface *surface, + struct wlr_output_layout *output_layout, struct wlr_output *wlr_output, + double lx, double ly, float rotation, struct wlr_box *box) { + double ox = lx, oy = ly; + wlr_output_layout_output_coords(output_layout, wlr_output, &ox, &oy); + + ox += surface->sx; + oy += surface->sy; + + if (box != NULL) { + box->x = ox * wlr_output->scale; + box->y = oy * wlr_output->scale; + box->width = surface->current.width * wlr_output->scale; + box->height = surface->current.height * wlr_output->scale; + } + + struct wlr_box layout_box = { + .x = lx, .y = ly, + .width = surface->current.width, .height = surface->current.height, + }; + wlr_box_rotated_bounds(&layout_box, &layout_box, rotation); + return wlr_output_layout_intersects(output_layout, wlr_output, &layout_box); +} + +static void scissor_output(struct roots_output *output, pixman_box32_t *rect) { + struct wlr_output *wlr_output = output->wlr_output; + struct wlr_renderer *renderer = wlr_backend_get_renderer(wlr_output->backend); + assert(renderer); + + struct wlr_box box = { + .x = rect->x1, + .y = rect->y1, + .width = rect->x2 - rect->x1, + .height = rect->y2 - rect->y1, + }; + + int ow, oh; + wlr_output_transformed_resolution(wlr_output, &ow, &oh); + + enum wl_output_transform transform = + wlr_output_transform_invert(wlr_output->transform); + wlr_box_transform(&box, &box, transform, ow, oh); + + wlr_renderer_scissor(renderer, &box); +} + +static void render_surface(struct wlr_surface *surface, int sx, int sy, + void *_data) { + struct render_data *data = _data; + struct roots_output *output = data->output; + float rotation = data->layout.rotation; + + struct wlr_texture *texture = wlr_surface_get_texture(surface); + if (texture == NULL) { + return; + } + + struct wlr_renderer *renderer = + wlr_backend_get_renderer(output->wlr_output->backend); + assert(renderer); + + double lx, ly; + get_layout_position(&data->layout, &lx, &ly, surface, sx, sy); + + struct wlr_box box; + bool intersects = surface_intersect_output(surface, output->desktop->layout, + output->wlr_output, lx, ly, rotation, &box); + if (!intersects) { + return; + } + + struct wlr_box rotated; + wlr_box_rotated_bounds(&rotated, &box, rotation); + + pixman_region32_t damage; + pixman_region32_init(&damage); + pixman_region32_union_rect(&damage, &damage, rotated.x, rotated.y, + rotated.width, rotated.height); + pixman_region32_intersect(&damage, &damage, data->damage); + bool damaged = pixman_region32_not_empty(&damage); + if (!damaged) { + goto damage_finish; + } + + float matrix[9]; + enum wl_output_transform transform = + wlr_output_transform_invert(surface->current.transform); + wlr_matrix_project_box(matrix, &box, transform, rotation, + output->wlr_output->transform_matrix); + + int nrects; + pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects); + for (int i = 0; i < nrects; ++i) { + scissor_output(output, &rects[i]); + wlr_render_texture_with_matrix(renderer, texture, matrix, data->alpha); + } + +damage_finish: + pixman_region32_fini(&damage); +} + +static void get_decoration_box(struct roots_view *view, + struct roots_output *output, struct wlr_box *box) { + struct wlr_output *wlr_output = output->wlr_output; + + struct wlr_box deco_box; + view_get_deco_box(view, &deco_box); + double sx = deco_box.x - view->box.x; + double sy = deco_box.y - view->box.y; + rotate_child_position(&sx, &sy, deco_box.width, deco_box.height, + view->wlr_surface->current.width, + view->wlr_surface->current.height, view->rotation); + double x = sx + view->box.x; + double y = sy + view->box.y; + + wlr_output_layout_output_coords(output->desktop->layout, wlr_output, &x, &y); + + box->x = x * wlr_output->scale; + box->y = y * wlr_output->scale; + box->width = deco_box.width * wlr_output->scale; + box->height = deco_box.height * wlr_output->scale; +} + +static void render_decorations(struct roots_view *view, + struct render_data *data) { + if (!view->decorated || view->wlr_surface == NULL) { + return; + } + + struct roots_output *output = data->output; + struct wlr_renderer *renderer = + wlr_backend_get_renderer(output->wlr_output->backend); + assert(renderer); + + struct wlr_box box; + get_decoration_box(view, output, &box); + + struct wlr_box rotated; + wlr_box_rotated_bounds(&rotated, &box, view->rotation); + + pixman_region32_t damage; + pixman_region32_init(&damage); + pixman_region32_union_rect(&damage, &damage, rotated.x, rotated.y, + rotated.width, rotated.height); + pixman_region32_intersect(&damage, &damage, data->damage); + bool damaged = pixman_region32_not_empty(&damage); + if (!damaged) { + goto damage_finish; + } + + float matrix[9]; + wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, + view->rotation, output->wlr_output->transform_matrix); + float color[] = { 0.2, 0.2, 0.2, view->alpha }; + + int nrects; + pixman_box32_t *rects = + pixman_region32_rectangles(&damage, &nrects); + for (int i = 0; i < nrects; ++i) { + scissor_output(output, &rects[i]); + wlr_render_quad_with_matrix(renderer, color, matrix); + } + +damage_finish: + pixman_region32_fini(&damage); +} + +static void render_view(struct roots_view *view, struct render_data *data) { + // Do not render views fullscreened on other outputs + if (view->fullscreen_output != NULL && + view->fullscreen_output != data->output) { + return; + } + + data->alpha = view->alpha; + render_decorations(view, data); + view_for_each_surface(view, &data->layout, render_surface, data); +} + +static void render_layer(struct roots_output *output, + const struct wlr_box *output_layout_box, struct render_data *data, + struct wl_list *layer) { + data->alpha = 1; + layer_for_each_surface(layer, output_layout_box, render_surface, + &data->layout, data); +} + +static void surface_send_frame_done(struct wlr_surface *surface, int sx, int sy, + void *_data) { + struct render_data *data = _data; + struct roots_output *output = data->output; + struct timespec *when = data->when; + float rotation = data->layout.rotation; + + double lx, ly; + get_layout_position(&data->layout, &lx, &ly, surface, sx, sy); + + if (!surface_intersect_output(surface, output->desktop->layout, + output->wlr_output, lx, ly, rotation, NULL)) { + return; + } + + wlr_surface_send_frame_done(surface, when); +} + +static void render_output(struct roots_output *output) { + struct wlr_output *wlr_output = output->wlr_output; + struct roots_desktop *desktop = output->desktop; + struct roots_server *server = desktop->server; + struct wlr_renderer *renderer = wlr_backend_get_renderer(wlr_output->backend); + assert(renderer); + + if (!wlr_output->enabled) { + return; + } + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + float clear_color[] = {0.25f, 0.25f, 0.25f, 1.0f}; + + const struct wlr_box *output_box = + wlr_output_layout_get_box(desktop->layout, wlr_output); + + // Check if we can delegate the fullscreen surface to the output + if (output->fullscreen_view != NULL && + output->fullscreen_view->wlr_surface != NULL) { + struct roots_view *view = output->fullscreen_view; + + // Make sure the view is centered on screen + struct wlr_box view_box; + view_get_box(view, &view_box); + double view_x = (double)(output_box->width - view_box.width) / 2 + + output_box->x; + double view_y = (double)(output_box->height - view_box.height) / 2 + + output_box->y; + view_move(view, view_x, view_y); + + // Fullscreen views are rendered on a black background + clear_color[0] = clear_color[1] = clear_color[2] = 0; + } + + bool needs_swap; + pixman_region32_t damage; + pixman_region32_init(&damage); + if (!wlr_output_damage_make_current(output->damage, &needs_swap, &damage)) { + return; + } + + struct render_data data = { + .output = output, + .when = &now, + .damage = &damage, + .alpha = 1.0, + }; + + if (!needs_swap) { + // Output doesn't need swap and isn't damaged, skip rendering completely + goto damage_finish; + } + + wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height); + + if (!pixman_region32_not_empty(&damage)) { + // Output isn't damaged but needs buffer swap + goto renderer_end; + } + + if (server->config->debug_damage_tracking) { + wlr_renderer_clear(renderer, (float[]){1, 1, 0, 1}); + } + + int nrects; + pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects); + for (int i = 0; i < nrects; ++i) { + scissor_output(output, &rects[i]); + wlr_renderer_clear(renderer, clear_color); + } + + render_layer(output, output_box, &data, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]); + render_layer(output, output_box, &data, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]); + + // If a view is fullscreen on this output, render it + if (output->fullscreen_view != NULL) { + struct roots_view *view = output->fullscreen_view; + + if (view->wlr_surface != NULL) { + view_for_each_surface(view, &data.layout, render_surface, &data); + } + + // During normal rendering the xwayland window tree isn't traversed + // because all windows are rendered. Here we only want to render + // the fullscreen window's children so we have to traverse the tree. +#if WLR_HAS_XWAYLAND + if (view->type == ROOTS_XWAYLAND_VIEW) { + xwayland_children_for_each_surface(view->xwayland_surface, + render_surface, &data.layout, &data); + } +#endif + } else { + // Render all views + struct roots_view *view; + wl_list_for_each_reverse(view, &desktop->views, link) { + render_view(view, &data); + } + // Render top layer above shell views + render_layer(output, output_box, &data, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]); + } + + // Render drag icons + data.alpha = 1.0; + drag_icons_for_each_surface(server->input, render_surface, &data.layout, + &data); + + render_layer(output, output_box, &data, + &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]); + +renderer_end: + wlr_output_render_software_cursors(wlr_output, &damage); + wlr_renderer_scissor(renderer, NULL); + wlr_renderer_end(renderer); + + int width, height; + wlr_output_transformed_resolution(wlr_output, &width, &height); + + if (server->config->debug_damage_tracking) { + pixman_region32_union_rect(&damage, &damage, 0, 0, width, height); + } + + enum wl_output_transform transform = + wlr_output_transform_invert(wlr_output->transform); + wlr_region_transform(&damage, &damage, transform, width, height); + + if (!wlr_output_damage_swap_buffers(output->damage, &now, &damage)) { + goto damage_finish; + } + output->last_frame = desktop->last_frame = now; + +damage_finish: + pixman_region32_fini(&damage); + + // Send frame done events to all surfaces + output_for_each_surface(output, surface_send_frame_done, + &data.layout, &data); +} + +void output_damage_whole(struct roots_output *output) { + wlr_output_damage_add_whole(output->damage); +} + +static bool view_accept_damage(struct roots_output *output, + struct roots_view *view) { + if (view->wlr_surface == NULL) { + return false; + } + if (output->fullscreen_view == NULL) { + return true; + } + if (output->fullscreen_view == view) { + return true; + } +#if WLR_HAS_XWAYLAND + if (output->fullscreen_view->type == ROOTS_XWAYLAND_VIEW && + view->type == ROOTS_XWAYLAND_VIEW) { + // Special case: accept damage from children + struct wlr_xwayland_surface *xsurface = view->xwayland_surface; + while (xsurface != NULL) { + if (output->fullscreen_view->xwayland_surface == xsurface) { + return true; + } + xsurface = xsurface->parent; + } + } +#endif + return false; +} + +struct damage_data { + struct layout_data layout; + struct roots_output *output; +}; + +static void damage_whole_surface(struct wlr_surface *surface, int sx, int sy, + void *_data) { + struct damage_data *data = _data; + struct roots_output *output = data->output; + float rotation = data->layout.rotation; + + double lx, ly; + get_layout_position(&data->layout, &lx, &ly, surface, sx, sy); + + if (!wlr_surface_has_buffer(surface)) { + return; + } + + int ow, oh; + wlr_output_transformed_resolution(output->wlr_output, &ow, &oh); + + struct wlr_box box; + bool intersects = surface_intersect_output(surface, output->desktop->layout, + output->wlr_output, lx, ly, rotation, &box); + if (!intersects) { + return; + } + + wlr_box_rotated_bounds(&box, &box, rotation); + + wlr_output_damage_add_box(output->damage, &box); +} + +void output_damage_whole_local_surface(struct roots_output *output, + struct wlr_surface *surface, double ox, double oy, float rotation) { + struct wlr_output_layout_output *layout = wlr_output_layout_get( + output->desktop->layout, output->wlr_output); + struct damage_data data = { .output = output }; + surface_for_each_surface(surface, ox + layout->x, oy + layout->y, 0, + &data.layout, damage_whole_surface, &data); +} + +static void damage_whole_decoration(struct roots_view *view, + struct roots_output *output) { + if (!view->decorated || view->wlr_surface == NULL) { + return; + } + + struct wlr_box box; + get_decoration_box(view, output, &box); + + wlr_box_rotated_bounds(&box, &box, view->rotation); + + wlr_output_damage_add_box(output->damage, &box); +} + +void output_damage_whole_view(struct roots_output *output, + struct roots_view *view) { + if (!view_accept_damage(output, view)) { + return; + } + + damage_whole_decoration(view, output); + + struct damage_data data = { .output = output }; + view_for_each_surface(view, &data.layout, damage_whole_surface, &data); +} + +void output_damage_whole_drag_icon(struct roots_output *output, + struct roots_drag_icon *icon) { + struct damage_data data = { .output = output }; + surface_for_each_surface(icon->wlr_drag_icon->surface, icon->x, icon->y, 0, + &data.layout, damage_whole_surface, &data); +} + +static void damage_from_surface(struct wlr_surface *surface, int sx, int sy, + void *_data) { + struct damage_data *data = _data; + struct roots_output *output = data->output; + struct wlr_output *wlr_output = output->wlr_output; + float rotation = data->layout.rotation; + + double lx, ly; + get_layout_position(&data->layout, &lx, &ly, surface, sx, sy); + + if (!wlr_surface_has_buffer(surface)) { + return; + } + + int ow, oh; + wlr_output_transformed_resolution(wlr_output, &ow, &oh); + + struct wlr_box box; + surface_intersect_output(surface, output->desktop->layout, + wlr_output, lx, ly, rotation, &box); + + int center_x = box.x + box.width/2; + int center_y = box.y + box.height/2; + + pixman_region32_t damage; + pixman_region32_init(&damage); + wlr_surface_get_effective_damage(surface, &damage); + + wlr_region_scale(&damage, &damage, wlr_output->scale); + if (ceil(wlr_output->scale) > surface->current.scale) { + // When scaling up a surface, it'll become blurry so we need to + // expand the damage region + wlr_region_expand(&damage, &damage, + ceil(wlr_output->scale) - surface->current.scale); + } + pixman_region32_translate(&damage, box.x, box.y); + wlr_region_rotated_bounds(&damage, &damage, rotation, center_x, center_y); + wlr_output_damage_add(output->damage, &damage); + pixman_region32_fini(&damage); +} + +void output_damage_from_local_surface(struct roots_output *output, + struct wlr_surface *surface, double ox, double oy, float rotation) { + struct wlr_output_layout_output *layout = wlr_output_layout_get( + output->desktop->layout, output->wlr_output); + struct damage_data data = { .output = output }; + surface_for_each_surface(surface, ox + layout->x, oy + layout->y, 0, + &data.layout, damage_from_surface, &data); +} + +void output_damage_from_view(struct roots_output *output, + struct roots_view *view) { + if (!view_accept_damage(output, view)) { + return; + } + + struct damage_data data = { .output = output }; + view_for_each_surface(view, &data.layout, damage_from_surface, &data); +} + +static void set_mode(struct wlr_output *output, + struct roots_output_config *oc) { + int mhz = (int)(oc->mode.refresh_rate * 1000); + + if (wl_list_empty(&output->modes)) { + // Output has no mode, try setting a custom one + wlr_output_set_custom_mode(output, oc->mode.width, oc->mode.height, mhz); + return; + } + + struct wlr_output_mode *mode, *best = NULL; + wl_list_for_each(mode, &output->modes, link) { + if (mode->width == oc->mode.width && mode->height == oc->mode.height) { + if (mode->refresh == mhz) { + best = mode; + break; + } + best = mode; + } + } + if (!best) { + wlr_log(WLR_ERROR, "Configured mode for %s not available", output->name); + } else { + wlr_log(WLR_DEBUG, "Assigning configured mode to %s", output->name); + wlr_output_set_mode(output, best); + } +} + +static void output_destroy(struct roots_output *output) { + // TODO: cursor + //example_config_configure_cursor(sample->config, sample->cursor, + // sample->compositor); + + wl_list_remove(&output->link); + wl_list_remove(&output->destroy.link); + wl_list_remove(&output->mode.link); + wl_list_remove(&output->transform.link); + wl_list_remove(&output->present.link); + wl_list_remove(&output->damage_frame.link); + wl_list_remove(&output->damage_destroy.link); + free(output); +} + +static void output_handle_destroy(struct wl_listener *listener, void *data) { + struct roots_output *output = wl_container_of(listener, output, destroy); + output_destroy(output); +} + +static void output_damage_handle_frame(struct wl_listener *listener, + void *data) { + struct roots_output *output = + wl_container_of(listener, output, damage_frame); + render_output(output); +} + +static void output_damage_handle_destroy(struct wl_listener *listener, + void *data) { + struct roots_output *output = + wl_container_of(listener, output, damage_destroy); + output_destroy(output); +} + +static void output_handle_mode(struct wl_listener *listener, void *data) { + struct roots_output *output = + wl_container_of(listener, output, mode); + arrange_layers(output); +} + +static void output_handle_transform(struct wl_listener *listener, void *data) { + struct roots_output *output = + wl_container_of(listener, output, transform); + arrange_layers(output); +} + +struct presentation_data { + struct layout_data layout; + struct roots_output *output; + struct wlr_presentation_event *event; +}; + +static void surface_send_presented(struct wlr_surface *surface, int sx, int sy, + void *_data) { + struct presentation_data *data = _data; + struct roots_output *output = data->output; + float rotation = data->layout.rotation; + + double lx, ly; + get_layout_position(&data->layout, &lx, &ly, surface, sx, sy); + + if (!surface_intersect_output(surface, output->desktop->layout, + output->wlr_output, lx, ly, rotation, NULL)) { + return; + } + + wlr_presentation_send_surface_presented(output->desktop->presentation, + surface, data->event); +} + +static void output_handle_present(struct wl_listener *listener, void *data) { + struct roots_output *output = + wl_container_of(listener, output, present); + struct wlr_output_event_present *output_event = data; + + struct wlr_presentation_event event = { + .output = output->wlr_output, + .tv_sec = (uint64_t)output_event->when->tv_sec, + .tv_nsec = (uint32_t)output_event->when->tv_nsec, + .refresh = (uint32_t)output_event->refresh, + .seq = (uint64_t)output_event->seq, + .flags = output_event->flags, + }; + + struct presentation_data presentation_data = { + .output = output, + .event = &event, + }; + output_for_each_surface(output, surface_send_presented, + &presentation_data.layout, &presentation_data); +} + +void handle_new_output(struct wl_listener *listener, void *data) { + struct roots_desktop *desktop = wl_container_of(listener, desktop, + new_output); + struct wlr_output *wlr_output = data; + struct roots_input *input = desktop->server->input; + struct roots_config *config = desktop->config; + + wlr_log(WLR_DEBUG, "Output '%s' added", wlr_output->name); + wlr_log(WLR_DEBUG, "'%s %s %s' %"PRId32"mm x %"PRId32"mm", wlr_output->make, + wlr_output->model, wlr_output->serial, wlr_output->phys_width, + wlr_output->phys_height); + + struct roots_output *output = calloc(1, sizeof(struct roots_output)); + clock_gettime(CLOCK_MONOTONIC, &output->last_frame); + output->desktop = desktop; + output->wlr_output = wlr_output; + wlr_output->data = output; + wl_list_insert(&desktop->outputs, &output->link); + + output->damage = wlr_output_damage_create(wlr_output); + + output->destroy.notify = output_handle_destroy; + wl_signal_add(&wlr_output->events.destroy, &output->destroy); + output->mode.notify = output_handle_mode; + wl_signal_add(&wlr_output->events.mode, &output->mode); + output->transform.notify = output_handle_transform; + wl_signal_add(&wlr_output->events.transform, &output->transform); + output->present.notify = output_handle_present; + wl_signal_add(&wlr_output->events.present, &output->present); + + output->damage_frame.notify = output_damage_handle_frame; + wl_signal_add(&output->damage->events.frame, &output->damage_frame); + output->damage_destroy.notify = output_damage_handle_destroy; + wl_signal_add(&output->damage->events.destroy, &output->damage_destroy); + + size_t len = sizeof(output->layers) / sizeof(output->layers[0]); + for (size_t i = 0; i < len; ++i) { + wl_list_init(&output->layers[i]); + } + + struct roots_output_config *output_config = + roots_config_get_output(config, wlr_output); + + if ((!output_config || output_config->enable) && !wl_list_empty(&wlr_output->modes)) { + struct wlr_output_mode *mode = + wl_container_of(wlr_output->modes.prev, mode, link); + wlr_output_set_mode(wlr_output, mode); + } + + if (output_config) { + if (output_config->enable) { + if (wlr_output_is_drm(wlr_output)) { + struct roots_output_mode_config *mode_config; + wl_list_for_each(mode_config, &output_config->modes, link) { + wlr_drm_connector_add_mode(wlr_output, &mode_config->info); + } + } else if (!wl_list_empty(&output_config->modes)) { + wlr_log(WLR_ERROR, "Can only add modes for DRM backend"); + } + + if (output_config->mode.width) { + set_mode(wlr_output, output_config); + } + + wlr_output_set_scale(wlr_output, output_config->scale); + wlr_output_set_transform(wlr_output, output_config->transform); + wlr_output_layout_add(desktop->layout, wlr_output, output_config->x, + output_config->y); + } else { + wlr_output_enable(wlr_output, false); + } + } else { + wlr_output_layout_add_auto(desktop->layout, wlr_output); + } + + struct roots_seat *seat; + wl_list_for_each(seat, &input->seats, link) { + roots_seat_configure_cursor(seat); + roots_seat_configure_xcursor(seat); + } + + arrange_layers(output); + output_damage_whole(output); +} diff --git a/rootston/rootston.ini.example b/rootston/rootston.ini.example new file mode 100644 index 00000000..4b75e9c6 --- /dev/null +++ b/rootston/rootston.ini.example @@ -0,0 +1,63 @@ +[core] +# X11 support +# - true: enables X11, xwayland is started only when an X11 client connects +# - immediate: enables X11, xwayland is started immediately +# - false: disables xwayland +xwayland=false + +# Single output configuration. String after colon must match output's name. +[output:VGA-1] +# Set logical (layout) coordinates for this screen +x = 1920 +y = 0 + +# Screen transformation +# possible values are: +# '90', '180' or '270' - rotate output by specified angle clockwise +# 'flipped' - flip output horizontally +# 'flipped-90', 'flipped-180', 'flipped-270' - flip output horizontally +# and rotate by specified angle +rotate = 90 + +# Additional video mode to add +# Format is generated by cvt and is documented in x.org.conf(5) +modeline = 87.25 720 776 848 976 1440 1443 1453 1493 -hsync +vsync +modeline = 65.13 768 816 896 1024 1024 1025 1028 1060 -HSync +VSync +# Select one of the above modes +mode = 768x1024 + +[cursor] +# Restrict cursor movements to single output +map-to-output = VGA-1 +# Restrict cursor movements to concrete rectangle +geometry = 2500x800 +# Load a custom XCursor theme +theme = default + +# Single device configuration. String after colon must match device's name. +[device:PixArt Dell MS116 USB Optical Mouse] +# Restrict cursor movements for this mouse to single output +map-to-output = VGA-1 +# Restrict cursor movements for this mouse to concrete rectangle +geometry = 2500x800 +# tap_enabled=true + +[keyboard] +meta-key = Logo + +# Keybindings +# Maps key combinations with commands to execute +# Commands include: +# - "exit" to stop the compositor +# - "exec" to execute a shell command +# - "close" to close the current view +# - "next_window" to cycle through windows +# - "alpha" to cycle a window's alpha channel +# - "break_pointer_constraint" to decline and deactivate all pointer constraints +[bindings] +Logo+Shift+e = exit +Logo+q = close +Logo+m = maximize +Logo+Escape = break_pointer_constraint +Alt+Tab = next_window +Ctrl+Shift+a = alpha diff --git a/rootston/seat.c b/rootston/seat.c new file mode 100644 index 00000000..dda2f8df --- /dev/null +++ b/rootston/seat.c @@ -0,0 +1,1473 @@ +#define _POSIX_C_SOURCE 200112L +#include <assert.h> +#include <libinput.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <wayland-server.h> +#include <wlr/backend/libinput.h> +#include <wlr/config.h> +#include <wlr/types/wlr_idle.h> +#include <wlr/types/wlr_layer_shell_v1.h> +#include "wlr/types/wlr_switch.h" +#include <wlr/types/wlr_tablet_v2.h> +#include <wlr/types/wlr_xcursor_manager.h> +#include <wlr/util/log.h> +#include "rootston/cursor.h" +#include "rootston/input.h" +#include "rootston/keyboard.h" +#include "rootston/seat.h" +#include "rootston/text_input.h" +#include "rootston/xcursor.h" + + +static void handle_keyboard_key(struct wl_listener *listener, void *data) { + struct roots_keyboard *keyboard = + wl_container_of(listener, keyboard, keyboard_key); + struct roots_desktop *desktop = keyboard->input->server->desktop; + wlr_idle_notify_activity(desktop->idle, keyboard->seat->seat); + struct wlr_event_keyboard_key *event = data; + roots_keyboard_handle_key(keyboard, event); +} + +static void handle_keyboard_modifiers(struct wl_listener *listener, + void *data) { + struct roots_keyboard *keyboard = + wl_container_of(listener, keyboard, keyboard_modifiers); + struct roots_desktop *desktop = keyboard->input->server->desktop; + wlr_idle_notify_activity(desktop->idle, keyboard->seat->seat); + roots_keyboard_handle_modifiers(keyboard); +} + +static void handle_cursor_motion(struct wl_listener *listener, void *data) { + struct roots_cursor *cursor = + wl_container_of(listener, cursor, motion); + struct roots_desktop *desktop = cursor->seat->input->server->desktop; + wlr_idle_notify_activity(desktop->idle, cursor->seat->seat); + struct wlr_event_pointer_motion *event = data; + roots_cursor_handle_motion(cursor, event); +} + +static void handle_cursor_motion_absolute(struct wl_listener *listener, + void *data) { + struct roots_cursor *cursor = + wl_container_of(listener, cursor, motion_absolute); + struct roots_desktop *desktop = cursor->seat->input->server->desktop; + wlr_idle_notify_activity(desktop->idle, cursor->seat->seat); + struct wlr_event_pointer_motion_absolute *event = data; + roots_cursor_handle_motion_absolute(cursor, event); +} + +static void handle_cursor_button(struct wl_listener *listener, void *data) { + struct roots_cursor *cursor = + wl_container_of(listener, cursor, button); + struct roots_desktop *desktop = cursor->seat->input->server->desktop; + wlr_idle_notify_activity(desktop->idle, cursor->seat->seat); + struct wlr_event_pointer_button *event = data; + roots_cursor_handle_button(cursor, event); +} + +static void handle_cursor_axis(struct wl_listener *listener, void *data) { + struct roots_cursor *cursor = + wl_container_of(listener, cursor, axis); + struct roots_desktop *desktop = cursor->seat->input->server->desktop; + wlr_idle_notify_activity(desktop->idle, cursor->seat->seat); + struct wlr_event_pointer_axis *event = data; + roots_cursor_handle_axis(cursor, event); +} + +static void handle_switch_toggle(struct wl_listener *listener, void *data) { + struct roots_switch *lid_switch = + wl_container_of(listener, lid_switch, toggle); + struct roots_desktop *desktop = lid_switch->seat->input->server->desktop; + wlr_idle_notify_activity(desktop->idle, lid_switch->seat->seat); + struct wlr_event_switch_toggle *event = data; + roots_switch_handle_toggle(lid_switch, event); +} + +static void handle_touch_down(struct wl_listener *listener, void *data) { + struct roots_cursor *cursor = + wl_container_of(listener, cursor, touch_down); + struct roots_desktop *desktop = cursor->seat->input->server->desktop; + wlr_idle_notify_activity(desktop->idle, cursor->seat->seat); + struct wlr_event_touch_down *event = data; + roots_cursor_handle_touch_down(cursor, event); +} + +static void handle_touch_up(struct wl_listener *listener, void *data) { + struct roots_cursor *cursor = + wl_container_of(listener, cursor, touch_up); + struct roots_desktop *desktop = cursor->seat->input->server->desktop; + wlr_idle_notify_activity(desktop->idle, cursor->seat->seat); + struct wlr_event_touch_up *event = data; + roots_cursor_handle_touch_up(cursor, event); +} + +static void handle_touch_motion(struct wl_listener *listener, void *data) { + struct roots_cursor *cursor = + wl_container_of(listener, cursor, touch_motion); + struct roots_desktop *desktop = cursor->seat->input->server->desktop; + wlr_idle_notify_activity(desktop->idle, cursor->seat->seat); + struct wlr_event_touch_motion *event = data; + roots_cursor_handle_touch_motion(cursor, event); +} + +static void handle_tablet_tool_position(struct roots_cursor *cursor, + struct roots_tablet *tablet, + struct wlr_tablet_tool *tool, + bool change_x, bool change_y, + double x, double y, double dx, double dy) { + if (!change_x && !change_y) { + return; + } + + switch (tool->type) { + case WLR_TABLET_TOOL_TYPE_MOUSE: + // They are 0 either way when they weren't modified + wlr_cursor_move(cursor->cursor, tablet->device, dx, dy); + break; + default: + wlr_cursor_warp_absolute(cursor->cursor, tablet->device, + change_x ? x : NAN, change_y ? y : NAN); + } + + double sx, sy; + struct roots_view *view = NULL; + struct roots_seat *seat = cursor->seat; + struct roots_desktop *desktop = seat->input->server->desktop; + struct wlr_surface *surface = desktop_surface_at(desktop, + cursor->cursor->x, cursor->cursor->y, &sx, &sy, &view); + struct roots_tablet_tool *roots_tool = tool->data; + + if (!surface) { + wlr_tablet_v2_tablet_tool_notify_proximity_out(roots_tool->tablet_v2_tool); + /* XXX: TODO: Fallback pointer semantics */ + return; + } + + if (!wlr_surface_accepts_tablet_v2(tablet->tablet_v2, surface)) { + wlr_tablet_v2_tablet_tool_notify_proximity_out(roots_tool->tablet_v2_tool); + /* XXX: TODO: Fallback pointer semantics */ + return; + } + + wlr_tablet_v2_tablet_tool_notify_proximity_in(roots_tool->tablet_v2_tool, + tablet->tablet_v2, surface); + + wlr_tablet_v2_tablet_tool_notify_motion(roots_tool->tablet_v2_tool, sx, sy); +} + +static void handle_tool_axis(struct wl_listener *listener, void *data) { + struct roots_cursor *cursor = + wl_container_of(listener, cursor, tool_axis); + struct roots_desktop *desktop = cursor->seat->input->server->desktop; + wlr_idle_notify_activity(desktop->idle, cursor->seat->seat); + struct wlr_event_tablet_tool_axis *event = data; + struct roots_tablet_tool *roots_tool = event->tool->data; + + if (!roots_tool) { // Should this be an assert? + wlr_log(WLR_DEBUG, "Tool Axis, before proximity"); + return; + } + + /** + * We need to handle them ourselves, not pass it into the cursor + * without any consideration + */ + handle_tablet_tool_position(cursor, event->device->data, event->tool, + event->updated_axes & WLR_TABLET_TOOL_AXIS_X, + event->updated_axes & WLR_TABLET_TOOL_AXIS_Y, + event->x, event->y, event->dx, event->dy); + + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE) { + wlr_tablet_v2_tablet_tool_notify_pressure( + roots_tool->tablet_v2_tool, event->pressure); + } + + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE) { + wlr_tablet_v2_tablet_tool_notify_distance( + roots_tool->tablet_v2_tool, event->distance); + } + + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X) { + roots_tool->tilt_x = event->tilt_x; + } + + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y) { + roots_tool->tilt_y = event->tilt_y; + } + + if (event->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) { + wlr_tablet_v2_tablet_tool_notify_tilt( + roots_tool->tablet_v2_tool, + roots_tool->tilt_x, roots_tool->tilt_y); + } + + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION) { + wlr_tablet_v2_tablet_tool_notify_rotation( + roots_tool->tablet_v2_tool, event->rotation); + } + + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER) { + wlr_tablet_v2_tablet_tool_notify_slider( + roots_tool->tablet_v2_tool, event->slider); + } + + if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL) { + wlr_tablet_v2_tablet_tool_notify_wheel( + roots_tool->tablet_v2_tool, event->wheel_delta, 0); + } +} + +static void handle_tool_tip(struct wl_listener *listener, void *data) { + struct roots_cursor *cursor = + wl_container_of(listener, cursor, tool_tip); + struct roots_desktop *desktop = cursor->seat->input->server->desktop; + wlr_idle_notify_activity(desktop->idle, cursor->seat->seat); + struct wlr_event_tablet_tool_tip *event = data; + struct roots_tablet_tool *roots_tool = event->tool->data; + + if (event->state == WLR_TABLET_TOOL_TIP_DOWN) { + wlr_tablet_v2_tablet_tool_notify_down(roots_tool->tablet_v2_tool); + wlr_tablet_tool_v2_start_implicit_grab(roots_tool->tablet_v2_tool); + } else { + wlr_tablet_v2_tablet_tool_notify_up(roots_tool->tablet_v2_tool); + } +} + +static void handle_tablet_tool_destroy(struct wl_listener *listener, void *data) { + struct roots_tablet_tool *tool = + wl_container_of(listener, tool, tool_destroy); + + wl_list_remove(&tool->link); + wl_list_remove(&tool->tool_link); + + wl_list_remove(&tool->tool_destroy.link); + wl_list_remove(&tool->set_cursor.link); + + free(tool); +} + +static void handle_tool_button(struct wl_listener *listener, void *data) { + struct roots_cursor *cursor = + wl_container_of(listener, cursor, tool_button); + struct roots_desktop *desktop = cursor->seat->input->server->desktop; + wlr_idle_notify_activity(desktop->idle, cursor->seat->seat); + struct wlr_event_tablet_tool_button *event = data; + struct roots_tablet_tool *roots_tool = event->tool->data; + + wlr_tablet_v2_tablet_tool_notify_button(roots_tool->tablet_v2_tool, + (enum zwp_tablet_pad_v2_button_state)event->button, + (enum zwp_tablet_pad_v2_button_state)event->state); +} + +static void handle_tablet_tool_set_cursor(struct wl_listener *listener, void *data) { + struct roots_tablet_tool *tool = + wl_container_of(listener, tool, set_cursor); + struct wlr_tablet_v2_event_cursor *evt = data; + + + struct wlr_seat_pointer_request_set_cursor_event event = { + .surface = evt->surface, + .hotspot_x = evt->hotspot_x, + .hotspot_y = evt->hotspot_y, + .serial = evt->serial, + .seat_client = evt->seat_client, + }; + + roots_cursor_handle_request_set_cursor(tool->seat->cursor, &event); +} + +static void handle_tool_proximity(struct wl_listener *listener, void *data) { + struct roots_cursor *cursor = + wl_container_of(listener, cursor, tool_proximity); + struct roots_desktop *desktop = cursor->seat->input->server->desktop; + wlr_idle_notify_activity(desktop->idle, cursor->seat->seat); + struct wlr_event_tablet_tool_proximity *event = data; + + struct wlr_tablet_tool *tool = event->tool; + if (!tool->data) { + struct roots_tablet_tool *roots_tool = + calloc(1, sizeof(struct roots_tablet_tool)); + roots_tool->seat = cursor->seat; + tool->data = roots_tool; + roots_tool->tablet_v2_tool = + wlr_tablet_tool_create(desktop->tablet_v2, + cursor->seat->seat, tool); + roots_tool->tool_destroy.notify = handle_tablet_tool_destroy; + wl_signal_add(&tool->events.destroy, &roots_tool->tool_destroy); + + roots_tool->set_cursor.notify = handle_tablet_tool_set_cursor; + wl_signal_add(&roots_tool->tablet_v2_tool->events.set_cursor, + &roots_tool->set_cursor); + + wl_list_init(&roots_tool->link); + wl_list_init(&roots_tool->tool_link); + } + + if (event->state == WLR_TABLET_TOOL_PROXIMITY_OUT) { + struct roots_tablet_tool *roots_tool = tool->data; + wlr_tablet_v2_tablet_tool_notify_proximity_out(roots_tool->tablet_v2_tool); + return; + } + + handle_tablet_tool_position(cursor, event->device->data, event->tool, + true, true, event->x, event->y, 0, 0); +} + +static void handle_request_set_cursor(struct wl_listener *listener, + void *data) { + struct roots_cursor *cursor = + wl_container_of(listener, cursor, request_set_cursor); + struct roots_desktop *desktop = cursor->seat->input->server->desktop; + wlr_idle_notify_activity(desktop->idle, cursor->seat->seat); + struct wlr_seat_pointer_request_set_cursor_event *event = data; + roots_cursor_handle_request_set_cursor(cursor, event); +} + +static void handle_pointer_focus_change(struct wl_listener *listener, + void *data) { + struct roots_cursor *cursor = + wl_container_of(listener, cursor, focus_change); + struct wlr_seat_pointer_focus_change_event *event = data; + roots_cursor_handle_focus_change(cursor, event); +} + +static void seat_reset_device_mappings(struct roots_seat *seat, + struct wlr_input_device *device) { + struct wlr_cursor *cursor = seat->cursor->cursor; + struct roots_config *config = seat->input->config; + + wlr_cursor_map_input_to_output(cursor, device, NULL); + struct roots_device_config *dconfig; + if ((dconfig = roots_config_get_device(config, device))) { + wlr_cursor_map_input_to_region(cursor, device, dconfig->mapped_box); + } +} + +static void seat_set_device_output_mappings(struct roots_seat *seat, + struct wlr_input_device *device, struct wlr_output *output) { + struct wlr_cursor *cursor = seat->cursor->cursor; + struct roots_config *config = seat->input->config; + struct roots_device_config *dconfig = + roots_config_get_device(config, device); + + const char *mapped_output = NULL; + if (dconfig != NULL) { + mapped_output = dconfig->mapped_output; + } + if (mapped_output == NULL) { + mapped_output = device->output_name; + } + + if (mapped_output && strcmp(mapped_output, output->name) == 0) { + wlr_cursor_map_input_to_output(cursor, device, output); + } +} + +void roots_seat_configure_cursor(struct roots_seat *seat) { + struct roots_config *config = seat->input->config; + struct roots_desktop *desktop = seat->input->server->desktop; + struct wlr_cursor *cursor = seat->cursor->cursor; + + struct roots_pointer *pointer; + struct roots_touch *touch; + struct roots_tablet *tablet; + struct roots_output *output; + + // reset mappings + wlr_cursor_map_to_output(cursor, NULL); + wl_list_for_each(pointer, &seat->pointers, link) { + seat_reset_device_mappings(seat, pointer->device); + } + wl_list_for_each(touch, &seat->touch, link) { + seat_reset_device_mappings(seat, touch->device); + } + wl_list_for_each(tablet, &seat->tablets, link) { + seat_reset_device_mappings(seat, tablet->device); + } + + // configure device to output mappings + const char *mapped_output = NULL; + struct roots_cursor_config *cc = + roots_config_get_cursor(config, seat->seat->name); + if (cc != NULL) { + mapped_output = cc->mapped_output; + } + wl_list_for_each(output, &desktop->outputs, link) { + if (mapped_output && + strcmp(mapped_output, output->wlr_output->name) == 0) { + wlr_cursor_map_to_output(cursor, output->wlr_output); + } + + wl_list_for_each(pointer, &seat->pointers, link) { + seat_set_device_output_mappings(seat, pointer->device, + output->wlr_output); + } + wl_list_for_each(tablet, &seat->tablets, link) { + seat_set_device_output_mappings(seat, tablet->device, + output->wlr_output); + } + wl_list_for_each(touch, &seat->touch, link) { + seat_set_device_output_mappings(seat, touch->device, + output->wlr_output); + } + } +} + +static void roots_seat_init_cursor(struct roots_seat *seat) { + seat->cursor = roots_cursor_create(seat); + if (!seat->cursor) { + return; + } + seat->cursor->seat = seat; + struct wlr_cursor *wlr_cursor = seat->cursor->cursor; + struct roots_desktop *desktop = seat->input->server->desktop; + wlr_cursor_attach_output_layout(wlr_cursor, desktop->layout); + + roots_seat_configure_cursor(seat); + roots_seat_configure_xcursor(seat); + + // add input signals + wl_signal_add(&wlr_cursor->events.motion, &seat->cursor->motion); + seat->cursor->motion.notify = handle_cursor_motion; + + wl_signal_add(&wlr_cursor->events.motion_absolute, + &seat->cursor->motion_absolute); + seat->cursor->motion_absolute.notify = handle_cursor_motion_absolute; + + wl_signal_add(&wlr_cursor->events.button, &seat->cursor->button); + seat->cursor->button.notify = handle_cursor_button; + + wl_signal_add(&wlr_cursor->events.axis, &seat->cursor->axis); + seat->cursor->axis.notify = handle_cursor_axis; + + wl_signal_add(&wlr_cursor->events.touch_down, &seat->cursor->touch_down); + seat->cursor->touch_down.notify = handle_touch_down; + + wl_signal_add(&wlr_cursor->events.touch_up, &seat->cursor->touch_up); + seat->cursor->touch_up.notify = handle_touch_up; + + wl_signal_add(&wlr_cursor->events.touch_motion, + &seat->cursor->touch_motion); + seat->cursor->touch_motion.notify = handle_touch_motion; + + wl_signal_add(&wlr_cursor->events.tablet_tool_axis, + &seat->cursor->tool_axis); + seat->cursor->tool_axis.notify = handle_tool_axis; + + wl_signal_add(&wlr_cursor->events.tablet_tool_tip, &seat->cursor->tool_tip); + seat->cursor->tool_tip.notify = handle_tool_tip; + + wl_signal_add(&wlr_cursor->events.tablet_tool_proximity, &seat->cursor->tool_proximity); + seat->cursor->tool_proximity.notify = handle_tool_proximity; + + wl_signal_add(&wlr_cursor->events.tablet_tool_button, &seat->cursor->tool_button); + seat->cursor->tool_button.notify = handle_tool_button; + + wl_signal_add(&seat->seat->events.request_set_cursor, + &seat->cursor->request_set_cursor); + seat->cursor->request_set_cursor.notify = handle_request_set_cursor; + + wl_signal_add(&seat->seat->pointer_state.events.focus_change, + &seat->cursor->focus_change); + seat->cursor->focus_change.notify = handle_pointer_focus_change; + + wl_list_init(&seat->cursor->constraint_commit.link); +} + +static void roots_drag_icon_handle_surface_commit(struct wl_listener *listener, + void *data) { + struct roots_drag_icon *icon = + wl_container_of(listener, icon, surface_commit); + roots_drag_icon_update_position(icon); +} + +static void roots_drag_icon_handle_map(struct wl_listener *listener, + void *data) { + struct roots_drag_icon *icon = + wl_container_of(listener, icon, map); + roots_drag_icon_damage_whole(icon); +} + +static void roots_drag_icon_handle_unmap(struct wl_listener *listener, + void *data) { + struct roots_drag_icon *icon = + wl_container_of(listener, icon, unmap); + roots_drag_icon_damage_whole(icon); +} + +static void roots_drag_icon_handle_destroy(struct wl_listener *listener, + void *data) { + struct roots_drag_icon *icon = + wl_container_of(listener, icon, destroy); + roots_drag_icon_damage_whole(icon); + + wl_list_remove(&icon->link); + wl_list_remove(&icon->surface_commit.link); + wl_list_remove(&icon->unmap.link); + wl_list_remove(&icon->destroy.link); + free(icon); +} + +static void roots_seat_handle_new_drag_icon(struct wl_listener *listener, + void *data) { + struct roots_seat *seat = wl_container_of(listener, seat, new_drag_icon); + struct wlr_drag_icon *wlr_drag_icon = data; + + struct roots_drag_icon *icon = calloc(1, sizeof(struct roots_drag_icon)); + if (icon == NULL) { + return; + } + icon->seat = seat; + icon->wlr_drag_icon = wlr_drag_icon; + + icon->surface_commit.notify = roots_drag_icon_handle_surface_commit; + wl_signal_add(&wlr_drag_icon->surface->events.commit, &icon->surface_commit); + icon->unmap.notify = roots_drag_icon_handle_unmap; + wl_signal_add(&wlr_drag_icon->events.unmap, &icon->unmap); + icon->map.notify = roots_drag_icon_handle_map; + wl_signal_add(&wlr_drag_icon->events.map, &icon->map); + icon->destroy.notify = roots_drag_icon_handle_destroy; + wl_signal_add(&wlr_drag_icon->events.destroy, &icon->destroy); + + wl_list_insert(&seat->drag_icons, &icon->link); + + roots_drag_icon_update_position(icon); +} + +void roots_drag_icon_update_position(struct roots_drag_icon *icon) { + roots_drag_icon_damage_whole(icon); + + struct wlr_drag_icon *wlr_icon = icon->wlr_drag_icon; + struct roots_seat *seat = icon->seat; + struct wlr_cursor *cursor = seat->cursor->cursor; + if (wlr_icon->is_pointer) { + icon->x = cursor->x; + icon->y = cursor->y; + } else { + struct wlr_touch_point *point = + wlr_seat_touch_get_point(seat->seat, wlr_icon->touch_id); + if (point == NULL) { + return; + } + icon->x = seat->touch_x; + icon->y = seat->touch_y; + } + + roots_drag_icon_damage_whole(icon); +} + +void roots_drag_icon_damage_whole(struct roots_drag_icon *icon) { + struct roots_output *output; + wl_list_for_each(output, &icon->seat->input->server->desktop->outputs, + link) { + output_damage_whole_drag_icon(output, icon); + } +} + +static void seat_view_destroy(struct roots_seat_view *seat_view); + +static void roots_seat_handle_destroy(struct wl_listener *listener, + void *data) { + struct roots_seat *seat = wl_container_of(listener, seat, destroy); + + // TODO: probably more to be freed here + wl_list_remove(&seat->destroy.link); + + struct roots_seat_view *view, *nview; + wl_list_for_each_safe(view, nview, &seat->views, link) { + seat_view_destroy(view); + } +} + +void roots_seat_destroy(struct roots_seat *seat) { + roots_seat_handle_destroy(&seat->destroy, seat->seat); + wlr_seat_destroy(seat->seat); +} + +struct roots_seat *roots_seat_create(struct roots_input *input, char *name) { + struct roots_seat *seat = calloc(1, sizeof(struct roots_seat)); + if (!seat) { + return NULL; + } + + wl_list_init(&seat->keyboards); + wl_list_init(&seat->pointers); + wl_list_init(&seat->touch); + wl_list_init(&seat->tablets); + wl_list_init(&seat->tablet_pads); + wl_list_init(&seat->switches); + wl_list_init(&seat->views); + wl_list_init(&seat->drag_icons); + + seat->input = input; + + seat->seat = wlr_seat_create(input->server->wl_display, name); + if (!seat->seat) { + free(seat); + return NULL; + } + seat->seat->data = seat; + + roots_seat_init_cursor(seat); + if (!seat->cursor) { + wlr_seat_destroy(seat->seat); + free(seat); + return NULL; + } + + roots_input_method_relay_init(seat, &seat->im_relay); + + wl_list_insert(&input->seats, &seat->link); + + seat->new_drag_icon.notify = roots_seat_handle_new_drag_icon; + wl_signal_add(&seat->seat->events.new_drag_icon, &seat->new_drag_icon); + seat->destroy.notify = roots_seat_handle_destroy; + wl_signal_add(&seat->seat->events.destroy, &seat->destroy); + + return seat; +} + +static void seat_update_capabilities(struct roots_seat *seat) { + uint32_t caps = 0; + if (!wl_list_empty(&seat->keyboards)) { + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + } + if (!wl_list_empty(&seat->pointers) || !wl_list_empty(&seat->tablets)) { + caps |= WL_SEAT_CAPABILITY_POINTER; + } + if (!wl_list_empty(&seat->touch)) { + caps |= WL_SEAT_CAPABILITY_TOUCH; + } + wlr_seat_set_capabilities(seat->seat, caps); + + // Hide cursor if seat doesn't have pointer capability + if ((caps & WL_SEAT_CAPABILITY_POINTER) == 0) { + wlr_cursor_set_image(seat->cursor->cursor, NULL, 0, 0, 0, 0, 0, 0); + } else { + wlr_xcursor_manager_set_cursor_image(seat->cursor->xcursor_manager, + seat->cursor->default_xcursor, seat->cursor->cursor); + } +} + +static void handle_keyboard_destroy(struct wl_listener *listener, void *data) { + struct roots_keyboard *keyboard = + wl_container_of(listener, keyboard, device_destroy); + struct roots_seat *seat = keyboard->seat; + wl_list_remove(&keyboard->device_destroy.link); + wl_list_remove(&keyboard->keyboard_key.link); + wl_list_remove(&keyboard->keyboard_modifiers.link); + roots_keyboard_destroy(keyboard); + seat_update_capabilities(seat); +} + +static void seat_add_keyboard(struct roots_seat *seat, + struct wlr_input_device *device) { + assert(device->type == WLR_INPUT_DEVICE_KEYBOARD); + struct roots_keyboard *keyboard = + roots_keyboard_create(device, seat->input); + if (keyboard == NULL) { + wlr_log(WLR_ERROR, "could not allocate keyboard for seat"); + return; + } + + keyboard->seat = seat; + + wl_list_insert(&seat->keyboards, &keyboard->link); + + keyboard->device_destroy.notify = handle_keyboard_destroy; + wl_signal_add(&keyboard->device->events.destroy, &keyboard->device_destroy); + keyboard->keyboard_key.notify = handle_keyboard_key; + wl_signal_add(&keyboard->device->keyboard->events.key, + &keyboard->keyboard_key); + keyboard->keyboard_modifiers.notify = handle_keyboard_modifiers; + wl_signal_add(&keyboard->device->keyboard->events.modifiers, + &keyboard->keyboard_modifiers); + + wlr_seat_set_keyboard(seat->seat, device); +} + +static void handle_pointer_destroy(struct wl_listener *listener, void *data) { + struct roots_pointer *pointer = + wl_container_of(listener, pointer, device_destroy); + struct roots_seat *seat = pointer->seat; + + wl_list_remove(&pointer->link); + wlr_cursor_detach_input_device(seat->cursor->cursor, pointer->device); + wl_list_remove(&pointer->device_destroy.link); + free(pointer); + + seat_update_capabilities(seat); +} + +static void seat_add_pointer(struct roots_seat *seat, + struct wlr_input_device *device) { + struct roots_pointer *pointer = calloc(1, sizeof(struct roots_pointer)); + if (!pointer) { + wlr_log(WLR_ERROR, "could not allocate pointer for seat"); + return; + } + + device->data = pointer; + pointer->device = device; + pointer->seat = seat; + wl_list_insert(&seat->pointers, &pointer->link); + + pointer->device_destroy.notify = handle_pointer_destroy; + wl_signal_add(&pointer->device->events.destroy, &pointer->device_destroy); + + wlr_cursor_attach_input_device(seat->cursor->cursor, device); + roots_seat_configure_cursor(seat); +} + +static void handle_switch_destroy(struct wl_listener *listener, void *data) { + struct roots_switch *lid_switch = + wl_container_of(listener, lid_switch, device_destroy); + struct roots_seat *seat = lid_switch->seat; + + wl_list_remove(&lid_switch->link); + wl_list_remove(&lid_switch->device_destroy.link); + free(lid_switch); + + seat_update_capabilities(seat); +} + +static void seat_add_switch(struct roots_seat *seat, + struct wlr_input_device *device) { + assert(device->type == WLR_INPUT_DEVICE_SWITCH); + struct roots_switch *lid_switch = calloc(1, sizeof(struct roots_switch)); + if (!lid_switch) { + wlr_log(WLR_ERROR, "could not allocate switch for seat"); + return; + } + device->data = lid_switch; + lid_switch->device = device; + lid_switch->seat = seat; + wl_list_insert(&seat->switches, &lid_switch->link); + lid_switch->device_destroy.notify = handle_switch_destroy; + + lid_switch->toggle.notify = handle_switch_toggle; + wl_signal_add(&lid_switch->device->lid_switch->events.toggle, &lid_switch->toggle); +} + +static void handle_touch_destroy(struct wl_listener *listener, void *data) { + struct roots_pointer *touch = + wl_container_of(listener, touch, device_destroy); + struct roots_seat *seat = touch->seat; + + wl_list_remove(&touch->link); + wlr_cursor_detach_input_device(seat->cursor->cursor, touch->device); + wl_list_remove(&touch->device_destroy.link); + free(touch); + + seat_update_capabilities(seat); +} + +static void seat_add_touch(struct roots_seat *seat, + struct wlr_input_device *device) { + struct roots_touch *touch = calloc(1, sizeof(struct roots_touch)); + if (!touch) { + wlr_log(WLR_ERROR, "could not allocate touch for seat"); + return; + } + + device->data = touch; + touch->device = device; + touch->seat = seat; + wl_list_insert(&seat->touch, &touch->link); + + touch->device_destroy.notify = handle_touch_destroy; + wl_signal_add(&touch->device->events.destroy, &touch->device_destroy); + + wlr_cursor_attach_input_device(seat->cursor->cursor, device); + roots_seat_configure_cursor(seat); +} + +static void handle_tablet_pad_destroy(struct wl_listener *listener, + void *data) { + struct roots_tablet_pad *tablet_pad = + wl_container_of(listener, tablet_pad, device_destroy); + struct roots_seat *seat = tablet_pad->seat; + + wl_list_remove(&tablet_pad->device_destroy.link); + wl_list_remove(&tablet_pad->tablet_destroy.link); + wl_list_remove(&tablet_pad->attach.link); + wl_list_remove(&tablet_pad->link); + + wl_list_remove(&tablet_pad->button.link); + wl_list_remove(&tablet_pad->strip.link); + wl_list_remove(&tablet_pad->ring.link); + free(tablet_pad); + + seat_update_capabilities(seat); +} + +static void handle_pad_tool_destroy(struct wl_listener *listener, void *data) { + struct roots_tablet_pad *pad = + wl_container_of(listener, pad, tablet_destroy); + + pad->tablet = NULL; + + wl_list_remove(&pad->tablet_destroy.link); + wl_list_init(&pad->tablet_destroy.link); +} + +static void attach_tablet_pad(struct roots_tablet_pad *pad, + struct roots_tablet *tool) { + wlr_log(WLR_DEBUG, "Attaching tablet pad \"%s\" to tablet tool \"%s\"", + pad->device->name, tool->device->name); + + pad->tablet = tool; + + wl_list_remove(&pad->tablet_destroy.link); + pad->tablet_destroy.notify = handle_pad_tool_destroy; + wl_signal_add(&tool->device->events.destroy, &pad->tablet_destroy); +} + +static void handle_tablet_pad_attach(struct wl_listener *listener, void *data) { + struct roots_tablet_pad *pad = + wl_container_of(listener, pad, attach); + struct wlr_tablet_tool *wlr_tool = data; + struct roots_tablet *tool = wlr_tool->data; + + attach_tablet_pad(pad, tool); +} + +static void handle_tablet_pad_ring(struct wl_listener *listener, void *data) { + struct roots_tablet_pad *pad = + wl_container_of(listener, pad, ring); + struct wlr_event_tablet_pad_ring *event = data; + + wlr_tablet_v2_tablet_pad_notify_ring(pad->tablet_v2_pad, + event->ring, event->position, + event->source == WLR_TABLET_PAD_RING_SOURCE_FINGER, + event->time_msec); +} + +static void handle_tablet_pad_strip(struct wl_listener *listener, void *data) { + struct roots_tablet_pad *pad = + wl_container_of(listener, pad, strip); + struct wlr_event_tablet_pad_strip *event = data; + + wlr_tablet_v2_tablet_pad_notify_strip(pad->tablet_v2_pad, + event->strip, event->position, + event->source == WLR_TABLET_PAD_STRIP_SOURCE_FINGER, + event->time_msec); +} + +static void handle_tablet_pad_button(struct wl_listener *listener, void *data) { + struct roots_tablet_pad *pad = + wl_container_of(listener, pad, button); + struct wlr_event_tablet_pad_button *event = data; + + wlr_tablet_v2_tablet_pad_notify_mode(pad->tablet_v2_pad, + event->group, event->mode, event->time_msec); + + wlr_tablet_v2_tablet_pad_notify_button(pad->tablet_v2_pad, + event->button, event->time_msec, + (enum zwp_tablet_pad_v2_button_state)event->state); +} + +static void seat_add_tablet_pad(struct roots_seat *seat, + struct wlr_input_device *device) { + struct roots_tablet_pad *tablet_pad = + calloc(1, sizeof(struct roots_tablet_pad)); + if (!tablet_pad) { + wlr_log(WLR_ERROR, "could not allocate tablet_pad for seat"); + return; + } + + device->data = tablet_pad; + tablet_pad->device = device; + tablet_pad->seat = seat; + wl_list_insert(&seat->tablet_pads, &tablet_pad->link); + + tablet_pad->device_destroy.notify = handle_tablet_pad_destroy; + wl_signal_add(&tablet_pad->device->events.destroy, + &tablet_pad->device_destroy); + + tablet_pad->attach.notify = handle_tablet_pad_attach; + wl_signal_add(&tablet_pad->device->tablet_pad->events.attach_tablet, + &tablet_pad->attach); + + tablet_pad->button.notify = handle_tablet_pad_button; + wl_signal_add(&tablet_pad->device->tablet_pad->events.button, &tablet_pad->button); + + tablet_pad->strip.notify = handle_tablet_pad_strip; + wl_signal_add(&tablet_pad->device->tablet_pad->events.strip, &tablet_pad->strip); + + tablet_pad->ring.notify = handle_tablet_pad_ring; + wl_signal_add(&tablet_pad->device->tablet_pad->events.ring, &tablet_pad->ring); + + wl_list_init(&tablet_pad->tablet_destroy.link); + + struct roots_desktop *desktop = seat->input->server->desktop; + tablet_pad->tablet_v2_pad = + wlr_tablet_pad_create(desktop->tablet_v2, seat->seat, device); + + /* Search for a sibling tablet */ + if (!wlr_input_device_is_libinput(device)) { + /* We can only do this on libinput devices */ + return; + } + + struct libinput_device_group *group = + libinput_device_get_device_group(wlr_libinput_get_device_handle(device)); + struct roots_tablet *tool; + wl_list_for_each(tool, &seat->tablets, link) { + if (!wlr_input_device_is_libinput(tool->device)) { + continue; + } + + struct libinput_device *li_dev = + wlr_libinput_get_device_handle(tool->device); + if (libinput_device_get_device_group(li_dev) == group) { + attach_tablet_pad(tablet_pad, tool); + break; + } + } +} + +static void handle_tablet_destroy(struct wl_listener *listener, + void *data) { + struct roots_tablet *tablet = + wl_container_of(listener, tablet, device_destroy); + struct roots_seat *seat = tablet->seat; + + wlr_cursor_detach_input_device(seat->cursor->cursor, tablet->device); + wl_list_remove(&tablet->device_destroy.link); + wl_list_remove(&tablet->link); + free(tablet); + + seat_update_capabilities(seat); +} + +static void seat_add_tablet_tool(struct roots_seat *seat, + struct wlr_input_device *device) { + struct roots_tablet *tablet = + calloc(1, sizeof(struct roots_tablet)); + if (!tablet) { + wlr_log(WLR_ERROR, "could not allocate tablet for seat"); + return; + } + + device->data = tablet; + tablet->device = device; + tablet->seat = seat; + wl_list_insert(&seat->tablets, &tablet->link); + + tablet->device_destroy.notify = handle_tablet_destroy; + wl_signal_add(&tablet->device->events.destroy, + &tablet->device_destroy); + + wlr_cursor_attach_input_device(seat->cursor->cursor, device); + roots_seat_configure_cursor(seat); + + struct roots_desktop *desktop = seat->input->server->desktop; + + tablet->tablet_v2 = + wlr_tablet_create(desktop->tablet_v2, seat->seat, device); + + struct libinput_device_group *group = + libinput_device_get_device_group(wlr_libinput_get_device_handle(device)); + struct roots_tablet_pad *pad; + wl_list_for_each(pad, &seat->tablet_pads, link) { + if (!wlr_input_device_is_libinput(pad->device)) { + continue; + } + + struct libinput_device *li_dev = + wlr_libinput_get_device_handle(pad->device); + if (libinput_device_get_device_group(li_dev) == group) { + attach_tablet_pad(pad, tablet); + } + } +} + +void roots_seat_add_device(struct roots_seat *seat, + struct wlr_input_device *device) { + switch (device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + seat_add_keyboard(seat, device); + break; + case WLR_INPUT_DEVICE_POINTER: + seat_add_pointer(seat, device); + break; + case WLR_INPUT_DEVICE_SWITCH: + seat_add_switch(seat, device); + break; + case WLR_INPUT_DEVICE_TOUCH: + seat_add_touch(seat, device); + break; + case WLR_INPUT_DEVICE_TABLET_PAD: + seat_add_tablet_pad(seat, device); + break; + case WLR_INPUT_DEVICE_TABLET_TOOL: + seat_add_tablet_tool(seat, device); + break; + } + + seat_update_capabilities(seat); +} + +void roots_seat_configure_xcursor(struct roots_seat *seat) { + const char *cursor_theme = NULL; + struct roots_cursor_config *cc = + roots_config_get_cursor(seat->input->config, seat->seat->name); + if (cc != NULL) { + cursor_theme = cc->theme; + if (cc->default_image != NULL) { + seat->cursor->default_xcursor = cc->default_image; + } + } + + if (!seat->cursor->xcursor_manager) { + seat->cursor->xcursor_manager = + wlr_xcursor_manager_create(cursor_theme, ROOTS_XCURSOR_SIZE); + if (seat->cursor->xcursor_manager == NULL) { + wlr_log(WLR_ERROR, "Cannot create XCursor manager for theme %s", + cursor_theme); + return; + } + } + + struct roots_output *output; + wl_list_for_each(output, &seat->input->server->desktop->outputs, link) { + float scale = output->wlr_output->scale; + if (wlr_xcursor_manager_load(seat->cursor->xcursor_manager, scale)) { + wlr_log(WLR_ERROR, "Cannot load xcursor theme for output '%s' " + "with scale %f", output->wlr_output->name, scale); + } + } + + wlr_xcursor_manager_set_cursor_image(seat->cursor->xcursor_manager, + seat->cursor->default_xcursor, seat->cursor->cursor); + wlr_cursor_warp(seat->cursor->cursor, NULL, seat->cursor->cursor->x, + seat->cursor->cursor->y); +} + +bool roots_seat_has_meta_pressed(struct roots_seat *seat) { + struct roots_keyboard *keyboard; + wl_list_for_each(keyboard, &seat->keyboards, link) { + if (!keyboard->config->meta_key) { + continue; + } + + uint32_t modifiers = + wlr_keyboard_get_modifiers(keyboard->device->keyboard); + if ((modifiers ^ keyboard->config->meta_key) == 0) { + return true; + } + } + + return false; +} + +struct roots_view *roots_seat_get_focus(struct roots_seat *seat) { + if (!seat->has_focus || wl_list_empty(&seat->views)) { + return NULL; + } + struct roots_seat_view *seat_view = + wl_container_of(seat->views.next, seat_view, link); + return seat_view->view; +} + +static void seat_view_destroy(struct roots_seat_view *seat_view) { + struct roots_seat *seat = seat_view->seat; + + if (seat_view->view == roots_seat_get_focus(seat)) { + seat->has_focus = false; + seat->cursor->mode = ROOTS_CURSOR_PASSTHROUGH; + } + + if (seat_view == seat->cursor->pointer_view) { + seat->cursor->pointer_view = NULL; + } + + wl_list_remove(&seat_view->view_unmap.link); + wl_list_remove(&seat_view->view_destroy.link); + wl_list_remove(&seat_view->link); + free(seat_view); + + // Focus first view + if (!wl_list_empty(&seat->views)) { + struct roots_seat_view *first_seat_view = wl_container_of( + seat->views.next, first_seat_view, link); + roots_seat_set_focus(seat, first_seat_view->view); + } +} + +static void seat_view_handle_unmap(struct wl_listener *listener, void *data) { + struct roots_seat_view *seat_view = + wl_container_of(listener, seat_view, view_unmap); + seat_view_destroy(seat_view); +} + +static void seat_view_handle_destroy(struct wl_listener *listener, void *data) { + struct roots_seat_view *seat_view = + wl_container_of(listener, seat_view, view_destroy); + seat_view_destroy(seat_view); +} + +static struct roots_seat_view *seat_add_view(struct roots_seat *seat, + struct roots_view *view) { + struct roots_seat_view *seat_view = + calloc(1, sizeof(struct roots_seat_view)); + if (seat_view == NULL) { + return NULL; + } + seat_view->seat = seat; + seat_view->view = view; + + wl_list_insert(seat->views.prev, &seat_view->link); + + seat_view->view_unmap.notify = seat_view_handle_unmap; + wl_signal_add(&view->events.unmap, &seat_view->view_unmap); + seat_view->view_destroy.notify = seat_view_handle_destroy; + wl_signal_add(&view->events.destroy, &seat_view->view_destroy); + + return seat_view; +} + +struct roots_seat_view *roots_seat_view_from_view( + struct roots_seat *seat, struct roots_view *view) { + if (view == NULL) { + return NULL; + } + + bool found = false; + struct roots_seat_view *seat_view = NULL; + wl_list_for_each(seat_view, &seat->views, link) { + if (seat_view->view == view) { + found = true; + break; + } + } + if (!found) { + seat_view = seat_add_view(seat, view); + if (seat_view == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + return NULL; + } + } + + return seat_view; +} + +bool roots_seat_allow_input(struct roots_seat *seat, + struct wl_resource *resource) { + return !seat->exclusive_client || + wl_resource_get_client(resource) == seat->exclusive_client; +} + +void roots_seat_set_focus(struct roots_seat *seat, struct roots_view *view) { + if (view && !roots_seat_allow_input(seat, view->wlr_surface->resource)) { + return; + } + + // Make sure the view will be rendered on top of others, even if it's + // already focused in this seat + if (view != NULL) { + wl_list_remove(&view->link); + wl_list_insert(&seat->input->server->desktop->views, &view->link); + } + + bool unfullscreen = true; + +#if WLR_HAS_XWAYLAND + if (view && view->type == ROOTS_XWAYLAND_VIEW && + view->xwayland_surface->override_redirect) { + unfullscreen = false; + } +#endif + + if (view && unfullscreen) { + struct roots_desktop *desktop = view->desktop; + struct roots_output *output; + struct wlr_box box; + view_get_box(view, &box); + wl_list_for_each(output, &desktop->outputs, link) { + if (output->fullscreen_view && + output->fullscreen_view != view && + wlr_output_layout_intersects( + desktop->layout, + output->wlr_output, &box)) { + view_set_fullscreen(output->fullscreen_view, + false, NULL); + } + } + } + + struct roots_view *prev_focus = roots_seat_get_focus(seat); + if (view == prev_focus) { + return; + } + +#if WLR_HAS_XWAYLAND + if (view && view->type == ROOTS_XWAYLAND_VIEW && + !wlr_xwayland_or_surface_wants_focus( + view->xwayland_surface)) { + return; + } +#endif + struct roots_seat_view *seat_view = NULL; + if (view != NULL) { + seat_view = roots_seat_view_from_view(seat, view); + if (seat_view == NULL) { + return; + } + } + + seat->has_focus = false; + + // Deactivate the old view if it is not focused by some other seat + if (prev_focus != NULL && !input_view_has_focus(seat->input, prev_focus)) { + view_activate(prev_focus, false); + } + + if (view == NULL) { + seat->cursor->mode = ROOTS_CURSOR_PASSTHROUGH; + wlr_seat_keyboard_clear_focus(seat->seat); + roots_input_method_relay_set_focus(&seat->im_relay, NULL); + return; + } + + wl_list_remove(&seat_view->link); + wl_list_insert(&seat->views, &seat_view->link); + + view_damage_whole(view); + + if (seat->focused_layer) { + return; + } + + view_activate(view, true); + seat->has_focus = true; + + // An existing keyboard grab might try to deny setting focus, so cancel it + wlr_seat_keyboard_end_grab(seat->seat); + + struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->seat); + if (keyboard != NULL) { + wlr_seat_keyboard_notify_enter(seat->seat, view->wlr_surface, + keyboard->keycodes, keyboard->num_keycodes, + &keyboard->modifiers); + /* FIXME: Move this to a better place */ + struct roots_tablet_pad *pad; + wl_list_for_each(pad, &seat->tablet_pads, link) { + if (pad->tablet) { + wlr_tablet_v2_tablet_pad_notify_enter(pad->tablet_v2_pad, pad->tablet->tablet_v2, view->wlr_surface); + } + } + } else { + wlr_seat_keyboard_notify_enter(seat->seat, view->wlr_surface, + NULL, 0, NULL); + } + + if (seat->cursor) { + roots_cursor_update_focus(seat->cursor); + } + + roots_input_method_relay_set_focus(&seat->im_relay, view->wlr_surface); +} + +/** + * Focus semantics of layer surfaces are somewhat detached from the normal focus + * flow. For layers above the shell layer, for example, you cannot unfocus them. + * You also cannot alt-tab between layer surfaces and shell surfaces. + */ +void roots_seat_set_focus_layer(struct roots_seat *seat, + struct wlr_layer_surface_v1 *layer) { + if (!layer) { + seat->focused_layer = NULL; + return; + } + struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->seat); + if (!roots_seat_allow_input(seat, layer->resource)) { + return; + } + if (seat->has_focus) { + struct roots_view *prev_focus = roots_seat_get_focus(seat); + wlr_seat_keyboard_clear_focus(seat->seat); + view_activate(prev_focus, false); + } + seat->has_focus = false; + if (layer->layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { + seat->focused_layer = layer; + } + if (keyboard != NULL) { + wlr_seat_keyboard_notify_enter(seat->seat, layer->surface, + keyboard->keycodes, keyboard->num_keycodes, + &keyboard->modifiers); + } else { + wlr_seat_keyboard_notify_enter(seat->seat, layer->surface, + NULL, 0, NULL); + } + + + if (seat->cursor) { + roots_cursor_update_focus(seat->cursor); + } +} + +void roots_seat_set_exclusive_client(struct roots_seat *seat, + struct wl_client *client) { + if (!client) { + seat->exclusive_client = client; + // Triggers a refocus of the topmost surface layer if necessary + // TODO: Make layer surface focus per-output based on cursor position + struct roots_output *output; + wl_list_for_each(output, &seat->input->server->desktop->outputs, link) { + arrange_layers(output); + } + return; + } + if (seat->focused_layer) { + if (wl_resource_get_client(seat->focused_layer->resource) != client) { + roots_seat_set_focus_layer(seat, NULL); + } + } + if (seat->has_focus) { + struct roots_view *focus = roots_seat_get_focus(seat); + if (wl_resource_get_client(focus->wlr_surface->resource) != client) { + roots_seat_set_focus(seat, NULL); + } + } + if (seat->seat->pointer_state.focused_client) { + if (seat->seat->pointer_state.focused_client->client != client) { + wlr_seat_pointer_clear_focus(seat->seat); + } + } + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + struct wlr_touch_point *point; + wl_list_for_each(point, &seat->seat->touch_state.touch_points, link) { + if (point->client->client != client) { + wlr_seat_touch_point_clear_focus(seat->seat, + now.tv_nsec / 1000, point->touch_id); + } + } + seat->exclusive_client = client; +} + +void roots_seat_cycle_focus(struct roots_seat *seat) { + if (wl_list_empty(&seat->views)) { + return; + } + + struct roots_seat_view *first_seat_view = wl_container_of( + seat->views.next, first_seat_view, link); + if (!seat->has_focus) { + roots_seat_set_focus(seat, first_seat_view->view); + return; + } + if (wl_list_length(&seat->views) < 2) { + return; + } + + // Focus the next view + struct roots_seat_view *next_seat_view = wl_container_of( + first_seat_view->link.next, next_seat_view, link); + roots_seat_set_focus(seat, next_seat_view->view); + + // Move the first view to the end of the list + wl_list_remove(&first_seat_view->link); + wl_list_insert(seat->views.prev, &first_seat_view->link); +} + +void roots_seat_begin_move(struct roots_seat *seat, struct roots_view *view) { + struct roots_cursor *cursor = seat->cursor; + cursor->mode = ROOTS_CURSOR_MOVE; + cursor->offs_x = cursor->cursor->x; + cursor->offs_y = cursor->cursor->y; + if (view->maximized) { + cursor->view_x = view->saved.x; + cursor->view_y = view->saved.y; + } else { + cursor->view_x = view->box.x; + cursor->view_y = view->box.y; + } + view_maximize(view, false); + wlr_seat_pointer_clear_focus(seat->seat); + + wlr_xcursor_manager_set_cursor_image(seat->cursor->xcursor_manager, + ROOTS_XCURSOR_MOVE, seat->cursor->cursor); +} + +void roots_seat_begin_resize(struct roots_seat *seat, struct roots_view *view, + uint32_t edges) { + struct roots_cursor *cursor = seat->cursor; + cursor->mode = ROOTS_CURSOR_RESIZE; + cursor->offs_x = cursor->cursor->x; + cursor->offs_y = cursor->cursor->y; + if (view->maximized) { + cursor->view_x = view->saved.x; + cursor->view_y = view->saved.y; + cursor->view_width = view->saved.width; + cursor->view_height = view->saved.height; + } else { + cursor->view_x = view->box.x; + cursor->view_y = view->box.y; + struct wlr_box box; + view_get_box(view, &box); + cursor->view_width = box.width; + cursor->view_height = box.height; + } + cursor->resize_edges = edges; + view_maximize(view, false); + wlr_seat_pointer_clear_focus(seat->seat); + + const char *resize_name = wlr_xcursor_get_resize_name(edges); + wlr_xcursor_manager_set_cursor_image(seat->cursor->xcursor_manager, + resize_name, seat->cursor->cursor); +} + +void roots_seat_begin_rotate(struct roots_seat *seat, struct roots_view *view) { + struct roots_cursor *cursor = seat->cursor; + cursor->mode = ROOTS_CURSOR_ROTATE; + cursor->offs_x = cursor->cursor->x; + cursor->offs_y = cursor->cursor->y; + cursor->view_rotation = view->rotation; + view_maximize(view, false); + wlr_seat_pointer_clear_focus(seat->seat); + + wlr_xcursor_manager_set_cursor_image(seat->cursor->xcursor_manager, + ROOTS_XCURSOR_ROTATE, seat->cursor->cursor); +} + +void roots_seat_end_compositor_grab(struct roots_seat *seat) { + struct roots_cursor *cursor = seat->cursor; + struct roots_view *view = roots_seat_get_focus(seat); + + if (view == NULL) { + return; + } + + switch(cursor->mode) { + case ROOTS_CURSOR_MOVE: + view_move(view, cursor->view_x, cursor->view_y); + break; + case ROOTS_CURSOR_RESIZE: + view_move_resize(view, cursor->view_x, cursor->view_y, cursor->view_width, cursor->view_height); + break; + case ROOTS_CURSOR_ROTATE: + view->rotation = cursor->view_rotation; + break; + case ROOTS_CURSOR_PASSTHROUGH: + break; + } + + cursor->mode = ROOTS_CURSOR_PASSTHROUGH; +} + +struct roots_seat *input_last_active_seat(struct roots_input *input) { + struct roots_seat *seat = NULL, *_seat; + wl_list_for_each(_seat, &input->seats, link) { + if (!seat || (seat->seat->last_event.tv_sec > _seat->seat->last_event.tv_sec && + seat->seat->last_event.tv_nsec > _seat->seat->last_event.tv_nsec)) { + seat = _seat; + } + } + return seat; +} diff --git a/rootston/switch.c b/rootston/switch.c new file mode 100644 index 00000000..65c5e627 --- /dev/null +++ b/rootston/switch.c @@ -0,0 +1,26 @@ +#include <stdlib.h> + +#include <wlr/util/log.h> + +#include "rootston/bindings.h" +#include "rootston/config.h" +#include "rootston/input.h" +#include "rootston/seat.h" +#include "rootston/switch.h" + +void roots_switch_handle_toggle(struct roots_switch *lid_switch, + struct wlr_event_switch_toggle *event) { + struct wl_list *bound_switches = &lid_switch->seat->input->server->config->switches; + struct roots_switch_config *sc; + wl_list_for_each(sc, bound_switches, link) { + if ((sc->name != NULL && strcmp(event->device->name, sc->name) != 0) && + (sc->name == NULL && event->switch_type != sc->switch_type)) { + continue; + } + if (sc->switch_state != WLR_SWITCH_STATE_TOGGLE && + event->switch_state != sc->switch_state) { + continue; + } + execute_binding_command(lid_switch->seat, lid_switch->seat->input, sc->command); + } +} diff --git a/rootston/text_input.c b/rootston/text_input.c new file mode 100644 index 00000000..70c92761 --- /dev/null +++ b/rootston/text_input.c @@ -0,0 +1,310 @@ +#include <assert.h> +#include <stdlib.h> +#include <wlr/util/log.h> +#include "rootston/seat.h" +#include "rootston/text_input.h" + +static struct roots_text_input *relay_get_focusable_text_input( + struct roots_input_method_relay *relay) { + struct roots_text_input *text_input = NULL; + wl_list_for_each(text_input, &relay->text_inputs, link) { + if (text_input->pending_focused_surface) { + return text_input; + } + } + return NULL; +} + +static struct roots_text_input *relay_get_focused_text_input( + struct roots_input_method_relay *relay) { + struct roots_text_input *text_input = NULL; + wl_list_for_each(text_input, &relay->text_inputs, link) { + if (text_input->input->focused_surface) { + return text_input; + } + } + return NULL; +} + +static void handle_im_commit(struct wl_listener *listener, void *data) { + struct roots_input_method_relay *relay = wl_container_of(listener, relay, + input_method_commit); + + struct roots_text_input *text_input = relay_get_focused_text_input(relay); + if (!text_input) { + return; + } + struct wlr_input_method_v2 *context = data; + assert(context == relay->input_method); + if (context->current.preedit.text) { + wlr_text_input_v3_send_preedit_string(text_input->input, + context->current.preedit.text, + context->current.preedit.cursor_begin, + context->current.preedit.cursor_end); + } + if (context->current.commit_text) { + wlr_text_input_v3_send_commit_string(text_input->input, + context->current.commit_text); + } + if (context->current.delete.before_length + || context->current.delete.after_length) { + wlr_text_input_v3_send_delete_surrounding_text(text_input->input, + context->current.delete.before_length, + context->current.delete.after_length); + } + wlr_text_input_v3_send_done(text_input->input); +} + +static void text_input_set_pending_focused_surface( + struct roots_text_input *text_input, struct wlr_surface *surface) { + text_input->pending_focused_surface = surface; + wl_signal_add(&surface->events.destroy, + &text_input->pending_focused_surface_destroy); +} + +static void text_input_clear_pending_focused_surface( + struct roots_text_input *text_input) { + wl_list_remove(&text_input->pending_focused_surface_destroy.link); + wl_list_init(&text_input->pending_focused_surface_destroy.link); + text_input->pending_focused_surface = NULL; +} + +static void handle_im_destroy(struct wl_listener *listener, void *data) { + struct roots_input_method_relay *relay = wl_container_of(listener, relay, + input_method_destroy); + struct wlr_input_method_v2 *context = data; + assert(context == relay->input_method); + relay->input_method = NULL; + struct roots_text_input *text_input = relay_get_focused_text_input(relay); + if (text_input) { + // keyboard focus is still there, so keep the surface at hand in case + // the input method returns + text_input_set_pending_focused_surface(text_input, + text_input->input->focused_surface); + wlr_text_input_v3_send_leave(text_input->input); + } +} + +static void relay_send_im_done(struct roots_input_method_relay *relay, + struct wlr_text_input_v3 *input) { + struct wlr_input_method_v2 *input_method = relay->input_method; + if (!input_method) { + wlr_log(WLR_INFO, "Sending IM_DONE but im is gone"); + return; + } + // TODO: only send each of those if they were modified + wlr_input_method_v2_send_surrounding_text(input_method, + input->current.surrounding.text, input->current.surrounding.cursor, + input->current.surrounding.anchor); + wlr_input_method_v2_send_text_change_cause(input_method, + input->current.text_change_cause); + wlr_input_method_v2_send_content_type(input_method, + input->current.content_type.hint, input->current.content_type.purpose); + wlr_input_method_v2_send_done(input_method); + // TODO: pass intent, display popup size +} + +static struct roots_text_input *text_input_to_roots( + struct roots_input_method_relay *relay, + struct wlr_text_input_v3 *text_input) { + struct roots_text_input *roots_text_input = NULL; + wl_list_for_each(roots_text_input, &relay->text_inputs, link) { + if (roots_text_input->input == text_input) { + return roots_text_input; + } + } + return NULL; +} + +static void handle_text_input_enable(struct wl_listener *listener, void *data) { + struct roots_input_method_relay *relay = wl_container_of(listener, relay, + text_input_enable); + if (relay->input_method == NULL) { + wlr_log(WLR_INFO, "Enabling text input when input method is gone"); + return; + } + struct roots_text_input *text_input = text_input_to_roots(relay, + (struct wlr_text_input_v3*)data); + wlr_input_method_v2_send_activate(relay->input_method); + relay_send_im_done(relay, text_input->input); +} + +static void handle_text_input_commit(struct wl_listener *listener, + void *data) { + struct roots_input_method_relay *relay = wl_container_of(listener, relay, + text_input_commit); + struct roots_text_input *text_input = text_input_to_roots(relay, + (struct wlr_text_input_v3*)data); + if (!text_input->input->current_enabled) { + wlr_log(WLR_INFO, "Inactive text input tried to commit an update"); + return; + } + wlr_log(WLR_DEBUG, "Text input committed update"); + if (relay->input_method == NULL) { + wlr_log(WLR_INFO, "Text input committed, but input method is gone"); + return; + } + relay_send_im_done(relay, text_input->input); +} + +static void relay_disable_text_input(struct roots_input_method_relay *relay, + struct roots_text_input *text_input) { + if (relay->input_method == NULL) { + wlr_log(WLR_DEBUG, "Disabling text input, but input method is gone"); + return; + } + wlr_input_method_v2_send_deactivate(relay->input_method); + relay_send_im_done(relay, text_input->input); +} + +static void handle_text_input_disable(struct wl_listener *listener, + void *data) { + struct roots_input_method_relay *relay = wl_container_of(listener, relay, + text_input_disable); + struct roots_text_input *text_input = text_input_to_roots(relay, + (struct wlr_text_input_v3*)data); + relay_disable_text_input(relay, text_input); +} + +static void handle_text_input_destroy(struct wl_listener *listener, + void *data) { + struct roots_input_method_relay *relay = wl_container_of(listener, relay, + text_input_destroy); + struct roots_text_input *text_input = text_input_to_roots(relay, + (struct wlr_text_input_v3*)data); + + if (text_input->input->current_enabled) { + relay_disable_text_input(relay, text_input); + } + text_input_clear_pending_focused_surface(text_input); + wl_list_remove(&text_input->link); + text_input->input = NULL; + free(text_input); +} + +static void handle_pending_focused_surface_destroy(struct wl_listener *listener, + void *data) { + struct roots_text_input *text_input = wl_container_of(listener, text_input, + pending_focused_surface_destroy); + struct wlr_surface *surface = data; + assert(text_input->pending_focused_surface == surface); + text_input->pending_focused_surface = NULL; +} + +struct roots_text_input *roots_text_input_create( + struct roots_input_method_relay *relay, + struct wlr_text_input_v3 *text_input) { + struct roots_text_input *input = calloc(1, sizeof(struct roots_text_input)); + if (!input) { + return NULL; + } + input->input = text_input; + input->relay = relay; + + wl_signal_add(&text_input->events.enable, &relay->text_input_enable); + relay->text_input_enable.notify = handle_text_input_enable; + + wl_signal_add(&text_input->events.commit, &relay->text_input_commit); + relay->text_input_commit.notify = handle_text_input_commit; + + wl_signal_add(&text_input->events.disable, &relay->text_input_disable); + relay->text_input_disable.notify = handle_text_input_disable; + + wl_signal_add(&text_input->events.destroy, &relay->text_input_destroy); + relay->text_input_destroy.notify = handle_text_input_destroy; + + input->pending_focused_surface_destroy.notify = + handle_pending_focused_surface_destroy; + wl_list_init(&input->pending_focused_surface_destroy.link); + return input; +} + +static void relay_handle_text_input(struct wl_listener *listener, + void *data) { + struct roots_input_method_relay *relay = wl_container_of(listener, relay, + text_input_new); + struct wlr_text_input_v3 *wlr_text_input = data; + if (relay->seat->seat != wlr_text_input->seat) { + return; + } + + struct roots_text_input *text_input = roots_text_input_create(relay, + wlr_text_input); + if (!text_input) { + return; + } + wl_list_insert(&relay->text_inputs, &text_input->link); +} + +static void relay_handle_input_method(struct wl_listener *listener, + void *data) { + struct roots_input_method_relay *relay = wl_container_of(listener, relay, + input_method_new); + struct wlr_input_method_v2 *input_method = data; + if (relay->seat->seat != input_method->seat) { + return; + } + + if (relay->input_method != NULL) { + wlr_log(WLR_INFO, "Attempted to connect second input method to a seat"); + wlr_input_method_v2_send_unavailable(input_method); + return; + } + + relay->input_method = input_method; + wl_signal_add(&relay->input_method->events.commit, + &relay->input_method_commit); + relay->input_method_commit.notify = handle_im_commit; + wl_signal_add(&relay->input_method->events.destroy, + &relay->input_method_destroy); + relay->input_method_destroy.notify = handle_im_destroy; + + struct roots_text_input *text_input = relay_get_focusable_text_input(relay); + if (text_input) { + wlr_text_input_v3_send_enter(text_input->input, + text_input->pending_focused_surface); + text_input_clear_pending_focused_surface(text_input); + } +} + +void roots_input_method_relay_init(struct roots_seat *seat, + struct roots_input_method_relay *relay) { + relay->seat = seat; + wl_list_init(&relay->text_inputs); + + relay->text_input_new.notify = relay_handle_text_input; + wl_signal_add(&seat->input->server->desktop->text_input->events.text_input, + &relay->text_input_new); + + relay->input_method_new.notify = relay_handle_input_method; + wl_signal_add( + &seat->input->server->desktop->input_method->events.input_method, + &relay->input_method_new); +} + +void roots_input_method_relay_set_focus(struct roots_input_method_relay *relay, + struct wlr_surface *surface) { + struct roots_text_input *text_input; + wl_list_for_each(text_input, &relay->text_inputs, link) { + if (text_input->pending_focused_surface) { + assert(text_input->input->focused_surface == NULL); + if (surface != text_input->pending_focused_surface) { + text_input_clear_pending_focused_surface(text_input); + } + } else if (text_input->input->focused_surface) { + assert(text_input->pending_focused_surface == NULL); + if (surface != text_input->input->focused_surface) { + relay_disable_text_input(relay, text_input); + wlr_text_input_v3_send_leave(text_input->input); + } + } else if (surface + && wl_resource_get_client(text_input->input->resource) + == wl_resource_get_client(surface->resource)) { + if (relay->input_method) { + wlr_text_input_v3_send_enter(text_input->input, surface); + } else { + text_input_set_pending_focused_surface(text_input, surface); + } + } + } +} diff --git a/rootston/virtual_keyboard.c b/rootston/virtual_keyboard.c new file mode 100644 index 00000000..e862caf4 --- /dev/null +++ b/rootston/virtual_keyboard.c @@ -0,0 +1,21 @@ +#define _POSIX_C_SOURCE 199309L + +#include <wlr/util/log.h> +#include <wlr/types/wlr_virtual_keyboard_v1.h> +#include "rootston/virtual_keyboard.h" +#include "rootston/seat.h" + +void handle_virtual_keyboard(struct wl_listener *listener, void *data) { + struct roots_desktop *desktop = + wl_container_of(listener, desktop, virtual_keyboard_new); + struct wlr_virtual_keyboard_v1 *keyboard = data; + + struct roots_seat *seat = input_seat_from_wlr_seat(desktop->server->input, + keyboard->seat); + if (!seat) { + wlr_log(WLR_ERROR, "could not find roots seat"); + return; + } + + roots_seat_add_device(seat, &keyboard->input_device); +} diff --git a/rootston/wl_shell.c b/rootston/wl_shell.c new file mode 100644 index 00000000..2bf4f4c2 --- /dev/null +++ b/rootston/wl_shell.c @@ -0,0 +1,295 @@ +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <wayland-server.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_surface.h> +#include <wlr/types/wlr_wl_shell.h> +#include <wlr/util/log.h> +#include "rootston/desktop.h" +#include "rootston/input.h" +#include "rootston/server.h" + +static void popup_destroy(struct roots_view_child *child) { + assert(child->destroy == popup_destroy); + struct roots_wl_shell_popup *popup = (struct roots_wl_shell_popup *)child; + if (popup == NULL) { + return; + } + wl_list_remove(&popup->destroy.link); + wl_list_remove(&popup->set_state.link); + wl_list_remove(&popup->new_popup.link); + view_child_finish(&popup->view_child); + free(popup); +} + +static void popup_handle_destroy(struct wl_listener *listener, void *data) { + struct roots_wl_shell_popup *popup = + wl_container_of(listener, popup, destroy); + popup_destroy((struct roots_view_child *)popup); +} + +static void popup_handle_set_state(struct wl_listener *listener, void *data) { + struct roots_wl_shell_popup *popup = + wl_container_of(listener, popup, set_state); + popup_destroy((struct roots_view_child *)popup); +} + +static struct roots_wl_shell_popup *popup_create(struct roots_view *view, + struct wlr_wl_shell_surface *wlr_wl_shell_surface); + +static void popup_handle_new_popup(struct wl_listener *listener, void *data) { + struct roots_wl_shell_popup *popup = + wl_container_of(listener, popup, new_popup); + struct wlr_wl_shell_surface *wlr_wl_shell_surface = data; + popup_create(popup->view_child.view, wlr_wl_shell_surface); +} + +static struct roots_wl_shell_popup *popup_create(struct roots_view *view, + struct wlr_wl_shell_surface *wlr_wl_shell_surface) { + struct roots_wl_shell_popup *popup = + calloc(1, sizeof(struct roots_wl_shell_popup)); + if (popup == NULL) { + return NULL; + } + popup->wlr_wl_shell_surface = wlr_wl_shell_surface; + popup->view_child.destroy = popup_destroy; + view_child_init(&popup->view_child, view, wlr_wl_shell_surface->surface); + popup->destroy.notify = popup_handle_destroy; + wl_signal_add(&wlr_wl_shell_surface->events.destroy, &popup->destroy); + popup->set_state.notify = popup_handle_set_state; + wl_signal_add(&wlr_wl_shell_surface->events.set_state, &popup->set_state); + popup->new_popup.notify = popup_handle_new_popup; + wl_signal_add(&wlr_wl_shell_surface->events.new_popup, &popup->new_popup); + return popup; +} + + +static void resize(struct roots_view *view, uint32_t width, uint32_t height) { + assert(view->type == ROOTS_WL_SHELL_VIEW); + struct wlr_wl_shell_surface *surf = view->wl_shell_surface; + wlr_wl_shell_surface_configure(surf, WL_SHELL_SURFACE_RESIZE_NONE, width, + height); +} + +static void close(struct roots_view *view) { + assert(view->type == ROOTS_WL_SHELL_VIEW); + struct wlr_wl_shell_surface *surf = view->wl_shell_surface; + wl_client_destroy(surf->client); +} + +static void destroy(struct roots_view *view) { + assert(view->type == ROOTS_WL_SHELL_VIEW); + struct roots_wl_shell_surface *roots_surface = view->roots_wl_shell_surface; + wl_list_remove(&roots_surface->destroy.link); + wl_list_remove(&roots_surface->request_move.link); + wl_list_remove(&roots_surface->request_resize.link); + wl_list_remove(&roots_surface->request_maximize.link); + wl_list_remove(&roots_surface->request_fullscreen.link); + wl_list_remove(&roots_surface->set_state.link); + wl_list_remove(&roots_surface->set_title.link); + wl_list_remove(&roots_surface->set_class.link); + wl_list_remove(&roots_surface->surface_commit.link); + free(roots_surface); +} + +static void handle_request_move(struct wl_listener *listener, void *data) { + struct roots_wl_shell_surface *roots_surface = + wl_container_of(listener, roots_surface, request_move); + struct roots_view *view = roots_surface->view; + struct roots_input *input = view->desktop->server->input; + struct wlr_wl_shell_surface_move_event *e = data; + struct roots_seat *seat = input_seat_from_wlr_seat(input, e->seat->seat); + if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) { + return; + } + roots_seat_begin_move(seat, view); +} + +static void handle_request_resize(struct wl_listener *listener, void *data) { + struct roots_wl_shell_surface *roots_surface = + wl_container_of(listener, roots_surface, request_resize); + struct roots_view *view = roots_surface->view; + struct roots_input *input = view->desktop->server->input; + struct wlr_wl_shell_surface_resize_event *e = data; + struct roots_seat *seat = input_seat_from_wlr_seat(input, e->seat->seat); + if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) { + return; + } + roots_seat_begin_resize(seat, view, e->edges); +} + +static void handle_request_maximize(struct wl_listener *listener, + void *data) { + struct roots_wl_shell_surface *roots_surface = + wl_container_of(listener, roots_surface, request_maximize); + struct roots_view *view = roots_surface->view; + //struct wlr_wl_shell_surface_maximize_event *e = data; + view_maximize(view, true); +} + +static void handle_request_fullscreen(struct wl_listener *listener, + void *data) { + struct roots_wl_shell_surface *roots_surface = + wl_container_of(listener, roots_surface, request_fullscreen); + struct roots_view *view = roots_surface->view; + struct wlr_wl_shell_surface_set_fullscreen_event *e = data; + view_set_fullscreen(view, true, e->output); +} + +static void handle_set_state(struct wl_listener *listener, void *data) { + struct roots_wl_shell_surface *roots_surface = + wl_container_of(listener, roots_surface, set_state); + struct roots_view *view = roots_surface->view; + struct wlr_wl_shell_surface *surface = view->wl_shell_surface; + if (view->maximized && + surface->state != WLR_WL_SHELL_SURFACE_STATE_MAXIMIZED) { + view_maximize(view, false); + } + if (view->fullscreen_output != NULL && + surface->state != WLR_WL_SHELL_SURFACE_STATE_FULLSCREEN) { + view_set_fullscreen(view, false, NULL); + } +} + +static void handle_set_title(struct wl_listener *listener, void *data) { + struct roots_wl_shell_surface *roots_surface = + wl_container_of(listener, roots_surface, set_state); + view_set_title(roots_surface->view, + roots_surface->view->wl_shell_surface->title); +} + +static void handle_set_class(struct wl_listener *listener, void *data) { + struct roots_wl_shell_surface *roots_surface = + wl_container_of(listener, roots_surface, set_state); + view_set_app_id(roots_surface->view, + roots_surface->view->wl_shell_surface->class); +} + +static void handle_surface_commit(struct wl_listener *listener, void *data) { + struct roots_wl_shell_surface *roots_surface = + wl_container_of(listener, roots_surface, surface_commit); + struct roots_view *view = roots_surface->view; + struct wlr_surface *wlr_surface = view->wlr_surface; + + view_apply_damage(view); + + int width = wlr_surface->current.width; + int height = wlr_surface->current.height; + view_update_size(view, width, height); + + double x = view->box.x; + double y = view->box.y; + if (view->pending_move_resize.update_x) { + x = view->pending_move_resize.x + view->pending_move_resize.width - + width; + view->pending_move_resize.update_x = false; + } + if (view->pending_move_resize.update_y) { + y = view->pending_move_resize.y + view->pending_move_resize.height - + height; + view->pending_move_resize.update_y = false; + } + view_update_position(view, x, y); +} + +static void handle_new_popup(struct wl_listener *listener, void *data) { + struct roots_wl_shell_surface *roots_surface = + wl_container_of(listener, roots_surface, new_popup); + struct wlr_wl_shell_surface *wlr_wl_shell_surface = data; + popup_create(roots_surface->view, wlr_wl_shell_surface); +} + +static void handle_destroy(struct wl_listener *listener, void *data) { + struct roots_wl_shell_surface *roots_surface = + wl_container_of(listener, roots_surface, destroy); + view_destroy(roots_surface->view); +} + +void handle_wl_shell_surface(struct wl_listener *listener, void *data) { + struct roots_desktop *desktop = + wl_container_of(listener, desktop, wl_shell_surface); + struct wlr_wl_shell_surface *surface = data; + + if (surface->state == WLR_WL_SHELL_SURFACE_STATE_POPUP) { + wlr_log(WLR_DEBUG, "new wl shell popup"); + return; + } + + wlr_log(WLR_DEBUG, "new wl shell surface: title=%s, class=%s", + surface->title, surface->class); + wlr_wl_shell_surface_ping(surface); + + struct roots_wl_shell_surface *roots_surface = + calloc(1, sizeof(struct roots_wl_shell_surface)); + if (!roots_surface) { + return; + } + roots_surface->destroy.notify = handle_destroy; + wl_signal_add(&surface->events.destroy, &roots_surface->destroy); + roots_surface->new_popup.notify = handle_new_popup; + wl_signal_add(&surface->events.new_popup, &roots_surface->new_popup); + roots_surface->request_move.notify = handle_request_move; + wl_signal_add(&surface->events.request_move, &roots_surface->request_move); + roots_surface->request_resize.notify = handle_request_resize; + wl_signal_add(&surface->events.request_resize, + &roots_surface->request_resize); + roots_surface->request_maximize.notify = handle_request_maximize; + wl_signal_add(&surface->events.request_maximize, + &roots_surface->request_maximize); + roots_surface->request_fullscreen.notify = + handle_request_fullscreen; + wl_signal_add(&surface->events.request_fullscreen, + &roots_surface->request_fullscreen); + + roots_surface->set_state.notify = handle_set_state; + wl_signal_add(&surface->events.set_state, &roots_surface->set_state); + roots_surface->set_title.notify = handle_set_title; + wl_signal_add(&surface->events.set_title, &roots_surface->set_title); + roots_surface->set_class.notify = handle_set_class; + wl_signal_add(&surface->events.set_class, &roots_surface->set_class); + roots_surface->surface_commit.notify = handle_surface_commit; + wl_signal_add(&surface->surface->events.commit, &roots_surface->surface_commit); + + struct roots_view *view = view_create(desktop); + if (!view) { + free(roots_surface); + return; + } + view->type = ROOTS_WL_SHELL_VIEW; + view->box.width = surface->surface->current.width; + view->box.height = surface->surface->current.height; + + view->wl_shell_surface = surface; + view->roots_wl_shell_surface = roots_surface; + view->resize = resize; + view->close = close; + view->destroy = destroy; + roots_surface->view = view; + + view_map(view, surface->surface); + view_setup(view); + + wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle, + view->wl_shell_surface->title ?: "none"); + wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle, + view->wl_shell_surface->class ?: "none"); + + if (surface->state == WLR_WL_SHELL_SURFACE_STATE_TRANSIENT) { + // We need to map it relative to the parent + bool found = false; + struct roots_view *parent; + wl_list_for_each(parent, &desktop->views, link) { + if (parent->type == ROOTS_WL_SHELL_VIEW && + parent->wl_shell_surface == surface->parent) { + found = true; + break; + } + } + if (found) { + view_move(view, + parent->box.x + surface->transient_state->x, + parent->box.y + surface->transient_state->y); + } + } +} diff --git a/rootston/xdg_shell.c b/rootston/xdg_shell.c new file mode 100644 index 00000000..da8909ba --- /dev/null +++ b/rootston/xdg_shell.c @@ -0,0 +1,565 @@ +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <wayland-server.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_surface.h> +#include <wlr/types/wlr_xdg_shell.h> +#include <wlr/util/log.h> +#include "rootston/cursor.h" +#include "rootston/desktop.h" +#include "rootston/input.h" +#include "rootston/server.h" + +static void popup_destroy(struct roots_view_child *child) { + assert(child->destroy == popup_destroy); + struct roots_xdg_popup *popup = (struct roots_xdg_popup *)child; + if (popup == NULL) { + return; + } + wl_list_remove(&popup->destroy.link); + wl_list_remove(&popup->new_popup.link); + wl_list_remove(&popup->map.link); + wl_list_remove(&popup->unmap.link); + view_child_finish(&popup->view_child); + free(popup); +} + +static void popup_handle_destroy(struct wl_listener *listener, void *data) { + struct roots_xdg_popup *popup = + wl_container_of(listener, popup, destroy); + popup_destroy((struct roots_view_child *)popup); +} + +static void popup_handle_map(struct wl_listener *listener, void *data) { + struct roots_xdg_popup *popup = wl_container_of(listener, popup, map); + view_damage_whole(popup->view_child.view); + input_update_cursor_focus(popup->view_child.view->desktop->server->input); +} + +static void popup_handle_unmap(struct wl_listener *listener, void *data) { + struct roots_xdg_popup *popup = wl_container_of(listener, popup, unmap); + view_damage_whole(popup->view_child.view); +} + +static struct roots_xdg_popup *popup_create(struct roots_view *view, + struct wlr_xdg_popup *wlr_popup); + +static void popup_handle_new_popup(struct wl_listener *listener, void *data) { + struct roots_xdg_popup *popup = + wl_container_of(listener, popup, new_popup); + struct wlr_xdg_popup *wlr_popup = data; + popup_create(popup->view_child.view, wlr_popup); +} + +static void popup_unconstrain(struct roots_xdg_popup *popup) { + // get the output of the popup's positioner anchor point and convert it to + // the toplevel parent's coordinate system and then pass it to + // wlr_xdg_popup_v6_unconstrain_from_box + + // TODO: unconstrain popups for rotated windows + if (popup->view_child.view->rotation != 0.0) { + return; + } + + struct roots_view *view = popup->view_child.view; + struct wlr_output_layout *layout = view->desktop->layout; + struct wlr_xdg_popup *wlr_popup = popup->wlr_popup; + + int anchor_lx, anchor_ly; + wlr_xdg_popup_get_anchor_point(wlr_popup, &anchor_lx, &anchor_ly); + + int popup_lx, popup_ly; + wlr_xdg_popup_get_toplevel_coords(wlr_popup, wlr_popup->geometry.x, + wlr_popup->geometry.y, &popup_lx, &popup_ly); + popup_lx += view->box.x; + popup_ly += view->box.y; + + anchor_lx += popup_lx; + anchor_ly += popup_ly; + + double dest_x = 0, dest_y = 0; + wlr_output_layout_closest_point(layout, NULL, anchor_lx, anchor_ly, + &dest_x, &dest_y); + + struct wlr_output *output = + wlr_output_layout_output_at(layout, dest_x, dest_y); + + if (output == NULL) { + return; + } + + int width = 0, height = 0; + wlr_output_effective_resolution(output, &width, &height); + + // the output box expressed in the coordinate system of the toplevel parent + // of the popup + struct wlr_box output_toplevel_sx_box = { + .x = output->lx - view->box.x, + .y = output->ly - view->box.y, + .width = width, + .height = height + }; + + wlr_xdg_popup_unconstrain_from_box( + popup->wlr_popup, &output_toplevel_sx_box); +} + +static struct roots_xdg_popup *popup_create(struct roots_view *view, + struct wlr_xdg_popup *wlr_popup) { + struct roots_xdg_popup *popup = + calloc(1, sizeof(struct roots_xdg_popup)); + if (popup == NULL) { + return NULL; + } + popup->wlr_popup = wlr_popup; + popup->view_child.destroy = popup_destroy; + view_child_init(&popup->view_child, view, wlr_popup->base->surface); + popup->destroy.notify = popup_handle_destroy; + wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); + popup->map.notify = popup_handle_map; + wl_signal_add(&wlr_popup->base->events.map, &popup->map); + popup->unmap.notify = popup_handle_unmap; + wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap); + popup->new_popup.notify = popup_handle_new_popup; + wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); + + popup_unconstrain(popup); + + return popup; +} + + +static void get_size(const struct roots_view *view, struct wlr_box *box) { + assert(view->type == ROOTS_XDG_SHELL_VIEW); + struct wlr_xdg_surface *surface = view->xdg_surface; + + struct wlr_box geo_box; + wlr_xdg_surface_get_geometry(surface, &geo_box); + box->width = geo_box.width; + box->height = geo_box.height; +} + +static void activate(struct roots_view *view, bool active) { + assert(view->type == ROOTS_XDG_SHELL_VIEW); + struct wlr_xdg_surface *surface = view->xdg_surface; + if (surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { + wlr_xdg_toplevel_set_activated(surface, active); + } +} + +static void apply_size_constraints(struct wlr_xdg_surface *surface, + uint32_t width, uint32_t height, uint32_t *dest_width, + uint32_t *dest_height) { + *dest_width = width; + *dest_height = height; + + struct wlr_xdg_toplevel_state *state = &surface->toplevel->current; + if (width < state->min_width) { + *dest_width = state->min_width; + } else if (state->max_width > 0 && + width > state->max_width) { + *dest_width = state->max_width; + } + if (height < state->min_height) { + *dest_height = state->min_height; + } else if (state->max_height > 0 && + height > state->max_height) { + *dest_height = state->max_height; + } +} + +static void resize(struct roots_view *view, uint32_t width, uint32_t height) { + assert(view->type == ROOTS_XDG_SHELL_VIEW); + struct wlr_xdg_surface *surface = view->xdg_surface; + if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { + return; + } + + uint32_t constrained_width, constrained_height; + apply_size_constraints(surface, width, height, &constrained_width, + &constrained_height); + + wlr_xdg_toplevel_set_size(surface, constrained_width, + constrained_height); +} + +static void move_resize(struct roots_view *view, double x, double y, + uint32_t width, uint32_t height) { + assert(view->type == ROOTS_XDG_SHELL_VIEW); + struct roots_xdg_surface *roots_surface = view->roots_xdg_surface; + struct wlr_xdg_surface *surface = view->xdg_surface; + if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { + return; + } + + bool update_x = x != view->box.x; + bool update_y = y != view->box.y; + + uint32_t constrained_width, constrained_height; + apply_size_constraints(surface, width, height, &constrained_width, + &constrained_height); + + if (update_x) { + x = x + width - constrained_width; + } + if (update_y) { + y = y + height - constrained_height; + } + + view->pending_move_resize.update_x = update_x; + view->pending_move_resize.update_y = update_y; + view->pending_move_resize.x = x; + view->pending_move_resize.y = y; + view->pending_move_resize.width = constrained_width; + view->pending_move_resize.height = constrained_height; + + uint32_t serial = wlr_xdg_toplevel_set_size(surface, constrained_width, + constrained_height); + if (serial > 0) { + roots_surface->pending_move_resize_configure_serial = serial; + } else if (roots_surface->pending_move_resize_configure_serial == 0) { + view_update_position(view, x, y); + } +} + +static void maximize(struct roots_view *view, bool maximized) { + assert(view->type == ROOTS_XDG_SHELL_VIEW); + struct wlr_xdg_surface *surface = view->xdg_surface; + if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { + return; + } + + wlr_xdg_toplevel_set_maximized(surface, maximized); +} + +static void set_fullscreen(struct roots_view *view, bool fullscreen) { + assert(view->type == ROOTS_XDG_SHELL_VIEW); + struct wlr_xdg_surface *surface = view->xdg_surface; + if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { + return; + } + + wlr_xdg_toplevel_set_fullscreen(surface, fullscreen); +} + +static void close(struct roots_view *view) { + assert(view->type == ROOTS_XDG_SHELL_VIEW); + struct wlr_xdg_surface *surface = view->xdg_surface; + struct wlr_xdg_popup *popup = NULL; + wl_list_for_each(popup, &surface->popups, link) { + wlr_xdg_surface_send_close(popup->base); + } + wlr_xdg_surface_send_close(surface); +} + +static void destroy(struct roots_view *view) { + assert(view->type == ROOTS_XDG_SHELL_VIEW); + struct roots_xdg_surface *roots_xdg_surface = view->roots_xdg_surface; + wl_list_remove(&roots_xdg_surface->surface_commit.link); + wl_list_remove(&roots_xdg_surface->destroy.link); + wl_list_remove(&roots_xdg_surface->new_popup.link); + wl_list_remove(&roots_xdg_surface->map.link); + wl_list_remove(&roots_xdg_surface->unmap.link); + wl_list_remove(&roots_xdg_surface->request_move.link); + wl_list_remove(&roots_xdg_surface->request_resize.link); + wl_list_remove(&roots_xdg_surface->request_maximize.link); + wl_list_remove(&roots_xdg_surface->request_fullscreen.link); + wl_list_remove(&roots_xdg_surface->set_title.link); + wl_list_remove(&roots_xdg_surface->set_app_id.link); + roots_xdg_surface->view->xdg_surface->data = NULL; + free(roots_xdg_surface); +} + +static void handle_request_move(struct wl_listener *listener, void *data) { + struct roots_xdg_surface *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, request_move); + struct roots_view *view = roots_xdg_surface->view; + struct roots_input *input = view->desktop->server->input; + struct wlr_xdg_toplevel_move_event *e = data; + struct roots_seat *seat = input_seat_from_wlr_seat(input, e->seat->seat); + // TODO verify event serial + if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) { + return; + } + roots_seat_begin_move(seat, view); +} + +static void handle_request_resize(struct wl_listener *listener, void *data) { + struct roots_xdg_surface *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, request_resize); + struct roots_view *view = roots_xdg_surface->view; + struct roots_input *input = view->desktop->server->input; + struct wlr_xdg_toplevel_resize_event *e = data; + // TODO verify event serial + struct roots_seat *seat = input_seat_from_wlr_seat(input, e->seat->seat); + assert(seat); + if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) { + return; + } + roots_seat_begin_resize(seat, view, e->edges); +} + +static void handle_request_maximize(struct wl_listener *listener, void *data) { + struct roots_xdg_surface *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, request_maximize); + struct roots_view *view = roots_xdg_surface->view; + struct wlr_xdg_surface *surface = view->xdg_surface; + + if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { + return; + } + + view_maximize(view, surface->toplevel->client_pending.maximized); +} + +static void handle_request_fullscreen(struct wl_listener *listener, + void *data) { + struct roots_xdg_surface *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, request_fullscreen); + struct roots_view *view = roots_xdg_surface->view; + struct wlr_xdg_surface *surface = view->xdg_surface; + struct wlr_xdg_toplevel_set_fullscreen_event *e = data; + + if (surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { + return; + } + + view_set_fullscreen(view, e->fullscreen, e->output); +} + +static void handle_set_title(struct wl_listener *listener, void *data) { + struct roots_xdg_surface *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, set_title); + + view_set_title(roots_xdg_surface->view, + roots_xdg_surface->view->xdg_surface->toplevel->title); +} + +static void handle_set_app_id(struct wl_listener *listener, void *data) { + struct roots_xdg_surface *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, set_app_id); + + view_set_app_id(roots_xdg_surface->view, + roots_xdg_surface->view->xdg_surface->toplevel->app_id); +} + +static void handle_surface_commit(struct wl_listener *listener, void *data) { + struct roots_xdg_surface *roots_surface = + wl_container_of(listener, roots_surface, surface_commit); + struct roots_view *view = roots_surface->view; + struct wlr_xdg_surface *surface = view->xdg_surface; + + if (!surface->mapped) { + return; + } + + view_apply_damage(view); + + struct wlr_box size; + get_size(view, &size); + view_update_size(view, size.width, size.height); + + uint32_t pending_serial = + roots_surface->pending_move_resize_configure_serial; + if (pending_serial > 0 && pending_serial >= surface->configure_serial) { + double x = view->box.x; + double y = view->box.y; + if (view->pending_move_resize.update_x) { + x = view->pending_move_resize.x + view->pending_move_resize.width - + size.width; + } + if (view->pending_move_resize.update_y) { + y = view->pending_move_resize.y + view->pending_move_resize.height - + size.height; + } + view_update_position(view, x, y); + + if (pending_serial == surface->configure_serial) { + roots_surface->pending_move_resize_configure_serial = 0; + } + } +} + +static void handle_new_popup(struct wl_listener *listener, void *data) { + struct roots_xdg_surface *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, new_popup); + struct wlr_xdg_popup *wlr_popup = data; + popup_create(roots_xdg_surface->view, wlr_popup); +} + +static void handle_map(struct wl_listener *listener, void *data) { + struct roots_xdg_surface *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, map); + struct roots_view *view = roots_xdg_surface->view; + + struct wlr_box box; + get_size(view, &box); + view->box.width = box.width; + view->box.height = box.height; + + view_map(view, view->xdg_surface->surface); + view_setup(view); + + wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle, + view->xdg_surface->toplevel->title ?: "none"); + wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle, + view->xdg_surface->toplevel->app_id ?: "none"); +} + +static void handle_unmap(struct wl_listener *listener, void *data) { + struct roots_xdg_surface *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, unmap); + view_unmap(roots_xdg_surface->view); +} + +static void handle_destroy(struct wl_listener *listener, void *data) { + struct roots_xdg_surface *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, destroy); + view_destroy(roots_xdg_surface->view); +} + +void handle_xdg_shell_surface(struct wl_listener *listener, void *data) { + struct wlr_xdg_surface *surface = data; + assert(surface->role != WLR_XDG_SURFACE_ROLE_NONE); + + if (surface->role == WLR_XDG_SURFACE_ROLE_POPUP) { + wlr_log(WLR_DEBUG, "new xdg popup"); + return; + } + + struct roots_desktop *desktop = + wl_container_of(listener, desktop, xdg_shell_surface); + + wlr_log(WLR_DEBUG, "new xdg toplevel: title=%s, app_id=%s", + surface->toplevel->title, surface->toplevel->app_id); + wlr_xdg_surface_ping(surface); + + struct roots_xdg_surface *roots_surface = + calloc(1, sizeof(struct roots_xdg_surface)); + if (!roots_surface) { + return; + } + roots_surface->surface_commit.notify = handle_surface_commit; + wl_signal_add(&surface->surface->events.commit, + &roots_surface->surface_commit); + roots_surface->destroy.notify = handle_destroy; + wl_signal_add(&surface->events.destroy, &roots_surface->destroy); + roots_surface->map.notify = handle_map; + wl_signal_add(&surface->events.map, &roots_surface->map); + roots_surface->unmap.notify = handle_unmap; + wl_signal_add(&surface->events.unmap, &roots_surface->unmap); + roots_surface->request_move.notify = handle_request_move; + wl_signal_add(&surface->toplevel->events.request_move, + &roots_surface->request_move); + roots_surface->request_resize.notify = handle_request_resize; + wl_signal_add(&surface->toplevel->events.request_resize, + &roots_surface->request_resize); + roots_surface->request_maximize.notify = handle_request_maximize; + wl_signal_add(&surface->toplevel->events.request_maximize, + &roots_surface->request_maximize); + roots_surface->request_fullscreen.notify = handle_request_fullscreen; + wl_signal_add(&surface->toplevel->events.request_fullscreen, + &roots_surface->request_fullscreen); + roots_surface->set_title.notify = handle_set_title; + wl_signal_add(&surface->toplevel->events.set_title, &roots_surface->set_title); + roots_surface->set_app_id.notify = handle_set_app_id; + wl_signal_add(&surface->toplevel->events.set_app_id, + &roots_surface->set_app_id); + roots_surface->new_popup.notify = handle_new_popup; + wl_signal_add(&surface->events.new_popup, &roots_surface->new_popup); + surface->data = roots_surface; + + struct roots_view *view = view_create(desktop); + if (!view) { + free(roots_surface); + return; + } + view->type = ROOTS_XDG_SHELL_VIEW; + + view->xdg_surface = surface; + view->roots_xdg_surface = roots_surface; + view->activate = activate; + view->resize = resize; + view->move_resize = move_resize; + view->maximize = maximize; + view->set_fullscreen = set_fullscreen; + view->close = close; + view->destroy = destroy; + roots_surface->view = view; + + if (surface->toplevel->client_pending.maximized) { + view_maximize(view, true); + } + if (surface->toplevel->client_pending.fullscreen) { + view_set_fullscreen(view, true, NULL); + } +} + + + +static void decoration_handle_destroy(struct wl_listener *listener, + void *data) { + struct roots_xdg_toplevel_decoration *decoration = + wl_container_of(listener, decoration, destroy); + + decoration->surface->xdg_toplevel_decoration = NULL; + view_update_decorated(decoration->surface->view, false); + wl_list_remove(&decoration->destroy.link); + wl_list_remove(&decoration->request_mode.link); + wl_list_remove(&decoration->surface_commit.link); + free(decoration); +} + +static void decoration_handle_request_mode(struct wl_listener *listener, + void *data) { + struct roots_xdg_toplevel_decoration *decoration = + wl_container_of(listener, decoration, request_mode); + + enum wlr_xdg_toplevel_decoration_v1_mode mode = + decoration->wlr_decoration->client_pending_mode; + if (mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE) { + mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; + } + wlr_xdg_toplevel_decoration_v1_set_mode(decoration->wlr_decoration, mode); +} + +static void decoration_handle_surface_commit(struct wl_listener *listener, + void *data) { + struct roots_xdg_toplevel_decoration *decoration = + wl_container_of(listener, decoration, surface_commit); + + bool decorated = decoration->wlr_decoration->current_mode == + WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; + view_update_decorated(decoration->surface->view, decorated); +} + +void handle_xdg_toplevel_decoration(struct wl_listener *listener, void *data) { + struct wlr_xdg_toplevel_decoration_v1 *wlr_decoration = data; + + wlr_log(WLR_DEBUG, "new xdg toplevel decoration"); + + struct roots_xdg_surface *xdg_surface = wlr_decoration->surface->data; + assert(xdg_surface != NULL); + struct wlr_xdg_surface *wlr_xdg_surface = xdg_surface->view->xdg_surface; + + struct roots_xdg_toplevel_decoration *decoration = + calloc(1, sizeof(struct roots_xdg_toplevel_decoration)); + if (decoration == NULL) { + return; + } + decoration->wlr_decoration = wlr_decoration; + decoration->surface = xdg_surface; + xdg_surface->xdg_toplevel_decoration = decoration; + + decoration->destroy.notify = decoration_handle_destroy; + wl_signal_add(&wlr_decoration->events.destroy, &decoration->destroy); + decoration->request_mode.notify = decoration_handle_request_mode; + wl_signal_add(&wlr_decoration->events.request_mode, + &decoration->request_mode); + decoration->surface_commit.notify = decoration_handle_surface_commit; + wl_signal_add(&wlr_xdg_surface->surface->events.commit, + &decoration->surface_commit); + + decoration_handle_request_mode(&decoration->request_mode, wlr_decoration); +} diff --git a/rootston/xdg_shell_v6.c b/rootston/xdg_shell_v6.c new file mode 100644 index 00000000..8d989aef --- /dev/null +++ b/rootston/xdg_shell_v6.c @@ -0,0 +1,495 @@ +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <wayland-server.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_surface.h> +#include <wlr/types/wlr_xdg_shell_v6.h> +#include <wlr/util/log.h> +#include "rootston/desktop.h" +#include "rootston/input.h" +#include "rootston/server.h" + +static void popup_destroy(struct roots_view_child *child) { + assert(child->destroy == popup_destroy); + struct roots_xdg_popup_v6 *popup = (struct roots_xdg_popup_v6 *)child; + if (popup == NULL) { + return; + } + wl_list_remove(&popup->destroy.link); + wl_list_remove(&popup->new_popup.link); + wl_list_remove(&popup->map.link); + wl_list_remove(&popup->unmap.link); + view_child_finish(&popup->view_child); + free(popup); +} + +static void popup_handle_destroy(struct wl_listener *listener, void *data) { + struct roots_xdg_popup_v6 *popup = + wl_container_of(listener, popup, destroy); + popup_destroy((struct roots_view_child *)popup); +} + +static void popup_handle_map(struct wl_listener *listener, void *data) { + struct roots_xdg_popup_v6 *popup = + wl_container_of(listener, popup, map); + view_damage_whole(popup->view_child.view); + input_update_cursor_focus(popup->view_child.view->desktop->server->input); +} + +static void popup_handle_unmap(struct wl_listener *listener, void *data) { + struct roots_xdg_popup_v6 *popup = + wl_container_of(listener, popup, unmap); + view_damage_whole(popup->view_child.view); +} + +static struct roots_xdg_popup_v6 *popup_create(struct roots_view *view, + struct wlr_xdg_popup_v6 *wlr_popup); + +static void popup_handle_new_popup(struct wl_listener *listener, void *data) { + struct roots_xdg_popup_v6 *popup = + wl_container_of(listener, popup, new_popup); + struct wlr_xdg_popup_v6 *wlr_popup = data; + popup_create(popup->view_child.view, wlr_popup); +} + +static void popup_unconstrain(struct roots_xdg_popup_v6 *popup) { + // get the output of the popup's positioner anchor point and convert it to + // the toplevel parent's coordinate system and then pass it to + // wlr_xdg_popup_v6_unconstrain_from_box + + // TODO: unconstrain popups for rotated windows + if (popup->view_child.view->rotation != 0.0) { + return; + } + + struct roots_view *view = popup->view_child.view; + struct wlr_output_layout *layout = view->desktop->layout; + struct wlr_xdg_popup_v6 *wlr_popup = popup->wlr_popup; + + int anchor_lx, anchor_ly; + wlr_xdg_popup_v6_get_anchor_point(wlr_popup, &anchor_lx, &anchor_ly); + + int popup_lx, popup_ly; + wlr_xdg_popup_v6_get_toplevel_coords(wlr_popup, wlr_popup->geometry.x, + wlr_popup->geometry.y, &popup_lx, &popup_ly); + popup_lx += view->box.x; + popup_ly += view->box.y; + + anchor_lx += popup_lx; + anchor_ly += popup_ly; + + double dest_x = 0, dest_y = 0; + wlr_output_layout_closest_point(layout, NULL, anchor_lx, anchor_ly, + &dest_x, &dest_y); + + struct wlr_output *output = + wlr_output_layout_output_at(layout, dest_x, dest_y); + + if (output == NULL) { + return; + } + + int width = 0, height = 0; + wlr_output_effective_resolution(output, &width, &height); + + // the output box expressed in the coordinate system of the toplevel parent + // of the popup + struct wlr_box output_toplevel_sx_box = { + .x = output->lx - view->box.x, + .y = output->ly - view->box.y, + .width = width, + .height = height + }; + + wlr_xdg_popup_v6_unconstrain_from_box(popup->wlr_popup, &output_toplevel_sx_box); +} + +static struct roots_xdg_popup_v6 *popup_create(struct roots_view *view, + struct wlr_xdg_popup_v6 *wlr_popup) { + struct roots_xdg_popup_v6 *popup = + calloc(1, sizeof(struct roots_xdg_popup_v6)); + if (popup == NULL) { + return NULL; + } + popup->wlr_popup = wlr_popup; + popup->view_child.destroy = popup_destroy; + view_child_init(&popup->view_child, view, wlr_popup->base->surface); + popup->destroy.notify = popup_handle_destroy; + wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); + popup->map.notify = popup_handle_map; + wl_signal_add(&wlr_popup->base->events.map, &popup->map); + popup->unmap.notify = popup_handle_unmap; + wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap); + popup->new_popup.notify = popup_handle_new_popup; + wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); + + popup_unconstrain(popup); + + return popup; +} + + +static void get_size(const struct roots_view *view, struct wlr_box *box) { + assert(view->type == ROOTS_XDG_SHELL_V6_VIEW); + struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6; + + struct wlr_box geo_box; + wlr_xdg_surface_v6_get_geometry(surface, &geo_box); + box->width = geo_box.width; + box->height = geo_box.height; +} + +static void activate(struct roots_view *view, bool active) { + assert(view->type == ROOTS_XDG_SHELL_V6_VIEW); + struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6; + if (surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) { + wlr_xdg_toplevel_v6_set_activated(surface, active); + } +} + +static void apply_size_constraints(struct wlr_xdg_surface_v6 *surface, + uint32_t width, uint32_t height, uint32_t *dest_width, + uint32_t *dest_height) { + *dest_width = width; + *dest_height = height; + + struct wlr_xdg_toplevel_v6_state *state = &surface->toplevel->current; + if (width < state->min_width) { + *dest_width = state->min_width; + } else if (state->max_width > 0 && + width > state->max_width) { + *dest_width = state->max_width; + } + if (height < state->min_height) { + *dest_height = state->min_height; + } else if (state->max_height > 0 && + height > state->max_height) { + *dest_height = state->max_height; + } +} + +static void resize(struct roots_view *view, uint32_t width, uint32_t height) { + assert(view->type == ROOTS_XDG_SHELL_V6_VIEW); + struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6; + if (surface->role != WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) { + return; + } + + uint32_t constrained_width, constrained_height; + apply_size_constraints(surface, width, height, &constrained_width, + &constrained_height); + + wlr_xdg_toplevel_v6_set_size(surface, constrained_width, + constrained_height); +} + +static void move_resize(struct roots_view *view, double x, double y, + uint32_t width, uint32_t height) { + assert(view->type == ROOTS_XDG_SHELL_V6_VIEW); + struct roots_xdg_surface_v6 *roots_surface = view->roots_xdg_surface_v6; + struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6; + if (surface->role != WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) { + return; + } + + bool update_x = x != view->box.x; + bool update_y = y != view->box.y; + + uint32_t constrained_width, constrained_height; + apply_size_constraints(surface, width, height, &constrained_width, + &constrained_height); + + if (update_x) { + x = x + width - constrained_width; + } + if (update_y) { + y = y + height - constrained_height; + } + + view->pending_move_resize.update_x = update_x; + view->pending_move_resize.update_y = update_y; + view->pending_move_resize.x = x; + view->pending_move_resize.y = y; + view->pending_move_resize.width = constrained_width; + view->pending_move_resize.height = constrained_height; + + uint32_t serial = wlr_xdg_toplevel_v6_set_size(surface, constrained_width, + constrained_height); + if (serial > 0) { + roots_surface->pending_move_resize_configure_serial = serial; + } else if (roots_surface->pending_move_resize_configure_serial == 0) { + view_update_position(view, x, y); + } +} + +static void maximize(struct roots_view *view, bool maximized) { + assert(view->type == ROOTS_XDG_SHELL_V6_VIEW); + struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6; + if (surface->role != WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) { + return; + } + + wlr_xdg_toplevel_v6_set_maximized(surface, maximized); +} + +static void set_fullscreen(struct roots_view *view, bool fullscreen) { + assert(view->type == ROOTS_XDG_SHELL_V6_VIEW); + struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6; + if (surface->role != WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) { + return; + } + + wlr_xdg_toplevel_v6_set_fullscreen(surface, fullscreen); +} + +static void close(struct roots_view *view) { + assert(view->type == ROOTS_XDG_SHELL_V6_VIEW); + struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6; + struct wlr_xdg_popup_v6 *popup = NULL; + wl_list_for_each(popup, &surface->popups, link) { + wlr_xdg_surface_v6_send_close(popup->base); + } + wlr_xdg_surface_v6_send_close(surface); +} + +static void destroy(struct roots_view *view) { + assert(view->type == ROOTS_XDG_SHELL_V6_VIEW); + struct roots_xdg_surface_v6 *roots_xdg_surface = view->roots_xdg_surface_v6; + wl_list_remove(&roots_xdg_surface->surface_commit.link); + wl_list_remove(&roots_xdg_surface->destroy.link); + wl_list_remove(&roots_xdg_surface->new_popup.link); + wl_list_remove(&roots_xdg_surface->map.link); + wl_list_remove(&roots_xdg_surface->unmap.link); + wl_list_remove(&roots_xdg_surface->request_move.link); + wl_list_remove(&roots_xdg_surface->request_resize.link); + wl_list_remove(&roots_xdg_surface->request_maximize.link); + wl_list_remove(&roots_xdg_surface->request_fullscreen.link); + wl_list_remove(&roots_xdg_surface->set_title.link); + wl_list_remove(&roots_xdg_surface->set_app_id.link); + free(roots_xdg_surface); +} + +static void handle_request_move(struct wl_listener *listener, void *data) { + struct roots_xdg_surface_v6 *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, request_move); + struct roots_view *view = roots_xdg_surface->view; + struct roots_input *input = view->desktop->server->input; + struct wlr_xdg_toplevel_v6_move_event *e = data; + struct roots_seat *seat = input_seat_from_wlr_seat(input, e->seat->seat); + // TODO verify event serial + if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) { + return; + } + roots_seat_begin_move(seat, view); +} + +static void handle_request_resize(struct wl_listener *listener, void *data) { + struct roots_xdg_surface_v6 *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, request_resize); + struct roots_view *view = roots_xdg_surface->view; + struct roots_input *input = view->desktop->server->input; + struct wlr_xdg_toplevel_v6_resize_event *e = data; + // TODO verify event serial + struct roots_seat *seat = input_seat_from_wlr_seat(input, e->seat->seat); + assert(seat); + if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) { + return; + } + roots_seat_begin_resize(seat, view, e->edges); +} + +static void handle_request_maximize(struct wl_listener *listener, void *data) { + struct roots_xdg_surface_v6 *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, request_maximize); + struct roots_view *view = roots_xdg_surface->view; + struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6; + + if (surface->role != WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) { + return; + } + + view_maximize(view, surface->toplevel->client_pending.maximized); +} + +static void handle_request_fullscreen(struct wl_listener *listener, + void *data) { + struct roots_xdg_surface_v6 *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, request_fullscreen); + struct roots_view *view = roots_xdg_surface->view; + struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6; + struct wlr_xdg_toplevel_v6_set_fullscreen_event *e = data; + + if (surface->role != WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL) { + return; + } + + view_set_fullscreen(view, e->fullscreen, e->output); +} + +static void handle_set_title(struct wl_listener *listener, void *data) { + struct roots_xdg_surface_v6 *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, set_title); + + view_set_title(roots_xdg_surface->view, + roots_xdg_surface->view->xdg_surface_v6->toplevel->title); +} + +static void handle_set_app_id(struct wl_listener *listener, void *data) { + struct roots_xdg_surface_v6 *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, set_app_id); + + view_set_app_id(roots_xdg_surface->view, + roots_xdg_surface->view->xdg_surface_v6->toplevel->app_id); +} + +static void handle_surface_commit(struct wl_listener *listener, void *data) { + struct roots_xdg_surface_v6 *roots_surface = + wl_container_of(listener, roots_surface, surface_commit); + struct roots_view *view = roots_surface->view; + struct wlr_xdg_surface_v6 *surface = view->xdg_surface_v6; + + if (!surface->mapped) { + return; + } + + view_apply_damage(view); + + struct wlr_box size; + get_size(view, &size); + view_update_size(view, size.width, size.height); + + uint32_t pending_serial = + roots_surface->pending_move_resize_configure_serial; + if (pending_serial > 0 && pending_serial >= surface->configure_serial) { + double x = view->box.x; + double y = view->box.y; + if (view->pending_move_resize.update_x) { + x = view->pending_move_resize.x + view->pending_move_resize.width - + size.width; + } + if (view->pending_move_resize.update_y) { + y = view->pending_move_resize.y + view->pending_move_resize.height - + size.height; + } + view_update_position(view, x, y); + + if (pending_serial == surface->configure_serial) { + roots_surface->pending_move_resize_configure_serial = 0; + } + } +} + +static void handle_new_popup(struct wl_listener *listener, void *data) { + struct roots_xdg_surface_v6 *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, new_popup); + struct wlr_xdg_popup_v6 *wlr_popup = data; + popup_create(roots_xdg_surface->view, wlr_popup); +} + +static void handle_map(struct wl_listener *listener, void *data) { + struct roots_xdg_surface_v6 *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, map); + struct roots_view *view = roots_xdg_surface->view; + + struct wlr_box box; + get_size(view, &box); + view->box.width = box.width; + view->box.height = box.height; + + view_map(view, view->xdg_surface_v6->surface); + view_setup(view); + + wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle, + view->xdg_surface_v6->toplevel->title ?: "none"); + wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle, + view->xdg_surface_v6->toplevel->app_id ?: "none"); +} + +static void handle_unmap(struct wl_listener *listener, void *data) { + struct roots_xdg_surface_v6 *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, unmap); + view_unmap(roots_xdg_surface->view); +} + +static void handle_destroy(struct wl_listener *listener, void *data) { + struct roots_xdg_surface_v6 *roots_xdg_surface = + wl_container_of(listener, roots_xdg_surface, destroy); + view_destroy(roots_xdg_surface->view); +} + +void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data) { + struct wlr_xdg_surface_v6 *surface = data; + assert(surface->role != WLR_XDG_SURFACE_V6_ROLE_NONE); + + if (surface->role == WLR_XDG_SURFACE_V6_ROLE_POPUP) { + wlr_log(WLR_DEBUG, "new xdg popup"); + return; + } + + struct roots_desktop *desktop = + wl_container_of(listener, desktop, xdg_shell_v6_surface); + + wlr_log(WLR_DEBUG, "new xdg toplevel: title=%s, app_id=%s", + surface->toplevel->title, surface->toplevel->app_id); + wlr_xdg_surface_v6_ping(surface); + + struct roots_xdg_surface_v6 *roots_surface = + calloc(1, sizeof(struct roots_xdg_surface_v6)); + if (!roots_surface) { + return; + } + roots_surface->surface_commit.notify = handle_surface_commit; + wl_signal_add(&surface->surface->events.commit, + &roots_surface->surface_commit); + roots_surface->destroy.notify = handle_destroy; + wl_signal_add(&surface->events.destroy, &roots_surface->destroy); + roots_surface->map.notify = handle_map; + wl_signal_add(&surface->events.map, &roots_surface->map); + roots_surface->unmap.notify = handle_unmap; + wl_signal_add(&surface->events.unmap, &roots_surface->unmap); + roots_surface->request_move.notify = handle_request_move; + wl_signal_add(&surface->toplevel->events.request_move, + &roots_surface->request_move); + roots_surface->request_resize.notify = handle_request_resize; + wl_signal_add(&surface->toplevel->events.request_resize, + &roots_surface->request_resize); + roots_surface->request_maximize.notify = handle_request_maximize; + wl_signal_add(&surface->toplevel->events.request_maximize, + &roots_surface->request_maximize); + roots_surface->request_fullscreen.notify = handle_request_fullscreen; + wl_signal_add(&surface->toplevel->events.request_fullscreen, + &roots_surface->request_fullscreen); + roots_surface->set_title.notify = handle_set_title; + wl_signal_add(&surface->toplevel->events.set_title, &roots_surface->set_title); + roots_surface->set_app_id.notify = handle_set_app_id; + wl_signal_add(&surface->toplevel->events.set_app_id, + &roots_surface->set_app_id); + roots_surface->new_popup.notify = handle_new_popup; + wl_signal_add(&surface->events.new_popup, &roots_surface->new_popup); + + struct roots_view *view = view_create(desktop); + if (!view) { + free(roots_surface); + return; + } + view->type = ROOTS_XDG_SHELL_V6_VIEW; + + view->xdg_surface_v6 = surface; + view->roots_xdg_surface_v6 = roots_surface; + view->activate = activate; + view->resize = resize; + view->move_resize = move_resize; + view->maximize = maximize; + view->set_fullscreen = set_fullscreen; + view->close = close; + view->destroy = destroy; + roots_surface->view = view; + + if (surface->toplevel->client_pending.maximized) { + view_maximize(view, true); + } + if (surface->toplevel->client_pending.fullscreen) { + view_set_fullscreen(view, true, NULL); + } +} diff --git a/rootston/xwayland.c b/rootston/xwayland.c new file mode 100644 index 00000000..f3f962e8 --- /dev/null +++ b/rootston/xwayland.c @@ -0,0 +1,351 @@ +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <wayland-server.h> +#include <wlr/config.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_surface.h> +#include <wlr/util/log.h> +#include <wlr/xwayland.h> +#include "rootston/server.h" +#include "rootston/server.h" + +static void activate(struct roots_view *view, bool active) { + assert(view->type == ROOTS_XWAYLAND_VIEW); + wlr_xwayland_surface_activate(view->xwayland_surface, active); +} + +static void move(struct roots_view *view, double x, double y) { + assert(view->type == ROOTS_XWAYLAND_VIEW); + struct wlr_xwayland_surface *xwayland_surface = view->xwayland_surface; + view_update_position(view, x, y); + wlr_xwayland_surface_configure(xwayland_surface, x, y, + xwayland_surface->width, xwayland_surface->height); +} + +static void apply_size_constraints( + struct wlr_xwayland_surface *xwayland_surface, uint32_t width, + uint32_t height, uint32_t *dest_width, uint32_t *dest_height) { + *dest_width = width; + *dest_height = height; + + struct wlr_xwayland_surface_size_hints *size_hints = + xwayland_surface->size_hints; + if (size_hints != NULL) { + if (width < (uint32_t)size_hints->min_width) { + *dest_width = size_hints->min_width; + } else if (size_hints->max_width > 0 && + width > (uint32_t)size_hints->max_width) { + *dest_width = size_hints->max_width; + } + if (height < (uint32_t)size_hints->min_height) { + *dest_height = size_hints->min_height; + } else if (size_hints->max_height > 0 && + height > (uint32_t)size_hints->max_height) { + *dest_height = size_hints->max_height; + } + } +} + +static void resize(struct roots_view *view, uint32_t width, uint32_t height) { + assert(view->type == ROOTS_XWAYLAND_VIEW); + struct wlr_xwayland_surface *xwayland_surface = view->xwayland_surface; + + uint32_t constrained_width, constrained_height; + apply_size_constraints(xwayland_surface, width, height, &constrained_width, + &constrained_height); + + wlr_xwayland_surface_configure(xwayland_surface, xwayland_surface->x, + xwayland_surface->y, constrained_width, constrained_height); +} + +static void move_resize(struct roots_view *view, double x, double y, + uint32_t width, uint32_t height) { + assert(view->type == ROOTS_XWAYLAND_VIEW); + struct wlr_xwayland_surface *xwayland_surface = view->xwayland_surface; + + bool update_x = x != view->box.x; + bool update_y = y != view->box.y; + + uint32_t constrained_width, constrained_height; + apply_size_constraints(xwayland_surface, width, height, &constrained_width, + &constrained_height); + + if (update_x) { + x = x + width - constrained_width; + } + if (update_y) { + y = y + height - constrained_height; + } + + view->pending_move_resize.update_x = update_x; + view->pending_move_resize.update_y = update_y; + view->pending_move_resize.x = x; + view->pending_move_resize.y = y; + view->pending_move_resize.width = constrained_width; + view->pending_move_resize.height = constrained_height; + + wlr_xwayland_surface_configure(xwayland_surface, x, y, constrained_width, + constrained_height); +} + +static void close(struct roots_view *view) { + assert(view->type == ROOTS_XWAYLAND_VIEW); + wlr_xwayland_surface_close(view->xwayland_surface); +} + +static void maximize(struct roots_view *view, bool maximized) { + assert(view->type == ROOTS_XWAYLAND_VIEW); + + wlr_xwayland_surface_set_maximized(view->xwayland_surface, maximized); +} + +static void set_fullscreen(struct roots_view *view, bool fullscreen) { + assert(view->type == ROOTS_XWAYLAND_VIEW); + + wlr_xwayland_surface_set_fullscreen(view->xwayland_surface, fullscreen); +} + +static void destroy(struct roots_view *view) { + assert(view->type == ROOTS_XWAYLAND_VIEW); + struct roots_xwayland_surface *roots_surface = view->roots_xwayland_surface; + wl_list_remove(&roots_surface->destroy.link); + wl_list_remove(&roots_surface->request_configure.link); + wl_list_remove(&roots_surface->request_move.link); + wl_list_remove(&roots_surface->request_resize.link); + wl_list_remove(&roots_surface->request_maximize.link); + wl_list_remove(&roots_surface->set_title.link); + wl_list_remove(&roots_surface->set_class.link); + wl_list_remove(&roots_surface->map.link); + wl_list_remove(&roots_surface->unmap.link); + free(roots_surface); +} + +static void handle_destroy(struct wl_listener *listener, void *data) { + struct roots_xwayland_surface *roots_surface = + wl_container_of(listener, roots_surface, destroy); + view_destroy(roots_surface->view); +} + +static void handle_request_configure(struct wl_listener *listener, void *data) { + struct roots_xwayland_surface *roots_surface = + wl_container_of(listener, roots_surface, request_configure); + struct wlr_xwayland_surface *xwayland_surface = + roots_surface->view->xwayland_surface; + struct wlr_xwayland_surface_configure_event *event = data; + + view_update_position(roots_surface->view, event->x, event->y); + + wlr_xwayland_surface_configure(xwayland_surface, event->x, event->y, + event->width, event->height); +} + +static struct roots_seat *guess_seat_for_view(struct roots_view *view) { + // the best we can do is to pick the first seat that has the surface focused + // for the pointer + struct roots_input *input = view->desktop->server->input; + struct roots_seat *seat; + wl_list_for_each(seat, &input->seats, link) { + if (seat->seat->pointer_state.focused_surface == view->wlr_surface) { + return seat; + } + } + return NULL; +} + +static void handle_request_move(struct wl_listener *listener, void *data) { + struct roots_xwayland_surface *roots_surface = + wl_container_of(listener, roots_surface, request_move); + struct roots_view *view = roots_surface->view; + struct roots_seat *seat = guess_seat_for_view(view); + + if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) { + return; + } + + roots_seat_begin_move(seat, view); +} + +static void handle_request_resize(struct wl_listener *listener, void *data) { + struct roots_xwayland_surface *roots_surface = + wl_container_of(listener, roots_surface, request_resize); + struct roots_view *view = roots_surface->view; + struct roots_seat *seat = guess_seat_for_view(view); + struct wlr_xwayland_resize_event *e = data; + + if (!seat || seat->cursor->mode != ROOTS_CURSOR_PASSTHROUGH) { + return; + } + roots_seat_begin_resize(seat, view, e->edges); +} + +static void handle_request_maximize(struct wl_listener *listener, void *data) { + struct roots_xwayland_surface *roots_surface = + wl_container_of(listener, roots_surface, request_maximize); + struct roots_view *view = roots_surface->view; + struct wlr_xwayland_surface *xwayland_surface = view->xwayland_surface; + + bool maximized = xwayland_surface->maximized_vert && + xwayland_surface->maximized_horz; + view_maximize(view, maximized); +} + +static void handle_request_fullscreen(struct wl_listener *listener, + void *data) { + struct roots_xwayland_surface *roots_surface = + wl_container_of(listener, roots_surface, request_fullscreen); + struct roots_view *view = roots_surface->view; + struct wlr_xwayland_surface *xwayland_surface = view->xwayland_surface; + + view_set_fullscreen(view, xwayland_surface->fullscreen, NULL); +} + +static void handle_set_title(struct wl_listener *listener, void *data) { + struct roots_xwayland_surface *roots_surface = + wl_container_of(listener, roots_surface, set_title); + + view_set_title(roots_surface->view, + roots_surface->view->xwayland_surface->title); +} + +static void handle_set_class(struct wl_listener *listener, void *data) { + struct roots_xwayland_surface *roots_surface = + wl_container_of(listener, roots_surface, set_class); + + view_set_app_id(roots_surface->view, + roots_surface->view->xwayland_surface->class); +} + +static void handle_surface_commit(struct wl_listener *listener, void *data) { + struct roots_xwayland_surface *roots_surface = + wl_container_of(listener, roots_surface, surface_commit); + struct roots_view *view = roots_surface->view; + struct wlr_surface *wlr_surface = view->wlr_surface; + + view_apply_damage(view); + + int width = wlr_surface->current.width; + int height = wlr_surface->current.height; + view_update_size(view, width, height); + + double x = view->box.x; + double y = view->box.y; + if (view->pending_move_resize.update_x) { + x = view->pending_move_resize.x + view->pending_move_resize.width - + width; + view->pending_move_resize.update_x = false; + } + if (view->pending_move_resize.update_y) { + y = view->pending_move_resize.y + view->pending_move_resize.height - + height; + view->pending_move_resize.update_y = false; + } + view_update_position(view, x, y); +} + +static void handle_map(struct wl_listener *listener, void *data) { + struct roots_xwayland_surface *roots_surface = + wl_container_of(listener, roots_surface, map); + struct wlr_xwayland_surface *surface = data; + struct roots_view *view = roots_surface->view; + + view->box.x = surface->x; + view->box.y = surface->y; + view->box.width = surface->surface->current.width; + view->box.height = surface->surface->current.height; + + roots_surface->surface_commit.notify = handle_surface_commit; + wl_signal_add(&surface->surface->events.commit, + &roots_surface->surface_commit); + + view_map(view, surface->surface); + + if (!surface->override_redirect) { + if (surface->decorations == WLR_XWAYLAND_SURFACE_DECORATIONS_ALL) { + view->decorated = true; + view->border_width = 4; + view->titlebar_height = 12; + } + + view_setup(view); + + wlr_foreign_toplevel_handle_v1_set_title(view->toplevel_handle, + view->xwayland_surface->title ?: "none"); + wlr_foreign_toplevel_handle_v1_set_app_id(view->toplevel_handle, + view->xwayland_surface->class ?: "none"); + } else { + view_initial_focus(view); + } +} + +static void handle_unmap(struct wl_listener *listener, void *data) { + struct roots_xwayland_surface *roots_surface = + wl_container_of(listener, roots_surface, unmap); + struct roots_view *view = roots_surface->view; + + wl_list_remove(&roots_surface->surface_commit.link); + view_unmap(view); +} + +void handle_xwayland_surface(struct wl_listener *listener, void *data) { + struct roots_desktop *desktop = + wl_container_of(listener, desktop, xwayland_surface); + + struct wlr_xwayland_surface *surface = data; + wlr_log(WLR_DEBUG, "new xwayland surface: title=%s, class=%s, instance=%s", + surface->title, surface->class, surface->instance); + wlr_xwayland_surface_ping(surface); + + struct roots_xwayland_surface *roots_surface = + calloc(1, sizeof(struct roots_xwayland_surface)); + if (roots_surface == NULL) { + return; + } + + roots_surface->destroy.notify = handle_destroy; + wl_signal_add(&surface->events.destroy, &roots_surface->destroy); + roots_surface->request_configure.notify = handle_request_configure; + wl_signal_add(&surface->events.request_configure, + &roots_surface->request_configure); + roots_surface->map.notify = handle_map; + wl_signal_add(&surface->events.map, &roots_surface->map); + roots_surface->unmap.notify = handle_unmap; + wl_signal_add(&surface->events.unmap, &roots_surface->unmap); + roots_surface->request_move.notify = handle_request_move; + wl_signal_add(&surface->events.request_move, &roots_surface->request_move); + roots_surface->request_resize.notify = handle_request_resize; + wl_signal_add(&surface->events.request_resize, + &roots_surface->request_resize); + roots_surface->request_maximize.notify = handle_request_maximize; + wl_signal_add(&surface->events.request_maximize, + &roots_surface->request_maximize); + roots_surface->request_fullscreen.notify = handle_request_fullscreen; + wl_signal_add(&surface->events.request_fullscreen, + &roots_surface->request_fullscreen); + roots_surface->set_title.notify = handle_set_title; + wl_signal_add(&surface->events.set_title, &roots_surface->set_title); + roots_surface->set_class.notify = handle_set_class; + wl_signal_add(&surface->events.set_class, + &roots_surface->set_class); + + struct roots_view *view = view_create(desktop); + if (view == NULL) { + free(roots_surface); + return; + } + view->type = ROOTS_XWAYLAND_VIEW; + view->box.x = surface->x; + view->box.y = surface->y; + + view->xwayland_surface = surface; + view->roots_xwayland_surface = roots_surface; + view->activate = activate; + view->resize = resize; + view->move = move; + view->move_resize = move_resize; + view->maximize = maximize; + view->set_fullscreen = set_fullscreen; + view->close = close; + view->destroy = destroy; + roots_surface->view = view; +} diff --git a/types/data_device/wlr_data_device.c b/types/data_device/wlr_data_device.c new file mode 100644 index 00000000..f868ea37 --- /dev/null +++ b/types/data_device/wlr_data_device.c @@ -0,0 +1,284 @@ +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/types/wlr_data_device.h> +#include <wlr/types/wlr_seat.h> +#include <wlr/util/log.h> +#include "types/wlr_data_device.h" +#include "util/signal.h" + +#define DATA_DEVICE_MANAGER_VERSION 3 + +static const struct wl_data_device_interface data_device_impl; + +static struct wlr_seat_client *seat_client_from_data_device_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_data_device_interface, + &data_device_impl)); + return wl_resource_get_user_data(resource); +} + +static void data_device_set_selection(struct wl_client *client, + struct wl_resource *device_resource, + struct wl_resource *source_resource, uint32_t serial) { + struct wlr_seat_client *seat_client = + seat_client_from_data_device_resource(device_resource); + + struct wlr_client_data_source *source = NULL; + if (source_resource != NULL) { + source = client_data_source_from_resource(source_resource); + } + + struct wlr_data_source *wlr_source = + source != NULL ? &source->source : NULL; + wlr_seat_set_selection(seat_client->seat, wlr_source, serial); + + if (source != NULL) { + source->finalized = true; + } +} + +static void data_device_start_drag(struct wl_client *client, + struct wl_resource *device_resource, + struct wl_resource *source_resource, + struct wl_resource *origin_resource, struct wl_resource *icon_resource, + uint32_t serial) { + struct wlr_seat_client *seat_client = + seat_client_from_data_device_resource(device_resource); + struct wlr_surface *origin = wlr_surface_from_resource(origin_resource); + + struct wlr_client_data_source *source = NULL; + if (source_resource != NULL) { + source = client_data_source_from_resource(source_resource); + } + + struct wlr_surface *icon = NULL; + if (icon_resource) { + icon = wlr_surface_from_resource(icon_resource); + if (!wlr_surface_set_role(icon, &drag_icon_surface_role, NULL, + icon_resource, WL_DATA_DEVICE_ERROR_ROLE)) { + return; + } + } + + struct wlr_data_source *wlr_source = + source != NULL ? &source->source : NULL; + if (!seat_client_start_drag(seat_client, wlr_source, icon, + origin, serial)) { + wl_resource_post_no_memory(device_resource); + return; + } + + if (source != NULL) { + source->finalized = true; + } +} + +static void data_device_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_data_device_interface data_device_impl = { + .start_drag = data_device_start_drag, + .set_selection = data_device_set_selection, + .release = data_device_release, +}; + +static void data_device_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + + +void wlr_seat_client_send_selection(struct wlr_seat_client *seat_client) { + struct wlr_data_source *source = seat_client->seat->selection_source; + if (source != NULL) { + source->accepted = false; + } + + struct wl_resource *device_resource; + wl_resource_for_each(device_resource, &seat_client->data_devices) { + if (source != NULL) { + struct wlr_data_offer *offer = + data_source_send_offer(source, device_resource); + if (offer == NULL) { + wl_client_post_no_memory(seat_client->client); + return; + } + + wl_data_device_send_selection(device_resource, offer->resource); + } else { + wl_data_device_send_selection(device_resource, NULL); + } + } +} + +static void seat_client_selection_source_destroy( + struct wl_listener *listener, void *data) { + struct wlr_seat *seat = + wl_container_of(listener, seat, selection_source_destroy); + struct wlr_seat_client *seat_client = seat->keyboard_state.focused_client; + + if (seat_client && seat->keyboard_state.focused_surface) { + struct wl_resource *resource; + wl_resource_for_each(resource, &seat_client->data_devices) { + wl_data_device_send_selection(resource, NULL); + } + } + + seat->selection_source = NULL; + + wlr_signal_emit_safe(&seat->events.selection, seat); +} + +void wlr_seat_set_selection(struct wlr_seat *seat, + struct wlr_data_source *source, uint32_t serial) { + if (seat->selection_source && + seat->selection_serial - serial < UINT32_MAX / 2) { + return; + } + + if (seat->selection_source) { + wl_list_remove(&seat->selection_source_destroy.link); + wlr_data_source_cancel(seat->selection_source); + seat->selection_source = NULL; + } + + seat->selection_source = source; + seat->selection_serial = serial; + + struct wlr_seat_client *focused_client = + seat->keyboard_state.focused_client; + + if (focused_client) { + wlr_seat_client_send_selection(focused_client); + } + + wlr_signal_emit_safe(&seat->events.selection, seat); + + if (source) { + seat->selection_source_destroy.notify = + seat_client_selection_source_destroy; + wl_signal_add(&source->events.destroy, + &seat->selection_source_destroy); + } +} + + +static const struct wl_data_device_manager_interface data_device_manager_impl; + +static struct wlr_data_device_manager *data_device_manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_data_device_manager_interface, + &data_device_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void data_device_manager_get_data_device(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *seat_resource) { + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + + struct wl_resource *resource = wl_resource_create(client, + &wl_data_device_interface, wl_resource_get_version(manager_resource), + id); + if (resource == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_implementation(resource, &data_device_impl, seat_client, + &data_device_handle_resource_destroy); + wl_list_insert(&seat_client->data_devices, wl_resource_get_link(resource)); +} + +static void data_device_manager_create_data_source(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id) { + struct wlr_data_device_manager *manager = + data_device_manager_from_resource(manager_resource); + + client_data_source_create(client, wl_resource_get_version(manager_resource), + id, &manager->data_sources); +} + +static const struct wl_data_device_manager_interface + data_device_manager_impl = { + .create_data_source = data_device_manager_create_data_source, + .get_data_device = data_device_manager_get_data_device, +}; + +static void data_device_manager_handle_resource_destroy( + struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void data_device_manager_bind(struct wl_client *client, + void *data, uint32_t version, uint32_t id) { + struct wlr_data_device_manager *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &wl_data_device_manager_interface, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &data_device_manager_impl, + manager, data_device_manager_handle_resource_destroy); + + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); +} + +void wlr_data_device_manager_destroy(struct wlr_data_device_manager *manager) { + if (!manager) { + return; + } + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &manager->resources) { + wl_resource_destroy(resource); + } + wl_resource_for_each_safe(resource, tmp, &manager->data_sources) { + wl_resource_destroy(resource); + } + free(manager); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_data_device_manager *manager = + wl_container_of(listener, manager, display_destroy); + wlr_data_device_manager_destroy(manager); +} + +struct wlr_data_device_manager *wlr_data_device_manager_create( + struct wl_display *display) { + struct wlr_data_device_manager *manager = + calloc(1, sizeof(struct wlr_data_device_manager)); + if (manager == NULL) { + wlr_log(WLR_ERROR, "could not create data device manager"); + return NULL; + } + + wl_list_init(&manager->resources); + wl_list_init(&manager->data_sources); + wl_signal_init(&manager->events.destroy); + + manager->global = + wl_global_create(display, &wl_data_device_manager_interface, + DATA_DEVICE_MANAGER_VERSION, manager, data_device_manager_bind); + if (!manager->global) { + wlr_log(WLR_ERROR, "could not create data device manager wl_global"); + free(manager); + return NULL; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/data_device/wlr_data_offer.c b/types/data_device/wlr_data_offer.c new file mode 100644 index 00000000..b8cec091 --- /dev/null +++ b/types/data_device/wlr_data_offer.c @@ -0,0 +1,222 @@ +#define _XOPEN_SOURCE 700 +#include <assert.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/types/wlr_data_device.h> +#include <wlr/types/wlr_seat.h> +#include <wlr/util/log.h> +#include "types/wlr_data_device.h" +#include "util/signal.h" + +static const struct wl_data_offer_interface data_offer_impl; + +static struct wlr_data_offer *data_offer_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_data_offer_interface, + &data_offer_impl)); + return wl_resource_get_user_data(resource); +} + +static uint32_t data_offer_choose_action(struct wlr_data_offer *offer) { + uint32_t offer_actions, preferred_action = 0; + if (wl_resource_get_version(offer->resource) >= + WL_DATA_OFFER_ACTION_SINCE_VERSION) { + offer_actions = offer->actions; + preferred_action = offer->preferred_action; + } else { + offer_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + } + + uint32_t source_actions; + if (offer->source->actions >= 0) { + source_actions = offer->source->actions; + } else { + source_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + } + + uint32_t available_actions = offer_actions & source_actions; + if (!available_actions) { + return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + } + + if (offer->source->compositor_action & available_actions) { + return offer->source->compositor_action; + } + + // If the dest side has a preferred DnD action, use it + if ((preferred_action & available_actions) != 0) { + return preferred_action; + } + + // Use the first found action, in bit order + return 1 << (ffs(available_actions) - 1); +} + +void data_offer_update_action(struct wlr_data_offer *offer) { + uint32_t action = data_offer_choose_action(offer); + if (offer->source->current_dnd_action == action) { + return; + } + offer->source->current_dnd_action = action; + + if (offer->in_ask) { + return; + } + + wlr_data_source_dnd_action(offer->source, action); + + if (wl_resource_get_version(offer->resource) >= + WL_DATA_OFFER_ACTION_SINCE_VERSION) { + wl_data_offer_send_action(offer->resource, action); + } +} + +static void data_offer_handle_accept(struct wl_client *client, + struct wl_resource *resource, uint32_t serial, const char *mime_type) { + struct wlr_data_offer *offer = data_offer_from_resource(resource); + if (offer == NULL) { + return; + } + + wlr_data_source_accept(offer->source, serial, mime_type); +} + +static void data_offer_handle_receive(struct wl_client *client, + struct wl_resource *resource, const char *mime_type, int32_t fd) { + struct wlr_data_offer *offer = data_offer_from_resource(resource); + if (offer == NULL) { + close(fd); + return; + } + + wlr_data_source_send(offer->source, mime_type, fd); +} + +static void data_offer_dnd_finish(struct wlr_data_offer *offer) { + struct wlr_data_source *source = offer->source; + if (source->actions < 0) { + return; + } + + if (offer->in_ask) { + wlr_data_source_dnd_action(source, source->current_dnd_action); + } + + wlr_data_source_dnd_finish(source); +} + +static void data_offer_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_data_offer *offer = data_offer_from_resource(resource); + if (offer == NULL) { + goto out; + } + + // If the drag destination has version < 3, wl_data_offer.finish + // won't be called, so do this here as a safety net, because + // we still want the version >= 3 drag source to be happy. + if (wl_resource_get_version(offer->resource) < + WL_DATA_OFFER_ACTION_SINCE_VERSION) { + data_offer_dnd_finish(offer); + } + +out: + wl_resource_destroy(resource); +} + +static void data_offer_handle_finish(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_data_offer *offer = data_offer_from_resource(resource); + if (offer == NULL) { + return; + } + + data_offer_dnd_finish(offer); + data_offer_destroy(offer); +} + +static void data_offer_handle_set_actions(struct wl_client *client, + struct wl_resource *resource, uint32_t actions, + uint32_t preferred_action) { + struct wlr_data_offer *offer = data_offer_from_resource(resource); + if (offer == NULL) { + return; + } + + if (actions & ~DATA_DEVICE_ALL_ACTIONS) { + wl_resource_post_error(offer->resource, + WL_DATA_OFFER_ERROR_INVALID_ACTION_MASK, + "invalid action mask %x", actions); + return; + } + + if (preferred_action && (!(preferred_action & actions) || + __builtin_popcount(preferred_action) > 1)) { + wl_resource_post_error(offer->resource, + WL_DATA_OFFER_ERROR_INVALID_ACTION, + "invalid action %x", preferred_action); + return; + } + + offer->actions = actions; + offer->preferred_action = preferred_action; + + data_offer_update_action(offer); +} + +void data_offer_destroy(struct wlr_data_offer *offer) { + if (offer == NULL) { + return; + } + + wl_list_remove(&offer->source_destroy.link); + + // Make the resource inert + wl_resource_set_user_data(offer->resource, NULL); + free(offer); +} + +static const struct wl_data_offer_interface data_offer_impl = { + .accept = data_offer_handle_accept, + .receive = data_offer_handle_receive, + .destroy = data_offer_handle_destroy, + .finish = data_offer_handle_finish, + .set_actions = data_offer_handle_set_actions, +}; + +static void data_offer_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_data_offer *offer = data_offer_from_resource(resource); + data_offer_destroy(offer); +} + +static void data_offer_handle_source_destroy(struct wl_listener *listener, + void *data) { + struct wlr_data_offer *offer = + wl_container_of(listener, offer, source_destroy); + data_offer_destroy(offer); +} + +struct wlr_data_offer *data_offer_create(struct wl_client *client, + struct wlr_data_source *source, uint32_t version) { + struct wlr_data_offer *offer = calloc(1, sizeof(struct wlr_data_offer)); + if (offer == NULL) { + return NULL; + } + offer->source = source; + + offer->resource = + wl_resource_create(client, &wl_data_offer_interface, version, 0); + if (offer->resource == NULL) { + free(offer); + return NULL; + } + wl_resource_set_implementation(offer->resource, &data_offer_impl, offer, + data_offer_handle_resource_destroy); + + offer->source_destroy.notify = data_offer_handle_source_destroy; + wl_signal_add(&source->events.destroy, &offer->source_destroy); + + return offer; +} diff --git a/types/data_device/wlr_data_source.c b/types/data_device/wlr_data_source.c new file mode 100644 index 00000000..413f461a --- /dev/null +++ b/types/data_device/wlr_data_source.c @@ -0,0 +1,265 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/types/wlr_data_device.h> +#include <wlr/types/wlr_seat.h> +#include <wlr/util/log.h> +#include "types/wlr_data_device.h" +#include "util/signal.h" + +struct wlr_data_offer *data_source_send_offer(struct wlr_data_source *source, + struct wl_resource *device_resource) { + struct wl_client *client = wl_resource_get_client(device_resource); + uint32_t version = wl_resource_get_version(device_resource); + struct wlr_data_offer *offer = data_offer_create(client, source, version); + if (offer == NULL) { + return NULL; + } + + wl_data_device_send_data_offer(device_resource, offer->resource); + + char **p; + wl_array_for_each(p, &source->mime_types) { + wl_data_offer_send_offer(offer->resource, *p); + } + + return offer; +} + +void wlr_data_source_init(struct wlr_data_source *source, + const struct wlr_data_source_impl *impl) { + assert(impl->send); + + source->impl = impl; + wl_array_init(&source->mime_types); + wl_signal_init(&source->events.destroy); + source->actions = -1; +} + +void wlr_data_source_finish(struct wlr_data_source *source) { + if (source == NULL) { + return; + } + + wlr_signal_emit_safe(&source->events.destroy, source); + + char **p; + wl_array_for_each(p, &source->mime_types) { + free(*p); + } + wl_array_release(&source->mime_types); +} + +void wlr_data_source_send(struct wlr_data_source *source, const char *mime_type, + int32_t fd) { + source->impl->send(source, mime_type, fd); +} + +void wlr_data_source_accept(struct wlr_data_source *source, uint32_t serial, + const char *mime_type) { + source->accepted = (mime_type != NULL); + if (source->impl->accept) { + source->impl->accept(source, serial, mime_type); + } +} + +void wlr_data_source_cancel(struct wlr_data_source *source) { + if (source->impl->cancel) { + source->impl->cancel(source); + } +} + +void wlr_data_source_dnd_drop(struct wlr_data_source *source) { + if (source->impl->dnd_drop) { + source->impl->dnd_drop(source); + } +} + +void wlr_data_source_dnd_finish(struct wlr_data_source *source) { + if (source->impl->dnd_finish) { + source->impl->dnd_finish(source); + } +} + +void wlr_data_source_dnd_action(struct wlr_data_source *source, + enum wl_data_device_manager_dnd_action action) { + source->current_dnd_action = action; + if (source->impl->dnd_action) { + source->impl->dnd_action(source, action); + } +} + + +static const struct wl_data_source_interface data_source_impl; + +struct wlr_client_data_source *client_data_source_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_data_source_interface, + &data_source_impl)); + return wl_resource_get_user_data(resource); +} + +static void client_data_source_accept(struct wlr_data_source *wlr_source, + uint32_t serial, const char *mime_type); + +static struct wlr_client_data_source *client_data_source_from_wlr_data_source( + struct wlr_data_source *wlr_source) { + assert(wlr_source->impl->accept == client_data_source_accept); + return (struct wlr_client_data_source *)wlr_source; +} + +static void client_data_source_accept(struct wlr_data_source *wlr_source, + uint32_t serial, const char *mime_type) { + struct wlr_client_data_source *source = + client_data_source_from_wlr_data_source(wlr_source); + wl_data_source_send_target(source->resource, mime_type); +} + +static void client_data_source_send(struct wlr_data_source *wlr_source, + const char *mime_type, int32_t fd) { + struct wlr_client_data_source *source = + client_data_source_from_wlr_data_source(wlr_source); + wl_data_source_send_send(source->resource, mime_type, fd); + close(fd); +} + +static void client_data_source_cancel(struct wlr_data_source *wlr_source) { + struct wlr_client_data_source *source = + client_data_source_from_wlr_data_source(wlr_source); + wl_data_source_send_cancelled(source->resource); +} + +static void client_data_source_dnd_drop(struct wlr_data_source *wlr_source) { + struct wlr_client_data_source *source = + client_data_source_from_wlr_data_source(wlr_source); + assert(wl_resource_get_version(source->resource) >= + WL_DATA_SOURCE_DND_DROP_PERFORMED_SINCE_VERSION); + wl_data_source_send_dnd_drop_performed(source->resource); +} + +static void client_data_source_dnd_finish(struct wlr_data_source *wlr_source) { + struct wlr_client_data_source *source = + client_data_source_from_wlr_data_source(wlr_source); + assert(wl_resource_get_version(source->resource) >= + WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION); + wl_data_source_send_dnd_finished(source->resource); +} + +static void client_data_source_dnd_action(struct wlr_data_source *wlr_source, + enum wl_data_device_manager_dnd_action action) { + struct wlr_client_data_source *source = + client_data_source_from_wlr_data_source(wlr_source); + assert(wl_resource_get_version(source->resource) >= + WL_DATA_SOURCE_ACTION_SINCE_VERSION); + wl_data_source_send_action(source->resource, action); +} + +static void data_source_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void data_source_set_actions(struct wl_client *client, + struct wl_resource *resource, uint32_t dnd_actions) { + struct wlr_client_data_source *source = + client_data_source_from_resource(resource); + + if (source->source.actions >= 0) { + wl_resource_post_error(source->resource, + WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK, + "cannot set actions more than once"); + return; + } + + if (dnd_actions & ~DATA_DEVICE_ALL_ACTIONS) { + wl_resource_post_error(source->resource, + WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK, + "invalid action mask %x", dnd_actions); + return; + } + + if (source->finalized) { + wl_resource_post_error(source->resource, + WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK, + "invalid action change after wl_data_device.start_drag"); + return; + } + + source->source.actions = dnd_actions; +} + +static void data_source_offer(struct wl_client *client, + struct wl_resource *resource, const char *mime_type) { + struct wlr_client_data_source *source = + client_data_source_from_resource(resource); + + char **p = wl_array_add(&source->source.mime_types, sizeof(*p)); + if (p) { + *p = strdup(mime_type); + } + if (!p || !*p) { + if (p) { + source->source.mime_types.size -= sizeof(*p); + } + wl_resource_post_no_memory(resource); + } +} + +static const struct wl_data_source_interface data_source_impl = { + .offer = data_source_offer, + .destroy = data_source_destroy, + .set_actions = data_source_set_actions, +}; + +static void data_source_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_client_data_source *source = + client_data_source_from_resource(resource); + wlr_data_source_finish(&source->source); + wl_list_remove(wl_resource_get_link(source->resource)); + free(source); +} + +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 *source = + calloc(1, sizeof(struct wlr_client_data_source)); + if (source == NULL) { + return NULL; + } + + source->resource = wl_resource_create(client, &wl_data_source_interface, + version, id); + if (source->resource == NULL) { + wl_resource_post_no_memory(source->resource); + free(source); + return NULL; + } + wl_resource_set_implementation(source->resource, &data_source_impl, + source, data_source_handle_resource_destroy); + wl_list_insert(resource_list, wl_resource_get_link(source->resource)); + + source->impl.accept = client_data_source_accept; + source->impl.send = client_data_source_send; + source->impl.cancel = client_data_source_cancel; + + if (wl_resource_get_version(source->resource) >= + WL_DATA_SOURCE_DND_DROP_PERFORMED_SINCE_VERSION) { + source->impl.dnd_drop = client_data_source_dnd_drop; + } + if (wl_resource_get_version(source->resource) >= + WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION) { + source->impl.dnd_finish = client_data_source_dnd_finish; + } + if (wl_resource_get_version(source->resource) >= + WL_DATA_SOURCE_ACTION_SINCE_VERSION) { + source->impl.dnd_action = client_data_source_dnd_action; + } + + wlr_data_source_init(&source->source, &source->impl); + return source; +} diff --git a/types/data_device/wlr_drag.c b/types/data_device/wlr_drag.c new file mode 100644 index 00000000..8e737597 --- /dev/null +++ b/types/data_device/wlr_drag.c @@ -0,0 +1,490 @@ +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/types/wlr_data_device.h> +#include <wlr/types/wlr_seat.h> +#include <wlr/util/log.h> +#include "types/wlr_data_device.h" +#include "util/signal.h" + +static void drag_handle_seat_client_destroy(struct wl_listener *listener, + void *data) { + struct wlr_drag *drag = + wl_container_of(listener, drag, seat_client_destroy); + + drag->focus_client = NULL; + wl_list_remove(&drag->seat_client_destroy.link); +} + +static void drag_set_focus(struct wlr_drag *drag, + struct wlr_surface *surface, double sx, double sy) { + if (drag->focus == surface) { + return; + } + + if (drag->focus_client) { + wl_list_remove(&drag->seat_client_destroy.link); + + struct wl_resource *resource; + wl_resource_for_each(resource, &drag->focus_client->data_devices) { + wl_data_device_send_leave(resource); + } + + drag->focus_client = NULL; + drag->focus = NULL; + } + + if (!surface || !surface->resource) { + return; + } + + if (!drag->source && + wl_resource_get_client(surface->resource) != + drag->seat_client->client) { + return; + } + + struct wlr_seat_client *focus_client = wlr_seat_client_for_wl_client( + drag->seat_client->seat, wl_resource_get_client(surface->resource)); + if (!focus_client) { + return; + } + + if (drag->source != NULL) { + drag->source->accepted = false; + + uint32_t serial = + wl_display_next_serial(drag->seat_client->seat->display); + + struct wl_resource *device_resource; + wl_resource_for_each(device_resource, &focus_client->data_devices) { + struct wlr_data_offer *offer = + data_source_send_offer(drag->source, device_resource); + if (offer == NULL) { + wl_resource_post_no_memory(device_resource); + return; + } + + data_offer_update_action(offer); + + if (wl_resource_get_version(offer->resource) >= + WL_DATA_OFFER_SOURCE_ACTIONS_SINCE_VERSION) { + wl_data_offer_send_source_actions(offer->resource, + drag->source->actions); + } + + wl_data_device_send_enter(device_resource, serial, + surface->resource, + wl_fixed_from_double(sx), wl_fixed_from_double(sy), + offer->resource); + } + } + + drag->focus = surface; + drag->focus_client = focus_client; + drag->seat_client_destroy.notify = drag_handle_seat_client_destroy; + wl_signal_add(&focus_client->events.destroy, &drag->seat_client_destroy); + + wlr_signal_emit_safe(&drag->events.focus, drag); +} + +static void drag_icon_set_mapped(struct wlr_drag_icon *icon, bool mapped) { + if (mapped && !icon->mapped) { + icon->mapped = true; + wlr_signal_emit_safe(&icon->events.map, icon); + } else if (!mapped && icon->mapped) { + icon->mapped = false; + wlr_signal_emit_safe(&icon->events.unmap, icon); + } +} + +static void drag_end(struct wlr_drag *drag) { + if (!drag->cancelling) { + drag->cancelling = true; + if (drag->is_pointer_grab) { + wlr_seat_pointer_end_grab(drag->seat); + } else { + wlr_seat_touch_end_grab(drag->seat); + } + wlr_seat_keyboard_end_grab(drag->seat); + + if (drag->source) { + wl_list_remove(&drag->source_destroy.link); + } + + drag_set_focus(drag, NULL, 0, 0); + + if (drag->icon) { + wl_list_remove(&drag->icon_destroy.link); + drag_icon_set_mapped(drag->icon, false); + } + + wlr_signal_emit_safe(&drag->events.destroy, drag); + free(drag); + } +} + +static void drag_handle_pointer_enter(struct wlr_seat_pointer_grab *grab, + struct wlr_surface *surface, double sx, double sy) { + struct wlr_drag *drag = grab->data; + drag_set_focus(drag, surface, sx, sy); +} + +static void drag_handle_pointer_motion(struct wlr_seat_pointer_grab *grab, + uint32_t time, double sx, double sy) { + struct wlr_drag *drag = grab->data; + if (drag->focus != NULL && drag->focus_client != NULL) { + struct wl_resource *resource; + wl_resource_for_each(resource, &drag->focus_client->data_devices) { + wl_data_device_send_motion(resource, time, wl_fixed_from_double(sx), + wl_fixed_from_double(sy)); + } + + struct wlr_drag_motion_event event = { + .drag = drag, + .time = time, + .sx = sx, + .sy = sy, + }; + wlr_signal_emit_safe(&drag->events.motion, &event); + } +} + +static uint32_t drag_handle_pointer_button(struct wlr_seat_pointer_grab *grab, + uint32_t time, uint32_t button, uint32_t state) { + struct wlr_drag *drag = grab->data; + + if (drag->source && + grab->seat->pointer_state.grab_button == button && + state == WL_POINTER_BUTTON_STATE_RELEASED) { + if (drag->focus_client && drag->source->current_dnd_action && + drag->source->accepted) { + struct wl_resource *resource; + wl_resource_for_each(resource, &drag->focus_client->data_devices) { + wl_data_device_send_drop(resource); + } + wlr_data_source_dnd_drop(drag->source); + + struct wlr_drag_drop_event event = { + .drag = drag, + .time = time, + }; + wlr_signal_emit_safe(&drag->events.drop, &event); + } else if (drag->source->impl->dnd_finish) { + wlr_data_source_cancel(drag->source); + } + } + + if (grab->seat->pointer_state.button_count == 0 && + state == WL_POINTER_BUTTON_STATE_RELEASED) { + drag_end(drag); + } + + return 0; +} + +static void drag_handle_pointer_axis(struct wlr_seat_pointer_grab *grab, + uint32_t time, enum wlr_axis_orientation orientation, double value, + int32_t value_discrete, enum wlr_axis_source source) { + // This space is intentionally left blank +} + +static void drag_handle_pointer_cancel(struct wlr_seat_pointer_grab *grab) { + struct wlr_drag *drag = grab->data; + drag_end(drag); +} + +static const struct wlr_pointer_grab_interface + data_device_pointer_drag_interface = { + .enter = drag_handle_pointer_enter, + .motion = drag_handle_pointer_motion, + .button = drag_handle_pointer_button, + .axis = drag_handle_pointer_axis, + .cancel = drag_handle_pointer_cancel, +}; + +uint32_t drag_handle_touch_down(struct wlr_seat_touch_grab *grab, + uint32_t time, struct wlr_touch_point *point) { + // eat the event + return 0; +} + +static void drag_handle_touch_up(struct wlr_seat_touch_grab *grab, + uint32_t time, struct wlr_touch_point *point) { + struct wlr_drag *drag = grab->data; + if (drag->grab_touch_id != point->touch_id) { + return; + } + + if (drag->focus_client) { + struct wl_resource *resource; + wl_resource_for_each(resource, &drag->focus_client->data_devices) { + wl_data_device_send_drop(resource); + } + } + + drag_end(drag); +} + +static void drag_handle_touch_motion(struct wlr_seat_touch_grab *grab, + uint32_t time, struct wlr_touch_point *point) { + struct wlr_drag *drag = grab->data; + if (drag->focus && drag->focus_client) { + struct wl_resource *resource; + wl_resource_for_each(resource, &drag->focus_client->data_devices) { + wl_data_device_send_motion(resource, time, + wl_fixed_from_double(point->sx), + wl_fixed_from_double(point->sy)); + } + } +} + +static void drag_handle_touch_enter(struct wlr_seat_touch_grab *grab, + uint32_t time, struct wlr_touch_point *point) { + struct wlr_drag *drag = grab->data; + drag_set_focus(drag, point->focus_surface, point->sx, point->sy); +} + +static void drag_handle_touch_cancel(struct wlr_seat_touch_grab *grab) { + struct wlr_drag *drag = grab->data; + drag_end(drag); +} + +static const struct wlr_touch_grab_interface + data_device_touch_drag_interface = { + .down = drag_handle_touch_down, + .up = drag_handle_touch_up, + .motion = drag_handle_touch_motion, + .enter = drag_handle_touch_enter, + .cancel = drag_handle_touch_cancel, +}; + +static void drag_handle_keyboard_enter(struct wlr_seat_keyboard_grab *grab, + struct wlr_surface *surface, uint32_t keycodes[], size_t num_keycodes, + struct wlr_keyboard_modifiers *modifiers) { + // nothing has keyboard focus during drags +} + +static void drag_handle_keyboard_key(struct wlr_seat_keyboard_grab *grab, + uint32_t time, uint32_t key, uint32_t state) { + // no keyboard input during drags +} + +static void drag_handle_keyboard_modifiers(struct wlr_seat_keyboard_grab *grab, + struct wlr_keyboard_modifiers *modifiers) { + //struct wlr_keyboard *keyboard = grab->seat->keyboard_state.keyboard; + // TODO change the dnd action based on what modifier is pressed on the + // keyboard +} + +static void drag_handle_keyboard_cancel(struct wlr_seat_keyboard_grab *grab) { + struct wlr_drag *drag = grab->data; + drag_end(drag); +} + +static const struct wlr_keyboard_grab_interface + data_device_keyboard_drag_interface = { + .enter = drag_handle_keyboard_enter, + .key = drag_handle_keyboard_key, + .modifiers = drag_handle_keyboard_modifiers, + .cancel = drag_handle_keyboard_cancel, +}; + +static void drag_handle_icon_destroy(struct wl_listener *listener, void *data) { + struct wlr_drag *drag = wl_container_of(listener, drag, icon_destroy); + drag->icon = NULL; +} + +static void drag_handle_drag_source_destroy(struct wl_listener *listener, + void *data) { + struct wlr_drag *drag = wl_container_of(listener, drag, source_destroy); + drag_end(drag); +} + + +static void drag_icon_destroy(struct wlr_drag_icon *icon) { + if (icon == NULL) { + return; + } + drag_icon_set_mapped(icon, false); + wlr_signal_emit_safe(&icon->events.destroy, icon); + icon->surface->role_data = NULL; + wl_list_remove(&icon->surface_destroy.link); + wl_list_remove(&icon->seat_client_destroy.link); + wl_list_remove(&icon->link); + free(icon); +} + +static void drag_icon_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_drag_icon *icon = + wl_container_of(listener, icon, surface_destroy); + drag_icon_destroy(icon); +} + +static void drag_icon_surface_role_commit(struct wlr_surface *surface) { + assert(surface->role == &drag_icon_surface_role); + struct wlr_drag_icon *icon = surface->role_data; + if (icon == NULL) { + return; + } + + drag_icon_set_mapped(icon, wlr_surface_has_buffer(surface)); +} + +const struct wlr_surface_role drag_icon_surface_role = { + .name = "wl_data_device-icon", + .commit = drag_icon_surface_role_commit, +}; + +static void drag_icon_handle_seat_client_destroy(struct wl_listener *listener, + void *data) { + struct wlr_drag_icon *icon = + wl_container_of(listener, icon, seat_client_destroy); + + drag_icon_destroy(icon); +} + +static struct wlr_drag_icon *drag_icon_create( + struct wlr_surface *icon_surface, struct wlr_seat_client *client, + bool is_pointer, int32_t touch_id) { + struct wlr_drag_icon *icon = calloc(1, sizeof(struct wlr_drag_icon)); + if (!icon) { + return NULL; + } + + icon->surface = icon_surface; + icon->client = client; + icon->is_pointer = is_pointer; + icon->touch_id = touch_id; + + wl_signal_init(&icon->events.map); + wl_signal_init(&icon->events.unmap); + wl_signal_init(&icon->events.destroy); + + wl_signal_add(&icon->surface->events.destroy, &icon->surface_destroy); + icon->surface_destroy.notify = drag_icon_handle_surface_destroy; + + wl_signal_add(&client->events.destroy, &icon->seat_client_destroy); + icon->seat_client_destroy.notify = drag_icon_handle_seat_client_destroy; + + icon->surface->role_data = icon; + + wl_list_insert(&client->seat->drag_icons, &icon->link); + wlr_signal_emit_safe(&client->seat->events.new_drag_icon, icon); + + if (wlr_surface_has_buffer(icon_surface)) { + drag_icon_set_mapped(icon, true); + } + + return icon; +} + + +static void seat_handle_drag_source_destroy(struct wl_listener *listener, + void *data) { + struct wlr_seat *seat = + wl_container_of(listener, seat, drag_source_destroy); + wl_list_remove(&seat->drag_source_destroy.link); + seat->drag_source = NULL; +} + +bool seat_client_start_drag(struct wlr_seat_client *client, + struct wlr_data_source *source, struct wlr_surface *icon_surface, + struct wlr_surface *origin, uint32_t serial) { + struct wlr_drag *drag = calloc(1, sizeof(struct wlr_drag)); + if (drag == NULL) { + return false; + } + + wl_signal_init(&drag->events.focus); + wl_signal_init(&drag->events.motion); + wl_signal_init(&drag->events.drop); + wl_signal_init(&drag->events.destroy); + + struct wlr_seat *seat = client->seat; + drag->seat = seat; + + drag->is_pointer_grab = !wl_list_empty(&client->pointers) && + seat->pointer_state.button_count == 1 && + seat->pointer_state.grab_serial == serial && + seat->pointer_state.focused_surface && + seat->pointer_state.focused_surface == origin; + + bool is_touch_grab = !wl_list_empty(&client->touches) && + wlr_seat_touch_num_points(seat) == 1 && + seat->touch_state.grab_serial == serial; + + // set in the iteration + struct wlr_touch_point *point = NULL; + if (is_touch_grab) { + wl_list_for_each(point, &seat->touch_state.touch_points, link) { + is_touch_grab = point->surface && point->surface == origin; + break; + } + } + + if (!drag->is_pointer_grab && !is_touch_grab) { + free(drag); + return true; + } + + if (icon_surface) { + int32_t touch_id = (point ? point->touch_id : 0); + struct wlr_drag_icon *icon = + drag_icon_create(icon_surface, client, drag->is_pointer_grab, + touch_id); + if (!icon) { + free(drag); + return false; + } + + drag->icon = icon; + drag->icon_destroy.notify = drag_handle_icon_destroy; + wl_signal_add(&icon->events.destroy, &drag->icon_destroy); + } + + drag->source = source; + if (source != NULL) { + drag->source_destroy.notify = drag_handle_drag_source_destroy; + wl_signal_add(&source->events.destroy, &drag->source_destroy); + } + + drag->seat_client = client; + drag->pointer_grab.data = drag; + drag->pointer_grab.interface = &data_device_pointer_drag_interface; + + drag->touch_grab.data = drag; + drag->touch_grab.interface = &data_device_touch_drag_interface; + drag->grab_touch_id = seat->touch_state.grab_id; + + drag->keyboard_grab.data = drag; + drag->keyboard_grab.interface = &data_device_keyboard_drag_interface; + + wlr_seat_keyboard_start_grab(seat, &drag->keyboard_grab); + + if (drag->is_pointer_grab) { + wlr_seat_pointer_clear_focus(seat); + wlr_seat_pointer_start_grab(seat, &drag->pointer_grab); + } else { + assert(point); + wlr_seat_touch_start_grab(seat, &drag->touch_grab); + drag_set_focus(drag, point->surface, point->sx, point->sy); + } + + seat->drag = drag; // TODO: unset this thing somewhere + seat->drag_serial = serial; + + seat->drag_source = source; + if (source != NULL) { + seat->drag_source_destroy.notify = seat_handle_drag_source_destroy; + wl_signal_add(&source->events.destroy, &seat->drag_source_destroy); + } + + wlr_signal_emit_safe(&seat->events.start_drag, drag); + + return true; +} diff --git a/types/meson.build b/types/meson.build new file mode 100644 index 00000000..1813b144 --- /dev/null +++ b/types/meson.build @@ -0,0 +1,70 @@ +lib_wlr_types = static_library( + 'wlr_types', + files( + 'data_device/wlr_data_device.c', + 'data_device/wlr_data_offer.c', + 'data_device/wlr_data_source.c', + 'data_device/wlr_drag.c', + 'seat/wlr_seat_keyboard.c', + 'seat/wlr_seat_pointer.c', + 'seat/wlr_seat_touch.c', + 'seat/wlr_seat.c', + 'tablet_v2/wlr_tablet_v2_pad.c', + 'tablet_v2/wlr_tablet_v2_tablet.c', + 'tablet_v2/wlr_tablet_v2_tool.c', + 'tablet_v2/wlr_tablet_v2.c', + 'xdg_shell_v6/wlr_xdg_popup_v6.c', + 'xdg_shell_v6/wlr_xdg_positioner_v6.c', + 'xdg_shell_v6/wlr_xdg_shell_v6.c', + 'xdg_shell_v6/wlr_xdg_surface_v6.c', + 'xdg_shell_v6/wlr_xdg_toplevel_v6.c', + 'xdg_shell/wlr_xdg_popup.c', + 'xdg_shell/wlr_xdg_positioner.c', + 'xdg_shell/wlr_xdg_shell.c', + 'xdg_shell/wlr_xdg_surface.c', + 'xdg_shell/wlr_xdg_toplevel.c', + 'wlr_box.c', + 'wlr_buffer.c', + 'wlr_compositor.c', + 'wlr_cursor.c', + 'wlr_export_dmabuf_v1.c', + 'wlr_foreign_toplevel_management_v1.c', + 'wlr_gamma_control_v1.c', + 'wlr_gamma_control.c', + 'wlr_gtk_primary_selection.c', + 'wlr_idle_inhibit_v1.c', + 'wlr_idle.c', + 'wlr_input_device.c', + 'wlr_input_inhibitor.c', + 'wlr_input_method_v2.c', + 'wlr_keyboard.c', + 'wlr_layer_shell_v1.c', + 'wlr_linux_dmabuf_v1.c', + 'wlr_list.c', + 'wlr_matrix.c', + 'wlr_output_damage.c', + 'wlr_output_layout.c', + 'wlr_output.c', + 'wlr_pointer_constraints_v1.c', + 'wlr_pointer.c', + 'wlr_presentation_time.c', + 'wlr_primary_selection.c', + 'wlr_region.c', + 'wlr_screencopy_v1.c', + 'wlr_screenshooter.c', + 'wlr_server_decoration.c', + 'wlr_surface.c', + 'wlr_switch.c', + 'wlr_tablet_pad.c', + 'wlr_tablet_tool.c', + 'wlr_text_input_v3.c', + 'wlr_touch.c', + 'wlr_virtual_keyboard_v1.c', + 'wlr_wl_shell.c', + 'wlr_xcursor_manager.c', + 'wlr_xdg_decoration_v1.c', + 'wlr_xdg_output_v1.c', + ), + include_directories: wlr_inc, + dependencies: [pixman, xkbcommon, wayland_server, wlr_protos, libinput], +) diff --git a/types/seat/wlr_seat.c b/types/seat/wlr_seat.c new file mode 100644 index 00000000..8b1d67fd --- /dev/null +++ b/types/seat/wlr_seat.c @@ -0,0 +1,364 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <wayland-server.h> +#include <wlr/types/wlr_data_device.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_primary_selection.h> +#include <wlr/types/wlr_seat.h> +#include <wlr/util/log.h> +#include "types/wlr_seat.h" +#include "util/signal.h" + +#define SEAT_VERSION 6 + +static void seat_handle_get_pointer(struct wl_client *client, + struct wl_resource *seat_resource, uint32_t id) { + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + if (!(seat_client->seat->capabilities & WL_SEAT_CAPABILITY_POINTER)) { + wlr_log(WLR_ERROR, "Client sent get_pointer on seat without the " + "pointer capability"); + return; + } + + uint32_t version = wl_resource_get_version(seat_resource); + seat_client_create_pointer(seat_client, version, id); +} + +static void seat_handle_get_keyboard(struct wl_client *client, + struct wl_resource *seat_resource, uint32_t id) { + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + if (!(seat_client->seat->capabilities & WL_SEAT_CAPABILITY_KEYBOARD)) { + wlr_log(WLR_ERROR, "Client sent get_keyboard on seat without the " + "keyboard capability"); + return; + } + + uint32_t version = wl_resource_get_version(seat_resource); + seat_client_create_keyboard(seat_client, version, id); +} + +static void seat_handle_get_touch(struct wl_client *client, + struct wl_resource *seat_resource, uint32_t id) { + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + if (!(seat_client->seat->capabilities & WL_SEAT_CAPABILITY_TOUCH)) { + wlr_log(WLR_ERROR, "Client sent get_touch on seat without the " + "touch capability"); + return; + } + + uint32_t version = wl_resource_get_version(seat_resource); + seat_client_create_touch(seat_client, version, id); +} + +static void seat_client_handle_resource_destroy( + struct wl_resource *seat_resource) { + struct wlr_seat_client *client = + wlr_seat_client_from_resource(seat_resource); + + wl_list_remove(wl_resource_get_link(seat_resource)); + if (!wl_list_empty(&client->resources)) { + return; + } + + wlr_signal_emit_safe(&client->events.destroy, client); + + if (client == client->seat->pointer_state.focused_client) { + client->seat->pointer_state.focused_client = NULL; + } + if (client == client->seat->keyboard_state.focused_client) { + client->seat->keyboard_state.focused_client = NULL; + } + + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &client->pointers) { + wl_resource_destroy(resource); + } + wl_resource_for_each_safe(resource, tmp, &client->keyboards) { + wl_resource_destroy(resource); + } + wl_resource_for_each_safe(resource, tmp, &client->touches) { + wl_resource_destroy(resource); + } + wl_resource_for_each_safe(resource, tmp, &client->data_devices) { + wl_resource_destroy(resource); + } + + wl_list_remove(&client->link); + free(client); +} + +static void seat_handle_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_seat_interface seat_impl = { + .get_pointer = seat_handle_get_pointer, + .get_keyboard = seat_handle_get_keyboard, + .get_touch = seat_handle_get_touch, + .release = seat_handle_release, +}; + +static void seat_handle_bind(struct wl_client *client, void *_wlr_seat, + uint32_t version, uint32_t id) { + struct wlr_seat *wlr_seat = _wlr_seat; + assert(client && wlr_seat); + + struct wl_resource *wl_resource = + wl_resource_create(client, &wl_seat_interface, version, id); + if (wl_resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + struct wlr_seat_client *seat_client = + wlr_seat_client_for_wl_client(wlr_seat, client); + if (seat_client == NULL) { + seat_client = calloc(1, sizeof(struct wlr_seat_client)); + if (seat_client == NULL) { + wl_resource_destroy(wl_resource); + wl_client_post_no_memory(client); + return; + } + + seat_client->client = client; + seat_client->seat = wlr_seat; + wl_list_init(&seat_client->resources); + wl_list_init(&seat_client->pointers); + wl_list_init(&seat_client->keyboards); + wl_list_init(&seat_client->touches); + wl_list_init(&seat_client->data_devices); + wl_signal_init(&seat_client->events.destroy); + + wl_list_insert(&wlr_seat->clients, &seat_client->link); + } + + wl_resource_set_implementation(wl_resource, &seat_impl, + seat_client, seat_client_handle_resource_destroy); + wl_list_insert(&seat_client->resources, wl_resource_get_link(wl_resource)); + if (version >= WL_SEAT_NAME_SINCE_VERSION) { + wl_seat_send_name(wl_resource, wlr_seat->name); + } + wl_seat_send_capabilities(wl_resource, wlr_seat->capabilities); +} + +void wlr_seat_destroy(struct wlr_seat *seat) { + if (!seat) { + return; + } + + wlr_signal_emit_safe(&seat->events.destroy, seat); + + wl_list_remove(&seat->display_destroy.link); + + if (seat->selection_source) { + wl_list_remove(&seat->selection_source_destroy.link); + wlr_data_source_cancel(seat->selection_source); + seat->selection_source = NULL; + } + + wlr_seat_set_primary_selection(seat, NULL); + + struct wlr_seat_client *client, *tmp; + wl_list_for_each_safe(client, tmp, &seat->clients, link) { + struct wl_resource *resource, *next; + /* wl_resource_for_each_safe isn't safe to use here, because the last + * wl_resource_destroy will also destroy the head we cannot do the last + * 'next' update that usually is harmless here. + * Work around this by breaking one step ahead + */ + wl_resource_for_each_safe(resource, next, &client->resources) { + // will destroy other resources as well + wl_resource_destroy(resource); + if (wl_resource_get_link(next) == &client->resources) { + break; + } + } + } + + wl_global_destroy(seat->global); + free(seat->pointer_state.default_grab); + free(seat->keyboard_state.default_grab); + free(seat->touch_state.default_grab); + free(seat->name); + free(seat); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_seat *seat = + wl_container_of(listener, seat, display_destroy); + wlr_seat_destroy(seat); +} + +struct wlr_seat *wlr_seat_create(struct wl_display *display, const char *name) { + struct wlr_seat *seat = calloc(1, sizeof(struct wlr_seat)); + if (!seat) { + return NULL; + } + + // pointer state + seat->pointer_state.seat = seat; + wl_list_init(&seat->pointer_state.surface_destroy.link); + + struct wlr_seat_pointer_grab *pointer_grab = + calloc(1, sizeof(struct wlr_seat_pointer_grab)); + if (!pointer_grab) { + free(seat); + return NULL; + } + pointer_grab->interface = &default_pointer_grab_impl; + pointer_grab->seat = seat; + seat->pointer_state.default_grab = pointer_grab; + seat->pointer_state.grab = pointer_grab; + + wl_signal_init(&seat->pointer_state.events.focus_change); + + // keyboard state + struct wlr_seat_keyboard_grab *keyboard_grab = + calloc(1, sizeof(struct wlr_seat_keyboard_grab)); + if (!keyboard_grab) { + free(pointer_grab); + free(seat); + return NULL; + } + keyboard_grab->interface = &default_keyboard_grab_impl; + keyboard_grab->seat = seat; + seat->keyboard_state.default_grab = keyboard_grab; + seat->keyboard_state.grab = keyboard_grab; + + seat->keyboard_state.seat = seat; + wl_list_init(&seat->keyboard_state.surface_destroy.link); + + wl_signal_init(&seat->keyboard_state.events.focus_change); + + // touch state + struct wlr_seat_touch_grab *touch_grab = + calloc(1, sizeof(struct wlr_seat_touch_grab)); + if (!touch_grab) { + free(pointer_grab); + free(keyboard_grab); + free(seat); + return NULL; + } + touch_grab->interface = &default_touch_grab_impl; + touch_grab->seat = seat; + seat->touch_state.default_grab = touch_grab; + seat->touch_state.grab = touch_grab; + + seat->touch_state.seat = seat; + wl_list_init(&seat->touch_state.touch_points); + + seat->global = wl_global_create(display, &wl_seat_interface, + SEAT_VERSION, seat, seat_handle_bind); + if (seat->global == NULL) { + free(touch_grab); + free(pointer_grab); + free(keyboard_grab); + free(seat); + return NULL; + } + seat->display = display; + seat->name = strdup(name); + wl_list_init(&seat->clients); + wl_list_init(&seat->drag_icons); + + wl_signal_init(&seat->events.start_drag); + wl_signal_init(&seat->events.new_drag_icon); + + wl_signal_init(&seat->events.request_set_cursor); + + wl_signal_init(&seat->events.selection); + wl_signal_init(&seat->events.primary_selection); + + wl_signal_init(&seat->events.pointer_grab_begin); + wl_signal_init(&seat->events.pointer_grab_end); + + wl_signal_init(&seat->events.keyboard_grab_begin); + wl_signal_init(&seat->events.keyboard_grab_end); + + wl_signal_init(&seat->events.touch_grab_begin); + wl_signal_init(&seat->events.touch_grab_end); + + wl_signal_init(&seat->events.destroy); + + seat->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &seat->display_destroy); + + return seat; +} + +struct wlr_seat_client *wlr_seat_client_for_wl_client(struct wlr_seat *wlr_seat, + struct wl_client *wl_client) { + struct wlr_seat_client *seat_client; + wl_list_for_each(seat_client, &wlr_seat->clients, link) { + if (seat_client->client == wl_client) { + return seat_client; + } + } + return NULL; +} + +void wlr_seat_set_capabilities(struct wlr_seat *wlr_seat, + uint32_t capabilities) { + wlr_seat->capabilities = capabilities; + + struct wlr_seat_client *client; + wl_list_for_each(client, &wlr_seat->clients, link) { + // Make resources inert if necessary + if ((capabilities & WL_SEAT_CAPABILITY_POINTER) == 0) { + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &client->pointers) { + seat_client_destroy_pointer(resource); + } + } + if ((capabilities & WL_SEAT_CAPABILITY_KEYBOARD) == 0) { + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &client->keyboards) { + seat_client_destroy_keyboard(resource); + } + } + if ((capabilities & WL_SEAT_CAPABILITY_TOUCH) == 0) { + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &client->touches) { + seat_client_destroy_touch(resource); + } + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &client->resources) { + wl_seat_send_capabilities(resource, capabilities); + } + } +} + +void wlr_seat_set_name(struct wlr_seat *wlr_seat, const char *name) { + free(wlr_seat->name); + wlr_seat->name = strdup(name); + struct wlr_seat_client *client; + wl_list_for_each(client, &wlr_seat->clients, link) { + struct wl_resource *resource; + wl_resource_for_each(resource, &client->resources) { + wl_seat_send_name(resource, name); + } + } +} + +struct wlr_seat_client *wlr_seat_client_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_seat_interface, + &seat_impl)); + return wl_resource_get_user_data(resource); +} + +bool wlr_seat_validate_grab_serial(struct wlr_seat *seat, uint32_t serial) { + // TODO + //return serial == seat->pointer_state.grab_serial || + // serial == seat->touch_state.grab_serial; + return true; +} diff --git a/types/seat/wlr_seat_keyboard.c b/types/seat/wlr_seat_keyboard.c new file mode 100644 index 00000000..96176b6d --- /dev/null +++ b/types/seat/wlr_seat_keyboard.c @@ -0,0 +1,422 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sys/mman.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/types/wlr_data_device.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_gtk_primary_selection.h> +#include <wlr/util/log.h> +#include "types/wlr_seat.h" +#include "util/signal.h" +#include "util/shm.h" + +static void default_keyboard_enter(struct wlr_seat_keyboard_grab *grab, + struct wlr_surface *surface, uint32_t keycodes[], size_t num_keycodes, + struct wlr_keyboard_modifiers *modifiers) { + wlr_seat_keyboard_enter(grab->seat, surface, keycodes, num_keycodes, modifiers); +} + +static void default_keyboard_key(struct wlr_seat_keyboard_grab *grab, + uint32_t time, uint32_t key, uint32_t state) { + wlr_seat_keyboard_send_key(grab->seat, time, key, state); +} + +static void default_keyboard_modifiers(struct wlr_seat_keyboard_grab *grab, + struct wlr_keyboard_modifiers *modifiers) { + wlr_seat_keyboard_send_modifiers(grab->seat, modifiers); +} + +static void default_keyboard_cancel(struct wlr_seat_keyboard_grab *grab) { + // cannot be cancelled +} + +const struct wlr_keyboard_grab_interface default_keyboard_grab_impl = { + .enter = default_keyboard_enter, + .key = default_keyboard_key, + .modifiers = default_keyboard_modifiers, + .cancel = default_keyboard_cancel, +}; + + +static void keyboard_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_keyboard_interface keyboard_impl = { + .release = keyboard_release, +}; + +static struct wlr_seat_client *seat_client_from_keyboard_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_keyboard_interface, + &keyboard_impl)); + return wl_resource_get_user_data(resource); +} + +static void keyboard_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); + seat_client_destroy_keyboard(resource); +} + + +void wlr_seat_keyboard_send_key(struct wlr_seat *wlr_seat, uint32_t time, + uint32_t key, uint32_t state) { + struct wlr_seat_client *client = wlr_seat->keyboard_state.focused_client; + if (!client) { + return; + } + + uint32_t serial = wl_display_next_serial(wlr_seat->display); + struct wl_resource *resource; + wl_resource_for_each(resource, &client->keyboards) { + if (seat_client_from_keyboard_resource(resource) == NULL) { + continue; + } + + wl_keyboard_send_key(resource, serial, time, key, state); + } +} + +static void seat_client_send_keymap(struct wlr_seat_client *client, + struct wlr_keyboard *keyboard); + +static void handle_keyboard_keymap(struct wl_listener *listener, void *data) { + struct wlr_seat_keyboard_state *state = + wl_container_of(listener, state, keyboard_keymap); + struct wlr_seat_client *client; + struct wlr_keyboard *keyboard = data; + if (keyboard == state->keyboard) { + wl_list_for_each(client, &state->seat->clients, link) { + seat_client_send_keymap(client, state->keyboard); + } + } +} + +static void seat_client_send_repeat_info(struct wlr_seat_client *client, + struct wlr_keyboard *keyboard); + +static void handle_keyboard_repeat_info(struct wl_listener *listener, + void *data) { + struct wlr_seat_keyboard_state *state = + wl_container_of(listener, state, keyboard_repeat_info); + struct wlr_seat_client *client; + wl_list_for_each(client, &state->seat->clients, link) { + seat_client_send_repeat_info(client, state->keyboard); + } +} + +static void handle_keyboard_destroy(struct wl_listener *listener, void *data) { + struct wlr_seat_keyboard_state *state = + wl_container_of(listener, state, keyboard_destroy); + wlr_seat_set_keyboard(state->seat, NULL); +} + +void wlr_seat_set_keyboard(struct wlr_seat *seat, + struct wlr_input_device *device) { + // TODO call this on device key event before the event reaches the + // compositor and set a pending keyboard and then send the new keyboard + // state on the next keyboard notify event. + struct wlr_keyboard *keyboard = (device ? device->keyboard : NULL); + if (seat->keyboard_state.keyboard == keyboard) { + return; + } + + if (seat->keyboard_state.keyboard) { + wl_list_remove(&seat->keyboard_state.keyboard_destroy.link); + wl_list_remove(&seat->keyboard_state.keyboard_keymap.link); + wl_list_remove(&seat->keyboard_state.keyboard_repeat_info.link); + seat->keyboard_state.keyboard = NULL; + } + + if (keyboard) { + assert(device->type == WLR_INPUT_DEVICE_KEYBOARD); + seat->keyboard_state.keyboard = keyboard; + + wl_signal_add(&device->events.destroy, + &seat->keyboard_state.keyboard_destroy); + seat->keyboard_state.keyboard_destroy.notify = handle_keyboard_destroy; + wl_signal_add(&device->keyboard->events.keymap, + &seat->keyboard_state.keyboard_keymap); + seat->keyboard_state.keyboard_keymap.notify = handle_keyboard_keymap; + wl_signal_add(&device->keyboard->events.repeat_info, + &seat->keyboard_state.keyboard_repeat_info); + seat->keyboard_state.keyboard_repeat_info.notify = + handle_keyboard_repeat_info; + + struct wlr_seat_client *client; + wl_list_for_each(client, &seat->clients, link) { + seat_client_send_keymap(client, keyboard); + seat_client_send_repeat_info(client, keyboard); + } + + wlr_seat_keyboard_send_modifiers(seat, &keyboard->modifiers); + } else { + seat->keyboard_state.keyboard = NULL; + } +} + +struct wlr_keyboard *wlr_seat_get_keyboard(struct wlr_seat *seat) { + return seat->keyboard_state.keyboard; +} + +void wlr_seat_keyboard_start_grab(struct wlr_seat *wlr_seat, + struct wlr_seat_keyboard_grab *grab) { + grab->seat = wlr_seat; + wlr_seat->keyboard_state.grab = grab; + + wlr_signal_emit_safe(&wlr_seat->events.keyboard_grab_begin, grab); +} + +void wlr_seat_keyboard_end_grab(struct wlr_seat *wlr_seat) { + struct wlr_seat_keyboard_grab *grab = wlr_seat->keyboard_state.grab; + + if (grab != wlr_seat->keyboard_state.default_grab) { + wlr_seat->keyboard_state.grab = wlr_seat->keyboard_state.default_grab; + wlr_signal_emit_safe(&wlr_seat->events.keyboard_grab_end, grab); + if (grab->interface->cancel) { + grab->interface->cancel(grab); + } + } +} + +static void seat_keyboard_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_seat_keyboard_state *state = wl_container_of( + listener, state, surface_destroy); + wl_list_remove(&state->surface_destroy.link); + wl_list_init(&state->surface_destroy.link); + wlr_seat_keyboard_clear_focus(state->seat); +} + +void wlr_seat_keyboard_send_modifiers(struct wlr_seat *seat, + struct wlr_keyboard_modifiers *modifiers) { + struct wlr_seat_client *client = seat->keyboard_state.focused_client; + if (client == NULL) { + return; + } + + uint32_t serial = wl_display_next_serial(seat->display); + struct wl_resource *resource; + wl_resource_for_each(resource, &client->keyboards) { + if (seat_client_from_keyboard_resource(resource) == NULL) { + continue; + } + + if (modifiers == NULL) { + wl_keyboard_send_modifiers(resource, serial, 0, 0, 0, 0); + } else { + wl_keyboard_send_modifiers(resource, serial, + modifiers->depressed, modifiers->latched, + modifiers->locked, modifiers->group); + } + } +} + +void wlr_seat_keyboard_enter(struct wlr_seat *seat, + struct wlr_surface *surface, uint32_t keycodes[], size_t num_keycodes, + struct wlr_keyboard_modifiers *modifiers) { + if (seat->keyboard_state.focused_surface == surface) { + // this surface already got an enter notify + return; + } + + struct wlr_seat_client *client = NULL; + + if (surface) { + struct wl_client *wl_client = wl_resource_get_client(surface->resource); + client = wlr_seat_client_for_wl_client(seat, wl_client); + } + + struct wlr_seat_client *focused_client = + seat->keyboard_state.focused_client; + struct wlr_surface *focused_surface = + seat->keyboard_state.focused_surface; + + // leave the previously entered surface + if (focused_client != NULL && focused_surface != NULL) { + uint32_t serial = wl_display_next_serial(seat->display); + struct wl_resource *resource; + wl_resource_for_each(resource, &focused_client->keyboards) { + if (seat_client_from_keyboard_resource(resource) == NULL) { + continue; + } + wl_keyboard_send_leave(resource, serial, focused_surface->resource); + } + } + + // enter the current surface + if (client != NULL) { + struct wl_array keys; + wl_array_init(&keys); + for (size_t i = 0; i < num_keycodes; ++i) { + uint32_t *p = wl_array_add(&keys, sizeof(uint32_t)); + if (!p) { + wlr_log(WLR_ERROR, "Cannot allocate memory, skipping keycode: %d\n", + keycodes[i]); + continue; + } + *p = keycodes[i]; + } + uint32_t serial = wl_display_next_serial(seat->display); + struct wl_resource *resource; + wl_resource_for_each(resource, &client->keyboards) { + if (seat_client_from_keyboard_resource(resource) == NULL) { + continue; + } + wl_keyboard_send_enter(resource, serial, surface->resource, &keys); + } + wl_array_release(&keys); + + wlr_seat_client_send_selection(client); + } + + // reinitialize the focus destroy events + wl_list_remove(&seat->keyboard_state.surface_destroy.link); + wl_list_init(&seat->keyboard_state.surface_destroy.link); + if (surface) { + wl_signal_add(&surface->events.destroy, + &seat->keyboard_state.surface_destroy); + seat->keyboard_state.surface_destroy.notify = + seat_keyboard_handle_surface_destroy; + } + + seat->keyboard_state.focused_client = client; + seat->keyboard_state.focused_surface = surface; + + if (client != NULL) { + // tell new client about any modifier change last, + // as it targets seat->keyboard_state.focused_client + wlr_seat_keyboard_send_modifiers(seat, modifiers); + } + + struct wlr_seat_keyboard_focus_change_event event = { + .seat = seat, + .old_surface = focused_surface, + .new_surface = surface, + }; + wlr_signal_emit_safe(&seat->keyboard_state.events.focus_change, &event); +} + +void wlr_seat_keyboard_notify_enter(struct wlr_seat *seat, + struct wlr_surface *surface, uint32_t keycodes[], size_t num_keycodes, + struct wlr_keyboard_modifiers *modifiers) { + struct wlr_seat_keyboard_grab *grab = seat->keyboard_state.grab; + grab->interface->enter(grab, surface, keycodes, num_keycodes, modifiers); +} + +void wlr_seat_keyboard_clear_focus(struct wlr_seat *seat) { + // TODO respect grabs here? + wlr_seat_keyboard_enter(seat, NULL, NULL, 0, NULL); +} + +bool wlr_seat_keyboard_has_grab(struct wlr_seat *seat) { + return seat->keyboard_state.grab->interface != &default_keyboard_grab_impl; +} + +void wlr_seat_keyboard_notify_modifiers(struct wlr_seat *seat, + struct wlr_keyboard_modifiers *modifiers) { + clock_gettime(CLOCK_MONOTONIC, &seat->last_event); + struct wlr_seat_keyboard_grab *grab = seat->keyboard_state.grab; + grab->interface->modifiers(grab, modifiers); +} + +void wlr_seat_keyboard_notify_key(struct wlr_seat *seat, uint32_t time, + uint32_t key, uint32_t state) { + clock_gettime(CLOCK_MONOTONIC, &seat->last_event); + struct wlr_seat_keyboard_grab *grab = seat->keyboard_state.grab; + grab->interface->key(grab, time, key, state); +} + + +static void seat_client_send_keymap(struct wlr_seat_client *client, + struct wlr_keyboard *keyboard) { + if (!keyboard) { + return; + } + + // TODO: We should probably lift all of the keys set by the other + // keyboard + struct wl_resource *resource; + wl_resource_for_each(resource, &client->keyboards) { + if (seat_client_from_keyboard_resource(resource) == NULL) { + continue; + } + + int keymap_fd = allocate_shm_file(keyboard->keymap_size); + if (keymap_fd < 0) { + wlr_log(WLR_ERROR, "creating a keymap file for %zu bytes failed", keyboard->keymap_size); + continue; + } + + void *ptr = mmap(NULL, keyboard->keymap_size, PROT_READ | PROT_WRITE, + MAP_SHARED, keymap_fd, 0); + if (ptr == MAP_FAILED) { + wlr_log(WLR_ERROR, "failed to mmap() %zu bytes", keyboard->keymap_size); + close(keymap_fd); + continue; + } + + strcpy(ptr, keyboard->keymap_string); + munmap(ptr, keyboard->keymap_size); + + wl_keyboard_send_keymap(resource, + WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, keymap_fd, + keyboard->keymap_size); + + close(keymap_fd); + } +} + +static void seat_client_send_repeat_info(struct wlr_seat_client *client, + struct wlr_keyboard *keyboard) { + if (!keyboard) { + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &client->keyboards) { + if (seat_client_from_keyboard_resource(resource) == NULL) { + continue; + } + + if (wl_resource_get_version(resource) >= + WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION) { + wl_keyboard_send_repeat_info(resource, + keyboard->repeat_info.rate, keyboard->repeat_info.delay); + } + } +} + +void seat_client_create_keyboard(struct wlr_seat_client *seat_client, + uint32_t version, uint32_t id) { + struct wl_resource *resource = wl_resource_create(seat_client->client, + &wl_keyboard_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(seat_client->client); + return; + } + wl_resource_set_implementation(resource, &keyboard_impl, seat_client, + keyboard_handle_resource_destroy); + wl_list_insert(&seat_client->keyboards, wl_resource_get_link(resource)); + + struct wlr_keyboard *keyboard = seat_client->seat->keyboard_state.keyboard; + seat_client_send_keymap(seat_client, keyboard); + seat_client_send_repeat_info(seat_client, keyboard); + + // TODO possibly handle the case where this keyboard needs an enter + // right away +} + +void seat_client_destroy_keyboard(struct wl_resource *resource) { + struct wlr_seat_client *seat_client = + seat_client_from_keyboard_resource(resource); + if (seat_client == NULL) { + return; + } + wl_resource_set_user_data(resource, NULL); +} diff --git a/types/seat/wlr_seat_pointer.c b/types/seat/wlr_seat_pointer.c new file mode 100644 index 00000000..594a5b81 --- /dev/null +++ b/types/seat/wlr_seat_pointer.c @@ -0,0 +1,364 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <wayland-server.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/util/log.h> +#include "types/wlr_seat.h" +#include "util/signal.h" + +static void default_pointer_enter(struct wlr_seat_pointer_grab *grab, + struct wlr_surface *surface, double sx, double sy) { + wlr_seat_pointer_enter(grab->seat, surface, sx, sy); +} + +static void default_pointer_motion(struct wlr_seat_pointer_grab *grab, + uint32_t time, double sx, double sy) { + wlr_seat_pointer_send_motion(grab->seat, time, sx, sy); +} + +static uint32_t default_pointer_button(struct wlr_seat_pointer_grab *grab, + uint32_t time, uint32_t button, uint32_t state) { + return wlr_seat_pointer_send_button(grab->seat, time, button, state); +} + +static void default_pointer_axis(struct wlr_seat_pointer_grab *grab, + uint32_t time, enum wlr_axis_orientation orientation, double value, + int32_t value_discrete, enum wlr_axis_source source) { + wlr_seat_pointer_send_axis(grab->seat, time, orientation, value, + value_discrete, source); +} + +static void default_pointer_cancel(struct wlr_seat_pointer_grab *grab) { + // cannot be cancelled +} + +const struct wlr_pointer_grab_interface default_pointer_grab_impl = { + .enter = default_pointer_enter, + .motion = default_pointer_motion, + .button = default_pointer_button, + .axis = default_pointer_axis, + .cancel = default_pointer_cancel, +}; + + +static void pointer_send_frame(struct wl_resource *resource) { + if (wl_resource_get_version(resource) >= + WL_POINTER_FRAME_SINCE_VERSION) { + wl_pointer_send_frame(resource); + } +} + +static const struct wl_pointer_interface pointer_impl; + +struct wlr_seat_client *wlr_seat_client_from_pointer_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_pointer_interface, + &pointer_impl)); + return wl_resource_get_user_data(resource); +} + +static const struct wlr_surface_role pointer_cursor_surface_role = { + .name = "wl_pointer-cursor", +}; + +static void pointer_set_cursor(struct wl_client *client, + struct wl_resource *pointer_resource, uint32_t serial, + struct wl_resource *surface_resource, + int32_t hotspot_x, int32_t hotspot_y) { + struct wlr_seat_client *seat_client = + wlr_seat_client_from_pointer_resource(pointer_resource); + if (seat_client == NULL) { + return; + } + + struct wlr_surface *surface = NULL; + if (surface_resource != NULL) { + surface = wlr_surface_from_resource(surface_resource); + if (!wlr_surface_set_role(surface, &pointer_cursor_surface_role, NULL, + surface_resource, WL_POINTER_ERROR_ROLE)) { + return; + } + } + + struct wlr_seat_pointer_request_set_cursor_event event = { + .seat_client = seat_client, + .surface = surface, + .serial = serial, + .hotspot_x = hotspot_x, + .hotspot_y = hotspot_y, + }; + wlr_signal_emit_safe(&seat_client->seat->events.request_set_cursor, &event); +} + +static void pointer_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_pointer_interface pointer_impl = { + .set_cursor = pointer_set_cursor, + .release = pointer_release, +}; + +static void pointer_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); + seat_client_destroy_pointer(resource); +} + + +bool wlr_seat_pointer_surface_has_focus(struct wlr_seat *wlr_seat, + struct wlr_surface *surface) { + return surface == wlr_seat->pointer_state.focused_surface; +} + +static void seat_pointer_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_seat_pointer_state *state = + wl_container_of(listener, state, surface_destroy); + wl_list_remove(&state->surface_destroy.link); + wl_list_init(&state->surface_destroy.link); + wlr_seat_pointer_clear_focus(state->seat); +} + +void wlr_seat_pointer_enter(struct wlr_seat *wlr_seat, + struct wlr_surface *surface, double sx, double sy) { + if (wlr_seat->pointer_state.focused_surface == surface) { + // this surface already got an enter notify + return; + } + + struct wlr_seat_client *client = NULL; + if (surface) { + struct wl_client *wl_client = wl_resource_get_client(surface->resource); + client = wlr_seat_client_for_wl_client(wlr_seat, wl_client); + } + + struct wlr_seat_client *focused_client = + wlr_seat->pointer_state.focused_client; + struct wlr_surface *focused_surface = + wlr_seat->pointer_state.focused_surface; + + // leave the previously entered surface + if (focused_client != NULL && focused_surface != NULL) { + uint32_t serial = wl_display_next_serial(wlr_seat->display); + struct wl_resource *resource; + wl_resource_for_each(resource, &focused_client->pointers) { + if (wlr_seat_client_from_pointer_resource(resource) == NULL) { + continue; + } + + wl_pointer_send_leave(resource, serial, focused_surface->resource); + pointer_send_frame(resource); + } + } + + // enter the current surface + if (client != NULL && surface != NULL) { + uint32_t serial = wl_display_next_serial(wlr_seat->display); + struct wl_resource *resource; + wl_resource_for_each(resource, &client->pointers) { + if (wlr_seat_client_from_pointer_resource(resource) == NULL) { + continue; + } + + wl_pointer_send_enter(resource, serial, surface->resource, + wl_fixed_from_double(sx), wl_fixed_from_double(sy)); + pointer_send_frame(resource); + } + } + + // reinitialize the focus destroy events + wl_list_remove(&wlr_seat->pointer_state.surface_destroy.link); + wl_list_init(&wlr_seat->pointer_state.surface_destroy.link); + if (surface != NULL) { + wl_signal_add(&surface->events.destroy, + &wlr_seat->pointer_state.surface_destroy); + wlr_seat->pointer_state.surface_destroy.notify = + seat_pointer_handle_surface_destroy; + } + + wlr_seat->pointer_state.focused_client = client; + wlr_seat->pointer_state.focused_surface = surface; + + struct wlr_seat_pointer_focus_change_event event = { + .seat = wlr_seat, + .new_surface = surface, + .old_surface = focused_surface, + .sx = sx, + .sy = sy, + }; + wlr_signal_emit_safe(&wlr_seat->pointer_state.events.focus_change, &event); +} + +void wlr_seat_pointer_clear_focus(struct wlr_seat *wlr_seat) { + wlr_seat_pointer_enter(wlr_seat, NULL, 0, 0); +} + +void wlr_seat_pointer_send_motion(struct wlr_seat *wlr_seat, uint32_t time, + double sx, double sy) { + struct wlr_seat_client *client = wlr_seat->pointer_state.focused_client; + if (client == NULL) { + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &client->pointers) { + if (wlr_seat_client_from_pointer_resource(resource) == NULL) { + continue; + } + + wl_pointer_send_motion(resource, time, wl_fixed_from_double(sx), + wl_fixed_from_double(sy)); + pointer_send_frame(resource); + } +} + +uint32_t wlr_seat_pointer_send_button(struct wlr_seat *wlr_seat, uint32_t time, + uint32_t button, uint32_t state) { + struct wlr_seat_client *client = wlr_seat->pointer_state.focused_client; + if (client == NULL) { + return 0; + } + + uint32_t serial = wl_display_next_serial(wlr_seat->display); + struct wl_resource *resource; + wl_resource_for_each(resource, &client->pointers) { + if (wlr_seat_client_from_pointer_resource(resource) == NULL) { + continue; + } + + wl_pointer_send_button(resource, serial, time, button, state); + pointer_send_frame(resource); + } + return serial; +} + +void wlr_seat_pointer_send_axis(struct wlr_seat *wlr_seat, uint32_t time, + enum wlr_axis_orientation orientation, double value, + int32_t value_discrete, enum wlr_axis_source source) { + struct wlr_seat_client *client = wlr_seat->pointer_state.focused_client; + if (client == NULL) { + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &client->pointers) { + if (wlr_seat_client_from_pointer_resource(resource) == NULL) { + continue; + } + + uint32_t version = wl_resource_get_version(resource); + + if (version >= WL_POINTER_AXIS_SOURCE_SINCE_VERSION) { + wl_pointer_send_axis_source(resource, source); + } + if (value) { + if (value_discrete && + version >= WL_POINTER_AXIS_DISCRETE_SINCE_VERSION) { + wl_pointer_send_axis_discrete(resource, orientation, + value_discrete); + } + + wl_pointer_send_axis(resource, time, orientation, + wl_fixed_from_double(value)); + } else if (version >= WL_POINTER_AXIS_STOP_SINCE_VERSION) { + wl_pointer_send_axis_stop(resource, time, orientation); + } + pointer_send_frame(resource); + } +} + +void wlr_seat_pointer_start_grab(struct wlr_seat *wlr_seat, + struct wlr_seat_pointer_grab *grab) { + assert(wlr_seat); + grab->seat = wlr_seat; + wlr_seat->pointer_state.grab = grab; + + wlr_signal_emit_safe(&wlr_seat->events.pointer_grab_begin, grab); +} + +void wlr_seat_pointer_end_grab(struct wlr_seat *wlr_seat) { + struct wlr_seat_pointer_grab *grab = wlr_seat->pointer_state.grab; + if (grab != wlr_seat->pointer_state.default_grab) { + wlr_seat->pointer_state.grab = wlr_seat->pointer_state.default_grab; + wlr_signal_emit_safe(&wlr_seat->events.pointer_grab_end, grab); + if (grab->interface->cancel) { + grab->interface->cancel(grab); + } + } +} + +void wlr_seat_pointer_notify_enter(struct wlr_seat *wlr_seat, + struct wlr_surface *surface, double sx, double sy) { + struct wlr_seat_pointer_grab *grab = wlr_seat->pointer_state.grab; + grab->interface->enter(grab, surface, sx, sy); +} + +void wlr_seat_pointer_notify_motion(struct wlr_seat *wlr_seat, uint32_t time, + double sx, double sy) { + clock_gettime(CLOCK_MONOTONIC, &wlr_seat->last_event); + struct wlr_seat_pointer_grab *grab = wlr_seat->pointer_state.grab; + grab->interface->motion(grab, time, sx, sy); +} + +uint32_t wlr_seat_pointer_notify_button(struct wlr_seat *wlr_seat, + uint32_t time, uint32_t button, uint32_t state) { + clock_gettime(CLOCK_MONOTONIC, &wlr_seat->last_event); + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + if (wlr_seat->pointer_state.button_count == 0) { + wlr_seat->pointer_state.grab_button = button; + wlr_seat->pointer_state.grab_time = time; + } + wlr_seat->pointer_state.button_count++; + } else { + wlr_seat->pointer_state.button_count--; + } + + struct wlr_seat_pointer_grab *grab = wlr_seat->pointer_state.grab; + uint32_t serial = grab->interface->button(grab, time, button, state); + + if (serial && wlr_seat->pointer_state.button_count == 1) { + wlr_seat->pointer_state.grab_serial = serial; + } + + return serial; +} + +void wlr_seat_pointer_notify_axis(struct wlr_seat *wlr_seat, uint32_t time, + enum wlr_axis_orientation orientation, double value, + int32_t value_discrete, enum wlr_axis_source source) { + clock_gettime(CLOCK_MONOTONIC, &wlr_seat->last_event); + struct wlr_seat_pointer_grab *grab = wlr_seat->pointer_state.grab; + grab->interface->axis(grab, time, orientation, value, value_discrete, + source); +} + +bool wlr_seat_pointer_has_grab(struct wlr_seat *seat) { + return seat->pointer_state.grab->interface != &default_pointer_grab_impl; +} + + +void seat_client_create_pointer(struct wlr_seat_client *seat_client, + uint32_t version, uint32_t id) { + struct wl_resource *resource = wl_resource_create(seat_client->client, + &wl_pointer_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(seat_client->client); + return; + } + wl_resource_set_implementation(resource, &pointer_impl, seat_client, + &pointer_handle_resource_destroy); + wl_list_insert(&seat_client->pointers, wl_resource_get_link(resource)); +} + +void seat_client_destroy_pointer(struct wl_resource *resource) { + struct wlr_seat_client *seat_client = + wlr_seat_client_from_pointer_resource(resource); + if (seat_client == NULL) { + return; + } + wl_resource_set_user_data(resource, NULL); +} diff --git a/types/seat/wlr_seat_touch.c b/types/seat/wlr_seat_touch.c new file mode 100644 index 00000000..d4fa6f0b --- /dev/null +++ b/types/seat/wlr_seat_touch.c @@ -0,0 +1,361 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <wayland-server.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/util/log.h> +#include "types/wlr_seat.h" +#include "util/signal.h" + +static uint32_t default_touch_down(struct wlr_seat_touch_grab *grab, + uint32_t time, struct wlr_touch_point *point) { + return wlr_seat_touch_send_down(grab->seat, point->surface, time, + point->touch_id, point->sx, point->sy); +} + +static void default_touch_up(struct wlr_seat_touch_grab *grab, uint32_t time, + struct wlr_touch_point *point) { + wlr_seat_touch_send_up(grab->seat, time, point->touch_id); +} + +static void default_touch_motion(struct wlr_seat_touch_grab *grab, + uint32_t time, struct wlr_touch_point *point) { + if (!point->focus_surface || point->focus_surface == point->surface) { + wlr_seat_touch_send_motion(grab->seat, time, point->touch_id, point->sx, + point->sy); + } +} + +static void default_touch_enter(struct wlr_seat_touch_grab *grab, + uint32_t time, struct wlr_touch_point *point) { + // not handled by default +} + +static void default_touch_cancel(struct wlr_seat_touch_grab *grab) { + // cannot be cancelled +} + +const struct wlr_touch_grab_interface default_touch_grab_impl = { + .down = default_touch_down, + .up = default_touch_up, + .motion = default_touch_motion, + .enter = default_touch_enter, + .cancel = default_touch_cancel, +}; + + +static void touch_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_touch_interface touch_impl = { + .release = touch_release, +}; + +static void touch_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); + seat_client_destroy_touch(resource); +} + +static struct wlr_seat_client *seat_client_from_touch_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_touch_interface, + &touch_impl)); + return wl_resource_get_user_data(resource); +} + + +void wlr_seat_touch_start_grab(struct wlr_seat *wlr_seat, + struct wlr_seat_touch_grab *grab) { + grab->seat = wlr_seat; + wlr_seat->touch_state.grab = grab; + + wlr_signal_emit_safe(&wlr_seat->events.touch_grab_begin, grab); +} + +void wlr_seat_touch_end_grab(struct wlr_seat *wlr_seat) { + struct wlr_seat_touch_grab *grab = wlr_seat->touch_state.grab; + + if (grab != wlr_seat->touch_state.default_grab) { + wlr_seat->touch_state.grab = wlr_seat->touch_state.default_grab; + wlr_signal_emit_safe(&wlr_seat->events.touch_grab_end, grab); + if (grab->interface->cancel) { + grab->interface->cancel(grab); + } + } +} + +static void touch_point_clear_focus(struct wlr_touch_point *point) { + if (point->focus_surface) { + wl_list_remove(&point->focus_surface_destroy.link); + point->focus_client = NULL; + point->focus_surface = NULL; + } +} + +static void touch_point_destroy(struct wlr_touch_point *point) { + wlr_signal_emit_safe(&point->events.destroy, point); + + touch_point_clear_focus(point); + wl_list_remove(&point->surface_destroy.link); + wl_list_remove(&point->link); + free(point); +} + +static void touch_point_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_touch_point *point = + wl_container_of(listener, point, surface_destroy); + touch_point_destroy(point); +} + +static struct wlr_touch_point *touch_point_create( + struct wlr_seat *seat, int32_t touch_id, + struct wlr_surface *surface, double sx, double sy) { + struct wl_client *wl_client = wl_resource_get_client(surface->resource); + struct wlr_seat_client *client = + wlr_seat_client_for_wl_client(seat, wl_client); + + if (client == NULL || wl_list_empty(&client->touches)) { + // touch points are not valid without a connected client with touch + return NULL; + } + + struct wlr_touch_point *point = calloc(1, sizeof(struct wlr_touch_point)); + if (!point) { + return NULL; + } + + point->touch_id = touch_id; + point->surface = surface; + point->client = client; + + point->sx = sx; + point->sy = sy; + + wl_signal_init(&point->events.destroy); + + wl_signal_add(&surface->events.destroy, &point->surface_destroy); + point->surface_destroy.notify = touch_point_handle_surface_destroy; + + wl_list_insert(&seat->touch_state.touch_points, &point->link); + + return point; +} + +struct wlr_touch_point *wlr_seat_touch_get_point( + struct wlr_seat *seat, int32_t touch_id) { + struct wlr_touch_point *point = NULL; + wl_list_for_each(point, &seat->touch_state.touch_points, link) { + if (point->touch_id == touch_id) { + return point; + } + } + + return NULL; +} + +uint32_t wlr_seat_touch_notify_down(struct wlr_seat *seat, + struct wlr_surface *surface, uint32_t time, int32_t touch_id, double sx, + double sy) { + clock_gettime(CLOCK_MONOTONIC, &seat->last_event); + struct wlr_seat_touch_grab *grab = seat->touch_state.grab; + struct wlr_touch_point *point = + touch_point_create(seat, touch_id, surface, sx, sy); + if (!point) { + wlr_log(WLR_ERROR, "could not create touch point"); + return 0; + } + + uint32_t serial = grab->interface->down(grab, time, point); + + if (serial && wlr_seat_touch_num_points(seat) == 1) { + seat->touch_state.grab_serial = serial; + seat->touch_state.grab_id = touch_id; + } + + return serial; +} + +void wlr_seat_touch_notify_up(struct wlr_seat *seat, uint32_t time, + int32_t touch_id) { + clock_gettime(CLOCK_MONOTONIC, &seat->last_event); + struct wlr_seat_touch_grab *grab = seat->touch_state.grab; + struct wlr_touch_point *point = wlr_seat_touch_get_point(seat, touch_id); + if (!point) { + return; + } + + grab->interface->up(grab, time, point); + + touch_point_destroy(point); +} + +void wlr_seat_touch_notify_motion(struct wlr_seat *seat, uint32_t time, + int32_t touch_id, double sx, double sy) { + clock_gettime(CLOCK_MONOTONIC, &seat->last_event); + struct wlr_seat_touch_grab *grab = seat->touch_state.grab; + struct wlr_touch_point *point = wlr_seat_touch_get_point(seat, touch_id); + if (!point) { + return; + } + + point->sx = sx; + point->sy = sy; + + grab->interface->motion(grab, time, point); +} + +static void handle_point_focus_destroy(struct wl_listener *listener, + void *data) { + struct wlr_touch_point *point = + wl_container_of(listener, point, focus_surface_destroy); + touch_point_clear_focus(point); +} + +static void touch_point_set_focus(struct wlr_touch_point *point, + struct wlr_surface *surface, double sx, double sy) { + if (point->focus_surface == surface) { + return; + } + + touch_point_clear_focus(point); + + if (surface && surface->resource) { + struct wlr_seat_client *client = + wlr_seat_client_for_wl_client(point->client->seat, + wl_resource_get_client(surface->resource)); + + if (client && !wl_list_empty(&client->touches)) { + wl_signal_add(&surface->events.destroy, &point->focus_surface_destroy); + point->focus_surface_destroy.notify = handle_point_focus_destroy; + point->focus_surface = surface; + point->focus_client = client; + point->sx = sx; + point->sy = sy; + } + } +} + +void wlr_seat_touch_point_focus(struct wlr_seat *seat, + struct wlr_surface *surface, uint32_t time, int32_t touch_id, double sx, + double sy) { + assert(surface); + struct wlr_touch_point *point = wlr_seat_touch_get_point(seat, touch_id); + if (!point) { + wlr_log(WLR_ERROR, "got touch point focus for unknown touch point"); + return; + } + struct wlr_surface *focus = point->focus_surface; + touch_point_set_focus(point, surface, sx, sy); + + if (focus != point->focus_surface) { + struct wlr_seat_touch_grab *grab = seat->touch_state.grab; + grab->interface->enter(grab, time, point); + } +} + +void wlr_seat_touch_point_clear_focus(struct wlr_seat *seat, uint32_t time, + int32_t touch_id) { + struct wlr_touch_point *point = wlr_seat_touch_get_point(seat, touch_id); + if (!point) { + wlr_log(WLR_ERROR, "got touch point focus for unknown touch point"); + return; + } + + touch_point_clear_focus(point); +} + +uint32_t wlr_seat_touch_send_down(struct wlr_seat *seat, + struct wlr_surface *surface, uint32_t time, int32_t touch_id, double sx, + double sy) { + struct wlr_touch_point *point = wlr_seat_touch_get_point(seat, touch_id); + if (!point) { + wlr_log(WLR_ERROR, "got touch down for unknown touch point"); + return 0; + } + + uint32_t serial = wl_display_next_serial(seat->display); + struct wl_resource *resource; + wl_resource_for_each(resource, &point->client->touches) { + if (seat_client_from_touch_resource(resource) == NULL) { + continue; + } + wl_touch_send_down(resource, serial, time, surface->resource, + touch_id, wl_fixed_from_double(sx), wl_fixed_from_double(sy)); + wl_touch_send_frame(resource); + } + + return serial; +} + +void wlr_seat_touch_send_up(struct wlr_seat *seat, uint32_t time, int32_t touch_id) { + struct wlr_touch_point *point = wlr_seat_touch_get_point(seat, touch_id); + if (!point) { + wlr_log(WLR_ERROR, "got touch up for unknown touch point"); + return; + } + + uint32_t serial = wl_display_next_serial(seat->display); + struct wl_resource *resource; + wl_resource_for_each(resource, &point->client->touches) { + if (seat_client_from_touch_resource(resource) == NULL) { + continue; + } + wl_touch_send_up(resource, serial, time, touch_id); + wl_touch_send_frame(resource); + } +} + +void wlr_seat_touch_send_motion(struct wlr_seat *seat, uint32_t time, int32_t touch_id, + double sx, double sy) { + struct wlr_touch_point *point = wlr_seat_touch_get_point(seat, touch_id); + if (!point) { + wlr_log(WLR_ERROR, "got touch motion for unknown touch point"); + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &point->client->touches) { + if (seat_client_from_touch_resource(resource) == NULL) { + continue; + } + wl_touch_send_motion(resource, time, touch_id, wl_fixed_from_double(sx), + wl_fixed_from_double(sy)); + wl_touch_send_frame(resource); + } +} + +int wlr_seat_touch_num_points(struct wlr_seat *seat) { + return wl_list_length(&seat->touch_state.touch_points); +} + +bool wlr_seat_touch_has_grab(struct wlr_seat *seat) { + return seat->touch_state.grab->interface != &default_touch_grab_impl; +} + + +void seat_client_create_touch(struct wlr_seat_client *seat_client, + uint32_t version, uint32_t id) { + struct wl_resource *resource = wl_resource_create(seat_client->client, + &wl_touch_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(seat_client->client); + return; + } + wl_resource_set_implementation(resource, &touch_impl, seat_client, + &touch_handle_resource_destroy); + wl_list_insert(&seat_client->touches, wl_resource_get_link(resource)); +} + +void seat_client_destroy_touch(struct wl_resource *resource) { + struct wlr_seat_client *seat_client = + seat_client_from_touch_resource(resource); + if (seat_client == NULL) { + return; + } + wl_resource_set_user_data(resource, NULL); +} diff --git a/types/tablet_v2/wlr_tablet_v2.c b/types/tablet_v2/wlr_tablet_v2.c new file mode 100644 index 00000000..d0bc799d --- /dev/null +++ b/types/tablet_v2/wlr_tablet_v2.c @@ -0,0 +1,322 @@ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif + +#include <assert.h> +#include <libinput.h> +#include <stdlib.h> +#include <string.h> +#include <wayland-server.h> +#include <types/wlr_tablet_v2.h> +#include <wlr/config.h> +#include <wlr/types/wlr_seat.h> +#include <wlr/types/wlr_tablet_tool.h> +#include <wlr/types/wlr_tablet_v2.h> +#include <wlr/util/log.h> +#include "tablet-unstable-v2-protocol.h" +#include "util/signal.h" + +#define TABLET_MANAGER_VERSION 1 + +struct wlr_tablet_manager_client_v2 { + struct wl_list link; + struct wl_client *client; + struct wl_resource *resource; + struct wlr_tablet_manager_v2 *manager; + + struct wl_list tablet_seats; // wlr_tablet_seat_client_v2::link +}; + +static void tablet_seat_destroy(struct wlr_tablet_seat_v2 *seat) { + struct wlr_tablet_seat_client_v2 *client, *client_tmp; + wl_list_for_each_safe(client, client_tmp, &seat->clients, seat_link) { + tablet_seat_client_v2_destroy(client->resource); + } + + wl_list_remove(&seat->link); + wl_list_remove(&seat->seat_destroy.link); + free(seat); +} + +static void handle_wlr_seat_destroy(struct wl_listener *listener, void *data) { + struct wlr_tablet_seat_v2 *seat = + wl_container_of(listener, seat, seat_destroy); + tablet_seat_destroy(seat); +} + +static struct wlr_tablet_seat_v2 *create_tablet_seat( + struct wlr_tablet_manager_v2 *manager, + struct wlr_seat *wlr_seat) { + struct wlr_tablet_seat_v2 *tablet_seat = + calloc(1, sizeof(struct wlr_tablet_seat_v2)); + if (!tablet_seat) { + return NULL; + } + + tablet_seat->manager = manager; + tablet_seat->wlr_seat = wlr_seat; + + wl_list_init(&tablet_seat->clients); + + wl_list_init(&tablet_seat->tablets); + wl_list_init(&tablet_seat->tools); + wl_list_init(&tablet_seat->pads); + + tablet_seat->seat_destroy.notify = handle_wlr_seat_destroy; + wl_signal_add(&wlr_seat->events.destroy, &tablet_seat->seat_destroy); + + wl_list_insert(&manager->seats, &tablet_seat->link); + return tablet_seat; +} + +struct wlr_tablet_seat_v2 *get_or_create_tablet_seat( + struct wlr_tablet_manager_v2 *manager, + struct wlr_seat *wlr_seat) { + struct wlr_tablet_seat_v2 *pos; + wl_list_for_each(pos, &manager->seats, link) { + if (pos->wlr_seat == wlr_seat) { + return pos; + } + } + + return create_tablet_seat(manager, wlr_seat); +} + +static void tablet_seat_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static struct zwp_tablet_seat_v2_interface seat_impl = { + .destroy = tablet_seat_handle_destroy, +}; + +struct wlr_tablet_seat_client_v2 *tablet_seat_client_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_tablet_seat_v2_interface, + &seat_impl)); + return wl_resource_get_user_data(resource); +} + +void tablet_seat_client_v2_destroy(struct wl_resource *resource) { + struct wlr_tablet_seat_client_v2 *seat = tablet_seat_client_from_resource(resource); + if (!seat) { + return; + } + + struct wlr_tablet_client_v2 *tablet; + struct wlr_tablet_client_v2 *tmp_tablet; + wl_list_for_each_safe(tablet, tmp_tablet, &seat->tablets, seat_link) { + destroy_tablet_v2(tablet->resource); + } + + struct wlr_tablet_pad_client_v2 *pad; + struct wlr_tablet_pad_client_v2 *tmp_pad; + wl_list_for_each_safe(pad, tmp_pad, &seat->pads, seat_link) { + destroy_tablet_pad_v2(pad->resource); + } + + struct wlr_tablet_tool_client_v2 *tool; + struct wlr_tablet_tool_client_v2 *tmp_tool; + wl_list_for_each_safe(tool, tmp_tool, &seat->tools, seat_link) { + destroy_tablet_tool_v2(tool->resource); + } + + wl_list_remove(&seat->seat_link); + wl_list_remove(&seat->client_link); + wl_list_remove(&seat->seat_client_destroy.link); + + free(seat); + wl_resource_set_user_data(resource, NULL); +} + +static void handle_seat_client_destroy(struct wl_listener *listener, void *data) { + struct wlr_tablet_seat_client_v2 *seat = + wl_container_of(listener, seat, seat_client_destroy); + tablet_seat_client_v2_destroy(seat->resource); +} + +static void tablet_manager_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static struct wlr_tablet_manager_client_v2 *tablet_manager_client_from_resource( + struct wl_resource *resource); + +static void get_tablet_seat(struct wl_client *wl_client, struct wl_resource *resource, + uint32_t id, struct wl_resource *seat_resource) { + struct wlr_tablet_manager_client_v2 *manager = + tablet_manager_client_from_resource(resource); + if (!manager) { + /* Inert manager, just set up the resource for later + * destruction, without allocations or advertising things + */ + wl_resource_set_implementation(seat_resource, &seat_impl, NULL, + tablet_seat_client_v2_destroy); + return; + } + struct wlr_seat_client *seat = wlr_seat_client_from_resource(seat_resource); + struct wlr_tablet_seat_v2 *tablet_seat = + get_or_create_tablet_seat(manager->manager, seat->seat); + + if (!tablet_seat) { // This can only happen when we ran out of memory + wl_client_post_no_memory(wl_client); + return; + } + + struct wlr_tablet_seat_client_v2 *seat_client = + calloc(1, sizeof(struct wlr_tablet_seat_client_v2)); + if (seat_client == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + seat_client->resource = + wl_resource_create(wl_client, &zwp_tablet_seat_v2_interface, TABLET_MANAGER_VERSION, id); + if (seat_client->resource == NULL) { + free(seat_client); + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(seat_client->resource, &seat_impl, seat_client, + tablet_seat_client_v2_destroy); + + + seat_client->seat_client = seat; + seat_client->client = manager; + seat_client->wl_client = wl_client; + wl_list_init(&seat_client->tools); + wl_list_init(&seat_client->tablets); + wl_list_init(&seat_client->pads); + + seat_client->seat_client_destroy.notify = handle_seat_client_destroy; + wl_signal_add(&seat->events.destroy, &seat_client->seat_client_destroy); + + wl_list_insert(&manager->tablet_seats, &seat_client->client_link); + wl_list_insert(&tablet_seat->clients, &seat_client->seat_link); + + // We need to emit the devices allready on the seat + struct wlr_tablet_v2_tablet *tablet_pos; + wl_list_for_each(tablet_pos, &tablet_seat->tablets, link) { + add_tablet_client(seat_client, tablet_pos); + } + + struct wlr_tablet_v2_tablet_pad *pad_pos; + wl_list_for_each(pad_pos, &tablet_seat->pads, link) { + add_tablet_pad_client(seat_client, pad_pos); + } + + struct wlr_tablet_v2_tablet_tool *tool_pos; + wl_list_for_each(tool_pos, &tablet_seat->tools, link) { + add_tablet_tool_client(seat_client, tool_pos); + } +} + +static struct zwp_tablet_manager_v2_interface manager_impl = { + .get_tablet_seat = get_tablet_seat, + .destroy = tablet_manager_destroy, +}; + +static struct wlr_tablet_manager_client_v2 *tablet_manager_client_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_tablet_manager_v2_interface, + &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void wlr_tablet_manager_v2_destroy(struct wl_resource *resource) { + struct wlr_tablet_manager_client_v2 *client = + tablet_manager_client_from_resource(resource); + if (!client) { + return; + } + + struct wlr_tablet_seat_client_v2 *pos; + struct wlr_tablet_seat_client_v2 *tmp; + wl_list_for_each_safe(pos, tmp, &client->tablet_seats, client_link) { + tablet_seat_client_v2_destroy(pos->resource); + } + + wl_list_remove(&client->link); + + free(client); + wl_resource_set_user_data(resource, NULL); +} + +static void tablet_v2_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_tablet_manager_v2 *manager = data; + assert(wl_client && manager); + + struct wlr_tablet_manager_client_v2 *client = + calloc(1, sizeof(struct wlr_tablet_manager_client_v2)); + if (client == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + wl_list_init(&client->tablet_seats); + + client->resource = + wl_resource_create(wl_client, &zwp_tablet_manager_v2_interface, version, id); + if (client->resource == NULL) { + free(client); + wl_client_post_no_memory(wl_client); + return; + } + client->client = wl_client; + client->manager = manager; + + wl_resource_set_implementation(client->resource, &manager_impl, client, + wlr_tablet_manager_v2_destroy); + wl_list_insert(&manager->clients, &client->link); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_tablet_manager_v2 *tablet = + wl_container_of(listener, tablet, display_destroy); + wlr_tablet_v2_destroy(tablet); +} + +void wlr_tablet_v2_destroy(struct wlr_tablet_manager_v2 *manager) { + struct wlr_tablet_manager_client_v2 *client, *client_tmp; + wl_list_for_each_safe(client, client_tmp, &manager->clients, link) { + wlr_tablet_manager_v2_destroy(client->resource); + } + + struct wlr_tablet_seat_v2 *seat, *seat_tmp; + wl_list_for_each_safe(seat, seat_tmp, &manager->seats, link) { + tablet_seat_destroy(seat); + } + + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->wl_global); + free(manager); +} + +struct wlr_tablet_manager_v2 *wlr_tablet_v2_create(struct wl_display *display) { + struct wlr_tablet_manager_v2 *tablet = + calloc(1, sizeof(struct wlr_tablet_manager_v2)); + if (!tablet) { + return NULL; + } + + tablet->wl_global = wl_global_create(display, + &zwp_tablet_manager_v2_interface, TABLET_MANAGER_VERSION, + tablet, tablet_v2_bind); + if (tablet->wl_global == NULL) { + free(tablet); + return NULL; + } + + wl_signal_init(&tablet->events.destroy); + wl_list_init(&tablet->clients); + wl_list_init(&tablet->seats); + + tablet->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &tablet->display_destroy); + + return tablet; +} diff --git a/types/tablet_v2/wlr_tablet_v2_pad.c b/types/tablet_v2/wlr_tablet_v2_pad.c new file mode 100644 index 00000000..578eef06 --- /dev/null +++ b/types/tablet_v2/wlr_tablet_v2_pad.c @@ -0,0 +1,697 @@ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif + +#include "tablet-unstable-v2-protocol.h" +#include <assert.h> +#include <stdlib.h> +#include <types/wlr_tablet_v2.h> +#include <wayland-util.h> +#include <wlr/types/wlr_tablet_tool.h> +#include <wlr/types/wlr_tablet_v2.h> +#include <wlr/util/log.h> + +static struct wlr_tablet_pad_v2_grab_interface default_pad_grab_interface; + +struct tablet_pad_auxiliary_user_data { + struct wlr_tablet_pad_client_v2 *pad; + size_t index; +}; + +static void handle_tablet_pad_v2_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void destroy_tablet_pad_ring_v2(struct wl_resource *resource) { + struct tablet_pad_auxiliary_user_data *aux = wl_resource_get_user_data(resource); + + if (!aux) { + return; + } + + aux->pad->rings[aux->index] = NULL; + free(aux); + wl_resource_set_user_data(resource, NULL); +} + +static void handle_tablet_pad_ring_v2_set_feedback(struct wl_client *client, + struct wl_resource *resource, const char *description, + uint32_t serial) { + struct tablet_pad_auxiliary_user_data *aux = wl_resource_get_user_data(resource); + if (!aux) { + return; + } + + struct wlr_tablet_v2_event_feedback evt = { + .serial = serial, + .description = description, + .index = aux->index + }; + + wl_signal_emit(&aux->pad->pad->events.ring_feedback, &evt); +} + +static void handle_tablet_pad_ring_v2_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static struct zwp_tablet_pad_ring_v2_interface tablet_pad_ring_impl = { + .set_feedback = handle_tablet_pad_ring_v2_set_feedback, + .destroy = handle_tablet_pad_ring_v2_destroy, +}; + +static void destroy_tablet_pad_strip_v2(struct wl_resource *resource) { + struct tablet_pad_auxiliary_user_data *aux = wl_resource_get_user_data(resource); + if (!aux) { + return; + } + + aux->pad->strips[aux->index] = NULL; + free(aux); + wl_resource_set_user_data(resource, NULL); +} + +static void handle_tablet_pad_strip_v2_set_feedback(struct wl_client *client, + struct wl_resource *resource, const char *description, + uint32_t serial) { + struct tablet_pad_auxiliary_user_data *aux = wl_resource_get_user_data(resource); + if (!aux) { + return; + } + + struct wlr_tablet_v2_event_feedback evt = { + .serial = serial, + .description = description, + .index = aux->index + }; + + wl_signal_emit(&aux->pad->pad->events.strip_feedback, &evt); +} + +static void handle_tablet_pad_strip_v2_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static struct zwp_tablet_pad_strip_v2_interface tablet_pad_strip_impl = { + .set_feedback = handle_tablet_pad_strip_v2_set_feedback, + .destroy = handle_tablet_pad_strip_v2_destroy, +}; + +static void handle_tablet_pad_v2_set_feedback( struct wl_client *client, + struct wl_resource *resource, uint32_t button, + const char *description, uint32_t serial) { + struct wlr_tablet_pad_client_v2 *pad = tablet_pad_client_from_resource(resource); + if (!pad) { + return; + } + + struct wlr_tablet_v2_event_feedback evt = { + .serial = serial, + .index = button, + .description = description, + }; + + wl_signal_emit(&pad->pad->events.button_feedback, &evt); +} + +static struct zwp_tablet_pad_v2_interface tablet_pad_impl = { + .set_feedback = handle_tablet_pad_v2_set_feedback, + .destroy = handle_tablet_pad_v2_destroy, +}; + +static void destroy_tablet_pad_group_v2(struct wl_resource *resource) { + struct tablet_pad_auxiliary_user_data *aux = wl_resource_get_user_data(resource); + + if (!aux) { + return; + } + + aux->pad->groups[aux->index] = NULL; + free(aux); + wl_resource_set_user_data(resource, NULL); +} + +void destroy_tablet_pad_v2(struct wl_resource *resource) { + struct wlr_tablet_pad_client_v2 *pad = + tablet_pad_client_from_resource(resource); + + if (!pad) { + return; + } + + wl_list_remove(&pad->seat_link); + wl_list_remove(&pad->pad_link); + + /* This isn't optimal, if the client destroys the resources in another + * order, it will be disconnected. + * But this makes things *way* easier for us, and (untested) I doubt + * clients will destroy it in another order. + */ + for (size_t i = 0; i < pad->group_count; ++i) { + if (pad->groups[i]) { + destroy_tablet_pad_group_v2(pad->groups[i]); + } + } + free(pad->groups); + + for (size_t i = 0; i < pad->ring_count; ++i) { + if (pad->rings[i]) { + destroy_tablet_pad_ring_v2(pad->rings[i]); + } + } + free(pad->rings); + + for (size_t i = 0; i < pad->strip_count; ++i) { + if (pad->strips[i]) { + destroy_tablet_pad_strip_v2(pad->strips[i]); + } + } + free(pad->strips); + + free(pad); + wl_resource_set_user_data(resource, NULL); +} + +static void handle_tablet_pad_group_v2_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static struct zwp_tablet_pad_group_v2_interface tablet_pad_group_impl = { + .destroy = handle_tablet_pad_group_v2_destroy, +}; + +static void add_tablet_pad_group(struct wlr_tablet_v2_tablet_pad *pad, + struct wlr_tablet_pad_client_v2 *client, + struct wlr_tablet_pad_group *group, size_t index) { + + uint32_t version = wl_resource_get_version(client->resource); + client->groups[index] = wl_resource_create(client->client, + &zwp_tablet_pad_group_v2_interface, version, 0); + if (!client->groups[index]) { + wl_client_post_no_memory(client->client); + return; + } + struct tablet_pad_auxiliary_user_data *user_data = + calloc(1, sizeof(struct tablet_pad_auxiliary_user_data)); + if (!user_data) { + wl_client_post_no_memory(client->client); + return; + } + user_data->pad = client; + user_data->index = index; + wl_resource_set_implementation(client->groups[index], &tablet_pad_group_impl, + user_data, destroy_tablet_pad_group_v2); + + zwp_tablet_pad_v2_send_group(client->resource, client->groups[index]); + zwp_tablet_pad_group_v2_send_modes(client->groups[index], group->mode_count); + + struct wl_array button_array; + wl_array_init(&button_array); + wl_array_add(&button_array, group->button_count * sizeof(int)); + memcpy(button_array.data, group->buttons, group->button_count * sizeof(int)); + zwp_tablet_pad_group_v2_send_buttons(client->groups[index], &button_array); + wl_array_release(&button_array); + + client->strip_count = group->strip_count; + for (size_t i = 0; i < group->strip_count; ++i) { + size_t strip = group->strips[i]; + struct tablet_pad_auxiliary_user_data *user_data = + calloc(1, sizeof(struct tablet_pad_auxiliary_user_data)); + if (!user_data) { + wl_client_post_no_memory(client->client); + return; + } + user_data->pad = client; + user_data->index = strip; + client->strips[strip] = wl_resource_create(client->client, + &zwp_tablet_pad_strip_v2_interface, version, 0); + if (!client->strips[strip]) { + free(user_data); + wl_client_post_no_memory(client->client); + return; + } + wl_resource_set_implementation(client->strips[strip], + &tablet_pad_strip_impl, user_data, destroy_tablet_pad_strip_v2); + zwp_tablet_pad_group_v2_send_strip(client->groups[index], + client->strips[strip]); + } + + client->ring_count = group->ring_count; + for (size_t i = 0; i < group->ring_count; ++i) { + size_t ring = group->rings[i]; + struct tablet_pad_auxiliary_user_data *user_data = + calloc(1, sizeof(struct tablet_pad_auxiliary_user_data)); + if (!user_data) { + wl_client_post_no_memory(client->client); + return; + } + user_data->pad = client; + user_data->index = ring; + client->rings[ring] = wl_resource_create(client->client, + &zwp_tablet_pad_ring_v2_interface, version, 0); + if (!client->rings[ring]) { + free(user_data); + wl_client_post_no_memory(client->client); + return; + } + wl_resource_set_implementation(client->rings[ring], + &tablet_pad_ring_impl, user_data, destroy_tablet_pad_ring_v2); + zwp_tablet_pad_group_v2_send_ring(client->groups[index], + client->rings[ring]); + } + + zwp_tablet_pad_group_v2_send_done(client->groups[index]); +} + +void add_tablet_pad_client(struct wlr_tablet_seat_client_v2 *seat, + struct wlr_tablet_v2_tablet_pad *pad) { + struct wlr_tablet_pad_client_v2 *client = + calloc(1, sizeof(struct wlr_tablet_pad_client_v2)); + if (!client) { + wl_client_post_no_memory(seat->wl_client); + return; + } + client->pad = pad; + + client->groups = calloc(wl_list_length(&pad->wlr_pad->groups), sizeof(struct wl_resource*)); + if (!client->groups) { + wl_client_post_no_memory(seat->wl_client); + free(client); + return; + } + + client->rings = calloc(pad->wlr_pad->ring_count, sizeof(struct wl_resource*)); + if (!client->rings) { + wl_client_post_no_memory(seat->wl_client); + free(client->groups); + free(client); + return; + } + + client->strips = calloc(pad->wlr_pad->strip_count, sizeof(struct wl_resource*)); + if (!client->strips) { + wl_client_post_no_memory(seat->wl_client); + free(client->groups); + free(client->rings); + free(client); + return; + } + + client->resource = + wl_resource_create(seat->wl_client, &zwp_tablet_pad_v2_interface, 1, 0); + if (!client->resource) { + wl_client_post_no_memory(seat->wl_client); + free(client->groups); + free(client->rings); + free(client->strips); + free(client); + return; + } + wl_resource_set_implementation(client->resource, &tablet_pad_impl, + client, destroy_tablet_pad_v2); + zwp_tablet_seat_v2_send_pad_added(seat->resource, client->resource); + client->client = seat->wl_client; + + // Send the expected events + if (pad->wlr_pad->button_count) { + zwp_tablet_pad_v2_send_buttons(client->resource, pad->wlr_pad->button_count); + } + for (size_t i = 0; i < pad->wlr_pad->paths.length; ++i) { + zwp_tablet_pad_v2_send_path(client->resource, + pad->wlr_pad->paths.items[i]); + } + size_t i = 0; + struct wlr_tablet_pad_group *group; + client->group_count = pad->group_count; + wl_list_for_each(group, &pad->wlr_pad->groups, link) { + add_tablet_pad_group(pad, client, group, i++); + } + + zwp_tablet_pad_v2_send_done(client->resource); + + wl_list_insert(&seat->pads, &client->seat_link); + wl_list_insert(&pad->clients, &client->pad_link); +} + +static void handle_wlr_tablet_pad_destroy(struct wl_listener *listener, void *data) { + struct wlr_tablet_v2_tablet_pad *pad = + wl_container_of(listener, pad, pad_destroy); + + struct wlr_tablet_pad_client_v2 *client; + struct wlr_tablet_pad_client_v2 *tmp_client; + wl_list_for_each_safe(client, tmp_client, &pad->clients, pad_link) { + zwp_tablet_pad_v2_send_removed(client->resource); + destroy_tablet_pad_v2(client->resource); + } + + wl_list_remove(&pad->clients); + wl_list_remove(&pad->link); + wl_list_remove(&pad->pad_destroy.link); + wl_list_remove(&pad->events.button_feedback.listener_list); + wl_list_remove(&pad->events.strip_feedback.listener_list); + wl_list_remove(&pad->events.ring_feedback.listener_list); + free(pad); +} + +struct wlr_tablet_v2_tablet_pad *wlr_tablet_pad_create( + struct wlr_tablet_manager_v2 *manager, + struct wlr_seat *wlr_seat, + struct wlr_input_device *wlr_device) { + assert(wlr_device->type == WLR_INPUT_DEVICE_TABLET_PAD); + struct wlr_tablet_seat_v2 *seat = get_or_create_tablet_seat(manager, wlr_seat); + if (!seat) { + return NULL; + } + struct wlr_tablet_pad *wlr_pad = wlr_device->tablet_pad; + struct wlr_tablet_v2_tablet_pad *pad = calloc(1, sizeof(struct wlr_tablet_v2_tablet_pad)); + if (!pad) { + return NULL; + } + pad->default_grab.interface = &default_pad_grab_interface; + pad->default_grab.pad = pad; + pad->grab = &pad->default_grab; + + pad->group_count = wl_list_length(&wlr_pad->groups); + pad->groups = calloc(pad->group_count, sizeof(uint32_t)); + if (!pad->groups) { + free(pad); + return NULL; + } + + pad->wlr_pad = wlr_pad; + wl_list_init(&pad->clients); + + pad->pad_destroy.notify = handle_wlr_tablet_pad_destroy; + wl_signal_add(&wlr_device->events.destroy, &pad->pad_destroy); + wl_list_insert(&seat->pads, &pad->link); + + // We need to create a tablet client for all clients on the seat + struct wlr_tablet_seat_client_v2 *pos; + wl_list_for_each(pos, &seat->clients, seat_link) { + // Tell the clients about the new tool + add_tablet_pad_client(pos, pad); + } + + wl_signal_init(&pad->events.button_feedback); + wl_signal_init(&pad->events.strip_feedback); + wl_signal_init(&pad->events.ring_feedback); + + return pad; +} + +struct wlr_tablet_pad_client_v2 *tablet_pad_client_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_tablet_pad_v2_interface, + &tablet_pad_impl)); + return wl_resource_get_user_data(resource); +} + +/* Actual protocol foo */ +uint32_t wlr_send_tablet_v2_tablet_pad_enter( + struct wlr_tablet_v2_tablet_pad *pad, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface) { + struct wl_client *client = wl_resource_get_client(surface->resource); + + struct wlr_tablet_client_v2 *tablet_tmp; + struct wlr_tablet_client_v2 *tablet_client = NULL; + wl_list_for_each(tablet_tmp, &tablet->clients, tablet_link) { + if (tablet_tmp->client == client) { + tablet_client = tablet_tmp; + break; + } + } + + // Couldn't find the client binding for the surface's client. Either + // the client didn't bind tablet_v2 at all, or not for the relevant + // seat + if (!tablet_client) { + return 0; + } + + struct wlr_tablet_pad_client_v2 *pad_tmp = NULL; + struct wlr_tablet_pad_client_v2 *pad_client = NULL; + wl_list_for_each(pad_tmp, &pad->clients, pad_link) { + if (pad_tmp->client == client) { + pad_client = pad_tmp; + break; + } + } + + // Couldn't find the client binding for the surface's client. Either + // the client didn't bind tablet_v2 at all, or not for the relevant + // seat + if (!pad_client) { + return 0; + } + + pad->current_client = pad_client; + + uint32_t serial = wl_display_next_serial(wl_client_get_display(client)); + + zwp_tablet_pad_v2_send_enter(pad_client->resource, serial, + tablet_client->resource, surface->resource); + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + uint32_t time = now.tv_nsec / 1000; + + for (size_t i = 0; i < pad->group_count; ++i) { + zwp_tablet_pad_group_v2_send_mode_switch( + pad_client->groups[i], time, serial, pad->groups[i]); + } + + return serial; +} + +void wlr_send_tablet_v2_tablet_pad_button( + struct wlr_tablet_v2_tablet_pad *pad, size_t button, + uint32_t time, enum zwp_tablet_pad_v2_button_state state) { + + if (pad->current_client) { + zwp_tablet_pad_v2_send_button(pad->current_client->resource, + time, button, state); + } +} + +void wlr_send_tablet_v2_tablet_pad_strip(struct wlr_tablet_v2_tablet_pad *pad, + uint32_t strip, double position, bool finger, uint32_t time) { + if (!pad->current_client || + !pad->current_client->strips || + !pad->current_client->strips[strip]) { + return; + } + struct wl_resource *resource = pad->current_client->strips[strip]; + + if (finger) { + zwp_tablet_pad_strip_v2_send_source(resource, ZWP_TABLET_PAD_STRIP_V2_SOURCE_FINGER); + } + + if (position < 0) { + zwp_tablet_pad_strip_v2_send_stop(resource); + } else { + zwp_tablet_pad_strip_v2_send_position(resource, position * 65535); + } + zwp_tablet_pad_strip_v2_send_frame(resource, time); +} + +void wlr_send_tablet_v2_tablet_pad_ring(struct wlr_tablet_v2_tablet_pad *pad, + uint32_t ring, double position, bool finger, uint32_t time) { + if (!pad->current_client || + !pad->current_client->rings || + !pad->current_client->rings[ring]) { + return; + } + struct wl_resource *resource = pad->current_client->rings[ring]; + + if (finger) { + zwp_tablet_pad_ring_v2_send_source(resource, ZWP_TABLET_PAD_RING_V2_SOURCE_FINGER); + } + + if (position < 0) { + zwp_tablet_pad_ring_v2_send_stop(resource); + } else { + zwp_tablet_pad_ring_v2_send_angle(resource, position); + } + zwp_tablet_pad_ring_v2_send_frame(resource, time); +} + +uint32_t wlr_send_tablet_v2_tablet_pad_leave(struct wlr_tablet_v2_tablet_pad *pad, + struct wlr_surface *surface) { + struct wl_client *client = wl_resource_get_client(surface->resource); + if (!pad->current_client || client != pad->current_client->client) { + return 0; + } + + uint32_t serial = wl_display_next_serial(wl_client_get_display(client)); + + zwp_tablet_pad_v2_send_leave(pad->current_client->resource, serial, surface->resource); + return serial; +} + +uint32_t wlr_send_tablet_v2_tablet_pad_mode(struct wlr_tablet_v2_tablet_pad *pad, + size_t group, uint32_t mode, uint32_t time) { + if (!pad->current_client || + !pad->current_client->groups || + !pad->current_client->groups[group] ) { + return 0; + } + + if (pad->groups[group] == mode) { + return 0; + } + + pad->groups[group] = mode; + + struct wl_client *client = wl_resource_get_client(pad->current_client->resource); + uint32_t serial = wl_display_next_serial(wl_client_get_display(client)); + + zwp_tablet_pad_group_v2_send_mode_switch( + pad->current_client->groups[group], time, serial, mode); + return serial; +} + +bool wlr_surface_accepts_tablet_v2(struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface) { + struct wl_client *client = wl_resource_get_client(surface->resource); + + if (tablet->current_client && + tablet->current_client->client == client) { + return true; + } + + struct wlr_tablet_client_v2 *tablet_tmp; + wl_list_for_each(tablet_tmp, &tablet->clients, tablet_link) { + if (tablet_tmp->client == client) { + return true; + } + } + + return false; +} + + +uint32_t wlr_tablet_v2_tablet_pad_notify_enter( + struct wlr_tablet_v2_tablet_pad *pad, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface) { + if (pad->grab && pad->grab->interface->enter) { + return pad->grab->interface->enter(pad->grab, tablet, surface); + } + + return 0; +} + +void wlr_tablet_v2_tablet_pad_notify_button( + struct wlr_tablet_v2_tablet_pad *pad, size_t button, + uint32_t time, enum zwp_tablet_pad_v2_button_state state) { + if (pad->grab && pad->grab->interface->button) { + pad->grab->interface->button(pad->grab, button, time, state); + } +} + +void wlr_tablet_v2_tablet_pad_notify_strip( + struct wlr_tablet_v2_tablet_pad *pad, + uint32_t strip, double position, bool finger, uint32_t time) { + if (pad->grab && pad->grab->interface->strip) { + pad->grab->interface->strip(pad->grab, strip, position, finger, time); + } +} + +void wlr_tablet_v2_tablet_pad_notify_ring( + struct wlr_tablet_v2_tablet_pad *pad, + uint32_t ring, double position, bool finger, uint32_t time) { + if (pad->grab && pad->grab->interface->ring) { + pad->grab->interface->ring(pad->grab, ring, position, finger, time); + } +} + +uint32_t wlr_tablet_v2_tablet_pad_notify_leave( + struct wlr_tablet_v2_tablet_pad *pad, struct wlr_surface *surface) { + if (pad->grab && pad->grab->interface->leave) { + return pad->grab->interface->leave(pad->grab, surface); + } + + return 0; +} + +uint32_t wlr_tablet_v2_tablet_pad_notify_mode( + struct wlr_tablet_v2_tablet_pad *pad, + size_t group, uint32_t mode, uint32_t time) { + if (pad->grab && pad->grab->interface->mode) { + return pad->grab->interface->mode(pad->grab, group, mode, time); + } + + return 0; +} + +void wlr_tablet_v2_start_grab(struct wlr_tablet_v2_tablet_pad *pad, + struct wlr_tablet_pad_v2_grab *grab) { + if (grab != &pad->default_grab) { + struct wlr_tablet_pad_v2_grab *prev = pad->grab; + grab->pad = pad; + pad->grab = grab; + if (prev && prev->interface->cancel) { + prev->interface->cancel(prev); + } + } +} + +void wlr_tablet_v2_end_grab(struct wlr_tablet_v2_tablet_pad *pad) { + struct wlr_tablet_pad_v2_grab *grab = pad->grab; + if (grab && grab != &pad->default_grab) { + pad->grab = &pad->default_grab; + if (grab->interface->cancel) { + grab->interface->cancel(grab); + } + } +} + +static uint32_t default_pad_enter( + struct wlr_tablet_pad_v2_grab *grab, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface) { + return wlr_send_tablet_v2_tablet_pad_enter(grab->pad, tablet, surface); +} + +static void default_pad_button(struct wlr_tablet_pad_v2_grab *grab,size_t button, + uint32_t time, enum zwp_tablet_pad_v2_button_state state) { + wlr_send_tablet_v2_tablet_pad_button(grab->pad, button, time, state); +} + +static void default_pad_strip(struct wlr_tablet_pad_v2_grab *grab, + uint32_t strip, double position, bool finger, uint32_t time) { + wlr_send_tablet_v2_tablet_pad_strip(grab->pad, strip, position, finger, time); +} + +static void default_pad_ring(struct wlr_tablet_pad_v2_grab *grab, + uint32_t ring, double position, bool finger, uint32_t time) { + wlr_send_tablet_v2_tablet_pad_ring(grab->pad, ring, position, finger, time); +} + +static uint32_t default_pad_leave(struct wlr_tablet_pad_v2_grab *grab, + struct wlr_surface *surface) { + return wlr_send_tablet_v2_tablet_pad_leave(grab->pad, surface); +} + +static uint32_t default_pad_mode(struct wlr_tablet_pad_v2_grab *grab, + size_t group, uint32_t mode, uint32_t time) { + return wlr_send_tablet_v2_tablet_pad_mode(grab->pad, group, mode, time); +} + +static void default_pad_cancel(struct wlr_tablet_pad_v2_grab *grab) { + // Do nothing, the default cancel can be ignored. +} + +static struct wlr_tablet_pad_v2_grab_interface default_pad_grab_interface = { + .enter = default_pad_enter, + .button = default_pad_button, + .strip = default_pad_strip, + .ring = default_pad_ring, + .leave = default_pad_leave, + .mode = default_pad_mode, + .cancel = default_pad_cancel, +}; diff --git a/types/tablet_v2/wlr_tablet_v2_tablet.c b/types/tablet_v2/wlr_tablet_v2_tablet.c new file mode 100644 index 00000000..0d1b5aa3 --- /dev/null +++ b/types/tablet_v2/wlr_tablet_v2_tablet.c @@ -0,0 +1,132 @@ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif + +#include <assert.h> +#include <stdlib.h> +#include <types/wlr_tablet_v2.h> +#include <wayland-util.h> +#include <wlr/types/wlr_tablet_tool.h> +#include <wlr/types/wlr_tablet_v2.h> +#include <wlr/util/log.h> + +#include "tablet-unstable-v2-protocol.h" + +void destroy_tablet_v2(struct wl_resource *resource) { + struct wlr_tablet_client_v2 *tablet = tablet_client_from_resource(resource); + + if (!tablet) { + return; + } + + wl_list_remove(&tablet->seat_link); + wl_list_remove(&tablet->tablet_link); + free(tablet); + wl_resource_set_user_data(resource, NULL); +} + +static void handle_tablet_v2_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static struct zwp_tablet_v2_interface tablet_impl = { + .destroy = handle_tablet_v2_destroy, +}; + +static void handle_wlr_tablet_destroy(struct wl_listener *listener, void *data) { + struct wlr_tablet_v2_tablet *tablet = + wl_container_of(listener, tablet, tool_destroy); + + struct wlr_tablet_client_v2 *pos; + struct wlr_tablet_client_v2 *tmp; + wl_list_for_each_safe(pos, tmp, &tablet->clients, tablet_link) { + zwp_tablet_v2_send_removed(pos->resource); + } + + wl_list_remove(&tablet->clients); + wl_list_remove(&tablet->link); + wl_list_remove(&tablet->tool_destroy.link); + free(tablet); +} + +struct wlr_tablet_v2_tablet *wlr_tablet_create( + struct wlr_tablet_manager_v2 *manager, + struct wlr_seat *wlr_seat, + struct wlr_input_device *wlr_device) { + assert(wlr_device->type == WLR_INPUT_DEVICE_TABLET_TOOL); + struct wlr_tablet_seat_v2 *seat = get_or_create_tablet_seat(manager, wlr_seat); + if (!seat) { + return NULL; + } + struct wlr_tablet *wlr_tablet = wlr_device->tablet; + struct wlr_tablet_v2_tablet *tablet = calloc(1, sizeof(struct wlr_tablet_v2_tablet)); + if (!tablet) { + return NULL; + } + + tablet->wlr_tablet = wlr_tablet; + tablet->wlr_device = wlr_device; + wl_list_init(&tablet->clients); + + + tablet->tool_destroy.notify = handle_wlr_tablet_destroy; + wl_signal_add(&wlr_device->events.destroy, &tablet->tool_destroy); + wl_list_insert(&seat->tablets, &tablet->link); + + // We need to create a tablet client for all clients on the seat + struct wlr_tablet_seat_client_v2 *pos; + wl_list_for_each(pos, &seat->clients, seat_link) { + // Tell the clients about the new tool + add_tablet_client(pos, tablet); + } + + return tablet; +} + + +void add_tablet_client(struct wlr_tablet_seat_client_v2 *seat, + struct wlr_tablet_v2_tablet *tablet) { + struct wlr_tablet_client_v2 *client = + calloc(1, sizeof(struct wlr_tablet_client_v2)); + if (!client) { + return; + } + + uint32_t version = wl_resource_get_version(seat->resource); + client->resource = + wl_resource_create(seat->wl_client, &zwp_tablet_v2_interface, + version, 0); + if (!client->resource) { + wl_resource_post_no_memory(seat->resource); + free(client); + return; + } + wl_resource_set_implementation(client->resource, &tablet_impl, + client, destroy_tablet_v2); + zwp_tablet_seat_v2_send_tablet_added(seat->resource, client->resource); + + // Send the expected events + if (tablet->wlr_tablet->name) { + zwp_tablet_v2_send_name(client->resource, + tablet->wlr_tablet->name); + } + zwp_tablet_v2_send_id(client->resource, + tablet->wlr_device->vendor, tablet->wlr_device->product); + for (size_t i = 0; i < tablet->wlr_tablet->paths.length; ++i) { + zwp_tablet_v2_send_path(client->resource, + tablet->wlr_tablet->paths.items[i]); + } + zwp_tablet_v2_send_done(client->resource); + + client->client = seat->wl_client; + wl_list_insert(&seat->tablets, &client->seat_link); + wl_list_insert(&tablet->clients, &client->tablet_link); +} + + +struct wlr_tablet_client_v2 *tablet_client_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_tablet_v2_interface, + &tablet_impl)); + return wl_resource_get_user_data(resource); +} diff --git a/types/tablet_v2/wlr_tablet_v2_tool.c b/types/tablet_v2/wlr_tablet_v2_tool.c new file mode 100644 index 00000000..81f13aa1 --- /dev/null +++ b/types/tablet_v2/wlr_tablet_v2_tool.c @@ -0,0 +1,834 @@ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif + +#include "tablet-unstable-v2-protocol.h" +#include "util/array.h" +#include <assert.h> +#include <stdlib.h> +#include <types/wlr_tablet_v2.h> +#include <wayland-util.h> +#include <wlr/types/wlr_tablet_tool.h> +#include <wlr/types/wlr_tablet_v2.h> +#include <wlr/util/log.h> + +static const struct wlr_tablet_tool_v2_grab_interface default_tool_grab_interface; + +static const struct wlr_surface_role tablet_tool_cursor_surface_role = { + .name = "wp_tablet_tool-cursor", +}; + +static void handle_tablet_tool_v2_set_cursor(struct wl_client *client, + struct wl_resource *resource, uint32_t serial, + struct wl_resource *surface_resource, + int32_t hotspot_x, int32_t hotspot_y) { + struct wlr_tablet_tool_client_v2 *tool = tablet_tool_client_from_resource(resource); + if (!tool) { + return; + } + + struct wlr_surface *surface = NULL; + if (surface_resource != NULL) { + surface = wlr_surface_from_resource(surface_resource); + if (!wlr_surface_set_role(surface, &tablet_tool_cursor_surface_role, NULL, + surface_resource, ZWP_TABLET_TOOL_V2_ERROR_ROLE)) { + return; + } + } + + struct wlr_tablet_v2_event_cursor evt = { + .surface = surface, + .serial = serial, + .hotspot_x = hotspot_x, + .hotspot_y = hotspot_y, + .seat_client = tool->seat->seat_client, + }; + + wl_signal_emit(&tool->tool->events.set_cursor, &evt); +} + +static void handle_tablet_tool_v2_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} +static struct zwp_tablet_tool_v2_interface tablet_tool_impl = { + .set_cursor = handle_tablet_tool_v2_set_cursor, + .destroy = handle_tablet_tool_v2_destroy, +}; + +static enum zwp_tablet_tool_v2_type tablet_type_from_wlr_type( + enum wlr_tablet_tool_type wlr_type) { + switch(wlr_type) { + case WLR_TABLET_TOOL_TYPE_PEN: + return ZWP_TABLET_TOOL_V2_TYPE_PEN; + case WLR_TABLET_TOOL_TYPE_ERASER: + return ZWP_TABLET_TOOL_V2_TYPE_ERASER; + case WLR_TABLET_TOOL_TYPE_BRUSH: + return ZWP_TABLET_TOOL_V2_TYPE_BRUSH; + case WLR_TABLET_TOOL_TYPE_PENCIL: + return ZWP_TABLET_TOOL_V2_TYPE_PENCIL; + case WLR_TABLET_TOOL_TYPE_AIRBRUSH: + return ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH; + case WLR_TABLET_TOOL_TYPE_MOUSE: + return ZWP_TABLET_TOOL_V2_TYPE_MOUSE; + case WLR_TABLET_TOOL_TYPE_LENS: + return ZWP_TABLET_TOOL_V2_TYPE_LENS; + } + + assert(false && "Unreachable"); +} + +void destroy_tablet_tool_v2(struct wl_resource *resource) { + struct wlr_tablet_tool_client_v2 *client = + tablet_tool_client_from_resource(resource); + + if (!client) { + return; + } + + if (client->frame_source) { + wl_event_source_remove(client->frame_source); + } + + if (client->tool && client->tool->current_client == client) { + client->tool->current_client = NULL; + } + + wl_list_remove(&client->seat_link); + wl_list_remove(&client->tool_link); + free(client); + + wl_resource_set_user_data(resource, NULL); +} + +void add_tablet_tool_client(struct wlr_tablet_seat_client_v2 *seat, + struct wlr_tablet_v2_tablet_tool *tool) { + struct wlr_tablet_tool_client_v2 *client = + calloc(1, sizeof(struct wlr_tablet_tool_client_v2)); + if (!client) { + return; + } + client->tool = tool; + client->seat = seat; + + client->resource = + wl_resource_create(seat->wl_client, &zwp_tablet_tool_v2_interface, 1, 0); + if (!client->resource) { + free(client); + return; + } + wl_resource_set_implementation(client->resource, &tablet_tool_impl, + client, destroy_tablet_tool_v2); + zwp_tablet_seat_v2_send_tool_added(seat->resource, client->resource); + + // Send the expected events + if (tool->wlr_tool->hardware_serial) { + zwp_tablet_tool_v2_send_hardware_serial( + client->resource, + tool->wlr_tool->hardware_serial >> 32, + tool->wlr_tool->hardware_serial & 0xFFFFFFFF); + } + if (tool->wlr_tool->hardware_wacom) { + zwp_tablet_tool_v2_send_hardware_id_wacom( + client->resource, + tool->wlr_tool->hardware_wacom >> 32, + tool->wlr_tool->hardware_wacom & 0xFFFFFFFF); + } + zwp_tablet_tool_v2_send_type(client->resource, + tablet_type_from_wlr_type(tool->wlr_tool->type)); + + if (tool->wlr_tool->tilt) { + zwp_tablet_tool_v2_send_capability(client->resource, + ZWP_TABLET_TOOL_V2_CAPABILITY_TILT); + } + + if (tool->wlr_tool->pressure) { + zwp_tablet_tool_v2_send_capability(client->resource, + ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE); + } + + if (tool->wlr_tool->distance) { + zwp_tablet_tool_v2_send_capability(client->resource, + ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE); + } + + if (tool->wlr_tool->rotation) { + zwp_tablet_tool_v2_send_capability(client->resource, + ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION); + } + + if (tool->wlr_tool->slider) { + zwp_tablet_tool_v2_send_capability(client->resource, + ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER); + } + + if (tool->wlr_tool->wheel) { + zwp_tablet_tool_v2_send_capability(client->resource, + ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL); + } + + zwp_tablet_tool_v2_send_done(client->resource); + + client->client = seat->wl_client; + wl_list_insert(&seat->tools, &client->seat_link); + wl_list_insert(&tool->clients, &client->tool_link); +} + +static void handle_wlr_tablet_tool_destroy(struct wl_listener *listener, void *data) { + struct wlr_tablet_v2_tablet_tool *tool = + wl_container_of(listener, tool, tool_destroy); + + struct wlr_tablet_tool_client_v2 *pos; + struct wlr_tablet_tool_client_v2 *tmp; + wl_list_for_each_safe(pos, tmp, &tool->clients, tool_link) { + zwp_tablet_tool_v2_send_removed(pos->resource); + pos->tool = NULL; + } + + wl_list_remove(&tool->clients); + wl_list_remove(&tool->link); + wl_list_remove(&tool->tool_destroy.link); + wl_list_remove(&tool->events.set_cursor.listener_list); + free(tool); +} + +struct wlr_tablet_v2_tablet_tool *wlr_tablet_tool_create( + struct wlr_tablet_manager_v2 *manager, + struct wlr_seat *wlr_seat, + struct wlr_tablet_tool *wlr_tool) { + struct wlr_tablet_seat_v2 *seat = get_or_create_tablet_seat(manager, wlr_seat); + if (!seat) { + return NULL; + } + struct wlr_tablet_v2_tablet_tool *tool = + calloc(1, sizeof(struct wlr_tablet_v2_tablet_tool)); + if (!tool) { + return NULL; + } + + tool->wlr_tool = wlr_tool; + wl_list_init(&tool->clients); + tool->default_grab.tool = tool; + tool->default_grab.interface = &default_tool_grab_interface; + tool->grab = &tool->default_grab; + + + tool->tool_destroy.notify = handle_wlr_tablet_tool_destroy; + wl_signal_add(&wlr_tool->events.destroy, &tool->tool_destroy); + wl_list_insert(&seat->tools, &tool->link); + + // We need to create a tablet client for all clients on the seat + struct wlr_tablet_seat_client_v2 *pos; + wl_list_for_each(pos, &seat->clients, seat_link) { + // Tell the clients about the new tool + add_tablet_tool_client(pos, tool); + } + + wl_signal_init(&tool->events.set_cursor); + + return tool; +} + +struct wlr_tablet_tool_client_v2 *tablet_tool_client_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_tablet_tool_v2_interface, + &tablet_tool_impl)); + return wl_resource_get_user_data(resource); +} + + +/* Actual protocol foo */ + +// Button 0 is KEY_RESERVED in input-event-codes on linux (and freebsd) +static ssize_t tablet_tool_button_update(struct wlr_tablet_v2_tablet_tool *tool, + uint32_t button, enum zwp_tablet_pad_v2_button_state state) { + bool found = false; + size_t i = 0; + for (; i < tool->num_buttons; ++i) { + if (tool->pressed_buttons[i] == button) { + found = true; + wlr_log(WLR_DEBUG, "Found the button \\o/: %u", button); + break; + + } + } + + if (state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED && found) { + /* Already have the button saved, durr */ + return -1; + } + + if (state == ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED && !found) { + if (tool->num_buttons < WLR_TABLET_V2_TOOL_BUTTONS_CAP) { + i = tool->num_buttons++; + tool->pressed_buttons[i] = button; + tool->pressed_serials[i] = -1; + } else { + i = -1; + wlr_log(WLR_ERROR, "You pressed more than %d tablet tool buttons. This is currently not supporte by wlroots. Please report this with a description of your tablet, since this is either a bug, or fancy hardware", + WLR_TABLET_V2_TOOL_BUTTONS_CAP); + } + } + if (state == ZWP_TABLET_PAD_V2_BUTTON_STATE_RELEASED && found) { + wlr_log(WLR_DEBUG, "Removed the button \\o/: %u", button); + tool->pressed_buttons[i] = 0; + tool->pressed_serials[i] = 0; + tool->num_buttons = push_zeroes_to_end(tool->pressed_buttons, WLR_TABLET_V2_TOOL_BUTTONS_CAP); + tool->num_buttons = push_zeroes_to_end(tool->pressed_serials, WLR_TABLET_V2_TOOL_BUTTONS_CAP); + } + + assert(tool->num_buttons <= WLR_TABLET_V2_TOOL_BUTTONS_CAP); + return i; +} + +static inline int64_t timespec_to_msec(const struct timespec *a) { + return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000; +} + +static void send_tool_frame(void *data) { + struct wlr_tablet_tool_client_v2 *tool = data; + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + zwp_tablet_tool_v2_send_frame(tool->resource, timespec_to_msec(&now)); + tool->frame_source = NULL; +} + +static void queue_tool_frame(struct wlr_tablet_tool_client_v2 *tool) { + struct wl_display *display = wl_client_get_display(tool->client); + struct wl_event_loop *loop = wl_display_get_event_loop(display); + if (!tool->frame_source) { + tool->frame_source = + wl_event_loop_add_idle(loop, send_tool_frame, tool); + } +} + +void wlr_send_tablet_v2_tablet_tool_proximity_in( + struct wlr_tablet_v2_tablet_tool *tool, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface) { + struct wl_client *client = wl_resource_get_client(surface->resource); + + if (tool->focused_surface == surface) { + return; + } + + struct wlr_tablet_client_v2 *tablet_tmp; + struct wlr_tablet_client_v2 *tablet_client = NULL; + wl_list_for_each(tablet_tmp, &tablet->clients, tablet_link) { + if (tablet_tmp->client == client) { + tablet_client = tablet_tmp; + break; + } + } + + // Couldn't find the client binding for the surface's client. Either + // the client didn't bind tablet_v2 at all, or not for the relevant + // seat + if (!tablet_client) { + return; + } + + struct wlr_tablet_tool_client_v2 *tool_tmp = NULL; + struct wlr_tablet_tool_client_v2 *tool_client = NULL; + wl_list_for_each(tool_tmp, &tool->clients, tool_link) { + if (tool_tmp->client == client) { + tool_client = tool_tmp; + break; + } + } + + // Couldn't find the client binding for the surface's client. Either + // the client didn't bind tablet_v2 at all, or not for the relevant + // seat + if (!tool_client) { + return; + } + + tool->current_client = tool_client; + + uint32_t serial = wl_display_next_serial(wl_client_get_display(client)); + tool->focused_surface = surface; + tool->proximity_serial = serial; + + zwp_tablet_tool_v2_send_proximity_in(tool_client->resource, serial, + tablet_client->resource, surface->resource); + /* Send all the pressed buttons */ + for (size_t i = 0; i < tool->num_buttons; ++i) { + wlr_send_tablet_v2_tablet_tool_button(tool, + tool->pressed_buttons[i], + ZWP_TABLET_PAD_V2_BUTTON_STATE_PRESSED); + } + if (tool->is_down) { + wlr_send_tablet_v2_tablet_tool_down(tool); + } + + queue_tool_frame(tool_client); +} + +void wlr_send_tablet_v2_tablet_tool_motion( + struct wlr_tablet_v2_tablet_tool *tool, double x, double y) { + if (!tool->current_client) { + return; + } + + zwp_tablet_tool_v2_send_motion(tool->current_client->resource, + wl_fixed_from_double(x), wl_fixed_from_double(y)); + + queue_tool_frame(tool->current_client); +} + +void wlr_send_tablet_v2_tablet_tool_proximity_out( + struct wlr_tablet_v2_tablet_tool *tool) { + if (tool->current_client) { + for (size_t i = 0; i < tool->num_buttons; ++i) { + zwp_tablet_tool_v2_send_button(tool->current_client->resource, + tool->pressed_serials[i], + tool->pressed_buttons[i], + ZWP_TABLET_PAD_V2_BUTTON_STATE_RELEASED); + } + if (tool->is_down) { + zwp_tablet_tool_v2_send_up(tool->current_client->resource); + } + if (tool->current_client->frame_source) { + wl_event_source_remove(tool->current_client->frame_source); + send_tool_frame(tool->current_client); + } + zwp_tablet_tool_v2_send_proximity_out(tool->current_client->resource); + + tool->current_client = NULL; + tool->focused_surface = NULL; + } +} + +void wlr_send_tablet_v2_tablet_tool_pressure( + struct wlr_tablet_v2_tablet_tool *tool, double pressure) { + if (tool->current_client) { + zwp_tablet_tool_v2_send_pressure(tool->current_client->resource, + pressure * 65535); + + queue_tool_frame(tool->current_client); + } +} + +void wlr_send_tablet_v2_tablet_tool_distance( + struct wlr_tablet_v2_tablet_tool *tool, double distance) { + if (tool->current_client) { + zwp_tablet_tool_v2_send_distance(tool->current_client->resource, + distance * 65535); + + queue_tool_frame(tool->current_client); + } +} + +void wlr_send_tablet_v2_tablet_tool_tilt( + struct wlr_tablet_v2_tablet_tool *tool, double x, double y) { + if (!tool->current_client) { + return; + } + + zwp_tablet_tool_v2_send_tilt(tool->current_client->resource, + wl_fixed_from_double(x), wl_fixed_from_double(y)); + + queue_tool_frame(tool->current_client); +} + +void wlr_send_tablet_v2_tablet_tool_rotation( + struct wlr_tablet_v2_tablet_tool *tool, double degrees) { + if (!tool->current_client) { + return; + } + + zwp_tablet_tool_v2_send_rotation(tool->current_client->resource, + wl_fixed_from_double(degrees)); + + queue_tool_frame(tool->current_client); +} + +void wlr_send_tablet_v2_tablet_tool_slider( + struct wlr_tablet_v2_tablet_tool *tool, double position) { + if (!tool->current_client) { + return; + } + + zwp_tablet_tool_v2_send_slider(tool->current_client->resource, + position * 65535); + + queue_tool_frame(tool->current_client); +} + +void wlr_send_tablet_v2_tablet_tool_button( + struct wlr_tablet_v2_tablet_tool *tool, uint32_t button, + enum zwp_tablet_pad_v2_button_state state) { + ssize_t index = tablet_tool_button_update(tool, button, state); + + if (tool->current_client) { + struct wl_client *client = + wl_resource_get_client(tool->current_client->resource); + uint32_t serial = wl_display_next_serial(wl_client_get_display(client)); + if (index >= 0) { + tool->pressed_serials[index] = serial; + } + + zwp_tablet_tool_v2_send_button(tool->current_client->resource, + serial, button, state); + queue_tool_frame(tool->current_client); + } +} + +void wlr_send_tablet_v2_tablet_tool_wheel( + struct wlr_tablet_v2_tablet_tool *tool, double degrees, int32_t clicks) { + if (tool->current_client) { + zwp_tablet_tool_v2_send_wheel(tool->current_client->resource, + clicks, degrees); + + queue_tool_frame(tool->current_client); + } +} + +void wlr_send_tablet_v2_tablet_tool_down(struct wlr_tablet_v2_tablet_tool *tool) { + if (tool->is_down) { + return; + } + + tool->is_down = true; + if (tool->current_client) { + struct wl_client *client = + wl_resource_get_client(tool->current_client->resource); + uint32_t serial = wl_display_next_serial(wl_client_get_display(client)); + + zwp_tablet_tool_v2_send_down(tool->current_client->resource, + serial); + queue_tool_frame(tool->current_client); + + tool->down_serial = serial; + } +} + +void wlr_send_tablet_v2_tablet_tool_up(struct wlr_tablet_v2_tablet_tool *tool) { + if (!tool->is_down) { + return; + } + tool->is_down = false; + tool->down_serial = 0; + + if (tool->current_client) { + zwp_tablet_tool_v2_send_up(tool->current_client->resource); + queue_tool_frame(tool->current_client); + } +} + + +void wlr_tablet_v2_tablet_tool_notify_proximity_in( + struct wlr_tablet_v2_tablet_tool *tool, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface) { + if (tool->grab->interface->proximity_in) { + tool->grab->interface->proximity_in(tool->grab, tablet, surface); + } +} + +void wlr_tablet_v2_tablet_tool_notify_down(struct wlr_tablet_v2_tablet_tool *tool) { + if (tool->grab->interface->down) { + tool->grab->interface->down(tool->grab); + } +} +void wlr_tablet_v2_tablet_tool_notify_up(struct wlr_tablet_v2_tablet_tool *tool) { + if (tool->grab->interface->up) { + tool->grab->interface->up(tool->grab); + } +} + +void wlr_tablet_v2_tablet_tool_notify_motion( + struct wlr_tablet_v2_tablet_tool *tool, double x, double y) { + if (tool->grab->interface->motion) { + tool->grab->interface->motion(tool->grab, x, y); + } +} + +void wlr_tablet_v2_tablet_tool_notify_pressure( + struct wlr_tablet_v2_tablet_tool *tool, double pressure) { + if (tool->grab->interface->pressure) { + tool->grab->interface->pressure(tool->grab, pressure); + } +} + +void wlr_tablet_v2_tablet_tool_notify_distance( + struct wlr_tablet_v2_tablet_tool *tool, double distance) { + if (tool->grab->interface->distance) { + tool->grab->interface->distance(tool->grab, distance); + } +} + +void wlr_tablet_v2_tablet_tool_notify_tilt( + struct wlr_tablet_v2_tablet_tool *tool, double x, double y) { + if (tool->grab->interface->tilt) { + tool->grab->interface->tilt(tool->grab, x, y); + } +} + +void wlr_tablet_v2_tablet_tool_notify_rotation( + struct wlr_tablet_v2_tablet_tool *tool, double degrees) { + if (tool->grab->interface->rotation) { + tool->grab->interface->rotation(tool->grab, degrees); + } +} + +void wlr_tablet_v2_tablet_tool_notify_slider( + struct wlr_tablet_v2_tablet_tool *tool, double position) { + if (tool->grab->interface->slider) { + tool->grab->interface->slider(tool->grab, position); + } +} + +void wlr_tablet_v2_tablet_tool_notify_wheel( + struct wlr_tablet_v2_tablet_tool *tool, double degrees, int32_t clicks) { + if (tool->grab->interface->wheel) { + tool->grab->interface->wheel(tool->grab, degrees, clicks); + } +} + +void wlr_tablet_v2_tablet_tool_notify_proximity_out( + struct wlr_tablet_v2_tablet_tool *tool) { + if (tool->grab->interface->proximity_out) { + tool->grab->interface->proximity_out(tool->grab); + } +} + +void wlr_tablet_v2_tablet_tool_notify_button( + struct wlr_tablet_v2_tablet_tool *tool, uint32_t button, + enum zwp_tablet_pad_v2_button_state state) { + if (tool->grab->interface->button) { + tool->grab->interface->button(tool->grab, button, state); + } +} + +void wlr_tablet_tool_v2_start_grab(struct wlr_tablet_v2_tablet_tool *tool, + struct wlr_tablet_tool_v2_grab *grab) { + wlr_tablet_tool_v2_end_grab(tool); + tool->grab = grab; +} + +void wlr_tablet_tool_v2_end_grab(struct wlr_tablet_v2_tablet_tool *tool) { + if (tool->grab->interface->cancel) { + tool->grab->interface->cancel(tool->grab); + } + tool->grab = &tool->default_grab; +} + + +static void default_tool_proximity_in( + struct wlr_tablet_tool_v2_grab *grab, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface) { + wlr_send_tablet_v2_tablet_tool_proximity_in(grab->tool, tablet, surface); +} + +static void default_tool_down(struct wlr_tablet_tool_v2_grab *grab) { + wlr_send_tablet_v2_tablet_tool_down(grab->tool); +} +static void default_tool_up(struct wlr_tablet_tool_v2_grab *grab) { + wlr_send_tablet_v2_tablet_tool_up(grab->tool); +} + +static void default_tool_motion( + struct wlr_tablet_tool_v2_grab *grab, double x, double y) { + wlr_send_tablet_v2_tablet_tool_motion(grab->tool, x, y); +} + +static void default_tool_pressure( + struct wlr_tablet_tool_v2_grab *grab, double pressure) { + wlr_send_tablet_v2_tablet_tool_pressure(grab->tool, pressure); +} + +static void default_tool_distance( + struct wlr_tablet_tool_v2_grab *grab, double distance) { + wlr_send_tablet_v2_tablet_tool_distance(grab->tool, distance); +} + +static void default_tool_tilt( + struct wlr_tablet_tool_v2_grab *grab, double x, double y) { + wlr_send_tablet_v2_tablet_tool_tilt(grab->tool, x, y); +} + +static void default_tool_rotation( + struct wlr_tablet_tool_v2_grab *grab, double degrees) { + wlr_send_tablet_v2_tablet_tool_rotation(grab->tool, degrees); +} + +static void default_tool_slider( + struct wlr_tablet_tool_v2_grab *grab, double position) { + wlr_send_tablet_v2_tablet_tool_slider(grab->tool, position); +} + +static void default_tool_wheel( + struct wlr_tablet_tool_v2_grab *grab, double degrees, int32_t clicks) { + wlr_send_tablet_v2_tablet_tool_wheel(grab->tool, degrees, clicks); +} + +static void default_tool_proximity_out(struct wlr_tablet_tool_v2_grab *grab) { + wlr_send_tablet_v2_tablet_tool_proximity_out(grab->tool); +} + +static void default_tool_button( + struct wlr_tablet_tool_v2_grab *grab, uint32_t button, + enum zwp_tablet_pad_v2_button_state state) { + wlr_send_tablet_v2_tablet_tool_button(grab->tool, button, state); +} + +static void default_tool_cancel(struct wlr_tablet_tool_v2_grab *grab) { + /* Do nothing. Default grab can't be canceled */ +} + +static const struct wlr_tablet_tool_v2_grab_interface + default_tool_grab_interface = { + .proximity_in = default_tool_proximity_in, + .down = default_tool_down, + .up = default_tool_up, + .motion = default_tool_motion, + .pressure = default_tool_pressure, + .distance = default_tool_distance, + .tilt = default_tool_tilt, + .rotation = default_tool_rotation, + .slider = default_tool_slider, + .wheel = default_tool_wheel, + .proximity_out = default_tool_proximity_out, + .button = default_tool_button, + .cancel = default_tool_cancel, +}; + +struct implicit_grab_state { + struct wlr_surface *original; + bool released; + + struct wlr_surface *focused; + struct wlr_tablet_v2_tablet *tablet; +}; + +static void check_and_release_implicit_grab(struct wlr_tablet_tool_v2_grab *grab) { + struct implicit_grab_state *state = grab->data; + /* Still button or tip pressed. We should hold the grab */ + if (grab->tool->is_down || grab->tool->num_buttons > 0 || state->released) { + return; + } + + state->released = true; + + /* We should still focus the same surface. Do nothing */ + if (state->original == state->focused) { + wlr_tablet_tool_v2_end_grab(grab->tool); + return; + } + + wlr_send_tablet_v2_tablet_tool_proximity_out(grab->tool); + if (state->focused) { + wlr_send_tablet_v2_tablet_tool_proximity_in(grab->tool, + state->tablet, state->focused); + } + + wlr_tablet_tool_v2_end_grab(grab->tool); +} + +static void implicit_tool_proximity_in( + struct wlr_tablet_tool_v2_grab *grab, + struct wlr_tablet_v2_tablet *tablet, + struct wlr_surface *surface) { + + /* As long as we got an implicit grab, proximity won't change + * But should track the currently focused surface to change to it when + * the grab is released. + */ + struct implicit_grab_state *state = grab->data; + state->focused = surface; + state->tablet = tablet; +} + +static void implicit_tool_proximity_out(struct wlr_tablet_tool_v2_grab *grab) { + struct implicit_grab_state *state = grab->data; + state->focused = NULL; +} + +static void implicit_tool_down(struct wlr_tablet_tool_v2_grab *grab) { + wlr_send_tablet_v2_tablet_tool_down(grab->tool); +} + +static void implicit_tool_up(struct wlr_tablet_tool_v2_grab *grab) { + wlr_send_tablet_v2_tablet_tool_up(grab->tool); + check_and_release_implicit_grab(grab); +} + +/* Only send the motion event, when we are over the surface for now */ +static void implicit_tool_motion( + struct wlr_tablet_tool_v2_grab *grab, double x, double y) { + struct implicit_grab_state *state = grab->data; + if (state->focused != state->original) { + return; + } + + wlr_send_tablet_v2_tablet_tool_motion(grab->tool, x, y); +} + + +static void implicit_tool_button( + struct wlr_tablet_tool_v2_grab *grab, uint32_t button, + enum zwp_tablet_pad_v2_button_state state) { + wlr_send_tablet_v2_tablet_tool_button(grab->tool, button, state); + check_and_release_implicit_grab(grab); +} + +static void implicit_tool_cancel(struct wlr_tablet_tool_v2_grab *grab) { + check_and_release_implicit_grab(grab); + free(grab->data); + free(grab); +} + +static const struct wlr_tablet_tool_v2_grab_interface + implicit_tool_grab_interface = { + .proximity_in = implicit_tool_proximity_in, + .down = implicit_tool_down, + .up = implicit_tool_up, + .motion = implicit_tool_motion, + .pressure = default_tool_pressure, + .distance = default_tool_distance, + .tilt = default_tool_tilt, + .rotation = default_tool_rotation, + .slider = default_tool_slider, + .wheel = default_tool_wheel, + .proximity_out = implicit_tool_proximity_out, + .button = implicit_tool_button, + .cancel = implicit_tool_cancel, +}; + +static bool tool_has_implicit_grab(struct wlr_tablet_v2_tablet_tool *tool) { + return tool->grab->interface == &implicit_tool_grab_interface; +} + +void wlr_tablet_tool_v2_start_implicit_grab( + struct wlr_tablet_v2_tablet_tool *tool) { + if (tool_has_implicit_grab(tool) || !tool->focused_surface) { + return; + } + + /* No current implicit grab */ + if (!(tool->is_down || tool->num_buttons > 0)) { + return; + } + + struct wlr_tablet_tool_v2_grab *grab = + calloc(1, sizeof(struct wlr_tablet_tool_v2_grab)); + if (!grab) { + return; + } + + grab->interface = &implicit_tool_grab_interface; + grab->tool = tool; + struct implicit_grab_state *state = calloc(1, sizeof(struct implicit_grab_state)); + if (!state) { + free(grab); + return; + } + + state->original = tool->focused_surface; + grab->data = state; + + wlr_tablet_tool_v2_start_grab(tool, grab); +} diff --git a/types/wlr_box.c b/types/wlr_box.c new file mode 100644 index 00000000..0020b7a4 --- /dev/null +++ b/types/wlr_box.c @@ -0,0 +1,153 @@ +#include <limits.h> +#include <math.h> +#include <stdbool.h> +#include <stdlib.h> +#include <wayland-server-protocol.h> +#include <wlr/types/wlr_box.h> +#include <wlr/util/log.h> + +void wlr_box_closest_point(const struct wlr_box *box, double x, double y, + double *dest_x, double *dest_y) { + // find the closest x point + if (x < box->x) { + *dest_x = box->x; + } else if (x >= box->x + box->width) { + *dest_x = box->x + box->width - 1; + } else { + *dest_x = x; + } + + // find closest y point + if (y < box->y) { + *dest_y = box->y; + } else if (y >= box->y + box->height) { + *dest_y = box->y + box->height - 1; + } else { + *dest_y = y; + } +} + +bool wlr_box_empty(const struct wlr_box *box) { + return box == NULL || box->width <= 0 || box->height <= 0; +} + +bool wlr_box_intersection(struct wlr_box *dest, const struct wlr_box *box_a, + const struct wlr_box *box_b) { + bool a_empty = wlr_box_empty(box_a); + bool b_empty = wlr_box_empty(box_b); + + if (a_empty || b_empty) { + dest->x = 0; + dest->y = 0; + dest->width = -100; + dest->height = -100; + return false; + } + + int x1 = fmax(box_a->x, box_b->x); + int y1 = fmax(box_a->y, box_b->y); + int x2 = fmin(box_a->x + box_a->width, box_b->x + box_b->width); + int y2 = fmin(box_a->y + box_a->height, box_b->y + box_b->height); + + dest->x = x1; + dest->y = y1; + dest->width = x2 - x1; + dest->height = y2 - y1; + + return !wlr_box_empty(dest); +} + +bool wlr_box_contains_point(const struct wlr_box *box, double x, double y) { + if (wlr_box_empty(box)) { + return false; + } else { + return x >= box->x && x < box->x + box->width && + y >= box->y && y < box->y + box->height; + } +} + +void wlr_box_transform(struct wlr_box *dest, const struct wlr_box *box, + enum wl_output_transform transform, int width, int height) { + struct wlr_box src = *box; + + if (transform % 2 == 0) { + dest->width = src.width; + dest->height = src.height; + } else { + dest->width = src.height; + dest->height = src.width; + } + + switch (transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + dest->x = src.x; + dest->y = src.y; + break; + case WL_OUTPUT_TRANSFORM_90: + dest->x = src.y; + dest->y = width - src.x - src.width; + break; + case WL_OUTPUT_TRANSFORM_180: + dest->x = width - src.x - src.width; + dest->y = height - src.y - src.height; + break; + case WL_OUTPUT_TRANSFORM_270: + dest->x = height - src.y - src.height; + dest->y = src.x; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + dest->x = width - src.x - src.width; + dest->y = src.y; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + dest->x = height - src.y - src.height; + dest->y = width - src.x - src.width; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + dest->x = src.x; + dest->y = height - src.y - src.height; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + dest->x = src.y; + dest->y = src.x; + break; + } +} + +void wlr_box_rotated_bounds(struct wlr_box *dest, const struct wlr_box *box, + float rotation) { + if (rotation == 0) { + *dest = *box; + return; + } + + double ox = box->x + (double)box->width/2; + double oy = box->y + (double)box->height/2; + + double c = fabs(cos(rotation)); + double s = fabs(sin(rotation)); + + double x1 = ox + (box->x - ox) * c + (box->y - oy) * s; + double x2 = ox + + (box->x + box->width - ox) * c + + (box->y + box->height - oy) * s; + + double y1 = oy + (box->x - ox) * s + (box->y - oy) * c; + double y2 = oy + + (box->x + box->width - ox) * s + + (box->y + box->height - oy) * c; + + dest->x = floor(fmin(x1, x2)); + dest->width = ceil(fmax(x1, x2) - fmin(x1, x2)); + dest->y = floor(fmin(y1, y2)); + dest->height = ceil(fmax(y1, y2) - fmin(y1, y2)); +} + +void wlr_box_from_pixman_box32(struct wlr_box *dest, const pixman_box32_t box) { + *dest = (struct wlr_box){ + .x = box.x1, + .y = box.y1, + .width = box.x2 - box.x1, + .height = box.y2 - box.y1, + }; +} diff --git a/types/wlr_buffer.c b/types/wlr_buffer.c new file mode 100644 index 00000000..cec3475c --- /dev/null +++ b/types/wlr_buffer.c @@ -0,0 +1,205 @@ +#include <assert.h> +#include <stdlib.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_buffer.h> +#include <wlr/types/wlr_linux_dmabuf_v1.h> +#include <wlr/util/log.h> + +bool wlr_resource_is_buffer(struct wl_resource *resource) { + return strcmp(wl_resource_get_class(resource), wl_buffer_interface.name) == 0; +} + +bool wlr_buffer_get_resource_size(struct wl_resource *resource, + struct wlr_renderer *renderer, int *width, int *height) { + assert(wlr_resource_is_buffer(resource)); + + struct wl_shm_buffer *shm_buf = wl_shm_buffer_get(resource); + if (shm_buf != NULL) { + *width = wl_shm_buffer_get_width(shm_buf); + *height = wl_shm_buffer_get_height(shm_buf); + } else if (wlr_renderer_resource_is_wl_drm_buffer(renderer, + resource)) { + wlr_renderer_wl_drm_buffer_get_size(renderer, resource, + width, height); + } else if (wlr_dmabuf_v1_resource_is_buffer(resource)) { + struct wlr_dmabuf_v1_buffer *dmabuf = + wlr_dmabuf_v1_buffer_from_buffer_resource(resource); + *width = dmabuf->attributes.width; + *height = dmabuf->attributes.height; + } else { + *width = *height = 0; + return false; + } + + return true; +} + + +static void buffer_resource_handle_destroy(struct wl_listener *listener, + void *data) { + struct wlr_buffer *buffer = + wl_container_of(listener, buffer, resource_destroy); + wl_list_remove(&buffer->resource_destroy.link); + wl_list_init(&buffer->resource_destroy.link); + buffer->resource = NULL; + + // At this point, if the wl_buffer comes from linux-dmabuf or wl_drm, we + // still haven't released it (ie. we'll read it in the future) but the + // client destroyed it. Reading the texture itself should be fine because + // we still hold a reference to the DMA-BUF via the texture. However the + // client could decide to re-use the same DMA-BUF for something else, in + // which case we'll read garbage. We decide to accept this risk. +} + +struct wlr_buffer *wlr_buffer_create(struct wlr_renderer *renderer, + struct wl_resource *resource) { + assert(wlr_resource_is_buffer(resource)); + + struct wlr_texture *texture = NULL; + bool released = false; + + struct wl_shm_buffer *shm_buf = wl_shm_buffer_get(resource); + if (shm_buf != NULL) { + enum wl_shm_format fmt = wl_shm_buffer_get_format(shm_buf); + int32_t stride = wl_shm_buffer_get_stride(shm_buf); + int32_t width = wl_shm_buffer_get_width(shm_buf); + int32_t height = wl_shm_buffer_get_height(shm_buf); + + wl_shm_buffer_begin_access(shm_buf); + void *data = wl_shm_buffer_get_data(shm_buf); + texture = wlr_texture_from_pixels(renderer, fmt, stride, + width, height, data); + wl_shm_buffer_end_access(shm_buf); + + // We have uploaded the data, we don't need to access the wl_buffer + // anymore + wl_buffer_send_release(resource); + released = true; + } else if (wlr_renderer_resource_is_wl_drm_buffer(renderer, resource)) { + texture = wlr_texture_from_wl_drm(renderer, resource); + } else if (wlr_dmabuf_v1_resource_is_buffer(resource)) { + struct wlr_dmabuf_v1_buffer *dmabuf = + wlr_dmabuf_v1_buffer_from_buffer_resource(resource); + texture = wlr_texture_from_dmabuf(renderer, &dmabuf->attributes); + + // We have imported the DMA-BUF, but we need to prevent the client from + // re-using the same DMA-BUF for the next frames, so we don't release + // the buffer yet. + } else { + wlr_log(WLR_ERROR, "Cannot upload texture: unknown buffer type"); + + // Instead of just logging the error, also disconnect the client with a + // fatal protocol error so that it's clear something went wrong. + wl_resource_post_error(resource, 0, "unknown buffer type"); + return NULL; + } + + if (texture == NULL) { + wlr_log(WLR_ERROR, "Failed to upload texture"); + return NULL; + } + + struct wlr_buffer *buffer = calloc(1, sizeof(struct wlr_buffer)); + if (buffer == NULL) { + wlr_texture_destroy(texture); + return NULL; + } + buffer->resource = resource; + buffer->texture = texture; + buffer->released = released; + buffer->n_refs = 1; + + wl_resource_add_destroy_listener(resource, &buffer->resource_destroy); + buffer->resource_destroy.notify = buffer_resource_handle_destroy; + + return buffer; +} + +struct wlr_buffer *wlr_buffer_ref(struct wlr_buffer *buffer) { + buffer->n_refs++; + return buffer; +} + +void wlr_buffer_unref(struct wlr_buffer *buffer) { + if (buffer == NULL) { + return; + } + + assert(buffer->n_refs > 0); + buffer->n_refs--; + if (buffer->n_refs > 0) { + return; + } + + if (!buffer->released && buffer->resource != NULL) { + wl_buffer_send_release(buffer->resource); + } + + wl_list_remove(&buffer->resource_destroy.link); + wlr_texture_destroy(buffer->texture); + free(buffer); +} + +struct wlr_buffer *wlr_buffer_apply_damage(struct wlr_buffer *buffer, + struct wl_resource *resource, pixman_region32_t *damage) { + assert(wlr_resource_is_buffer(resource)); + + if (buffer->n_refs > 1) { + // Someone else still has a reference to the buffer + return NULL; + } + + struct wl_shm_buffer *shm_buf = wl_shm_buffer_get(resource); + struct wl_shm_buffer *old_shm_buf = wl_shm_buffer_get(buffer->resource); + if (shm_buf == NULL || old_shm_buf == NULL) { + // Uploading only damaged regions only works for wl_shm buffers and + // mutable textures (created from wl_shm buffer) + return NULL; + } + + enum wl_shm_format new_fmt = wl_shm_buffer_get_format(shm_buf); + enum wl_shm_format old_fmt = wl_shm_buffer_get_format(old_shm_buf); + if (new_fmt != old_fmt) { + // Uploading to textures can't change the format + return NULL; + } + + int32_t stride = wl_shm_buffer_get_stride(shm_buf); + int32_t width = wl_shm_buffer_get_width(shm_buf); + int32_t height = wl_shm_buffer_get_height(shm_buf); + + int32_t texture_width, texture_height; + wlr_texture_get_size(buffer->texture, &texture_width, &texture_height); + if (width != texture_width || height != texture_height) { + return NULL; + } + + wl_shm_buffer_begin_access(shm_buf); + void *data = wl_shm_buffer_get_data(shm_buf); + + int n; + pixman_box32_t *rects = pixman_region32_rectangles(damage, &n); + for (int i = 0; i < n; ++i) { + pixman_box32_t *r = &rects[i]; + if (!wlr_texture_write_pixels(buffer->texture, stride, + r->x2 - r->x1, r->y2 - r->y1, r->x1, r->y1, + r->x1, r->y1, data)) { + wl_shm_buffer_end_access(shm_buf); + return NULL; + } + } + + wl_shm_buffer_end_access(shm_buf); + + // We have uploaded the data, we don't need to access the wl_buffer + // anymore + wl_buffer_send_release(resource); + + wl_list_remove(&buffer->resource_destroy.link); + wl_resource_add_destroy_listener(resource, &buffer->resource_destroy); + buffer->resource_destroy.notify = buffer_resource_handle_destroy; + + buffer->resource = resource; + buffer->released = true; + return buffer; +} diff --git a/types/wlr_compositor.c b/types/wlr_compositor.c new file mode 100644 index 00000000..7702a778 --- /dev/null +++ b/types/wlr_compositor.c @@ -0,0 +1,245 @@ +#include <assert.h> +#include <stdlib.h> +#include <wayland-server.h> +#include <wlr/types/wlr_compositor.h> +#include <wlr/types/wlr_region.h> +#include <wlr/types/wlr_surface.h> +#include <wlr/util/log.h> +#include "util/signal.h" + +#define COMPOSITOR_VERSION 4 +#define SUBCOMPOSITOR_VERSION 1 + +extern const struct wlr_surface_role subsurface_role; + +bool wlr_surface_is_subsurface(struct wlr_surface *surface) { + return surface->role == &subsurface_role; +} + +struct wlr_subsurface *wlr_subsurface_from_wlr_surface( + struct wlr_surface *surface) { + assert(wlr_surface_is_subsurface(surface)); + return (struct wlr_subsurface *)surface->role_data; +} + +static const struct wl_subcompositor_interface subcompositor_impl; + +static struct wlr_subcompositor *subcompositor_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_subcompositor_interface, + &subcompositor_impl)); + return wl_resource_get_user_data(resource); +} + +static void subcompositor_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void subcompositor_handle_get_subsurface(struct wl_client *client, + struct wl_resource *resource, uint32_t id, + struct wl_resource *surface_resource, + struct wl_resource *parent_resource) { + struct wlr_subcompositor *subcompositor = + subcompositor_from_resource(resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + struct wlr_surface *parent = wlr_surface_from_resource(parent_resource); + + static const char msg[] = "get_subsurface: wl_subsurface@"; + + if (surface == parent) { + wl_resource_post_error(resource, + WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, + "%s%d: wl_surface@%d cannot be its own parent", + msg, id, wl_resource_get_id(surface_resource)); + return; + } + + if (wlr_surface_is_subsurface(surface) && + wlr_subsurface_from_wlr_surface(surface) != NULL) { + wl_resource_post_error(resource, + WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, + "%s%d: wl_surface@%d is already a sub-surface", + msg, id, wl_resource_get_id(surface_resource)); + return; + } + + if (wlr_surface_get_root_surface(parent) == surface) { + wl_resource_post_error(resource, + WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, + "%s%d: wl_surface@%d is an ancestor of parent", + msg, id, wl_resource_get_id(surface_resource)); + return; + } + + if (!wlr_surface_set_role(surface, &subsurface_role, NULL, + resource, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE)) { + return; + } + + wlr_subsurface_create(surface, parent, wl_resource_get_version(resource), + id, &subcompositor->subsurface_resources); +} + +static const struct wl_subcompositor_interface subcompositor_impl = { + .destroy = subcompositor_handle_destroy, + .get_subsurface = subcompositor_handle_get_subsurface, +}; + +static void subcompositor_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void subcompositor_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_subcompositor *subcompositor = data; + struct wl_resource *resource = + wl_resource_create(client, &wl_subcompositor_interface, 1, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &subcompositor_impl, + subcompositor, subcompositor_resource_destroy); + wl_list_insert(&subcompositor->resources, wl_resource_get_link(resource)); +} + +static void subcompositor_init(struct wlr_subcompositor *subcompositor, + struct wl_display *display) { + subcompositor->global = wl_global_create(display, + &wl_subcompositor_interface, SUBCOMPOSITOR_VERSION, subcompositor, + subcompositor_bind); + if (subcompositor->global == NULL) { + wlr_log_errno(WLR_ERROR, "Could not allocate subcompositor global"); + return; + } + wl_list_init(&subcompositor->resources); + wl_list_init(&subcompositor->subsurface_resources); +} + +static void subcompositor_finish(struct wlr_subcompositor *subcompositor) { + wl_global_destroy(subcompositor->global); + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, + &subcompositor->subsurface_resources) { + wl_resource_destroy(resource); + } + wl_resource_for_each_safe(resource, tmp, &subcompositor->resources) { + wl_resource_destroy(resource); + } +} + + +static const struct wl_compositor_interface compositor_impl; + +static struct wlr_compositor *compositor_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_compositor_interface, + &compositor_impl)); + return wl_resource_get_user_data(resource); +} + +static void compositor_create_surface(struct wl_client *client, + struct wl_resource *resource, uint32_t id) { + struct wlr_compositor *compositor = compositor_from_resource(resource); + + struct wlr_surface *surface = wlr_surface_create(client, + wl_resource_get_version(resource), id, compositor->renderer, + &compositor->surface_resources); + if (surface == NULL) { + return; + } + + wlr_signal_emit_safe(&compositor->events.new_surface, surface); +} + +static void compositor_create_region(struct wl_client *client, + struct wl_resource *resource, uint32_t id) { + struct wlr_compositor *compositor = compositor_from_resource(resource); + + wlr_region_create(client, 1, id, &compositor->region_resources); +} + +static const struct wl_compositor_interface compositor_impl = { + .create_surface = compositor_create_surface, + .create_region = compositor_create_region, +}; + +static void compositor_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void compositor_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_compositor *compositor = data; + assert(wl_client && compositor); + + struct wl_resource *resource = + wl_resource_create(wl_client, &wl_compositor_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(resource, &compositor_impl, + compositor, compositor_resource_destroy); + wl_list_insert(&compositor->resources, wl_resource_get_link(resource)); +} + +void wlr_compositor_destroy(struct wlr_compositor *compositor) { + if (compositor == NULL) { + return; + } + wlr_signal_emit_safe(&compositor->events.destroy, compositor); + subcompositor_finish(&compositor->subcompositor); + wl_list_remove(&compositor->display_destroy.link); + wl_global_destroy(compositor->global); + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &compositor->surface_resources) { + wl_resource_destroy(resource); + } + wl_resource_for_each_safe(resource, tmp, &compositor->region_resources) { + wl_resource_destroy(resource); + } + wl_resource_for_each_safe(resource, tmp, &compositor->resources) { + wl_resource_destroy(resource); + } + free(compositor); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_compositor *compositor = + wl_container_of(listener, compositor, display_destroy); + wlr_compositor_destroy(compositor); +} + +struct wlr_compositor *wlr_compositor_create(struct wl_display *display, + struct wlr_renderer *renderer) { + struct wlr_compositor *compositor = + calloc(1, sizeof(struct wlr_compositor)); + if (!compositor) { + wlr_log_errno(WLR_ERROR, "Could not allocate wlr compositor"); + return NULL; + } + + compositor->global = wl_global_create(display, &wl_compositor_interface, + COMPOSITOR_VERSION, compositor, compositor_bind); + if (!compositor->global) { + free(compositor); + wlr_log_errno(WLR_ERROR, "Could not allocate compositor global"); + return NULL; + } + compositor->renderer = renderer; + + wl_list_init(&compositor->resources); + wl_list_init(&compositor->surface_resources); + wl_list_init(&compositor->region_resources); + wl_signal_init(&compositor->events.new_surface); + wl_signal_init(&compositor->events.destroy); + + subcompositor_init(&compositor->subcompositor, display); + + compositor->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &compositor->display_destroy); + + return compositor; +} diff --git a/types/wlr_cursor.c b/types/wlr_cursor.c new file mode 100644 index 00000000..8094ff18 --- /dev/null +++ b/types/wlr_cursor.c @@ -0,0 +1,741 @@ +#include <assert.h> +#include <limits.h> +#include <math.h> +#include <stdlib.h> +#include <wayland-server.h> +#include <wlr/types/wlr_cursor.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/types/wlr_output_layout.h> +#include <wlr/types/wlr_output.h> +#include <wlr/util/log.h> +#include "util/signal.h" + +struct wlr_cursor_device { + struct wlr_cursor *cursor; + struct wlr_input_device *device; + struct wl_list link; + struct wlr_output *mapped_output; + struct wlr_box *mapped_box; + + struct wl_listener motion; + struct wl_listener motion_absolute; + struct wl_listener button; + struct wl_listener axis; + + struct wl_listener touch_down; + struct wl_listener touch_up; + struct wl_listener touch_motion; + struct wl_listener touch_cancel; + + struct wl_listener tablet_tool_axis; + struct wl_listener tablet_tool_proximity; + struct wl_listener tablet_tool_tip; + struct wl_listener tablet_tool_button; + + struct wl_listener destroy; +}; + +struct wlr_cursor_output_cursor { + struct wlr_cursor *cursor; + struct wlr_output_cursor *output_cursor; + struct wl_list link; + + struct wl_listener layout_output_destroy; +}; + +struct wlr_cursor_state { + struct wlr_cursor *cursor; + struct wl_list devices; // wlr_cursor_device::link + struct wl_list output_cursors; // wlr_cursor_output_cursor::link + struct wlr_output_layout *layout; + struct wlr_output *mapped_output; + struct wlr_box *mapped_box; + + struct wl_listener layout_add; + struct wl_listener layout_change; + struct wl_listener layout_destroy; +}; + +struct wlr_cursor *wlr_cursor_create(void) { + struct wlr_cursor *cur = calloc(1, sizeof(struct wlr_cursor)); + if (!cur) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_cursor"); + return NULL; + } + + cur->state = calloc(1, sizeof(struct wlr_cursor_state)); + if (!cur->state) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_cursor_state"); + free(cur); + return NULL; + } + + cur->state->cursor = cur; + cur->state->mapped_output = NULL; + + wl_list_init(&cur->state->devices); + wl_list_init(&cur->state->output_cursors); + + // pointer signals + wl_signal_init(&cur->events.motion); + wl_signal_init(&cur->events.motion_absolute); + wl_signal_init(&cur->events.button); + wl_signal_init(&cur->events.axis); + + // touch signals + wl_signal_init(&cur->events.touch_up); + wl_signal_init(&cur->events.touch_down); + wl_signal_init(&cur->events.touch_motion); + wl_signal_init(&cur->events.touch_cancel); + + // tablet tool signals + wl_signal_init(&cur->events.tablet_tool_tip); + wl_signal_init(&cur->events.tablet_tool_axis); + wl_signal_init(&cur->events.tablet_tool_button); + wl_signal_init(&cur->events.tablet_tool_proximity); + + cur->x = 100; + cur->y = 100; + + return cur; +} + +static void output_cursor_destroy( + struct wlr_cursor_output_cursor *output_cursor) { + wl_list_remove(&output_cursor->layout_output_destroy.link); + wl_list_remove(&output_cursor->link); + wlr_output_cursor_destroy(output_cursor->output_cursor); + free(output_cursor); +} + +static void cursor_detach_output_layout(struct wlr_cursor *cur) { + if (!cur->state->layout) { + return; + } + + struct wlr_cursor_output_cursor *output_cursor, *tmp; + wl_list_for_each_safe(output_cursor, tmp, &cur->state->output_cursors, + link) { + output_cursor_destroy(output_cursor); + } + + wl_list_remove(&cur->state->layout_destroy.link); + wl_list_remove(&cur->state->layout_change.link); + wl_list_remove(&cur->state->layout_add.link); + + cur->state->layout = NULL; +} + +static void cursor_device_destroy(struct wlr_cursor_device *c_device) { + struct wlr_input_device *dev = c_device->device; + if (dev->type == WLR_INPUT_DEVICE_POINTER) { + wl_list_remove(&c_device->motion.link); + wl_list_remove(&c_device->motion_absolute.link); + wl_list_remove(&c_device->button.link); + wl_list_remove(&c_device->axis.link); + } else if (dev->type == WLR_INPUT_DEVICE_TOUCH) { + wl_list_remove(&c_device->touch_down.link); + wl_list_remove(&c_device->touch_up.link); + wl_list_remove(&c_device->touch_motion.link); + wl_list_remove(&c_device->touch_cancel.link); + } else if (dev->type == WLR_INPUT_DEVICE_TABLET_TOOL) { + wl_list_remove(&c_device->tablet_tool_axis.link); + wl_list_remove(&c_device->tablet_tool_proximity.link); + wl_list_remove(&c_device->tablet_tool_tip.link); + wl_list_remove(&c_device->tablet_tool_button.link); + } + + wl_list_remove(&c_device->link); + wl_list_remove(&c_device->destroy.link); + free(c_device); +} + +void wlr_cursor_destroy(struct wlr_cursor *cur) { + cursor_detach_output_layout(cur); + + struct wlr_cursor_device *device, *device_tmp = NULL; + wl_list_for_each_safe(device, device_tmp, &cur->state->devices, link) { + cursor_device_destroy(device); + } + + free(cur->state); + free(cur); +} + +static struct wlr_cursor_device *get_cursor_device(struct wlr_cursor *cur, + struct wlr_input_device *device) { + struct wlr_cursor_device *c_device, *ret = NULL; + wl_list_for_each(c_device, &cur->state->devices, link) { + if (c_device->device == device) { + ret = c_device; + break; + } + } + + return ret; +} + +static void cursor_warp_unchecked(struct wlr_cursor *cur, + double lx, double ly) { + assert(cur->state->layout); + + struct wlr_cursor_output_cursor *output_cursor; + wl_list_for_each(output_cursor, &cur->state->output_cursors, link) { + double output_x = lx, output_y = ly; + wlr_output_layout_output_coords(cur->state->layout, + output_cursor->output_cursor->output, &output_x, &output_y); + wlr_output_cursor_move(output_cursor->output_cursor, + output_x, output_y); + } + + cur->x = lx; + cur->y = ly; +} + +/** + * Get the most specific mapping box for the device in this order: + * + * 1. device geometry mapping + * 2. device output mapping + * 3. cursor geometry mapping + * 4. cursor output mapping + * + * Absolute movement for touch and pen devices will be relative to this box and + * pointer movement will be constrained to this box. + * + * If none of these are set, returns NULL and absolute movement should be + * relative to the extents of the layout. + */ +static struct wlr_box *get_mapping(struct wlr_cursor *cur, + struct wlr_input_device *dev) { + assert(cur->state->layout); + struct wlr_cursor_device *c_device = get_cursor_device(cur, dev); + + if (c_device) { + if (c_device->mapped_box) { + return c_device->mapped_box; + } + if (c_device->mapped_output) { + return wlr_output_layout_get_box(cur->state->layout, + c_device->mapped_output); + } + } + + if (cur->state->mapped_box) { + return cur->state->mapped_box; + } + if (cur->state->mapped_output) { + return wlr_output_layout_get_box(cur->state->layout, + cur->state->mapped_output); + } + + return NULL; +} + +bool wlr_cursor_warp(struct wlr_cursor *cur, struct wlr_input_device *dev, + double lx, double ly) { + assert(cur->state->layout); + + bool result = false; + struct wlr_box *mapping = get_mapping(cur, dev); + if (mapping) { + result = wlr_box_contains_point(mapping, lx, ly); + } else { + result = wlr_output_layout_contains_point(cur->state->layout, NULL, + lx, ly); + } + + if (result) { + cursor_warp_unchecked(cur, lx, ly); + } + + return result; +} + +void wlr_cursor_warp_closest(struct wlr_cursor *cur, + struct wlr_input_device *dev, double lx, double ly) { + struct wlr_box *mapping = get_mapping(cur, dev); + if (mapping) { + wlr_box_closest_point(mapping, lx, ly, &lx, &ly); + } else { + wlr_output_layout_closest_point(cur->state->layout, NULL, lx, ly, + &lx, &ly); + } + + cursor_warp_unchecked(cur, lx, ly); +} + +void wlr_cursor_absolute_to_layout_coords(struct wlr_cursor *cur, + struct wlr_input_device *dev, double x, double y, + double *lx, double *ly) { + assert(cur->state->layout); + + struct wlr_box *mapping = get_mapping(cur, dev); + if (!mapping) { + mapping = wlr_output_layout_get_box(cur->state->layout, NULL); + } + + *lx = !isnan(x) ? mapping->width * x + mapping->x : cur->x; + *ly = !isnan(y) ? mapping->height * y + mapping->y : cur->y; +} + +void wlr_cursor_warp_absolute(struct wlr_cursor *cur, + struct wlr_input_device *dev, double x, double y) { + assert(cur->state->layout); + + double lx, ly; + wlr_cursor_absolute_to_layout_coords(cur, dev, x, y, &lx, &ly); + + wlr_cursor_warp_closest(cur, dev, lx, ly); +} + +void wlr_cursor_move(struct wlr_cursor *cur, struct wlr_input_device *dev, + double delta_x, double delta_y) { + assert(cur->state->layout); + + double lx = !isnan(delta_x) ? cur->x + delta_x : cur->x; + double ly = !isnan(delta_y) ? cur->y + delta_y : cur->y; + + wlr_cursor_warp_closest(cur, dev, lx, ly); +} + +void wlr_cursor_set_image(struct wlr_cursor *cur, const uint8_t *pixels, + int32_t stride, uint32_t width, uint32_t height, int32_t hotspot_x, + int32_t hotspot_y, float scale) { + struct wlr_cursor_output_cursor *output_cursor; + wl_list_for_each(output_cursor, &cur->state->output_cursors, link) { + float output_scale = output_cursor->output_cursor->output->scale; + if (scale > 0 && output_scale != scale) { + continue; + } + + wlr_output_cursor_set_image(output_cursor->output_cursor, pixels, + stride, width, height, hotspot_x, hotspot_y); + } +} + +void wlr_cursor_set_surface(struct wlr_cursor *cur, struct wlr_surface *surface, + int32_t hotspot_x, int32_t hotspot_y) { + struct wlr_cursor_output_cursor *output_cursor; + wl_list_for_each(output_cursor, &cur->state->output_cursors, link) { + wlr_output_cursor_set_surface(output_cursor->output_cursor, surface, + hotspot_x, hotspot_y); + } +} + +static void handle_pointer_motion(struct wl_listener *listener, void *data) { + struct wlr_event_pointer_motion *event = data; + struct wlr_cursor_device *device = + wl_container_of(listener, device, motion); + wlr_signal_emit_safe(&device->cursor->events.motion, event); +} + +static void apply_output_transform(double *x, double *y, + enum wl_output_transform transform) { + double dx = 0.0, dy = 0.0; + double width = 1.0, height = 1.0; + + switch (transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + dx = *x; + dy = *y; + break; + case WL_OUTPUT_TRANSFORM_90: + dx = *y; + dy = width - *x; + break; + case WL_OUTPUT_TRANSFORM_180: + dx = width - *x; + dy = height - *y; + break; + case WL_OUTPUT_TRANSFORM_270: + dx = height - *y; + dy = *x; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + dx = width - *x; + dy = *y; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + dx = height - *y; + dy = width - *x; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + dx = *x; + dy = height - *y; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + dx = *y; + dy = *x; + break; + } + *x = dx; + *y = dy; +} + + +static struct wlr_output *get_mapped_output(struct wlr_cursor_device *cursor_device) { + if (cursor_device->mapped_output) { + return cursor_device->mapped_output; + } + + struct wlr_cursor *cursor = cursor_device->cursor; + assert(cursor); + if (cursor->state->mapped_output) { + return cursor->state->mapped_output; + } + return NULL; +} + + +static void handle_pointer_motion_absolute(struct wl_listener *listener, + void *data) { + struct wlr_event_pointer_motion_absolute *event = data; + struct wlr_cursor_device *device = + wl_container_of(listener, device, motion_absolute); + + struct wlr_output *output = + get_mapped_output(device); + if (output) { + apply_output_transform(&event->x, &event->y, output->transform); + } + wlr_signal_emit_safe(&device->cursor->events.motion_absolute, event); +} + +static void handle_pointer_button(struct wl_listener *listener, void *data) { + struct wlr_event_pointer_button *event = data; + struct wlr_cursor_device *device = + wl_container_of(listener, device, button); + wlr_signal_emit_safe(&device->cursor->events.button, event); +} + +static void handle_pointer_axis(struct wl_listener *listener, void *data) { + struct wlr_event_pointer_axis *event = data; + struct wlr_cursor_device *device = wl_container_of(listener, device, axis); + wlr_signal_emit_safe(&device->cursor->events.axis, event); +} + +static void handle_touch_up(struct wl_listener *listener, void *data) { + struct wlr_event_touch_up *event = data; + struct wlr_cursor_device *device; + device = wl_container_of(listener, device, touch_up); + wlr_signal_emit_safe(&device->cursor->events.touch_up, event); +} + +static void handle_touch_down(struct wl_listener *listener, void *data) { + struct wlr_event_touch_down *event = data; + struct wlr_cursor_device *device; + device = wl_container_of(listener, device, touch_down); + + struct wlr_output *output = + get_mapped_output(device); + if (output) { + apply_output_transform(&event->x, &event->y, output->transform); + } + wlr_signal_emit_safe(&device->cursor->events.touch_down, event); +} + +static void handle_touch_motion(struct wl_listener *listener, void *data) { + struct wlr_event_touch_motion *event = data; + struct wlr_cursor_device *device; + device = wl_container_of(listener, device, touch_motion); + + struct wlr_output *output = + get_mapped_output(device); + if (output) { + apply_output_transform(&event->x, &event->y, output->transform); + } + wlr_signal_emit_safe(&device->cursor->events.touch_motion, event); +} + +static void handle_touch_cancel(struct wl_listener *listener, void *data) { + struct wlr_event_touch_cancel *event = data; + struct wlr_cursor_device *device; + device = wl_container_of(listener, device, touch_cancel); + wlr_signal_emit_safe(&device->cursor->events.touch_cancel, event); +} + +static void handle_tablet_tool_tip(struct wl_listener *listener, void *data) { + struct wlr_event_tablet_tool_tip *event = data; + struct wlr_cursor_device *device; + device = wl_container_of(listener, device, tablet_tool_tip); + + struct wlr_output *output = + get_mapped_output(device); + if (output) { + apply_output_transform(&event->x, &event->y, output->transform); + } + wlr_signal_emit_safe(&device->cursor->events.tablet_tool_tip, event); +} + +static void handle_tablet_tool_axis(struct wl_listener *listener, void *data) { + struct wlr_event_tablet_tool_axis *event = data; + struct wlr_cursor_device *device; + device = wl_container_of(listener, device, tablet_tool_axis); + + struct wlr_output *output = + get_mapped_output(device); + if (output) { + apply_output_transform(&event->x, &event->y, output->transform); + } + wlr_signal_emit_safe(&device->cursor->events.tablet_tool_axis, event); +} + +static void handle_tablet_tool_button(struct wl_listener *listener, + void *data) { + struct wlr_event_tablet_tool_button *event = data; + struct wlr_cursor_device *device; + device = wl_container_of(listener, device, tablet_tool_button); + wlr_signal_emit_safe(&device->cursor->events.tablet_tool_button, event); +} + +static void handle_tablet_tool_proximity(struct wl_listener *listener, + void *data) { + struct wlr_event_tablet_tool_proximity *event = data; + struct wlr_cursor_device *device; + device = wl_container_of(listener, device, tablet_tool_proximity); + + struct wlr_output *output = + get_mapped_output(device); + if (output) { + apply_output_transform(&event->x, &event->y, output->transform); + } + wlr_signal_emit_safe(&device->cursor->events.tablet_tool_proximity, event); +} + +static void handle_device_destroy(struct wl_listener *listener, void *data) { + struct wlr_cursor_device *c_device; + c_device = wl_container_of(listener, c_device, destroy); + wlr_cursor_detach_input_device(c_device->cursor, c_device->device); +} + +static struct wlr_cursor_device *cursor_device_create( + struct wlr_cursor *cursor, struct wlr_input_device *device) { + struct wlr_cursor_device *c_device = + calloc(1, sizeof(struct wlr_cursor_device)); + if (!c_device) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_cursor_device"); + return NULL; + } + + c_device->cursor = cursor; + c_device->device = device; + + // listen to events + wl_signal_add(&device->events.destroy, &c_device->destroy); + c_device->destroy.notify = handle_device_destroy; + + if (device->type == WLR_INPUT_DEVICE_POINTER) { + wl_signal_add(&device->pointer->events.motion, &c_device->motion); + c_device->motion.notify = handle_pointer_motion; + + wl_signal_add(&device->pointer->events.motion_absolute, + &c_device->motion_absolute); + c_device->motion_absolute.notify = handle_pointer_motion_absolute; + + wl_signal_add(&device->pointer->events.button, &c_device->button); + c_device->button.notify = handle_pointer_button; + + wl_signal_add(&device->pointer->events.axis, &c_device->axis); + c_device->axis.notify = handle_pointer_axis; + } else if (device->type == WLR_INPUT_DEVICE_TOUCH) { + wl_signal_add(&device->touch->events.motion, &c_device->touch_motion); + c_device->touch_motion.notify = handle_touch_motion; + + wl_signal_add(&device->touch->events.down, &c_device->touch_down); + c_device->touch_down.notify = handle_touch_down; + + wl_signal_add(&device->touch->events.up, &c_device->touch_up); + c_device->touch_up.notify = handle_touch_up; + + wl_signal_add(&device->touch->events.cancel, &c_device->touch_cancel); + c_device->touch_cancel.notify = handle_touch_cancel; + } else if (device->type == WLR_INPUT_DEVICE_TABLET_TOOL) { + wl_signal_add(&device->tablet->events.tip, + &c_device->tablet_tool_tip); + c_device->tablet_tool_tip.notify = handle_tablet_tool_tip; + + wl_signal_add(&device->tablet->events.proximity, + &c_device->tablet_tool_proximity); + c_device->tablet_tool_proximity.notify = handle_tablet_tool_proximity; + + wl_signal_add(&device->tablet->events.axis, + &c_device->tablet_tool_axis); + c_device->tablet_tool_axis.notify = handle_tablet_tool_axis; + + wl_signal_add(&device->tablet->events.button, + &c_device->tablet_tool_button); + c_device->tablet_tool_button.notify = handle_tablet_tool_button; + } + + wl_list_insert(&cursor->state->devices, &c_device->link); + + return c_device; +} + +void wlr_cursor_attach_input_device(struct wlr_cursor *cur, + struct wlr_input_device *dev) { + if (dev->type != WLR_INPUT_DEVICE_POINTER && + dev->type != WLR_INPUT_DEVICE_TOUCH && + dev->type != WLR_INPUT_DEVICE_TABLET_TOOL) { + wlr_log(WLR_ERROR, "only device types of pointer, touch or tablet tool" + "are supported"); + return; + } + + // make sure it is not already attached + struct wlr_cursor_device *_dev; + wl_list_for_each(_dev, &cur->state->devices, link) { + if (_dev->device == dev) { + return; + } + } + + cursor_device_create(cur, dev); +} + +void wlr_cursor_detach_input_device(struct wlr_cursor *cur, + struct wlr_input_device *dev) { + struct wlr_cursor_device *c_device, *tmp = NULL; + wl_list_for_each_safe(c_device, tmp, &cur->state->devices, link) { + if (c_device->device == dev) { + cursor_device_destroy(c_device); + } + } +} + +static void handle_layout_destroy(struct wl_listener *listener, void *data) { + struct wlr_cursor_state *state = + wl_container_of(listener, state, layout_destroy); + cursor_detach_output_layout(state->cursor); +} + +static void handle_layout_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_cursor_output_cursor *output_cursor = + wl_container_of(listener, output_cursor, layout_output_destroy); + //struct wlr_output_layout_output *l_output = data; + output_cursor_destroy(output_cursor); +} + +static void layout_add(struct wlr_cursor_state *state, + struct wlr_output_layout_output *l_output) { + struct wlr_cursor_output_cursor *output_cursor; + wl_list_for_each(output_cursor, &state->output_cursors, link) { + if (output_cursor->output_cursor->output == l_output->output) { + return; // already added + } + } + + output_cursor = calloc(1, sizeof(struct wlr_cursor_output_cursor)); + if (output_cursor == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_cursor_output_cursor"); + return; + } + output_cursor->cursor = state->cursor; + + output_cursor->output_cursor = wlr_output_cursor_create(l_output->output); + if (output_cursor->output_cursor == NULL) { + wlr_log(WLR_ERROR, "Failed to create wlr_output_cursor"); + free(output_cursor); + return; + } + + output_cursor->layout_output_destroy.notify = handle_layout_output_destroy; + wl_signal_add(&l_output->events.destroy, + &output_cursor->layout_output_destroy); + + wl_list_insert(&state->output_cursors, &output_cursor->link); +} + +static void handle_layout_add(struct wl_listener *listener, void *data) { + struct wlr_cursor_state *state = + wl_container_of(listener, state, layout_add); + struct wlr_output_layout_output *l_output = data; + layout_add(state, l_output); +} + +static void handle_layout_change(struct wl_listener *listener, void *data) { + struct wlr_cursor_state *state = + wl_container_of(listener, state, layout_change); + struct wlr_output_layout *layout = data; + + if (!wlr_output_layout_contains_point(layout, NULL, state->cursor->x, + state->cursor->y)) { + // the output we were on has gone away so go to the closest boundary + // point + double x, y; + wlr_output_layout_closest_point(layout, NULL, state->cursor->x, + state->cursor->y, &x, &y); + + cursor_warp_unchecked(state->cursor, x, y); + } +} + +void wlr_cursor_attach_output_layout(struct wlr_cursor *cur, + struct wlr_output_layout *l) { + cursor_detach_output_layout(cur); + + if (l == NULL) { + return; + } + + wl_signal_add(&l->events.add, &cur->state->layout_add); + cur->state->layout_add.notify = handle_layout_add; + wl_signal_add(&l->events.change, &cur->state->layout_change); + cur->state->layout_change.notify = handle_layout_change; + wl_signal_add(&l->events.destroy, &cur->state->layout_destroy); + cur->state->layout_destroy.notify = handle_layout_destroy; + + cur->state->layout = l; + + struct wlr_output_layout_output *l_output; + wl_list_for_each(l_output, &l->outputs, link) { + layout_add(cur->state, l_output); + } +} + +void wlr_cursor_map_to_output(struct wlr_cursor *cur, + struct wlr_output *output) { + cur->state->mapped_output = output; +} + +void wlr_cursor_map_input_to_output(struct wlr_cursor *cur, + struct wlr_input_device *dev, struct wlr_output *output) { + struct wlr_cursor_device *c_device = get_cursor_device(cur, dev); + if (!c_device) { + wlr_log(WLR_ERROR, "Cannot map device \"%s\" to output" + "(not found in this cursor)", dev->name); + return; + } + + c_device->mapped_output = output; +} + +void wlr_cursor_map_to_region(struct wlr_cursor *cur, + struct wlr_box *box) { + if (box && wlr_box_empty(box)) { + wlr_log(WLR_ERROR, "cannot map cursor to an empty region"); + return; + } + + cur->state->mapped_box = box; +} + +void wlr_cursor_map_input_to_region(struct wlr_cursor *cur, + struct wlr_input_device *dev, struct wlr_box *box) { + if (box && wlr_box_empty(box)) { + wlr_log(WLR_ERROR, "cannot map device \"%s\" input to an empty region", + dev->name); + return; + } + + struct wlr_cursor_device *c_device = get_cursor_device(cur, dev); + if (!c_device) { + wlr_log(WLR_ERROR, "Cannot map device \"%s\" to geometry (not found in" + "this cursor)", dev->name); + return; + } + + c_device->mapped_box = box; +} diff --git a/types/wlr_export_dmabuf_v1.c b/types/wlr_export_dmabuf_v1.c new file mode 100644 index 00000000..3af7e69f --- /dev/null +++ b/types/wlr_export_dmabuf_v1.c @@ -0,0 +1,228 @@ +#include <assert.h> +#include <stdlib.h> +#include <unistd.h> +#include <wlr/interfaces/wlr_output.h> +#include <wlr/render/dmabuf.h> +#include <wlr/types/wlr_export_dmabuf_v1.h> +#include <wlr/types/wlr_output.h> +#include <wlr/util/log.h> +#include "util/signal.h" +#include "wlr-export-dmabuf-unstable-v1-protocol.h" + +#define EXPORT_DMABUF_MANAGER_VERSION 1 + + +static const struct zwlr_export_dmabuf_frame_v1_interface frame_impl; + +static struct wlr_export_dmabuf_frame_v1 *frame_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_export_dmabuf_frame_v1_interface, &frame_impl)); + return wl_resource_get_user_data(resource); +} + +static void frame_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwlr_export_dmabuf_frame_v1_interface frame_impl = { + .destroy = frame_handle_destroy, +}; + +static void frame_destroy(struct wlr_export_dmabuf_frame_v1 *frame) { + if (frame == NULL) { + return; + } + if (frame->cursor_locked) { + wlr_output_lock_software_cursors(frame->output, false); + } + wl_list_remove(&frame->link); + wl_list_remove(&frame->output_swap_buffers.link); + wlr_dmabuf_attributes_finish(&frame->attribs); + // Make the frame resource inert + wl_resource_set_user_data(frame->resource, NULL); + free(frame); +} + +static void frame_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_export_dmabuf_frame_v1 *frame = frame_from_resource(resource); + frame_destroy(frame); +} + +static void frame_output_handle_swap_buffers(struct wl_listener *listener, + void *data) { + struct wlr_export_dmabuf_frame_v1 *frame = + wl_container_of(listener, frame, output_swap_buffers); + struct wlr_output_event_swap_buffers *event = data; + + wl_list_remove(&frame->output_swap_buffers.link); + wl_list_init(&frame->output_swap_buffers.link); + + time_t tv_sec = event->when->tv_sec; + uint32_t tv_sec_hi = (sizeof(tv_sec) > 4) ? tv_sec >> 32 : 0; + uint32_t tv_sec_lo = tv_sec & 0xFFFFFFFF; + zwlr_export_dmabuf_frame_v1_send_ready(frame->resource, + tv_sec_hi, tv_sec_lo, event->when->tv_nsec); + frame_destroy(frame); +} + + +static const struct zwlr_export_dmabuf_manager_v1_interface manager_impl; + +static struct wlr_export_dmabuf_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_export_dmabuf_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void manager_handle_capture_output(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + int32_t overlay_cursor, struct wl_resource *output_resource) { + struct wlr_export_dmabuf_manager_v1 *manager = + manager_from_resource(manager_resource); + struct wlr_output *output = wlr_output_from_resource(output_resource); + + struct wlr_export_dmabuf_frame_v1 *frame = + calloc(1, sizeof(struct wlr_export_dmabuf_frame_v1)); + if (frame == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + frame->manager = manager; + frame->output = output; + wl_list_init(&frame->output_swap_buffers.link); + + uint32_t version = wl_resource_get_version(manager_resource); + frame->resource = wl_resource_create(client, + &zwlr_export_dmabuf_frame_v1_interface, version, id); + if (frame->resource == NULL) { + wl_client_post_no_memory(client); + free(frame); + return; + } + wl_resource_set_implementation(frame->resource, &frame_impl, frame, + frame_handle_resource_destroy); + + wl_list_insert(&manager->frames, &frame->link); + + if (!output->impl->export_dmabuf) { + zwlr_export_dmabuf_frame_v1_send_cancel(frame->resource, + ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERMANENT); + frame_destroy(frame); + return; + } + + struct wlr_dmabuf_attributes *attribs = &frame->attribs; + if (!wlr_output_export_dmabuf(output, attribs)) { + zwlr_export_dmabuf_frame_v1_send_cancel(frame->resource, + ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_TEMPORARY); + frame_destroy(frame); + return; + } + + if (overlay_cursor) { + wlr_output_lock_software_cursors(frame->output, true); + frame->cursor_locked = true; + } + + uint32_t frame_flags = ZWLR_EXPORT_DMABUF_FRAME_V1_FLAGS_TRANSIENT; + uint32_t mod_high = attribs->modifier >> 32; + uint32_t mod_low = attribs->modifier & 0xFFFFFFFF; + + zwlr_export_dmabuf_frame_v1_send_frame(frame->resource, + output->width, output->height, 0, 0, attribs->flags, frame_flags, + attribs->format, mod_high, mod_low, attribs->n_planes); + + for (int i = 0; i < attribs->n_planes; ++i) { + off_t size = lseek(attribs->fd[i], 0, SEEK_END); + + zwlr_export_dmabuf_frame_v1_send_object(frame->resource, i, + attribs->fd[i], size, attribs->offset[i], attribs->stride[i], i); + } + + wl_list_remove(&frame->output_swap_buffers.link); + wl_signal_add(&output->events.swap_buffers, &frame->output_swap_buffers); + frame->output_swap_buffers.notify = frame_output_handle_swap_buffers; +} + +static void manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct zwlr_export_dmabuf_manager_v1_interface manager_impl = { + .capture_output = manager_handle_capture_output, + .destroy = manager_handle_destroy, +}; + +static void manager_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void manager_bind(struct wl_client *client, void *data, uint32_t version, + uint32_t id) { + struct wlr_export_dmabuf_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwlr_export_dmabuf_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, + manager_handle_resource_destroy); + + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_export_dmabuf_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wlr_export_dmabuf_manager_v1_destroy(manager); +} + +struct wlr_export_dmabuf_manager_v1 *wlr_export_dmabuf_manager_v1_create( + struct wl_display *display) { + struct wlr_export_dmabuf_manager_v1 *manager = + calloc(1, sizeof(struct wlr_export_dmabuf_manager_v1)); + if (manager == NULL) { + return NULL; + } + wl_list_init(&manager->resources); + wl_list_init(&manager->frames); + wl_signal_init(&manager->events.destroy); + + manager->global = wl_global_create(display, + &zwlr_export_dmabuf_manager_v1_interface, EXPORT_DMABUF_MANAGER_VERSION, + manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +void wlr_export_dmabuf_manager_v1_destroy( + struct wlr_export_dmabuf_manager_v1 *manager) { + if (manager == NULL) { + return; + } + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + struct wl_resource *resource, *resource_tmp; + wl_resource_for_each_safe(resource, resource_tmp, &manager->resources) { + wl_resource_destroy(resource); + } + struct wlr_export_dmabuf_frame_v1 *frame, *frame_tmp; + wl_list_for_each_safe(frame, frame_tmp, &manager->frames, link) { + wl_resource_destroy(frame->resource); + } + free(manager); +} diff --git a/types/wlr_foreign_toplevel_management_v1.c b/types/wlr_foreign_toplevel_management_v1.c new file mode 100644 index 00000000..7570716c --- /dev/null +++ b/types/wlr_foreign_toplevel_management_v1.c @@ -0,0 +1,578 @@ +#define _POSIX_C_SOURCE 200809L + +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <wlr/types/wlr_foreign_toplevel_management_v1.h> +#include <wlr/types/wlr_seat.h> +#include <wlr/util/log.h> +#include "util/signal.h" +#include "wlr-foreign-toplevel-management-unstable-v1-protocol.h" + +#define FOREIGN_TOPLEVEL_MANAGEMENT_V1_VERSION 1 + +static const struct zwlr_foreign_toplevel_handle_v1_interface toplevel_handle_impl; + +static struct wlr_foreign_toplevel_handle_v1 *toplevel_handle_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_foreign_toplevel_handle_v1_interface, + &toplevel_handle_impl)); + return wl_resource_get_user_data(resource); +} + +static void toplevel_handle_send_maximized_event(struct wl_resource *resource, + bool state) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = + toplevel_handle_from_resource(resource); + if (!toplevel) { + return; + } + struct wlr_foreign_toplevel_handle_v1_maximized_event event = { + .toplevel = toplevel, + .maximized = state, + }; + wlr_signal_emit_safe(&toplevel->events.request_maximize, &event); +} + +void foreign_toplevel_handle_set_maximized(struct wl_client *client, + struct wl_resource *resource) { + toplevel_handle_send_maximized_event(resource, true); +} + +void foreign_toplevel_handle_unset_maximized(struct wl_client *client, + struct wl_resource *resource) { + toplevel_handle_send_maximized_event(resource, false); +} + +static void toplevel_send_minimized_event(struct wl_resource *resource, + bool state) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = + toplevel_handle_from_resource(resource); + if (!toplevel) { + return; + } + + struct wlr_foreign_toplevel_handle_v1_minimized_event event = { + .toplevel = toplevel, + .minimized = state, + }; + wlr_signal_emit_safe(&toplevel->events.request_minimize, &event); +} + +static void foreign_toplevel_handle_set_minimized(struct wl_client *client, + struct wl_resource *resource) { + toplevel_send_minimized_event(resource, true); +} + +static void foreign_toplevel_handle_unset_minimized(struct wl_client *client, + struct wl_resource *resource) { + toplevel_send_minimized_event(resource, false); +} + +static void foreign_toplevel_handle_activate(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = + toplevel_handle_from_resource(resource); + if (!toplevel) { + return; + } + + struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat); + struct wlr_foreign_toplevel_handle_v1_activated_event event = { + .toplevel = toplevel, + .seat = seat_client->seat, + }; + wlr_signal_emit_safe(&toplevel->events.request_activate, &event); +} + +static void foreign_toplevel_handle_close(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = + toplevel_handle_from_resource(resource); + if (!toplevel) { + return; + } + wlr_signal_emit_safe(&toplevel->events.request_close, toplevel); +} + +static void foreign_toplevel_handle_set_rectangle(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *surface, + int32_t x, int32_t y, int32_t width, int32_t height) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = + toplevel_handle_from_resource(resource); + if (!toplevel) { + return; + } + + if (width < 0 || height < 0) { + wl_resource_post_error(resource, + ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_ERROR_INVALID_RECTANGLE, + "invalid rectangle passed to set_rectangle: width/height < 0"); + return; + } + + struct wlr_foreign_toplevel_handle_v1_set_rectangle_event event = { + .toplevel = toplevel, + .surface = wlr_surface_from_resource(surface), + .x = x, + .y = y, + .width = width, + .height = height, + }; + wlr_signal_emit_safe(&toplevel->events.set_rectangle, &event); +} + +static void foreign_toplevel_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwlr_foreign_toplevel_handle_v1_interface toplevel_handle_impl = { + .set_maximized = foreign_toplevel_handle_set_maximized, + .unset_maximized = foreign_toplevel_handle_unset_maximized, + .set_minimized = foreign_toplevel_handle_set_minimized, + .unset_minimized = foreign_toplevel_handle_unset_minimized, + .activate = foreign_toplevel_handle_activate, + .close = foreign_toplevel_handle_close, + .set_rectangle = foreign_toplevel_handle_set_rectangle, + .destroy = foreign_toplevel_handle_destroy +}; + +static void toplevel_idle_send_done(void *data) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = data; + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + zwlr_foreign_toplevel_handle_v1_send_done(resource); + } + + toplevel->idle_source = NULL; +} + +static void toplevel_update_idle_source( + struct wlr_foreign_toplevel_handle_v1 *toplevel) { + if (toplevel->idle_source) { + return; + } + + toplevel->idle_source = wl_event_loop_add_idle(toplevel->manager->event_loop, + toplevel_idle_send_done, toplevel); +} + +void wlr_foreign_toplevel_handle_v1_set_title( + struct wlr_foreign_toplevel_handle_v1 *toplevel, const char *title) { + free(toplevel->title); + toplevel->title = strdup(title); + + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + zwlr_foreign_toplevel_handle_v1_send_title(resource, title); + } + + toplevel_update_idle_source(toplevel); +} + +void wlr_foreign_toplevel_handle_v1_set_app_id( + struct wlr_foreign_toplevel_handle_v1 *toplevel, const char *app_id) { + free(toplevel->app_id); + toplevel->app_id = strdup(app_id); + + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + zwlr_foreign_toplevel_handle_v1_send_app_id(resource, app_id); + } + + toplevel_update_idle_source(toplevel); +} + +static void send_output_to_resource(struct wl_resource *resource, + struct wlr_output *output, bool enter) { + struct wl_client *client = wl_resource_get_client(resource); + struct wl_resource *output_resource; + + wl_resource_for_each(output_resource, &output->resources) { + if (wl_resource_get_client(output_resource) == client) { + if (enter) { + zwlr_foreign_toplevel_handle_v1_send_output_enter(resource, + output_resource); + } else { + zwlr_foreign_toplevel_handle_v1_send_output_leave(resource, + output_resource); + } + } + } +} + +static void toplevel_send_output(struct wlr_foreign_toplevel_handle_v1 *toplevel, + struct wlr_output *output, bool enter) { + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + send_output_to_resource(resource, output, enter); + } + + toplevel_update_idle_source(toplevel); +} + +static void toplevel_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output = + wl_container_of(listener, toplevel_output, output_destroy); + wlr_foreign_toplevel_handle_v1_output_leave(toplevel_output->toplevel, + toplevel_output->output); +} + +void wlr_foreign_toplevel_handle_v1_output_enter( + struct wlr_foreign_toplevel_handle_v1 *toplevel, + struct wlr_output *output) { + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output; + wl_list_for_each(toplevel_output, &toplevel->outputs, link) { + if (toplevel_output->output == output) { + return; // we have already sent output_enter event + } + } + + toplevel_output = + calloc(1, sizeof(struct wlr_foreign_toplevel_handle_v1_output)); + if (!toplevel_output) { + wlr_log(WLR_ERROR, "failed to allocate memory for toplevel output"); + return; + } + + toplevel_output->output = output; + toplevel_output->toplevel = toplevel; + wl_list_insert(&toplevel->outputs, &toplevel_output->link); + + toplevel_output->output_destroy.notify = toplevel_handle_output_destroy; + wl_signal_add(&output->events.destroy, &toplevel_output->output_destroy); + + toplevel_send_output(toplevel, output, true); +} + +static void toplevel_output_destroy( + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output) { + wl_list_remove(&toplevel_output->link); + wl_list_remove(&toplevel_output->output_destroy.link); + free(toplevel_output); +} + +void wlr_foreign_toplevel_handle_v1_output_leave( + struct wlr_foreign_toplevel_handle_v1 *toplevel, + struct wlr_output *output) { + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output_iterator; + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output = NULL; + + wl_list_for_each(toplevel_output_iterator, &toplevel->outputs, link) { + if (toplevel_output_iterator->output == output) { + toplevel_output = toplevel_output_iterator; + break; + } + } + + if (toplevel_output) { + toplevel_send_output(toplevel, output, false); + toplevel_output_destroy(toplevel_output); + } else { + // XXX: log an error? crash? + } +} + +static bool fill_array_from_toplevel_state(struct wl_array *array, + uint32_t state) { + if (state & WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED) { + uint32_t *index = wl_array_add(array, sizeof(uint32_t)); + if (index == NULL) { + return false; + } + *index = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED; + } + if (state & WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED) { + uint32_t *index = wl_array_add(array, sizeof(uint32_t)); + if (index == NULL) { + return false; + } + *index = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED; + } + if (state & WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) { + uint32_t *index = wl_array_add(array, sizeof(uint32_t)); + if (index == NULL) { + return false; + } + *index = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED; + } + + return true; +} + +static void toplevel_send_state(struct wlr_foreign_toplevel_handle_v1 *toplevel) { + struct wl_array states; + wl_array_init(&states); + bool r = fill_array_from_toplevel_state(&states, toplevel->state); + if (!r) { + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + wl_resource_post_no_memory(resource); + } + + wl_array_release(&states); + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &toplevel->resources) { + zwlr_foreign_toplevel_handle_v1_send_state(resource, &states); + } + + wl_array_release(&states); + toplevel_update_idle_source(toplevel); +} + +void wlr_foreign_toplevel_handle_v1_set_maximized( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool maximized) { + if (maximized) { + toplevel->state |= WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED; + } else { + toplevel->state &= ~WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED; + } + toplevel_send_state(toplevel); +} + +void wlr_foreign_toplevel_handle_v1_set_minimized( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool minimized) { + if (minimized) { + toplevel->state |= WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED; + } else { + toplevel->state &= ~WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED; + } + toplevel_send_state(toplevel); +} + +void wlr_foreign_toplevel_handle_v1_set_activated( + struct wlr_foreign_toplevel_handle_v1 *toplevel, bool activated) { + if (activated) { + toplevel->state |= WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED; + } else { + toplevel->state &= ~WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED; + } + toplevel_send_state(toplevel); +} + +void wlr_foreign_toplevel_handle_v1_destroy( + struct wlr_foreign_toplevel_handle_v1 *toplevel) { + if (!toplevel) { + return; + } + + wlr_signal_emit_safe(&toplevel->events.destroy, toplevel); + + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &toplevel->resources) { + zwlr_foreign_toplevel_handle_v1_send_closed(resource); + wl_resource_set_user_data(resource, NULL); + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + } + + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output, *tmp2; + wl_list_for_each_safe(toplevel_output, tmp2, &toplevel->outputs, link) { + toplevel_output_destroy(toplevel_output); + } + + if (toplevel->idle_source) { + wl_event_source_remove(toplevel->idle_source); + } + + wl_list_remove(&toplevel->link); + + free(toplevel->title); + free(toplevel->app_id); + free(toplevel); +} + +static void foreign_toplevel_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static struct wl_resource *create_toplevel_resource_for_resource( + struct wlr_foreign_toplevel_handle_v1 *toplevel, + struct wl_resource *manager_resource) { + struct wl_client *client = wl_resource_get_client(manager_resource); + struct wl_resource *resource = wl_resource_create(client, + &zwlr_foreign_toplevel_handle_v1_interface, + wl_resource_get_version(manager_resource), 0); + if (!resource) { + wl_client_post_no_memory(client); + return NULL; + } + + wl_resource_set_implementation(resource, &toplevel_handle_impl, toplevel, + foreign_toplevel_resource_destroy); + + wl_list_insert(&toplevel->resources, wl_resource_get_link(resource)); + zwlr_foreign_toplevel_manager_v1_send_toplevel(manager_resource, resource); + return resource; +} + +struct wlr_foreign_toplevel_handle_v1 * +wlr_foreign_toplevel_handle_v1_create( + struct wlr_foreign_toplevel_manager_v1 *manager) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = calloc(1, + sizeof(struct wlr_foreign_toplevel_handle_v1)); + if (!toplevel) { + return NULL; + } + + wl_list_insert(&manager->toplevels, &toplevel->link); + toplevel->manager = manager; + + wl_list_init(&toplevel->resources); + wl_list_init(&toplevel->outputs); + + wl_signal_init(&toplevel->events.request_maximize); + wl_signal_init(&toplevel->events.request_minimize); + wl_signal_init(&toplevel->events.request_activate); + wl_signal_init(&toplevel->events.request_close); + wl_signal_init(&toplevel->events.set_rectangle); + wl_signal_init(&toplevel->events.destroy); + + struct wl_resource *manager_resource, *tmp; + wl_resource_for_each_safe(manager_resource, tmp, &manager->resources) { + create_toplevel_resource_for_resource(toplevel, manager_resource); + } + + return toplevel; +} + +static const struct zwlr_foreign_toplevel_manager_v1_interface + foreign_toplevel_manager_impl; + +static void foreign_toplevel_manager_handle_stop(struct wl_client *client, + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_foreign_toplevel_manager_v1_interface, + &foreign_toplevel_manager_impl)); + + zwlr_foreign_toplevel_manager_v1_send_finished(resource); + wl_resource_destroy(resource); +} + +static const struct zwlr_foreign_toplevel_manager_v1_interface + foreign_toplevel_manager_impl = { + .stop = foreign_toplevel_manager_handle_stop +}; + +static void foreign_toplevel_manager_resource_destroy( + struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void toplevel_send_details_to_toplevel_resource( + struct wlr_foreign_toplevel_handle_v1 *toplevel, + struct wl_resource *resource) { + if (toplevel->title) { + zwlr_foreign_toplevel_handle_v1_send_title(resource, + toplevel->title); + } + if (toplevel->app_id) { + zwlr_foreign_toplevel_handle_v1_send_app_id(resource, + toplevel->app_id); + } + + struct wlr_foreign_toplevel_handle_v1_output *output; + wl_list_for_each(output, &toplevel->outputs, link) { + send_output_to_resource(resource, output->output, true); + } + + struct wl_array states; + wl_array_init(&states); + bool r = fill_array_from_toplevel_state(&states, toplevel->state); + if (!r) { + wl_resource_post_no_memory(resource); + wl_array_release(&states); + return; + } + + zwlr_foreign_toplevel_handle_v1_send_state(resource, &states); + wl_array_release(&states); + + zwlr_foreign_toplevel_handle_v1_send_done(resource); +} + +static void foreign_toplevel_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_foreign_toplevel_manager_v1 *manager = data; + struct wl_resource *resource = wl_resource_create(client, + &zwlr_foreign_toplevel_manager_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &foreign_toplevel_manager_impl, + manager, foreign_toplevel_manager_resource_destroy); + + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); + + struct wlr_foreign_toplevel_handle_v1 *toplevel, *tmp; + wl_list_for_each_safe(toplevel, tmp, &manager->toplevels, link) { + struct wl_resource *toplevel_resource = + create_toplevel_resource_for_resource(toplevel, resource); + toplevel_send_details_to_toplevel_resource(toplevel, + toplevel_resource); + } +} + +void wlr_foreign_toplevel_manager_v1_destroy( + struct wlr_foreign_toplevel_manager_v1 *manager) { + if (!manager) { + return; + } + + struct wlr_foreign_toplevel_handle_v1 *toplevel, *tmp_toplevel; + wl_list_for_each_safe(toplevel, tmp_toplevel, &manager->toplevels, link) { + wlr_foreign_toplevel_handle_v1_destroy(toplevel); + } + + struct wl_resource *resource, *tmp_resource; + wl_resource_for_each_safe(resource, tmp_resource, &manager->resources) { + wl_resource_destroy(resource); + } + + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + + wl_global_destroy(manager->global); + free(manager); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_foreign_toplevel_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wlr_foreign_toplevel_manager_v1_destroy(manager); +} + +struct wlr_foreign_toplevel_manager_v1 *wlr_foreign_toplevel_manager_v1_create( + struct wl_display *display) { + struct wlr_foreign_toplevel_manager_v1 *manager = calloc(1, + sizeof(struct wlr_foreign_toplevel_manager_v1)); + if (!manager) { + return NULL; + } + + manager->event_loop = wl_display_get_event_loop(display); + manager->global = wl_global_create(display, + &zwlr_foreign_toplevel_manager_v1_interface, + FOREIGN_TOPLEVEL_MANAGEMENT_V1_VERSION, manager, + foreign_toplevel_manager_bind); + if (!manager->global) { + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.destroy); + wl_list_init(&manager->resources); + wl_list_init(&manager->toplevels); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/wlr_gamma_control.c b/types/wlr_gamma_control.c new file mode 100644 index 00000000..40ca2aca --- /dev/null +++ b/types/wlr_gamma_control.c @@ -0,0 +1,197 @@ +#include <assert.h> +#include <stdlib.h> +#include <wayland-server.h> +#include <wlr/types/wlr_gamma_control.h> +#include <wlr/types/wlr_output.h> +#include <wlr/util/log.h> +#include "gamma-control-protocol.h" +#include "util/signal.h" + +static void resource_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void gamma_control_destroy(struct wlr_gamma_control *gamma_control) { + if (gamma_control == NULL) { + return; + } + wlr_signal_emit_safe(&gamma_control->events.destroy, gamma_control); + wl_list_remove(&gamma_control->output_destroy_listener.link); + wl_resource_set_user_data(gamma_control->resource, NULL); + wl_list_remove(&gamma_control->link); + free(gamma_control); +} + +static const struct gamma_control_interface gamma_control_impl; + +static struct wlr_gamma_control *gamma_control_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &gamma_control_interface, + &gamma_control_impl)); + return wl_resource_get_user_data(resource); +} + +static void gamma_control_destroy_resource(struct wl_resource *resource) { + struct wlr_gamma_control *gamma_control = + gamma_control_from_resource(resource); + gamma_control_destroy(gamma_control); +} + +static void gamma_control_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_gamma_control *gamma_control = + wl_container_of(listener, gamma_control, output_destroy_listener); + gamma_control_destroy(gamma_control); +} + +static void gamma_control_set_gamma(struct wl_client *client, + struct wl_resource *gamma_control_resource, struct wl_array *red, + struct wl_array *green, struct wl_array *blue) { + struct wlr_gamma_control *gamma_control = + gamma_control_from_resource(gamma_control_resource); + + if (gamma_control == NULL) { + return; + } + + if (red->size != green->size || red->size != blue->size) { + wl_resource_post_error(gamma_control_resource, + GAMMA_CONTROL_ERROR_INVALID_GAMMA, + "The gamma ramps don't have the same size"); + return; + } + + uint32_t size = red->size / sizeof(uint16_t); + uint16_t *r = (uint16_t *)red->data; + uint16_t *g = (uint16_t *)green->data; + uint16_t *b = (uint16_t *)blue->data; + + wlr_output_set_gamma(gamma_control->output, size, r, g, b); +} + +static void gamma_control_reset_gamma(struct wl_client *client, + struct wl_resource *gamma_control_resource) { + // TODO +} + +static const struct gamma_control_interface gamma_control_impl = { + .destroy = resource_destroy, + .set_gamma = gamma_control_set_gamma, + .reset_gamma = gamma_control_reset_gamma, +}; + +static const struct gamma_control_manager_interface gamma_control_manager_impl; + +static struct wlr_gamma_control_manager *gamma_control_manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &gamma_control_manager_interface, + &gamma_control_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void gamma_control_manager_get_gamma_control(struct wl_client *client, + struct wl_resource *gamma_control_manager_resource, uint32_t id, + struct wl_resource *output_resource) { + struct wlr_gamma_control_manager *manager = + gamma_control_manager_from_resource(gamma_control_manager_resource); + struct wlr_output *output = wlr_output_from_resource(output_resource); + + struct wlr_gamma_control *gamma_control = + calloc(1, sizeof(struct wlr_gamma_control)); + if (gamma_control == NULL) { + wl_client_post_no_memory(client); + return; + } + gamma_control->output = output; + + int version = wl_resource_get_version(gamma_control_manager_resource); + gamma_control->resource = wl_resource_create(client, + &gamma_control_interface, version, id); + if (gamma_control->resource == NULL) { + free(gamma_control); + wl_client_post_no_memory(client); + return; + } + wlr_log(WLR_DEBUG, "new gamma_control %p (res %p)", gamma_control, + gamma_control->resource); + wl_resource_set_implementation(gamma_control->resource, + &gamma_control_impl, gamma_control, gamma_control_destroy_resource); + + wl_signal_init(&gamma_control->events.destroy); + + wl_signal_add(&output->events.destroy, + &gamma_control->output_destroy_listener); + gamma_control->output_destroy_listener.notify = + gamma_control_handle_output_destroy; + + wl_list_insert(&manager->controls, &gamma_control->link); + + gamma_control_send_gamma_size(gamma_control->resource, + wlr_output_get_gamma_size(output)); +} + +static const struct gamma_control_manager_interface gamma_control_manager_impl = { + .get_gamma_control = gamma_control_manager_get_gamma_control, +}; + +static void gamma_control_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_gamma_control_manager *manager = data; + assert(client && manager); + + struct wl_resource *resource = wl_resource_create(client, + &gamma_control_manager_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &gamma_control_manager_impl, + manager, NULL); +} + +void wlr_gamma_control_manager_destroy( + struct wlr_gamma_control_manager *manager) { + if (!manager) { + return; + } + struct wlr_gamma_control *gamma_control, *tmp; + wl_list_for_each_safe(gamma_control, tmp, &manager->controls, link) { + gamma_control_destroy(gamma_control); + } + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_gamma_control_manager *manager = + wl_container_of(listener, manager, display_destroy); + wlr_gamma_control_manager_destroy(manager); +} + +struct wlr_gamma_control_manager *wlr_gamma_control_manager_create( + struct wl_display *display) { + struct wlr_gamma_control_manager *manager = + calloc(1, sizeof(struct wlr_gamma_control_manager)); + if (!manager) { + return NULL; + } + struct wl_global *global = wl_global_create(display, + &gamma_control_manager_interface, 1, manager, + gamma_control_manager_bind); + if (!global) { + free(manager); + return NULL; + } + manager->global = global; + + wl_signal_init(&manager->events.destroy); + wl_list_init(&manager->controls); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/wlr_gamma_control_v1.c b/types/wlr_gamma_control_v1.c new file mode 100644 index 00000000..8ccc96e5 --- /dev/null +++ b/types/wlr_gamma_control_v1.c @@ -0,0 +1,272 @@ +#include <assert.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/interfaces/wlr_output.h> +#include <wlr/types/wlr_gamma_control_v1.h> +#include <wlr/types/wlr_output.h> +#include <wlr/util/log.h> +#include "util/signal.h" +#include "wlr-gamma-control-unstable-v1-protocol.h" + +#define GAMMA_CONTROL_MANAGER_V1_VERSION 1 + +static void gamma_control_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void gamma_control_destroy(struct wlr_gamma_control_v1 *gamma_control) { + if (gamma_control == NULL) { + return; + } + wlr_output_set_gamma(gamma_control->output, 0, NULL, NULL, NULL); + wl_resource_set_user_data(gamma_control->resource, NULL); + wl_list_remove(&gamma_control->output_destroy_listener.link); + wl_list_remove(&gamma_control->link); + free(gamma_control); +} + +static void gamma_control_send_failed( + struct wlr_gamma_control_v1 *gamma_control) { + zwlr_gamma_control_v1_send_failed(gamma_control->resource); + gamma_control_destroy(gamma_control); +} + +static const struct zwlr_gamma_control_v1_interface gamma_control_impl; + +static struct wlr_gamma_control_v1 *gamma_control_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwlr_gamma_control_v1_interface, + &gamma_control_impl)); + return wl_resource_get_user_data(resource); +} + +static void gamma_control_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_gamma_control_v1 *gamma_control = + gamma_control_from_resource(resource); + gamma_control_destroy(gamma_control); +} + +static void gamma_control_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_gamma_control_v1 *gamma_control = + wl_container_of(listener, gamma_control, output_destroy_listener); + gamma_control_destroy(gamma_control); +} + +static void gamma_control_handle_set_gamma(struct wl_client *client, + struct wl_resource *gamma_control_resource, int fd) { + struct wlr_gamma_control_v1 *gamma_control = + gamma_control_from_resource(gamma_control_resource); + if (gamma_control == NULL) { + goto error_fd; + } + + uint32_t ramp_size = wlr_output_get_gamma_size(gamma_control->output); + size_t table_size = ramp_size * 3 * sizeof(uint16_t); + + // Refuse to block when reading + int fd_flags = fcntl(fd, F_GETFL, 0); + if (fd_flags == -1) { + wlr_log_errno(WLR_ERROR, "failed to get FD flags"); + gamma_control_send_failed(gamma_control); + goto error_fd; + } + if (fcntl(fd, F_SETFL, fd_flags | O_NONBLOCK) == -1) { + wlr_log_errno(WLR_ERROR, "failed to set FD flags"); + gamma_control_send_failed(gamma_control); + goto error_fd; + } + + // Use the heap since gamma tables can be large + uint16_t *table = malloc(table_size); + if (table == NULL) { + wl_resource_post_no_memory(gamma_control_resource); + goto error_fd; + } + + ssize_t n_read = read(fd, table, table_size); + if (n_read < 0) { + wlr_log_errno(WLR_ERROR, "failed to read gamma table"); + gamma_control_send_failed(gamma_control); + goto error_table; + } else if ((size_t)n_read != table_size) { + wl_resource_post_error(gamma_control_resource, + ZWLR_GAMMA_CONTROL_V1_ERROR_INVALID_GAMMA, + "The gamma ramps don't have the correct size"); + goto error_table; + } + close(fd); + fd = -1; + + uint16_t *r = table; + uint16_t *g = table + ramp_size; + uint16_t *b = table + 2 * ramp_size; + + bool ok = wlr_output_set_gamma(gamma_control->output, ramp_size, r, g, b); + if (!ok) { + gamma_control_send_failed(gamma_control); + goto error_table; + } + free(table); + + return; + +error_table: + free(table); +error_fd: + close(fd); +} + +static const struct zwlr_gamma_control_v1_interface gamma_control_impl = { + .destroy = gamma_control_handle_destroy, + .set_gamma = gamma_control_handle_set_gamma, +}; + +static const struct zwlr_gamma_control_manager_v1_interface + gamma_control_manager_impl; + +static struct wlr_gamma_control_manager_v1 *gamma_control_manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_gamma_control_manager_v1_interface, &gamma_control_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void gamma_control_manager_get_gamma_control(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *output_resource) { + struct wlr_gamma_control_manager_v1 *manager = + gamma_control_manager_from_resource(manager_resource); + struct wlr_output *output = wlr_output_from_resource(output_resource); + + struct wlr_gamma_control_v1 *gamma_control = + calloc(1, sizeof(struct wlr_gamma_control_v1)); + if (gamma_control == NULL) { + wl_client_post_no_memory(client); + return; + } + gamma_control->output = output; + + uint32_t version = wl_resource_get_version(manager_resource); + gamma_control->resource = wl_resource_create(client, + &zwlr_gamma_control_v1_interface, version, id); + if (gamma_control->resource == NULL) { + free(gamma_control); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(gamma_control->resource, &gamma_control_impl, + gamma_control, gamma_control_handle_resource_destroy); + + wl_signal_add(&output->events.destroy, + &gamma_control->output_destroy_listener); + gamma_control->output_destroy_listener.notify = + gamma_control_handle_output_destroy; + + wl_list_init(&gamma_control->link); + + if (!output->impl->set_gamma) { + zwlr_gamma_control_v1_send_failed(gamma_control->resource); + gamma_control_destroy(gamma_control); + return; + } + + struct wlr_gamma_control_v1 *gc; + wl_list_for_each(gc, &manager->controls, link) { + if (gc->output == output) { + zwlr_gamma_control_v1_send_failed(gc->resource); + gamma_control_destroy(gc); + return; + } + } + + wl_list_remove(&gamma_control->link); + wl_list_insert(&manager->controls, &gamma_control->link); + zwlr_gamma_control_v1_send_gamma_size(gamma_control->resource, + wlr_output_get_gamma_size(output)); +} + +static void gamma_control_manager_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct zwlr_gamma_control_manager_v1_interface + gamma_control_manager_impl = { + .get_gamma_control = gamma_control_manager_get_gamma_control, + .destroy = gamma_control_manager_destroy, +}; + +static void gamma_control_manager_handle_resource_destroy( + struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void gamma_control_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_gamma_control_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwlr_gamma_control_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &gamma_control_manager_impl, + manager, gamma_control_manager_handle_resource_destroy); + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); +} + +void wlr_gamma_control_manager_v1_destroy( + struct wlr_gamma_control_manager_v1 *manager) { + if (!manager) { + return; + } + struct wlr_gamma_control_v1 *gamma_control, *tmp; + wl_list_for_each_safe(gamma_control, tmp, &manager->controls, link) { + wl_resource_destroy(gamma_control->resource); + } + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + struct wl_resource *resource, *resource_tmp; + wl_resource_for_each_safe(resource, resource_tmp, &manager->resources) { + wl_resource_destroy(resource); + } + wl_global_destroy(manager->global); + free(manager); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_gamma_control_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wlr_gamma_control_manager_v1_destroy(manager); +} + +struct wlr_gamma_control_manager_v1 *wlr_gamma_control_manager_v1_create( + struct wl_display *display) { + struct wlr_gamma_control_manager_v1 *manager = + calloc(1, sizeof(struct wlr_gamma_control_manager_v1)); + if (!manager) { + return NULL; + } + + manager->global = wl_global_create(display, + &zwlr_gamma_control_manager_v1_interface, + GAMMA_CONTROL_MANAGER_V1_VERSION, manager, gamma_control_manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.destroy); + wl_list_init(&manager->resources); + wl_list_init(&manager->controls); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/wlr_gtk_primary_selection.c b/types/wlr_gtk_primary_selection.c new file mode 100644 index 00000000..18bc624a --- /dev/null +++ b/types/wlr_gtk_primary_selection.c @@ -0,0 +1,501 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wlr/types/wlr_gtk_primary_selection.h> +#include <wlr/types/wlr_primary_selection.h> +#include <wlr/types/wlr_seat.h> +#include <wlr/util/log.h> +#include "gtk-primary-selection-protocol.h" +#include "util/signal.h" + +#define DEVICE_MANAGER_VERSION 1 + +static const struct gtk_primary_selection_offer_interface offer_impl; + +static struct wlr_gtk_primary_selection_device *device_from_offer_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + >k_primary_selection_offer_interface, &offer_impl)); + return wl_resource_get_user_data(resource); +} + +static void offer_handle_receive(struct wl_client *client, + struct wl_resource *resource, const char *mime_type, int32_t fd) { + struct wlr_gtk_primary_selection_device *device = + device_from_offer_resource(resource); + if (device == NULL || device->seat->primary_selection_source == NULL) { + close(fd); + return; + } + + wlr_primary_selection_source_send(device->seat->primary_selection_source, + mime_type, fd); +} + +static void offer_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct gtk_primary_selection_offer_interface offer_impl = { + .receive = offer_handle_receive, + .destroy = offer_handle_destroy, +}; + +static void offer_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static struct wlr_gtk_primary_selection_device *device_from_resource( + struct wl_resource *resource); + +static void create_offer(struct wl_resource *device_resource, + struct wlr_primary_selection_source *source) { + struct wlr_gtk_primary_selection_device *device = + device_from_resource(device_resource); + assert(device != NULL); + + struct wl_client *client = wl_resource_get_client(device_resource); + uint32_t version = wl_resource_get_version(device_resource); + struct wl_resource *resource = wl_resource_create(client, + >k_primary_selection_offer_interface, version, 0); + if (resource == NULL) { + wl_resource_post_no_memory(device_resource); + return; + } + wl_resource_set_implementation(resource, &offer_impl, device, + offer_handle_resource_destroy); + + wl_list_insert(&device->offers, wl_resource_get_link(resource)); + + gtk_primary_selection_device_send_data_offer(device_resource, resource); + + char **p; + wl_array_for_each(p, &source->mime_types) { + gtk_primary_selection_offer_send_offer(resource, *p); + } + + gtk_primary_selection_device_send_selection(device_resource, resource); +} + +static void destroy_offer(struct wl_resource *resource) { + if (device_from_offer_resource(resource) == NULL) { + return; + } + + // Make the offer inert + wl_resource_set_user_data(resource, NULL); + + struct wl_list *link = wl_resource_get_link(resource); + wl_list_remove(link); + wl_list_init(link); +} + + +struct client_data_source { + struct wlr_primary_selection_source source; + struct wl_resource *resource; + bool finalized; +}; + +static void client_source_send( + struct wlr_primary_selection_source *wlr_source, + const char *mime_type, int fd) { + struct client_data_source *source = (struct client_data_source *)wlr_source; + gtk_primary_selection_source_send_send(source->resource, mime_type, fd); + close(fd); +} + +static void client_source_destroy( + struct wlr_primary_selection_source *wlr_source) { + struct client_data_source *source = (struct client_data_source *)wlr_source; + gtk_primary_selection_source_send_cancelled(source->resource); + // Make the source resource inert + wl_resource_set_user_data(source->resource, NULL); + free(source); +} + +static const struct wlr_primary_selection_source_impl client_source_impl = { + .send = client_source_send, + .destroy = client_source_destroy, +}; + +static const struct gtk_primary_selection_source_interface source_impl; + +static struct client_data_source *client_data_source_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + >k_primary_selection_source_interface, &source_impl)); + return wl_resource_get_user_data(resource); +} + +static void source_handle_offer(struct wl_client *client, + struct wl_resource *resource, const char *mime_type) { + struct client_data_source *source = + client_data_source_from_resource(resource); + if (source == NULL) { + return; + } + if (source->finalized) { + wlr_log(WLR_DEBUG, "Offering additional MIME type after set_selection"); + } + + char *dup_mime_type = strdup(mime_type); + if (dup_mime_type == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + char **p = wl_array_add(&source->source.mime_types, sizeof(*p)); + if (p == NULL) { + free(dup_mime_type); + wl_resource_post_no_memory(resource); + return; + } + + *p = dup_mime_type; +} + +static void source_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct gtk_primary_selection_source_interface source_impl = { + .offer = source_handle_offer, + .destroy = source_handle_destroy, +}; + +static void source_resource_handle_destroy(struct wl_resource *resource) { + struct client_data_source *source = + client_data_source_from_resource(resource); + if (source == NULL) { + return; + } + wlr_primary_selection_source_destroy(&source->source); +} + + +static const struct gtk_primary_selection_device_interface device_impl; + +static struct wlr_gtk_primary_selection_device *device_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + >k_primary_selection_device_interface, &device_impl)); + return wl_resource_get_user_data(resource); +} + +static void device_handle_set_selection(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *source_resource, + uint32_t serial) { + struct wlr_gtk_primary_selection_device *device = + device_from_resource(resource); + if (device == NULL) { + return; + } + + struct client_data_source *client_source = NULL; + if (source_resource != NULL) { + client_source = client_data_source_from_resource(source_resource); + } + + struct wlr_primary_selection_source *source = NULL; + if (client_source != NULL) { + client_source->finalized = true; + source = &client_source->source; + } + + // TODO: improve serial validation + if (device->seat->primary_selection_source != NULL && + device->selection_serial - serial < UINT32_MAX / 2) { + wlr_log(WLR_DEBUG, "Rejecting set_selection request, invalid serial " + "(%"PRIu32" <= %"PRIu32")", serial, device->selection_serial); + return; + } + device->selection_serial = serial; + + wlr_seat_set_primary_selection(device->seat, source); +} + +static void device_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct gtk_primary_selection_device_interface device_impl = { + .set_selection = device_handle_set_selection, + .destroy = device_handle_destroy, +}; + +static void device_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + + +static void device_resource_send_selection(struct wl_resource *resource, + struct wlr_primary_selection_source *source) { + assert(device_from_resource(resource) != NULL); + + if (source != NULL) { + create_offer(resource, source); + } else { + gtk_primary_selection_device_send_selection(resource, NULL); + } +} + +static void device_send_selection( + struct wlr_gtk_primary_selection_device *device) { + struct wlr_seat_client *seat_client = + device->seat->keyboard_state.focused_client; + if (seat_client == NULL) { + return; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &device->resources) { + if (wl_resource_get_client(resource) == seat_client->client) { + device_resource_send_selection(resource, + device->seat->primary_selection_source); + } + } +} + +static void device_destroy(struct wlr_gtk_primary_selection_device *device); + +static void device_handle_seat_destroy(struct wl_listener *listener, + void *data) { + struct wlr_gtk_primary_selection_device *device = + wl_container_of(listener, device, seat_destroy); + device_destroy(device); +} + +static void device_handle_seat_focus_change(struct wl_listener *listener, + void *data) { + struct wlr_gtk_primary_selection_device *device = + wl_container_of(listener, device, seat_focus_change); + // TODO: maybe make previous offers inert, or set a NULL selection for + // previous client? + device_send_selection(device); +} + +static void device_handle_seat_primary_selection(struct wl_listener *listener, + void *data) { + struct wlr_gtk_primary_selection_device *device = + wl_container_of(listener, device, seat_primary_selection); + + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &device->offers) { + destroy_offer(resource); + } + + device_send_selection(device); +} + +static struct wlr_gtk_primary_selection_device *get_or_create_device( + struct wlr_gtk_primary_selection_device_manager *manager, + struct wlr_seat *seat) { + struct wlr_gtk_primary_selection_device *device; + wl_list_for_each(device, &manager->devices, link) { + if (device->seat == seat) { + return device; + } + } + + device = calloc(1, sizeof(struct wlr_gtk_primary_selection_device)); + if (device == NULL) { + return NULL; + } + device->manager = manager; + device->seat = seat; + + wl_list_init(&device->resources); + wl_list_insert(&manager->devices, &device->link); + + wl_list_init(&device->offers); + + device->seat_destroy.notify = device_handle_seat_destroy; + wl_signal_add(&seat->events.destroy, &device->seat_destroy); + + device->seat_focus_change.notify = device_handle_seat_focus_change; + wl_signal_add(&seat->keyboard_state.events.focus_change, + &device->seat_focus_change); + + device->seat_primary_selection.notify = + device_handle_seat_primary_selection; + wl_signal_add(&seat->events.primary_selection, + &device->seat_primary_selection); + + return device; +} + +static void device_destroy(struct wlr_gtk_primary_selection_device *device) { + if (device == NULL) { + return; + } + wl_list_remove(&device->link); + wl_list_remove(&device->seat_destroy.link); + wl_list_remove(&device->seat_focus_change.link); + wl_list_remove(&device->seat_primary_selection.link); + struct wl_resource *resource, *resource_tmp; + wl_resource_for_each_safe(resource, resource_tmp, &device->offers) { + destroy_offer(resource); + } + wl_resource_for_each_safe(resource, resource_tmp, &device->resources) { + // Make the resource inert + wl_resource_set_user_data(resource, NULL); + + struct wl_list *link = wl_resource_get_link(resource); + wl_list_remove(link); + wl_list_init(link); + } + free(device); +} + + +static const struct gtk_primary_selection_device_manager_interface + device_manager_impl; + +struct wlr_gtk_primary_selection_device_manager *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + >k_primary_selection_device_manager_interface, &device_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void device_manager_handle_create_source(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id) { + struct client_data_source *source = + calloc(1, sizeof(struct client_data_source)); + if (source == NULL) { + wl_client_post_no_memory(client); + return; + } + wlr_primary_selection_source_init(&source->source, &client_source_impl); + + uint32_t version = wl_resource_get_version(manager_resource); + source->resource = wl_resource_create(client, + >k_primary_selection_source_interface, version, id); + if (source->resource == NULL) { + free(source); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(source->resource, &source_impl, source, + source_resource_handle_destroy); +} + +void device_manager_handle_get_device(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *seat_resource) { + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + struct wlr_gtk_primary_selection_device_manager *manager = + manager_from_resource(manager_resource); + + struct wlr_gtk_primary_selection_device *device = + get_or_create_device(manager, seat_client->seat); + if (device == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *resource = wl_resource_create(client, + >k_primary_selection_device_interface, version, id); + if (resource == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_implementation(resource, &device_impl, device, + device_handle_resource_destroy); + wl_list_insert(&device->resources, wl_resource_get_link(resource)); + + if (device->seat->keyboard_state.focused_client == seat_client) { + device_resource_send_selection(resource, + device->seat->primary_selection_source); + } +} + +static void device_manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct gtk_primary_selection_device_manager_interface + device_manager_impl = { + .create_source = device_manager_handle_create_source, + .get_device = device_manager_handle_get_device, + .destroy = device_manager_handle_destroy, +}; + +static void device_manager_handle_resource_destroy( + struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + + +static void primary_selection_device_manager_bind(struct wl_client *client, + void *data, uint32_t version, uint32_t id) { + struct wlr_gtk_primary_selection_device_manager *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + >k_primary_selection_device_manager_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &device_manager_impl, manager, + device_manager_handle_resource_destroy); + + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_gtk_primary_selection_device_manager *manager = + wl_container_of(listener, manager, display_destroy); + wlr_gtk_primary_selection_device_manager_destroy(manager); +} + +struct wlr_gtk_primary_selection_device_manager * + wlr_gtk_primary_selection_device_manager_create( + struct wl_display *display) { + struct wlr_gtk_primary_selection_device_manager *manager = + calloc(1, sizeof(struct wlr_gtk_primary_selection_device_manager)); + if (manager == NULL) { + return NULL; + } + manager->global = wl_global_create(display, + >k_primary_selection_device_manager_interface, DEVICE_MANAGER_VERSION, + manager, primary_selection_device_manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + wl_list_init(&manager->resources); + wl_list_init(&manager->devices); + wl_signal_init(&manager->events.destroy); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +void wlr_gtk_primary_selection_device_manager_destroy( + struct wlr_gtk_primary_selection_device_manager *manager) { + if (manager == NULL) { + return; + } + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + struct wl_resource *resource, *resource_tmp; + wl_resource_for_each_safe(resource, resource_tmp, &manager->resources) { + wl_resource_destroy(resource); + } + wl_global_destroy(manager->global); + free(manager); +} diff --git a/types/wlr_idle.c b/types/wlr_idle.c new file mode 100644 index 00000000..29367ceb --- /dev/null +++ b/types/wlr_idle.c @@ -0,0 +1,248 @@ +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <wayland-server.h> +#include <wlr/types/wlr_idle.h> +#include <wlr/util/log.h> +#include "idle-protocol.h" +#include "util/signal.h" + +static const struct org_kde_kwin_idle_timeout_interface idle_timeout_impl; + +static struct wlr_idle_timeout *idle_timeout_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &org_kde_kwin_idle_timeout_interface, &idle_timeout_impl)); + return wl_resource_get_user_data(resource); +} + +static void idle_timeout_destroy(struct wlr_idle_timeout *timer) { + wl_list_remove(&timer->input_listener.link); + wl_list_remove(&timer->seat_destroy.link); + wl_event_source_remove(timer->idle_source); + wl_list_remove(&timer->link); + wl_resource_set_user_data(timer->resource, NULL); + free(timer); +} + +static int idle_notify(void *data) { + struct wlr_idle_timeout *timer = data; + if (timer->idle_state) { + return 0; + } + timer->idle_state = true; + org_kde_kwin_idle_timeout_send_idle(timer->resource); + return 1; +} + +static void handle_activity(struct wlr_idle_timeout *timer) { + if (!timer->enabled) { + return; + } + + // in case the previous state was sleeping send a resume event and switch state + if (timer->idle_state) { + timer->idle_state = false; + org_kde_kwin_idle_timeout_send_resumed(timer->resource); + } + + // rearm the timer + wl_event_source_timer_update(timer->idle_source, timer->timeout); + if (timer->timeout == 0) { + idle_notify(timer); + } +} + +static void handle_timer_resource_destroy(struct wl_resource *timer_resource) { + struct wlr_idle_timeout *timer = idle_timeout_from_resource(timer_resource); + if (timer != NULL) { + idle_timeout_destroy(timer); + } +} + +static void handle_seat_destroy(struct wl_listener *listener, void *data) { + struct wlr_idle_timeout *timer = wl_container_of(listener, timer, seat_destroy); + if (timer != NULL) { + idle_timeout_destroy(timer); + } +} + +static void release_idle_timeout(struct wl_client *client, + struct wl_resource *resource){ + handle_timer_resource_destroy(resource); +} + +static void simulate_activity(struct wl_client *client, + struct wl_resource *resource){ + struct wlr_idle_timeout *timer = idle_timeout_from_resource(resource); + handle_activity(timer); +} + +static const struct org_kde_kwin_idle_timeout_interface idle_timeout_impl = { + .release = release_idle_timeout, + .simulate_user_activity = simulate_activity, +}; + +static const struct org_kde_kwin_idle_interface idle_impl; + +static struct wlr_idle *idle_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &org_kde_kwin_idle_interface, + &idle_impl)); + return wl_resource_get_user_data(resource); +} + +static void handle_input_notification(struct wl_listener *listener, void *data) { + struct wlr_idle_timeout *timer = + wl_container_of(listener, timer, input_listener); + struct wlr_seat *seat = data; + if (timer->seat == seat) { + handle_activity(timer); + } +} + +static void create_idle_timer(struct wl_client *client, + struct wl_resource *idle_resource, uint32_t id, + struct wl_resource *seat_resource, uint32_t timeout) { + struct wlr_idle *idle = idle_from_resource(idle_resource); + struct wlr_seat_client *client_seat = + wlr_seat_client_from_resource(seat_resource); + + struct wlr_idle_timeout *timer = + calloc(1, sizeof(struct wlr_idle_timeout)); + if (!timer) { + wl_resource_post_no_memory(idle_resource); + return; + } + timer->seat = client_seat->seat; + timer->timeout = timeout; + timer->idle_state = false; + timer->enabled = idle->enabled; + timer->resource = wl_resource_create(client, + &org_kde_kwin_idle_timeout_interface, + wl_resource_get_version(idle_resource), id); + if (timer->resource == NULL) { + free(timer); + wl_resource_post_no_memory(idle_resource); + return; + } + wl_resource_set_implementation(timer->resource, &idle_timeout_impl, timer, + handle_timer_resource_destroy); + wl_list_insert(&idle->idle_timers, &timer->link); + + timer->seat_destroy.notify = handle_seat_destroy; + wl_signal_add(&timer->seat->events.destroy, &timer->seat_destroy); + + timer->input_listener.notify = handle_input_notification; + wl_signal_add(&idle->events.activity_notify, &timer->input_listener); + // create the timer + timer->idle_source = + wl_event_loop_add_timer(idle->event_loop, idle_notify, timer); + if (timer->idle_source == NULL) { + wl_list_remove(&timer->link); + wl_list_remove(&timer->input_listener.link); + wl_list_remove(&timer->seat_destroy.link); + wl_resource_set_user_data(timer->resource, NULL); + free(timer); + wl_resource_post_no_memory(idle_resource); + return; + } + if (timer->enabled) { + // arm the timer + wl_event_source_timer_update(timer->idle_source, timer->timeout); + if (timer->timeout == 0) { + idle_notify(timer); + } + } +} + +static const struct org_kde_kwin_idle_interface idle_impl = { + .get_idle_timeout = create_idle_timer, +}; + +void wlr_idle_set_enabled(struct wlr_idle *idle, struct wlr_seat *seat, + bool enabled) { + if (idle->enabled == enabled) { + return; + } + wlr_log(WLR_DEBUG, "%s idle timers for %s", + enabled ? "Enabling" : "Disabling", + seat ? seat->name : "all seats"); + idle->enabled = enabled; + struct wlr_idle_timeout *timer; + wl_list_for_each(timer, &idle->idle_timers, link) { + if (seat != NULL && timer->seat != seat) { + continue; + } + int timeout = enabled ? timer->timeout : 0; + wl_event_source_timer_update(timer->idle_source, timeout); + timer->enabled = enabled; + } +} + +static void idle_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_idle *idle = data; + assert(wl_client && idle); + + struct wl_resource *wl_resource = wl_resource_create(wl_client, + &org_kde_kwin_idle_interface, version, id); + if (wl_resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(wl_resource, &idle_impl, idle, NULL); +} + +void wlr_idle_destroy(struct wlr_idle *idle) { + if (!idle) { + return; + } + wlr_signal_emit_safe(&idle->events.destroy, idle); + wl_list_remove(&idle->display_destroy.link); + struct wlr_idle_timeout *timer, *tmp; + wl_list_for_each_safe(timer, tmp, &idle->idle_timers, link) { + idle_timeout_destroy(timer); + } + wl_global_destroy(idle->global); + free(idle); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_idle *idle = wl_container_of(listener, idle, display_destroy); + wlr_idle_destroy(idle); +} + +struct wlr_idle *wlr_idle_create(struct wl_display *display) { + struct wlr_idle *idle = calloc(1, sizeof(struct wlr_idle)); + if (!idle) { + return NULL; + } + wl_list_init(&idle->idle_timers); + wl_signal_init(&idle->events.activity_notify); + wl_signal_init(&idle->events.destroy); + idle->enabled = true; + + idle->event_loop = wl_display_get_event_loop(display); + if (idle->event_loop == NULL) { + free(idle); + return NULL; + } + + idle->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &idle->display_destroy); + + idle->global = wl_global_create(display, &org_kde_kwin_idle_interface, + 1, idle, idle_bind); + if (idle->global == NULL){ + wl_list_remove(&idle->display_destroy.link); + free(idle); + return NULL; + } + wlr_log(WLR_DEBUG, "idle manager created"); + return idle; +} + +void wlr_idle_notify_activity(struct wlr_idle *idle, struct wlr_seat *seat) { + wlr_signal_emit_safe(&idle->events.activity_notify, seat); +} diff --git a/types/wlr_idle_inhibit_v1.c b/types/wlr_idle_inhibit_v1.c new file mode 100644 index 00000000..5f3d3d40 --- /dev/null +++ b/types/wlr_idle_inhibit_v1.c @@ -0,0 +1,194 @@ +#include <assert.h> +#include <stdlib.h> +#include <wlr/util/log.h> +#include <util/signal.h> +#include <wlr/types/wlr_surface.h> +#include <wlr/types/wlr_idle_inhibit_v1.h> +#include "wayland-util.h" +#include "wayland-server.h" +#include "idle-inhibit-unstable-v1-protocol.h" + +static const struct zwp_idle_inhibit_manager_v1_interface idle_inhibit_impl; + +static const struct zwp_idle_inhibitor_v1_interface idle_inhibitor_impl; + +static struct wlr_idle_inhibit_manager_v1 * +wlr_idle_inhibit_manager_v1_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_idle_inhibit_manager_v1_interface, + &idle_inhibit_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_idle_inhibitor_v1 * +wlr_idle_inhibitor_v1_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_idle_inhibitor_v1_interface, + &idle_inhibitor_impl)); + return wl_resource_get_user_data(resource); +} + +static void idle_inhibitor_v1_destroy(struct wlr_idle_inhibitor_v1 *inhibitor) { + if (!inhibitor) { + return; + } + + wlr_signal_emit_safe(&inhibitor->events.destroy, inhibitor->surface); + + wl_resource_set_user_data(inhibitor->resource, NULL); + wl_list_remove(&inhibitor->link); + wl_list_remove(&inhibitor->surface_destroy.link); + free(inhibitor); +} + +static void idle_inhibitor_v1_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_idle_inhibitor_v1 *inhibitor = + wlr_idle_inhibitor_v1_from_resource(resource); + idle_inhibitor_v1_destroy(inhibitor); +} + +static void idle_inhibitor_handle_surface_destroy( + struct wl_listener *listener, void *data) { + struct wlr_idle_inhibitor_v1 *inhibitor = + wl_container_of(listener, inhibitor, surface_destroy); + idle_inhibitor_v1_destroy(inhibitor); +} + +static void idle_inhibitor_v1_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct zwp_idle_inhibitor_v1_interface idle_inhibitor_impl = { + .destroy = idle_inhibitor_v1_handle_destroy, +}; + +static void manager_handle_create_inhibitor(struct wl_client *client, + struct wl_resource *resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + struct wlr_idle_inhibit_manager_v1 *manager = + wlr_idle_inhibit_manager_v1_from_resource(resource); + + struct wlr_idle_inhibitor_v1 *inhibitor = + calloc(1, sizeof(struct wlr_idle_inhibitor_v1)); + if (!inhibitor) { + wl_client_post_no_memory(client); + return; + } + + struct wl_resource *wl_resource = wl_resource_create(client, + &zwp_idle_inhibitor_v1_interface, 1, id); + if (!wl_resource) { + wl_client_post_no_memory(client); + free(inhibitor); + return; + } + + inhibitor->resource = wl_resource; + inhibitor->surface = surface; + wl_signal_init(&inhibitor->events.destroy); + + inhibitor->surface_destroy.notify = idle_inhibitor_handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &inhibitor->surface_destroy); + + + wl_resource_set_implementation(wl_resource, &idle_inhibitor_impl, + inhibitor, idle_inhibitor_v1_handle_resource_destroy); + + wl_list_insert(&manager->inhibitors, &inhibitor->link); + wlr_signal_emit_safe(&manager->events.new_inhibitor, inhibitor); +} + +static void idle_inhibit_manager_v1_handle_resource_destroy( + struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct zwp_idle_inhibit_manager_v1_interface idle_inhibit_impl = { + .destroy = manager_handle_destroy, + .create_inhibitor = manager_handle_create_inhibitor, +}; + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_idle_inhibit_manager_v1 *idle_inhibit = + wl_container_of(listener, idle_inhibit, display_destroy); + + wlr_idle_inhibit_v1_destroy(idle_inhibit); +} + +static void idle_inhibit_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_idle_inhibit_manager_v1 *idle_inhibit = data; + + struct wl_resource *wl_resource = wl_resource_create(wl_client, + &zwp_idle_inhibit_manager_v1_interface, version, id); + + if (!wl_resource) { + wl_client_post_no_memory(wl_client); + return; + } + + wl_list_insert(&idle_inhibit->resources, wl_resource_get_link(wl_resource)); + + wl_resource_set_implementation(wl_resource, &idle_inhibit_impl, + idle_inhibit, idle_inhibit_manager_v1_handle_resource_destroy); + wlr_log(WLR_DEBUG, "idle_inhibit bound"); +} + +void wlr_idle_inhibit_v1_destroy(struct wlr_idle_inhibit_manager_v1 *idle_inhibit) { + if (!idle_inhibit) { + return; + } + + struct wlr_idle_inhibitor_v1 *inhibitor; + struct wlr_idle_inhibitor_v1 *tmp; + wl_list_for_each_safe(inhibitor, tmp, &idle_inhibit->inhibitors, link) { + idle_inhibitor_v1_destroy(inhibitor); + } + + wlr_signal_emit_safe(&idle_inhibit->events.destroy, idle_inhibit); + wl_list_remove(&idle_inhibit->display_destroy.link); + + struct wl_resource *resource; + struct wl_resource *tmp_resource; + wl_resource_for_each_safe(resource, tmp_resource, &idle_inhibit->resources) { + wl_resource_destroy(resource); + } + + wl_global_destroy(idle_inhibit->global); + free(idle_inhibit); +} + +struct wlr_idle_inhibit_manager_v1 *wlr_idle_inhibit_v1_create(struct wl_display *display) { + struct wlr_idle_inhibit_manager_v1 *idle_inhibit = + calloc(1, sizeof(struct wlr_idle_inhibit_manager_v1)); + + if (!idle_inhibit) { + return NULL; + } + + wl_list_init(&idle_inhibit->resources); + wl_list_init(&idle_inhibit->inhibitors); + idle_inhibit->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &idle_inhibit->display_destroy); + wl_signal_init(&idle_inhibit->events.new_inhibitor); + wl_signal_init(&idle_inhibit->events.destroy); + + idle_inhibit->global = wl_global_create(display, + &zwp_idle_inhibit_manager_v1_interface, 1, + idle_inhibit, idle_inhibit_bind); + + if (!idle_inhibit->global) { + wl_list_remove(&idle_inhibit->display_destroy.link); + free(idle_inhibit); + return NULL; + } + + wlr_log(WLR_DEBUG, "idle_inhibit manager created"); + + return idle_inhibit; +} diff --git a/types/wlr_input_device.c b/types/wlr_input_device.c new file mode 100644 index 00000000..f7e5f04a --- /dev/null +++ b/types/wlr_input_device.c @@ -0,0 +1,69 @@ +#define _POSIX_C_SOURCE 200809L +#include <stdlib.h> +#include <string.h> +#include <wayland-server.h> +#include <wlr/interfaces/wlr_input_device.h> +#include <wlr/interfaces/wlr_keyboard.h> +#include <wlr/interfaces/wlr_pointer.h> +#include <wlr/interfaces/wlr_switch.h> +#include <wlr/interfaces/wlr_tablet_pad.h> +#include <wlr/interfaces/wlr_tablet_tool.h> +#include <wlr/interfaces/wlr_touch.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/util/log.h> +#include "util/signal.h" + +void wlr_input_device_init(struct wlr_input_device *dev, + enum wlr_input_device_type type, + const struct wlr_input_device_impl *impl, + const char *name, int vendor, int product) { + dev->type = type; + dev->impl = impl; + dev->name = strdup(name); + dev->vendor = vendor; + dev->product = product; + + wl_signal_init(&dev->events.destroy); +} + +void wlr_input_device_destroy(struct wlr_input_device *dev) { + if (!dev) { + return; + } + + wlr_signal_emit_safe(&dev->events.destroy, dev); + + if (dev->_device) { + switch (dev->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + wlr_keyboard_destroy(dev->keyboard); + break; + case WLR_INPUT_DEVICE_POINTER: + wlr_pointer_destroy(dev->pointer); + break; + case WLR_INPUT_DEVICE_SWITCH: + wlr_switch_destroy(dev->lid_switch); + break; + case WLR_INPUT_DEVICE_TOUCH: + wlr_touch_destroy(dev->touch); + break; + case WLR_INPUT_DEVICE_TABLET_TOOL: + wlr_tablet_destroy(dev->tablet); + break; + case WLR_INPUT_DEVICE_TABLET_PAD: + wlr_tablet_pad_destroy(dev->tablet_pad); + break; + default: + wlr_log(WLR_DEBUG, "Warning: leaking memory %p %p %d", + dev->_device, dev, dev->type); + break; + } + } + free(dev->name); + free(dev->output_name); + if (dev->impl && dev->impl->destroy) { + dev->impl->destroy(dev); + } else { + free(dev); + } +} diff --git a/types/wlr_input_inhibitor.c b/types/wlr_input_inhibitor.c new file mode 100644 index 00000000..fa692d6c --- /dev/null +++ b/types/wlr_input_inhibitor.c @@ -0,0 +1,153 @@ +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <wayland-server.h> +#include "wlr/types/wlr_input_inhibitor.h" +#include "wlr-input-inhibitor-unstable-v1-protocol.h" +#include "util/signal.h" + +static const struct zwlr_input_inhibit_manager_v1_interface inhibit_manager_implementation; +static struct zwlr_input_inhibitor_v1_interface input_inhibitor_implementation; + +static struct wlr_input_inhibit_manager *input_inhibit_manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_input_inhibit_manager_v1_interface, + &inhibit_manager_implementation) + || wl_resource_instance_of(resource, + &zwlr_input_inhibitor_v1_interface, + &input_inhibitor_implementation)); + return wl_resource_get_user_data(resource); +} + +static void input_inhibit_manager_deactivate( + struct wlr_input_inhibit_manager *manager) { + if (manager->active_client == NULL && manager->active_inhibitor == NULL) { + return; + } + manager->active_client = NULL; + manager->active_inhibitor = NULL; + wlr_signal_emit_safe(&manager->events.deactivate, manager); +} + +static void input_inhibitor_destroy(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_input_inhibit_manager *manager = + input_inhibit_manager_from_resource(resource); + input_inhibit_manager_deactivate(manager); + wl_resource_destroy(resource); +} + +static void input_inhibitor_resource_destroy(struct wl_resource *resource) { + struct wlr_input_inhibit_manager *manager = + input_inhibit_manager_from_resource(resource); + input_inhibit_manager_deactivate(manager); +} + +static struct zwlr_input_inhibitor_v1_interface input_inhibitor_implementation = { + .destroy = input_inhibitor_destroy, +}; + +static void inhibit_manager_get_inhibitor(struct wl_client *client, + struct wl_resource *resource, uint32_t id) { + struct wlr_input_inhibit_manager *manager = + input_inhibit_manager_from_resource(resource); + if (manager->active_client || manager->active_inhibitor) { + wl_resource_post_error(resource, + ZWLR_INPUT_INHIBIT_MANAGER_V1_ERROR_ALREADY_INHIBITED, + "this compositor already has input inhibited"); + return; + } + + struct wl_resource *wl_resource = wl_resource_create(client, + &zwlr_input_inhibitor_v1_interface, + wl_resource_get_version(resource), id); + if (!wl_resource) { + wl_client_post_no_memory(client); + } + wl_resource_set_implementation(wl_resource, &input_inhibitor_implementation, + manager, input_inhibitor_resource_destroy); + + manager->active_client = client; + manager->active_inhibitor = wl_resource; + + wlr_signal_emit_safe(&manager->events.activate, manager); +} + +static const struct zwlr_input_inhibit_manager_v1_interface inhibit_manager_implementation = { + .get_inhibitor = inhibit_manager_get_inhibitor +}; + +static void input_manager_resource_destroy(struct wl_resource *resource) { + struct wlr_input_inhibit_manager *manager = + input_inhibit_manager_from_resource(resource); + struct wl_client *client = wl_resource_get_client(resource); + if (manager->active_client == client) { + input_inhibit_manager_deactivate(manager); + } +} + +static void inhibit_manager_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_input_inhibit_manager *manager = data; + assert(wl_client && manager); + + struct wl_resource *wl_resource = wl_resource_create(wl_client, + &zwlr_input_inhibit_manager_v1_interface, version, id); + if (wl_resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(wl_resource, + &inhibit_manager_implementation, manager, + input_manager_resource_destroy); +} + +void wlr_input_inhibit_manager_destroy( + struct wlr_input_inhibit_manager *manager) { + if (!manager) { + return; + } + if (manager->active_client) { + input_inhibitor_destroy(manager->active_client, + manager->active_inhibitor); + } + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_input_inhibit_manager *manager = + wl_container_of(listener, manager, display_destroy); + wlr_input_inhibit_manager_destroy(manager); +} + +struct wlr_input_inhibit_manager *wlr_input_inhibit_manager_create( + struct wl_display *display) { + // TODO: Client destroy + struct wlr_input_inhibit_manager *manager = + calloc(1, sizeof(struct wlr_input_inhibit_manager)); + if (!manager) { + return NULL; + } + + manager->global = wl_global_create(display, + &zwlr_input_inhibit_manager_v1_interface, + 1, manager, inhibit_manager_bind); + if (manager->global == NULL){ + wl_list_remove(&manager->display_destroy.link); + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.activate); + wl_signal_init(&manager->events.deactivate); + wl_signal_init(&manager->events.destroy); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/wlr_input_method_v2.c b/types/wlr_input_method_v2.c new file mode 100644 index 00000000..c68ff88b --- /dev/null +++ b/types/wlr_input_method_v2.c @@ -0,0 +1,298 @@ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#include <assert.h> +#include <string.h> +#include <wayland-util.h> +#include <wlr/types/wlr_input_method_v2.h> +#include <wlr/types/wlr_surface.h> +#include <wlr/util/log.h> +#include <xkbcommon/xkbcommon.h> +#include "input-method-unstable-v2-protocol.h" +#include "util/signal.h" + +static const struct zwp_input_method_v2_interface input_method_impl; + +static struct wlr_input_method_v2 *input_method_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_input_method_v2_interface, &input_method_impl)); + return wl_resource_get_user_data(resource); +} + +static void input_method_destroy(struct wlr_input_method_v2 *input_method) { + wlr_signal_emit_safe(&input_method->events.destroy, input_method); + wl_list_remove(&input_method->seat_destroy.link); + free(input_method->pending.commit_text); + free(input_method->pending.preedit.text); + free(input_method->current.commit_text); + free(input_method->current.preedit.text); + free(input_method); +} + +static void input_method_resource_destroy(struct wl_resource *resource) { + struct wlr_input_method_v2 *input_method = + input_method_from_resource(resource); + if (!input_method) { + return; + } + input_method_destroy(input_method); +} + +static void im_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void im_commit(struct wl_client *client, struct wl_resource *resource, + uint32_t serial) { + struct wlr_input_method_v2 *input_method = + input_method_from_resource(resource); + if (!input_method) { + return; + } + input_method->current = input_method->pending; + input_method->current_serial = serial; + struct wlr_input_method_v2_state default_state = {0}; + input_method->pending = default_state; + wlr_signal_emit_safe(&input_method->events.commit, (void*)input_method); +} + +static void im_commit_string(struct wl_client *client, + struct wl_resource *resource, const char *text) { + struct wlr_input_method_v2 *input_method = + input_method_from_resource(resource); + if (!input_method) { + return; + } + free(input_method->pending.commit_text); + input_method->pending.commit_text = strdup(text); +} + +static void im_set_preedit_string(struct wl_client *client, + struct wl_resource *resource, const char *text, int32_t cursor_begin, + int32_t cursor_end) { + struct wlr_input_method_v2 *input_method = + input_method_from_resource(resource); + if (!input_method) { + return; + } + input_method->pending.preedit.cursor_begin = cursor_begin; + input_method->pending.preedit.cursor_end = cursor_end; + free(input_method->pending.preedit.text); + input_method->pending.preedit.text = strdup(text); +} + +static void im_delete_surrounding_text(struct wl_client *client, + struct wl_resource *resource, + uint32_t before_length, uint32_t after_length) { + struct wlr_input_method_v2 *input_method = + input_method_from_resource(resource); + if (!input_method) { + return; + } + input_method->pending.delete.before_length = before_length; + input_method->pending.delete.after_length = after_length; +} + +static void im_get_input_popup_surface(struct wl_client *client, + struct wl_resource *resource, uint32_t id, + struct wl_resource *surface) { + wlr_log(WLR_INFO, "Stub: zwp_input_method_v2::get_input_popup_surface"); +} + + +static void im_grab_keyboard(struct wl_client *client, + struct wl_resource *resource, uint32_t keyboard) { + wlr_log(WLR_INFO, "Stub: zwp_input_method_v2::grab_keyboard"); +} + +static const struct zwp_input_method_v2_interface input_method_impl = { + .destroy = im_destroy, + .commit = im_commit, + .commit_string = im_commit_string, + .set_preedit_string = im_set_preedit_string, + .delete_surrounding_text = im_delete_surrounding_text, + .get_input_popup_surface = im_get_input_popup_surface, + .grab_keyboard = im_grab_keyboard, +}; + +void wlr_input_method_v2_send_activate( + struct wlr_input_method_v2 *input_method) { + zwp_input_method_v2_send_activate(input_method->resource); + input_method->active = true; +} + +void wlr_input_method_v2_send_deactivate( + struct wlr_input_method_v2 *input_method) { + zwp_input_method_v2_send_deactivate(input_method->resource); + input_method->active = false; +} + +void wlr_input_method_v2_send_surrounding_text( + struct wlr_input_method_v2 *input_method, const char *text, + uint32_t cursor, uint32_t anchor) { + const char *send_text = text; + if (!send_text) { + send_text = ""; + } + zwp_input_method_v2_send_surrounding_text(input_method->resource, send_text, + cursor, anchor); +} + +void wlr_input_method_v2_send_text_change_cause( + struct wlr_input_method_v2 *input_method, uint32_t cause) { + zwp_input_method_v2_send_text_change_cause(input_method->resource, cause); +} + +void wlr_input_method_v2_send_content_type( + struct wlr_input_method_v2 *input_method, + uint32_t hint, uint32_t purpose) { + zwp_input_method_v2_send_content_type(input_method->resource, hint, + purpose); +} + +void wlr_input_method_v2_send_done(struct wlr_input_method_v2 *input_method) { + zwp_input_method_v2_send_done(input_method->resource); + input_method->client_active = input_method->active; + input_method->current_serial++; +} + +void wlr_input_method_v2_send_unavailable( + struct wlr_input_method_v2 *input_method) { + zwp_input_method_v2_send_unavailable(input_method->resource); + struct wl_resource *resource = input_method->resource; + input_method_destroy(input_method); + wl_resource_set_user_data(resource, NULL); +} + +static const struct zwp_input_method_manager_v2_interface + input_method_manager_impl; + +static struct wlr_input_method_manager_v2 *input_method_manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_input_method_manager_v2_interface, &input_method_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void input_method_handle_seat_destroy(struct wl_listener *listener, + void *data) { + struct wlr_input_method_v2 *input_method = wl_container_of(listener, + input_method, seat_destroy); + wlr_input_method_v2_send_unavailable(input_method); +} + +static void manager_get_input_method(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat, + uint32_t input_method_id) { + struct wlr_input_method_manager_v2 *im_manager = + input_method_manager_from_resource(resource); + + struct wlr_input_method_v2 *input_method = calloc(1, + sizeof(struct wlr_input_method_v2)); + if (!input_method) { + wl_client_post_no_memory(client); + return; + } + wl_signal_init(&input_method->events.commit); + wl_signal_init(&input_method->events.destroy); + int version = wl_resource_get_version(resource); + struct wl_resource *im_resource = wl_resource_create(client, + &zwp_input_method_v2_interface, version, input_method_id); + if (im_resource == NULL) { + free(input_method); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(im_resource, &input_method_impl, + input_method, input_method_resource_destroy); + + struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat); + wl_signal_add(&seat_client->events.destroy, + &input_method->seat_destroy); + input_method->seat_destroy.notify = + input_method_handle_seat_destroy; + + input_method->resource = im_resource; + input_method->seat = seat_client->seat; + wl_list_insert(&im_manager->input_methods, + wl_resource_get_link(input_method->resource)); + wlr_signal_emit_safe(&im_manager->events.input_method, input_method); +} + +static void manager_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_input_method_manager_v2_interface + input_method_manager_impl = { + .get_input_method = manager_get_input_method, + .destroy = manager_destroy, +}; + +static void input_method_manager_unbind(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void input_method_manager_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + assert(wl_client); + struct wlr_input_method_manager_v2 *im_manager = data; + + struct wl_resource *bound_resource = wl_resource_create(wl_client, + &zwp_input_method_manager_v2_interface, version, id); + if (bound_resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(bound_resource, &input_method_manager_impl, + im_manager, input_method_manager_unbind); + wl_list_insert(&im_manager->bound_resources, + wl_resource_get_link(bound_resource)); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_input_method_manager_v2 *manager = + wl_container_of(listener, manager, display_destroy); + wlr_input_method_manager_v2_destroy(manager); +} + +struct wlr_input_method_manager_v2 *wlr_input_method_manager_v2_create( + struct wl_display *display) { + struct wlr_input_method_manager_v2 *im_manager = calloc(1, + sizeof(struct wlr_input_method_manager_v2)); + if (!im_manager) { + return NULL; + } + wl_signal_init(&im_manager->events.input_method); + wl_signal_init(&im_manager->events.destroy); + wl_list_init(&im_manager->bound_resources); + wl_list_init(&im_manager->input_methods); + + im_manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &im_manager->display_destroy); + + im_manager->global = wl_global_create(display, + &zwp_input_method_manager_v2_interface, 1, im_manager, + input_method_manager_bind); + return im_manager; +} + +void wlr_input_method_manager_v2_destroy( + struct wlr_input_method_manager_v2 *manager) { + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + + struct wl_resource *resource, *resource_tmp; + wl_resource_for_each_safe(resource, resource_tmp, + &manager->bound_resources) { + wl_resource_destroy(resource); + } + struct wlr_input_method_v2 *im, *im_tmp; + wl_list_for_each_safe(im, im_tmp, &manager->input_methods, link) { + wl_resource_destroy(im->resource); + } + wl_global_destroy(manager->global); + free(manager); +} diff --git a/types/wlr_keyboard.c b/types/wlr_keyboard.c new file mode 100644 index 00000000..89c7ca50 --- /dev/null +++ b/types/wlr_keyboard.c @@ -0,0 +1,237 @@ +#include "util/array.h" +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <wayland-server.h> +#include <wlr/interfaces/wlr_keyboard.h> +#include <wlr/types/wlr_keyboard.h> +#include <wlr/util/log.h> +#include "util/signal.h" + +static void keyboard_led_update(struct wlr_keyboard *keyboard) { + if (keyboard->xkb_state == NULL) { + return; + } + + uint32_t leds = 0; + for (uint32_t i = 0; i < WLR_LED_COUNT; ++i) { + if (xkb_state_led_index_is_active(keyboard->xkb_state, + keyboard->led_indexes[i])) { + leds |= (1 << i); + } + } + wlr_keyboard_led_update(keyboard, leds); +} + +/** + * Update the modifier state of the wlr-keyboard. Returns true if the modifier + * state changed. + */ +static bool keyboard_modifier_update(struct wlr_keyboard *keyboard) { + if (keyboard->xkb_state == NULL) { + return false; + } + + xkb_mod_mask_t depressed = xkb_state_serialize_mods(keyboard->xkb_state, + XKB_STATE_MODS_DEPRESSED); + xkb_mod_mask_t latched = xkb_state_serialize_mods(keyboard->xkb_state, + XKB_STATE_MODS_LATCHED); + xkb_mod_mask_t locked = xkb_state_serialize_mods(keyboard->xkb_state, + XKB_STATE_MODS_LOCKED); + xkb_mod_mask_t group = xkb_state_serialize_layout(keyboard->xkb_state, + XKB_STATE_LAYOUT_EFFECTIVE); + if (depressed == keyboard->modifiers.depressed && + latched == keyboard->modifiers.latched && + locked == keyboard->modifiers.locked && + group == keyboard->modifiers.group) { + return false; + } + + keyboard->modifiers.depressed = depressed; + keyboard->modifiers.latched = latched; + keyboard->modifiers.locked = locked; + keyboard->modifiers.group = group; + + return true; +} + +static void keyboard_key_update(struct wlr_keyboard *keyboard, + struct wlr_event_keyboard_key *event) { + bool found = false; + size_t i = 0; + for (; i < keyboard->num_keycodes; ++i) { + if (keyboard->keycodes[i] == event->keycode) { + found = true; + break; + } + } + + if (event->state == WLR_KEY_PRESSED && !found && + keyboard->num_keycodes < WLR_KEYBOARD_KEYS_CAP) { + keyboard->keycodes[keyboard->num_keycodes++] = event->keycode; + } + if (event->state == WLR_KEY_RELEASED && found) { + keyboard->keycodes[i] = 0; + keyboard->num_keycodes = push_zeroes_to_end(keyboard->keycodes, WLR_KEYBOARD_KEYS_CAP); + } + + assert(keyboard->num_keycodes <= WLR_KEYBOARD_KEYS_CAP); +} + +void wlr_keyboard_notify_modifiers(struct wlr_keyboard *keyboard, + uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) { + if (keyboard->xkb_state == NULL) { + return; + } + xkb_state_update_mask(keyboard->xkb_state, mods_depressed, mods_latched, + mods_locked, 0, 0, group); + + bool updated = keyboard_modifier_update(keyboard); + if (updated) { + wlr_signal_emit_safe(&keyboard->events.modifiers, keyboard); + } +} + +void wlr_keyboard_notify_key(struct wlr_keyboard *keyboard, + struct wlr_event_keyboard_key *event) { + if (keyboard->xkb_state == NULL) { + return; + } + + keyboard_key_update(keyboard, event); + wlr_signal_emit_safe(&keyboard->events.key, event); + + if (event->update_state) { + uint32_t keycode = event->keycode + 8; + xkb_state_update_key(keyboard->xkb_state, keycode, + event->state == WLR_KEY_PRESSED ? XKB_KEY_DOWN : XKB_KEY_UP); + } + keyboard_led_update(keyboard); + + bool updated = keyboard_modifier_update(keyboard); + if (updated) { + wlr_signal_emit_safe(&keyboard->events.modifiers, keyboard); + } +} + +void wlr_keyboard_init(struct wlr_keyboard *kb, + const struct wlr_keyboard_impl *impl) { + kb->impl = impl; + wl_signal_init(&kb->events.key); + wl_signal_init(&kb->events.modifiers); + wl_signal_init(&kb->events.keymap); + wl_signal_init(&kb->events.repeat_info); + + // Sane defaults + kb->repeat_info.rate = 25; + kb->repeat_info.delay = 600; +} + +void wlr_keyboard_destroy(struct wlr_keyboard *kb) { + if (kb == NULL) { + return; + } + xkb_state_unref(kb->xkb_state); + xkb_keymap_unref(kb->keymap); + free(kb->keymap_string); + if (kb->impl && kb->impl->destroy) { + kb->impl->destroy(kb); + } else { + wl_list_remove(&kb->events.key.listener_list); + free(kb); + } +} + +void wlr_keyboard_led_update(struct wlr_keyboard *kb, uint32_t leds) { + if (kb->impl && kb->impl->led_update) { + kb->impl->led_update(kb, leds); + } +} + +void wlr_keyboard_set_keymap(struct wlr_keyboard *kb, + struct xkb_keymap *keymap) { + xkb_keymap_unref(kb->keymap); + kb->keymap = xkb_keymap_ref(keymap); + + xkb_state_unref(kb->xkb_state); + kb->xkb_state = xkb_state_new(kb->keymap); + if (kb->xkb_state == NULL) { + wlr_log(WLR_ERROR, "Failed to create XKB state"); + goto err; + } + + const char *led_names[WLR_LED_COUNT] = { + XKB_LED_NAME_NUM, + XKB_LED_NAME_CAPS, + XKB_LED_NAME_SCROLL, + }; + for (size_t i = 0; i < WLR_LED_COUNT; ++i) { + kb->led_indexes[i] = xkb_map_led_get_index(kb->keymap, led_names[i]); + } + + const char *mod_names[WLR_MODIFIER_COUNT] = { + XKB_MOD_NAME_SHIFT, + XKB_MOD_NAME_CAPS, + XKB_MOD_NAME_CTRL, // "Control" + XKB_MOD_NAME_ALT, // "Mod1" + XKB_MOD_NAME_NUM, // "Mod2" + "Mod3", + XKB_MOD_NAME_LOGO, // "Mod4" + "Mod5", + }; + // TODO: there's also "Ctrl", "Alt"? + for (size_t i = 0; i < WLR_MODIFIER_COUNT; ++i) { + kb->mod_indexes[i] = xkb_map_mod_get_index(kb->keymap, mod_names[i]); + } + + char *tmp_keymap_string = xkb_keymap_get_as_string(kb->keymap, + XKB_KEYMAP_FORMAT_TEXT_V1); + if (tmp_keymap_string == NULL) { + wlr_log(WLR_ERROR, "Failed to get string version of keymap"); + goto err; + } + free(kb->keymap_string); + kb->keymap_string = tmp_keymap_string; + kb->keymap_size = strlen(kb->keymap_string) + 1; + + for (size_t i = 0; i < kb->num_keycodes; ++i) { + xkb_keycode_t keycode = kb->keycodes[i] + 8; + xkb_state_update_key(kb->xkb_state, keycode, XKB_KEY_DOWN); + } + + keyboard_modifier_update(kb); + + wlr_signal_emit_safe(&kb->events.keymap, kb); + return; + +err: + xkb_state_unref(kb->xkb_state); + kb->xkb_state = NULL; + xkb_keymap_unref(keymap); + kb->keymap = NULL; + free(kb->keymap_string); + kb->keymap_string = NULL; +} + +void wlr_keyboard_set_repeat_info(struct wlr_keyboard *kb, int32_t rate, + int32_t delay) { + if (kb->repeat_info.rate == rate && kb->repeat_info.delay == delay) { + return; + } + kb->repeat_info.rate = rate; + kb->repeat_info.delay = delay; + wlr_signal_emit_safe(&kb->events.repeat_info, kb); +} + +uint32_t wlr_keyboard_get_modifiers(struct wlr_keyboard *kb) { + xkb_mod_mask_t mask = kb->modifiers.depressed | kb->modifiers.latched; + uint32_t modifiers = 0; + for (size_t i = 0; i < WLR_MODIFIER_COUNT; ++i) { + if (kb->mod_indexes[i] != XKB_MOD_INVALID && + (mask & (1 << kb->mod_indexes[i]))) { + modifiers |= (1 << i); + } + } + return modifiers; +} diff --git a/types/wlr_layer_shell_v1.c b/types/wlr_layer_shell_v1.c new file mode 100644 index 00000000..aa959d89 --- /dev/null +++ b/types/wlr_layer_shell_v1.c @@ -0,0 +1,564 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <wayland-server.h> +#include <wlr/types/wlr_layer_shell_v1.h> +#include <wlr/types/wlr_output.h> +#include <wlr/types/wlr_surface.h> +#include <wlr/types/wlr_xdg_shell.h> +#include <wlr/util/log.h> +#include "util/signal.h" +#include "wlr-layer-shell-unstable-v1-protocol.h" + +static void resource_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwlr_layer_shell_v1_interface layer_shell_implementation; +static const struct zwlr_layer_surface_v1_interface layer_surface_implementation; + +static struct wlr_layer_shell_v1 *layer_shell_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwlr_layer_shell_v1_interface, + &layer_shell_implementation)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_layer_surface_v1 *layer_surface_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwlr_layer_surface_v1_interface, + &layer_surface_implementation)); + return wl_resource_get_user_data(resource); +} + +static const struct wlr_surface_role layer_surface_role; + +bool wlr_surface_is_layer_surface(struct wlr_surface *surface) { + return surface->role == &layer_surface_role; +} + +struct wlr_layer_surface_v1 *wlr_layer_surface_v1_from_wlr_surface( + struct wlr_surface *surface) { + assert(wlr_surface_is_layer_surface(surface)); + return (struct wlr_layer_surface_v1 *)surface->role_data; +} + +static void layer_surface_configure_destroy( + struct wlr_layer_surface_v1_configure *configure) { + if (configure == NULL) { + return; + } + wl_list_remove(&configure->link); + free(configure); +} + +static void layer_surface_handle_ack_configure(struct wl_client *client, + struct wl_resource *resource, uint32_t serial) { + struct wlr_layer_surface_v1 *surface = layer_surface_from_resource(resource); + + bool found = false; + struct wlr_layer_surface_v1_configure *configure, *tmp; + wl_list_for_each_safe(configure, tmp, &surface->configure_list, link) { + if (configure->serial < serial) { + layer_surface_configure_destroy(configure); + } else if (configure->serial == serial) { + found = true; + break; + } else { + break; + } + } + if (!found) { + wl_resource_post_error(resource, + ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SURFACE_STATE, + "wrong configure serial: %u", serial); + return; + } + + if (surface->acked_configure) { + layer_surface_configure_destroy(surface->acked_configure); + } + surface->acked_configure = configure; + wl_list_remove(&configure->link); + wl_list_init(&configure->link); +} + +static void layer_surface_handle_set_size(struct wl_client *client, + struct wl_resource *resource, uint32_t width, uint32_t height) { + struct wlr_layer_surface_v1 *surface = layer_surface_from_resource(resource); + surface->client_pending.desired_width = width; + surface->client_pending.desired_height = height; +} + +static void layer_surface_handle_set_anchor(struct wl_client *client, + struct wl_resource *resource, uint32_t anchor) { + const uint32_t max_anchor = + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + if (anchor > max_anchor) { + wl_resource_post_error(resource, + ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_ANCHOR, + "invalid anchor %d", anchor); + } + struct wlr_layer_surface_v1 *surface = layer_surface_from_resource(resource); + surface->client_pending.anchor = anchor; +} + +static void layer_surface_handle_set_exclusive_zone(struct wl_client *client, + struct wl_resource *resource, int32_t zone) { + struct wlr_layer_surface_v1 *surface = layer_surface_from_resource(resource); + surface->client_pending.exclusive_zone = zone; +} + +static void layer_surface_handle_set_margin( + struct wl_client *client, struct wl_resource *resource, + int32_t top, int32_t right, int32_t bottom, int32_t left) { + struct wlr_layer_surface_v1 *surface = layer_surface_from_resource(resource); + surface->client_pending.margin.top = top; + surface->client_pending.margin.right = right; + surface->client_pending.margin.bottom = bottom; + surface->client_pending.margin.left = left; +} + +static void layer_surface_handle_set_keyboard_interactivity( + struct wl_client *client, struct wl_resource *resource, + uint32_t interactive) { + struct wlr_layer_surface_v1 *surface = layer_surface_from_resource(resource); + surface->client_pending.keyboard_interactive = !!interactive; +} + +static void layer_surface_handle_get_popup(struct wl_client *client, + struct wl_resource *layer_resource, + struct wl_resource *popup_resource) { + struct wlr_layer_surface_v1 *parent = + layer_surface_from_resource(layer_resource); + struct wlr_xdg_surface *popup_surface = + wlr_xdg_surface_from_popup_resource(popup_resource); + + assert(popup_surface->role == WLR_XDG_SURFACE_ROLE_POPUP); + struct wlr_xdg_popup *popup = popup_surface->popup; + popup->parent = parent->surface; + wl_list_insert(&parent->popups, &popup->link); + wlr_signal_emit_safe(&parent->events.new_popup, popup); +} + +static const struct zwlr_layer_surface_v1_interface layer_surface_implementation = { + .destroy = resource_handle_destroy, + .ack_configure = layer_surface_handle_ack_configure, + .set_size = layer_surface_handle_set_size, + .set_anchor = layer_surface_handle_set_anchor, + .set_exclusive_zone = layer_surface_handle_set_exclusive_zone, + .set_margin = layer_surface_handle_set_margin, + .set_keyboard_interactivity = layer_surface_handle_set_keyboard_interactivity, + .get_popup = layer_surface_handle_get_popup, +}; + +static void layer_surface_unmap(struct wlr_layer_surface_v1 *surface) { + // TODO: probably need to ungrab before this event + wlr_signal_emit_safe(&surface->events.unmap, surface); + + struct wlr_layer_surface_v1_configure *configure, *tmp; + wl_list_for_each_safe(configure, tmp, &surface->configure_list, link) { + layer_surface_configure_destroy(configure); + } + + surface->configured = surface->mapped = false; + surface->configure_serial = 0; + if (surface->configure_idle) { + wl_event_source_remove(surface->configure_idle); + surface->configure_idle = NULL; + } + surface->configure_next_serial = 0; +} + +static void layer_surface_destroy(struct wlr_layer_surface_v1 *surface) { + if (surface->configured && surface->mapped) { + layer_surface_unmap(surface); + } + wlr_signal_emit_safe(&surface->events.destroy, surface); + wl_resource_set_user_data(surface->resource, NULL); + surface->surface->role_data = NULL; + wl_list_remove(&surface->surface_destroy.link); + wl_list_remove(&surface->link); + free(surface->namespace); + free(surface); +} + +static void layer_surface_resource_destroy(struct wl_resource *resource) { + struct wlr_layer_surface_v1 *surface = + layer_surface_from_resource(resource); + if (surface != NULL) { + layer_surface_destroy(surface); + } +} + +static bool layer_surface_state_changed(struct wlr_layer_surface_v1 *surface) { + struct wlr_layer_surface_v1_state *state; + if (wl_list_empty(&surface->configure_list)) { + if (surface->acked_configure) { + state = &surface->acked_configure->state; + } else if (!surface->configured) { + return true; + } else { + state = &surface->current; + } + } else { + struct wlr_layer_surface_v1_configure *configure = + wl_container_of(surface->configure_list.prev, configure, link); + state = &configure->state; + } + + bool changed = state->actual_width != surface->server_pending.actual_width + || state->actual_height != surface->server_pending.actual_height; + return changed; +} + +void wlr_layer_surface_v1_configure(struct wlr_layer_surface_v1 *surface, + uint32_t width, uint32_t height) { + surface->server_pending.actual_width = width; + surface->server_pending.actual_height = height; + if (layer_surface_state_changed(surface)) { + struct wl_display *display = + wl_client_get_display(wl_resource_get_client(surface->resource)); + struct wlr_layer_surface_v1_configure *configure = + calloc(1, sizeof(struct wlr_layer_surface_v1_configure)); + if (configure == NULL) { + wl_client_post_no_memory(wl_resource_get_client(surface->resource)); + return; + } + surface->configure_next_serial = wl_display_next_serial(display); + wl_list_insert(surface->configure_list.prev, &configure->link); + configure->state.actual_width = width; + configure->state.actual_height = height; + configure->serial = surface->configure_next_serial; + zwlr_layer_surface_v1_send_configure(surface->resource, + configure->serial, configure->state.actual_width, + configure->state.actual_height); + } +} + +void wlr_layer_surface_v1_close(struct wlr_layer_surface_v1 *surface) { + if (surface->closed) { + return; + } + surface->closed = true; + layer_surface_unmap(surface); + zwlr_layer_surface_v1_send_closed(surface->resource); +} + +static void layer_surface_role_commit(struct wlr_surface *wlr_surface) { + struct wlr_layer_surface_v1 *surface = + wlr_layer_surface_v1_from_wlr_surface(wlr_surface); + if (surface == NULL) { + return; + } + + if (surface->closed) { + // Ignore commits after the compositor has closed it + return; + } + + if (surface->acked_configure) { + struct wlr_layer_surface_v1_configure *configure = + surface->acked_configure; + surface->configured = true; + surface->configure_serial = configure->serial; + surface->current.actual_width = configure->state.actual_width; + surface->current.actual_height = configure->state.actual_height; + layer_surface_configure_destroy(configure); + surface->acked_configure = NULL; + } + + if (wlr_surface_has_buffer(surface->surface) && !surface->configured) { + wl_resource_post_error(surface->resource, + ZWLR_LAYER_SHELL_V1_ERROR_ALREADY_CONSTRUCTED, + "layer_surface has never been configured"); + return; + } + + surface->current.anchor = surface->client_pending.anchor; + surface->current.exclusive_zone = surface->client_pending.exclusive_zone; + surface->current.margin = surface->client_pending.margin; + surface->current.keyboard_interactive = + surface->client_pending.keyboard_interactive; + surface->current.desired_width = surface->client_pending.desired_width; + surface->current.desired_height = surface->client_pending.desired_height; + + if (!surface->added) { + surface->added = true; + wlr_signal_emit_safe(&surface->shell->events.new_surface, + surface); + // either the compositor found a suitable output or it must + // have closed the surface + assert(surface->output || surface->closed); + } + if (surface->configured && wlr_surface_has_buffer(surface->surface) && + !surface->mapped) { + surface->mapped = true; + wlr_signal_emit_safe(&surface->events.map, surface); + } + if (surface->configured && !wlr_surface_has_buffer(surface->surface) && + surface->mapped) { + layer_surface_unmap(surface); + } +} + +static const struct wlr_surface_role layer_surface_role = { + .name = "zwlr_layer_surface_v1", + .commit = layer_surface_role_commit, +}; + +static void handle_surface_destroyed(struct wl_listener *listener, + void *data) { + struct wlr_layer_surface_v1 *layer_surface = + wl_container_of(listener, layer_surface, surface_destroy); + layer_surface_destroy(layer_surface); +} + +static void layer_shell_handle_get_layer_surface(struct wl_client *wl_client, + struct wl_resource *client_resource, uint32_t id, + struct wl_resource *surface_resource, + struct wl_resource *output_resource, + uint32_t layer, const char *namespace) { + struct wlr_layer_shell_v1 *shell = + layer_shell_from_resource(client_resource); + struct wlr_surface *wlr_surface = + wlr_surface_from_resource(surface_resource); + + struct wlr_layer_surface_v1 *surface = + calloc(1, sizeof(struct wlr_layer_surface_v1)); + if (surface == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + if (!wlr_surface_set_role(wlr_surface, &layer_surface_role, surface, + client_resource, ZWLR_LAYER_SHELL_V1_ERROR_ROLE)) { + free(surface); + return; + } + + surface->shell = shell; + surface->surface = wlr_surface; + if (output_resource) { + surface->output = wlr_output_from_resource(output_resource); + } + surface->layer = layer; + if (layer > ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY) { + free(surface); + wl_resource_post_error(client_resource, + ZWLR_LAYER_SHELL_V1_ERROR_INVALID_LAYER, + "Invalid layer %d", layer); + return; + } + surface->namespace = strdup(namespace); + if (surface->namespace == NULL) { + free(surface); + wl_client_post_no_memory(wl_client); + return; + } + surface->resource = wl_resource_create(wl_client, + &zwlr_layer_surface_v1_interface, + wl_resource_get_version(client_resource), + id); + if (surface->resource == NULL) { + free(surface->namespace); + free(surface); + wl_client_post_no_memory(wl_client); + return; + } + + wl_list_init(&surface->configure_list); + wl_list_init(&surface->popups); + + wl_signal_init(&surface->events.destroy); + wl_signal_init(&surface->events.map); + wl_signal_init(&surface->events.unmap); + wl_signal_init(&surface->events.new_popup); + + wl_signal_add(&surface->surface->events.destroy, + &surface->surface_destroy); + surface->surface_destroy.notify = handle_surface_destroyed; + + wlr_log(WLR_DEBUG, "new layer_surface %p (res %p)", + surface, surface->resource); + wl_resource_set_implementation(surface->resource, + &layer_surface_implementation, surface, layer_surface_resource_destroy); + wl_list_insert(&shell->surfaces, &surface->link); +} + +static const struct zwlr_layer_shell_v1_interface layer_shell_implementation = { + .get_layer_surface = layer_shell_handle_get_layer_surface, +}; + +static void client_handle_destroy(struct wl_resource *resource) { + struct wl_client *client = wl_resource_get_client(resource); + struct wlr_layer_shell_v1 *shell = layer_shell_from_resource(resource); + struct wlr_layer_surface_v1 *surface, *tmp = NULL; + wl_list_for_each_safe(surface, tmp, &shell->surfaces, link) { + if (wl_resource_get_client(surface->resource) == client) { + layer_surface_destroy(surface); + } + } + wl_list_remove(wl_resource_get_link(resource)); +} + +static void layer_shell_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_layer_shell_v1 *layer_shell = data; + assert(wl_client && layer_shell); + + struct wl_resource *resource = wl_resource_create( + wl_client, &zwlr_layer_shell_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(resource, + &layer_shell_implementation, layer_shell, client_handle_destroy); + wl_list_insert(&layer_shell->resources, wl_resource_get_link(resource)); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_layer_shell_v1 *layer_shell = + wl_container_of(listener, layer_shell, display_destroy); + wlr_layer_shell_v1_destroy(layer_shell); +} + +struct wlr_layer_shell_v1 *wlr_layer_shell_v1_create(struct wl_display *display) { + struct wlr_layer_shell_v1 *layer_shell = + calloc(1, sizeof(struct wlr_layer_shell_v1)); + if (!layer_shell) { + return NULL; + } + + wl_list_init(&layer_shell->resources); + wl_list_init(&layer_shell->surfaces); + + struct wl_global *global = wl_global_create(display, + &zwlr_layer_shell_v1_interface, 1, layer_shell, layer_shell_bind); + if (!global) { + free(layer_shell); + return NULL; + } + layer_shell->global = global; + + wl_signal_init(&layer_shell->events.new_surface); + wl_signal_init(&layer_shell->events.destroy); + + layer_shell->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &layer_shell->display_destroy); + + return layer_shell; +} + +void wlr_layer_shell_v1_destroy(struct wlr_layer_shell_v1 *layer_shell) { + if (!layer_shell) { + return; + } + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &layer_shell->resources) { + wl_resource_destroy(resource); + } + wlr_signal_emit_safe(&layer_shell->events.destroy, layer_shell); + wl_list_remove(&layer_shell->display_destroy.link); + wl_global_destroy(layer_shell->global); + free(layer_shell); +} + +struct layer_surface_iterator_data { + wlr_surface_iterator_func_t user_iterator; + void *user_data; + int x, y; +}; + +static void layer_surface_iterator(struct wlr_surface *surface, + int sx, int sy, void *data) { + struct layer_surface_iterator_data *iter_data = data; + iter_data->user_iterator(surface, iter_data->x + sx, iter_data->y + sy, + iter_data->user_data); +} + +static void xdg_surface_for_each_surface(struct wlr_xdg_surface *surface, + int x, int y, wlr_surface_iterator_func_t iterator, void *user_data) { + struct layer_surface_iterator_data data = { + .user_iterator = iterator, + .user_data = user_data, + .x = x, .y = y, + }; + wlr_surface_for_each_surface( + surface->surface, layer_surface_iterator, &data); + + struct wlr_xdg_popup *popup_state; + wl_list_for_each(popup_state, &surface->popups, link) { + struct wlr_xdg_surface *popup = popup_state->base; + if (!popup->configured) { + continue; + } + + double popup_sx = popup_state->geometry.x - popup_state->base->geometry.x; + double popup_sy = popup_state->geometry.y - popup_state->base->geometry.y; + + xdg_surface_for_each_surface(popup, + x + popup_sx, + y + popup_sy, + iterator, user_data); + } +} + +static void layer_surface_for_each_surface(struct wlr_layer_surface_v1 *surface, + int x, int y, wlr_surface_iterator_func_t iterator, void *user_data) { + struct layer_surface_iterator_data data = { + .user_iterator = iterator, + .user_data = user_data, + .x = x, .y = y, + }; + wlr_surface_for_each_surface(surface->surface, + layer_surface_iterator, &data); + + struct wlr_xdg_popup *popup_state; + wl_list_for_each(popup_state, &surface->popups, link) { + struct wlr_xdg_surface *popup = popup_state->base; + if (!popup->configured) { + continue; + } + + double popup_sx, popup_sy; + popup_sx = popup->popup->geometry.x - popup->geometry.x; + popup_sy = popup->popup->geometry.y - popup->geometry.y; + + xdg_surface_for_each_surface(popup, + popup_sx, popup_sy, iterator, user_data); + } +} + +void wlr_layer_surface_v1_for_each_surface(struct wlr_layer_surface_v1 *surface, + wlr_surface_iterator_func_t iterator, void *user_data) { + layer_surface_for_each_surface(surface, 0, 0, iterator, user_data); +} + +struct wlr_surface *wlr_layer_surface_v1_surface_at( + struct wlr_layer_surface_v1 *surface, double sx, double sy, + double *sub_x, double *sub_y) { + struct wlr_xdg_popup *popup_state; + wl_list_for_each(popup_state, &surface->popups, link) { + struct wlr_xdg_surface *popup = popup_state->base; + + double popup_sx = popup_state->geometry.x - popup->geometry.x; + double popup_sy = popup_state->geometry.y - popup->geometry.y; + + struct wlr_surface *sub = wlr_xdg_surface_surface_at(popup, + sx - popup_sx, + sy - popup_sy, + sub_x, sub_y); + if (sub != NULL) { + return sub; + } + } + + return wlr_surface_surface_at(surface->surface, sx, sy, sub_x, sub_y); +} diff --git a/types/wlr_linux_dmabuf_v1.c b/types/wlr_linux_dmabuf_v1.c new file mode 100644 index 00000000..eb7b2f9e --- /dev/null +++ b/types/wlr_linux_dmabuf_v1.c @@ -0,0 +1,509 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <stdlib.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_linux_dmabuf_v1.h> +#include <wlr/util/log.h> +#include "linux-dmabuf-unstable-v1-protocol.h" +#include "util/signal.h" + +#define LINUX_DMABUF_VERSION 3 + +static void buffer_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_buffer_interface buffer_impl = { + .destroy = buffer_handle_destroy, +}; + +bool wlr_dmabuf_v1_resource_is_buffer(struct wl_resource *buffer_resource) { + if (!wl_resource_instance_of(buffer_resource, &wl_buffer_interface, + &buffer_impl)) { + return false; + } + + struct wlr_dmabuf_v1_buffer *buffer = + wl_resource_get_user_data(buffer_resource); + if (buffer && buffer->buffer_resource && !buffer->params_resource && + buffer->buffer_resource == buffer_resource) { + return true; + } + + return false; +} + +struct wlr_dmabuf_v1_buffer *wlr_dmabuf_v1_buffer_from_buffer_resource( + struct wl_resource *buffer_resource) { + assert(wl_resource_instance_of(buffer_resource, &wl_buffer_interface, + &buffer_impl)); + + struct wlr_dmabuf_v1_buffer *buffer = + wl_resource_get_user_data(buffer_resource); + assert(buffer); + assert(buffer->buffer_resource); + assert(!buffer->params_resource); + assert(buffer->buffer_resource == buffer_resource); + + return buffer; +} + +static void linux_dmabuf_buffer_destroy(struct wlr_dmabuf_v1_buffer *buffer) { + wlr_dmabuf_attributes_finish(&buffer->attributes); + free(buffer); +} + +static void params_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void params_add(struct wl_client *client, + struct wl_resource *params_resource, int32_t fd, + uint32_t plane_idx, uint32_t offset, uint32_t stride, + uint32_t modifier_hi, uint32_t modifier_lo) { + struct wlr_dmabuf_v1_buffer *buffer = + wlr_dmabuf_v1_buffer_from_params_resource(params_resource); + + if (!buffer) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, + "params was already used to create a wl_buffer"); + close(fd); + return; + } + + if (plane_idx >= WLR_DMABUF_MAX_PLANES) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX, + "plane index %u > %u", plane_idx, WLR_DMABUF_MAX_PLANES); + close(fd); + return; + } + + if (buffer->attributes.fd[plane_idx] != -1) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET, + "a dmabuf with FD %d has already been added for plane %u", + buffer->attributes.fd[plane_idx], plane_idx); + close(fd); + return; + } + + uint64_t modifier = ((uint64_t)modifier_hi << 32) | modifier_lo; + if (buffer->has_modifier && modifier != buffer->attributes.modifier) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT, + "sent modifier %" PRIu64 " for plane %u, expected" + " modifier %" PRIu64 " like other planes", + modifier, plane_idx, buffer->attributes.modifier); + close(fd); + return; + } + + buffer->attributes.modifier = modifier; + buffer->has_modifier = true; + + buffer->attributes.fd[plane_idx] = fd; + buffer->attributes.offset[plane_idx] = offset; + buffer->attributes.stride[plane_idx] = stride; + buffer->attributes.n_planes++; +} + +static void buffer_handle_resource_destroy(struct wl_resource *buffer_resource) { + struct wlr_dmabuf_v1_buffer *buffer = + wlr_dmabuf_v1_buffer_from_buffer_resource(buffer_resource); + linux_dmabuf_buffer_destroy(buffer); +} + +static bool check_import_dmabuf(struct wlr_dmabuf_v1_buffer *buffer) { + struct wlr_texture *texture = + wlr_texture_from_dmabuf(buffer->renderer, &buffer->attributes); + if (texture == NULL) { + return false; + } + + // We can import the image, good. No need to keep it since wlr_surface will + // import it again on commit. + wlr_texture_destroy(texture); + return true; +} + +static void params_create_common(struct wl_client *client, + struct wl_resource *params_resource, uint32_t buffer_id, int32_t width, + int32_t height, uint32_t format, uint32_t flags) { + if (!wl_resource_get_user_data(params_resource)) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, + "params was already used to create a wl_buffer"); + return; + } + struct wlr_dmabuf_v1_buffer *buffer = + wlr_dmabuf_v1_buffer_from_params_resource(params_resource); + + /* Switch the linux_dmabuf_buffer object from params resource to + * eventually wl_buffer resource. */ + wl_resource_set_user_data(buffer->params_resource, NULL); + buffer->params_resource = NULL; + + if (!buffer->attributes.n_planes) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, + "no dmabuf has been added to the params"); + goto err_out; + } + + if (buffer->attributes.fd[0] == -1) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, + "no dmabuf has been added for plane 0"); + goto err_out; + } + + if ((buffer->attributes.fd[3] >= 0 || buffer->attributes.fd[2] >= 0) && + (buffer->attributes.fd[2] == -1 || buffer->attributes.fd[1] == -1)) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, + "gap in dmabuf planes"); + goto err_out; + } + + buffer->attributes.width = width; + buffer->attributes.height = height; + buffer->attributes.format = format; + buffer->attributes.flags = flags; + + if (width < 1 || height < 1) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS, + "invalid width %d or height %d", width, height); + goto err_out; + } + + for (int i = 0; i < buffer->attributes.n_planes; i++) { + if ((uint64_t)buffer->attributes.offset[i] + + buffer->attributes.stride[i] > UINT32_MAX) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "size overflow for plane %d", i); + goto err_out; + } + + if ((uint64_t)buffer->attributes.offset[i] + + (uint64_t)buffer->attributes.stride[i] * height > UINT32_MAX) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "size overflow for plane %d", i); + goto err_out; + } + + off_t size = lseek(buffer->attributes.fd[i], 0, SEEK_END); + if (size == -1) { + // Skip checks if kernel does no support seek on buffer + continue; + } + if (buffer->attributes.offset[i] > size) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "invalid offset %i for plane %d", + buffer->attributes.offset[i], i); + goto err_out; + } + + if (buffer->attributes.offset[i] + buffer->attributes.stride[i] > size || + buffer->attributes.stride[i] == 0) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "invalid stride %i for plane %d", + buffer->attributes.stride[i], i); + goto err_out; + } + + // planes > 0 might be subsampled according to fourcc format + if (i == 0 && buffer->attributes.offset[i] + + buffer->attributes.stride[i] * height > size) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, + "invalid buffer stride or height for plane %d", i); + goto err_out; + } + } + + /* reject unknown flags */ + if (buffer->attributes.flags & ~ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT, + "Unknown dmabuf flags %"PRIu32, buffer->attributes.flags); + goto err_out; + } + + /* Check if dmabuf is usable */ + if (!check_import_dmabuf(buffer)) { + goto err_failed; + } + + buffer->buffer_resource = wl_resource_create(client, &wl_buffer_interface, + 1, buffer_id); + if (!buffer->buffer_resource) { + wl_resource_post_no_memory(params_resource); + goto err_failed; + } + + wl_resource_set_implementation(buffer->buffer_resource, + &buffer_impl, buffer, buffer_handle_resource_destroy); + + /* send 'created' event when the request is not for an immediate + * import, that is buffer_id is zero */ + if (buffer_id == 0) { + zwp_linux_buffer_params_v1_send_created(params_resource, + buffer->buffer_resource); + } + return; + +err_failed: + if (buffer_id == 0) { + zwp_linux_buffer_params_v1_send_failed(params_resource); + } else { + /* since the behavior is left implementation defined by the + * protocol in case of create_immed failure due to an unknown cause, + * we choose to treat it as a fatal error and immediately kill the + * client instead of creating an invalid handle and waiting for it + * to be used. + */ + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER, + "importing the supplied dmabufs failed"); + } +err_out: + linux_dmabuf_buffer_destroy(buffer); +} + +static void params_create(struct wl_client *client, + struct wl_resource *params_resource, + int32_t width, int32_t height, uint32_t format, uint32_t flags) { + params_create_common(client, params_resource, 0, width, height, format, + flags); +} + +static void params_create_immed(struct wl_client *client, + struct wl_resource *params_resource, uint32_t buffer_id, + int32_t width, int32_t height, uint32_t format, uint32_t flags) { + params_create_common(client, params_resource, buffer_id, width, height, + format, flags); +} + +static const struct zwp_linux_buffer_params_v1_interface + linux_buffer_params_impl = { + .destroy = params_destroy, + .add = params_add, + .create = params_create, + .create_immed = params_create_immed, +}; + +struct wlr_dmabuf_v1_buffer *wlr_dmabuf_v1_buffer_from_params_resource( + struct wl_resource *params_resource) { + assert(wl_resource_instance_of(params_resource, + &zwp_linux_buffer_params_v1_interface, + &linux_buffer_params_impl)); + + struct wlr_dmabuf_v1_buffer *buffer = + wl_resource_get_user_data(params_resource); + assert(buffer); + assert(buffer->params_resource); + assert(!buffer->buffer_resource); + assert(buffer->params_resource == params_resource); + + return buffer; +} + +static void handle_params_destroy(struct wl_resource *params_resource) { + /* Check for NULL since wlr_dmabuf_v1_buffer_from_params_resource will choke */ + if (!wl_resource_get_user_data(params_resource)) { + return; + } + + struct wlr_dmabuf_v1_buffer *buffer = + wlr_dmabuf_v1_buffer_from_params_resource(params_resource); + linux_dmabuf_buffer_destroy(buffer); +} + +static void linux_dmabuf_create_params(struct wl_client *client, + struct wl_resource *linux_dmabuf_resource, + uint32_t params_id) { + struct wlr_linux_dmabuf_v1 *linux_dmabuf = + wlr_linux_dmabuf_v1_from_resource(linux_dmabuf_resource); + + uint32_t version = wl_resource_get_version(linux_dmabuf_resource); + struct wlr_dmabuf_v1_buffer *buffer = calloc(1, sizeof *buffer); + if (!buffer) { + goto err; + } + + for (int i = 0; i < WLR_DMABUF_MAX_PLANES; i++) { + buffer->attributes.fd[i] = -1; + } + + buffer->renderer = linux_dmabuf->renderer; + buffer->params_resource = wl_resource_create(client, + &zwp_linux_buffer_params_v1_interface, version, params_id); + if (!buffer->params_resource) { + goto err_free; + } + + wl_resource_set_implementation(buffer->params_resource, + &linux_buffer_params_impl, buffer, handle_params_destroy); + return; + +err_free: + free(buffer); +err: + wl_resource_post_no_memory(linux_dmabuf_resource); +} + +static void linux_dmabuf_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_linux_dmabuf_v1_interface linux_dmabuf_impl = { + .destroy = linux_dmabuf_destroy, + .create_params = linux_dmabuf_create_params, +}; + +struct wlr_linux_dmabuf_v1 *wlr_linux_dmabuf_v1_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_linux_dmabuf_v1_interface, + &linux_dmabuf_impl)); + + struct wlr_linux_dmabuf_v1 *dmabuf = wl_resource_get_user_data(resource); + assert(dmabuf); + return dmabuf; +} + +static void linux_dmabuf_send_formats(struct wlr_linux_dmabuf_v1 *linux_dmabuf, + struct wl_resource *resource, uint32_t version) { + struct wlr_renderer *renderer = linux_dmabuf->renderer; + uint64_t modifier_invalid = DRM_FORMAT_MOD_INVALID; + int *formats = NULL; + int num_formats = wlr_renderer_get_dmabuf_formats(renderer, &formats); + if (num_formats < 0) { + return; + } + + for (int i = 0; i < num_formats; i++) { + uint64_t *modifiers = NULL; + int num_modifiers = wlr_renderer_get_dmabuf_modifiers(renderer, + formats[i], &modifiers); + if (num_modifiers < 0) { + return; + } + /* send DRM_FORMAT_MOD_INVALID token when no modifiers are supported + * for this format */ + if (num_modifiers == 0) { + num_modifiers = 1; + modifiers = &modifier_invalid; + } + for (int j = 0; j < num_modifiers; j++) { + if (version >= ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) { + uint32_t modifier_lo = modifiers[j] & 0xFFFFFFFF; + uint32_t modifier_hi = modifiers[j] >> 32; + zwp_linux_dmabuf_v1_send_modifier(resource, + formats[i], + modifier_hi, + modifier_lo); + } else if (modifiers[j] == DRM_FORMAT_MOD_LINEAR || + modifiers == &modifier_invalid) { + zwp_linux_dmabuf_v1_send_format(resource, formats[i]); + } + } + if (modifiers != &modifier_invalid) { + free(modifiers); + } + } + free(formats); +} + +static void linux_dmabuf_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void linux_dmabuf_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_linux_dmabuf_v1 *linux_dmabuf = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwp_linux_dmabuf_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &linux_dmabuf_impl, + linux_dmabuf, linux_dmabuf_resource_destroy); + wl_list_insert(&linux_dmabuf->resources, wl_resource_get_link(resource)); + linux_dmabuf_send_formats(linux_dmabuf, resource, version); +} + +void wlr_linux_dmabuf_v1_destroy(struct wlr_linux_dmabuf_v1 *linux_dmabuf) { + if (!linux_dmabuf) { + return; + } + + wlr_signal_emit_safe(&linux_dmabuf->events.destroy, linux_dmabuf); + + wl_list_remove(&linux_dmabuf->display_destroy.link); + wl_list_remove(&linux_dmabuf->renderer_destroy.link); + + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &linux_dmabuf->resources) { + wl_resource_destroy(resource); + } + + wl_global_destroy(linux_dmabuf->global); + free(linux_dmabuf); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_linux_dmabuf_v1 *linux_dmabuf = + wl_container_of(listener, linux_dmabuf, display_destroy); + wlr_linux_dmabuf_v1_destroy(linux_dmabuf); +} + +static void handle_renderer_destroy(struct wl_listener *listener, void *data) { + struct wlr_linux_dmabuf_v1 *linux_dmabuf = + wl_container_of(listener, linux_dmabuf, renderer_destroy); + wlr_linux_dmabuf_v1_destroy(linux_dmabuf); +} + +struct wlr_linux_dmabuf_v1 *wlr_linux_dmabuf_v1_create(struct wl_display *display, + struct wlr_renderer *renderer) { + struct wlr_linux_dmabuf_v1 *linux_dmabuf = + calloc(1, sizeof(struct wlr_linux_dmabuf_v1)); + if (linux_dmabuf == NULL) { + wlr_log(WLR_ERROR, "could not create simple dmabuf manager"); + return NULL; + } + linux_dmabuf->renderer = renderer; + + wl_list_init(&linux_dmabuf->resources); + wl_signal_init(&linux_dmabuf->events.destroy); + + linux_dmabuf->global = + wl_global_create(display, &zwp_linux_dmabuf_v1_interface, + LINUX_DMABUF_VERSION, linux_dmabuf, linux_dmabuf_bind); + if (!linux_dmabuf->global) { + wlr_log(WLR_ERROR, "could not create linux dmabuf v1 wl global"); + free(linux_dmabuf); + return NULL; + } + + linux_dmabuf->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &linux_dmabuf->display_destroy); + + linux_dmabuf->renderer_destroy.notify = handle_renderer_destroy; + wl_signal_add(&renderer->events.destroy, &linux_dmabuf->renderer_destroy); + + return linux_dmabuf; +} diff --git a/types/wlr_list.c b/types/wlr_list.c new file mode 100644 index 00000000..2be0912a --- /dev/null +++ b/types/wlr_list.c @@ -0,0 +1,109 @@ +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <wlr/types/wlr_list.h> + +bool wlr_list_init(struct wlr_list *list) { + list->capacity = 10; + list->length = 0; + list->items = malloc(sizeof(void *) * list->capacity); + if (list->items == NULL) { + return false; + } + return true; +} + +static bool list_resize(struct wlr_list *list) { + if (list->length == list->capacity) { + void *new_items = realloc(list->items, + sizeof(void *) * (list->capacity + 10)); + if (!new_items) { + return false; + } + list->capacity += 10; + list->items = new_items; + } + return true; +} + +void wlr_list_finish(struct wlr_list *list) { + free(list->items); +} + +void wlr_list_for_each(struct wlr_list *list, void (*callback)(void *item)) { + for (size_t i = 0; i < list->length; i++) { + callback(list->items[i]); + } +} + +ssize_t wlr_list_push(struct wlr_list *list, void *item) { + if (!list_resize(list)) { + return -1; + } + list->items[list->length++] = item; + return list->length; +} + +ssize_t wlr_list_insert(struct wlr_list *list, size_t index, void *item) { + if (!list_resize(list)) { + return -1; + } + memmove(&list->items[index + 1], &list->items[index], + sizeof(void *) * (list->length - index)); + list->length++; + list->items[index] = item; + return list->length; +} + +void wlr_list_del(struct wlr_list *list, size_t index) { + list->length--; + memmove(&list->items[index], &list->items[index + 1], + sizeof(void *) * (list->length - index)); +} + +void *wlr_list_pop(struct wlr_list *list) { + if (list->length == 0) { + return NULL; + } + void *last = list->items[list->length - 1]; + wlr_list_del(list, list->length - 1); + return last; +} + +void *wlr_list_peek(struct wlr_list *list) { + if (list->length == 0) { + return NULL; + } + return list->items[list->length - 1]; +} + +ssize_t wlr_list_cat(struct wlr_list *list, const struct wlr_list *source) { + size_t old_len = list->length; + size_t i; + for (i = 0; i < source->length; ++i) { + if (wlr_list_push(list, source->items[i]) == -1) { + list->length = old_len; + return -1; + } + } + return list->length; +} + +void wlr_list_qsort(struct wlr_list *list, + int compare(const void *left, const void *right)) { + qsort(list->items, list->length, sizeof(void *), compare); +} + +ssize_t wlr_list_find(struct wlr_list *list, + int compare(const void *item, const void *data), const void *data) { + for (size_t i = 0; i < list->length; i++) { + void *item = list->items[i]; + if (compare(item, data) == 0) { + return i; + } + } + return -1; +} diff --git a/types/wlr_matrix.c b/types/wlr_matrix.c new file mode 100644 index 00000000..2c896313 --- /dev/null +++ b/types/wlr_matrix.c @@ -0,0 +1,169 @@ +#include <math.h> +#include <string.h> +#include <wayland-server-protocol.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_output.h> + +void wlr_matrix_identity(float mat[static 9]) { + static const float identity[9] = { + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }; + memcpy(mat, identity, sizeof(identity)); +} + +void wlr_matrix_multiply(float mat[static 9], const float a[static 9], + const float b[static 9]) { + float product[9]; + + product[0] = a[0]*b[0] + a[1]*b[3] + a[2]*b[6]; + product[1] = a[0]*b[1] + a[1]*b[4] + a[2]*b[7]; + product[2] = a[0]*b[2] + a[1]*b[5] + a[2]*b[8]; + + product[3] = a[3]*b[0] + a[4]*b[3] + a[5]*b[6]; + product[4] = a[3]*b[1] + a[4]*b[4] + a[5]*b[7]; + product[5] = a[3]*b[2] + a[4]*b[5] + a[5]*b[8]; + + product[6] = a[6]*b[0] + a[7]*b[3] + a[8]*b[6]; + product[7] = a[6]*b[1] + a[7]*b[4] + a[8]*b[7]; + product[8] = a[6]*b[2] + a[7]*b[5] + a[8]*b[8]; + + memcpy(mat, product, sizeof(product)); +} + +void wlr_matrix_transpose(float mat[static 9], const float a[static 9]) { + float transposition[9] = { + a[0], a[3], a[6], + a[1], a[4], a[7], + a[2], a[5], a[8], + }; + memcpy(mat, transposition, sizeof(transposition)); +} + +void wlr_matrix_translate(float mat[static 9], float x, float y) { + float translate[9] = { + 1.0f, 0.0f, x, + 0.0f, 1.0f, y, + 0.0f, 0.0f, 1.0f, + }; + wlr_matrix_multiply(mat, mat, translate); +} + +void wlr_matrix_scale(float mat[static 9], float x, float y) { + float scale[9] = { + x, 0.0f, 0.0f, + 0.0f, y, 0.0f, + 0.0f, 0.0f, 1.0f, + }; + wlr_matrix_multiply(mat, mat, scale); +} + +void wlr_matrix_rotate(float mat[static 9], float rad) { + float rotate[9] = { + cos(rad), -sin(rad), 0.0f, + sin(rad), cos(rad), 0.0f, + 0.0f, 0.0f, 1.0f, + }; + wlr_matrix_multiply(mat, mat, rotate); +} + +static const float transforms[][9] = { + [WL_OUTPUT_TRANSFORM_NORMAL] = { + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_90] = { + 0.0f, -1.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_180] = { + -1.0f, 0.0f, 0.0f, + 0.0f, -1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_270] = { + 0.0f, 1.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_FLIPPED] = { + -1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_FLIPPED_90] = { + 0.0f, -1.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_FLIPPED_180] = { + 1.0f, 0.0f, 0.0f, + 0.0f, -1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, + [WL_OUTPUT_TRANSFORM_FLIPPED_270] = { + 0.0f, 1.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + }, +}; + +void wlr_matrix_transform(float mat[static 9], + enum wl_output_transform transform) { + wlr_matrix_multiply(mat, mat, transforms[transform]); +} + +// Equivalent to glOrtho(0, width, 0, height, 1, -1) with the transform applied +void wlr_matrix_projection(float mat[static 9], int width, int height, + enum wl_output_transform transform) { + memset(mat, 0, sizeof(*mat) * 9); + + const float *t = transforms[transform]; + float x = 2.0f / width; + float y = 2.0f / height; + + // Rotation + reflection + mat[0] = x * t[0]; + mat[1] = x * t[1]; + mat[3] = y * -t[3]; + mat[4] = y * -t[4]; + + // Translation + mat[2] = -copysign(1.0f, mat[0] + mat[1]); + mat[5] = -copysign(1.0f, mat[3] + mat[4]); + + // Identity + mat[8] = 1.0f; +} + +void wlr_matrix_project_box(float mat[static 9], const struct wlr_box *box, + enum wl_output_transform transform, float rotation, + const float projection[static 9]) { + int x = box->x; + int y = box->y; + int width = box->width; + int height = box->height; + + wlr_matrix_identity(mat); + wlr_matrix_translate(mat, x, y); + + if (rotation != 0) { + wlr_matrix_translate(mat, width/2, height/2); + wlr_matrix_rotate(mat, rotation); + wlr_matrix_translate(mat, -width/2, -height/2); + } + + wlr_matrix_scale(mat, width, height); + + if (transform != WL_OUTPUT_TRANSFORM_NORMAL) { + wlr_matrix_translate(mat, 0.5, 0.5); + wlr_matrix_transform(mat, transform); + wlr_matrix_translate(mat, -0.5, -0.5); + } + + wlr_matrix_multiply(mat, projection, mat); +} diff --git a/types/wlr_output.c b/types/wlr_output.c new file mode 100644 index 00000000..9a21196e --- /dev/null +++ b/types/wlr_output.c @@ -0,0 +1,921 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <tgmath.h> +#include <time.h> +#include <wayland-server.h> +#include <wlr/interfaces/wlr_output.h> +#include <wlr/render/interface.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/types/wlr_output.h> +#include <wlr/types/wlr_seat.h> +#include <wlr/types/wlr_surface.h> +#include <wlr/util/log.h> +#include <wlr/util/region.h> +#include "util/signal.h" + +#define OUTPUT_VERSION 3 + +static void output_send_to_resource(struct wl_resource *resource) { + struct wlr_output *output = wlr_output_from_resource(resource); + const uint32_t version = wl_resource_get_version(resource); + if (version >= WL_OUTPUT_GEOMETRY_SINCE_VERSION) { + wl_output_send_geometry(resource, output->lx, output->ly, + output->phys_width, output->phys_height, output->subpixel, + output->make, output->model, output->transform); + } + if (version >= WL_OUTPUT_MODE_SINCE_VERSION) { + struct wlr_output_mode *mode; + wl_list_for_each(mode, &output->modes, link) { + uint32_t flags = mode->flags & WL_OUTPUT_MODE_PREFERRED; + if (output->current_mode == mode) { + flags |= WL_OUTPUT_MODE_CURRENT; + } + wl_output_send_mode(resource, flags, mode->width, mode->height, + mode->refresh); + } + + if (wl_list_length(&output->modes) == 0) { + // Output has no mode, send the current width/height + wl_output_send_mode(resource, WL_OUTPUT_MODE_CURRENT, + output->width, output->height, output->refresh); + } + } + if (version >= WL_OUTPUT_SCALE_SINCE_VERSION) { + wl_output_send_scale(resource, (uint32_t)ceil(output->scale)); + } + if (version >= WL_OUTPUT_DONE_SINCE_VERSION) { + wl_output_send_done(resource); + } +} + +static void output_send_current_mode_to_resource( + struct wl_resource *resource) { + struct wlr_output *output = wlr_output_from_resource(resource); + const uint32_t version = wl_resource_get_version(resource); + if (version < WL_OUTPUT_MODE_SINCE_VERSION) { + return; + } + if (output->current_mode != NULL) { + struct wlr_output_mode *mode = output->current_mode; + uint32_t flags = mode->flags & WL_OUTPUT_MODE_PREFERRED; + wl_output_send_mode(resource, flags | WL_OUTPUT_MODE_CURRENT, + mode->width, mode->height, mode->refresh); + } else { + // Output has no mode + wl_output_send_mode(resource, WL_OUTPUT_MODE_CURRENT, output->width, + output->height, output->refresh); + } + if (version >= WL_OUTPUT_DONE_SINCE_VERSION) { + wl_output_send_done(resource); + } +} + +static void output_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void output_handle_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_output_interface output_impl = { + .release = output_handle_release, +}; + +static void output_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_output *output = data; + + struct wl_resource *resource = wl_resource_create(wl_client, + &wl_output_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(resource, &output_impl, output, + output_handle_resource_destroy); + wl_list_insert(&output->resources, wl_resource_get_link(resource)); + output_send_to_resource(resource); +} + +void wlr_output_create_global(struct wlr_output *output) { + if (output->global != NULL) { + return; + } + output->global = wl_global_create(output->display, + &wl_output_interface, OUTPUT_VERSION, output, output_bind); + if (output->global == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate wl_output global"); + } +} + +void wlr_output_destroy_global(struct wlr_output *output) { + if (output->global == NULL) { + return; + } + // Make all output resources inert + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &output->resources) { + wl_resource_set_user_data(resource, NULL); + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + } + wl_global_destroy(output->global); + output->global = NULL; +} + +void wlr_output_update_enabled(struct wlr_output *output, bool enabled) { + if (output->enabled == enabled) { + return; + } + + output->enabled = enabled; + wlr_signal_emit_safe(&output->events.enable, output); +} + +static void output_update_matrix(struct wlr_output *output) { + wlr_matrix_projection(output->transform_matrix, output->width, + output->height, output->transform); +} + +bool wlr_output_enable(struct wlr_output *output, bool enable) { + if (output->enabled == enable) { + return true; + } + + if (output->impl->enable) { + return output->impl->enable(output, enable); + } + return false; +} + +bool wlr_output_set_mode(struct wlr_output *output, + struct wlr_output_mode *mode) { + if (!output->impl || !output->impl->set_mode) { + return false; + } + return output->impl->set_mode(output, mode); +} + +bool wlr_output_set_custom_mode(struct wlr_output *output, int32_t width, + int32_t height, int32_t refresh) { + if (!output->impl || !output->impl->set_custom_mode) { + return false; + } + return output->impl->set_custom_mode(output, width, height, refresh); +} + +void wlr_output_update_mode(struct wlr_output *output, + struct wlr_output_mode *mode) { + output->current_mode = mode; + wlr_output_update_custom_mode(output, mode->width, mode->height, + mode->refresh); +} + +void wlr_output_update_custom_mode(struct wlr_output *output, int32_t width, + int32_t height, int32_t refresh) { + if (output->width == width && output->height == height && + output->refresh == refresh) { + return; + } + + output->width = width; + output->height = height; + output_update_matrix(output); + + output->refresh = refresh; + + struct wl_resource *resource; + wl_resource_for_each(resource, &output->resources) { + output_send_current_mode_to_resource(resource); + } + + wlr_signal_emit_safe(&output->events.mode, output); +} + +void wlr_output_set_transform(struct wlr_output *output, + enum wl_output_transform transform) { + output->impl->transform(output, transform); + output_update_matrix(output); + + // TODO: only send geometry and done + struct wl_resource *resource; + wl_resource_for_each(resource, &output->resources) { + output_send_to_resource(resource); + } + + wlr_signal_emit_safe(&output->events.transform, output); +} + +void wlr_output_set_position(struct wlr_output *output, int32_t lx, + int32_t ly) { + if (lx == output->lx && ly == output->ly) { + return; + } + + output->lx = lx; + output->ly = ly; + + // TODO: only send geometry and done + struct wl_resource *resource; + wl_resource_for_each(resource, &output->resources) { + output_send_to_resource(resource); + } +} + +void wlr_output_set_scale(struct wlr_output *output, float scale) { + if (output->scale == scale) { + return; + } + + output->scale = scale; + + // TODO: only send mode and done + struct wl_resource *resource; + wl_resource_for_each(resource, &output->resources) { + output_send_to_resource(resource); + } + + wlr_signal_emit_safe(&output->events.scale, output); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_output *output = + wl_container_of(listener, output, display_destroy); + wlr_output_destroy_global(output); +} + +void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend, + const struct wlr_output_impl *impl, struct wl_display *display) { + assert(impl->make_current && impl->swap_buffers && impl->transform); + if (impl->set_cursor || impl->move_cursor) { + assert(impl->set_cursor && impl->move_cursor); + } + output->backend = backend; + output->impl = impl; + output->display = display; + wl_list_init(&output->modes); + output->transform = WL_OUTPUT_TRANSFORM_NORMAL; + output->scale = 1; + wl_list_init(&output->cursors); + wl_list_init(&output->resources); + wl_signal_init(&output->events.frame); + wl_signal_init(&output->events.needs_swap); + wl_signal_init(&output->events.swap_buffers); + wl_signal_init(&output->events.present); + wl_signal_init(&output->events.enable); + wl_signal_init(&output->events.mode); + wl_signal_init(&output->events.scale); + wl_signal_init(&output->events.transform); + wl_signal_init(&output->events.destroy); + pixman_region32_init(&output->damage); + + const char *no_hardware_cursors = getenv("WLR_NO_HARDWARE_CURSORS"); + if (no_hardware_cursors != NULL && strcmp(no_hardware_cursors, "1") == 0) { + wlr_log(WLR_DEBUG, + "WLR_NO_HARDWARE_CURSORS set, forcing software cursors"); + output->software_cursor_locks = 1; + } + + output->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &output->display_destroy); + + output->frame_pending = true; +} + +void wlr_output_destroy(struct wlr_output *output) { + if (!output) { + return; + } + + wl_list_remove(&output->display_destroy.link); + wlr_output_destroy_global(output); + + wlr_signal_emit_safe(&output->events.destroy, output); + + // The backend is responsible for free-ing the list of modes + + struct wlr_output_cursor *cursor, *tmp_cursor; + wl_list_for_each_safe(cursor, tmp_cursor, &output->cursors, link) { + wlr_output_cursor_destroy(cursor); + } + + if (output->idle_frame != NULL) { + wl_event_source_remove(output->idle_frame); + } + + pixman_region32_fini(&output->damage); + + if (output->impl && output->impl->destroy) { + output->impl->destroy(output); + } else { + free(output); + } +} + +void wlr_output_transformed_resolution(struct wlr_output *output, + int *width, int *height) { + if (output->transform % 2 == 0) { + *width = output->width; + *height = output->height; + } else { + *width = output->height; + *height = output->width; + } +} + +void wlr_output_effective_resolution(struct wlr_output *output, + int *width, int *height) { + wlr_output_transformed_resolution(output, width, height); + *width /= output->scale; + *height /= output->scale; +} + +bool wlr_output_make_current(struct wlr_output *output, int *buffer_age) { + return output->impl->make_current(output, buffer_age); +} + +bool wlr_output_preferred_read_format(struct wlr_output *output, + enum wl_shm_format *fmt) { + if (!wlr_output_make_current(output, NULL)) { + return false; + } + + struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend); + if (!renderer->impl->preferred_read_format || !renderer->impl->read_pixels) { + return false; + } + *fmt = renderer->impl->preferred_read_format(renderer); + return true; +} + +bool wlr_output_swap_buffers(struct wlr_output *output, struct timespec *when, + pixman_region32_t *damage) { + if (output->frame_pending) { + wlr_log(WLR_ERROR, "Tried to swap buffers when a frame is pending"); + return false; + } + if (output->idle_frame != NULL) { + wl_event_source_remove(output->idle_frame); + output->idle_frame = NULL; + } + + struct timespec now; + if (when == NULL) { + clock_gettime(CLOCK_MONOTONIC, &now); + when = &now; + } + + struct wlr_output_event_swap_buffers event = { + .output = output, + .when = when, + .damage = damage, + }; + wlr_signal_emit_safe(&output->events.swap_buffers, &event); + + pixman_region32_t render_damage; + pixman_region32_init(&render_damage); + pixman_region32_union_rect(&render_damage, &render_damage, 0, 0, + output->width, output->height); + if (damage != NULL) { + // Damage tracking supported + pixman_region32_intersect(&render_damage, &render_damage, damage); + } + + if (!output->impl->swap_buffers(output, damage ? &render_damage : NULL)) { + pixman_region32_fini(&render_damage); + return false; + } + + pixman_region32_fini(&render_damage); + + struct wlr_output_cursor *cursor; + wl_list_for_each(cursor, &output->cursors, link) { + if (!cursor->enabled || !cursor->visible || cursor->surface == NULL) { + continue; + } + wlr_surface_send_frame_done(cursor->surface, when); + } + + output->frame_pending = true; + output->needs_swap = false; + pixman_region32_clear(&output->damage); + return true; +} + +void wlr_output_send_frame(struct wlr_output *output) { + output->frame_pending = false; + wlr_signal_emit_safe(&output->events.frame, output); +} + +static void schedule_frame_handle_idle_timer(void *data) { + struct wlr_output *output = data; + output->idle_frame = NULL; + if (!output->frame_pending && output->impl->schedule_frame) { + // Ask the backend to send a frame event when appropriate + if (output->impl->schedule_frame(output)) { + output->frame_pending = true; + } + } +} + +void wlr_output_schedule_frame(struct wlr_output *output) { + if (output->frame_pending || output->idle_frame != NULL) { + return; + } + + // We're using an idle timer here in case a buffer swap happens right after + // this function is called + struct wl_event_loop *ev = wl_display_get_event_loop(output->display); + output->idle_frame = + wl_event_loop_add_idle(ev, schedule_frame_handle_idle_timer, output); +} + +void wlr_output_send_present(struct wlr_output *output, + struct wlr_output_event_present *event) { + struct wlr_output_event_present _event = {0}; + if (event == NULL) { + event = &_event; + } + + event->output = output; + + struct timespec now; + if (event->when == NULL) { + clockid_t clock = wlr_backend_get_presentation_clock(output->backend); + errno = 0; + if (clock_gettime(clock, &now) != 0) { + wlr_log_errno(WLR_ERROR, "failed to send output present event: " + "failed to read clock"); + return; + } + event->when = &now; + } + + wlr_signal_emit_safe(&output->events.present, event); +} + +bool wlr_output_set_gamma(struct wlr_output *output, size_t size, + const uint16_t *r, const uint16_t *g, const uint16_t *b) { + if (!output->impl->set_gamma) { + return false; + } + return output->impl->set_gamma(output, size, r, g, b); +} + +size_t wlr_output_get_gamma_size(struct wlr_output *output) { + if (!output->impl->get_gamma_size) { + return 0; + } + return output->impl->get_gamma_size(output); +} + +bool wlr_output_export_dmabuf(struct wlr_output *output, + struct wlr_dmabuf_attributes *attribs) { + if (!output->impl->export_dmabuf) { + return false; + } + return output->impl->export_dmabuf(output, attribs); +} + +void wlr_output_update_needs_swap(struct wlr_output *output) { + output->needs_swap = true; + wlr_signal_emit_safe(&output->events.needs_swap, output); +} + +void wlr_output_damage_whole(struct wlr_output *output) { + int width, height; + wlr_output_transformed_resolution(output, &width, &height); + + pixman_region32_union_rect(&output->damage, &output->damage, 0, 0, + width, height); + wlr_output_update_needs_swap(output); +} + +struct wlr_output *wlr_output_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_output_interface, + &output_impl)); + return wl_resource_get_user_data(resource); +} + +static void output_cursor_damage_whole(struct wlr_output_cursor *cursor); + +void wlr_output_lock_software_cursors(struct wlr_output *output, bool lock) { + if (lock) { + ++output->software_cursor_locks; + } else { + assert(output->software_cursor_locks > 0); + --output->software_cursor_locks; + } + wlr_log(WLR_DEBUG, "%s hardware cursors on output '%s' (locks: %d)", + lock ? "Disabling" : "Enabling", output->name, + output->software_cursor_locks); + + if (output->software_cursor_locks > 0 && output->hardware_cursor != NULL) { + assert(output->impl->set_cursor); + output->impl->set_cursor(output, NULL, 1, + WL_OUTPUT_TRANSFORM_NORMAL, 0, 0, true); + output_cursor_damage_whole(output->hardware_cursor); + output->hardware_cursor = NULL; + } + + // If it's possible to use hardware cursors again, don't switch immediately + // since a recorder is likely to lock software cursors for the next frame + // again. +} + +static void output_scissor(struct wlr_output *output, pixman_box32_t *rect) { + struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend); + assert(renderer); + + struct wlr_box box = { + .x = rect->x1, + .y = rect->y1, + .width = rect->x2 - rect->x1, + .height = rect->y2 - rect->y1, + }; + + int ow, oh; + wlr_output_transformed_resolution(output, &ow, &oh); + + enum wl_output_transform transform = + wlr_output_transform_invert(output->transform); + wlr_box_transform(&box, &box, transform, ow, oh); + + wlr_renderer_scissor(renderer, &box); +} + +static void output_cursor_get_box(struct wlr_output_cursor *cursor, + struct wlr_box *box); + +static void output_cursor_render(struct wlr_output_cursor *cursor, + pixman_region32_t *damage) { + struct wlr_renderer *renderer = + wlr_backend_get_renderer(cursor->output->backend); + assert(renderer); + + struct wlr_texture *texture = cursor->texture; + if (cursor->surface != NULL) { + texture = wlr_surface_get_texture(cursor->surface); + } + if (texture == NULL) { + return; + } + + struct wlr_box box; + output_cursor_get_box(cursor, &box); + + pixman_region32_t surface_damage; + pixman_region32_init(&surface_damage); + pixman_region32_union_rect(&surface_damage, &surface_damage, box.x, box.y, + box.width, box.height); + pixman_region32_intersect(&surface_damage, &surface_damage, damage); + if (!pixman_region32_not_empty(&surface_damage)) { + goto surface_damage_finish; + } + + float matrix[9]; + wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0, + cursor->output->transform_matrix); + + int nrects; + pixman_box32_t *rects = pixman_region32_rectangles(&surface_damage, &nrects); + for (int i = 0; i < nrects; ++i) { + output_scissor(cursor->output, &rects[i]); + wlr_render_texture_with_matrix(renderer, texture, matrix, 1.0f); + } + wlr_renderer_scissor(renderer, NULL); + +surface_damage_finish: + pixman_region32_fini(&surface_damage); +} + +void wlr_output_render_software_cursors(struct wlr_output *output, + pixman_region32_t *damage) { + int width, height; + wlr_output_transformed_resolution(output, &width, &height); + + pixman_region32_t render_damage; + pixman_region32_init(&render_damage); + pixman_region32_union_rect(&render_damage, &render_damage, 0, 0, + width, height); + if (damage != NULL) { + // Damage tracking supported + pixman_region32_intersect(&render_damage, &render_damage, damage); + } + + if (pixman_region32_not_empty(&render_damage)) { + struct wlr_output_cursor *cursor; + wl_list_for_each(cursor, &output->cursors, link) { + if (!cursor->enabled || !cursor->visible || + output->hardware_cursor == cursor) { + continue; + } + output_cursor_render(cursor, &render_damage); + } + } + + pixman_region32_fini(&render_damage); +} + + +/** + * Returns the cursor box, scaled for its output. + */ +static void output_cursor_get_box(struct wlr_output_cursor *cursor, + struct wlr_box *box) { + box->x = cursor->x - cursor->hotspot_x; + box->y = cursor->y - cursor->hotspot_y; + box->width = cursor->width; + box->height = cursor->height; +} + +static void output_cursor_damage_whole(struct wlr_output_cursor *cursor) { + struct wlr_box box; + output_cursor_get_box(cursor, &box); + pixman_region32_union_rect(&cursor->output->damage, &cursor->output->damage, + box.x, box.y, box.width, box.height); + wlr_output_update_needs_swap(cursor->output); +} + +static void output_cursor_reset(struct wlr_output_cursor *cursor) { + if (cursor->output->hardware_cursor != cursor) { + output_cursor_damage_whole(cursor); + } + if (cursor->surface != NULL) { + wl_list_remove(&cursor->surface_commit.link); + wl_list_remove(&cursor->surface_destroy.link); + cursor->surface = NULL; + } +} + +static void output_cursor_update_visible(struct wlr_output_cursor *cursor) { + struct wlr_box output_box; + output_box.x = output_box.y = 0; + wlr_output_transformed_resolution(cursor->output, &output_box.width, + &output_box.height); + + struct wlr_box cursor_box; + output_cursor_get_box(cursor, &cursor_box); + + struct wlr_box intersection; + bool visible = + wlr_box_intersection(&intersection, &output_box, &cursor_box); + + if (cursor->surface != NULL) { + if (cursor->visible && !visible) { + wlr_surface_send_leave(cursor->surface, cursor->output); + } + if (!cursor->visible && visible) { + wlr_surface_send_enter(cursor->surface, cursor->output); + } + } + + cursor->visible = visible; +} + +static bool output_cursor_attempt_hardware(struct wlr_output_cursor *cursor) { + int32_t scale = cursor->output->scale; + enum wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; + struct wlr_texture *texture = cursor->texture; + if (cursor->surface != NULL) { + texture = wlr_surface_get_texture(cursor->surface); + scale = cursor->surface->current.scale; + transform = cursor->surface->current.transform; + } + + if (cursor->output->software_cursor_locks > 0) { + return false; + } + + struct wlr_output_cursor *hwcur = cursor->output->hardware_cursor; + if (cursor->output->impl->set_cursor && (hwcur == NULL || hwcur == cursor)) { + // If the cursor was hidden or was a software cursor, the hardware + // cursor position is outdated + assert(cursor->output->impl->move_cursor); + cursor->output->impl->move_cursor(cursor->output, + (int)cursor->x, (int)cursor->y); + if (cursor->output->impl->set_cursor(cursor->output, texture, + scale, transform, cursor->hotspot_x, cursor->hotspot_y, true)) { + cursor->output->hardware_cursor = cursor; + return true; + } + } + return false; +} + +bool wlr_output_cursor_set_image(struct wlr_output_cursor *cursor, + const uint8_t *pixels, int32_t stride, uint32_t width, uint32_t height, + int32_t hotspot_x, int32_t hotspot_y) { + struct wlr_renderer *renderer = + wlr_backend_get_renderer(cursor->output->backend); + assert(renderer); + + output_cursor_reset(cursor); + + cursor->width = width; + cursor->height = height; + cursor->hotspot_x = hotspot_x; + cursor->hotspot_y = hotspot_y; + output_cursor_update_visible(cursor); + + wlr_texture_destroy(cursor->texture); + cursor->texture = NULL; + + cursor->enabled = false; + if (pixels != NULL) { + cursor->texture = wlr_texture_from_pixels(renderer, + WL_SHM_FORMAT_ARGB8888, stride, width, height, pixels); + if (cursor->texture == NULL) { + return false; + } + cursor->enabled = true; + } + + if (output_cursor_attempt_hardware(cursor)) { + return true; + } + + wlr_log(WLR_DEBUG, "Falling back to software cursor on output '%s'", + cursor->output->name); + output_cursor_damage_whole(cursor); + return true; +} + +static void output_cursor_commit(struct wlr_output_cursor *cursor, + bool update_hotspot) { + if (cursor->output->hardware_cursor != cursor) { + output_cursor_damage_whole(cursor); + } + + struct wlr_surface *surface = cursor->surface; + assert(surface != NULL); + + // Some clients commit a cursor surface with a NULL buffer to hide it. + cursor->enabled = wlr_surface_has_buffer(surface); + cursor->width = surface->current.width * cursor->output->scale; + cursor->height = surface->current.height * cursor->output->scale; + output_cursor_update_visible(cursor); + if (update_hotspot) { + cursor->hotspot_x -= surface->current.dx * cursor->output->scale; + cursor->hotspot_y -= surface->current.dy * cursor->output->scale; + } + + if (output_cursor_attempt_hardware(cursor)) { + return; + } + + // Fallback to software cursor + output_cursor_damage_whole(cursor); +} + +static void output_cursor_handle_commit(struct wl_listener *listener, + void *data) { + struct wlr_output_cursor *cursor = + wl_container_of(listener, cursor, surface_commit); + output_cursor_commit(cursor, true); +} + +static void output_cursor_handle_destroy(struct wl_listener *listener, + void *data) { + struct wlr_output_cursor *cursor = wl_container_of(listener, cursor, + surface_destroy); + output_cursor_reset(cursor); +} + +void wlr_output_cursor_set_surface(struct wlr_output_cursor *cursor, + struct wlr_surface *surface, int32_t hotspot_x, int32_t hotspot_y) { + hotspot_x *= cursor->output->scale; + hotspot_y *= cursor->output->scale; + + if (surface && surface == cursor->surface) { + // Only update the hotspot: surface hasn't changed + + if (cursor->output->hardware_cursor != cursor) { + output_cursor_damage_whole(cursor); + } + cursor->hotspot_x = hotspot_x; + cursor->hotspot_y = hotspot_y; + if (cursor->output->hardware_cursor != cursor) { + output_cursor_damage_whole(cursor); + } else { + assert(cursor->output->impl->set_cursor); + cursor->output->impl->set_cursor(cursor->output, NULL, + 1, WL_OUTPUT_TRANSFORM_NORMAL, hotspot_x, hotspot_y, false); + } + return; + } + + output_cursor_reset(cursor); + + cursor->surface = surface; + cursor->hotspot_x = hotspot_x; + cursor->hotspot_y = hotspot_y; + + if (surface != NULL) { + wl_signal_add(&surface->events.commit, &cursor->surface_commit); + wl_signal_add(&surface->events.destroy, &cursor->surface_destroy); + + cursor->visible = false; + output_cursor_commit(cursor, false); + } else { + cursor->enabled = false; + cursor->width = 0; + cursor->height = 0; + + if (cursor->output->hardware_cursor == cursor) { + assert(cursor->output->impl->set_cursor); + cursor->output->impl->set_cursor(cursor->output, NULL, 1, + WL_OUTPUT_TRANSFORM_NORMAL, 0, 0, true); + } + } +} + +bool wlr_output_cursor_move(struct wlr_output_cursor *cursor, + double x, double y) { + if (cursor->x == x && cursor->y == y) { + return true; + } + + if (cursor->output->hardware_cursor != cursor) { + output_cursor_damage_whole(cursor); + } + + bool was_visible = cursor->visible; + x *= cursor->output->scale; + y *= cursor->output->scale; + cursor->x = x; + cursor->y = y; + output_cursor_update_visible(cursor); + + if (!was_visible && !cursor->visible) { + // Cursor is still hidden, do nothing + return true; + } + + if (cursor->output->hardware_cursor != cursor) { + output_cursor_damage_whole(cursor); + return true; + } + + assert(cursor->output->impl->move_cursor); + return cursor->output->impl->move_cursor(cursor->output, (int)x, (int)y); +} + +struct wlr_output_cursor *wlr_output_cursor_create(struct wlr_output *output) { + struct wlr_output_cursor *cursor = + calloc(1, sizeof(struct wlr_output_cursor)); + if (cursor == NULL) { + return NULL; + } + cursor->output = output; + wl_signal_init(&cursor->events.destroy); + wl_list_init(&cursor->surface_commit.link); + cursor->surface_commit.notify = output_cursor_handle_commit; + wl_list_init(&cursor->surface_destroy.link); + cursor->surface_destroy.notify = output_cursor_handle_destroy; + wl_list_insert(&output->cursors, &cursor->link); + cursor->visible = true; // default position is at (0, 0) + return cursor; +} + +void wlr_output_cursor_destroy(struct wlr_output_cursor *cursor) { + if (cursor == NULL) { + return; + } + output_cursor_reset(cursor); + wlr_signal_emit_safe(&cursor->events.destroy, cursor); + if (cursor->output->hardware_cursor == cursor) { + // If this cursor was the hardware cursor, disable it + if (cursor->output->impl->set_cursor) { + cursor->output->impl->set_cursor(cursor->output, NULL, 1, + WL_OUTPUT_TRANSFORM_NORMAL, 0, 0, true); + } + cursor->output->hardware_cursor = NULL; + } + wlr_texture_destroy(cursor->texture); + wl_list_remove(&cursor->link); + free(cursor); +} + + +enum wl_output_transform wlr_output_transform_invert( + enum wl_output_transform tr) { + if ((tr & WL_OUTPUT_TRANSFORM_90) && !(tr & WL_OUTPUT_TRANSFORM_FLIPPED)) { + tr ^= WL_OUTPUT_TRANSFORM_180; + } + return tr; +} + +enum wl_output_transform wlr_output_transform_compose( + enum wl_output_transform tr_a, enum wl_output_transform tr_b) { + uint32_t flipped = (tr_a ^ tr_b) & WL_OUTPUT_TRANSFORM_FLIPPED; + uint32_t rotated = + (tr_a + tr_b) & (WL_OUTPUT_TRANSFORM_90 | WL_OUTPUT_TRANSFORM_180); + return flipped | rotated; +} diff --git a/types/wlr_output_damage.c b/types/wlr_output_damage.c new file mode 100644 index 00000000..bf3a671d --- /dev/null +++ b/types/wlr_output_damage.c @@ -0,0 +1,192 @@ +#include <stddef.h> +#include <stdlib.h> +#include <time.h> +#include <wayland-server.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_output_damage.h> +#include <wlr/types/wlr_output.h> +#include "util/signal.h" + +static void output_handle_destroy(struct wl_listener *listener, void *data) { + struct wlr_output_damage *output_damage = + wl_container_of(listener, output_damage, output_destroy); + wlr_output_damage_destroy(output_damage); +} + +static void output_handle_mode(struct wl_listener *listener, void *data) { + struct wlr_output_damage *output_damage = + wl_container_of(listener, output_damage, output_mode); + wlr_output_damage_add_whole(output_damage); +} + +static void output_handle_transform(struct wl_listener *listener, void *data) { + struct wlr_output_damage *output_damage = + wl_container_of(listener, output_damage, output_transform); + wlr_output_damage_add_whole(output_damage); +} + +static void output_handle_scale(struct wl_listener *listener, void *data) { + struct wlr_output_damage *output_damage = + wl_container_of(listener, output_damage, output_scale); + wlr_output_damage_add_whole(output_damage); +} + +static void output_handle_needs_swap(struct wl_listener *listener, void *data) { + struct wlr_output_damage *output_damage = + wl_container_of(listener, output_damage, output_needs_swap); + pixman_region32_union(&output_damage->current, &output_damage->current, + &output_damage->output->damage); + wlr_output_schedule_frame(output_damage->output); +} + +static void output_handle_frame(struct wl_listener *listener, void *data) { + struct wlr_output_damage *output_damage = + wl_container_of(listener, output_damage, output_frame); + + if (!output_damage->output->enabled) { + return; + } + + wlr_signal_emit_safe(&output_damage->events.frame, output_damage); +} + +struct wlr_output_damage *wlr_output_damage_create(struct wlr_output *output) { + struct wlr_output_damage *output_damage = + calloc(1, sizeof(struct wlr_output_damage)); + if (output_damage == NULL) { + return NULL; + } + + output_damage->output = output; + output_damage->max_rects = 20; + wl_signal_init(&output_damage->events.frame); + wl_signal_init(&output_damage->events.destroy); + + pixman_region32_init(&output_damage->current); + for (size_t i = 0; i < WLR_OUTPUT_DAMAGE_PREVIOUS_LEN; ++i) { + pixman_region32_init(&output_damage->previous[i]); + } + + wl_signal_add(&output->events.destroy, &output_damage->output_destroy); + output_damage->output_destroy.notify = output_handle_destroy; + wl_signal_add(&output->events.mode, &output_damage->output_mode); + output_damage->output_mode.notify = output_handle_mode; + wl_signal_add(&output->events.transform, &output_damage->output_transform); + output_damage->output_transform.notify = output_handle_transform; + wl_signal_add(&output->events.scale, &output_damage->output_scale); + output_damage->output_scale.notify = output_handle_scale; + wl_signal_add(&output->events.needs_swap, &output_damage->output_needs_swap); + output_damage->output_needs_swap.notify = output_handle_needs_swap; + wl_signal_add(&output->events.frame, &output_damage->output_frame); + output_damage->output_frame.notify = output_handle_frame; + + return output_damage; +} + +void wlr_output_damage_destroy(struct wlr_output_damage *output_damage) { + if (output_damage == NULL) { + return; + } + wlr_signal_emit_safe(&output_damage->events.destroy, output_damage); + wl_list_remove(&output_damage->output_destroy.link); + wl_list_remove(&output_damage->output_mode.link); + wl_list_remove(&output_damage->output_transform.link); + wl_list_remove(&output_damage->output_scale.link); + wl_list_remove(&output_damage->output_needs_swap.link); + wl_list_remove(&output_damage->output_frame.link); + pixman_region32_fini(&output_damage->current); + for (size_t i = 0; i < WLR_OUTPUT_DAMAGE_PREVIOUS_LEN; ++i) { + pixman_region32_fini(&output_damage->previous[i]); + } + free(output_damage); +} + +bool wlr_output_damage_make_current(struct wlr_output_damage *output_damage, + bool *needs_swap, pixman_region32_t *damage) { + struct wlr_output *output = output_damage->output; + + int buffer_age = -1; + if (!wlr_output_make_current(output, &buffer_age)) { + return false; + } + + // Check if we can use damage tracking + if (buffer_age <= 0 || buffer_age - 1 > WLR_OUTPUT_DAMAGE_PREVIOUS_LEN) { + int width, height; + wlr_output_transformed_resolution(output, &width, &height); + + // Buffer new or too old, damage the whole output + pixman_region32_union_rect(damage, damage, 0, 0, width, height); + } else { + pixman_region32_copy(damage, &output_damage->current); + + // Accumulate damage from old buffers + size_t idx = output_damage->previous_idx; + for (int i = 0; i < buffer_age - 1; ++i) { + int j = (idx + i) % WLR_OUTPUT_DAMAGE_PREVIOUS_LEN; + pixman_region32_union(damage, damage, &output_damage->previous[j]); + } + + // Check the number of rectangles + int n_rects = pixman_region32_n_rects(damage); + if (n_rects > output_damage->max_rects) { + pixman_box32_t *extents = pixman_region32_extents(damage); + pixman_region32_union_rect(damage, damage, extents->x1, extents->y1, + extents->x2 - extents->x1, extents->y2 - extents->y1); + } + } + + *needs_swap = output->needs_swap || pixman_region32_not_empty(damage); + return true; +} + +bool wlr_output_damage_swap_buffers(struct wlr_output_damage *output_damage, + struct timespec *when, pixman_region32_t *damage) { + if (!wlr_output_swap_buffers(output_damage->output, when, damage)) { + return false; + } + + // same as decrementing, but works on unsigned integers + output_damage->previous_idx += WLR_OUTPUT_DAMAGE_PREVIOUS_LEN - 1; + output_damage->previous_idx %= WLR_OUTPUT_DAMAGE_PREVIOUS_LEN; + + pixman_region32_copy(&output_damage->previous[output_damage->previous_idx], + &output_damage->current); + pixman_region32_clear(&output_damage->current); + + return true; +} + +void wlr_output_damage_add(struct wlr_output_damage *output_damage, + pixman_region32_t *damage) { + int width, height; + wlr_output_transformed_resolution(output_damage->output, &width, &height); + + pixman_region32_union(&output_damage->current, &output_damage->current, + damage); + pixman_region32_intersect_rect(&output_damage->current, + &output_damage->current, 0, 0, width, height); + wlr_output_schedule_frame(output_damage->output); +} + +void wlr_output_damage_add_whole(struct wlr_output_damage *output_damage) { + int width, height; + wlr_output_transformed_resolution(output_damage->output, &width, &height); + + pixman_region32_union_rect(&output_damage->current, &output_damage->current, + 0, 0, width, height); + + wlr_output_schedule_frame(output_damage->output); +} + +void wlr_output_damage_add_box(struct wlr_output_damage *output_damage, + struct wlr_box *box) { + int width, height; + wlr_output_transformed_resolution(output_damage->output, &width, &height); + + pixman_region32_union_rect(&output_damage->current, &output_damage->current, + box->x, box->y, box->width, box->height); + pixman_region32_intersect_rect(&output_damage->current, + &output_damage->current, 0, 0, width, height); + wlr_output_schedule_frame(output_damage->output); +} diff --git a/types/wlr_output_layout.c b/types/wlr_output_layout.c new file mode 100644 index 00000000..553275d7 --- /dev/null +++ b/types/wlr_output_layout.c @@ -0,0 +1,521 @@ +#include <assert.h> +#include <float.h> +#include <limits.h> +#include <stdlib.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_output_layout.h> +#include <wlr/types/wlr_output.h> +#include <wlr/util/log.h> +#include "util/signal.h" + +struct wlr_output_layout_state { + struct wlr_box _box; // should never be read directly, use the getter +}; + +struct wlr_output_layout_output_state { + struct wlr_output_layout *layout; + struct wlr_output_layout_output *l_output; + + struct wlr_box _box; // should never be read directly, use the getter + bool auto_configured; + + struct wl_listener mode; + struct wl_listener scale; + struct wl_listener transform; + struct wl_listener output_destroy; +}; + +struct wlr_output_layout *wlr_output_layout_create(void) { + struct wlr_output_layout *layout = + calloc(1, sizeof(struct wlr_output_layout)); + if (layout == NULL) { + return NULL; + } + layout->state = calloc(1, sizeof(struct wlr_output_layout_state)); + if (layout->state == NULL) { + free(layout); + return NULL; + } + wl_list_init(&layout->outputs); + + wl_signal_init(&layout->events.add); + wl_signal_init(&layout->events.change); + wl_signal_init(&layout->events.destroy); + + return layout; +} + +static void output_layout_output_destroy( + struct wlr_output_layout_output *l_output) { + wlr_signal_emit_safe(&l_output->events.destroy, l_output); + wlr_output_destroy_global(l_output->output); + wl_list_remove(&l_output->state->mode.link); + wl_list_remove(&l_output->state->scale.link); + wl_list_remove(&l_output->state->transform.link); + wl_list_remove(&l_output->state->output_destroy.link); + wl_list_remove(&l_output->link); + free(l_output->state); + free(l_output); +} + +void wlr_output_layout_destroy(struct wlr_output_layout *layout) { + if (!layout) { + return; + } + + wlr_signal_emit_safe(&layout->events.destroy, layout); + + struct wlr_output_layout_output *l_output, *temp; + wl_list_for_each_safe(l_output, temp, &layout->outputs, link) { + output_layout_output_destroy(l_output); + } + + free(layout->state); + free(layout); +} + +static struct wlr_box *output_layout_output_get_box( + struct wlr_output_layout_output *l_output) { + l_output->state->_box.x = l_output->x; + l_output->state->_box.y = l_output->y; + int width, height; + wlr_output_effective_resolution(l_output->output, &width, &height); + l_output->state->_box.width = width; + l_output->state->_box.height = height; + return &l_output->state->_box; +} + +/** + * This must be called whenever the layout changes to reconfigure the auto + * configured outputs and emit the `changed` event. + * + * Auto configured outputs are placed to the right of the north east corner of + * the rightmost output in the layout in a horizontal line. + */ +static void output_layout_reconfigure(struct wlr_output_layout *layout) { + int max_x = INT_MIN; + int max_x_y = INT_MIN; // y value for the max_x output + + // find the rightmost x coordinate occupied by a manually configured output + // in the layout + struct wlr_output_layout_output *l_output; + wl_list_for_each(l_output, &layout->outputs, link) { + if (l_output->state->auto_configured) { + continue; + } + + struct wlr_box *box = output_layout_output_get_box(l_output); + if (box->x + box->width > max_x) { + max_x = box->x + box->width; + max_x_y = box->y; + } + } + + if (max_x == INT_MIN) { + // there are no manually configured outputs + max_x = 0; + max_x_y = 0; + } + + wl_list_for_each(l_output, &layout->outputs, link) { + if (!l_output->state->auto_configured) { + continue; + } + struct wlr_box *box = output_layout_output_get_box(l_output); + l_output->x = max_x; + l_output->y = max_x_y; + max_x += box->width; + } + + wl_list_for_each(l_output, &layout->outputs, link) { + wlr_output_set_position(l_output->output, l_output->x, l_output->y); + } + + wlr_signal_emit_safe(&layout->events.change, layout); +} + +static void output_update_global(struct wlr_output *output) { + // Don't expose the output if it doesn't have a current mode + if (wl_list_empty(&output->modes) || output->current_mode != NULL) { + wlr_output_create_global(output); + } else { + wlr_output_destroy_global(output); + } +} + +static void handle_output_mode(struct wl_listener *listener, void *data) { + struct wlr_output_layout_output_state *state = + wl_container_of(listener, state, mode); + output_layout_reconfigure(state->layout); + output_update_global(state->l_output->output); +} + +static void handle_output_scale(struct wl_listener *listener, void *data) { + struct wlr_output_layout_output_state *state = + wl_container_of(listener, state, scale); + output_layout_reconfigure(state->layout); +} + +static void handle_output_transform(struct wl_listener *listener, void *data) { + struct wlr_output_layout_output_state *state = + wl_container_of(listener, state, transform); + output_layout_reconfigure(state->layout); +} + +static void handle_output_destroy(struct wl_listener *listener, void *data) { + struct wlr_output_layout_output_state *state = + wl_container_of(listener, state, output_destroy); + struct wlr_output_layout *layout = state->layout; + output_layout_output_destroy(state->l_output); + output_layout_reconfigure(layout); +} + +static struct wlr_output_layout_output *output_layout_output_create( + struct wlr_output_layout *layout, struct wlr_output *output) { + struct wlr_output_layout_output *l_output = + calloc(1, sizeof(struct wlr_output_layout_output)); + if (l_output == NULL) { + return NULL; + } + l_output->state = calloc(1, sizeof(struct wlr_output_layout_output_state)); + if (l_output->state == NULL) { + free(l_output); + return NULL; + } + l_output->state->l_output = l_output; + l_output->state->layout = layout; + l_output->output = output; + wl_signal_init(&l_output->events.destroy); + wl_list_insert(&layout->outputs, &l_output->link); + + wl_signal_add(&output->events.mode, &l_output->state->mode); + l_output->state->mode.notify = handle_output_mode; + wl_signal_add(&output->events.scale, &l_output->state->scale); + l_output->state->scale.notify = handle_output_scale; + wl_signal_add(&output->events.transform, &l_output->state->transform); + l_output->state->transform.notify = handle_output_transform; + wl_signal_add(&output->events.destroy, &l_output->state->output_destroy); + l_output->state->output_destroy.notify = handle_output_destroy; + + return l_output; +} + +void wlr_output_layout_add(struct wlr_output_layout *layout, + struct wlr_output *output, int lx, int ly) { + struct wlr_output_layout_output *l_output = + wlr_output_layout_get(layout, output); + bool is_new = l_output == NULL; + if (!l_output) { + l_output = output_layout_output_create(layout, output); + if (!l_output) { + wlr_log(WLR_ERROR, "Failed to create wlr_output_layout_output"); + return; + } + } + + l_output->x = lx; + l_output->y = ly; + l_output->state->auto_configured = false; + output_layout_reconfigure(layout); + output_update_global(output); + + if (is_new) { + wlr_signal_emit_safe(&layout->events.add, l_output); + } +} + +struct wlr_output_layout_output *wlr_output_layout_get( + struct wlr_output_layout *layout, struct wlr_output *reference) { + struct wlr_output_layout_output *l_output; + wl_list_for_each(l_output, &layout->outputs, link) { + if (l_output->output == reference) { + return l_output; + } + } + return NULL; +} + +bool wlr_output_layout_contains_point(struct wlr_output_layout *layout, + struct wlr_output *reference, int lx, int ly) { + if (reference) { + struct wlr_output_layout_output *l_output = + wlr_output_layout_get(layout, reference); + struct wlr_box *box = output_layout_output_get_box(l_output); + return wlr_box_contains_point(box, lx, ly); + } else { + return !!wlr_output_layout_output_at(layout, lx, ly); + } +} + +bool wlr_output_layout_intersects(struct wlr_output_layout *layout, + struct wlr_output *reference, const struct wlr_box *target_lbox) { + struct wlr_box out_box; + + if (reference == NULL) { + struct wlr_output_layout_output *l_output; + wl_list_for_each(l_output, &layout->outputs, link) { + struct wlr_box *output_box = + output_layout_output_get_box(l_output); + if (wlr_box_intersection(&out_box, output_box, target_lbox)) { + return true; + } + } + return false; + } else { + struct wlr_output_layout_output *l_output = + wlr_output_layout_get(layout, reference); + if (!l_output) { + return false; + } + + struct wlr_box *output_box = output_layout_output_get_box(l_output); + return wlr_box_intersection(&out_box, output_box, target_lbox); + } +} + +struct wlr_output *wlr_output_layout_output_at(struct wlr_output_layout *layout, + double lx, double ly) { + struct wlr_output_layout_output *l_output; + wl_list_for_each(l_output, &layout->outputs, link) { + struct wlr_box *box = output_layout_output_get_box(l_output); + if (wlr_box_contains_point(box, lx, ly)) { + return l_output->output; + } + } + return NULL; +} + +void wlr_output_layout_move(struct wlr_output_layout *layout, + struct wlr_output *output, int lx, int ly) { + struct wlr_output_layout_output *l_output = + wlr_output_layout_get(layout, output); + if (l_output) { + l_output->x = lx; + l_output->y = ly; + l_output->state->auto_configured = false; + output_layout_reconfigure(layout); + } else { + wlr_log(WLR_ERROR, "output not found in this layout: %s", output->name); + } +} + +void wlr_output_layout_remove(struct wlr_output_layout *layout, + struct wlr_output *output) { + struct wlr_output_layout_output *l_output = + wlr_output_layout_get(layout, output); + if (l_output) { + output_layout_output_destroy(l_output); + output_layout_reconfigure(layout); + } +} + +void wlr_output_layout_output_coords(struct wlr_output_layout *layout, + struct wlr_output *reference, double *lx, double *ly) { + assert(layout && reference); + double src_x = *lx; + double src_y = *ly; + + struct wlr_output_layout_output *l_output; + wl_list_for_each(l_output, &layout->outputs, link) { + if (l_output->output == reference) { + *lx = src_x - (double)l_output->x; + *ly = src_y - (double)l_output->y; + return; + } + } +} + +void wlr_output_layout_closest_point(struct wlr_output_layout *layout, + struct wlr_output *reference, double lx, double ly, double *dest_lx, + double *dest_ly) { + if (dest_lx == NULL && dest_ly == NULL) { + return; + } + + double min_x = DBL_MAX, min_y = DBL_MAX, min_distance = DBL_MAX; + struct wlr_output_layout_output *l_output; + wl_list_for_each(l_output, &layout->outputs, link) { + if (reference != NULL && reference != l_output->output) { + continue; + } + + double output_x, output_y, output_distance; + struct wlr_box *box = output_layout_output_get_box(l_output); + wlr_box_closest_point(box, lx, ly, &output_x, &output_y); + + // calculate squared distance suitable for comparison + output_distance = + (lx - output_x) * (lx - output_x) + (ly - output_y) * (ly - output_y); + + if (!isfinite(output_distance)) { + output_distance = DBL_MAX; + } + + if (output_distance <= min_distance) { + min_x = output_x; + min_y = output_y; + min_distance = output_distance; + } + } + + if (dest_lx) { + *dest_lx = min_x; + } + if (dest_ly) { + *dest_ly = min_y; + } +} + +struct wlr_box *wlr_output_layout_get_box( + struct wlr_output_layout *layout, struct wlr_output *reference) { + struct wlr_output_layout_output *l_output; + if (reference) { + // output extents + l_output = wlr_output_layout_get(layout, reference); + + if (l_output) { + return output_layout_output_get_box(l_output); + } else { + return NULL; + } + } else { + // layout extents + int min_x = INT_MAX, min_y = INT_MAX; + int max_x = INT_MIN, max_y = INT_MIN; + wl_list_for_each(l_output, &layout->outputs, link) { + struct wlr_box *box = output_layout_output_get_box(l_output); + + if (box->x < min_x) { + min_x = box->x; + } + if (box->y < min_y) { + min_y = box->y; + } + if (box->x + box->width > max_x) { + max_x = box->x + box->width; + } + if (box->y + box->height > max_y) { + max_y = box->y + box->height; + } + } + + layout->state->_box.x = min_x; + layout->state->_box.y = min_y; + layout->state->_box.width = max_x - min_x; + layout->state->_box.height = max_y - min_y; + + return &layout->state->_box; + } + + // not reached +} + +void wlr_output_layout_add_auto(struct wlr_output_layout *layout, + struct wlr_output *output) { + struct wlr_output_layout_output *l_output = + wlr_output_layout_get(layout, output); + bool is_new = l_output == NULL; + if (!l_output) { + l_output = output_layout_output_create(layout, output); + if (!l_output) { + wlr_log(WLR_ERROR, "Failed to create wlr_output_layout_output"); + return; + } + } + + l_output->state->auto_configured = true; + output_layout_reconfigure(layout); + output_update_global(output); + + if (is_new) { + wlr_signal_emit_safe(&layout->events.add, l_output); + } +} + +struct wlr_output *wlr_output_layout_get_center_output( + struct wlr_output_layout *layout) { + if (wl_list_empty(&layout->outputs)) { + return NULL; + } + + struct wlr_box *extents = wlr_output_layout_get_box(layout, NULL); + double center_x = extents->width / 2 + extents->x; + double center_y = extents->height / 2 + extents->y; + + double dest_x = 0, dest_y = 0; + wlr_output_layout_closest_point(layout, NULL, center_x, center_y, + &dest_x, &dest_y); + + return wlr_output_layout_output_at(layout, dest_x, dest_y); +} + +enum distance_selection_method { + NEAREST, + FARTHEST +}; + +struct wlr_output *wlr_output_layout_output_in_direction( + struct wlr_output_layout *layout, enum wlr_direction direction, + struct wlr_output *reference, double ref_lx, double ref_ly, + enum distance_selection_method distance_method) { + assert(reference); + + struct wlr_box *ref_box = wlr_output_layout_get_box(layout, reference); + + double min_distance = (distance_method == NEAREST) ? DBL_MAX : DBL_MIN; + struct wlr_output *closest_output = NULL; + struct wlr_output_layout_output *l_output; + wl_list_for_each(l_output, &layout->outputs, link) { + if (reference != NULL && reference == l_output->output) { + continue; + } + struct wlr_box *box = output_layout_output_get_box(l_output); + + bool match = false; + // test to make sure this output is in the given direction + if (direction & WLR_DIRECTION_LEFT) { + match = box->x + box->width <= ref_box->x || match; + } + if (direction & WLR_DIRECTION_RIGHT) { + match = box->x >= ref_box->x + ref_box->width || match; + } + if (direction & WLR_DIRECTION_UP) { + match = box->y + box->height <= ref_box->y || match; + } + if (direction & WLR_DIRECTION_DOWN) { + match = box->y >= ref_box->y + ref_box->height || match; + } + if (!match) { + continue; + } + + // calculate distance from the given reference point + double x, y; + wlr_output_layout_closest_point(layout, l_output->output, + ref_lx, ref_ly, &x, &y); + double distance = + (x - ref_lx) * (x - ref_lx) + (y - ref_ly) * (y - ref_ly); + + if ((distance_method == NEAREST) + ? distance < min_distance + : distance > min_distance) { + min_distance = distance; + closest_output = l_output->output; + } + } + return closest_output; +} + +struct wlr_output *wlr_output_layout_adjacent_output( + struct wlr_output_layout *layout, enum wlr_direction direction, + struct wlr_output *reference, double ref_lx, double ref_ly) { + return wlr_output_layout_output_in_direction(layout, direction, + reference, ref_lx, ref_ly, NEAREST); +} + +struct wlr_output *wlr_output_layout_farthest_output( + struct wlr_output_layout *layout, enum wlr_direction direction, + struct wlr_output *reference, double ref_lx, double ref_ly) { + return wlr_output_layout_output_in_direction(layout, direction, + reference, ref_lx, ref_ly, FARTHEST); +} diff --git a/types/wlr_pointer.c b/types/wlr_pointer.c new file mode 100644 index 00000000..20e75731 --- /dev/null +++ b/types/wlr_pointer.c @@ -0,0 +1,29 @@ +#include <stdlib.h> +#include <string.h> +#include <wayland-server.h> +#include <wlr/interfaces/wlr_pointer.h> +#include <wlr/types/wlr_pointer.h> + +void wlr_pointer_init(struct wlr_pointer *pointer, + const struct wlr_pointer_impl *impl) { + pointer->impl = impl; + wl_signal_init(&pointer->events.motion); + wl_signal_init(&pointer->events.motion_absolute); + wl_signal_init(&pointer->events.button); + wl_signal_init(&pointer->events.axis); +} + +void wlr_pointer_destroy(struct wlr_pointer *pointer) { + if (!pointer) { + return; + } + if (pointer->impl && pointer->impl->destroy) { + pointer->impl->destroy(pointer); + } else { + wl_list_remove(&pointer->events.motion.listener_list); + wl_list_remove(&pointer->events.motion_absolute.listener_list); + wl_list_remove(&pointer->events.button.listener_list); + wl_list_remove(&pointer->events.axis.listener_list); + free(pointer); + } +} diff --git a/types/wlr_pointer_constraints_v1.c b/types/wlr_pointer_constraints_v1.c new file mode 100644 index 00000000..196af92e --- /dev/null +++ b/types/wlr_pointer_constraints_v1.c @@ -0,0 +1,372 @@ +#include <assert.h> +#include <limits.h> +#include <pixman.h> +#include <stdbool.h> +#include <stdlib.h> +#include <wayland-server.h> +#include <wlr/types/wlr_box.h> +#include <wlr/types/wlr_pointer_constraints_v1.h> +#include <wlr/types/wlr_region.h> +#include <wlr/util/log.h> +#include "util/signal.h" + +static const struct zwp_locked_pointer_v1_interface locked_pointer_impl; +static const struct zwp_confined_pointer_v1_interface confined_pointer_impl; +static const struct zwp_pointer_constraints_v1_interface pointer_constraints_impl; +static void pointer_constraint_destroy(struct wlr_pointer_constraint_v1 *constraint); + +static struct wlr_pointer_constraint_v1 *pointer_constraint_from_resource( + struct wl_resource *resource) { + assert( + wl_resource_instance_of( + resource, &zwp_confined_pointer_v1_interface, + &confined_pointer_impl) || + wl_resource_instance_of( + resource, &zwp_locked_pointer_v1_interface, + &locked_pointer_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_pointer_constraints_v1 *pointer_constraints_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_pointer_constraints_v1_interface, + &pointer_constraints_impl)); + return wl_resource_get_user_data(resource); +} + +static void resource_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void pointer_constraint_destroy(struct wlr_pointer_constraint_v1 *constraint) { + if (constraint == NULL) { + return; + } + + wlr_log(WLR_DEBUG, "destroying constraint %p", constraint); + + wlr_signal_emit_safe(&constraint->events.destroy, constraint); + + wl_resource_set_user_data(constraint->resource, NULL); + wl_list_remove(&constraint->link); + wl_list_remove(&constraint->surface_commit.link); + wl_list_remove(&constraint->surface_destroy.link); + wl_list_remove(&constraint->seat_destroy.link); + pixman_region32_fini(&constraint->current.region); + pixman_region32_fini(&constraint->pending.region); + pixman_region32_fini(&constraint->region); + free(constraint); +} + +static void pointer_constraint_destroy_resource(struct wl_resource *resource) { + struct wlr_pointer_constraint_v1 *constraint = + pointer_constraint_from_resource(resource); + + pointer_constraint_destroy(constraint); +} + +static void pointer_constraint_set_region( + struct wlr_pointer_constraint_v1 *constraint, + struct wl_resource *region_resource) { + pixman_region32_clear(&constraint->pending.region); + + if (region_resource) { + pixman_region32_t *region = wlr_region_from_resource(region_resource); + pixman_region32_copy(&constraint->pending.region, region); + } + + constraint->pending.committed |= WLR_POINTER_CONSTRAINT_V1_STATE_REGION; +} + +static void pointer_constraint_handle_set_region(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *region_resource) { + struct wlr_pointer_constraint_v1 *constraint = + pointer_constraint_from_resource(resource); + if (constraint == NULL) { + return; + } + + pointer_constraint_set_region(constraint, region_resource); +} + +static void pointer_constraint_set_cursor_position_hint(struct wl_client *client, + struct wl_resource *resource, wl_fixed_t x, wl_fixed_t y) { + struct wlr_pointer_constraint_v1 *constraint = + pointer_constraint_from_resource(resource); + if (constraint == NULL) { + return; + } + + constraint->pending.cursor_hint.x = wl_fixed_to_double(x); + constraint->pending.cursor_hint.y = wl_fixed_to_double(y); + constraint->pending.committed |= WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT; +} + +static void pointer_constraint_commit( + struct wlr_pointer_constraint_v1 *constraint) { + if (constraint->pending.committed & + WLR_POINTER_CONSTRAINT_V1_STATE_REGION) { + pixman_region32_copy(&constraint->current.region, + &constraint->pending.region); + } + if (constraint->pending.committed & + WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT) { + constraint->current.cursor_hint = constraint->pending.cursor_hint; + } + constraint->current.committed |= constraint->pending.committed; + + constraint->pending.committed = 0; + + pixman_region32_clear(&constraint->region); + if (pixman_region32_not_empty(&constraint->current.region)) { + pixman_region32_intersect(&constraint->region, + &constraint->surface->input_region, &constraint->current.region); + } else { + pixman_region32_copy(&constraint->region, + &constraint->surface->input_region); + } +} + +static void handle_surface_commit(struct wl_listener *listener, void *data) { + struct wlr_pointer_constraint_v1 *constraint = + wl_container_of(listener, constraint, surface_commit); + + pointer_constraint_commit(constraint); +} + +static void handle_surface_destroy(struct wl_listener *listener, void *data) { + struct wlr_pointer_constraint_v1 *constraint = + wl_container_of(listener, constraint, surface_destroy); + + pointer_constraint_destroy(constraint); +} + +static void handle_seat_destroy(struct wl_listener *listener, void *data) { + struct wlr_pointer_constraint_v1 *constraint = + wl_container_of(listener, constraint, seat_destroy); + + pointer_constraint_destroy(constraint); +} + +static const struct zwp_confined_pointer_v1_interface confined_pointer_impl = { + .destroy = resource_destroy, + .set_region = pointer_constraint_handle_set_region, +}; + +static const struct zwp_locked_pointer_v1_interface locked_pointer_impl = { + .destroy = resource_destroy, + .set_region = pointer_constraint_handle_set_region, + .set_cursor_position_hint = pointer_constraint_set_cursor_position_hint, +}; + +static void pointer_constraint_create(struct wl_client *client, + struct wl_resource *pointer_constraints_resource, uint32_t id, + struct wl_resource *surface_resource, + struct wl_resource *pointer_resource, + struct wl_resource *region_resource, + enum zwp_pointer_constraints_v1_lifetime lifetime, + enum wlr_pointer_constraint_v1_type type) { + struct wlr_pointer_constraints_v1 *pointer_constraints = + pointer_constraints_from_resource(pointer_constraints_resource); + + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + struct wlr_seat *seat = + wlr_seat_client_from_pointer_resource(pointer_resource)->seat; + + if (wlr_pointer_constraints_v1_constraint_for_surface(pointer_constraints, + surface, seat)) { + wl_resource_post_error(pointer_constraints_resource, + ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED, + "a pointer constraint with a wl_pointer of the same wl_seat" + " is already on this surface"); + return; + } + + uint32_t version = wl_resource_get_version(pointer_constraints_resource); + + bool locked_pointer = type == WLR_POINTER_CONSTRAINT_V1_LOCKED; + + struct wl_resource *resource = locked_pointer ? + wl_resource_create(client, &zwp_locked_pointer_v1_interface, version, id) : + wl_resource_create(client, &zwp_confined_pointer_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + struct wlr_pointer_constraint_v1 *constraint = calloc(1, sizeof(*constraint)); + if (constraint == NULL) { + wl_resource_destroy(resource); + wl_client_post_no_memory(client); + return; + } + + constraint->resource = resource; + constraint->surface = surface; + constraint->seat = seat; + constraint->lifetime = lifetime; + constraint->type = type; + constraint->pointer_constraints = pointer_constraints; + + wl_signal_init(&constraint->events.destroy); + + pixman_region32_init(&constraint->region); + + pixman_region32_init(&constraint->pending.region); + pixman_region32_init(&constraint->current.region); + + pointer_constraint_set_region(constraint, region_resource); + pointer_constraint_commit(constraint); + + constraint->surface_commit.notify = handle_surface_commit; + wl_signal_add(&surface->events.commit, &constraint->surface_commit); + + constraint->surface_destroy.notify = handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &constraint->surface_destroy); + + constraint->seat_destroy.notify = handle_seat_destroy; + wl_signal_add(&seat->events.destroy, &constraint->seat_destroy); + + void *impl = locked_pointer ? + (void *)&locked_pointer_impl : (void *)&confined_pointer_impl; + wl_resource_set_implementation(constraint->resource, impl, constraint, + pointer_constraint_destroy_resource); + + wlr_log(WLR_DEBUG, "new %s_pointer %p (res %p)", + locked_pointer ? "locked" : "confined", + constraint, constraint->resource); + + wl_list_insert(&pointer_constraints->constraints, &constraint->link); + + wlr_signal_emit_safe(&pointer_constraints->events.new_constraint, + constraint); +} + +static void pointer_constraints_lock_pointer(struct wl_client *client, + struct wl_resource *cons_resource, uint32_t id, + struct wl_resource *surface, struct wl_resource *pointer, + struct wl_resource *region, enum zwp_pointer_constraints_v1_lifetime lifetime) { + pointer_constraint_create(client, cons_resource, id, surface, pointer, + region, lifetime, WLR_POINTER_CONSTRAINT_V1_LOCKED); +} + +static void pointer_constraints_confine_pointer(struct wl_client *client, + struct wl_resource *cons_resource, uint32_t id, + struct wl_resource *surface, struct wl_resource *pointer, + struct wl_resource *region, + enum zwp_pointer_constraints_v1_lifetime lifetime) { + pointer_constraint_create(client, cons_resource, id, surface, pointer, + region, lifetime, WLR_POINTER_CONSTRAINT_V1_CONFINED); +} + +static const struct zwp_pointer_constraints_v1_interface + pointer_constraints_impl = { + .destroy = resource_destroy, + .lock_pointer = pointer_constraints_lock_pointer, + .confine_pointer = pointer_constraints_confine_pointer, +}; + +static void pointer_constraints_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void pointer_constraints_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_pointer_constraints_v1 *pointer_constraints = data; + assert(client && pointer_constraints); + + struct wl_resource *resource = wl_resource_create(client, + &zwp_pointer_constraints_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_list_insert(&pointer_constraints->resources, + wl_resource_get_link(resource)); + wl_resource_set_implementation(resource, &pointer_constraints_impl, + pointer_constraints, pointer_constraints_destroy); +} + +struct wlr_pointer_constraints_v1 *wlr_pointer_constraints_v1_create( + struct wl_display *display) { + struct wlr_pointer_constraints_v1 *pointer_constraints = + calloc(1, sizeof(*pointer_constraints)); + + if (!pointer_constraints) { + return NULL; + } + + struct wl_global *wl_global = wl_global_create(display, + &zwp_pointer_constraints_v1_interface, 1, pointer_constraints, + pointer_constraints_bind); + if (!wl_global) { + free(pointer_constraints); + return NULL; + } + pointer_constraints->global = wl_global; + + wl_list_init(&pointer_constraints->resources); + wl_list_init(&pointer_constraints->constraints); + wl_signal_init(&pointer_constraints->events.new_constraint); + + return pointer_constraints; +} + +void wlr_pointer_constraints_v1_destroy( + struct wlr_pointer_constraints_v1 *pointer_constraints) { + struct wl_resource *resource, *_tmp_res; + wl_resource_for_each_safe(resource, _tmp_res, + &pointer_constraints->resources) { + wl_resource_destroy(resource); + } + + struct wlr_pointer_constraint_v1 *constraint, *_tmp_cons; + wl_list_for_each_safe(constraint, _tmp_cons, + &pointer_constraints->constraints, link) { + wl_resource_destroy(constraint->resource); + } + + wl_global_destroy(pointer_constraints->global); + free(pointer_constraints); +} + +struct wlr_pointer_constraint_v1 * + wlr_pointer_constraints_v1_constraint_for_surface( + struct wlr_pointer_constraints_v1 *pointer_constraints, + struct wlr_surface *surface, struct wlr_seat *seat) { + struct wlr_pointer_constraint_v1 *constraint; + wl_list_for_each(constraint, &pointer_constraints->constraints, link) { + if (constraint->surface == surface && constraint->seat == seat) { + return constraint; + } + } + + return NULL; +} + +void wlr_pointer_constraint_v1_send_activated( + struct wlr_pointer_constraint_v1 *constraint) { + wlr_log(WLR_DEBUG, "constrained %p", constraint); + if (constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) { + zwp_locked_pointer_v1_send_locked(constraint->resource); + } else { + zwp_confined_pointer_v1_send_confined(constraint->resource); + } +} + +void wlr_pointer_constraint_v1_send_deactivated( + struct wlr_pointer_constraint_v1 *constraint) { + wlr_log(WLR_DEBUG, "unconstrained %p", constraint); + if (constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) { + zwp_locked_pointer_v1_send_unlocked(constraint->resource); + } else { + zwp_confined_pointer_v1_send_unconfined(constraint->resource); + } + + if (constraint->lifetime == + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT) { + pointer_constraint_destroy(constraint); + } +} diff --git a/types/wlr_presentation_time.c b/types/wlr_presentation_time.c new file mode 100644 index 00000000..eeba47b6 --- /dev/null +++ b/types/wlr_presentation_time.c @@ -0,0 +1,223 @@ +#define _POSIX_C_SOURCE 199309L +#include <assert.h> +#include <stdlib.h> +#include <wlr/types/wlr_presentation_time.h> +#include <wlr/types/wlr_surface.h> +#include <wlr/backend.h> +#include "presentation-time-protocol.h" +#include "util/signal.h" + +#define PRESENTATION_VERSION 1 + +static struct wlr_presentation_feedback *presentation_feedback_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &wp_presentation_feedback_interface, NULL)); + return wl_resource_get_user_data(resource); +} + +static void feedback_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_presentation_feedback *feedback = + presentation_feedback_from_resource(resource); + wl_list_remove(&feedback->surface_commit.link); + wl_list_remove(&feedback->surface_destroy.link); + wl_list_remove(&feedback->link); + free(feedback); +} + +// Destroys the feedback +static void feedback_send_presented(struct wlr_presentation_feedback *feedback, + struct wlr_presentation_event *event) { + struct wl_client *client = wl_resource_get_client(feedback->resource); + struct wl_resource *resource; + wl_resource_for_each(resource, &event->output->resources) { + if (wl_resource_get_client(resource) == client) { + wp_presentation_feedback_send_sync_output(feedback->resource, + resource); + } + } + + uint32_t tv_sec_hi = event->tv_sec >> 32; + uint32_t tv_sec_lo = event->tv_sec & 0xFFFFFFFF; + uint32_t seq_hi = event->seq >> 32; + uint32_t seq_lo = event->seq & 0xFFFFFFFF; + wp_presentation_feedback_send_presented(feedback->resource, + tv_sec_hi, tv_sec_lo, event->tv_nsec, event->refresh, + seq_hi, seq_lo, event->flags); + + wl_resource_destroy(feedback->resource); +} + +// Destroys the feedback +static void feedback_send_discarded( + struct wlr_presentation_feedback *feedback) { + wp_presentation_feedback_send_discarded(feedback->resource); + wl_resource_destroy(feedback->resource); +} + +static void feedback_handle_surface_commit(struct wl_listener *listener, + void *data) { + struct wlr_presentation_feedback *feedback = + wl_container_of(listener, feedback, surface_commit); + + if (feedback->committed) { + // The content update has been superseded + feedback_send_discarded(feedback); + } else { + feedback->committed = true; + } +} + +static void feedback_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_presentation_feedback *feedback = + wl_container_of(listener, feedback, surface_destroy); + feedback_send_discarded(feedback); +} + +static const struct wp_presentation_interface presentation_impl; + +static struct wlr_presentation *presentation_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wp_presentation_interface, + &presentation_impl)); + return wl_resource_get_user_data(resource); +} + +static void presentation_handle_feedback(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *surface_resource, + uint32_t id) { + struct wlr_presentation *presentation = + presentation_from_resource(resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + struct wlr_presentation_feedback *feedback = + calloc(1, sizeof(struct wlr_presentation_feedback)); + if (feedback == NULL) { + wl_client_post_no_memory(client); + return; + } + + uint32_t version = wl_resource_get_version(resource); + feedback->resource = wl_resource_create(client, + &wp_presentation_feedback_interface, version, id); + if (feedback->resource == NULL) { + free(feedback); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(feedback->resource, NULL, feedback, + feedback_handle_resource_destroy); + + feedback->surface = surface; + + feedback->surface_commit.notify = feedback_handle_surface_commit; + wl_signal_add(&surface->events.commit, &feedback->surface_commit); + + feedback->surface_destroy.notify = feedback_handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &feedback->surface_destroy); + + wl_list_insert(&presentation->feedbacks, &feedback->link); +} + +static void presentation_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wp_presentation_interface presentation_impl = { + .feedback = presentation_handle_feedback, + .destroy = presentation_handle_destroy, +}; + +static void presentation_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void presentation_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_presentation *presentation = data; + + struct wl_resource *resource = wl_resource_create(client, + &wp_presentation_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &presentation_impl, presentation, + presentation_handle_resource_destroy); + wl_list_insert(&presentation->resources, wl_resource_get_link(resource)); + + wp_presentation_send_clock_id(resource, (uint32_t)presentation->clock); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_presentation *presentation = + wl_container_of(listener, presentation, display_destroy); + wlr_presentation_destroy(presentation); +} + +struct wlr_presentation *wlr_presentation_create(struct wl_display *display, + struct wlr_backend *backend) { + struct wlr_presentation *presentation = + calloc(1, sizeof(struct wlr_presentation)); + if (presentation == NULL) { + return NULL; + } + + presentation->global = wl_global_create(display, &wp_presentation_interface, + PRESENTATION_VERSION, presentation, presentation_bind); + if (presentation->global == NULL) { + free(presentation); + return NULL; + } + + presentation->clock = wlr_backend_get_presentation_clock(backend); + + wl_list_init(&presentation->resources); + wl_list_init(&presentation->feedbacks); + wl_signal_init(&presentation->events.destroy); + + presentation->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &presentation->display_destroy); + + return presentation; +} + +void wlr_presentation_destroy(struct wlr_presentation *presentation) { + if (presentation == NULL) { + return; + } + + wlr_signal_emit_safe(&presentation->events.destroy, presentation); + + wl_global_destroy(presentation->global); + + struct wlr_presentation_feedback *feedback, *feedback_tmp; + wl_list_for_each_safe(feedback, feedback_tmp, &presentation->feedbacks, + link) { + wl_resource_destroy(feedback->resource); + } + + struct wl_resource *resource, *resource_tmp; + wl_resource_for_each_safe(resource, resource_tmp, + &presentation->resources) { + wl_resource_destroy(resource); + } + + wl_list_remove(&presentation->display_destroy.link); + free(presentation); +} + +void wlr_presentation_send_surface_presented( + struct wlr_presentation *presentation, struct wlr_surface *surface, + struct wlr_presentation_event *event) { + // TODO: maybe use a hashtable to optimize this function + struct wlr_presentation_feedback *feedback, *feedback_tmp; + wl_list_for_each_safe(feedback, feedback_tmp, + &presentation->feedbacks, link) { + if (feedback->surface == surface && feedback->committed) { + feedback_send_presented(feedback, event); + } + } +} diff --git a/types/wlr_primary_selection.c b/types/wlr_primary_selection.c new file mode 100644 index 00000000..921f44ee --- /dev/null +++ b/types/wlr_primary_selection.c @@ -0,0 +1,69 @@ +#include <assert.h> +#include <stdlib.h> +#include <wlr/types/wlr_primary_selection.h> +#include "util/signal.h" + +void wlr_primary_selection_source_init( + struct wlr_primary_selection_source *source, + const struct wlr_primary_selection_source_impl *impl) { + assert(impl->send); + wl_array_init(&source->mime_types); + wl_signal_init(&source->events.destroy); + source->impl = impl; +} + +void wlr_primary_selection_source_destroy( + struct wlr_primary_selection_source *source) { + if (source == NULL) { + return; + } + + wlr_signal_emit_safe(&source->events.destroy, source); + + char **p; + wl_array_for_each(p, &source->mime_types) { + free(*p); + } + wl_array_release(&source->mime_types); + + if (source->impl->destroy) { + source->impl->destroy(source); + } else { + free(source); + } +} + +void wlr_primary_selection_source_send( + struct wlr_primary_selection_source *source, const char *mime_type, + int32_t fd) { + source->impl->send(source, mime_type, fd); +} + + +static void seat_handle_primary_selection_source_destroy( + struct wl_listener *listener, void *data) { + struct wlr_seat *seat = + wl_container_of(listener, seat, primary_selection_source_destroy); + wl_list_remove(&seat->primary_selection_source_destroy.link); + seat->primary_selection_source = NULL; + wlr_signal_emit_safe(&seat->events.primary_selection, seat); +} + +void wlr_seat_set_primary_selection(struct wlr_seat *seat, + struct wlr_primary_selection_source *source) { + if (seat->primary_selection_source != NULL) { + wl_list_remove(&seat->primary_selection_source_destroy.link); + wlr_primary_selection_source_destroy(seat->primary_selection_source); + seat->primary_selection_source = NULL; + } + + seat->primary_selection_source = source; + if (source != NULL) { + seat->primary_selection_source_destroy.notify = + seat_handle_primary_selection_source_destroy; + wl_signal_add(&source->events.destroy, + &seat->primary_selection_source_destroy); + } + + wlr_signal_emit_safe(&seat->events.primary_selection, seat); +} diff --git a/types/wlr_region.c b/types/wlr_region.c new file mode 100644 index 00000000..6996ab14 --- /dev/null +++ b/types/wlr_region.c @@ -0,0 +1,78 @@ +#include <assert.h> +#include <pixman.h> +#include <stdio.h> +#include <stdlib.h> +#include <wayland-server.h> +#include <wlr/types/wlr_region.h> + +static void region_add(struct wl_client *client, struct wl_resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) { + pixman_region32_t *region = wlr_region_from_resource(resource); + pixman_region32_union_rect(region, region, x, y, width, height); +} + +static void region_subtract(struct wl_client *client, struct wl_resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) { + pixman_region32_t *region = wlr_region_from_resource(resource); + pixman_region32_union_rect(region, region, x, y, width, height); + + pixman_region32_t rect; + pixman_region32_init_rect(&rect, x, y, width, height); + pixman_region32_subtract(region, region, &rect); + pixman_region32_fini(&rect); +} + +static void region_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wl_region_interface region_impl = { + .destroy = region_destroy, + .add = region_add, + .subtract = region_subtract, +}; + +static void region_handle_resource_destroy(struct wl_resource *resource) { + pixman_region32_t *reg = wlr_region_from_resource(resource); + + wl_list_remove(wl_resource_get_link(resource)); + + pixman_region32_fini(reg); + free(reg); +} + +struct wl_resource *wlr_region_create(struct wl_client *client, + uint32_t version, uint32_t id, struct wl_list *resource_list) { + pixman_region32_t *region = calloc(1, sizeof(pixman_region32_t)); + if (region == NULL) { + wl_client_post_no_memory(client); + return NULL; + } + + pixman_region32_init(region); + + struct wl_resource *region_resource = wl_resource_create(client, + &wl_region_interface, version, id); + if (region_resource == NULL) { + free(region); + wl_client_post_no_memory(client); + return NULL; + } + wl_resource_set_implementation(region_resource, ®ion_impl, region, + region_handle_resource_destroy); + + struct wl_list *resource_link = wl_resource_get_link(region_resource); + if (resource_list != NULL) { + wl_list_insert(resource_list, resource_link); + } else { + wl_list_init(resource_link); + } + + return region_resource; +} + +pixman_region32_t *wlr_region_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_region_interface, + ®ion_impl)); + return wl_resource_get_user_data(resource); +} diff --git a/types/wlr_screencopy_v1.c b/types/wlr_screencopy_v1.c new file mode 100644 index 00000000..8beb650f --- /dev/null +++ b/types/wlr_screencopy_v1.c @@ -0,0 +1,349 @@ +#include <assert.h> +#include <stdlib.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_output.h> +#include <wlr/types/wlr_screencopy_v1.h> +#include <wlr/backend.h> +#include <wlr/util/log.h> +#include "wlr-screencopy-unstable-v1-protocol.h" +#include "util/signal.h" + +#define SCREENCOPY_MANAGER_VERSION 1 + +static const struct zwlr_screencopy_frame_v1_interface frame_impl; + +static struct wlr_screencopy_frame_v1 *frame_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_screencopy_frame_v1_interface, &frame_impl)); + return wl_resource_get_user_data(resource); +} + +static void frame_destroy(struct wlr_screencopy_frame_v1 *frame) { + if (frame == NULL) { + return; + } + if (frame->cursor_locked) { + wlr_output_lock_software_cursors(frame->output, false); + } + wl_list_remove(&frame->link); + wl_list_remove(&frame->output_swap_buffers.link); + wl_list_remove(&frame->buffer_destroy.link); + // Make the frame resource inert + wl_resource_set_user_data(frame->resource, NULL); + free(frame); +} + +static void frame_handle_output_swap_buffers(struct wl_listener *listener, + void *_data) { + struct wlr_screencopy_frame_v1 *frame = + wl_container_of(listener, frame, output_swap_buffers); + struct wlr_output_event_swap_buffers *event = _data; + struct wlr_output *output = frame->output; + struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend); + assert(renderer); + + wl_list_remove(&frame->output_swap_buffers.link); + wl_list_init(&frame->output_swap_buffers.link); + + int x = frame->box.x; + int y = frame->box.y; + + struct wl_shm_buffer *buffer = frame->buffer; + assert(buffer != NULL); + + enum wl_shm_format fmt = wl_shm_buffer_get_format(buffer); + int32_t width = wl_shm_buffer_get_width(buffer); + int32_t height = wl_shm_buffer_get_height(buffer); + int32_t stride = wl_shm_buffer_get_stride(buffer); + + wl_shm_buffer_begin_access(buffer); + void *data = wl_shm_buffer_get_data(buffer); + uint32_t flags = 0; + bool ok = wlr_renderer_read_pixels(renderer, fmt, &flags, stride, + width, height, x, y, 0, 0, data); + wl_shm_buffer_end_access(buffer); + + if (!ok) { + zwlr_screencopy_frame_v1_send_failed(frame->resource); + frame_destroy(frame); + return; + } + + zwlr_screencopy_frame_v1_send_flags(frame->resource, flags); + + time_t tv_sec = event->when->tv_sec; + uint32_t tv_sec_hi = (sizeof(tv_sec) > 4) ? tv_sec >> 32 : 0; + uint32_t tv_sec_lo = tv_sec & 0xFFFFFFFF; + zwlr_screencopy_frame_v1_send_ready(frame->resource, + tv_sec_hi, tv_sec_lo, event->when->tv_nsec); + + frame_destroy(frame); +} + +static void frame_handle_buffer_destroy(struct wl_listener *listener, + void *data) { + struct wlr_screencopy_frame_v1 *frame = + wl_container_of(listener, frame, buffer_destroy); + zwlr_screencopy_frame_v1_send_failed(frame->resource); + frame_destroy(frame); +} + +static void frame_handle_copy(struct wl_client *client, + struct wl_resource *frame_resource, + struct wl_resource *buffer_resource) { + struct wlr_screencopy_frame_v1 *frame = frame_from_resource(frame_resource); + if (frame == NULL) { + return; + } + + struct wlr_output *output = frame->output; + + struct wl_shm_buffer *buffer = wl_shm_buffer_get(buffer_resource); + if (buffer == NULL) { + wl_resource_post_error(frame->resource, + ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, + "unsupported buffer type"); + return; + } + + enum wl_shm_format fmt = wl_shm_buffer_get_format(buffer); + int32_t width = wl_shm_buffer_get_width(buffer); + int32_t height = wl_shm_buffer_get_height(buffer); + int32_t stride = wl_shm_buffer_get_stride(buffer); + if (fmt != frame->format || width != frame->box.width || + height != frame->box.height || stride != frame->stride) { + wl_resource_post_error(frame->resource, + ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, + "invalid buffer attributes"); + return; + } + + if (!wl_list_empty(&frame->output_swap_buffers.link) || + frame->buffer != NULL) { + wl_resource_post_error(frame->resource, + ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED, + "frame already used"); + return; + } + + frame->buffer = buffer; + + wl_signal_add(&output->events.swap_buffers, &frame->output_swap_buffers); + frame->output_swap_buffers.notify = frame_handle_output_swap_buffers; + + wl_resource_add_destroy_listener(buffer_resource, &frame->buffer_destroy); + frame->buffer_destroy.notify = frame_handle_buffer_destroy; + + // Schedule a buffer swap + output->needs_swap = true; + wlr_output_schedule_frame(output); + + if (frame->overlay_cursor) { + wlr_output_lock_software_cursors(output, true); + frame->cursor_locked = true; + } +} + +static void frame_handle_destroy(struct wl_client *client, + struct wl_resource *frame_resource) { + wl_resource_destroy(frame_resource); +} + +static const struct zwlr_screencopy_frame_v1_interface frame_impl = { + .copy = frame_handle_copy, + .destroy = frame_handle_destroy, +}; + +static void frame_handle_resource_destroy(struct wl_resource *frame_resource) { + struct wlr_screencopy_frame_v1 *frame = frame_from_resource(frame_resource); + frame_destroy(frame); +} + + +static const struct zwlr_screencopy_manager_v1_interface manager_impl; + +static struct wlr_screencopy_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwlr_screencopy_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void capture_output(struct wl_client *client, + struct wlr_screencopy_manager_v1 *manager, uint32_t version, uint32_t id, + int32_t overlay_cursor, struct wlr_output *output, + const struct wlr_box *box) { + struct wlr_box buffer_box = {0}; + if (box == NULL) { + buffer_box.width = output->width; + buffer_box.height = output->height; + } else { + int ow, oh; + wlr_output_effective_resolution(output, &ow, &oh); + + buffer_box = *box; + + wlr_box_transform(&buffer_box, &buffer_box, output->transform, ow, oh); + buffer_box.x *= output->scale; + buffer_box.y *= output->scale; + buffer_box.width *= output->scale; + buffer_box.height *= output->scale; + } + + struct wlr_screencopy_frame_v1 *frame = + calloc(1, sizeof(struct wlr_screencopy_frame_v1)); + if (frame == NULL) { + wl_client_post_no_memory(client); + return; + } + frame->manager = manager; + frame->output = output; + frame->overlay_cursor = !!overlay_cursor; + + frame->resource = wl_resource_create(client, + &zwlr_screencopy_frame_v1_interface, version, id); + if (frame->resource == NULL) { + free(frame); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(frame->resource, &frame_impl, frame, + frame_handle_resource_destroy); + + wl_list_insert(&manager->frames, &frame->link); + + wl_list_init(&frame->output_swap_buffers.link); + wl_list_init(&frame->buffer_destroy.link); + + struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend); + assert(renderer); + + if (!wlr_output_preferred_read_format(frame->output, &frame->format)) { + wlr_log(WLR_ERROR, + "Failed to capture output: no read format supported by renderer"); + goto error; + } + + frame->box = buffer_box; + frame->stride = 4 * buffer_box.width; // TODO: depends on read format + + zwlr_screencopy_frame_v1_send_buffer(frame->resource, frame->format, + buffer_box.width, buffer_box.height, frame->stride); + return; + +error: + zwlr_screencopy_frame_v1_send_failed(frame->resource); + frame_destroy(frame); +} + +static void manager_handle_capture_output(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + int32_t overlay_cursor, struct wl_resource *output_resource) { + struct wlr_screencopy_manager_v1 *manager = + manager_from_resource(manager_resource); + uint32_t version = wl_resource_get_version(manager_resource); + struct wlr_output *output = wlr_output_from_resource(output_resource); + + capture_output(client, manager, version, id, overlay_cursor, output, NULL); +} + +static void manager_handle_capture_output_region(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + int32_t overlay_cursor, struct wl_resource *output_resource, + int32_t x, int32_t y, int32_t width, int32_t height) { + struct wlr_screencopy_manager_v1 *manager = + manager_from_resource(manager_resource); + uint32_t version = wl_resource_get_version(manager_resource); + struct wlr_output *output = wlr_output_from_resource(output_resource); + + struct wlr_box box = { + .x = x, + .y = y, + .width = width, + .height = height, + }; + capture_output(client, manager, version, id, overlay_cursor, output, &box); +} + +static void manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct zwlr_screencopy_manager_v1_interface manager_impl = { + .capture_output = manager_handle_capture_output, + .capture_output_region = manager_handle_capture_output_region, + .destroy = manager_handle_destroy, +}; + +void manager_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void manager_bind(struct wl_client *client, void *data, uint32_t version, + uint32_t id) { + struct wlr_screencopy_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwlr_screencopy_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, + manager_handle_resource_destroy); + + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_screencopy_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wlr_screencopy_manager_v1_destroy(manager); +} + +struct wlr_screencopy_manager_v1 *wlr_screencopy_manager_v1_create( + struct wl_display *display) { + struct wlr_screencopy_manager_v1 *manager = + calloc(1, sizeof(struct wlr_screencopy_manager_v1)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, + &zwlr_screencopy_manager_v1_interface, SCREENCOPY_MANAGER_VERSION, + manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + wl_list_init(&manager->resources); + wl_list_init(&manager->frames); + + wl_signal_init(&manager->events.destroy); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +void wlr_screencopy_manager_v1_destroy( + struct wlr_screencopy_manager_v1 *manager) { + if (manager == NULL) { + return; + } + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + struct wlr_screencopy_frame_v1 *frame, *tmp_frame; + wl_list_for_each_safe(frame, tmp_frame, &manager->frames, link) { + wl_resource_destroy(frame->resource); + } + struct wl_resource *resource, *tmp_resource; + wl_resource_for_each_safe(resource, tmp_resource, &manager->resources) { + wl_resource_destroy(resource); + } + wl_global_destroy(manager->global); + free(manager); +} diff --git a/types/wlr_screenshooter.c b/types/wlr_screenshooter.c new file mode 100644 index 00000000..c85e6ba5 --- /dev/null +++ b/types/wlr_screenshooter.c @@ -0,0 +1,213 @@ +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <wayland-server.h> +#include <wlr/backend.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_output.h> +#include <wlr/types/wlr_screenshooter.h> +#include <wlr/util/log.h> +#include "screenshooter-protocol.h" +#include "util/signal.h" + +static struct wlr_screenshot *screenshot_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &orbital_screenshot_interface, + NULL)); + return wl_resource_get_user_data(resource); +} + +struct screenshot_state { + struct wl_shm_buffer *shm_buffer; + struct wlr_screenshot *screenshot; + struct wl_listener frame_listener; +}; + +static void screenshot_destroy(struct wlr_screenshot *screenshot) { + wl_list_remove(&screenshot->link); + wl_resource_set_user_data(screenshot->resource, NULL); + free(screenshot); +} + +static void handle_screenshot_resource_destroy( + struct wl_resource *screenshot_resource) { + struct wlr_screenshot *screenshot = + screenshot_from_resource(screenshot_resource); + if (screenshot != NULL) { + screenshot_destroy(screenshot); + } +} + +static void output_handle_frame(struct wl_listener *listener, void *_data) { + struct screenshot_state *state = wl_container_of(listener, state, + frame_listener); + struct wlr_output *output = state->screenshot->output; + struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend); + struct wl_shm_buffer *shm_buffer = state->shm_buffer; + + enum wl_shm_format format = wl_shm_buffer_get_format(shm_buffer); + int32_t width = wl_shm_buffer_get_width(shm_buffer); + int32_t height = wl_shm_buffer_get_height(shm_buffer); + int32_t stride = wl_shm_buffer_get_stride(shm_buffer); + wl_shm_buffer_begin_access(shm_buffer); + void *data = wl_shm_buffer_get_data(shm_buffer); + bool ok = wlr_renderer_read_pixels(renderer, format, NULL, stride, + width, height, 0, 0, 0, 0, data); + wl_shm_buffer_end_access(shm_buffer); + + if (!ok) { + wlr_log(WLR_ERROR, "Cannot read pixels"); + goto cleanup; + } + + orbital_screenshot_send_done(state->screenshot->resource); + +cleanup: + wl_list_remove(&listener->link); + free(state); +} + +static const struct orbital_screenshooter_interface screenshooter_impl; + +static struct wlr_screenshooter *screenshooter_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &orbital_screenshooter_interface, + &screenshooter_impl)); + return wl_resource_get_user_data(resource); +} + +static void screenshooter_shoot(struct wl_client *client, + struct wl_resource *screenshooter_resource, uint32_t id, + struct wl_resource *output_resource, + struct wl_resource *buffer_resource) { + struct wlr_screenshooter *screenshooter = + screenshooter_from_resource(screenshooter_resource); + struct wlr_output *output = wlr_output_from_resource(output_resource); + + struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend); + if (renderer == NULL) { + wlr_log(WLR_ERROR, "Backend doesn't have a renderer"); + return; + } + + struct wl_shm_buffer *shm_buffer = wl_shm_buffer_get(buffer_resource); + if (shm_buffer == NULL) { + wlr_log(WLR_ERROR, "Invalid buffer: not a shared memory buffer"); + return; + } + + int32_t width = wl_shm_buffer_get_width(shm_buffer); + int32_t height = wl_shm_buffer_get_height(shm_buffer); + if (width < output->width || height < output->height) { + wlr_log(WLR_ERROR, "Invalid buffer: too small"); + return; + } + + uint32_t format = wl_shm_buffer_get_format(shm_buffer); + if (!wlr_renderer_format_supported(renderer, format)) { + wlr_log(WLR_ERROR, "Invalid buffer: unsupported format"); + return; + } + + struct wlr_screenshot *screenshot = + calloc(1, sizeof(struct wlr_screenshot)); + if (!screenshot) { + wl_resource_post_no_memory(screenshooter_resource); + return; + } + screenshot->output_resource = output_resource; + screenshot->output = output; + screenshot->screenshooter = screenshooter; + screenshot->resource = wl_resource_create(client, + &orbital_screenshot_interface, + wl_resource_get_version(screenshooter_resource), id); + if (screenshot->resource == NULL) { + free(screenshot); + wl_resource_post_no_memory(screenshooter_resource); + return; + } + wl_resource_set_implementation(screenshot->resource, NULL, screenshot, + handle_screenshot_resource_destroy); + wl_list_insert(&screenshooter->screenshots, &screenshot->link); + + wlr_log(WLR_DEBUG, "new screenshot %p (res %p)", screenshot, + screenshot->resource); + + struct screenshot_state *state = calloc(1, sizeof(struct screenshot_state)); + if (!state) { + wl_resource_destroy(screenshot->resource); + free(screenshot); + wl_resource_post_no_memory(screenshooter_resource); + return; + } + state->shm_buffer = shm_buffer; + state->screenshot = screenshot; + state->frame_listener.notify = output_handle_frame; + wl_signal_add(&output->events.swap_buffers, &state->frame_listener); + + // Schedule a buffer swap + output->needs_swap = true; + wlr_output_schedule_frame(output); +} + +static const struct orbital_screenshooter_interface screenshooter_impl = { + .shoot = screenshooter_shoot, +}; + +static void screenshooter_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_screenshooter *screenshooter = data; + assert(wl_client && screenshooter); + + struct wl_resource *wl_resource = wl_resource_create(wl_client, + &orbital_screenshooter_interface, version, id); + if (wl_resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(wl_resource, &screenshooter_impl, + screenshooter, NULL); +} + +void wlr_screenshooter_destroy(struct wlr_screenshooter *screenshooter) { + if (!screenshooter) { + return; + } + wl_list_remove(&screenshooter->display_destroy.link); + struct wlr_screenshot *screenshot, *tmp; + wl_list_for_each_safe(screenshot, tmp, &screenshooter->screenshots, link) { + screenshot_destroy(screenshot); + } + wlr_signal_emit_safe(&screenshooter->events.destroy, screenshooter); + wl_global_destroy(screenshooter->global); + free(screenshooter); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_screenshooter *screenshooter = + wl_container_of(listener, screenshooter, display_destroy); + wlr_screenshooter_destroy(screenshooter); +} + +struct wlr_screenshooter *wlr_screenshooter_create(struct wl_display *display) { + struct wlr_screenshooter *screenshooter = + calloc(1, sizeof(struct wlr_screenshooter)); + if (!screenshooter) { + return NULL; + } + + wl_list_init(&screenshooter->screenshots); + wl_signal_init(&screenshooter->events.destroy); + + screenshooter->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &screenshooter->display_destroy); + + screenshooter->global = wl_global_create(display, + &orbital_screenshooter_interface, 1, screenshooter, screenshooter_bind); + if (screenshooter->global == NULL) { + free(screenshooter); + return NULL; + } + + return screenshooter; +} diff --git a/types/wlr_server_decoration.c b/types/wlr_server_decoration.c new file mode 100644 index 00000000..fd92fd04 --- /dev/null +++ b/types/wlr_server_decoration.c @@ -0,0 +1,215 @@ +#include <assert.h> +#include <stdlib.h> +#include <wlr/types/wlr_server_decoration.h> +#include <wlr/types/wlr_surface.h> +#include <wlr/util/log.h> +#include "server-decoration-protocol.h" +#include "util/signal.h" + +static const struct org_kde_kwin_server_decoration_interface + server_decoration_impl; + +static struct wlr_server_decoration *decoration_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &org_kde_kwin_server_decoration_interface, &server_decoration_impl)); + return wl_resource_get_user_data(resource); +} + +static void server_decoration_handle_release(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void server_decoration_handle_request_mode(struct wl_client *client, + struct wl_resource *resource, uint32_t mode) { + struct wlr_server_decoration *decoration = + decoration_from_resource(resource); + if (decoration->mode == mode) { + return; + } + decoration->mode = mode; + wlr_signal_emit_safe(&decoration->events.mode, decoration); + org_kde_kwin_server_decoration_send_mode(decoration->resource, + decoration->mode); +} + +static void server_decoration_destroy( + struct wlr_server_decoration *decoration) { + wlr_signal_emit_safe(&decoration->events.destroy, decoration); + wl_list_remove(&decoration->surface_destroy_listener.link); + wl_resource_set_user_data(decoration->resource, NULL); + wl_list_remove(&decoration->link); + free(decoration); +} + +static void server_decoration_destroy_resource(struct wl_resource *resource) { + struct wlr_server_decoration *decoration = + decoration_from_resource(resource); + if (decoration != NULL) { + server_decoration_destroy(decoration); + } +} + +static void server_decoration_handle_surface_destroy( + struct wl_listener *listener, void *data) { + struct wlr_server_decoration *decoration = + wl_container_of(listener, decoration, surface_destroy_listener); + server_decoration_destroy(decoration); +} + +static const struct org_kde_kwin_server_decoration_interface + server_decoration_impl = { + .release = server_decoration_handle_release, + .request_mode = server_decoration_handle_request_mode, +}; + +static const struct org_kde_kwin_server_decoration_manager_interface + server_decoration_manager_impl; + +static struct wlr_server_decoration_manager *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &org_kde_kwin_server_decoration_manager_interface, + &server_decoration_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void server_decoration_manager_handle_create(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_server_decoration_manager *manager = + manager_from_resource(manager_resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + struct wlr_server_decoration *decoration = + calloc(1, sizeof(struct wlr_server_decoration)); + if (decoration == NULL) { + wl_client_post_no_memory(client); + return; + } + decoration->surface = surface; + decoration->mode = manager->default_mode; + + int version = wl_resource_get_version(manager_resource); + decoration->resource = wl_resource_create(client, + &org_kde_kwin_server_decoration_interface, version, id); + if (decoration->resource == NULL) { + free(decoration); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(decoration->resource, + &server_decoration_impl, decoration, + server_decoration_destroy_resource); + + wlr_log(WLR_DEBUG, "new server_decoration %p (res %p)", decoration, + decoration->resource); + + wl_signal_init(&decoration->events.destroy); + wl_signal_init(&decoration->events.mode); + + wl_signal_add(&surface->events.destroy, + &decoration->surface_destroy_listener); + decoration->surface_destroy_listener.notify = + server_decoration_handle_surface_destroy; + + wl_list_insert(&manager->decorations, &decoration->link); + + org_kde_kwin_server_decoration_send_mode(decoration->resource, + decoration->mode); + + wlr_signal_emit_safe(&manager->events.new_decoration, decoration); +} + +static const struct org_kde_kwin_server_decoration_manager_interface + server_decoration_manager_impl = { + .create = server_decoration_manager_handle_create, +}; + +void wlr_server_decoration_manager_set_default_mode( + struct wlr_server_decoration_manager *manager, uint32_t default_mode) { + manager->default_mode = default_mode; + + struct wl_resource *resource; + wl_resource_for_each(resource, &manager->resources) { + org_kde_kwin_server_decoration_manager_send_default_mode(resource, + manager->default_mode); + } +} + +void server_decoration_manager_destroy_resource(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void server_decoration_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_server_decoration_manager *manager = data; + assert(client && manager); + + struct wl_resource *resource = wl_resource_create(client, + &org_kde_kwin_server_decoration_manager_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &server_decoration_manager_impl, + manager, server_decoration_manager_destroy_resource); + + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); + + org_kde_kwin_server_decoration_manager_send_default_mode(resource, + manager->default_mode); +} + +void wlr_server_decoration_manager_destroy( + struct wlr_server_decoration_manager *manager) { + if (manager == NULL) { + return; + } + struct wlr_server_decoration *decoration, *tmp_decoration; + wl_list_for_each_safe(decoration, tmp_decoration, &manager->decorations, + link) { + server_decoration_destroy(decoration); + } + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + struct wl_resource *resource, *tmp_resource; + wl_resource_for_each_safe(resource, tmp_resource, &manager->resources) { + server_decoration_manager_destroy_resource(resource); + } + wl_global_destroy(manager->global); + free(manager); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_server_decoration_manager *manager = + wl_container_of(listener, manager, display_destroy); + wlr_server_decoration_manager_destroy(manager); +} + +struct wlr_server_decoration_manager *wlr_server_decoration_manager_create( + struct wl_display *display) { + struct wlr_server_decoration_manager *manager = + calloc(1, sizeof(struct wlr_server_decoration_manager)); + if (manager == NULL) { + return NULL; + } + manager->global = wl_global_create(display, + &org_kde_kwin_server_decoration_manager_interface, 1, manager, + server_decoration_manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + manager->default_mode = ORG_KDE_KWIN_SERVER_DECORATION_MANAGER_MODE_NONE; + wl_list_init(&manager->resources); + wl_list_init(&manager->decorations); + wl_signal_init(&manager->events.new_decoration); + wl_signal_init(&manager->events.destroy); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/wlr_surface.c b/types/wlr_surface.c new file mode 100644 index 00000000..27176ef0 --- /dev/null +++ b/types/wlr_surface.c @@ -0,0 +1,1061 @@ +#include <assert.h> +#include <stdlib.h> +#include <wayland-server.h> +#include <wlr/render/interface.h> +#include <wlr/types/wlr_buffer.h> +#include <wlr/types/wlr_compositor.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/types/wlr_region.h> +#include <wlr/types/wlr_surface.h> +#include <wlr/types/wlr_output.h> +#include <wlr/util/log.h> +#include <wlr/util/region.h> +#include "util/signal.h" + +#define CALLBACK_VERSION 1 +#define SURFACE_VERSION 4 +#define SUBSURFACE_VERSION 1 + +static int min(int fst, int snd) { + if (fst < snd) { + return fst; + } else { + return snd; + } +} + +static int max(int fst, int snd) { + if (fst > snd) { + return fst; + } else { + return snd; + } +} + +static void surface_state_reset_buffer(struct wlr_surface_state *state) { + if (state->buffer_resource) { + wl_list_remove(&state->buffer_destroy.link); + state->buffer_resource = NULL; + } +} + +static void surface_handle_buffer_destroy(struct wl_listener *listener, + void *data) { + struct wlr_surface_state *state = + wl_container_of(listener, state, buffer_destroy); + surface_state_reset_buffer(state); +} + +static void surface_state_set_buffer(struct wlr_surface_state *state, + struct wl_resource *buffer_resource) { + surface_state_reset_buffer(state); + + state->buffer_resource = buffer_resource; + if (buffer_resource != NULL) { + wl_resource_add_destroy_listener(buffer_resource, + &state->buffer_destroy); + state->buffer_destroy.notify = surface_handle_buffer_destroy; + } +} + +static void surface_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void surface_attach(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *buffer, int32_t dx, int32_t dy) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + + surface->pending.committed |= WLR_SURFACE_STATE_BUFFER; + surface->pending.dx = dx; + surface->pending.dy = dy; + surface_state_set_buffer(&surface->pending, buffer); +} + +static void surface_damage(struct wl_client *client, + struct wl_resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + if (width < 0 || height < 0) { + return; + } + surface->pending.committed |= WLR_SURFACE_STATE_SURFACE_DAMAGE; + pixman_region32_union_rect(&surface->pending.surface_damage, + &surface->pending.surface_damage, + x, y, width, height); +} + +static void callback_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void surface_frame(struct wl_client *client, + struct wl_resource *resource, uint32_t callback) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + + struct wl_resource *callback_resource = wl_resource_create(client, + &wl_callback_interface, CALLBACK_VERSION, callback); + if (callback_resource == NULL) { + wl_resource_post_no_memory(resource); + return; + } + wl_resource_set_implementation(callback_resource, NULL, NULL, + callback_handle_resource_destroy); + + wl_list_insert(surface->pending.frame_callback_list.prev, + wl_resource_get_link(callback_resource)); + + surface->pending.committed |= WLR_SURFACE_STATE_FRAME_CALLBACK_LIST; +} + +static void surface_set_opaque_region(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *region_resource) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + surface->pending.committed |= WLR_SURFACE_STATE_OPAQUE_REGION; + if (region_resource) { + pixman_region32_t *region = wlr_region_from_resource(region_resource); + pixman_region32_copy(&surface->pending.opaque, region); + } else { + pixman_region32_clear(&surface->pending.opaque); + } +} + +static void surface_set_input_region(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *region_resource) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + surface->pending.committed |= WLR_SURFACE_STATE_INPUT_REGION; + if (region_resource) { + pixman_region32_t *region = wlr_region_from_resource(region_resource); + pixman_region32_copy(&surface->pending.input, region); + } else { + pixman_region32_fini(&surface->pending.input); + pixman_region32_init_rect(&surface->pending.input, + INT32_MIN, INT32_MIN, UINT32_MAX, UINT32_MAX); + } +} + +static void surface_state_finalize(struct wlr_surface *surface, + struct wlr_surface_state *state) { + if ((state->committed & WLR_SURFACE_STATE_BUFFER)) { + if (state->buffer_resource != NULL) { + wlr_buffer_get_resource_size(state->buffer_resource, + surface->renderer, &state->buffer_width, &state->buffer_height); + } else { + state->buffer_width = state->buffer_height = 0; + } + } + + int width = state->buffer_width / state->scale; + int height = state->buffer_height / state->scale; + if ((state->transform & WL_OUTPUT_TRANSFORM_90) != 0) { + int tmp = width; + width = height; + height = tmp; + } + state->width = width; + state->height = height; + + pixman_region32_intersect_rect(&state->surface_damage, + &state->surface_damage, 0, 0, state->width, state->height); + + pixman_region32_intersect_rect(&state->buffer_damage, + &state->buffer_damage, 0, 0, state->buffer_width, + state->buffer_height); +} + +static void surface_update_damage(pixman_region32_t *buffer_damage, + struct wlr_surface_state *current, struct wlr_surface_state *pending) { + pixman_region32_clear(buffer_damage); + + if (pending->width != current->width || + pending->height != current->height) { + // Damage the whole buffer on resize + pixman_region32_union_rect(buffer_damage, buffer_damage, 0, 0, + pending->buffer_width, pending->buffer_height); + } else { + // Copy over surface damage + buffer damage + pixman_region32_t surface_damage; + pixman_region32_init(&surface_damage); + + pixman_region32_copy(&surface_damage, &pending->surface_damage); + wlr_region_transform(&surface_damage, &surface_damage, + wlr_output_transform_invert(pending->transform), + pending->width, pending->height); + wlr_region_scale(&surface_damage, &surface_damage, pending->scale); + + pixman_region32_union(buffer_damage, + &pending->buffer_damage, &surface_damage); + + pixman_region32_fini(&surface_damage); + } +} + +static void surface_state_copy(struct wlr_surface_state *state, + struct wlr_surface_state *next) { + state->width = next->width; + state->height = next->height; + state->buffer_width = next->buffer_width; + state->buffer_height = next->buffer_height; + + if (next->committed & WLR_SURFACE_STATE_SCALE) { + state->scale = next->scale; + } + if (next->committed & WLR_SURFACE_STATE_TRANSFORM) { + state->transform = next->transform; + } + if (next->committed & WLR_SURFACE_STATE_BUFFER) { + state->dx = next->dx; + state->dy = next->dy; + } else { + state->dx = state->dy = 0; + } + if (next->committed & WLR_SURFACE_STATE_SURFACE_DAMAGE) { + pixman_region32_copy(&state->surface_damage, &next->surface_damage); + } else { + pixman_region32_clear(&state->surface_damage); + } + if (next->committed & WLR_SURFACE_STATE_BUFFER_DAMAGE) { + pixman_region32_copy(&state->buffer_damage, &next->buffer_damage); + } else { + pixman_region32_clear(&state->buffer_damage); + } + if (next->committed & WLR_SURFACE_STATE_OPAQUE_REGION) { + pixman_region32_copy(&state->opaque, &next->opaque); + } + if (next->committed & WLR_SURFACE_STATE_INPUT_REGION) { + pixman_region32_copy(&state->input, &next->input); + } + + state->committed |= next->committed; +} + +/** + * Append pending state to current state and clear pending state. + */ +static void surface_state_move(struct wlr_surface_state *state, + struct wlr_surface_state *next) { + surface_state_copy(state, next); + + if (next->committed & WLR_SURFACE_STATE_BUFFER) { + surface_state_set_buffer(state, next->buffer_resource); + surface_state_reset_buffer(next); + next->dx = next->dy = 0; + } + if (next->committed & WLR_SURFACE_STATE_SURFACE_DAMAGE) { + pixman_region32_clear(&next->surface_damage); + } + if (next->committed & WLR_SURFACE_STATE_BUFFER_DAMAGE) { + pixman_region32_clear(&next->buffer_damage); + } + if (next->committed & WLR_SURFACE_STATE_FRAME_CALLBACK_LIST) { + wl_list_insert_list(&state->frame_callback_list, + &next->frame_callback_list); + wl_list_init(&next->frame_callback_list); + } + + next->committed = 0; +} + +static void surface_damage_subsurfaces(struct wlr_subsurface *subsurface) { + // XXX: This is probably the wrong way to do it, because this damage should + // come from the client, but weston doesn't do it correctly either and it + // seems to work ok. See the comment on weston_surface_damage for more info + // about a better approach. + struct wlr_surface *surface = subsurface->surface; + pixman_region32_union_rect(&surface->buffer_damage, + &surface->buffer_damage, 0, 0, + surface->current.buffer_width, surface->current.buffer_height); + + subsurface->reordered = false; + + struct wlr_subsurface *child; + wl_list_for_each(child, &subsurface->surface->subsurfaces, parent_link) { + surface_damage_subsurfaces(child); + } +} + +static void surface_apply_damage(struct wlr_surface *surface) { + struct wl_resource *resource = surface->current.buffer_resource; + if (resource == NULL) { + // NULL commit + wlr_buffer_unref(surface->buffer); + surface->buffer = NULL; + return; + } + + if (surface->buffer != NULL && surface->buffer->released) { + struct wlr_buffer *updated_buffer = wlr_buffer_apply_damage( + surface->buffer, resource, &surface->buffer_damage); + if (updated_buffer != NULL) { + surface->buffer = updated_buffer; + return; + } + } + + wlr_buffer_unref(surface->buffer); + surface->buffer = NULL; + + struct wlr_buffer *buffer = wlr_buffer_create(surface->renderer, resource); + if (buffer == NULL) { + wlr_log(WLR_ERROR, "Failed to upload buffer"); + return; + } + + surface->buffer = buffer; +} + +static void surface_update_opaque_region(struct wlr_surface *surface) { + struct wlr_texture *texture = wlr_surface_get_texture(surface); + if (texture == NULL) { + pixman_region32_clear(&surface->opaque_region); + return; + } + + if (wlr_texture_is_opaque(texture)) { + pixman_region32_init_rect(&surface->opaque_region, + 0, 0, surface->current.width, surface->current.height); + return; + } + + pixman_region32_intersect_rect(&surface->opaque_region, + &surface->current.opaque, + 0, 0, surface->current.width, surface->current.height); +} + +static void surface_update_input_region(struct wlr_surface *surface) { + pixman_region32_intersect_rect(&surface->input_region, + &surface->current.input, + 0, 0, surface->current.width, surface->current.height); +} + +static void surface_commit_pending(struct wlr_surface *surface) { + surface_state_finalize(surface, &surface->pending); + + if (surface->role && surface->role->precommit) { + surface->role->precommit(surface); + } + + bool invalid_buffer = surface->pending.committed & WLR_SURFACE_STATE_BUFFER; + + surface->sx += surface->pending.dx; + surface->sy += surface->pending.dy; + surface_update_damage(&surface->buffer_damage, + &surface->current, &surface->pending); + + surface_state_copy(&surface->previous, &surface->current); + surface_state_move(&surface->current, &surface->pending); + + if (invalid_buffer) { + surface_apply_damage(surface); + } + surface_update_opaque_region(surface); + surface_update_input_region(surface); + + // commit subsurface order + struct wlr_subsurface *subsurface; + wl_list_for_each_reverse(subsurface, &surface->subsurface_pending_list, + parent_pending_link) { + wl_list_remove(&subsurface->parent_link); + wl_list_insert(&surface->subsurfaces, &subsurface->parent_link); + + if (subsurface->reordered) { + // TODO: damage all the subsurfaces + surface_damage_subsurfaces(subsurface); + } + } + + if (surface->role && surface->role->commit) { + surface->role->commit(surface); + } + + wlr_signal_emit_safe(&surface->events.commit, surface); +} + +static bool subsurface_is_synchronized(struct wlr_subsurface *subsurface) { + while (subsurface != NULL) { + if (subsurface->synchronized) { + return true; + } + + if (!subsurface->parent) { + return false; + } + + if (!wlr_surface_is_subsurface(subsurface->parent)) { + break; + } + subsurface = wlr_subsurface_from_wlr_surface(subsurface->parent); + } + + return false; +} + +/** + * Recursive function to commit the effectively synchronized children. + */ +static void subsurface_parent_commit(struct wlr_subsurface *subsurface, + bool synchronized) { + struct wlr_surface *surface = subsurface->surface; + if (synchronized || subsurface->synchronized) { + if (subsurface->has_cache) { + surface_state_move(&surface->pending, &subsurface->cached); + surface_commit_pending(surface); + subsurface->has_cache = false; + subsurface->cached.committed = 0; + } + + struct wlr_subsurface *subsurface; + wl_list_for_each(subsurface, &surface->subsurfaces, parent_link) { + subsurface_parent_commit(subsurface, true); + } + } +} + +static void subsurface_commit(struct wlr_subsurface *subsurface) { + struct wlr_surface *surface = subsurface->surface; + + if (subsurface_is_synchronized(subsurface)) { + surface_state_move(&subsurface->cached, &surface->pending); + subsurface->has_cache = true; + } else { + if (subsurface->has_cache) { + surface_state_move(&surface->pending, &subsurface->cached); + surface_commit_pending(surface); + subsurface->has_cache = false; + } else { + surface_commit_pending(surface); + } + } +} + +static void surface_commit(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + + struct wlr_subsurface *subsurface = wlr_surface_is_subsurface(surface) ? + wlr_subsurface_from_wlr_surface(surface) : NULL; + if (subsurface != NULL) { + subsurface_commit(subsurface); + } else { + surface_commit_pending(surface); + } + + wl_list_for_each(subsurface, &surface->subsurfaces, parent_link) { + subsurface_parent_commit(subsurface, false); + } +} + +static void surface_set_buffer_transform(struct wl_client *client, + struct wl_resource *resource, int transform) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + surface->pending.committed |= WLR_SURFACE_STATE_TRANSFORM; + surface->pending.transform = transform; +} + +static void surface_set_buffer_scale(struct wl_client *client, + struct wl_resource *resource, + int32_t scale) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + surface->pending.committed |= WLR_SURFACE_STATE_SCALE; + surface->pending.scale = scale; +} + +static void surface_damage_buffer(struct wl_client *client, + struct wl_resource *resource, + int32_t x, int32_t y, int32_t width, + int32_t height) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + if (width < 0 || height < 0) { + return; + } + surface->pending.committed |= WLR_SURFACE_STATE_BUFFER_DAMAGE; + pixman_region32_union_rect(&surface->pending.buffer_damage, + &surface->pending.buffer_damage, + x, y, width, height); +} + +static const struct wl_surface_interface surface_interface = { + .destroy = surface_destroy, + .attach = surface_attach, + .damage = surface_damage, + .frame = surface_frame, + .set_opaque_region = surface_set_opaque_region, + .set_input_region = surface_set_input_region, + .commit = surface_commit, + .set_buffer_transform = surface_set_buffer_transform, + .set_buffer_scale = surface_set_buffer_scale, + .damage_buffer = surface_damage_buffer +}; + +struct wlr_surface *wlr_surface_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_surface_interface, + &surface_interface)); + return wl_resource_get_user_data(resource); +} + +static void surface_state_init(struct wlr_surface_state *state) { + state->scale = 1; + state->transform = WL_OUTPUT_TRANSFORM_NORMAL; + + wl_list_init(&state->frame_callback_list); + + pixman_region32_init(&state->surface_damage); + pixman_region32_init(&state->buffer_damage); + pixman_region32_init(&state->opaque); + pixman_region32_init_rect(&state->input, + INT32_MIN, INT32_MIN, UINT32_MAX, UINT32_MAX); +} + +static void surface_state_finish(struct wlr_surface_state *state) { + surface_state_reset_buffer(state); + + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &state->frame_callback_list) { + wl_resource_destroy(resource); + } + + pixman_region32_fini(&state->surface_damage); + pixman_region32_fini(&state->buffer_damage); + pixman_region32_fini(&state->opaque); + pixman_region32_fini(&state->input); +} + +static void subsurface_destroy(struct wlr_subsurface *subsurface) { + if (subsurface == NULL) { + return; + } + + wlr_signal_emit_safe(&subsurface->events.destroy, subsurface); + + wl_list_remove(&subsurface->surface_destroy.link); + surface_state_finish(&subsurface->cached); + + if (subsurface->parent) { + wl_list_remove(&subsurface->parent_link); + wl_list_remove(&subsurface->parent_pending_link); + wl_list_remove(&subsurface->parent_destroy.link); + } + + wl_resource_set_user_data(subsurface->resource, NULL); + if (subsurface->surface) { + subsurface->surface->role_data = NULL; + } + free(subsurface); +} + +static void surface_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + + wlr_signal_emit_safe(&surface->events.destroy, surface); + + wl_list_remove(wl_resource_get_link(surface->resource)); + + wl_list_remove(&surface->renderer_destroy.link); + surface_state_finish(&surface->pending); + surface_state_finish(&surface->current); + surface_state_finish(&surface->previous); + pixman_region32_fini(&surface->buffer_damage); + pixman_region32_fini(&surface->opaque_region); + pixman_region32_fini(&surface->input_region); + wlr_buffer_unref(surface->buffer); + free(surface); +} + +static void surface_handle_renderer_destroy(struct wl_listener *listener, + void *data) { + struct wlr_surface *surface = + wl_container_of(listener, surface, renderer_destroy); + wl_resource_destroy(surface->resource); +} + +struct wlr_surface *wlr_surface_create(struct wl_client *client, + uint32_t version, uint32_t id, struct wlr_renderer *renderer, + struct wl_list *resource_list) { + assert(version <= SURFACE_VERSION); + + struct wlr_surface *surface = calloc(1, sizeof(struct wlr_surface)); + if (!surface) { + wl_client_post_no_memory(client); + return NULL; + } + surface->resource = wl_resource_create(client, &wl_surface_interface, + version, id); + if (surface->resource == NULL) { + free(surface); + wl_client_post_no_memory(client); + return NULL; + } + wl_resource_set_implementation(surface->resource, &surface_interface, + surface, surface_handle_resource_destroy); + + wlr_log(WLR_DEBUG, "New wlr_surface %p (res %p)", surface, surface->resource); + + surface->renderer = renderer; + + surface_state_init(&surface->current); + surface_state_init(&surface->pending); + surface_state_init(&surface->previous); + + wl_signal_init(&surface->events.commit); + wl_signal_init(&surface->events.destroy); + wl_signal_init(&surface->events.new_subsurface); + wl_list_init(&surface->subsurfaces); + wl_list_init(&surface->subsurface_pending_list); + pixman_region32_init(&surface->buffer_damage); + pixman_region32_init(&surface->opaque_region); + pixman_region32_init(&surface->input_region); + + wl_signal_add(&renderer->events.destroy, &surface->renderer_destroy); + surface->renderer_destroy.notify = surface_handle_renderer_destroy; + + struct wl_list *resource_link = wl_resource_get_link(surface->resource); + if (resource_list != NULL) { + wl_list_insert(resource_list, resource_link); + } else { + wl_list_init(resource_link); + } + + return surface; +} + +struct wlr_texture *wlr_surface_get_texture(struct wlr_surface *surface) { + if (surface->buffer == NULL) { + return NULL; + } + return surface->buffer->texture; +} + +bool wlr_surface_has_buffer(struct wlr_surface *surface) { + return wlr_surface_get_texture(surface) != NULL; +} + +bool wlr_surface_set_role(struct wlr_surface *surface, + const struct wlr_surface_role *role, void *role_data, + struct wl_resource *error_resource, uint32_t error_code) { + assert(role != NULL); + + if (surface->role != NULL && surface->role != role) { + if (error_resource != NULL) { + wl_resource_post_error(error_resource, error_code, + "Cannot assign role %s to wl_surface@%d, already has role %s\n", + role->name, wl_resource_get_id(surface->resource), + surface->role->name); + } + return false; + } + + assert(surface->role_data == NULL); + surface->role = role; + surface->role_data = role_data; + return true; +} + +static const struct wl_subsurface_interface subsurface_implementation; + +static struct wlr_subsurface *subsurface_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_subsurface_interface, + &subsurface_implementation)); + return wl_resource_get_user_data(resource); +} + +static void subsurface_resource_destroy(struct wl_resource *resource) { + struct wlr_subsurface *subsurface = subsurface_from_resource(resource); + wl_list_remove(wl_resource_get_link(resource)); + subsurface_destroy(subsurface); +} + +static void subsurface_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void subsurface_handle_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; + } + + subsurface->pending.x = x; + subsurface->pending.y = y; +} + +static struct wlr_subsurface *subsurface_find_sibling( + struct wlr_subsurface *subsurface, struct wlr_surface *surface) { + struct wlr_surface *parent = subsurface->parent; + + struct wlr_subsurface *sibling; + wl_list_for_each(sibling, &parent->subsurfaces, parent_link) { + if (sibling->surface == surface && sibling != subsurface) { + return sibling; + } + } + + return NULL; +} + +static void subsurface_handle_place_above(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *sibling_resource) { + struct wlr_subsurface *subsurface = subsurface_from_resource(resource); + if (subsurface == NULL) { + return; + } + + struct wlr_surface *sibling_surface = + wlr_surface_from_resource(sibling_resource); + struct wlr_subsurface *sibling = + subsurface_find_sibling(subsurface, sibling_surface); + + if (!sibling) { + wl_resource_post_error(subsurface->resource, + WL_SUBSURFACE_ERROR_BAD_SURFACE, + "%s: wl_surface@%d is not a parent or sibling", + "place_above", wl_resource_get_id(sibling_surface->resource)); + return; + } + + wl_list_remove(&subsurface->parent_pending_link); + wl_list_insert(&sibling->parent_pending_link, + &subsurface->parent_pending_link); + + subsurface->reordered = true; +} + +static void subsurface_handle_place_below(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *sibling_resource) { + struct wlr_subsurface *subsurface = subsurface_from_resource(resource); + if (subsurface == NULL) { + return; + } + + struct wlr_surface *sibling_surface = + wlr_surface_from_resource(sibling_resource); + struct wlr_subsurface *sibling = + subsurface_find_sibling(subsurface, sibling_surface); + + if (!sibling) { + wl_resource_post_error(subsurface->resource, + WL_SUBSURFACE_ERROR_BAD_SURFACE, + "%s: wl_surface@%d is not a parent or sibling", + "place_below", wl_resource_get_id(sibling_surface->resource)); + return; + } + + wl_list_remove(&subsurface->parent_pending_link); + wl_list_insert(sibling->parent_pending_link.prev, + &subsurface->parent_pending_link); + + subsurface->reordered = true; +} + +static void subsurface_handle_set_sync(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_subsurface *subsurface = subsurface_from_resource(resource); + if (subsurface == NULL) { + return; + } + + subsurface->synchronized = true; +} + +static void subsurface_handle_set_desync(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_subsurface *subsurface = subsurface_from_resource(resource); + if (subsurface == NULL) { + return; + } + + if (subsurface->synchronized) { + subsurface->synchronized = false; + + if (!subsurface_is_synchronized(subsurface)) { + // TODO: do a synchronized commit to flush the cache + subsurface_parent_commit(subsurface, true); + } + } +} + +static const struct wl_subsurface_interface subsurface_implementation = { + .destroy = subsurface_handle_destroy, + .set_position = subsurface_handle_set_position, + .place_above = subsurface_handle_place_above, + .place_below = subsurface_handle_place_below, + .set_sync = subsurface_handle_set_sync, + .set_desync = subsurface_handle_set_desync, +}; + +static void subsurface_role_commit(struct wlr_surface *surface) { + struct wlr_subsurface *subsurface = + wlr_subsurface_from_wlr_surface(surface); + if (subsurface == NULL) { + return; + } + + if (subsurface->current.x != subsurface->pending.x || + subsurface->current.y != subsurface->pending.y) { + // Subsurface has moved + int dx = subsurface->current.x - subsurface->pending.x; + int dy = subsurface->current.y - subsurface->pending.y; + + subsurface->current.x = subsurface->pending.x; + subsurface->current.y = subsurface->pending.y; + + if ((surface->current.transform & WL_OUTPUT_TRANSFORM_90) != 0) { + int tmp = dx; + dx = dy; + dy = tmp; + } + + pixman_region32_union_rect(&surface->buffer_damage, + &surface->buffer_damage, + dx * surface->previous.scale, dy * surface->previous.scale, + surface->previous.buffer_width, surface->previous.buffer_height); + pixman_region32_union_rect(&surface->buffer_damage, + &surface->buffer_damage, 0, 0, + surface->current.buffer_width, surface->current.buffer_height); + } +} + +const struct wlr_surface_role subsurface_role = { + .name = "wl_subsurface", + .commit = subsurface_role_commit, +}; + +static void subsurface_handle_parent_destroy(struct wl_listener *listener, + void *data) { + struct wlr_subsurface *subsurface = + wl_container_of(listener, subsurface, parent_destroy); + wl_list_remove(&subsurface->parent_link); + wl_list_remove(&subsurface->parent_pending_link); + wl_list_remove(&subsurface->parent_destroy.link); + subsurface->parent = NULL; +} + +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); +} + +struct wlr_subsurface *wlr_subsurface_create(struct wlr_surface *surface, + struct wlr_surface *parent, uint32_t version, uint32_t id, + struct wl_list *resource_list) { + assert(version <= SUBSURFACE_VERSION); + + struct wl_client *client = wl_resource_get_client(surface->resource); + + struct wlr_subsurface *subsurface = + calloc(1, sizeof(struct wlr_subsurface)); + if (!subsurface) { + wl_client_post_no_memory(client); + return NULL; + } + surface_state_init(&subsurface->cached); + subsurface->synchronized = true; + subsurface->surface = surface; + subsurface->resource = + wl_resource_create(client, &wl_subsurface_interface, version, id); + if (subsurface->resource == NULL) { + surface_state_finish(&subsurface->cached); + free(subsurface); + wl_client_post_no_memory(client); + return NULL; + } + wl_resource_set_implementation(subsurface->resource, + &subsurface_implementation, subsurface, + subsurface_resource_destroy); + + wl_signal_init(&subsurface->events.destroy); + + wl_signal_add(&surface->events.destroy, &subsurface->surface_destroy); + subsurface->surface_destroy.notify = subsurface_handle_surface_destroy; + + // link parent + subsurface->parent = parent; + wl_signal_add(&parent->events.destroy, &subsurface->parent_destroy); + subsurface->parent_destroy.notify = subsurface_handle_parent_destroy; + wl_list_insert(parent->subsurfaces.prev, &subsurface->parent_link); + wl_list_insert(parent->subsurface_pending_list.prev, + &subsurface->parent_pending_link); + + surface->role_data = subsurface; + + struct wl_list *resource_link = wl_resource_get_link(subsurface->resource); + if (resource_list != NULL) { + wl_list_insert(resource_list, resource_link); + } else { + wl_list_init(resource_link); + } + + wlr_signal_emit_safe(&parent->events.new_subsurface, subsurface); + + return subsurface; +} + + +struct wlr_surface *wlr_surface_get_root_surface(struct wlr_surface *surface) { + while (wlr_surface_is_subsurface(surface)) { + struct wlr_subsurface *subsurface = + wlr_subsurface_from_wlr_surface(surface); + if (subsurface == NULL) { + break; + } + surface = subsurface->parent; + } + return surface; +} + +bool wlr_surface_point_accepts_input(struct wlr_surface *surface, + double sx, double sy) { + return sx >= 0 && sx < surface->current.width && + sy >= 0 && sy < surface->current.height && + pixman_region32_contains_point(&surface->current.input, floor(sx), floor(sy), NULL); +} + +struct wlr_surface *wlr_surface_surface_at(struct wlr_surface *surface, + double sx, double sy, double *sub_x, double *sub_y) { + struct wlr_subsurface *subsurface; + wl_list_for_each_reverse(subsurface, &surface->subsurfaces, parent_link) { + double _sub_x = subsurface->current.x; + double _sub_y = subsurface->current.y; + struct wlr_surface *sub = wlr_surface_surface_at(subsurface->surface, + sx - _sub_x, sy - _sub_y, sub_x, sub_y); + if (sub != NULL) { + return sub; + } + } + + if (wlr_surface_point_accepts_input(surface, sx, sy)) { + *sub_x = sx; + *sub_y = sy; + return surface; + } + + return NULL; +} + +void wlr_surface_send_enter(struct wlr_surface *surface, + struct wlr_output *output) { + struct wl_client *client = wl_resource_get_client(surface->resource); + struct wl_resource *resource; + wl_resource_for_each(resource, &output->resources) { + if (client == wl_resource_get_client(resource)) { + wl_surface_send_enter(surface->resource, resource); + } + } +} + +void wlr_surface_send_leave(struct wlr_surface *surface, + struct wlr_output *output) { + struct wl_client *client = wl_resource_get_client(surface->resource); + struct wl_resource *resource; + wl_resource_for_each(resource, &output->resources) { + if (client == wl_resource_get_client(resource)) { + wl_surface_send_leave(surface->resource, resource); + } + } +} + +static inline int64_t timespec_to_msec(const struct timespec *a) { + return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000; +} + +void wlr_surface_send_frame_done(struct wlr_surface *surface, + const struct timespec *when) { + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, + &surface->current.frame_callback_list) { + wl_callback_send_done(resource, timespec_to_msec(when)); + wl_resource_destroy(resource); + } +} + +static void surface_for_each_surface(struct wlr_surface *surface, int x, int y, + wlr_surface_iterator_func_t iterator, void *user_data) { + iterator(surface, x, y, user_data); + + struct wlr_subsurface *subsurface; + wl_list_for_each(subsurface, &surface->subsurfaces, parent_link) { + struct wlr_subsurface_state *state = &subsurface->current; + int sx = state->x; + int sy = state->y; + + surface_for_each_surface(subsurface->surface, x + sx, y + sy, + iterator, user_data); + } +} + +void wlr_surface_for_each_surface(struct wlr_surface *surface, + wlr_surface_iterator_func_t iterator, void *user_data) { + surface_for_each_surface(surface, 0, 0, iterator, user_data); +} + +struct bound_acc { + int32_t min_x, min_y; + int32_t max_x, max_y; +}; + +static void handle_bounding_box_surface(struct wlr_surface *surface, + int x, int y, void *data) { + struct bound_acc *acc = data; + + acc->min_x = min(x, acc->min_x); + acc->min_y = min(y, acc->min_y); + + acc->max_x = max(x + surface->current.width, acc->max_x); + acc->max_y = max(y + surface->current.height, acc->max_y); +} + +void wlr_surface_get_extends(struct wlr_surface *surface, struct wlr_box *box) { + struct bound_acc acc = { + .min_x = 0, + .min_y = 0, + .max_x = surface->current.width, + .max_y = surface->current.height, + }; + + wlr_surface_for_each_surface(surface, handle_bounding_box_surface, &acc); + + box->x = acc.min_x; + box->y = acc.min_y; + box->width = acc.max_x - acc.min_x; + box->height = acc.max_y - acc.min_y; +} + +void wlr_surface_get_effective_damage(struct wlr_surface *surface, + pixman_region32_t *damage) { + pixman_region32_clear(damage); + + // Transform and copy the buffer damage in terms of surface coordinates. + wlr_region_transform(damage, &surface->buffer_damage, + surface->current.transform, surface->current.buffer_width, + surface->current.buffer_height); + wlr_region_scale(damage, damage, 1.0 / (float)surface->current.scale); + + // On resize, damage the previous bounds of the surface. The current bounds + // have already been damaged in surface_update_damage. + if (surface->previous.width > surface->current.width || + surface->previous.height > surface->current.height) { + pixman_region32_union_rect(damage, damage, 0, 0, + surface->previous.width, surface->previous.height); + } + + // On move, damage where the surface was with its old dimensions. + if (surface->current.dx != 0 || surface->current.dy != 0) { + int prev_x = -surface->current.dx; + int prev_y = -surface->current.dy; + if ((surface->previous.transform & WL_OUTPUT_TRANSFORM_90) != 0) { + int temp = prev_x; + prev_x = prev_y; + prev_y = temp; + } + pixman_region32_union_rect(damage, damage, prev_x, prev_y, + surface->previous.width, surface->previous.height); + } +} diff --git a/types/wlr_switch.c b/types/wlr_switch.c new file mode 100644 index 00000000..68cdde9e --- /dev/null +++ b/types/wlr_switch.c @@ -0,0 +1,22 @@ +#include <stdlib.h> +#include <string.h> +#include <wayland-server.h> +#include <wlr/interfaces/wlr_switch.h> +#include <wlr/types/wlr_switch.h> + +void wlr_switch_init(struct wlr_switch *lid_switch, + struct wlr_switch_impl *impl) { + lid_switch->impl = impl; + wl_signal_init(&lid_switch->events.toggle); +} + +void wlr_switch_destroy(struct wlr_switch *lid_switch) { + if (!lid_switch) { + return; + } + if (lid_switch->impl && lid_switch->impl->destroy) { + lid_switch->impl->destroy(lid_switch); + } else { + free(lid_switch); + } +} diff --git a/types/wlr_tablet_pad.c b/types/wlr_tablet_pad.c new file mode 100644 index 00000000..804d6910 --- /dev/null +++ b/types/wlr_tablet_pad.c @@ -0,0 +1,29 @@ +#include <stdlib.h> +#include <string.h> +#include <wayland-server.h> +#include <wlr/interfaces/wlr_tablet_pad.h> +#include <wlr/types/wlr_tablet_pad.h> + +void wlr_tablet_pad_init(struct wlr_tablet_pad *pad, + struct wlr_tablet_pad_impl *impl) { + pad->impl = impl; + wl_signal_init(&pad->events.button); + wl_signal_init(&pad->events.ring); + wl_signal_init(&pad->events.strip); + wl_signal_init(&pad->events.attach_tablet); +} + +void wlr_tablet_pad_destroy(struct wlr_tablet_pad *pad) { + if (!pad) { + return; + } + + wlr_list_for_each(&pad->paths, free); + wlr_list_finish(&pad->paths); + + if (pad->impl && pad->impl->destroy) { + pad->impl->destroy(pad); + } else { + free(pad); + } +} diff --git a/types/wlr_tablet_tool.c b/types/wlr_tablet_tool.c new file mode 100644 index 00000000..ca92d4e2 --- /dev/null +++ b/types/wlr_tablet_tool.c @@ -0,0 +1,29 @@ +#include <stdlib.h> +#include <string.h> +#include <wayland-server.h> +#include <wlr/interfaces/wlr_tablet_tool.h> +#include <wlr/types/wlr_tablet_tool.h> + +void wlr_tablet_init(struct wlr_tablet *tablet, + struct wlr_tablet_impl *impl) { + tablet->impl = impl; + wl_signal_init(&tablet->events.axis); + wl_signal_init(&tablet->events.proximity); + wl_signal_init(&tablet->events.tip); + wl_signal_init(&tablet->events.button); +} + +void wlr_tablet_destroy(struct wlr_tablet *tablet) { + if (!tablet) { + return; + } + + wlr_list_for_each(&tablet->paths, free); + wlr_list_finish(&tablet->paths); + + if (tablet->impl && tablet->impl->destroy) { + tablet->impl->destroy(tablet); + } else { + free(tablet); + } +} diff --git a/types/wlr_text_input_v3.c b/types/wlr_text_input_v3.c new file mode 100644 index 00000000..e8f7a613 --- /dev/null +++ b/types/wlr_text_input_v3.c @@ -0,0 +1,337 @@ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#include <assert.h> +#include <stdlib.h> +#include <stddef.h> +#include <wlr/types/wlr_text_input_v3.h> +#include <wlr/util/log.h> +#include "text-input-unstable-v3-protocol.h" +#include "util/signal.h" + +static void text_input_clear_focused_surface(struct wlr_text_input_v3 *text_input) { + wl_list_remove(&text_input->surface_destroy.link); + wl_list_init(&text_input->surface_destroy.link); + text_input->focused_surface = NULL; +} + +static const struct zwp_text_input_v3_interface text_input_impl; + +static struct wlr_text_input_v3 *text_input_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zwp_text_input_v3_interface, + &text_input_impl)); + return wl_resource_get_user_data(resource); +} + +void wlr_text_input_v3_send_enter(struct wlr_text_input_v3 *text_input, + struct wlr_surface *surface) { + assert(wl_resource_get_client(text_input->resource) + == wl_resource_get_client(surface->resource)); + text_input->focused_surface = surface; + wl_signal_add(&text_input->focused_surface->events.destroy, + &text_input->surface_destroy); + zwp_text_input_v3_send_enter(text_input->resource, + text_input->focused_surface->resource); +} + +void wlr_text_input_v3_send_leave(struct wlr_text_input_v3 *text_input) { + zwp_text_input_v3_send_leave(text_input->resource, + text_input->focused_surface->resource); + text_input_clear_focused_surface(text_input); +} + +void wlr_text_input_v3_send_preedit_string(struct wlr_text_input_v3 *text_input, + const char *text, uint32_t cursor_begin, uint32_t cursor_end) { + zwp_text_input_v3_send_preedit_string(text_input->resource, text, + cursor_begin, cursor_end); +} + +void wlr_text_input_v3_send_commit_string(struct wlr_text_input_v3 *text_input, + const char *text) { + zwp_text_input_v3_send_commit_string(text_input->resource, text); +} + +void wlr_text_input_v3_send_delete_surrounding_text( + struct wlr_text_input_v3 *text_input, uint32_t before_length, + uint32_t after_length) { + zwp_text_input_v3_send_delete_surrounding_text(text_input->resource, + before_length, after_length); +} + +void wlr_text_input_v3_send_done(struct wlr_text_input_v3 *text_input) { + zwp_text_input_v3_send_done(text_input->resource, + text_input->current_serial); +} + +static void wlr_text_input_destroy(struct wlr_text_input_v3 *text_input) { + wlr_signal_emit_safe(&text_input->events.destroy, text_input); + text_input_clear_focused_surface(text_input); + wl_list_remove(&text_input->seat_destroy.link); + // remove from manager::text_inputs + wl_list_remove(&text_input->link); + free(text_input->current.surrounding.text); + free(text_input->pending.surrounding.text); + free(text_input); +} + +static void text_input_resource_destroy(struct wl_resource *resource) { + struct wlr_text_input_v3 *text_input = text_input_from_resource(resource); + if (!text_input) { + return; + } + wlr_text_input_destroy(text_input); +} + +static void text_input_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void text_input_enable(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_text_input_v3 *text_input = text_input_from_resource(resource); + if (!text_input) { + return; + } + struct wlr_text_input_v3_state defaults = {0}; + free(text_input->pending.surrounding.text); + text_input->pending = defaults; + text_input->pending_enabled = true; +} + +static void text_input_disable(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_text_input_v3 *text_input = text_input_from_resource(resource); + if (!text_input) { + return; + } + text_input->pending_enabled = false; +} + +static void text_input_set_surrounding_text(struct wl_client *client, + struct wl_resource *resource, const char *text, int32_t cursor, + int32_t anchor) { + struct wlr_text_input_v3 *text_input = text_input_from_resource(resource); + if (!text_input) { + return; + } + free(text_input->pending.surrounding.text); + text_input->pending.surrounding.text = strdup(text); + if (!text_input->pending.surrounding.text) { + wl_client_post_no_memory(client); + } + + text_input->pending.surrounding.cursor = cursor; + text_input->pending.surrounding.anchor = anchor; +} + +static void text_input_set_text_change_cause(struct wl_client *client, + struct wl_resource *resource, uint32_t cause) { + struct wlr_text_input_v3 *text_input = text_input_from_resource(resource); + if (!text_input) { + return; + } + text_input->pending.text_change_cause = cause; +} + +static void text_input_set_content_type(struct wl_client *client, + struct wl_resource *resource, uint32_t hint, uint32_t purpose) { + struct wlr_text_input_v3 *text_input = text_input_from_resource(resource); + if (!text_input) { + return; + } + text_input->pending.content_type.hint = hint; + text_input->pending.content_type.purpose = purpose; +} + +static void text_input_set_cursor_rectangle(struct wl_client *client, + struct wl_resource *resource, int32_t x, int32_t y, int32_t width, + int32_t height) { + struct wlr_text_input_v3 *text_input = text_input_from_resource(resource); + if (!text_input) { + return; + } + text_input->pending.cursor_rectangle.x = x; + text_input->pending.cursor_rectangle.y = y; + text_input->pending.cursor_rectangle.width = width; + text_input->pending.cursor_rectangle.height = height; +} + +static void text_input_commit(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_text_input_v3 *text_input = text_input_from_resource(resource); + if (!text_input) { + return; + } + free(text_input->current.surrounding.text); + text_input->current = text_input->pending; + if (text_input->pending.surrounding.text) { + text_input->current.surrounding.text = + strdup(text_input->pending.surrounding.text); + } + + bool old_enabled = text_input->current_enabled; + text_input->current_enabled = text_input->pending_enabled; + text_input->current_serial++; + + if (text_input->focused_surface == NULL) { + wlr_log(WLR_DEBUG, "Text input commit received without focus"); + } + + if (!old_enabled && text_input->current_enabled) { + wlr_signal_emit_safe(&text_input->events.enable, text_input); + } else if (old_enabled && !text_input->current_enabled) { + wlr_signal_emit_safe(&text_input->events.disable, text_input); + } else { // including never enabled + wlr_signal_emit_safe(&text_input->events.commit, text_input); + } +} + +static const struct zwp_text_input_v3_interface text_input_impl = { + .destroy = text_input_destroy, + .enable = text_input_enable, + .disable = text_input_disable, + .set_surrounding_text = text_input_set_surrounding_text, + .set_text_change_cause = text_input_set_text_change_cause, + .set_content_type = text_input_set_content_type, + .set_cursor_rectangle = text_input_set_cursor_rectangle, + .commit = text_input_commit, +}; + +static const struct zwp_text_input_manager_v3_interface text_input_manager_impl; + +static struct wlr_text_input_manager_v3 *text_input_manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_text_input_manager_v3_interface, &text_input_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void text_input_manager_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void text_input_handle_seat_destroy(struct wl_listener *listener, + void *data) { + struct wlr_text_input_v3 *text_input = wl_container_of(listener, text_input, + seat_destroy); + struct wl_resource *resource = text_input->resource; + wlr_text_input_destroy(text_input); + wl_resource_set_user_data(resource, NULL); +} + +static void text_input_handle_focused_surface_destroy( + struct wl_listener *listener, void *data) { + struct wlr_text_input_v3 *text_input = wl_container_of(listener, text_input, + surface_destroy); + text_input_clear_focused_surface(text_input); +} + +static void text_input_manager_get_text_input(struct wl_client *client, + struct wl_resource *resource, uint32_t id, struct wl_resource *seat) { + struct wlr_text_input_v3 *text_input = + calloc(1, sizeof(struct wlr_text_input_v3)); + if (text_input == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_signal_init(&text_input->events.enable); + wl_signal_init(&text_input->events.commit); + wl_signal_init(&text_input->events.disable); + wl_signal_init(&text_input->events.destroy); + + int version = wl_resource_get_version(resource); + struct wl_resource *text_input_resource = wl_resource_create(client, + &zwp_text_input_v3_interface, version, id); + if (text_input_resource == NULL) { + free(text_input); + wl_client_post_no_memory(client); + return; + } + text_input->resource = text_input_resource; + + wl_resource_set_implementation(text_input->resource, &text_input_impl, + text_input, text_input_resource_destroy); + + struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat); + struct wlr_seat *wlr_seat = seat_client->seat; + text_input->seat = wlr_seat; + wl_signal_add(&seat_client->events.destroy, + &text_input->seat_destroy); + text_input->seat_destroy.notify = + text_input_handle_seat_destroy; + text_input->surface_destroy.notify = + text_input_handle_focused_surface_destroy; + wl_list_init(&text_input->surface_destroy.link); + + struct wlr_text_input_manager_v3 *manager = + text_input_manager_from_resource(resource); + wl_list_insert(&manager->text_inputs, &text_input->link); + + wlr_signal_emit_safe(&manager->events.text_input, text_input); +} + +static const struct zwp_text_input_manager_v3_interface + text_input_manager_impl = { + .destroy = text_input_manager_destroy, + .get_text_input = text_input_manager_get_text_input, +}; + +static void text_input_manager_unbind(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void text_input_manager_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_text_input_manager_v3 *manager = data; + assert(wl_client && manager); + + struct wl_resource *resource = wl_resource_create(wl_client, + &zwp_text_input_manager_v3_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_list_insert(&manager->bound_resources, wl_resource_get_link(resource)); + wl_resource_set_implementation(resource, &text_input_manager_impl, + manager, text_input_manager_unbind); +} + +struct wlr_text_input_manager_v3 *wlr_text_input_manager_v3_create( + struct wl_display *wl_display) { + struct wlr_text_input_manager_v3 *manager = + calloc(1, sizeof(struct wlr_text_input_manager_v3)); + wl_list_init(&manager->bound_resources); + wl_list_init(&manager->text_inputs); + wl_signal_init(&manager->events.text_input); + manager->global = wl_global_create(wl_display, + &zwp_text_input_manager_v3_interface, 1, manager, + text_input_manager_bind); + if (!manager->global) { + free(manager); + return NULL; + } + return manager; +} + +void wlr_text_input_manager_v3_destroy( + struct wlr_text_input_manager_v3 *manager) { + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + + struct wl_resource *resource, *resource_tmp; + wl_resource_for_each_safe(resource, resource_tmp, + &manager->bound_resources) { + wl_resource_destroy(resource); + } + struct wlr_text_input_v3 *text_input, *text_input_tmp; + wl_list_for_each_safe(text_input, text_input_tmp, &manager->text_inputs, + link) { + wl_resource_destroy(text_input->resource); + } + wl_global_destroy(manager->global); + free(manager); +} diff --git a/types/wlr_touch.c b/types/wlr_touch.c new file mode 100644 index 00000000..ba7ffac7 --- /dev/null +++ b/types/wlr_touch.c @@ -0,0 +1,22 @@ +#include <stdlib.h> +#include <string.h> +#include <wayland-server.h> +#include <wlr/interfaces/wlr_touch.h> +#include <wlr/types/wlr_touch.h> + +void wlr_touch_init(struct wlr_touch *touch, + struct wlr_touch_impl *impl) { + touch->impl = impl; + wl_signal_init(&touch->events.down); + wl_signal_init(&touch->events.up); + wl_signal_init(&touch->events.motion); + wl_signal_init(&touch->events.cancel); +} + +void wlr_touch_destroy(struct wlr_touch *touch) { + if (touch && touch->impl && touch->impl->destroy) { + touch->impl->destroy(touch); + } else { + free(touch); + } +} diff --git a/types/wlr_virtual_keyboard_v1.c b/types/wlr_virtual_keyboard_v1.c new file mode 100644 index 00000000..e5463295 --- /dev/null +++ b/types/wlr_virtual_keyboard_v1.c @@ -0,0 +1,248 @@ +#define _POSIX_C_SOURCE 199309L +#include <assert.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <wlr/types/wlr_seat.h> +#include <wlr/types/wlr_virtual_keyboard_v1.h> +#include <wlr/util/log.h> +#include <xkbcommon/xkbcommon.h> +#include "util/signal.h" +#include "virtual-keyboard-unstable-v1-protocol.h" + + +static void keyboard_led_update(struct wlr_keyboard *wlr_kb, uint32_t leds) { + // unsupported by virtual keyboard protocol +} + +static void keyboard_destroy(struct wlr_keyboard *wlr_kb) { + // safe to ignore - keyboard will be destroyed only iff associated virtual + // keyboard is torn down, no need to tear down the keyboard separately +} + +static const struct wlr_keyboard_impl keyboard_impl = { + .destroy = keyboard_destroy, + .led_update = keyboard_led_update +}; + +static void input_device_destroy(struct wlr_input_device *dev) { +} + +static const struct wlr_input_device_impl input_device_impl = { + .destroy = input_device_destroy +}; + +static const struct zwp_virtual_keyboard_v1_interface virtual_keyboard_impl; + +static struct wlr_virtual_keyboard_v1 *virtual_keyboard_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_virtual_keyboard_v1_interface, &virtual_keyboard_impl)); + return wl_resource_get_user_data(resource); +} + +static void virtual_keyboard_keymap(struct wl_client *client, + struct wl_resource *resource, uint32_t format, int32_t fd, + uint32_t size) { + struct wlr_virtual_keyboard_v1 *keyboard = + virtual_keyboard_from_resource(resource); + + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!context) { + goto context_fail; + } + void *data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (!data) { + goto fd_fail; + } + struct xkb_keymap *keymap = xkb_keymap_new_from_string(context, data, + XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(data, size); + if (!keymap) { + goto keymap_fail; + } + wlr_keyboard_set_keymap(keyboard->input_device.keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + return; +keymap_fail: +fd_fail: + xkb_context_unref(context); +context_fail: + wl_client_post_no_memory(client); +} + +static void virtual_keyboard_key(struct wl_client *client, + struct wl_resource *resource, uint32_t time, uint32_t key, + uint32_t state) { + struct wlr_virtual_keyboard_v1 *keyboard = + virtual_keyboard_from_resource(resource); + struct wlr_event_keyboard_key event = { + .time_msec = time, + .keycode = key, + .update_state = false, + .state = state, + }; + wlr_keyboard_notify_key(keyboard->input_device.keyboard, &event); +} + +static void virtual_keyboard_modifiers(struct wl_client *client, + struct wl_resource *resource, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { + struct wlr_virtual_keyboard_v1 *keyboard = + virtual_keyboard_from_resource(resource); + wlr_keyboard_notify_modifiers(keyboard->input_device.keyboard, + mods_depressed, mods_latched, mods_locked, group); +} + +static void virtual_keyboard_destroy_resource(struct wl_resource *resource) { + struct wlr_virtual_keyboard_v1 *keyboard = + virtual_keyboard_from_resource(resource); + wlr_signal_emit_safe(&keyboard->events.destroy, keyboard); + wl_list_remove(&keyboard->link); + wlr_input_device_destroy(&keyboard->input_device); + free(keyboard); +} + +static void virtual_keyboard_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zwp_virtual_keyboard_v1_interface virtual_keyboard_impl = { + .keymap = virtual_keyboard_keymap, + .key = virtual_keyboard_key, + .modifiers = virtual_keyboard_modifiers, + .destroy = virtual_keyboard_destroy, +}; + +static const struct zwp_virtual_keyboard_manager_v1_interface manager_impl; + +static struct wlr_virtual_keyboard_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zwp_virtual_keyboard_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void virtual_keyboard_manager_create_virtual_keyboard( + struct wl_client *client, struct wl_resource *resource, + struct wl_resource *seat, uint32_t id) { + struct wlr_virtual_keyboard_manager_v1 *manager = + manager_from_resource(resource); + + struct wlr_virtual_keyboard_v1 *virtual_keyboard = calloc(1, + sizeof(struct wlr_virtual_keyboard_v1)); + if (!virtual_keyboard) { + wl_client_post_no_memory(client); + return; + } + + struct wlr_keyboard* keyboard = calloc(1, sizeof(struct wlr_keyboard)); + if (!keyboard) { + wlr_log(WLR_ERROR, "Cannot allocate wlr_keyboard"); + free(virtual_keyboard); + wl_client_post_no_memory(client); + return; + } + wlr_keyboard_init(keyboard, &keyboard_impl); + + struct wl_resource *keyboard_resource = wl_resource_create(client, + &zwp_virtual_keyboard_v1_interface, wl_resource_get_version(resource), + id); + if (!keyboard_resource) { + free(keyboard); + free(virtual_keyboard); + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(keyboard_resource, &virtual_keyboard_impl, + virtual_keyboard, virtual_keyboard_destroy_resource); + + wlr_input_device_init(&virtual_keyboard->input_device, + WLR_INPUT_DEVICE_KEYBOARD, &input_device_impl, "virtual keyboard", + 0x0, 0x0); + + struct wlr_seat_client *seat_client = wlr_seat_client_from_resource(seat); + + virtual_keyboard->input_device.keyboard = keyboard; + virtual_keyboard->resource = keyboard_resource; + virtual_keyboard->seat = seat_client->seat; + wl_signal_init(&virtual_keyboard->events.destroy); + + wl_list_insert(&manager->virtual_keyboards, &virtual_keyboard->link); + + wlr_signal_emit_safe(&manager->events.new_virtual_keyboard, + virtual_keyboard); +} + +static const struct zwp_virtual_keyboard_manager_v1_interface manager_impl = { + .create_virtual_keyboard = virtual_keyboard_manager_create_virtual_keyboard, +}; + +static void handle_manager_unbind(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void virtual_keyboard_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_virtual_keyboard_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &zwp_virtual_keyboard_manager_v1_interface, version, id); + + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &manager_impl, manager, + handle_manager_unbind); + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_virtual_keyboard_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wlr_virtual_keyboard_manager_v1_destroy(manager); +} + +struct wlr_virtual_keyboard_manager_v1* + wlr_virtual_keyboard_manager_v1_create( + struct wl_display *display) { + struct wlr_virtual_keyboard_manager_v1 *manager = calloc(1, + sizeof(struct wlr_virtual_keyboard_manager_v1)); + if (!manager) { + return NULL; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + wl_list_init(&manager->resources); + wl_list_init(&manager->virtual_keyboards); + + wl_signal_init(&manager->events.new_virtual_keyboard); + wl_signal_init(&manager->events.destroy); + manager->global = wl_global_create(display, + &zwp_virtual_keyboard_manager_v1_interface, 1, manager, + virtual_keyboard_manager_bind); + return manager; +} + +void wlr_virtual_keyboard_manager_v1_destroy( + struct wlr_virtual_keyboard_manager_v1 *manager) { + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + struct wl_resource *resource, *resource_tmp; + wl_resource_for_each_safe(resource, resource_tmp, &manager->resources) { + wl_resource_destroy(resource); + } + struct wlr_virtual_keyboard_v1 *keyboard, *keyboard_tmp; + wl_list_for_each_safe(keyboard, keyboard_tmp, &manager->virtual_keyboards, + link) { + wl_resource_destroy(keyboard->resource); + } + free(manager); +} diff --git a/types/wlr_wl_shell.c b/types/wlr_wl_shell.c new file mode 100644 index 00000000..3543b287 --- /dev/null +++ b/types/wlr_wl_shell.c @@ -0,0 +1,731 @@ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#include <assert.h> +#include <stdlib.h> +#include <wayland-server-protocol.h> +#include <wayland-server.h> +#include <wlr/types/wlr_surface.h> +#include <wlr/types/wlr_wl_shell.h> +#include <wlr/util/log.h> +#include "util/signal.h" + +static const struct wlr_surface_role shell_surface_role; + +bool wlr_surface_is_wl_shell_surface(struct wlr_surface *surface) { + return surface->role == &shell_surface_role; +} + +struct wlr_wl_shell_surface *wlr_wl_shell_surface_from_wlr_surface( + struct wlr_surface *surface) { + assert(wlr_surface_is_wl_shell_surface(surface)); + return (struct wlr_wl_shell_surface *)surface->role_data; +} + +static void shell_pointer_grab_end(struct wlr_seat_pointer_grab *grab) { + struct wlr_wl_shell_popup_grab *popup_grab = grab->data; + + struct wlr_wl_shell_surface *popup, *tmp = NULL; + wl_list_for_each_safe(popup, tmp, &popup_grab->popups, grab_link) { + if (popup->popup_mapped) { + wl_shell_surface_send_popup_done(popup->resource); + popup->popup_mapped = false; + } + } + + if (grab->seat->pointer_state.grab == grab) { + wlr_seat_pointer_end_grab(grab->seat); + } +} + +static void shell_pointer_grab_maybe_end(struct wlr_seat_pointer_grab *grab) { + struct wlr_wl_shell_popup_grab *popup_grab = grab->data; + + if (grab->seat->pointer_state.grab != grab) { + return; + } + + bool has_mapped = false; + + struct wlr_wl_shell_surface *popup, *tmp = NULL; + wl_list_for_each_safe(popup, tmp, &popup_grab->popups, grab_link) { + if (popup->popup_mapped) { + has_mapped = true; + break; + } + } + + if (!has_mapped) { + shell_pointer_grab_end(grab); + } +} + +static void shell_pointer_grab_enter(struct wlr_seat_pointer_grab *grab, + struct wlr_surface *surface, double sx, double sy) { + struct wlr_wl_shell_popup_grab *popup_grab = grab->data; + if (wl_resource_get_client(surface->resource) == popup_grab->client) { + wlr_seat_pointer_enter(grab->seat, surface, sx, sy); + } else { + wlr_seat_pointer_clear_focus(grab->seat); + } +} + +static void shell_pointer_grab_motion(struct wlr_seat_pointer_grab *grab, + uint32_t time, double sx, double sy) { + wlr_seat_pointer_send_motion(grab->seat, time, sx, sy); +} + +static uint32_t shell_pointer_grab_button(struct wlr_seat_pointer_grab *grab, + uint32_t time, uint32_t button, uint32_t state) { + uint32_t serial = + wlr_seat_pointer_send_button(grab->seat, time, button, state); + if (serial) { + return serial; + } else { + shell_pointer_grab_end(grab); + return 0; + } +} + +static void shell_pointer_grab_cancel(struct wlr_seat_pointer_grab *grab) { + shell_pointer_grab_end(grab); +} + +static void shell_pointer_grab_axis(struct wlr_seat_pointer_grab *grab, + uint32_t time, enum wlr_axis_orientation orientation, double value, + int32_t value_discrete, enum wlr_axis_source source) { + wlr_seat_pointer_send_axis(grab->seat, time, orientation, value, + value_discrete, source); +} + +static const struct wlr_pointer_grab_interface shell_pointer_grab_impl = { + .enter = shell_pointer_grab_enter, + .motion = shell_pointer_grab_motion, + .button = shell_pointer_grab_button, + .cancel = shell_pointer_grab_cancel, + .axis = shell_pointer_grab_axis, +}; + +static const struct wl_shell_surface_interface shell_surface_impl; + +static struct wlr_wl_shell_surface *shell_surface_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_shell_surface_interface, + &shell_surface_impl)); + return wl_resource_get_user_data(resource); +} + +static void shell_surface_protocol_pong(struct wl_client *client, + struct wl_resource *resource, uint32_t serial) { + wlr_log(WLR_DEBUG, "got shell surface pong"); + struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource); + if (surface->ping_serial != serial) { + return; + } + + wl_event_source_timer_update(surface->ping_timer, 0); + surface->ping_serial = 0; +} + +static void shell_surface_protocol_move(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat_resource, + uint32_t serial) { + struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource); + struct wlr_seat_client *seat = wlr_seat_client_from_resource(seat_resource); + + if (!wlr_seat_validate_grab_serial(seat->seat, serial)) { + wlr_log(WLR_DEBUG, "invalid serial for grab"); + return; + } + + struct wlr_wl_shell_surface_move_event event = { + .surface = surface, + .seat = seat, + .serial = serial, + }; + + wlr_signal_emit_safe(&surface->events.request_move, &event); +} + +static struct wlr_wl_shell_popup_grab *shell_popup_grab_from_seat( + struct wlr_wl_shell *shell, struct wlr_seat *seat) { + struct wlr_wl_shell_popup_grab *shell_grab; + wl_list_for_each(shell_grab, &shell->popup_grabs, link) { + if (shell_grab->seat == seat) { + return shell_grab; + } + } + + shell_grab = calloc(1, sizeof(struct wlr_wl_shell_popup_grab)); + if (!shell_grab) { + return NULL; + } + + shell_grab->pointer_grab.data = shell_grab; + shell_grab->pointer_grab.interface = &shell_pointer_grab_impl; + + wl_list_init(&shell_grab->popups); + + wl_list_insert(&shell->popup_grabs, &shell_grab->link); + shell_grab->seat = seat; + + return shell_grab; +} + +static void shell_surface_destroy_popup_state( + struct wlr_wl_shell_surface *surface) { + if (surface->popup_state) { + wl_list_remove(&surface->grab_link); + struct wlr_wl_shell_popup_grab *grab = + shell_popup_grab_from_seat(surface->shell, + surface->popup_state->seat); + if (wl_list_empty(&grab->popups)) { + if (grab->seat->pointer_state.grab == &grab->pointer_grab) { + wlr_seat_pointer_end_grab(grab->seat); + } + } + free(surface->popup_state); + surface->popup_state = NULL; + } +} + +static void shell_surface_protocol_resize(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat_resource, + uint32_t serial, enum wl_shell_surface_resize edges) { + struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource); + struct wlr_seat_client *seat = wlr_seat_client_from_resource(seat_resource); + + if (!wlr_seat_validate_grab_serial(seat->seat, serial)) { + wlr_log(WLR_DEBUG, "invalid serial for grab"); + return; + } + + struct wlr_wl_shell_surface_resize_event event = { + .surface = surface, + .seat = seat, + .serial = serial, + .edges = edges, + }; + + wlr_signal_emit_safe(&surface->events.request_resize, &event); +} + +static void shell_surface_set_state(struct wlr_wl_shell_surface *surface, + enum wlr_wl_shell_surface_state state, + struct wlr_wl_shell_surface_transient_state *transient_state, + struct wlr_wl_shell_surface_popup_state *popup_state) { + surface->state = state; + free(surface->transient_state); + surface->transient_state = transient_state; + shell_surface_destroy_popup_state(surface); + surface->popup_state = popup_state; + + wlr_signal_emit_safe(&surface->events.set_state, surface); +} + +static void shell_surface_protocol_set_toplevel(struct wl_client *client, + struct wl_resource *resource) { + wlr_log(WLR_DEBUG, "got shell surface toplevel"); + struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource); + shell_surface_set_state(surface, WLR_WL_SHELL_SURFACE_STATE_TOPLEVEL, NULL, + NULL); +} + +static void shell_surface_popup_set_parent(struct wlr_wl_shell_surface *surface, + struct wlr_wl_shell_surface *parent) { + assert(surface && surface->state == WLR_WL_SHELL_SURFACE_STATE_POPUP); + if (surface->parent == parent) { + return; + } + surface->parent = parent; + if (parent) { + wl_list_remove(&surface->popup_link); + wl_list_insert(&parent->popups, &surface->popup_link); + wlr_signal_emit_safe(&parent->events.new_popup, surface); + } +} + +static struct wlr_wl_shell_surface *shell_find_shell_surface( + struct wlr_wl_shell *shell, struct wlr_surface *surface) { + if (surface) { + struct wlr_wl_shell_surface *wl_surface; + wl_list_for_each(wl_surface, &shell->surfaces, link) { + if (wl_surface->surface == surface) { + return wl_surface; + } + } + } + return NULL; +} + +static void shell_surface_protocol_set_transient(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *parent_resource, + int32_t x, int32_t y, enum wl_shell_surface_transient flags) { + wlr_log(WLR_DEBUG, "got shell surface transient"); + struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource); + struct wlr_surface *parent = wlr_surface_from_resource(parent_resource); + // TODO: check if parent_resource == NULL? + + struct wlr_wl_shell_surface *wl_parent = + shell_find_shell_surface(surface->shell, parent); + + if (!wl_parent) { + return; + } + + struct wlr_wl_shell_surface_transient_state *transient_state = + calloc(1, sizeof(struct wlr_wl_shell_surface_transient_state)); + if (transient_state == NULL) { + wl_client_post_no_memory(client); + return; + } + + surface->parent = wl_parent; + transient_state->x = x; + transient_state->y = y; + transient_state->flags = flags; + + shell_surface_set_state(surface, WLR_WL_SHELL_SURFACE_STATE_TRANSIENT, + transient_state, NULL); +} + +static void shell_surface_protocol_set_fullscreen(struct wl_client *client, + struct wl_resource *resource, + enum wl_shell_surface_fullscreen_method method, uint32_t framerate, + struct wl_resource *output_resource) { + struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource); + struct wlr_output *output = NULL; + if (output_resource != NULL) { + output = wlr_output_from_resource(output_resource); + } + + shell_surface_set_state(surface, WLR_WL_SHELL_SURFACE_STATE_FULLSCREEN, + NULL, NULL); + + struct wlr_wl_shell_surface_set_fullscreen_event event = { + .surface = surface, + .method = method, + .framerate = framerate, + .output = output, + }; + + wlr_signal_emit_safe(&surface->events.request_fullscreen, &event); +} + +static void shell_surface_protocol_set_popup(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat_resource, + uint32_t serial, struct wl_resource *parent_resource, int32_t x, + int32_t y, enum wl_shell_surface_transient flags) { + struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource); + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + struct wlr_surface *parent = wlr_surface_from_resource(parent_resource); + struct wlr_wl_shell_popup_grab *grab = + shell_popup_grab_from_seat(surface->shell, seat_client->seat); + if (!grab) { + wl_client_post_no_memory(client); + return; + } + + struct wlr_wl_shell_surface *wl_parent = + shell_find_shell_surface(surface->shell, parent); + + if (surface->state == WLR_WL_SHELL_SURFACE_STATE_POPUP) { + surface->transient_state->x = x; + surface->transient_state->y = y; + shell_surface_popup_set_parent(surface, wl_parent); + grab->client = surface->client; + surface->popup_mapped = true; + wlr_seat_pointer_start_grab(seat_client->seat, &grab->pointer_grab); + return; + } + + struct wlr_wl_shell_surface_transient_state *transient_state = + calloc(1, sizeof(struct wlr_wl_shell_surface_transient_state)); + if (transient_state == NULL) { + wl_client_post_no_memory(client); + return; + } + transient_state->x = x; + transient_state->y = y; + transient_state->flags = flags; + + struct wlr_wl_shell_surface_popup_state *popup_state = + calloc(1, sizeof(struct wlr_wl_shell_surface_popup_state)); + if (popup_state == NULL) { + free(transient_state); + wl_client_post_no_memory(client); + return; + } + popup_state->seat = seat_client->seat; + popup_state->serial = serial; + + shell_surface_set_state(surface, WLR_WL_SHELL_SURFACE_STATE_POPUP, + transient_state, popup_state); + + shell_surface_popup_set_parent(surface, wl_parent); + grab->client = surface->client; + wl_list_insert(&grab->popups, &surface->grab_link); + surface->popup_mapped = true; + wlr_seat_pointer_start_grab(seat_client->seat, &grab->pointer_grab); +} + +static void shell_surface_protocol_set_maximized(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *output_resource) { + struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource); + struct wlr_output *output = NULL; + if (output_resource != NULL) { + output = wlr_output_from_resource(output_resource); + } + + shell_surface_set_state(surface, WLR_WL_SHELL_SURFACE_STATE_MAXIMIZED, + NULL, NULL); + + struct wlr_wl_shell_surface_maximize_event event = { + .surface = surface, + .output = output, + }; + + wlr_signal_emit_safe(&surface->events.request_maximize, &event); +} + +static void shell_surface_protocol_set_title(struct wl_client *client, + struct wl_resource *resource, const char *title) { + wlr_log(WLR_DEBUG, "new shell surface title: %s", title); + struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource); + + char *tmp = strdup(title); + if (tmp == NULL) { + return; + } + + free(surface->title); + surface->title = tmp; + + wlr_signal_emit_safe(&surface->events.set_title, surface); +} + +static void shell_surface_protocol_set_class(struct wl_client *client, + struct wl_resource *resource, const char *class) { + wlr_log(WLR_DEBUG, "new shell surface class: %s", class); + struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource); + + char *tmp = strdup(class); + if (tmp == NULL) { + return; + } + + free(surface->class); + surface->class = tmp; + + wlr_signal_emit_safe(&surface->events.set_class, surface); +} + +static const struct wl_shell_surface_interface shell_surface_impl = { + .pong = shell_surface_protocol_pong, + .move = shell_surface_protocol_move, + .resize = shell_surface_protocol_resize, + .set_toplevel = shell_surface_protocol_set_toplevel, + .set_transient = shell_surface_protocol_set_transient, + .set_fullscreen = shell_surface_protocol_set_fullscreen, + .set_popup = shell_surface_protocol_set_popup, + .set_maximized = shell_surface_protocol_set_maximized, + .set_title = shell_surface_protocol_set_title, + .set_class = shell_surface_protocol_set_class, +}; + +static void shell_surface_destroy(struct wlr_wl_shell_surface *surface) { + wlr_signal_emit_safe(&surface->events.destroy, surface); + shell_surface_destroy_popup_state(surface); + wl_resource_set_user_data(surface->resource, NULL); + + struct wlr_wl_shell_surface *child; + wl_list_for_each(child, &surface->popups, popup_link) { + shell_surface_popup_set_parent(child, NULL); + } + wl_list_remove(&surface->popup_link); + + surface->surface->role_data = NULL; + wl_list_remove(&surface->link); + wl_list_remove(&surface->surface_destroy.link); + wl_event_source_remove(surface->ping_timer); + free(surface->transient_state); + free(surface->title); + free(surface->class); + free(surface); +} + +static void shell_surface_resource_destroy(struct wl_resource *resource) { + struct wlr_wl_shell_surface *surface = shell_surface_from_resource(resource); + if (surface != NULL) { + shell_surface_destroy(surface); + } +} + +static void shell_surface_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_wl_shell_surface *surface = + wl_container_of(listener, surface, surface_destroy); + shell_surface_destroy(surface); +} + +static void shell_surface_role_commit(struct wlr_surface *wlr_surface) { + struct wlr_wl_shell_surface *surface = + wlr_wl_shell_surface_from_wlr_surface(wlr_surface); + if (surface == NULL) { + return; + } + + if (!surface->configured && + wlr_surface_has_buffer(surface->surface) && + surface->state != WLR_WL_SHELL_SURFACE_STATE_NONE) { + surface->configured = true; + wlr_signal_emit_safe(&surface->shell->events.new_surface, surface); + } + + if (surface->popup_mapped && + surface->state == WLR_WL_SHELL_SURFACE_STATE_POPUP && + !wlr_surface_has_buffer(surface->surface)) { + surface->popup_mapped = false; + struct wlr_wl_shell_popup_grab *grab = + shell_popup_grab_from_seat(surface->shell, + surface->popup_state->seat); + shell_pointer_grab_maybe_end(&grab->pointer_grab); + } +} + +static const struct wlr_surface_role shell_surface_role = { + .name = "wl_shell_surface", + .commit = shell_surface_role_commit, +}; + +static int shell_surface_ping_timeout(void *user_data) { + struct wlr_wl_shell_surface *surface = user_data; + wlr_signal_emit_safe(&surface->events.ping_timeout, surface); + + surface->ping_serial = 0; + return 1; +} + +static const struct wl_shell_interface shell_impl; + +static struct wlr_wl_shell *shell_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wl_shell_interface, &shell_impl)); + return wl_resource_get_user_data(resource); +} + +static void shell_protocol_get_shell_surface(struct wl_client *client, + struct wl_resource *shell_resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + struct wlr_wl_shell *wl_shell = shell_from_resource(shell_resource); + struct wlr_wl_shell_surface *wl_surface = + calloc(1, sizeof(struct wlr_wl_shell_surface)); + if (wl_surface == NULL) { + wl_resource_post_no_memory(shell_resource); + return; + } + + if (!wlr_surface_set_role(surface, &shell_surface_role, wl_surface, + shell_resource, WL_SHELL_ERROR_ROLE)) { + free(wl_surface); + return; + } + + wl_list_init(&wl_surface->grab_link); + wl_list_init(&wl_surface->popup_link); + wl_list_init(&wl_surface->popups); + + wl_surface->shell = wl_shell; + wl_surface->client = client; + wl_surface->surface = surface; + + wl_surface->resource = wl_resource_create(client, + &wl_shell_surface_interface, wl_resource_get_version(shell_resource), + id); + if (wl_surface->resource == NULL) { + free(wl_surface); + wl_resource_post_no_memory(shell_resource); + return; + } + wl_resource_set_implementation(wl_surface->resource, + &shell_surface_impl, wl_surface, + shell_surface_resource_destroy); + + wlr_log(WLR_DEBUG, "new wl_shell %p (res %p)", wl_surface, + wl_surface->resource); + + wl_signal_init(&wl_surface->events.destroy); + wl_signal_init(&wl_surface->events.ping_timeout); + wl_signal_init(&wl_surface->events.new_popup); + wl_signal_init(&wl_surface->events.request_move); + wl_signal_init(&wl_surface->events.request_resize); + wl_signal_init(&wl_surface->events.request_fullscreen); + wl_signal_init(&wl_surface->events.request_maximize); + wl_signal_init(&wl_surface->events.set_state); + wl_signal_init(&wl_surface->events.set_title); + wl_signal_init(&wl_surface->events.set_class); + + wl_signal_add(&wl_surface->surface->events.destroy, + &wl_surface->surface_destroy); + wl_surface->surface_destroy.notify = shell_surface_handle_surface_destroy; + + struct wl_display *display = wl_client_get_display(client); + struct wl_event_loop *loop = wl_display_get_event_loop(display); + wl_surface->ping_timer = wl_event_loop_add_timer(loop, + shell_surface_ping_timeout, wl_surface); + if (wl_surface->ping_timer == NULL) { + wl_client_post_no_memory(client); + } + + wl_list_insert(&wl_shell->surfaces, &wl_surface->link); +} + +static const struct wl_shell_interface shell_impl = { + .get_shell_surface = shell_protocol_get_shell_surface +}; + +static void shell_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void shell_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_wl_shell *wl_shell = data; + assert(wl_client && wl_shell); + + struct wl_resource *wl_resource = wl_resource_create(wl_client, + &wl_shell_interface, version, id); + if (wl_resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(wl_resource, &shell_impl, wl_shell, + shell_destroy); + wl_list_insert(&wl_shell->resources, wl_resource_get_link(wl_resource)); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_wl_shell *wl_shell = + wl_container_of(listener, wl_shell, display_destroy); + wlr_wl_shell_destroy(wl_shell); +} + +struct wlr_wl_shell *wlr_wl_shell_create(struct wl_display *display) { + struct wlr_wl_shell *wl_shell = calloc(1, sizeof(struct wlr_wl_shell)); + if (!wl_shell) { + return NULL; + } + wl_shell->ping_timeout = 10000; + struct wl_global *global = wl_global_create(display, &wl_shell_interface, + 1, wl_shell, shell_bind); + if (!global) { + free(wl_shell); + return NULL; + } + wl_shell->global = global; + wl_list_init(&wl_shell->resources); + wl_list_init(&wl_shell->surfaces); + wl_list_init(&wl_shell->popup_grabs); + wl_signal_init(&wl_shell->events.new_surface); + + wl_shell->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &wl_shell->display_destroy); + + return wl_shell; +} + +void wlr_wl_shell_destroy(struct wlr_wl_shell *wlr_wl_shell) { + if (!wlr_wl_shell) { + return; + } + wl_list_remove(&wlr_wl_shell->display_destroy.link); + struct wl_resource *resource = NULL, *temp = NULL; + wl_resource_for_each_safe(resource, temp, &wlr_wl_shell->resources) { + // shell_destroy will remove the resource from the list + wl_resource_destroy(resource); + } + // TODO: destroy surfaces + wl_global_destroy(wlr_wl_shell->global); + free(wlr_wl_shell); +} + +void wlr_wl_shell_surface_ping(struct wlr_wl_shell_surface *surface) { + if (surface->ping_serial != 0) { + // already pinged + return; + } + + surface->ping_serial = + wl_display_next_serial(wl_client_get_display(surface->client)); + wl_event_source_timer_update(surface->ping_timer, + surface->shell->ping_timeout); + wl_shell_surface_send_ping(surface->resource, surface->ping_serial); +} + +void wlr_wl_shell_surface_configure(struct wlr_wl_shell_surface *surface, + uint32_t edges, int32_t width, int32_t height) { + wl_shell_surface_send_configure(surface->resource, edges, width, height); +} + +struct wlr_surface *wlr_wl_shell_surface_surface_at( + struct wlr_wl_shell_surface *surface, double sx, double sy, + double *sub_sx, double *sub_sy) { + struct wlr_wl_shell_surface *popup; + wl_list_for_each(popup, &surface->popups, popup_link) { + if (!popup->popup_mapped) { + continue; + } + + double popup_sx = popup->transient_state->x; + double popup_sy = popup->transient_state->y; + struct wlr_surface *sub = wlr_wl_shell_surface_surface_at(popup, + sx - popup_sx, sy - popup_sy, sub_sx, sub_sy); + if (sub != NULL) { + return sub; + } + } + + return wlr_surface_surface_at(surface->surface, sx, sy, sub_sx, sub_sy); +} + +struct wl_shell_surface_iterator_data { + wlr_surface_iterator_func_t user_iterator; + void *user_data; + int x, y; +}; + +static void wl_shell_surface_iterator(struct wlr_surface *surface, + int sx, int sy, void *data) { + struct wl_shell_surface_iterator_data *iter_data = data; + iter_data->user_iterator(surface, iter_data->x + sx, iter_data->y + sy, + iter_data->user_data); +} + +static void wl_shell_surface_for_each_surface( + struct wlr_wl_shell_surface *surface, int x, int y, + wlr_surface_iterator_func_t iterator, void *user_data) { + struct wl_shell_surface_iterator_data data = { + .user_iterator = iterator, + .user_data = user_data, + .x = x, .y = y, + }; + wlr_surface_for_each_surface(surface->surface, wl_shell_surface_iterator, + &data); + + struct wlr_wl_shell_surface *popup; + wl_list_for_each(popup, &surface->popups, popup_link) { + double popup_x = popup->transient_state->x; + double popup_y = popup->transient_state->y; + + wl_shell_surface_for_each_surface(popup, x + popup_x, y + popup_y, + iterator, user_data); + } +} + +void wlr_wl_shell_surface_for_each_surface(struct wlr_wl_shell_surface *surface, + wlr_surface_iterator_func_t iterator, void *user_data) { + wl_shell_surface_for_each_surface(surface, 0, 0, iterator, user_data); +} diff --git a/types/wlr_xcursor_manager.c b/types/wlr_xcursor_manager.c new file mode 100644 index 00000000..7333a7a1 --- /dev/null +++ b/types/wlr_xcursor_manager.c @@ -0,0 +1,84 @@ +#define _POSIX_C_SOURCE 200809L +#include <stdlib.h> +#include <string.h> +#include <wlr/types/wlr_xcursor_manager.h> + +struct wlr_xcursor_manager *wlr_xcursor_manager_create(const char *name, + uint32_t size) { + struct wlr_xcursor_manager *manager = + calloc(1, sizeof(struct wlr_xcursor_manager)); + if (manager == NULL) { + return NULL; + } + if (name != NULL) { + manager->name = strdup(name); + } + manager->size = size; + wl_list_init(&manager->scaled_themes); + return manager; +} + +void wlr_xcursor_manager_destroy(struct wlr_xcursor_manager *manager) { + if (manager == NULL) { + return; + } + struct wlr_xcursor_manager_theme *theme, *tmp; + wl_list_for_each_safe(theme, tmp, &manager->scaled_themes, link) { + wl_list_remove(&theme->link); + wlr_xcursor_theme_destroy(theme->theme); + free(theme); + } + free(manager->name); + free(manager); +} + +int wlr_xcursor_manager_load(struct wlr_xcursor_manager *manager, + float scale) { + struct wlr_xcursor_manager_theme *theme; + wl_list_for_each(theme, &manager->scaled_themes, link) { + if (theme->scale == scale) { + return 0; + } + } + + theme = calloc(1, sizeof(struct wlr_xcursor_manager_theme)); + if (theme == NULL) { + return 1; + } + theme->scale = scale; + theme->theme = wlr_xcursor_theme_load(manager->name, manager->size * scale); + if (theme->theme == NULL) { + free(theme); + return 1; + } + wl_list_insert(&manager->scaled_themes, &theme->link); + return 0; +} + +struct wlr_xcursor *wlr_xcursor_manager_get_xcursor( + struct wlr_xcursor_manager *manager, const char *name, float scale) { + struct wlr_xcursor_manager_theme *theme; + wl_list_for_each(theme, &manager->scaled_themes, link) { + if (theme->scale == scale) { + return wlr_xcursor_theme_get_cursor(theme->theme, name); + } + } + return NULL; +} + +void wlr_xcursor_manager_set_cursor_image(struct wlr_xcursor_manager *manager, + const char *name, struct wlr_cursor *cursor) { + struct wlr_xcursor_manager_theme *theme; + wl_list_for_each(theme, &manager->scaled_themes, link) { + struct wlr_xcursor *xcursor = + wlr_xcursor_theme_get_cursor(theme->theme, name); + if (xcursor == NULL) { + continue; + } + + struct wlr_xcursor_image *image = xcursor->images[0]; + wlr_cursor_set_image(cursor, image->buffer, image->width * 4, + image->width, image->height, image->hotspot_x, image->hotspot_y, + theme->scale); + } +} diff --git a/types/wlr_xdg_decoration_v1.c b/types/wlr_xdg_decoration_v1.c new file mode 100644 index 00000000..607b75e4 --- /dev/null +++ b/types/wlr_xdg_decoration_v1.c @@ -0,0 +1,310 @@ +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <wlr/types/wlr_xdg_decoration_v1.h> +#include <wlr/util/log.h> +#include "util/signal.h" +#include "xdg-decoration-unstable-v1-protocol.h" + +#define DECORATION_MANAGER_VERSION 1 + +static const struct zxdg_toplevel_decoration_v1_interface + toplevel_decoration_impl; + +static struct wlr_xdg_toplevel_decoration_v1 *toplevel_decoration_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zxdg_toplevel_decoration_v1_interface, &toplevel_decoration_impl)); + return wl_resource_get_user_data(resource); +} + +static void toplevel_decoration_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void toplevel_decoration_handle_set_mode(struct wl_client *client, + struct wl_resource *resource, + enum zxdg_toplevel_decoration_v1_mode mode) { + struct wlr_xdg_toplevel_decoration_v1 *decoration = + toplevel_decoration_from_resource(resource); + + decoration->client_pending_mode = + (enum wlr_xdg_toplevel_decoration_v1_mode)mode; + wlr_signal_emit_safe(&decoration->events.request_mode, decoration); +} + +static void toplevel_decoration_handle_unset_mode(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_toplevel_decoration_v1 *decoration = + toplevel_decoration_from_resource(resource); + + decoration->client_pending_mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE; + wlr_signal_emit_safe(&decoration->events.request_mode, decoration); +} + +static const struct zxdg_toplevel_decoration_v1_interface + toplevel_decoration_impl = { + .destroy = toplevel_decoration_handle_destroy, + .set_mode = toplevel_decoration_handle_set_mode, + .unset_mode = toplevel_decoration_handle_unset_mode, +}; + +uint32_t wlr_xdg_toplevel_decoration_v1_set_mode( + struct wlr_xdg_toplevel_decoration_v1 *decoration, + enum wlr_xdg_toplevel_decoration_v1_mode mode) { + assert(mode != WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE); + decoration->server_pending_mode = mode; + return wlr_xdg_surface_schedule_configure(decoration->surface); +} + +static void toplevel_decoration_handle_resource_destroy( + struct wl_resource *resource) { + struct wlr_xdg_toplevel_decoration_v1 *decoration = + toplevel_decoration_from_resource(resource); + wlr_signal_emit_safe(&decoration->events.destroy, decoration); + wl_list_remove(&decoration->surface_commit.link); + wl_list_remove(&decoration->surface_destroy.link); + wl_list_remove(&decoration->surface_configure.link); + wl_list_remove(&decoration->surface_ack_configure.link); + struct wlr_xdg_toplevel_decoration_v1_configure *configure, *tmp; + wl_list_for_each_safe(configure, tmp, &decoration->configure_list, link) { + free(configure); + } + wl_list_remove(&decoration->link); + free(decoration); +} + +static void toplevel_decoration_handle_surface_destroy( + struct wl_listener *listener, void *data) { + struct wlr_xdg_toplevel_decoration_v1 *decoration = + wl_container_of(listener, decoration, surface_destroy); + wl_resource_destroy(decoration->resource); +} + +static void toplevel_decoration_handle_surface_configure( + struct wl_listener *listener, void *data) { + struct wlr_xdg_toplevel_decoration_v1 *decoration = + wl_container_of(listener, decoration, surface_configure); + struct wlr_xdg_surface_configure *surface_configure = data; + + if (decoration->current_mode == decoration->server_pending_mode) { + return; + } + + struct wlr_xdg_toplevel_decoration_v1_configure *configure = + calloc(1, sizeof(struct wlr_xdg_toplevel_decoration_v1_configure)); + if (configure == NULL) { + return; + } + configure->surface_configure = surface_configure; + configure->mode = decoration->server_pending_mode; + wl_list_insert(decoration->configure_list.prev, &configure->link); + + zxdg_toplevel_decoration_v1_send_configure(decoration->resource, + configure->mode); +} + +static void toplevel_decoration_handle_surface_ack_configure( + struct wl_listener *listener, void *data) { + struct wlr_xdg_toplevel_decoration_v1 *decoration = + wl_container_of(listener, decoration, surface_ack_configure); + struct wlr_xdg_surface_configure *surface_configure = data; + + bool found = false; + struct wlr_xdg_toplevel_decoration_v1_configure *configure; + wl_list_for_each(configure, &decoration->configure_list, link) { + if (configure->surface_configure == surface_configure) { + found = true; + break; + } + } + if (!found) { + return; + } + + decoration->current_mode = configure->mode; + + wl_list_remove(&configure->link); + free(configure); +} + +static void toplevel_decoration_handle_surface_commit( + struct wl_listener *listener, void *data) { + struct wlr_xdg_toplevel_decoration_v1 *decoration = + wl_container_of(listener, decoration, surface_commit); + struct wlr_xdg_decoration_manager_v1 *manager = decoration->manager; + + if (decoration->surface->added) { + wl_list_remove(&decoration->surface_commit.link); + wl_list_init(&decoration->surface_commit.link); + + decoration->added = true; + wlr_signal_emit_safe(&manager->events.new_toplevel_decoration, + decoration); + } +} + + +static const struct zxdg_decoration_manager_v1_interface decoration_manager_impl; + +static struct wlr_xdg_decoration_manager_v1 * + decoration_manager_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zxdg_decoration_manager_v1_interface, + &decoration_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void decoration_manager_handle_destroy( + struct wl_client *client, struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static void decoration_manager_handle_get_toplevel_decoration( + struct wl_client *client, struct wl_resource *manager_resource, + uint32_t id, struct wl_resource *toplevel_resource) { + struct wlr_xdg_decoration_manager_v1 *manager = + decoration_manager_from_resource(manager_resource); + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_toplevel_resource(toplevel_resource); + assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); + + if (wlr_surface_has_buffer(surface->surface)) { + wl_resource_post_error(manager_resource, + ZXDG_TOPLEVEL_DECORATION_V1_ERROR_UNCONFIGURED_BUFFER, + "xdg_toplevel_decoration must not have a buffer at creation"); + return; + } + + struct wlr_xdg_toplevel_decoration_v1 *decoration = + calloc(1, sizeof(struct wlr_xdg_toplevel_decoration_v1)); + if (decoration == NULL) { + wl_client_post_no_memory(client); + return; + } + decoration->manager = manager; + decoration->surface = surface; + + uint32_t version = wl_resource_get_version(manager_resource); + decoration->resource = wl_resource_create(client, + &zxdg_toplevel_decoration_v1_interface, version, id); + if (decoration->resource == NULL) { + free(decoration); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(decoration->resource, + &toplevel_decoration_impl, decoration, + toplevel_decoration_handle_resource_destroy); + + wlr_log(WLR_DEBUG, "new xdg_toplevel_decoration %p (res %p)", decoration, + decoration->resource); + + wl_list_init(&decoration->configure_list); + wl_signal_init(&decoration->events.destroy); + wl_signal_init(&decoration->events.request_mode); + + wl_signal_add(&surface->events.destroy, &decoration->surface_destroy); + decoration->surface_destroy.notify = + toplevel_decoration_handle_surface_destroy; + wl_signal_add(&surface->events.configure, &decoration->surface_configure); + decoration->surface_configure.notify = + toplevel_decoration_handle_surface_configure; + wl_signal_add(&surface->events.ack_configure, + &decoration->surface_ack_configure); + decoration->surface_ack_configure.notify = + toplevel_decoration_handle_surface_ack_configure; + wl_list_init(&decoration->surface_commit.link); + + wl_list_insert(&manager->decorations, &decoration->link); + + if (surface->added) { + decoration->added = true; + wlr_signal_emit_safe(&manager->events.new_toplevel_decoration, + decoration); + } else { + wl_list_remove(&decoration->surface_commit.link); + wl_signal_add(&surface->surface->events.commit, + &decoration->surface_commit); + decoration->surface_commit.notify = + toplevel_decoration_handle_surface_commit; + } +} + +static const struct zxdg_decoration_manager_v1_interface + decoration_manager_impl = { + .destroy = decoration_manager_handle_destroy, + .get_toplevel_decoration = decoration_manager_handle_get_toplevel_decoration, +}; + +void decoration_manager_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void decoration_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_xdg_decoration_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &zxdg_decoration_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &decoration_manager_impl, + manager, decoration_manager_handle_resource_destroy); + + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_decoration_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wlr_xdg_decoration_manager_v1_destroy(manager); +} + +struct wlr_xdg_decoration_manager_v1 * + wlr_xdg_decoration_manager_v1_create(struct wl_display *display) { + struct wlr_xdg_decoration_manager_v1 *manager = + calloc(1, sizeof(struct wlr_xdg_decoration_manager_v1)); + if (manager == NULL) { + return NULL; + } + manager->global = wl_global_create(display, + &zxdg_decoration_manager_v1_interface, DECORATION_MANAGER_VERSION, + manager, decoration_manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + wl_list_init(&manager->resources); + wl_list_init(&manager->decorations); + wl_signal_init(&manager->events.new_toplevel_decoration); + wl_signal_init(&manager->events.destroy); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +void wlr_xdg_decoration_manager_v1_destroy( + struct wlr_xdg_decoration_manager_v1 *manager) { + if (manager == NULL) { + return; + } + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + struct wlr_xdg_toplevel_decoration_v1 *decoration, *tmp_decoration; + wl_list_for_each_safe(decoration, tmp_decoration, &manager->decorations, + link) { + wl_resource_destroy(decoration->resource); + } + struct wl_resource *resource, *tmp_resource; + wl_resource_for_each_safe(resource, tmp_resource, &manager->resources) { + wl_resource_destroy(resource); + } + wl_global_destroy(manager->global); + free(manager); +} diff --git a/types/wlr_xdg_output_v1.c b/types/wlr_xdg_output_v1.c new file mode 100644 index 00000000..5e8419ae --- /dev/null +++ b/types/wlr_xdg_output_v1.c @@ -0,0 +1,256 @@ +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <wlr/types/wlr_output_layout.h> +#include <wlr/types/wlr_output.h> +#include <wlr/types/wlr_xdg_output_v1.h> +#include <wlr/util/log.h> +#include "xdg-output-unstable-v1-protocol.h" +#include "util/signal.h" + +#define OUTPUT_MANAGER_VERSION 2 + +static void output_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zxdg_output_v1_interface output_implementation = { + .destroy = output_handle_destroy, +}; + +static void output_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void output_send_details(struct wlr_xdg_output_v1 *xdg_output, + struct wl_resource *resource) { + struct wlr_output *output = xdg_output->layout_output->output; + + zxdg_output_v1_send_logical_position(resource, + xdg_output->x, xdg_output->y); + zxdg_output_v1_send_logical_size(resource, + xdg_output->width, xdg_output->height); + + uint32_t version = wl_resource_get_version(resource); + if (version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) { + zxdg_output_v1_send_name(resource, output->name); + } + if (version >= ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION) { + char description[128]; + snprintf(description, sizeof(description), "%s %s %s (%s)", + output->make, output->model, output->serial, output->name); + zxdg_output_v1_send_description(resource, description); + } + + zxdg_output_v1_send_done(resource); +} + +static void output_update(struct wlr_xdg_output_v1 *xdg_output) { + struct wlr_output_layout_output *layout_output = xdg_output->layout_output; + bool updated = false; + + if (layout_output->x != xdg_output->x || layout_output->y != xdg_output->y) { + xdg_output->x = layout_output->x; + xdg_output->y = layout_output->y; + updated = true; + } + + int width, height; + wlr_output_effective_resolution(layout_output->output, &width, &height); + if (xdg_output->width != width || xdg_output->height != height) { + xdg_output->width = width; + xdg_output->height = height; + updated = true; + } + + if (updated) { + struct wl_resource *resource; + wl_resource_for_each(resource, &xdg_output->resources) { + output_send_details(xdg_output, resource); + } + } +} + +static void output_destroy(struct wlr_xdg_output_v1 *output) { + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &output->resources) { + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + } + wl_list_remove(&output->destroy.link); + wl_list_remove(&output->link); + free(output); +} + + +static void output_manager_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct zxdg_output_manager_v1_interface + output_manager_implementation; + +static void output_manager_handle_get_xdg_output(struct wl_client *client, + struct wl_resource *resource, uint32_t id, + struct wl_resource *output_resource) { + assert(wl_resource_instance_of(resource, &zxdg_output_manager_v1_interface, + &output_manager_implementation)); + + struct wlr_xdg_output_manager_v1 *manager = + wl_resource_get_user_data(resource); + struct wlr_output_layout *layout = manager->layout; + struct wlr_output *output = wlr_output_from_resource(output_resource); + + struct wlr_output_layout_output *layout_output = + wlr_output_layout_get(layout, output); + assert(layout_output); + + struct wlr_xdg_output_v1 *_xdg_output, *xdg_output = NULL; + wl_list_for_each(_xdg_output, &manager->outputs, link) { + if (_xdg_output->layout_output == layout_output) { + xdg_output = _xdg_output; + break; + } + } + assert(xdg_output); + + struct wl_resource *xdg_output_resource = wl_resource_create(client, + &zxdg_output_v1_interface, wl_resource_get_version(resource), id); + if (!xdg_output_resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(xdg_output_resource, &output_implementation, + NULL, output_handle_resource_destroy); + + wl_list_insert(&xdg_output->resources, + wl_resource_get_link(xdg_output_resource)); + + output_send_details(xdg_output, xdg_output_resource); +} + +static const struct zxdg_output_manager_v1_interface + output_manager_implementation = { + .destroy = output_manager_handle_destroy, + .get_xdg_output = output_manager_handle_get_xdg_output, +}; + +static void output_manager_handle_resource_destroy( + struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void output_manager_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_xdg_output_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(wl_client, + &zxdg_output_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(resource, &output_manager_implementation, + manager, output_manager_handle_resource_destroy); + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); +} + +static void handle_output_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_output_v1 *output = wl_container_of(listener, output, destroy); + output_destroy(output); +} + +static void add_output(struct wlr_xdg_output_manager_v1 *manager, + struct wlr_output_layout_output *layout_output) { + struct wlr_xdg_output_v1 *output = calloc(1, sizeof(struct wlr_xdg_output_v1)); + if (output == NULL) { + return; + } + wl_list_init(&output->resources); + output->manager = manager; + output->layout_output = layout_output; + output->destroy.notify = handle_output_destroy; + wl_signal_add(&layout_output->events.destroy, &output->destroy); + wl_list_insert(&manager->outputs, &output->link); + output_update(output); +} + +static void output_manager_send_details( + struct wlr_xdg_output_manager_v1 *manager) { + struct wlr_xdg_output_v1 *output; + wl_list_for_each(output, &manager->outputs, link) { + output_update(output); + } +} + +static void handle_layout_add(struct wl_listener *listener, void *data) { + struct wlr_xdg_output_manager_v1 *manager = + wl_container_of(listener, manager, layout_add); + struct wlr_output_layout_output *layout_output = data; + add_output(manager, layout_output); +} + +static void handle_layout_change(struct wl_listener *listener, void *data) { + struct wlr_xdg_output_manager_v1 *manager = + wl_container_of(listener, manager, layout_change); + output_manager_send_details(manager); +} + +static void handle_layout_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_output_manager_v1 *manager = + wl_container_of(listener, manager, layout_destroy); + wlr_xdg_output_manager_v1_destroy(manager); +} + +struct wlr_xdg_output_manager_v1 *wlr_xdg_output_manager_v1_create( + struct wl_display *display, struct wlr_output_layout *layout) { + assert(display && layout); + struct wlr_xdg_output_manager_v1 *manager = + calloc(1, sizeof(struct wlr_xdg_output_manager_v1)); + if (manager == NULL) { + return NULL; + } + manager->layout = layout; + manager->global = wl_global_create(display, + &zxdg_output_manager_v1_interface, OUTPUT_MANAGER_VERSION, manager, + output_manager_bind); + if (!manager->global) { + free(manager); + return NULL; + } + + wl_list_init(&manager->resources); + wl_list_init(&manager->outputs); + struct wlr_output_layout_output *layout_output; + wl_list_for_each(layout_output, &layout->outputs, link) { + add_output(manager, layout_output); + } + + wl_signal_init(&manager->events.destroy); + + manager->layout_add.notify = handle_layout_add; + wl_signal_add(&layout->events.add, &manager->layout_add); + manager->layout_change.notify = handle_layout_change; + wl_signal_add(&layout->events.change, &manager->layout_change); + manager->layout_destroy.notify = handle_layout_destroy; + wl_signal_add(&layout->events.destroy, &manager->layout_destroy); + return manager; +} + +void wlr_xdg_output_manager_v1_destroy(struct wlr_xdg_output_manager_v1 *manager) { + struct wlr_xdg_output_v1 *output, *output_tmp; + wl_list_for_each_safe(output, output_tmp, &manager->outputs, link) { + output_destroy(output); + } + struct wl_resource *resource, *resource_tmp; + wl_resource_for_each_safe(resource, resource_tmp, &manager->resources) { + wl_resource_destroy(resource); + } + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->layout_add.link); + wl_list_remove(&manager->layout_change.link); + wl_list_remove(&manager->layout_destroy.link); + free(manager); +} diff --git a/types/xdg_shell/wlr_xdg_popup.c b/types/xdg_shell/wlr_xdg_popup.c new file mode 100644 index 00000000..02dbec73 --- /dev/null +++ b/types/xdg_shell/wlr_xdg_popup.c @@ -0,0 +1,512 @@ +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include "types/wlr_xdg_shell.h" +#include "util/signal.h" + +static void xdg_pointer_grab_end(struct wlr_seat_pointer_grab *grab) { + struct wlr_xdg_popup_grab *popup_grab = grab->data; + + struct wlr_xdg_popup *popup, *tmp; + wl_list_for_each_safe(popup, tmp, &popup_grab->popups, grab_link) { + xdg_popup_send_popup_done(popup->resource); + } + + wlr_seat_pointer_end_grab(grab->seat); + wlr_seat_keyboard_end_grab(grab->seat); +} + +static void xdg_pointer_grab_enter(struct wlr_seat_pointer_grab *grab, + struct wlr_surface *surface, double sx, double sy) { + struct wlr_xdg_popup_grab *popup_grab = grab->data; + if (wl_resource_get_client(surface->resource) == popup_grab->client) { + wlr_seat_pointer_enter(grab->seat, surface, sx, sy); + } else { + wlr_seat_pointer_clear_focus(grab->seat); + } +} + +static void xdg_pointer_grab_motion(struct wlr_seat_pointer_grab *grab, + uint32_t time, double sx, double sy) { + wlr_seat_pointer_send_motion(grab->seat, time, sx, sy); +} + +static uint32_t xdg_pointer_grab_button(struct wlr_seat_pointer_grab *grab, + uint32_t time, uint32_t button, uint32_t state) { + uint32_t serial = + wlr_seat_pointer_send_button(grab->seat, time, button, state); + if (serial) { + return serial; + } else { + xdg_pointer_grab_end(grab); + return 0; + } +} + +static void xdg_pointer_grab_axis(struct wlr_seat_pointer_grab *grab, + uint32_t time, enum wlr_axis_orientation orientation, double value, + int32_t value_discrete, enum wlr_axis_source source) { + wlr_seat_pointer_send_axis(grab->seat, time, orientation, value, + value_discrete, source); +} + +static void xdg_pointer_grab_cancel(struct wlr_seat_pointer_grab *grab) { + xdg_pointer_grab_end(grab); +} + +static const struct wlr_pointer_grab_interface xdg_pointer_grab_impl = { + .enter = xdg_pointer_grab_enter, + .motion = xdg_pointer_grab_motion, + .button = xdg_pointer_grab_button, + .cancel = xdg_pointer_grab_cancel, + .axis = xdg_pointer_grab_axis, +}; + +static void xdg_keyboard_grab_enter(struct wlr_seat_keyboard_grab *grab, + struct wlr_surface *surface, uint32_t keycodes[], size_t num_keycodes, + struct wlr_keyboard_modifiers *modifiers) { + // keyboard focus should remain on the popup +} + +static void xdg_keyboard_grab_key(struct wlr_seat_keyboard_grab *grab, uint32_t time, + uint32_t key, uint32_t state) { + wlr_seat_keyboard_send_key(grab->seat, time, key, state); +} + +static void xdg_keyboard_grab_modifiers(struct wlr_seat_keyboard_grab *grab, + struct wlr_keyboard_modifiers *modifiers) { + wlr_seat_keyboard_send_modifiers(grab->seat, modifiers); +} + +static void xdg_keyboard_grab_cancel(struct wlr_seat_keyboard_grab *grab) { + wlr_seat_pointer_end_grab(grab->seat); +} + +static const struct wlr_keyboard_grab_interface xdg_keyboard_grab_impl = { + .enter = xdg_keyboard_grab_enter, + .key = xdg_keyboard_grab_key, + .modifiers = xdg_keyboard_grab_modifiers, + .cancel = xdg_keyboard_grab_cancel, +}; + +static void xdg_popup_grab_handle_seat_destroy( + struct wl_listener *listener, void *data) { + struct wlr_xdg_popup_grab *xdg_grab = + wl_container_of(listener, xdg_grab, seat_destroy); + + wl_list_remove(&xdg_grab->seat_destroy.link); + + struct wlr_xdg_popup *popup, *next; + wl_list_for_each_safe(popup, next, &xdg_grab->popups, grab_link) { + destroy_xdg_surface(popup->base); + } + + wl_list_remove(&xdg_grab->link); + free(xdg_grab); +} + +struct wlr_xdg_popup_grab *get_xdg_shell_popup_grab_from_seat( + struct wlr_xdg_shell *shell, struct wlr_seat *seat) { + struct wlr_xdg_popup_grab *xdg_grab; + wl_list_for_each(xdg_grab, &shell->popup_grabs, link) { + if (xdg_grab->seat == seat) { + return xdg_grab; + } + } + + xdg_grab = calloc(1, sizeof(struct wlr_xdg_popup_grab)); + if (!xdg_grab) { + return NULL; + } + + xdg_grab->pointer_grab.data = xdg_grab; + xdg_grab->pointer_grab.interface = &xdg_pointer_grab_impl; + xdg_grab->keyboard_grab.data = xdg_grab; + xdg_grab->keyboard_grab.interface = &xdg_keyboard_grab_impl; + + wl_list_init(&xdg_grab->popups); + + wl_list_insert(&shell->popup_grabs, &xdg_grab->link); + xdg_grab->seat = seat; + + xdg_grab->seat_destroy.notify = xdg_popup_grab_handle_seat_destroy; + wl_signal_add(&seat->events.destroy, &xdg_grab->seat_destroy); + + return xdg_grab; +} + + +static const struct xdg_popup_interface xdg_popup_implementation; + +struct wlr_xdg_surface *wlr_xdg_surface_from_popup_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &xdg_popup_interface, + &xdg_popup_implementation)); + return wl_resource_get_user_data(resource); +} + +static void xdg_popup_handle_grab(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat_resource, + uint32_t serial) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_popup_resource(resource); + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + if (!surface) { + return; + } + + if (surface->popup->committed) { + wl_resource_post_error(surface->popup->resource, + XDG_POPUP_ERROR_INVALID_GRAB, + "xdg_popup is already mapped"); + return; + } + + struct wlr_xdg_popup_grab *popup_grab = get_xdg_shell_popup_grab_from_seat( + surface->client->shell, seat_client->seat); + + if (!wl_list_empty(&surface->popups)) { + wl_resource_post_error(surface->client->resource, + XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP, + "xdg_popup was not created on the topmost popup"); + return; + } + + popup_grab->client = surface->client->client; + surface->popup->seat = seat_client->seat; + + wl_list_insert(&popup_grab->popups, &surface->popup->grab_link); + + wlr_seat_pointer_start_grab(seat_client->seat, + &popup_grab->pointer_grab); + wlr_seat_keyboard_start_grab(seat_client->seat, + &popup_grab->keyboard_grab); +} + +static void xdg_popup_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_popup_resource(resource); + + if (surface && !wl_list_empty(&surface->popups)) { + wl_resource_post_error(surface->client->resource, + XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP, + "xdg_popup was destroyed while it was not the topmost popup"); + return; + } + + wl_resource_destroy(resource); +} + +static const struct xdg_popup_interface xdg_popup_implementation = { + .destroy = xdg_popup_handle_destroy, + .grab = xdg_popup_handle_grab, +}; + +static void xdg_popup_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_popup_resource(resource); + destroy_xdg_popup(surface); +} + +const struct wlr_surface_role xdg_popup_surface_role = { + .name = "xdg_popup", + .commit = handle_xdg_surface_commit, + .precommit = handle_xdg_surface_precommit, +}; + +void create_xdg_popup(struct wlr_xdg_surface *xdg_surface, + struct wlr_xdg_surface *parent, + struct wlr_xdg_positioner_resource *positioner, int32_t id) { + if (positioner->attrs.size.width == 0 || + positioner->attrs.anchor_rect.width == 0) { + wl_resource_post_error(xdg_surface->resource, + XDG_WM_BASE_ERROR_INVALID_POSITIONER, + "positioner object is not complete"); + return; + } + + if (!wlr_surface_set_role(xdg_surface->surface, &xdg_popup_surface_role, + xdg_surface, xdg_surface->resource, XDG_WM_BASE_ERROR_ROLE)) { + return; + } + + if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_NONE) { + wl_resource_post_error(xdg_surface->resource, + XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED, + "xdg-surface has already been constructed"); + return; + } + + assert(xdg_surface->popup == NULL); + xdg_surface->popup = calloc(1, sizeof(struct wlr_xdg_popup)); + if (!xdg_surface->popup) { + wl_resource_post_no_memory(xdg_surface->resource); + return; + } + xdg_surface->popup->base = xdg_surface; + + xdg_surface->popup->resource = wl_resource_create( + xdg_surface->client->client, &xdg_popup_interface, + wl_resource_get_version(xdg_surface->resource), id); + if (xdg_surface->popup->resource == NULL) { + free(xdg_surface->popup); + wl_resource_post_no_memory(xdg_surface->resource); + return; + } + wl_resource_set_implementation(xdg_surface->popup->resource, + &xdg_popup_implementation, xdg_surface, + xdg_popup_handle_resource_destroy); + + xdg_surface->role = WLR_XDG_SURFACE_ROLE_POPUP; + + // positioner properties + memcpy(&xdg_surface->popup->positioner, &positioner->attrs, + sizeof(struct wlr_xdg_positioner)); + xdg_surface->popup->geometry = + wlr_xdg_positioner_get_geometry(&positioner->attrs); + + if (parent) { + xdg_surface->popup->parent = parent->surface; + wl_list_insert(&parent->popups, &xdg_surface->popup->link); + wlr_signal_emit_safe(&parent->events.new_popup, xdg_surface->popup); + } else { + wl_list_init(&xdg_surface->popup->link); + } +} + +void destroy_xdg_popup(struct wlr_xdg_surface *xdg_surface) { + if (xdg_surface == NULL) { + return; + } + assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP); + reset_xdg_surface(xdg_surface); +} + +void wlr_xdg_popup_get_anchor_point(struct wlr_xdg_popup *popup, + int *root_sx, int *root_sy) { + struct wlr_box rect = popup->positioner.anchor_rect; + enum xdg_positioner_anchor anchor = popup->positioner.anchor; + int sx = 0, sy = 0; + + if (anchor == XDG_POSITIONER_ANCHOR_NONE) { + sx = (rect.x + rect.width) / 2; + sy = (rect.y + rect.height) / 2; + } else if (anchor == XDG_POSITIONER_ANCHOR_TOP) { + sx = (rect.x + rect.width) / 2; + sy = rect.y; + } else if (anchor == XDG_POSITIONER_ANCHOR_BOTTOM) { + sx = (rect.x + rect.width) / 2; + sy = rect.y + rect.height; + } else if (anchor == XDG_POSITIONER_ANCHOR_LEFT) { + sx = rect.x; + sy = (rect.y + rect.height) / 2; + } else if (anchor == XDG_POSITIONER_ANCHOR_RIGHT) { + sx = rect.x + rect.width; + sy = (rect.y + rect.height) / 2; + } else if (anchor == XDG_POSITIONER_ANCHOR_TOP_LEFT) { + sx = rect.x; + sy = rect.y; + } else if (anchor == XDG_POSITIONER_ANCHOR_TOP_RIGHT) { + sx = rect.x + rect.width; + sy = rect.y; + } else if (anchor == XDG_POSITIONER_ANCHOR_BOTTOM_LEFT) { + sx = rect.x; + sy = rect.y + rect.height; + } else if (anchor == XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT) { + sx = rect.x + rect.width; + sy = rect.y + rect.height; + } + + *root_sx = sx; + *root_sy = sy; +} + +void wlr_xdg_popup_get_toplevel_coords(struct wlr_xdg_popup *popup, + int popup_sx, int popup_sy, int *toplevel_sx, int *toplevel_sy) { + struct wlr_surface *parent = popup->parent; + while (wlr_surface_is_xdg_surface(parent)) { + struct wlr_xdg_surface *xdg_surface = + wlr_xdg_surface_from_wlr_surface(parent); + + if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) { + popup_sx += xdg_surface->popup->geometry.x; + popup_sy += xdg_surface->popup->geometry.y; + parent = xdg_surface->popup->parent; + } else { + popup_sx += xdg_surface->geometry.x; + popup_sy += xdg_surface->geometry.y; + break; + } + } + assert(parent); + + *toplevel_sx = popup_sx; + *toplevel_sy = popup_sy; +} + +static void xdg_popup_box_constraints(struct wlr_xdg_popup *popup, + struct wlr_box *toplevel_sx_box, int *offset_x, int *offset_y) { + int popup_width = popup->geometry.width; + int popup_height = popup->geometry.height; + int anchor_sx = 0, anchor_sy = 0; + wlr_xdg_popup_get_anchor_point(popup, &anchor_sx, &anchor_sy); + int popup_sx = 0, popup_sy = 0; + wlr_xdg_popup_get_toplevel_coords(popup, popup->geometry.x, + popup->geometry.y, &popup_sx, &popup_sy); + *offset_x = 0, *offset_y = 0; + + if (popup_sx < toplevel_sx_box->x) { + *offset_x = toplevel_sx_box->x - popup_sx; + } else if (popup_sx + popup_width > + toplevel_sx_box->x + toplevel_sx_box->width) { + *offset_x = toplevel_sx_box->x + toplevel_sx_box->width - + (popup_sx + popup_width); + } + + if (popup_sy < toplevel_sx_box->y) { + *offset_y = toplevel_sx_box->y - popup_sy; + } else if (popup_sy + popup_height > + toplevel_sx_box->y + toplevel_sx_box->height) { + *offset_y = toplevel_sx_box->y + toplevel_sx_box->height - + (popup_sy + popup_height); + } +} + +static bool xdg_popup_unconstrain_flip(struct wlr_xdg_popup *popup, + struct wlr_box *toplevel_sx_box) { + int offset_x = 0, offset_y = 0; + xdg_popup_box_constraints(popup, toplevel_sx_box, + &offset_x, &offset_y); + + if (!offset_x && !offset_y) { + return true; + } + + bool flip_x = offset_x && + (popup->positioner.constraint_adjustment & + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X); + + bool flip_y = offset_y && + (popup->positioner.constraint_adjustment & + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y); + + if (flip_x) { + wlr_positioner_invert_x(&popup->positioner); + } + if (flip_y) { + wlr_positioner_invert_y(&popup->positioner); + } + + popup->geometry = + wlr_xdg_positioner_get_geometry(&popup->positioner); + + xdg_popup_box_constraints(popup, toplevel_sx_box, + &offset_x, &offset_y); + + if (!offset_x && !offset_y) { + // no longer constrained + return true; + } + + // revert the positioner back if it didn't fix it and go to the next part + if (flip_x) { + wlr_positioner_invert_x(&popup->positioner); + } + if (flip_y) { + wlr_positioner_invert_y(&popup->positioner); + } + + popup->geometry = + wlr_xdg_positioner_get_geometry(&popup->positioner); + + return false; +} + +static bool xdg_popup_unconstrain_slide(struct wlr_xdg_popup *popup, + struct wlr_box *toplevel_sx_box) { + int offset_x = 0, offset_y = 0; + xdg_popup_box_constraints(popup, toplevel_sx_box, + &offset_x, &offset_y); + + if (!offset_x && !offset_y) { + return true; + } + + bool slide_x = offset_x && + (popup->positioner.constraint_adjustment & + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X); + + bool slide_y = offset_y && + (popup->positioner.constraint_adjustment & + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y); + + if (slide_x) { + popup->geometry.x += offset_x; + } + + if (slide_y) { + popup->geometry.y += offset_y; + } + + int toplevel_x = 0, toplevel_y = 0; + wlr_xdg_popup_get_toplevel_coords(popup, popup->geometry.x, + popup->geometry.y, &toplevel_x, &toplevel_y); + + if (slide_x && toplevel_x < toplevel_sx_box->x) { + popup->geometry.x += toplevel_sx_box->x - toplevel_x; + } + if (slide_y && toplevel_y < toplevel_sx_box->y) { + popup->geometry.y += toplevel_sx_box->y - toplevel_y; + } + + xdg_popup_box_constraints(popup, toplevel_sx_box, + &offset_x, &offset_y); + + return !offset_x && !offset_y; +} + +static bool xdg_popup_unconstrain_resize(struct wlr_xdg_popup *popup, + struct wlr_box *toplevel_sx_box) { + int offset_x, offset_y; + xdg_popup_box_constraints(popup, toplevel_sx_box, + &offset_x, &offset_y); + + if (!offset_x && !offset_y) { + return true; + } + + bool resize_x = offset_x && + (popup->positioner.constraint_adjustment & + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X); + + bool resize_y = offset_y && + (popup->positioner.constraint_adjustment & + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y); + + if (resize_x) { + popup->geometry.width -= offset_x; + } + if (resize_y) { + popup->geometry.height -= offset_y; + } + + xdg_popup_box_constraints(popup, toplevel_sx_box, + &offset_x, &offset_y); + + return !offset_x && !offset_y; +} + +void wlr_xdg_popup_unconstrain_from_box(struct wlr_xdg_popup *popup, + struct wlr_box *toplevel_sx_box) { + if (xdg_popup_unconstrain_flip(popup, toplevel_sx_box)) { + return; + } + if (xdg_popup_unconstrain_slide(popup, toplevel_sx_box)) { + return; + } + if (xdg_popup_unconstrain_resize(popup, toplevel_sx_box)) { + return; + } +} diff --git a/types/xdg_shell/wlr_xdg_positioner.c b/types/xdg_shell/wlr_xdg_positioner.c new file mode 100644 index 00000000..6f902f92 --- /dev/null +++ b/types/xdg_shell/wlr_xdg_positioner.c @@ -0,0 +1,311 @@ +#include <assert.h> +#include <stdlib.h> +#include "types/wlr_xdg_shell.h" + +static const struct xdg_positioner_interface xdg_positioner_implementation; + +struct wlr_xdg_positioner_resource *get_xdg_positioner_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &xdg_positioner_interface, + &xdg_positioner_implementation)); + return wl_resource_get_user_data(resource); +} + +static void xdg_positioner_handle_set_size(struct wl_client *client, + struct wl_resource *resource, int32_t width, int32_t height) { + struct wlr_xdg_positioner_resource *positioner = + get_xdg_positioner_from_resource(resource); + + if (width < 1 || height < 1) { + wl_resource_post_error(resource, + XDG_POSITIONER_ERROR_INVALID_INPUT, + "width and height must be positive and non-zero"); + return; + } + + positioner->attrs.size.width = width; + positioner->attrs.size.height = height; +} + +static void xdg_positioner_handle_set_anchor_rect(struct wl_client *client, + struct wl_resource *resource, int32_t x, int32_t y, int32_t width, + int32_t height) { + struct wlr_xdg_positioner_resource *positioner = + get_xdg_positioner_from_resource(resource); + + if (width < 0 || height < 0) { + wl_resource_post_error(resource, + XDG_POSITIONER_ERROR_INVALID_INPUT, + "width and height must be positive"); + return; + } + + positioner->attrs.anchor_rect.x = x; + positioner->attrs.anchor_rect.y = y; + positioner->attrs.anchor_rect.width = width; + positioner->attrs.anchor_rect.height = height; +} + +static void xdg_positioner_handle_set_anchor(struct wl_client *client, + struct wl_resource *resource, uint32_t anchor) { + struct wlr_xdg_positioner_resource *positioner = + get_xdg_positioner_from_resource(resource); + + if (anchor > XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT) { + wl_resource_post_error(resource, + XDG_POSITIONER_ERROR_INVALID_INPUT, + "invalid anchor value"); + return; + } + + positioner->attrs.anchor = anchor; +} + +static void xdg_positioner_handle_set_gravity(struct wl_client *client, + struct wl_resource *resource, uint32_t gravity) { + struct wlr_xdg_positioner_resource *positioner = + get_xdg_positioner_from_resource(resource); + + if (gravity > XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT) { + wl_resource_post_error(resource, + XDG_POSITIONER_ERROR_INVALID_INPUT, + "invalid gravity value"); + return; + } + + positioner->attrs.gravity = gravity; +} + +static void xdg_positioner_handle_set_constraint_adjustment( + struct wl_client *client, struct wl_resource *resource, + uint32_t constraint_adjustment) { + struct wlr_xdg_positioner_resource *positioner = + get_xdg_positioner_from_resource(resource); + + positioner->attrs.constraint_adjustment = constraint_adjustment; +} + +static void xdg_positioner_handle_set_offset(struct wl_client *client, + struct wl_resource *resource, int32_t x, int32_t y) { + struct wlr_xdg_positioner_resource *positioner = + get_xdg_positioner_from_resource(resource); + + positioner->attrs.offset.x = x; + positioner->attrs.offset.y = y; +} + +static void xdg_positioner_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct xdg_positioner_interface + xdg_positioner_implementation = { + .destroy = xdg_positioner_handle_destroy, + .set_size = xdg_positioner_handle_set_size, + .set_anchor_rect = xdg_positioner_handle_set_anchor_rect, + .set_anchor = xdg_positioner_handle_set_anchor, + .set_gravity = xdg_positioner_handle_set_gravity, + .set_constraint_adjustment = + xdg_positioner_handle_set_constraint_adjustment, + .set_offset = xdg_positioner_handle_set_offset, +}; + +static void xdg_positioner_handle_resource_destroy( + struct wl_resource *resource) { + struct wlr_xdg_positioner_resource *positioner = + get_xdg_positioner_from_resource(resource); + free(positioner); +} + +void create_xdg_positioner(struct wlr_xdg_client *client, uint32_t id) { + struct wlr_xdg_positioner_resource *positioner = + calloc(1, sizeof(struct wlr_xdg_positioner_resource)); + if (positioner == NULL) { + wl_client_post_no_memory(client->client); + return; + } + + positioner->resource = wl_resource_create(client->client, + &xdg_positioner_interface, + wl_resource_get_version(client->resource), + id); + if (positioner->resource == NULL) { + free(positioner); + wl_client_post_no_memory(client->client); + return; + } + wl_resource_set_implementation(positioner->resource, + &xdg_positioner_implementation, + positioner, xdg_positioner_handle_resource_destroy); +} + +void handle_xdg_surface_popup_committed(struct wlr_xdg_surface *surface) { + assert(surface->role == WLR_XDG_SURFACE_ROLE_POPUP); + + if (!surface->popup->parent) { + wl_resource_post_error(surface->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "xdg_popup has no parent"); + return; + } + + if (!surface->popup->committed) { + schedule_xdg_surface_configure(surface); + surface->popup->committed = true; + } +} + +static bool positioner_anchor_has_edge(enum xdg_positioner_anchor anchor, + enum xdg_positioner_anchor edge) { + switch (edge) { + case XDG_POSITIONER_ANCHOR_TOP: + return anchor == XDG_POSITIONER_ANCHOR_TOP || + anchor == XDG_POSITIONER_ANCHOR_TOP_LEFT || + anchor == XDG_POSITIONER_ANCHOR_TOP_RIGHT; + case XDG_POSITIONER_ANCHOR_BOTTOM: + return anchor == XDG_POSITIONER_ANCHOR_BOTTOM || + anchor == XDG_POSITIONER_ANCHOR_BOTTOM_LEFT || + anchor == XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT; + case XDG_POSITIONER_ANCHOR_LEFT: + return anchor == XDG_POSITIONER_ANCHOR_LEFT || + anchor == XDG_POSITIONER_ANCHOR_TOP_LEFT || + anchor == XDG_POSITIONER_ANCHOR_BOTTOM_LEFT; + case XDG_POSITIONER_ANCHOR_RIGHT: + return anchor == XDG_POSITIONER_ANCHOR_RIGHT || + anchor == XDG_POSITIONER_ANCHOR_TOP_RIGHT || + anchor == XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT; + default: + assert(false); // not reached + } +} + +static bool positioner_gravity_has_edge(enum xdg_positioner_gravity gravity, + enum xdg_positioner_gravity edge) { + // gravity and edge enums are the same + return positioner_anchor_has_edge((enum xdg_positioner_anchor)gravity, + (enum xdg_positioner_anchor)edge); +} + +struct wlr_box wlr_xdg_positioner_get_geometry( + struct wlr_xdg_positioner *positioner) { + struct wlr_box geometry = { + .x = positioner->offset.x, + .y = positioner->offset.y, + .width = positioner->size.width, + .height = positioner->size.height, + }; + + if (positioner_anchor_has_edge(positioner->anchor, + XDG_POSITIONER_ANCHOR_TOP)) { + geometry.y += positioner->anchor_rect.y; + } else if (positioner_anchor_has_edge(positioner->anchor, + XDG_POSITIONER_ANCHOR_BOTTOM)) { + geometry.y += + positioner->anchor_rect.y + positioner->anchor_rect.height; + } else { + geometry.y += + positioner->anchor_rect.y + positioner->anchor_rect.height / 2; + } + + if (positioner_anchor_has_edge(positioner->anchor, + XDG_POSITIONER_ANCHOR_LEFT)) { + geometry.x += positioner->anchor_rect.x; + } else if (positioner_anchor_has_edge(positioner->anchor, + XDG_POSITIONER_ANCHOR_RIGHT)) { + geometry.x += positioner->anchor_rect.x + positioner->anchor_rect.width; + } else { + geometry.x += + positioner->anchor_rect.x + positioner->anchor_rect.width / 2; + } + + if (positioner_gravity_has_edge(positioner->gravity, + XDG_POSITIONER_GRAVITY_TOP)) { + geometry.y -= geometry.height; + } else if (positioner_gravity_has_edge(positioner->gravity, + XDG_POSITIONER_GRAVITY_BOTTOM)) { + geometry.y = geometry.y; + } else { + geometry.y -= geometry.height / 2; + } + + if (positioner_gravity_has_edge(positioner->gravity, + XDG_POSITIONER_GRAVITY_LEFT)) { + geometry.x -= geometry.width; + } else if (positioner_gravity_has_edge(positioner->gravity, + XDG_POSITIONER_GRAVITY_RIGHT)) { + geometry.x = geometry.x; + } else { + geometry.x -= geometry.width / 2; + } + + if (positioner->constraint_adjustment == + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE) { + return geometry; + } + + return geometry; +} + +static enum xdg_positioner_anchor positioner_anchor_invert_x( + enum xdg_positioner_anchor anchor) { + switch (anchor) { + case XDG_POSITIONER_ANCHOR_LEFT: + return XDG_POSITIONER_ANCHOR_RIGHT; + case XDG_POSITIONER_ANCHOR_RIGHT: + return XDG_POSITIONER_ANCHOR_LEFT; + case XDG_POSITIONER_ANCHOR_TOP_LEFT: + return XDG_POSITIONER_ANCHOR_TOP_RIGHT; + case XDG_POSITIONER_ANCHOR_TOP_RIGHT: + return XDG_POSITIONER_ANCHOR_TOP_LEFT; + case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT: + return XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT; + case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT: + return XDG_POSITIONER_ANCHOR_BOTTOM_LEFT; + default: + return anchor; + } +} + +static enum xdg_positioner_gravity positioner_gravity_invert_x( + enum xdg_positioner_gravity gravity) { + // gravity and edge enums are the same + return (enum xdg_positioner_gravity)positioner_anchor_invert_x( + (enum xdg_positioner_anchor)gravity); +} + +static enum xdg_positioner_anchor positioner_anchor_invert_y( + enum xdg_positioner_anchor anchor) { + switch (anchor) { + case XDG_POSITIONER_ANCHOR_TOP: + return XDG_POSITIONER_ANCHOR_BOTTOM; + case XDG_POSITIONER_ANCHOR_BOTTOM: + return XDG_POSITIONER_ANCHOR_TOP; + case XDG_POSITIONER_ANCHOR_TOP_LEFT: + return XDG_POSITIONER_ANCHOR_BOTTOM_LEFT; + case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT: + return XDG_POSITIONER_ANCHOR_TOP_LEFT; + case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT: + return XDG_POSITIONER_ANCHOR_TOP_RIGHT; + default: + return anchor; + } +} + +static enum xdg_positioner_gravity positioner_gravity_invert_y( + enum xdg_positioner_gravity gravity) { + // gravity and edge enums are the same + return (enum xdg_positioner_gravity)positioner_anchor_invert_y( + (enum xdg_positioner_anchor)gravity); +} + + +void wlr_positioner_invert_x(struct wlr_xdg_positioner *positioner) { + positioner->anchor = positioner_anchor_invert_x(positioner->anchor); + positioner->gravity = positioner_gravity_invert_x(positioner->gravity); +} + +void wlr_positioner_invert_y(struct wlr_xdg_positioner *positioner) { + positioner->anchor = positioner_anchor_invert_y(positioner->anchor); + positioner->gravity = positioner_gravity_invert_y(positioner->gravity); +} diff --git a/types/xdg_shell/wlr_xdg_shell.c b/types/xdg_shell/wlr_xdg_shell.c new file mode 100644 index 00000000..58dc376c --- /dev/null +++ b/types/xdg_shell/wlr_xdg_shell.c @@ -0,0 +1,174 @@ +#include <assert.h> +#include <stdlib.h> +#include "types/wlr_xdg_shell.h" +#include "util/signal.h" + +#define WM_BASE_VERSION 2 + +static const struct xdg_wm_base_interface xdg_shell_impl; + +static struct wlr_xdg_client *xdg_client_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &xdg_wm_base_interface, + &xdg_shell_impl)); + return wl_resource_get_user_data(resource); +} + +static void xdg_shell_handle_create_positioner(struct wl_client *wl_client, + struct wl_resource *resource, uint32_t id) { + struct wlr_xdg_client *client = + xdg_client_from_resource(resource); + create_xdg_positioner(client, id); +} + +static void xdg_shell_handle_get_xdg_surface(struct wl_client *wl_client, + struct wl_resource *client_resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_xdg_client *client = + xdg_client_from_resource(client_resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + create_xdg_surface(client, surface, id); +} + +static void xdg_shell_handle_pong(struct wl_client *wl_client, + struct wl_resource *resource, uint32_t serial) { + struct wlr_xdg_client *client = xdg_client_from_resource(resource); + + if (client->ping_serial != serial) { + return; + } + + wl_event_source_timer_update(client->ping_timer, 0); + client->ping_serial = 0; +} + +static void xdg_shell_handle_destroy(struct wl_client *wl_client, + struct wl_resource *resource) { + struct wlr_xdg_client *client = xdg_client_from_resource(resource); + + if (!wl_list_empty(&client->surfaces)) { + wl_resource_post_error(client->resource, + XDG_WM_BASE_ERROR_DEFUNCT_SURFACES, + "xdg_wm_base was destroyed before children"); + return; + } + + wl_resource_destroy(resource); +} + +static const struct xdg_wm_base_interface xdg_shell_impl = { + .destroy = xdg_shell_handle_destroy, + .create_positioner = xdg_shell_handle_create_positioner, + .get_xdg_surface = xdg_shell_handle_get_xdg_surface, + .pong = xdg_shell_handle_pong, +}; + +static void xdg_client_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_xdg_client *client = xdg_client_from_resource(resource); + + struct wlr_xdg_surface *surface, *tmp = NULL; + wl_list_for_each_safe(surface, tmp, &client->surfaces, link) { + destroy_xdg_surface(surface); + } + + if (client->ping_timer != NULL) { + wl_event_source_remove(client->ping_timer); + } + + wl_list_remove(&client->link); + free(client); +} + +static int xdg_client_ping_timeout(void *user_data) { + struct wlr_xdg_client *client = user_data; + + struct wlr_xdg_surface *surface; + wl_list_for_each(surface, &client->surfaces, link) { + wlr_signal_emit_safe(&surface->events.ping_timeout, surface); + } + + client->ping_serial = 0; + return 1; +} + +static void xdg_shell_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_xdg_shell *xdg_shell = data; + assert(wl_client && xdg_shell); + + struct wlr_xdg_client *client = + calloc(1, sizeof(struct wlr_xdg_client)); + if (client == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + wl_list_init(&client->surfaces); + + client->resource = + wl_resource_create(wl_client, &xdg_wm_base_interface, version, id); + if (client->resource == NULL) { + free(client); + wl_client_post_no_memory(wl_client); + return; + } + client->client = wl_client; + client->shell = xdg_shell; + + wl_resource_set_implementation(client->resource, &xdg_shell_impl, client, + xdg_client_handle_resource_destroy); + wl_list_insert(&xdg_shell->clients, &client->link); + + struct wl_display *display = wl_client_get_display(client->client); + struct wl_event_loop *loop = wl_display_get_event_loop(display); + client->ping_timer = wl_event_loop_add_timer(loop, + xdg_client_ping_timeout, client); + if (client->ping_timer == NULL) { + wl_client_post_no_memory(client->client); + } +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_shell *xdg_shell = + wl_container_of(listener, xdg_shell, display_destroy); + wlr_xdg_shell_destroy(xdg_shell); +} + +struct wlr_xdg_shell *wlr_xdg_shell_create(struct wl_display *display) { + struct wlr_xdg_shell *xdg_shell = + calloc(1, sizeof(struct wlr_xdg_shell)); + if (!xdg_shell) { + return NULL; + } + + xdg_shell->ping_timeout = 10000; + + wl_list_init(&xdg_shell->clients); + wl_list_init(&xdg_shell->popup_grabs); + + struct wl_global *global = wl_global_create(display, + &xdg_wm_base_interface, WM_BASE_VERSION, xdg_shell, xdg_shell_bind); + if (!global) { + free(xdg_shell); + return NULL; + } + xdg_shell->global = global; + + wl_signal_init(&xdg_shell->events.new_surface); + wl_signal_init(&xdg_shell->events.destroy); + + xdg_shell->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &xdg_shell->display_destroy); + + return xdg_shell; +} + +void wlr_xdg_shell_destroy(struct wlr_xdg_shell *xdg_shell) { + if (!xdg_shell) { + return; + } + wlr_signal_emit_safe(&xdg_shell->events.destroy, xdg_shell); + wl_list_remove(&xdg_shell->display_destroy.link); + wl_global_destroy(xdg_shell->global); + free(xdg_shell); +} diff --git a/types/xdg_shell/wlr_xdg_surface.c b/types/xdg_shell/wlr_xdg_surface.c new file mode 100644 index 00000000..17edbe47 --- /dev/null +++ b/types/xdg_shell/wlr_xdg_surface.c @@ -0,0 +1,651 @@ +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <wlr/util/log.h> +#include "types/wlr_xdg_shell.h" +#include "util/signal.h" + +bool wlr_surface_is_xdg_surface(struct wlr_surface *surface) { + return surface->role == &xdg_toplevel_surface_role || + surface->role == &xdg_popup_surface_role; +} + +struct wlr_xdg_surface *wlr_xdg_surface_from_wlr_surface( + struct wlr_surface *surface) { + assert(wlr_surface_is_xdg_surface(surface)); + return (struct wlr_xdg_surface *)surface->role_data; +} + +static void xdg_surface_configure_destroy( + struct wlr_xdg_surface_configure *configure) { + if (configure == NULL) { + return; + } + wl_list_remove(&configure->link); + free(configure->toplevel_state); + free(configure); +} + +void unmap_xdg_surface(struct wlr_xdg_surface *surface) { + assert(surface->role != WLR_XDG_SURFACE_ROLE_NONE); + + // TODO: probably need to ungrab before this event + if (surface->mapped) { + wlr_signal_emit_safe(&surface->events.unmap, surface); + } + + switch (surface->role) { + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + free(surface->toplevel->title); + surface->toplevel->title = NULL; + free(surface->toplevel->app_id); + surface->toplevel->app_id = NULL; + break; + case WLR_XDG_SURFACE_ROLE_POPUP: + if (surface->popup->seat != NULL) { + struct wlr_xdg_popup_grab *grab = + get_xdg_shell_popup_grab_from_seat(surface->client->shell, + surface->popup->seat); + + wl_list_remove(&surface->popup->grab_link); + + if (wl_list_empty(&grab->popups)) { + if (grab->seat->pointer_state.grab == &grab->pointer_grab) { + wlr_seat_pointer_end_grab(grab->seat); + } + if (grab->seat->keyboard_state.grab == &grab->keyboard_grab) { + wlr_seat_keyboard_end_grab(grab->seat); + } + } + + surface->popup->seat = NULL; + } + break; + case WLR_XDG_SURFACE_ROLE_NONE: + assert(false && "not reached"); + } + + struct wlr_xdg_surface_configure *configure, *tmp; + wl_list_for_each_safe(configure, tmp, &surface->configure_list, link) { + xdg_surface_configure_destroy(configure); + } + + surface->configured = surface->mapped = false; + surface->configure_serial = 0; + if (surface->configure_idle) { + wl_event_source_remove(surface->configure_idle); + surface->configure_idle = NULL; + } + surface->configure_next_serial = 0; + + surface->has_next_geometry = false; + memset(&surface->geometry, 0, sizeof(struct wlr_box)); + memset(&surface->next_geometry, 0, sizeof(struct wlr_box)); +} + + +static void xdg_surface_handle_ack_configure(struct wl_client *client, + struct wl_resource *resource, uint32_t serial) { + struct wlr_xdg_surface *surface = wlr_xdg_surface_from_resource(resource); + + if (surface->role == WLR_XDG_SURFACE_ROLE_NONE) { + wl_resource_post_error(surface->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "xdg_surface must have a role"); + return; + } + + bool found = false; + struct wlr_xdg_surface_configure *configure, *tmp; + wl_list_for_each_safe(configure, tmp, &surface->configure_list, link) { + if (configure->serial < serial) { + wlr_signal_emit_safe(&surface->events.ack_configure, configure); + xdg_surface_configure_destroy(configure); + } else if (configure->serial == serial) { + found = true; + break; + } else { + break; + } + } + if (!found) { + wl_resource_post_error(surface->client->resource, + XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE, + "wrong configure serial: %u", serial); + return; + } + + switch (surface->role) { + case WLR_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + handle_xdg_toplevel_ack_configure(surface, configure); + break; + case WLR_XDG_SURFACE_ROLE_POPUP: + break; + } + + surface->configured = true; + surface->configure_serial = serial; + + wlr_signal_emit_safe(&surface->events.ack_configure, configure); + xdg_surface_configure_destroy(configure); +} + +static void surface_send_configure(void *user_data) { + struct wlr_xdg_surface *surface = user_data; + + surface->configure_idle = NULL; + + struct wlr_xdg_surface_configure *configure = + calloc(1, sizeof(struct wlr_xdg_surface_configure)); + if (configure == NULL) { + wl_client_post_no_memory(surface->client->client); + return; + } + + wl_list_insert(surface->configure_list.prev, &configure->link); + configure->serial = surface->configure_next_serial; + configure->surface = surface; + + switch (surface->role) { + case WLR_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + send_xdg_toplevel_configure(surface, configure); + break; + case WLR_XDG_SURFACE_ROLE_POPUP: + xdg_popup_send_configure(surface->popup->resource, + surface->popup->geometry.x, + surface->popup->geometry.y, + surface->popup->geometry.width, + surface->popup->geometry.height); + break; + } + + wlr_signal_emit_safe(&surface->events.configure, configure); + + xdg_surface_send_configure(surface->resource, configure->serial); +} + +static uint32_t schedule_configure(struct wlr_xdg_surface *surface, + bool pending_same) { + struct wl_display *display = wl_client_get_display(surface->client->client); + struct wl_event_loop *loop = wl_display_get_event_loop(display); + + if (surface->configure_idle != NULL) { + if (!pending_same) { + // configure request already scheduled + return surface->configure_next_serial; + } + + // configure request not necessary anymore + wl_event_source_remove(surface->configure_idle); + surface->configure_idle = NULL; + return 0; + } else { + if (pending_same) { + // configure request not necessary + return 0; + } + + surface->configure_next_serial = wl_display_next_serial(display); + surface->configure_idle = wl_event_loop_add_idle(loop, + surface_send_configure, surface); + return surface->configure_next_serial; + } +} + +uint32_t schedule_xdg_surface_configure(struct wlr_xdg_surface *surface) { + bool pending_same = false; + + switch (surface->role) { + case WLR_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + pending_same = compare_xdg_surface_toplevel_state(surface->toplevel); + break; + case WLR_XDG_SURFACE_ROLE_POPUP: + break; + } + + return schedule_configure(surface, pending_same); +} + +uint32_t wlr_xdg_surface_schedule_configure(struct wlr_xdg_surface *surface) { + return schedule_configure(surface, false); +} + +static void xdg_surface_handle_get_popup(struct wl_client *client, + struct wl_resource *resource, uint32_t id, + struct wl_resource *parent_resource, + struct wl_resource *positioner_resource) { + struct wlr_xdg_surface *xdg_surface = + wlr_xdg_surface_from_resource(resource); + struct wlr_xdg_surface *parent = + wlr_xdg_surface_from_resource(parent_resource); + struct wlr_xdg_positioner_resource *positioner = + get_xdg_positioner_from_resource(positioner_resource); + create_xdg_popup(xdg_surface, parent, positioner, id); +} + +static void xdg_surface_handle_get_toplevel(struct wl_client *client, + struct wl_resource *resource, uint32_t id) { + struct wlr_xdg_surface *xdg_surface = + wlr_xdg_surface_from_resource(resource); + create_xdg_toplevel(xdg_surface, id); +} + +static void xdg_surface_handle_set_window_geometry(struct wl_client *client, + struct wl_resource *resource, int32_t x, int32_t y, int32_t width, + int32_t height) { + struct wlr_xdg_surface *surface = wlr_xdg_surface_from_resource(resource); + + if (surface->role == WLR_XDG_SURFACE_ROLE_NONE) { + wl_resource_post_error(surface->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "xdg_surface must have a role"); + return; + } + + if (width <= 0 || height <= 0) { + wlr_log(WLR_ERROR, "Client tried to set invalid geometry"); + //XXX: Switch to the proper error value once available + wl_resource_post_error(resource, -1, "Tried to set invalid xdg-surface geometry"); + return; + } + + surface->has_next_geometry = true; + surface->next_geometry.height = height; + surface->next_geometry.width = width; + surface->next_geometry.x = x; + surface->next_geometry.y = y; +} + +static void xdg_surface_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_surface *surface = wlr_xdg_surface_from_resource(resource); + + if (surface->role != WLR_XDG_SURFACE_ROLE_NONE) { + wlr_log(WLR_ERROR, "Tried to destroy an xdg_surface before its role " + "object"); + return; + } + + wl_resource_destroy(resource); +} + +static const struct xdg_surface_interface xdg_surface_implementation = { + .destroy = xdg_surface_handle_destroy, + .get_toplevel = xdg_surface_handle_get_toplevel, + .get_popup = xdg_surface_handle_get_popup, + .ack_configure = xdg_surface_handle_ack_configure, + .set_window_geometry = xdg_surface_handle_set_window_geometry, +}; + +static void xdg_surface_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_resource(resource); + if (surface != NULL) { + destroy_xdg_surface(surface); + } +} + +static void xdg_surface_handle_surface_commit(struct wl_listener *listener, + void *data) { + struct wlr_xdg_surface *surface = + wl_container_of(listener, surface, surface_commit); + + if (wlr_surface_has_buffer(surface->surface) && !surface->configured) { + wl_resource_post_error(surface->resource, + XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER, + "xdg_surface has never been configured"); + return; + } + + if (surface->role == WLR_XDG_SURFACE_ROLE_NONE) { + wl_resource_post_error(surface->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "xdg_surface must have a role"); + return; + } +} + +void handle_xdg_surface_commit(struct wlr_surface *wlr_surface) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_wlr_surface(wlr_surface); + if (surface == NULL) { + return; + } + + if (surface->has_next_geometry) { + surface->has_next_geometry = false; + surface->geometry.x = surface->next_geometry.x; + surface->geometry.y = surface->next_geometry.y; + surface->geometry.width = surface->next_geometry.width; + surface->geometry.height = surface->next_geometry.height; + } + + switch (surface->role) { + case WLR_XDG_SURFACE_ROLE_NONE: + assert(false); + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + handle_xdg_surface_toplevel_committed(surface); + break; + case WLR_XDG_SURFACE_ROLE_POPUP: + handle_xdg_surface_popup_committed(surface); + break; + } + + if (!surface->added) { + surface->added = true; + wlr_signal_emit_safe(&surface->client->shell->events.new_surface, + surface); + } + if (surface->configured && wlr_surface_has_buffer(surface->surface) && + !surface->mapped) { + surface->mapped = true; + wlr_signal_emit_safe(&surface->events.map, surface); + } + if (surface->configured && !wlr_surface_has_buffer(surface->surface) && + surface->mapped) { + unmap_xdg_surface(surface); + } +} + +void handle_xdg_surface_precommit(struct wlr_surface *wlr_surface) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_wlr_surface(wlr_surface); + if (surface == NULL) { + return; + } + + if (wlr_surface->pending.committed & WLR_SURFACE_STATE_BUFFER && + wlr_surface->pending.buffer_resource == NULL) { + // This is a NULL commit + if (surface->configured && surface->mapped) { + unmap_xdg_surface(surface); + } + } +} + +static void xdg_surface_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_xdg_surface *xdg_surface = + wl_container_of(listener, xdg_surface, surface_destroy); + destroy_xdg_surface(xdg_surface); +} + +struct wlr_xdg_surface *create_xdg_surface( + struct wlr_xdg_client *client, struct wlr_surface *surface, + uint32_t id) { + struct wlr_xdg_surface *xdg_surface = + calloc(1, sizeof(struct wlr_xdg_surface)); + if (xdg_surface == NULL) { + wl_client_post_no_memory(client->client); + return NULL; + } + + xdg_surface->client = client; + xdg_surface->role = WLR_XDG_SURFACE_ROLE_NONE; + xdg_surface->surface = surface; + xdg_surface->resource = wl_resource_create(client->client, + &xdg_surface_interface, wl_resource_get_version(client->resource), + id); + if (xdg_surface->resource == NULL) { + free(xdg_surface); + wl_client_post_no_memory(client->client); + return NULL; + } + + if (wlr_surface_has_buffer(xdg_surface->surface)) { + wl_resource_destroy(xdg_surface->resource); + free(xdg_surface); + wl_resource_post_error(client->resource, + XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER, + "xdg_surface must not have a buffer at creation"); + return NULL; + } + + wl_list_init(&xdg_surface->configure_list); + wl_list_init(&xdg_surface->popups); + + wl_signal_init(&xdg_surface->events.destroy); + wl_signal_init(&xdg_surface->events.ping_timeout); + wl_signal_init(&xdg_surface->events.new_popup); + wl_signal_init(&xdg_surface->events.map); + wl_signal_init(&xdg_surface->events.unmap); + wl_signal_init(&xdg_surface->events.configure); + wl_signal_init(&xdg_surface->events.ack_configure); + + wl_signal_add(&xdg_surface->surface->events.destroy, + &xdg_surface->surface_destroy); + xdg_surface->surface_destroy.notify = xdg_surface_handle_surface_destroy; + + wl_signal_add(&xdg_surface->surface->events.commit, + &xdg_surface->surface_commit); + xdg_surface->surface_commit.notify = xdg_surface_handle_surface_commit; + + wlr_log(WLR_DEBUG, "new xdg_surface %p (res %p)", xdg_surface, + xdg_surface->resource); + wl_resource_set_implementation(xdg_surface->resource, + &xdg_surface_implementation, xdg_surface, + xdg_surface_handle_resource_destroy); + wl_list_insert(&client->surfaces, &xdg_surface->link); + + return xdg_surface; +} + +void reset_xdg_surface(struct wlr_xdg_surface *xdg_surface) { + if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_NONE) { + unmap_xdg_surface(xdg_surface); + } + + if (xdg_surface->added) { + wlr_signal_emit_safe(&xdg_surface->events.destroy, xdg_surface); + xdg_surface->added = false; + } + + switch (xdg_surface->role) { + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + wl_resource_set_user_data(xdg_surface->toplevel->resource, NULL); + xdg_surface->toplevel->resource = NULL; + + free(xdg_surface->toplevel); + xdg_surface->toplevel = NULL; + break; + case WLR_XDG_SURFACE_ROLE_POPUP: + wl_resource_set_user_data(xdg_surface->popup->resource, NULL); + xdg_surface->toplevel->resource = NULL; + + wl_list_remove(&xdg_surface->popup->link); + + free(xdg_surface->popup); + xdg_surface->popup = NULL; + break; + case WLR_XDG_SURFACE_ROLE_NONE: + // This space is intentionally left blank + break; + } + + xdg_surface->role = WLR_XDG_SURFACE_ROLE_NONE; +} + +void destroy_xdg_surface(struct wlr_xdg_surface *surface) { + reset_xdg_surface(surface); + + struct wlr_xdg_popup *popup_state, *next; + wl_list_for_each_safe(popup_state, next, &surface->popups, link) { + xdg_popup_send_popup_done(popup_state->resource); + destroy_xdg_popup(popup_state->base); + } + + wl_resource_set_user_data(surface->resource, NULL); + surface->surface->role_data = NULL; + + wl_list_remove(&surface->link); + wl_list_remove(&surface->surface_destroy.link); + wl_list_remove(&surface->surface_commit.link); + free(surface); +} + +struct wlr_xdg_surface *wlr_xdg_surface_from_resource( + struct wl_resource *resource) { + // TODO: Double check that all of the callers can deal with NULL + if (!resource) { + return NULL; + } + assert(wl_resource_instance_of(resource, &xdg_surface_interface, + &xdg_surface_implementation)); + return wl_resource_get_user_data(resource); +} + +void wlr_xdg_surface_ping(struct wlr_xdg_surface *surface) { + if (surface->client->ping_serial != 0) { + // already pinged + return; + } + + surface->client->ping_serial = + wl_display_next_serial(wl_client_get_display(surface->client->client)); + wl_event_source_timer_update(surface->client->ping_timer, + surface->client->shell->ping_timeout); + xdg_wm_base_send_ping(surface->client->resource, + surface->client->ping_serial); +} + +void wlr_xdg_surface_send_close(struct wlr_xdg_surface *surface) { + switch (surface->role) { + case WLR_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WLR_XDG_SURFACE_ROLE_TOPLEVEL: + if (surface->toplevel) { + xdg_toplevel_send_close(surface->toplevel->resource); + } + break; + case WLR_XDG_SURFACE_ROLE_POPUP: + if (surface->popup) { + xdg_popup_send_popup_done(surface->popup->resource); + } + break; + } +} + +static void xdg_popup_get_position(struct wlr_xdg_popup *popup, + double *popup_sx, double *popup_sy) { + struct wlr_xdg_surface *parent = + wlr_xdg_surface_from_wlr_surface(popup->parent); + struct wlr_box parent_geo; + wlr_xdg_surface_get_geometry(parent, &parent_geo); + *popup_sx = parent_geo.x + popup->geometry.x - + popup->base->geometry.x; + *popup_sy = parent_geo.y + popup->geometry.y - + popup->base->geometry.y; +} + +struct wlr_surface *wlr_xdg_surface_surface_at( + struct wlr_xdg_surface *surface, double sx, double sy, + double *sub_x, double *sub_y) { + struct wlr_xdg_popup *popup_state; + wl_list_for_each(popup_state, &surface->popups, link) { + struct wlr_xdg_surface *popup = popup_state->base; + + double popup_sx, popup_sy; + xdg_popup_get_position(popup_state, &popup_sx, &popup_sy); + + struct wlr_surface *sub = wlr_xdg_surface_surface_at(popup, + sx - popup_sx, + sy - popup_sy, + sub_x, sub_y); + if (sub != NULL) { + return sub; + } + } + + return wlr_surface_surface_at(surface->surface, sx, sy, sub_x, sub_y); +} + +struct xdg_surface_iterator_data { + wlr_surface_iterator_func_t user_iterator; + void *user_data; + int x, y; +}; + +static void xdg_surface_iterator(struct wlr_surface *surface, + int sx, int sy, void *data) { + struct xdg_surface_iterator_data *iter_data = data; + iter_data->user_iterator(surface, iter_data->x + sx, iter_data->y + sy, + iter_data->user_data); +} + +static void xdg_surface_for_each_surface(struct wlr_xdg_surface *surface, + int x, int y, wlr_surface_iterator_func_t iterator, void *user_data) { + struct xdg_surface_iterator_data data = { + .user_iterator = iterator, + .user_data = user_data, + .x = x, .y = y, + }; + wlr_surface_for_each_surface(surface->surface, xdg_surface_iterator, + &data); + + struct wlr_xdg_popup *popup_state; + wl_list_for_each(popup_state, &surface->popups, link) { + struct wlr_xdg_surface *popup = popup_state->base; + if (!popup->configured) { + continue; + } + + double popup_sx, popup_sy; + xdg_popup_get_position(popup_state, &popup_sx, &popup_sy); + + xdg_surface_for_each_surface(popup, + x + popup_sx, + y + popup_sy, + iterator, user_data); + } +} + +static void xdg_surface_for_each_popup(struct wlr_xdg_surface *surface, + int x, int y, wlr_surface_iterator_func_t iterator, void *user_data) { + struct wlr_xdg_popup *popup_state; + wl_list_for_each(popup_state, &surface->popups, link) { + struct wlr_xdg_surface *popup = popup_state->base; + if (!popup->configured) { + continue; + } + + double popup_sx, popup_sy; + xdg_popup_get_position(popup_state, &popup_sx, &popup_sy); + iterator(popup->surface, x + popup_sx, y + popup_sy, user_data); + + xdg_surface_for_each_popup(popup, + x + popup_sx, + y + popup_sy, + iterator, user_data); + } +} + +void wlr_xdg_surface_for_each_surface(struct wlr_xdg_surface *surface, + wlr_surface_iterator_func_t iterator, void *user_data) { + xdg_surface_for_each_surface(surface, 0, 0, iterator, user_data); +} + +void wlr_xdg_surface_for_each_popup(struct wlr_xdg_surface *surface, + wlr_surface_iterator_func_t iterator, void *user_data) { + xdg_surface_for_each_popup(surface, 0, 0, iterator, user_data); +} + +void wlr_xdg_surface_get_geometry(struct wlr_xdg_surface *surface, + struct wlr_box *box) { + wlr_surface_get_extends(surface->surface, box); + /* The client never set the geometry */ + if (!surface->geometry.width) { + return; + } + + wlr_box_intersection(box, &surface->geometry, box); +} diff --git a/types/xdg_shell/wlr_xdg_toplevel.c b/types/xdg_shell/wlr_xdg_toplevel.c new file mode 100644 index 00000000..d3ee0cb7 --- /dev/null +++ b/types/xdg_shell/wlr_xdg_toplevel.c @@ -0,0 +1,558 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <wlr/util/log.h> +#include <wlr/util/edges.h> +#include "types/wlr_xdg_shell.h" +#include "util/signal.h" + +void handle_xdg_toplevel_ack_configure( + struct wlr_xdg_surface *surface, + struct wlr_xdg_surface_configure *configure) { + assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); + assert(configure->toplevel_state != NULL); + + surface->toplevel->current.maximized = + configure->toplevel_state->maximized; + surface->toplevel->current.fullscreen = + configure->toplevel_state->fullscreen; + surface->toplevel->current.resizing = + configure->toplevel_state->resizing; + surface->toplevel->current.activated = + configure->toplevel_state->activated; + surface->toplevel->current.tiled = + configure->toplevel_state->tiled; +} + +bool compare_xdg_surface_toplevel_state(struct wlr_xdg_toplevel *state) { + struct { + struct wlr_xdg_toplevel_state state; + uint32_t width, height; + } configured; + + // is pending state different from current state? + if (!state->base->configured) { + return false; + } + + if (wl_list_empty(&state->base->configure_list)) { + // last configure is actually the current state, just use it + configured.state = state->current; + configured.width = state->base->surface->current.width; + configured.height = state->base->surface->current.height; + } else { + struct wlr_xdg_surface_configure *configure = + wl_container_of(state->base->configure_list.prev, configure, link); + configured.state = *configure->toplevel_state; + configured.width = configure->toplevel_state->width; + configured.height = configure->toplevel_state->height; + } + + if (state->server_pending.activated != configured.state.activated) { + return false; + } + if (state->server_pending.fullscreen != configured.state.fullscreen) { + return false; + } + if (state->server_pending.maximized != configured.state.maximized) { + return false; + } + if (state->server_pending.resizing != configured.state.resizing) { + return false; + } + if (state->server_pending.tiled != configured.state.tiled) { + return false; + } + + if (state->server_pending.width == configured.width && + state->server_pending.height == configured.height) { + return true; + } + + if (state->server_pending.width == 0 && state->server_pending.height == 0) { + return true; + } + + return false; +} + +void send_xdg_toplevel_configure(struct wlr_xdg_surface *surface, + struct wlr_xdg_surface_configure *configure) { + assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); + + configure->toplevel_state = malloc(sizeof(*configure->toplevel_state)); + if (configure->toplevel_state == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + wl_resource_post_no_memory(surface->toplevel->resource); + return; + } + *configure->toplevel_state = surface->toplevel->server_pending; + + struct wl_array states; + wl_array_init(&states); + if (surface->toplevel->server_pending.maximized) { + uint32_t *s = wl_array_add(&states, sizeof(uint32_t)); + if (!s) { + wlr_log(WLR_ERROR, "Could not allocate state for maximized xdg_toplevel"); + goto error_out; + } + *s = XDG_TOPLEVEL_STATE_MAXIMIZED; + } + if (surface->toplevel->server_pending.fullscreen) { + uint32_t *s = wl_array_add(&states, sizeof(uint32_t)); + if (!s) { + wlr_log(WLR_ERROR, "Could not allocate state for fullscreen xdg_toplevel"); + goto error_out; + } + *s = XDG_TOPLEVEL_STATE_FULLSCREEN; + } + if (surface->toplevel->server_pending.resizing) { + uint32_t *s = wl_array_add(&states, sizeof(uint32_t)); + if (!s) { + wlr_log(WLR_ERROR, "Could not allocate state for resizing xdg_toplevel"); + goto error_out; + } + *s = XDG_TOPLEVEL_STATE_RESIZING; + } + if (surface->toplevel->server_pending.activated) { + uint32_t *s = wl_array_add(&states, sizeof(uint32_t)); + if (!s) { + wlr_log(WLR_ERROR, "Could not allocate state for activated xdg_toplevel"); + goto error_out; + } + *s = XDG_TOPLEVEL_STATE_ACTIVATED; + } + if (surface->toplevel->server_pending.tiled) { + if (wl_resource_get_version(surface->resource) >= + XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION) { + const struct { + enum wlr_edges edge; + enum xdg_toplevel_state state; + } tiled[] = { + { WLR_EDGE_LEFT, XDG_TOPLEVEL_STATE_TILED_LEFT }, + { WLR_EDGE_RIGHT, XDG_TOPLEVEL_STATE_TILED_RIGHT }, + { WLR_EDGE_TOP, XDG_TOPLEVEL_STATE_TILED_TOP }, + { WLR_EDGE_BOTTOM, XDG_TOPLEVEL_STATE_TILED_BOTTOM }, + }; + + for (size_t i = 0; i < sizeof(tiled)/sizeof(tiled[0]); ++i) { + if ((surface->toplevel->server_pending.tiled & + tiled[i].edge) == 0) { + continue; + } + + uint32_t *s = wl_array_add(&states, sizeof(uint32_t)); + if (!s) { + wlr_log(WLR_ERROR, + "Could not allocate state for tiled xdg_toplevel"); + goto error_out; + } + *s = tiled[i].state; + } + } else if (!surface->toplevel->server_pending.maximized) { + // This version doesn't support tiling, best we can do is make the + // toplevel maximized + uint32_t *s = wl_array_add(&states, sizeof(uint32_t)); + if (!s) { + wlr_log(WLR_ERROR, + "Could not allocate state for maximized xdg_toplevel"); + goto error_out; + } + *s = XDG_TOPLEVEL_STATE_MAXIMIZED; + } + } + + uint32_t width = surface->toplevel->server_pending.width; + uint32_t height = surface->toplevel->server_pending.height; + xdg_toplevel_send_configure(surface->toplevel->resource, width, height, + &states); + + wl_array_release(&states); + return; + +error_out: + wl_array_release(&states); + wl_resource_post_no_memory(surface->toplevel->resource); +} + +void handle_xdg_surface_toplevel_committed(struct wlr_xdg_surface *surface) { + assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); + + if (!surface->toplevel->added) { + // on the first commit, send a configure request to tell the client it + // is added + schedule_xdg_surface_configure(surface); + surface->toplevel->added = true; + return; + } + + // update state that doesn't need compositor approval + surface->toplevel->current.max_width = + surface->toplevel->client_pending.max_width; + surface->toplevel->current.min_width = + surface->toplevel->client_pending.min_width; + surface->toplevel->current.max_height = + surface->toplevel->client_pending.max_height; + surface->toplevel->current.min_height = + surface->toplevel->client_pending.min_height; +} + +static const struct xdg_toplevel_interface xdg_toplevel_implementation; + +struct wlr_xdg_surface *wlr_xdg_surface_from_toplevel_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &xdg_toplevel_interface, + &xdg_toplevel_implementation)); + return wl_resource_get_user_data(resource); +} + +static void xdg_toplevel_handle_set_parent(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *parent_resource) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_toplevel_resource(resource); + struct wlr_xdg_surface *parent = NULL; + + if (parent_resource != NULL) { + parent = wlr_xdg_surface_from_toplevel_resource(parent_resource); + } + + surface->toplevel->parent = parent; + wlr_signal_emit_safe(&surface->toplevel->events.set_parent, surface); +} + +static void xdg_toplevel_handle_set_title(struct wl_client *client, + struct wl_resource *resource, const char *title) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_toplevel_resource(resource); + char *tmp; + + tmp = strdup(title); + if (tmp == NULL) { + return; + } + + free(surface->toplevel->title); + surface->toplevel->title = tmp; + wlr_signal_emit_safe(&surface->toplevel->events.set_title, surface); +} + +static void xdg_toplevel_handle_set_app_id(struct wl_client *client, + struct wl_resource *resource, const char *app_id) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_toplevel_resource(resource); + char *tmp; + + tmp = strdup(app_id); + if (tmp == NULL) { + return; + } + + free(surface->toplevel->app_id); + surface->toplevel->app_id = tmp; + wlr_signal_emit_safe(&surface->toplevel->events.set_app_id, surface); +} + +static void xdg_toplevel_handle_show_window_menu(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat_resource, + uint32_t serial, int32_t x, int32_t y) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_toplevel_resource(resource); + struct wlr_seat_client *seat = + wlr_seat_client_from_resource(seat_resource); + + if (!surface->configured) { + wl_resource_post_error(surface->toplevel->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "surface has not been configured yet"); + return; + } + + if (!wlr_seat_validate_grab_serial(seat->seat, serial)) { + wlr_log(WLR_DEBUG, "invalid serial for grab"); + return; + } + + struct wlr_xdg_toplevel_show_window_menu_event event = { + .surface = surface, + .seat = seat, + .serial = serial, + .x = x, + .y = y, + }; + + wlr_signal_emit_safe(&surface->toplevel->events.request_show_window_menu, &event); +} + +static void xdg_toplevel_handle_move(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat_resource, + uint32_t serial) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_toplevel_resource(resource); + struct wlr_seat_client *seat = + wlr_seat_client_from_resource(seat_resource); + + if (!surface->configured) { + wl_resource_post_error(surface->toplevel->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "surface has not been configured yet"); + return; + } + + if (!wlr_seat_validate_grab_serial(seat->seat, serial)) { + wlr_log(WLR_DEBUG, "invalid serial for grab"); + return; + } + + struct wlr_xdg_toplevel_move_event event = { + .surface = surface, + .seat = seat, + .serial = serial, + }; + + wlr_signal_emit_safe(&surface->toplevel->events.request_move, &event); +} + +static void xdg_toplevel_handle_resize(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat_resource, + uint32_t serial, uint32_t edges) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_toplevel_resource(resource); + struct wlr_seat_client *seat = + wlr_seat_client_from_resource(seat_resource); + + if (!surface->configured) { + wl_resource_post_error(surface->toplevel->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "surface has not been configured yet"); + return; + } + + if (!wlr_seat_validate_grab_serial(seat->seat, serial)) { + wlr_log(WLR_DEBUG, "invalid serial for grab"); + return; + } + + struct wlr_xdg_toplevel_resize_event event = { + .surface = surface, + .seat = seat, + .serial = serial, + .edges = edges, + }; + + wlr_signal_emit_safe(&surface->toplevel->events.request_resize, &event); +} + +static void xdg_toplevel_handle_set_max_size(struct wl_client *client, + struct wl_resource *resource, int32_t width, int32_t height) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_toplevel_resource(resource); + surface->toplevel->client_pending.max_width = width; + surface->toplevel->client_pending.max_height = height; +} + +static void xdg_toplevel_handle_set_min_size(struct wl_client *client, + struct wl_resource *resource, int32_t width, int32_t height) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_toplevel_resource(resource); + surface->toplevel->client_pending.min_width = width; + surface->toplevel->client_pending.min_height = height; +} + +static void xdg_toplevel_handle_set_maximized(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_toplevel_resource(resource); + surface->toplevel->client_pending.maximized = true; + wlr_signal_emit_safe(&surface->toplevel->events.request_maximize, surface); +} + +static void xdg_toplevel_handle_unset_maximized(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_toplevel_resource(resource); + surface->toplevel->client_pending.maximized = false; + wlr_signal_emit_safe(&surface->toplevel->events.request_maximize, surface); +} + +static void xdg_toplevel_handle_set_fullscreen(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *output_resource) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_toplevel_resource(resource); + + struct wlr_output *output = NULL; + if (output_resource != NULL) { + output = wlr_output_from_resource(output_resource); + } + + surface->toplevel->client_pending.fullscreen = true; + + struct wlr_xdg_toplevel_set_fullscreen_event event = { + .surface = surface, + .fullscreen = true, + .output = output, + }; + + wlr_signal_emit_safe(&surface->toplevel->events.request_fullscreen, &event); +} + +static void xdg_toplevel_handle_unset_fullscreen(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_toplevel_resource(resource); + + surface->toplevel->client_pending.fullscreen = false; + + struct wlr_xdg_toplevel_set_fullscreen_event event = { + .surface = surface, + .fullscreen = false, + .output = NULL, + }; + + wlr_signal_emit_safe(&surface->toplevel->events.request_fullscreen, &event); +} + +static void xdg_toplevel_handle_set_minimized(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_toplevel_resource(resource); + wlr_signal_emit_safe(&surface->toplevel->events.request_minimize, surface); +} + +static void xdg_toplevel_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct xdg_toplevel_interface xdg_toplevel_implementation = { + .destroy = xdg_toplevel_handle_destroy, + .set_parent = xdg_toplevel_handle_set_parent, + .set_title = xdg_toplevel_handle_set_title, + .set_app_id = xdg_toplevel_handle_set_app_id, + .show_window_menu = xdg_toplevel_handle_show_window_menu, + .move = xdg_toplevel_handle_move, + .resize = xdg_toplevel_handle_resize, + .set_max_size = xdg_toplevel_handle_set_max_size, + .set_min_size = xdg_toplevel_handle_set_min_size, + .set_maximized = xdg_toplevel_handle_set_maximized, + .unset_maximized = xdg_toplevel_handle_unset_maximized, + .set_fullscreen = xdg_toplevel_handle_set_fullscreen, + .unset_fullscreen = xdg_toplevel_handle_unset_fullscreen, + .set_minimized = xdg_toplevel_handle_set_minimized, +}; + +static void xdg_toplevel_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_xdg_surface *surface = + wlr_xdg_surface_from_toplevel_resource(resource); + destroy_xdg_toplevel(surface); +} + +const struct wlr_surface_role xdg_toplevel_surface_role = { + .name = "xdg_toplevel", + .commit = handle_xdg_surface_commit, + .precommit = handle_xdg_surface_precommit, +}; + +void create_xdg_toplevel(struct wlr_xdg_surface *xdg_surface, + uint32_t id) { + if (!wlr_surface_set_role(xdg_surface->surface, &xdg_toplevel_surface_role, + xdg_surface, xdg_surface->resource, XDG_WM_BASE_ERROR_ROLE)) { + return; + } + + if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_NONE) { + wl_resource_post_error(xdg_surface->resource, + XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED, + "xdg-surface has already been constructed"); + return; + } + + assert(xdg_surface->toplevel == NULL); + xdg_surface->toplevel = calloc(1, sizeof(struct wlr_xdg_toplevel)); + if (xdg_surface->toplevel == NULL) { + wl_resource_post_no_memory(xdg_surface->resource); + return; + } + xdg_surface->toplevel->base = xdg_surface; + + wl_signal_init(&xdg_surface->toplevel->events.request_maximize); + wl_signal_init(&xdg_surface->toplevel->events.request_fullscreen); + wl_signal_init(&xdg_surface->toplevel->events.request_minimize); + wl_signal_init(&xdg_surface->toplevel->events.request_move); + wl_signal_init(&xdg_surface->toplevel->events.request_resize); + wl_signal_init(&xdg_surface->toplevel->events.request_show_window_menu); + wl_signal_init(&xdg_surface->toplevel->events.set_parent); + wl_signal_init(&xdg_surface->toplevel->events.set_title); + wl_signal_init(&xdg_surface->toplevel->events.set_app_id); + + xdg_surface->toplevel->resource = wl_resource_create( + xdg_surface->client->client, &xdg_toplevel_interface, + wl_resource_get_version(xdg_surface->resource), id); + if (xdg_surface->toplevel->resource == NULL) { + free(xdg_surface->toplevel); + wl_resource_post_no_memory(xdg_surface->resource); + return; + } + wl_resource_set_implementation(xdg_surface->toplevel->resource, + &xdg_toplevel_implementation, xdg_surface, + xdg_toplevel_handle_resource_destroy); + + xdg_surface->role = WLR_XDG_SURFACE_ROLE_TOPLEVEL; +} + +void destroy_xdg_toplevel(struct wlr_xdg_surface *xdg_surface) { + if (xdg_surface == NULL) { + return; + } + assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); + reset_xdg_surface(xdg_surface); +} + +uint32_t wlr_xdg_toplevel_set_size(struct wlr_xdg_surface *surface, + uint32_t width, uint32_t height) { + assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); + surface->toplevel->server_pending.width = width; + surface->toplevel->server_pending.height = height; + + return schedule_xdg_surface_configure(surface); +} + +uint32_t wlr_xdg_toplevel_set_activated(struct wlr_xdg_surface *surface, + bool activated) { + assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); + surface->toplevel->server_pending.activated = activated; + + return schedule_xdg_surface_configure(surface); +} + +uint32_t wlr_xdg_toplevel_set_maximized(struct wlr_xdg_surface *surface, + bool maximized) { + assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); + surface->toplevel->server_pending.maximized = maximized; + + return schedule_xdg_surface_configure(surface); +} + +uint32_t wlr_xdg_toplevel_set_fullscreen(struct wlr_xdg_surface *surface, + bool fullscreen) { + assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); + surface->toplevel->server_pending.fullscreen = fullscreen; + + return schedule_xdg_surface_configure(surface); +} + +uint32_t wlr_xdg_toplevel_set_resizing(struct wlr_xdg_surface *surface, + bool resizing) { + assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); + surface->toplevel->server_pending.resizing = resizing; + + return schedule_xdg_surface_configure(surface); +} + +uint32_t wlr_xdg_toplevel_set_tiled(struct wlr_xdg_surface *surface, + uint32_t tiled) { + assert(surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); + surface->toplevel->server_pending.tiled = tiled; + + return schedule_xdg_surface_configure(surface); +} diff --git a/types/xdg_shell_v6/wlr_xdg_popup_v6.c b/types/xdg_shell_v6/wlr_xdg_popup_v6.c new file mode 100644 index 00000000..097e0253 --- /dev/null +++ b/types/xdg_shell_v6/wlr_xdg_popup_v6.c @@ -0,0 +1,527 @@ +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include "types/wlr_xdg_shell_v6.h" +#include "util/signal.h" + +static struct wlr_xdg_surface_v6 *xdg_popup_grab_get_topmost( + struct wlr_xdg_popup_grab_v6 *grab) { + struct wlr_xdg_popup_v6 *popup; + wl_list_for_each(popup, &grab->popups, grab_link) { + return popup->base; + } + + return NULL; +} + +static void xdg_pointer_grab_end(struct wlr_seat_pointer_grab *grab) { + struct wlr_xdg_popup_grab_v6 *popup_grab = grab->data; + + struct wlr_xdg_popup_v6 *popup, *tmp; + wl_list_for_each_safe(popup, tmp, &popup_grab->popups, grab_link) { + zxdg_popup_v6_send_popup_done(popup->resource); + } + + wlr_seat_pointer_end_grab(grab->seat); + wlr_seat_keyboard_end_grab(grab->seat); +} + +static void xdg_pointer_grab_enter(struct wlr_seat_pointer_grab *grab, + struct wlr_surface *surface, double sx, double sy) { + struct wlr_xdg_popup_grab_v6 *popup_grab = grab->data; + if (wl_resource_get_client(surface->resource) == popup_grab->client) { + wlr_seat_pointer_enter(grab->seat, surface, sx, sy); + } else { + wlr_seat_pointer_clear_focus(grab->seat); + } +} + +static void xdg_pointer_grab_motion(struct wlr_seat_pointer_grab *grab, + uint32_t time, double sx, double sy) { + wlr_seat_pointer_send_motion(grab->seat, time, sx, sy); +} + +static uint32_t xdg_pointer_grab_button(struct wlr_seat_pointer_grab *grab, + uint32_t time, uint32_t button, uint32_t state) { + uint32_t serial = + wlr_seat_pointer_send_button(grab->seat, time, button, state); + if (serial) { + return serial; + } else { + xdg_pointer_grab_end(grab); + return 0; + } +} + +static void xdg_pointer_grab_axis(struct wlr_seat_pointer_grab *grab, + uint32_t time, enum wlr_axis_orientation orientation, double value, + int32_t value_discrete, enum wlr_axis_source source) { + wlr_seat_pointer_send_axis(grab->seat, time, orientation, value, + value_discrete, source); +} + +static void xdg_pointer_grab_cancel(struct wlr_seat_pointer_grab *grab) { + xdg_pointer_grab_end(grab); +} + +static const struct wlr_pointer_grab_interface xdg_pointer_grab_impl = { + .enter = xdg_pointer_grab_enter, + .motion = xdg_pointer_grab_motion, + .button = xdg_pointer_grab_button, + .cancel = xdg_pointer_grab_cancel, + .axis = xdg_pointer_grab_axis, +}; + +static void xdg_keyboard_grab_enter(struct wlr_seat_keyboard_grab *grab, + struct wlr_surface *surface, uint32_t keycodes[], size_t num_keycodes, + struct wlr_keyboard_modifiers *modifiers) { + // keyboard focus should remain on the popup +} + +static void xdg_keyboard_grab_key(struct wlr_seat_keyboard_grab *grab, + uint32_t time, uint32_t key, uint32_t state) { + wlr_seat_keyboard_send_key(grab->seat, time, key, state); +} + +static void xdg_keyboard_grab_modifiers(struct wlr_seat_keyboard_grab *grab, + struct wlr_keyboard_modifiers *modifiers) { + wlr_seat_keyboard_send_modifiers(grab->seat, modifiers); +} + +static void xdg_keyboard_grab_cancel(struct wlr_seat_keyboard_grab *grab) { + wlr_seat_pointer_end_grab(grab->seat); +} + +static const struct wlr_keyboard_grab_interface xdg_keyboard_grab_impl = { + .enter = xdg_keyboard_grab_enter, + .key = xdg_keyboard_grab_key, + .modifiers = xdg_keyboard_grab_modifiers, + .cancel = xdg_keyboard_grab_cancel, +}; + +static void xdg_popup_grab_handle_seat_destroy( + struct wl_listener *listener, void *data) { + struct wlr_xdg_popup_grab_v6 *xdg_grab = + wl_container_of(listener, xdg_grab, seat_destroy); + + wl_list_remove(&xdg_grab->seat_destroy.link); + + struct wlr_xdg_popup_v6 *popup, *next; + wl_list_for_each_safe(popup, next, &xdg_grab->popups, grab_link) { + destroy_xdg_surface_v6(popup->base); + } + + wl_list_remove(&xdg_grab->link); + free(xdg_grab); +} + +struct wlr_xdg_popup_grab_v6 *get_xdg_shell_v6_popup_grab_from_seat( + struct wlr_xdg_shell_v6 *shell, struct wlr_seat *seat) { + struct wlr_xdg_popup_grab_v6 *xdg_grab; + wl_list_for_each(xdg_grab, &shell->popup_grabs, link) { + if (xdg_grab->seat == seat) { + return xdg_grab; + } + } + + xdg_grab = calloc(1, sizeof(struct wlr_xdg_popup_grab_v6)); + if (!xdg_grab) { + return NULL; + } + + xdg_grab->pointer_grab.data = xdg_grab; + xdg_grab->pointer_grab.interface = &xdg_pointer_grab_impl; + xdg_grab->keyboard_grab.data = xdg_grab; + xdg_grab->keyboard_grab.interface = &xdg_keyboard_grab_impl; + + wl_list_init(&xdg_grab->popups); + + wl_list_insert(&shell->popup_grabs, &xdg_grab->link); + xdg_grab->seat = seat; + + xdg_grab->seat_destroy.notify = xdg_popup_grab_handle_seat_destroy; + wl_signal_add(&seat->events.destroy, &xdg_grab->seat_destroy); + + return xdg_grab; +} + + +static const struct zxdg_popup_v6_interface zxdg_popup_v6_implementation; + +static struct wlr_xdg_surface_v6 *xdg_surface_from_xdg_popup_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zxdg_popup_v6_interface, + &zxdg_popup_v6_implementation)); + return wl_resource_get_user_data(resource); +} + +void destroy_xdg_popup_v6(struct wlr_xdg_surface_v6 *surface) { + assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_POPUP); + unmap_xdg_surface_v6(surface); + + wl_resource_set_user_data(surface->popup->resource, NULL); + wl_list_remove(&surface->popup->link); + free(surface->popup); + surface->popup = NULL; + + surface->role = WLR_XDG_SURFACE_V6_ROLE_NONE; +} + +static void xdg_popup_handle_grab(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat_resource, + uint32_t serial) { + struct wlr_xdg_surface_v6 *surface = + xdg_surface_from_xdg_popup_resource(resource); + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + if (!surface) { + return; + } + + if (surface->popup->committed) { + wl_resource_post_error(surface->popup->resource, + ZXDG_POPUP_V6_ERROR_INVALID_GRAB, + "xdg_popup is already mapped"); + return; + } + + struct wlr_xdg_popup_grab_v6 *popup_grab = + get_xdg_shell_v6_popup_grab_from_seat(surface->client->shell, + seat_client->seat); + + struct wlr_xdg_surface_v6 *topmost = xdg_popup_grab_get_topmost(popup_grab); + bool parent_is_toplevel = + surface->popup->parent->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL; + + if ((topmost == NULL && !parent_is_toplevel) || + (topmost != NULL && topmost != surface->popup->parent)) { + wl_resource_post_error(surface->client->resource, + ZXDG_SHELL_V6_ERROR_NOT_THE_TOPMOST_POPUP, + "xdg_popup was not created on the topmost popup"); + return; + } + + popup_grab->client = surface->client->client; + surface->popup->seat = seat_client->seat; + + wl_list_insert(&popup_grab->popups, &surface->popup->grab_link); + + wlr_seat_pointer_start_grab(seat_client->seat, + &popup_grab->pointer_grab); + wlr_seat_keyboard_start_grab(seat_client->seat, + &popup_grab->keyboard_grab); +} + +static void xdg_popup_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_surface_v6 *surface = + xdg_surface_from_xdg_popup_resource(resource); + + if (surface && !wl_list_empty(&surface->popups)) { + wl_resource_post_error(surface->client->resource, + ZXDG_SHELL_V6_ERROR_NOT_THE_TOPMOST_POPUP, + "xdg_popup was destroyed while it was not the topmost popup"); + return; + } + + wl_resource_destroy(resource); +} + +static const struct zxdg_popup_v6_interface zxdg_popup_v6_implementation = { + .destroy = xdg_popup_handle_destroy, + .grab = xdg_popup_handle_grab, +}; + +static void xdg_popup_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_xdg_surface_v6 *surface = + xdg_surface_from_xdg_popup_resource(resource); + if (surface != NULL) { + destroy_xdg_popup_v6(surface); + } +} + +void handle_xdg_surface_v6_popup_committed(struct wlr_xdg_surface_v6 *surface) { + assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_POPUP); + + if (!surface->popup->committed) { + schedule_xdg_surface_v6_configure(surface); + surface->popup->committed = true; + } +} + +const struct wlr_surface_role xdg_popup_v6_surface_role = { + .name = "xdg_popup_v6", + .commit = handle_xdg_surface_v6_commit, + .precommit = handle_xdg_surface_v6_precommit, +}; + +void create_xdg_popup_v6(struct wlr_xdg_surface_v6 *xdg_surface, + struct wlr_xdg_surface_v6 *parent, + struct wlr_xdg_positioner_v6_resource *positioner, int32_t id) { + if (positioner->attrs.size.width == 0 || + positioner->attrs.anchor_rect.width == 0) { + wl_resource_post_error(xdg_surface->resource, + ZXDG_SHELL_V6_ERROR_INVALID_POSITIONER, + "positioner object is not complete"); + return; + } + + if (!wlr_surface_set_role(xdg_surface->surface, &xdg_popup_v6_surface_role, + xdg_surface, xdg_surface->resource, ZXDG_SHELL_V6_ERROR_ROLE)) { + return; + } + + xdg_surface->popup = calloc(1, sizeof(struct wlr_xdg_popup_v6)); + if (!xdg_surface->popup) { + wl_resource_post_no_memory(xdg_surface->resource); + return; + } + + xdg_surface->popup->resource = + wl_resource_create(xdg_surface->client->client, &zxdg_popup_v6_interface, + wl_resource_get_version(xdg_surface->resource), id); + if (xdg_surface->popup->resource == NULL) { + free(xdg_surface->popup); + wl_resource_post_no_memory(xdg_surface->resource); + return; + } + wl_resource_set_implementation(xdg_surface->popup->resource, + &zxdg_popup_v6_implementation, xdg_surface, + xdg_popup_handle_resource_destroy); + + xdg_surface->role = WLR_XDG_SURFACE_V6_ROLE_POPUP; + xdg_surface->popup->base = xdg_surface; + xdg_surface->popup->parent = parent; + xdg_surface->popup->geometry = + wlr_xdg_positioner_v6_get_geometry(&positioner->attrs); + + // positioner properties + memcpy(&xdg_surface->popup->positioner, &positioner->attrs, + sizeof(struct wlr_xdg_positioner_v6)); + + wl_list_insert(&parent->popups, &xdg_surface->popup->link); + + wlr_signal_emit_safe(&parent->events.new_popup, xdg_surface->popup); +} + +void wlr_xdg_popup_v6_get_anchor_point(struct wlr_xdg_popup_v6 *popup, + int *root_sx, int *root_sy) { + struct wlr_box rect = popup->positioner.anchor_rect; + enum zxdg_positioner_v6_anchor anchor = popup->positioner.anchor; + int sx = 0, sy = 0; + + if (anchor == ZXDG_POSITIONER_V6_ANCHOR_NONE) { + sx = (rect.x + rect.width) / 2; + sy = (rect.y + rect.height) / 2; + } else if (anchor == ZXDG_POSITIONER_V6_ANCHOR_TOP) { + sx = (rect.x + rect.width) / 2; + sy = rect.y; + } else if (anchor == ZXDG_POSITIONER_V6_ANCHOR_BOTTOM) { + sx = (rect.x + rect.width) / 2; + sy = rect.y + rect.height; + } else if (anchor == ZXDG_POSITIONER_V6_ANCHOR_LEFT) { + sx = rect.x; + sy = (rect.y + rect.height) / 2; + } else if (anchor == ZXDG_POSITIONER_V6_ANCHOR_RIGHT) { + sx = rect.x + rect.width; + sy = (rect.y + rect.height) / 2; + } else if (anchor == (ZXDG_POSITIONER_V6_ANCHOR_TOP | + ZXDG_POSITIONER_V6_ANCHOR_LEFT)) { + sx = rect.x; + sy = rect.y; + } else if (anchor == (ZXDG_POSITIONER_V6_ANCHOR_TOP | + ZXDG_POSITIONER_V6_ANCHOR_RIGHT)) { + sx = rect.x + rect.width; + sy = rect.y; + } else if (anchor == (ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | + ZXDG_POSITIONER_V6_ANCHOR_LEFT)) { + sx = rect.x; + sy = rect.y + rect.height; + } else if (anchor == (ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | + ZXDG_POSITIONER_V6_ANCHOR_RIGHT)) { + sx = rect.x + rect.width; + sy = rect.y + rect.height; + } + + *root_sx = sx; + *root_sy = sy; +} + +void wlr_xdg_popup_v6_get_toplevel_coords(struct wlr_xdg_popup_v6 *popup, + int popup_sx, int popup_sy, int *toplevel_sx, int *toplevel_sy) { + struct wlr_xdg_surface_v6 *parent = popup->parent; + while (parent != NULL && parent->role == WLR_XDG_SURFACE_V6_ROLE_POPUP) { + popup_sx += parent->popup->geometry.x; + popup_sy += parent->popup->geometry.y; + parent = parent->popup->parent; + } + assert(parent); + + *toplevel_sx = popup_sx + parent->geometry.x; + *toplevel_sy = popup_sy + parent->geometry.y; +} + +static void xdg_popup_v6_box_constraints(struct wlr_xdg_popup_v6 *popup, + struct wlr_box *toplevel_sx_box, int *offset_x, int *offset_y) { + int popup_width = popup->geometry.width; + int popup_height = popup->geometry.height; + int anchor_sx = 0, anchor_sy = 0; + wlr_xdg_popup_v6_get_anchor_point(popup, &anchor_sx, &anchor_sy); + int popup_sx = 0, popup_sy = 0; + wlr_xdg_popup_v6_get_toplevel_coords(popup, popup->geometry.x, + popup->geometry.y, &popup_sx, &popup_sy); + *offset_x = 0, *offset_y = 0; + + if (popup_sx < toplevel_sx_box->x) { + *offset_x = toplevel_sx_box->x - popup_sx; + } else if (popup_sx + popup_width > + toplevel_sx_box->x + toplevel_sx_box->width) { + *offset_x = toplevel_sx_box->x + toplevel_sx_box->width - + (popup_sx + popup_width); + } + + if (popup_sy < toplevel_sx_box->y) { + *offset_y = toplevel_sx_box->y - popup_sy; + } else if (popup_sy + popup_height > + toplevel_sx_box->y + toplevel_sx_box->height) { + *offset_y = toplevel_sx_box->y + toplevel_sx_box->height - + (popup_sy + popup_height); + } +} + +static bool xdg_popup_v6_unconstrain_flip(struct wlr_xdg_popup_v6 *popup, + struct wlr_box *toplevel_sx_box) { + int offset_x = 0, offset_y = 0; + xdg_popup_v6_box_constraints(popup, toplevel_sx_box, + &offset_x, &offset_y); + + if (!offset_x && !offset_y) { + return true; + } + + bool flip_x = offset_x && + (popup->positioner.constraint_adjustment & + ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_X); + + bool flip_y = offset_y && + (popup->positioner.constraint_adjustment & + ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y); + + if (flip_x) { + wlr_positioner_v6_invert_x(&popup->positioner); + } + if (flip_y) { + wlr_positioner_v6_invert_y(&popup->positioner); + } + + popup->geometry = + wlr_xdg_positioner_v6_get_geometry(&popup->positioner); + + xdg_popup_v6_box_constraints(popup, toplevel_sx_box, + &offset_x, &offset_y); + + if (!offset_x && !offset_y) { + // no longer constrained + return true; + } + + // revert the positioner back if it didn't fix it and go to the next part + if (flip_x) { + wlr_positioner_v6_invert_x(&popup->positioner); + } + if (flip_y) { + wlr_positioner_v6_invert_y(&popup->positioner); + } + + popup->geometry = + wlr_xdg_positioner_v6_get_geometry(&popup->positioner); + + return false; +} + +static bool xdg_popup_v6_unconstrain_slide(struct wlr_xdg_popup_v6 *popup, + struct wlr_box *toplevel_sx_box) { + int offset_x = 0, offset_y = 0; + xdg_popup_v6_box_constraints(popup, toplevel_sx_box, + &offset_x, &offset_y); + + if (!offset_x && !offset_y) { + return true; + } + + bool slide_x = offset_x && + (popup->positioner.constraint_adjustment & + ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X); + + bool slide_y = offset_y && + (popup->positioner.constraint_adjustment & + ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y); + + if (slide_x) { + popup->geometry.x += offset_x; + } + + if (slide_y) { + popup->geometry.y += offset_y; + } + + int toplevel_x = 0, toplevel_y = 0; + wlr_xdg_popup_v6_get_toplevel_coords(popup, popup->geometry.x, + popup->geometry.y, &toplevel_x, &toplevel_y); + + if (slide_x && toplevel_x < toplevel_sx_box->x) { + popup->geometry.x += toplevel_sx_box->x - toplevel_x; + } + if (slide_y && toplevel_y < toplevel_sx_box->y) { + popup->geometry.y += toplevel_sx_box->y - toplevel_y; + } + + xdg_popup_v6_box_constraints(popup, toplevel_sx_box, + &offset_x, &offset_y); + + return !offset_x && !offset_y; +} + +static bool xdg_popup_v6_unconstrain_resize(struct wlr_xdg_popup_v6 *popup, + struct wlr_box *toplevel_sx_box) { + int offset_x, offset_y; + xdg_popup_v6_box_constraints(popup, toplevel_sx_box, + &offset_x, &offset_y); + + if (!offset_x && !offset_y) { + return true; + } + + bool resize_x = offset_x && + (popup->positioner.constraint_adjustment & + ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_X); + + bool resize_y = offset_y && + (popup->positioner.constraint_adjustment & + ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_Y); + + if (resize_x) { + popup->geometry.width -= offset_x; + } + if (resize_y) { + popup->geometry.height -= offset_y; + } + + xdg_popup_v6_box_constraints(popup, toplevel_sx_box, + &offset_x, &offset_y); + + return !offset_x && !offset_y; +} + +void wlr_xdg_popup_v6_unconstrain_from_box(struct wlr_xdg_popup_v6 *popup, + struct wlr_box *toplevel_sx_box) { + if (xdg_popup_v6_unconstrain_flip(popup, toplevel_sx_box)) { + return; + } + if (xdg_popup_v6_unconstrain_slide(popup, toplevel_sx_box)) { + return; + } + if (xdg_popup_v6_unconstrain_resize(popup, toplevel_sx_box)) { + return; + } +} diff --git a/types/xdg_shell_v6/wlr_xdg_positioner_v6.c b/types/xdg_shell_v6/wlr_xdg_positioner_v6.c new file mode 100644 index 00000000..837f9290 --- /dev/null +++ b/types/xdg_shell_v6/wlr_xdg_positioner_v6.c @@ -0,0 +1,234 @@ +#include <assert.h> +#include <stdlib.h> +#include "types/wlr_xdg_shell_v6.h" + +static const struct zxdg_positioner_v6_interface + zxdg_positioner_v6_implementation; + +struct wlr_xdg_positioner_v6_resource *get_xdg_positioner_v6_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zxdg_positioner_v6_interface, + &zxdg_positioner_v6_implementation)); + return wl_resource_get_user_data(resource); +} + +static void xdg_positioner_destroy(struct wl_resource *resource) { + struct wlr_xdg_positioner_v6_resource *positioner = + get_xdg_positioner_v6_from_resource(resource); + free(positioner); +} + +static void xdg_positioner_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void xdg_positioner_handle_set_size(struct wl_client *client, + struct wl_resource *resource, int32_t width, int32_t height) { + struct wlr_xdg_positioner_v6_resource *positioner = + get_xdg_positioner_v6_from_resource(resource); + + if (width < 1 || height < 1) { + wl_resource_post_error(resource, + ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, + "width and height must be positive and non-zero"); + return; + } + + positioner->attrs.size.width = width; + positioner->attrs.size.height = height; +} + +static void xdg_positioner_handle_set_anchor_rect(struct wl_client *client, + struct wl_resource *resource, int32_t x, int32_t y, int32_t width, + int32_t height) { + struct wlr_xdg_positioner_v6_resource *positioner = + get_xdg_positioner_v6_from_resource(resource); + + if (width < 1 || height < 1) { + wl_resource_post_error(resource, + ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, + "width and height must be positive and non-zero"); + return; + } + + positioner->attrs.anchor_rect.x = x; + positioner->attrs.anchor_rect.y = y; + positioner->attrs.anchor_rect.width = width; + positioner->attrs.anchor_rect.height = height; +} + +static void xdg_positioner_handle_set_anchor(struct wl_client *client, + struct wl_resource *resource, uint32_t anchor) { + struct wlr_xdg_positioner_v6_resource *positioner = + get_xdg_positioner_v6_from_resource(resource); + + if (((anchor & ZXDG_POSITIONER_V6_ANCHOR_TOP ) && + (anchor & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM)) || + ((anchor & ZXDG_POSITIONER_V6_ANCHOR_LEFT) && + (anchor & ZXDG_POSITIONER_V6_ANCHOR_RIGHT))) { + wl_resource_post_error(resource, + ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, + "same-axis values are not allowed"); + return; + } + + positioner->attrs.anchor = anchor; +} + +static void xdg_positioner_handle_set_gravity(struct wl_client *client, + struct wl_resource *resource, uint32_t gravity) { + struct wlr_xdg_positioner_v6_resource *positioner = + get_xdg_positioner_v6_from_resource(resource); + + if (((gravity & ZXDG_POSITIONER_V6_GRAVITY_TOP) && + (gravity & ZXDG_POSITIONER_V6_GRAVITY_BOTTOM)) || + ((gravity & ZXDG_POSITIONER_V6_GRAVITY_LEFT) && + (gravity & ZXDG_POSITIONER_V6_GRAVITY_RIGHT))) { + wl_resource_post_error(resource, + ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, + "same-axis values are not allowed"); + return; + } + + positioner->attrs.gravity = gravity; +} + +static void xdg_positioner_handle_set_constraint_adjustment( + struct wl_client *client, struct wl_resource *resource, + uint32_t constraint_adjustment) { + struct wlr_xdg_positioner_v6_resource *positioner = + get_xdg_positioner_v6_from_resource(resource); + + positioner->attrs.constraint_adjustment = constraint_adjustment; +} + +static void xdg_positioner_handle_set_offset(struct wl_client *client, + struct wl_resource *resource, int32_t x, int32_t y) { + struct wlr_xdg_positioner_v6_resource *positioner = + get_xdg_positioner_v6_from_resource(resource); + + positioner->attrs.offset.x = x; + positioner->attrs.offset.y = y; +} + +static const struct zxdg_positioner_v6_interface + zxdg_positioner_v6_implementation = { + .destroy = xdg_positioner_handle_destroy, + .set_size = xdg_positioner_handle_set_size, + .set_anchor_rect = xdg_positioner_handle_set_anchor_rect, + .set_anchor = xdg_positioner_handle_set_anchor, + .set_gravity = xdg_positioner_handle_set_gravity, + .set_constraint_adjustment = + xdg_positioner_handle_set_constraint_adjustment, + .set_offset = xdg_positioner_handle_set_offset, +}; + +struct wlr_box wlr_xdg_positioner_v6_get_geometry( + struct wlr_xdg_positioner_v6 *positioner) { + struct wlr_box geometry = { + .x = positioner->offset.x, + .y = positioner->offset.y, + .width = positioner->size.width, + .height = positioner->size.height, + }; + + if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_TOP) { + geometry.y += positioner->anchor_rect.y; + } else if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM) { + geometry.y += + positioner->anchor_rect.y + positioner->anchor_rect.height; + } else { + geometry.y += + positioner->anchor_rect.y + positioner->anchor_rect.height / 2; + } + + if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_LEFT) { + geometry.x += positioner->anchor_rect.x; + } else if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_RIGHT) { + geometry.x += positioner->anchor_rect.x + positioner->anchor_rect.width; + } else { + geometry.x += + positioner->anchor_rect.x + positioner->anchor_rect.width / 2; + } + + if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_TOP) { + geometry.y -= geometry.height; + } else if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_BOTTOM) { + geometry.y = geometry.y; + } else { + geometry.y -= geometry.height / 2; + } + + if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_LEFT) { + geometry.x -= geometry.width; + } else if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_RIGHT) { + geometry.x = geometry.x; + } else { + geometry.x -= geometry.width / 2; + } + + if (positioner->constraint_adjustment == + ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_NONE) { + return geometry; + } + + return geometry; +} + +void wlr_positioner_v6_invert_x(struct wlr_xdg_positioner_v6 *positioner) { + if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_LEFT) { + positioner->anchor &= ~ZXDG_POSITIONER_V6_ANCHOR_LEFT; + positioner->anchor |= ZXDG_POSITIONER_V6_ANCHOR_RIGHT; + } else if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_RIGHT) { + positioner->anchor &= ~ZXDG_POSITIONER_V6_ANCHOR_RIGHT; + positioner->anchor |= ZXDG_POSITIONER_V6_ANCHOR_LEFT; + } + + if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_RIGHT) { + positioner->gravity &= ~ZXDG_POSITIONER_V6_GRAVITY_RIGHT; + positioner->gravity |= ZXDG_POSITIONER_V6_GRAVITY_LEFT; + } else if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_LEFT) { + positioner->gravity &= ~ZXDG_POSITIONER_V6_GRAVITY_LEFT; + positioner->gravity |= ZXDG_POSITIONER_V6_GRAVITY_RIGHT; + } +} + +void wlr_positioner_v6_invert_y( + struct wlr_xdg_positioner_v6 *positioner) { + if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_TOP) { + positioner->anchor &= ~ZXDG_POSITIONER_V6_ANCHOR_TOP; + positioner->anchor |= ZXDG_POSITIONER_V6_ANCHOR_BOTTOM; + } else if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM) { + positioner->anchor &= ~ZXDG_POSITIONER_V6_ANCHOR_BOTTOM; + positioner->anchor |= ZXDG_POSITIONER_V6_ANCHOR_TOP; + } + + if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_TOP) { + positioner->gravity &= ~ZXDG_POSITIONER_V6_GRAVITY_TOP; + positioner->gravity |= ZXDG_POSITIONER_V6_GRAVITY_BOTTOM; + } else if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_BOTTOM) { + positioner->gravity &= ~ZXDG_POSITIONER_V6_GRAVITY_BOTTOM; + positioner->gravity |= ZXDG_POSITIONER_V6_GRAVITY_TOP; + } +} + +void create_xdg_positioner_v6(struct wlr_xdg_client_v6 *client, uint32_t id) { + struct wlr_xdg_positioner_v6_resource *positioner = + calloc(1, sizeof(struct wlr_xdg_positioner_v6_resource)); + if (positioner == NULL) { + wl_client_post_no_memory(client->client); + return; + } + + positioner->resource = wl_resource_create(client->client, + &zxdg_positioner_v6_interface, + wl_resource_get_version(client->resource), id); + if (positioner->resource == NULL) { + free(positioner); + wl_client_post_no_memory(client->client); + return; + } + wl_resource_set_implementation(positioner->resource, + &zxdg_positioner_v6_implementation, positioner, xdg_positioner_destroy); +} diff --git a/types/xdg_shell_v6/wlr_xdg_shell_v6.c b/types/xdg_shell_v6/wlr_xdg_shell_v6.c new file mode 100644 index 00000000..fce8e96a --- /dev/null +++ b/types/xdg_shell_v6/wlr_xdg_shell_v6.c @@ -0,0 +1,175 @@ +#include <assert.h> +#include <stdlib.h> +#include "types/wlr_xdg_shell_v6.h" +#include "util/signal.h" + +#define SHELL_VERSION 1 + +static const struct zxdg_shell_v6_interface xdg_shell_impl; + +static struct wlr_xdg_client_v6 *xdg_client_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zxdg_shell_v6_interface, + &xdg_shell_impl)); + return wl_resource_get_user_data(resource); +} + +static void xdg_shell_handle_create_positioner(struct wl_client *wl_client, + struct wl_resource *resource, uint32_t id) { + struct wlr_xdg_client_v6 *client = + xdg_client_from_resource(resource); + create_xdg_positioner_v6(client, id); +} + +static void xdg_shell_handle_get_xdg_surface(struct wl_client *wl_client, + struct wl_resource *client_resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_xdg_client_v6 *client = + xdg_client_from_resource(client_resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + create_xdg_surface_v6(client, surface, id); +} + +static void xdg_shell_handle_pong(struct wl_client *wl_client, + struct wl_resource *resource, uint32_t serial) { + struct wlr_xdg_client_v6 *client = xdg_client_from_resource(resource); + + if (client->ping_serial != serial) { + return; + } + + wl_event_source_timer_update(client->ping_timer, 0); + client->ping_serial = 0; +} + +static void xdg_shell_handle_destroy(struct wl_client *wl_client, + struct wl_resource *resource) { + struct wlr_xdg_client_v6 *client = xdg_client_from_resource(resource); + + if (!wl_list_empty(&client->surfaces)) { + wl_resource_post_error(client->resource, + ZXDG_SHELL_V6_ERROR_DEFUNCT_SURFACES, + "xdg_wm_base was destroyed before children"); + return; + } + + wl_resource_destroy(resource); +} + +static const struct zxdg_shell_v6_interface xdg_shell_impl = { + .destroy = xdg_shell_handle_destroy, + .create_positioner = xdg_shell_handle_create_positioner, + .get_xdg_surface = xdg_shell_handle_get_xdg_surface, + .pong = xdg_shell_handle_pong, +}; + +static void xdg_client_v6_handle_resource_destroy( + struct wl_resource *resource) { + struct wlr_xdg_client_v6 *client = xdg_client_from_resource(resource); + + struct wlr_xdg_surface_v6 *surface, *tmp = NULL; + wl_list_for_each_safe(surface, tmp, &client->surfaces, link) { + wl_resource_destroy(surface->resource); + } + + if (client->ping_timer != NULL) { + wl_event_source_remove(client->ping_timer); + } + + wl_list_remove(&client->link); + free(client); +} + +static int xdg_client_v6_ping_timeout(void *user_data) { + struct wlr_xdg_client_v6 *client = user_data; + + struct wlr_xdg_surface_v6 *surface; + wl_list_for_each(surface, &client->surfaces, link) { + wlr_signal_emit_safe(&surface->events.ping_timeout, surface); + } + + client->ping_serial = 0; + return 1; +} + +static void xdg_shell_bind(struct wl_client *wl_client, void *data, + uint32_t version, uint32_t id) { + struct wlr_xdg_shell_v6 *xdg_shell = data; + assert(wl_client && xdg_shell); + + struct wlr_xdg_client_v6 *client = + calloc(1, sizeof(struct wlr_xdg_client_v6)); + if (client == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + wl_list_init(&client->surfaces); + + client->resource = + wl_resource_create(wl_client, &zxdg_shell_v6_interface, version, id); + if (client->resource == NULL) { + free(client); + wl_client_post_no_memory(wl_client); + return; + } + client->client = wl_client; + client->shell = xdg_shell; + + wl_resource_set_implementation(client->resource, &xdg_shell_impl, client, + xdg_client_v6_handle_resource_destroy); + wl_list_insert(&xdg_shell->clients, &client->link); + + struct wl_display *display = wl_client_get_display(client->client); + struct wl_event_loop *loop = wl_display_get_event_loop(display); + client->ping_timer = wl_event_loop_add_timer(loop, + xdg_client_v6_ping_timeout, client); + if (client->ping_timer == NULL) { + wl_client_post_no_memory(client->client); + } +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_shell_v6 *xdg_shell = + wl_container_of(listener, xdg_shell, display_destroy); + wlr_xdg_shell_v6_destroy(xdg_shell); +} + +struct wlr_xdg_shell_v6 *wlr_xdg_shell_v6_create(struct wl_display *display) { + struct wlr_xdg_shell_v6 *xdg_shell = + calloc(1, sizeof(struct wlr_xdg_shell_v6)); + if (!xdg_shell) { + return NULL; + } + + xdg_shell->ping_timeout = 10000; + + wl_list_init(&xdg_shell->clients); + wl_list_init(&xdg_shell->popup_grabs); + + struct wl_global *global = wl_global_create(display, + &zxdg_shell_v6_interface, SHELL_VERSION, xdg_shell, xdg_shell_bind); + if (!global) { + free(xdg_shell); + return NULL; + } + xdg_shell->global = global; + + wl_signal_init(&xdg_shell->events.new_surface); + wl_signal_init(&xdg_shell->events.destroy); + + xdg_shell->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &xdg_shell->display_destroy); + + return xdg_shell; +} + +void wlr_xdg_shell_v6_destroy(struct wlr_xdg_shell_v6 *xdg_shell) { + if (!xdg_shell) { + return; + } + wlr_signal_emit_safe(&xdg_shell->events.destroy, xdg_shell); + wl_list_remove(&xdg_shell->display_destroy.link); + wl_global_destroy(xdg_shell->global); + free(xdg_shell); +} diff --git a/types/xdg_shell_v6/wlr_xdg_surface_v6.c b/types/xdg_shell_v6/wlr_xdg_surface_v6.c new file mode 100644 index 00000000..ed87d6fe --- /dev/null +++ b/types/xdg_shell_v6/wlr_xdg_surface_v6.c @@ -0,0 +1,602 @@ +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <wlr/util/log.h> +#include "types/wlr_xdg_shell_v6.h" +#include "util/signal.h" + +bool wlr_surface_is_xdg_surface_v6(struct wlr_surface *surface) { + return surface->role == &xdg_toplevel_v6_surface_role || + surface->role == &xdg_popup_v6_surface_role; +} + +struct wlr_xdg_surface_v6 *wlr_xdg_surface_v6_from_wlr_surface( + struct wlr_surface *surface) { + assert(wlr_surface_is_xdg_surface_v6(surface)); + return (struct wlr_xdg_surface_v6 *)surface->role_data; +} + +static const struct zxdg_surface_v6_interface zxdg_surface_v6_implementation; + +static struct wlr_xdg_surface_v6 *xdg_surface_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zxdg_surface_v6_interface, + &zxdg_surface_v6_implementation)); + return wl_resource_get_user_data(resource); +} + +static void xdg_surface_configure_destroy( + struct wlr_xdg_surface_v6_configure *configure) { + if (configure == NULL) { + return; + } + wl_list_remove(&configure->link); + free(configure->toplevel_state); + free(configure); +} + +void unmap_xdg_surface_v6(struct wlr_xdg_surface_v6 *surface) { + assert(surface->role != WLR_XDG_SURFACE_V6_ROLE_NONE); + + // TODO: probably need to ungrab before this event + if (surface->mapped) { + wlr_signal_emit_safe(&surface->events.unmap, surface); + } + + switch (surface->role) { + case WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL: + free(surface->toplevel->title); + surface->toplevel->title = NULL; + free(surface->toplevel->app_id); + surface->toplevel->app_id = NULL; + break; + case WLR_XDG_SURFACE_V6_ROLE_POPUP: + if (surface->popup->seat != NULL) { + struct wlr_xdg_popup_grab_v6 *grab = + get_xdg_shell_v6_popup_grab_from_seat(surface->client->shell, + surface->popup->seat); + + wl_list_remove(&surface->popup->grab_link); + + if (wl_list_empty(&grab->popups)) { + if (grab->seat->pointer_state.grab == &grab->pointer_grab) { + wlr_seat_pointer_end_grab(grab->seat); + } + if (grab->seat->keyboard_state.grab == &grab->keyboard_grab) { + wlr_seat_keyboard_end_grab(grab->seat); + } + } + + surface->popup->seat = NULL; + } + break; + case WLR_XDG_SURFACE_V6_ROLE_NONE: + assert(false && "not reached"); + } + + struct wlr_xdg_surface_v6_configure *configure, *tmp; + wl_list_for_each_safe(configure, tmp, &surface->configure_list, link) { + xdg_surface_configure_destroy(configure); + } + + surface->configured = surface->mapped = false; + surface->configure_serial = 0; + if (surface->configure_idle) { + wl_event_source_remove(surface->configure_idle); + surface->configure_idle = NULL; + } + surface->configure_next_serial = 0; + + surface->has_next_geometry = false; + memset(&surface->geometry, 0, sizeof(struct wlr_box)); + memset(&surface->next_geometry, 0, sizeof(struct wlr_box)); +} + +void destroy_xdg_surface_v6(struct wlr_xdg_surface_v6 *surface) { + if (surface->role != WLR_XDG_SURFACE_V6_ROLE_NONE) { + unmap_xdg_surface_v6(surface); + } + + wlr_signal_emit_safe(&surface->events.destroy, surface); + + struct wlr_xdg_popup_v6 *popup_state, *next; + wl_list_for_each_safe(popup_state, next, &surface->popups, link) { + zxdg_popup_v6_send_popup_done(popup_state->resource); + destroy_xdg_popup_v6(popup_state->base); + } + + switch (surface->role) { + case WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL: + destroy_xdg_toplevel_v6(surface); + break; + case WLR_XDG_SURFACE_V6_ROLE_POPUP: + destroy_xdg_popup_v6(surface); + break; + case WLR_XDG_SURFACE_V6_ROLE_NONE: + // This space is intentionally left blank + break; + } + + wl_resource_set_user_data(surface->resource, NULL); + surface->surface->role_data = NULL; + wl_list_remove(&surface->link); + wl_list_remove(&surface->surface_destroy.link); + wl_list_remove(&surface->surface_commit.link); + free(surface); +} + +static void xdg_surface_handle_get_toplevel(struct wl_client *client, + struct wl_resource *resource, uint32_t id) { + struct wlr_xdg_surface_v6 *xdg_surface = xdg_surface_from_resource(resource); + create_xdg_toplevel_v6(xdg_surface, id); +} + +static void xdg_surface_handle_get_popup(struct wl_client *wl_client, + struct wl_resource *resource, uint32_t id, + struct wl_resource *parent_resource, + struct wl_resource *positioner_resource) { + struct wlr_xdg_surface_v6 *xdg_surface = + xdg_surface_from_resource(resource); + struct wlr_xdg_surface_v6 *parent = + xdg_surface_from_resource(parent_resource); + struct wlr_xdg_positioner_v6_resource *positioner = + get_xdg_positioner_v6_from_resource(positioner_resource); + + create_xdg_popup_v6(xdg_surface, parent, positioner, id); +} + +static void xdg_surface_handle_ack_configure(struct wl_client *client, + struct wl_resource *resource, uint32_t serial) { + struct wlr_xdg_surface_v6 *surface = xdg_surface_from_resource(resource); + + if (surface->role == WLR_XDG_SURFACE_V6_ROLE_NONE) { + wl_resource_post_error(surface->resource, + ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, + "xdg_surface must have a role"); + return; + } + + bool found = false; + struct wlr_xdg_surface_v6_configure *configure, *tmp; + wl_list_for_each_safe(configure, tmp, &surface->configure_list, link) { + if (configure->serial < serial) { + xdg_surface_configure_destroy(configure); + } else if (configure->serial == serial) { + found = true; + break; + } else { + break; + } + } + if (!found) { + wl_resource_post_error(surface->client->resource, + ZXDG_SHELL_V6_ERROR_INVALID_SURFACE_STATE, + "wrong configure serial: %u", serial); + return; + } + + switch (surface->role) { + case WLR_XDG_SURFACE_V6_ROLE_NONE: + assert(0 && "not reached"); + break; + case WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL: + handle_xdg_toplevel_v6_ack_configure(surface, configure); + break; + case WLR_XDG_SURFACE_V6_ROLE_POPUP: + break; + } + + surface->configured = true; + surface->configure_serial = serial; + + xdg_surface_configure_destroy(configure); +} + +static void xdg_surface_handle_set_window_geometry(struct wl_client *client, + struct wl_resource *resource, int32_t x, int32_t y, int32_t width, + int32_t height) { + struct wlr_xdg_surface_v6 *surface = xdg_surface_from_resource(resource); + + if (surface->role == WLR_XDG_SURFACE_V6_ROLE_NONE) { + wl_resource_post_error(surface->resource, + ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, + "xdg_surface must have a role"); + return; + } + + if (width <= 0 || height <= 0) { + wlr_log(WLR_ERROR, "Client tried to set invalid geometry"); + wl_resource_post_error(resource, -1, "Tried to set invalid xdg-surface geometry"); + } + + + surface->has_next_geometry = true; + surface->next_geometry.height = height; + surface->next_geometry.width = width; + surface->next_geometry.x = x; + surface->next_geometry.y = y; +} + +static void xdg_surface_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_surface_v6 *surface = xdg_surface_from_resource(resource); + + if (surface->role != WLR_XDG_SURFACE_V6_ROLE_NONE) { + wlr_log(WLR_ERROR, "Tried to destroy an xdg_surface before its role " + "object"); + return; + } + + wl_resource_destroy(resource); +} + +static const struct zxdg_surface_v6_interface zxdg_surface_v6_implementation = { + .destroy = xdg_surface_handle_destroy, + .get_toplevel = xdg_surface_handle_get_toplevel, + .get_popup = xdg_surface_handle_get_popup, + .ack_configure = xdg_surface_handle_ack_configure, + .set_window_geometry = xdg_surface_handle_set_window_geometry, +}; + +static void xdg_surface_send_configure(void *user_data) { + struct wlr_xdg_surface_v6 *surface = user_data; + + surface->configure_idle = NULL; + + struct wlr_xdg_surface_v6_configure *configure = + calloc(1, sizeof(struct wlr_xdg_surface_v6_configure)); + if (configure == NULL) { + wl_client_post_no_memory(surface->client->client); + return; + } + + wl_list_insert(surface->configure_list.prev, &configure->link); + configure->serial = surface->configure_next_serial; + + switch (surface->role) { + case WLR_XDG_SURFACE_V6_ROLE_NONE: + assert(0 && "not reached"); + break; + case WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL: + send_xdg_toplevel_v6_configure(surface, configure); + break; + case WLR_XDG_SURFACE_V6_ROLE_POPUP: + zxdg_popup_v6_send_configure(surface->popup->resource, + surface->popup->geometry.x, + surface->popup->geometry.y, + surface->popup->geometry.width, + surface->popup->geometry.height); + break; + } + + zxdg_surface_v6_send_configure(surface->resource, configure->serial); +} + +uint32_t schedule_xdg_surface_v6_configure(struct wlr_xdg_surface_v6 *surface) { + struct wl_display *display = wl_client_get_display(surface->client->client); + struct wl_event_loop *loop = wl_display_get_event_loop(display); + bool pending_same = false; + + switch (surface->role) { + case WLR_XDG_SURFACE_V6_ROLE_NONE: + assert(0 && "not reached"); + break; + case WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL: + pending_same = + compare_xdg_surface_v6_toplevel_state(surface->toplevel); + break; + case WLR_XDG_SURFACE_V6_ROLE_POPUP: + break; + } + + if (surface->configure_idle != NULL) { + if (!pending_same) { + // configure request already scheduled + return surface->configure_next_serial; + } + + // configure request not necessary anymore + wl_event_source_remove(surface->configure_idle); + surface->configure_idle = NULL; + return 0; + } else { + if (pending_same) { + // configure request not necessary + return 0; + } + + surface->configure_next_serial = wl_display_next_serial(display); + surface->configure_idle = wl_event_loop_add_idle(loop, + xdg_surface_send_configure, surface); + return surface->configure_next_serial; + } +} + +void wlr_xdg_surface_v6_ping(struct wlr_xdg_surface_v6 *surface) { + if (surface->client->ping_serial != 0) { + // already pinged + return; + } + + surface->client->ping_serial = + wl_display_next_serial(wl_client_get_display(surface->client->client)); + wl_event_source_timer_update(surface->client->ping_timer, + surface->client->shell->ping_timeout); + zxdg_shell_v6_send_ping(surface->client->resource, + surface->client->ping_serial); +} + +void wlr_xdg_surface_v6_send_close(struct wlr_xdg_surface_v6 *surface) { + switch (surface->role) { + case WLR_XDG_SURFACE_V6_ROLE_NONE: + assert(0 && "not reached"); + break; + case WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL: + if (surface->toplevel) { + zxdg_toplevel_v6_send_close(surface->toplevel->resource); + } + break; + case WLR_XDG_SURFACE_V6_ROLE_POPUP: + if (surface->popup) { + zxdg_popup_v6_send_popup_done(surface->popup->resource); + } + break; + } +} + +static void xdg_surface_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_xdg_surface_v6 *xdg_surface = + wl_container_of(listener, xdg_surface, surface_destroy); + destroy_xdg_surface_v6(xdg_surface); +} + +static void xdg_surface_handle_surface_commit(struct wl_listener *listener, + void *data) { + struct wlr_xdg_surface_v6 *surface = + wl_container_of(listener, surface, surface_commit); + + if (wlr_surface_has_buffer(surface->surface) && !surface->configured) { + wl_resource_post_error(surface->resource, + ZXDG_SURFACE_V6_ERROR_UNCONFIGURED_BUFFER, + "xdg_surface has never been configured"); + return; + } + + if (surface->role == WLR_XDG_SURFACE_V6_ROLE_NONE) { + wl_resource_post_error(surface->resource, + ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, + "xdg_surface must have a role"); + return; + } +} + +void handle_xdg_surface_v6_commit(struct wlr_surface *wlr_surface) { + struct wlr_xdg_surface_v6 *surface = + wlr_xdg_surface_v6_from_wlr_surface(wlr_surface); + if (surface == NULL) { + return; + } + + if (surface->has_next_geometry) { + surface->has_next_geometry = false; + surface->geometry.x = surface->next_geometry.x; + surface->geometry.y = surface->next_geometry.y; + surface->geometry.width = surface->next_geometry.width; + surface->geometry.height = surface->next_geometry.height; + } + + switch (surface->role) { + case WLR_XDG_SURFACE_V6_ROLE_NONE: + assert(false); + case WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL: + handle_xdg_surface_v6_toplevel_committed(surface); + break; + case WLR_XDG_SURFACE_V6_ROLE_POPUP: + handle_xdg_surface_v6_popup_committed(surface); + break; + } + + if (!surface->added) { + surface->added = true; + wlr_signal_emit_safe(&surface->client->shell->events.new_surface, + surface); + } + if (surface->configured && wlr_surface_has_buffer(surface->surface) && + !surface->mapped) { + surface->mapped = true; + wlr_signal_emit_safe(&surface->events.map, surface); + } +} + +void handle_xdg_surface_v6_precommit(struct wlr_surface *wlr_surface) { + struct wlr_xdg_surface_v6 *surface = + wlr_xdg_surface_v6_from_wlr_surface(wlr_surface); + if (surface == NULL) { + return; + } + + if (wlr_surface->pending.committed & WLR_SURFACE_STATE_BUFFER && + wlr_surface->pending.buffer_resource == NULL) { + // This is a NULL commit + if (surface->configured && surface->mapped) { + unmap_xdg_surface_v6(surface); + } + } +} + +static void xdg_surface_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_xdg_surface_v6 *surface = xdg_surface_from_resource(resource); + if (surface != NULL) { + destroy_xdg_surface_v6(surface); + } +} + +struct wlr_xdg_surface_v6 *create_xdg_surface_v6( + struct wlr_xdg_client_v6 *client, struct wlr_surface *surface, + uint32_t id) { + if (wlr_surface_has_buffer(surface)) { + wl_resource_post_error(client->resource, + ZXDG_SURFACE_V6_ERROR_UNCONFIGURED_BUFFER, + "xdg_surface must not have a buffer at creation"); + return NULL; + } + + struct wlr_xdg_surface_v6 *xdg_surface = + calloc(1, sizeof(struct wlr_xdg_surface_v6)); + if (xdg_surface == NULL) { + wl_client_post_no_memory(client->client); + return NULL; + } + + xdg_surface->client = client; + xdg_surface->role = WLR_XDG_SURFACE_V6_ROLE_NONE; + xdg_surface->surface = surface; + xdg_surface->resource = wl_resource_create(client->client, + &zxdg_surface_v6_interface, wl_resource_get_version(client->resource), + id); + if (xdg_surface->resource == NULL) { + free(xdg_surface); + wl_client_post_no_memory(client->client); + return NULL; + } + wl_resource_set_implementation(xdg_surface->resource, + &zxdg_surface_v6_implementation, xdg_surface, + xdg_surface_handle_resource_destroy); + + wl_list_init(&xdg_surface->configure_list); + wl_list_init(&xdg_surface->popups); + + wl_signal_init(&xdg_surface->events.destroy); + wl_signal_init(&xdg_surface->events.ping_timeout); + wl_signal_init(&xdg_surface->events.new_popup); + wl_signal_init(&xdg_surface->events.map); + wl_signal_init(&xdg_surface->events.unmap); + + wl_signal_add(&xdg_surface->surface->events.destroy, + &xdg_surface->surface_destroy); + xdg_surface->surface_destroy.notify = xdg_surface_handle_surface_destroy; + + wl_signal_add(&xdg_surface->surface->events.commit, + &xdg_surface->surface_commit); + xdg_surface->surface_commit.notify = xdg_surface_handle_surface_commit; + + wlr_log(WLR_DEBUG, "new xdg_surface %p (res %p)", xdg_surface, + xdg_surface->resource); + wl_list_insert(&client->surfaces, &xdg_surface->link); + + return xdg_surface; +} + +static void xdg_popup_v6_get_position(struct wlr_xdg_popup_v6 *popup, + double *popup_sx, double *popup_sy) { + struct wlr_xdg_surface_v6 *parent = popup->parent; + struct wlr_box parent_geo; + wlr_xdg_surface_v6_get_geometry(parent, &parent_geo); + *popup_sx = parent_geo.x + popup->geometry.x - + popup->base->geometry.x; + *popup_sy = parent_geo.y + popup->geometry.y - + popup->base->geometry.y; +} + +struct wlr_surface *wlr_xdg_surface_v6_surface_at( + struct wlr_xdg_surface_v6 *surface, double sx, double sy, + double *sub_x, double *sub_y) { + struct wlr_xdg_popup_v6 *popup_state; + wl_list_for_each(popup_state, &surface->popups, link) { + struct wlr_xdg_surface_v6 *popup = popup_state->base; + + double popup_sx, popup_sy; + xdg_popup_v6_get_position(popup_state, &popup_sx, &popup_sy); + + struct wlr_surface *sub = wlr_xdg_surface_v6_surface_at(popup, + sx - popup_sx, + sy - popup_sy, + sub_x, sub_y); + if (sub != NULL) { + return sub; + } + } + + return wlr_surface_surface_at(surface->surface, sx, sy, sub_x, sub_y); +} + +struct xdg_surface_v6_iterator_data { + wlr_surface_iterator_func_t user_iterator; + void *user_data; + int x, y; +}; + +static void xdg_surface_v6_iterator(struct wlr_surface *surface, + int sx, int sy, void *data) { + struct xdg_surface_v6_iterator_data *iter_data = data; + iter_data->user_iterator(surface, iter_data->x + sx, iter_data->y + sy, + iter_data->user_data); +} + +static void xdg_surface_v6_for_each_surface(struct wlr_xdg_surface_v6 *surface, + int x, int y, wlr_surface_iterator_func_t iterator, void *user_data) { + struct xdg_surface_v6_iterator_data data = { + .user_iterator = iterator, + .user_data = user_data, + .x = x, .y = y, + }; + wlr_surface_for_each_surface(surface->surface, xdg_surface_v6_iterator, + &data); + + struct wlr_xdg_popup_v6 *popup_state; + wl_list_for_each(popup_state, &surface->popups, link) { + struct wlr_xdg_surface_v6 *popup = popup_state->base; + if (!popup->configured) { + continue; + } + + double popup_sx, popup_sy; + xdg_popup_v6_get_position(popup_state, &popup_sx, &popup_sy); + + xdg_surface_v6_for_each_surface(popup, + x + popup_sx, + y + popup_sy, + iterator, user_data); + } +} + +static void xdg_surface_v6_for_each_popup(struct wlr_xdg_surface_v6 *surface, + int x, int y, wlr_surface_iterator_func_t iterator, void *user_data) { + struct wlr_xdg_popup_v6 *popup_state; + wl_list_for_each(popup_state, &surface->popups, link) { + struct wlr_xdg_surface_v6 *popup = popup_state->base; + if (!popup->configured) { + continue; + } + + double popup_sx, popup_sy; + xdg_popup_v6_get_position(popup_state, &popup_sx, &popup_sy); + iterator(popup->surface, x + popup_sx, y + popup_sy, user_data); + + xdg_surface_v6_for_each_popup(popup, + x + popup_sx, + y + popup_sy, + iterator, user_data); + } +} + +void wlr_xdg_surface_v6_for_each_surface(struct wlr_xdg_surface_v6 *surface, + wlr_surface_iterator_func_t iterator, void *user_data) { + xdg_surface_v6_for_each_surface(surface, 0, 0, iterator, user_data); +} + +void wlr_xdg_surface_v6_for_each_popup(struct wlr_xdg_surface_v6 *surface, + wlr_surface_iterator_func_t iterator, void *user_data) { + xdg_surface_v6_for_each_popup(surface, 0, 0, iterator, user_data); +} + +void wlr_xdg_surface_v6_get_geometry(struct wlr_xdg_surface_v6 *surface, struct wlr_box *box) { + wlr_surface_get_extends(surface->surface, box); + /* The client never set the geometry */ + if (!surface->geometry.width) { + return; + } + + wlr_box_intersection(box, &surface->geometry, box); +} diff --git a/types/xdg_shell_v6/wlr_xdg_toplevel_v6.c b/types/xdg_shell_v6/wlr_xdg_toplevel_v6.c new file mode 100644 index 00000000..297f49f5 --- /dev/null +++ b/types/xdg_shell_v6/wlr_xdg_toplevel_v6.c @@ -0,0 +1,506 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <wlr/util/log.h> +#include "types/wlr_xdg_shell_v6.h" +#include "util/signal.h" + +static const struct zxdg_toplevel_v6_interface zxdg_toplevel_v6_implementation; + +static struct wlr_xdg_surface_v6 *xdg_surface_from_xdg_toplevel_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &zxdg_toplevel_v6_interface, + &zxdg_toplevel_v6_implementation)); + return wl_resource_get_user_data(resource); +} + +void destroy_xdg_toplevel_v6(struct wlr_xdg_surface_v6 *surface) { + assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL); + unmap_xdg_surface_v6(surface); + + wl_resource_set_user_data(surface->toplevel->resource, NULL); + free(surface->toplevel); + surface->toplevel = NULL; + + surface->role = WLR_XDG_SURFACE_V6_ROLE_NONE; +} + +static void xdg_toplevel_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void xdg_toplevel_handle_set_parent(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *parent_resource) { + struct wlr_xdg_surface_v6 *surface = + xdg_surface_from_xdg_toplevel_resource(resource); + + struct wlr_xdg_surface_v6 *parent = NULL; + if (parent_resource != NULL) { + parent = xdg_surface_from_xdg_toplevel_resource(parent_resource); + } + + surface->toplevel->parent = parent; + wlr_signal_emit_safe(&surface->toplevel->events.set_parent, surface); +} + +static void xdg_toplevel_handle_set_title(struct wl_client *client, + struct wl_resource *resource, const char *title) { + struct wlr_xdg_surface_v6 *surface = + xdg_surface_from_xdg_toplevel_resource(resource); + + char *tmp = strdup(title); + if (tmp == NULL) { + return; + } + + free(surface->toplevel->title); + surface->toplevel->title = tmp; + wlr_signal_emit_safe(&surface->toplevel->events.set_title, surface); +} + +static void xdg_toplevel_handle_set_app_id(struct wl_client *client, + struct wl_resource *resource, const char *app_id) { + struct wlr_xdg_surface_v6 *surface = + xdg_surface_from_xdg_toplevel_resource(resource); + + char *tmp = strdup(app_id); + if (tmp == NULL) { + return; + } + + free(surface->toplevel->app_id); + surface->toplevel->app_id = tmp; + wlr_signal_emit_safe(&surface->toplevel->events.set_app_id, surface); +} + +static void xdg_toplevel_handle_show_window_menu(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat_resource, + uint32_t serial, int32_t x, int32_t y) { + struct wlr_xdg_surface_v6 *surface = + xdg_surface_from_xdg_toplevel_resource(resource); + struct wlr_seat_client *seat = + wlr_seat_client_from_resource(seat_resource); + + if (!surface->configured) { + wl_resource_post_error(surface->toplevel->resource, + ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, + "surface has not been configured yet"); + return; + } + + if (!wlr_seat_validate_grab_serial(seat->seat, serial)) { + wlr_log(WLR_DEBUG, "invalid serial for grab"); + return; + } + + struct wlr_xdg_toplevel_v6_show_window_menu_event event = { + .surface = surface, + .seat = seat, + .serial = serial, + .x = x, + .y = y, + }; + + wlr_signal_emit_safe(&surface->toplevel->events.request_show_window_menu, + &event); +} + +static void xdg_toplevel_handle_move(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat_resource, + uint32_t serial) { + struct wlr_xdg_surface_v6 *surface = + xdg_surface_from_xdg_toplevel_resource(resource); + struct wlr_seat_client *seat = + wlr_seat_client_from_resource(seat_resource); + + if (!surface->configured) { + wl_resource_post_error(surface->toplevel->resource, + ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, + "surface has not been configured yet"); + return; + } + + if (!wlr_seat_validate_grab_serial(seat->seat, serial)) { + wlr_log(WLR_DEBUG, "invalid serial for grab"); + return; + } + + struct wlr_xdg_toplevel_v6_move_event event = { + .surface = surface, + .seat = seat, + .serial = serial, + }; + + wlr_signal_emit_safe(&surface->toplevel->events.request_move, &event); +} + +static void xdg_toplevel_handle_resize(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *seat_resource, + uint32_t serial, uint32_t edges) { + struct wlr_xdg_surface_v6 *surface = + xdg_surface_from_xdg_toplevel_resource(resource); + struct wlr_seat_client *seat = + wlr_seat_client_from_resource(seat_resource); + + if (!surface->configured) { + wl_resource_post_error(surface->toplevel->resource, + ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, + "surface has not been configured yet"); + return; + } + + if (!wlr_seat_validate_grab_serial(seat->seat, serial)) { + wlr_log(WLR_DEBUG, "invalid serial for grab"); + return; + } + + struct wlr_xdg_toplevel_v6_resize_event event = { + .surface = surface, + .seat = seat, + .serial = serial, + .edges = edges, + }; + + wlr_signal_emit_safe(&surface->toplevel->events.request_resize, &event); +} + +static void xdg_toplevel_handle_set_max_size(struct wl_client *client, + struct wl_resource *resource, int32_t width, int32_t height) { + struct wlr_xdg_surface_v6 *surface = + xdg_surface_from_xdg_toplevel_resource(resource); + surface->toplevel->client_pending.max_width = width; + surface->toplevel->client_pending.max_height = height; +} + +static void xdg_toplevel_handle_set_min_size(struct wl_client *client, + struct wl_resource *resource, int32_t width, int32_t height) { + struct wlr_xdg_surface_v6 *surface = + xdg_surface_from_xdg_toplevel_resource(resource); + surface->toplevel->client_pending.min_width = width; + surface->toplevel->client_pending.min_height = height; +} + +static void xdg_toplevel_handle_set_maximized(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_surface_v6 *surface = + xdg_surface_from_xdg_toplevel_resource(resource); + surface->toplevel->client_pending.maximized = true; + wlr_signal_emit_safe(&surface->toplevel->events.request_maximize, surface); +} + +static void xdg_toplevel_handle_unset_maximized(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_surface_v6 *surface = + xdg_surface_from_xdg_toplevel_resource(resource); + surface->toplevel->client_pending.maximized = false; + wlr_signal_emit_safe(&surface->toplevel->events.request_maximize, surface); +} + +static void xdg_toplevel_handle_set_fullscreen(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *output_resource) { + struct wlr_xdg_surface_v6 *surface = + xdg_surface_from_xdg_toplevel_resource(resource); + + struct wlr_output *output = NULL; + if (output_resource != NULL) { + output = wlr_output_from_resource(output_resource); + } + + surface->toplevel->client_pending.fullscreen = true; + + struct wlr_xdg_toplevel_v6_set_fullscreen_event event = { + .surface = surface, + .fullscreen = true, + .output = output, + }; + + wlr_signal_emit_safe(&surface->toplevel->events.request_fullscreen, &event); +} + +static void xdg_toplevel_handle_unset_fullscreen(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_surface_v6 *surface = + xdg_surface_from_xdg_toplevel_resource(resource); + + surface->toplevel->client_pending.fullscreen = false; + + struct wlr_xdg_toplevel_v6_set_fullscreen_event event = { + .surface = surface, + .fullscreen = false, + .output = NULL, + }; + + wlr_signal_emit_safe(&surface->toplevel->events.request_fullscreen, &event); +} + +static void xdg_toplevel_handle_set_minimized(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_xdg_surface_v6 *surface = + xdg_surface_from_xdg_toplevel_resource(resource); + wlr_signal_emit_safe(&surface->toplevel->events.request_minimize, surface); +} + +static const struct zxdg_toplevel_v6_interface + zxdg_toplevel_v6_implementation = { + .destroy = xdg_toplevel_handle_destroy, + .set_parent = xdg_toplevel_handle_set_parent, + .set_title = xdg_toplevel_handle_set_title, + .set_app_id = xdg_toplevel_handle_set_app_id, + .show_window_menu = xdg_toplevel_handle_show_window_menu, + .move = xdg_toplevel_handle_move, + .resize = xdg_toplevel_handle_resize, + .set_max_size = xdg_toplevel_handle_set_max_size, + .set_min_size = xdg_toplevel_handle_set_min_size, + .set_maximized = xdg_toplevel_handle_set_maximized, + .unset_maximized = xdg_toplevel_handle_unset_maximized, + .set_fullscreen = xdg_toplevel_handle_set_fullscreen, + .unset_fullscreen = xdg_toplevel_handle_unset_fullscreen, + .set_minimized = xdg_toplevel_handle_set_minimized, +}; + +void handle_xdg_toplevel_v6_ack_configure(struct wlr_xdg_surface_v6 *surface, + struct wlr_xdg_surface_v6_configure *configure) { + assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL); + assert(configure->toplevel_state != NULL); + + surface->toplevel->current.maximized = + configure->toplevel_state->maximized; + surface->toplevel->current.fullscreen = + configure->toplevel_state->fullscreen; + surface->toplevel->current.resizing = + configure->toplevel_state->resizing; + surface->toplevel->current.activated = + configure->toplevel_state->activated; +} + +bool compare_xdg_surface_v6_toplevel_state(struct wlr_xdg_toplevel_v6 *state) { + struct { + struct wlr_xdg_toplevel_v6_state state; + uint32_t width, height; + } configured; + + // is pending state different from current state? + if (!state->base->configured) { + return false; + } + + if (wl_list_empty(&state->base->configure_list)) { + // last configure is actually the current state, just use it + configured.state = state->current; + configured.width = state->base->surface->current.width; + configured.height = state->base->surface->current.height; + } else { + struct wlr_xdg_surface_v6_configure *configure = + wl_container_of(state->base->configure_list.prev, configure, link); + configured.state = *configure->toplevel_state; + configured.width = configure->toplevel_state->width; + configured.height = configure->toplevel_state->height; + } + + if (state->server_pending.activated != configured.state.activated) { + return false; + } + if (state->server_pending.fullscreen != configured.state.fullscreen) { + return false; + } + if (state->server_pending.maximized != configured.state.maximized) { + return false; + } + if (state->server_pending.resizing != configured.state.resizing) { + return false; + } + + if (state->server_pending.width == configured.width && + state->server_pending.height == configured.height) { + return true; + } + + if (state->server_pending.width == 0 && state->server_pending.height == 0) { + return true; + } + + return false; +} + +void send_xdg_toplevel_v6_configure(struct wlr_xdg_surface_v6 *surface, + struct wlr_xdg_surface_v6_configure *configure) { + assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL); + + configure->toplevel_state = malloc(sizeof(*configure->toplevel_state)); + if (configure->toplevel_state == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + wl_resource_post_no_memory(surface->toplevel->resource); + return; + } + *configure->toplevel_state = surface->toplevel->server_pending; + + uint32_t *s; + struct wl_array states; + wl_array_init(&states); + if (surface->toplevel->server_pending.maximized) { + s = wl_array_add(&states, sizeof(uint32_t)); + if (!s) { + wlr_log(WLR_ERROR, + "Could not allocate state for maximized xdg_toplevel"); + goto error_out; + } + *s = ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED; + } + if (surface->toplevel->server_pending.fullscreen) { + s = wl_array_add(&states, sizeof(uint32_t)); + if (!s) { + wlr_log(WLR_ERROR, + "Could not allocate state for fullscreen xdg_toplevel"); + goto error_out; + } + *s = ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN; + } + if (surface->toplevel->server_pending.resizing) { + s = wl_array_add(&states, sizeof(uint32_t)); + if (!s) { + wlr_log(WLR_ERROR, + "Could not allocate state for resizing xdg_toplevel"); + goto error_out; + } + *s = ZXDG_TOPLEVEL_V6_STATE_RESIZING; + } + if (surface->toplevel->server_pending.activated) { + s = wl_array_add(&states, sizeof(uint32_t)); + if (!s) { + wlr_log(WLR_ERROR, + "Could not allocate state for activated xdg_toplevel"); + goto error_out; + } + *s = ZXDG_TOPLEVEL_V6_STATE_ACTIVATED; + } + + uint32_t width = surface->toplevel->server_pending.width; + uint32_t height = surface->toplevel->server_pending.height; + zxdg_toplevel_v6_send_configure(surface->toplevel->resource, width, + height, &states); + + wl_array_release(&states); + return; + +error_out: + wl_array_release(&states); + wl_resource_post_no_memory(surface->toplevel->resource); +} + +void handle_xdg_surface_v6_toplevel_committed(struct wlr_xdg_surface_v6 *surface) { + assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL); + + if (!surface->toplevel->added) { + // on the first commit, send a configure request to tell the client it + // is added + schedule_xdg_surface_v6_configure(surface); + surface->toplevel->added = true; + return; + } + + // update state that doesn't need compositor approval + surface->toplevel->current.max_width = + surface->toplevel->client_pending.max_width; + surface->toplevel->current.min_width = + surface->toplevel->client_pending.min_width; + surface->toplevel->current.max_height = + surface->toplevel->client_pending.max_height; + surface->toplevel->current.min_height = + surface->toplevel->client_pending.min_height; +} + +static void xdg_toplevel_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_xdg_surface_v6 *surface = + xdg_surface_from_xdg_toplevel_resource(resource); + if (surface != NULL) { + destroy_xdg_toplevel_v6(surface); + } +} + +const struct wlr_surface_role xdg_toplevel_v6_surface_role = { + .name = "xdg_toplevel_v6", + .commit = handle_xdg_surface_v6_commit, + .precommit = handle_xdg_surface_v6_precommit, +}; + +void create_xdg_toplevel_v6(struct wlr_xdg_surface_v6 *xdg_surface, + uint32_t id) { + if (!wlr_surface_set_role(xdg_surface->surface, &xdg_toplevel_v6_surface_role, + xdg_surface, xdg_surface->resource, ZXDG_SHELL_V6_ERROR_ROLE)) { + return; + } + + xdg_surface->toplevel = calloc(1, sizeof(struct wlr_xdg_toplevel_v6)); + if (xdg_surface->toplevel == NULL) { + wl_resource_post_no_memory(xdg_surface->resource); + return; + } + wl_signal_init(&xdg_surface->toplevel->events.request_maximize); + wl_signal_init(&xdg_surface->toplevel->events.request_fullscreen); + wl_signal_init(&xdg_surface->toplevel->events.request_minimize); + wl_signal_init(&xdg_surface->toplevel->events.request_move); + wl_signal_init(&xdg_surface->toplevel->events.request_resize); + wl_signal_init(&xdg_surface->toplevel->events.request_show_window_menu); + wl_signal_init(&xdg_surface->toplevel->events.set_parent); + wl_signal_init(&xdg_surface->toplevel->events.set_title); + wl_signal_init(&xdg_surface->toplevel->events.set_app_id); + + xdg_surface->role = WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL; + xdg_surface->toplevel->base = xdg_surface; + + struct wl_resource *toplevel_resource = wl_resource_create( + xdg_surface->client->client, &zxdg_toplevel_v6_interface, + wl_resource_get_version(xdg_surface->resource), id); + if (toplevel_resource == NULL) { + free(xdg_surface->toplevel); + wl_resource_post_no_memory(xdg_surface->resource); + return; + } + xdg_surface->toplevel->resource = toplevel_resource; + wl_resource_set_implementation(toplevel_resource, + &zxdg_toplevel_v6_implementation, xdg_surface, + xdg_toplevel_handle_resource_destroy); +} + +uint32_t wlr_xdg_toplevel_v6_set_size(struct wlr_xdg_surface_v6 *surface, + uint32_t width, uint32_t height) { + assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL); + surface->toplevel->server_pending.width = width; + surface->toplevel->server_pending.height = height; + + return schedule_xdg_surface_v6_configure(surface); +} + +uint32_t wlr_xdg_toplevel_v6_set_activated(struct wlr_xdg_surface_v6 *surface, + bool activated) { + assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL); + surface->toplevel->server_pending.activated = activated; + + return schedule_xdg_surface_v6_configure(surface); +} + +uint32_t wlr_xdg_toplevel_v6_set_maximized(struct wlr_xdg_surface_v6 *surface, + bool maximized) { + assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL); + surface->toplevel->server_pending.maximized = maximized; + + return schedule_xdg_surface_v6_configure(surface); +} + +uint32_t wlr_xdg_toplevel_v6_set_fullscreen(struct wlr_xdg_surface_v6 *surface, + bool fullscreen) { + assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL); + surface->toplevel->server_pending.fullscreen = fullscreen; + + return schedule_xdg_surface_v6_configure(surface); +} + +uint32_t wlr_xdg_toplevel_v6_set_resizing(struct wlr_xdg_surface_v6 *surface, + bool resizing) { + assert(surface->role == WLR_XDG_SURFACE_V6_ROLE_TOPLEVEL); + surface->toplevel->server_pending.resizing = resizing; + + return schedule_xdg_surface_v6_configure(surface); +} diff --git a/util/array.c b/util/array.c new file mode 100644 index 00000000..9ee39d33 --- /dev/null +++ b/util/array.c @@ -0,0 +1,21 @@ +#include <stdlib.h> +#include <stdint.h> + +// https://www.geeksforgeeks.org/move-zeroes-end-array/ +size_t push_zeroes_to_end(uint32_t arr[], size_t n) { + size_t count = 0; + + for (size_t i = 0; i < n; i++) { + if (arr[i] != 0) { + arr[count++] = arr[i]; + } + } + + size_t ret = count; + + while (count < n) { + arr[count++] = 0; + } + + return ret; +} diff --git a/util/log.c b/util/log.c new file mode 100644 index 00000000..3ef5f484 --- /dev/null +++ b/util/log.c @@ -0,0 +1,91 @@ +#define _POSIX_C_SOURCE 199506L +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <wlr/util/log.h> + +static bool colored = true; +static enum wlr_log_importance log_importance = WLR_ERROR; + +static const char *verbosity_colors[] = { + [WLR_SILENT] = "", + [WLR_ERROR ] = "\x1B[1;31m", + [WLR_INFO ] = "\x1B[1;34m", + [WLR_DEBUG ] = "\x1B[1;30m", +}; + +static void log_stderr(enum wlr_log_importance verbosity, const char *fmt, + va_list args) { + if (verbosity > log_importance) { + return; + } + // prefix the time to the log message + struct tm result; + time_t t = time(NULL); + struct tm *tm_info = localtime_r(&t, &result); + char buffer[26]; + + // generate time prefix + strftime(buffer, sizeof(buffer), "%F %T - ", tm_info); + fprintf(stderr, "%s", buffer); + + unsigned c = (verbosity < WLR_LOG_IMPORTANCE_LAST) ? verbosity : WLR_LOG_IMPORTANCE_LAST - 1; + + if (colored && isatty(STDERR_FILENO)) { + fprintf(stderr, "%s", verbosity_colors[c]); + } + + vfprintf(stderr, fmt, args); + + if (colored && isatty(STDERR_FILENO)) { + fprintf(stderr, "\x1B[0m"); + } + fprintf(stderr, "\n"); +} + +static wlr_log_func_t log_callback = log_stderr; + +void wlr_log_init(enum wlr_log_importance verbosity, wlr_log_func_t callback) { + if (verbosity < WLR_LOG_IMPORTANCE_LAST) { + log_importance = verbosity; + } + if (callback) { + log_callback = callback; + } +} + +void _wlr_vlog(enum wlr_log_importance verbosity, const char *fmt, va_list args) { + log_callback(verbosity, fmt, args); +} + +void _wlr_log(enum wlr_log_importance verbosity, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + log_callback(verbosity, fmt, args); + va_end(args); +} + +// strips the path prefix from filepath +// will try to strip WLR_SRC_DIR as well as a relative src dir +// e.g. '/src/build/wlroots/backend/wayland/backend.c' and +// '../backend/wayland/backend.c' will both be stripped to +// 'backend/wayland/backend.c' +const char *_wlr_strip_path(const char *filepath) { + static int srclen = sizeof(WLR_SRC_DIR); + if (strstr(filepath, WLR_SRC_DIR) == filepath) { + filepath += srclen; + } else if (*filepath == '.') { + while (*filepath == '.' || *filepath == '/') { + ++filepath; + } + } + return filepath; +} + +enum wlr_log_importance wlr_log_get_verbosity(void) { + return log_importance; +} diff --git a/util/meson.build b/util/meson.build new file mode 100644 index 00000000..dca3e9a4 --- /dev/null +++ b/util/meson.build @@ -0,0 +1,12 @@ +lib_wlr_util = static_library( + 'wlr_util', + files( + 'array.c', + 'log.c', + 'region.c', + 'shm.c', + 'signal.c', + ), + include_directories: wlr_inc, + dependencies: [wayland_server, pixman, rt], +) diff --git a/util/region.c b/util/region.c new file mode 100644 index 00000000..61f9c7c7 --- /dev/null +++ b/util/region.c @@ -0,0 +1,250 @@ +#include <assert.h> +#include <math.h> +#include <limits.h> +#include <stdlib.h> +#include <wlr/types/wlr_box.h> +#include <wlr/util/region.h> + +void wlr_region_scale(pixman_region32_t *dst, pixman_region32_t *src, + float scale) { + if (scale == 1) { + pixman_region32_copy(dst, src); + return; + } + + int nrects; + pixman_box32_t *src_rects = pixman_region32_rectangles(src, &nrects); + + pixman_box32_t *dst_rects = malloc(nrects * sizeof(pixman_box32_t)); + if (dst_rects == NULL) { + return; + } + + for (int i = 0; i < nrects; ++i) { + dst_rects[i].x1 = floor(src_rects[i].x1 * scale); + dst_rects[i].x2 = ceil(src_rects[i].x2 * scale); + dst_rects[i].y1 = floor(src_rects[i].y1 * scale); + dst_rects[i].y2 = ceil(src_rects[i].y2 * scale); + } + + pixman_region32_fini(dst); + pixman_region32_init_rects(dst, dst_rects, nrects); + free(dst_rects); +} + +void wlr_region_transform(pixman_region32_t *dst, pixman_region32_t *src, + enum wl_output_transform transform, int width, int height) { + if (transform == WL_OUTPUT_TRANSFORM_NORMAL) { + pixman_region32_copy(dst, src); + return; + } + + int nrects; + pixman_box32_t *src_rects = pixman_region32_rectangles(src, &nrects); + + pixman_box32_t *dst_rects = malloc(nrects * sizeof(pixman_box32_t)); + if (dst_rects == NULL) { + return; + } + + for (int i = 0; i < nrects; ++i) { + switch (transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + dst_rects[i].x1 = src_rects[i].x1; + dst_rects[i].y1 = src_rects[i].y1; + dst_rects[i].x2 = src_rects[i].x2; + dst_rects[i].y2 = src_rects[i].y2; + break; + case WL_OUTPUT_TRANSFORM_90: + dst_rects[i].x1 = src_rects[i].y1; + dst_rects[i].y1 = width - src_rects[i].x2; + dst_rects[i].x2 = src_rects[i].y2; + dst_rects[i].y2 = width - src_rects[i].x1; + break; + case WL_OUTPUT_TRANSFORM_180: + dst_rects[i].x1 = width - src_rects[i].x2; + dst_rects[i].y1 = height - src_rects[i].y2; + dst_rects[i].x2 = width - src_rects[i].x1; + dst_rects[i].y2 = height - src_rects[i].y1; + break; + case WL_OUTPUT_TRANSFORM_270: + dst_rects[i].x1 = height - src_rects[i].y2; + dst_rects[i].y1 = src_rects[i].x1; + dst_rects[i].x2 = height - src_rects[i].y1; + dst_rects[i].y2 = src_rects[i].x2; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + dst_rects[i].x1 = width - src_rects[i].x2; + dst_rects[i].y1 = src_rects[i].y1; + dst_rects[i].x2 = width - src_rects[i].x1; + dst_rects[i].y2 = src_rects[i].y2; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + dst_rects[i].x1 = height - src_rects[i].y2; + dst_rects[i].y1 = width - src_rects[i].x2; + dst_rects[i].x2 = height - src_rects[i].y1; + dst_rects[i].y2 = width - src_rects[i].x1; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + dst_rects[i].x1 = src_rects[i].x1; + dst_rects[i].y1 = height - src_rects[i].y2; + dst_rects[i].x2 = src_rects[i].x2; + dst_rects[i].y2 = height - src_rects[i].y1; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + dst_rects[i].x1 = src_rects[i].y1; + dst_rects[i].y1 = src_rects[i].x1; + dst_rects[i].x2 = src_rects[i].y2; + dst_rects[i].y2 = src_rects[i].x2; + break; + } + } + + pixman_region32_fini(dst); + pixman_region32_init_rects(dst, dst_rects, nrects); + free(dst_rects); +} + +void wlr_region_expand(pixman_region32_t *dst, pixman_region32_t *src, + int distance) { + if (distance == 0) { + pixman_region32_copy(dst, src); + return; + } + + int nrects; + pixman_box32_t *src_rects = pixman_region32_rectangles(src, &nrects); + + pixman_box32_t *dst_rects = malloc(nrects * sizeof(pixman_box32_t)); + if (dst_rects == NULL) { + return; + } + + for (int i = 0; i < nrects; ++i) { + dst_rects[i].x1 = src_rects[i].x1 - distance; + dst_rects[i].x2 = src_rects[i].x2 + distance; + dst_rects[i].y1 = src_rects[i].y1 - distance; + dst_rects[i].y2 = src_rects[i].y2 + distance; + } + + pixman_region32_fini(dst); + pixman_region32_init_rects(dst, dst_rects, nrects); + free(dst_rects); +} + +void wlr_region_rotated_bounds(pixman_region32_t *dst, pixman_region32_t *src, + float rotation, int ox, int oy) { + if (rotation == 0) { + pixman_region32_copy(dst, src); + return; + } + + int nrects; + pixman_box32_t *src_rects = pixman_region32_rectangles(src, &nrects); + + pixman_box32_t *dst_rects = malloc(nrects * sizeof(pixman_box32_t)); + if (dst_rects == NULL) { + return; + } + + for (int i = 0; i < nrects; ++i) { + double x1 = src_rects[i].x1 - ox; + double y1 = src_rects[i].y1 - oy; + double x2 = src_rects[i].x2 - ox; + double y2 = src_rects[i].y2 - oy; + + double rx1 = x1 * cos(rotation) - y1 * sin(rotation); + double ry1 = x1 * sin(rotation) + y1 * cos(rotation); + + double rx2 = x2 * cos(rotation) - y1 * sin(rotation); + double ry2 = x2 * sin(rotation) + y1 * cos(rotation); + + double rx3 = x2 * cos(rotation) - y2 * sin(rotation); + double ry3 = x2 * sin(rotation) + y2 * cos(rotation); + + double rx4 = x1 * cos(rotation) - y2 * sin(rotation); + double ry4 = x1 * sin(rotation) + y2 * cos(rotation); + + x1 = fmin(fmin(rx1, rx2), fmin(rx3, rx4)); + y1 = fmin(fmin(ry1, ry2), fmin(ry3, ry4)); + x2 = fmax(fmax(rx1, rx2), fmax(rx3, rx4)); + y2 = fmax(fmax(ry1, ry2), fmax(ry3, ry4)); + + dst_rects[i].x1 = floor(ox + x1); + dst_rects[i].x2 = ceil(ox + x2); + dst_rects[i].y1 = floor(oy + y1); + dst_rects[i].y2 = ceil(oy + y2); + } + + pixman_region32_fini(dst); + pixman_region32_init_rects(dst, dst_rects, nrects); + free(dst_rects); +} + +static void region_confine(pixman_region32_t *region, double x1, double y1, double x2, + double y2, double *x2_out, double *y2_out, pixman_box32_t box) { + double x_clamped = fmax(fmin(x2, box.x2 - 1), box.x1); + double y_clamped = fmax(fmin(y2, box.y2 - 1), box.y1); + + // If the target coordinates are above box.{x,y}2 - 1, but less than + // box.{x,y}2, then they are still within the box. + if (floor(x_clamped) == floor(x2) && floor(y_clamped) == floor(y2)) { + *x2_out = x2; + *y2_out = y2; + return; + } + + double dx = x2 - x1; + double dy = y2 - y1; + + // We use fabs to avoid negative zeroes and thus avoid a bug + // with negative infinity. + double delta = fmin(fabs(x_clamped - x1) / fabs(dx), fabs(y_clamped - y1) / fabs(dy)); + + // We clamp it again due to precision errors. + double x = fmax(fmin(delta * dx + x1, box.x2 - 1), box.x1); + double y = fmax(fmin(delta * dy + y1, box.y2 - 1), box.y1); + + // Go one unit past the boundary to find an adjacent box. + int x_ext = floor(x) + (dx == 0 ? 0 : dx > 0 ? 1 : -1); + int y_ext = floor(y) + (dy == 0 ? 0 : dy > 0 ? 1 : -1); + + if (pixman_region32_contains_point(region, x_ext, y_ext, &box)) { + return region_confine(region, x1, y1, x2, y2, x2_out, y2_out, box); + } else if (dx == 0 || dy == 0) { + *x2_out = x; + *y2_out = y; + } else { + bool bordering_x = x == box.x1 || x == box.x2 - 1; + bool bordering_y = y == box.y1 || y == box.y2 - 1; + + if ((bordering_x && bordering_y) || (!bordering_x && !bordering_y)) { + double x2_potential, y2_potential; + double tmp1, tmp2; + region_confine(region, x, y, x, y2, &tmp1, &y2_potential, box); + region_confine(region, x, y, x2, y, &x2_potential, &tmp2, box); + if (fabs(x2_potential - x) > fabs(y2_potential - y)) { + *x2_out = x2_potential; + *y2_out = y; + } else { + *x2_out = x; + *y2_out = y2_potential; + } + } else if (bordering_x) { + return region_confine(region, x, y, x, y2, x2_out, y2_out, box); + } else if (bordering_y) { + return region_confine(region, x, y, x2, y, x2_out, y2_out, box); + } + } +} + +bool wlr_region_confine(pixman_region32_t *region, double x1, double y1, double x2, + double y2, double *x2_out, double *y2_out) { + pixman_box32_t box; + if (pixman_region32_contains_point(region, floor(x1), floor(y1), &box)) { + region_confine(region, x1, y1, x2, y2, x2_out, y2_out, box); + return true; + } else { + return false; + } +} diff --git a/util/shm.c b/util/shm.c new file mode 100644 index 00000000..f7c7303e --- /dev/null +++ b/util/shm.c @@ -0,0 +1,55 @@ +#define _POSIX_C_SOURCE 200112L +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <sys/mman.h> +#include <time.h> +#include <unistd.h> +#include <wlr/config.h> +#include "util/shm.h" + +static void randname(char *buf) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + long r = ts.tv_nsec; + for (int i = 0; i < 6; ++i) { + buf[i] = 'A'+(r&15)+(r&16)*2; + r >>= 5; + } +} + +int create_shm_file(void) { + int retries = 100; + do { + char name[] = "/wlroots-XXXXXX"; + randname(name + strlen(name) - 6); + + --retries; + // CLOEXEC is guaranteed to be set by shm_open + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + shm_unlink(name); + return fd; + } + } while (retries > 0 && errno == EEXIST); + + return -1; +} + +int allocate_shm_file(size_t size) { + int fd = create_shm_file(); + if (fd < 0) { + return -1; + } + + int ret; + do { + ret = ftruncate(fd, size); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + close(fd); + return -1; + } + + return fd; +} diff --git a/util/signal.c b/util/signal.c new file mode 100644 index 00000000..39618465 --- /dev/null +++ b/util/signal.c @@ -0,0 +1,34 @@ +#include "util/signal.h" + +static void handle_noop(struct wl_listener *listener, void *data) { + // Do nothing +} + +void wlr_signal_emit_safe(struct wl_signal *signal, void *data) { + struct wl_listener cursor; + struct wl_listener end; + + /* Add two special markers: one cursor and one end marker. This way, we know + * that we've already called listeners on the left of the cursor and that we + * don't want to call listeners on the right of the end marker. The 'it' + * function can remove any element it wants from the list without troubles. + * wl_list_for_each_safe tries to be safe but it fails: it works fine + * if the current item is removed, but not if the next one is. */ + wl_list_insert(&signal->listener_list, &cursor.link); + cursor.notify = handle_noop; + wl_list_insert(signal->listener_list.prev, &end.link); + end.notify = handle_noop; + + while (cursor.link.next != &end.link) { + struct wl_list *pos = cursor.link.next; + struct wl_listener *l = wl_container_of(pos, l, link); + + wl_list_remove(&cursor.link); + wl_list_insert(pos, &cursor.link); + + l->notify(l, data); + } + + wl_list_remove(&cursor.link); + wl_list_remove(&end.link); +} diff --git a/wlroots.syms b/wlroots.syms new file mode 100644 index 00000000..3176f874 --- /dev/null +++ b/wlroots.syms @@ -0,0 +1,10 @@ +{ + global: + wlr_*; + _wlr_log; + _wlr_vlog; + _wlr_strip_path; + local: + wlr_signal_emit_safe; + *; +}; diff --git a/xcursor/meson.build b/xcursor/meson.build new file mode 100644 index 00000000..31040a92 --- /dev/null +++ b/xcursor/meson.build @@ -0,0 +1,9 @@ +lib_wlr_xcursor = static_library( + 'wlr_xcursor', + files( + 'wlr_xcursor.c', + 'xcursor.c', + ), + include_directories: wlr_inc, + dependencies: [egl] # header required via include/wlr/render.h +) diff --git a/xcursor/wlr_xcursor.c b/xcursor/wlr_xcursor.c new file mode 100644 index 00000000..d651497b --- /dev/null +++ b/xcursor/wlr_xcursor.c @@ -0,0 +1,350 @@ +/* + * Copyright © 2012 Intel Corporation + * + * 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 (including the + * next paragraph) 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. + */ + +#define _POSIX_C_SOURCE 200809L +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <wlr/util/log.h> +#include <wlr/xcursor.h> +#include "xcursor/xcursor.h" + +static void xcursor_destroy(struct wlr_xcursor *cursor) { + for (size_t i = 0; i < cursor->image_count; i++) { + free(cursor->images[i]->buffer); + free(cursor->images[i]); + } + + free(cursor->images); + free(cursor->name); + free(cursor); +} + +#include "xcursor/cursor_data.h" + +static struct wlr_xcursor *xcursor_create_from_data( + struct cursor_metadata *metadata, struct wlr_xcursor_theme *theme) { + struct wlr_xcursor *cursor; + struct wlr_xcursor_image *image; + int size; + + cursor = malloc(sizeof(*cursor)); + if (!cursor) { + return NULL; + } + + cursor->image_count = 1; + cursor->images = malloc(sizeof(*cursor->images)); + if (!cursor->images) { + goto err_free_cursor; + } + + cursor->name = strdup(metadata->name); + cursor->total_delay = 0; + + image = malloc(sizeof(*image)); + if (!image) { + goto err_free_images; + } + + cursor->images[0] = image; + image->buffer = NULL; + image->width = metadata->width; + image->height = metadata->height; + image->hotspot_x = metadata->hotspot_x; + image->hotspot_y = metadata->hotspot_y; + image->delay = 0; + + size = metadata->width * metadata->height * sizeof(uint32_t); + image->buffer = malloc(size); + + if (!image->buffer) { + goto err_free_image; + } + + memcpy(image->buffer, cursor_data + metadata->offset, size); + + return cursor; + +err_free_image: + free(image); + +err_free_images: + free(cursor->name); + free(cursor->images); + +err_free_cursor: + free(cursor); + return NULL; +} + +static void load_default_theme(struct wlr_xcursor_theme *theme) { + uint32_t i; + + free(theme->name); + theme->name = strdup("default"); + + theme->cursor_count = sizeof(cursor_metadata) / sizeof(cursor_metadata[0]); + theme->cursors = malloc(theme->cursor_count * sizeof(*theme->cursors)); + + if (theme->cursors == NULL) { + theme->cursor_count = 0; + return; + } + + for (i = 0; i < theme->cursor_count; ++i) { + theme->cursors[i] = + xcursor_create_from_data(&cursor_metadata[i], theme); + + if (theme->cursors[i] == NULL) { + break; + } + } + theme->cursor_count = i; +} + +static struct wlr_xcursor *xcursor_create_from_xcursor_images( + XcursorImages *images, struct wlr_xcursor_theme *theme) { + struct wlr_xcursor *cursor; + struct wlr_xcursor_image *image; + int i, size; + + cursor = malloc(sizeof(*cursor)); + if (!cursor) { + return NULL; + } + + cursor->images = malloc(images->nimage * sizeof(cursor->images[0])); + if (!cursor->images) { + free(cursor); + return NULL; + } + + cursor->name = strdup(images->name); + cursor->total_delay = 0; + + for (i = 0; i < images->nimage; i++) { + image = malloc(sizeof(*image)); + if (image == NULL) { + break; + } + + image->buffer = NULL; + + image->width = images->images[i]->width; + image->height = images->images[i]->height; + image->hotspot_x = images->images[i]->xhot; + image->hotspot_y = images->images[i]->yhot; + image->delay = images->images[i]->delay; + + size = image->width * image->height * 4; + image->buffer = malloc(size); + if (!image->buffer) { + free(image); + break; + } + + /* copy pixels to shm pool */ + memcpy(image->buffer, images->images[i]->pixels, size); + cursor->total_delay += image->delay; + cursor->images[i] = image; + } + cursor->image_count = i; + + if (cursor->image_count == 0) { + free(cursor->name); + free(cursor->images); + free(cursor); + return NULL; + } + + return cursor; +} + +static void load_callback(XcursorImages *images, void *data) { + struct wlr_xcursor_theme *theme = data; + struct wlr_xcursor *cursor; + + if (wlr_xcursor_theme_get_cursor(theme, images->name)) { + XcursorImagesDestroy(images); + return; + } + + cursor = xcursor_create_from_xcursor_images(images, theme); + + if (cursor) { + theme->cursor_count++; + theme->cursors = + realloc(theme->cursors, + theme->cursor_count * sizeof(theme->cursors[0])); + + if (theme->cursors == NULL) { + theme->cursor_count--; + free(cursor); + } else { + theme->cursors[theme->cursor_count - 1] = cursor; + } + } + + XcursorImagesDestroy(images); +} + +struct wlr_xcursor_theme *wlr_xcursor_theme_load(const char *name, int size) { + struct wlr_xcursor_theme *theme; + + theme = malloc(sizeof(*theme)); + if (!theme) { + return NULL; + } + + if (!name) { + name = "default"; + } + + theme->name = strdup(name); + if (!theme->name) { + goto out_error_name; + } + theme->size = size; + theme->cursor_count = 0; + theme->cursors = NULL; + + xcursor_load_theme(name, size, load_callback, theme); + + if (theme->cursor_count == 0) { + load_default_theme(theme); + } + + wlr_log(WLR_DEBUG, "Loaded cursor theme '%s', available cursors:", + theme->name); + for (size_t i = 0; i < theme->cursor_count; ++i) { + struct wlr_xcursor *c = theme->cursors[i]; + struct wlr_xcursor_image *i = c->images[0]; + wlr_log(WLR_DEBUG, "%s (%u images) %dx%d+%d,%d", + c->name, c->image_count, + i->width, i->height, i->hotspot_x, i->hotspot_y); + } + + return theme; + +out_error_name: + free(theme); + return NULL; +} + +void wlr_xcursor_theme_destroy(struct wlr_xcursor_theme *theme) { + unsigned int i; + + for (i = 0; i < theme->cursor_count; i++) { + xcursor_destroy(theme->cursors[i]); + } + + free(theme->name); + free(theme->cursors); + free(theme); +} + +struct wlr_xcursor *wlr_xcursor_theme_get_cursor(struct wlr_xcursor_theme *theme, + const char *name) { + unsigned int i; + + for (i = 0; i < theme->cursor_count; i++) { + if (strcmp(name, theme->cursors[i]->name) == 0) { + return theme->cursors[i]; + } + } + + return NULL; +} + +static int xcursor_frame_and_duration(struct wlr_xcursor *cursor, + uint32_t time, uint32_t *duration) { + uint32_t t; + int i; + + if (cursor->image_count == 1) { + if (duration) { + *duration = 0; + } + return 0; + } + + i = 0; + t = time % cursor->total_delay; + + /* If there is a 0 delay in the image set then this + * loop breaks on it and we display that cursor until + * time % cursor->total_delay wraps again. + * Since a 0 delay is silly, and we've never actually + * seen one in a cursor file, we haven't bothered to + * "fix" this. + */ + while (t - cursor->images[i]->delay < t) { + t -= cursor->images[i++]->delay; + } + + if (!duration) { + return i; + } + + /* Make sure we don't accidentally tell the caller this is + * a static cursor image. + */ + if (t >= cursor->images[i]->delay) { + *duration = 1; + } else { + *duration = cursor->images[i]->delay - t; + } + + return i; +} + +int wlr_xcursor_frame(struct wlr_xcursor *_cursor, uint32_t time) { + return xcursor_frame_and_duration(_cursor, time, NULL); +} + +const char *wlr_xcursor_get_resize_name(enum wlr_edges edges) { + if (edges & WLR_EDGE_TOP) { + if (edges & WLR_EDGE_RIGHT) { + return "ne-resize"; + } else if (edges & WLR_EDGE_LEFT) { + return "nw-resize"; + } + return "n-resize"; + } else if (edges & WLR_EDGE_BOTTOM) { + if (edges & WLR_EDGE_RIGHT) { + return "se-resize"; + } else if (edges & WLR_EDGE_LEFT) { + return "sw-resize"; + } + return "s-resize"; + } else if (edges & WLR_EDGE_RIGHT) { + return "e-resize"; + } else if (edges & WLR_EDGE_LEFT) { + return "w-resize"; + } + return "se-resize"; // fallback +} diff --git a/xcursor/xcursor.c b/xcursor/xcursor.c new file mode 100644 index 00000000..5b20fc56 --- /dev/null +++ b/xcursor/xcursor.c @@ -0,0 +1,983 @@ +/* + * Copyright © 2002 Keith Packard + * + * 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 (including the + * next paragraph) 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. + */ + +#define _DEFAULT_SOURCE +#include <dirent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "xcursor/xcursor.h" + +/* + * From libXcursor/include/X11/extensions/Xcursor.h + */ + +#define XcursorTrue 1 +#define XcursorFalse 0 + +/* + * Cursor files start with a header. The header + * contains a magic number, a version number and a + * table of contents which has type and offset information + * for the remaining tables in the file. + * + * File minor versions increment for compatible changes + * File major versions increment for incompatible changes (never, we hope) + * + * Chunks of the same type are always upward compatible. Incompatible + * changes are made with new chunk types; the old data can remain under + * the old type. Upward compatible changes can add header data as the + * header lengths are specified in the file. + * + * File: + * FileHeader + * LISTofChunk + * + * FileHeader: + * CARD32 magic magic number + * CARD32 header bytes in file header + * CARD32 version file version + * CARD32 ntoc number of toc entries + * LISTofFileToc toc table of contents + * + * FileToc: + * CARD32 type entry type + * CARD32 subtype entry subtype (size for images) + * CARD32 position absolute file position + */ + +#define XCURSOR_MAGIC 0x72756358 /* "Xcur" LSBFirst */ + +/* + * Current Xcursor version number. Will be substituted by configure + * from the version in the libXcursor configure.ac file. + */ + +#define XCURSOR_LIB_MAJOR 1 +#define XCURSOR_LIB_MINOR 1 +#define XCURSOR_LIB_REVISION 13 +#define XCURSOR_LIB_VERSION ((XCURSOR_LIB_MAJOR * 10000) + \ + (XCURSOR_LIB_MINOR * 100) + \ + (XCURSOR_LIB_REVISION)) + +/* + * This version number is stored in cursor files; changes to the + * file format require updating this version number + */ +#define XCURSOR_FILE_MAJOR 1 +#define XCURSOR_FILE_MINOR 0 +#define XCURSOR_FILE_VERSION ((XCURSOR_FILE_MAJOR << 16) | (XCURSOR_FILE_MINOR)) +#define XCURSOR_FILE_HEADER_LEN (4 * 4) +#define XCURSOR_FILE_TOC_LEN (3 * 4) + +typedef struct _XcursorFileToc { + XcursorUInt type; /* chunk type */ + XcursorUInt subtype; /* subtype (size for images) */ + XcursorUInt position; /* absolute position in file */ +} XcursorFileToc; + +typedef struct _XcursorFileHeader { + XcursorUInt magic; /* magic number */ + XcursorUInt header; /* byte length of header */ + XcursorUInt version; /* file version number */ + XcursorUInt ntoc; /* number of toc entries */ + XcursorFileToc *tocs; /* table of contents */ +} XcursorFileHeader; + +/* + * The rest of the file is a list of chunks, each tagged by type + * and version. + * + * Chunk: + * ChunkHeader + * <extra type-specific header fields> + * <type-specific data> + * + * ChunkHeader: + * CARD32 header bytes in chunk header + type header + * CARD32 type chunk type + * CARD32 subtype chunk subtype + * CARD32 version chunk type version + */ + +#define XCURSOR_CHUNK_HEADER_LEN (4 * 4) + +typedef struct _XcursorChunkHeader { + XcursorUInt header; /* bytes in chunk header */ + XcursorUInt type; /* chunk type */ + XcursorUInt subtype; /* chunk subtype (size for images) */ + XcursorUInt version; /* version of this type */ +} XcursorChunkHeader; + +/* + * Here's a list of the known chunk types + */ + +/* + * Comments consist of a 4-byte length field followed by + * UTF-8 encoded text + * + * Comment: + * ChunkHeader header chunk header + * CARD32 length bytes in text + * LISTofCARD8 text UTF-8 encoded text + */ + +#define XCURSOR_COMMENT_TYPE 0xfffe0001 +#define XCURSOR_COMMENT_VERSION 1 +#define XCURSOR_COMMENT_HEADER_LEN (XCURSOR_CHUNK_HEADER_LEN + (1 *4)) +#define XCURSOR_COMMENT_COPYRIGHT 1 +#define XCURSOR_COMMENT_LICENSE 2 +#define XCURSOR_COMMENT_OTHER 3 +#define XCURSOR_COMMENT_MAX_LEN 0x100000 + +typedef struct _XcursorComment { + XcursorUInt version; + XcursorUInt comment_type; + char *comment; +} XcursorComment; + +/* + * Each cursor image occupies a separate image chunk. + * The length of the image header follows the chunk header + * so that future versions can extend the header without + * breaking older applications + * + * Image: + * ChunkHeader header chunk header + * CARD32 width actual width + * CARD32 height actual height + * CARD32 xhot hot spot x + * CARD32 yhot hot spot y + * CARD32 delay animation delay + * LISTofCARD32 pixels ARGB pixels + */ + +#define XCURSOR_IMAGE_TYPE 0xfffd0002 +#define XCURSOR_IMAGE_VERSION 1 +#define XCURSOR_IMAGE_HEADER_LEN (XCURSOR_CHUNK_HEADER_LEN + (5*4)) +#define XCURSOR_IMAGE_MAX_SIZE 0x7fff /* 32767x32767 max cursor size */ + +typedef struct _XcursorFile XcursorFile; + +struct _XcursorFile { + void *closure; + int (*read) (XcursorFile *file, unsigned char *buf, int len); + int (*write) (XcursorFile *file, unsigned char *buf, int len); + int (*seek) (XcursorFile *file, long offset, int whence); +}; + +typedef struct _XcursorComments { + int ncomment; /* number of comments */ + XcursorComment **comments; /* array of XcursorComment pointers */ +} XcursorComments; + +/* + * From libXcursor/src/file.c + */ + +static XcursorImage * +XcursorImageCreate (int width, int height) +{ + XcursorImage *image; + + if (width < 0 || height < 0) + return NULL; + if (width > XCURSOR_IMAGE_MAX_SIZE || height > XCURSOR_IMAGE_MAX_SIZE) + return NULL; + + image = malloc (sizeof (XcursorImage) + + width * height * sizeof (XcursorPixel)); + if (!image) + return NULL; + image->version = XCURSOR_IMAGE_VERSION; + image->pixels = (XcursorPixel *) (image + 1); + image->size = width > height ? width : height; + image->width = width; + image->height = height; + image->delay = 0; + return image; +} + +static void +XcursorImageDestroy (XcursorImage *image) +{ + free (image); +} + +static XcursorImages * +XcursorImagesCreate (int size) +{ + XcursorImages *images; + + images = malloc (sizeof (XcursorImages) + + size * sizeof (XcursorImage *)); + if (!images) + return NULL; + images->nimage = 0; + images->images = (XcursorImage **) (images + 1); + images->name = NULL; + return images; +} + +void +XcursorImagesDestroy (XcursorImages *images) +{ + int n; + + if (!images) + return; + + for (n = 0; n < images->nimage; n++) + XcursorImageDestroy (images->images[n]); + if (images->name) + free (images->name); + free (images); +} + +static void +XcursorImagesSetName (XcursorImages *images, const char *name) +{ + char *new; + + if (!images || !name) + return; + + new = malloc (strlen (name) + 1); + + if (!new) + return; + + strcpy (new, name); + if (images->name) + free (images->name); + images->name = new; +} + +static XcursorBool +_XcursorReadUInt (XcursorFile *file, XcursorUInt *u) +{ + unsigned char bytes[4]; + + if (!file || !u) + return XcursorFalse; + + if ((*file->read) (file, bytes, 4) != 4) + return XcursorFalse; + *u = ((bytes[0] << 0) | + (bytes[1] << 8) | + (bytes[2] << 16) | + (bytes[3] << 24)); + return XcursorTrue; +} + +static void +_XcursorFileHeaderDestroy (XcursorFileHeader *fileHeader) +{ + free (fileHeader); +} + +static XcursorFileHeader * +_XcursorFileHeaderCreate (int ntoc) +{ + XcursorFileHeader *fileHeader; + + if (ntoc > 0x10000) + return NULL; + fileHeader = malloc (sizeof (XcursorFileHeader) + + ntoc * sizeof (XcursorFileToc)); + if (!fileHeader) + return NULL; + fileHeader->magic = XCURSOR_MAGIC; + fileHeader->header = XCURSOR_FILE_HEADER_LEN; + fileHeader->version = XCURSOR_FILE_VERSION; + fileHeader->ntoc = ntoc; + fileHeader->tocs = (XcursorFileToc *) (fileHeader + 1); + return fileHeader; +} + +static XcursorFileHeader * +_XcursorReadFileHeader (XcursorFile *file) +{ + XcursorFileHeader head, *fileHeader; + XcursorUInt skip; + unsigned int n; + + if (!file) + return NULL; + + if (!_XcursorReadUInt (file, &head.magic)) + return NULL; + if (head.magic != XCURSOR_MAGIC) + return NULL; + if (!_XcursorReadUInt (file, &head.header)) + return NULL; + if (!_XcursorReadUInt (file, &head.version)) + return NULL; + if (!_XcursorReadUInt (file, &head.ntoc)) + return NULL; + skip = head.header - XCURSOR_FILE_HEADER_LEN; + if (skip) + if ((*file->seek) (file, skip, SEEK_CUR) == EOF) + return NULL; + fileHeader = _XcursorFileHeaderCreate (head.ntoc); + if (!fileHeader) + return NULL; + fileHeader->magic = head.magic; + fileHeader->header = head.header; + fileHeader->version = head.version; + fileHeader->ntoc = head.ntoc; + for (n = 0; n < fileHeader->ntoc; n++) + { + if (!_XcursorReadUInt (file, &fileHeader->tocs[n].type)) + break; + if (!_XcursorReadUInt (file, &fileHeader->tocs[n].subtype)) + break; + if (!_XcursorReadUInt (file, &fileHeader->tocs[n].position)) + break; + } + if (n != fileHeader->ntoc) + { + _XcursorFileHeaderDestroy (fileHeader); + return NULL; + } + return fileHeader; +} + +static XcursorBool +_XcursorSeekToToc (XcursorFile *file, + XcursorFileHeader *fileHeader, + int toc) +{ + if (!file || !fileHeader || \ + (*file->seek) (file, fileHeader->tocs[toc].position, SEEK_SET) == EOF) + return XcursorFalse; + return XcursorTrue; +} + +static XcursorBool +_XcursorFileReadChunkHeader (XcursorFile *file, + XcursorFileHeader *fileHeader, + int toc, + XcursorChunkHeader *chunkHeader) +{ + if (!file || !fileHeader || !chunkHeader) + return XcursorFalse; + if (!_XcursorSeekToToc (file, fileHeader, toc)) + return XcursorFalse; + if (!_XcursorReadUInt (file, &chunkHeader->header)) + return XcursorFalse; + if (!_XcursorReadUInt (file, &chunkHeader->type)) + return XcursorFalse; + if (!_XcursorReadUInt (file, &chunkHeader->subtype)) + return XcursorFalse; + if (!_XcursorReadUInt (file, &chunkHeader->version)) + return XcursorFalse; + /* sanity check */ + if (chunkHeader->type != fileHeader->tocs[toc].type || + chunkHeader->subtype != fileHeader->tocs[toc].subtype) + return XcursorFalse; + return XcursorTrue; +} + +#define dist(a,b) ((a) > (b) ? (a) - (b) : (b) - (a)) + +static XcursorDim +_XcursorFindBestSize (XcursorFileHeader *fileHeader, + XcursorDim size, + int *nsizesp) +{ + unsigned int n; + int nsizes = 0; + XcursorDim bestSize = 0; + XcursorDim thisSize; + + if (!fileHeader || !nsizesp) + return 0; + + for (n = 0; n < fileHeader->ntoc; n++) + { + if (fileHeader->tocs[n].type != XCURSOR_IMAGE_TYPE) + continue; + thisSize = fileHeader->tocs[n].subtype; + if (!bestSize || dist (thisSize, size) < dist (bestSize, size)) + { + bestSize = thisSize; + nsizes = 1; + } + else if (thisSize == bestSize) + nsizes++; + } + *nsizesp = nsizes; + return bestSize; +} + +static int +_XcursorFindImageToc (XcursorFileHeader *fileHeader, + XcursorDim size, + int count) +{ + unsigned int toc; + XcursorDim thisSize; + + if (!fileHeader) + return 0; + + for (toc = 0; toc < fileHeader->ntoc; toc++) + { + if (fileHeader->tocs[toc].type != XCURSOR_IMAGE_TYPE) + continue; + thisSize = fileHeader->tocs[toc].subtype; + if (thisSize != size) + continue; + if (!count) + break; + count--; + } + if (toc == fileHeader->ntoc) + return -1; + return toc; +} + +static XcursorImage * +_XcursorReadImage (XcursorFile *file, + XcursorFileHeader *fileHeader, + int toc) +{ + XcursorChunkHeader chunkHeader; + XcursorImage head; + XcursorImage *image; + int n; + XcursorPixel *p; + + if (!file || !fileHeader) + return NULL; + + if (!_XcursorFileReadChunkHeader (file, fileHeader, toc, &chunkHeader)) + return NULL; + if (!_XcursorReadUInt (file, &head.width)) + return NULL; + if (!_XcursorReadUInt (file, &head.height)) + return NULL; + if (!_XcursorReadUInt (file, &head.xhot)) + return NULL; + if (!_XcursorReadUInt (file, &head.yhot)) + return NULL; + if (!_XcursorReadUInt (file, &head.delay)) + return NULL; + /* sanity check data */ + if (head.width > XCURSOR_IMAGE_MAX_SIZE || + head.height > XCURSOR_IMAGE_MAX_SIZE) + return NULL; + if (head.width == 0 || head.height == 0) + return NULL; + if (head.xhot > head.width || head.yhot > head.height) + return NULL; + + /* Create the image and initialize it */ + image = XcursorImageCreate (head.width, head.height); + if (image == NULL) + return NULL; + if (chunkHeader.version < image->version) + image->version = chunkHeader.version; + image->size = chunkHeader.subtype; + image->xhot = head.xhot; + image->yhot = head.yhot; + image->delay = head.delay; + n = image->width * image->height; + p = image->pixels; + while (n--) + { + if (!_XcursorReadUInt (file, p)) + { + XcursorImageDestroy (image); + return NULL; + } + p++; + } + return image; +} + +static XcursorImages * +XcursorXcFileLoadImages (XcursorFile *file, int size) +{ + XcursorFileHeader *fileHeader; + XcursorDim bestSize; + int nsize; + XcursorImages *images; + int n; + int toc; + + if (!file || size < 0) + return NULL; + fileHeader = _XcursorReadFileHeader (file); + if (!fileHeader) + return NULL; + bestSize = _XcursorFindBestSize (fileHeader, (XcursorDim) size, &nsize); + if (!bestSize) + { + _XcursorFileHeaderDestroy (fileHeader); + return NULL; + } + images = XcursorImagesCreate (nsize); + if (!images) + { + _XcursorFileHeaderDestroy (fileHeader); + return NULL; + } + for (n = 0; n < nsize; n++) + { + toc = _XcursorFindImageToc (fileHeader, bestSize, n); + if (toc < 0) + break; + images->images[images->nimage] = _XcursorReadImage (file, fileHeader, + toc); + if (!images->images[images->nimage]) + break; + images->nimage++; + } + _XcursorFileHeaderDestroy (fileHeader); + if (images->nimage != nsize) + { + XcursorImagesDestroy (images); + images = NULL; + } + return images; +} + +static int +_XcursorStdioFileRead (XcursorFile *file, unsigned char *buf, int len) +{ + FILE *f = file->closure; + return fread (buf, 1, len, f); +} + +static int +_XcursorStdioFileWrite (XcursorFile *file, unsigned char *buf, int len) +{ + FILE *f = file->closure; + return fwrite (buf, 1, len, f); +} + +static int +_XcursorStdioFileSeek (XcursorFile *file, long offset, int whence) +{ + FILE *f = file->closure; + return fseek (f, offset, whence); +} + +static void +_XcursorStdioFileInitialize (FILE *stdfile, XcursorFile *file) +{ + file->closure = stdfile; + file->read = _XcursorStdioFileRead; + file->write = _XcursorStdioFileWrite; + file->seek = _XcursorStdioFileSeek; +} + +static XcursorImages * +XcursorFileLoadImages (FILE *file, int size) +{ + XcursorFile f; + + if (!file) + return NULL; + + _XcursorStdioFileInitialize (file, &f); + return XcursorXcFileLoadImages (&f, size); +} + +/* + * From libXcursor/src/library.c + */ + +#ifndef ICONDIR +#define ICONDIR "/usr/X11R6/lib/X11/icons" +#endif + +#ifndef XCURSORPATH +#define XCURSORPATH "~/.local/share/icons:~/.icons:/usr/share/icons:/usr/share/pixmaps:"ICONDIR +#endif + +static const char * +XcursorLibraryPath (void) +{ + static const char *path; + + if (!path) + { + path = getenv ("XCURSOR_PATH"); + if (!path) + path = XCURSORPATH; + } + return path; +} + +static void +_XcursorAddPathElt (char *path, const char *elt, int len) +{ + int pathlen = strlen (path); + + /* append / if the path doesn't currently have one */ + if (path[0] == '\0' || path[pathlen - 1] != '/') + { + strcat (path, "/"); + pathlen++; + } + if (len == -1) + len = strlen (elt); + /* strip leading slashes */ + while (len && elt[0] == '/') + { + elt++; + len--; + } + strncpy (path + pathlen, elt, len); + path[pathlen + len] = '\0'; +} + +static char * +_XcursorBuildThemeDir (const char *dir, const char *theme) +{ + const char *colon; + const char *tcolon; + char *full; + char *home; + int dirlen; + int homelen; + int themelen; + int len; + + if (!dir || !theme) + return NULL; + + colon = strchr (dir, ':'); + if (!colon) + colon = dir + strlen (dir); + + dirlen = colon - dir; + + tcolon = strchr (theme, ':'); + if (!tcolon) + tcolon = theme + strlen (theme); + + themelen = tcolon - theme; + + home = NULL; + homelen = 0; + if (*dir == '~') + { + home = getenv ("HOME"); + if (!home) + return NULL; + homelen = strlen (home); + dir++; + dirlen--; + } + + /* + * add space for any needed directory separators, one per component, + * and one for the trailing null + */ + len = 1 + homelen + 1 + dirlen + 1 + themelen + 1; + + full = malloc (len); + if (!full) + return NULL; + full[0] = '\0'; + + if (home) + _XcursorAddPathElt (full, home, -1); + _XcursorAddPathElt (full, dir, dirlen); + _XcursorAddPathElt (full, theme, themelen); + return full; +} + +static char * +_XcursorBuildFullname (const char *dir, const char *subdir, const char *file) +{ + char *full; + + if (!dir || !subdir || !file) + return NULL; + + full = malloc (strlen (dir) + 1 + strlen (subdir) + 1 + strlen (file) + 1); + if (!full) + return NULL; + full[0] = '\0'; + _XcursorAddPathElt (full, dir, -1); + _XcursorAddPathElt (full, subdir, -1); + _XcursorAddPathElt (full, file, -1); + return full; +} + +static const char * +_XcursorNextPath (const char *path) +{ + char *colon = strchr (path, ':'); + + if (!colon) + return NULL; + return colon + 1; +} + +#define XcursorWhite(c) ((c) == ' ' || (c) == '\t' || (c) == '\n') +#define XcursorSep(c) ((c) == ';' || (c) == ',') + +static char * +_XcursorThemeInherits (const char *full) +{ + char line[8192]; + char *result = NULL; + FILE *f; + + if (!full) + return NULL; + + f = fopen (full, "r"); + if (f) + { + while (fgets (line, sizeof (line), f)) + { + if (!strncmp (line, "Inherits", 8)) + { + char *l = line + 8; + char *r; + while (*l == ' ') l++; + if (*l != '=') continue; + l++; + while (*l == ' ') l++; + result = malloc (strlen (l) + 1); + if (result) + { + r = result; + while (*l) + { + while (XcursorSep(*l) || XcursorWhite (*l)) l++; + if (!*l) + break; + if (r != result) + *r++ = ':'; + while (*l && !XcursorWhite(*l) && + !XcursorSep(*l)) + *r++ = *l++; + } + *r++ = '\0'; + } + break; + } + } + fclose (f); + } + return result; +} + +static FILE * +XcursorScanTheme (const char *theme, const char *name) +{ + FILE *f = NULL; + char *full; + char *dir; + const char *path; + char *inherits = NULL; + const char *i; + + if (!theme || !name) + return NULL; + + /* + * Scan this theme + */ + for (path = XcursorLibraryPath (); + path && f == NULL; + path = _XcursorNextPath (path)) + { + dir = _XcursorBuildThemeDir (path, theme); + if (dir) + { + full = _XcursorBuildFullname (dir, "cursors", name); + if (full) + { + f = fopen (full, "r"); + free (full); + } + if (!f && !inherits) + { + full = _XcursorBuildFullname (dir, "", "index.theme"); + if (full) + { + inherits = _XcursorThemeInherits (full); + free (full); + } + } + free (dir); + } + } + /* + * Recurse to scan inherited themes + */ + for (i = inherits; i && f == NULL; i = _XcursorNextPath (i)) + { + if (strcmp(i, theme) != 0) + f = XcursorScanTheme (i, name); + else + printf("Not calling XcursorScanTheme because of circular dependency: %s. %s", i, name); + } + if (inherits != NULL) + free (inherits); + return f; +} + +XcursorImages * +XcursorLibraryLoadImages (const char *file, const char *theme, int size) +{ + FILE *f = NULL; + XcursorImages *images = NULL; + + if (!file) + return NULL; + + if (theme) + f = XcursorScanTheme (theme, file); + if (!f) + f = XcursorScanTheme ("default", file); + if (f) + { + images = XcursorFileLoadImages (f, size); + if (images) + XcursorImagesSetName (images, file); + fclose (f); + } + return images; +} + +static void +load_all_cursors_from_dir(const char *path, int size, + void (*load_callback)(XcursorImages *, void *), + void *user_data) +{ + FILE *f; + DIR *dir = opendir(path); + struct dirent *ent; + char *full; + XcursorImages *images; + + if (!dir) + return; + + for(ent = readdir(dir); ent; ent = readdir(dir)) { +#ifdef _DIRENT_HAVE_D_TYPE + if (ent->d_type != DT_UNKNOWN && + (ent->d_type != DT_REG && ent->d_type != DT_LNK)) + continue; +#endif + + full = _XcursorBuildFullname(path, "", ent->d_name); + if (!full) + continue; + + f = fopen(full, "r"); + if (!f) { + free(full); + continue; + } + + images = XcursorFileLoadImages(f, size); + + if (images) { + XcursorImagesSetName(images, ent->d_name); + load_callback(images, user_data); + } + + fclose (f); + free(full); + } + + closedir(dir); +} + +/** Load all the cursor of a theme + * + * This function loads all the cursor images of a given theme and its + * inherited themes. Each cursor is loaded into an XcursorImages object + * which is passed to the caller's load callback. If a cursor appears + * more than once across all the inherited themes, the load callback + * will be called multiple times, with possibly different XcursorImages + * object which have the same name. The user is expected to destroy the + * XcursorImages objects passed to the callback with + * XcursorImagesDestroy(). + * + * \param theme The name of theme that should be loaded + * \param size The desired size of the cursor images + * \param load_callback A callback function that will be called + * for each cursor loaded. The first parameter is the XcursorImages + * object representing the loaded cursor and the second is a pointer + * to data provided by the user. + * \param user_data The data that should be passed to the load callback + */ +void +xcursor_load_theme(const char *theme, int size, + void (*load_callback)(XcursorImages *, void *), + void *user_data) +{ + char *full, *dir; + char *inherits = NULL; + const char *path, *i; + + if (!theme) + theme = "default"; + + for (path = XcursorLibraryPath(); + path; + path = _XcursorNextPath(path)) { + dir = _XcursorBuildThemeDir(path, theme); + if (!dir) + continue; + + full = _XcursorBuildFullname(dir, "cursors", ""); + + if (full) { + load_all_cursors_from_dir(full, size, load_callback, + user_data); + free(full); + } + + if (!inherits) { + full = _XcursorBuildFullname(dir, "", "index.theme"); + if (full) { + inherits = _XcursorThemeInherits(full); + free(full); + } + } + + free(dir); + } + + for (i = inherits; i; i = _XcursorNextPath(i)) + xcursor_load_theme(i, size, load_callback, user_data); + + if (inherits) + free(inherits); +} diff --git a/xwayland/meson.build b/xwayland/meson.build new file mode 100644 index 00000000..e5f9b9be --- /dev/null +++ b/xwayland/meson.build @@ -0,0 +1,51 @@ +xwayland_libs = [] +xwayland_required = [ + 'xcb', + 'xcb-composite', + 'xcb-render', + 'xcb-xfixes', +] +xwayland_optional = [ + 'xcb-errors', + 'xcb-icccm', +] + +foreach lib : xwayland_required + dep = dependency(lib, required: get_option('xwayland')) + if not dep.found() + subdir_done() + endif + + xwayland_libs += dep +endforeach + +foreach lib : xwayland_optional + dep = dependency(lib, required: get_option(lib)) + if dep.found() + xwayland_libs += dep + conf_data.set10('WLR_HAS_' + lib.underscorify().to_upper(), true) + endif +endforeach + +lib_wlr_xwayland = static_library( + 'wlr_xwayland', + files( + 'selection/dnd.c', + 'selection/incoming.c', + 'selection/outgoing.c', + 'selection/selection.c', + 'sockets.c', + 'xwayland.c', + 'xwm.c', + ), + include_directories: wlr_inc, + dependencies: [ + wayland_server, + xwayland_libs, + xkbcommon, + pixman, + ], +) + +wlr_parts += lib_wlr_xwayland +conf_data.set10('WLR_HAS_XWAYLAND', true) diff --git a/xwayland/selection/dnd.c b/xwayland/selection/dnd.c new file mode 100644 index 00000000..ec5f16c7 --- /dev/null +++ b/xwayland/selection/dnd.c @@ -0,0 +1,339 @@ +#include <assert.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wlr/types/wlr_data_device.h> +#include <wlr/types/wlr_gtk_primary_selection.h> +#include <wlr/util/log.h> +#include <xcb/xfixes.h> +#include "xwayland/xwm.h" +#include "xwayland/selection.h" + +static xcb_atom_t data_device_manager_dnd_action_to_atom( + struct wlr_xwm *xwm, enum wl_data_device_manager_dnd_action action) { + if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) { + return xwm->atoms[DND_ACTION_COPY]; + } else if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) { + return xwm->atoms[DND_ACTION_MOVE]; + } else if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) { + return xwm->atoms[DND_ACTION_ASK]; + } + return XCB_ATOM_NONE; +} + +static enum wl_data_device_manager_dnd_action + data_device_manager_dnd_action_from_atom(struct wlr_xwm *xwm, + enum atom_name atom) { + if (atom == xwm->atoms[DND_ACTION_COPY] || + atom == xwm->atoms[DND_ACTION_PRIVATE]) { + return WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + } else if (atom == xwm->atoms[DND_ACTION_MOVE]) { + return WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + } else if (atom == xwm->atoms[DND_ACTION_ASK]) { + return WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; + } + return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; +} + +static void xwm_dnd_send_event(struct wlr_xwm *xwm, xcb_atom_t type, + xcb_client_message_data_t *data) { + struct wlr_xwayland_surface *dest = xwm->drag_focus; + assert(dest != NULL); + + xcb_client_message_event_t event = { + .response_type = XCB_CLIENT_MESSAGE, + .format = 32, + .sequence = 0, + .window = dest->window_id, + .type = type, + .data = *data, + }; + + xcb_send_event(xwm->xcb_conn, + 0, // propagate + dest->window_id, + XCB_EVENT_MASK_NO_EVENT, + (const char *)&event); + xcb_flush(xwm->xcb_conn); +} + +static void xwm_dnd_send_enter(struct wlr_xwm *xwm) { + struct wlr_drag *drag = xwm->drag; + assert(drag != NULL); + struct wl_array *mime_types = &drag->source->mime_types; + + xcb_client_message_data_t data = { 0 }; + data.data32[0] = xwm->dnd_window; + data.data32[1] = XDND_VERSION << 24; + + // If we have 3 MIME types or less, we can send them directly in the + // DND_ENTER message + size_t n = mime_types->size / sizeof(char *); + if (n <= 3) { + size_t i = 0; + char **mime_type_ptr; + wl_array_for_each(mime_type_ptr, mime_types) { + char *mime_type = *mime_type_ptr; + data.data32[2+i] = xwm_mime_type_to_atom(xwm, mime_type); + ++i; + } + } else { + // Let the client know that targets are not contained in the message + // data and must be retrieved with the DND_TYPE_LIST property + data.data32[1] |= 1; + + xcb_atom_t targets[n]; + size_t i = 0; + char **mime_type_ptr; + wl_array_for_each(mime_type_ptr, mime_types) { + char *mime_type = *mime_type_ptr; + targets[i] = xwm_mime_type_to_atom(xwm, mime_type); + ++i; + } + + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xwm->dnd_window, + xwm->atoms[DND_TYPE_LIST], + XCB_ATOM_ATOM, + 32, // format + n, targets); + } + + xwm_dnd_send_event(xwm, xwm->atoms[DND_ENTER], &data); +} + +static void xwm_dnd_send_position(struct wlr_xwm *xwm, uint32_t time, int16_t x, + int16_t y) { + struct wlr_drag *drag = xwm->drag; + assert(drag != NULL); + + xcb_client_message_data_t data = { 0 }; + data.data32[0] = xwm->dnd_window; + data.data32[2] = (x << 16) | y; + data.data32[3] = time; + data.data32[4] = + data_device_manager_dnd_action_to_atom(xwm, drag->source->actions); + + xwm_dnd_send_event(xwm, xwm->atoms[DND_POSITION], &data); +} + +static void xwm_dnd_send_drop(struct wlr_xwm *xwm, uint32_t time) { + struct wlr_drag *drag = xwm->drag; + assert(drag != NULL); + struct wlr_xwayland_surface *dest = xwm->drag_focus; + assert(dest != NULL); + + xcb_client_message_data_t data = { 0 }; + data.data32[0] = xwm->dnd_window; + data.data32[2] = time; + + xwm_dnd_send_event(xwm, xwm->atoms[DND_DROP], &data); +} + +static void xwm_dnd_send_leave(struct wlr_xwm *xwm) { + struct wlr_drag *drag = xwm->drag; + assert(drag != NULL); + struct wlr_xwayland_surface *dest = xwm->drag_focus; + assert(dest != NULL); + + xcb_client_message_data_t data = { 0 }; + data.data32[0] = xwm->dnd_window; + + xwm_dnd_send_event(xwm, xwm->atoms[DND_LEAVE], &data); +} + +/*static void xwm_dnd_send_finished(struct wlr_xwm *xwm) { + struct wlr_drag *drag = xwm->drag; + assert(drag != NULL); + struct wlr_xwayland_surface *dest = xwm->drag_focus; + assert(dest != NULL); + + xcb_client_message_data_t data = { 0 }; + data.data32[0] = xwm->dnd_window; + data.data32[1] = drag->source->accepted; + + if (drag->source->accepted) { + data.data32[2] = data_device_manager_dnd_action_to_atom(xwm, + drag->source->current_dnd_action); + } + + xwm_dnd_send_event(xwm, xwm->atoms[DND_FINISHED], &data); +}*/ + +int xwm_handle_selection_client_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *ev) { + if (ev->type == xwm->atoms[DND_STATUS]) { + if (xwm->drag == NULL) { + wlr_log(WLR_DEBUG, "ignoring XdndStatus client message because " + "there's no drag"); + return 1; + } + + xcb_client_message_data_t *data = &ev->data; + xcb_window_t target_window = data->data32[0]; + bool accepted = data->data32[1] & 1; + xcb_atom_t action_atom = data->data32[4]; + + if (xwm->drag_focus == NULL || + target_window != xwm->drag_focus->window_id) { + wlr_log(WLR_DEBUG, "ignoring XdndStatus client message because " + "it doesn't match the current drag focus window ID"); + return 1; + } + + enum wl_data_device_manager_dnd_action action = + data_device_manager_dnd_action_from_atom(xwm, action_atom); + + struct wlr_drag *drag = xwm->drag; + assert(drag != NULL); + + drag->source->accepted = accepted; + wlr_data_source_dnd_action(drag->source, action); + + wlr_log(WLR_DEBUG, "DND_STATUS window=%d accepted=%d action=%d", + target_window, accepted, action); + return 1; + } else if (ev->type == xwm->atoms[DND_FINISHED]) { + // This should only happen after the drag has ended, but before the drag + // source is destroyed + if (xwm->seat == NULL || xwm->seat->drag_source == NULL || + xwm->drag != NULL) { + wlr_log(WLR_DEBUG, "ignoring XdndFinished client message because " + "there's no finished drag"); + return 1; + } + + struct wlr_data_source *source = xwm->seat->drag_source; + + xcb_client_message_data_t *data = &ev->data; + xcb_window_t target_window = data->data32[0]; + bool performed = data->data32[1] & 1; + xcb_atom_t action_atom = data->data32[2]; + + if (xwm->drag_focus == NULL || + target_window != xwm->drag_focus->window_id) { + wlr_log(WLR_DEBUG, "ignoring XdndFinished client message because " + "it doesn't match the finished drag focus window ID"); + return 1; + } + + enum wl_data_device_manager_dnd_action action = + data_device_manager_dnd_action_from_atom(xwm, action_atom); + + if (performed) { + wlr_data_source_dnd_finish(source); + } + + wlr_log(WLR_DEBUG, "DND_FINISH window=%d performed=%d action=%d", + target_window, performed, action); + return 1; + } else { + return 0; + } +} + +static void seat_handle_drag_focus(struct wl_listener *listener, void *data) { + struct wlr_drag *drag = data; + struct wlr_xwm *xwm = wl_container_of(listener, xwm, seat_drag_focus); + + struct wlr_xwayland_surface *focus = NULL; + if (drag->focus != NULL) { + // TODO: check for subsurfaces? + struct wlr_xwayland_surface *surface; + wl_list_for_each(surface, &xwm->surfaces, link) { + if (surface->surface == drag->focus) { + focus = surface; + break; + } + } + } + + if (focus == xwm->drag_focus) { + return; + } + + if (xwm->drag_focus != NULL) { + wlr_data_source_dnd_action(drag->source, + WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE); + xwm_dnd_send_leave(xwm); + } + + xwm->drag_focus = focus; + + if (xwm->drag_focus != NULL) { + xwm_dnd_send_enter(xwm); + } +} + +static void seat_handle_drag_motion(struct wl_listener *listener, void *data) { + struct wlr_xwm *xwm = wl_container_of(listener, xwm, seat_drag_motion); + struct wlr_drag_motion_event *event = data; + struct wlr_xwayland_surface *surface = xwm->drag_focus; + + if (surface == NULL) { + return; // No xwayland surface focused + } + + xwm_dnd_send_position(xwm, event->time, surface->x + (int16_t)event->sx, + surface->y + (int16_t)event->sy); +} + +static void seat_handle_drag_drop(struct wl_listener *listener, void *data) { + struct wlr_xwm *xwm = wl_container_of(listener, xwm, seat_drag_drop); + struct wlr_drag_drop_event *event = data; + + if (xwm->drag_focus == NULL) { + return; // No xwayland surface focused + } + + wlr_log(WLR_DEBUG, "Wayland drag dropped over an Xwayland window"); + xwm_dnd_send_drop(xwm, event->time); +} + +static void seat_handle_drag_destroy(struct wl_listener *listener, void *data) { + struct wlr_xwm *xwm = wl_container_of(listener, xwm, seat_drag_destroy); + + // Don't reset drag focus yet because the target will read the drag source + // right after + if (xwm->drag_focus != NULL && !xwm->drag->source->accepted) { + wlr_log(WLR_DEBUG, "Wayland drag cancelled over an Xwayland window"); + xwm_dnd_send_leave(xwm); + } + + wl_list_remove(&xwm->seat_drag_focus.link); + wl_list_remove(&xwm->seat_drag_motion.link); + wl_list_remove(&xwm->seat_drag_drop.link); + wl_list_remove(&xwm->seat_drag_destroy.link); + xwm->drag = NULL; +} + +static void seat_handle_drag_source_destroy(struct wl_listener *listener, + void *data) { + struct wlr_xwm *xwm = + wl_container_of(listener, xwm, seat_drag_source_destroy); + + wl_list_remove(&xwm->seat_drag_source_destroy.link); + xwm->drag_focus = NULL; +} + +void xwm_seat_handle_start_drag(struct wlr_xwm *xwm, struct wlr_drag *drag) { + xwm->drag = drag; + xwm->drag_focus = NULL; + + if (drag != NULL) { + wl_signal_add(&drag->events.focus, &xwm->seat_drag_focus); + xwm->seat_drag_focus.notify = seat_handle_drag_focus; + wl_signal_add(&drag->events.motion, &xwm->seat_drag_motion); + xwm->seat_drag_motion.notify = seat_handle_drag_motion; + wl_signal_add(&drag->events.drop, &xwm->seat_drag_drop); + xwm->seat_drag_drop.notify = seat_handle_drag_drop; + wl_signal_add(&drag->events.destroy, &xwm->seat_drag_destroy); + xwm->seat_drag_destroy.notify = seat_handle_drag_destroy; + + wl_signal_add(&drag->source->events.destroy, + &xwm->seat_drag_source_destroy); + xwm->seat_drag_source_destroy.notify = seat_handle_drag_source_destroy; + } +} diff --git a/xwayland/selection/incoming.c b/xwayland/selection/incoming.c new file mode 100644 index 00000000..3e97ca04 --- /dev/null +++ b/xwayland/selection/incoming.c @@ -0,0 +1,464 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wlr/types/wlr_data_device.h> +#include <wlr/types/wlr_primary_selection.h> +#include <wlr/util/log.h> +#include <xcb/xfixes.h> +#include "xwayland/selection.h" +#include "xwayland/xwm.h" + +/** + * Write the X11 selection to a Wayland client. + */ +static int xwm_data_source_write(int fd, uint32_t mask, void *data) { + struct wlr_xwm_selection_transfer *transfer = data; + struct wlr_xwm *xwm = transfer->selection->xwm; + + char *property = xcb_get_property_value(transfer->property_reply); + int remainder = xcb_get_property_value_length(transfer->property_reply) - + transfer->property_start; + + ssize_t len = write(fd, property + transfer->property_start, remainder); + if (len == -1) { + xwm_selection_transfer_destroy_property_reply(transfer); + xwm_selection_transfer_remove_source(transfer); + xwm_selection_transfer_close_source_fd(transfer); + wlr_log(WLR_ERROR, "write error to target fd: %m"); + return 1; + } + + wlr_log(WLR_DEBUG, "wrote %zd (chunk size %zd) of %d bytes", + transfer->property_start + len, + len, xcb_get_property_value_length(transfer->property_reply)); + + transfer->property_start += len; + if (len == remainder) { + xwm_selection_transfer_destroy_property_reply(transfer); + xwm_selection_transfer_remove_source(transfer); + + if (transfer->incr) { + wlr_log(WLR_DEBUG, "deleting property"); + xcb_delete_property(xwm->xcb_conn, transfer->selection->window, + xwm->atoms[WL_SELECTION]); + xcb_flush(xwm->xcb_conn); + } else { + wlr_log(WLR_DEBUG, "transfer complete"); + xwm_selection_transfer_close_source_fd(transfer); + } + } + + return 1; +} + +static void xwm_write_property(struct wlr_xwm_selection_transfer *transfer, + xcb_get_property_reply_t *reply) { + struct wlr_xwm *xwm = transfer->selection->xwm; + + transfer->property_start = 0; + transfer->property_reply = reply; + + xwm_data_source_write(transfer->source_fd, WL_EVENT_WRITABLE, transfer); + + if (transfer->property_reply != NULL) { + struct wl_event_loop *loop = + wl_display_get_event_loop(xwm->xwayland->wl_display); + transfer->source = wl_event_loop_add_fd(loop, + transfer->source_fd, WL_EVENT_WRITABLE, xwm_data_source_write, + transfer); + } +} + +void xwm_get_incr_chunk(struct wlr_xwm_selection_transfer *transfer) { + struct wlr_xwm *xwm = transfer->selection->xwm; + wlr_log(WLR_DEBUG, "xwm_get_incr_chunk"); + + xcb_get_property_cookie_t cookie = xcb_get_property(xwm->xcb_conn, + 0, // delete + transfer->selection->window, + xwm->atoms[WL_SELECTION], + XCB_GET_PROPERTY_TYPE_ANY, + 0, // offset + 0x1fffffff // length + ); + + xcb_get_property_reply_t *reply = + xcb_get_property_reply(xwm->xcb_conn, cookie, NULL); + if (reply == NULL) { + wlr_log(WLR_ERROR, "cannot get selection property"); + return; + } + //dump_property(xwm, xwm->atoms[WL_SELECTION], reply); + + if (xcb_get_property_value_length(reply) > 0) { + /* Reply's ownership is transferred to xwm, which is responsible + * for freeing it */ + xwm_write_property(transfer, reply); + } else { + wlr_log(WLR_DEBUG, "transfer complete"); + xwm_selection_transfer_close_source_fd(transfer); + free(reply); + } +} + +static void xwm_selection_get_data(struct wlr_xwm_selection *selection) { + struct wlr_xwm *xwm = selection->xwm; + + xcb_get_property_cookie_t cookie = xcb_get_property(xwm->xcb_conn, + 1, // delete + selection->window, + xwm->atoms[WL_SELECTION], + XCB_GET_PROPERTY_TYPE_ANY, + 0, // offset + 0x1fffffff // length + ); + + xcb_get_property_reply_t *reply = + xcb_get_property_reply(xwm->xcb_conn, cookie, NULL); + if (reply == NULL) { + wlr_log(WLR_ERROR, "Cannot get selection property"); + return; + } + + struct wlr_xwm_selection_transfer *transfer = &selection->incoming; + if (reply->type == xwm->atoms[INCR]) { + transfer->incr = true; + free(reply); + } else { + transfer->incr = false; + // reply's ownership is transferred to wm, which is responsible + // for freeing it + xwm_write_property(transfer, reply); + } +} + +static void source_send(struct wlr_xwm_selection *selection, + struct wl_array *mime_types, struct wl_array *mime_types_atoms, + const char *requested_mime_type, int fd) { + struct wlr_xwm *xwm = selection->xwm; + struct wlr_xwm_selection_transfer *transfer = &selection->incoming; + + xcb_atom_t *atoms = mime_types_atoms->data; + bool found = false; + xcb_atom_t mime_type_atom; + char **mime_type_ptr; + size_t i = 0; + wl_array_for_each(mime_type_ptr, mime_types) { + char *mime_type = *mime_type_ptr; + if (strcmp(mime_type, requested_mime_type) == 0) { + found = true; + mime_type_atom = atoms[i]; + break; + } + ++i; + } + if (!found) { + wlr_log(WLR_DEBUG, "Cannot send X11 selection to Wayland: " + "unsupported MIME type"); + return; + } + + xcb_convert_selection(xwm->xcb_conn, + selection->window, + selection->atom, + mime_type_atom, + xwm->atoms[WL_SELECTION], + XCB_TIME_CURRENT_TIME); + + xcb_flush(xwm->xcb_conn); + + fcntl(fd, F_SETFL, O_WRONLY | O_NONBLOCK); + transfer->source_fd = fd; +} + +struct x11_data_source { + struct wlr_data_source base; + struct wlr_xwm_selection *selection; + struct wl_array mime_types_atoms; +}; + +static const struct wlr_data_source_impl data_source_impl; + +bool data_source_is_xwayland( + struct wlr_data_source *wlr_source) { + return wlr_source->impl == &data_source_impl; +} + +static struct x11_data_source *data_source_from_wlr_data_source( + struct wlr_data_source *wlr_source) { + assert(data_source_is_xwayland(wlr_source)); + return (struct x11_data_source *)wlr_source; +} + +static void data_source_send(struct wlr_data_source *wlr_source, + const char *mime_type, int32_t fd) { + struct x11_data_source *source = + data_source_from_wlr_data_source(wlr_source); + struct wlr_xwm_selection *selection = source->selection; + + source_send(selection, &wlr_source->mime_types, &source->mime_types_atoms, + mime_type, fd); +} + +static void data_source_cancel(struct wlr_data_source *wlr_source) { + struct x11_data_source *source = + data_source_from_wlr_data_source(wlr_source); + wlr_data_source_finish(&source->base); + wl_array_release(&source->mime_types_atoms); + free(source); +} + +static const struct wlr_data_source_impl data_source_impl = { + .send = data_source_send, + .cancel = data_source_cancel, +}; + +struct x11_primary_selection_source { + struct wlr_primary_selection_source base; + struct wlr_xwm_selection *selection; + struct wl_array mime_types_atoms; +}; + +static const struct wlr_primary_selection_source_impl + primary_selection_source_impl; + +bool primary_selection_source_is_xwayland( + struct wlr_primary_selection_source *wlr_source) { + return wlr_source->impl == &primary_selection_source_impl; +} + +static void primary_selection_source_send( + struct wlr_primary_selection_source *wlr_source, + const char *mime_type, int fd) { + struct x11_primary_selection_source *source = + (struct x11_primary_selection_source *)wlr_source; + struct wlr_xwm_selection *selection = source->selection; + + source_send(selection, &wlr_source->mime_types, &source->mime_types_atoms, + mime_type, fd); +} + +static void primary_selection_source_destroy( + struct wlr_primary_selection_source *wlr_source) { + struct x11_primary_selection_source *source = + (struct x11_primary_selection_source *)wlr_source; + wl_array_release(&source->mime_types_atoms); + free(source); +} + +static const struct wlr_primary_selection_source_impl + primary_selection_source_impl = { + .send = primary_selection_source_send, + .destroy = primary_selection_source_destroy, +}; + +static bool source_get_targets(struct wlr_xwm_selection *selection, + struct wl_array *mime_types, struct wl_array *mime_types_atoms) { + struct wlr_xwm *xwm = selection->xwm; + + xcb_get_property_cookie_t cookie = xcb_get_property(xwm->xcb_conn, + 1, // delete + selection->window, + xwm->atoms[WL_SELECTION], + XCB_GET_PROPERTY_TYPE_ANY, + 0, // offset + 4096 // length + ); + + xcb_get_property_reply_t *reply = + xcb_get_property_reply(xwm->xcb_conn, cookie, NULL); + if (reply == NULL) { + return false; + } + + if (reply->type != XCB_ATOM_ATOM) { + free(reply); + return false; + } + + xcb_atom_t *value = xcb_get_property_value(reply); + for (uint32_t i = 0; i < reply->value_len; i++) { + char *mime_type = NULL; + + if (value[i] == xwm->atoms[UTF8_STRING]) { + mime_type = strdup("text/plain;charset=utf-8"); + } else if (value[i] == xwm->atoms[TEXT]) { + mime_type = strdup("text/plain"); + } else if (value[i] != xwm->atoms[TARGETS] && + value[i] != xwm->atoms[TIMESTAMP]) { + xcb_get_atom_name_cookie_t name_cookie = + xcb_get_atom_name(xwm->xcb_conn, value[i]); + xcb_get_atom_name_reply_t *name_reply = + xcb_get_atom_name_reply(xwm->xcb_conn, name_cookie, NULL); + if (name_reply == NULL) { + continue; + } + size_t len = xcb_get_atom_name_name_length(name_reply); + char *name = xcb_get_atom_name_name(name_reply); // not a C string + if (memchr(name, '/', len) != NULL) { + mime_type = malloc((len + 1) * sizeof(char)); + if (mime_type == NULL) { + free(name_reply); + continue; + } + memcpy(mime_type, name, len); + mime_type[len] = '\0'; + } + free(name_reply); + } + + if (mime_type != NULL) { + char **mime_type_ptr = + wl_array_add(mime_types, sizeof(*mime_type_ptr)); + if (mime_type_ptr == NULL) { + free(mime_type); + break; + } + *mime_type_ptr = mime_type; + + xcb_atom_t *atom_ptr = + wl_array_add(mime_types_atoms, sizeof(*atom_ptr)); + if (atom_ptr == NULL) { + break; + } + *atom_ptr = value[i]; + } + } + + free(reply); + return true; +} + +static void xwm_selection_get_targets(struct wlr_xwm_selection *selection) { + // set the wayland selection to the X11 selection + struct wlr_xwm *xwm = selection->xwm; + + if (selection == &xwm->clipboard_selection) { + struct x11_data_source *source = + calloc(1, sizeof(struct x11_data_source)); + if (source == NULL) { + return; + } + wlr_data_source_init(&source->base, &data_source_impl); + + source->selection = selection; + wl_array_init(&source->mime_types_atoms); + + bool ok = source_get_targets(selection, &source->base.mime_types, + &source->mime_types_atoms); + if (ok) { + wlr_seat_set_selection(xwm->seat, &source->base, + wl_display_next_serial(xwm->xwayland->wl_display)); + } else { + wlr_data_source_cancel(&source->base); + } + } else if (selection == &xwm->primary_selection) { + struct x11_primary_selection_source *source = + calloc(1, sizeof(struct x11_primary_selection_source)); + if (source == NULL) { + return; + } + wlr_primary_selection_source_init(&source->base, + &primary_selection_source_impl); + + source->selection = selection; + wl_array_init(&source->mime_types_atoms); + + bool ok = source_get_targets(selection, &source->base.mime_types, + &source->mime_types_atoms); + if (ok) { + wlr_seat_set_primary_selection(xwm->seat, &source->base); + } else { + wlr_primary_selection_source_destroy(&source->base); + } + } else if (selection == &xwm->dnd_selection) { + // TODO + } +} + +void xwm_handle_selection_notify(struct wlr_xwm *xwm, + xcb_selection_notify_event_t *event) { + wlr_log(WLR_DEBUG, "XCB_SELECTION_NOTIFY (selection=%u, property=%u, target=%u)", + event->selection, event->property, + event->target); + + struct wlr_xwm_selection *selection = + xwm_get_selection(xwm, event->selection); + if (selection == NULL) { + return; + } + + if (event->property == XCB_ATOM_NONE) { + wlr_log(WLR_ERROR, "convert selection failed"); + } else if (event->target == xwm->atoms[TARGETS]) { + // No xwayland surface focused, deny access to clipboard + if (xwm->focus_surface == NULL) { + wlr_log(WLR_DEBUG, "denying write access to clipboard: " + "no xwayland surface focused"); + return; + } + + // This sets the Wayland clipboard (by calling wlr_seat_set_selection) + xwm_selection_get_targets(selection); + } else { + xwm_selection_get_data(selection); + } +} + +int xwm_handle_xfixes_selection_notify(struct wlr_xwm *xwm, + xcb_xfixes_selection_notify_event_t *event) { + wlr_log(WLR_DEBUG, "XCB_XFIXES_SELECTION_NOTIFY (selection=%u, owner=%u)", + event->selection, event->owner); + + struct wlr_xwm_selection *selection = + xwm_get_selection(xwm, event->selection); + if (selection == NULL) { + return 0; + } + + if (event->owner == XCB_WINDOW_NONE) { + if (selection->owner != selection->window) { + // A real X client selection went away, not our + // proxy selection + if (selection == &xwm->clipboard_selection) { + wlr_seat_set_selection(xwm->seat, NULL, + wl_display_next_serial(xwm->xwayland->wl_display)); + } else if (selection == &xwm->primary_selection) { + wlr_seat_set_primary_selection(xwm->seat, NULL); + } else if (selection == &xwm->dnd_selection) { + // TODO: DND + } else { + wlr_log(WLR_DEBUG, "X11 selection has been cleared, but cannot " + "clear Wayland selection"); + } + } + + selection->owner = XCB_WINDOW_NONE; + return 1; + } + + selection->owner = event->owner; + + // We have to use XCB_TIME_CURRENT_TIME when we claim the + // selection, so grab the actual timestamp here so we can + // answer TIMESTAMP conversion requests correctly. + if (event->owner == selection->window) { + selection->timestamp = event->timestamp; + return 1; + } + + struct wlr_xwm_selection_transfer *transfer = &selection->incoming; + transfer->incr = false; + // doing this will give a selection notify where we actually handle the sync + xcb_convert_selection(xwm->xcb_conn, selection->window, + selection->atom, + xwm->atoms[TARGETS], + xwm->atoms[WL_SELECTION], + event->timestamp); + xcb_flush(xwm->xcb_conn); + + return 1; +} diff --git a/xwayland/selection/outgoing.c b/xwayland/selection/outgoing.c new file mode 100644 index 00000000..fd5021d5 --- /dev/null +++ b/xwayland/selection/outgoing.c @@ -0,0 +1,419 @@ +#include <assert.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wlr/types/wlr_data_device.h> +#include <wlr/types/wlr_primary_selection.h> +#include <wlr/util/log.h> +#include <xcb/xfixes.h> +#include "xwayland/selection.h" +#include "xwayland/xwm.h" + +static void xwm_selection_send_notify(struct wlr_xwm *xwm, + xcb_selection_request_event_t *req, bool success) { + xcb_selection_notify_event_t selection_notify = { + .response_type = XCB_SELECTION_NOTIFY, + .sequence = 0, + .time = req->time, + .requestor = req->requestor, + .selection = req->selection, + .target = req->target, + .property = success ? req->property : XCB_ATOM_NONE, + }; + + wlr_log(WLR_DEBUG, "SendEvent destination=%d SelectionNotify(31) time=%d " + "requestor=%d selection=%d target=%d property=%d", req->requestor, + req->time, req->requestor, req->selection, req->target, + selection_notify.property); + xcb_send_event(xwm->xcb_conn, + 0, // propagate + req->requestor, + XCB_EVENT_MASK_NO_EVENT, + (const char *)&selection_notify); + xcb_flush(xwm->xcb_conn); +} + +static int xwm_selection_flush_source_data( + struct wlr_xwm_selection_transfer *transfer) { + xcb_change_property(transfer->selection->xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + transfer->request.requestor, + transfer->request.property, + transfer->request.target, + 8, // format + transfer->source_data.size, + transfer->source_data.data); + xcb_flush(transfer->selection->xwm->xcb_conn); + transfer->property_set = true; + size_t length = transfer->source_data.size; + transfer->source_data.size = 0; + return length; +} + +static void xwm_selection_transfer_start_outgoing( + struct wlr_xwm_selection_transfer *transfer); + +static void xwm_selection_transfer_destroy_outgoing( + struct wlr_xwm_selection_transfer *transfer) { + wl_list_remove(&transfer->outgoing_link); + + // Start next queued transfer + struct wlr_xwm_selection_transfer *first = NULL; + if (!wl_list_empty(&transfer->selection->outgoing)) { + first = wl_container_of(transfer->selection->outgoing.prev, first, + outgoing_link); + xwm_selection_transfer_start_outgoing(first); + } + + xwm_selection_transfer_remove_source(transfer); + xwm_selection_transfer_close_source_fd(transfer); + wl_array_release(&transfer->source_data); + free(transfer); +} + +static int xwm_data_source_read(int fd, uint32_t mask, void *data) { + struct wlr_xwm_selection_transfer *transfer = data; + struct wlr_xwm *xwm = transfer->selection->xwm; + + void *p; + size_t current = transfer->source_data.size; + if (transfer->source_data.size < INCR_CHUNK_SIZE) { + p = wl_array_add(&transfer->source_data, INCR_CHUNK_SIZE); + if (p == NULL) { + wlr_log(WLR_ERROR, "Could not allocate selection source_data"); + goto error_out; + } + } else { + p = (char *)transfer->source_data.data + transfer->source_data.size; + } + + size_t available = transfer->source_data.alloc - current; + ssize_t len = read(fd, p, available); + if (len == -1) { + wlr_log(WLR_ERROR, "read error from data source: %m"); + goto error_out; + } + + wlr_log(WLR_DEBUG, "read %zd bytes (available %zu, mask 0x%x)", len, + available, mask); + + transfer->source_data.size = current + len; + if (transfer->source_data.size >= INCR_CHUNK_SIZE) { + if (!transfer->incr) { + wlr_log(WLR_DEBUG, "got %zu bytes, starting incr", + transfer->source_data.size); + + size_t incr_chunk_size = INCR_CHUNK_SIZE; + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + transfer->request.requestor, + transfer->request.property, + xwm->atoms[INCR], + 32, /* format */ + 1, &incr_chunk_size); + transfer->incr = true; + transfer->property_set = true; + transfer->flush_property_on_delete = true; + xwm_selection_transfer_remove_source(transfer); + xwm_selection_send_notify(xwm, &transfer->request, true); + } else if (transfer->property_set) { + wlr_log(WLR_DEBUG, "got %zu bytes, waiting for property delete", + transfer->source_data.size); + + transfer->flush_property_on_delete = true; + xwm_selection_transfer_remove_source(transfer); + } else { + wlr_log(WLR_DEBUG, "got %zu bytes, property deleted, setting new " + "property", transfer->source_data.size); + xwm_selection_flush_source_data(transfer); + } + } else if (len == 0 && !transfer->incr) { + wlr_log(WLR_DEBUG, "non-incr transfer complete"); + xwm_selection_flush_source_data(transfer); + xwm_selection_send_notify(xwm, &transfer->request, true); + xwm_selection_transfer_destroy_outgoing(transfer); + } else if (len == 0 && transfer->incr) { + wlr_log(WLR_DEBUG, "incr transfer complete"); + + transfer->flush_property_on_delete = true; + if (transfer->property_set) { + wlr_log(WLR_DEBUG, "got %zu bytes, waiting for property delete", + transfer->source_data.size); + } else { + wlr_log(WLR_DEBUG, "got %zu bytes, property deleted, setting new " + "property", transfer->source_data.size); + xwm_selection_flush_source_data(transfer); + } + xwm_selection_transfer_remove_source(transfer); + xwm_selection_transfer_close_source_fd(transfer); + } else { + wlr_log(WLR_DEBUG, "nothing happened, buffered the bytes"); + } + + return 1; + +error_out: + xwm_selection_send_notify(xwm, &transfer->request, false); + xwm_selection_transfer_destroy_outgoing(transfer); + return 0; +} + +void xwm_send_incr_chunk(struct wlr_xwm_selection_transfer *transfer) { + wlr_log(WLR_DEBUG, "property deleted"); + + transfer->property_set = false; + if (transfer->flush_property_on_delete) { + wlr_log(WLR_DEBUG, "setting new property, %zu bytes", + transfer->source_data.size); + transfer->flush_property_on_delete = false; + int length = xwm_selection_flush_source_data(transfer); + + if (transfer->source_fd >= 0) { + xwm_selection_transfer_start_outgoing(transfer); + } else if (length > 0) { + /* Transfer is all done, but queue a flush for + * the delete of the last chunk so we can set + * the 0 sized property to signal the end of + * the transfer. */ + transfer->flush_property_on_delete = true; + wl_array_release(&transfer->source_data); + wl_array_init(&transfer->source_data); + } else { + xwm_selection_transfer_destroy_outgoing(transfer); + } + } +} + +static void xwm_selection_source_send(struct wlr_xwm_selection *selection, + const char *mime_type, int32_t fd) { + if (selection == &selection->xwm->clipboard_selection) { + struct wlr_data_source *source = + selection->xwm->seat->selection_source; + if (source != NULL) { + wlr_data_source_send(source, mime_type, fd); + return; + } + } else if (selection == &selection->xwm->primary_selection) { + struct wlr_primary_selection_source *source = + selection->xwm->seat->primary_selection_source; + if (source != NULL) { + wlr_primary_selection_source_send(source, mime_type, fd); + return; + } + } else if (selection == &selection->xwm->dnd_selection) { + struct wlr_data_source *source = + selection->xwm->seat->drag_source; + if (source != NULL) { + wlr_data_source_send(source, mime_type, fd); + return; + } + } + + wlr_log(WLR_DEBUG, "not sending selection: no selection source available"); +} + +static void xwm_selection_transfer_start_outgoing( + struct wlr_xwm_selection_transfer *transfer) { + struct wlr_xwm *xwm = transfer->selection->xwm; + struct wl_event_loop *loop = + wl_display_get_event_loop(xwm->xwayland->wl_display); + transfer->source = wl_event_loop_add_fd(loop, transfer->source_fd, + WL_EVENT_READABLE, xwm_data_source_read, transfer); +} + +static struct wl_array *xwm_selection_source_get_mime_types( + struct wlr_xwm_selection *selection) { + if (selection == &selection->xwm->clipboard_selection) { + struct wlr_data_source *source = + selection->xwm->seat->selection_source; + if (source != NULL) { + return &source->mime_types; + } + } else if (selection == &selection->xwm->primary_selection) { + struct wlr_primary_selection_source *source = + selection->xwm->seat->primary_selection_source; + if (source != NULL) { + return &source->mime_types; + } + } else if (selection == &selection->xwm->dnd_selection) { + struct wlr_data_source *source = + selection->xwm->seat->drag_source; + if (source != NULL) { + return &source->mime_types; + } + } + return NULL; +} + +/** + * Read the Wayland selection and send it to an Xwayland client. + */ +static void xwm_selection_send_data(struct wlr_xwm_selection *selection, + xcb_selection_request_event_t *req, const char *mime_type) { + // Check MIME type + struct wl_array *mime_types = + xwm_selection_source_get_mime_types(selection); + if (mime_types == NULL) { + wlr_log(WLR_ERROR, "not sending selection: no MIME type list available"); + xwm_selection_send_notify(selection->xwm, req, false); + return; + } + + bool found = false; + char **mime_type_ptr; + wl_array_for_each(mime_type_ptr, mime_types) { + char *t = *mime_type_ptr; + if (strcmp(t, mime_type) == 0) { + found = true; + break; + } + } + if (!found) { + wlr_log(WLR_ERROR, "not sending selection: " + "requested an unsupported MIME type %s", mime_type); + xwm_selection_send_notify(selection->xwm, req, false); + return; + } + + struct wlr_xwm_selection_transfer *transfer = + calloc(1, sizeof(struct wlr_xwm_selection_transfer)); + if (transfer == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + return; + } + transfer->selection = selection; + transfer->request = *req; + wl_array_init(&transfer->source_data); + + int p[2]; + if (pipe(p) == -1) { + wlr_log(WLR_ERROR, "pipe() failed: %m"); + xwm_selection_send_notify(selection->xwm, req, false); + return; + } + fcntl(p[0], F_SETFD, FD_CLOEXEC); + fcntl(p[0], F_SETFL, O_NONBLOCK); + fcntl(p[1], F_SETFD, FD_CLOEXEC); + fcntl(p[1], F_SETFL, O_NONBLOCK); + + transfer->source_fd = p[0]; + + wlr_log(WLR_DEBUG, "Sending Wayland selection %u to Xwayland window with " + "MIME type %s, target %u", req->target, mime_type, req->target); + xwm_selection_source_send(selection, mime_type, p[1]); + + wl_list_insert(&selection->outgoing, &transfer->outgoing_link); + + // We can only handle one transfer at a time + if (wl_list_length(&selection->outgoing) == 1) { + xwm_selection_transfer_start_outgoing(transfer); + } +} + +static void xwm_selection_send_targets(struct wlr_xwm_selection *selection, + xcb_selection_request_event_t *req) { + struct wlr_xwm *xwm = selection->xwm; + + struct wl_array *mime_types = + xwm_selection_source_get_mime_types(selection); + if (mime_types == NULL) { + wlr_log(WLR_ERROR, "not sending selection targets: " + "no selection source available"); + xwm_selection_send_notify(selection->xwm, req, false); + return; + } + + size_t n = 2 + mime_types->size / sizeof(char *); + xcb_atom_t targets[n]; + targets[0] = xwm->atoms[TIMESTAMP]; + targets[1] = xwm->atoms[TARGETS]; + + size_t i = 0; + char **mime_type_ptr; + wl_array_for_each(mime_type_ptr, mime_types) { + char *mime_type = *mime_type_ptr; + targets[2+i] = xwm_mime_type_to_atom(xwm, mime_type); + ++i; + } + + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + req->requestor, + req->property, + XCB_ATOM_ATOM, + 32, // format + n, targets); + + xwm_selection_send_notify(selection->xwm, req, true); +} + +static void xwm_selection_send_timestamp(struct wlr_xwm_selection *selection, + xcb_selection_request_event_t *req) { + xcb_change_property(selection->xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + req->requestor, + req->property, + XCB_ATOM_INTEGER, + 32, // format + 1, &selection->timestamp); + + xwm_selection_send_notify(selection->xwm, req, true); +} + +void xwm_handle_selection_request(struct wlr_xwm *xwm, + xcb_selection_request_event_t *req) { + wlr_log(WLR_DEBUG, "XCB_SELECTION_REQUEST (time=%u owner=%u, requestor=%u " + "selection=%u, target=%u, property=%u)", + req->time, req->owner, req->requestor, req->selection, req->target, + req->property); + + if (req->selection == xwm->atoms[CLIPBOARD_MANAGER]) { + // The wlroots clipboard should already have grabbed the first target, + // so just send selection notify now. This isn't synchronized with the + // clipboard finishing getting the data, so there's a race here. + xwm_selection_send_notify(xwm, req, true); + return; + } + + struct wlr_xwm_selection *selection = + xwm_get_selection(xwm, req->selection); + if (selection == NULL) { + wlr_log(WLR_DEBUG, "received selection request for unknown selection"); + return; + } + + if (selection->window != req->owner) { + wlr_log(WLR_DEBUG, "received selection request with invalid owner"); + return; + } + + // No xwayland surface focused, deny access to clipboard + if (xwm->focus_surface == NULL && xwm->drag_focus == NULL) { + char *selection_name = xwm_get_atom_name(xwm, selection->atom); + wlr_log(WLR_DEBUG, "denying read access to selection %u (%s): " + "no xwayland surface focused", selection->atom, selection_name); + free(selection_name); + xwm_selection_send_notify(xwm, req, false); + return; + } + + if (req->target == xwm->atoms[TARGETS]) { + xwm_selection_send_targets(selection, req); + } else if (req->target == xwm->atoms[TIMESTAMP]) { + xwm_selection_send_timestamp(selection, req); + } else if (req->target == xwm->atoms[DELETE]) { + xwm_selection_send_notify(selection->xwm, req, true); + } else { + // Send data + char *mime_type = xwm_mime_type_from_atom(xwm, req->target); + if (mime_type == NULL) { + wlr_log(WLR_ERROR, "ignoring selection request: unknown atom %u", + req->target); + xwm_selection_send_notify(xwm, req, false); + return; + } + xwm_selection_send_data(selection, req, mime_type); + free(mime_type); + } +} diff --git a/xwayland/selection/selection.c b/xwayland/selection/selection.c new file mode 100644 index 00000000..4d7732cb --- /dev/null +++ b/xwayland/selection/selection.c @@ -0,0 +1,317 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wlr/types/wlr_data_device.h> +#include <wlr/types/wlr_primary_selection.h> +#include <wlr/util/log.h> +#include <xcb/xfixes.h> +#include "xwayland/selection.h" +#include "xwayland/xwm.h" + +void xwm_selection_transfer_remove_source( + struct wlr_xwm_selection_transfer *transfer) { + if (transfer->source != NULL) { + wl_event_source_remove(transfer->source); + transfer->source = NULL; + } +} + +void xwm_selection_transfer_close_source_fd( + struct wlr_xwm_selection_transfer *transfer) { + if (transfer->source_fd >= 0) { + close(transfer->source_fd); + transfer->source_fd = -1; + } +} + +void xwm_selection_transfer_destroy_property_reply( + struct wlr_xwm_selection_transfer *transfer) { + free(transfer->property_reply); + transfer->property_reply = NULL; +} + +xcb_atom_t xwm_mime_type_to_atom(struct wlr_xwm *xwm, char *mime_type) { + if (strcmp(mime_type, "text/plain;charset=utf-8") == 0) { + return xwm->atoms[UTF8_STRING]; + } else if (strcmp(mime_type, "text/plain") == 0) { + return xwm->atoms[TEXT]; + } + + xcb_intern_atom_cookie_t cookie = + xcb_intern_atom(xwm->xcb_conn, 0, strlen(mime_type), mime_type); + xcb_intern_atom_reply_t *reply = + xcb_intern_atom_reply(xwm->xcb_conn, cookie, NULL); + if (reply == NULL) { + return XCB_ATOM_NONE; + } + xcb_atom_t atom = reply->atom; + free(reply); + return atom; +} + +char *xwm_mime_type_from_atom(struct wlr_xwm *xwm, xcb_atom_t atom) { + if (atom == xwm->atoms[UTF8_STRING]) { + return strdup("text/plain;charset=utf-8"); + } else if (atom == xwm->atoms[TEXT]) { + return strdup("text/plain"); + } else { + return xwm_get_atom_name(xwm, atom); + } +} + +struct wlr_xwm_selection *xwm_get_selection(struct wlr_xwm *xwm, + xcb_atom_t selection_atom) { + if (selection_atom == xwm->atoms[CLIPBOARD]) { + return &xwm->clipboard_selection; + } else if (selection_atom == xwm->atoms[PRIMARY]) { + return &xwm->primary_selection; + } else if (selection_atom == xwm->atoms[DND_SELECTION]) { + return &xwm->dnd_selection; + } else { + return NULL; + } +} + +static int xwm_handle_selection_property_notify(struct wlr_xwm *xwm, + xcb_property_notify_event_t *event) { + struct wlr_xwm_selection *selections[] = { + &xwm->clipboard_selection, + &xwm->primary_selection, + &xwm->dnd_selection, + }; + + for (size_t i = 0; i < sizeof(selections)/sizeof(selections[0]); ++i) { + struct wlr_xwm_selection *selection = selections[i]; + + if (event->window == selection->window) { + if (event->state == XCB_PROPERTY_NEW_VALUE && + event->atom == xwm->atoms[WL_SELECTION] && + selection->incoming.incr) { + xwm_get_incr_chunk(&selection->incoming); + } + return 1; + } + + struct wlr_xwm_selection_transfer *outgoing; + wl_list_for_each(outgoing, &selection->outgoing, outgoing_link) { + if (event->window == outgoing->request.requestor) { + if (event->state == XCB_PROPERTY_DELETE && + event->atom == outgoing->request.property && + outgoing->incr) { + xwm_send_incr_chunk(outgoing); + } + return 1; + } + } + } + + return 0; +} + +int xwm_handle_selection_event(struct wlr_xwm *xwm, + xcb_generic_event_t *event) { + if (xwm->seat == NULL) { + wlr_log(WLR_DEBUG, "not handling selection events: " + "no seat assigned to xwayland"); + return 0; + } + + switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) { + case XCB_SELECTION_NOTIFY: + xwm_handle_selection_notify(xwm, (xcb_selection_notify_event_t *)event); + return 1; + case XCB_PROPERTY_NOTIFY: + return xwm_handle_selection_property_notify(xwm, + (xcb_property_notify_event_t *)event); + case XCB_SELECTION_REQUEST: + xwm_handle_selection_request(xwm, + (xcb_selection_request_event_t *)event); + return 1; + } + + switch (event->response_type - xwm->xfixes->first_event) { + case XCB_XFIXES_SELECTION_NOTIFY: + // an X11 window has copied something to the clipboard + return xwm_handle_xfixes_selection_notify(xwm, + (xcb_xfixes_selection_notify_event_t *)event); + } + + return 0; +} + +static void selection_init(struct wlr_xwm *xwm, + struct wlr_xwm_selection *selection, xcb_atom_t atom) { + selection->xwm = xwm; + selection->atom = atom; + selection->window = xwm->selection_window; + selection->incoming.selection = selection; + wl_list_init(&selection->outgoing); + + uint32_t mask = + XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | + XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | + XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE; + xcb_xfixes_select_selection_input(xwm->xcb_conn, selection->window, + selection->atom, mask); +} + +void xwm_selection_init(struct wlr_xwm *xwm) { + // Clipboard and primary selection + uint32_t selection_values[] = { + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE + }; + xwm->selection_window = xcb_generate_id(xwm->xcb_conn); + xcb_create_window(xwm->xcb_conn, + XCB_COPY_FROM_PARENT, + xwm->selection_window, + xwm->screen->root, + 0, 0, + 10, 10, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + xwm->screen->root_visual, + XCB_CW_EVENT_MASK, selection_values); + + xcb_set_selection_owner(xwm->xcb_conn, + xwm->selection_window, + xwm->atoms[CLIPBOARD_MANAGER], + XCB_TIME_CURRENT_TIME); + + selection_init(xwm, &xwm->clipboard_selection, xwm->atoms[CLIPBOARD]); + selection_init(xwm, &xwm->primary_selection, xwm->atoms[PRIMARY]); + + // Drag'n'drop + uint32_t dnd_values[] = { + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE + }; + xwm->dnd_window = xcb_generate_id(xwm->xcb_conn); + xcb_create_window(xwm->xcb_conn, + XCB_COPY_FROM_PARENT, + xwm->dnd_window, + xwm->screen->root, + 0, 0, + 8192, 8192, + 0, + XCB_WINDOW_CLASS_INPUT_ONLY, + xwm->screen->root_visual, + XCB_CW_EVENT_MASK, dnd_values); + + uint32_t version = XDND_VERSION; + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xwm->dnd_window, + xwm->atoms[DND_AWARE], + XCB_ATOM_ATOM, + 32, // format + 1, &version); + + selection_init(xwm, &xwm->dnd_selection, xwm->atoms[DND_SELECTION]); +} + +void xwm_selection_finish(struct wlr_xwm *xwm) { + if (!xwm) { + return; + } + if (xwm->selection_window) { + xcb_destroy_window(xwm->xcb_conn, xwm->selection_window); + } + if (xwm->dnd_window) { + xcb_destroy_window(xwm->xcb_conn, xwm->dnd_window); + } + if (xwm->seat) { + if (xwm->seat->selection_source && + data_source_is_xwayland( + xwm->seat->selection_source)) { + wlr_seat_set_selection(xwm->seat, NULL, + wl_display_next_serial(xwm->xwayland->wl_display)); + } + if (xwm->seat->primary_selection_source && + primary_selection_source_is_xwayland( + xwm->seat->primary_selection_source)) { + wlr_seat_set_primary_selection(xwm->seat, NULL); + } + wlr_xwayland_set_seat(xwm->xwayland, NULL); + } +} + +static void xwm_selection_set_owner(struct wlr_xwm_selection *selection, + bool set) { + if (set) { + xcb_set_selection_owner(selection->xwm->xcb_conn, + selection->window, + selection->atom, + XCB_TIME_CURRENT_TIME); + } else { + if (selection->owner == selection->window) { + xcb_set_selection_owner(selection->xwm->xcb_conn, + XCB_WINDOW_NONE, + selection->atom, + selection->timestamp); + } + } +} + +static void seat_handle_selection(struct wl_listener *listener, + void *data) { + struct wlr_seat *seat = data; + struct wlr_xwm *xwm = + wl_container_of(listener, xwm, seat_selection); + struct wlr_data_source *source = seat->selection_source; + + if (source != NULL && data_source_is_xwayland(source)) { + return; + } + + xwm_selection_set_owner(&xwm->clipboard_selection, source != NULL); +} + +static void seat_handle_primary_selection(struct wl_listener *listener, + void *data) { + struct wlr_seat *seat = data; + struct wlr_xwm *xwm = + wl_container_of(listener, xwm, seat_primary_selection); + struct wlr_primary_selection_source *source = + seat->primary_selection_source; + + if (source != NULL && primary_selection_source_is_xwayland(source)) { + return; + } + + xwm_selection_set_owner(&xwm->primary_selection, source != NULL); +} + +static void seat_handle_start_drag(struct wl_listener *listener, void *data) { + struct wlr_xwm *xwm = wl_container_of(listener, xwm, seat_start_drag); + struct wlr_drag *drag = data; + + xwm_selection_set_owner(&xwm->dnd_selection, drag != NULL); + xwm_seat_handle_start_drag(xwm, drag); +} + +void xwm_set_seat(struct wlr_xwm *xwm, struct wlr_seat *seat) { + if (xwm->seat != NULL) { + wl_list_remove(&xwm->seat_selection.link); + wl_list_remove(&xwm->seat_primary_selection.link); + wl_list_remove(&xwm->seat_start_drag.link); + xwm->seat = NULL; + } + + if (seat == NULL) { + return; + } + + xwm->seat = seat; + + wl_signal_add(&seat->events.selection, &xwm->seat_selection); + xwm->seat_selection.notify = seat_handle_selection; + wl_signal_add(&seat->events.primary_selection, &xwm->seat_primary_selection); + xwm->seat_primary_selection.notify = seat_handle_primary_selection; + wl_signal_add(&seat->events.start_drag, &xwm->seat_start_drag); + xwm->seat_start_drag.notify = seat_handle_start_drag; + + seat_handle_selection(&xwm->seat_selection, seat); + seat_handle_primary_selection(&xwm->seat_primary_selection, seat); +} diff --git a/xwayland/sockets.c b/xwayland/sockets.c new file mode 100644 index 00000000..112a8bb0 --- /dev/null +++ b/xwayland/sockets.c @@ -0,0 +1,168 @@ +#define _POSIX_C_SOURCE 200809L +#ifdef __FreeBSD__ +// for SOCK_CLOEXEC +#define __BSD_VISIBLE 1 +#endif +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> +#include <unistd.h> +#include <wlr/util/log.h> +#include "sockets.h" + +static const char *lock_fmt = "/tmp/.X%d-lock"; +static const char *socket_dir = "/tmp/.X11-unix"; +static const char *socket_fmt = "/tmp/.X11-unix/X%d"; +#ifndef __linux__ +static const char *socket_fmt2 = "/tmp/.X11-unix/X%d_"; +#endif + +static int open_socket(struct sockaddr_un *addr, size_t path_size) { + int fd, rc; + socklen_t size = offsetof(struct sockaddr_un, sun_path) + path_size + 1; + + fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + wlr_log_errno(WLR_DEBUG, "Failed to create socket %c%s", + addr->sun_path[0] ? addr->sun_path[0] : '@', + addr->sun_path + 1); + return -1; + } + + if (addr->sun_path[0]) { + unlink(addr->sun_path); + } + if (bind(fd, (struct sockaddr*)addr, size) < 0) { + rc = errno; + wlr_log_errno(WLR_DEBUG, "Failed to bind socket %c%s", + addr->sun_path[0] ? addr->sun_path[0] : '@', + addr->sun_path + 1); + goto cleanup; + } + if (listen(fd, 1) < 0) { + rc = errno; + wlr_log_errno(WLR_DEBUG, "Failed to listen to socket %c%s", + addr->sun_path[0] ? addr->sun_path[0] : '@', + addr->sun_path + 1); + goto cleanup; + } + + return fd; + +cleanup: + close(fd); + if (addr->sun_path[0]) { + unlink(addr->sun_path); + } + errno = rc; + return -1; +} + +static bool open_sockets(int socks[2], int display) { + struct sockaddr_un addr = { .sun_family = AF_LOCAL }; + size_t path_size; + + mkdir(socket_dir, 0777); + +#ifdef __linux__ + addr.sun_path[0] = 0; + path_size = snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1, socket_fmt, display); +#else + path_size = snprintf(addr.sun_path, sizeof(addr.sun_path), socket_fmt2, display); +#endif + socks[0] = open_socket(&addr, path_size); + if (socks[0] < 0) { + return false; + } + + path_size = snprintf(addr.sun_path, sizeof(addr.sun_path), socket_fmt, display); + socks[1] = open_socket(&addr, path_size); + if (socks[1] < 0) { + close(socks[0]); + socks[0] = -1; + return false; + } + + return true; +} + +void unlink_display_sockets(int display) { + char sun_path[64]; + + snprintf(sun_path, sizeof(sun_path), socket_fmt, display); + unlink(sun_path); + +#ifndef __linux__ + snprintf(sun_path, sizeof(sun_path), socket_fmt2, display); + unlink(sun_path); +#endif + + snprintf(sun_path, sizeof(sun_path), lock_fmt, display); + unlink(sun_path); +} + +int open_display_sockets(int socks[2]) { + int lock_fd, display; + char lock_name[64]; + + for (display = 0; display <= 32; display++) { + snprintf(lock_name, sizeof(lock_name), lock_fmt, display); + if ((lock_fd = open(lock_name, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0444)) >= 0) { + if (!open_sockets(socks, display)) { + unlink(lock_name); + close(lock_fd); + continue; + } + char pid[12]; + snprintf(pid, sizeof(pid), "%10d", getpid()); + if (write(lock_fd, pid, sizeof(pid) - 1) != sizeof(pid) - 1) { + unlink(lock_name); + close(lock_fd); + continue; + } + close(lock_fd); + break; + } + + if ((lock_fd = open(lock_name, O_RDONLY | O_CLOEXEC)) < 0) { + continue; + } + + char pid[12] = { 0 }, *end_pid; + ssize_t bytes = read(lock_fd, pid, sizeof(pid) - 1); + close(lock_fd); + + if (bytes != sizeof(pid) - 1) { + continue; + } + long int read_pid; + read_pid = strtol(pid, &end_pid, 10); + if (read_pid < 0 || read_pid > INT32_MAX || end_pid != pid + sizeof(pid) - 2) { + continue; + } + errno = 0; + if (kill((pid_t)read_pid, 0) != 0 && errno == ESRCH) { + if (unlink(lock_name) != 0) { + continue; + } + // retry + display--; + continue; + } + } + + if (display > 32) { + wlr_log(WLR_ERROR, "No display available in the first 33"); + return -1; + } + + return display; +} diff --git a/xwayland/sockets.h b/xwayland/sockets.h new file mode 100644 index 00000000..73eb36e0 --- /dev/null +++ b/xwayland/sockets.h @@ -0,0 +1,7 @@ +#ifndef XWAYLAND_SOCKETS_H +#define XWAYLAND_SOCKETS_H + +void unlink_display_sockets(int display); +int open_display_sockets(int socks[2]); + +#endif diff --git a/xwayland/xwayland.c b/xwayland/xwayland.c new file mode 100644 index 00000000..e6f15735 --- /dev/null +++ b/xwayland/xwayland.c @@ -0,0 +1,488 @@ +#define _POSIX_C_SOURCE 200112L +#ifdef __FreeBSD__ +// for SOCK_CLOEXEC +#define __BSD_VISIBLE 1 +#endif +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/util/log.h> +#include <wlr/xwayland.h> +#include "sockets.h" +#include "util/signal.h" +#include "xwayland/xwm.h" + +struct wlr_xwayland_cursor { + uint8_t *pixels; + uint32_t stride; + uint32_t width; + uint32_t height; + int32_t hotspot_x; + int32_t hotspot_y; +}; + +static void safe_close(int fd) { + if (fd >= 0) { + close(fd); + } +} + +static int unset_cloexec(int fd) { + if (fcntl(fd, F_SETFD, 0) != 0) { + wlr_log_errno(WLR_ERROR, "fcntl() failed on fd %d", fd); + return -1; + } + return 0; +} + +static int fill_arg(char ***argv, const char *fmt, ...) { + int len; + char **cur_arg = *argv; + va_list args; + va_start(args, fmt); + len = vsnprintf(NULL, 0, fmt, args) + 1; + va_end(args); + while (*cur_arg) { + cur_arg++; + } + *cur_arg = malloc(len); + if (!*cur_arg) { + return -1; + } + *argv = cur_arg; + va_start(args, fmt); + len = vsnprintf(*cur_arg, len, fmt, args); + va_end(args); + return len; +} + +_Noreturn +static void exec_xwayland(struct wlr_xwayland *wlr_xwayland) { + if (unset_cloexec(wlr_xwayland->x_fd[0]) || + unset_cloexec(wlr_xwayland->x_fd[1]) || + unset_cloexec(wlr_xwayland->wm_fd[1]) || + unset_cloexec(wlr_xwayland->wl_fd[1])) { + _exit(EXIT_FAILURE); + } + + /* Make Xwayland signal us when it's ready */ + signal(SIGUSR1, SIG_IGN); + + char *argv[] = { + "Xwayland", NULL /* display, e.g. :1 */, + "-rootless", "-terminate", + "-listen", NULL /* x_fd[0] */, + "-listen", NULL /* x_fd[1] */, + "-wm", NULL /* wm_fd[1] */, + NULL }; + char **cur_arg = argv; + + if (fill_arg(&cur_arg, ":%d", wlr_xwayland->display) < 0 || + fill_arg(&cur_arg, "%d", wlr_xwayland->x_fd[0]) < 0 || + fill_arg(&cur_arg, "%d", wlr_xwayland->x_fd[1]) < 0 || + fill_arg(&cur_arg, "%d", wlr_xwayland->wm_fd[1]) < 0) { + wlr_log_errno(WLR_ERROR, "alloc/print failure"); + _exit(EXIT_FAILURE); + } + + char wayland_socket_str[16]; + snprintf(wayland_socket_str, sizeof(wayland_socket_str), "%d", wlr_xwayland->wl_fd[1]); + setenv("WAYLAND_SOCKET", wayland_socket_str, true); + + wlr_log(WLR_INFO, "WAYLAND_SOCKET=%d Xwayland :%d -rootless -terminate -listen %d -listen %d -wm %d", + wlr_xwayland->wl_fd[1], wlr_xwayland->display, wlr_xwayland->x_fd[0], + wlr_xwayland->x_fd[1], wlr_xwayland->wm_fd[1]); + + // Closes stdout/stderr depending on log verbosity + enum wlr_log_importance verbosity = wlr_log_get_verbosity(); + int devnull = open("/dev/null", O_WRONLY | O_CREAT, 0666); + if (devnull < 0) { + wlr_log_errno(WLR_ERROR, "XWayland: failed to open /dev/null"); + _exit(EXIT_FAILURE); + } + if (verbosity < WLR_INFO) { + dup2(devnull, STDOUT_FILENO); + } + if (verbosity < WLR_ERROR) { + dup2(devnull, STDERR_FILENO); + } + + // This returns if and only if the call fails + execvp("Xwayland", argv); + + wlr_log_errno(WLR_ERROR, "failed to exec Xwayland"); + close(devnull); + _exit(EXIT_FAILURE); +} + +static void xwayland_finish_server(struct wlr_xwayland *wlr_xwayland) { + if (!wlr_xwayland || wlr_xwayland->display == -1) { + return; + } + + if (wlr_xwayland->x_fd_read_event[0]) { + wl_event_source_remove(wlr_xwayland->x_fd_read_event[0]); + wl_event_source_remove(wlr_xwayland->x_fd_read_event[1]); + + wlr_xwayland->x_fd_read_event[0] = wlr_xwayland->x_fd_read_event[1] = NULL; + } + + if (wlr_xwayland->cursor != NULL) { + free(wlr_xwayland->cursor); + } + + xwm_destroy(wlr_xwayland->xwm); + + if (wlr_xwayland->client) { + wl_list_remove(&wlr_xwayland->client_destroy.link); + wl_client_destroy(wlr_xwayland->client); + } + if (wlr_xwayland->sigusr1_source) { + wl_event_source_remove(wlr_xwayland->sigusr1_source); + } + + safe_close(wlr_xwayland->wl_fd[0]); + safe_close(wlr_xwayland->wl_fd[1]); + safe_close(wlr_xwayland->wm_fd[0]); + safe_close(wlr_xwayland->wm_fd[1]); + memset(wlr_xwayland, 0, offsetof(struct wlr_xwayland, display)); + wlr_xwayland->wl_fd[0] = wlr_xwayland->wl_fd[1] = -1; + wlr_xwayland->wm_fd[0] = wlr_xwayland->wm_fd[1] = -1; + + /* We do not kill the Xwayland process, it dies to broken pipe + * after we close our side of the wm/wl fds. This is more reliable + * than trying to kill something that might no longer be Xwayland. + */ +} + +static void xwayland_finish_display(struct wlr_xwayland *wlr_xwayland) { + if (!wlr_xwayland || wlr_xwayland->display == -1) { + return; + } + + safe_close(wlr_xwayland->x_fd[0]); + safe_close(wlr_xwayland->x_fd[1]); + wlr_xwayland->x_fd[0] = wlr_xwayland->x_fd[1] = -1; + + wl_list_remove(&wlr_xwayland->display_destroy.link); + + unlink_display_sockets(wlr_xwayland->display); + wlr_xwayland->display = -1; + unsetenv("DISPLAY"); +} + +static bool xwayland_start_display(struct wlr_xwayland *wlr_xwayland, + struct wl_display *wl_display); + +static bool xwayland_start_server(struct wlr_xwayland *wlr_xwayland); +static bool xwayland_start_server_lazy(struct wlr_xwayland *wlr_xwayland); + +static void handle_client_destroy(struct wl_listener *listener, void *data) { + struct wlr_xwayland *wlr_xwayland = + wl_container_of(listener, wlr_xwayland, client_destroy); + + if (wlr_xwayland->sigusr1_source) { + // Xwayland failed to start, let the sigusr1 handler deal with it + return; + } + + // Don't call client destroy: it's being destroyed already + wlr_xwayland->client = NULL; + wl_list_remove(&wlr_xwayland->client_destroy.link); + + xwayland_finish_server(wlr_xwayland); + + if (time(NULL) - wlr_xwayland->server_start > 5) { + if (wlr_xwayland->lazy) { + wlr_log(WLR_INFO, "Restarting Xwayland (lazy)"); + xwayland_start_server_lazy(wlr_xwayland); + } else { + wlr_log(WLR_INFO, "Restarting Xwayland"); + xwayland_start_server(wlr_xwayland); + } + } +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xwayland *wlr_xwayland = + wl_container_of(listener, wlr_xwayland, display_destroy); + + // Don't call client destroy: the display is being destroyed, it's too late + if (wlr_xwayland->client) { + wlr_xwayland->client = NULL; + wl_list_remove(&wlr_xwayland->client_destroy.link); + } + + wlr_xwayland_destroy(wlr_xwayland); +} + +static int xserver_handle_ready(int signal_number, void *data) { + struct wlr_xwayland *wlr_xwayland = data; + + int stat_val = -1; + while (waitpid(wlr_xwayland->pid, &stat_val, 0) < 0) { + if (errno == EINTR) { + continue; + } + wlr_log_errno(WLR_ERROR, "waitpid for Xwayland fork failed"); + return 1; + } + if (stat_val) { + wlr_log(WLR_ERROR, "Xwayland startup failed, not setting up xwm"); + return 1; + } + wlr_log(WLR_DEBUG, "Xserver is ready"); + + wlr_xwayland->xwm = xwm_create(wlr_xwayland); + if (!wlr_xwayland->xwm) { + xwayland_finish_server(wlr_xwayland); + return 1; + } + + if (wlr_xwayland->seat) { + xwm_set_seat(wlr_xwayland->xwm, wlr_xwayland->seat); + } + + wl_event_source_remove(wlr_xwayland->sigusr1_source); + wlr_xwayland->sigusr1_source = NULL; + + if (wlr_xwayland->cursor != NULL) { + struct wlr_xwayland_cursor *cur = wlr_xwayland->cursor; + xwm_set_cursor(wlr_xwayland->xwm, cur->pixels, cur->stride, cur->width, + cur->height, cur->hotspot_x, cur->hotspot_y); + free(cur); + wlr_xwayland->cursor = NULL; + } + + + wlr_signal_emit_safe(&wlr_xwayland->events.ready, wlr_xwayland); + /* ready is a one-shot signal, fire and forget */ + wl_signal_init(&wlr_xwayland->events.ready); + + return 1; /* wayland event loop dispatcher's count */ +} + +static int xwayland_socket_connected(int fd, uint32_t mask, void* data){ + struct wlr_xwayland *wlr_xwayland = data; + + wl_event_source_remove(wlr_xwayland->x_fd_read_event[0]); + wl_event_source_remove(wlr_xwayland->x_fd_read_event[1]); + + wlr_xwayland->x_fd_read_event[0] = wlr_xwayland->x_fd_read_event[1] = NULL; + + xwayland_start_server(wlr_xwayland); + + return 0; +} + +static bool xwayland_start_display(struct wlr_xwayland *wlr_xwayland, + struct wl_display *wl_display) { + + wlr_xwayland->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(wl_display, &wlr_xwayland->display_destroy); + + wlr_xwayland->display = open_display_sockets(wlr_xwayland->x_fd); + if (wlr_xwayland->display < 0) { + xwayland_finish_display(wlr_xwayland); + return false; + } + + char display_name[16]; + snprintf(display_name, sizeof(display_name), ":%d", wlr_xwayland->display); + setenv("DISPLAY", display_name, true); + + return true; +} + +static bool xwayland_start_server(struct wlr_xwayland *wlr_xwayland) { + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wlr_xwayland->wl_fd) != 0 || + socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wlr_xwayland->wm_fd) != 0) { + wlr_log_errno(WLR_ERROR, "failed to create socketpair"); + xwayland_finish_server(wlr_xwayland); + return false; + } + wlr_xwayland->server_start = time(NULL); + + if (!(wlr_xwayland->client = wl_client_create(wlr_xwayland->wl_display, wlr_xwayland->wl_fd[0]))) { + wlr_log_errno(WLR_ERROR, "wl_client_create failed"); + xwayland_finish_server(wlr_xwayland); + return false; + } + + wlr_xwayland->wl_fd[0] = -1; /* not ours anymore */ + + wlr_xwayland->client_destroy.notify = handle_client_destroy; + wl_client_add_destroy_listener(wlr_xwayland->client, + &wlr_xwayland->client_destroy); + + struct wl_event_loop *loop = wl_display_get_event_loop(wlr_xwayland->wl_display); + wlr_xwayland->sigusr1_source = wl_event_loop_add_signal(loop, SIGUSR1, + xserver_handle_ready, wlr_xwayland); + + if ((wlr_xwayland->pid = fork()) == 0) { + /* Double-fork, but we need to forward SIGUSR1 once Xserver(1) + * is ready, or error if there was one. */ + pid_t pid, ppid; + sigset_t sigset; + int sig; + ppid = getppid(); + sigemptyset(&sigset); + sigaddset(&sigset, SIGUSR1); + sigaddset(&sigset, SIGCHLD); + sigprocmask(SIG_BLOCK, &sigset, NULL); + if ((pid = fork()) == 0) { + exec_xwayland(wlr_xwayland); + } + if (pid < 0) { + wlr_log_errno(WLR_ERROR, "second fork failed"); + _exit(EXIT_FAILURE); + } + sigwait(&sigset, &sig); + kill(ppid, SIGUSR1); + wlr_log(WLR_DEBUG, "sent SIGUSR1 to process %d", ppid); + if (sig == SIGCHLD) { + waitpid(pid, NULL, 0); + _exit(EXIT_FAILURE); + } + _exit(EXIT_SUCCESS); + } + if (wlr_xwayland->pid < 0) { + wlr_log_errno(WLR_ERROR, "fork failed"); + xwayland_finish_server(wlr_xwayland); + return false; + } + + /* close child fds */ + /* remain managing x sockets for lazy start */ + close(wlr_xwayland->wl_fd[1]); + close(wlr_xwayland->wm_fd[1]); + wlr_xwayland->wl_fd[1] = wlr_xwayland->wm_fd[1] = -1; + + return true; +} + +static bool xwayland_start_server_lazy(struct wlr_xwayland *wlr_xwayland) { + struct wl_event_loop *loop = wl_display_get_event_loop(wlr_xwayland->wl_display); + wlr_xwayland->x_fd_read_event[0] = + wl_event_loop_add_fd(loop, wlr_xwayland->x_fd[0], WL_EVENT_READABLE, + xwayland_socket_connected, wlr_xwayland); + wlr_xwayland->x_fd_read_event[1] = + wl_event_loop_add_fd(loop, wlr_xwayland->x_fd[1], WL_EVENT_READABLE, + xwayland_socket_connected, wlr_xwayland); + + return true; +} + +void wlr_xwayland_destroy(struct wlr_xwayland *wlr_xwayland) { + if (!wlr_xwayland) { + return; + } + + wlr_xwayland_set_seat(wlr_xwayland, NULL); + xwayland_finish_server(wlr_xwayland); + xwayland_finish_display(wlr_xwayland); + free(wlr_xwayland); +} + +struct wlr_xwayland *wlr_xwayland_create(struct wl_display *wl_display, + struct wlr_compositor *compositor, bool lazy) { + struct wlr_xwayland *wlr_xwayland = calloc(1, sizeof(struct wlr_xwayland)); + if (!wlr_xwayland) { + return NULL; + } + + wlr_xwayland->wl_display = wl_display; + wlr_xwayland->compositor = compositor; + wlr_xwayland->lazy = lazy; + + wlr_xwayland->x_fd[0] = wlr_xwayland->x_fd[1] = -1; + wlr_xwayland->wl_fd[0] = wlr_xwayland->wl_fd[1] = -1; + wlr_xwayland->wm_fd[0] = wlr_xwayland->wm_fd[1] = -1; + + wl_signal_init(&wlr_xwayland->events.new_surface); + wl_signal_init(&wlr_xwayland->events.ready); + + if (!xwayland_start_display(wlr_xwayland, wl_display)) { + goto error_alloc; + } + + if (wlr_xwayland->lazy) { + if (!xwayland_start_server_lazy(wlr_xwayland)) { + goto error_display; + } + } else { + if (!xwayland_start_server(wlr_xwayland)) { + goto error_display; + } + } + + return wlr_xwayland; + +error_display: + xwayland_finish_display(wlr_xwayland); + +error_alloc: + free(wlr_xwayland); + return NULL; +} + +void wlr_xwayland_set_cursor(struct wlr_xwayland *wlr_xwayland, + uint8_t *pixels, uint32_t stride, uint32_t width, uint32_t height, + int32_t hotspot_x, int32_t hotspot_y) { + if (wlr_xwayland->xwm != NULL) { + xwm_set_cursor(wlr_xwayland->xwm, pixels, stride, width, height, + hotspot_x, hotspot_y); + return; + } + + free(wlr_xwayland->cursor); + + wlr_xwayland->cursor = calloc(1, sizeof(struct wlr_xwayland_cursor)); + if (wlr_xwayland->cursor == NULL) { + return; + } + wlr_xwayland->cursor->pixels = pixels; + wlr_xwayland->cursor->stride = stride; + wlr_xwayland->cursor->width = width; + wlr_xwayland->cursor->height = height; + wlr_xwayland->cursor->hotspot_x = hotspot_x; + wlr_xwayland->cursor->hotspot_y = hotspot_y; +} + +static void xwayland_handle_seat_destroy(struct wl_listener *listener, + void *data) { + struct wlr_xwayland *xwayland = + wl_container_of(listener, xwayland, seat_destroy); + + wlr_xwayland_set_seat(xwayland, NULL); +} + +void wlr_xwayland_set_seat(struct wlr_xwayland *xwayland, + struct wlr_seat *seat) { + if (xwayland->seat) { + wl_list_remove(&xwayland->seat_destroy.link); + } + + xwayland->seat = seat; + + if (xwayland->xwm) { + xwm_set_seat(xwayland->xwm, seat); + } + + if (seat == NULL) { + return; + } + + xwayland->seat_destroy.notify = xwayland_handle_seat_destroy; + wl_signal_add(&seat->events.destroy, &xwayland->seat_destroy); +} diff --git a/xwayland/xwm.c b/xwayland/xwm.c new file mode 100644 index 00000000..91fa7e3d --- /dev/null +++ b/xwayland/xwm.c @@ -0,0 +1,1802 @@ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#include <assert.h> +#include <stdlib.h> +#include <unistd.h> +#include <wlr/config.h> +#include <wlr/types/wlr_surface.h> +#include <wlr/util/edges.h> +#include <wlr/util/log.h> +#include <wlr/xcursor.h> +#include <wlr/xwayland.h> +#include <xcb/composite.h> +#include <xcb/render.h> +#include <xcb/xfixes.h> +#include "util/signal.h" +#include "xwayland/xwm.h" + +const char *atom_map[ATOM_LAST] = { + "WL_SURFACE_ID", + "WM_DELETE_WINDOW", + "WM_PROTOCOLS", + "WM_HINTS", + "WM_NORMAL_HINTS", + "WM_SIZE_HINTS", + "WM_WINDOW_ROLE", + "_MOTIF_WM_HINTS", + "UTF8_STRING", + "WM_S0", + "_NET_SUPPORTED", + "_NET_WM_CM_S0", + "_NET_WM_PID", + "_NET_WM_NAME", + "_NET_WM_STATE", + "_NET_WM_WINDOW_TYPE", + "WM_TAKE_FOCUS", + "WINDOW", + "_NET_ACTIVE_WINDOW", + "_NET_WM_MOVERESIZE", + "_NET_WM_NAME", + "_NET_SUPPORTING_WM_CHECK", + "_NET_WM_STATE_MODAL", + "_NET_WM_STATE_FULLSCREEN", + "_NET_WM_STATE_MAXIMIZED_VERT", + "_NET_WM_STATE_MAXIMIZED_HORZ", + "_NET_WM_PING", + "WM_STATE", + "CLIPBOARD", + "PRIMARY", + "_WL_SELECTION", + "TARGETS", + "CLIPBOARD_MANAGER", + "INCR", + "TEXT", + "TIMESTAMP", + "DELETE", + "_NET_WM_WINDOW_TYPE_NORMAL", + "_NET_WM_WINDOW_TYPE_UTILITY", + "_NET_WM_WINDOW_TYPE_TOOLTIP", + "_NET_WM_WINDOW_TYPE_DND", + "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", + "_NET_WM_WINDOW_TYPE_POPUP_MENU", + "_NET_WM_WINDOW_TYPE_COMBO", + "_NET_WM_WINDOW_TYPE_MENU", + "_NET_WM_WINDOW_TYPE_NOTIFICATION", + "_NET_WM_WINDOW_TYPE_SPLASH", + "XdndSelection", + "XdndAware", + "XdndStatus", + "XdndPosition", + "XdndEnter", + "XdndLeave", + "XdndDrop", + "XdndFinished", + "XdndProxy", + "XdndTypeList", + "XdndActionMove", + "XdndActionCopy", + "XdndActionAsk", + "XdndActionPrivate", +}; + +static const struct wlr_surface_role xwayland_surface_role; + +bool wlr_surface_is_xwayland_surface(struct wlr_surface *surface) { + return surface->role == &xwayland_surface_role; +} + +struct wlr_xwayland_surface *wlr_xwayland_surface_from_wlr_surface( + struct wlr_surface *surface) { + assert(wlr_surface_is_xwayland_surface(surface)); + return (struct wlr_xwayland_surface *)surface->role_data; +} + +// TODO: replace this with hash table? +static struct wlr_xwayland_surface *lookup_surface(struct wlr_xwm *xwm, + xcb_window_t window_id) { + struct wlr_xwayland_surface *surface; + wl_list_for_each(surface, &xwm->surfaces, link) { + if (surface->window_id == window_id) { + return surface; + } + } + return NULL; +} + +static int xwayland_surface_handle_ping_timeout(void *data) { + struct wlr_xwayland_surface *surface = data; + + wlr_signal_emit_safe(&surface->events.ping_timeout, surface); + surface->pinging = false; + return 1; +} + +static struct wlr_xwayland_surface *xwayland_surface_create( + struct wlr_xwm *xwm, xcb_window_t window_id, int16_t x, int16_t y, + uint16_t width, uint16_t height, bool override_redirect) { + struct wlr_xwayland_surface *surface = + calloc(1, sizeof(struct wlr_xwayland_surface)); + if (!surface) { + wlr_log(WLR_ERROR, "Could not allocate wlr xwayland surface"); + return NULL; + } + + xcb_get_geometry_cookie_t geometry_cookie = + xcb_get_geometry(xwm->xcb_conn, window_id); + + uint32_t values[1]; + values[0] = + XCB_EVENT_MASK_FOCUS_CHANGE | + XCB_EVENT_MASK_PROPERTY_CHANGE; + xcb_change_window_attributes(xwm->xcb_conn, window_id, + XCB_CW_EVENT_MASK, values); + + surface->xwm = xwm; + surface->window_id = window_id; + surface->x = x; + surface->y = y; + surface->width = width; + surface->height = height; + surface->override_redirect = override_redirect; + wl_list_insert(&xwm->surfaces, &surface->link); + wl_list_init(&surface->children); + wl_list_init(&surface->parent_link); + wl_signal_init(&surface->events.destroy); + wl_signal_init(&surface->events.request_configure); + wl_signal_init(&surface->events.request_move); + wl_signal_init(&surface->events.request_resize); + wl_signal_init(&surface->events.request_maximize); + wl_signal_init(&surface->events.request_fullscreen); + wl_signal_init(&surface->events.request_activate); + wl_signal_init(&surface->events.map); + wl_signal_init(&surface->events.unmap); + wl_signal_init(&surface->events.set_class); + wl_signal_init(&surface->events.set_role); + wl_signal_init(&surface->events.set_title); + wl_signal_init(&surface->events.set_parent); + wl_signal_init(&surface->events.set_pid); + wl_signal_init(&surface->events.set_window_type); + wl_signal_init(&surface->events.set_hints); + wl_signal_init(&surface->events.set_decorations); + wl_signal_init(&surface->events.set_override_redirect); + wl_signal_init(&surface->events.ping_timeout); + + xcb_get_geometry_reply_t *geometry_reply = + xcb_get_geometry_reply(xwm->xcb_conn, geometry_cookie, NULL); + if (geometry_reply != NULL) { + surface->has_alpha = geometry_reply->depth == 32; + } + free(geometry_reply); + + struct wl_display *display = xwm->xwayland->wl_display; + struct wl_event_loop *loop = wl_display_get_event_loop(display); + surface->ping_timer = wl_event_loop_add_timer(loop, + xwayland_surface_handle_ping_timeout, surface); + if (surface->ping_timer == NULL) { + free(surface); + wlr_log(WLR_ERROR, "Could not add timer to event loop"); + return NULL; + } + + wlr_signal_emit_safe(&xwm->xwayland->events.new_surface, surface); + + return surface; +} + +static void xwm_set_net_active_window(struct wlr_xwm *xwm, + xcb_window_t window) { + xcb_change_property(xwm->xcb_conn, XCB_PROP_MODE_REPLACE, + xwm->screen->root, xwm->atoms[_NET_ACTIVE_WINDOW], + xwm->atoms[WINDOW], 32, 1, &window); +} + +static void xwm_send_wm_message(struct wlr_xwayland_surface *surface, + xcb_client_message_data_t *data, uint32_t event_mask) { + struct wlr_xwm *xwm = surface->xwm; + + xcb_client_message_event_t event = { + .response_type = XCB_CLIENT_MESSAGE, + .format = 32, + .sequence = 0, + .window = surface->window_id, + .type = xwm->atoms[WM_PROTOCOLS], + .data = *data, + }; + + xcb_send_event(xwm->xcb_conn, + 0, // propagate + surface->window_id, + event_mask, + (const char *)&event); + xcb_flush(xwm->xcb_conn); +} + +static void xwm_send_focus_window(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface) { + if (!xsurface) { + xcb_set_input_focus_checked(xwm->xcb_conn, + XCB_INPUT_FOCUS_POINTER_ROOT, + XCB_NONE, XCB_CURRENT_TIME); + return; + } + + if (xsurface->override_redirect) { + return; + } + + xcb_client_message_data_t message_data = { 0 }; + message_data.data32[0] = xwm->atoms[WM_TAKE_FOCUS]; + message_data.data32[1] = XCB_TIME_CURRENT_TIME; + + if (xsurface->hints && !xsurface->hints->input) { + // if the surface doesn't allow the focus request, we will send him + // only the take focus event. It will get the focus by itself. + xwm_send_wm_message(xsurface, &message_data, XCB_EVENT_MASK_NO_EVENT); + } else { + xwm_send_wm_message(xsurface, &message_data, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT); + + xcb_set_input_focus(xwm->xcb_conn, XCB_INPUT_FOCUS_POINTER_ROOT, + xsurface->window_id, XCB_CURRENT_TIME); + } + + uint32_t values[1]; + values[0] = XCB_STACK_MODE_ABOVE; + xcb_configure_window(xwm->xcb_conn, xsurface->window_id, + XCB_CONFIG_WINDOW_STACK_MODE, values); +} + +static void xwm_surface_activate(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface) { + if (xwm->focus_surface == xsurface || + (xsurface && xsurface->override_redirect)) { + return; + } + + if (xsurface) { + xwm_set_net_active_window(xwm, xsurface->window_id); + } else { + xwm_set_net_active_window(xwm, XCB_WINDOW_NONE); + } + + xwm_send_focus_window(xwm, xsurface); + + xwm->focus_surface = xsurface; + + xcb_flush(xwm->xcb_conn); +} + +static void xsurface_set_net_wm_state(struct wlr_xwayland_surface *xsurface) { + struct wlr_xwm *xwm = xsurface->xwm; + uint32_t property[3]; + int i; + + i = 0; + if (xsurface->modal) { + property[i++] = xwm->atoms[_NET_WM_STATE_MODAL]; + } + if (xsurface->fullscreen) { + property[i++] = xwm->atoms[_NET_WM_STATE_FULLSCREEN]; + } + if (xsurface->maximized_vert) { + property[i++] = xwm->atoms[_NET_WM_STATE_MAXIMIZED_VERT]; + } + if (xsurface->maximized_horz) { + property[i++] = xwm->atoms[_NET_WM_STATE_MAXIMIZED_HORZ]; + } + + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xsurface->window_id, + xwm->atoms[NET_WM_STATE], + XCB_ATOM_ATOM, + 32, // format + i, property); +} + +static void xsurface_unmap(struct wlr_xwayland_surface *surface); + +static void xwayland_surface_destroy( + struct wlr_xwayland_surface *xsurface) { + xsurface_unmap(xsurface); + + wlr_signal_emit_safe(&xsurface->events.destroy, xsurface); + + if (xsurface == xsurface->xwm->focus_surface) { + xwm_surface_activate(xsurface->xwm, NULL); + } + + wl_list_remove(&xsurface->link); + wl_list_remove(&xsurface->parent_link); + + struct wlr_xwayland_surface *child, *next; + wl_list_for_each_safe(child, next, &xsurface->children, parent_link) { + wl_list_remove(&child->parent_link); + wl_list_init(&child->parent_link); + child->parent = NULL; + } + + if (xsurface->surface_id) { + wl_list_remove(&xsurface->unpaired_link); + } + + if (xsurface->surface) { + wl_list_remove(&xsurface->surface_destroy.link); + xsurface->surface->role_data = NULL; + } + + wl_event_source_remove(xsurface->ping_timer); + + free(xsurface->title); + free(xsurface->class); + free(xsurface->instance); + free(xsurface->role); + free(xsurface->window_type); + free(xsurface->protocols); + free(xsurface->hints); + free(xsurface->size_hints); + free(xsurface); +} + +static void read_surface_class(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *surface, xcb_get_property_reply_t *reply) { + if (reply->type != XCB_ATOM_STRING && + reply->type != xwm->atoms[UTF8_STRING]) { + return; + } + + size_t len = xcb_get_property_value_length(reply); + char *class = xcb_get_property_value(reply); + + // Unpack two sequentially stored strings: instance, class + size_t instance_len = strnlen(class, len); + free(surface->instance); + if (len > 0 && instance_len < len) { + surface->instance = strndup(class, instance_len); + class += instance_len + 1; + } else { + surface->instance = NULL; + } + free(surface->class); + if (len > 0) { + surface->class = strndup(class, len); + } else { + surface->class = NULL; + } + + wlr_log(WLR_DEBUG, "XCB_ATOM_WM_CLASS: %s %s", surface->instance, + surface->class); + wlr_signal_emit_safe(&surface->events.set_class, surface); +} + +static void read_surface_role(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + if (reply->type != XCB_ATOM_STRING && + reply->type != xwm->atoms[UTF8_STRING]) { + return; + } + + size_t len = xcb_get_property_value_length(reply); + char *role = xcb_get_property_value(reply); + + free(xsurface->role); + if (len > 0) { + xsurface->role = strndup(role, len); + } else { + xsurface->role = NULL; + } + + wlr_log(WLR_DEBUG, "XCB_ATOM_WM_WINDOW_ROLE: %s", xsurface->role); + wlr_signal_emit_safe(&xsurface->events.set_role, xsurface); +} + +static void read_surface_title(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + if (reply->type != XCB_ATOM_STRING && + reply->type != xwm->atoms[UTF8_STRING]) { + return; + } + + bool is_utf8 = reply->type == xwm->atoms[UTF8_STRING]; + if (!is_utf8 && xsurface->has_utf8_title) { + return; + } + + size_t len = xcb_get_property_value_length(reply); + char *title = xcb_get_property_value(reply); + + free(xsurface->title); + if (len > 0) { + xsurface->title = strndup(title, len); + } else { + xsurface->title = NULL; + } + xsurface->has_utf8_title = is_utf8; + + wlr_log(WLR_DEBUG, "XCB_ATOM_WM_NAME: %s", xsurface->title); + wlr_signal_emit_safe(&xsurface->events.set_title, xsurface); +} + +static void read_surface_parent(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + if (reply->type != XCB_ATOM_WINDOW) { + return; + } + + xcb_window_t *xid = xcb_get_property_value(reply); + if (xid != NULL) { + xsurface->parent = lookup_surface(xwm, *xid); + } else { + xsurface->parent = NULL; + } + + wl_list_remove(&xsurface->parent_link); + if (xsurface->parent != NULL) { + wl_list_insert(&xsurface->parent->children, &xsurface->parent_link); + } else { + wl_list_init(&xsurface->parent_link); + } + + wlr_log(WLR_DEBUG, "XCB_ATOM_WM_TRANSIENT_FOR: %p", xsurface->parent); + wlr_signal_emit_safe(&xsurface->events.set_parent, xsurface); +} + +static void read_surface_pid(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + if (reply->type != XCB_ATOM_CARDINAL) { + return; + } + + pid_t *pid = xcb_get_property_value(reply); + xsurface->pid = *pid; + wlr_log(WLR_DEBUG, "NET_WM_PID %d", xsurface->pid); + wlr_signal_emit_safe(&xsurface->events.set_pid, xsurface); +} + +static void read_surface_window_type(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + if (reply->type != XCB_ATOM_ATOM) { + return; + } + + xcb_atom_t *atoms = xcb_get_property_value(reply); + size_t atoms_len = reply->value_len; + size_t atoms_size = sizeof(xcb_atom_t) * atoms_len; + + free(xsurface->window_type); + xsurface->window_type = malloc(atoms_size); + if (xsurface->window_type == NULL) { + return; + } + memcpy(xsurface->window_type, atoms, atoms_size); + xsurface->window_type_len = atoms_len; + + wlr_log(WLR_DEBUG, "NET_WM_WINDOW_TYPE (%zu)", atoms_len); + wlr_signal_emit_safe(&xsurface->events.set_window_type, xsurface); +} + +static void read_surface_protocols(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + if (reply->type != XCB_ATOM_ATOM) { + return; + } + + xcb_atom_t *atoms = xcb_get_property_value(reply); + size_t atoms_len = reply->value_len; + size_t atoms_size = sizeof(xcb_atom_t) * atoms_len; + + free(xsurface->protocols); + xsurface->protocols = malloc(atoms_size); + if (xsurface->protocols == NULL) { + return; + } + memcpy(xsurface->protocols, atoms, atoms_size); + xsurface->protocols_len = atoms_len; + + wlr_log(WLR_DEBUG, "WM_PROTOCOLS (%zu)", atoms_len); +} + +#if WLR_HAS_XCB_ICCCM +static void read_surface_hints(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + // According to the docs, reply->type == xwm->atoms[WM_HINTS] + // In practice, reply->type == XCB_ATOM_ATOM + if (reply->value_len == 0) { + return; + } + + xcb_icccm_wm_hints_t hints; + xcb_icccm_get_wm_hints_from_reply(&hints, reply); + + free(xsurface->hints); + xsurface->hints = calloc(1, sizeof(struct wlr_xwayland_surface_hints)); + if (xsurface->hints == NULL) { + return; + } + memcpy(xsurface->hints, &hints, sizeof(struct wlr_xwayland_surface_hints)); + xsurface->hints_urgency = xcb_icccm_wm_hints_get_urgency(&hints); + + if (!(xsurface->hints->flags & XCB_ICCCM_WM_HINT_INPUT)) { + // The client didn't specify whether it wants input. + // Assume it does. + xsurface->hints->input = true; + } + + wlr_log(WLR_DEBUG, "WM_HINTS (%d)", reply->value_len); + wlr_signal_emit_safe(&xsurface->events.set_hints, xsurface); +} +#else +static void read_surface_hints(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + // Do nothing +} +#endif + +#if WLR_HAS_XCB_ICCCM +static void read_surface_normal_hints(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + if (reply->type != xwm->atoms[WM_SIZE_HINTS] || reply->value_len == 0) { + return; + } + + xcb_size_hints_t size_hints; + xcb_icccm_get_wm_size_hints_from_reply(&size_hints, reply); + + free(xsurface->size_hints); + xsurface->size_hints = + calloc(1, sizeof(struct wlr_xwayland_surface_size_hints)); + if (xsurface->size_hints == NULL) { + return; + } + memcpy(xsurface->size_hints, &size_hints, + sizeof(struct wlr_xwayland_surface_size_hints)); + + if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) == 0) { + xsurface->size_hints->min_width = -1; + xsurface->size_hints->min_height = -1; + } + if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE) == 0) { + xsurface->size_hints->max_width = -1; + xsurface->size_hints->max_height = -1; + } + + wlr_log(WLR_DEBUG, "WM_NORMAL_HINTS (%d)", reply->value_len); +} +#else +static void read_surface_normal_hints(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + // Do nothing +} +#endif + + +#define MWM_HINTS_FLAGS_FIELD 0 +#define MWM_HINTS_DECORATIONS_FIELD 2 + +#define MWM_HINTS_DECORATIONS (1 << 1) + +#define MWM_DECOR_ALL (1 << 0) +#define MWM_DECOR_BORDER (1 << 1) +#define MWM_DECOR_TITLE (1 << 3) + +static void read_surface_motif_hints(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + if (reply->value_len < 5) { + return; + } + + uint32_t *motif_hints = xcb_get_property_value(reply); + if (motif_hints[MWM_HINTS_FLAGS_FIELD] & MWM_HINTS_DECORATIONS) { + xsurface->decorations = WLR_XWAYLAND_SURFACE_DECORATIONS_ALL; + uint32_t decorations = motif_hints[MWM_HINTS_DECORATIONS_FIELD]; + if ((decorations & MWM_DECOR_ALL) == 0) { + if ((decorations & MWM_DECOR_BORDER) == 0) { + xsurface->decorations |= + WLR_XWAYLAND_SURFACE_DECORATIONS_NO_BORDER; + } + if ((decorations & MWM_DECOR_TITLE) == 0) { + xsurface->decorations |= + WLR_XWAYLAND_SURFACE_DECORATIONS_NO_TITLE; + } + } + wlr_signal_emit_safe(&xsurface->events.set_decorations, xsurface); + } + + wlr_log(WLR_DEBUG, "MOTIF_WM_HINTS (%d)", reply->value_len); +} + +static void read_surface_net_wm_state(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + xsurface->fullscreen = 0; + xcb_atom_t *atom = xcb_get_property_value(reply); + for (uint32_t i = 0; i < reply->value_len; i++) { + if (atom[i] == xwm->atoms[_NET_WM_STATE_MODAL]) { + xsurface->modal = true; + } else if (atom[i] == xwm->atoms[_NET_WM_STATE_FULLSCREEN]) { + xsurface->fullscreen = true; + } else if (atom[i] == xwm->atoms[_NET_WM_STATE_MAXIMIZED_VERT]) { + xsurface->maximized_vert = true; + } else if (atom[i] == xwm->atoms[_NET_WM_STATE_MAXIMIZED_HORZ]) { + xsurface->maximized_horz = true; + } + } +} + +char *xwm_get_atom_name(struct wlr_xwm *xwm, xcb_atom_t atom) { + xcb_get_atom_name_cookie_t name_cookie = + xcb_get_atom_name(xwm->xcb_conn, atom); + xcb_get_atom_name_reply_t *name_reply = + xcb_get_atom_name_reply(xwm->xcb_conn, name_cookie, NULL); + if (name_reply == NULL) { + return NULL; + } + size_t len = xcb_get_atom_name_name_length(name_reply); + char *buf = xcb_get_atom_name_name(name_reply); // not a C string + char *name = strndup(buf, len); + free(name_reply); + return name; +} + +static void read_surface_property(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, xcb_atom_t property) { + xcb_get_property_cookie_t cookie = xcb_get_property(xwm->xcb_conn, 0, + xsurface->window_id, property, XCB_ATOM_ANY, 0, 2048); + xcb_get_property_reply_t *reply = xcb_get_property_reply(xwm->xcb_conn, + cookie, NULL); + if (reply == NULL) { + return; + } + + if (property == XCB_ATOM_WM_CLASS) { + read_surface_class(xwm, xsurface, reply); + } else if (property == XCB_ATOM_WM_NAME || + property == xwm->atoms[NET_WM_NAME]) { + read_surface_title(xwm, xsurface, reply); + } else if (property == XCB_ATOM_WM_TRANSIENT_FOR) { + read_surface_parent(xwm, xsurface, reply); + } else if (property == xwm->atoms[NET_WM_PID]) { + read_surface_pid(xwm, xsurface, reply); + } else if (property == xwm->atoms[NET_WM_WINDOW_TYPE]) { + read_surface_window_type(xwm, xsurface, reply); + } else if (property == xwm->atoms[WM_PROTOCOLS]) { + read_surface_protocols(xwm, xsurface, reply); + } else if (property == xwm->atoms[NET_WM_STATE]) { + read_surface_net_wm_state(xwm, xsurface, reply); + } else if (property == xwm->atoms[WM_HINTS]) { + read_surface_hints(xwm, xsurface, reply); + } else if (property == xwm->atoms[WM_NORMAL_HINTS]) { + read_surface_normal_hints(xwm, xsurface, reply); + } else if (property == xwm->atoms[MOTIF_WM_HINTS]) { + read_surface_motif_hints(xwm, xsurface, reply); + } else if (property == xwm->atoms[WM_WINDOW_ROLE]) { + read_surface_role(xwm, xsurface, reply); + } else { + char *prop_name = xwm_get_atom_name(xwm, property); + wlr_log(WLR_DEBUG, "unhandled X11 property %u (%s) for window %u", + property, prop_name, xsurface->window_id); + free(prop_name); + } + + free(reply); +} + +static void xwayland_surface_role_commit(struct wlr_surface *wlr_surface) { + assert(wlr_surface->role == &xwayland_surface_role); + struct wlr_xwayland_surface *surface = wlr_surface->role_data; + if (surface == NULL) { + return; + } + + if (!surface->mapped && wlr_surface_has_buffer(surface->surface)) { + wlr_signal_emit_safe(&surface->events.map, surface); + surface->mapped = true; + } +} + +static void xwayland_surface_role_precommit(struct wlr_surface *wlr_surface) { + assert(wlr_surface->role == &xwayland_surface_role); + struct wlr_xwayland_surface *surface = wlr_surface->role_data; + if (surface == NULL) { + return; + } + + if (wlr_surface->pending.committed & WLR_SURFACE_STATE_BUFFER && + wlr_surface->pending.buffer_resource == NULL) { + // This is a NULL commit + if (surface->mapped) { + wlr_signal_emit_safe(&surface->events.unmap, surface); + surface->mapped = false; + } + } +} + +static const struct wlr_surface_role xwayland_surface_role = { + .name = "wlr_xwayland_surface", + .commit = xwayland_surface_role_commit, + .precommit = xwayland_surface_role_precommit, +}; + +static void handle_surface_destroy(struct wl_listener *listener, void *data) { + struct wlr_xwayland_surface *surface = + wl_container_of(listener, surface, surface_destroy); + xsurface_unmap(surface); +} + +static void xwm_map_shell_surface(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, struct wlr_surface *surface) { + if (!wlr_surface_set_role(surface, &xwayland_surface_role, xsurface, + NULL, 0)) { + wlr_log(WLR_ERROR, "Failed to set xwayland surface role"); + return; + } + + xsurface->surface = surface; + + // read all surface properties + const xcb_atom_t props[] = { + XCB_ATOM_WM_CLASS, + XCB_ATOM_WM_NAME, + XCB_ATOM_WM_TRANSIENT_FOR, + xwm->atoms[WM_PROTOCOLS], + xwm->atoms[WM_HINTS], + xwm->atoms[WM_NORMAL_HINTS], + xwm->atoms[MOTIF_WM_HINTS], + xwm->atoms[NET_WM_STATE], + xwm->atoms[NET_WM_WINDOW_TYPE], + xwm->atoms[NET_WM_NAME], + xwm->atoms[NET_WM_PID], + }; + for (size_t i = 0; i < sizeof(props)/sizeof(xcb_atom_t); i++) { + read_surface_property(xwm, xsurface, props[i]); + } + + xsurface->surface_destroy.notify = handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &xsurface->surface_destroy); +} + +static void xsurface_unmap(struct wlr_xwayland_surface *surface) { + if (surface->mapped) { + surface->mapped = false; + wlr_signal_emit_safe(&surface->events.unmap, surface); + } + + if (surface->surface_id) { + // Make sure we're not on the unpaired surface list or we + // could be assigned a surface during surface creation that + // was mapped before this unmap request. + wl_list_remove(&surface->unpaired_link); + surface->surface_id = 0; + } + + if (surface->surface) { + wl_list_remove(&surface->surface_destroy.link); + surface->surface->role_data = NULL; + surface->surface = NULL; + } +} + +static void xwm_handle_create_notify(struct wlr_xwm *xwm, + xcb_create_notify_event_t *ev) { + wlr_log(WLR_DEBUG, "XCB_CREATE_NOTIFY (%u)", ev->window); + + if (ev->window == xwm->window || + ev->window == xwm->selection_window || + ev->window == xwm->dnd_window) { + return; + } + + xwayland_surface_create(xwm, ev->window, ev->x, ev->y, + ev->width, ev->height, ev->override_redirect); +} + +static void xwm_handle_destroy_notify(struct wlr_xwm *xwm, + xcb_destroy_notify_event_t *ev) { + wlr_log(WLR_DEBUG, "XCB_DESTROY_NOTIFY (%u)", ev->window); + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); + if (xsurface == NULL) { + return; + } + xwayland_surface_destroy(xsurface); +} + +static void xwm_handle_configure_request(struct wlr_xwm *xwm, + xcb_configure_request_event_t *ev) { + struct wlr_xwayland_surface *surface = lookup_surface(xwm, ev->window); + if (surface == NULL) { + return; + } + + // TODO: handle ev->{parent,sibling}? + + uint16_t mask = ev->value_mask; + uint16_t geo_mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; + if ((mask & geo_mask) == 0) { + return; + } + + struct wlr_xwayland_surface_configure_event wlr_event = { + .surface = surface, + .x = mask & XCB_CONFIG_WINDOW_X ? ev->x : surface->x, + .y = mask & XCB_CONFIG_WINDOW_Y ? ev->y : surface->y, + .width = mask & XCB_CONFIG_WINDOW_WIDTH ? ev->width : surface->width, + .height = mask & XCB_CONFIG_WINDOW_HEIGHT ? ev->height : surface->height, + }; + wlr_log(WLR_DEBUG, "XCB_CONFIGURE_REQUEST (%u) [%ux%u+%d,%d]", ev->window, + wlr_event.width, wlr_event.height, wlr_event.x, wlr_event.y); + + wlr_signal_emit_safe(&surface->events.request_configure, &wlr_event); +} + +static void xwm_handle_configure_notify(struct wlr_xwm *xwm, + xcb_configure_notify_event_t *ev) { + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); + if (!xsurface) { + return; + } + + xsurface->x = ev->x; + xsurface->y = ev->y; + xsurface->width = ev->width; + xsurface->height = ev->height; + + if (xsurface->override_redirect != ev->override_redirect) { + xsurface->override_redirect = ev->override_redirect; + wlr_signal_emit_safe(&xsurface->events.set_override_redirect, xsurface); + } +} + +#define ICCCM_WITHDRAWN_STATE 0 +#define ICCCM_NORMAL_STATE 1 +#define ICCCM_ICONIC_STATE 3 + +static void xsurface_set_wm_state(struct wlr_xwayland_surface *xsurface, + int32_t state) { + struct wlr_xwm *xwm = xsurface->xwm; + uint32_t property[2]; + + property[0] = state; + property[1] = XCB_WINDOW_NONE; + + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xsurface->window_id, + xwm->atoms[WM_STATE], + xwm->atoms[WM_STATE], + 32, // format + 2, property); +} + +static void xwm_handle_map_request(struct wlr_xwm *xwm, + xcb_map_request_event_t *ev) { + wlr_log(WLR_DEBUG, "XCB_MAP_REQUEST (%u)", ev->window); + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); + if (!xsurface) { + return; + } + + xsurface_set_wm_state(xsurface, ICCCM_NORMAL_STATE); + xsurface_set_net_wm_state(xsurface); + xcb_map_window(xwm->xcb_conn, ev->window); +} + +static void xwm_handle_map_notify(struct wlr_xwm *xwm, + xcb_map_notify_event_t *ev) { + wlr_log(WLR_DEBUG, "XCB_MAP_NOTIFY (%u)", ev->window); + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); + if (!xsurface) { + return; + } + + if (xsurface->override_redirect != ev->override_redirect) { + xsurface->override_redirect = ev->override_redirect; + wlr_signal_emit_safe(&xsurface->events.set_override_redirect, xsurface); + } +} + +static void xwm_handle_unmap_notify(struct wlr_xwm *xwm, + xcb_unmap_notify_event_t *ev) { + wlr_log(WLR_DEBUG, "XCB_UNMAP_NOTIFY (%u)", ev->window); + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); + if (xsurface == NULL) { + return; + } + + xsurface_unmap(xsurface); + xsurface_set_wm_state(xsurface, ICCCM_WITHDRAWN_STATE); +} + +static void xwm_handle_property_notify(struct wlr_xwm *xwm, + xcb_property_notify_event_t *ev) { + wlr_log(WLR_DEBUG, "XCB_PROPERTY_NOTIFY (%u)", ev->window); + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); + if (xsurface == NULL) { + return; + } + + read_surface_property(xwm, xsurface, ev->atom); +} + +static void xwm_handle_surface_id_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *ev) { + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); + if (xsurface == NULL) { + wlr_log(WLR_DEBUG, + "client message WL_SURFACE_ID but no new window %u ?", + ev->window); + return; + } + /* Check if we got notified after wayland surface create event */ + uint32_t id = ev->data.data32[0]; + struct wl_resource *resource = + wl_client_get_object(xwm->xwayland->client, id); + if (resource) { + struct wlr_surface *surface = wlr_surface_from_resource(resource); + xsurface->surface_id = 0; + xwm_map_shell_surface(xwm, xsurface, surface); + } else { + xsurface->surface_id = id; + wl_list_insert(&xwm->unpaired_surfaces, &xsurface->unpaired_link); + } +} + +#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 +#define _NET_WM_MOVERESIZE_SIZE_TOP 1 +#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 +#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 +#define _NET_WM_MOVERESIZE_SIZE_LEFT 7 +#define _NET_WM_MOVERESIZE_MOVE 8 // movement only +#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 // size via keyboard +#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 // move via keyboard +#define _NET_WM_MOVERESIZE_CANCEL 11 // cancel operation + +static enum wlr_edges net_wm_edges_to_wlr(uint32_t net_wm_edges) { + enum wlr_edges edges = WLR_EDGE_NONE; + + switch(net_wm_edges) { + case _NET_WM_MOVERESIZE_SIZE_TOPLEFT: + edges = WLR_EDGE_TOP | WLR_EDGE_LEFT; + break; + case _NET_WM_MOVERESIZE_SIZE_TOP: + edges = WLR_EDGE_TOP; + break; + case _NET_WM_MOVERESIZE_SIZE_TOPRIGHT: + edges = WLR_EDGE_TOP | WLR_EDGE_RIGHT; + break; + case _NET_WM_MOVERESIZE_SIZE_RIGHT: + edges = WLR_EDGE_RIGHT; + break; + case _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT: + edges = WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT; + break; + case _NET_WM_MOVERESIZE_SIZE_BOTTOM: + edges = WLR_EDGE_BOTTOM; + break; + case _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT: + edges = WLR_EDGE_BOTTOM | WLR_EDGE_LEFT; + break; + case _NET_WM_MOVERESIZE_SIZE_LEFT: + edges = WLR_EDGE_LEFT; + break; + default: + break; + } + + return edges; +} + +static void xwm_handle_net_wm_moveresize_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *ev) { + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window); + if (!xsurface) { + return; + } + + // TODO: we should probably add input or seat info to this but we would just + // be guessing + struct wlr_xwayland_resize_event resize_event; + struct wlr_xwayland_move_event move_event; + + int detail = ev->data.data32[2]; + switch (detail) { + case _NET_WM_MOVERESIZE_MOVE: + move_event.surface = xsurface; + wlr_signal_emit_safe(&xsurface->events.request_move, &move_event); + break; + case _NET_WM_MOVERESIZE_SIZE_TOPLEFT: + case _NET_WM_MOVERESIZE_SIZE_TOP: + case _NET_WM_MOVERESIZE_SIZE_TOPRIGHT: + case _NET_WM_MOVERESIZE_SIZE_RIGHT: + case _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT: + case _NET_WM_MOVERESIZE_SIZE_BOTTOM: + case _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT: + case _NET_WM_MOVERESIZE_SIZE_LEFT: + resize_event.surface = xsurface; + resize_event.edges = net_wm_edges_to_wlr(detail); + wlr_signal_emit_safe(&xsurface->events.request_resize, &resize_event); + break; + case _NET_WM_MOVERESIZE_CANCEL: + // handled by the compositor + break; + } +} + +#define _NET_WM_STATE_REMOVE 0 +#define _NET_WM_STATE_ADD 1 +#define _NET_WM_STATE_TOGGLE 2 + +static bool update_state(int action, bool *state) { + int new_state, changed; + + switch (action) { + case _NET_WM_STATE_REMOVE: + new_state = false; + break; + case _NET_WM_STATE_ADD: + new_state = true; + break; + case _NET_WM_STATE_TOGGLE: + new_state = !*state; + break; + default: + return false; + } + + changed = (*state != new_state); + *state = new_state; + + return changed; +} + +static inline bool xsurface_is_maximized( + struct wlr_xwayland_surface *xsurface) { + return xsurface->maximized_horz && xsurface->maximized_vert; +} + +static void xwm_handle_net_wm_state_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *client_message) { + struct wlr_xwayland_surface *xsurface = + lookup_surface(xwm, client_message->window); + if (!xsurface) { + return; + } + if (client_message->format != 32) { + return; + } + + bool fullscreen = xsurface->fullscreen; + bool maximized = xsurface_is_maximized(xsurface); + + uint32_t action = client_message->data.data32[0]; + for (size_t i = 0; i < 2; ++i) { + uint32_t property = client_message->data.data32[1 + i]; + + if (property == xwm->atoms[_NET_WM_STATE_MODAL] && + update_state(action, &xsurface->modal)) { + xsurface_set_net_wm_state(xsurface); + } else if (property == xwm->atoms[_NET_WM_STATE_FULLSCREEN] && + update_state(action, &xsurface->fullscreen)) { + xsurface_set_net_wm_state(xsurface); + } else if (property == xwm->atoms[_NET_WM_STATE_MAXIMIZED_VERT] && + update_state(action, &xsurface->maximized_vert)) { + xsurface_set_net_wm_state(xsurface); + } else if (property == xwm->atoms[_NET_WM_STATE_MAXIMIZED_HORZ] && + update_state(action, &xsurface->maximized_horz)) { + xsurface_set_net_wm_state(xsurface); + } + } + // client_message->data.data32[3] is the source indication + // all other values are set to 0 + + if (fullscreen != xsurface->fullscreen) { + if (xsurface->fullscreen) { + xsurface->saved_width = xsurface->width; + xsurface->saved_height = xsurface->height; + } + + wlr_signal_emit_safe(&xsurface->events.request_fullscreen, xsurface); + } + + if (maximized != xsurface_is_maximized(xsurface)) { + if (xsurface_is_maximized(xsurface)) { + xsurface->saved_width = xsurface->width; + xsurface->saved_height = xsurface->height; + } + + wlr_signal_emit_safe(&xsurface->events.request_maximize, xsurface); + } +} + +static void xwm_handle_wm_protocols_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *ev) { + xcb_atom_t type = ev->data.data32[0]; + + if (type == xwm->atoms[_NET_WM_PING]) { + xcb_window_t window_id = ev->data.data32[2]; + + struct wlr_xwayland_surface *surface = lookup_surface(xwm, window_id); + if (surface == NULL) { + return; + } + + if (!surface->pinging) { + return; + } + + wl_event_source_timer_update(surface->ping_timer, 0); + surface->pinging = false; + } else { + char *type_name = xwm_get_atom_name(xwm, type); + wlr_log(WLR_DEBUG, "unhandled WM_PROTOCOLS client message %u (%s)", + type, type_name); + free(type_name); + } +} + +static void xwm_handle_net_active_window_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *ev) { + struct wlr_xwayland_surface *surface = lookup_surface(xwm, ev->window); + if (surface == NULL) { + return; + } + wlr_signal_emit_safe(&surface->events.request_activate, surface); +} + +static void xwm_handle_client_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *ev) { + wlr_log(WLR_DEBUG, "XCB_CLIENT_MESSAGE (%u)", ev->window); + + if (ev->type == xwm->atoms[WL_SURFACE_ID]) { + xwm_handle_surface_id_message(xwm, ev); + } else if (ev->type == xwm->atoms[NET_WM_STATE]) { + xwm_handle_net_wm_state_message(xwm, ev); + } else if (ev->type == xwm->atoms[_NET_WM_MOVERESIZE]) { + xwm_handle_net_wm_moveresize_message(xwm, ev); + } else if (ev->type == xwm->atoms[WM_PROTOCOLS]) { + xwm_handle_wm_protocols_message(xwm, ev); + } else if (ev->type == xwm->atoms[_NET_ACTIVE_WINDOW]) { + xwm_handle_net_active_window_message(xwm, ev); + } else if (!xwm_handle_selection_client_message(xwm, ev)) { + char *type_name = xwm_get_atom_name(xwm, ev->type); + wlr_log(WLR_DEBUG, "unhandled x11 client message %u (%s)", ev->type, + type_name); + free(type_name); + } +} + +static void xwm_handle_focus_in(struct wlr_xwm *xwm, + xcb_focus_in_event_t *ev) { + // Do not interfere with grabs + if (ev->mode == XCB_NOTIFY_MODE_GRAB || + ev->mode == XCB_NOTIFY_MODE_UNGRAB) { + return; + } + + // Do not let X clients change the focus behind the compositor's + // back. Reset the focus to the old one if it changed. + if (!xwm->focus_surface || ev->event != xwm->focus_surface->window_id) { + xwm_send_focus_window(xwm, xwm->focus_surface); + } +} + +static void xwm_handle_xcb_error(struct wlr_xwm *xwm, xcb_value_error_t *ev) { +#if WLR_HAS_XCB_ERRORS + const char *major_name = + xcb_errors_get_name_for_major_code(xwm->errors_context, + ev->major_opcode); + if (!major_name) { + wlr_log(WLR_DEBUG, "xcb error happened, but could not get major name"); + goto log_raw; + } + + const char *minor_name = + xcb_errors_get_name_for_minor_code(xwm->errors_context, + ev->major_opcode, ev->minor_opcode); + + const char *extension; + const char *error_name = + xcb_errors_get_name_for_error(xwm->errors_context, + ev->error_code, &extension); + if (!error_name) { + wlr_log(WLR_DEBUG, "xcb error happened, but could not get error name"); + goto log_raw; + } + + wlr_log(WLR_ERROR, "xcb 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, + "xcb 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 xwm_handle_unhandled_event(struct wlr_xwm *xwm, xcb_generic_event_t *ev) { +#if WLR_HAS_XCB_ERRORS + const char *extension; + const char *event_name = + xcb_errors_get_name_for_xcb_event(xwm->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 +} + +static int x11_event_handler(int fd, uint32_t mask, void *data) { + int count = 0; + xcb_generic_event_t *event; + struct wlr_xwm *xwm = data; + + while ((event = xcb_poll_for_event(xwm->xcb_conn))) { + count++; + + if (xwm->xwayland->user_event_handler && + xwm->xwayland->user_event_handler(xwm, event)) { + break; + } + + if (xwm_handle_selection_event(xwm, event)) { + free(event); + continue; + } + + switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) { + case XCB_CREATE_NOTIFY: + xwm_handle_create_notify(xwm, (xcb_create_notify_event_t *)event); + break; + case XCB_DESTROY_NOTIFY: + xwm_handle_destroy_notify(xwm, (xcb_destroy_notify_event_t *)event); + break; + case XCB_CONFIGURE_REQUEST: + xwm_handle_configure_request(xwm, + (xcb_configure_request_event_t *)event); + break; + case XCB_CONFIGURE_NOTIFY: + xwm_handle_configure_notify(xwm, + (xcb_configure_notify_event_t *)event); + break; + case XCB_MAP_REQUEST: + xwm_handle_map_request(xwm, (xcb_map_request_event_t *)event); + break; + case XCB_MAP_NOTIFY: + xwm_handle_map_notify(xwm, (xcb_map_notify_event_t *)event); + break; + case XCB_UNMAP_NOTIFY: + xwm_handle_unmap_notify(xwm, (xcb_unmap_notify_event_t *)event); + break; + case XCB_PROPERTY_NOTIFY: + xwm_handle_property_notify(xwm, + (xcb_property_notify_event_t *)event); + break; + case XCB_CLIENT_MESSAGE: + xwm_handle_client_message(xwm, (xcb_client_message_event_t *)event); + break; + case XCB_FOCUS_IN: + xwm_handle_focus_in(xwm, (xcb_focus_in_event_t *)event); + break; + case 0: + xwm_handle_xcb_error(xwm, (xcb_value_error_t *)event); + break; + default: + xwm_handle_unhandled_event(xwm, event); + break; + } + free(event); + } + + if (count) { + xcb_flush(xwm->xcb_conn); + } + + return count; +} + +static void handle_compositor_new_surface(struct wl_listener *listener, + void *data) { + struct wlr_xwm *xwm = + wl_container_of(listener, xwm, compositor_new_surface); + struct wlr_surface *surface = data; + if (wl_resource_get_client(surface->resource) != xwm->xwayland->client) { + return; + } + + wlr_log(WLR_DEBUG, "New xwayland surface: %p", surface); + + uint32_t surface_id = wl_resource_get_id(surface->resource); + struct wlr_xwayland_surface *xsurface; + wl_list_for_each(xsurface, &xwm->unpaired_surfaces, unpaired_link) { + if (xsurface->surface_id == surface_id) { + xwm_map_shell_surface(xwm, xsurface, surface); + xsurface->surface_id = 0; + wl_list_remove(&xsurface->unpaired_link); + xcb_flush(xwm->xcb_conn); + return; + } + } +} + +static void handle_compositor_destroy(struct wl_listener *listener, + void *data) { + struct wlr_xwm *xwm = + wl_container_of(listener, xwm, compositor_destroy); + wl_list_remove(&xwm->compositor_new_surface.link); + wl_list_remove(&xwm->compositor_destroy.link); + wl_list_init(&xwm->compositor_new_surface.link); + wl_list_init(&xwm->compositor_destroy.link); +} + +void wlr_xwayland_surface_activate(struct wlr_xwayland_surface *xsurface, + bool activated) { + struct wlr_xwayland_surface *focused = xsurface->xwm->focus_surface; + if (activated) { + xwm_surface_activate(xsurface->xwm, xsurface); + } else if (focused == xsurface) { + xwm_surface_activate(xsurface->xwm, NULL); + } +} + +void wlr_xwayland_surface_configure(struct wlr_xwayland_surface *xsurface, + int16_t x, int16_t y, uint16_t width, uint16_t height) { + xsurface->x = x; + xsurface->y = y; + xsurface->width = width; + xsurface->height = height; + + struct wlr_xwm *xwm = xsurface->xwm; + uint32_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | + XCB_CONFIG_WINDOW_BORDER_WIDTH; + uint32_t values[] = {x, y, width, height, 0}; + xcb_configure_window(xwm->xcb_conn, xsurface->window_id, mask, values); + xcb_flush(xwm->xcb_conn); +} + +void wlr_xwayland_surface_close(struct wlr_xwayland_surface *xsurface) { + struct wlr_xwm *xwm = xsurface->xwm; + + bool supports_delete = false; + for (size_t i = 0; i < xsurface->protocols_len; i++) { + if (xsurface->protocols[i] == xwm->atoms[WM_DELETE_WINDOW]) { + supports_delete = true; + break; + } + } + + if (supports_delete) { + xcb_client_message_data_t message_data = {0}; + message_data.data32[0] = xwm->atoms[WM_DELETE_WINDOW]; + message_data.data32[1] = XCB_CURRENT_TIME; + xwm_send_wm_message(xsurface, &message_data, XCB_EVENT_MASK_NO_EVENT); + } else { + xcb_kill_client(xwm->xcb_conn, xsurface->window_id); + xcb_flush(xwm->xcb_conn); + } +} + +void xwm_destroy(struct wlr_xwm *xwm) { + if (!xwm) { + return; + } + xwm_selection_finish(xwm); + if (xwm->cursor) { + xcb_free_cursor(xwm->xcb_conn, xwm->cursor); + } + if (xwm->colormap) { + xcb_free_colormap(xwm->xcb_conn, xwm->colormap); + } + if (xwm->window) { + xcb_destroy_window(xwm->xcb_conn, xwm->window); + } + if (xwm->event_source) { + wl_event_source_remove(xwm->event_source); + } +#if WLR_HAS_XCB_ERRORS + if (xwm->errors_context) { + xcb_errors_context_free(xwm->errors_context); + } +#endif + struct wlr_xwayland_surface *xsurface, *tmp; + wl_list_for_each_safe(xsurface, tmp, &xwm->surfaces, link) { + xwayland_surface_destroy(xsurface); + } + wl_list_for_each_safe(xsurface, tmp, &xwm->unpaired_surfaces, link) { + xwayland_surface_destroy(xsurface); + } + wl_list_remove(&xwm->compositor_new_surface.link); + wl_list_remove(&xwm->compositor_destroy.link); + xcb_disconnect(xwm->xcb_conn); + + free(xwm); +} + +static void xwm_get_resources(struct wlr_xwm *xwm) { + xcb_prefetch_extension_data(xwm->xcb_conn, &xcb_xfixes_id); + xcb_prefetch_extension_data(xwm->xcb_conn, &xcb_composite_id); + + size_t i; + xcb_intern_atom_cookie_t cookies[ATOM_LAST]; + + for (i = 0; i < ATOM_LAST; i++) { + cookies[i] = + xcb_intern_atom(xwm->xcb_conn, 0, strlen(atom_map[i]), atom_map[i]); + } + for (i = 0; i < ATOM_LAST; i++) { + xcb_generic_error_t *error; + xcb_intern_atom_reply_t *reply = + xcb_intern_atom_reply(xwm->xcb_conn, cookies[i], &error); + if (reply && !error) { + xwm->atoms[i] = reply->atom; + } + free(reply); + + if (error) { + wlr_log(WLR_ERROR, "could not resolve atom %s, x11 error code %d", + atom_map[i], error->error_code); + free(error); + return; + } + } + + xwm->xfixes = xcb_get_extension_data(xwm->xcb_conn, &xcb_xfixes_id); + + if (!xwm->xfixes || !xwm->xfixes->present) { + wlr_log(WLR_DEBUG, "xfixes not available"); + } + + xcb_xfixes_query_version_cookie_t xfixes_cookie; + xcb_xfixes_query_version_reply_t *xfixes_reply; + xfixes_cookie = + xcb_xfixes_query_version(xwm->xcb_conn, XCB_XFIXES_MAJOR_VERSION, + XCB_XFIXES_MINOR_VERSION); + xfixes_reply = + xcb_xfixes_query_version_reply(xwm->xcb_conn, xfixes_cookie, NULL); + + wlr_log(WLR_DEBUG, "xfixes version: %d.%d", + xfixes_reply->major_version, xfixes_reply->minor_version); + + free(xfixes_reply); +} + +static void xwm_create_wm_window(struct wlr_xwm *xwm) { + static const char name[] = "wlroots wm"; + + xwm->window = xcb_generate_id(xwm->xcb_conn); + + xcb_create_window(xwm->xcb_conn, + XCB_COPY_FROM_PARENT, + xwm->window, + xwm->screen->root, + 0, 0, + 10, 10, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + xwm->screen->root_visual, + 0, NULL); + + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xwm->window, + xwm->atoms[_NET_WM_NAME], + xwm->atoms[UTF8_STRING], + 8, // format + strlen(name), name); + + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xwm->screen->root, + xwm->atoms[_NET_SUPPORTING_WM_CHECK], + XCB_ATOM_WINDOW, + 32, // format + 1, &xwm->window); + + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xwm->window, + xwm->atoms[_NET_SUPPORTING_WM_CHECK], + XCB_ATOM_WINDOW, + 32, // format + 1, &xwm->window); + + xcb_set_selection_owner(xwm->xcb_conn, + xwm->window, + xwm->atoms[WM_S0], + XCB_CURRENT_TIME); + + xcb_set_selection_owner(xwm->xcb_conn, + xwm->window, + xwm->atoms[NET_WM_CM_S0], + XCB_CURRENT_TIME); +} + +// TODO use me to support 32 bit color somehow +static void xwm_get_visual_and_colormap(struct wlr_xwm *xwm) { + xcb_depth_iterator_t d_iter; + xcb_visualtype_iterator_t vt_iter; + xcb_visualtype_t *visualtype; + + d_iter = xcb_screen_allowed_depths_iterator(xwm->screen); + visualtype = NULL; + while (d_iter.rem > 0) { + if (d_iter.data->depth == 32) { + vt_iter = xcb_depth_visuals_iterator(d_iter.data); + visualtype = vt_iter.data; + break; + } + + xcb_depth_next(&d_iter); + } + + if (visualtype == NULL) { + wlr_log(WLR_DEBUG, "No 32 bit visualtype\n"); + return; + } + + xwm->visual_id = visualtype->visual_id; + xwm->colormap = xcb_generate_id(xwm->xcb_conn); + xcb_create_colormap(xwm->xcb_conn, + XCB_COLORMAP_ALLOC_NONE, + xwm->colormap, + xwm->screen->root, + xwm->visual_id); +} + +static void xwm_get_render_format(struct wlr_xwm *xwm) { + xcb_render_query_pict_formats_cookie_t cookie = + xcb_render_query_pict_formats(xwm->xcb_conn); + xcb_render_query_pict_formats_reply_t *reply = + xcb_render_query_pict_formats_reply(xwm->xcb_conn, cookie, NULL); + if (!reply) { + wlr_log(WLR_ERROR, "Did not get any reply from xcb_render_query_pict_formats"); + return; + } + xcb_render_pictforminfo_iterator_t iter = + xcb_render_query_pict_formats_formats_iterator(reply); + xcb_render_pictforminfo_t *format = NULL; + while (iter.rem > 0) { + if (iter.data->depth == 32) { + format = iter.data; + break; + } + + xcb_render_pictforminfo_next(&iter); + } + + if (format == NULL) { + wlr_log(WLR_DEBUG, "No 32 bit render format"); + free(reply); + return; + } + + xwm->render_format_id = format->id; + free(reply); +} + +void xwm_set_cursor(struct wlr_xwm *xwm, const uint8_t *pixels, uint32_t stride, + uint32_t width, uint32_t height, int32_t hotspot_x, int32_t hotspot_y) { + if (!xwm->render_format_id) { + wlr_log(WLR_ERROR, "Cannot set xwm cursor: no render format available"); + return; + } + if (xwm->cursor) { + xcb_free_cursor(xwm->xcb_conn, xwm->cursor); + } + + int depth = 32; + + xcb_pixmap_t pix = xcb_generate_id(xwm->xcb_conn); + xcb_create_pixmap(xwm->xcb_conn, depth, pix, xwm->screen->root, width, + height); + + xcb_render_picture_t pic = xcb_generate_id(xwm->xcb_conn); + xcb_render_create_picture(xwm->xcb_conn, pic, pix, xwm->render_format_id, + 0, 0); + + xcb_gcontext_t gc = xcb_generate_id(xwm->xcb_conn); + xcb_create_gc(xwm->xcb_conn, gc, pix, 0, NULL); + + xcb_put_image(xwm->xcb_conn, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc, + width, height, 0, 0, 0, depth, stride * height * sizeof(uint8_t), + pixels); + xcb_free_gc(xwm->xcb_conn, gc); + + xwm->cursor = xcb_generate_id(xwm->xcb_conn); + xcb_render_create_cursor(xwm->xcb_conn, xwm->cursor, pic, hotspot_x, + hotspot_y); + xcb_free_pixmap(xwm->xcb_conn, pix); + + uint32_t values[] = {xwm->cursor}; + xcb_change_window_attributes(xwm->xcb_conn, xwm->screen->root, + XCB_CW_CURSOR, values); + xcb_flush(xwm->xcb_conn); +} + +struct wlr_xwm *xwm_create(struct wlr_xwayland *wlr_xwayland) { + struct wlr_xwm *xwm = calloc(1, sizeof(struct wlr_xwm)); + if (xwm == NULL) { + return NULL; + } + + xwm->xwayland = wlr_xwayland; + wl_list_init(&xwm->surfaces); + wl_list_init(&xwm->unpaired_surfaces); + xwm->ping_timeout = 10000; + + xwm->xcb_conn = xcb_connect_to_fd(wlr_xwayland->wm_fd[0], NULL); + + int rc = xcb_connection_has_error(xwm->xcb_conn); + if (rc) { + wlr_log(WLR_ERROR, "xcb connect failed: %d", rc); + close(wlr_xwayland->wm_fd[0]); + free(xwm); + return NULL; + } + +#if WLR_HAS_XCB_ERRORS + if (xcb_errors_context_new(xwm->xcb_conn, &xwm->errors_context)) { + wlr_log(WLR_ERROR, "Could not allocate error context"); + xwm_destroy(xwm); + return NULL; + } +#endif + xcb_screen_iterator_t screen_iterator = + xcb_setup_roots_iterator(xcb_get_setup(xwm->xcb_conn)); + xwm->screen = screen_iterator.data; + + struct wl_event_loop *event_loop = wl_display_get_event_loop( + wlr_xwayland->wl_display); + xwm->event_source = + wl_event_loop_add_fd(event_loop, + wlr_xwayland->wm_fd[0], + WL_EVENT_READABLE, + x11_event_handler, + xwm); + wl_event_source_check(xwm->event_source); + + xwm_get_resources(xwm); + xwm_get_visual_and_colormap(xwm); + xwm_get_render_format(xwm); + + uint32_t values[] = { + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | + XCB_EVENT_MASK_PROPERTY_CHANGE, + }; + xcb_change_window_attributes(xwm->xcb_conn, + xwm->screen->root, + XCB_CW_EVENT_MASK, + values); + + xcb_composite_redirect_subwindows(xwm->xcb_conn, + xwm->screen->root, + XCB_COMPOSITE_REDIRECT_MANUAL); + + xcb_atom_t supported[] = { + xwm->atoms[NET_WM_STATE], + xwm->atoms[_NET_ACTIVE_WINDOW], + xwm->atoms[_NET_WM_MOVERESIZE], + xwm->atoms[_NET_WM_STATE_MODAL], + xwm->atoms[_NET_WM_STATE_FULLSCREEN], + xwm->atoms[_NET_WM_STATE_MAXIMIZED_VERT], + xwm->atoms[_NET_WM_STATE_MAXIMIZED_HORZ], + }; + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xwm->screen->root, + xwm->atoms[NET_SUPPORTED], + XCB_ATOM_ATOM, + 32, + sizeof(supported)/sizeof(*supported), + supported); + + xcb_flush(xwm->xcb_conn); + + xwm_set_net_active_window(xwm, XCB_WINDOW_NONE); + + xwm_selection_init(xwm); + + xwm->compositor_new_surface.notify = handle_compositor_new_surface; + wl_signal_add(&wlr_xwayland->compositor->events.new_surface, + &xwm->compositor_new_surface); + xwm->compositor_destroy.notify = handle_compositor_destroy; + wl_signal_add(&wlr_xwayland->compositor->events.destroy, + &xwm->compositor_destroy); + + xwm_create_wm_window(xwm); + + xcb_flush(xwm->xcb_conn); + + return xwm; +} + +void wlr_xwayland_surface_set_maximized(struct wlr_xwayland_surface *surface, + bool maximized) { + surface->maximized_horz = maximized; + surface->maximized_vert = maximized; + xsurface_set_net_wm_state(surface); + xcb_flush(surface->xwm->xcb_conn); +} + +void wlr_xwayland_surface_set_fullscreen(struct wlr_xwayland_surface *surface, + bool fullscreen) { + surface->fullscreen = fullscreen; + xsurface_set_net_wm_state(surface); + xcb_flush(surface->xwm->xcb_conn); +} + +bool xwm_atoms_contains(struct wlr_xwm *xwm, xcb_atom_t *atoms, + size_t num_atoms, enum atom_name needle) { + xcb_atom_t atom = xwm->atoms[needle]; + + for (size_t i = 0; i < num_atoms; ++i) { + if (atom == atoms[i]) { + return true; + } + } + + return false; +} + +void wlr_xwayland_surface_ping(struct wlr_xwayland_surface *surface) { + xcb_client_message_data_t data = { 0 }; + data.data32[0] = surface->xwm->atoms[_NET_WM_PING]; + data.data32[1] = XCB_CURRENT_TIME; + data.data32[2] = surface->window_id; + + xwm_send_wm_message(surface, &data, XCB_EVENT_MASK_NO_EVENT); + + wl_event_source_timer_update(surface->ping_timer, + surface->xwm->ping_timeout); + surface->pinging = true; +} + +bool wlr_xwayland_or_surface_wants_focus( + const struct wlr_xwayland_surface *surface) { + bool ret = true; + static enum atom_name needles[] = { + NET_WM_WINDOW_TYPE_COMBO, + NET_WM_WINDOW_TYPE_DND, + NET_WM_WINDOW_TYPE_DROPDOWN_MENU, + NET_WM_WINDOW_TYPE_MENU, + NET_WM_WINDOW_TYPE_NOTIFICATION, + NET_WM_WINDOW_TYPE_POPUP_MENU, + NET_WM_WINDOW_TYPE_SPLASH, + NET_WM_WINDOW_TYPE_TOOLTIP, + NET_WM_WINDOW_TYPE_UTILITY, + }; + for (size_t i = 0; i < sizeof(needles) / sizeof(needles[0]); ++i) { + if (xwm_atoms_contains(surface->xwm, surface->window_type, + surface->window_type_len, needles[i])) { + ret = false; + } + } + + return ret; +} |