diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b6445093..65ae360b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,7 +43,7 @@ variables: FDO_UPSTREAM_REPO: wayland/weston FDO_REPO_SUFFIX: "$BUILD_OS/$BUILD_ARCH" - FDO_DISTRIBUTION_TAG: '2021-11-25.0-dmabuf-feedback' + FDO_DISTRIBUTION_TAG: '2022-07-13.00-wayland-protocols-1.26' include: @@ -202,7 +202,7 @@ aarch64-debian-container_prep: - $BUILDDIR/weston-virtme - $PREFIX reports: - junit: $BUILDDIR/meson-logs/testlog.junit.xml + junit: $BUILDDIR/meson-logs/testlog-per-test-asan.sh.junit.xml # Same as above, but without running any tests. .build-no-test: @@ -283,8 +283,8 @@ aarch64-debian-container_prep: -Dwerror=true -Dtest-skip-is-failure=true -Dlauncher-libseat=true - -Ddeprecated-backend-fbdev=true - -Ddeprecated-weston-launch=true + -Ddeprecated-color-management-static=true + -Ddeprecated-color-management-colord=true after_script: - ninja -C "$BUILDDIR" coverage-html > "$BUILDDIR/meson-logs/ninja-coverage-html.txt" - ninja -C "$BUILDDIR" coverage-xml @@ -340,7 +340,6 @@ docs-build: -Dpipewire=false -Dwerror=true -Dlauncher-libseat=true - -Ddeprecated-weston-launch=true x86_64-debian-no-gl-build: extends: diff --git a/.gitlab-ci/build-deps.sh b/.gitlab-ci/build-deps.sh index bce36e92..3c8f36fc 100755 --- a/.gitlab-ci/build-deps.sh +++ b/.gitlab-ci/build-deps.sh @@ -75,7 +75,8 @@ if [[ -n "$KERNEL_DEFCONFIG" ]]; then --enable CONFIG_DRM \ --enable CONFIG_DRM_KMS_HELPER \ --enable CONFIG_DRM_KMS_FB_HELPER \ - --enable CONFIG_DRM_VKMS + --enable CONFIG_DRM_VKMS \ + --enable CONFIG_DRM_VGEM make ARCH=${LINUX_ARCH} oldconfig make ARCH=${LINUX_ARCH} @@ -94,7 +95,7 @@ fi # Build and install Wayland; keep this version in sync with our dependency # in meson.build. -git clone --branch 1.18.0 --depth=1 https://gitlab.freedesktop.org/wayland/wayland +git clone --branch 1.20.0 --depth=1 https://gitlab.freedesktop.org/wayland/wayland cd wayland git show -s HEAD mkdir build @@ -106,7 +107,7 @@ rm -rf wayland # Keep this version in sync with our dependency in meson.build. If you wish to # raise a MR against custom protocol, please change this reference to clone # your relevant tree, and make sure you bump $FDO_DISTRIBUTION_TAG. -git clone --branch 1.24 --depth=1 https://gitlab.freedesktop.org/wayland/wayland-protocols +git clone --branch 1.26 --depth=1 https://gitlab.freedesktop.org/wayland/wayland-protocols cd wayland-protocols git show -s HEAD meson build @@ -129,6 +130,17 @@ ninja ${NINJAFLAGS} -C build install cd .. rm -rf mesa +# Build and install our own version of libdrm. Debian 11 (bullseye) provides +# libdrm 2.4.104 which doesn't have the IN_FORMATS iterator api. We can stop +# building and installing libdrm as soon as we move to Debian 12. +git clone --branch libdrm-2.4.108 --depth=1 https://gitlab.freedesktop.org/mesa/drm.git +cd drm +meson build -Dauto_features=disabled \ + -Dvc4=false -Dfreedreno=false -Detnaviv=false +ninja ${NINJAFLAGS} -C build install +cd .. +rm -rf drm + # PipeWire is used for remoting support. Unlike our other dependencies its # behaviour will be stable, however as a pre-1.0 project its API is not yet # stable, so again we lock it to a fixed version. @@ -142,9 +154,8 @@ ninja ${NINJAFLAGS} -C build install cd .. rm -rf pipewire-src -# seatd lets us avoid the pain of handling VTs manually through weston-launch -# or open-coding TTY assignment within Weston. We use this for our tests using -# the DRM backend. +# seatd lets us avoid the pain of open-coding TTY assignment within Weston. +# We use this for our tests using the DRM backend. git clone --depth=1 --branch 0.6.1 https://git.sr.ht/~kennylevinsen/seatd cd seatd meson build -Dauto_features=disabled \ diff --git a/.gitlab-ci/debian-install.sh b/.gitlab-ci/debian-install.sh index 2c243101..8adfd835 100644 --- a/.gitlab-ci/debian-install.sh +++ b/.gitlab-ci/debian-install.sh @@ -39,6 +39,7 @@ apt-get -y --no-install-recommends install \ clang-11 \ curl \ doxygen \ + graphviz \ freerdp2-dev \ gcovr \ git \ @@ -68,6 +69,7 @@ apt-get -y --no-install-recommends install \ libmtdev-dev \ libpam0g-dev \ libpango1.0-dev \ + libpciaccess-dev \ libpixman-1-dev \ libpng-dev \ libpulse-dev \ diff --git a/.gitlab-ci/leak-sanitizer.supp b/.gitlab-ci/leak-sanitizer.supp index a5561804..a919405f 100644 --- a/.gitlab-ci/leak-sanitizer.supp +++ b/.gitlab-ci/leak-sanitizer.supp @@ -3,3 +3,9 @@ # Cairo internal leaks from weston-keyboard leak:cairo_select_font_face leak:cairo_text_extents + +# Pango thread-global state (not destroyable?) +leak:pango_language_get_default + +# This leaks in Debian's fontconfig/Xwayland setup? +leak:FcConfigSubstituteWithPat diff --git a/.gitlab-ci/virtme-scripts/per-test-asan.sh b/.gitlab-ci/virtme-scripts/per-test-asan.sh new file mode 100755 index 00000000..044ac6d2 --- /dev/null +++ b/.gitlab-ci/virtme-scripts/per-test-asan.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# When running Debian's Xwayland and fontconfig, we hit memory leaks which +# aren't visible on other setups. We do have suppressions for these tests, but +# regrettably ASan can't see through omitted frame pointers in Expat, so for +# Xwayland specifically, we disable fast-unwind. +# +# Doing it globally makes the other tests far, far, too slow to run. +case "$1" in + *xwayland*) + export ASAN_OPTIONS="detect_leaks=0,fast_unwind_on_malloc=0" + ;; + *) + export ASAN_OPTIONS="detect_leaks=0" + ;; +esac + +export ASAN_OPTIONS + +exec "$@" diff --git a/.gitlab-ci/virtme-scripts/run-weston-tests.sh b/.gitlab-ci/virtme-scripts/run-weston-tests.sh index 66da7b33..c61bd615 100755 --- a/.gitlab-ci/virtme-scripts/run-weston-tests.sh +++ b/.gitlab-ci/virtme-scripts/run-weston-tests.sh @@ -7,8 +7,15 @@ chmod -R 0700 /tmp # set environment variables to run Weston tests export XDG_RUNTIME_DIR=/tmp/tests -export WESTON_TEST_SUITE_DRM_DEVICE=card0 export LIBSEAT_BACKEND=seatd +# In our test suite, we use VKMS to run DRM-backend tests. The order in which +# devices are loaded is not predictable, so the DRM node that VKMS takes can +# change across each boot. That's why we have this one-liner shell script to get +# the appropriate node for VKMS. +export WESTON_TEST_SUITE_DRM_DEVICE=$(basename /sys/devices/platform/vkms/drm/card*) +# To run tests in the CI that exercise the zwp_linux_dmabuf_v1 implementation in +# Weston, we use VGEM to allocate buffers. +export WESTON_TEST_SUITE_ALLOC_DEVICE=$(basename /sys/devices/platform/vgem/drm/card*) # ninja test depends on meson, and meson itself looks for its modules on folder # $HOME/.local/lib/pythonX.Y/site-packages (the Python version may differ). @@ -19,13 +26,17 @@ export HOME=/root export PATH=$HOME/.local/bin:$PATH export PATH=/usr/local/bin:$PATH -export ASAN_OPTIONS=detect_leaks=0,atexit=1 export SEATD_LOGLEVEL=debug +# Terrible hack, per comment in weston-test-runner.c's main(): find Mesa's +# llvmpipe driver module location +export WESTON_CI_LEAK_DL_HANDLE=$(find /usr/local -name swrast_dri.so -print 2>/dev/null || true) + # run the tests and save the exit status # we give ourselves a very generous timeout multiplier due to ASan overhead echo 0x1f > /sys/module/drm/parameters/debug -seatd-launch -- meson test --no-rebuild --timeout-multiplier 4 +seatd-launch -- meson test --no-rebuild --timeout-multiplier 4 \ + --wrapper $(pwd)/../.gitlab-ci/virtme-scripts/per-test-asan.sh # note that we need to store the return value from the tests in order to # determine if the test suite ran successfully or not. TEST_RES=$? diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 55e6e8a1..b8687bb1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -208,6 +208,10 @@ my_function(void) parameter3, parameter4); ``` +- do not write fallback paths for failed simple memory allocations, use the + `x*alloc()` wrappers from `shared/xalloc.h` instead or use + `abort_oom_if_null()` + Conduct ======= diff --git a/README.md b/README.md index bbc93b45..03e32392 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,12 @@ bugs and shortcomings, we avoid unknown or variable behaviour as much as possible, including variable performance such as occasional spikes in frame display time. +Weston and libweston are not suitable for memory constrained environments +where the compositor is expected to continue running even in the face of +trivial memory allocations failing. If standard functions like `malloc()` +fail for small allocations, +[you can expect libweston to abort](https://gitlab.freedesktop.org/wayland/weston/-/issues/631). + A small suite of example or demo clients are also provided: though they can be useful in themselves, their main purpose is to be an example or test case for others building compositors or clients. @@ -93,13 +99,6 @@ the available configuration options and display backends. It can also be configured through a file on disk; more information on this can be found through `man weston.ini`. -In some special cases, such as when running remotely or without logind's session -control, Weston may not be able to run directly from a text console. In these -situations, you can instead execute the `weston-launch` helper, which will gain -privileged access to input and output devices by running as root, then granting -access to the main Weston binary running as your user. Running Weston this way -is not recommended unless necessary. - Documentation ============= @@ -294,7 +293,7 @@ Details: - Child process execution and management will be outside of libweston. -- The different backends (drm, fbdev, x11, etc) will be an internal +- The different backends (drm, x11, etc) will be an internal detail of libweston. Libweston will not support third party backends. However, hosting programs need to handle backend-specific configuration due to differences in behaviour and @@ -308,19 +307,12 @@ Details: - xwayland ??? -- weston-launch is still with libweston even though it can only launch - Weston and nothing else. We would like to allow it to launch any compositor, - but since it gives by design root access to input devices and DRM, how can - we restrict it to intended programs? - There are still many more details to be decided. For packagers ------------- -Always build Weston with --with-cairo=image. - The Weston project is (will be) intended to be split into several binary packages, each with its own dependencies. The maximal split would be roughly like this: @@ -337,15 +329,13 @@ would be roughly like this: - xwayland (depends on X11/xcb libs) -- fbdev-backend (depends on libudev...) - - rdp-backend (depends on freerdp) - weston (the executable, not parallel-installable): + desktop shell + ivi-shell + fullscreen shell - + weston-info (deprecated), weston-terminal, etc. we install by default + + weston-terminal, etc. we install by default + screen-share - weston demos (not parallel-installable) diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c index fb53069e..b450a010 100644 --- a/clients/desktop-shell.c +++ b/clients/desktop-shell.c @@ -41,22 +41,23 @@ #include #include -#include "window.h" -#include "shared/cairo-util.h" + #include +#include #include "shared/helpers.h" #include "shared/xalloc.h" -#include +#include "shared/cairo-util.h" #include "shared/file-util.h" +#include "shared/process-util.h" #include "shared/timespec-util.h" +#include "window.h" + #include "weston-desktop-shell-client-protocol.h" #define DEFAULT_CLOCK_FORMAT CLOCK_FORMAT_MINUTES #define DEFAULT_SPACING 10 -extern char **environ; /* defined by libc */ - enum clock_format { CLOCK_FORMAT_MINUTES, CLOCK_FORMAT_SECONDS, @@ -142,9 +143,11 @@ struct panel_launcher { cairo_surface_t *icon; int focused, pressed; char *path; + char *displayname; struct wl_list link; - struct wl_array envp; - struct wl_array argv; + struct custom_env env; + char * const *argp; + char * const *envp; }; struct panel_clock { @@ -211,7 +214,6 @@ check_desktop_ready(struct window *window) static void panel_launcher_activate(struct panel_launcher *widget) { - char **argv; pid_t pid; pid = fork(); @@ -223,13 +225,11 @@ panel_launcher_activate(struct panel_launcher *widget) if (pid) return; - argv = widget->argv.data; - if (setsid() == -1) exit(EXIT_FAILURE); - if (execve(argv[0], argv, widget->envp.data) < 0) { - fprintf(stderr, "execl '%s' failed: %s\n", argv[0], + if (execve(widget->argp[0], widget->argp, widget->envp) < 0) { + fprintf(stderr, "execl '%s' failed: %s\n", widget->argp[0], strerror(errno)); exit(1); } @@ -277,7 +277,7 @@ panel_launcher_motion_handler(struct widget *widget, struct input *input, { struct panel_launcher *launcher = data; - widget_set_tooltip(widget, basename((char *)launcher->path), x, y); + widget_set_tooltip(widget, launcher->displayname, x, y); return CURSOR_LEFT_PTR; } @@ -575,10 +575,10 @@ panel_configure(void *data, static void panel_destroy_launcher(struct panel_launcher *launcher) { - wl_array_release(&launcher->argv); - wl_array_release(&launcher->envp); + custom_env_fini(&launcher->env); free(launcher->path); + free(launcher->displayname); cairo_surface_destroy(launcher->icon); @@ -678,58 +678,19 @@ load_icon_or_fallback(const char *icon) } static void -panel_add_launcher(struct panel *panel, const char *icon, const char *path) +panel_add_launcher(struct panel *panel, const char *icon, const char *path, const char *displayname) { struct panel_launcher *launcher; - char *start, *p, *eq, **ps; - int i, j, k; launcher = xzalloc(sizeof *launcher); launcher->icon = load_icon_or_fallback(icon); launcher->path = xstrdup(path); + launcher->displayname = xstrdup(displayname); - wl_array_init(&launcher->envp); - wl_array_init(&launcher->argv); - for (i = 0; environ[i]; i++) { - ps = wl_array_add(&launcher->envp, sizeof *ps); - *ps = environ[i]; - } - j = 0; - - start = launcher->path; - while (*start) { - for (p = start, eq = NULL; *p && !isspace(*p); p++) - if (*p == '=') - eq = p; - - if (eq && j == 0) { - ps = launcher->envp.data; - for (k = 0; k < i; k++) - if (strncmp(ps[k], start, eq - start) == 0) { - ps[k] = start; - break; - } - if (k == i) { - ps = wl_array_add(&launcher->envp, sizeof *ps); - *ps = start; - i++; - } - } else { - ps = wl_array_add(&launcher->argv, sizeof *ps); - *ps = start; - j++; - } - - while (*p && isspace(*p)) - *p++ = '\0'; - - start = p; - } - - ps = wl_array_add(&launcher->envp, sizeof *ps); - *ps = NULL; - ps = wl_array_add(&launcher->argv, sizeof *ps); - *ps = NULL; + custom_env_init_from_environ(&launcher->env); + custom_env_add_from_exec_string(&launcher->env, launcher->path); + launcher->envp = custom_env_get_envp(&launcher->env); + launcher->argp = custom_env_get_argp(&launcher->env); launcher->panel = panel; wl_list_insert(panel->launcher_list.prev, &launcher->link); @@ -1447,7 +1408,7 @@ static void panel_add_launchers(struct panel *panel, struct desktop *desktop) { struct weston_config_section *s; - char *icon, *path; + char *icon, *path, *displayname; const char *name; int count; @@ -1459,9 +1420,12 @@ panel_add_launchers(struct panel *panel, struct desktop *desktop) weston_config_section_get_string(s, "icon", &icon, NULL); weston_config_section_get_string(s, "path", &path, NULL); + weston_config_section_get_string(s, "displayname", &displayname, NULL); + if (displayname == NULL) + displayname = xstrdup(basename(path)); if (icon != NULL && path != NULL) { - panel_add_launcher(panel, icon, path); + panel_add_launcher(panel, icon, path, displayname); count++; } else { fprintf(stderr, "invalid launcher section\n"); @@ -1469,6 +1433,7 @@ panel_add_launchers(struct panel *panel, struct desktop *desktop) free(icon); free(path); + free(displayname); } if (count == 0) { @@ -1477,7 +1442,8 @@ panel_add_launchers(struct panel *panel, struct desktop *desktop) /* add default launcher */ panel_add_launcher(panel, name, - BINDIR "/weston-terminal"); + BINDIR "/weston-terminal", + "Terminal"); free(name); } } diff --git a/clients/eventdemo.c b/clients/eventdemo.c index 1362db7e..822cb98d 100644 --- a/clients/eventdemo.c +++ b/clients/eventdemo.c @@ -351,8 +351,6 @@ axis_discrete_handler(struct widget *widget, struct input *input, * \param widget widget * \param input input device that caused the motion event * \param time time the event happened - * \param x absolute x position - * \param y absolute y position * \param x x position relative to the window * \param y y position relative to the window * \param data user data associated to the window diff --git a/clients/gears.c b/clients/gears.c deleted file mode 100644 index ef19a480..00000000 --- a/clients/gears.c +++ /dev/null @@ -1,504 +0,0 @@ -/* - * 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. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include "window.h" - -struct gears { - struct window *window; - struct widget *widget; - - struct display *d; - - EGLDisplay display; - EGLDisplay config; - EGLContext context; - GLfloat angle; - - struct { - GLfloat rotx; - GLfloat roty; - } view; - - int button_down; - int last_x, last_y; - - GLint gear_list[3]; - int fullscreen; - int frames; - uint32_t last_fps; -}; - -struct gear_template { - GLfloat material[4]; - GLfloat inner_radius; - GLfloat outer_radius; - GLfloat width; - GLint teeth; - GLfloat tooth_depth; -}; - -static const struct gear_template gear_templates[] = { - { { 0.8, 0.1, 0.0, 1.0 }, 1.0, 4.0, 1.0, 20, 0.7 }, - { { 0.0, 0.8, 0.2, 1.0 }, 0.5, 2.0, 2.0, 10, 0.7 }, - { { 0.2, 0.2, 1.0, 1.0 }, 1.3, 2.0, 0.5, 10, 0.7 }, -}; - -static GLfloat light_pos[4] = {5.0, 5.0, 10.0, 0.0}; - -static void die(const char *msg) -{ - fprintf(stderr, "%s", msg); - exit(EXIT_FAILURE); -} - -static void -make_gear(const struct gear_template *t) -{ - GLint i; - GLfloat r0, r1, r2; - GLfloat angle, da; - GLfloat u, v, len; - - glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, t->material); - - r0 = t->inner_radius; - r1 = t->outer_radius - t->tooth_depth / 2.0; - r2 = t->outer_radius + t->tooth_depth / 2.0; - - da = 2.0 * M_PI / t->teeth / 4.0; - - glShadeModel(GL_FLAT); - - glNormal3f(0.0, 0.0, 1.0); - - /* draw front face */ - glBegin(GL_QUAD_STRIP); - for (i = 0; i <= t->teeth; i++) { - angle = i * 2.0 * M_PI / t->teeth; - glVertex3f(r0 * cos(angle), r0 * sin(angle), t->width * 0.5); - glVertex3f(r1 * cos(angle), r1 * sin(angle), t->width * 0.5); - if (i < t->teeth) { - glVertex3f(r0 * cos(angle), r0 * sin(angle), t->width * 0.5); - glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), t->width * 0.5); - } - } - glEnd(); - - /* draw front sides of teeth */ - glBegin(GL_QUADS); - da = 2.0 * M_PI / t->teeth / 4.0; - for (i = 0; i < t->teeth; i++) { - angle = i * 2.0 * M_PI / t->teeth; - - glVertex3f(r1 * cos(angle), r1 * sin(angle), t->width * 0.5); - glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), t->width * 0.5); - glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), t->width * 0.5); - glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), t->width * 0.5); - } - glEnd(); - - glNormal3f(0.0, 0.0, -1.0); - - /* draw back face */ - glBegin(GL_QUAD_STRIP); - for (i = 0; i <= t->teeth; i++) { - angle = i * 2.0 * M_PI / t->teeth; - glVertex3f(r1 * cos(angle), r1 * sin(angle), -t->width * 0.5); - glVertex3f(r0 * cos(angle), r0 * sin(angle), -t->width * 0.5); - if (i < t->teeth) { - glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -t->width * 0.5); - glVertex3f(r0 * cos(angle), r0 * sin(angle), -t->width * 0.5); - } - } - glEnd(); - - /* draw back sides of teeth */ - glBegin(GL_QUADS); - da = 2.0 * M_PI / t->teeth / 4.0; - for (i = 0; i < t->teeth; i++) { - angle = i * 2.0 * M_PI / t->teeth; - - glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -t->width * 0.5); - glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), -t->width * 0.5); - glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -t->width * 0.5); - glVertex3f(r1 * cos(angle), r1 * sin(angle), -t->width * 0.5); - } - glEnd(); - - /* draw outward faces of teeth */ - glBegin(GL_QUAD_STRIP); - for (i = 0; i < t->teeth; i++) { - angle = i * 2.0 * M_PI / t->teeth; - - glVertex3f(r1 * cos(angle), r1 * sin(angle), t->width * 0.5); - glVertex3f(r1 * cos(angle), r1 * sin(angle), -t->width * 0.5); - u = r2 * cos(angle + da) - r1 * cos(angle); - v = r2 * sin(angle + da) - r1 * sin(angle); - len = sqrt(u * u + v * v); - u /= len; - v /= len; - glNormal3f(v, -u, 0.0); - glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), t->width * 0.5); - glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -t->width * 0.5); - glNormal3f(cos(angle), sin(angle), 0.0); - glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), t->width * 0.5); - glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), -t->width * 0.5); - u = r1 * cos(angle + 3 * da) - r2 * cos(angle + 2 * da); - v = r1 * sin(angle + 3 * da) - r2 * sin(angle + 2 * da); - glNormal3f(v, -u, 0.0); - glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), t->width * 0.5); - glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -t->width * 0.5); - glNormal3f(cos(angle), sin(angle), 0.0); - } - - glVertex3f(r1 * cos(0), r1 * sin(0), t->width * 0.5); - glVertex3f(r1 * cos(0), r1 * sin(0), -t->width * 0.5); - - glEnd(); - - glShadeModel(GL_SMOOTH); - - /* draw inside radius cylinder */ - glBegin(GL_QUAD_STRIP); - for (i = 0; i <= t->teeth; i++) { - angle = i * 2.0 * M_PI / t->teeth; - glNormal3f(-cos(angle), -sin(angle), 0.0); - glVertex3f(r0 * cos(angle), r0 * sin(angle), -t->width * 0.5); - glVertex3f(r0 * cos(angle), r0 * sin(angle), t->width * 0.5); - } - glEnd(); -} - -static void -update_fps(struct gears *gears, uint32_t time) -{ - long diff_ms; - static bool first_call = true; - - if (first_call) { - gears->last_fps = time; - first_call = false; - } else - gears->frames++; - - diff_ms = time - gears->last_fps; - - if (diff_ms > 5000) { - float seconds = diff_ms / 1000.0; - float fps = gears->frames / seconds; - - printf("%d frames in %6.3f seconds = %6.3f FPS\n", gears->frames, seconds, fps); - fflush(stdout); - - gears->frames = 0; - gears->last_fps = time; - } -} - -static void -frame_callback(void *data, struct wl_callback *callback, uint32_t time) -{ - struct gears *gears = data; - - update_fps(gears, time); - - gears->angle = (GLfloat) (time % 8192) * 360 / 8192.0; - - window_schedule_redraw(gears->window); - - if (callback) - wl_callback_destroy(callback); -} - -static const struct wl_callback_listener listener = { - frame_callback -}; - -static int -motion_handler(struct widget *widget, struct input *input, - uint32_t time, float x, float y, void *data) -{ - struct gears *gears = data; - int offset_x, offset_y; - float step = 0.5; - - if (gears->button_down) { - offset_x = x - gears->last_x; - offset_y = y - gears->last_y; - gears->last_x = x; - gears->last_y = y; - gears->view.roty += offset_x * step; - gears->view.rotx += offset_y * step; - if (gears->view.roty >= 360) - gears->view.roty = gears->view.roty - 360; - if (gears->view.roty <= 0) - gears->view.roty = gears->view.roty + 360; - if (gears->view.rotx >= 360) - gears->view.rotx = gears->view.rotx - 360; - if (gears->view.rotx <= 0) - gears->view.rotx = gears->view.rotx + 360; - } - - return CURSOR_LEFT_PTR; -} - -static void -button_handler(struct widget *widget, struct input *input, - uint32_t time, uint32_t button, - enum wl_pointer_button_state state, void *data) -{ - struct gears *gears = data; - - if (button == BTN_LEFT) { - if (state == WL_POINTER_BUTTON_STATE_PRESSED) { - gears->button_down = 1; - input_get_position(input, - &gears->last_x, &gears->last_y); - } else { - gears->button_down = 0; - } - } -} - -static void -redraw_handler(struct widget *widget, void *data) -{ - struct rectangle window_allocation; - struct rectangle allocation; - struct wl_callback *callback; - struct gears *gears = data; - - widget_get_allocation(gears->widget, &allocation); - window_get_allocation(gears->window, &window_allocation); - - if (display_acquire_window_surface(gears->d, - gears->window, - gears->context) < 0) { - die("Unable to acquire window surface, " - "compiled without cairo-egl?\n"); - } - - glViewport(allocation.x, - window_allocation.height - allocation.height - allocation.y, - allocation.width, allocation.height); - glScissor(allocation.x, - window_allocation.height - allocation.height - allocation.y, - allocation.width, allocation.height); - - glEnable(GL_SCISSOR_TEST); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - glPushMatrix(); - - glTranslatef(0.0, 0.0, -50); - - glRotatef(gears->view.rotx, 1.0, 0.0, 0.0); - glRotatef(gears->view.roty, 0.0, 1.0, 0.0); - - glPushMatrix(); - glTranslatef(-3.0, -2.0, 0.0); - glRotatef(gears->angle, 0.0, 0.0, 1.0); - glCallList(gears->gear_list[0]); - glPopMatrix(); - - glPushMatrix(); - glTranslatef(3.1, -2.0, 0.0); - glRotatef(-2.0 * gears->angle - 9.0, 0.0, 0.0, 1.0); - glCallList(gears->gear_list[1]); - glPopMatrix(); - - glPushMatrix(); - glTranslatef(-3.1, 4.2, 0.0); - glRotatef(-2.0 * gears->angle - 25.0, 0.0, 0.0, 1.0); - glCallList(gears->gear_list[2]); - glPopMatrix(); - - glPopMatrix(); - - glFlush(); - - display_release_window_surface(gears->d, gears->window); - - callback = wl_surface_frame(window_get_wl_surface(gears->window)); - wl_callback_add_listener(callback, &listener, gears); -} - -static void -resize_handler(struct widget *widget, - int32_t width, int32_t height, void *data) -{ - struct gears *gears = data; - int32_t size, big, small; - - /* Constrain child size to be square and at least 300x300 */ - if (width < height) { - small = width; - big = height; - } else { - small = height; - big = width; - } - - if (gears->fullscreen) - size = small; - else - size = big; - - widget_set_size(gears->widget, size, size); -} - -static void -keyboard_focus_handler(struct window *window, - struct input *device, void *data) -{ - window_schedule_redraw(window); -} - -static void -fullscreen_handler(struct window *window, void *data) -{ - struct gears *gears = data; - - gears->fullscreen ^= 1; - window_set_fullscreen(window, gears->fullscreen); -} - -static struct gears * -gears_create(struct display *display) -{ - const int width = 450, height = 500; - struct gears *gears; - int i; - - gears = zalloc(sizeof *gears); - gears->d = display; - gears->window = window_create(display); - gears->widget = window_frame_create(gears->window, gears); - window_set_title(gears->window, "Wayland Gears"); - window_set_appid(gears->window, "org.freedesktop.weston.wayland-gears"); - - gears->display = display_get_egl_display(gears->d); - if (gears->display == NULL) - die("failed to create egl display\n"); - - eglBindAPI(EGL_OPENGL_API); - - gears->config = display_get_argb_egl_config(gears->d); - - gears->context = eglCreateContext(gears->display, gears->config, - EGL_NO_CONTEXT, NULL); - if (gears->context == NULL) - die("failed to create context\n"); - - if (!eglMakeCurrent(gears->display, NULL, NULL, gears->context)) - die("failed to make context current\n"); - - for (i = 0; i < 3; i++) { - gears->gear_list[i] = glGenLists(1); - glNewList(gears->gear_list[i], GL_COMPILE); - make_gear(&gear_templates[i]); - glEndList(); - } - - gears->button_down = 0; - gears->last_x = 0; - gears->last_y = 0; - - gears->view.rotx = 20.0; - gears->view.roty = 30.0; - - printf("Warning: FPS count is limited by the wayland compositor or monitor refresh rate\n"); - - glEnable(GL_NORMALIZE); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glFrustum(-1.0, 1.0, -1.0, 1.0, 5.0, 200.0); - glMatrixMode(GL_MODELVIEW); - - glLightfv(GL_LIGHT0, GL_POSITION, light_pos); - glEnable(GL_CULL_FACE); - glEnable(GL_LIGHTING); - glEnable(GL_LIGHT0); - glEnable(GL_DEPTH_TEST); - glClearColor(0, 0, 0, 0.92); - - window_set_user_data(gears->window, gears); - widget_set_resize_handler(gears->widget, resize_handler); - widget_set_redraw_handler(gears->widget, redraw_handler); - widget_set_button_handler(gears->widget, button_handler); - widget_set_motion_handler(gears->widget, motion_handler); - window_set_keyboard_focus_handler(gears->window, - keyboard_focus_handler); - window_set_fullscreen_handler(gears->window, fullscreen_handler); - - window_schedule_resize(gears->window, width, height); - - return gears; -} - -static void -gears_destroy(struct gears *gears) -{ - widget_destroy(gears->widget); - window_destroy(gears->window); - free(gears); -} - -int main(int argc, char *argv[]) -{ - struct display *d; - struct gears *gears; - - d = display_create(&argc, argv); - if (d == NULL) { - fprintf(stderr, "failed to create display: %s\n", - strerror(errno)); - return -1; - } - gears = gears_create(d); - display_run(d); - - gears_destroy(gears); - display_destroy(d); - - return 0; -} diff --git a/clients/meson.build b/clients/meson.build index 362f7fe3..8e6446a8 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -35,6 +35,10 @@ dep_toytoolkit = declare_dependency( link_with: lib_toytoolkit, dependencies: deps_toytoolkit, ) +dep_gbm = dependency('gbm', required: false) +if dep_gbm.found() and dep_gbm.version().version_compare('>= 21.3') + config_h.set('HAVE_GBM_BO_CREATE_WITH_MODIFIERS2', '1') +endif simple_clients_enabled = get_option('simple-clients') simple_build_all = simple_clients_enabled.contains('all') @@ -60,6 +64,8 @@ simple_clients = [ '../libweston/pixel-formats.c', linux_dmabuf_unstable_v1_client_protocol_h, linux_dmabuf_unstable_v1_protocol_c, + presentation_time_client_protocol_h, + presentation_time_protocol_c, xdg_shell_client_protocol_h, xdg_shell_protocol_c, ], @@ -125,7 +131,12 @@ simple_clients = [ ivi_application_client_protocol_h, ivi_application_protocol_c, ], - 'dep_objs': [ dep_wayland_client, dep_libshared, dep_libm ], + 'dep_objs': [ + dep_libm, + dep_libshared, + dep_matrix_c, + dep_wayland_client, + ], 'deps': [ 'egl', 'wayland-egl', 'glesv2', 'wayland-cursor' ], 'options': [ 'renderer-gl' ] }, @@ -217,21 +228,6 @@ tools_list = [ ], 'deps': [ dep_wayland_client ] }, - { - 'name': 'info', - 'sources': [ - 'weston-info.c', - presentation_time_client_protocol_h, - presentation_time_protocol_c, - linux_dmabuf_unstable_v1_client_protocol_h, - linux_dmabuf_unstable_v1_protocol_c, - tablet_unstable_v2_client_protocol_h, - tablet_unstable_v2_protocol_c, - xdg_output_unstable_v1_client_protocol_h, - xdg_output_unstable_v1_protocol_c, - ], - 'deps': [ dep_wayland_client, dep_libshared ] - }, { 'name': 'terminal', 'sources': [ 'terminal.c' ], diff --git a/clients/nested-client.c b/clients/nested-client.c deleted file mode 100644 index a9e034ef..00000000 --- a/clients/nested-client.c +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright © 2013 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. - */ - -#include "config.h" - -#include -#include -#include -#include - -#include -#include - -#include -#include - -#include "shared/platform.h" - -struct window; -struct seat; - -struct nested_client { - struct wl_display *display; - struct wl_registry *registry; - struct wl_compositor *compositor; - - EGLDisplay egl_display; - EGLContext egl_context; - EGLConfig egl_config; - EGLSurface egl_surface; - struct program *color_program; - - GLuint vert, frag, program; - GLuint rotation; - GLuint pos; - GLuint col; - - struct wl_surface *surface; - struct wl_egl_window *native; - int width, height; -}; - -#define POS 0 -#define COL 1 - -static GLuint -create_shader(const char *source, GLenum shader_type) -{ - GLuint shader; - GLint status; - - shader = glCreateShader(shader_type); - if (shader == 0) - return 0; - - glShaderSource(shader, 1, (const char **) &source, NULL); - glCompileShader(shader); - - glGetShaderiv(shader, GL_COMPILE_STATUS, &status); - if (!status) { - char log[1000]; - GLsizei len; - glGetShaderInfoLog(shader, 1000, &len, log); - fprintf(stderr, "Error: compiling %s: %.*s\n", - shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment", - len, log); - return 0; - } - - return shader; -} - -static void -create_program(struct nested_client *client, - const char *vert, const char *frag) -{ - GLint status; - - client->vert = create_shader(vert, GL_VERTEX_SHADER); - client->frag = create_shader(frag, GL_FRAGMENT_SHADER); - - client->program = glCreateProgram(); - glAttachShader(client->program, client->frag); - glAttachShader(client->program, client->vert); - glBindAttribLocation(client->program, POS, "pos"); - glBindAttribLocation(client->program, COL, "color"); - glLinkProgram(client->program); - - glGetProgramiv(client->program, GL_LINK_STATUS, &status); - if (!status) { - char log[1000]; - GLsizei len; - glGetProgramInfoLog(client->program, 1000, &len, log); - fprintf(stderr, "Error: linking:\n%.*s\n", len, log); - exit(1); - } - - client->rotation = - glGetUniformLocation(client->program, "rotation"); -} - -static const char vertex_shader_text[] = - "uniform mat4 rotation;\n" - "attribute vec4 pos;\n" - "attribute vec4 color;\n" - "varying vec4 v_color;\n" - "void main() {\n" - " gl_Position = rotation * pos;\n" - " v_color = color;\n" - "}\n"; - -static const char color_fragment_shader_text[] = - "precision mediump float;\n" - "varying vec4 v_color;\n" - "void main() {\n" - " gl_FragColor = v_color;\n" - "}\n"; - -static void -render_triangle(struct nested_client *client, uint32_t time) -{ - static const GLfloat verts[3][2] = { - { -0.5, -0.5 }, - { 0.5, -0.5 }, - { 0, 0.5 } - }; - static const GLfloat colors[3][3] = { - { 1, 0, 0 }, - { 0, 1, 0 }, - { 0, 0, 1 } - }; - GLfloat angle; - GLfloat rotation[4][4] = { - { 1, 0, 0, 0 }, - { 0, 1, 0, 0 }, - { 0, 0, 1, 0 }, - { 0, 0, 0, 1 } - }; - static const int32_t speed_div = 5; - static uint32_t start_time = 0; - - if (client->program == 0) - create_program(client, vertex_shader_text, - color_fragment_shader_text); - - if (start_time == 0) - start_time = time; - - angle = ((time - start_time) / speed_div) % 360 * M_PI / 180.0; - rotation[0][0] = cos(angle); - rotation[0][2] = sin(angle); - rotation[2][0] = -sin(angle); - rotation[2][2] = cos(angle); - - glClearColor(0.4, 0.4, 0.4, 1.0); - glClear(GL_COLOR_BUFFER_BIT); - - glUseProgram(client->program); - - glViewport(0, 0, client->width, client->height); - - glUniformMatrix4fv(client->rotation, 1, GL_FALSE, - (GLfloat *) rotation); - - glVertexAttribPointer(POS, 2, GL_FLOAT, GL_FALSE, 0, verts); - glVertexAttribPointer(COL, 3, GL_FLOAT, GL_FALSE, 0, colors); - glEnableVertexAttribArray(POS); - glEnableVertexAttribArray(COL); - - glDrawArrays(GL_TRIANGLES, 0, 3); - - glDisableVertexAttribArray(POS); - glDisableVertexAttribArray(COL); - - glFlush(); -} - -static void -frame_callback(void *data, struct wl_callback *callback, uint32_t time); - -static const struct wl_callback_listener frame_listener = { - frame_callback -}; - -static void -frame_callback(void *data, struct wl_callback *callback, uint32_t time) -{ - struct nested_client *client = data; - - if (callback) - wl_callback_destroy(callback); - - callback = wl_surface_frame(client->surface); - wl_callback_add_listener(callback, &frame_listener, client); - - render_triangle(client, time); - - eglSwapBuffers(client->egl_display, client->egl_surface); -} - -static void -registry_handle_global(void *data, struct wl_registry *registry, - uint32_t name, const char *interface, uint32_t version) -{ - struct nested_client *client = data; - - if (strcmp(interface, "wl_compositor") == 0) { - client->compositor = - wl_registry_bind(registry, name, - &wl_compositor_interface, 1); - } -} - -static void -registry_handle_global_remove(void *data, struct wl_registry *registry, - uint32_t name) -{ -} - -static const struct wl_registry_listener registry_listener = { - registry_handle_global, - registry_handle_global_remove -}; - -static struct nested_client * -nested_client_create(void) -{ - static const EGLint context_attribs[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; - - static const 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_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_NONE - }; - - EGLint major, minor, n; - EGLBoolean ret; - - struct nested_client *client; - - client = malloc(sizeof *client); - if (client == NULL) - return NULL; - - client->width = 250; - client->height = 250; - - client->display = wl_display_connect(NULL); - - client->registry = wl_display_get_registry(client->display); - wl_registry_add_listener(client->registry, - ®istry_listener, client); - - /* get globals */ - wl_display_roundtrip(client->display); - - client->egl_display = - weston_platform_get_egl_display(EGL_PLATFORM_WAYLAND_KHR, - client->display, NULL); - if (client->egl_display == NULL) - return NULL; - - ret = eglInitialize(client->egl_display, &major, &minor); - if (!ret) - return NULL; - ret = eglBindAPI(EGL_OPENGL_ES_API); - if (!ret) - return NULL; - - ret = eglChooseConfig(client->egl_display, config_attribs, - &client->egl_config, 1, &n); - if (!ret || n != 1) - return NULL; - - client->egl_context = eglCreateContext(client->egl_display, - client->egl_config, - EGL_NO_CONTEXT, - context_attribs); - if (!client->egl_context) - return NULL; - - client->surface = wl_compositor_create_surface(client->compositor); - - client->native = wl_egl_window_create(client->surface, - client->width, client->height); - - client->egl_surface = weston_platform_create_egl_surface(client->egl_display, - client->egl_config, - client->native, NULL); - - eglMakeCurrent(client->egl_display, client->egl_surface, - client->egl_surface, client->egl_context); - - wl_egl_window_resize(client->native, - client->width, client->height, 0, 0); - - frame_callback(client, NULL, 0); - - return client; -} - -static void -nested_client_destroy(struct nested_client *client) -{ - eglMakeCurrent(client->egl_display, - EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT); - - weston_platform_destroy_egl_surface(client->egl_display, - client->egl_surface); - wl_egl_window_destroy(client->native); - - wl_surface_destroy(client->surface); - - if (client->compositor) - wl_compositor_destroy(client->compositor); - - wl_registry_destroy(client->registry); - eglTerminate(client->egl_display); - eglReleaseThread(); - wl_display_flush(client->display); - wl_display_disconnect(client->display); -} - -int -main(int argc, char **argv) -{ - struct nested_client *client; - int ret = 0; - - if (getenv("WAYLAND_SOCKET") == NULL) { - fprintf(stderr, - "must be run by nested, don't run standalone\n"); - return EXIT_FAILURE; - } - - client = nested_client_create(); - if (client == NULL) - return EXIT_FAILURE; - - while (ret != -1) - ret = wl_display_dispatch(client->display); - - nested_client_destroy(client); - - return 0; -} diff --git a/clients/nested.c b/clients/nested.c deleted file mode 100644 index c21ab975..00000000 --- a/clients/nested.c +++ /dev/null @@ -1,1139 +0,0 @@ -/* - * Copyright © 2013 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. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include -#define WL_HIDE_DEPRECATED -#include - -#include "shared/helpers.h" -#include "shared/xalloc.h" -#include "window.h" - -#include "shared/weston-egl-ext.h" - - -static bool option_blit; - -struct nested { - struct display *display; - struct window *window; - struct widget *widget; - struct wl_display *child_display; - struct task child_task; - - EGLDisplay egl_display; - struct program *texture_program; - - struct wl_list surface_list; - - const struct nested_renderer *renderer; -}; - -struct nested_region { - struct wl_resource *resource; - pixman_region32_t region; -}; - -struct nested_buffer_reference { - struct nested_buffer *buffer; - struct wl_listener destroy_listener; -}; - -struct nested_buffer { - struct wl_resource *resource; - struct wl_signal destroy_signal; - struct wl_listener destroy_listener; - uint32_t busy_count; - - /* A buffer in the parent compositor representing the same - * data. This is created on-demand when the subsurface - * renderer is used */ - struct wl_buffer *parent_buffer; - /* This reference is used to mark when the parent buffer has - * been attached to the subsurface. It will be unrefenced when - * we receive a buffer release event. That way we won't inform - * the client that the buffer is free until the parent - * compositor is also finished with it */ - struct nested_buffer_reference parent_ref; -}; - -struct nested_surface { - struct wl_resource *resource; - struct nested *nested; - EGLImageKHR *image; - struct wl_list link; - - struct wl_list frame_callback_list; - - struct { - /* wl_surface.attach */ - int newly_attached; - struct nested_buffer *buffer; - struct wl_listener buffer_destroy_listener; - - /* wl_surface.frame */ - struct wl_list frame_callback_list; - - /* wl_surface.damage */ - pixman_region32_t damage; - } pending; - - void *renderer_data; -}; - -/* Data used for the blit renderer */ -struct nested_blit_surface { - struct nested_buffer_reference buffer_ref; - GLuint texture; - cairo_surface_t *cairo_surface; -}; - -/* Data used for the subsurface renderer */ -struct nested_ss_surface { - struct widget *widget; - struct wl_surface *surface; - struct wl_subsurface *subsurface; - struct wl_callback *frame_callback; -}; - -struct nested_frame_callback { - struct wl_resource *resource; - struct wl_list link; -}; - -struct nested_renderer { - void (* surface_init)(struct nested_surface *surface); - void (* surface_fini)(struct nested_surface *surface); - void (* render_clients)(struct nested *nested, cairo_t *cr); - void (* surface_attach)(struct nested_surface *surface, - struct nested_buffer *buffer); -}; - -static const struct weston_option nested_options[] = { - { WESTON_OPTION_BOOLEAN, "blit", 'b', &option_blit }, -}; - -static const struct nested_renderer nested_blit_renderer; -static const struct nested_renderer nested_ss_renderer; - -static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d; -static PFNEGLCREATEIMAGEKHRPROC create_image; -static PFNEGLDESTROYIMAGEKHRPROC destroy_image; -static PFNEGLBINDWAYLANDDISPLAYWL bind_display; -static PFNEGLUNBINDWAYLANDDISPLAYWL unbind_display; -static PFNEGLQUERYWAYLANDBUFFERWL query_buffer; -static PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWL create_wayland_buffer_from_image; - -static void -nested_buffer_destroy_handler(struct wl_listener *listener, void *data) -{ - struct nested_buffer *buffer = - container_of(listener, struct nested_buffer, destroy_listener); - - wl_signal_emit(&buffer->destroy_signal, buffer); - - if (buffer->parent_buffer) - wl_buffer_destroy(buffer->parent_buffer); - - free(buffer); -} - -static struct nested_buffer * -nested_buffer_from_resource(struct wl_resource *resource) -{ - struct nested_buffer *buffer; - struct wl_listener *listener; - - listener = - wl_resource_get_destroy_listener(resource, - nested_buffer_destroy_handler); - - if (listener) - return container_of(listener, struct nested_buffer, - destroy_listener); - - buffer = zalloc(sizeof *buffer); - if (buffer == NULL) - return NULL; - - buffer->resource = resource; - wl_signal_init(&buffer->destroy_signal); - buffer->destroy_listener.notify = nested_buffer_destroy_handler; - wl_resource_add_destroy_listener(resource, &buffer->destroy_listener); - - return buffer; -} - -static void -nested_buffer_reference_handle_destroy(struct wl_listener *listener, - void *data) -{ - struct nested_buffer_reference *ref = - container_of(listener, struct nested_buffer_reference, - destroy_listener); - - assert((struct nested_buffer *)data == ref->buffer); - ref->buffer = NULL; -} - -static void -nested_buffer_reference(struct nested_buffer_reference *ref, - struct nested_buffer *buffer) -{ - if (buffer == ref->buffer) - return; - - if (ref->buffer) { - ref->buffer->busy_count--; - if (ref->buffer->busy_count == 0) { - assert(wl_resource_get_client(ref->buffer->resource)); - wl_buffer_send_release(ref->buffer->resource); - } - wl_list_remove(&ref->destroy_listener.link); - } - - if (buffer) { - buffer->busy_count++; - wl_signal_add(&buffer->destroy_signal, - &ref->destroy_listener); - - ref->destroy_listener.notify = - nested_buffer_reference_handle_destroy; - } - - ref->buffer = buffer; -} - -static void -flush_surface_frame_callback_list(struct nested_surface *surface, - uint32_t time) -{ - struct nested_frame_callback *nc, *next; - - wl_list_for_each_safe(nc, next, &surface->frame_callback_list, link) { - wl_callback_send_done(nc->resource, time); - wl_resource_destroy(nc->resource); - } - wl_list_init(&surface->frame_callback_list); - - /* FIXME: toytoolkit need a pre-block handler where we can - * call this. */ - wl_display_flush_clients(surface->nested->child_display); -} - -static void -redraw_handler(struct widget *widget, void *data) -{ - struct nested *nested = data; - cairo_surface_t *surface; - cairo_t *cr; - struct rectangle allocation; - - widget_get_allocation(nested->widget, &allocation); - - surface = window_get_surface(nested->window); - - cr = cairo_create(surface); - cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); - cairo_rectangle(cr, - allocation.x, - allocation.y, - allocation.width, - allocation.height); - cairo_set_source_rgba(cr, 0, 0, 0, 0.8); - cairo_fill(cr); - - nested->renderer->render_clients(nested, cr); - - cairo_destroy(cr); - - cairo_surface_destroy(surface); -} - -static void -keyboard_focus_handler(struct window *window, - struct input *device, void *data) -{ - struct nested *nested = data; - - window_schedule_redraw(nested->window); -} - -static void -handle_child_data(struct task *task, uint32_t events) -{ - struct nested *nested = container_of(task, struct nested, child_task); - struct wl_event_loop *loop; - - loop = wl_display_get_event_loop(nested->child_display); - - wl_event_loop_dispatch(loop, -1); - wl_display_flush_clients(nested->child_display); -} - -struct nested_client { - struct wl_client *client; - pid_t pid; -}; - -static struct nested_client * -launch_client(struct nested *nested, const char *path) -{ - int sv[2]; - pid_t pid; - struct nested_client *client; - - client = malloc(sizeof *client); - if (client == NULL) - return NULL; - - if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) < 0) { - fprintf(stderr, "launch_client: " - "socketpair failed while launching '%s': %s\n", - path, strerror(errno)); - free(client); - return NULL; - } - - pid = fork(); - if (pid == -1) { - close(sv[0]); - close(sv[1]); - free(client); - fprintf(stderr, "launch_client: " - "fork failed while launching '%s': %s\n", path, - strerror(errno)); - return NULL; - } - - if (pid == 0) { - int clientfd; - char s[32]; - - /* SOCK_CLOEXEC closes both ends, so we dup the fd to - * get a non-CLOEXEC fd to pass through exec. */ - clientfd = dup(sv[1]); - if (clientfd == -1) { - fprintf(stderr, "compositor: dup failed: %s\n", - strerror(errno)); - exit(-1); - } - - snprintf(s, sizeof s, "%d", clientfd); - setenv("WAYLAND_SOCKET", s, 1); - - execl(path, path, NULL); - - fprintf(stderr, "compositor: executing '%s' failed: %s\n", - path, strerror(errno)); - exit(-1); - } - - close(sv[1]); - - client->client = wl_client_create(nested->child_display, sv[0]); - if (!client->client) { - close(sv[0]); - free(client); - fprintf(stderr, "launch_client: " - "wl_client_create failed while launching '%s'.\n", - path); - return NULL; - } - - client->pid = pid; - - return client; -} - -static void -destroy_surface(struct wl_resource *resource) -{ - struct nested_surface *surface = wl_resource_get_user_data(resource); - struct nested *nested = surface->nested; - struct nested_frame_callback *cb, *next; - - wl_list_for_each_safe(cb, next, - &surface->frame_callback_list, link) - wl_resource_destroy(cb->resource); - - wl_list_for_each_safe(cb, next, - &surface->pending.frame_callback_list, link) - wl_resource_destroy(cb->resource); - - pixman_region32_fini(&surface->pending.damage); - - nested->renderer->surface_fini(surface); - - wl_list_remove(&surface->link); - - free(surface); -} - -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_resource, int32_t sx, int32_t sy) -{ - struct nested_surface *surface = wl_resource_get_user_data(resource); - struct nested *nested = surface->nested; - struct nested_buffer *buffer = NULL; - - if (buffer_resource) { - int format; - - if (!query_buffer(nested->egl_display, (void *) buffer_resource, - EGL_TEXTURE_FORMAT, &format)) { - wl_resource_post_error(buffer_resource, - WL_DISPLAY_ERROR_INVALID_OBJECT, - "attaching non-egl wl_buffer"); - return; - } - - switch (format) { - case EGL_TEXTURE_RGB: - case EGL_TEXTURE_RGBA: - break; - default: - wl_resource_post_error(buffer_resource, - WL_DISPLAY_ERROR_INVALID_OBJECT, - "invalid format"); - return; - } - - buffer = nested_buffer_from_resource(buffer_resource); - if (buffer == NULL) { - wl_client_post_no_memory(client); - return; - } - } - - if (surface->pending.buffer) - wl_list_remove(&surface->pending.buffer_destroy_listener.link); - - surface->pending.buffer = buffer; - surface->pending.newly_attached = 1; - if (buffer) { - wl_signal_add(&buffer->destroy_signal, - &surface->pending.buffer_destroy_listener); - } -} - -static void -nested_surface_attach(struct nested_surface *surface, - struct nested_buffer *buffer) -{ - struct nested *nested = surface->nested; - - if (surface->image != EGL_NO_IMAGE_KHR) - destroy_image(nested->egl_display, surface->image); - - surface->image = create_image(nested->egl_display, NULL, - EGL_WAYLAND_BUFFER_WL, buffer->resource, - NULL); - if (surface->image == EGL_NO_IMAGE_KHR) { - fprintf(stderr, "failed to create img\n"); - return; - } - - nested->renderer->surface_attach(surface, 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 nested_surface *surface = wl_resource_get_user_data(resource); - - pixman_region32_union_rect(&surface->pending.damage, - &surface->pending.damage, - x, y, width, height); -} - -static void -destroy_frame_callback(struct wl_resource *resource) -{ - struct nested_frame_callback *callback = wl_resource_get_user_data(resource); - - wl_list_remove(&callback->link); - free(callback); -} - -static void -surface_frame(struct wl_client *client, - struct wl_resource *resource, uint32_t id) -{ - struct nested_frame_callback *callback; - struct nested_surface *surface = wl_resource_get_user_data(resource); - - callback = malloc(sizeof *callback); - if (callback == NULL) { - wl_resource_post_no_memory(resource); - return; - } - - callback->resource = wl_resource_create(client, - &wl_callback_interface, 1, id); - wl_resource_set_implementation(callback->resource, NULL, callback, - destroy_frame_callback); - - wl_list_insert(surface->pending.frame_callback_list.prev, - &callback->link); -} - -static void -surface_set_opaque_region(struct wl_client *client, - struct wl_resource *resource, - struct wl_resource *region_resource) -{ - fprintf(stderr, "surface_set_opaque_region\n"); -} - -static void -surface_set_input_region(struct wl_client *client, - struct wl_resource *resource, - struct wl_resource *region_resource) -{ - fprintf(stderr, "surface_set_input_region\n"); -} - -static void -surface_commit(struct wl_client *client, struct wl_resource *resource) -{ - struct nested_surface *surface = wl_resource_get_user_data(resource); - struct nested *nested = surface->nested; - - /* wl_surface.attach */ - if (surface->pending.newly_attached) - nested_surface_attach(surface, surface->pending.buffer); - - if (surface->pending.buffer) { - wl_list_remove(&surface->pending.buffer_destroy_listener.link); - surface->pending.buffer = NULL; - } - surface->pending.newly_attached = 0; - - /* wl_surface.damage */ - pixman_region32_clear(&surface->pending.damage); - - /* wl_surface.frame */ - wl_list_insert_list(&surface->frame_callback_list, - &surface->pending.frame_callback_list); - wl_list_init(&surface->pending.frame_callback_list); - - /* FIXME: For the subsurface renderer we don't need to - * actually redraw the window. However we do want to cause a - * commit because the subsurface is synchronized. Ideally we - * would just queue the commit */ - window_schedule_redraw(nested->window); -} - -static void -surface_set_buffer_transform(struct wl_client *client, - struct wl_resource *resource, int transform) -{ - fprintf(stderr, "surface_set_buffer_transform\n"); -} - -static const struct wl_surface_interface surface_interface = { - surface_destroy, - surface_attach, - surface_damage, - surface_frame, - surface_set_opaque_region, - surface_set_input_region, - surface_commit, - surface_set_buffer_transform -}; - -static void -surface_handle_pending_buffer_destroy(struct wl_listener *listener, void *data) -{ - struct nested_surface *surface = - container_of(listener, struct nested_surface, - pending.buffer_destroy_listener); - - surface->pending.buffer = NULL; -} - -static void -compositor_create_surface(struct wl_client *client, - struct wl_resource *resource, uint32_t id) -{ - struct nested *nested = wl_resource_get_user_data(resource); - struct nested_surface *surface; - - surface = zalloc(sizeof *surface); - if (surface == NULL) { - wl_resource_post_no_memory(resource); - return; - } - - surface->nested = nested; - - wl_list_init(&surface->frame_callback_list); - - wl_list_init(&surface->pending.frame_callback_list); - surface->pending.buffer_destroy_listener.notify = - surface_handle_pending_buffer_destroy; - pixman_region32_init(&surface->pending.damage); - - display_acquire_window_surface(nested->display, - nested->window, NULL); - - nested->renderer->surface_init(surface); - - display_release_window_surface(nested->display, nested->window); - - surface->resource = - wl_resource_create(client, &wl_surface_interface, 1, id); - - wl_resource_set_implementation(surface->resource, - &surface_interface, surface, - destroy_surface); - - wl_list_insert(nested->surface_list.prev, &surface->link); -} - -static void -destroy_region(struct wl_resource *resource) -{ - struct nested_region *region = wl_resource_get_user_data(resource); - - pixman_region32_fini(®ion->region); - free(region); -} - -static void -region_destroy(struct wl_client *client, struct wl_resource *resource) -{ - wl_resource_destroy(resource); -} - -static void -region_add(struct wl_client *client, struct wl_resource *resource, - int32_t x, int32_t y, int32_t width, int32_t height) -{ - struct nested_region *region = wl_resource_get_user_data(resource); - - pixman_region32_union_rect(®ion->region, ®ion->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) -{ - struct nested_region *region = wl_resource_get_user_data(resource); - pixman_region32_t rect; - - pixman_region32_init_rect(&rect, x, y, width, height); - pixman_region32_subtract(®ion->region, ®ion->region, &rect); - pixman_region32_fini(&rect); -} - -static const struct wl_region_interface region_interface = { - region_destroy, - region_add, - region_subtract -}; - -static void -compositor_create_region(struct wl_client *client, - struct wl_resource *resource, uint32_t id) -{ - struct nested_region *region; - - region = malloc(sizeof *region); - if (region == NULL) { - wl_resource_post_no_memory(resource); - return; - } - - pixman_region32_init(®ion->region); - - region->resource = - wl_resource_create(client, &wl_region_interface, 1, id); - wl_resource_set_implementation(region->resource, ®ion_interface, - region, destroy_region); -} - -static const struct wl_compositor_interface compositor_interface = { - compositor_create_surface, - compositor_create_region -}; - -static void -compositor_bind(struct wl_client *client, - void *data, uint32_t version, uint32_t id) -{ - struct nested *nested = data; - struct wl_resource *resource; - - resource = wl_resource_create(client, &wl_compositor_interface, - MIN(version, 3), id); - wl_resource_set_implementation(resource, &compositor_interface, - nested, NULL); -} - -static int -nested_init_compositor(struct nested *nested) -{ - const char *extensions; - struct wl_event_loop *loop; - int use_ss_renderer = 0; - int fd, ret; - - wl_list_init(&nested->surface_list); - nested->child_display = wl_display_create(); - loop = wl_display_get_event_loop(nested->child_display); - fd = wl_event_loop_get_fd(loop); - nested->child_task.run = handle_child_data; - display_watch_fd(nested->display, fd, - EPOLLIN, &nested->child_task); - - if (!wl_global_create(nested->child_display, - &wl_compositor_interface, 1, - nested, compositor_bind)) - return -1; - - wl_display_init_shm(nested->child_display); - - nested->egl_display = display_get_egl_display(nested->display); - extensions = eglQueryString(nested->egl_display, EGL_EXTENSIONS); - if (!weston_check_egl_extension(extensions, "EGL_WL_bind_wayland_display")) { - fprintf(stderr, "no EGL_WL_bind_wayland_display extension\n"); - return -1; - } - - bind_display = (void *) eglGetProcAddress("eglBindWaylandDisplayWL"); - unbind_display = (void *) eglGetProcAddress("eglUnbindWaylandDisplayWL"); - create_image = (void *) eglGetProcAddress("eglCreateImageKHR"); - destroy_image = (void *) eglGetProcAddress("eglDestroyImageKHR"); - query_buffer = (void *) eglGetProcAddress("eglQueryWaylandBufferWL"); - image_target_texture_2d = - (void *) eglGetProcAddress("glEGLImageTargetTexture2DOES"); - - ret = bind_display(nested->egl_display, nested->child_display); - if (!ret) { - fprintf(stderr, "failed to bind wl_display\n"); - return -1; - } - - if (display_has_subcompositor(nested->display)) { - const char *func = "eglCreateWaylandBufferFromImageWL"; - const char *ext = "EGL_WL_create_wayland_buffer_from_image"; - - if (weston_check_egl_extension(extensions, ext)) { - create_wayland_buffer_from_image = - (void *) eglGetProcAddress(func); - use_ss_renderer = 1; - } - } - - if (option_blit) - use_ss_renderer = 0; - - if (use_ss_renderer) { - printf("Using subsurfaces to render client surfaces\n"); - nested->renderer = &nested_ss_renderer; - } else { - printf("Using local compositing with blits to " - "render client surfaces\n"); - nested->renderer = &nested_blit_renderer; - } - - return 0; -} - -static struct nested * -nested_create(struct display *display) -{ - struct nested *nested; - - nested = zalloc(sizeof *nested); - if (nested == NULL) - return nested; - - nested->window = window_create(display); - nested->widget = window_frame_create(nested->window, nested); - window_set_title(nested->window, "Wayland Nested"); - window_set_appid(nested->window, - "org.freedesktop.weston.wayland-nested"); - nested->display = display; - - window_set_user_data(nested->window, nested); - widget_set_redraw_handler(nested->widget, redraw_handler); - window_set_keyboard_focus_handler(nested->window, - keyboard_focus_handler); - - nested_init_compositor(nested); - - widget_schedule_resize(nested->widget, 400, 400); - - return nested; -} - -static void -nested_destroy(struct nested *nested) -{ - widget_destroy(nested->widget); - window_destroy(nested->window); - free(nested); -} - -/*** blit renderer ***/ - -static void -blit_surface_init(struct nested_surface *surface) -{ - struct nested_blit_surface *blit_surface = - xzalloc(sizeof *blit_surface); - - glGenTextures(1, &blit_surface->texture); - glBindTexture(GL_TEXTURE_2D, blit_surface->texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - surface->renderer_data = blit_surface; -} - -static void -blit_surface_fini(struct nested_surface *surface) -{ - struct nested_blit_surface *blit_surface = surface->renderer_data; - - nested_buffer_reference(&blit_surface->buffer_ref, NULL); - - glDeleteTextures(1, &blit_surface->texture); - - free(blit_surface); -} - -static void -blit_frame_callback(void *data, struct wl_callback *callback, uint32_t time) -{ - struct nested *nested = data; - struct nested_surface *surface; - - wl_list_for_each(surface, &nested->surface_list, link) - flush_surface_frame_callback_list(surface, time); - - if (callback) - wl_callback_destroy(callback); -} - -static const struct wl_callback_listener blit_frame_listener = { - blit_frame_callback -}; - -static void -blit_render_clients(struct nested *nested, - cairo_t *cr) -{ - struct nested_surface *s; - struct rectangle allocation; - struct wl_callback *callback; - - widget_get_allocation(nested->widget, &allocation); - - wl_list_for_each(s, &nested->surface_list, link) { - struct nested_blit_surface *blit_surface = s->renderer_data; - - display_acquire_window_surface(nested->display, - nested->window, NULL); - - glBindTexture(GL_TEXTURE_2D, blit_surface->texture); - image_target_texture_2d(GL_TEXTURE_2D, s->image); - - display_release_window_surface(nested->display, - nested->window); - - cairo_set_operator(cr, CAIRO_OPERATOR_OVER); - cairo_set_source_surface(cr, blit_surface->cairo_surface, - allocation.x + 10, - allocation.y + 10); - cairo_rectangle(cr, allocation.x + 10, - allocation.y + 10, - allocation.width - 10, - allocation.height - 10); - - cairo_fill(cr); - } - - callback = wl_surface_frame(window_get_wl_surface(nested->window)); - wl_callback_add_listener(callback, &blit_frame_listener, nested); -} - -static void -blit_surface_attach(struct nested_surface *surface, - struct nested_buffer *buffer) -{ - struct nested *nested = surface->nested; - struct nested_blit_surface *blit_surface = surface->renderer_data; - EGLint width, height; - cairo_device_t *device; - - nested_buffer_reference(&blit_surface->buffer_ref, buffer); - - if (blit_surface->cairo_surface) - cairo_surface_destroy(blit_surface->cairo_surface); - - query_buffer(nested->egl_display, (void *) buffer->resource, - EGL_WIDTH, &width); - query_buffer(nested->egl_display, (void *) buffer->resource, - EGL_HEIGHT, &height); - - device = display_get_cairo_device(nested->display); - blit_surface->cairo_surface = - cairo_gl_surface_create_for_texture(device, - CAIRO_CONTENT_COLOR_ALPHA, - blit_surface->texture, - width, height); -} - -static const struct nested_renderer -nested_blit_renderer = { - .surface_init = blit_surface_init, - .surface_fini = blit_surface_fini, - .render_clients = blit_render_clients, - .surface_attach = blit_surface_attach -}; - -/*** subsurface renderer ***/ - -static void -ss_surface_init(struct nested_surface *surface) -{ - struct nested *nested = surface->nested; - struct wl_compositor *compositor = - display_get_compositor(nested->display); - struct nested_ss_surface *ss_surface = - xzalloc(sizeof *ss_surface); - struct rectangle allocation; - struct wl_region *region; - - ss_surface->widget = - window_add_subsurface(nested->window, - nested, - SUBSURFACE_SYNCHRONIZED); - - widget_set_use_cairo(ss_surface->widget, 0); - - ss_surface->surface = widget_get_wl_surface(ss_surface->widget); - ss_surface->subsurface = widget_get_wl_subsurface(ss_surface->widget); - - /* The toy toolkit gets confused about the pointer position - * when it gets motion events for a subsurface so we'll just - * disable input on it */ - region = wl_compositor_create_region(compositor); - wl_surface_set_input_region(ss_surface->surface, region); - wl_region_destroy(region); - - widget_get_allocation(nested->widget, &allocation); - wl_subsurface_set_position(ss_surface->subsurface, - allocation.x + 10, - allocation.y + 10); - - surface->renderer_data = ss_surface; -} - -static void -ss_surface_fini(struct nested_surface *surface) -{ - struct nested_ss_surface *ss_surface = surface->renderer_data; - - widget_destroy(ss_surface->widget); - - if (ss_surface->frame_callback) - wl_callback_destroy(ss_surface->frame_callback); - - free(ss_surface); -} - -static void -ss_render_clients(struct nested *nested, - cairo_t *cr) -{ - /* The clients are composited by the parent compositor so we - * don't need to do anything here */ -} - -static void -ss_buffer_release(void *data, struct wl_buffer *wl_buffer) -{ - struct nested_buffer *buffer = data; - - nested_buffer_reference(&buffer->parent_ref, NULL); -} - -static struct wl_buffer_listener ss_buffer_listener = { - ss_buffer_release -}; - -static void -ss_frame_callback(void *data, struct wl_callback *callback, uint32_t time) -{ - struct nested_surface *surface = data; - struct nested_ss_surface *ss_surface = surface->renderer_data; - - flush_surface_frame_callback_list(surface, time); - - if (callback) - wl_callback_destroy(callback); - - ss_surface->frame_callback = NULL; -} - -static const struct wl_callback_listener ss_frame_listener = { - ss_frame_callback -}; - -static void -ss_surface_attach(struct nested_surface *surface, - struct nested_buffer *buffer) -{ - struct nested *nested = surface->nested; - struct nested_ss_surface *ss_surface = surface->renderer_data; - struct wl_buffer *parent_buffer; - const pixman_box32_t *rects; - int n_rects, i; - - if (buffer) { - /* Create a representation of the buffer in the parent - * compositor if we haven't already */ - if (buffer->parent_buffer == NULL) { - EGLDisplay *edpy = nested->egl_display; - EGLImageKHR image = surface->image; - - buffer->parent_buffer = - create_wayland_buffer_from_image(edpy, image); - - wl_buffer_add_listener(buffer->parent_buffer, - &ss_buffer_listener, - buffer); - } - - parent_buffer = buffer->parent_buffer; - - /* We'll take a reference to the buffer while the parent - * compositor is using it so that we won't report the release - * event until the parent has also finished with it */ - nested_buffer_reference(&buffer->parent_ref, buffer); - } else { - parent_buffer = NULL; - } - - wl_surface_attach(ss_surface->surface, parent_buffer, 0, 0); - - rects = pixman_region32_rectangles(&surface->pending.damage, &n_rects); - - for (i = 0; i < n_rects; i++) { - const pixman_box32_t *rect = rects + i; - wl_surface_damage(ss_surface->surface, - rect->x1, - rect->y1, - rect->x2 - rect->x1, - rect->y2 - rect->y1); - } - - if (ss_surface->frame_callback) - wl_callback_destroy(ss_surface->frame_callback); - - ss_surface->frame_callback = wl_surface_frame(ss_surface->surface); - wl_callback_add_listener(ss_surface->frame_callback, - &ss_frame_listener, - surface); - - wl_surface_commit(ss_surface->surface); -} - -static const struct nested_renderer -nested_ss_renderer = { - .surface_init = ss_surface_init, - .surface_fini = ss_surface_fini, - .render_clients = ss_render_clients, - .surface_attach = ss_surface_attach -}; - -int -main(int argc, char *argv[]) -{ - struct display *display; - struct nested *nested; - - if (parse_options(nested_options, - ARRAY_LENGTH(nested_options), &argc, argv) > 1) { - printf("Usage: %s [OPTIONS]\n --blit or -b\n", argv[0]); - exit(1); - } - - display = display_create(&argc, argv); - if (display == NULL) { - fprintf(stderr, "failed to create display: %s\n", - strerror(errno)); - return -1; - } - - nested = nested_create(display); - - launch_client(nested, "weston-nested-client"); - - display_run(display); - - nested_destroy(nested); - display_destroy(display); - - return 0; -} diff --git a/clients/presentation-shm.c b/clients/presentation-shm.c index d5d73a2a..828cb08d 100644 --- a/clients/presentation-shm.c +++ b/clients/presentation-shm.c @@ -765,7 +765,7 @@ registry_handle_global(void *data, struct wl_registry *registry, } else if (strcmp(interface, "xdg_wm_base") == 0) { d->wm_base = wl_registry_bind(registry, name, - &xdg_wm_base_interface, version); + &xdg_wm_base_interface, 1); } else if (strcmp(interface, "wl_shm") == 0) { d->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); diff --git a/clients/simple-dmabuf-egl.c b/clients/simple-dmabuf-egl.c index ef0d9de6..16d47ba9 100644 --- a/clients/simple-dmabuf-egl.c +++ b/clients/simple-dmabuf-egl.c @@ -343,12 +343,22 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer, #ifdef HAVE_GBM_MODIFIERS if (display->modifiers_count > 0) { +#ifdef HAVE_GBM_BO_CREATE_WITH_MODIFIERS2 + buffer->bo = gbm_bo_create_with_modifiers2(display->gbm.device, + buffer->width, + buffer->height, + buffer->format, + display->modifiers, + display->modifiers_count, + GBM_BO_USE_RENDERING); +#else buffer->bo = gbm_bo_create_with_modifiers(display->gbm.device, buffer->width, buffer->height, buffer->format, display->modifiers, display->modifiers_count); +#endif if (buffer->bo) buffer->modifier = gbm_bo_get_modifier(buffer->bo); } diff --git a/clients/simple-dmabuf-feedback.c b/clients/simple-dmabuf-feedback.c index 516cdbc8..f0d6febf 100644 --- a/clients/simple-dmabuf-feedback.c +++ b/clients/simple-dmabuf-feedback.c @@ -41,6 +41,7 @@ #include #include "xdg-shell-client-protocol.h" #include "linux-dmabuf-unstable-v1-client-protocol.h" +#include "presentation-time-client-protocol.h" #include #include @@ -49,6 +50,11 @@ #include #include +#define L_LINE "│ " +#define L_VAL "├───" +#define L_LAST "└───" +#define L_GAP " " + #define NUM_BUFFERS 4 /* We have to hack the DRM-backend to pretend that planes of the underlying @@ -135,6 +141,7 @@ struct display { struct output output; struct xdg_wm_base *wm_base; struct zwp_linux_dmabuf_v1 *dmabuf; + struct wp_presentation *presentation; struct gbm_device *gbm_device; struct egl egl; }; @@ -166,12 +173,14 @@ struct window { struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; struct wl_callback *callback; + struct wp_presentation_feedback *presentation_feedback; bool wait_for_configure; - uint32_t n_redraws; + bool presented_zero_copy; struct zwp_linux_dmabuf_feedback_v1 *dmabuf_feedback_obj; struct dmabuf_feedback dmabuf_feedback, pending_dmabuf_feedback; int card_fd; struct drm_format format; + uint32_t bo_flags; struct buffer buffers[NUM_BUFFERS]; }; @@ -453,7 +462,7 @@ buffer_free(struct buffer *buf) static void create_dmabuf_buffer(struct window *window, struct buffer *buf, uint32_t width, uint32_t height, uint32_t format, unsigned int count_modifiers, - uint64_t *modifiers); + uint64_t *modifiers, uint32_t bo_flags); static void buffer_recreate(struct buffer *buf) @@ -466,7 +475,7 @@ buffer_recreate(struct buffer *buf) create_dmabuf_buffer(window, buf, width, height, window->format.format, window->format.modifiers.size / sizeof(uint64_t), - window->format.modifiers.data); + window->format.modifiers.data, window->bo_flags); buf->recreate = false; } @@ -516,7 +525,7 @@ static const struct zwp_linux_buffer_params_v1_listener params_listener = { static void create_dmabuf_buffer(struct window *window, struct buffer *buf, uint32_t width, uint32_t height, uint32_t format, unsigned int count_modifiers, - uint64_t *modifiers) + uint64_t *modifiers, uint32_t bo_flags) { struct display *display = window->display; static uint32_t flags = 0; @@ -531,10 +540,18 @@ create_dmabuf_buffer(struct window *window, struct buffer *buf, uint32_t width, #ifdef HAVE_GBM_MODIFIERS if (count_modifiers > 0) { +#ifdef HAVE_GBM_BO_CREATE_WITH_MODIFIERS2 + buf->bo = gbm_bo_create_with_modifiers2(display->gbm_device, + buf->width, buf->height, + format, modifiers, + count_modifiers, + bo_flags); +#else buf->bo = gbm_bo_create_with_modifiers(display->gbm_device, buf->width, buf->height, format, modifiers, count_modifiers); +#endif if (buf->bo) buf->modifier = gbm_bo_get_modifier(buf->bo); } @@ -543,7 +560,7 @@ create_dmabuf_buffer(struct window *window, struct buffer *buf, uint32_t width, if (!buf->bo) { buf->bo = gbm_bo_create(display->gbm_device, buf->width, buf->height, buf->format, - GBM_BO_USE_RENDERING); + bo_flags); buf->modifier = DRM_FORMAT_MOD_INVALID; } @@ -639,6 +656,7 @@ render(struct buffer *buffer) } static const struct wl_callback_listener frame_listener; +static const struct wp_presentation_feedback_listener presentation_feedback_listener; static void redraw(void *data, struct wl_callback *callback, uint32_t time) @@ -660,6 +678,18 @@ redraw(void *data, struct wl_callback *callback, uint32_t time) window->callback = wl_surface_frame(window->surface); wl_callback_add_listener(window->callback, &frame_listener, window); + + if (window->presentation_feedback) + wp_presentation_feedback_destroy(window->presentation_feedback); + if (window->display->presentation) { + window->presentation_feedback = + wp_presentation_feedback(window->display->presentation, + window->surface); + wp_presentation_feedback_add_listener(window->presentation_feedback, + &presentation_feedback_listener, + window); + } + wl_surface_commit(window->surface); buf->status = IN_USE; @@ -674,6 +704,48 @@ static const struct wl_callback_listener frame_listener = { redraw }; +static void presentation_feedback_handle_sync_output(void *data, + struct wp_presentation_feedback *feedback, + struct wl_output *output) +{ +} + +static void presentation_feedback_handle_presented(void *data, + struct wp_presentation_feedback *feedback, + uint32_t tv_sec_hi, + uint32_t tv_sec_lo, + uint32_t tv_nsec, + uint32_t refresh, + uint32_t seq_hi, + uint32_t seq_lo, + uint32_t flags) +{ + struct window *window = data; + bool zero_copy = flags & WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY; + + if (zero_copy && !window->presented_zero_copy) { + fprintf(stderr, "Presenting in zero-copy mode\n"); + } + if (!zero_copy && window->presented_zero_copy) { + fprintf(stderr, "Stopped presenting in zero-copy mode\n"); + } + + window->presented_zero_copy = zero_copy; + wp_presentation_feedback_destroy(feedback); +} + +static void presentation_feedback_handle_discarded(void *data, + struct wp_presentation_feedback *feedback) +{ + wp_presentation_feedback_destroy(feedback); +} + +static const struct wp_presentation_feedback_listener presentation_feedback_listener = { + .sync_output = presentation_feedback_handle_sync_output, + .presented = presentation_feedback_handle_presented, + .discarded = presentation_feedback_handle_discarded, +}; + static void xdg_surface_handle_configure(void *data, struct xdg_surface *surface, uint32_t serial) @@ -810,6 +882,8 @@ destroy_window(struct window *window) if (window->callback) wl_callback_destroy(window->callback); + if (window->presentation_feedback) + wp_presentation_feedback_destroy(window->presentation_feedback); for (i = 0; i < NUM_BUFFERS; i++) if (window->buffers[i].buffer) @@ -873,7 +947,7 @@ create_window(struct display *display) create_dmabuf_buffer(window, &window->buffers[i], width, height, window->format.format, window->format.modifiers.size / sizeof(uint64_t), - window->format.modifiers.data); + window->format.modifiers.data, window->bo_flags); window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base, @@ -1000,8 +1074,7 @@ dmabuf_feedback_main_device(void *data, drm_node = get_drm_node(feedback->main_device, false); assert(drm_node && "error: failed to retrieve drm node"); - fprintf(stderr, "compositor sent main_device event for dma-buf feedback - %s\n", - drm_node); + fprintf(stderr, "feedback: main device %s\n", drm_node); if (!window->card_fd) { window->card_fd = open(drm_node, O_RDWR | O_CLOEXEC); @@ -1110,12 +1183,12 @@ print_tranche_format_modifier(uint32_t format, uint64_t modifier) char fourcc_str[5]; fourcc2str(format, fourcc_str, sizeof(fourcc_str)); - len = asprintf(&format_str, "0x%08x (%s)", format, fourcc_str); + len = asprintf(&format_str, "%s (0x%08x)", fourcc_str, format); } assert(len > 0); - fprintf(stderr, "│ ├────────tranche format/modifier pair - format %s, modifier %s\n", - format_str, mod_name); + fprintf(stderr, L_LINE L_VAL " format %s, modifier %s\n", + format_str, mod_name); free(format_str); free(mod_name); @@ -1131,14 +1204,14 @@ print_dmabuf_feedback_tranche(struct dmabuf_feedback_tranche *tranche) drm_node = get_drm_node(tranche->target_device, tranche->is_scanout_tranche); assert(drm_node && "error: could not retrieve drm node"); - fprintf(stderr, "├──────target_device for tranche - %s\n", drm_node); - fprintf(stderr, "│ └scanout tranche? %s\n", tranche->is_scanout_tranche ? "yes" : "no"); + fprintf(stderr, L_VAL " tranche: target device %s, %s\n", + drm_node, tranche->is_scanout_tranche ? "scanout" : "no flags"); wl_array_for_each(fmt, &tranche->formats.arr) wl_array_for_each(mod, &fmt->modifiers) print_tranche_format_modifier(fmt->format, *mod); - fprintf(stderr, "│ └end of tranche\n"); + fprintf(stderr, L_LINE L_LAST " end of tranche\n"); } static void @@ -1173,6 +1246,8 @@ pick_initial_format_from_renderer_tranche(struct window *window, window->format.format = fmt->format; wl_array_copy(&window->format.modifiers, &fmt->modifiers); + window->bo_flags = GBM_BO_USE_RENDERING; + return true; } return false; @@ -1203,6 +1278,8 @@ pick_format_from_scanout_tranche(struct window *window, window->format.format = fmt->format; wl_array_copy(&window->format.modifiers, &fmt->modifiers); + window->bo_flags = GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT; + return true; } return false; @@ -1216,7 +1293,7 @@ dmabuf_feedback_done(void *data, struct zwp_linux_dmabuf_feedback_v1 *dmabuf_fee bool got_scanout_tranche = false; unsigned int i; - fprintf(stderr, "└end of dma-buf feedback\n\n"); + fprintf(stderr, L_LAST " end of dma-buf feedback\n\n"); /* The first time that we receive dma-buf feedback for a surface it * contains only the renderer tranches. We pick the INITIAL_BUFFER_FORMAT @@ -1344,6 +1421,10 @@ registry_handle_global(void *data, struct wl_registry *registry, d->dmabuf = wl_registry_bind(registry, id, &zwp_linux_dmabuf_v1_interface, MIN(version, 4)); + } else if (strcmp(interface, "wp_presentation") == 0) { + d->presentation = wl_registry_bind(registry, id, + &wp_presentation_interface, + 1); } } @@ -1368,6 +1449,9 @@ destroy_display(struct display *display) if (display->egl.display != EGL_NO_DISPLAY) eglTerminate(display->egl.display); + if (display->presentation) + wp_presentation_destroy(display->presentation); + zwp_linux_dmabuf_v1_destroy(display->dmabuf); xdg_wm_base_destroy(display->wm_base); diff --git a/clients/simple-egl.c b/clients/simple-egl.c index 2c7059c0..657ec49f 100644 --- a/clients/simple-egl.c +++ b/clients/simple-egl.c @@ -46,9 +46,11 @@ #include #include +#include #include "shared/helpers.h" #include "shared/platform.h" #include "shared/weston-egl-ext.h" +#include "shared/xalloc.h" struct window; struct seat; @@ -73,6 +75,8 @@ struct display { } egl; struct window *window; + struct wl_list output_list; /* struct output::link */ + PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC swap_buffers_with_damage; }; @@ -82,22 +86,45 @@ struct geometry { struct window { struct display *display; - struct geometry geometry, window_size; + struct geometry window_size; + struct geometry logical_size; + struct geometry buffer_size; + int32_t buffer_scale; + enum wl_output_transform buffer_transform; + bool needs_buffer_geometry_update; + struct { GLuint rotation_uniform; GLuint pos; GLuint col; } gl; - uint32_t benchmark_time, frames; + uint32_t frames; + uint32_t initial_frame_time; + uint32_t benchmark_time; struct wl_egl_window *native; struct wl_surface *surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; EGLSurface egl_surface; - struct wl_callback *callback; - int fullscreen, maximized, opaque, buffer_size, frame_sync, delay; + int fullscreen, maximized, opaque, buffer_bpp, frame_sync, delay; bool wait_for_configure; + + struct wl_list window_output_list; /* struct window_output::link */ +}; + +struct output { + struct display *display; + struct wl_output *wl_output; + uint32_t name; + struct wl_list link; /* struct display::output_list */ + enum wl_output_transform transform; + int32_t scale; +}; + +struct window_output { + struct output *output; + struct wl_list link; /* struct window::window_output_list */ }; static const char *vert_shader_text = @@ -155,7 +182,7 @@ init_egl(struct display *display, struct window *window) EGLConfig *configs; EGLBoolean ret; - if (window->opaque || window->buffer_size == 16) + if (window->opaque || window->buffer_bpp == 16) config_attribs[9] = 0; display->egl.dpy = @@ -179,13 +206,13 @@ init_egl(struct display *display, struct window *window) assert(ret && n >= 1); for (i = 0; i < n; i++) { - EGLint buffer_size, red_size; + EGLint buffer_bpp, red_size; eglGetConfigAttrib(display->egl.dpy, - configs[i], EGL_BUFFER_SIZE, &buffer_size); + configs[i], EGL_BUFFER_SIZE, &buffer_bpp); eglGetConfigAttrib(display->egl.dpy, configs[i], EGL_RED_SIZE, &red_size); - if ((window->buffer_size == 0 || - window->buffer_size == buffer_size) && red_size < 10) { + if ((window->buffer_bpp == 0 || + window->buffer_bpp == buffer_bpp) && red_size < 10) { display->egl.conf = configs[i]; break; } @@ -193,7 +220,7 @@ init_egl(struct display *display, struct window *window) free(configs); if (display->egl.conf == NULL) { fprintf(stderr, "did not find config with buffer size %d\n", - window->buffer_size); + window->buffer_bpp); exit(EXIT_FAILURE); } @@ -256,6 +283,92 @@ create_shader(struct window *window, const char *source, GLenum shader_type) return shader; } +static int32_t +compute_buffer_scale(struct window *window) +{ + struct window_output *window_output; + int32_t scale = 1; + + wl_list_for_each(window_output, &window->window_output_list, link) { + if (window_output->output->scale > scale) + scale = window_output->output->scale; + } + + return scale; +} + +static enum wl_output_transform +compute_buffer_transform(struct window *window) +{ + struct window_output *window_output; + enum wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; + + wl_list_for_each(window_output, &window->window_output_list, link) { + /* If the surface spans over multiple outputs the optimal + * transform value can be ambiguous. Thus just return the value + * from the oldest entered output. + */ + transform = window_output->output->transform; + break; + } + + return transform; +} + +static void +update_buffer_geometry(struct window *window) +{ + enum wl_output_transform new_buffer_transform; + int32_t new_buffer_scale; + struct geometry new_buffer_size; + + new_buffer_transform = compute_buffer_transform(window); + if (window->buffer_transform != new_buffer_transform) { + window->buffer_transform = new_buffer_transform; + wl_surface_set_buffer_transform(window->surface, + window->buffer_transform); + } + + new_buffer_scale = compute_buffer_scale(window); + if (window->buffer_scale != new_buffer_scale) { + window->buffer_scale = new_buffer_scale; + wl_surface_set_buffer_scale(window->surface, + window->buffer_scale); + } + + switch (window->buffer_transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + new_buffer_size.width = window->logical_size.width; + new_buffer_size.height = window->logical_size.height; + break; + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + new_buffer_size.width = window->logical_size.height; + new_buffer_size.height = window->logical_size.width; + break; + } + + new_buffer_size.width *= window->buffer_scale; + new_buffer_size.height *= window->buffer_scale; + + if (window->buffer_size.width != new_buffer_size.width || + window->buffer_size.height != new_buffer_size.height) { + window->buffer_size = new_buffer_size; + if (window->native) + wl_egl_window_resize(window->native, + window->buffer_size.width, + window->buffer_size.height, 0, 0); + } + + window->needs_buffer_geometry_update = false; +} + + static void init_gl(struct window *window) { @@ -264,9 +377,12 @@ init_gl(struct window *window) GLint status; EGLBoolean ret; + if (window->needs_buffer_geometry_update) + update_buffer_geometry(window); + window->native = wl_egl_window_create(window->surface, - window->geometry.width, - window->geometry.height); + window->buffer_size.width, + window->buffer_size.height); window->egl_surface = weston_platform_create_egl_surface(window->display->egl.dpy, window->display->egl.conf, @@ -276,6 +392,9 @@ init_gl(struct window *window) window->egl_surface, window->display->egl.ctx); assert(ret == EGL_TRUE); + if (!window->frame_sync) + eglSwapInterval(window->display->egl.dpy, 0); + frag = create_shader(window, frag_shader_text, GL_FRAGMENT_SHADER); vert = create_shader(window, vert_shader_text, GL_VERTEX_SHADER); @@ -348,16 +467,13 @@ handle_toplevel_configure(void *data, struct xdg_toplevel *toplevel, window->window_size.width = width; window->window_size.height = height; } - window->geometry.width = width; - window->geometry.height = height; + window->logical_size.width = width; + window->logical_size.height = height; } else if (!window->fullscreen && !window->maximized) { - window->geometry = window->window_size; + window->logical_size = window->window_size; } - if (window->native) - wl_egl_window_resize(window->native, - window->geometry.width, - window->geometry.height, 0, 0); + window->needs_buffer_geometry_update = true; } static void @@ -371,12 +487,80 @@ static const struct xdg_toplevel_listener xdg_toplevel_listener = { handle_toplevel_close, }; +static void +add_window_output(struct window *window, struct wl_output *wl_output) +{ + struct output *output; + struct output *output_found = NULL; + struct window_output *window_output; + + wl_list_for_each(output, &window->display->output_list, link) { + if (output->wl_output == wl_output) { + output_found = output; + break; + } + } + + if (!output_found) + return; + + window_output = xmalloc(sizeof *window_output); + window_output->output = output_found; + + wl_list_insert(window->window_output_list.prev, &window_output->link); + window->needs_buffer_geometry_update = true; +} + +static void +destroy_window_output(struct window *window, struct wl_output *wl_output) +{ + struct window_output *window_output; + struct window_output *window_output_found = NULL; + + wl_list_for_each(window_output, &window->window_output_list, link) { + if (window_output->output->wl_output == wl_output) { + window_output_found = window_output; + break; + } + } + + if (window_output_found) { + wl_list_remove(&window_output_found->link); + free(window_output_found); + window->needs_buffer_geometry_update = true; + } +} + +static void +surface_enter(void *data, + struct wl_surface *wl_surface, struct wl_output *wl_output) +{ + struct window *window = data; + + add_window_output(window, wl_output); +} + +static void +surface_leave(void *data, + struct wl_surface *wl_surface, struct wl_output *wl_output) +{ + struct window *window = data; + + destroy_window_output(window, wl_output); +} + +static const struct wl_surface_listener surface_listener = { + surface_enter, + surface_leave +}; + static void create_surface(struct window *window) { struct display *display = window->display; window->surface = wl_compositor_create_surface(display->compositor); + wl_surface_add_listener(window->surface, &surface_listener, window); window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base, window->surface); @@ -399,9 +583,6 @@ create_surface(struct window *window) window->wait_for_configure = true; wl_surface_commit(window->surface); - - if (!window->frame_sync) - eglSwapInterval(display->egl.dpy, 0); } static void @@ -421,15 +602,12 @@ destroy_surface(struct window *window) if (window->xdg_surface) xdg_surface_destroy(window->xdg_surface); wl_surface_destroy(window->surface); - - if (window->callback) - wl_callback_destroy(window->callback); } + static void -redraw(void *data, struct wl_callback *callback, uint32_t time) +redraw(struct window *window) { - struct window *window = data; struct display *display = window->display; static const GLfloat verts[3][2] = { { -0.5, -0.5 }, @@ -442,28 +620,22 @@ redraw(void *data, struct wl_callback *callback, uint32_t time) { 0, 0, 1 } }; GLfloat angle; - GLfloat rotation[4][4] = { - { 1, 0, 0, 0 }, - { 0, 1, 0, 0 }, - { 0, 0, 1, 0 }, - { 0, 0, 0, 1 } - }; + struct weston_matrix rotation; static const uint32_t speed_div = 5, benchmark_interval = 5; struct wl_region *region; EGLint rect[4]; EGLint buffer_age = 0; struct timeval tv; - assert(window->callback == callback); - window->callback = NULL; - - if (callback) - wl_callback_destroy(callback); + if (window->needs_buffer_geometry_update) + update_buffer_geometry(window); gettimeofday(&tv, NULL); - time = tv.tv_sec * 1000 + tv.tv_usec / 1000; - if (window->frames == 0) + uint32_t time = tv.tv_sec * 1000 + tv.tv_usec / 1000; + if (window->frames == 0) { + window->initial_frame_time = time; window->benchmark_time = time; + } if (time - window->benchmark_time > (benchmark_interval * 1000)) { printf("%d frames in %d seconds: %f fps\n", window->frames, @@ -473,20 +645,51 @@ redraw(void *data, struct wl_callback *callback, uint32_t time) window->frames = 0; } - angle = (time / speed_div) % 360 * M_PI / 180.0; - rotation[0][0] = cos(angle); - rotation[0][2] = sin(angle); - rotation[2][0] = -sin(angle); - rotation[2][2] = cos(angle); + weston_matrix_init(&rotation); + angle = ((time - window->initial_frame_time) / speed_div) % 360 * M_PI / 180.0; + rotation.d[0] = cos(angle); + rotation.d[2] = sin(angle); + rotation.d[8] = -sin(angle); + rotation.d[10] = cos(angle); + + switch (window->buffer_transform) { + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + weston_matrix_scale(&rotation, -1, 1, 1); + break; + default: + break; + } + + switch (window->buffer_transform) { + default: + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_FLIPPED: + break; + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + weston_matrix_rotate_xy(&rotation, 0, 1); + break; + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + weston_matrix_rotate_xy(&rotation, -1, 0); + break; + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + weston_matrix_rotate_xy(&rotation, 0, -1); + break; + } if (display->swap_buffers_with_damage) eglQuerySurface(display->egl.dpy, window->egl_surface, EGL_BUFFER_AGE_EXT, &buffer_age); - glViewport(0, 0, window->geometry.width, window->geometry.height); + glViewport(0, 0, window->buffer_size.width, window->buffer_size.height); glUniformMatrix4fv(window->gl.rotation_uniform, 1, GL_FALSE, - (GLfloat *) rotation); + (GLfloat *) rotation.d); if (window->opaque || window->fullscreen) glClearColor(0.0, 0.0, 0.0, 1); @@ -508,9 +711,7 @@ redraw(void *data, struct wl_callback *callback, uint32_t time) if (window->opaque || window->fullscreen) { region = wl_compositor_create_region(window->display->compositor); - wl_region_add(region, 0, 0, - window->geometry.width, - window->geometry.height); + wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX); wl_surface_set_opaque_region(window->surface, region); wl_region_destroy(region); } else { @@ -518,10 +719,10 @@ redraw(void *data, struct wl_callback *callback, uint32_t time) } if (display->swap_buffers_with_damage && buffer_age > 0) { - rect[0] = window->geometry.width / 4 - 1; - rect[1] = window->geometry.height / 4 - 1; - rect[2] = window->geometry.width / 2 + 2; - rect[3] = window->geometry.height / 2 + 2; + rect[0] = window->buffer_size.width / 4 - 1; + rect[1] = window->buffer_size.height / 4 - 1; + rect[2] = window->buffer_size.width / 2 + 2; + rect[3] = window->buffer_size.height / 2 + 2; display->swap_buffers_with_damage(display->egl.dpy, window->egl_surface, rect, 1); @@ -745,6 +946,92 @@ static const struct xdg_wm_base_listener wm_base_listener = { xdg_wm_base_ping, }; +static void +display_handle_geometry(void *data, + struct wl_output *wl_output, + int32_t x, int32_t y, + int32_t physical_width, + int32_t physical_height, + int32_t subpixel, + const char *make, + const char *model, + int32_t transform) +{ + struct output *output = data; + + output->transform = transform; + output->display->window->needs_buffer_geometry_update = true; +} + +static void +display_handle_mode(void *data, + struct wl_output *wl_output, + uint32_t flags, + int32_t width, + int32_t height, + int32_t refresh) +{ +} + +static void +display_handle_done(void *data, + struct wl_output *wl_output) +{ +} + +static void +display_handle_scale(void *data, + struct wl_output *wl_output, + int32_t scale) +{ + struct output *output = data; + + output->scale = scale; + output->display->window->needs_buffer_geometry_update = true; +} + +static const struct wl_output_listener output_listener = { + display_handle_geometry, + display_handle_mode, + display_handle_done, + display_handle_scale +}; + +static void +display_add_output(struct display *d, uint32_t name) +{ + struct output *output; + + output = xzalloc(sizeof *output); + output->display = d; + output->scale = 1; + output->wl_output = + wl_registry_bind(d->registry, name, &wl_output_interface, 2); + output->name = name; + wl_list_insert(d->output_list.prev, &output->link); + + wl_output_add_listener(output->wl_output, &output_listener, output); +} + +static void +display_destroy_output(struct display *d, struct output *output) +{ + destroy_window_output(d->window, output->wl_output); + wl_output_destroy(output->wl_output); + wl_list_remove(&output->link); + free(output); +} + +static void +display_destroy_outputs(struct display *d) +{ + struct output *tmp; + struct output *output; + + wl_list_for_each_safe(output, tmp, &d->output_list, link) + display_destroy_output(d, output); +} + static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) @@ -778,6 +1065,8 @@ registry_handle_global(void *data, struct wl_registry *registry, fprintf(stderr, "unable to load default left pointer\n"); // TODO: abort ? } + } else if (strcmp(interface, "wl_output") == 0 && version >= 2) { + display_add_output(d, name); } } @@ -785,6 +1074,15 @@ static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { + struct display *d = data; + struct output *output; + + wl_list_for_each(output, &d->output_list, link) { + if (output->name == name) { + display_destroy_output(d, output); + break; + } + } } static const struct wl_registry_listener registry_listener = { @@ -823,13 +1121,19 @@ main(int argc, char **argv) window.display = &display; display.window = &window; - window.geometry.width = 250; - window.geometry.height = 250; - window.window_size = window.geometry; - window.buffer_size = 0; + window.buffer_size.width = 250; + window.buffer_size.height = 250; + window.window_size = window.buffer_size; + window.buffer_scale = 1; + window.buffer_transform = WL_OUTPUT_TRANSFORM_NORMAL; + window.needs_buffer_geometry_update = false; + window.buffer_bpp = 0; window.frame_sync = 1; window.delay = 0; + wl_list_init(&display.output_list); + wl_list_init(&window.window_output_list); + for (i = 1; i < argc; i++) { if (strcmp("-d", argv[i]) == 0 && i+1 < argc) window.delay = atoi(argv[++i]); @@ -840,7 +1144,7 @@ main(int argc, char **argv) else if (strcmp("-o", argv[i]) == 0) window.opaque = 1; else if (strcmp("-s", argv[i]) == 0) - window.buffer_size = 16; + window.buffer_bpp = 16; else if (strcmp("-b", argv[i]) == 0) window.frame_sync = 0; else if (strcmp("-h", argv[i]) == 0) @@ -887,7 +1191,7 @@ main(int argc, char **argv) while (running && ret != -1) { ret = wl_display_dispatch_pending(display.display); - redraw(&window, NULL, 0); + redraw(&window); } fprintf(stderr, "simple-egl exiting\n"); @@ -897,6 +1201,8 @@ main(int argc, char **argv) wl_surface_destroy(display.cursor_surface); out_no_xdg_shell: + display_destroy_outputs(&display); + if (display.cursor_theme) wl_cursor_theme_destroy(display.cursor_theme); diff --git a/clients/simple-touch.c b/clients/simple-touch.c index 7c9ada64..6559aa24 100644 --- a/clients/simple-touch.c +++ b/clients/simple-touch.c @@ -75,11 +75,9 @@ create_shm_buffer(struct touch *touch) struct wl_shm_pool *pool; int fd, size, stride; void *data; - struct buffer *buffer = NULL; + struct buffer *buffer; - buffer = zalloc(sizeof(*buffer)); - if (!buffer) - return NULL; + buffer = xzalloc(sizeof(*buffer)); stride = touch->width * 4; size = stride * touch->height; diff --git a/clients/terminal.c b/clients/terminal.c index b7537aad..545ddd4f 100644 --- a/clients/terminal.c +++ b/clients/terminal.c @@ -3023,13 +3023,22 @@ static void terminal_destroy(struct terminal *terminal) { display_unwatch_fd(terminal->display, terminal->master); - window_destroy(terminal->window); close(terminal->master); + + cairo_scaled_font_destroy(terminal->font_bold); + cairo_scaled_font_destroy(terminal->font_normal); + + widget_destroy(terminal->widget); + window_destroy(terminal->window); + wl_list_remove(&terminal->link); if (wl_list_empty(&terminal_list)) display_exit(terminal->display); + free(terminal->data); + free(terminal->data_attr); + free(terminal->tab_ruler); free(terminal->title); free(terminal); } @@ -3048,10 +3057,12 @@ io_handler(struct task *task, uint32_t events) } len = read(terminal->master, buffer, sizeof buffer); - if (len < 0) + if (len < 0) { terminal_destroy(terminal); - else - terminal_data(terminal, buffer, len); + return; + } + + terminal_data(terminal, buffer, len); } static int @@ -3128,7 +3139,7 @@ static const struct weston_option terminal_options[] = { int main(int argc, char *argv[]) { struct display *d; - struct terminal *terminal; + struct terminal *terminal, *tmp; const char *config_file; struct sigaction sigpipe; struct weston_config *config; @@ -3183,5 +3194,9 @@ int main(int argc, char *argv[]) display_run(d); + wl_list_for_each_safe(terminal, tmp, &terminal_list, link) + terminal_destroy(terminal); + display_destroy(d); + return 0; } diff --git a/clients/weston-info.c b/clients/weston-info.c deleted file mode 100644 index 97725273..00000000 --- a/clients/weston-info.c +++ /dev/null @@ -1,1890 +0,0 @@ -/* - * Copyright © 2012 Philipp Brüschweiler - * - * 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. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "shared/helpers.h" -#include "shared/os-compatibility.h" -#include "shared/xalloc.h" -#include -#include "presentation-time-client-protocol.h" -#include "linux-dmabuf-unstable-v1-client-protocol.h" -#include "tablet-unstable-v2-client-protocol.h" -#include "xdg-output-unstable-v1-client-protocol.h" - -typedef void (*print_info_t)(void *info); -typedef void (*destroy_info_t)(void *info); - -struct global_info { - struct wl_list link; - - uint32_t id; - uint32_t version; - char *interface; - - print_info_t print; - destroy_info_t destroy; -}; - -struct output_mode { - struct wl_list link; - - uint32_t flags; - int32_t width, height; - int32_t refresh; -}; - -struct output_info { - struct global_info global; - struct wl_list global_link; - - struct wl_output *output; - - int32_t version; - - struct { - int32_t x, y; - int32_t scale; - int32_t physical_width, physical_height; - enum wl_output_subpixel subpixel; - enum wl_output_transform output_transform; - char *make; - char *model; - } geometry; - - struct wl_list modes; -}; - -struct shm_format { - struct wl_list link; - - uint32_t format; -}; - -struct shm_info { - struct global_info global; - struct wl_shm *shm; - - struct wl_list formats; -}; - -struct linux_dmabuf_modifier { - struct wl_list link; - - uint32_t format; - uint64_t modifier; -}; - -struct linux_dmabuf_info { - struct global_info global; - struct zwp_linux_dmabuf_v1 *dmabuf; - - struct wl_list modifiers; -}; - -struct seat_info { - struct global_info global; - struct wl_list global_link; - struct wl_seat *seat; - struct weston_info *info; - - struct wl_keyboard *keyboard; - uint32_t capabilities; - char *name; - - int32_t repeat_rate; - int32_t repeat_delay; -}; - -struct tablet_v2_path { - struct wl_list link; - char *path; -}; - -struct tablet_tool_info { - struct wl_list link; - struct zwp_tablet_tool_v2 *tool; - - uint64_t hardware_serial; - uint64_t hardware_id_wacom; - enum zwp_tablet_tool_v2_type type; - - bool has_tilt; - bool has_pressure; - bool has_distance; - bool has_rotation; - bool has_slider; - bool has_wheel; -}; - -struct tablet_pad_group_info { - struct wl_list link; - struct zwp_tablet_pad_group_v2 *group; - - uint32_t modes; - size_t button_count; - int *buttons; - size_t strips; - size_t rings; -}; - -struct tablet_pad_info { - struct wl_list link; - struct zwp_tablet_pad_v2 *pad; - - uint32_t buttons; - struct wl_list paths; - struct wl_list groups; -}; - -struct tablet_info { - struct wl_list link; - struct zwp_tablet_v2 *tablet; - - char *name; - uint32_t vid, pid; - struct wl_list paths; -}; - -struct tablet_seat_info { - struct wl_list link; - - struct zwp_tablet_seat_v2 *seat; - struct seat_info *seat_info; - - struct wl_list tablets; - struct wl_list tools; - struct wl_list pads; -}; - -struct tablet_v2_info { - struct global_info global; - struct zwp_tablet_manager_v2 *manager; - struct weston_info *info; - - struct wl_list seats; -}; - -struct xdg_output_v1_info { - struct wl_list link; - - struct zxdg_output_v1 *xdg_output; - struct output_info *output; - - struct { - int32_t x, y; - int32_t width, height; - } logical; - - char *name, *description; -}; - -struct xdg_output_manager_v1_info { - struct global_info global; - struct zxdg_output_manager_v1 *manager; - struct weston_info *info; - - struct wl_list outputs; -}; - -struct presentation_info { - struct global_info global; - struct wp_presentation *presentation; - - clockid_t clk_id; -}; - -struct weston_info { - struct wl_display *display; - struct wl_registry *registry; - - struct wl_list infos; - bool roundtrip_needed; - - /* required for tablet-unstable-v2 */ - struct wl_list seats; - struct tablet_v2_info *tablet_info; - - /* required for xdg-output-unstable-v1 */ - struct wl_list outputs; - struct xdg_output_manager_v1_info *xdg_output_manager_v1_info; -}; - -static void -print_global_info(void *data) -{ - struct global_info *global = data; - - printf("interface: '%s', version: %u, name: %u\n", - global->interface, global->version, global->id); -} - -static void -init_global_info(struct weston_info *info, - struct global_info *global, uint32_t id, - const char *interface, uint32_t version) -{ - global->id = id; - global->version = version; - global->interface = xstrdup(interface); - - wl_list_insert(info->infos.prev, &global->link); -} - -static void -print_output_info(void *data) -{ - struct output_info *output = data; - struct output_mode *mode; - const char *subpixel_orientation; - const char *transform; - - print_global_info(data); - - switch (output->geometry.subpixel) { - case WL_OUTPUT_SUBPIXEL_UNKNOWN: - subpixel_orientation = "unknown"; - break; - case WL_OUTPUT_SUBPIXEL_NONE: - subpixel_orientation = "none"; - break; - case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB: - subpixel_orientation = "horizontal rgb"; - break; - case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR: - subpixel_orientation = "horizontal bgr"; - break; - case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB: - subpixel_orientation = "vertical rgb"; - break; - case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR: - subpixel_orientation = "vertical bgr"; - break; - default: - fprintf(stderr, "unknown subpixel orientation %u\n", - output->geometry.subpixel); - subpixel_orientation = "unexpected value"; - break; - } - - switch (output->geometry.output_transform) { - case WL_OUTPUT_TRANSFORM_NORMAL: - transform = "normal"; - break; - case WL_OUTPUT_TRANSFORM_90: - transform = "90°"; - break; - case WL_OUTPUT_TRANSFORM_180: - transform = "180°"; - break; - case WL_OUTPUT_TRANSFORM_270: - transform = "270°"; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED: - transform = "flipped"; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_90: - transform = "flipped 90°"; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - transform = "flipped 180°"; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - transform = "flipped 270°"; - break; - default: - fprintf(stderr, "unknown output transform %u\n", - output->geometry.output_transform); - transform = "unexpected value"; - break; - } - - printf("\tx: %d, y: %d,", - output->geometry.x, output->geometry.y); - if (output->version >= 2) - printf(" scale: %d,", output->geometry.scale); - printf("\n"); - - printf("\tphysical_width: %d mm, physical_height: %d mm,\n", - output->geometry.physical_width, - output->geometry.physical_height); - printf("\tmake: '%s', model: '%s',\n", - output->geometry.make, output->geometry.model); - printf("\tsubpixel_orientation: %s, output_transform: %s,\n", - subpixel_orientation, transform); - - wl_list_for_each(mode, &output->modes, link) { - printf("\tmode:\n"); - - printf("\t\twidth: %d px, height: %d px, refresh: %.3f Hz,\n", - mode->width, mode->height, - (float) mode->refresh / 1000); - - printf("\t\tflags:"); - if (mode->flags & WL_OUTPUT_MODE_CURRENT) - printf(" current"); - if (mode->flags & WL_OUTPUT_MODE_PREFERRED) - printf(" preferred"); - printf("\n"); - } -} - -static char -bits2graph(uint32_t value, unsigned bitoffset) -{ - int c = (value >> bitoffset) & 0xff; - - if (isgraph(c) || isspace(c)) - return c; - - return '?'; -} - -static void -fourcc2str(uint32_t format, char *str, int len) -{ - int i; - - assert(len >= 5); - - for (i = 0; i < 4; i++) - str[i] = bits2graph(format, i * 8); - str[i] = '\0'; -} - -static void -print_shm_info(void *data) -{ - char str[5]; - struct shm_info *shm = data; - struct shm_format *format; - - print_global_info(data); - - printf("\tformats:"); - - wl_list_for_each(format, &shm->formats, link) - switch (format->format) { - case WL_SHM_FORMAT_ARGB8888: - printf(" ARGB8888"); - break; - case WL_SHM_FORMAT_XRGB8888: - printf(" XRGB8888"); - break; - case WL_SHM_FORMAT_RGB565: - printf(" RGB565"); - break; - default: - fourcc2str(format->format, str, sizeof(str)); - printf(" '%s'(0x%08x)", str, format->format); - break; - } - - printf("\n"); -} - -static void -print_linux_dmabuf_info(void *data) -{ - char str[5]; - struct linux_dmabuf_info *dmabuf = data; - struct linux_dmabuf_modifier *modifier; - - print_global_info(data); - - printf("\tformats:"); - - wl_list_for_each(modifier, &dmabuf->modifiers, link) { - fourcc2str(modifier->format, str, sizeof(str)); - printf("\n\t'%s'(0x%08x), modifier: 0x%016"PRIx64, str, modifier->format, modifier->modifier); - } - - printf("\n"); -} - -static void -print_seat_info(void *data) -{ - struct seat_info *seat = data; - - print_global_info(data); - - printf("\tname: %s\n", seat->name); - printf("\tcapabilities:"); - - if (seat->capabilities & WL_SEAT_CAPABILITY_POINTER) - printf(" pointer"); - if (seat->capabilities & WL_SEAT_CAPABILITY_KEYBOARD) - printf(" keyboard"); - if (seat->capabilities & WL_SEAT_CAPABILITY_TOUCH) - printf(" touch"); - - printf("\n"); - - if (seat->repeat_rate > 0) - printf("\tkeyboard repeat rate: %d\n", seat->repeat_rate); - if (seat->repeat_delay > 0) - printf("\tkeyboard repeat delay: %d\n", seat->repeat_delay); -} - -static void -keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, - uint32_t format, int fd, uint32_t size) -{ - /* Just so we don’t leak the keymap fd */ - close(fd); -} - -static void -keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, - uint32_t serial, struct wl_surface *surface, - struct wl_array *keys) -{ -} - -static void -keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, - uint32_t serial, struct wl_surface *surface) -{ -} - -static void -keyboard_handle_key(void *data, struct wl_keyboard *keyboard, - uint32_t serial, uint32_t time, uint32_t key, - uint32_t state) -{ -} - -static void -keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, - uint32_t serial, uint32_t mods_depressed, - uint32_t mods_latched, uint32_t mods_locked, - uint32_t group) -{ -} - -static void -keyboard_handle_repeat_info(void *data, struct wl_keyboard *keyboard, - int32_t rate, int32_t delay) -{ - struct seat_info *seat = data; - - seat->repeat_rate = rate; - seat->repeat_delay = delay; -} - -static const struct wl_keyboard_listener keyboard_listener = { - keyboard_handle_keymap, - keyboard_handle_enter, - keyboard_handle_leave, - keyboard_handle_key, - keyboard_handle_modifiers, - keyboard_handle_repeat_info, -}; - -static void -seat_handle_capabilities(void *data, struct wl_seat *wl_seat, - enum wl_seat_capability caps) -{ - struct seat_info *seat = data; - - seat->capabilities = caps; - - /* we want listen for repeat_info from wl_keyboard, but only - * do so if the seat info is >= 4 and if we actually have a - * keyboard */ - if (seat->global.version < 4) - return; - - if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { - seat->keyboard = wl_seat_get_keyboard(seat->seat); - wl_keyboard_add_listener(seat->keyboard, &keyboard_listener, - seat); - - seat->info->roundtrip_needed = true; - } -} - -static void -seat_handle_name(void *data, struct wl_seat *wl_seat, - const char *name) -{ - struct seat_info *seat = data; - seat->name = xstrdup(name); -} - -static const struct wl_seat_listener seat_listener = { - seat_handle_capabilities, - seat_handle_name, -}; - -static void -destroy_seat_info(void *data) -{ - struct seat_info *seat = data; - - wl_seat_destroy(seat->seat); - - if (seat->name != NULL) - free(seat->name); - - if (seat->keyboard) - wl_keyboard_destroy(seat->keyboard); - - wl_list_remove(&seat->global_link); -} - -static const char * -tablet_tool_type_to_str(enum zwp_tablet_tool_v2_type type) -{ - switch (type) { - case ZWP_TABLET_TOOL_V2_TYPE_PEN: - return "pen"; - case ZWP_TABLET_TOOL_V2_TYPE_ERASER: - return "eraser"; - case ZWP_TABLET_TOOL_V2_TYPE_BRUSH: - return "brush"; - case ZWP_TABLET_TOOL_V2_TYPE_PENCIL: - return "pencil"; - case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH: - return "airbrush"; - case ZWP_TABLET_TOOL_V2_TYPE_FINGER: - return "finger"; - case ZWP_TABLET_TOOL_V2_TYPE_MOUSE: - return "mouse"; - case ZWP_TABLET_TOOL_V2_TYPE_LENS: - return "lens"; - } - - return "Unknown type"; -} - -static void -print_tablet_tool_info(const struct tablet_tool_info *info) -{ - printf("\t\ttablet_tool: %s\n", tablet_tool_type_to_str(info->type)); - if (info->hardware_serial) { - printf("\t\t\thardware serial: %" PRIx64 "\n", info->hardware_serial); - } - if (info->hardware_id_wacom) { - printf("\t\t\thardware wacom: %" PRIx64 "\n", info->hardware_id_wacom); - } - - printf("\t\t\tcapabilities:"); - - if (info->has_tilt) { - printf(" tilt"); - } - if (info->has_pressure) { - printf(" pressure"); - } - if (info->has_distance) { - printf(" distance"); - } - if (info->has_rotation) { - printf(" rotation"); - } - if (info->has_slider) { - printf(" slider"); - } - if (info->has_wheel) { - printf(" wheel"); - } - printf("\n"); -} - -static void -destroy_tablet_tool_info(struct tablet_tool_info *info) -{ - wl_list_remove(&info->link); - zwp_tablet_tool_v2_destroy(info->tool); - free(info); -} - -static void -print_tablet_pad_group_info(const struct tablet_pad_group_info *info) -{ - size_t i; - printf("\t\t\tgroup:\n"); - printf("\t\t\t\tmodes: %u\n", info->modes); - printf("\t\t\t\tstrips: %zu\n", info->strips); - printf("\t\t\t\trings: %zu\n", info->rings); - printf("\t\t\t\tbuttons:"); - - for (i = 0; i < info->button_count; ++i) { - printf(" %d", info->buttons[i]); - } - - printf("\n"); -} - -static void -destroy_tablet_pad_group_info(struct tablet_pad_group_info *info) -{ - wl_list_remove(&info->link); - zwp_tablet_pad_group_v2_destroy(info->group); - - if (info->buttons) { - free(info->buttons); - } - free(info); -} - -static void -print_tablet_pad_info(const struct tablet_pad_info *info) -{ - const struct tablet_v2_path *path; - const struct tablet_pad_group_info *group; - - printf("\t\tpad:\n"); - printf("\t\t\tbuttons: %u\n", info->buttons); - - wl_list_for_each(path, &info->paths, link) { - printf("\t\t\tpath: %s\n", path->path); - } - - wl_list_for_each(group, &info->groups, link) { - print_tablet_pad_group_info(group); - } -} - -static void -destroy_tablet_pad_info(struct tablet_pad_info *info) -{ - struct tablet_v2_path *path; - struct tablet_v2_path *tmp_path; - struct tablet_pad_group_info *group; - struct tablet_pad_group_info *tmp_group; - - wl_list_remove(&info->link); - zwp_tablet_pad_v2_destroy(info->pad); - - wl_list_for_each_safe(path, tmp_path, &info->paths, link) { - wl_list_remove(&path->link); - free(path->path); - free(path); - } - - wl_list_for_each_safe(group, tmp_group, &info->groups, link) { - destroy_tablet_pad_group_info(group); - } - - free(info); -} - -static void -print_tablet_info(const struct tablet_info *info) -{ - const struct tablet_v2_path *path; - - printf("\t\ttablet: %s\n", info->name); - printf("\t\t\tvendor: %u\n", info->vid); - printf("\t\t\tproduct: %u\n", info->pid); - - wl_list_for_each(path, &info->paths, link) { - printf("\t\t\tpath: %s\n", path->path); - } -} - -static void -destroy_tablet_info(struct tablet_info *info) -{ - struct tablet_v2_path *path; - struct tablet_v2_path *tmp; - - wl_list_remove(&info->link); - zwp_tablet_v2_destroy(info->tablet); - - if (info->name) { - free(info->name); - } - - wl_list_for_each_safe(path, tmp, &info->paths, link) { - wl_list_remove(&path->link); - free(path->path); - free(path); - } - - free(info); -} - -static void -print_tablet_seat_info(const struct tablet_seat_info *info) -{ - const struct tablet_info *tablet; - const struct tablet_pad_info *pad; - const struct tablet_tool_info *tool; - - printf("\ttablet_seat: %s\n", info->seat_info->name); - - wl_list_for_each(tablet, &info->tablets, link) { - print_tablet_info(tablet); - } - - wl_list_for_each(pad, &info->pads, link) { - print_tablet_pad_info(pad); - } - - wl_list_for_each(tool, &info->tools, link) { - print_tablet_tool_info(tool); - } -} - -static void -destroy_tablet_seat_info(struct tablet_seat_info *info) -{ - struct tablet_info *tablet; - struct tablet_info *tmp_tablet; - struct tablet_pad_info *pad; - struct tablet_pad_info *tmp_pad; - struct tablet_tool_info *tool; - struct tablet_tool_info *tmp_tool; - - wl_list_remove(&info->link); - zwp_tablet_seat_v2_destroy(info->seat); - - wl_list_for_each_safe(tablet, tmp_tablet, &info->tablets, link) { - destroy_tablet_info(tablet); - } - - wl_list_for_each_safe(pad, tmp_pad, &info->pads, link) { - destroy_tablet_pad_info(pad); - } - - wl_list_for_each_safe(tool, tmp_tool, &info->tools, link) { - destroy_tablet_tool_info(tool); - } - - free(info); -} - -static void -print_tablet_v2_info(void *data) -{ - struct tablet_v2_info *info = data; - struct tablet_seat_info *seat; - print_global_info(data); - - wl_list_for_each(seat, &info->seats, link) { - /* Skip tablet_seats without a tablet, they are irrelevant */ - if (wl_list_empty(&seat->pads) && - wl_list_empty(&seat->tablets) && - wl_list_empty(&seat->tools)) { - continue; - } - - print_tablet_seat_info(seat); - } -} - -static void -destroy_tablet_v2_info(void *data) -{ - struct tablet_v2_info *info = data; - struct tablet_seat_info *seat; - struct tablet_seat_info *tmp; - - zwp_tablet_manager_v2_destroy(info->manager); - - wl_list_for_each_safe(seat, tmp, &info->seats, link) { - destroy_tablet_seat_info(seat); - } -} - -static void -handle_tablet_v2_tablet_tool_done(void *data, struct zwp_tablet_tool_v2 *tool) -{ - /* don't bother waiting for this; there's no good reason a - * compositor will wait more than one roundtrip before sending - * these initial events. */ -} - -static void -handle_tablet_v2_tablet_tool_removed(void *data, struct zwp_tablet_tool_v2 *tool) -{ - /* don't bother waiting for this; we never make any request either way. */ -} - -static void -handle_tablet_v2_tablet_tool_type(void *data, struct zwp_tablet_tool_v2 *tool, - uint32_t tool_type) -{ - struct tablet_tool_info *info = data; - info->type = tool_type; -} - -static void -handle_tablet_v2_tablet_tool_hardware_serial(void *data, - struct zwp_tablet_tool_v2 *tool, - uint32_t serial_hi, - uint32_t serial_lo) -{ - struct tablet_tool_info *info = data; - - info->hardware_serial = ((uint64_t) serial_hi) << 32 | - (uint64_t) serial_lo; -} - -static void -handle_tablet_v2_tablet_tool_hardware_id_wacom(void *data, - struct zwp_tablet_tool_v2 *tool, - uint32_t id_hi, uint32_t id_lo) -{ - struct tablet_tool_info *info = data; - - info->hardware_id_wacom = ((uint64_t) id_hi) << 32 | (uint64_t) id_lo; -} - -static void -handle_tablet_v2_tablet_tool_capability(void *data, - struct zwp_tablet_tool_v2 *tool, - uint32_t capability) -{ - struct tablet_tool_info *info = data; - enum zwp_tablet_tool_v2_capability cap = capability; - - switch(cap) { - case ZWP_TABLET_TOOL_V2_CAPABILITY_TILT: - info->has_tilt = true; - break; - case ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE: - info->has_pressure = true; - break; - case ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE: - info->has_distance = true; - break; - case ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION: - info->has_rotation = true; - break; - case ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER: - info->has_slider = true; - break; - case ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL: - info->has_wheel = true; - break; - } -} - -static void -handle_tablet_v2_tablet_tool_proximity_in(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - uint32_t serial, struct zwp_tablet_v2 *tablet, - struct wl_surface *surface) -{ - -} - -static void -handle_tablet_v2_tablet_tool_proximity_out(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) -{ - -} - -static void -handle_tablet_v2_tablet_tool_down(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - uint32_t serial) -{ - -} - -static void -handle_tablet_v2_tablet_tool_up(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) -{ - -} - - -static void -handle_tablet_v2_tablet_tool_motion(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - wl_fixed_t x, - wl_fixed_t y) -{ - -} - -static void -handle_tablet_v2_tablet_tool_pressure(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - uint32_t pressure) -{ - -} - -static void -handle_tablet_v2_tablet_tool_distance(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - uint32_t distance) -{ - -} - -static void -handle_tablet_v2_tablet_tool_tilt(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - wl_fixed_t tilt_x, - wl_fixed_t tilt_y) -{ - -} - -static void -handle_tablet_v2_tablet_tool_rotation(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - wl_fixed_t degrees) -{ - -} - -static void -handle_tablet_v2_tablet_tool_slider(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - int32_t position) -{ - -} - -static void -handle_tablet_v2_tablet_tool_wheel(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - wl_fixed_t degrees, - int32_t clicks) -{ - -} - -static void -handle_tablet_v2_tablet_tool_button(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - uint32_t serial, - uint32_t button, - uint32_t state) -{ - -} - -static void -handle_tablet_v2_tablet_tool_frame(void *data, - struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, - uint32_t time) -{ - -} - -static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = { - .removed = handle_tablet_v2_tablet_tool_removed, - .done = handle_tablet_v2_tablet_tool_done, - .type = handle_tablet_v2_tablet_tool_type, - .hardware_serial = handle_tablet_v2_tablet_tool_hardware_serial, - .hardware_id_wacom = handle_tablet_v2_tablet_tool_hardware_id_wacom, - .capability = handle_tablet_v2_tablet_tool_capability, - - .proximity_in = handle_tablet_v2_tablet_tool_proximity_in, - .proximity_out = handle_tablet_v2_tablet_tool_proximity_out, - .down = handle_tablet_v2_tablet_tool_down, - .up = handle_tablet_v2_tablet_tool_up, - - .motion = handle_tablet_v2_tablet_tool_motion, - .pressure = handle_tablet_v2_tablet_tool_pressure, - .distance = handle_tablet_v2_tablet_tool_distance, - .tilt = handle_tablet_v2_tablet_tool_tilt, - .rotation = handle_tablet_v2_tablet_tool_rotation, - .slider = handle_tablet_v2_tablet_tool_slider, - .wheel = handle_tablet_v2_tablet_tool_wheel, - .button = handle_tablet_v2_tablet_tool_button, - .frame = handle_tablet_v2_tablet_tool_frame, -}; - -static void add_tablet_v2_tablet_tool_info(void *data, - struct zwp_tablet_seat_v2 *tablet_seat_v2, - struct zwp_tablet_tool_v2 *tool) -{ - struct tablet_seat_info *tablet_seat = data; - struct tablet_tool_info *tool_info = xzalloc(sizeof *tool_info); - - tool_info->tool = tool; - wl_list_insert(&tablet_seat->tools, &tool_info->link); - - zwp_tablet_tool_v2_add_listener(tool, &tablet_tool_listener, tool_info); -} - -static void -handle_tablet_v2_tablet_pad_group_mode_switch(void *data, - struct zwp_tablet_pad_group_v2 *zwp_tablet_pad_group_v2, - uint32_t time, uint32_t serial, uint32_t mode) -{ - /* This shouldn't ever happen */ -} - -static void -handle_tablet_v2_tablet_pad_group_done(void *data, - struct zwp_tablet_pad_group_v2 *group) -{ - /* don't bother waiting for this; there's no good reason a - * compositor will wait more than one roundtrip before sending - * these initial events. */ -} - -static void -handle_tablet_v2_tablet_pad_group_modes(void *data, - struct zwp_tablet_pad_group_v2 *group, - uint32_t modes) -{ - struct tablet_pad_group_info *info = data; - info->modes = modes; -} - -static void -handle_tablet_v2_tablet_pad_group_buttons(void *data, - struct zwp_tablet_pad_group_v2 *group, - struct wl_array *buttons) -{ - struct tablet_pad_group_info *info = data; - - info->button_count = buttons->size / sizeof(int); - info->buttons = xzalloc(buttons->size); - memcpy(info->buttons, buttons->data, buttons->size); -} - -static void -handle_tablet_v2_tablet_pad_group_ring(void *data, - struct zwp_tablet_pad_group_v2 *group, - struct zwp_tablet_pad_ring_v2 *ring) -{ - struct tablet_pad_group_info *info = data; - ++info->rings; - - zwp_tablet_pad_ring_v2_destroy(ring); -} - -static void -handle_tablet_v2_tablet_pad_group_strip(void *data, - struct zwp_tablet_pad_group_v2 *group, - struct zwp_tablet_pad_strip_v2 *strip) -{ - struct tablet_pad_group_info *info = data; - ++info->strips; - - zwp_tablet_pad_strip_v2_destroy(strip); -} - -static const struct zwp_tablet_pad_group_v2_listener tablet_pad_group_listener = { - .buttons = handle_tablet_v2_tablet_pad_group_buttons, - .modes = handle_tablet_v2_tablet_pad_group_modes, - .ring = handle_tablet_v2_tablet_pad_group_ring, - .strip = handle_tablet_v2_tablet_pad_group_strip, - .done = handle_tablet_v2_tablet_pad_group_done, - .mode_switch = handle_tablet_v2_tablet_pad_group_mode_switch, -}; - -static void -handle_tablet_v2_tablet_pad_group(void *data, - struct zwp_tablet_pad_v2 *zwp_tablet_pad_v2, - struct zwp_tablet_pad_group_v2 *pad_group) -{ - struct tablet_pad_info *pad_info = data; - struct tablet_pad_group_info *group = xzalloc(sizeof *group); - - wl_list_insert(&pad_info->groups, &group->link); - group->group = pad_group; - zwp_tablet_pad_group_v2_add_listener(pad_group, - &tablet_pad_group_listener, group); -} - -static void -handle_tablet_v2_tablet_pad_path(void *data, struct zwp_tablet_pad_v2 *pad, - const char *path) -{ - struct tablet_pad_info *pad_info = data; - struct tablet_v2_path *path_elem = xzalloc(sizeof *path_elem); - path_elem->path = xstrdup(path); - - wl_list_insert(&pad_info->paths, &path_elem->link); -} - -static void -handle_tablet_v2_tablet_pad_buttons(void *data, struct zwp_tablet_pad_v2 *pad, - uint32_t buttons) -{ - struct tablet_pad_info *pad_info = data; - - pad_info->buttons = buttons; -} - -static void -handle_tablet_v2_tablet_pad_done(void *data, struct zwp_tablet_pad_v2 *pad) -{ - /* don't bother waiting for this; there's no good reason a - * compositor will wait more than one roundtrip before sending - * these initial events. */ -} - -static void -handle_tablet_v2_tablet_pad_removed(void *data, struct zwp_tablet_pad_v2 *pad) -{ - /* don't bother waiting for this; We never make any request that's not - * allowed to be issued either way. */ -} - -static void -handle_tablet_v2_tablet_pad_button(void *data, struct zwp_tablet_pad_v2 *pad, - uint32_t time, uint32_t button, uint32_t state) -{ - /* we don't have a surface, so this can't ever happen */ -} - -static void -handle_tablet_v2_tablet_pad_enter(void *data, struct zwp_tablet_pad_v2 *pad, - uint32_t serial, - struct zwp_tablet_v2 *tablet, - struct wl_surface *surface) -{ - /* we don't have a surface, so this can't ever happen */ -} - -static void -handle_tablet_v2_tablet_pad_leave(void *data, struct zwp_tablet_pad_v2 *pad, - uint32_t serial, struct wl_surface *surface) -{ - /* we don't have a surface, so this can't ever happen */ -} - -static const struct zwp_tablet_pad_v2_listener tablet_pad_listener = { - .group = handle_tablet_v2_tablet_pad_group, - .path = handle_tablet_v2_tablet_pad_path, - .buttons = handle_tablet_v2_tablet_pad_buttons, - .done = handle_tablet_v2_tablet_pad_done, - .removed = handle_tablet_v2_tablet_pad_removed, - .button = handle_tablet_v2_tablet_pad_button, - .enter = handle_tablet_v2_tablet_pad_enter, - .leave = handle_tablet_v2_tablet_pad_leave, -}; - -static void add_tablet_v2_tablet_pad_info(void *data, - struct zwp_tablet_seat_v2 *tablet_seat_v2, - struct zwp_tablet_pad_v2 *pad) -{ - struct tablet_seat_info *tablet_seat = data; - struct tablet_pad_info *pad_info = xzalloc(sizeof *pad_info); - - wl_list_init(&pad_info->paths); - wl_list_init(&pad_info->groups); - pad_info->pad = pad; - wl_list_insert(&tablet_seat->pads, &pad_info->link); - - zwp_tablet_pad_v2_add_listener(pad, &tablet_pad_listener, pad_info); -} - -static void -handle_tablet_v2_tablet_name(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, - const char *name) -{ - struct tablet_info *tablet_info = data; - tablet_info->name = xstrdup(name); -} - -static void -handle_tablet_v2_tablet_path(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, - const char *path) -{ - struct tablet_info *tablet_info = data; - struct tablet_v2_path *path_elem = xzalloc(sizeof *path_elem); - path_elem->path = xstrdup(path); - - wl_list_insert(&tablet_info->paths, &path_elem->link); -} - -static void -handle_tablet_v2_tablet_id(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, - uint32_t vid, uint32_t pid) -{ - struct tablet_info *tablet_info = data; - - tablet_info->vid = vid; - tablet_info->pid = pid; -} - -static void -handle_tablet_v2_tablet_done(void *data, struct zwp_tablet_v2 *zwp_tablet_v2) -{ - /* don't bother waiting for this; there's no good reason a - * compositor will wait more than one roundtrip before sending - * these initial events. */ -} - -static void -handle_tablet_v2_tablet_removed(void *data, struct zwp_tablet_v2 *zwp_tablet_v2) -{ - /* don't bother waiting for this; We never make any request that's not - * allowed to be issued either way. */ -} - -static const struct zwp_tablet_v2_listener tablet_listener = { - .name = handle_tablet_v2_tablet_name, - .id = handle_tablet_v2_tablet_id, - .path = handle_tablet_v2_tablet_path, - .done = handle_tablet_v2_tablet_done, - .removed = handle_tablet_v2_tablet_removed -}; - -static void -add_tablet_v2_tablet_info(void *data, struct zwp_tablet_seat_v2 *tablet_seat_v2, - struct zwp_tablet_v2 *tablet) -{ - struct tablet_seat_info *tablet_seat = data; - struct tablet_info *tablet_info = xzalloc(sizeof *tablet_info); - - wl_list_init(&tablet_info->paths); - tablet_info->tablet = tablet; - wl_list_insert(&tablet_seat->tablets, &tablet_info->link); - - zwp_tablet_v2_add_listener(tablet, &tablet_listener, tablet_info); -} - -static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = { - .tablet_added = add_tablet_v2_tablet_info, - .pad_added = add_tablet_v2_tablet_pad_info, - .tool_added = add_tablet_v2_tablet_tool_info, -}; - -static void -add_tablet_seat_info(struct tablet_v2_info *tablet_info, struct seat_info *seat) -{ - struct tablet_seat_info *tablet_seat = xzalloc(sizeof *tablet_seat); - - wl_list_insert(&tablet_info->seats, &tablet_seat->link); - tablet_seat->seat = zwp_tablet_manager_v2_get_tablet_seat( - tablet_info->manager, seat->seat); - zwp_tablet_seat_v2_add_listener(tablet_seat->seat, - &tablet_seat_listener, tablet_seat); - - wl_list_init(&tablet_seat->pads); - wl_list_init(&tablet_seat->tablets); - wl_list_init(&tablet_seat->tools); - tablet_seat->seat_info = seat; - - tablet_info->info->roundtrip_needed = true; -} - -static void -add_tablet_v2_info(struct weston_info *info, uint32_t id, uint32_t version) -{ - struct seat_info *seat; - struct tablet_v2_info *tablet = xzalloc(sizeof *tablet); - - wl_list_init(&tablet->seats); - tablet->info = info; - - init_global_info(info, &tablet->global, id, - zwp_tablet_manager_v2_interface.name, version); - tablet->global.print = print_tablet_v2_info; - tablet->global.destroy = destroy_tablet_v2_info; - - tablet->manager = wl_registry_bind(info->registry, - id, &zwp_tablet_manager_v2_interface, 1); - - wl_list_for_each(seat, &info->seats, global_link) { - add_tablet_seat_info(tablet, seat); - } - - info->tablet_info = tablet; -} - -static void -destroy_xdg_output_v1_info(struct xdg_output_v1_info *info) -{ - wl_list_remove(&info->link); - zxdg_output_v1_destroy(info->xdg_output); - free(info->name); - free(info->description); - free(info); -} - -static void -print_xdg_output_v1_info(const struct xdg_output_v1_info *info) -{ - printf("\txdg_output_v1\n"); - printf("\t\toutput: %d\n", info->output->global.id); - if (info->name) - printf("\t\tname: '%s'\n", info->name); - if (info->description) - printf("\t\tdescription: '%s'\n", info->description); - printf("\t\tlogical_x: %d, logical_y: %d\n", - info->logical.x, info->logical.y); - printf("\t\tlogical_width: %d, logical_height: %d\n", - info->logical.width, info->logical.height); -} - -static void -print_xdg_output_manager_v1_info(void *data) -{ - struct xdg_output_manager_v1_info *info = data; - struct xdg_output_v1_info *output; - - print_global_info(data); - - wl_list_for_each(output, &info->outputs, link) - print_xdg_output_v1_info(output); -} - -static void -destroy_xdg_output_manager_v1_info(void *data) -{ - struct xdg_output_manager_v1_info *info = data; - struct xdg_output_v1_info *output, *tmp; - - zxdg_output_manager_v1_destroy(info->manager); - - wl_list_for_each_safe(output, tmp, &info->outputs, link) - destroy_xdg_output_v1_info(output); -} - -static void -handle_xdg_output_v1_logical_position(void *data, struct zxdg_output_v1 *output, - int32_t x, int32_t y) -{ - struct xdg_output_v1_info *xdg_output = data; - xdg_output->logical.x = x; - xdg_output->logical.y = y; -} - -static void -handle_xdg_output_v1_logical_size(void *data, struct zxdg_output_v1 *output, - int32_t width, int32_t height) -{ - struct xdg_output_v1_info *xdg_output = data; - xdg_output->logical.width = width; - xdg_output->logical.height = height; -} - -static void -handle_xdg_output_v1_done(void *data, struct zxdg_output_v1 *output) -{ - /* Don't bother waiting for this; there's no good reason a - * compositor will wait more than one roundtrip before sending - * these initial events. */ -} - -static void -handle_xdg_output_v1_name(void *data, struct zxdg_output_v1 *output, - const char *name) -{ - struct xdg_output_v1_info *xdg_output = data; - xdg_output->name = strdup(name); -} - -static void -handle_xdg_output_v1_description(void *data, struct zxdg_output_v1 *output, - const char *description) -{ - struct xdg_output_v1_info *xdg_output = data; - xdg_output->description = strdup(description); -} - -static const struct zxdg_output_v1_listener xdg_output_v1_listener = { - .logical_position = handle_xdg_output_v1_logical_position, - .logical_size = handle_xdg_output_v1_logical_size, - .done = handle_xdg_output_v1_done, - .name = handle_xdg_output_v1_name, - .description = handle_xdg_output_v1_description, -}; - -static void -add_xdg_output_v1_info(struct xdg_output_manager_v1_info *manager_info, - struct output_info *output) -{ - struct xdg_output_v1_info *xdg_output = xzalloc(sizeof *xdg_output); - - wl_list_insert(&manager_info->outputs, &xdg_output->link); - xdg_output->xdg_output = zxdg_output_manager_v1_get_xdg_output( - manager_info->manager, output->output); - zxdg_output_v1_add_listener(xdg_output->xdg_output, - &xdg_output_v1_listener, xdg_output); - - xdg_output->output = output; - - manager_info->info->roundtrip_needed = true; -} - -static void -add_xdg_output_manager_v1_info(struct weston_info *info, uint32_t id, - uint32_t version) -{ - struct output_info *output; - struct xdg_output_manager_v1_info *manager = xzalloc(sizeof *manager); - - wl_list_init(&manager->outputs); - manager->info = info; - - init_global_info(info, &manager->global, id, - zxdg_output_manager_v1_interface.name, version); - manager->global.print = print_xdg_output_manager_v1_info; - manager->global.destroy = destroy_xdg_output_manager_v1_info; - - manager->manager = wl_registry_bind(info->registry, id, - &zxdg_output_manager_v1_interface, version > 2 ? 2 : version); - - wl_list_for_each(output, &info->outputs, global_link) - add_xdg_output_v1_info(manager, output); - - info->xdg_output_manager_v1_info = manager; -} - -static void -add_seat_info(struct weston_info *info, uint32_t id, uint32_t version) -{ - struct seat_info *seat = xzalloc(sizeof *seat); - - /* required to set roundtrip_needed to true in capabilities - * handler */ - seat->info = info; - - init_global_info(info, &seat->global, id, "wl_seat", version); - seat->global.print = print_seat_info; - seat->global.destroy = destroy_seat_info; - - seat->seat = wl_registry_bind(info->registry, - id, &wl_seat_interface, MIN(version, 4)); - wl_seat_add_listener(seat->seat, &seat_listener, seat); - - seat->repeat_rate = seat->repeat_delay = -1; - - info->roundtrip_needed = true; - wl_list_insert(&info->seats, &seat->global_link); - - if (info->tablet_info) { - add_tablet_seat_info(info->tablet_info, seat); - } -} - -static void -shm_handle_format(void *data, struct wl_shm *wl_shm, uint32_t format) -{ - struct shm_info *shm = data; - struct shm_format *shm_format = xzalloc(sizeof *shm_format); - - wl_list_insert(&shm->formats, &shm_format->link); - shm_format->format = format; -} - -static const struct wl_shm_listener shm_listener = { - shm_handle_format, -}; - -static void -destroy_shm_info(void *data) -{ - struct shm_info *shm = data; - struct shm_format *format, *tmp; - - wl_list_for_each_safe(format, tmp, &shm->formats, link) { - wl_list_remove(&format->link); - free(format); - } - - wl_shm_destroy(shm->shm); -} - -static void -add_shm_info(struct weston_info *info, uint32_t id, uint32_t version) -{ - struct shm_info *shm = xzalloc(sizeof *shm); - - init_global_info(info, &shm->global, id, "wl_shm", version); - shm->global.print = print_shm_info; - shm->global.destroy = destroy_shm_info; - - wl_list_init(&shm->formats); - - shm->shm = wl_registry_bind(info->registry, - id, &wl_shm_interface, 1); - wl_shm_add_listener(shm->shm, &shm_listener, shm); - - info->roundtrip_needed = true; -} - -static void -linux_dmabuf_handle_format(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format) -{ - /* This is a deprecated event, don’t use it. */ -} - -static void -linux_dmabuf_handle_modifier(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) -{ - struct linux_dmabuf_info *dmabuf = data; - struct linux_dmabuf_modifier *linux_dmabuf_modifier = xzalloc(sizeof *linux_dmabuf_modifier); - - wl_list_insert(&dmabuf->modifiers, &linux_dmabuf_modifier->link); - linux_dmabuf_modifier->format = format; - linux_dmabuf_modifier->modifier = ((uint64_t)modifier_hi) << 32 | modifier_lo; -} - -static const struct zwp_linux_dmabuf_v1_listener linux_dmabuf_listener = { - linux_dmabuf_handle_format, - linux_dmabuf_handle_modifier, -}; - -static void -destroy_linux_dmabuf_info(void *data) -{ - struct linux_dmabuf_info *dmabuf = data; - struct linux_dmabuf_modifier *modifier, *tmp; - - wl_list_for_each_safe(modifier, tmp, &dmabuf->modifiers, link) { - wl_list_remove(&modifier->link); - free(modifier); - } - - zwp_linux_dmabuf_v1_destroy(dmabuf->dmabuf); -} - -static void -add_linux_dmabuf_info(struct weston_info *info, uint32_t id, uint32_t version) -{ - struct linux_dmabuf_info *dmabuf = xzalloc(sizeof *dmabuf); - - init_global_info(info, &dmabuf->global, id, "zwp_linux_dmabuf_v1", version); - dmabuf->global.print = print_linux_dmabuf_info; - dmabuf->global.destroy = destroy_linux_dmabuf_info; - - wl_list_init(&dmabuf->modifiers); - - if (version >= 3) { - dmabuf->dmabuf = wl_registry_bind(info->registry, - id, &zwp_linux_dmabuf_v1_interface, 3); - zwp_linux_dmabuf_v1_add_listener(dmabuf->dmabuf, &linux_dmabuf_listener, dmabuf); - - info->roundtrip_needed = true; - } -} - -static void -output_handle_geometry(void *data, struct wl_output *wl_output, - int32_t x, int32_t y, - int32_t physical_width, int32_t physical_height, - int32_t subpixel, - const char *make, const char *model, - int32_t output_transform) -{ - struct output_info *output = data; - - output->geometry.x = x; - output->geometry.y = y; - output->geometry.physical_width = physical_width; - output->geometry.physical_height = physical_height; - output->geometry.subpixel = subpixel; - output->geometry.make = xstrdup(make); - output->geometry.model = xstrdup(model); - output->geometry.output_transform = output_transform; -} - -static void -output_handle_mode(void *data, struct wl_output *wl_output, - uint32_t flags, int32_t width, int32_t height, - int32_t refresh) -{ - struct output_info *output = data; - struct output_mode *mode = xmalloc(sizeof *mode); - - mode->flags = flags; - mode->width = width; - mode->height = height; - mode->refresh = refresh; - - wl_list_insert(output->modes.prev, &mode->link); -} - -static void -output_handle_done(void *data, struct wl_output *wl_output) -{ - /* don't bother waiting for this; there's no good reason a - * compositor will wait more than one roundtrip before sending - * these initial events. */ -} - -static void -output_handle_scale(void *data, struct wl_output *wl_output, - int32_t scale) -{ - struct output_info *output = data; - - output->geometry.scale = scale; -} - -static const struct wl_output_listener output_listener = { - output_handle_geometry, - output_handle_mode, - output_handle_done, - output_handle_scale, -}; - -static void -destroy_output_info(void *data) -{ - struct output_info *output = data; - struct output_mode *mode, *tmp; - - wl_output_destroy(output->output); - - if (output->geometry.make != NULL) - free(output->geometry.make); - if (output->geometry.model != NULL) - free(output->geometry.model); - - wl_list_for_each_safe(mode, tmp, &output->modes, link) { - wl_list_remove(&mode->link); - free(mode); - } -} - -static void -add_output_info(struct weston_info *info, uint32_t id, uint32_t version) -{ - struct output_info *output = xzalloc(sizeof *output); - - init_global_info(info, &output->global, id, "wl_output", version); - output->global.print = print_output_info; - output->global.destroy = destroy_output_info; - - output->version = MIN(version, 2); - output->geometry.scale = 1; - wl_list_init(&output->modes); - - output->output = wl_registry_bind(info->registry, id, - &wl_output_interface, output->version); - wl_output_add_listener(output->output, &output_listener, - output); - - info->roundtrip_needed = true; - wl_list_insert(&info->outputs, &output->global_link); - - if (info->xdg_output_manager_v1_info) - add_xdg_output_v1_info(info->xdg_output_manager_v1_info, - output); -} - -static void -destroy_presentation_info(void *info) -{ - struct presentation_info *prinfo = info; - - wp_presentation_destroy(prinfo->presentation); -} - -static const char * -clock_name(clockid_t clk_id) -{ - static const char *names[] = { - [CLOCK_REALTIME] = "CLOCK_REALTIME", - [CLOCK_MONOTONIC] = "CLOCK_MONOTONIC", - [CLOCK_MONOTONIC_RAW] = "CLOCK_MONOTONIC_RAW", - [CLOCK_REALTIME_COARSE] = "CLOCK_REALTIME_COARSE", - [CLOCK_MONOTONIC_COARSE] = "CLOCK_MONOTONIC_COARSE", -#ifdef CLOCK_BOOTTIME - [CLOCK_BOOTTIME] = "CLOCK_BOOTTIME", -#endif - }; - - if (clk_id < 0 || (unsigned)clk_id >= ARRAY_LENGTH(names)) - return "unknown"; - - return names[clk_id]; -} - -static void -print_presentation_info(void *info) -{ - struct presentation_info *prinfo = info; - - print_global_info(info); - - printf("\tpresentation clock id: %d (%s)\n", - prinfo->clk_id, clock_name(prinfo->clk_id)); -} - -static void -presentation_handle_clock_id(void *data, struct wp_presentation *presentation, - uint32_t clk_id) -{ - struct presentation_info *prinfo = data; - - prinfo->clk_id = clk_id; -} - -static const struct wp_presentation_listener presentation_listener = { - presentation_handle_clock_id -}; - -static void -add_presentation_info(struct weston_info *info, uint32_t id, uint32_t version) -{ - struct presentation_info *prinfo = xzalloc(sizeof *prinfo); - - init_global_info(info, &prinfo->global, id, - wp_presentation_interface.name, version); - prinfo->global.print = print_presentation_info; - prinfo->global.destroy = destroy_presentation_info; - - prinfo->clk_id = -1; - prinfo->presentation = wl_registry_bind(info->registry, id, - &wp_presentation_interface, 1); - wp_presentation_add_listener(prinfo->presentation, - &presentation_listener, prinfo); - - info->roundtrip_needed = true; -} - -static void -destroy_global_info(void *data) -{ -} - -static void -add_global_info(struct weston_info *info, uint32_t id, - const char *interface, uint32_t version) -{ - struct global_info *global = xzalloc(sizeof *global); - - init_global_info(info, global, id, interface, version); - global->print = print_global_info; - global->destroy = destroy_global_info; -} - -static void -global_handler(void *data, struct wl_registry *registry, uint32_t id, - const char *interface, uint32_t version) -{ - struct weston_info *info = data; - - if (!strcmp(interface, "wl_seat")) - add_seat_info(info, id, version); - else if (!strcmp(interface, "wl_shm")) - add_shm_info(info, id, version); - else if (!strcmp(interface, "zwp_linux_dmabuf_v1")) - add_linux_dmabuf_info(info, id, version); - else if (!strcmp(interface, "wl_output")) - add_output_info(info, id, version); - else if (!strcmp(interface, wp_presentation_interface.name)) - add_presentation_info(info, id, version); - else if (!strcmp(interface, zwp_tablet_manager_v2_interface.name)) - add_tablet_v2_info(info, id, version); - else if (!strcmp(interface, zxdg_output_manager_v1_interface.name)) - add_xdg_output_manager_v1_info(info, id, version); - else - add_global_info(info, id, interface, version); -} - -static void -global_remove_handler(void *data, struct wl_registry *registry, uint32_t name) -{ -} - -static const struct wl_registry_listener registry_listener = { - global_handler, - global_remove_handler -}; - -static void -print_infos(struct wl_list *infos) -{ - struct global_info *info; - - wl_list_for_each(info, infos, link) - info->print(info); -} - -static void -destroy_info(void *data) -{ - struct global_info *global = data; - - global->destroy(data); - wl_list_remove(&global->link); - free(global->interface); - free(data); -} - -static void -destroy_infos(struct wl_list *infos) -{ - struct global_info *info, *tmp; - wl_list_for_each_safe(info, tmp, infos, link) - destroy_info(info); -} - -int -main(int argc, char **argv) -{ - struct weston_info info; - - info.display = wl_display_connect(NULL); - if (!info.display) { - fprintf(stderr, "failed to create display: %s\n", - strerror(errno)); - return -1; - } - - fprintf(stderr, "\n"); - fprintf(stderr, "*** Please use wayland-info instead\n"); - fprintf(stderr, "*** weston-info is deprecated and will be removed in a future version\n"); - fprintf(stderr, "\n"); - - info.tablet_info = NULL; - info.xdg_output_manager_v1_info = NULL; - wl_list_init(&info.infos); - wl_list_init(&info.seats); - wl_list_init(&info.outputs); - - info.registry = wl_display_get_registry(info.display); - wl_registry_add_listener(info.registry, ®istry_listener, &info); - - do { - info.roundtrip_needed = false; - wl_display_roundtrip(info.display); - } while (info.roundtrip_needed); - - print_infos(&info.infos); - destroy_infos(&info.infos); - - wl_registry_destroy(info.registry); - wl_display_disconnect(info.display); - - return 0; -} diff --git a/clients/window.c b/clients/window.c index 1d98ee04..3036bb42 100644 --- a/clients/window.c +++ b/clients/window.c @@ -42,25 +42,6 @@ #include #include -#ifdef HAVE_CAIRO_EGL -#include - -#ifdef USE_CAIRO_GLESV2 -#include -#include -#else -#include -#endif -#include -#include - -#include -#elif !defined(ENABLE_EGL) /* platform.h defines these if EGL is enabled */ -typedef void *EGLDisplay; -typedef void *EGLConfig; -typedef void *EGLContext; -#define EGL_NO_DISPLAY ((EGLDisplay)0) -#endif /* no HAVE_CAIRO_EGL */ #include #ifdef HAVE_XKBCOMMON_COMPOSE @@ -109,10 +90,6 @@ struct display { struct xdg_wm_base *xdg_shell; struct zwp_relative_pointer_manager_v1 *relative_pointer_manager; struct zwp_pointer_constraints_v1 *pointer_constraints; - EGLDisplay dpy; - EGLConfig argb_config; - EGLContext argb_ctx; - cairo_device_t *argb_device; uint32_t serial; int display_fd; @@ -178,18 +155,6 @@ struct toysurface { enum wl_output_transform buffer_transform, int32_t buffer_scale, struct rectangle *server_allocation); - /* - * Make the toysurface current with the given EGL context. - * Returns 0 on success, and negative on failure. - */ - int (*acquire)(struct toysurface *base, EGLContext ctx); - - /* - * Release the toysurface from the EGL context, returning control - * to Cairo. - */ - void (*release)(struct toysurface *base); - /* * Destroy the toysurface, including the Cairo surface, any * backing storage, and the Wayland protocol objects. @@ -240,6 +205,7 @@ struct window { int redraw_needed; int redraw_task_scheduled; struct task redraw_task; + struct task close_task; int resize_needed; int custom; int focused; @@ -545,167 +511,6 @@ buffer_to_surface_size (enum wl_output_transform buffer_transform, int32_t buffe *height /= buffer_scale; } -#ifdef HAVE_CAIRO_EGL - -struct egl_window_surface { - struct toysurface base; - cairo_surface_t *cairo_surface; - struct display *display; - struct wl_surface *surface; - struct wl_egl_window *egl_window; - EGLSurface egl_surface; -}; - -static struct egl_window_surface * -to_egl_window_surface(struct toysurface *base) -{ - return container_of(base, struct egl_window_surface, base); -} - -static cairo_surface_t * -egl_window_surface_prepare(struct toysurface *base, int dx, int dy, - int32_t width, int32_t height, uint32_t flags, - enum wl_output_transform buffer_transform, int32_t buffer_scale) -{ - struct egl_window_surface *surface = to_egl_window_surface(base); - - surface_to_buffer_size (buffer_transform, buffer_scale, &width, &height); - - wl_egl_window_resize(surface->egl_window, width, height, dx, dy); - cairo_gl_surface_set_size(surface->cairo_surface, width, height); - - return cairo_surface_reference(surface->cairo_surface); -} - -static void -egl_window_surface_swap(struct toysurface *base, - enum wl_output_transform buffer_transform, int32_t buffer_scale, - struct rectangle *server_allocation) -{ - struct egl_window_surface *surface = to_egl_window_surface(base); - - cairo_gl_surface_swapbuffers(surface->cairo_surface); - wl_egl_window_get_attached_size(surface->egl_window, - &server_allocation->width, - &server_allocation->height); - - buffer_to_surface_size (buffer_transform, buffer_scale, - &server_allocation->width, - &server_allocation->height); -} - -static int -egl_window_surface_acquire(struct toysurface *base, EGLContext ctx) -{ - struct egl_window_surface *surface = to_egl_window_surface(base); - cairo_device_t *device; - - device = cairo_surface_get_device(surface->cairo_surface); - if (!device) - return -1; - - if (!ctx) { - if (device == surface->display->argb_device) - ctx = surface->display->argb_ctx; - else - assert(0); - } - - cairo_device_flush(device); - cairo_device_acquire(device); - if (!eglMakeCurrent(surface->display->dpy, surface->egl_surface, - surface->egl_surface, ctx)) - fprintf(stderr, "failed to make surface current\n"); - - return 0; -} - -static void -egl_window_surface_release(struct toysurface *base) -{ - struct egl_window_surface *surface = to_egl_window_surface(base); - cairo_device_t *device; - - device = cairo_surface_get_device(surface->cairo_surface); - if (!device) - return; - - if (!eglMakeCurrent(surface->display->dpy, - EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) - fprintf(stderr, "failed to make context current\n"); - - cairo_device_release(device); -} - -static void -egl_window_surface_destroy(struct toysurface *base) -{ - struct egl_window_surface *surface = to_egl_window_surface(base); - struct display *d = surface->display; - - cairo_surface_destroy(surface->cairo_surface); - weston_platform_destroy_egl_surface(d->dpy, surface->egl_surface); - wl_egl_window_destroy(surface->egl_window); - surface->surface = NULL; - - free(surface); -} - -static struct toysurface * -egl_window_surface_create(struct display *display, - struct wl_surface *wl_surface, - uint32_t flags, - struct rectangle *rectangle) -{ - struct egl_window_surface *surface; - - if (display->dpy == EGL_NO_DISPLAY) - return NULL; - - surface = zalloc(sizeof *surface); - if (!surface) - return NULL; - - surface->base.prepare = egl_window_surface_prepare; - surface->base.swap = egl_window_surface_swap; - surface->base.acquire = egl_window_surface_acquire; - surface->base.release = egl_window_surface_release; - surface->base.destroy = egl_window_surface_destroy; - - surface->display = display; - surface->surface = wl_surface; - - surface->egl_window = wl_egl_window_create(surface->surface, - rectangle->width, - rectangle->height); - - surface->egl_surface = - weston_platform_create_egl_surface(display->dpy, - display->argb_config, - surface->egl_window, NULL); - - surface->cairo_surface = - cairo_gl_surface_create_for_egl(display->argb_device, - surface->egl_surface, - rectangle->width, - rectangle->height); - - return &surface->base; -} - -#else - -static struct toysurface * -egl_window_surface_create(struct display *display, - struct wl_surface *wl_surface, - uint32_t flags, - struct rectangle *rectangle) -{ - return NULL; -} - -#endif - struct shm_surface_data { struct wl_buffer *buffer; struct shm_pool *pool; @@ -1159,17 +964,6 @@ shm_surface_swap(struct toysurface *base, surface->current = NULL; } -static int -shm_surface_acquire(struct toysurface *base, EGLContext ctx) -{ - return -1; -} - -static void -shm_surface_release(struct toysurface *base) -{ -} - static void shm_surface_destroy(struct toysurface *base) { @@ -1192,8 +986,6 @@ shm_surface_create(struct display *display, struct wl_surface *wl_surface, surface = xzalloc(sizeof *surface); surface->base.prepare = shm_surface_prepare; surface->base.swap = shm_surface_swap; - surface->base.acquire = shm_surface_acquire; - surface->base.release = shm_surface_release; surface->base.destroy = shm_surface_destroy; surface->display = display; @@ -1432,13 +1224,23 @@ window_has_focus(struct window *window) return window->focused; } + +static void +close_task_run(struct task *task, uint32_t events) +{ + struct window *window = container_of(task, struct window, close_task); + window->close_handler(window->user_data); +} + static void window_close(struct window *window) { - if (window->close_handler) - window->close_handler(window->user_data); - else + if (window->close_handler && !window->close_task.run) { + window->close_task.run = close_task_run; + display_defer(window->display, &window->close_task); + } else { display_exit(window->display); + } } struct display * @@ -1453,15 +1255,6 @@ surface_create_surface(struct surface *surface, uint32_t flags) struct display *display = surface->window->display; struct rectangle allocation = surface->allocation; - if (!surface->toysurface && display->dpy && - surface->buffer_type == WINDOW_BUFFER_TYPE_EGL_WINDOW) { - surface->toysurface = - egl_window_surface_create(display, - surface->surface, - flags, - &allocation); - } - if (!surface->toysurface) surface->toysurface = shm_surface_create(display, surface->surface, @@ -4439,9 +4232,24 @@ xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_surface) window_close(window); } +static void +xdg_toplevel_handle_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, + int32_t width, int32_t height) +{ +} + + +static void +xdg_toplevel_handle_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, + struct wl_array *caps) +{ +} + static const struct xdg_toplevel_listener xdg_toplevel_listener = { xdg_toplevel_handle_configure, xdg_toplevel_handle_close, + xdg_toplevel_handle_configure_bounds, + xdg_toplevel_handle_wm_capabilities, }; static void @@ -5272,11 +5080,6 @@ surface_create(struct window *window) static enum window_buffer_type get_preferred_buffer_type(struct display *display) { -#ifdef HAVE_CAIRO_EGL - if (display->argb_device && !getenv("TOYTOOLKIT_NO_EGL")) - return WINDOW_BUFFER_TYPE_EGL_WINDOW; -#endif - return WINDOW_BUFFER_TYPE_SHM; } @@ -5319,14 +5122,14 @@ window_create(struct display *display) window->xdg_surface = xdg_wm_base_get_xdg_surface(window->display->xdg_shell, window->main_surface->surface); - fail_on_null(window->xdg_surface, 0, __FILE__, __LINE__); + abort_oom_if_null(window->xdg_surface); xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, window); window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); - fail_on_null(window->xdg_toplevel, 0, __FILE__, __LINE__); + abort_oom_if_null(window->xdg_toplevel); xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener, window); @@ -5531,7 +5334,7 @@ create_menu(struct display *display, menu->widget = window_add_widget(menu->window, menu); menu->frame = frame_create(window->display->theme, 0, 0, FRAME_BUTTON_NONE, NULL, NULL); - fail_on_null(menu->frame, 0, __FILE__, __LINE__); + abort_oom_if_null(menu->frame); menu->entries = entries; menu->count = count; menu->release_count = 0; @@ -5565,7 +5368,7 @@ create_simple_positioner(struct display *display, struct xdg_positioner *positioner; positioner = xdg_wm_base_create_positioner(display->xdg_shell); - fail_on_null(positioner, 0, __FILE__, __LINE__); + abort_oom_if_null(positioner); xdg_positioner_set_anchor_rect(positioner, x, y, 1, 1); xdg_positioner_set_size(positioner, w, h); xdg_positioner_set_anchor(positioner, @@ -5610,7 +5413,7 @@ window_show_menu(struct display *display, window->xdg_surface = xdg_wm_base_get_xdg_surface(display->xdg_shell, window->main_surface->surface); - fail_on_null(window->xdg_surface, 0, __FILE__, __LINE__); + abort_oom_if_null(window->xdg_surface); xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, window); @@ -5623,7 +5426,7 @@ window_show_menu(struct display *display, window->xdg_popup = xdg_surface_get_popup(window->xdg_surface, parent->xdg_surface, positioner); - fail_on_null(window->xdg_popup, 0, __FILE__, __LINE__); + abort_oom_if_null(window->xdg_popup); xdg_positioner_destroy(positioner); xdg_popup_grab(window->xdg_popup, input->seat, display_get_serial(window->display)); @@ -6089,7 +5892,8 @@ registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, display_add_data_device(d, id, version); } else if (strcmp(interface, "xdg_wm_base") == 0) { d->xdg_shell = wl_registry_bind(registry, id, - &xdg_wm_base_interface, 1); + &xdg_wm_base_interface, + MIN(version, 5)); xdg_wm_base_add_listener(d->xdg_shell, &wm_base_listener, d); } else if (strcmp(interface, "text_cursor_position") == 0) { d->text_cursor_position = @@ -6140,90 +5944,6 @@ static const struct wl_registry_listener registry_listener = { registry_handle_global_remove }; -#ifdef HAVE_CAIRO_EGL -static int -init_egl(struct display *d) -{ - EGLint major, minor; - EGLint n; - -#ifdef USE_CAIRO_GLESV2 -# define GL_BIT EGL_OPENGL_ES2_BIT -#else -# define GL_BIT EGL_OPENGL_BIT -#endif - - static const EGLint argb_cfg_attribs[] = { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_RED_SIZE, 1, - EGL_GREEN_SIZE, 1, - EGL_BLUE_SIZE, 1, - EGL_ALPHA_SIZE, 1, - EGL_DEPTH_SIZE, 1, - EGL_RENDERABLE_TYPE, GL_BIT, - EGL_NONE - }; - -#ifdef USE_CAIRO_GLESV2 - static const EGLint context_attribs[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; - EGLint api = EGL_OPENGL_ES_API; -#else - EGLint *context_attribs = NULL; - EGLint api = EGL_OPENGL_API; -#endif - - d->dpy = - weston_platform_get_egl_display(EGL_PLATFORM_WAYLAND_KHR, - d->display, NULL); - - if (!eglInitialize(d->dpy, &major, &minor)) { - fprintf(stderr, "failed to initialize EGL\n"); - return -1; - } - - if (!eglBindAPI(api)) { - fprintf(stderr, "failed to bind EGL client API\n"); - return -1; - } - - if (!eglChooseConfig(d->dpy, argb_cfg_attribs, - &d->argb_config, 1, &n) || n != 1) { - fprintf(stderr, "failed to choose argb EGL config\n"); - return -1; - } - - d->argb_ctx = eglCreateContext(d->dpy, d->argb_config, - EGL_NO_CONTEXT, context_attribs); - if (d->argb_ctx == NULL) { - fprintf(stderr, "failed to create EGL context\n"); - return -1; - } - - d->argb_device = cairo_egl_device_create(d->dpy, d->argb_ctx); - if (cairo_device_status(d->argb_device) != CAIRO_STATUS_SUCCESS) { - fprintf(stderr, "failed to get cairo EGL argb device\n"); - return -1; - } - - return 0; -} - -static void -fini_egl(struct display *display) -{ - cairo_device_destroy(display->argb_device); - - eglMakeCurrent(display->dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT); - - eglTerminate(display->dpy); - eglReleaseThread(); -} -#endif - static void init_dummy_surface(struct display *display) { @@ -6329,12 +6049,6 @@ display_create(int *argc, char *argv[]) return NULL; } -#ifdef HAVE_CAIRO_EGL - if (init_egl(d) < 0) - fprintf(stderr, "EGL does not seem to work, " - "falling back to software rendering and wl_shm.\n"); -#endif - create_cursors(d); d->theme = theme_create(); @@ -6393,10 +6107,7 @@ display_destroy(struct display *display) theme_destroy(display->theme); destroy_cursors(display); -#ifdef HAVE_CAIRO_EGL - if (display->argb_device) - fini_egl(display); -#endif + cleanup_after_cairo(); if (display->relative_pointer_manager) zwp_relative_pointer_manager_v1_destroy(display->relative_pointer_manager); @@ -6462,12 +6173,6 @@ display_has_subcompositor(struct display *display) return display->subcompositor != NULL; } -cairo_device_t * -display_get_cairo_device(struct display *display) -{ - return display->argb_device; -} - struct output * display_get_output(struct display *display) { @@ -6489,12 +6194,6 @@ display_get_serial(struct display *display) return display->serial; } -EGLDisplay -display_get_egl_display(struct display *d) -{ - return d->dpy; -} - struct wl_data_source * display_create_data_source(struct display *display) { @@ -6504,38 +6203,6 @@ display_create_data_source(struct display *display) return NULL; } -EGLConfig -display_get_argb_egl_config(struct display *d) -{ - return d->argb_config; -} - -int -display_acquire_window_surface(struct display *display, - struct window *window, - EGLContext ctx) -{ - struct surface *surface = window->main_surface; - - if (surface->buffer_type != WINDOW_BUFFER_TYPE_EGL_WINDOW) - return -1; - - widget_get_cairo_surface(window->main_surface->widget); - return surface->toysurface->acquire(surface->toysurface, ctx); -} - -void -display_release_window_surface(struct display *display, - struct window *window) -{ - struct surface *surface = window->main_surface; - - if (surface->buffer_type != WINDOW_BUFFER_TYPE_EGL_WINDOW) - return; - - surface->toysurface->release(surface->toysurface); -} - void display_defer(struct display *display, struct task *task) { diff --git a/clients/window.h b/clients/window.h index 7cf82da1..22fd7e23 100644 --- a/clients/window.h +++ b/clients/window.h @@ -71,9 +71,6 @@ display_get_display(struct display *display); int display_has_subcompositor(struct display *display); -cairo_device_t * -display_get_cairo_device(struct display *display); - struct wl_compositor * display_get_compositor(struct display *display); @@ -114,22 +111,6 @@ display_set_output_configure_handler(struct display *display, struct wl_data_source * display_create_data_source(struct display *display); -#ifdef EGL_NO_DISPLAY -EGLDisplay -display_get_egl_display(struct display *d); - -EGLConfig -display_get_argb_egl_config(struct display *d); - -int -display_acquire_window_surface(struct display *display, - struct window *window, - EGLContext ctx); -void -display_release_window_surface(struct display *display, - struct window *window); -#endif - #define SURFACE_OPAQUE 0x01 #define SURFACE_SHM 0x02 @@ -416,7 +397,6 @@ struct wl_subsurface * widget_get_wl_subsurface(struct widget *widget); enum window_buffer_type { - WINDOW_BUFFER_TYPE_EGL_WINDOW, WINDOW_BUFFER_TYPE_SHM, }; diff --git a/compositor/main.c b/compositor/main.c index 322f2ff5..15f9d4e1 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -1,7 +1,7 @@ /* * Copyright © 2010-2011 Intel Corporation * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2012-2018 Collabora, Ltd. + * Copyright © 2012-2018,2022 Collabora, Ltd. * Copyright © 2010-2011 Benjamin Franzke * Copyright © 2013 Jason Ekstrand * Copyright © 2017, 2018 General Electric Company @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -50,15 +51,16 @@ #include #include "shared/os-compatibility.h" #include "shared/helpers.h" +#include "shared/process-util.h" #include "shared/string-helpers.h" #include "git-version.h" #include #include "weston.h" +#include "weston-private.h" #include #include #include -#include #include #include #include @@ -134,37 +136,6 @@ static struct weston_log_scope *log_scope; static struct weston_log_scope *protocol_scope; static int cached_tm_mday = -1; -static char * -weston_log_timestamp(char *buf, size_t len) -{ - struct timeval tv; - struct tm *brokendown_time; - char datestr[128]; - char timestr[128]; - - gettimeofday(&tv, NULL); - - brokendown_time = localtime(&tv.tv_sec); - if (brokendown_time == NULL) { - snprintf(buf, len, "%s", "[(NULL)localtime] "); - return buf; - } - - memset(datestr, 0, sizeof(datestr)); - if (brokendown_time->tm_mday != cached_tm_mday) { - strftime(datestr, sizeof(datestr), "Date: %Y-%m-%d %Z\n", - brokendown_time); - cached_tm_mday = brokendown_time->tm_mday; - } - - strftime(timestr, sizeof(timestr), "%H:%M:%S", brokendown_time); - /* if datestr is empty it prints only timestr*/ - snprintf(buf, len, "%s[%s.%03li]", datestr, - timestr, (tv.tv_usec / 1000)); - - return buf; -} - static void custom_handler(const char *fmt, va_list arg) { @@ -172,7 +143,7 @@ custom_handler(const char *fmt, va_list arg) weston_log_scope_printf(log_scope, "%s libwayland: ", weston_log_timestamp(timestr, - sizeof(timestr))); + sizeof(timestr), &cached_tm_mday)); weston_log_scope_vprintf(log_scope, fmt, arg); } @@ -218,7 +189,8 @@ vlog(const char *fmt, va_list ap) if (weston_log_scope_is_enabled(log_scope)) { int len_va; char *log_timestamp = weston_log_timestamp(timestr, - sizeof(timestr)); + sizeof(timestr), + &cached_tm_mday); len_va = vasprintf(&str, fmt, ap); if (len_va >= 0) { len = weston_log_scope_printf(log_scope, "%s %s", @@ -270,6 +242,8 @@ protocol_log_fn(void *user_data, size_t logsize; char timestr[128]; struct wl_resource *res = message->resource; + struct wl_client *client = wl_resource_get_client(res); + pid_t pid = 0; const char *signature = message->message->signature; int i; char type; @@ -281,10 +255,12 @@ protocol_log_fn(void *user_data, if (!fp) return; + wl_client_get_credentials(client, &pid, NULL, NULL); + weston_log_scope_timestamp(protocol_scope, timestr, sizeof timestr); fprintf(fp, "%s ", timestr); - fprintf(fp, "client %p %s ", wl_resource_get_client(res), + fprintf(fp, "client %p (PID %d) %s ", client, pid, direction == WL_PROTOCOL_LOGGER_REQUEST ? "rq" : "ev"); fprintf(fp, "%s@%u.%s(", wl_resource_get_class(res), @@ -382,6 +358,7 @@ sigchld_handler(int signal_number, void *data) } wl_list_remove(&p->link); + wl_list_init(&p->link); p->cleanup(p, status); } @@ -391,87 +368,111 @@ sigchld_handler(int signal_number, void *data) return 1; } -static void -child_client_exec(int sockfd, const char *path) -{ - int clientfd; - char s[32]; - sigset_t allsigs; - - /* do not give our signal mask to the new process */ - sigfillset(&allsigs); - sigprocmask(SIG_UNBLOCK, &allsigs, NULL); - - /* Launch clients as the user. Do not launch clients with wrong euid. */ - if (seteuid(getuid()) == -1) { - weston_log("compositor: failed seteuid\n"); - return; - } - - /* SOCK_CLOEXEC closes both ends, so we dup the fd to get a - * non-CLOEXEC fd to pass through exec. */ - clientfd = dup(sockfd); - if (clientfd == -1) { - weston_log("compositor: dup failed: %s\n", strerror(errno)); - return; - } - - snprintf(s, sizeof s, "%d", clientfd); - setenv("WAYLAND_SOCKET", s, 1); - - if (execl(path, path, NULL) < 0) - weston_log("compositor: executing '%s' failed: %s\n", - path, strerror(errno)); -} - WL_EXPORT struct wl_client * weston_client_launch(struct weston_compositor *compositor, struct weston_process *proc, const char *path, weston_process_cleanup_func_t cleanup) { - int sv[2]; + struct wl_client *client = NULL; + struct custom_env child_env; + struct fdstr wayland_socket; + const char *fail_cloexec = "Couldn't unset CLOEXEC on client socket"; + const char *fail_seteuid = "Couldn't call seteuid"; + char *fail_exec; + char * const *argp; + char * const *envp; + sigset_t allsigs; pid_t pid; - struct wl_client *client; + bool ret; + size_t written __attribute__((unused)); weston_log("launching '%s'\n", path); + str_printf(&fail_exec, "Error: Couldn't launch client '%s'\n", path); - if (os_socketpair_cloexec(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { + custom_env_init_from_environ(&child_env); + custom_env_add_arg(&child_env, path); + + if (os_socketpair_cloexec(AF_UNIX, SOCK_STREAM, 0, + wayland_socket.fds) < 0) { weston_log("weston_client_launch: " "socketpair failed while launching '%s': %s\n", path, strerror(errno)); + custom_env_fini(&child_env); return NULL; } + fdstr_update_str1(&wayland_socket); + custom_env_set_env_var(&child_env, "WAYLAND_SOCKET", + wayland_socket.str1); + + argp = custom_env_get_argp(&child_env); + envp = custom_env_get_envp(&child_env); pid = fork(); - if (pid == -1) { - close(sv[0]); - close(sv[1]); - weston_log("weston_client_launch: " - "fork failed while launching '%s': %s\n", path, - strerror(errno)); - return NULL; - } + switch (pid) { + case 0: + /* Put the client in a new session so it won't catch signals + * intended for the parent. Sharing a session can be + * confusing when launching weston under gdb, as the ctrl-c + * intended for gdb will pass to the child, and weston + * will cleanly shut down when the child exits. + */ + setsid(); - if (pid == 0) { - child_client_exec(sv[1], path); - _exit(-1); - } + /* do not give our signal mask to the new process */ + sigfillset(&allsigs); + sigprocmask(SIG_UNBLOCK, &allsigs, NULL); - close(sv[1]); + /* Launch clients as the user. Do not launch clients with wrong euid. */ + if (seteuid(getuid()) == -1) { + written = write(STDERR_FILENO, fail_seteuid, + strlen(fail_seteuid)); + _exit(EXIT_FAILURE); + } + + ret = fdstr_clear_cloexec_fd1(&wayland_socket); + if (!ret) { + written = write(STDERR_FILENO, fail_cloexec, + strlen(fail_cloexec)); + _exit(EXIT_FAILURE); + } - client = wl_client_create(compositor->wl_display, sv[0]); - if (!client) { - close(sv[0]); + execve(argp[0], argp, envp); + + if (fail_exec) + written = write(STDERR_FILENO, fail_exec, + strlen(fail_exec)); + _exit(EXIT_FAILURE); + + default: + close(wayland_socket.fds[1]); + client = wl_client_create(compositor->wl_display, + wayland_socket.fds[0]); + if (!client) { + custom_env_fini(&child_env); + close(wayland_socket.fds[0]); + free(fail_exec); + weston_log("weston_client_launch: " + "wl_client_create failed while launching '%s'.\n", + path); + return NULL; + } + + proc->pid = pid; + proc->cleanup = cleanup; + wet_watch_process(compositor, proc); + break; + + case -1: + fdstr_close_all(&wayland_socket); weston_log("weston_client_launch: " - "wl_client_create failed while launching '%s'.\n", - path); - return NULL; + "fork failed while launching '%s': %s\n", path, + strerror(errno)); + break; } - proc->pid = pid; - proc->cleanup = cleanup; - wet_watch_process(compositor, proc); + custom_env_fini(&child_env); + free(fail_exec); return client; } @@ -646,9 +647,6 @@ usage(int error_code) #if defined(BUILD_DRM_COMPOSITOR) "\t\t\t\tdrm-backend.so\n" #endif -#if defined(BUILD_FBDEV_COMPOSITOR) - "\t\t\t\tfbdev-backend.so\n" -#endif #if defined(BUILD_HEADLESS_COMPOSITOR) "\t\t\t\theadless-backend.so\n" #endif @@ -685,22 +683,12 @@ usage(int error_code) fprintf(out, "Options for drm-backend.so:\n\n" " --seat=SEAT\t\tThe seat that weston should run on, instead of the seat defined in XDG_SEAT\n" - " --tty=TTY\t\tThe tty to use\n" " --drm-device=CARD\tThe DRM device to use, e.g. \"card0\".\n" " --use-pixman\t\tUse the pixman (CPU) renderer\n" " --current-mode\tPrefer current KMS mode over EDID preferred mode\n" " --continue-without-input\tAllow the compositor to start without input devices\n\n"); #endif -#if defined(BUILD_FBDEV_COMPOSITOR) - fprintf(out, - "Options for fbdev-backend.so:\n\n" - " --tty=TTY\t\tThe tty to use\n" - " --device=DEVICE\tThe framebuffer device to use\n" - " --seat=SEAT\t\tThe seat that weston should run on, instead of the seat defined in XDG_SEAT\n" - "\n"); -#endif - #if defined(BUILD_HEADLESS_COMPOSITOR) fprintf(out, "Options for headless-backend.so:\n\n" @@ -721,6 +709,7 @@ usage(int error_code) " --width=WIDTH\t\tWidth of desktop\n" " --height=HEIGHT\tHeight of desktop\n" " --env-socket\t\tUse socket defined in RDP_FD env variable as peer connection\n" + " --external-listener-fd=FD\tUse socket as listener connection\n" " --address=ADDR\tThe address to bind\n" " --port=PORT\t\tThe port to listen on\n" " --no-clients-resize\tThe RDP peers will be forced to the size of the desktop\n" @@ -869,7 +858,7 @@ handle_primary_client_destroyed(struct wl_listener *listener, void *data) static int weston_create_listening_socket(struct wl_display *display, const char *socket_name) { - char name_candidate[16]; + char name_candidate[32]; if (socket_name) { if (wl_display_add_socket(display, socket_name)) { @@ -1002,7 +991,7 @@ wet_get_bindir_path(const char *name) static int load_modules(struct weston_compositor *ec, const char *modules, - int *argc, char *argv[], bool *xwayland) + int *argc, char *argv[]) { const char *p, *end; char buffer[256]; @@ -1016,11 +1005,11 @@ load_modules(struct weston_compositor *ec, const char *modules, snprintf(buffer, sizeof buffer, "%.*s", (int) (end - p), p); if (strstr(buffer, "xwayland.so")) { - weston_log("Old Xwayland module loading detected: " + weston_log("fatal: Old Xwayland module loading detected: " "Please use --xwayland command line option " "or set xwayland=true in the [core] section " "in weston.ini\n"); - *xwayland = true; + return -1; } else { if (wet_load_module(ec, buffer, argc, argv) < 0) return -1; @@ -1348,6 +1337,223 @@ wet_output_set_color_profile(struct weston_output *output, return ok ? 0 : -1; } +static int +wet_output_set_eotf_mode(struct weston_output *output, + struct weston_config_section *section) +{ + static const struct { + const char *name; + enum weston_eotf_mode eotf_mode; + } modes[] = { + { "sdr", WESTON_EOTF_MODE_SDR }, + { "hdr-gamma", WESTON_EOTF_MODE_TRADITIONAL_HDR }, + { "st2084", WESTON_EOTF_MODE_ST2084 }, + { "hlg", WESTON_EOTF_MODE_HLG }, + }; + struct wet_compositor *compositor; + enum weston_eotf_mode eotf_mode = WESTON_EOTF_MODE_SDR; + char *str = NULL; + unsigned i; + + compositor = to_wet_compositor(output->compositor); + + if (section) { + weston_config_section_get_string(section, "eotf-mode", + &str, NULL); + } + + if (!str) { + /* The default SDR mode is always supported. */ + assert(weston_output_get_supported_eotf_modes(output) & eotf_mode); + weston_output_set_eotf_mode(output, eotf_mode); + return 0; + } + + for (i = 0; i < ARRAY_LENGTH(modes); i++) + if (strcmp(str, modes[i].name) == 0) + break; + + if (i == ARRAY_LENGTH(modes)) { + weston_log("Error in config for output '%s': '%s' is not a valid EOTF mode. Try one of:", + output->name, str); + for (i = 0; i < ARRAY_LENGTH(modes); i++) + weston_log_continue(" %s", modes[i].name); + weston_log_continue("\n"); + return -1; + } + eotf_mode = modes[i].eotf_mode; + + if ((weston_output_get_supported_eotf_modes(output) & eotf_mode) == 0) { + weston_log("Error: output '%s' does not support EOTF mode %s.\n", + output->name, str); + free(str); + return -1; + } + + if (eotf_mode != WESTON_EOTF_MODE_SDR && + !compositor->use_color_manager) { + weston_log("Error: EOTF mode %s on output '%s' requires color-management=true in weston.ini\n", + str, output->name); + free(str); + return -1; + } + + weston_output_set_eotf_mode(output, eotf_mode); + + free(str); + return 0; +} + +struct wet_color_characteristics_keys { + const char *name; + enum weston_color_characteristics_groups group; + float minval; + float maxval; +}; + +#define COLOR_CHARAC_NAME "color_characteristics" + +static int +parse_color_characteristics(struct weston_color_characteristics *cc_out, + struct weston_config_section *section) +{ + static const struct wet_color_characteristics_keys keys[] = { + { "red_x", WESTON_COLOR_CHARACTERISTICS_GROUP_PRIMARIES, 0.0f, 1.0f }, + { "red_y", WESTON_COLOR_CHARACTERISTICS_GROUP_PRIMARIES, 0.0f, 1.0f }, + { "green_x", WESTON_COLOR_CHARACTERISTICS_GROUP_PRIMARIES, 0.0f, 1.0f }, + { "green_y", WESTON_COLOR_CHARACTERISTICS_GROUP_PRIMARIES, 0.0f, 1.0f }, + { "blue_x", WESTON_COLOR_CHARACTERISTICS_GROUP_PRIMARIES, 0.0f, 1.0f }, + { "blue_y", WESTON_COLOR_CHARACTERISTICS_GROUP_PRIMARIES, 0.0f, 1.0f }, + { "white_x", WESTON_COLOR_CHARACTERISTICS_GROUP_WHITE, 0.0f, 1.0f }, + { "white_y", WESTON_COLOR_CHARACTERISTICS_GROUP_WHITE, 0.0f, 1.0f }, + { "max_L", WESTON_COLOR_CHARACTERISTICS_GROUP_MAXL, 0.0f, 1e5f }, + { "min_L", WESTON_COLOR_CHARACTERISTICS_GROUP_MINL, 0.0f, 1e5f }, + { "maxFALL", WESTON_COLOR_CHARACTERISTICS_GROUP_MAXFALL, 0.0f, 1e5f }, + }; + static const char *msgpfx = "Config error in weston.ini [" COLOR_CHARAC_NAME "]"; + struct weston_color_characteristics cc = {}; + float *const keyvalp[ARRAY_LENGTH(keys)] = { + /* These must be in the same order as keys[]. */ + &cc.primary[0].x, &cc.primary[0].y, + &cc.primary[1].x, &cc.primary[1].y, + &cc.primary[2].x, &cc.primary[2].y, + &cc.white.x, &cc.white.y, + &cc.max_luminance, + &cc.min_luminance, + &cc.maxFALL, + }; + bool found[ARRAY_LENGTH(keys)] = {}; + uint32_t missing_group_mask = 0; + unsigned i; + char *section_name; + int ret = 0; + + weston_config_section_get_string(section, "name", + §ion_name, ""); + if (strchr(section_name, ':') != NULL) { + ret = -1; + weston_log("%s name=%s: reserved name. Do not use ':' character in the name.\n", + msgpfx, section_name); + } + + /* Parse keys if they exist */ + for (i = 0; i < ARRAY_LENGTH(keys); i++) { + double value; + + if (weston_config_section_get_double(section, keys[i].name, + &value, NAN) == 0) { + float f = value; + + found[i] = true; + + /* Range check, NaN shall not pass. */ + if (f >= keys[i].minval && f <= keys[i].maxval) { + /* Key found, parsed, and good value. */ + *keyvalp[i] = f; + continue; + } + + ret = -1; + weston_log("%s name=%s: %s value %f is outside of the range %f - %f.\n", + msgpfx, section_name, keys[i].name, value, + keys[i].minval, keys[i].maxval); + continue; + } + + if (errno == EINVAL) { + found[i] = true; + ret = -1; + weston_log("%s name=%s: failed to parse the value of key %s.\n", + msgpfx, section_name, keys[i].name); + } + } + + /* Collect set and unset groups */ + for (i = 0; i < ARRAY_LENGTH(keys); i++) { + uint32_t group = keys[i].group; + + if (found[i]) + cc.group_mask |= group; + else + missing_group_mask |= group; + } + + /* Ensure groups are given fully or not at all. */ + for (i = 0; i < ARRAY_LENGTH(keys); i++) { + uint32_t group = keys[i].group; + + if ((cc.group_mask & group) && (missing_group_mask & group)) { + ret = -1; + weston_log("%s name=%s: group %d key %s is %s. " + "You must set either none or all keys of a group.\n", + msgpfx, section_name, ffs(group), keys[i].name, + found[i] ? "set" : "missing"); + } + } + + free(section_name); + + if (ret == 0) + *cc_out = cc; + + return ret; +} + +WESTON_EXPORT_FOR_TESTS int +wet_output_set_color_characteristics(struct weston_output *output, + struct weston_config *wc, + struct weston_config_section *section) +{ + char *cc_name = NULL; + struct weston_config_section *cc_section; + struct weston_color_characteristics cc; + + weston_config_section_get_string(section, COLOR_CHARAC_NAME, + &cc_name, NULL); + if (!cc_name) + return 0; + + cc_section = weston_config_get_section(wc, COLOR_CHARAC_NAME, + "name", cc_name); + if (!cc_section) { + weston_log("Config error in weston.ini, output %s: " + "no [" COLOR_CHARAC_NAME "] section with 'name=%s' found.\n", + output->name, cc_name); + goto out_error; + } + + if (parse_color_characteristics(&cc, cc_section) < 0) + goto out_error; + + weston_output_set_color_characteristics(output, &cc); + free(cc_name); + return 0; + +out_error: + free(cc_name); + return -1; +} + static void allow_content_protection(struct weston_output *output, struct weston_config_section *section) @@ -1503,14 +1709,46 @@ wet_head_tracker_create(struct wet_compositor *compositor, weston_head_add_destroy_listener(head, &track->head_destroy_listener); } +/* Place output exactly to the right of the most recently enabled output. + * + * Historically, we haven't given much thought to output placement, + * simply adding outputs in a horizontal line as they're enabled. This + * function simply sets an output's x coordinate to the right of the + * most recently enabled output, and its y to zero. + * + * If you're adding new calls to this function, you're also not giving + * much thought to output placement, so please consider carefully if + * it's really doing what you want. + * + * You especially don't want to use this for any code that won't + * immediately enable the passed output. + */ +static void +weston_output_lazy_align(struct weston_output *output) +{ + struct weston_compositor *c; + struct weston_output *peer; + int next_x = 0; + + /* Put this output to the right of the most recently enabled output */ + c = output->compositor; + if (!wl_list_empty(&c->output_list)) { + peer = container_of(c->output_list.prev, + struct weston_output, link); + next_x = peer->x + peer->width; + } + output->x = next_x; + output->y = 0; +} + static void simple_head_enable(struct wet_compositor *wet, struct weston_head *head) { struct weston_output *output; int ret = 0; - output = weston_compositor_create_output_with_head(wet->compositor, - head); + output = weston_compositor_create_output(wet->compositor, head, + head->name); if (!output) { weston_log("Could not create an output for head \"%s\".\n", weston_head_get_name(head)); @@ -1519,6 +1757,8 @@ simple_head_enable(struct wet_compositor *wet, struct weston_head *head) return; } + weston_output_lazy_align(output); + if (wet->simple_output_configure) ret = wet->simple_output_configure(output); if (ret < 0) { @@ -1813,6 +2053,8 @@ drm_backend_output_configure(struct weston_output *output, enum weston_drm_backend_output_mode mode = WESTON_DRM_BACKEND_OUTPUT_PREFERRED; uint32_t transform = WL_OUTPUT_TRANSFORM_NORMAL; + uint32_t max_bpc = 0; + bool max_bpc_specified = false; char *s; char *modeline = NULL; char *gbm_format = NULL; @@ -1825,12 +2067,18 @@ drm_backend_output_configure(struct weston_output *output, } weston_config_section_get_string(section, "mode", &s, "preferred"); + if (weston_config_section_get_uint(section, "max-bpc", &max_bpc, 16) == 0) + max_bpc_specified = true; if (strcmp(s, "off") == 0) { assert(0 && "off was supposed to be pruned"); return -1; } else if (wet->drm_use_current_mode || strcmp(s, "current") == 0) { mode = WESTON_DRM_BACKEND_OUTPUT_CURRENT; + /* If mode=current and no max-bpc was specfied on the .ini file, + use current max_bpc so full modeset is not done. */ + if (!max_bpc_specified) + max_bpc = 0; } else if (strcmp(s, "preferred") != 0) { modeline = s; s = NULL; @@ -1844,6 +2092,8 @@ drm_backend_output_configure(struct weston_output *output, } free(modeline); + api->set_max_bpc(output, max_bpc); + if (count_remaining_heads(output, NULL) == 1) { struct weston_head *head = weston_output_get_first_head(output); transform = weston_head_get_transform(head); @@ -1871,6 +2121,13 @@ drm_backend_output_configure(struct weston_output *output, allow_content_protection(output, section); + if (wet_output_set_eotf_mode(output, section) < 0) + return -1; + + if (wet_output_set_color_characteristics(output, + wet->config, section) < 0) + return -1; + return 0; } @@ -1958,7 +2215,9 @@ wet_output_handle_destroy(struct wl_listener *listener, void *data) } static struct wet_output * -wet_layoutput_create_output(struct wet_layoutput *lo, const char *name) +wet_layoutput_create_output_with_head(struct wet_layoutput *lo, + const char *name, + struct weston_head *head) { struct wet_output *output; @@ -1968,7 +2227,7 @@ wet_layoutput_create_output(struct wet_layoutput *lo, const char *name) output->output = weston_compositor_create_output(lo->compositor->compositor, - name); + head, name); if (!output->output) { free(output); return NULL; @@ -2121,8 +2380,8 @@ drm_try_attach(struct weston_output *output, { unsigned i; - /* try to attach all heads, this probably succeeds */ - for (i = 0; i < add->n; i++) { + /* try to attach remaining heads, this probably succeeds */ + for (i = 1; i < add->n; i++) { if (!add->heads[i]) continue; @@ -2142,6 +2401,8 @@ drm_try_enable(struct weston_output *output, { /* Try to enable, and detach heads one by one until it succeeds. */ while (!output->enabled) { + weston_output_lazy_align(output); + if (weston_output_enable(output) == 0) return 0; @@ -2238,7 +2499,8 @@ drm_process_layoutput(struct wet_compositor *wet, struct wet_layoutput *lo) if (ret < 0) return -1; } - output = wet_layoutput_create_output(lo, name); + output = wet_layoutput_create_output_with_head(lo, name, + lo->add.heads[0]); free(name); name = NULL; @@ -2625,17 +2887,26 @@ load_drm_backend(struct weston_compositor *c, struct weston_config_section *section; struct wet_compositor *wet = to_wet_compositor(c); bool without_input = false; + bool use_pixman_default; int ret = 0; wet->drm_use_current_mode = false; section = weston_config_get_section(wc, "core", NULL, NULL); + + /* Use the pixman renderer by default when GBM/EGL support is + * not enabled */ +#if defined(BUILD_DRM_GBM) + use_pixman_default = false; +#else + use_pixman_default = true; +#endif + weston_config_section_get_bool(section, "use-pixman", &config.use_pixman, - false); + use_pixman_default); const struct weston_option options[] = { { WESTON_OPTION_STRING, "seat", 0, &config.seat_id }, - { WESTON_OPTION_INTEGER, "tty", 0, &config.tty }, { WESTON_OPTION_STRING, "drm-device", 0, &config.specific_device }, { WESTON_OPTION_BOOLEAN, "current-mode", 0, &wet->drm_use_current_mode }, { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, @@ -2688,6 +2959,15 @@ headless_backend_output_configure(struct weston_output *output) .scale = 1, .transform = WL_OUTPUT_TRANSFORM_NORMAL }; + struct weston_config *wc = wet_get_config(output->compositor); + struct weston_config_section *section; + + section = weston_config_get_section(wc, "output", "name", output->name); + if (wet_output_set_eotf_mode(output, section) < 0) + return -1; + + if (wet_output_set_color_characteristics(output, wc, section) < 0) + return -1; return wet_configure_windowed_output_from_config(output, &defaults); } @@ -2806,8 +3086,11 @@ weston_rdp_backend_config_init(struct weston_rdp_backend_config *config) config->server_cert = NULL; config->server_key = NULL; config->env_socket = 0; + config->external_listener_fd = -1; config->no_clients_resize = 0; config->force_no_compression = 0; + config->remotefx_codec = true; + config->refresh_rate = RDP_DEFAULT_FREQ; } static int @@ -2815,7 +3098,9 @@ load_rdp_backend(struct weston_compositor *c, int *argc, char *argv[], struct weston_config *wc) { struct weston_rdp_backend_config config = {{ 0, }}; + struct weston_config_section *section; int ret = 0; + bool no_remotefx_codec = false; struct wet_output_config *parsed_options = wet_init_parsed_options(c); if (!parsed_options) @@ -2825,6 +3110,7 @@ load_rdp_backend(struct weston_compositor *c, const struct weston_option rdp_options[] = { { WESTON_OPTION_BOOLEAN, "env-socket", 0, &config.env_socket }, + { WESTON_OPTION_INTEGER, "external-listener-fd", 0, &config.external_listener_fd }, { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, { WESTON_OPTION_STRING, "address", 0, &config.bind_address }, @@ -2834,11 +3120,17 @@ load_rdp_backend(struct weston_compositor *c, { WESTON_OPTION_STRING, "rdp-tls-cert", 0, &config.server_cert }, { WESTON_OPTION_STRING, "rdp-tls-key", 0, &config.server_key }, { WESTON_OPTION_BOOLEAN, "force-no-compression", 0, &config.force_no_compression }, + { WESTON_OPTION_BOOLEAN, "no-remotefx-codec", 0, &no_remotefx_codec }, }; parse_options(rdp_options, ARRAY_LENGTH(rdp_options), argc, argv); + config.remotefx_codec = !no_remotefx_codec; wet_set_simple_head_configurator(c, rdp_backend_output_configure); + section = weston_config_get_section(wc, "rdp", NULL, NULL); + weston_config_section_get_int(section, "refresh-rate", + &config.refresh_rate, + RDP_DEFAULT_FREQ); ret = weston_compositor_load_backend(c, WESTON_BACKEND_RDP, &config.base); @@ -2851,54 +3143,6 @@ load_rdp_backend(struct weston_compositor *c, return ret; } -static int -fbdev_backend_output_configure(struct weston_output *output) -{ - struct weston_config *wc = wet_get_config(output->compositor); - struct weston_config_section *section; - - section = weston_config_get_section(wc, "output", "name", "fbdev"); - - if (wet_output_set_transform(output, section, - WL_OUTPUT_TRANSFORM_NORMAL, - UINT32_MAX) < 0) { - return -1; - } - - weston_output_set_scale(output, 1); - - return 0; -} - -static int -load_fbdev_backend(struct weston_compositor *c, - int *argc, char **argv, struct weston_config *wc) -{ - struct weston_fbdev_backend_config config = {{ 0, }}; - int ret = 0; - - const struct weston_option fbdev_options[] = { - { WESTON_OPTION_INTEGER, "tty", 0, &config.tty }, - { WESTON_OPTION_STRING, "device", 0, &config.device }, - { WESTON_OPTION_STRING, "seat", 0, &config.seat_id }, - }; - - parse_options(fbdev_options, ARRAY_LENGTH(fbdev_options), argc, argv); - - config.base.struct_version = WESTON_FBDEV_BACKEND_CONFIG_VERSION; - config.base.struct_size = sizeof(struct weston_fbdev_backend_config); - config.configure_device = configure_input_device; - - wet_set_simple_head_configurator(c, fbdev_backend_output_configure); - - /* load the actual wayland backend and configure it */ - ret = weston_compositor_load_backend(c, WESTON_BACKEND_FBDEV, - &config.base); - - free(config.device); - return ret; -} - static int x11_backend_output_configure(struct weston_output *output) { @@ -3144,8 +3388,6 @@ load_backend(struct weston_compositor *compositor, const char *backend, return load_headless_backend(compositor, argc, argv, config); else if (strstr(backend, "rdp-backend.so")) return load_rdp_backend(compositor, argc, argv, config); - else if (strstr(backend, "fbdev-backend.so")) - return load_fbdev_backend(compositor, argc, argv, config); else if (strstr(backend, "drm-backend.so")) return load_drm_backend(compositor, argc, argv, config); else if (strstr(backend, "x11-backend.so")) @@ -3266,13 +3508,19 @@ weston_log_subscribe_to_scopes(struct weston_log_context *log_ctx, weston_log_setup_scopes(log_ctx, flight_rec, flight_rec_scopes); } +static void +sigint_helper(int sig) +{ + raise(SIGUSR2); +} + WL_EXPORT int wet_main(int argc, char *argv[], const struct weston_testsuite_data *test_data) { int ret = EXIT_FAILURE; char *cmdline; struct wl_display *display; - struct wl_event_source *signals[4]; + struct wl_event_source *signals[3]; struct wl_event_loop *loop; int i, fd; char *backend = NULL; @@ -3302,6 +3550,7 @@ wet_main(int argc, char *argv[], const struct weston_testsuite_data *test_data) struct weston_log_subscriber *logger = NULL; struct weston_log_subscriber *flight_rec = NULL; sigset_t mask; + struct sigaction action; bool wait_for_debugger = false; struct wl_protocol_logger *protologger = NULL; @@ -3392,16 +3641,28 @@ wet_main(int argc, char *argv[], const struct weston_testsuite_data *test_data) loop = wl_display_get_event_loop(display); signals[0] = wl_event_loop_add_signal(loop, SIGTERM, on_term_signal, display); - signals[1] = wl_event_loop_add_signal(loop, SIGINT, on_term_signal, - display); - signals[2] = wl_event_loop_add_signal(loop, SIGQUIT, on_term_signal, + signals[1] = wl_event_loop_add_signal(loop, SIGUSR2, on_term_signal, display); wl_list_init(&wet.child_process_list); - signals[3] = wl_event_loop_add_signal(loop, SIGCHLD, sigchld_handler, + signals[2] = wl_event_loop_add_signal(loop, SIGCHLD, sigchld_handler, &wet); - if (!signals[0] || !signals[1] || !signals[2] || !signals[3]) + /* When debugging weston, if use wl_event_loop_add_signal() to catch + * SIGINT, the debugger can't catch it, and attempting to stop + * weston from within the debugger results in weston exiting + * cleanly. + * + * Instead, use the sigaction() function, which sets up the signal + * in a way that gdb can successfully catch, but have the handler + * for SIGINT send SIGUSR2 (xwayland uses SIGUSR1), which we catch + * via wl_event_loop_add_signal(). + */ + action.sa_handler = sigint_helper; + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + sigaction(SIGINT, &action, NULL); + if (!signals[0] || !signals[1] || !signals[2]) goto out_signals; /* Xwayland uses SIGUSR1 for communicating with weston. Since some @@ -3522,13 +3783,10 @@ wet_main(int argc, char *argv[], const struct weston_testsuite_data *test_data) if (wet_load_shell(wet.compositor, shell, &argc, argv) < 0) goto out; - weston_config_section_get_string(section, "modules", &modules, ""); - if (load_modules(wet.compositor, modules, &argc, argv, &xwayland) < 0) - goto out; - - if (load_modules(wet.compositor, option_modules, &argc, argv, &xwayland) < 0) - goto out; - + /* Load xwayland before other modules - this way if we're using + * the systemd-notify module it will notify after we're ready + * to receive xwayland connections. + */ if (!xwayland) { weston_config_section_get_bool(section, "xwayland", &xwayland, false); @@ -3538,6 +3796,13 @@ wet_main(int argc, char *argv[], const struct weston_testsuite_data *test_data) goto out; } + weston_config_section_get_string(section, "modules", &modules, ""); + if (load_modules(wet.compositor, modules, &argc, argv) < 0) + goto out; + + if (load_modules(wet.compositor, option_modules, &argc, argv) < 0) + goto out; + section = weston_config_get_section(config, "keyboard", NULL, NULL); weston_config_section_get_bool(section, "numlock-on", &numlock_on, false); if (numlock_on) { @@ -3573,8 +3838,6 @@ wet_main(int argc, char *argv[], const struct weston_testsuite_data *test_data) ret = wet.compositor->exit_code; out: - wet_compositor_destroy_layout(&wet); - /* free(NULL) is valid, and it won't be NULL if it's used */ free(wet.parsed_options); @@ -3582,6 +3845,7 @@ out: wl_protocol_logger_destroy(protologger); weston_compositor_destroy(wet.compositor); + wet_compositor_destroy_layout(&wet); weston_log_scope_destroy(protocol_scope); protocol_scope = NULL; diff --git a/compositor/meson.build b/compositor/meson.build index 8a54ea99..1692481c 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -78,6 +78,7 @@ if get_option('screenshare') deps_screenshare = [ dep_libexec_weston, dep_libshared, + dep_shell_utils, dep_libweston_public, dep_libweston_private_h, # XXX: https://gitlab.freedesktop.org/wayland/weston/issues/292 dep_wayland_client, @@ -95,19 +96,18 @@ if get_option('screenshare') env_modmap += 'screen-share.so=@0@;'.format(plugin_screenshare.full_path()) endif -if get_option('color-management-lcms') - config_h.set('HAVE_LCMS', '1') - +if get_option('deprecated-color-management-static') srcs_lcms = [ 'cms-static.c', 'cms-helper.c', ] - dep_lcms2 = dependency('lcms2', required: false) if not dep_lcms2.found() - error('cms-static requires lcms2 which was not found. Or, you can use \'-Dcolor-management-lcms=false\'.') + error('cms-static requires lcms2 which was not found. Or, you can use \'-Ddeprecated-color-management-static=false\'.') endif + config_h.set('HAVE_LCMS', '1') + plugin_lcms = shared_library( 'cms-static', srcs_lcms, @@ -119,11 +119,13 @@ if get_option('color-management-lcms') install_rpath: '$ORIGIN' ) env_modmap += 'cms-static.so=@0@;'.format(plugin_lcms.full_path()) + + warning('deprecated-color-management-static is enabled. This will go away, see https://gitlab.freedesktop.org/wayland/weston/-/issues/634') endif -if get_option('color-management-colord') - if not get_option('color-management-lcms') - error('LCMS must be enabled to support colord. Or, you can use \'-Dcolor-management-colord=false\'.') +if get_option('deprecated-color-management-colord') + if not get_option('deprecated-color-management-static') + error('deprecated-color-management-static must be enabled to support colord. Or, you can use \'-Ddeprecated-color-management-colord=false\'.') endif srcs_colord = [ @@ -133,7 +135,7 @@ if get_option('color-management-colord') dep_colord = dependency('colord', version: '>= 0.1.27', required: false) if not dep_colord.found() - error('cms-colord requires colord >= 0.1.27 which was not found. Or, you can use \'-Dcolor-management-colord=false\'.') + error('cms-colord requires colord >= 0.1.27 which was not found. Or, you can use \'-Ddeprecated-color-management-colord=false\'.') endif plugin_colord_deps = [ dep_libweston_public, dep_colord, dep_lcms2 ] @@ -141,7 +143,7 @@ if get_option('color-management-colord') foreach depname : [ 'glib-2.0', 'gobject-2.0' ] dep = dependency(depname, required: false) if not dep.found() - error('cms-colord requires \'@0@\' which was not found. If you rather not build this, set \'-Dcolor-management-colord=false\'.'.format(depname)) + error('cms-colord requires \'@0@\' which was not found. If you rather not build this, set \'-Ddeprecated-color-management-colord=false\'.'.format(depname)) endif plugin_colord_deps += dep endforeach @@ -156,6 +158,8 @@ if get_option('color-management-colord') install_dir: dir_module_weston ) env_modmap += 'cms-colord.so=@0@;'.format(plugin_colord.full_path()) + + warning('deprecated-color-management-colord is enabled. This will go away, see https://gitlab.freedesktop.org/wayland/weston/-/issues/634') endif if get_option('systemd') diff --git a/compositor/screen-share.c b/compositor/screen-share.c index 8906c6ad..67b0c1e7 100644 --- a/compositor/screen-share.c +++ b/compositor/screen-share.c @@ -43,10 +43,12 @@ #include #include "backend.h" #include "libweston-internal.h" +#include "pixel-formats.h" #include "weston.h" #include "shared/helpers.h" #include "shared/os-compatibility.h" #include "shared/timespec-util.h" +#include "shell-utils/shell-utils.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" struct shared_output { @@ -59,7 +61,7 @@ struct shared_output { struct wl_registry *registry; struct wl_compositor *compositor; struct wl_shm *shm; - uint32_t shm_formats; + bool shm_formats_has_xrgb; struct zwp_fullscreen_shell_v1 *fshell; struct wl_output *output; struct wl_surface *surface; @@ -114,9 +116,7 @@ struct ss_shm_buffer { struct screen_share { struct weston_compositor *compositor; - /* XXX: missing compositor destroy listener - * https://gitlab.freedesktop.org/wayland/weston/issues/298 - */ + struct wl_listener compositor_destroy_listener; char *command; }; @@ -368,7 +368,7 @@ ss_seat_create(struct shared_output *so, uint32_t id) if (seat == NULL) return NULL; - weston_seat_init(&seat->base, so->output->compositor, "default"); + weston_seat_init(&seat->base, so->output->compositor, "screen-share"); seat->output = so; seat->id = id; seat->parent.seat = wl_registry_bind(so->parent.registry, id, @@ -702,7 +702,8 @@ shm_handle_format(void *data, struct wl_shm *wl_shm, uint32_t format) { struct shared_output *so = data; - so->parent.shm_formats |= (1 << format); + if (format == WL_SHM_FORMAT_XRGB8888) + so->parent.shm_formats_has_xrgb = true; } struct wl_shm_listener shm_listener = { @@ -828,6 +829,9 @@ shared_output_repainted(struct wl_listener *listener, void *data) pixman_box32_t *r; pixman_image_t *damaged_image; pixman_transform_t transform; + const struct pixel_format_info *read_format = + so->output->compositor->read_format; + const pixman_format_code_t pixman_format = read_format->pixman_format; width = so->output->current_mode->width; height = so->output->current_mode->height; @@ -882,13 +886,13 @@ shared_output_repainted(struct wl_listener *listener, void *data) y_orig = y; so->output->compositor->renderer->read_pixels( - so->output, PIXMAN_a8r8g8b8, so->tmp_data, - x, y_orig, width, height); + so->output, read_format, + so->tmp_data, x, y_orig, width, height); - damaged_image = pixman_image_create_bits(PIXMAN_a8r8g8b8, + damaged_image = pixman_image_create_bits(pixman_format, width, height, so->tmp_data, - (PIXMAN_FORMAT_BPP(PIXMAN_a8r8g8b8) / 8) * width); + (PIXMAN_FORMAT_BPP(pixman_format) / 8) * width); if (!damaged_image) goto err_pixman_init; @@ -968,7 +972,7 @@ shared_output_create(struct weston_output *output, int parent_fd) /* Get SHM formats */ wl_display_roundtrip(so->parent.display); - if (!(so->parent.shm_formats & (1 << WL_SHM_FORMAT_XRGB8888))) { + if (!so->parent.shm_formats_has_xrgb) { weston_log("Screen share failed: " "WL_SHM_FORMAT_XRGB8888 not available\n"); goto err_display; @@ -1144,22 +1148,37 @@ share_output_binding(struct weston_keyboard *keyboard, struct screen_share *ss = data; pointer = weston_seat_get_pointer(keyboard->seat); - if (!pointer) { - weston_log("Cannot pick output: Seat does not have pointer\n"); - return; + if (pointer) { + output = weston_output_find(pointer->seat->compositor, + wl_fixed_to_int(pointer->x), + wl_fixed_to_int(pointer->y)); + } else { + output = get_focused_output(keyboard->seat->compositor); + if (!output) + output = get_default_output(keyboard->seat->compositor); } - output = weston_output_find(pointer->seat->compositor, - wl_fixed_to_int(pointer->x), - wl_fixed_to_int(pointer->y)); if (!output) { - weston_log("Cannot pick output: Pointer not on any output\n"); + weston_log("Cannot pick output: Pointer not on any output, " + "or no focused/default output found\n"); return; } weston_output_share(output, ss->command); } +static void +compositor_destroy_listener(struct wl_listener *listener, void *data) +{ + struct screen_share *ss = + wl_container_of(listener, ss, compositor_destroy_listener); + + wl_list_remove(&ss->compositor_destroy_listener.link); + + free(ss->command); + free(ss); +} + WL_EXPORT int wet_module_init(struct weston_compositor *compositor, int *argc, char *argv[]) @@ -1175,11 +1194,16 @@ wet_module_init(struct weston_compositor *compositor, return -1; ss->compositor = compositor; + wl_list_init(&ss->compositor_destroy_listener.link); + + ss->compositor_destroy_listener.notify = compositor_destroy_listener; + wl_signal_add(&compositor->destroy_signal, &ss->compositor_destroy_listener); + config = wet_get_config(compositor); section = weston_config_get_section(config, "screen-share", NULL, NULL); - weston_config_section_get_string(section, "command", &ss->command, ""); + weston_config_section_get_string(section, "command", &ss->command, NULL); weston_compositor_add_key_binding(compositor, KEY_S, MODIFIER_CTRL | MODIFIER_ALT, diff --git a/compositor/text-backend.c b/compositor/text-backend.c index 5cd994cb..9e787650 100644 --- a/compositor/text-backend.c +++ b/compositor/text-backend.c @@ -141,6 +141,12 @@ deactivate_input_method(struct input_method *input_method) input_method->input = NULL; input_method->context = NULL; + /* text_input_manager::destroy_listener by compositor shutdown */ + if (!text_input->manager) { + zwp_text_input_v1_send_leave(text_input->resource); + return; + } + if (wl_list_empty(&text_input->input_methods) && text_input->input_panel_visible && text_input->manager->current_text_input == text_input) { @@ -456,6 +462,8 @@ text_input_manager_notifier_destroy(struct wl_listener *listener, void *data) wl_list_remove(&text_input_manager->destroy_listener.link); wl_global_destroy(text_input_manager->text_input_manager_global); + if (text_input_manager->current_text_input) + text_input_manager->current_text_input->manager = NULL; free(text_input_manager); } @@ -949,7 +957,7 @@ input_method_init_seat(struct weston_seat *seat) seat->input_method->focus_listener_initialized = true; } -static void launch_input_method(struct text_backend *text_backend); +static void launch_input_method(void *data); static void respawn_input_method_process(struct text_backend *text_backend) @@ -989,8 +997,10 @@ input_method_client_notifier(struct wl_listener *listener, void *data) } static void -launch_input_method(struct text_backend *text_backend) +launch_input_method(void *data) { + struct text_backend *text_backend = data; + if (!text_backend->input_method.path) return; @@ -1093,6 +1103,7 @@ text_backend_init(struct weston_compositor *ec) { struct text_backend *text_backend; struct weston_seat *seat; + struct wl_event_loop *loop; text_backend = zalloc(sizeof(*text_backend)); if (text_backend == NULL) @@ -1110,7 +1121,8 @@ text_backend_init(struct weston_compositor *ec) text_input_manager_create(ec); - launch_input_method(text_backend); + loop = wl_display_get_event_loop(ec->wl_display); + wl_event_loop_add_idle(loop, launch_input_method, text_backend); return text_backend; } diff --git a/shared/xalloc.c b/compositor/weston-private.h similarity index 68% rename from shared/xalloc.c rename to compositor/weston-private.h index 1cc5c12a..01b37dcf 100644 --- a/shared/xalloc.c +++ b/compositor/weston-private.h @@ -1,5 +1,5 @@ /* - * Copyright © 2008 Kristian Høgsberg + * Copyright 2022 Collabora, Ltd. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -23,28 +23,12 @@ * SOFTWARE. */ -#include "config.h" +#pragma once -#include -#include -#include -#include +#include +#include -#include "xalloc.h" - -void * -fail_on_null(void *p, size_t size, char *file, int32_t line) -{ - if (p == NULL) { - fprintf(stderr, "[%s] ", program_invocation_short_name); - if (file) - fprintf(stderr, "%s:%d: ", file, line); - fprintf(stderr, "out of memory"); - if (size) - fprintf(stderr, " (%zd)", size); - fprintf(stderr, "\n"); - exit(EXIT_FAILURE); - } - - return p; -} +int +wet_output_set_color_characteristics(struct weston_output *output, + struct weston_config *wc, + struct weston_config_section *section); diff --git a/compositor/weston-screenshooter.c b/compositor/weston-screenshooter.c index 18b2dca4..9b437d3c 100644 --- a/compositor/weston-screenshooter.c +++ b/compositor/weston-screenshooter.c @@ -68,8 +68,9 @@ screenshooter_take_shot(struct wl_client *client, { struct weston_output *output = weston_head_from_resource(output_resource)->output; + struct weston_compositor *ec = output->compositor; struct weston_buffer *buffer = - weston_buffer_from_resource(buffer_resource); + weston_buffer_from_resource(ec, buffer_resource); if (buffer == NULL) { wl_resource_post_no_memory(resource); diff --git a/compositor/xwayland.c b/compositor/xwayland.c index 8cdf0555..ea1ae1ef 100644 --- a/compositor/xwayland.c +++ b/compositor/xwayland.c @@ -30,34 +30,65 @@ #include #include #include +#include #include #include "compositor/weston.h" #include #include "shared/helpers.h" +#include "shared/os-compatibility.h" +#include "shared/process-util.h" +#include "shared/string-helpers.h" + +#ifdef HAVE_XWAYLAND_LISTENFD +# define LISTEN_STR "-listenfd" +#else +# define LISTEN_STR "-listen" +#endif struct wet_xwayland { struct weston_compositor *compositor; + struct wl_listener compositor_destroy_listener; const struct weston_xwayland_api *api; struct weston_xwayland *xwayland; - struct wl_event_source *sigusr1_source; + struct wl_event_source *display_fd_source; struct wl_client *client; int wm_fd; struct weston_process process; }; static int -handle_sigusr1(int signal_number, void *data) +handle_display_fd(int fd, uint32_t mask, void *data) { struct wet_xwayland *wxw = data; + char buf[64]; + ssize_t n; + + /* xwayland exited before being ready, don't finish initialization, + * the process watcher will cleanup */ + if (!(mask & WL_EVENT_READABLE)) + goto out; + + /* Xwayland writes to the pipe twice, so if we close it too early + * it's possible the second write will fail and Xwayland shuts down. + * Make sure we read until end of line marker to avoid this. */ + n = read(fd, buf, sizeof buf); + if (n < 0 && errno != EAGAIN) { + weston_log("read from Xwayland display_fd failed: %s\n", + strerror(errno)); + goto out; + } + /* Returning 1 here means recheck and call us again if required. */ + if (n <= 0 || (n > 0 && buf[n - 1] != '\n')) + return 1; - /* We'd be safer if we actually had the struct - * signalfd_siginfo from the signalfd data and could verify - * this came from Xwayland.*/ wxw->api->xserver_loaded(wxw->xwayland, wxw->client, wxw->wm_fd); - wl_event_source_remove(wxw->sigusr1_source); - return 1; +out: + wl_event_source_remove(wxw->display_fd_source); + close(fd); + + return 0; } static pid_t @@ -65,93 +96,109 @@ spawn_xserver(void *user_data, const char *display, int abstract_fd, int unix_fd { struct wet_xwayland *wxw = user_data; pid_t pid; - char s[12], abstract_fd_str[12], unix_fd_str[12], wm_fd_str[12]; - int sv[2], wm[2], fd; + struct fdstr wayland_socket; + struct fdstr x11_abstract_socket; + struct fdstr x11_unix_socket; + struct fdstr x11_wm_socket; + struct fdstr display_pipe; char *xserver = NULL; struct weston_config *config = wet_get_config(wxw->compositor); struct weston_config_section *section; + struct wl_event_loop *loop; + char *exec_failure_msg; + struct custom_env child_env; + char *const *envp; + char *const *argp; + bool ret; + size_t written __attribute__ ((unused)); - if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) < 0) { + if (os_socketpair_cloexec(AF_UNIX, SOCK_STREAM, 0, wayland_socket.fds) < 0) { weston_log("wl connection socketpair failed\n"); return 1; } + fdstr_update_str1(&wayland_socket); - if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, wm) < 0) { + if (os_socketpair_cloexec(AF_UNIX, SOCK_STREAM, 0, x11_wm_socket.fds) < 0) { weston_log("X wm connection socketpair failed\n"); return 1; } + fdstr_update_str1(&x11_wm_socket); + + if (pipe2(display_pipe.fds, O_CLOEXEC) < 0) { + weston_log("pipe creation for displayfd failed\n"); + return 1; + } + fdstr_update_str1(&display_pipe); + + fdstr_set_fd1(&x11_abstract_socket, abstract_fd); + fdstr_set_fd1(&x11_unix_socket, unix_fd); + + section = weston_config_get_section(config, "xwayland", NULL, NULL); + weston_config_section_get_string(section, "path", + &xserver, XSERVER_PATH); + str_printf(&exec_failure_msg, + "Error: executing Xwayland as '%s' failed.\n", xserver); + custom_env_init_from_environ(&child_env); + custom_env_set_env_var(&child_env, "WAYLAND_SOCKET", wayland_socket.str1); + + custom_env_add_arg(&child_env, xserver); + custom_env_add_arg(&child_env, display); + custom_env_add_arg(&child_env, "-rootless"); + custom_env_add_arg(&child_env, LISTEN_STR); + custom_env_add_arg(&child_env, x11_abstract_socket.str1); + custom_env_add_arg(&child_env, LISTEN_STR); + custom_env_add_arg(&child_env, x11_unix_socket.str1); + custom_env_add_arg(&child_env, "-displayfd"); + custom_env_add_arg(&child_env, display_pipe.str1); + custom_env_add_arg(&child_env, "-wm"); + custom_env_add_arg(&child_env, x11_wm_socket.str1); + custom_env_add_arg(&child_env, "-terminate"); + + envp = custom_env_get_envp(&child_env); + argp = custom_env_get_argp(&child_env); pid = fork(); switch (pid) { case 0: + setsid(); /* SOCK_CLOEXEC closes both ends, so we need to unset * the flag on the client fd. */ - fd = dup(sv[1]); - if (fd < 0) - goto fail; - snprintf(s, sizeof s, "%d", fd); - setenv("WAYLAND_SOCKET", s, 1); - - fd = dup(abstract_fd); - if (fd < 0) - goto fail; - snprintf(abstract_fd_str, sizeof abstract_fd_str, "%d", fd); - fd = dup(unix_fd); - if (fd < 0) - goto fail; - snprintf(unix_fd_str, sizeof unix_fd_str, "%d", fd); - fd = dup(wm[1]); - if (fd < 0) - goto fail; - snprintf(wm_fd_str, sizeof wm_fd_str, "%d", fd); - - section = weston_config_get_section(config, - "xwayland", NULL, NULL); - weston_config_section_get_string(section, "path", - &xserver, XSERVER_PATH); - - /* Ignore SIGUSR1 in the child, which will make the X - * server send SIGUSR1 to the parent (weston) when - * it's done with initialization. During - * initialization the X server will round trip and - * block on the wayland compositor, so avoid making - * blocking requests (like xcb_connect_to_fd) until - * it's done with that. */ - signal(SIGUSR1, SIG_IGN); + ret = fdstr_clear_cloexec_fd1(&wayland_socket); + ret &= fdstr_clear_cloexec_fd1(&x11_abstract_socket); + ret &= fdstr_clear_cloexec_fd1(&x11_unix_socket); + ret &= fdstr_clear_cloexec_fd1(&x11_wm_socket); + ret &= fdstr_clear_cloexec_fd1(&display_pipe); + if (!ret) + _exit(EXIT_FAILURE); + + execve(xserver, argp, envp); + /* execve does not return on success, so it failed */ + + if (exec_failure_msg) { + written = write(STDERR_FILENO, exec_failure_msg, + strlen(exec_failure_msg)); + } - if (execl(xserver, - xserver, - display, - "-rootless", -#ifdef HAVE_XWAYLAND_LISTENFD - "-listenfd", abstract_fd_str, - "-listenfd", unix_fd_str, -#else - "-listen", abstract_fd_str, - "-listen", unix_fd_str, -#endif - "-wm", wm_fd_str, - "-terminate", - NULL) < 0) - weston_log("exec of '%s %s -rootless " -#ifdef HAVE_XWAYLAND_LISTENFD - "-listenfd %s -listenfd %s " -#else - "-listen %s -listen %s " -#endif - "-wm %s -terminate' failed: %s\n", - xserver, display, - abstract_fd_str, unix_fd_str, wm_fd_str, - strerror(errno)); - fail: _exit(EXIT_FAILURE); default: - close(sv[1]); - wxw->client = wl_client_create(wxw->compositor->wl_display, sv[0]); + close(wayland_socket.fds[1]); + wxw->client = wl_client_create(wxw->compositor->wl_display, + wayland_socket.fds[0]); + + close(x11_wm_socket.fds[1]); + wxw->wm_fd = x11_wm_socket.fds[0]; - close(wm[1]); - wxw->wm_fd = wm[0]; + /* During initialization the X server will round trip + * and block on the wayland compositor, so avoid making + * blocking requests (like xcb_connect_to_fd) until + * it's done with that. */ + close(display_pipe.fds[1]); + loop = wl_display_get_event_loop(wxw->compositor->wl_display); + wxw->display_fd_source = + wl_event_loop_add_fd(loop, display_pipe.fds[0], + WL_EVENT_READABLE, + handle_display_fd, wxw); wxw->process.pid = pid; wet_watch_process(wxw->compositor, &wxw->process); @@ -159,9 +206,16 @@ spawn_xserver(void *user_data, const char *display, int abstract_fd, int unix_fd case -1: weston_log("Failed to fork to spawn xserver process\n"); + fdstr_close_all(&wayland_socket); + fdstr_close_all(&x11_wm_socket); + fdstr_close_all(&display_pipe); break; } + custom_env_fini(&child_env); + free(exec_failure_msg); + free(xserver); + return pid; } @@ -170,22 +224,34 @@ xserver_cleanup(struct weston_process *process, int status) { struct wet_xwayland *wxw = container_of(process, struct wet_xwayland, process); - struct wl_event_loop *loop = - wl_display_get_event_loop(wxw->compositor->wl_display); wxw->api->xserver_exited(wxw->xwayland, status); - wxw->sigusr1_source = wl_event_loop_add_signal(loop, SIGUSR1, - handle_sigusr1, wxw); wxw->client = NULL; } +static void +wxw_compositor_destroy(struct wl_listener *listener, void *data) +{ + struct wet_xwayland *wxw = + wl_container_of(listener, wxw, compositor_destroy_listener); + + wl_list_remove(&wxw->compositor_destroy_listener.link); + + /* Don't call xserver_exited because Xwayland's own destroy handler + * already does this for us ... */ + if (wxw->client) + kill(wxw->process.pid, SIGTERM); + + wl_list_remove(&wxw->process.link); + free(wxw); +} + int wet_load_xwayland(struct weston_compositor *comp) { const struct weston_xwayland_api *api; struct weston_xwayland *xwayland; struct wet_xwayland *wxw; - struct wl_event_loop *loop; if (weston_compositor_load_xwayland(comp) < 0) return -1; @@ -209,13 +275,13 @@ wet_load_xwayland(struct weston_compositor *comp) wxw->compositor = comp; wxw->api = api; wxw->xwayland = xwayland; + wl_list_init(&wxw->process.link); wxw->process.cleanup = xserver_cleanup; + wxw->compositor_destroy_listener.notify = wxw_compositor_destroy; if (api->listen(xwayland, wxw, spawn_xserver) < 0) return -1; - loop = wl_display_get_event_loop(comp->wl_display); - wxw->sigusr1_source = wl_event_loop_add_signal(loop, SIGUSR1, - handle_sigusr1, wxw); + wl_signal_add(&comp->destroy_signal, &wxw->compositor_destroy_listener); return 0; } diff --git a/desktop-shell/exposay.c b/desktop-shell/exposay.c deleted file mode 100644 index c7c064b5..00000000 --- a/desktop-shell/exposay.c +++ /dev/null @@ -1,737 +0,0 @@ -/* - * Copyright © 2010-2012 Intel Corporation - * Copyright © 2011-2012 Collabora, Ltd. - * Copyright © 2013 Raspberry Pi Foundation - * - * 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. - */ - -#include "config.h" - -#include -#include - -#include "shell.h" -#include "shared/helpers.h" - -struct exposay_surface { - struct desktop_shell *shell; - struct exposay_output *eoutput; - struct weston_surface *surface; - struct weston_view *view; - struct wl_listener view_destroy_listener; - struct wl_list link; - - int x; - int y; - int width; - int height; - double scale; - - int row; - int column; - - /* The animations only apply a transformation for their own lifetime, - * and don't have an option to indefinitely maintain the - * transformation in a steady state - so, we apply our own once the - * animation has finished. */ - struct weston_transform transform; -}; - -static void exposay_set_state(struct desktop_shell *shell, - enum exposay_target_state state, - struct weston_seat *seat); -static void exposay_check_state(struct desktop_shell *shell); - -static void -exposay_surface_destroy(struct exposay_surface *esurface) -{ - wl_list_remove(&esurface->link); - wl_list_remove(&esurface->view_destroy_listener.link); - - if (esurface->shell->exposay.focus_current == esurface->view) - esurface->shell->exposay.focus_current = NULL; - if (esurface->shell->exposay.focus_prev == esurface->view) - esurface->shell->exposay.focus_prev = NULL; - - free(esurface); -} - -static void -exposay_in_flight_inc(struct desktop_shell *shell) -{ - shell->exposay.in_flight++; -} - -static void -exposay_in_flight_dec(struct desktop_shell *shell) -{ - if (--shell->exposay.in_flight > 0) - return; - - exposay_check_state(shell); -} - -static void -exposay_animate_in_done(struct weston_view_animation *animation, void *data) -{ - struct exposay_surface *esurface = data; - - wl_list_insert(&esurface->view->geometry.transformation_list, - &esurface->transform.link); - weston_matrix_init(&esurface->transform.matrix); - weston_matrix_scale(&esurface->transform.matrix, - esurface->scale, esurface->scale, 1.0f); - weston_matrix_translate(&esurface->transform.matrix, - esurface->x - esurface->view->geometry.x, - esurface->y - esurface->view->geometry.y, - 0); - - weston_view_geometry_dirty(esurface->view); - weston_compositor_schedule_repaint(esurface->view->surface->compositor); - - exposay_in_flight_dec(esurface->shell); -} - -static void -exposay_animate_in(struct exposay_surface *esurface) -{ - exposay_in_flight_inc(esurface->shell); - - weston_move_scale_run(esurface->view, - esurface->x - esurface->view->geometry.x, - esurface->y - esurface->view->geometry.y, - 1.0, esurface->scale, 0, - exposay_animate_in_done, esurface); -} - -static void -exposay_animate_out_done(struct weston_view_animation *animation, void *data) -{ - struct exposay_surface *esurface = data; - struct desktop_shell *shell = esurface->shell; - - exposay_surface_destroy(esurface); - - exposay_in_flight_dec(shell); -} - -static void -exposay_animate_out(struct exposay_surface *esurface) -{ - exposay_in_flight_inc(esurface->shell); - - /* Remove the static transformation set up by - * exposay_transform_in_done(). */ - wl_list_remove(&esurface->transform.link); - weston_view_geometry_dirty(esurface->view); - - weston_move_scale_run(esurface->view, - esurface->x - esurface->view->geometry.x, - esurface->y - esurface->view->geometry.y, - 1.0, esurface->scale, 1, - exposay_animate_out_done, esurface); -} - -static void -exposay_highlight_surface(struct desktop_shell *shell, - struct exposay_surface *esurface) -{ - struct weston_view *view = esurface->view; - - if (shell->exposay.focus_current == view) - return; - - shell->exposay.row_current = esurface->row; - shell->exposay.column_current = esurface->column; - shell->exposay.cur_output = esurface->eoutput; - - activate(shell, view, shell->exposay.seat, - WESTON_ACTIVATE_FLAG_NONE); - shell->exposay.focus_current = view; -} - -static int -exposay_is_animating(struct desktop_shell *shell) -{ - if (shell->exposay.state_cur == EXPOSAY_LAYOUT_INACTIVE || - shell->exposay.state_cur == EXPOSAY_LAYOUT_OVERVIEW) - return 0; - - return (shell->exposay.in_flight > 0); -} - -static void -exposay_pick(struct desktop_shell *shell, int x, int y) -{ - struct exposay_surface *esurface; - - if (exposay_is_animating(shell)) - return; - - wl_list_for_each(esurface, &shell->exposay.surface_list, link) { - if (x < esurface->x || x > esurface->x + esurface->width) - continue; - if (y < esurface->y || y > esurface->y + esurface->height) - continue; - - exposay_highlight_surface(shell, esurface); - return; - } -} - -static void -handle_view_destroy(struct wl_listener *listener, void *data) -{ - struct exposay_surface *esurface = container_of(listener, - struct exposay_surface, - view_destroy_listener); - - exposay_surface_destroy(esurface); -} - -/* Compute each surface size and then inner pad (10% of surface size). - * After that, it's necessary to recompute surface size (90% of its - * original size). Also, each surface can't be bigger than half the - * exposay area width and height. - */ -static void -exposay_surface_and_inner_pad_size(pixman_rectangle32_t exposay_area, struct exposay_output *eoutput) -{ - if (exposay_area.height < exposay_area.width) - eoutput->surface_size = exposay_area.height / eoutput->grid_size; - else - eoutput->surface_size = exposay_area.width / eoutput->grid_size; - - eoutput->padding_inner = eoutput->surface_size / 10; - eoutput->surface_size -= eoutput->padding_inner; - - if ((uint32_t)eoutput->surface_size > (exposay_area.width / 2)) - eoutput->surface_size = exposay_area.width / 2; - if ((uint32_t)eoutput->surface_size > (exposay_area.height / 2)) - eoutput->surface_size = exposay_area.height / 2; -} - -/* Compute the exposay top/left margin in order to centralize it */ -static void -exposay_margin_size(struct desktop_shell *shell, pixman_rectangle32_t exposay_area, - int row_size, int column_size, int *left_margin, int *top_margin) -{ - (*left_margin) = exposay_area.x + (exposay_area.width - row_size) / 2; - (*top_margin) = exposay_area.y + (exposay_area.height - column_size) / 2; -} - -/* Pretty lame layout for now; just tries to make a square. Should take - * aspect ratio into account really. Also needs to be notified of surface - * addition and removal and adjust layout/animate accordingly. - * - * Lay the grid out as square as possible, losing surfaces from the - * bottom row if required. Start with fixed padding of a 10% margin - * around the outside, and maximise the area made available to surfaces - * after this. Also, add an inner padding between surfaces that varies - * with the surface size (10% of its size). - * - * If we can't make a square grid, add one extra row at the bottom which - * will have a smaller number of columns. - */ -static enum exposay_layout_state -exposay_layout(struct desktop_shell *shell, struct shell_output *shell_output) -{ - struct workspace *workspace = shell->exposay.workspace; - struct weston_output *output = shell_output->output; - struct exposay_output *eoutput = &shell_output->eoutput; - struct weston_view *view; - struct exposay_surface *esurface, *highlight = NULL; - pixman_rectangle32_t exposay_area; - int pad, row_size, column_size, left_margin, top_margin; - int last_row_size, last_row_margin_increase; - int populated_rows; - int i; - - eoutput->num_surfaces = 0; - wl_list_for_each(view, &workspace->layer.view_list.link, layer_link.link) { - if (!get_shell_surface(view->surface)) - continue; - if (view->output != output) - continue; - eoutput->num_surfaces++; - } - - if (eoutput->num_surfaces == 0) { - eoutput->grid_size = 0; - eoutput->padding_inner = 0; - eoutput->surface_size = 0; - return EXPOSAY_LAYOUT_OVERVIEW; - } - - /* Get exposay area and position, taking into account - * the shell panel position and size */ - get_output_work_area(shell, output, &exposay_area); - - /* Compute grid size */ - eoutput->grid_size = floor(sqrtf(eoutput->num_surfaces)); - if (pow(eoutput->grid_size, 2) != eoutput->num_surfaces) - eoutput->grid_size++; - - /* Compute each surface size and the inner padding between them */ - exposay_surface_and_inner_pad_size(exposay_area, eoutput); - - /* Compute each row/column size */ - pad = eoutput->surface_size + eoutput->padding_inner; - row_size = (pad * eoutput->grid_size) - eoutput->padding_inner; - /* We may have empty rows that should be desconsidered to compute - * column size */ - populated_rows = ceil(eoutput->num_surfaces / (float) eoutput->grid_size); - column_size = (pad * populated_rows) - eoutput->padding_inner; - - /* The last row size can be different, since it may have less surfaces - * than the grid size. Also, its margin may be increased to centralize - * its surfaces, in the case where we don't have a perfect grid. */ - last_row_size = ((eoutput->num_surfaces % eoutput->grid_size) * pad) - eoutput->padding_inner; - if (eoutput->num_surfaces % eoutput->grid_size) - last_row_margin_increase = (row_size - last_row_size) / 2; - else - last_row_margin_increase = 0; - - /* Compute a top/left margin to centralize the exposay */ - exposay_margin_size(shell, exposay_area, row_size, column_size, &left_margin, &top_margin); - - i = 0; - wl_list_for_each(view, &workspace->layer.view_list.link, layer_link.link) { - - if (!get_shell_surface(view->surface)) - continue; - if (view->output != output) - continue; - - esurface = malloc(sizeof(*esurface)); - if (!esurface) { - exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, - shell->exposay.seat); - break; - } - - wl_list_insert(&shell->exposay.surface_list, &esurface->link); - esurface->shell = shell; - esurface->eoutput = eoutput; - esurface->view = view; - - esurface->row = i / eoutput->grid_size; - esurface->column = i % eoutput->grid_size; - - esurface->x = left_margin + (pad * esurface->column); - esurface->y = top_margin + (pad * esurface->row); - - /* If this is the last row, increase left margin (it sums 0 if - * we have a perfect square) to centralize the surfaces */ - if (eoutput->num_surfaces / eoutput->grid_size == esurface->row) - esurface->x += last_row_margin_increase; - - if (view->surface->width > view->surface->height) - esurface->scale = eoutput->surface_size / (float) view->surface->width; - else - esurface->scale = eoutput->surface_size / (float) view->surface->height; - esurface->width = view->surface->width * esurface->scale; - esurface->height = view->surface->height * esurface->scale; - - /* Surfaces are usually rectangular, but their exposay surfaces - * are square. centralize them in their own square */ - if (esurface->width > esurface->height) - esurface->y += (esurface->width - esurface->height) / 2; - else - esurface->x += (esurface->height - esurface->width) / 2; - - if (shell->exposay.focus_current == esurface->view) - highlight = esurface; - - exposay_animate_in(esurface); - - /* We want our destroy handler to be after the animation - * destroy handler in the list, this way when the view is - * destroyed, the animation can safely call the animation - * completion callback before we free the esurface in our - * destroy handler. - */ - esurface->view_destroy_listener.notify = handle_view_destroy; - wl_signal_add(&view->destroy_signal, &esurface->view_destroy_listener); - - i++; - } - - if (highlight) { - shell->exposay.focus_current = NULL; - exposay_highlight_surface(shell, highlight); - } - - weston_compositor_schedule_repaint(shell->compositor); - - return EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW; -} - -static void -exposay_focus(struct weston_pointer_grab *grab) -{ -} - -static void -exposay_motion(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_motion_event *event) -{ - struct desktop_shell *shell = - container_of(grab, struct desktop_shell, exposay.grab_ptr); - - weston_pointer_move(grab->pointer, event); - - exposay_pick(shell, - wl_fixed_to_int(grab->pointer->x), - wl_fixed_to_int(grab->pointer->y)); -} - -static void -exposay_button(struct weston_pointer_grab *grab, const struct timespec *time, - uint32_t button, uint32_t state_w) -{ - struct desktop_shell *shell = - container_of(grab, struct desktop_shell, exposay.grab_ptr); - struct weston_seat *seat = grab->pointer->seat; - enum wl_pointer_button_state state = state_w; - - if (button != BTN_LEFT) - return; - - /* Store the surface we clicked on, and don't do anything if we end up - * releasing on a different surface. */ - if (state == WL_POINTER_BUTTON_STATE_PRESSED) { - shell->exposay.clicked = shell->exposay.focus_current; - return; - } - - if (shell->exposay.focus_current == shell->exposay.clicked) - exposay_set_state(shell, EXPOSAY_TARGET_SWITCH, seat); - else - shell->exposay.clicked = NULL; -} - -static void -exposay_axis(struct weston_pointer_grab *grab, - const struct timespec *time, - struct weston_pointer_axis_event *event) -{ -} - -static void -exposay_axis_source(struct weston_pointer_grab *grab, uint32_t source) -{ -} - -static void -exposay_frame(struct weston_pointer_grab *grab) -{ -} - -static void -exposay_pointer_grab_cancel(struct weston_pointer_grab *grab) -{ - struct desktop_shell *shell = - container_of(grab, struct desktop_shell, exposay.grab_ptr); - - exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, shell->exposay.seat); -} - -static const struct weston_pointer_grab_interface exposay_ptr_grab = { - exposay_focus, - exposay_motion, - exposay_button, - exposay_axis, - exposay_axis_source, - exposay_frame, - exposay_pointer_grab_cancel, -}; - -static int -exposay_maybe_move(struct desktop_shell *shell, int row, int column) -{ - struct exposay_surface *esurface; - - wl_list_for_each(esurface, &shell->exposay.surface_list, link) { - if (esurface->eoutput != shell->exposay.cur_output || - esurface->row != row || esurface->column != column) - continue; - - exposay_highlight_surface(shell, esurface); - return 1; - } - - return 0; -} - -static void -exposay_key(struct weston_keyboard_grab *grab, const struct timespec *time, - uint32_t key, uint32_t state_w) -{ - struct weston_seat *seat = grab->keyboard->seat; - struct desktop_shell *shell = - container_of(grab, struct desktop_shell, exposay.grab_kbd); - enum wl_keyboard_key_state state = state_w; - - if (state != WL_KEYBOARD_KEY_STATE_RELEASED) - return; - - switch (key) { - case KEY_ESC: - exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, seat); - break; - case KEY_ENTER: - exposay_set_state(shell, EXPOSAY_TARGET_SWITCH, seat); - break; - case KEY_UP: - exposay_maybe_move(shell, shell->exposay.row_current - 1, - shell->exposay.column_current); - break; - case KEY_DOWN: - /* Special case for trying to move to the bottom row when it - * has fewer items than all the others. */ - if (!exposay_maybe_move(shell, shell->exposay.row_current + 1, - shell->exposay.column_current) && - shell->exposay.row_current < (shell->exposay.cur_output->grid_size - 1)) { - exposay_maybe_move(shell, shell->exposay.row_current + 1, - (shell->exposay.cur_output->num_surfaces % - shell->exposay.cur_output->grid_size) - 1); - } - break; - case KEY_LEFT: - exposay_maybe_move(shell, shell->exposay.row_current, - shell->exposay.column_current - 1); - break; - case KEY_RIGHT: - exposay_maybe_move(shell, shell->exposay.row_current, - shell->exposay.column_current + 1); - break; - case KEY_TAB: - /* Try to move right, then down (and to the leftmost column), - * then if all else fails, to the top left. */ - if (!exposay_maybe_move(shell, shell->exposay.row_current, - shell->exposay.column_current + 1) && - !exposay_maybe_move(shell, shell->exposay.row_current + 1, 0)) - exposay_maybe_move(shell, 0, 0); - break; - default: - break; - } -} - -static void -exposay_modifier(struct weston_keyboard_grab *grab, uint32_t serial, - uint32_t mods_depressed, uint32_t mods_latched, - uint32_t mods_locked, uint32_t group) -{ - struct desktop_shell *shell = - container_of(grab, struct desktop_shell, exposay.grab_kbd); - struct weston_seat *seat = (struct weston_seat *) grab->keyboard->seat; - - /* We want to know when mod has been pressed and released. - * FIXME: There is a problem here: if mod is pressed, then a key - * is pressed and released, then mod is released, we will treat that - * as if only mod had been pressed and released. */ - if (seat->modifier_state) { - if (seat->modifier_state == shell->binding_modifier) { - shell->exposay.mod_pressed = true; - } else { - shell->exposay.mod_invalid = true; - } - } else { - if (shell->exposay.mod_pressed && !shell->exposay.mod_invalid) - exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, seat); - - shell->exposay.mod_invalid = false; - shell->exposay.mod_pressed = false; - } - - return; -} - -static void -exposay_cancel(struct weston_keyboard_grab *grab) -{ - struct desktop_shell *shell = - container_of(grab, struct desktop_shell, exposay.grab_kbd); - - exposay_set_state(shell, EXPOSAY_TARGET_CANCEL, shell->exposay.seat); -} - -static const struct weston_keyboard_grab_interface exposay_kbd_grab = { - exposay_key, - exposay_modifier, - exposay_cancel, -}; - -/** - * Called when the transition from overview -> inactive has completed. - */ -static enum exposay_layout_state -exposay_set_inactive(struct desktop_shell *shell) -{ - struct weston_seat *seat = shell->exposay.seat; - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - if (pointer) - weston_pointer_end_grab(pointer); - - if (keyboard) { - weston_keyboard_end_grab(keyboard); - if (keyboard->input_method_resource) - keyboard->grab = &keyboard->input_method_grab; - } - - return EXPOSAY_LAYOUT_INACTIVE; -} - -/** - * Begins the transition from overview to inactive. */ -static enum exposay_layout_state -exposay_transition_inactive(struct desktop_shell *shell, int switch_focus) -{ - struct exposay_surface *esurface; - - /* Call activate() before we start the animations to avoid - * animating back the old state and then immediately transitioning - * to the new. */ - if (switch_focus && shell->exposay.focus_current) - activate(shell, shell->exposay.focus_current, - shell->exposay.seat, - WESTON_ACTIVATE_FLAG_CONFIGURE); - else if (shell->exposay.focus_prev) - activate(shell, shell->exposay.focus_prev, - shell->exposay.seat, - WESTON_ACTIVATE_FLAG_CONFIGURE); - - wl_list_for_each(esurface, &shell->exposay.surface_list, link) - exposay_animate_out(esurface); - weston_compositor_schedule_repaint(shell->compositor); - - return EXPOSAY_LAYOUT_ANIMATE_TO_INACTIVE; -} - -static enum exposay_layout_state -exposay_transition_active(struct desktop_shell *shell) -{ - struct weston_seat *seat = shell->exposay.seat; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct shell_output *shell_output; - bool animate = false; - - shell->exposay.workspace = get_current_workspace(shell); - shell->exposay.focus_prev = get_default_view(keyboard->focus); - shell->exposay.focus_current = get_default_view(keyboard->focus); - shell->exposay.clicked = NULL; - wl_list_init(&shell->exposay.surface_list); - - lower_fullscreen_layer(shell, NULL); - shell->exposay.grab_kbd.interface = &exposay_kbd_grab; - weston_keyboard_start_grab(keyboard, - &shell->exposay.grab_kbd); - weston_keyboard_set_focus(keyboard, NULL); - - shell->exposay.grab_ptr.interface = &exposay_ptr_grab; - if (pointer) { - weston_pointer_start_grab(pointer, - &shell->exposay.grab_ptr); - weston_pointer_clear_focus(pointer); - } - wl_list_for_each(shell_output, &shell->output_list, link) { - enum exposay_layout_state state; - - state = exposay_layout(shell, shell_output); - - if (state == EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW) - animate = true; - } - - return animate ? EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW - : EXPOSAY_LAYOUT_OVERVIEW; -} - -static void -exposay_check_state(struct desktop_shell *shell) -{ - enum exposay_layout_state state_new = shell->exposay.state_cur; - int do_switch = 0; - - /* Don't do anything whilst animations are running, just store up - * target state changes and only act on them when the animations have - * completed. */ - if (exposay_is_animating(shell)) - return; - - switch (shell->exposay.state_target) { - case EXPOSAY_TARGET_OVERVIEW: - switch (shell->exposay.state_cur) { - case EXPOSAY_LAYOUT_OVERVIEW: - goto out; - case EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW: - state_new = EXPOSAY_LAYOUT_OVERVIEW; - break; - default: - state_new = exposay_transition_active(shell); - break; - } - break; - - case EXPOSAY_TARGET_SWITCH: - do_switch = 1; /* fallthrough */ - case EXPOSAY_TARGET_CANCEL: - switch (shell->exposay.state_cur) { - case EXPOSAY_LAYOUT_INACTIVE: - goto out; - case EXPOSAY_LAYOUT_ANIMATE_TO_INACTIVE: - state_new = exposay_set_inactive(shell); - break; - default: - state_new = exposay_transition_inactive(shell, do_switch); - break; - } - - break; - } - -out: - shell->exposay.state_cur = state_new; -} - -static void -exposay_set_state(struct desktop_shell *shell, enum exposay_target_state state, - struct weston_seat *seat) -{ - shell->exposay.state_target = state; - shell->exposay.seat = seat; - exposay_check_state(shell); -} - -void -exposay_binding(struct weston_keyboard *keyboard, enum weston_keyboard_modifier modifier, - void *data) -{ - struct desktop_shell *shell = data; - - exposay_set_state(shell, EXPOSAY_TARGET_OVERVIEW, keyboard->seat); -} diff --git a/desktop-shell/input-panel.c b/desktop-shell/input-panel.c index 0897ffde..6dc2e427 100644 --- a/desktop-shell/input-panel.c +++ b/desktop-shell/input-panel.c @@ -103,8 +103,8 @@ show_input_panel_surface(struct input_panel_surface *ipsurf) &ipsurf->view->layer_link); weston_view_geometry_dirty(ipsurf->view); weston_view_update_transform(ipsurf->view); - ipsurf->surface->is_mapped = true; ipsurf->view->is_mapped = true; + weston_surface_map(ipsurf->surface); weston_surface_damage(ipsurf->surface); if (ipsurf->anim) diff --git a/desktop-shell/meson.build b/desktop-shell/meson.build index cc7215cb..c00d4a8d 100644 --- a/desktop-shell/meson.build +++ b/desktop-shell/meson.build @@ -3,9 +3,7 @@ if get_option('shell-desktop') srcs_shell_desktop = [ 'shell.c', - 'exposay.c', 'input-panel.c', - '../shared/shell-utils.c', weston_desktop_shell_server_protocol_h, weston_desktop_shell_protocol_c, input_method_unstable_v1_server_protocol_h, @@ -15,8 +13,8 @@ if get_option('shell-desktop') dep_libm, dep_libexec_weston, dep_libshared, - dep_lib_desktop, dep_libweston_public, + dep_shell_utils, ] plugin_shell_desktop = shared_library( 'desktop-shell', diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 63e14311..e4ea90f9 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -41,8 +41,8 @@ #include "weston-desktop-shell-server-protocol.h" #include #include "shared/helpers.h" -#include "shared/shell-utils.h" #include "shared/timespec-util.h" +#include "shell-utils.h" #include #define DEFAULT_NUM_WORKSPACES 1 @@ -115,6 +115,7 @@ struct shell_surface { bool saved_rotation_valid; int unresponsive, grabbed; uint32_t resize_edges; + uint32_t orientation; struct { struct weston_transform transform; @@ -123,11 +124,9 @@ struct shell_surface { struct { struct weston_transform transform; /* matrix from x, y */ - struct weston_view *black_view; + struct weston_curtain *black_view; } fullscreen; - struct weston_transform workspace_transform; - struct weston_output *fullscreen_output; struct weston_output *output; struct wl_listener output_destroy_listener; @@ -262,6 +261,9 @@ desktop_shell_destroy_surface(struct shell_surface *shsurf) { struct shell_surface *shsurf_child, *tmp; + if (shsurf->fullscreen.black_view) + weston_curtain_destroy(shsurf->fullscreen.black_view); + wl_list_for_each_safe(shsurf_child, tmp, &shsurf->children_list, children_link) { wl_list_remove(&shsurf_child->children_link); wl_list_init(&shsurf_child->children_link); @@ -271,7 +273,7 @@ desktop_shell_destroy_surface(struct shell_surface *shsurf) weston_view_destroy(shsurf->view); wl_signal_emit(&shsurf->destroy_signal, shsurf); - weston_surface_destroy(shsurf->wsurface_anim_fade); + weston_surface_unref(shsurf->wsurface_anim_fade); if (shsurf->output_destroy_listener.notify) { wl_list_remove(&shsurf->output_destroy_listener.link); @@ -441,24 +443,6 @@ shell_touch_grab_end(struct shell_touch_grab *grab) weston_touch_end_grab(grab->touch); } -static enum weston_keyboard_modifier -get_modifier(char *modifier) -{ - if (!modifier) - return MODIFIER_SUPER; - - if (!strcmp("ctrl", modifier)) - return MODIFIER_CTRL; - else if (!strcmp("alt", modifier)) - return MODIFIER_ALT; - else if (!strcmp("super", modifier)) - return MODIFIER_SUPER; - else if (!strcmp("none", modifier)) - return 0; - else - return MODIFIER_SUPER; -} - static enum animation_type get_animation_type(char *animation) { @@ -479,11 +463,12 @@ static void shell_configuration(struct desktop_shell *shell) { struct weston_config_section *section; + struct weston_config *config; char *s, *client; bool allow_zap; - section = weston_config_get_section(wet_get_config(shell->compositor), - "shell", NULL, NULL); + config = wet_get_config(shell->compositor); + section = weston_config_get_section(config, "shell", NULL, NULL); client = wet_get_libexec_path(WESTON_SHELL_CLIENT); weston_config_section_get_string(section, "client", &s, client); free(client); @@ -493,15 +478,7 @@ shell_configuration(struct desktop_shell *shell) "allow-zap", &allow_zap, true); shell->allow_zap = allow_zap; - weston_config_section_get_string(section, - "binding-modifier", &s, "super"); - shell->binding_modifier = get_modifier(s); - free(s); - - weston_config_section_get_string(section, - "exposay-modifier", &s, "none"); - shell->exposay_modifier = get_modifier(s); - free(s); + shell->binding_modifier = weston_shell_get_binding_modifier(config, MODIFIER_SUPER); weston_config_section_get_string(section, "animation", &s, "none"); shell->win_animation_type = get_animation_type(s); @@ -518,9 +495,6 @@ shell_configuration(struct desktop_shell *shell) weston_config_section_get_string(section, "focus-animation", &s, "none"); shell->focus_animation_type = get_animation_type(s); free(s); - weston_config_section_get_uint(section, "num-workspaces", - &shell->workspaces.num, - DEFAULT_NUM_WORKSPACES); } static int @@ -536,25 +510,10 @@ focus_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) { } -static struct focus_surface * -get_focus_surface(struct weston_surface *surface) -{ - if (surface->committed == focus_surface_committed) - return surface->committed_private; - else - return NULL; -} - -static bool -is_focus_surface (struct weston_surface *es) -{ - return (es->committed == focus_surface_committed); -} - static bool is_focus_view (struct weston_view *view) { - return is_focus_surface (view->surface); + return (view->surface->committed == focus_surface_committed); } static struct focus_surface * @@ -562,44 +521,25 @@ create_focus_surface(struct weston_compositor *ec, struct weston_output *output) { struct focus_surface *fsurf = NULL; - struct weston_surface *surface = NULL; + struct weston_curtain_params curtain_params = { + .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0, + .x = output->x, .y = output->y, + .width = output->width, .height = output->height, + .surface_committed = focus_surface_committed, + .get_label = focus_surface_get_label, + .surface_private = NULL, + .capture_input = false, + }; fsurf = malloc(sizeof *fsurf); if (!fsurf) return NULL; - fsurf->surface = weston_surface_create(ec); - surface = fsurf->surface; - if (surface == NULL) { - free(fsurf); - return NULL; - } - - surface->committed = focus_surface_committed; - surface->output = output; - surface->is_mapped = true; - surface->committed_private = fsurf; - weston_surface_set_label_func(surface, focus_surface_get_label); - - fsurf->view = weston_view_create(surface); - if (fsurf->view == NULL) { - weston_surface_destroy(surface); - free(fsurf); - return NULL; - } - weston_view_set_output(fsurf->view, output); - fsurf->view->is_mapped = true; - - weston_surface_set_size(surface, output->width, output->height); - weston_view_set_position(fsurf->view, output->x, output->y); - weston_surface_set_color(surface, 0.0, 0.0, 0.0, 1.0); - pixman_region32_fini(&surface->opaque); - pixman_region32_init_rect(&surface->opaque, output->x, output->y, - output->width, output->height); - pixman_region32_fini(&surface->input); - pixman_region32_init(&surface->input); + curtain_params.surface_private = fsurf; - wl_list_init(&fsurf->workspace_transform.link); + fsurf->curtain = weston_curtain_create(ec, &curtain_params); + weston_view_set_output(fsurf->curtain->view, output); + fsurf->curtain->view->is_mapped = true; return fsurf; } @@ -607,7 +547,7 @@ create_focus_surface(struct weston_compositor *ec, static void focus_surface_destroy(struct focus_surface *fsurf) { - weston_surface_destroy(fsurf->surface); + weston_curtain_destroy(fsurf->curtain); free(fsurf); } @@ -682,8 +622,8 @@ focus_state_surface_destroy(struct wl_listener *listener, void *data) weston_view_animation_destroy(state->ws->focus_animation); state->ws->focus_animation = weston_fade_run( - state->ws->fsurf_front->view, - state->ws->fsurf_front->view->alpha, 0.0, 300, + state->ws->fsurf_front->curtain->view, + state->ws->fsurf_front->curtain->view->alpha, 0.0, 300, focus_animation_done, state->ws); } @@ -793,21 +733,6 @@ restore_focus_state(struct desktop_shell *shell, struct workspace *ws) } } -static void -replace_focus_state(struct desktop_shell *shell, struct workspace *ws, - struct weston_seat *seat) -{ - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct focus_state *state; - - wl_list_for_each(state, &ws->focus_list, link) { - if (state->seat == seat) { - focus_state_set_focus(state, keyboard->focus); - return; - } - } -} - static void drop_focus_state(struct desktop_shell *shell, struct workspace *ws, struct weston_surface *surface) @@ -835,19 +760,19 @@ animate_focus_change(struct desktop_shell *shell, struct workspace *ws, ws->fsurf_front = create_focus_surface(shell->compositor, output); if (ws->fsurf_front == NULL) return; - ws->fsurf_front->view->alpha = 0.0; + ws->fsurf_front->curtain->view->alpha = 0.0; ws->fsurf_back = create_focus_surface(shell->compositor, output); if (ws->fsurf_back == NULL) { focus_surface_destroy(ws->fsurf_front); return; } - ws->fsurf_back->view->alpha = 0.0; + ws->fsurf_back->curtain->view->alpha = 0.0; focus_surface_created = true; } else { - weston_layer_entry_remove(&ws->fsurf_front->view->layer_link); - weston_layer_entry_remove(&ws->fsurf_back->view->layer_link); + weston_layer_entry_remove(&ws->fsurf_front->curtain->view->layer_link); + weston_layer_entry_remove(&ws->fsurf_back->curtain->view->layer_link); } if (ws->focus_animation) { @@ -857,29 +782,29 @@ animate_focus_change(struct desktop_shell *shell, struct workspace *ws, if (to) weston_layer_entry_insert(&to->layer_link, - &ws->fsurf_front->view->layer_link); + &ws->fsurf_front->curtain->view->layer_link); else if (from) weston_layer_entry_insert(&ws->layer.view_list, - &ws->fsurf_front->view->layer_link); + &ws->fsurf_front->curtain->view->layer_link); if (focus_surface_created) { ws->focus_animation = weston_fade_run( - ws->fsurf_front->view, - ws->fsurf_front->view->alpha, 0.4, 300, + ws->fsurf_front->curtain->view, + ws->fsurf_front->curtain->view->alpha, 0.4, 300, focus_animation_done, ws); } else if (from) { weston_layer_entry_insert(&from->layer_link, - &ws->fsurf_back->view->layer_link); + &ws->fsurf_back->curtain->view->layer_link); ws->focus_animation = weston_stable_fade_run( - ws->fsurf_front->view, 0.0, - ws->fsurf_back->view, 0.4, + ws->fsurf_front->curtain->view, 0.0, + ws->fsurf_back->curtain->view, 0.4, focus_animation_done, ws); } else if (to) { weston_layer_entry_insert(&ws->layer.view_list, - &ws->fsurf_back->view->layer_link); + &ws->fsurf_back->curtain->view->layer_link); ws->focus_animation = weston_stable_fade_run( - ws->fsurf_front->view, 0.0, - ws->fsurf_back->view, 0.4, + ws->fsurf_front->curtain->view, 0.0, + ws->fsurf_back->curtain->view, 0.4, focus_animation_done, ws); } } @@ -901,7 +826,6 @@ workspace_destroy(struct workspace *ws) focus_surface_destroy(ws->fsurf_back); desktop_shell_destroy_layer(&ws->layer); - free(ws); } static void @@ -918,14 +842,13 @@ seat_destroyed(struct wl_listener *listener, void *data) wl_list_remove(&state->link); } -static struct workspace * +static void workspace_create(struct desktop_shell *shell) { - struct workspace *ws = malloc(sizeof *ws); - if (ws == NULL) - return NULL; + struct workspace *ws = &shell->workspace; weston_layer_init(&ws->layer, shell->compositor); + weston_layer_set_position(&ws->layer, WESTON_LAYER_POSITION_NORMAL); wl_list_init(&ws->focus_list); wl_list_init(&ws->seat_destroyed_listener.link); @@ -933,343 +856,12 @@ workspace_create(struct desktop_shell *shell) ws->fsurf_front = NULL; ws->fsurf_back = NULL; ws->focus_animation = NULL; - - return ws; -} - -static int -workspace_is_empty(struct workspace *ws) -{ - return wl_list_empty(&ws->layer.view_list.link); -} - -static struct workspace * -get_workspace(struct desktop_shell *shell, unsigned int index) -{ - struct workspace **pws = shell->workspaces.array.data; - assert(index < shell->workspaces.num); - pws += index; - return *pws; } struct workspace * get_current_workspace(struct desktop_shell *shell) { - return get_workspace(shell, shell->workspaces.current); -} - -static void -activate_workspace(struct desktop_shell *shell, unsigned int index) -{ - struct workspace *ws; - - ws = get_workspace(shell, index); - weston_layer_set_position(&ws->layer, WESTON_LAYER_POSITION_NORMAL); - - shell->workspaces.current = index; -} - -static unsigned int -get_output_height(struct weston_output *output) -{ - return abs(output->region.extents.y1 - output->region.extents.y2); -} - -static struct weston_transform * -view_get_transform(struct weston_view *view) -{ - struct focus_surface *fsurf = NULL; - struct shell_surface *shsurf = NULL; - - if (is_focus_view(view)) { - fsurf = get_focus_surface(view->surface); - return &fsurf->workspace_transform; - } - - shsurf = get_shell_surface(view->surface); - if (shsurf) - return &shsurf->workspace_transform; - - return NULL; -} - -static void -view_translate(struct workspace *ws, struct weston_view *view, double d) -{ - struct weston_transform *transform = view_get_transform(view); - - if (!transform) - return; - - if (wl_list_empty(&transform->link)) - wl_list_insert(view->geometry.transformation_list.prev, - &transform->link); - - weston_matrix_init(&transform->matrix); - weston_matrix_translate(&transform->matrix, - 0.0, d, 0.0); - weston_view_geometry_dirty(view); -} - -static void -workspace_translate_out(struct workspace *ws, double fraction) -{ - struct weston_view *view; - unsigned int height; - double d; - - wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { - height = get_output_height(view->surface->output); - d = height * fraction; - - view_translate(ws, view, d); - } -} - -static void -workspace_translate_in(struct workspace *ws, double fraction) -{ - struct weston_view *view; - unsigned int height; - double d; - - wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { - height = get_output_height(view->surface->output); - - if (fraction > 0) - d = -(height - height * fraction); - else - d = height + height * fraction; - - view_translate(ws, view, d); - } -} - -static void -reverse_workspace_change_animation(struct desktop_shell *shell, - unsigned int index, - struct workspace *from, - struct workspace *to) -{ - shell->workspaces.current = index; - - shell->workspaces.anim_to = to; - shell->workspaces.anim_from = from; - shell->workspaces.anim_dir = -1 * shell->workspaces.anim_dir; - shell->workspaces.anim_timestamp = (struct timespec) { 0 }; - - weston_layer_set_position(&to->layer, WESTON_LAYER_POSITION_NORMAL); - weston_layer_set_position(&from->layer, WESTON_LAYER_POSITION_NORMAL - 1); - - weston_compositor_schedule_repaint(shell->compositor); -} - -static void -workspace_deactivate_transforms(struct workspace *ws) -{ - struct weston_view *view; - struct weston_transform *transform; - - wl_list_for_each(view, &ws->layer.view_list.link, layer_link.link) { - transform = view_get_transform(view); - if (!transform) - continue; - - if (!wl_list_empty(&transform->link)) { - wl_list_remove(&transform->link); - wl_list_init(&transform->link); - } - weston_view_geometry_dirty(view); - } -} - -static void -finish_workspace_change_animation(struct desktop_shell *shell, - struct workspace *from, - struct workspace *to) -{ - struct weston_view *view; - - weston_compositor_schedule_repaint(shell->compositor); - - /* Views that extend past the bottom of the output are still - * visible after the workspace animation ends but before its layer - * is hidden. In that case, we need to damage below those views so - * that the screen is properly repainted. */ - wl_list_for_each(view, &from->layer.view_list.link, layer_link.link) - weston_view_damage_below(view); - - wl_list_remove(&shell->workspaces.animation.link); - workspace_deactivate_transforms(from); - workspace_deactivate_transforms(to); - shell->workspaces.anim_to = NULL; - - weston_layer_unset_position(&shell->workspaces.anim_from->layer); -} - -static void -animate_workspace_change_frame(struct weston_animation *animation, - struct weston_output *output, - const struct timespec *time) -{ - struct desktop_shell *shell = - container_of(animation, struct desktop_shell, - workspaces.animation); - struct workspace *from = shell->workspaces.anim_from; - struct workspace *to = shell->workspaces.anim_to; - int64_t t; - double x, y; - - if (workspace_is_empty(from) && workspace_is_empty(to)) { - finish_workspace_change_animation(shell, from, to); - return; - } - - if (timespec_is_zero(&shell->workspaces.anim_timestamp)) { - if (shell->workspaces.anim_current == 0.0) - shell->workspaces.anim_timestamp = *time; - else - timespec_add_msec(&shell->workspaces.anim_timestamp, - time, - /* Inverse of movement function 'y' below. */ - -(asin(1.0 - shell->workspaces.anim_current) * - DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH * - M_2_PI)); - } - - t = timespec_sub_to_msec(time, &shell->workspaces.anim_timestamp); - - /* - * x = [0, π/2] - * y(x) = sin(x) - */ - x = t * (1.0/DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH) * M_PI_2; - y = sin(x); - - if (t < DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH) { - weston_compositor_schedule_repaint(shell->compositor); - - workspace_translate_out(from, shell->workspaces.anim_dir * y); - workspace_translate_in(to, shell->workspaces.anim_dir * y); - shell->workspaces.anim_current = y; - - weston_compositor_schedule_repaint(shell->compositor); - } - else - finish_workspace_change_animation(shell, from, to); -} - -static void -animate_workspace_change(struct desktop_shell *shell, - unsigned int index, - struct workspace *from, - struct workspace *to) -{ - struct weston_output *output; - - int dir; - - if (index > shell->workspaces.current) - dir = -1; - else - dir = 1; - - shell->workspaces.current = index; - - shell->workspaces.anim_dir = dir; - shell->workspaces.anim_from = from; - shell->workspaces.anim_to = to; - shell->workspaces.anim_current = 0.0; - shell->workspaces.anim_timestamp = (struct timespec) { 0 }; - - output = container_of(shell->compositor->output_list.next, - struct weston_output, link); - wl_list_insert(&output->animation_list, - &shell->workspaces.animation.link); - - weston_layer_set_position(&to->layer, WESTON_LAYER_POSITION_NORMAL); - weston_layer_set_position(&from->layer, WESTON_LAYER_POSITION_NORMAL - 1); - - workspace_translate_in(to, 0); - - restore_focus_state(shell, to); - - weston_compositor_schedule_repaint(shell->compositor); -} - -static void -update_workspace(struct desktop_shell *shell, unsigned int index, - struct workspace *from, struct workspace *to) -{ - shell->workspaces.current = index; - weston_layer_set_position(&to->layer, WESTON_LAYER_POSITION_NORMAL); - weston_layer_unset_position(&from->layer); -} - -static void -change_workspace(struct desktop_shell *shell, unsigned int index) -{ - struct workspace *from; - struct workspace *to; - struct focus_state *state; - - if (index == shell->workspaces.current) - return; - - /* Don't change workspace when there is any fullscreen surfaces. */ - if (!wl_list_empty(&shell->fullscreen_layer.view_list.link)) - return; - - from = get_current_workspace(shell); - to = get_workspace(shell, index); - - if (shell->workspaces.anim_from == to && - shell->workspaces.anim_to == from) { - restore_focus_state(shell, to); - reverse_workspace_change_animation(shell, index, from, to); - return; - } - - if (shell->workspaces.anim_to != NULL) - finish_workspace_change_animation(shell, - shell->workspaces.anim_from, - shell->workspaces.anim_to); - - restore_focus_state(shell, to); - - if (shell->focus_animation_type != ANIMATION_NONE) { - wl_list_for_each(state, &from->focus_list, link) - if (state->keyboard_focus) - animate_focus_change(shell, from, - get_default_view(state->keyboard_focus), NULL); - - wl_list_for_each(state, &to->focus_list, link) - if (state->keyboard_focus) - animate_focus_change(shell, to, - NULL, get_default_view(state->keyboard_focus)); - } - - if (workspace_is_empty(to) && workspace_is_empty(from)) - update_workspace(shell, index, from, to); - else - animate_workspace_change(shell, index, from, to); -} - -static bool -workspace_has_only(struct workspace *ws, struct weston_surface *surface) -{ - struct wl_list *list = &ws->layer.view_list.link; - struct wl_list *e; - - if (wl_list_empty(list)) - return false; - - e = list->next; - - if (e->next != list) - return false; - - return container_of(e, struct weston_view, layer_link.link)->surface == surface; + return &shell->workspace; } static void @@ -1292,68 +884,6 @@ surface_keyboard_focus_lost(struct weston_surface *surface) } } -static void -take_surface_to_workspace_by_seat(struct desktop_shell *shell, - struct weston_seat *seat, - unsigned int index) -{ - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct weston_surface *surface; - struct weston_view *view; - struct shell_surface *shsurf; - struct workspace *from; - struct workspace *to; - struct focus_state *state; - - surface = weston_surface_get_main_surface(keyboard->focus); - view = get_default_view(surface); - if (view == NULL || - index == shell->workspaces.current || - is_focus_view(view)) - return; - - from = get_current_workspace(shell); - to = get_workspace(shell, index); - - weston_layer_entry_remove(&view->layer_link); - weston_layer_entry_insert(&to->layer.view_list, &view->layer_link); - - shsurf = get_shell_surface(surface); - if (shsurf != NULL) - shell_surface_update_child_surface_layers(shsurf); - - replace_focus_state(shell, to, seat); - drop_focus_state(shell, from, surface); - - if (shell->workspaces.anim_from == to && - shell->workspaces.anim_to == from) { - reverse_workspace_change_animation(shell, index, from, to); - - return; - } - - if (shell->workspaces.anim_to != NULL) - finish_workspace_change_animation(shell, - shell->workspaces.anim_from, - shell->workspaces.anim_to); - - if (workspace_is_empty(from) && - workspace_has_only(to, surface)) - update_workspace(shell, index, from, to); - else { - if (shsurf != NULL && - wl_list_empty(&shsurf->workspace_transform.link)) - wl_list_insert(&shell->workspaces.anim_sticky_list, - &shsurf->workspace_transform.link); - - animate_workspace_change(shell, index, from, to); - } - - state = ensure_focus_state(shell, seat); - if (state != NULL) - focus_state_set_focus(state, surface); -} - static void touch_move_grab_down(struct weston_touch_grab *grab, const struct timespec *time, @@ -1593,6 +1123,9 @@ surface_move(struct shell_surface *shsurf, struct weston_pointer *pointer, pointer->grab_y; move->client_initiated = client_initiated; + weston_desktop_surface_set_orientation(shsurf->desktop_surface, + WESTON_TOP_LEVEL_TILED_ORIENTATION_NONE); + shsurf->orientation = WESTON_TOP_LEVEL_TILED_ORIENTATION_NONE; shell_grab_start(&move->base, &move_grab_interface, shsurf, pointer, WESTON_DESKTOP_SHELL_CURSOR_MOVE); @@ -1620,7 +1153,7 @@ resize_grab_motion(struct weston_pointer_grab *grab, weston_pointer_move(pointer, event); - if (!shsurf) + if (!shsurf || !shsurf->desktop_surface) return; weston_view_from_global_fixed(shsurf->view, @@ -1630,16 +1163,16 @@ resize_grab_motion(struct weston_pointer_grab *grab, pointer->x, pointer->y, &to_x, &to_y); width = resize->width; - if (resize->edges & WL_SHELL_SURFACE_RESIZE_LEFT) { + if (resize->edges & WESTON_DESKTOP_SURFACE_EDGE_LEFT) { width += wl_fixed_to_int(from_x - to_x); - } else if (resize->edges & WL_SHELL_SURFACE_RESIZE_RIGHT) { + } else if (resize->edges & WESTON_DESKTOP_SURFACE_EDGE_RIGHT) { width += wl_fixed_to_int(to_x - from_x); } height = resize->height; - if (resize->edges & WL_SHELL_SURFACE_RESIZE_TOP) { + if (resize->edges & WESTON_DESKTOP_SURFACE_EDGE_TOP) { height += wl_fixed_to_int(from_y - to_y); - } else if (resize->edges & WL_SHELL_SURFACE_RESIZE_BOTTOM) { + } else if (resize->edges & WESTON_DESKTOP_SURFACE_EDGE_BOTTOM) { height += wl_fixed_to_int(to_y - from_y); } @@ -1655,8 +1188,8 @@ resize_grab_motion(struct weston_pointer_grab *grab, width = max_size.width; if (height < min_size.height) height = min_size.height; - else if (max_size.width > 0 && width > max_size.width) - width = max_size.width; + else if (max_size.height > 0 && height > max_size.height) + height = max_size.height; weston_desktop_surface_set_size(shsurf->desktop_surface, width, height); } @@ -1671,11 +1204,12 @@ resize_grab_button(struct weston_pointer_grab *grab, if (pointer->button_count == 0 && state == WL_POINTER_BUTTON_STATE_RELEASED) { - if (resize->base.shsurf != NULL) { + if (resize->base.shsurf && resize->base.shsurf->desktop_surface) { struct weston_desktop_surface *desktop_surface = resize->base.shsurf->desktop_surface; weston_desktop_surface_set_resizing(desktop_surface, false); + weston_desktop_surface_set_size(desktop_surface, 0, 0); } shell_grab_end(&resize->base); @@ -1688,10 +1222,11 @@ resize_grab_cancel(struct weston_pointer_grab *grab) { struct weston_resize_grab *resize = (struct weston_resize_grab *) grab; - if (resize->base.shsurf != NULL) { + if (resize->base.shsurf && resize->base.shsurf->desktop_surface) { struct weston_desktop_surface *desktop_surface = resize->base.shsurf->desktop_surface; weston_desktop_surface_set_resizing(desktop_surface, false); + weston_desktop_surface_set_size(desktop_surface, 0, 0); } shell_grab_end(&resize->base); @@ -1715,9 +1250,9 @@ surface_resize(struct shell_surface *shsurf, { struct weston_resize_grab *resize; const unsigned resize_topbottom = - WL_SHELL_SURFACE_RESIZE_TOP | WL_SHELL_SURFACE_RESIZE_BOTTOM; + WESTON_DESKTOP_SURFACE_EDGE_TOP | WESTON_DESKTOP_SURFACE_EDGE_BOTTOM; const unsigned resize_leftright = - WL_SHELL_SURFACE_RESIZE_LEFT | WL_SHELL_SURFACE_RESIZE_RIGHT; + WESTON_DESKTOP_SURFACE_EDGE_LEFT | WESTON_DESKTOP_SURFACE_EDGE_RIGHT; const unsigned resize_any = resize_topbottom | resize_leftright; struct weston_geometry geometry; @@ -1727,7 +1262,7 @@ surface_resize(struct shell_surface *shsurf, return 0; /* Check for invalid edge combinations. */ - if (edges == WL_SHELL_SURFACE_RESIZE_NONE || edges > resize_any || + if (edges == WESTON_DESKTOP_SURFACE_EDGE_NONE || edges > resize_any || (edges & resize_topbottom) == resize_topbottom || (edges & resize_leftright) == resize_leftright) return 0; @@ -1744,6 +1279,9 @@ surface_resize(struct shell_surface *shsurf, shsurf->resize_edges = edges; weston_desktop_surface_set_resizing(shsurf->desktop_surface, true); + weston_desktop_surface_set_orientation(shsurf->desktop_surface, + WESTON_TOP_LEVEL_TILED_ORIENTATION_NONE); + shsurf->orientation = WESTON_TOP_LEVEL_TILED_ORIENTATION_NONE; shell_grab_start(&resize->base, &resize_grab_interface, shsurf, pointer, edges); @@ -1961,7 +1499,7 @@ unset_fullscreen(struct shell_surface *shsurf) wl_list_init(&shsurf->fullscreen.transform.link); if (shsurf->fullscreen.black_view) - weston_surface_destroy(shsurf->fullscreen.black_view->surface); + weston_curtain_destroy(shsurf->fullscreen.black_view); shsurf->fullscreen.black_view = NULL; if (shsurf->saved_position_valid) @@ -1971,6 +1509,9 @@ unset_fullscreen(struct shell_surface *shsurf) weston_view_set_initial_position(shsurf->view, shsurf->shell); shsurf->saved_position_valid = false; + weston_desktop_surface_set_orientation(shsurf->desktop_surface, + shsurf->orientation); + if (shsurf->saved_rotation_valid) { wl_list_insert(&shsurf->view->geometry.transformation_list, &shsurf->rotation.transform.link); @@ -1994,6 +1535,9 @@ unset_maximized(struct shell_surface *shsurf) weston_view_set_initial_position(shsurf->view, shsurf->shell); shsurf->saved_position_valid = false; + weston_desktop_surface_set_orientation(shsurf->desktop_surface, + shsurf->orientation); + if (shsurf->saved_rotation_valid) { wl_list_insert(&shsurf->view->geometry.transformation_list, &shsurf->rotation.transform.link); @@ -2069,27 +1613,21 @@ black_surface_get_label(struct weston_surface *surface, char *buf, size_t len) } static void -black_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy); - -static struct weston_view * -create_black_surface(struct weston_compositor *ec, - struct weston_view *fs_view, - float x, float y, int w, int h) +black_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) { - struct weston_solid_color_surface surface_data = {}; - - surface_data.surface_committed = black_surface_committed; - surface_data.get_label = black_surface_get_label; - surface_data.surface_private = fs_view; - - surface_data.r = 0; - surface_data.g = 0; - surface_data.b = 0; +} - struct weston_view *view = - create_solid_color_surface(ec, &surface_data, x, y, w, h); +static bool +is_black_surface_view(struct weston_view *view, struct weston_view **fs_view) +{ + struct weston_surface *surface = view->surface; - return view; + if (surface->committed == black_surface_committed) { + if (fs_view) + *fs_view = surface->committed_private; + return true; + } + return false; } static void @@ -2097,26 +1635,35 @@ shell_ensure_fullscreen_black_view(struct shell_surface *shsurf) { struct weston_surface *surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); + struct weston_compositor *ec = surface->compositor; struct weston_output *output = shsurf->fullscreen_output; + struct weston_curtain_params curtain_params = { + .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0, + .x = output->x, .y = output->y, + .width = output->width, .height = output->height, + .surface_committed = black_surface_committed, + .get_label = black_surface_get_label, + .surface_private = shsurf->view, + .capture_input = true, + }; + struct weston_view *view; assert(weston_desktop_surface_get_fullscreen(shsurf->desktop_surface)); - if (!shsurf->fullscreen.black_view) + if (!shsurf->fullscreen.black_view) { shsurf->fullscreen.black_view = - create_black_surface(surface->compositor, - shsurf->view, - output->x, output->y, - output->width, - output->height); - - weston_view_geometry_dirty(shsurf->fullscreen.black_view); - weston_layer_entry_remove(&shsurf->fullscreen.black_view->layer_link); - weston_layer_entry_insert(&shsurf->view->layer_link, - &shsurf->fullscreen.black_view->layer_link); - weston_view_geometry_dirty(shsurf->fullscreen.black_view); + weston_curtain_create(ec, &curtain_params); + } + view = shsurf->fullscreen.black_view->view; + + weston_view_set_output(view, output); + view->is_mapped = true; + + weston_layer_entry_remove(&view->layer_link); + weston_layer_entry_insert(&shsurf->view->layer_link, &view->layer_link); + weston_view_geometry_dirty(view); weston_surface_damage(surface); - shsurf->fullscreen.black_view->is_mapped = true; shsurf->state.lowered = false; } @@ -2146,7 +1693,7 @@ shell_configure_fullscreen(struct shell_surface *shsurf) surface_subsurfaces_boundingbox(surface, &surf_x, &surf_y, &surf_width, &surf_height); - if (surface->buffer_ref.buffer) + if (weston_surface_has_content(surface)) center_on_output(shsurf->view, shsurf->fullscreen_output); } @@ -2328,8 +1875,6 @@ desktop_surface_added(struct weston_desktop_surface *desktop_surface, wl_list_init(&shsurf->rotation.transform.link); weston_matrix_init(&shsurf->rotation.rotation); - wl_list_init(&shsurf->workspace_transform.link); - /* * initialize list as well as link. The latter allows to use * wl_list_remove() even when this surface is not in another list. @@ -2365,8 +1910,10 @@ desktop_surface_removed(struct weston_desktop_surface *desktop_surface, shseat->focused_surface = NULL; } - if (shsurf->fullscreen.black_view) - weston_surface_destroy(shsurf->fullscreen.black_view->surface); + if (shsurf->fullscreen.black_view) { + weston_curtain_destroy(shsurf->fullscreen.black_view); + shsurf->fullscreen.black_view = NULL; + } weston_surface_set_label_func(surface, NULL); weston_desktop_surface_set_user_data(shsurf->desktop_surface, NULL); @@ -2474,6 +2021,8 @@ map(struct desktop_shell *shell, struct shell_surface *shsurf, weston_view_set_output(shsurf->view, shsurf->output); } + weston_surface_map(surface); + if (!shell->locked) { wl_list_for_each(seat, &compositor->seat_list, link) activate(shell, shsurf->view, seat, @@ -2521,15 +2070,15 @@ desktop_surface_committed(struct weston_desktop_surface *desktop_surface, if (!weston_surface_is_mapped(surface)) { map(shell, shsurf, sx, sy); - surface->is_mapped = true; /* as we need to survive the weston_surface destruction we'll * need to take another reference */ if (shsurf->shell->win_close_animation_type == ANIMATION_FADE) { - surface->ref_count++; - shsurf->wsurface_anim_fade = surface; + shsurf->wsurface_anim_fade = + weston_surface_ref(surface); shsurf->wview_anim_fade = shell_fade_create_fade_out_view(shsurf, surface); } + return; } @@ -2574,9 +2123,9 @@ desktop_surface_committed(struct weston_desktop_surface *desktop_surface, sy = 0; } - if (shsurf->resize_edges & WL_SHELL_SURFACE_RESIZE_LEFT) + if (shsurf->resize_edges & WESTON_DESKTOP_SURFACE_EDGE_LEFT) sx = shsurf->last_width - surface->width; - if (shsurf->resize_edges & WL_SHELL_SURFACE_RESIZE_TOP) + if (shsurf->resize_edges & WESTON_DESKTOP_SURFACE_EDGE_TOP) sy = shsurf->last_height - surface->height; weston_view_to_global_float(shsurf->view, 0, 0, &from_x, &from_y); @@ -2633,6 +2182,8 @@ set_fullscreen(struct shell_surface *shsurf, bool fullscreen, width = shsurf->output->width; height = shsurf->output->height; } + weston_desktop_surface_set_orientation(shsurf->desktop_surface, + WESTON_TOP_LEVEL_TILED_ORIENTATION_NONE); } else if (weston_desktop_surface_get_maximized(desktop_surface)) { get_maximized_size(shsurf, &width, &height); } @@ -2738,6 +2289,9 @@ set_maximized(struct shell_surface *shsurf, bool maximized) weston_desktop_surface_get_surface(shsurf->desktop_surface); int32_t width = 0, height = 0; + if (weston_desktop_surface_get_fullscreen(desktop_surface)) + return; + if (maximized) { struct weston_output *output; @@ -2749,6 +2303,9 @@ set_maximized(struct shell_surface *shsurf, bool maximized) shell_surface_set_output(shsurf, output); get_maximized_size(shsurf, &width, &height); + + weston_desktop_surface_set_orientation(shsurf->desktop_surface, + WESTON_TOP_LEVEL_TILED_ORIENTATION_NONE); } weston_desktop_surface_set_maximized(desktop_surface, maximized); weston_desktop_surface_set_size(desktop_surface, width, height); @@ -2894,6 +2451,17 @@ desktop_surface_set_xwayland_position(struct weston_desktop_surface *surface, shsurf->xwayland.is_set = true; } +static void +desktop_surface_get_position(struct weston_desktop_surface *surface, + int32_t *x, int32_t *y, + void *shell_) +{ + struct shell_surface *shsurf = weston_desktop_surface_get_user_data(surface); + + *x = shsurf->view->geometry.x; + *y = shsurf->view->geometry.y; +} + static const struct weston_desktop_api shell_desktop_api = { .struct_size = sizeof(struct weston_desktop_api), .surface_added = desktop_surface_added, @@ -2908,6 +2476,7 @@ static const struct weston_desktop_api shell_desktop_api = { .ping_timeout = desktop_surface_ping_timeout, .pong = desktop_surface_pong, .set_xwayland_position = desktop_surface_set_xwayland_position, + .get_position = desktop_surface_get_position, }; /* ************************ * @@ -2930,7 +2499,7 @@ configure_static_view(struct weston_view *ev, struct weston_layer *layer, int x, } weston_view_set_position(ev, ev->output->x + x, ev->output->y + y); - ev->surface->is_mapped = true; + weston_surface_map(ev->surface); ev->is_mapped = true; if (wl_list_empty(&ev->layer_link.link)) { @@ -3152,7 +2721,7 @@ lock_surface_committed(struct weston_surface *surface, int32_t sx, int32_t sy) weston_layer_entry_insert(&shell->lock_layer.view_list, &view->layer_link); weston_view_update_transform(view); - surface->is_mapped = true; + weston_surface_map(surface); view->is_mapped = true; shell_fade(shell, FADE_IN); } @@ -3347,6 +2916,84 @@ fullscreen_binding(struct weston_keyboard *keyboard, set_fullscreen(shsurf, !fullscreen, NULL); } +static void +set_tiled_orientation(struct weston_surface *focus, + enum weston_top_level_tiled_orientation orientation) +{ + struct weston_surface *surface; + struct shell_surface *shsurf; + int width, height; + pixman_rectangle32_t area; + struct weston_geometry geom; + int x, y; + + surface = weston_surface_get_main_surface(focus); + if (surface == NULL) + return; + + shsurf = get_shell_surface(surface); + if (shsurf == NULL) + return; + + shsurf->orientation = orientation; + get_maximized_size(shsurf, &width, &height); + get_output_work_area(shsurf->shell, shsurf->output, &area); + geom = weston_desktop_surface_get_geometry(shsurf->desktop_surface); + + if (orientation & WESTON_TOP_LEVEL_TILED_ORIENTATION_LEFT || + orientation & WESTON_TOP_LEVEL_TILED_ORIENTATION_RIGHT) + width /= 2; + else if (orientation & WESTON_TOP_LEVEL_TILED_ORIENTATION_TOP || + orientation & WESTON_TOP_LEVEL_TILED_ORIENTATION_BOTTOM) + height /= 2; + + x = area.x - geom.x; + y = area.y - geom.y; + + if (orientation & WESTON_TOP_LEVEL_TILED_ORIENTATION_RIGHT) + x += width; + else if (orientation & WESTON_TOP_LEVEL_TILED_ORIENTATION_BOTTOM) + y += height; + + weston_view_set_position(shsurf->view, x, y); + weston_desktop_surface_set_size(shsurf->desktop_surface, width, height); + weston_desktop_surface_set_orientation(shsurf->desktop_surface, orientation); + weston_compositor_schedule_repaint(surface->compositor); +} + +static void +set_tiled_orientation_left(struct weston_keyboard *keyboard, + const struct timespec *time, + uint32_t button, void *data) +{ + set_tiled_orientation(keyboard->focus, WESTON_TOP_LEVEL_TILED_ORIENTATION_LEFT); + +} + +static void +set_tiled_orientation_right(struct weston_keyboard *keyboard, + const struct timespec *time, + uint32_t button, void *data) +{ + set_tiled_orientation(keyboard->focus, WESTON_TOP_LEVEL_TILED_ORIENTATION_RIGHT); +} + +static void +set_tiled_orientation_up(struct weston_keyboard *keyboard, + const struct timespec *time, + uint32_t button, void *data) +{ + set_tiled_orientation(keyboard->focus, WESTON_TOP_LEVEL_TILED_ORIENTATION_TOP); +} + +static void +set_tiled_orientation_down(struct weston_keyboard *keyboard, + const struct timespec *time, + uint32_t button, void *data) +{ + set_tiled_orientation(keyboard->focus, WESTON_TOP_LEVEL_TILED_ORIENTATION_BOTTOM); +} + static void touch_move_binding(struct weston_touch *touch, const struct timespec *time, void *data) { @@ -3402,18 +3049,18 @@ resize_binding(struct weston_pointer *pointer, const struct timespec *time, &x, &y); if (x < surface->width / 3) - edges |= WL_SHELL_SURFACE_RESIZE_LEFT; + edges |= WESTON_DESKTOP_SURFACE_EDGE_LEFT; else if (x < 2 * surface->width / 3) edges |= 0; else - edges |= WL_SHELL_SURFACE_RESIZE_RIGHT; + edges |= WESTON_DESKTOP_SURFACE_EDGE_RIGHT; if (y < surface->height / 3) - edges |= WL_SHELL_SURFACE_RESIZE_TOP; + edges |= WESTON_DESKTOP_SURFACE_EDGE_TOP; else if (y < 2 * surface->height / 3) edges |= 0; else - edges |= WL_SHELL_SURFACE_RESIZE_BOTTOM; + edges |= WESTON_DESKTOP_SURFACE_EDGE_BOTTOM; surface_resize(shsurf, pointer, edges); } @@ -3449,71 +3096,6 @@ surface_opacity_binding(struct weston_pointer *pointer, weston_surface_damage(surface); } -static void -do_zoom(struct weston_seat *seat, const struct timespec *time, uint32_t key, - uint32_t axis, double value) -{ - struct weston_compositor *compositor = seat->compositor; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_output *output; - float increment; - - if (!pointer) { - weston_log("Zoom hotkey pressed but seat '%s' contains no pointer.\n", seat->seat_name); - return; - } - - wl_list_for_each(output, &compositor->output_list, link) { - if (pixman_region32_contains_point(&output->region, - wl_fixed_to_double(pointer->x), - wl_fixed_to_double(pointer->y), - NULL)) { - if (key == KEY_PAGEUP) - increment = output->zoom.increment; - else if (key == KEY_PAGEDOWN) - increment = -output->zoom.increment; - else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) - /* For every pixel zoom 20th of a step */ - increment = output->zoom.increment * - -value / 20.0; - else - increment = 0; - - output->zoom.level += increment; - - if (output->zoom.level < 0.0) - output->zoom.level = 0.0; - else if (output->zoom.level > output->zoom.max_level) - output->zoom.level = output->zoom.max_level; - - if (!output->zoom.active) { - if (output->zoom.level <= 0.0) - continue; - weston_output_activate_zoom(output, seat); - } - - output->zoom.spring_z.target = output->zoom.level; - - weston_output_update_zoom(output); - } - } -} - -static void -zoom_axis_binding(struct weston_pointer *pointer, const struct timespec *time, - struct weston_pointer_axis_event *event, - void *data) -{ - do_zoom(pointer->seat, time, 0, event->axis, event->value); -} - -static void -zoom_key_binding(struct weston_keyboard *keyboard, const struct timespec *time, - uint32_t key, void *data) -{ - do_zoom(keyboard->seat, time, key, 0, 0); -} - static void terminate_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) @@ -3734,10 +3316,9 @@ lower_fullscreen_layer(struct desktop_shell *shell, * in the fullscreen layer. */ if (weston_desktop_surface_get_fullscreen(shsurf->desktop_surface)) { /* Hide the black view */ - weston_layer_entry_remove(&shsurf->fullscreen.black_view->layer_link); - wl_list_init(&shsurf->fullscreen.black_view->layer_link.link); - weston_view_damage_below(shsurf->fullscreen.black_view); - + weston_layer_entry_remove(&shsurf->fullscreen.black_view->view->layer_link); + wl_list_init(&shsurf->fullscreen.black_view->view->layer_link.link); + weston_view_damage_below(shsurf->fullscreen.black_view->view); } /* Lower the view to the workspace layer */ @@ -3792,16 +3373,18 @@ activate(struct desktop_shell *shell, struct weston_view *view, weston_view_activate_input(view, seat, flags); - if (shseat && shseat->focused_surface) { + if (shseat && shseat->focused_surface && + shseat->focused_surface != main_surface) { struct shell_surface *current_focus = get_shell_surface(shseat->focused_surface); assert(current_focus); shell_surface_deactivate(current_focus); } - if (shseat) + if (shseat && shseat->focused_surface != main_surface) { + shell_surface_activate(shsurf); shseat->focused_surface = main_surface; - shell_surface_activate(shsurf); + } state = ensure_focus_state(shell, seat); if (state == NULL) @@ -3824,25 +3407,6 @@ activate(struct desktop_shell *shell, struct weston_view *view, } } -/* no-op func for checking black surface */ -static void -black_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) -{ -} - -static bool -is_black_surface_view(struct weston_view *view, struct weston_view **fs_view) -{ - struct weston_surface *surface = view->surface; - - if (surface->committed == black_surface_committed) { - if (fs_view) - *fs_view = surface->committed_private; - return true; - } - return false; -} - static void activate_binding(struct weston_seat *seat, struct desktop_shell *shell, @@ -3980,8 +3544,8 @@ shell_fade_done_for_output(struct weston_view_animation *animation, void *data) shell_output->fade.animation = NULL; switch (shell_output->fade.type) { case FADE_IN: - weston_surface_destroy(shell_output->fade.view->surface); - shell_output->fade.view = NULL; + weston_curtain_destroy(shell_output->fade.curtain); + shell_output->fade.curtain = NULL; break; case FADE_OUT: lock(shell); @@ -3991,33 +3555,45 @@ shell_fade_done_for_output(struct weston_view_animation *animation, void *data) } } -static struct weston_view * -shell_fade_create_surface_for_output(struct desktop_shell *shell, struct shell_output *shell_output) +static int +fade_surface_get_label(struct weston_surface *surface, + char *buf, size_t len) { - struct weston_compositor *compositor = shell->compositor; - struct weston_surface *surface; - struct weston_view *view; + struct shell_output *output = surface->committed_private; - surface = weston_surface_create(compositor); - if (!surface) - return NULL; + return snprintf(buf, len, "desktop shell fade surface for %s", + output->output->name); +} - view = weston_view_create(surface); - if (!view) { - weston_surface_destroy(surface); - return NULL; - } +static struct weston_curtain * +shell_fade_create_view_for_output(struct desktop_shell *shell, + struct shell_output *shell_output) +{ + struct weston_compositor *compositor = shell->compositor; + struct weston_output *output = shell_output->output; + struct weston_curtain_params curtain_params = { + .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0, + .x = output->x, .y = output->y, + .width = output->width, .height = output->height, + .surface_committed = black_surface_committed, + .get_label = fade_surface_get_label, + .surface_private = shell_output, + .capture_input = false, + }; + struct weston_curtain *curtain; + + curtain = weston_curtain_create(compositor, &curtain_params); + assert(curtain); + + weston_view_set_output(curtain->view, output); + curtain->view->is_mapped = true; - weston_surface_set_size(surface, shell_output->output->width, shell_output->output->height); - weston_view_set_position(view, shell_output->output->x, shell_output->output->y); - weston_surface_set_color(surface, 0.0, 0.0, 0.0, 1.0); weston_layer_entry_insert(&compositor->fade_layer.view_list, - &view->layer_link); - pixman_region32_init(&surface->input); - surface->is_mapped = true; - view->is_mapped = true; + &curtain->view->layer_link); + weston_view_geometry_dirty(curtain->view); + weston_surface_damage(curtain->view->surface); - return view; + return curtain; } static struct weston_view * @@ -4065,28 +3641,29 @@ shell_fade(struct desktop_shell *shell, enum fade_type type) wl_list_for_each(shell_output, &shell->output_list, link) { shell_output->fade.type = type; - if (shell_output->fade.view == NULL) { - shell_output->fade.view = shell_fade_create_surface_for_output(shell, shell_output); - if (!shell_output->fade.view) + if (shell_output->fade.curtain == NULL) { + shell_output->fade.curtain = + shell_fade_create_view_for_output(shell, shell_output); + if (!shell_output->fade.curtain) continue; - shell_output->fade.view->alpha = 1.0 - tint; - weston_view_update_transform(shell_output->fade.view); + shell_output->fade.curtain->view->alpha = 1.0 - tint; + weston_view_update_transform(shell_output->fade.curtain->view); } - if (shell_output->fade.view->output == NULL) { + if (shell_output->fade.curtain->view->output == NULL) { /* If the black view gets a NULL output, we lost the * last output and we'll just cancel the fade. This * happens when you close the last window under the * X11 or Wayland backends. */ shell->locked = false; - weston_surface_destroy(shell_output->fade.view->surface); - shell_output->fade.view = NULL; + weston_curtain_destroy(shell_output->fade.curtain); + shell_output->fade.curtain = NULL; } else if (shell_output->fade.animation) { weston_fade_update(shell_output->fade.animation, tint); } else { shell_output->fade.animation = - weston_fade_run(shell_output->fade.view, + weston_fade_run(shell_output->fade.curtain->view, 1.0 - tint, tint, 300.0, shell_fade_done_for_output, shell_output); } @@ -4106,8 +3683,8 @@ do_shell_fade_startup(void *data) "unexpected fade-in animation type %d\n", shell->startup_animation_type); wl_list_for_each(shell_output, &shell->output_list, link) { - weston_surface_destroy(shell_output->fade.view->surface); - shell_output->fade.view = NULL; + weston_curtain_destroy(shell_output->fade.curtain); + shell_output->fade.curtain = NULL; } } } @@ -4158,18 +3735,19 @@ shell_fade_init(struct desktop_shell *shell) return; wl_list_for_each(shell_output, &shell->output_list, link) { - if (shell_output->fade.view != NULL) { + if (shell_output->fade.curtain != NULL) { weston_log("%s: warning: fade surface already exists\n", __func__); continue; } - shell_output->fade.view = shell_fade_create_surface_for_output(shell, shell_output); - if (!shell_output->fade.view) + shell_output->fade.curtain = + shell_fade_create_view_for_output(shell, shell_output); + if (!shell_output->fade.curtain) continue; - weston_view_update_transform(shell_output->fade.view); - weston_surface_damage(shell_output->fade.view->surface); + weston_view_update_transform(shell_output->fade.curtain->view); + weston_surface_damage(shell_output->fade.curtain->view->surface); loop = wl_display_get_event_loop(shell->compositor->wl_display); shell_output->fade.startup_timer = @@ -4213,6 +3791,8 @@ transform_handler(struct wl_listener *listener, void *data) if (!shsurf) return; + shell_surface_set_output(shsurf, shsurf->view->output); + api = shsurf->shell->xwayland_surface_api; if (!api) { api = weston_xwayland_surface_get_api(shsurf->shell->compositor); @@ -4480,7 +4060,7 @@ switcher_next(struct switcher *switcher) shsurf = get_shell_surface(switcher->current->surface); if (shsurf && weston_desktop_surface_get_fullscreen(shsurf->desktop_surface)) - shsurf->fullscreen.black_view->alpha = 1.0; + shsurf->fullscreen.black_view->view->alpha = 1.0; } static void @@ -4654,86 +4234,6 @@ force_kill_binding(struct weston_keyboard *keyboard, kill(pid, SIGKILL); } -static void -workspace_up_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, void *data) -{ - struct desktop_shell *shell = data; - unsigned int new_index = shell->workspaces.current; - - if (shell->locked) - return; - if (new_index != 0) - new_index--; - - change_workspace(shell, new_index); -} - -static void -workspace_down_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, void *data) -{ - struct desktop_shell *shell = data; - unsigned int new_index = shell->workspaces.current; - - if (shell->locked) - return; - if (new_index < shell->workspaces.num - 1) - new_index++; - - change_workspace(shell, new_index); -} - -static void -workspace_f_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, void *data) -{ - struct desktop_shell *shell = data; - unsigned int new_index; - - if (shell->locked) - return; - new_index = key - KEY_F1; - if (new_index >= shell->workspaces.num) - new_index = shell->workspaces.num - 1; - - change_workspace(shell, new_index); -} - -static void -workspace_move_surface_up_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, - void *data) -{ - struct desktop_shell *shell = data; - unsigned int new_index = shell->workspaces.current; - - if (shell->locked) - return; - - if (new_index != 0) - new_index--; - - take_surface_to_workspace_by_seat(shell, keyboard->seat, new_index); -} - -static void -workspace_move_surface_down_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, - void *data) -{ - struct desktop_shell *shell = data; - unsigned int new_index = shell->workspaces.current; - - if (shell->locked) - return; - - if (new_index < shell->workspaces.num - 1) - new_index++; - - take_surface_to_workspace_by_seat(shell, keyboard->seat, new_index); -} - static void shell_reposition_view_on_output_change(struct weston_view *view) { @@ -4787,16 +4287,12 @@ void shell_for_each_layer(struct desktop_shell *shell, shell_for_each_layer_func_t func, void *data) { - struct workspace **ws; - func(shell, &shell->fullscreen_layer, data); func(shell, &shell->panel_layer, data); func(shell, &shell->background_layer, data); func(shell, &shell->lock_layer, data); func(shell, &shell->input_panel_layer, data); - - wl_array_for_each(ws, &shell->workspaces.array) - func(shell, &(*ws)->layer, data); + func(shell, &shell->workspace.layer, data); } static void @@ -4823,10 +4319,8 @@ shell_output_destroy(struct shell_output *shell_output) shell_output->fade.animation = NULL; } - if (shell_output->fade.view) { - /* destroys the view as well */ - weston_surface_destroy(shell_output->fade.view->surface); - } + if (shell_output->fade.curtain) + weston_curtain_destroy(shell_output->fade.curtain); if (shell_output->fade.startup_timer) wl_event_source_remove(shell_output->fade.startup_timer); @@ -4958,11 +4452,12 @@ setup_output_destroy_handler(struct weston_compositor *ec, static void desktop_shell_destroy_layer(struct weston_layer *layer) { - struct weston_view *view, *view_next; + struct weston_view *view; + bool removed; + + do { + removed = false; - wl_list_for_each_safe(view, view_next, &layer->view_list.link, layer_link.link) { - struct shell_surface *shsurf = - get_shell_surface(view->surface); /* fullscreen_layer is special as it would have a view with an * additional black_view created and added to its layer_link * fullscreen view. See shell_ensure_fullscreen_black_view() @@ -4977,12 +4472,22 @@ desktop_shell_destroy_layer(struct weston_layer *layer) * could create additional views, which are managed implicitly, * but which are still being added to the layer list. * + * We avoid using wl_list_for_each_safe() as it can't handle + * removal of the next item in the list, so with this approach + * we restart the loop as long as we keep removing views from + * the list. */ - if (shsurf) - desktop_shell_destroy_surface(shsurf); - else - weston_surface_destroy(view->surface); - } + wl_list_for_each(view, &layer->view_list.link, layer_link.link) { + struct shell_surface *shsurf = + get_shell_surface(view->surface); + if (shsurf) { + desktop_shell_destroy_surface(shsurf); + removed = true; + break; + } + } + + } while (removed); weston_layer_fini(layer); } @@ -4992,7 +4497,6 @@ shell_destroy(struct wl_listener *listener, void *data) { struct desktop_shell *shell = container_of(listener, struct desktop_shell, destroy_listener); - struct workspace **ws; struct shell_output *shell_output, *tmp; struct shell_seat *shseat, *shseat_next; @@ -5025,9 +4529,7 @@ shell_destroy(struct wl_listener *listener, void *data) weston_desktop_destroy(shell->desktop); - wl_array_for_each(ws, &shell->workspaces.array) - workspace_destroy(*ws); - wl_array_release(&shell->workspaces.array); + workspace_destroy(&shell->workspace); desktop_shell_destroy_layer(&shell->panel_layer); desktop_shell_destroy_layer(&shell->background_layer); @@ -5044,7 +4546,6 @@ static void shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) { uint32_t mod; - int i, num_workspace_bindings; if (shell->allow_zap) weston_compositor_add_key_binding(ec, KEY_BACKSPACE, @@ -5066,11 +4567,6 @@ shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) weston_compositor_add_key_binding(ec, KEY_BRIGHTNESSUP, 0, backlight_binding, ec); - /* configurable bindings */ - if (shell->exposay_modifier) - weston_compositor_add_modifier_binding(ec, shell->exposay_modifier, - exposay_binding, shell); - mod = shell->binding_modifier; if (!mod) return; @@ -5081,14 +4577,6 @@ shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) MODIFIER_SUPER | MODIFIER_ALT, surface_opacity_binding, NULL); - weston_compositor_add_axis_binding(ec, WL_POINTER_AXIS_VERTICAL_SCROLL, - mod, zoom_axis_binding, - NULL); - - weston_compositor_add_key_binding(ec, KEY_PAGEUP, mod, - zoom_key_binding, NULL); - weston_compositor_add_key_binding(ec, KEY_PAGEDOWN, mod, - zoom_key_binding, NULL); weston_compositor_add_key_binding(ec, KEY_M, mod | MODIFIER_SHIFT, maximize_binding, NULL); weston_compositor_add_key_binding(ec, KEY_F, mod | MODIFIER_SHIFT, @@ -5102,6 +4590,15 @@ shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) mod | MODIFIER_SHIFT, resize_binding, shell); + weston_compositor_add_key_binding(ec, KEY_LEFT, mod | MODIFIER_SHIFT, + set_tiled_orientation_left, NULL); + weston_compositor_add_key_binding(ec, KEY_RIGHT, mod | MODIFIER_SHIFT, + set_tiled_orientation_right, NULL); + weston_compositor_add_key_binding(ec, KEY_UP, mod | MODIFIER_SHIFT, + set_tiled_orientation_up, NULL); + weston_compositor_add_key_binding(ec, KEY_DOWN, mod | MODIFIER_SHIFT, + set_tiled_orientation_down, NULL); + if (ec->capabilities & WESTON_CAP_ROTATION_ANY) weston_compositor_add_button_binding(ec, BTN_MIDDLE, mod, rotate_binding, NULL); @@ -5114,27 +4611,6 @@ shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) ec); weston_compositor_add_key_binding(ec, KEY_K, mod, force_kill_binding, shell); - weston_compositor_add_key_binding(ec, KEY_UP, mod, - workspace_up_binding, shell); - weston_compositor_add_key_binding(ec, KEY_DOWN, mod, - workspace_down_binding, shell); - weston_compositor_add_key_binding(ec, KEY_UP, mod | MODIFIER_SHIFT, - workspace_move_surface_up_binding, - shell); - weston_compositor_add_key_binding(ec, KEY_DOWN, mod | MODIFIER_SHIFT, - workspace_move_surface_down_binding, - shell); - - /* Add bindings for mod+F[1-6] for workspace 1 to 6. */ - if (shell->workspaces.num > 1) { - num_workspace_bindings = shell->workspaces.num; - if (num_workspace_bindings > 6) - num_workspace_bindings = 6; - for (i = 0; i < num_workspace_bindings; i++) - weston_compositor_add_key_binding(ec, KEY_F1 + i, mod, - workspace_f_binding, - shell); - } weston_install_debug_key_binding(ec, mod); } @@ -5155,8 +4631,6 @@ wet_shell_init(struct weston_compositor *ec, { struct weston_seat *seat; struct desktop_shell *shell; - struct workspace **pws; - unsigned int i; struct wl_event_loop *loop; shell = zalloc(sizeof *shell); @@ -5192,8 +4666,6 @@ wet_shell_init(struct weston_compositor *ec, weston_layer_set_position(&shell->background_layer, WESTON_LAYER_POSITION_BACKGROUND); - wl_array_init(&shell->workspaces.array); - wl_list_init(&shell->workspaces.client_list); wl_list_init(&shell->seat_list); if (input_panel_setup(shell) < 0) @@ -5205,26 +4677,10 @@ wet_shell_init(struct weston_compositor *ec, shell_configuration(shell); - shell->exposay.state_cur = EXPOSAY_LAYOUT_INACTIVE; - shell->exposay.state_target = EXPOSAY_TARGET_CANCEL; - - for (i = 0; i < shell->workspaces.num; i++) { - pws = wl_array_add(&shell->workspaces.array, sizeof *pws); - if (pws == NULL) - return -1; - - *pws = workspace_create(shell); - if (*pws == NULL) - return -1; - } - activate_workspace(shell, 0); + workspace_create(shell); weston_layer_init(&shell->minimized_layer, ec); - wl_list_init(&shell->workspaces.anim_sticky_list); - wl_list_init(&shell->workspaces.animation.link); - shell->workspaces.animation.frame = animate_workspace_change_frame; - shell->desktop = weston_desktop_create(ec, &shell_desktop_api, shell); if (!shell->desktop) return -1; diff --git a/desktop-shell/shell.h b/desktop-shell/shell.h index b06b9066..e9e123e9 100644 --- a/desktop-shell/shell.h +++ b/desktop-shell/shell.h @@ -45,55 +45,8 @@ enum fade_type { FADE_OUT }; -enum exposay_target_state { - EXPOSAY_TARGET_OVERVIEW, /* show all windows */ - EXPOSAY_TARGET_CANCEL, /* return to normal, same focus */ - EXPOSAY_TARGET_SWITCH, /* return to normal, switch focus */ -}; - -enum exposay_layout_state { - EXPOSAY_LAYOUT_INACTIVE = 0, /* normal desktop */ - EXPOSAY_LAYOUT_ANIMATE_TO_INACTIVE, /* in transition to normal */ - EXPOSAY_LAYOUT_OVERVIEW, /* show all windows */ - EXPOSAY_LAYOUT_ANIMATE_TO_OVERVIEW, /* in transition to all windows */ -}; - -struct exposay_output { - int num_surfaces; - int grid_size; - int surface_size; - int padding_inner; -}; - -struct exposay { - /* XXX: Make these exposay_surfaces. */ - struct weston_view *focus_prev; - struct weston_view *focus_current; - struct weston_view *clicked; - struct workspace *workspace; - struct weston_seat *seat; - - struct wl_list surface_list; - - struct weston_keyboard_grab grab_kbd; - struct weston_pointer_grab grab_ptr; - - enum exposay_target_state state_target; - enum exposay_layout_state state_cur; - int in_flight; /* number of animations still running */ - - int row_current; - int column_current; - struct exposay_output *cur_output; - - bool mod_pressed; - bool mod_invalid; -}; - struct focus_surface { - struct weston_surface *surface; - struct weston_view *view; - struct weston_transform workspace_transform; + struct weston_curtain *curtain; }; struct workspace { @@ -110,7 +63,6 @@ struct workspace { struct shell_output { struct desktop_shell *shell; struct weston_output *output; - struct exposay_output eoutput; struct wl_listener destroy_listener; struct wl_list link; @@ -121,7 +73,7 @@ struct shell_output { struct wl_listener background_surface_listener; struct { - struct weston_view *view; + struct weston_curtain *curtain; struct weston_view_animation *animation; enum fade_type type; struct wl_event_source *startup_timer; @@ -175,32 +127,15 @@ struct desktop_shell { struct weston_surface *lock_surface; struct wl_listener lock_surface_listener; - struct { - struct wl_array array; - unsigned int current; - unsigned int num; - - struct wl_list client_list; - - struct weston_animation animation; - struct wl_list anim_sticky_list; - int anim_dir; - struct timespec anim_timestamp; - double anim_current; - struct workspace *anim_from; - struct workspace *anim_to; - } workspaces; + struct workspace workspace; struct { struct wl_resource *binding; struct wl_list surfaces; } input_panel; - struct exposay exposay; - bool allow_zap; uint32_t binding_modifier; - uint32_t exposay_modifier; enum animation_type win_animation_type; enum animation_type win_close_animation_type; enum animation_type startup_animation_type; @@ -246,10 +181,6 @@ void activate(struct desktop_shell *shell, struct weston_view *view, struct weston_seat *seat, uint32_t flags); -void -exposay_binding(struct weston_keyboard *keyboard, - enum weston_keyboard_modifier modifier, - void *data); int input_panel_setup(struct desktop_shell *shell); void diff --git a/doc/sphinx/doxygen.ini.in b/doc/sphinx/doxygen.ini.in index bfe77e8e..29f57882 100644 --- a/doc/sphinx/doxygen.ini.in +++ b/doc/sphinx/doxygen.ini.in @@ -759,7 +759,7 @@ WARN_NO_PARAMDOC = NO # a warning is encountered. # The default value is: NO. -WARN_AS_ERROR = YES +WARN_AS_ERROR = @MESON_WERROR@ # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which @@ -1486,17 +1486,6 @@ EXT_LINKS_IN_WINDOW = NO FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # http://www.mathjax.org) which uses client side Javascript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX @@ -1632,241 +1621,6 @@ EXTERNAL_SEARCH_ID = EXTRA_SEARCH_MAPPINGS = -#--------------------------------------------------------------------------- -# Configuration options related to the LaTeX output -#--------------------------------------------------------------------------- - -# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. -# The default value is: YES. - -GENERATE_LATEX = NO - -# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: latex. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_OUTPUT = latex - -# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be -# invoked. -# -# Note that when enabling USE_PDFLATEX this option is only used for generating -# bitmaps for formulas in the HTML output, but not in the Makefile that is -# written to the output directory. -# The default file is: latex. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_CMD_NAME = latex - -# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate -# index for LaTeX. -# The default file is: makeindex. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -MAKEINDEX_CMD_NAME = makeindex - -# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX -# documents. This may be useful for small projects and may help to save some -# trees in general. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -COMPACT_LATEX = NO - -# The PAPER_TYPE tag can be used to set the paper type that is used by the -# printer. -# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x -# 14 inches) and executive (7.25 x 10.5 inches). -# The default value is: a4. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -PAPER_TYPE = a4 - -# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names -# that should be included in the LaTeX output. The package can be specified just -# by its name or with the correct syntax as to be used with the LaTeX -# \usepackage command. To get the times font for instance you can specify : -# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} -# To use the option intlimits with the amsmath package you can specify: -# EXTRA_PACKAGES=[intlimits]{amsmath} -# If left blank no extra packages will be included. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -EXTRA_PACKAGES = - -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the -# generated LaTeX document. The header should contain everything until the first -# chapter. If it is left blank doxygen will generate a standard header. See -# section "Doxygen usage" for information on how to let doxygen write the -# default header to a separate file. -# -# Note: Only use a user-defined header if you know what you are doing! The -# following commands have a special meaning inside the header: $title, -# $datetime, $date, $doxygenversion, $projectname, $projectnumber, -# $projectbrief, $projectlogo. Doxygen will replace $title with the empty -# string, for the replacement values of the other commands the user is referred -# to HTML_HEADER. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_HEADER = - -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the -# generated LaTeX document. The footer should contain everything after the last -# chapter. If it is left blank doxygen will generate a standard footer. See -# LATEX_HEADER for more information on how to generate a default footer and what -# special commands can be used inside the footer. -# -# Note: Only use a user-defined footer if you know what you are doing! -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_FOOTER = - -# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# LaTeX style sheets that are included after the standard style sheets created -# by doxygen. Using this option one can overrule certain style aspects. Doxygen -# will copy the style sheet files to the output directory. -# Note: The order of the extra style sheet files is of importance (e.g. the last -# style sheet in the list overrules the setting of the previous ones in the -# list). -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_EXTRA_STYLESHEET = - -# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the LATEX_OUTPUT output -# directory. Note that the files will be copied as-is; there are no commands or -# markers available. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_EXTRA_FILES = - -# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is -# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will -# contain links (just like the HTML output) instead of page references. This -# makes the output suitable for online browsing using a PDF viewer. -# The default value is: YES. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -PDF_HYPERLINKS = YES - -# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate -# the PDF file directly from the LaTeX files. Set this option to YES, to get a -# higher quality PDF documentation. -# The default value is: YES. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -USE_PDFLATEX = YES - -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. This option is also used -# when generating formulas in HTML. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_BATCHMODE = NO - -# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the -# index chapters (such as File Index, Compound Index, etc.) in the output. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_HIDE_INDICES = NO - -# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source -# code with syntax highlighting in the LaTeX output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_SOURCE_CODE = NO - -# The LATEX_BIB_STYLE tag can be used to specify the style to use for the -# bibliography, e.g. plainnat, or ieeetr. See -# http://en.wikipedia.org/wiki/BibTeX and \cite for more info. -# The default value is: plain. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_BIB_STYLE = plain - -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_TIMESTAMP = NO - -#--------------------------------------------------------------------------- -# Configuration options related to the RTF output -#--------------------------------------------------------------------------- - -# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The -# RTF output is optimized for Word 97 and may not look too pretty with other RTF -# readers/editors. -# The default value is: NO. - -GENERATE_RTF = NO - -# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: rtf. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_OUTPUT = rtf - -# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF -# documents. This may be useful for small projects and may help to save some -# trees in general. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -COMPACT_RTF = NO - -# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will -# contain hyperlink fields. The RTF file will contain links (just like the HTML -# output) instead of page references. This makes the output suitable for online -# browsing using Word or some other Word compatible readers that support those -# fields. -# -# Note: WordPad (write) and others do not support links. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_HYPERLINKS = NO - -# Load stylesheet definitions from file. Syntax is similar to doxygen's config -# file, i.e. a series of assignments. You only have to provide replacements, -# missing definitions are set to their default value. -# -# See also section "Doxygen usage" for information on how to generate the -# default style sheet that doxygen normally uses. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_STYLESHEET_FILE = - -# Set optional variables used in the generation of an RTF document. Syntax is -# similar to doxygen's config file. A template extensions file can be generated -# using doxygen -e rtf extensionFile. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_EXTENSIONS_FILE = - -# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code -# with syntax highlighting in the RTF output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_SOURCE_CODE = NO - #--------------------------------------------------------------------------- # Configuration options related to the man page output #--------------------------------------------------------------------------- @@ -1956,15 +1710,6 @@ GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook -# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the -# program listings (including syntax highlighting and cross-referencing -# information) to the DOCBOOK output. Note that enabling this will significantly -# increase the size of the DOCBOOK output. -# The default value is: NO. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_PROGRAMLISTING = NO - #--------------------------------------------------------------------------- # Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- @@ -2143,15 +1888,6 @@ EXTERNAL_PAGES = YES # Configuration options related to the dot tool #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram -# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to -# NO turns the diagrams off. Note that this option also works with HAVE_DOT -# disabled, but it is recommended to install and use dot, since it yields more -# powerful graphs. -# The default value is: YES. - -CLASS_DIAGRAMS = YES - # You can include diagrams made with dia in doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. @@ -2172,7 +1908,7 @@ HIDE_UNDOC_RELATIONS = YES # set to NO # The default value is: YES. -HAVE_DOT = NO +HAVE_DOT = YES # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed # to run in parallel. When set to 0 doxygen will base this on the number of @@ -2184,23 +1920,6 @@ HAVE_DOT = NO DOT_NUM_THREADS = 0 -# When you want a differently looking font in the dot files that doxygen -# generates you can specify the font name using DOT_FONTNAME. You need to make -# sure dot is able to find the font, which can be done by putting it in a -# standard location or by setting the DOTFONTPATH environment variable or by -# setting DOT_FONTPATH to the directory containing the font. -# The default value is: Helvetica. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_FONTNAME = Helvetica - -# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of -# dot graphs. -# Minimum value: 4, maximum value: 24, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_FONTSIZE = 10 - # By default doxygen will tell dot to use the default font as specified with # DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set # the path where dot can find it using this tag. @@ -2401,7 +2120,7 @@ PLANTUML_INCLUDE_PATH = # Minimum value: 0, maximum value: 10000, default value: 50. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_GRAPH_MAX_NODES = 50 +DOT_GRAPH_MAX_NODES = 250 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs # generated by dot. A depth value of 3 means that only nodes reachable from the @@ -2415,18 +2134,6 @@ DOT_GRAPH_MAX_NODES = 50 MAX_DOT_GRAPH_DEPTH = 0 -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not seem -# to support this out of the box. -# -# Warning: Depending on the platform used, enabling this option may lead to -# badly anti-aliased labels on the edges of a graph (i.e. they become hard to -# read). -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_TRANSPARENT = YES - # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) support diff --git a/doc/sphinx/index.rst b/doc/sphinx/index.rst index 8aa53b3e..a47bccb9 100644 --- a/doc/sphinx/index.rst +++ b/doc/sphinx/index.rst @@ -9,6 +9,7 @@ Welcome to Weston documentation! toc/libweston.rst toc/test-suite.rst toc/kiosk-shell.rst + toc/ivi-shell.rst Weston ------ diff --git a/doc/sphinx/meson.build b/doc/sphinx/meson.build index 88f09e2c..1f573624 100644 --- a/doc/sphinx/meson.build +++ b/doc/sphinx/meson.build @@ -1,5 +1,6 @@ sphinx = find_program('sphinx-build', required: true) doxygen = find_program('doxygen', required: true) +dot = find_program('dot', required: true) breathe = find_program('breathe-apidoc', required: true) sphinx_c = run_command(sphinx, '--version') @@ -38,6 +39,7 @@ sphinx_conf = configure_file( doxy_conf_data = configuration_data() doxy_conf_data.set('SRC_ROOT', meson.source_root()) doxy_conf_data.set('OUTPUT_DIR', doxygen_database) +doxy_conf_data.set('MESON_WERROR', get_option('werror') == true ? 'YES' : 'NO') doxygen_conf_weston = configure_file( input: 'doxygen.ini.in', output: 'doxygen.ini', diff --git a/doc/sphinx/toc/images/ivi-shell.png b/doc/sphinx/toc/images/ivi-shell.png new file mode 100644 index 00000000..87ff7a36 Binary files /dev/null and b/doc/sphinx/toc/images/ivi-shell.png differ diff --git a/doc/sphinx/toc/images/meson.build b/doc/sphinx/toc/images/meson.build new file mode 100644 index 00000000..c73a8c60 --- /dev/null +++ b/doc/sphinx/toc/images/meson.build @@ -0,0 +1,9 @@ +# Sphinx does not know look for these files in the source directory, so +# they must be copied to the build directory. +files = [ + 'ivi-shell.png', +] + +foreach file : files + configure_file(input: file, output: file, copy: true) +endforeach diff --git a/doc/sphinx/toc/ivi-shell.rst b/doc/sphinx/toc/ivi-shell.rst new file mode 100644 index 00000000..42eb5fe5 --- /dev/null +++ b/doc/sphinx/toc/ivi-shell.rst @@ -0,0 +1,111 @@ +Weston IVI-shell +================ + +Weston's IVI-shell is a highly customizable shell targeted at use cases which +need custom control over the shell's window layout with one or more applications +without interactive configuration of the layout by the user. + +Example use cases for the IVI-shell are IVI applications or industrial human +machine interfaces. In general, whenever the user interface requires the exact +positioning of multiple application surfaces on one or more screens. + +The IVI-shell also provides a means for applications to identify themselves to +the shell by application IDs via the ivi_application Wayland protocol. + +IVI-shell client protocol +------------------------- + +Wayland clients can implement the ``ivi_application`` Wayland protocol, which +allows them to specify an ``ivi_id`` to allow the IVI controller to identify the +application. This allows the controller to implement special behavior for +well-known applications. + +The IVI-shell is able to also handle clients that use the ``xdg-shell`` +protocol, but in these cases the IVI-shell needs other means to identify client +applications. + +See ``ivi-application.xml`` for the protocol specification. + +IVI-shell Weston modules +------------------------ + +The IVI-shell consists of two main components: The ``ivi-shell.so`` and custom +IVI controller (with the ``hmi-controller.so`` example implementation). + +The ``ivi-shell.so`` is responsible for handling the application IDs and for +providing abstractions to configure the window layout via the +``ivi_layout_interface``. This interface is discussed in `IVI-shell compositor +implementation`. + +The IVI controller uses the ``ivi_layout_interface`` to implement a window +manager and is responsible for configuring the window layout, i.e. the position +of the applications on the screens. + +Due to this separation, both modules must be loaded in your ``weston.ini`` to +use the IVI-shell. + +.. code-block:: ini + + [core] + shell=ivi-shell.so + modules=hmi-controller.so + +If you are using your custom controller, replace ``hmi-controller.so`` with the +name of your own controller module. + +.. figure:: images/ivi-shell.png + :alt: IVI-shell architecture overview + +Controlling the IVI-shell +------------------------- + +The IVI-shell provides the ``ivi_layout_interface`` API that a controller must +use to control the window layout of the IVI-shell. See +``ivi-shell/ivi-layout-export.h`` for the definition of this API. + +For the initial configuration, the controller has to create at least one +``ivi_layout_layer`` and add the ``ivi_layout_layer`` to a ``weston_output``. +The layers allow to group multiple applications surfaces and control them +together and are the main mechanism to group and organize surfaces. These are +always necessary to show something using the IVI-shell. The IVI-shell will +internally create an ``ivi_layout_screen``, but a controller always uses the +``weston_output`` directly. + +To get control over the client surfaces, the controller must use notifiers that +trigger whenever there are changes to the client surfaces. The client surfaces +then show up as ``ivi_layout_surface``. These have an ID, which allows the +controller to identify the surface and reconfigure the window layout +accordingly. + +The controller must add the ``ivi_layout_surface`` to an ``ivi_layout_layer`` +and configure it's position and z-order wrt. the other surfaces in the layer. +Otherwise, the newly added surface will not show up on the screen. + +The IVI-shell will internally create an ``ivi_layout_view`` for each layer that +the surface was added to. However, the views are not provided to the IVI +controller. + +After configuring all expected changes, the controller must call the +``commit_changes`` to atomically update the display layout. + +IVI-shell example implementation +-------------------------------- + +The IVI-shell comes with an example implementation of an IVI controller -- the +`hmi-controller`. The hmi-controller will usually replaced by a custom +implementation that implements the use-case-specific behavior. + +The hmi-controller is split into two parts: + +The ``hmi-controller.so`` is a Weston Plugin that uses the +``ivi_layout_interface`` to perform the window manager tasks. It allows some +reconfiguration of the window layout via the ``ivi_hmi_controller`` protocol. +Other implementations may keep all window management inside the module or may +expose even more window management via a custom protocol to an external process. + +The ``weston-ivi-shell-user-interface`` is an example hmi-controller helper +client that serves as a user interface for controlling the hmi-controller. + +The hmi-controller can be customized using the ``[ivi-shell]`` section in the +``weston.ini``. An example configuration will be generated in +``/ivi-shell/weston.ini``. diff --git a/doc/sphinx/toc/libweston/images/create_output.msc b/doc/sphinx/toc/libweston/images/create_output.msc index f20cdb4e..b66b7548 100644 --- a/doc/sphinx/toc/libweston/images/create_output.msc +++ b/doc/sphinx/toc/libweston/images/create_output.msc @@ -11,7 +11,7 @@ msc { --- [label = "Compositor creates an output for a head"]; c box c [label = "Have an existing head to process."]; - c => w [label = "weston_compositor_create_output_with_head()"]; + c => w [label = "weston_compositor_create_output()"]; w => b [label = "weston_backend::create_output()"]; w << b [label = "an empty output, no hw resources"]; w => b [label = "weston_output::attach_head()"]; diff --git a/doc/sphinx/toc/meson.build b/doc/sphinx/toc/meson.build index b7190f78..4c638411 100644 --- a/doc/sphinx/toc/meson.build +++ b/doc/sphinx/toc/meson.build @@ -1,6 +1,7 @@ # you need to add here any files you add to the toc directory as well files = [ 'kiosk-shell.rst', + 'ivi-shell.rst', 'running-weston.rst', 'libweston.rst', 'test-suite.rst', @@ -11,4 +12,5 @@ foreach file : files configure_file(input: file, output: file, copy: true) endforeach +subdir('images') subdir('libweston') diff --git a/doc/sphinx/toc/running-weston.rst b/doc/sphinx/toc/running-weston.rst index e14ec55d..39e95f1b 100644 --- a/doc/sphinx/toc/running-weston.rst +++ b/doc/sphinx/toc/running-weston.rst @@ -6,7 +6,7 @@ underlying environment where it runs on. Ultimately, the back-end is responsible for handling the input and generate an output. Weston, as a libweston user, can be run on different back-ends, including nested, by using the wayland backend, but also on X11 or on a stand-alone back-end like -DRM/KMS and now deprecated fbdev. +DRM/KMS. In most cases, people should allow Weston to choose the backend automatically as it will produce the best results. That happens for instance when running @@ -28,7 +28,6 @@ Available back-ends: * **x11** -- run as a x11 application, nested in a X11 display server instance * **rdp** -- run as an RDP server without local input or output * **headless** -- run without input or output, useful for test suite -* **fbdev** -- run stand-alone on fbdev/evdev (deprecated) The job of gathering all the surfaces (windows) being displayed on an output and stitching them together is performed by a *renderer*. By doing so, it is @@ -91,17 +90,20 @@ You can start Weston from a VT assuming that there's a seat manager supported by backend to be used by ``libseat`` can optionally be selected with ``$LIBSEAT_BACKEND``. If ``libseat`` and ``seatd`` are both installed, but ``seatd`` is not already running, it can be started with ``sudo -- seatd -g -video``. If no seat manager supported by ``libseat`` is available, you can use -the ``weston-launch`` application that can handle VT switching. +video``. -Another way of launching Weston is via ssh or a serial terminal. The simplest -option here is to use the ``libseat`` launcher with ``seatd``. The process for +Launching Weston via ssh or a serial terminal is best with the ``libseat`` +launcher and ``seatd``. Logind will refuse to give access to local seats from +remote connections directly. The process for setting that up is identical to the one described above, where one just need to ensure that ``seatd`` is running with the appropriate arguments, after which one -can just run ``weston``. Another option, is to rely on logind and start weston -as systemd user service: :ref:`weston-user-service`. Alternatively and as a last -resort, one can run Weston as root, specifying the tty to use on the command -line: If TTY 2 is active, one would run ``weston --tty 2`` as root. +can just run ``weston``. ``seatd`` will lend out the current VT, and if you want +to run on a different VT you need to ``chvt`` first. Make sure nothing will try +to take over the seat or VT via logind at the same time in case logind is +running. + +If you want to rely on logind, you can start weston as a systemd user service: +:ref:`weston-user-service`. Running Weston on a different seat on a stand-alone back-end ------------------------------------------------------------ @@ -171,7 +173,14 @@ Then, weston can be run by selecting the DRM-backend and the seat ``seat-insecur :: - ./weston -Bdrm-backend.so --seat=seat-insecure + SEATD_VTBOUND=0 ./weston -Bdrm-backend.so --seat=seat-insecure + +This assumes you are using the libseat launcher of Weston with the "builtin" +backend of libseat. Libseat automatically falls back to the builtin backend if +``seatd`` is not running and a ``logind`` service is not running or refuses. +You can also force it with ``LIBSEAT_BACKEND=builtin`` if needed. +``SEATD_VTBOUND=0`` tells libseat that there is no VT associated with the +chosen seat. If everything went well you should see weston be up-and-running on an output connected to that DRM device. diff --git a/doc/sphinx/toc/test-suite.rst b/doc/sphinx/toc/test-suite.rst index f7803292..65083988 100644 --- a/doc/sphinx/toc/test-suite.rst +++ b/doc/sphinx/toc/test-suite.rst @@ -218,10 +218,19 @@ DRM-backend tests DRM-backend tests require a DRM device, so they are a special case. To select a device the test suite will simply look at the environment variable -``WESTON_TEST_SUITE_DRM_DEVICE``. So the first thing the user has to do in order -to run DRM-backend tests is to set this environment variable with the card that -should run the tests. For instance, in order to run DRM-backend tests with -``card0`` we need to run ``export WESTON_TEST_SUITE_DRM_DEVICE=card0``. +``WESTON_TEST_SUITE_DRM_DEVICE``. In Weston's CI, we set this variable to the +DRM node that VKMS takes (``cardX`` - X can change across each bot, as the order +in which devices are loaded is not predictable). + +**IMPORTANT**: our DRM-backend tests are written specifically to run on top of +VKMS (KMS driver created to be used by headless machines in test suites, so it +aims to be more configurable and predictable than real hardware). We don't +guarantee that these tests will work on real hardware. + +But if users want to run DRM-backend tests using real hardware anyway, the first +thing they need to do is to set this environment variable with the DRM node of +the card that should run the tests. For instance, in order to run DRM-backend +tests with ``card0`` we need to run ``export WESTON_TEST_SUITE_DRM_DEVICE=card0``. Note that the card should not be in use by a desktop environment (or any other program that requires master status), as there can only be one user at a time diff --git a/fullscreen-shell/fullscreen-shell.c b/fullscreen-shell/fullscreen-shell.c index 6975f65e..7ee09d79 100644 --- a/fullscreen-shell/fullscreen-shell.c +++ b/fullscreen-shell/fullscreen-shell.c @@ -37,6 +37,7 @@ #include "compositor/weston.h" #include "fullscreen-shell-unstable-v1-server-protocol.h" #include "shared/helpers.h" +#include "shell-utils.h" struct fullscreen_shell { struct wl_client *client; @@ -80,7 +81,7 @@ struct fs_output { struct weston_surface *surface; struct wl_listener surface_destroyed; struct weston_view *view; - struct weston_view *black_view; + struct weston_curtain *curtain; struct weston_transform transform; /* matrix from x, y */ int presented_for_mode; @@ -226,37 +227,27 @@ black_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) { } -static struct weston_view * -create_black_surface(struct weston_compositor *ec, struct fs_output *fsout, - float x, float y, int w, int h) +static struct weston_curtain * +create_curtain(struct weston_compositor *ec, struct fs_output *fsout, + float x, float y, int w, int h) { - struct weston_surface *surface = NULL; - struct weston_view *view; + struct weston_curtain_params curtain_params = { + .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0, + .x = x, .y = y, .width = w, .height = h, + .surface_committed = black_surface_committed, + .get_label = NULL, + .surface_private = fsout, + .capture_input = true, + }; + struct weston_curtain *curtain; - surface = weston_surface_create(ec); - if (surface == NULL) { - weston_log("no memory\n"); - return NULL; - } - view = weston_view_create(surface); - if (!view) { - weston_surface_destroy(surface); + curtain = weston_curtain_create(ec, &curtain_params); + if (!curtain) { weston_log("no memory\n"); return NULL; } - surface->committed = black_surface_committed; - surface->committed_private = fsout; - weston_surface_set_color(surface, 0.0f, 0.0f, 0.0f, 1.0f); - pixman_region32_fini(&surface->opaque); - pixman_region32_init_rect(&surface->opaque, 0, 0, w, h); - pixman_region32_fini(&surface->input); - pixman_region32_init_rect(&surface->input, 0, 0, w, h); - - weston_surface_set_size(surface, w, h); - weston_view_set_position(view, x, y); - - return view; + return curtain; } static void @@ -333,13 +324,12 @@ fs_output_create(struct fullscreen_shell *shell, struct weston_output *output) fsout->surface_destroyed.notify = surface_destroyed; fsout->pending.surface_destroyed.notify = pending_surface_destroyed; - fsout->black_view = create_black_surface(shell->compositor, fsout, - output->x, output->y, - output->width, output->height); - fsout->black_view->surface->is_mapped = true; - fsout->black_view->is_mapped = true; + fsout->curtain = create_curtain(shell->compositor, fsout, + output->x, output->y, + output->width, output->height); + fsout->curtain->view->is_mapped = true; weston_layer_entry_insert(&shell->layer.view_list, - &fsout->black_view->layer_link); + &fsout->curtain->view->layer_link); wl_list_init(&fsout->transform.link); if (!wl_list_empty(&shell->default_surface_list)) { @@ -373,41 +363,6 @@ restore_output_mode(struct weston_output *output) weston_output_mode_switch_to_native(output); } -/* - * Returns the bounding box of a surface and all its sub-surfaces, - * in surface-local coordinates. */ -static void -surface_subsurfaces_boundingbox(struct weston_surface *surface, int32_t *x, - int32_t *y, int32_t *w, int32_t *h) { - pixman_region32_t region; - pixman_box32_t *box; - struct weston_subsurface *subsurface; - - pixman_region32_init_rect(®ion, 0, 0, - surface->width, - surface->height); - - wl_list_for_each(subsurface, &surface->subsurface_list, parent_link) { - pixman_region32_union_rect(®ion, ®ion, - subsurface->position.x, - subsurface->position.y, - subsurface->surface->width, - subsurface->surface->height); - } - - box = pixman_region32_extents(®ion); - if (x) - *x = box->x1; - if (y) - *y = box->y1; - if (w) - *w = box->x2 - box->x1; - if (h) - *h = box->y2 - box->y1; - - pixman_region32_fini(®ion); -} - static void fs_output_center_view(struct fs_output *fsout) { @@ -520,10 +475,10 @@ fs_output_configure_simple(struct fs_output *fsout, break; } - weston_view_set_position(fsout->black_view, + weston_view_set_position(fsout->curtain->view, fsout->output->x - surf_x, fsout->output->y - surf_y); - weston_surface_set_size(fsout->black_view->surface, + weston_surface_set_size(fsout->curtain->view->surface, fsout->output->width, fsout->output->height); } @@ -670,6 +625,7 @@ fs_output_apply_pending(struct fs_output *fsout) return; } fsout->view->is_mapped = true; + fsout->surface->is_mapped = true; wl_signal_add(&fsout->surface->destroy_signal, &fsout->surface_destroyed); diff --git a/fullscreen-shell/meson.build b/fullscreen-shell/meson.build index 02a6c6f1..4ecac5bc 100644 --- a/fullscreen-shell/meson.build +++ b/fullscreen-shell/meson.build @@ -4,9 +4,10 @@ if get_option('shell-fullscreen') fullscreen_shell_unstable_v1_server_protocol_h, fullscreen_shell_unstable_v1_protocol_c, ] - deps_shell_fullscreen=[ + deps_shell_fullscreen = [ dep_libweston_public, dep_libexec_weston, + dep_shell_utils, ] shared_library( 'fullscreen-shell', diff --git a/include/libweston-desktop/libweston-desktop.h b/include/libweston-desktop/libweston-desktop.h index 3e7ac738..3536935b 100644 --- a/include/libweston-desktop/libweston-desktop.h +++ b/include/libweston-desktop/libweston-desktop.h @@ -44,6 +44,14 @@ enum weston_desktop_surface_edge { WESTON_DESKTOP_SURFACE_EDGE_BOTTOM_RIGHT = 10, }; +enum weston_top_level_tiled_orientation { + WESTON_TOP_LEVEL_TILED_ORIENTATION_NONE = 0 << 0, + WESTON_TOP_LEVEL_TILED_ORIENTATION_LEFT = 1 << 1, + WESTON_TOP_LEVEL_TILED_ORIENTATION_RIGHT = 1 << 2, + WESTON_TOP_LEVEL_TILED_ORIENTATION_TOP = 1 << 3, + WESTON_TOP_LEVEL_TILED_ORIENTATION_BOTTOM = 1 << 4, +}; + struct weston_desktop; struct weston_desktop_client; struct weston_desktop_surface; @@ -113,6 +121,9 @@ struct weston_desktop_api { */ void (*set_xwayland_position)(struct weston_desktop_surface *surface, int32_t x, int32_t y, void *user_data); + void (*get_position)(struct weston_desktop_surface *surface, + int32_t *x, int32_t *y, + void *user_data); }; void @@ -163,6 +174,9 @@ void weston_desktop_surface_set_size(struct weston_desktop_surface *surface, int32_t width, int32_t height); void +weston_desktop_surface_set_orientation(struct weston_desktop_surface *surface, + enum weston_top_level_tiled_orientation tile_orientation); +void weston_desktop_surface_close(struct weston_desktop_surface *surface); void weston_desktop_surface_add_metadata_listener(struct weston_desktop_surface *surface, @@ -195,6 +209,19 @@ weston_desktop_surface_get_max_size(struct weston_desktop_surface *surface); struct weston_size weston_desktop_surface_get_min_size(struct weston_desktop_surface *surface); +bool +weston_desktop_window_menu_supported(struct weston_desktop *desktop); +bool +weston_desktop_move_supported(struct weston_desktop *desktop); +bool +weston_desktop_resize_supported(struct weston_desktop *desktop); +bool +weston_desktop_fullscreen_supported(struct weston_desktop *desktop); +bool +weston_desktop_minimize_supported(struct weston_desktop *desktop); +bool +weston_desktop_maximize_supported(struct weston_desktop *desktop); + #ifdef __cplusplus } #endif diff --git a/include/libweston/backend-drm.h b/include/libweston/backend-drm.h index af2da4aa..071125fa 100644 --- a/include/libweston/backend-drm.h +++ b/include/libweston/backend-drm.h @@ -35,7 +35,7 @@ extern "C" { #endif -#define WESTON_DRM_BACKEND_CONFIG_VERSION 4 +#define WESTON_DRM_BACKEND_CONFIG_VERSION 5 struct libinput_device; @@ -78,6 +78,21 @@ struct weston_drm_output_api { */ void (*set_seat)(struct weston_output *output, const char *seat); + + /** Set the "max bpc" KMS connector property + * + * The property is used for working around faulty sink hardware like + * monitors or media converters that mishandle the kernel driver + * chosen bits-per-channel on the physical link. When having trouble, + * try a lower value like 8. A value of 0 means that the current max + * bpc will be reprogrammed. + * + * The value actually used in KMS is silently clamped to the range the + * KMS driver claims to support. The default value is 16. + * + * This can be set only while the output is disabled. + */ + void (*set_max_bpc)(struct weston_output *output, unsigned max_bpc); }; static inline const struct weston_drm_output_api * @@ -90,7 +105,7 @@ weston_drm_output_get_api(struct weston_compositor *compositor) return (const struct weston_drm_output_api *)api; } -#define WESTON_DRM_VIRTUAL_OUTPUT_API_NAME "weston_drm_virtual_output_api_v1" +#define WESTON_DRM_VIRTUAL_OUTPUT_API_NAME "weston_drm_virtual_output_api_v2" struct drm_fb; typedef int (*submit_frame_cb)(struct weston_output *output, int fd, @@ -101,12 +116,16 @@ struct weston_drm_virtual_output_api { * This is a low-level function, where the caller is expected to wrap * the weston_output function pointers as necessary to make the virtual * output useful. The caller must set up output make, model, serial, - * physical size, the mode list and current mode. + * physical size, the mode list and current mode. The destroy function + * pointer must not be overwritten, as it is used by the DRM backend to + * recognize its outputs. Instead, an auxiliary destroy callback has to + * be provided as a parameter. * * Returns output on success, NULL on failure. */ struct weston_output* (*create_output)(struct weston_compositor *c, - char *name); + char *name, + void (*destroy_func)(struct weston_output *base)); /** Set pixel format same as drm_output set_gbm_format(). * @@ -171,9 +190,6 @@ weston_drm_virtual_output_get_api(struct weston_compositor *compositor) struct weston_drm_backend_config { struct weston_backend_config base; - /** The tty to be used. Set to 0 to use the current tty. */ - int tty; - /** Whether to use the pixman renderer instead of the OpenGL ES renderer. */ bool use_pixman; diff --git a/include/libweston/backend-fbdev.h b/include/libweston/backend-fbdev.h deleted file mode 100644 index 4dbdce72..00000000 --- a/include/libweston/backend-fbdev.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright © 2016 Benoit Gschwind - * - * 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 WESTON_COMPOSITOR_FBDEV_H -#define WESTON_COMPOSITOR_FBDEV_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#include - -#define WESTON_FBDEV_BACKEND_CONFIG_VERSION 2 - -struct libinput_device; - -struct weston_fbdev_backend_config { - struct weston_backend_config base; - - int tty; - char *device; - - /** Callback used to configure input devices. - * - * This function will be called by the backend when a new input device - * needs to be configured. - * If NULL the device will use the default configuration. - */ - void (*configure_device)(struct weston_compositor *compositor, - struct libinput_device *device); - - /** The seat to be used for input and output. - * - * If seat_id is NULL, the seat is taken from XDG_SEAT environment - * variable. If neither is set, "seat0" is used. The backend will - * take ownership of the seat_id pointer and will free it on - * backend destruction. - */ - char *seat_id; -}; - -#ifdef __cplusplus -} -#endif - -#endif /* WESTON_COMPOSITOR_FBDEV_H */ diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index b3542507..c9eb1609 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -34,6 +34,7 @@ extern "C" { #include #define WESTON_RDP_OUTPUT_API_NAME "weston_rdp_output_api_v1" +#define RDP_DEFAULT_FREQ 60 struct weston_rdp_output_api { /** Initialize a RDP output with specified width and height. @@ -56,6 +57,11 @@ weston_rdp_output_get_api(struct weston_compositor *compositor) #define WESTON_RDP_BACKEND_CONFIG_VERSION 2 +typedef void *(*rdp_audio_in_setup)(struct weston_compositor *c, void *vcm); +typedef void (*rdp_audio_in_teardown)(void *audio_private); +typedef void *(*rdp_audio_out_setup)(struct weston_compositor *c, void *vcm); +typedef void (*rdp_audio_out_teardown)(void *audio_private); + struct weston_rdp_backend_config { struct weston_backend_config base; char *bind_address; @@ -66,6 +72,13 @@ struct weston_rdp_backend_config { int env_socket; int no_clients_resize; int force_no_compression; + bool remotefx_codec; + int external_listener_fd; + int refresh_rate; + rdp_audio_in_setup audio_in_setup; + rdp_audio_in_teardown audio_in_teardown; + rdp_audio_out_setup audio_out_setup; + rdp_audio_out_teardown audio_out_teardown; }; #ifdef __cplusplus diff --git a/include/libweston/config-parser.h b/include/libweston/config-parser.h index d82197bf..81f28b59 100644 --- a/include/libweston/config-parser.h +++ b/include/libweston/config-parser.h @@ -35,26 +35,6 @@ extern "C" { #define WESTON_CONFIG_FILE_ENV_VAR "WESTON_CONFIG_FILE" -enum config_key_type { - CONFIG_KEY_INTEGER, /* typeof data = int */ - CONFIG_KEY_UNSIGNED_INTEGER, /* typeof data = unsigned int */ - CONFIG_KEY_STRING, /* typeof data = char* */ - CONFIG_KEY_BOOLEAN /* typeof data = int */ -}; - -struct config_key { - const char *name; - enum config_key_type type; - void *data; -}; - -struct config_section { - const char *name; - const struct config_key *keys; - int num_keys; - void (*done)(void *data); -}; - enum weston_option_type { WESTON_OPTION_INTEGER, WESTON_OPTION_UNSIGNED_INTEGER, @@ -108,6 +88,9 @@ weston_config_section_get_bool(struct weston_config_section *section, const char * weston_config_get_name_from_env(void); +struct weston_config * +weston_config_parse_fp(FILE *file); + struct weston_config * weston_config_parse(const char *name); @@ -121,10 +104,8 @@ int weston_config_next_section(struct weston_config *config, struct weston_config_section **section, const char **name); - #ifdef __cplusplus } #endif #endif /* CONFIGPARSER_H */ - diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index 2174d34d..4bb9ea75 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -81,6 +81,7 @@ struct weston_pointer_constraint; struct ro_anonymous_file; struct weston_color_profile; struct weston_color_transform; +struct pixel_format_info; enum weston_keyboard_modifier { MODIFIER_CTRL = (1 << 0), @@ -150,21 +151,6 @@ struct weston_spring { uint32_t clip; }; -struct weston_output_zoom { - bool active; - float increment; - float level; - float max_level; - float trans_x, trans_y; - struct { - double x, y; - } current; - struct weston_seat *seat; - struct weston_animation animation_z; - struct weston_spring spring_z; - struct wl_listener motion_listener; -}; - /* bit compatible with drm definitions. */ enum dpms_enum { WESTON_DPMS_ON, @@ -222,6 +208,154 @@ struct weston_testsuite_data { void *test_private_data; }; +/** EOTF mode for outputs and heads + * + * A list of EOTF modes for driving displays, defined by CTA-861-G for + * Dynamic Range and Mastering InfoFrame. + * + * On heads, a bitmask of one or more entries shows which modes are claimed + * supported. + * + * On outputs, the mode to be used for driving the video sink. + * + * For traditional non-HDR sRGB, use WESTON_EOTF_MODE_SDR. + */ +enum weston_eotf_mode { + /** Invalid EOTF mode, or none supported. */ + WESTON_EOTF_MODE_NONE = 0, + + /** Traditional gamma, SDR luminance range */ + WESTON_EOTF_MODE_SDR = 0x01, + + /** Traditional gamma, HDR luminance range */ + WESTON_EOTF_MODE_TRADITIONAL_HDR = 0x02, + + /** Preceptual quantizer, SMPTE ST 2084 */ + WESTON_EOTF_MODE_ST2084 = 0x04, + + /** Hybrid log-gamma, ITU-R BT.2100 */ + WESTON_EOTF_MODE_HLG = 0x08, +}; + +/** Bitmask of all defined EOTF modes */ +#define WESTON_EOTF_MODE_ALL_MASK \ + ((uint32_t)(WESTON_EOTF_MODE_SDR | WESTON_EOTF_MODE_TRADITIONAL_HDR | \ + WESTON_EOTF_MODE_ST2084 | WESTON_EOTF_MODE_HLG)) + +/** CIE 1931 xy chromaticity coordinates */ +struct weston_CIExy { + float x; + float y; +}; + +enum weston_hdr_metadata_type1_groups { + /** weston_hdr_metadata_type1::primary is set */ + WESTON_HDR_METADATA_TYPE1_GROUP_PRIMARIES = 0x01, + + /** weston_hdr_metadata_type1::white is set */ + WESTON_HDR_METADATA_TYPE1_GROUP_WHITE = 0x02, + + /** weston_hdr_metadata_type1::maxDML is set */ + WESTON_HDR_METADATA_TYPE1_GROUP_MAXDML = 0x04, + + /** weston_hdr_metadata_type1::minDML is set */ + WESTON_HDR_METADATA_TYPE1_GROUP_MINDML = 0x08, + + /** weston_hdr_metadata_type1::maxCLL is set */ + WESTON_HDR_METADATA_TYPE1_GROUP_MAXCLL = 0x10, + + /** weston_hdr_metadata_type1::maxFALL is set */ + WESTON_HDR_METADATA_TYPE1_GROUP_MAXFALL = 0x20, + + /** all valid bits */ + WESTON_HDR_METADATA_TYPE1_GROUP_ALL_MASK = 0x3f +}; + +/** HDR static metadata type 1 + * + * The fields are defined by CTA-861-G except here they use float encoding. + * + * In Weston used only with HDR display modes. + */ +struct weston_hdr_metadata_type1 { + /** Which fields are valid + * + * A bitmask of values from enum weston_hdr_metadata_type1_groups. + */ + uint32_t group_mask; + + /* EOTF is tracked externally with enum weston_eotf_mode */ + + /** Chromaticities of the primaries, in any order */ + struct weston_CIExy primary[3]; + + /** White point chromaticity */ + struct weston_CIExy white; + + /** Maximum display mastering luminance, 1 - 65535 cd/m² */ + float maxDML; + + /** Minimum display mastering luminance, 0.0001 - 6.5535 cd/m² */ + float minDML; + + /** Maximum content light level, 1 - 65535 cd/m² */ + float maxCLL; + + /** Maximum frame-average light level, 1 - 65535 cd/m² */ + float maxFALL; +}; + +enum weston_color_characteristics_groups { + /** weston_color_characteristics::primary is set */ + WESTON_COLOR_CHARACTERISTICS_GROUP_PRIMARIES = 0x01, + + /** weston_color_characteristics::white is set */ + WESTON_COLOR_CHARACTERISTICS_GROUP_WHITE = 0x02, + + /** weston_color_characteristics::max_luminance is set */ + WESTON_COLOR_CHARACTERISTICS_GROUP_MAXL = 0x04, + + /** weston_color_characteristics::min_luminance is set */ + WESTON_COLOR_CHARACTERISTICS_GROUP_MINL = 0x08, + + /** weston_color_characteristics::maxFALL is set */ + WESTON_COLOR_CHARACTERISTICS_GROUP_MAXFALL = 0x10, + + /** all valid bits */ + WESTON_COLOR_CHARACTERISTICS_GROUP_ALL_MASK = 0x1f +}; + +/** Basic display color characteristics + * + * This is a simple description of a display or output (monitor) color + * characteristics. The parameters can be found in EDID, with caveats. They + * are particularly useful with HDR monitors. + */ +struct weston_color_characteristics { + /** Which fields are valid + * + * A bitmask of values from enum weston_color_characteristics_groups. + */ + uint32_t group_mask; + + /* EOTF is tracked externally with enum weston_eotf_mode */ + + /** Chromaticities of the primaries */ + struct weston_CIExy primary[3]; + + /** White point chromaticity */ + struct weston_CIExy white; + + /** Display's desired maximum content peak luminance, cd/m² */ + float max_luminance; + + /** Display's desired minimum content luminance, cd/m² */ + float min_luminance; + + /** Display's desired maximum frame-average light level, cd/m² */ + float maxFALL; +}; + /** Represents a head, usually a display connector * * \rst @@ -258,9 +392,36 @@ struct weston_head { char *name; /**< head name, e.g. connector name */ bool connected; /**< is physically connected */ bool non_desktop; /**< non-desktop display, e.g. HMD */ + uint32_t supported_eotf_mask; /**< supported weston_eotf_mode bits */ /** Current content protection status */ enum weston_hdcp_protection current_protection; + + /** Opaque pointer used by backends to identify heads as theirs */ + const void *backend_id; +}; + +/** Output properties derived from its color characteristics and profile + * + * These are constructed by a color manager. + * + * A weston_output_color_outcome owns (a reference to) everything it contains. + * + * \ingroup output + * \internal + */ +struct weston_output_color_outcome { + /** sRGB to output color space transformation */ + struct weston_color_transform *from_sRGB_to_output; + + /** sRGB to blending color space transformation */ + struct weston_color_transform *from_sRGB_to_blend; + + /** Blending to output color space transformation */ + struct weston_color_transform *from_blend_to_output; + + /** HDR Static Metadata Type 1 for WESTON_EOTF_MODE_ST2084 */ + struct weston_hdr_metadata_type1 hdr_meta; }; /** Content producer for heads @@ -324,7 +485,6 @@ struct weston_output { /** For cancelling the idle_repaint callback on output destruction. */ struct wl_event_source *idle_repaint_source; - struct weston_output_zoom zoom; int dirty; struct wl_signal frame_signal; struct wl_signal destroy_signal; /**< sent when disabled */ @@ -352,11 +512,9 @@ struct weston_output { bool allow_protection; int (*start_repaint_loop)(struct weston_output *output); - int (*repaint)(struct weston_output *output, - pixman_region32_t *damage, - void *repaint_data); + int (*repaint)(struct weston_output *output, pixman_region32_t *damage); void (*destroy)(struct weston_output *output); - void (*assign_planes)(struct weston_output *output, void *repaint_data); + void (*assign_planes)(struct weston_output *output); int (*switch_mode)(struct weston_output *output, struct weston_mode *mode); /* backlight values are on 0-255 range, where higher is brighter */ @@ -375,10 +533,12 @@ struct weston_output { int scale; struct weston_color_profile *color_profile; - struct weston_color_transform *from_sRGB_to_output; - struct weston_color_transform *from_sRGB_to_blend; - struct weston_color_transform *from_blend_to_output; bool from_blend_to_output_by_backend; + enum weston_eotf_mode eotf_mode; + struct weston_color_characteristics color_characteristics; + + struct weston_output_color_outcome *color_outcome; + uint64_t color_outcome_serial; int (*enable)(struct weston_output *output); int (*disable)(struct weston_output *output); @@ -937,39 +1097,6 @@ struct weston_plane { struct weston_drm_format_array; -struct weston_renderer { - int (*read_pixels)(struct weston_output *output, - pixman_format_code_t format, void *pixels, - uint32_t x, uint32_t y, - uint32_t width, uint32_t height); - void (*repaint_output)(struct weston_output *output, - pixman_region32_t *output_damage); - void (*flush_damage)(struct weston_surface *surface); - void (*attach)(struct weston_surface *es, struct weston_buffer *buffer); - void (*surface_set_color)(struct weston_surface *surface, - float red, float green, - float blue, float alpha); - void (*destroy)(struct weston_compositor *ec); - - - /** See weston_surface_get_content_size() */ - void (*surface_get_content_size)(struct weston_surface *surface, - int *width, int *height); - - /** See weston_surface_copy_content() */ - int (*surface_copy_content)(struct weston_surface *surface, - void *target, size_t size, - int src_x, int src_y, - int width, int height); - - /** See weston_compositor_import_dmabuf() */ - bool (*import_dmabuf)(struct weston_compositor *ec, - struct linux_dmabuf_buffer *buffer); - - const struct weston_drm_format_array * - (*get_supported_formats)(struct weston_compositor *ec); -}; - enum weston_capability { /* backend/renderer supports arbitrary rotation */ WESTON_CAP_ROTATION_ANY = 0x0001, @@ -1126,7 +1253,7 @@ struct weston_compositor { struct weston_color_manager *color_manager; struct weston_renderer *renderer; - pixman_format_code_t read_format; + const struct pixel_format_info *read_format; struct weston_backend *backend; struct weston_launcher *launcher; @@ -1137,6 +1264,7 @@ struct weston_compositor { struct wl_list plugin_api_list; /* struct weston_plugin_api::link */ uint32_t output_id_pool; + bool output_flow_dirty; struct xkb_rule_names xkb_names; struct xkb_context *xkb_context; @@ -1182,8 +1310,17 @@ struct weston_compositor { struct weston_log_context *weston_log_ctx; struct weston_log_scope *debug_scene; struct weston_log_scope *timeline; + struct weston_log_scope *libseat_debug; struct content_protection *content_protection; + + /* One-time warning about a view appearing in the layer list when it + * or its surface are not mapped. */ + bool warned_about_unmapped_surface_or_view; +}; + +struct weston_solid_buffer_values { + float r, g, b, a; }; struct weston_buffer { @@ -1191,19 +1328,45 @@ struct weston_buffer { struct wl_signal destroy_signal; struct wl_listener destroy_listener; + enum { + WESTON_BUFFER_SHM, + WESTON_BUFFER_DMABUF, + WESTON_BUFFER_RENDERER_OPAQUE, + WESTON_BUFFER_SOLID, + } type; + union { struct wl_shm_buffer *shm_buffer; + void *dmabuf; void *legacy_buffer; + struct weston_solid_buffer_values solid; }; + int32_t width, height; uint32_t busy_count; - int y_inverted; + uint32_t passive_count; + enum { + ORIGIN_TOP_LEFT, /* buffer content starts at (0,0) */ + ORIGIN_BOTTOM_LEFT, /* buffer content starts at (0, height) */ + } buffer_origin; + bool direct_display; + + void *renderer_private; void *backend_private; + + const struct pixel_format_info *pixel_format; + uint64_t format_modifier; +}; + +enum weston_buffer_reference_type { + BUFFER_REF_NONE, + BUFFER_MAY_BE_ACCESSED, + BUFFER_WILL_NOT_BE_ACCESSED, }; struct weston_buffer_reference { struct weston_buffer *buffer; - struct wl_listener destroy_listener; + enum weston_buffer_reference_type type; }; struct weston_buffer_viewport { @@ -1377,6 +1540,7 @@ struct weston_surface_state { int newly_attached; struct weston_buffer *buffer; struct wl_listener buffer_destroy_listener; + int32_t sx; int32_t sy; @@ -1762,6 +1926,21 @@ weston_surface_create(struct weston_compositor *compositor); struct weston_view * weston_view_create(struct weston_surface *surface); +struct weston_buffer_reference * +weston_buffer_create_solid_rgba(struct weston_compositor *compositor, + float r, float g, float b, float a); + +void +weston_surface_attach_solid(struct weston_surface *surface, + struct weston_buffer_reference *buffer_ref, + int w, int h); + +void +weston_buffer_destroy_solid(struct weston_buffer_reference *buffer_ref); + +bool +weston_surface_has_content(struct weston_surface *surface); + void weston_view_destroy(struct weston_view *view); @@ -1802,6 +1981,9 @@ weston_view_damage_below(struct weston_view *view); void weston_view_unmap(struct weston_view *view); +void +weston_surface_map(struct weston_surface *surface); + void weston_surface_unmap(struct weston_surface *surface); @@ -1835,7 +2017,8 @@ weston_surface_copy_content(struct weston_surface *surface, int width, int height); struct weston_buffer * -weston_buffer_from_resource(struct wl_resource *resource); +weston_buffer_from_resource(struct weston_compositor *ec, + struct wl_resource *resource); void weston_compositor_get_time(struct timespec *time); @@ -1858,7 +2041,6 @@ weston_compositor_add_destroy_listener_once(struct weston_compositor *compositor enum weston_compositor_backend { WESTON_BACKEND_DRM, - WESTON_BACKEND_FBDEV, WESTON_BACKEND_HEADLESS, WESTON_BACKEND_RDP, WESTON_BACKEND_WAYLAND, @@ -1877,11 +2059,6 @@ void weston_compositor_exit_with_code(struct weston_compositor *compositor, int exit_code); void -weston_output_update_zoom(struct weston_output *output); -void -weston_output_activate_zoom(struct weston_output *output, - struct weston_seat *seat); -void weston_output_add_destroy_listener(struct weston_output *output, struct wl_listener *listener); struct wl_listener * @@ -1960,12 +2137,11 @@ struct weston_view_animation * weston_slide_run(struct weston_view *view, float start, float stop, weston_view_animation_done_func_t done, void *data); -void -weston_surface_set_color(struct weston_surface *surface, - float red, float green, float blue, float alpha); +struct weston_surface * +weston_surface_ref(struct weston_surface *surface); void -weston_surface_destroy(struct weston_surface *surface); +weston_surface_unref(struct weston_surface *surface); int weston_output_mode_switch_to_temporary(struct weston_output *output, @@ -2068,11 +2244,7 @@ weston_compositor_find_output_by_name(struct weston_compositor *compositor, struct weston_output * weston_compositor_create_output(struct weston_compositor *compositor, - const char *name); - -struct weston_output * -weston_compositor_create_output_with_head(struct weston_compositor *compositor, - struct weston_head *head); + struct weston_head *head, const char *name); void weston_output_destroy(struct weston_output *output); @@ -2097,6 +2269,20 @@ bool weston_output_set_color_profile(struct weston_output *output, struct weston_color_profile *cprof); +void +weston_output_set_eotf_mode(struct weston_output *output, + enum weston_eotf_mode eotf_mode); + +enum weston_eotf_mode +weston_output_get_eotf_mode(const struct weston_output *output); + +void +weston_output_set_color_characteristics(struct weston_output *output, + const struct weston_color_characteristics *cc); + +const struct weston_color_characteristics * +weston_output_get_color_characteristics(struct weston_output *output); + void weston_output_init(struct weston_output *output, struct weston_compositor *compositor, @@ -2111,6 +2297,9 @@ weston_output_enable(struct weston_output *output); void weston_output_disable(struct weston_output *output); +uint32_t +weston_output_get_supported_eotf_modes(struct weston_output *output); + void weston_compositor_flush_heads_changed(struct weston_compositor *compositor); diff --git a/include/libweston/matrix.h b/include/libweston/matrix.h index be4d4eb0..95a262bb 100644 --- a/include/libweston/matrix.h +++ b/include/libweston/matrix.h @@ -65,19 +65,6 @@ int weston_matrix_invert(struct weston_matrix *inverse, const struct weston_matrix *matrix); -#ifdef UNIT_TEST -# define MATRIX_TEST_EXPORT WL_EXPORT - -int -matrix_invert(double *A, unsigned *p, const struct weston_matrix *matrix); - -void -inverse_transform(const double *LU, const unsigned *p, float *v); - -#else -# define MATRIX_TEST_EXPORT static -#endif - #ifdef __cplusplus } #endif diff --git a/include/libweston/meson.build b/include/libweston/meson.build index 1ad459bb..8ae10011 100644 --- a/include/libweston/meson.build +++ b/include/libweston/meson.build @@ -12,7 +12,6 @@ install_headers( ) backend_drm_h = files('backend-drm.h') -backend_fbdev_h = files('backend-fbdev.h') backend_headless_h = files('backend-headless.h') backend_rdp_h = files('backend-rdp.h') backend_wayland_h = files('backend-wayland.h') diff --git a/include/libweston/weston-log.h b/include/libweston/weston-log.h index aeb7768b..1786dea0 100644 --- a/include/libweston/weston-log.h +++ b/include/libweston/weston-log.h @@ -109,6 +109,8 @@ weston_log_subscription_complete(struct weston_log_subscription *sub); char * weston_log_scope_timestamp(struct weston_log_scope *scope, char *buf, size_t len); +char * +weston_log_timestamp(char *buf, size_t len, int *cached_tm_mday); void weston_log_subscriber_destroy(struct weston_log_subscriber *subscriber); diff --git a/ivi-shell/README b/ivi-shell/README deleted file mode 100644 index 1d2212ea..00000000 --- a/ivi-shell/README +++ /dev/null @@ -1,78 +0,0 @@ - In-vehicle infotainment (information and entertainment) - graphical environment support modules for Weston - - -IVI-shell is an alternative shell for Weston, a Wayland display server. -Window management and application interaction with the display server -are very different to that of a normal desktop, which is why this is -a separate shell and not an extension to the desktop-shell suite with -xdg_shell. As such, applications need to be specifically written to use -IVI-shell. - -IVI-shell contains two main features: -- Common layout library for surface, which allow ivi-shell developer - to develop own shell, linking Common layout library. - For the time being, the library refers Genivi ilm interface. - - https://at.projects.genivi.org/wiki/display/WIE/Wayland+IVI+Extension+Home - -- Extension protocol; ivi-application to tie wl_surface and a given ID. - With this ID, shell can identify which wl_surface is drawn by which - application. In in-vehicle infortainment system, a shell has to update - a property of a wl_surface. E.g. there may be a use case when vehicle - starts to move, the wl_surface drawn by Car navigation is expected to - move top of surfaces. - -The actual software components delivered with Weston are: - -- ivi-application.xml: - Wayland protocol extension for IVI-applications; the public - shell protocol (the same concept as xdg_shell). - Implemented by ivi-shell.so. - -- ivi-shell.so: - A Weston shell module that implements ivi-application.xml interfaces. - Loads ivi-layout.so. - -- ivi-layout.so: - Implements the IVI window management concepts: Screen, Layer, - Surface, groups of Layers, groups of Surfaces, see: - https://at.projects.genivi.org/wiki/display/WIE/Summary+of+Layer+manager+APIs - Offers a stable API for writing IVI-controller modules like - hmi-controller.so against the IVI concepts. In other words, - it offers an API to write IVI window manager modules. - -- hmi-controller.so: - A sample implementation of an IVI-controller module, usually - replaced by IVI system vendors. - Uses ivi-layout.so to perform essentially window manager tasks. - This implementation keeps all window management inside the module, - while IVI-systems may use another module that exposes all window - management via Wayland or other protocol for an external process - to control. - -- ivi-hmi-controller.xml: - Wayland protocol extension for IVI display control; the private - shell protocol for weston-ivi-shell-user-interface client - (the same concept as desktop-shell.xml). - Implemented by hmi-controller.so, and usually replaced by IVI - system vendors. - -- weston-ivi-shell-user-interface: - A sample implementation of an IVI shell helper client, usually - replaced by IVI system vendors. - A helper client for basic display content, similar to - weston-desktop-shell. - - -How to compile: -same as weston. To disable, use option: --disable-ivi-shell for configure. - -How to configure weston.ini: -reference ini file will be generated in /ivi-shell. - -How to run: -same as weston. exec weston. - -How to use UI: -http://lists.freedesktop.org/archives/wayland-devel/attachments/20140625/abbfc064/attachment-0001.png diff --git a/ivi-shell/hmi-controller.c b/ivi-shell/hmi-controller.c index 230e788b..a1822762 100644 --- a/ivi-shell/hmi-controller.c +++ b/ivi-shell/hmi-controller.c @@ -153,13 +153,6 @@ struct launcher_info { /***************************************************************************** * local functions ****************************************************************************/ -static void * -mem_alloc(size_t size, char *file, int32_t line) -{ - return fail_on_null(calloc(1, size), size, file, line); -} - -#define MEM_ALLOC(s) mem_alloc((s),__FILE__,__LINE__) static int32_t is_surf_in_ui_widget(struct hmi_controller *hmi_ctrl, @@ -222,8 +215,8 @@ mode_divided_into_tiling(struct hmi_controller *hmi_ctrl, int32_t surf_num = 0; int32_t idx = 0; - surfaces = MEM_ALLOC(sizeof(*surfaces) * surface_length); - new_order = MEM_ALLOC(sizeof(*surfaces) * surface_length); + surfaces = xcalloc(surface_length, sizeof(*surfaces)); + new_order = xcalloc(surface_length, sizeof(*surfaces)); for (i = 0; i < surface_length; i++) { ivisurf = pp_surface[i]; @@ -297,8 +290,8 @@ mode_divided_into_sidebyside(struct hmi_controller *hmi_ctrl, int32_t surf_num = 0; int32_t idx = 0; - surfaces = MEM_ALLOC(sizeof(*surfaces) * surface_length); - new_order = MEM_ALLOC(sizeof(*surfaces) * surface_length); + surfaces = xcalloc(surface_length, sizeof(*surfaces)); + new_order = xcalloc(surface_length, sizeof(*surfaces)); for (i = 0; i < surface_length; i++) { ivisurf = pp_surface[i]; @@ -362,7 +355,7 @@ mode_fullscreen_someone(struct hmi_controller *hmi_ctrl, int32_t surf_num = 0; struct ivi_layout_surface **surfaces; - surfaces = MEM_ALLOC(sizeof(*surfaces) * surface_length); + surfaces = xcalloc(surface_length, sizeof(*surfaces)); for (i = 0; i < surface_length; i++) { ivisurf = pp_surface[i]; @@ -412,7 +405,7 @@ mode_random_replace(struct hmi_controller *hmi_ctrl, int32_t i = 0; int32_t layer_idx = 0; - layers = MEM_ALLOC(sizeof(*layers) * hmi_ctrl->screen_num); + layers = xcalloc(hmi_ctrl->screen_num, sizeof(*layers)); wl_list_for_each(application_layer, layer_list, link) { layers[layer_idx] = application_layer; @@ -689,7 +682,7 @@ set_notification_configure_desktop_surface(struct wl_listener *listener, void *d static struct hmi_server_setting * hmi_server_setting_create(struct weston_compositor *ec) { - struct hmi_server_setting *setting = MEM_ALLOC(sizeof(*setting)); + struct hmi_server_setting *setting = xzalloc(sizeof(*setting)); struct weston_config *config = wet_get_config(ec); struct weston_config_section *shell_section = NULL; char *ivi_ui_config; @@ -745,6 +738,8 @@ hmi_controller_destroy(struct wl_listener *listener, void *data) struct hmi_controller *hmi_ctrl = container_of(listener, struct hmi_controller, destroy_listener); + wl_list_remove(&hmi_ctrl->destroy_listener.link); + wl_list_for_each_safe(link, next, &hmi_ctrl->workspace_fade.layer_list, link) { wl_list_remove(&link->link); @@ -804,7 +799,7 @@ hmi_controller_create(struct weston_compositor *ec) return NULL; } - hmi_ctrl = MEM_ALLOC(sizeof(*hmi_ctrl)); + hmi_ctrl = xzalloc(sizeof(*hmi_ctrl)); i = 0; wl_array_init(&hmi_ctrl->ui_widgets); @@ -817,7 +812,7 @@ hmi_controller_create(struct weston_compositor *ec) /* init base ivi_layer*/ wl_list_init(&hmi_ctrl->base_layer_list); wl_list_for_each(output, &ec->output_list, link) { - base_layer = MEM_ALLOC(1 * sizeof(struct hmi_controller_layer)); + base_layer = xzalloc(sizeof(struct hmi_controller_layer)); base_layer->x = 0; base_layer->y = 0; base_layer->width = output->current_mode->width; @@ -837,7 +832,7 @@ hmi_controller_create(struct weston_compositor *ec) /* init application ivi_layer */ wl_list_init(&hmi_ctrl->application_layer_list); wl_list_for_each(output, &ec->output_list, link) { - application_layer = MEM_ALLOC(1 * sizeof(struct hmi_controller_layer)); + application_layer = xzalloc(sizeof(struct hmi_controller_layer)); application_layer->x = 0; application_layer->y = 0; application_layer->width = output->current_mode->width; @@ -872,7 +867,7 @@ hmi_controller_create(struct weston_compositor *ec) wl_list_init(&hmi_ctrl->workspace_fade.layer_list); - tmp_link_layer = MEM_ALLOC(sizeof(*tmp_link_layer)); + tmp_link_layer = xzalloc(sizeof(*tmp_link_layer)); tmp_link_layer->layout_layer = hmi_ctrl->workspace_background_layer.ivilayer; wl_list_insert(&hmi_ctrl->workspace_fade.layer_list, @@ -1267,7 +1262,7 @@ ivi_hmi_controller_add_launchers(struct hmi_controller *hmi_ctrl, hmi_ctrl->interface->layer_set_visibility(hmi_ctrl->workspace_layer.ivilayer, false); - tmp_link_layer = MEM_ALLOC(sizeof(*tmp_link_layer)); + tmp_link_layer = xzalloc(sizeof(*tmp_link_layer)); tmp_link_layer->layout_layer = hmi_ctrl->workspace_layer.ivilayer; wl_list_insert(&hmi_ctrl->workspace_fade.layer_list, &tmp_link_layer->link); @@ -1756,7 +1751,7 @@ create_workspace_pointer_move(struct weston_pointer *pointer, struct wl_resource* resource) { struct pointer_move_grab *pnt_move_grab = - MEM_ALLOC(sizeof(*pnt_move_grab)); + xzalloc(sizeof(*pnt_move_grab)); pnt_move_grab->base.resource = resource; move_grab_init_workspace(&pnt_move_grab->move, pointer->grab_x, @@ -1770,7 +1765,7 @@ create_workspace_touch_move(struct weston_touch *touch, struct wl_resource* resource) { struct touch_move_grab *tch_move_grab = - MEM_ALLOC(sizeof(*tch_move_grab)); + xzalloc(sizeof(*tch_move_grab)); tch_move_grab->base.resource = resource; tch_move_grab->is_active = 1; diff --git a/ivi-shell/ivi-layout-export.h b/ivi-shell/ivi-layout-export.h index 740e1ac0..c2a47cd2 100644 --- a/ivi-shell/ivi-layout-export.h +++ b/ivi-shell/ivi-layout-export.h @@ -43,9 +43,6 @@ * way in In-Vehicle Infotainment system, which integrate several domains * in one system. A layer is allocated to a domain in order to control * application surfaces grouped to the layer all together. - * - * This API and ABI follow following specifications. - * https://at.projects.genivi.org/wiki/display/PROJ/Wayland+IVI+Extension+Design */ #ifndef _IVI_LAYOUT_EXPORT_H_ @@ -67,7 +64,6 @@ extern "C" { #define IVI_INVALID_ID UINT_MAX struct ivi_layout_layer; -struct ivi_layout_screen; struct ivi_layout_surface; struct ivi_layout_surface_properties diff --git a/ivi-shell/ivi-layout-shell.h b/ivi-shell/ivi-layout-shell.h index 63f66fa9..1c10a8ba 100644 --- a/ivi-shell/ivi-layout-shell.h +++ b/ivi-shell/ivi-layout-shell.h @@ -44,7 +44,8 @@ ivi_layout_desktop_surface_configure(struct ivi_layout_surface *ivisurf, int32_t width, int32_t height); struct ivi_layout_surface* -ivi_layout_desktop_surface_create(struct weston_surface *wl_surface); +ivi_layout_desktop_surface_create(struct weston_surface *wl_surface, + struct weston_desktop_surface *surface); void ivi_layout_surface_configure(struct ivi_layout_surface *ivisurf, diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index 0f0f4f8a..9f861a38 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -835,7 +835,7 @@ build_view_list(struct ivi_layout *layout) weston_layer_entry_insert(&layout->layout_layer.view_list, &ivi_view->view->layer_link); - ivi_view->ivisurf->surface->is_mapped = true; + weston_surface_map(ivi_view->ivisurf->surface); ivi_view->view->is_mapped = true; } } @@ -1192,15 +1192,14 @@ ivi_layout_get_layers_under_surface(struct ivi_layout_surface *ivisurf, else length--; } + if (length == 0) { + free(*ppArray); + *ppArray = NULL; + } } *pLength = length; - if (!length) { - free(*ppArray); - *ppArray = NULL; - } - return IVI_SUCCEEDED; } @@ -1882,7 +1881,6 @@ ivi_layout_surface_set_id(struct ivi_layout_surface *ivisurf, ivisurf->id_surface = id_surface; - wl_signal_emit(&layout->surface_notification.created, ivisurf); wl_signal_emit(&layout->surface_notification.configure_changed, ivisurf); @@ -1979,9 +1977,20 @@ ivi_layout_desktop_surface_configure(struct ivi_layout_surface *ivisurf, } struct ivi_layout_surface* -ivi_layout_desktop_surface_create(struct weston_surface *wl_surface) +ivi_layout_desktop_surface_create(struct weston_surface *wl_surface, + struct weston_desktop_surface *surface) { - return surface_create(wl_surface, IVI_INVALID_ID); + struct ivi_layout *layout = get_instance(); + struct ivi_layout_surface *ivisurf; + + ivisurf = surface_create(wl_surface, IVI_INVALID_ID); + + if (ivisurf) { + ivisurf->weston_desktop_surface = surface; + wl_signal_emit(&layout->surface_notification.created, ivisurf); + } + + return ivisurf; } void diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index 5ec1349e..79ff2191 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -346,7 +346,6 @@ shell_destroy(struct wl_listener *listener, void *data) wl_list_remove(&shell->destroy_listener.link); wl_list_remove(&shell->wake_listener.link); - weston_desktop_destroy(shell->desktop); wl_list_for_each_safe(ivisurf, next, &shell->ivi_surface_list, link) { if (ivisurf->layout_surface != NULL) @@ -357,6 +356,7 @@ shell_destroy(struct wl_listener *listener, void *data) ivi_layout_fini(); + weston_desktop_destroy(shell->desktop); free(shell); } @@ -489,13 +489,11 @@ desktop_surface_added(struct weston_desktop_surface *surface, struct weston_surface *weston_surf = weston_desktop_surface_get_surface(surface); - layout_surface = ivi_layout_desktop_surface_create(weston_surf); + layout_surface = ivi_layout_desktop_surface_create(weston_surf, surface); if (!layout_surface) { return; } - layout_surface->weston_desktop_surface = surface; - ivisurf = zalloc(sizeof *ivisurf); if (!ivisurf) { return; @@ -509,6 +507,8 @@ desktop_surface_added(struct weston_desktop_surface *surface, ivisurf->layout_surface = layout_surface; ivisurf->surface = weston_surf; + wl_list_insert(&shell->ivi_surface_list, &ivisurf->link); + weston_desktop_surface_set_user_data(surface, ivisurf); } @@ -521,8 +521,14 @@ desktop_surface_removed(struct weston_desktop_surface *surface, assert(ivisurf != NULL); + weston_desktop_surface_set_user_data(surface, NULL); + if (ivisurf->layout_surface) layout_surface_cleanup(ivisurf); + + wl_list_remove(&ivisurf->link); + + free(ivisurf); } static void diff --git a/ivi-shell/meson.build b/ivi-shell/meson.build index b071ca63..8e064fb7 100644 --- a/ivi-shell/meson.build +++ b/ivi-shell/meson.build @@ -15,7 +15,6 @@ if get_option('shell-ivi') dependencies: [ dep_libm, dep_libexec_weston, - dep_lib_desktop, dep_libweston_public ], name_prefix: '', diff --git a/kiosk-shell/kiosk-shell.c b/kiosk-shell/kiosk-shell.c index 14857ecc..ceff193e 100644 --- a/kiosk-shell/kiosk-shell.c +++ b/kiosk-shell/kiosk-shell.c @@ -33,7 +33,7 @@ #include "kiosk-shell-grab.h" #include "compositor/weston.h" #include "shared/helpers.h" -#include "shared/shell-utils.h" +#include "shell-utils.h" #include @@ -413,6 +413,7 @@ kiosk_shell_surface_activate(struct kiosk_shell_surface *shsurf, weston_layer_entry_insert(&shsurf->shell->normal_layer.view_list, &shsurf->view->layer_link); weston_view_geometry_dirty(shsurf->view); + weston_view_update_transform(shsurf->view); weston_surface_damage(shsurf->view->surface); } @@ -480,13 +481,14 @@ static void kiosk_shell_output_recreate_background(struct kiosk_shell_output *shoutput) { struct kiosk_shell *shell = shoutput->shell; + struct weston_compositor *ec = shell->compositor; struct weston_output *output = shoutput->output; struct weston_config_section *shell_section = NULL; uint32_t bg_color = 0x0; - struct weston_solid_color_surface solid_surface = {}; + struct weston_curtain_params curtain_params = {}; - if (shoutput->background_view) - weston_surface_destroy(shoutput->background_view->surface); + if (shoutput->curtain) + weston_curtain_destroy(shoutput->curtain); if (!output) return; @@ -497,31 +499,33 @@ kiosk_shell_output_recreate_background(struct kiosk_shell_output *shoutput) weston_config_section_get_color(shell_section, "background-color", &bg_color, 0x00000000); - solid_surface.r = ((bg_color >> 16) & 0xff) / 255.0; - solid_surface.g = ((bg_color >> 8) & 0xff) / 255.0; - solid_surface.b = ((bg_color >> 0) & 0xff) / 255.0; + curtain_params.r = ((bg_color >> 16) & 0xff) / 255.0; + curtain_params.g = ((bg_color >> 8) & 0xff) / 255.0; + curtain_params.b = ((bg_color >> 0) & 0xff) / 255.0; + curtain_params.a = 1.0; + + curtain_params.x = output->x; + curtain_params.y = output->y; + curtain_params.width = output->width; + curtain_params.height = output->height; - solid_surface.get_label = kiosk_shell_background_surface_get_label; - solid_surface.surface_committed = NULL; - solid_surface.surface_private = NULL; + curtain_params.capture_input = true; - shoutput->background_view = - create_solid_color_surface(shoutput->shell->compositor, - &solid_surface, - output->x, output->y, - output->width, - output->height); + curtain_params.get_label = kiosk_shell_background_surface_get_label; + curtain_params.surface_committed = NULL; + curtain_params.surface_private = NULL; - weston_surface_set_role(shoutput->background_view->surface, + shoutput->curtain = weston_curtain_create(ec, &curtain_params); + + weston_surface_set_role(shoutput->curtain->view->surface, "kiosk-shell-background", NULL, 0); weston_layer_entry_insert(&shell->background_layer.view_list, - &shoutput->background_view->layer_link); + &shoutput->curtain->view->layer_link); - shoutput->background_view->is_mapped = true; - shoutput->background_view->surface->is_mapped = true; - shoutput->background_view->surface->output = output; - weston_view_set_output(shoutput->background_view, output); + shoutput->curtain->view->is_mapped = true; + shoutput->curtain->view->surface->output = output; + weston_view_set_output(shoutput->curtain->view, output); } static void @@ -530,8 +534,8 @@ kiosk_shell_output_destroy(struct kiosk_shell_output *shoutput) shoutput->output = NULL; shoutput->output_destroy_listener.notify = NULL; - if (shoutput->background_view) - weston_surface_destroy(shoutput->background_view->surface); + if (shoutput->curtain) + weston_curtain_destroy(shoutput->curtain); wl_list_remove(&shoutput->output_destroy_listener.link); wl_list_remove(&shoutput->link); @@ -795,7 +799,7 @@ desktop_surface_committed(struct weston_desktop_surface *desktop_surface, struct kiosk_shell_seat *kiosk_seat; shsurf->view->is_mapped = true; - surface->is_mapped = true; + weston_surface_map(surface); kiosk_seat = get_kiosk_shell_seat(seat); if (seat && kiosk_seat) @@ -948,6 +952,17 @@ desktop_surface_set_xwayland_position(struct weston_desktop_surface *desktop_sur shsurf->xwayland.is_set = true; } +static void +desktop_surface_get_position(struct weston_desktop_surface *desktop_surface, + int32_t *x, int32_t *y, void *shell) +{ + struct kiosk_shell_surface *shsurf = + weston_desktop_surface_get_user_data(desktop_surface); + + *x = shsurf->view->geometry.x; + *y = shsurf->view->geometry.y; +} + static const struct weston_desktop_api kiosk_shell_desktop_api = { .struct_size = sizeof(struct weston_desktop_api), .surface_added = desktop_surface_added, @@ -962,6 +977,7 @@ static const struct weston_desktop_api kiosk_shell_desktop_api = { .ping_timeout = desktop_surface_ping_timeout, .pong = desktop_surface_pong, .set_xwayland_position = desktop_surface_set_xwayland_position, + .get_position = desktop_surface_get_position, }; /* @@ -1049,6 +1065,10 @@ kiosk_shell_touch_to_activate_binding(struct weston_touch *touch, static void kiosk_shell_add_bindings(struct kiosk_shell *shell) { + uint32_t mod = 0; + + mod = weston_shell_get_binding_modifier(shell->config, MODIFIER_SUPER); + weston_compositor_add_button_binding(shell->compositor, BTN_LEFT, 0, kiosk_shell_click_to_activate_binding, shell); @@ -1058,6 +1078,8 @@ kiosk_shell_add_bindings(struct kiosk_shell *shell) weston_compositor_add_touch_binding(shell->compositor, 0, kiosk_shell_touch_to_activate_binding, shell); + + weston_install_debug_key_binding(shell->compositor, mod); } static void diff --git a/kiosk-shell/kiosk-shell.h b/kiosk-shell/kiosk-shell.h index 070ba1ab..3eb82cd9 100644 --- a/kiosk-shell/kiosk-shell.h +++ b/kiosk-shell/kiosk-shell.h @@ -88,7 +88,7 @@ struct kiosk_shell_seat { struct kiosk_shell_output { struct weston_output *output; struct wl_listener output_destroy_listener; - struct weston_view *background_view; + struct weston_curtain *curtain; struct kiosk_shell *shell; struct wl_list link; diff --git a/kiosk-shell/meson.build b/kiosk-shell/meson.build index c3a37da2..0ebea621 100644 --- a/kiosk-shell/meson.build +++ b/kiosk-shell/meson.build @@ -2,9 +2,6 @@ if get_option('shell-kiosk') srcs_shell_kiosk = [ 'kiosk-shell.c', 'kiosk-shell-grab.c', - '../shared/shell-utils.c', - weston_desktop_shell_server_protocol_h, - weston_desktop_shell_protocol_c, input_method_unstable_v1_server_protocol_h, input_method_unstable_v1_protocol_c, ] @@ -12,8 +9,8 @@ if get_option('shell-kiosk') dep_libm, dep_libexec_weston, dep_libshared, - dep_lib_desktop, dep_libweston_public, + dep_shell_utils, ] plugin_shell_kiosk = shared_library( 'kiosk-shell', diff --git a/libweston-desktop/meson.build b/libweston-desktop/meson.build deleted file mode 100644 index 0a45d941..00000000 --- a/libweston-desktop/meson.build +++ /dev/null @@ -1,36 +0,0 @@ -srcs_libdesktop = [ - 'libweston-desktop.c', - 'client.c', - 'seat.c', - 'surface.c', - 'xwayland.c', - 'wl-shell.c', - 'xdg-shell.c', - 'xdg-shell-v6.c', - xdg_shell_unstable_v6_server_protocol_h, - xdg_shell_unstable_v6_protocol_c, - xdg_shell_server_protocol_h, - xdg_shell_protocol_c, -] -lib_desktop = shared_library( - 'weston-desktop-@0@'.format(libweston_major), - srcs_libdesktop, - include_directories: common_inc, - install: true, - version: '0.0.@0@'.format(libweston_revision), - dependencies: dep_libweston_public -) -dep_lib_desktop = declare_dependency( - link_with: lib_desktop, - dependencies: dep_libweston_public -) - -pkgconfig.generate( - lib_desktop, - filebase: 'libweston-desktop-@0@'.format(libweston_major), - name: 'libweston-desktop', - version: version_weston, - description: 'Desktop shells abstraction library for libweston compositors', - requires_private: [ lib_weston, dep_wayland_server ], - subdirs: dir_include_libweston -) diff --git a/libweston-desktop/wl-shell.c b/libweston-desktop/wl-shell.c deleted file mode 100644 index 9efec89b..00000000 --- a/libweston-desktop/wl-shell.c +++ /dev/null @@ -1,497 +0,0 @@ -/* - * Copyright © 2010-2012 Intel Corporation - * Copyright © 2011-2012 Collabora, Ltd. - * Copyright © 2013 Raspberry Pi Foundation - * Copyright © 2016 Quentin "Sardem FF7" Glidic - * - * 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. - */ - -#include "config.h" - -#include - -#include - -#include -#include - -#include -#include "internal.h" - -#define WD_WL_SHELL_PROTOCOL_VERSION 1 - -enum weston_desktop_wl_shell_surface_state { - NONE, - TOPLEVEL, - MAXIMIZED, - FULLSCREEN, - TRANSIENT, - POPUP, -}; - -struct weston_desktop_wl_shell_surface { - struct wl_resource *resource; - struct weston_desktop *desktop; - struct wl_display *display; - struct weston_desktop_surface *surface; - struct weston_desktop_surface *parent; - bool added; - struct weston_desktop_seat *popup_seat; - enum weston_desktop_wl_shell_surface_state state; - struct wl_listener wl_surface_resource_destroy_listener; -}; - -static void -weston_desktop_wl_shell_surface_set_size(struct weston_desktop_surface *dsurface, - void *user_data, - int32_t width, int32_t height) -{ - struct weston_desktop_wl_shell_surface *surface = user_data; - struct weston_surface *wsurface = - weston_desktop_surface_get_surface(surface->surface); - - if ((wsurface->width == width && wsurface->height == height) || - (width == 0 && height == 0)) - return; - - wl_shell_surface_send_configure(surface->resource, - WL_SHELL_SURFACE_RESIZE_NONE, - width, height); -} - -static void -weston_desktop_wl_shell_surface_maybe_ungrab(struct weston_desktop_wl_shell_surface *surface) -{ - if (surface->state != POPUP || - !weston_desktop_surface_get_grab(surface->surface)) - return; - - weston_desktop_surface_popup_ungrab(surface->surface, - surface->popup_seat); - surface->popup_seat = NULL; -} - -static void -weston_desktop_wl_shell_surface_committed(struct weston_desktop_surface *dsurface, - void *user_data, - int32_t sx, int32_t sy) -{ - struct weston_desktop_wl_shell_surface *surface = user_data; - struct weston_surface *wsurface = - weston_desktop_surface_get_surface(dsurface); - - if (wsurface->buffer_ref.buffer == NULL) - weston_desktop_wl_shell_surface_maybe_ungrab(surface); - - if (surface->added) - weston_desktop_api_committed(surface->desktop, surface->surface, - sx, sy); -} - -static void -weston_desktop_wl_shell_surface_ping(struct weston_desktop_surface *dsurface, - uint32_t serial, void *user_data) -{ - struct weston_desktop_wl_shell_surface *surface = user_data; - - wl_shell_surface_send_ping(surface->resource, serial); -} - -static void -weston_desktop_wl_shell_surface_close(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_wl_shell_surface *surface = user_data; - - if (surface->state == POPUP) - wl_shell_surface_send_popup_done(surface->resource); -} - -static bool -weston_desktop_wl_shell_surface_get_maximized(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_wl_shell_surface *surface = user_data; - - return surface->state == MAXIMIZED; -} - -static bool -weston_desktop_wl_shell_surface_get_fullscreen(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_wl_shell_surface *surface = user_data; - - return surface->state == FULLSCREEN; -} - -static void -weston_desktop_wl_shell_change_state(struct weston_desktop_wl_shell_surface *surface, - enum weston_desktop_wl_shell_surface_state state, - struct weston_desktop_surface *parent, - int32_t x, int32_t y) -{ - bool to_add = (parent == NULL); - - assert(state != NONE); - - if (to_add && surface->added) { - surface->state = state; - return; - } - - if (surface->state != state) { - if (surface->state == POPUP) - weston_desktop_wl_shell_surface_maybe_ungrab(surface); - - if (to_add) { - weston_desktop_surface_unset_relative_to(surface->surface); - weston_desktop_api_surface_added(surface->desktop, - surface->surface); - } else if (surface->added) { - weston_desktop_api_surface_removed(surface->desktop, - surface->surface); - } - - surface->state = state; - surface->added = to_add; - } - - if (parent != NULL) - weston_desktop_surface_set_relative_to(surface->surface, parent, - x, y, false); -} - -static void -weston_desktop_wl_shell_surface_destroy(struct weston_desktop_surface *dsurface, - void *user_data) -{ - struct weston_desktop_wl_shell_surface *surface = user_data; - - wl_list_remove(&surface->wl_surface_resource_destroy_listener.link); - - weston_desktop_wl_shell_surface_maybe_ungrab(surface); - weston_desktop_surface_unset_relative_to(surface->surface); - if (surface->added) - weston_desktop_api_surface_removed(surface->desktop, - surface->surface); - - free(surface); -} - -static void -weston_desktop_wl_shell_surface_protocol_pong(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t serial) -{ - struct weston_desktop_surface *surface = wl_resource_get_user_data(resource); - - weston_desktop_client_pong(weston_desktop_surface_get_client(surface), serial); -} - -static void -weston_desktop_wl_shell_surface_protocol_move(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *seat_resource, - uint32_t serial) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_seat *seat = - wl_resource_get_user_data(seat_resource); - struct weston_desktop_wl_shell_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - if (seat == NULL) - return; - - weston_desktop_api_move(surface->desktop, dsurface, seat, serial); -} - -static void -weston_desktop_wl_shell_surface_protocol_resize(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *seat_resource, - uint32_t serial, - enum wl_shell_surface_resize edges) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_seat *seat = wl_resource_get_user_data(seat_resource); - struct weston_desktop_wl_shell_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - enum weston_desktop_surface_edge surf_edges = - (enum weston_desktop_surface_edge) edges; - - if (seat == NULL) - return; - - weston_desktop_api_resize(surface->desktop, dsurface, seat, serial, surf_edges); -} - -static void -weston_desktop_wl_shell_surface_protocol_set_toplevel(struct wl_client *wl_client, - struct wl_resource *resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_wl_shell_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - weston_desktop_wl_shell_change_state(surface, TOPLEVEL, NULL, 0, 0); - if (surface->parent == NULL) - return; - surface->parent = NULL; - weston_desktop_api_set_parent(surface->desktop, surface->surface, NULL); -} - -static void -weston_desktop_wl_shell_surface_protocol_set_transient(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *parent_resource, - int32_t x, int32_t y, - enum wl_shell_surface_transient flags) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_surface *wparent = - wl_resource_get_user_data(parent_resource); - struct weston_desktop_surface *parent; - struct weston_desktop_wl_shell_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - if (!weston_surface_is_desktop_surface(wparent)) - return; - - parent = weston_surface_get_desktop_surface(wparent); - if (flags & WL_SHELL_SURFACE_TRANSIENT_INACTIVE) { - weston_desktop_wl_shell_change_state(surface, TRANSIENT, parent, - x, y); - } else { - weston_desktop_wl_shell_change_state(surface, TOPLEVEL, NULL, - 0, 0); - surface->parent = parent; - weston_desktop_api_set_parent(surface->desktop, - surface->surface, parent); - } -} - -static void -weston_desktop_wl_shell_surface_protocol_set_fullscreen(struct wl_client *wl_client, - struct wl_resource *resource, - enum wl_shell_surface_fullscreen_method method, - uint32_t framerate, - struct wl_resource *output_resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_wl_shell_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - struct weston_output *output = NULL; - - if (output_resource != NULL) - output = weston_head_from_resource(output_resource)->output; - - weston_desktop_wl_shell_change_state(surface, FULLSCREEN, NULL, 0, 0); - weston_desktop_api_fullscreen_requested(surface->desktop, dsurface, - true, output); -} - -static void -weston_desktop_wl_shell_surface_protocol_set_popup(struct wl_client *wl_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 weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_seat *wseat = wl_resource_get_user_data(seat_resource); - struct weston_desktop_seat *seat = weston_desktop_seat_from_seat(wseat); - struct weston_surface *parent = - wl_resource_get_user_data(parent_resource); - struct weston_desktop_surface *parent_surface; - struct weston_desktop_wl_shell_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - /* Check that if we have a valid wseat we also got a valid desktop seat */ - if (wseat != NULL && seat == NULL) { - wl_client_post_no_memory(wl_client); - return; - } - - if (!weston_surface_is_desktop_surface(parent)) - return; - - parent_surface = weston_surface_get_desktop_surface(parent); - - weston_desktop_wl_shell_change_state(surface, POPUP, - parent_surface, x, y); - weston_desktop_surface_popup_grab(surface->surface, seat, serial); - surface->popup_seat = seat; -} - -static void -weston_desktop_wl_shell_surface_protocol_set_maximized(struct wl_client *wl_client, - struct wl_resource *resource, - struct wl_resource *output_resource) -{ - struct weston_desktop_surface *dsurface = - wl_resource_get_user_data(resource); - struct weston_desktop_wl_shell_surface *surface = - weston_desktop_surface_get_implementation_data(dsurface); - - weston_desktop_wl_shell_change_state(surface, MAXIMIZED, NULL, 0, 0); - weston_desktop_api_maximized_requested(surface->desktop, dsurface, true); -} - -static void -weston_desktop_wl_shell_surface_protocol_set_title(struct wl_client *wl_client, - struct wl_resource *resource, - const char *title) -{ - struct weston_desktop_surface *surface = - wl_resource_get_user_data(resource); - - weston_desktop_surface_set_title(surface, title); -} - -static void -weston_desktop_wl_shell_surface_protocol_set_class(struct wl_client *wl_client, - struct wl_resource *resource, - const char *class_) -{ - struct weston_desktop_surface *surface = - wl_resource_get_user_data(resource); - - weston_desktop_surface_set_app_id(surface, class_); -} - - -static const struct wl_shell_surface_interface weston_desktop_wl_shell_surface_implementation = { - .pong = weston_desktop_wl_shell_surface_protocol_pong, - .move = weston_desktop_wl_shell_surface_protocol_move, - .resize = weston_desktop_wl_shell_surface_protocol_resize, - .set_toplevel = weston_desktop_wl_shell_surface_protocol_set_toplevel, - .set_transient = weston_desktop_wl_shell_surface_protocol_set_transient, - .set_fullscreen = weston_desktop_wl_shell_surface_protocol_set_fullscreen, - .set_popup = weston_desktop_wl_shell_surface_protocol_set_popup, - .set_maximized = weston_desktop_wl_shell_surface_protocol_set_maximized, - .set_title = weston_desktop_wl_shell_surface_protocol_set_title, - .set_class = weston_desktop_wl_shell_surface_protocol_set_class, -}; - -static const struct weston_desktop_surface_implementation weston_desktop_wl_shell_surface_internal_implementation = { - .set_size = weston_desktop_wl_shell_surface_set_size, - .committed = weston_desktop_wl_shell_surface_committed, - .ping = weston_desktop_wl_shell_surface_ping, - .close = weston_desktop_wl_shell_surface_close, - - .get_maximized = weston_desktop_wl_shell_surface_get_maximized, - .get_fullscreen = weston_desktop_wl_shell_surface_get_fullscreen, - - .destroy = weston_desktop_wl_shell_surface_destroy, -}; - -static void -wl_surface_resource_destroyed(struct wl_listener *listener, - void *data) -{ - struct weston_desktop_wl_shell_surface *surface = - wl_container_of(listener, surface, - wl_surface_resource_destroy_listener); - - /* the wl_shell_surface spec says that wl_shell_surfaces are to be - * destroyed automatically when the wl_surface is destroyed. */ - weston_desktop_surface_destroy(surface->surface); -} - -static void -weston_desktop_wl_shell_protocol_get_shell_surface(struct wl_client *wl_client, - struct wl_resource *resource, - uint32_t id, - struct wl_resource *surface_resource) -{ - struct weston_desktop_client *client = wl_resource_get_user_data(resource); - struct weston_surface *wsurface = wl_resource_get_user_data(surface_resource); - struct weston_desktop_wl_shell_surface *surface; - - - if (weston_surface_set_role(wsurface, "wl_shell_surface", resource, WL_SHELL_ERROR_ROLE) < 0) - return; - - surface = zalloc(sizeof(struct weston_desktop_wl_shell_surface)); - if (surface == NULL) { - wl_client_post_no_memory(wl_client); - return; - } - - surface->desktop = weston_desktop_client_get_desktop(client); - surface->display = weston_desktop_get_display(surface->desktop); - - surface->surface = - weston_desktop_surface_create(surface->desktop, client, wsurface, - &weston_desktop_wl_shell_surface_internal_implementation, - surface); - if (surface->surface == NULL) { - free(surface); - return; - } - - surface->wl_surface_resource_destroy_listener.notify = - wl_surface_resource_destroyed; - wl_resource_add_destroy_listener(wsurface->resource, - &surface->wl_surface_resource_destroy_listener); - - surface->resource = - weston_desktop_surface_add_resource(surface->surface, - &wl_shell_surface_interface, - &weston_desktop_wl_shell_surface_implementation, - id, NULL); -} - - -static const struct wl_shell_interface weston_desktop_wl_shell_implementation = { - .get_shell_surface = weston_desktop_wl_shell_protocol_get_shell_surface, -}; - -static void -weston_desktop_wl_shell_bind(struct wl_client *client, void *data, - uint32_t version, uint32_t id) -{ - struct weston_desktop *desktop = data; - - weston_desktop_client_create(desktop, client, NULL, &wl_shell_interface, - &weston_desktop_wl_shell_implementation, - version, id); -} - -struct wl_global * -weston_desktop_wl_shell_create(struct weston_desktop *desktop, - struct wl_display *display) -{ - return wl_global_create(display, - &wl_shell_interface, - WD_WL_SHELL_PROTOCOL_VERSION, desktop, - weston_desktop_wl_shell_bind); -} diff --git a/libweston/backend-drm/drm-gbm.c b/libweston/backend-drm/drm-gbm.c index d0a4c6ca..76fa79f8 100644 --- a/libweston/backend-drm/drm-gbm.c +++ b/libweston/backend-drm/drm-gbm.c @@ -118,8 +118,9 @@ drm_backend_create_gl_renderer(struct drm_backend *b) int init_egl(struct drm_backend *b) { - b->gbm = create_gbm_device(b->drm.fd); + struct drm_device *device = b->drm; + b->gbm = create_gbm_device(device->drm.fd); if (!b->gbm) return -1; @@ -145,6 +146,7 @@ static void drm_output_fini_cursor_egl(struct drm_output *output) static int drm_output_init_cursor_egl(struct drm_output *output, struct drm_backend *b) { + struct drm_device *device = output->device; unsigned int i; /* No point creating cursors if we don't have a plane for them. */ @@ -154,14 +156,14 @@ drm_output_init_cursor_egl(struct drm_output *output, struct drm_backend *b) for (i = 0; i < ARRAY_LENGTH(output->gbm_cursor_fb); i++) { struct gbm_bo *bo; - bo = gbm_bo_create(b->gbm, b->cursor_width, b->cursor_height, + bo = gbm_bo_create(b->gbm, device->cursor_width, device->cursor_height, GBM_FORMAT_ARGB8888, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE); if (!bo) goto err; output->gbm_cursor_fb[i] = - drm_fb_get_from_bo(bo, b, false, BUFFER_CURSOR); + drm_fb_get_from_bo(bo, device, false, BUFFER_CURSOR); if (!output->gbm_cursor_fb[i]) { gbm_bo_destroy(bo); goto err; @@ -173,7 +175,7 @@ drm_output_init_cursor_egl(struct drm_output *output, struct drm_backend *b) err: weston_log("cursor buffers unavailable, using gl cursors\n"); - b->cursors_are_broken = true; + device->cursors_are_broken = true; drm_output_fini_cursor_egl(output); return -1; } @@ -183,6 +185,7 @@ create_gbm_surface(struct gbm_device *gbm, struct drm_output *output) { struct weston_mode *mode = output->base.current_mode; struct drm_plane *plane = output->scanout_plane; + const struct pixel_format_info *pixel_format; struct weston_drm_format *fmt; const uint64_t *modifiers; unsigned int num_modifiers; @@ -190,8 +193,15 @@ create_gbm_surface(struct gbm_device *gbm, struct drm_output *output) fmt = weston_drm_format_array_find_format(&plane->formats, output->gbm_format); if (!fmt) { - weston_log("format 0x%x not supported by output %s\n", - output->gbm_format, output->base.name); + pixel_format = pixel_format_get_info(output->gbm_format); + if (pixel_format) + weston_log("format %s not supported by output %s\n", + pixel_format->drm_format_name, + output->base.name); + else + weston_log("format 0x%x not supported by output %s\n", + output->gbm_format, + output->base.name); return; } @@ -280,7 +290,7 @@ struct drm_fb * drm_output_render_gl(struct drm_output_state *state, pixman_region32_t *damage) { struct drm_output *output = state->output; - struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_device *device = output->device; struct gbm_bo *bo; struct drm_fb *ret; @@ -295,7 +305,7 @@ drm_output_render_gl(struct drm_output_state *state, pixman_region32_t *damage) } /* The renderer always produces an opaque image. */ - ret = drm_fb_get_from_bo(bo, b, true, BUFFER_GBM_SURFACE); + ret = drm_fb_get_from_bo(bo, device, true, BUFFER_GBM_SURFACE); if (!ret) { weston_log("failed to get drm_fb for bo\n"); gbm_surface_release_buffer(output->gbm_surface, bo); @@ -305,68 +315,3 @@ drm_output_render_gl(struct drm_output_state *state, pixman_region32_t *damage) return ret; } - -static void -switch_to_gl_renderer(struct drm_backend *b) -{ - struct drm_output *output; - bool dmabuf_support_inited; - bool linux_explicit_sync_inited; - - if (!b->use_pixman) - return; - - dmabuf_support_inited = !!b->compositor->renderer->import_dmabuf; - linux_explicit_sync_inited = - b->compositor->capabilities & WESTON_CAP_EXPLICIT_SYNC; - - weston_log("Switching to GL renderer\n"); - - b->gbm = create_gbm_device(b->drm.fd); - if (!b->gbm) { - weston_log("Failed to create gbm device. " - "Aborting renderer switch\n"); - return; - } - - wl_list_for_each(output, &b->compositor->output_list, base.link) - pixman_renderer_output_destroy(&output->base); - - b->compositor->renderer->destroy(b->compositor); - - if (drm_backend_create_gl_renderer(b) < 0) { - gbm_device_destroy(b->gbm); - weston_log("Failed to create GL renderer. Quitting.\n"); - /* FIXME: we need a function to shutdown cleanly */ - assert(0); - } - - wl_list_for_each(output, &b->compositor->output_list, base.link) - drm_output_init_egl(output, b); - - b->use_pixman = 0; - - if (!dmabuf_support_inited && b->compositor->renderer->import_dmabuf) { - if (linux_dmabuf_setup(b->compositor) < 0) - weston_log("Error: initializing dmabuf " - "support failed.\n"); - } - - if (!linux_explicit_sync_inited && - (b->compositor->capabilities & WESTON_CAP_EXPLICIT_SYNC)) { - if (linux_explicit_synchronization_setup(b->compositor) < 0) - weston_log("Error: initializing explicit " - " synchronization support failed.\n"); - } -} - -void -renderer_switch_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, void *data) -{ - struct drm_backend *b = - to_drm_backend(keyboard->seat->compositor); - - switch_to_gl_renderer(b); -} - diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index 48600880..d2cc7a5b 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -186,6 +186,8 @@ enum wdrm_connector_property { WDRM_CONNECTOR_CONTENT_PROTECTION, WDRM_CONNECTOR_HDCP_CONTENT_TYPE, WDRM_CONNECTOR_PANEL_ORIENTATION, + WDRM_CONNECTOR_HDR_OUTPUT_METADATA, + WDRM_CONNECTOR_MAX_BPC, WDRM_CONNECTOR__COUNT }; @@ -232,11 +234,20 @@ enum wdrm_crtc_property { */ enum try_view_on_plane_failure_reasons { FAILURE_REASONS_NONE = 0, - FAILURE_REASONS_FORCE_RENDERER = (1 << 0), - FAILURE_REASONS_FB_FORMAT_INCOMPATIBLE = (1 << 1), - FAILURE_REASONS_DMABUF_MODIFIER_INVALID = (1 << 2), - FAILURE_REASONS_ADD_FB_FAILED = (1 << 3), - FAILURE_REASONS_GBM_BO_IMPORT_FAILED = (1 << 4) + FAILURE_REASONS_FORCE_RENDERER = 1 << 0, + FAILURE_REASONS_FB_FORMAT_INCOMPATIBLE = 1 << 1, + FAILURE_REASONS_DMABUF_MODIFIER_INVALID = 1 << 2, + FAILURE_REASONS_ADD_FB_FAILED = 1 << 3, + FAILURE_REASONS_NO_PLANES_AVAILABLE = 1 << 4, + FAILURE_REASONS_PLANES_REJECTED = 1 << 5, + FAILURE_REASONS_INADEQUATE_CONTENT_PROTECTION = 1 << 6, + FAILURE_REASONS_INCOMPATIBLE_TRANSFORM = 1 << 7, + FAILURE_REASONS_NO_BUFFER = 1 << 8, + FAILURE_REASONS_BUFFER_TYPE = 1 << 9, + FAILURE_REASONS_GLOBAL_ALPHA = 1 << 10, + FAILURE_REASONS_NO_GBM = 1 << 11, + FAILURE_REASONS_GBM_BO_IMPORT_FAILED = 1 << 12, + FAILURE_REASONS_GBM_BO_GET_HANDLE_FAILED = 1 << 13, }; /** @@ -249,15 +260,8 @@ enum actions_needed_dmabuf_feedback { ACTION_NEEDED_REMOVE_SCANOUT_TRANCHE = (1 << 1), }; -struct drm_backend { - struct weston_backend base; - struct weston_compositor *compositor; - - struct udev *udev; - struct wl_event_source *drm_source; - - struct udev_monitor *udev_monitor; - struct wl_event_source *udev_drm_source; +struct drm_device { + struct drm_backend *backend; struct { int id; @@ -265,51 +269,63 @@ struct drm_backend { char *filename; dev_t devnum; } drm; - struct gbm_device *gbm; - struct wl_listener session_listener; - uint32_t gbm_format; - /* we need these parameters in order to not fail drmModeAddFB2() - * due to out of bounds dimensions, and then mistakenly set - * sprites_are_broken: - */ - int min_width, max_width; - int min_height, max_height; + /* drm_crtc::link */ + struct wl_list crtc_list; struct wl_list plane_list; - uint32_t next_plane_idx; - void *repaint_data; + /* drm_writeback::link */ + struct wl_list writeback_connector_list; bool state_invalid; - /* drm_crtc::link */ - struct wl_list crtc_list; + bool atomic_modeset; - /* drm_writeback::link */ - struct wl_list writeback_connector_list; + bool aspect_ratio_supported; + + int32_t cursor_width; + int32_t cursor_height; - bool sprites_are_broken; bool cursors_are_broken; + bool sprites_are_broken; - bool atomic_modeset; + void *repaint_data; + + bool fb_modifiers; + + /* we need these parameters in order to not fail drmModeAddFB2() + * due to out of bounds dimensions, and then mistakenly set + * sprites_are_broken: + */ + int min_width, max_width; + int min_height, max_height; +}; + +struct drm_backend { + struct weston_backend base; + struct weston_compositor *compositor; + + struct udev *udev; + struct wl_event_source *drm_source; + + struct udev_monitor *udev_monitor; + struct wl_event_source *udev_drm_source; + + struct drm_device *drm; + struct gbm_device *gbm; + struct wl_listener session_listener; + uint32_t gbm_format; bool use_pixman; bool use_pixman_shadow; struct udev_input input; - int32_t cursor_width; - int32_t cursor_height; - uint32_t pageflip_timeout; bool shutting_down; - bool aspect_ratio_supported; - - bool fb_modifiers; - struct weston_log_scope *debug; }; @@ -373,7 +389,7 @@ struct drm_edid { * output state will complete and be retired separately. */ struct drm_pending_state { - struct drm_backend *backend; + struct drm_device *device; struct wl_list output_list; }; @@ -396,16 +412,6 @@ struct drm_output_state { struct wl_list plane_list; }; -/** - * An instance of this class is created each time we believe we have a plane - * suitable to be used by a view as a direct scan-out. The list is initialized - * and populated locally. - */ -struct drm_plane_zpos { - struct drm_plane *plane; - struct wl_list link; /**< :candidate_plane_zpos_list */ -}; - /** * Plane state holds the dynamic state for a plane: where it is positioned, * and which buffer it is currently displaying. @@ -461,7 +467,7 @@ struct drm_plane_state { struct drm_plane { struct weston_plane base; - struct drm_backend *backend; + struct drm_device *device; enum wdrm_plane_type type; @@ -483,7 +489,7 @@ struct drm_plane { }; struct drm_connector { - struct drm_backend *backend; + struct drm_device *device; drmModeConnector *conn; uint32_t connector_id; @@ -495,16 +501,15 @@ struct drm_connector { }; struct drm_writeback { - /* drm_backend::writeback_connector_list */ + /* drm_device::writeback_connector_list */ struct wl_list link; - struct drm_backend *backend; + struct drm_device *device; struct drm_connector connector; }; struct drm_head { struct weston_head base; - struct drm_backend *backend; struct drm_connector connector; struct drm_edid edid; @@ -512,13 +517,17 @@ struct drm_head { struct backlight *backlight; drmModeModeInfo inherited_mode; /**< Original mode on the connector */ + uint32_t inherited_max_bpc; /**< Original max_bpc on the connector */ uint32_t inherited_crtc_id; /**< Original CRTC assignment */ + + /* drm_output::disable_head */ + struct wl_list disable_head_link; }; struct drm_crtc { - /* drm_backend::crtc_list */ + /* drm_device::crtc_list */ struct wl_list link; - struct drm_backend *backend; + struct drm_device *device; /* The output driven by the CRTC */ struct drm_output *output; @@ -532,14 +541,18 @@ struct drm_crtc { struct drm_output { struct weston_output base; - struct drm_backend *backend; + struct drm_device *device; struct drm_crtc *crtc; + /* drm_head::disable_head_link */ + struct wl_list disable_head; + bool page_flip_pending; bool atomic_complete_pending; bool destroy_pending; bool disable_pending; bool dpms_off_pending; + bool mode_switch_pending; uint32_t gbm_cursor_handle[2]; struct drm_fb *gbm_cursor_fb[2]; @@ -552,6 +565,11 @@ struct drm_output { uint32_t gbm_format; uint32_t gbm_bo_flags; + uint32_t hdr_output_metadata_blob_id; + uint64_t ackd_color_outcome_serial; + + unsigned max_bpc; + /* Plane being displayed directly on the CRTC */ struct drm_plane *scanout_plane; @@ -572,19 +590,36 @@ struct drm_output { struct wl_event_source *pageflip_timer; bool virtual; + void (*virtual_destroy)(struct weston_output *base); submit_frame_cb virtual_submit_frame; }; +void +drm_head_destroy(struct weston_head *head_base); + static inline struct drm_head * to_drm_head(struct weston_head *base) { + if (base->backend_id != drm_head_destroy) + return NULL; return container_of(base, struct drm_head, base); } +void +drm_output_destroy(struct weston_output *output_base); +void +drm_virtual_output_destroy(struct weston_output *output_base); + static inline struct drm_output * to_drm_output(struct weston_output *base) { + if ( +#ifdef BUILD_DRM_VIRTUAL + base->destroy != drm_virtual_output_destroy && +#endif + base->destroy != drm_output_destroy) + return NULL; return container_of(base, struct drm_output, base); } @@ -617,7 +652,7 @@ drm_output_get_plane_type_name(struct drm_plane *p) } struct drm_crtc * -drm_crtc_find(struct drm_backend *b, uint32_t crtc_id); +drm_crtc_find(struct drm_device *device, uint32_t crtc_id); struct drm_head * drm_head_find_by_connector(struct drm_backend *backend, uint32_t connector_id); @@ -642,7 +677,7 @@ drm_view_transform_supported(struct weston_view *ev, struct weston_output *outpu } int -drm_mode_ensure_blob(struct drm_backend *backend, struct drm_mode *mode); +drm_mode_ensure_blob(struct drm_device *device, struct drm_mode *mode); struct drm_mode * drm_output_choose_mode(struct drm_output *output, @@ -651,7 +686,7 @@ void update_head_from_connector(struct drm_head *head); void -drm_mode_list_destroy(struct drm_backend *backend, struct wl_list *mode_list); +drm_mode_list_destroy(struct drm_device *device, struct wl_list *mode_list); void drm_output_print_modes(struct drm_output *output); @@ -662,7 +697,7 @@ drm_output_set_mode(struct weston_output *base, const char *modeline); void -drm_property_info_populate(struct drm_backend *b, +drm_property_info_populate(struct drm_device *device, const struct drm_property_info *src, struct drm_property_info *info, unsigned int num_infos, @@ -690,7 +725,7 @@ extern const struct drm_property_info connector_props[]; extern const struct drm_property_info crtc_props[]; int -init_kms_caps(struct drm_backend *b); +init_kms_caps(struct drm_device *device); int drm_pending_state_test(struct drm_pending_state *pending_state); @@ -717,15 +752,18 @@ void drm_fb_unref(struct drm_fb *fb); struct drm_fb * -drm_fb_create_dumb(struct drm_backend *b, int width, int height, +drm_fb_create_dumb(struct drm_device *device, int width, int height, uint32_t format); struct drm_fb * -drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, +drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_device *device, bool is_opaque, enum drm_fb_type type); void drm_output_set_cursor_view(struct drm_output *output, struct weston_view *ev); +int +drm_output_ensure_hdr_output_metadata_blob(struct drm_output *output); + #ifdef BUILD_DRM_GBM extern struct drm_fb * drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev, @@ -749,7 +787,7 @@ drm_can_scanout_dmabuf(struct weston_compositor *ec, #endif struct drm_pending_state * -drm_pending_state_alloc(struct drm_backend *backend); +drm_pending_state_alloc(struct drm_device *device); void drm_pending_state_free(struct drm_pending_state *pending_state); struct drm_output_state * @@ -800,7 +838,7 @@ void drm_plane_reset_state(struct drm_plane *plane); void -drm_assign_planes(struct weston_output *output_base, void *repaint_data); +drm_assign_planes(struct weston_output *output_base); bool drm_plane_is_available(struct drm_plane *plane, struct drm_output *output); @@ -837,9 +875,6 @@ drm_output_fini_egl(struct drm_output *output); struct drm_fb * drm_output_render_gl(struct drm_output_state *state, pixman_region32_t *damage); -void -renderer_switch_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, void *data); #else inline static int init_egl(struct drm_backend *b) @@ -864,11 +899,4 @@ drm_output_render_gl(struct drm_output_state *state, pixman_region32_t *damage) { return NULL; } - -inline static void -renderer_switch_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, void *data) -{ - weston_log("Compiled without GBM/EGL support\n"); -} #endif diff --git a/libweston/backend-drm/drm-virtual.c b/libweston/backend-drm/drm-virtual.c index 597e71c1..931cb822 100644 --- a/libweston/backend-drm/drm-virtual.c +++ b/libweston/backend-drm/drm-virtual.c @@ -47,7 +47,7 @@ * CRTC's. Also, as this is a fake CRTC, it will not try to populate props. */ static struct drm_crtc * -drm_virtual_crtc_create(struct drm_backend *b, struct drm_output *output) +drm_virtual_crtc_create(struct drm_device *device, struct drm_output *output) { struct drm_crtc *crtc; @@ -55,7 +55,7 @@ drm_virtual_crtc_create(struct drm_backend *b, struct drm_output *output) if (!crtc) return NULL; - crtc->backend = b; + crtc->device = device; crtc->output = output; crtc->crtc_id = 0; @@ -81,17 +81,32 @@ drm_virtual_crtc_destroy(struct drm_crtc *crtc) free(crtc); } +static uint32_t +get_drm_plane_index_maximum(struct drm_device *device) +{ + uint32_t max = 0; + struct drm_plane *p; + + wl_list_for_each(p, &device->plane_list, link) { + if (p->plane_idx > max) + max = p->plane_idx; + } + + return max; +} + /** * Create a drm_plane for virtual output * * Call drm_virtual_plane_destroy to clean up the plane. * - * @param b DRM compositor backend + * @param device DRM device * @param output Output to create internal plane for */ static struct drm_plane * -drm_virtual_plane_create(struct drm_backend *b, struct drm_output *output) +drm_virtual_plane_create(struct drm_device *device, struct drm_output *output) { + struct drm_backend *b = device->backend; struct drm_plane *plane; struct weston_drm_format *fmt; uint64_t mod; @@ -103,7 +118,7 @@ drm_virtual_plane_create(struct drm_backend *b, struct drm_output *output) } plane->type = WDRM_PLANE_TYPE_PRIMARY; - plane->backend = b; + plane->device = device; plane->state_cur = drm_plane_state_alloc(NULL, plane); plane->state_cur->complete = true; @@ -115,7 +130,7 @@ drm_virtual_plane_create(struct drm_backend *b, struct drm_output *output) /* If output supports linear modifier, we add it to the plane. * Otherwise we add DRM_FORMAT_MOD_INVALID, as explicit modifiers * are not supported. */ - if ((output->gbm_bo_flags & GBM_BO_USE_LINEAR) && b->fb_modifiers) + if ((output->gbm_bo_flags & GBM_BO_USE_LINEAR) && device->fb_modifiers) mod = DRM_FORMAT_MOD_LINEAR; else mod = DRM_FORMAT_MOD_INVALID; @@ -124,7 +139,8 @@ drm_virtual_plane_create(struct drm_backend *b, struct drm_output *output) goto err; weston_plane_init(&plane->base, b->compositor, 0, 0); - wl_list_insert(&b->plane_list, &plane->link); + plane->plane_idx = get_drm_plane_index_maximum(device) + 1; + wl_list_insert(&device->plane_list, &plane->link); return plane; @@ -163,11 +179,10 @@ static int drm_virtual_output_submit_frame(struct drm_output *output, struct drm_fb *fb) { - struct drm_backend *b = to_drm_backend(output->base.compositor); int fd, ret; assert(fb->num_planes == 1); - ret = drmPrimeHandleToFD(b->drm.fd, fb->handles[0], DRM_CLOEXEC, &fd); + ret = drmPrimeHandleToFD(fb->fd, fb->handles[0], DRM_CLOEXEC, &fd); if (ret) { weston_log("drmPrimeHandleFD failed, errno=%d\n", errno); return -1; @@ -185,17 +200,20 @@ drm_virtual_output_submit_frame(struct drm_output *output, static int drm_virtual_output_repaint(struct weston_output *output_base, - pixman_region32_t *damage, - void *repaint_data) + pixman_region32_t *damage) { - struct drm_pending_state *pending_state = repaint_data; struct drm_output_state *state = NULL; struct drm_output *output = to_drm_output(output_base); struct drm_plane *scanout_plane = output->scanout_plane; struct drm_plane_state *scanout_state; + struct drm_pending_state *pending_state; + struct drm_device *device; assert(output->virtual); + device = output->device; + pending_state = device->repaint_data; + if (output->disable_pending || output->destroy_pending) goto err; @@ -242,7 +260,7 @@ drm_virtual_output_deinit(struct weston_output *base) drm_virtual_crtc_destroy(output->crtc); } -static void +void drm_virtual_output_destroy(struct weston_output *base) { struct drm_output *output = to_drm_output(base); @@ -256,6 +274,9 @@ drm_virtual_output_destroy(struct weston_output *base) drm_output_state_free(output->state_cur); + if (output->virtual_destroy) + output->virtual_destroy(base); + free(output); } @@ -263,7 +284,8 @@ static int drm_virtual_output_enable(struct weston_output *output_base) { struct drm_output *output = to_drm_output(output_base); - struct drm_backend *b = to_drm_backend(output_base->compositor); + struct drm_device *device = output->device; + struct drm_backend *b = device->backend; assert(output->virtual); @@ -277,7 +299,7 @@ drm_virtual_output_enable(struct weston_output *output_base) goto err; } - output->scanout_plane = drm_virtual_plane_create(b, output); + output->scanout_plane = drm_virtual_plane_create(device, output); if (!output->scanout_plane) { weston_log("Failed to find primary plane for output %s\n", output->base.name); @@ -320,22 +342,27 @@ drm_virtual_output_disable(struct weston_output *base) } static struct weston_output * -drm_virtual_output_create(struct weston_compositor *c, char *name) +drm_virtual_output_create(struct weston_compositor *c, char *name, + void (*destroy_func)(struct weston_output *)) { struct drm_output *output; struct drm_backend *b = to_drm_backend(c); + /* Always use the main device for virtual outputs */ + struct drm_device *device = b->drm; output = zalloc(sizeof *output); if (!output) return NULL; - output->crtc = drm_virtual_crtc_create(b, output); + output->device = device; + output->crtc = drm_virtual_crtc_create(device, output); if (!output->crtc) { free(output); return NULL; } output->virtual = true; + output->virtual_destroy = destroy_func; output->gbm_bo_flags = GBM_BO_USE_LINEAR | GBM_BO_USE_RENDERING; weston_output_init(&output->base, c, name); @@ -357,7 +384,8 @@ drm_virtual_output_set_gbm_format(struct weston_output *base, const char *gbm_format) { struct drm_output *output = to_drm_output(base); - struct drm_backend *b = to_drm_backend(base->compositor); + struct drm_device *device = output->device; + struct drm_backend *b = device->backend; if (parse_gbm_format(gbm_format, b->gbm_format, &output->gbm_format) == -1) output->gbm_format = b->gbm_format; diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 42787702..52996d50 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -71,6 +71,7 @@ static const char default_seat[] = "seat0"; static void drm_backend_create_faked_zpos(struct drm_backend *b) { + struct drm_device *device = b->drm; struct drm_plane *plane; uint64_t zpos = 0ULL; uint64_t zpos_min_primary; @@ -78,7 +79,7 @@ drm_backend_create_faked_zpos(struct drm_backend *b) uint64_t zpos_min_cursor; zpos_min_primary = zpos; - wl_list_for_each(plane, &b->plane_list, link) { + wl_list_for_each(plane, &device->plane_list, link) { /* if the property is there, bail out sooner */ if (plane->props[WDRM_PLANE_ZPOS].prop_id != 0) return; @@ -89,14 +90,14 @@ drm_backend_create_faked_zpos(struct drm_backend *b) } zpos_min_overlay = zpos; - wl_list_for_each(plane, &b->plane_list, link) { + wl_list_for_each(plane, &device->plane_list, link) { if (plane->type != WDRM_PLANE_TYPE_OVERLAY) continue; zpos++; } zpos_min_cursor = zpos; - wl_list_for_each(plane, &b->plane_list, link) { + wl_list_for_each(plane, &device->plane_list, link) { if (plane->type != WDRM_PLANE_TYPE_CURSOR) continue; zpos++; @@ -105,7 +106,7 @@ drm_backend_create_faked_zpos(struct drm_backend *b) drm_debug(b, "[drm-backend] zpos property not found. " "Using invented immutable zpos values:\n"); /* assume that invented zpos values are immutable */ - wl_list_for_each(plane, &b->plane_list, link) { + wl_list_for_each(plane, &device->plane_list, link) { if (plane->type == WDRM_PLANE_TYPE_PRIMARY) { plane->zpos_min = zpos_min_primary; plane->zpos_max = zpos_min_primary; @@ -163,9 +164,6 @@ drm_output_pageflip_timer_create(struct drm_output *output) return 0; } -static void -drm_output_destroy(struct weston_output *output_base); - /** * Returns true if the plane can be used on the given output for its current * repaint cycle. @@ -192,11 +190,11 @@ drm_plane_is_available(struct drm_plane *plane, struct drm_output *output) } struct drm_crtc * -drm_crtc_find(struct drm_backend *b, uint32_t crtc_id) +drm_crtc_find(struct drm_device *device, uint32_t crtc_id) { struct drm_crtc *crtc; - wl_list_for_each(crtc, &b->crtc_list, link) { + wl_list_for_each(crtc, &device->crtc_list, link) { if (crtc->crtc_id == crtc_id) return crtc; } @@ -213,6 +211,8 @@ drm_head_find_by_connector(struct drm_backend *backend, uint32_t connector_id) wl_list_for_each(base, &backend->compositor->head_list, compositor_link) { head = to_drm_head(base); + if (!head) + continue; if (head->connector.connector_id == connector_id) return head; } @@ -225,7 +225,7 @@ drm_writeback_find_by_connector(struct drm_backend *backend, uint32_t connector_ { struct drm_writeback *writeback; - wl_list_for_each(writeback, &backend->writeback_connector_list, link) { + wl_list_for_each(writeback, &backend->drm->writeback_connector_list, link) { if (writeback->connector.connector_id == connector_id) return writeback; } @@ -259,6 +259,8 @@ drm_output_get_disable_state(struct drm_pending_state *pending_state, return output_state; } +static int +drm_output_apply_mode(struct drm_output *output); /** * Mark a drm_output_state (the output's last state) as complete. This handles @@ -269,7 +271,7 @@ void drm_output_update_complete(struct drm_output *output, uint32_t flags, unsigned int sec, unsigned int usec) { - struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_device *device = output->device; struct drm_plane_state *ps; struct timespec ts; @@ -287,18 +289,24 @@ drm_output_update_complete(struct drm_output *output, uint32_t flags, output->destroy_pending = false; output->disable_pending = false; output->dpms_off_pending = false; + output->mode_switch_pending = false; drm_output_destroy(&output->base); return; } else if (output->disable_pending) { output->disable_pending = false; output->dpms_off_pending = false; + output->mode_switch_pending = false; weston_output_disable(&output->base); return; } else if (output->dpms_off_pending) { - struct drm_pending_state *pending = drm_pending_state_alloc(b); + struct drm_pending_state *pending = drm_pending_state_alloc(device); output->dpms_off_pending = false; + output->mode_switch_pending = false; drm_output_get_disable_state(pending, output); drm_pending_state_apply_sync(pending); + } else if (output->mode_switch_pending) { + output->mode_switch_pending = false; + drm_output_apply_mode(output); } if (output->state_cur->dpms == WESTON_DPMS_OFF && output->base.repaint_status != REPAINT_AWAITING_COMPLETION) { @@ -350,12 +358,13 @@ void drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) { struct drm_output *output = state->output; + struct drm_device *device = output->device; struct weston_compositor *c = output->base.compositor; struct drm_plane_state *scanout_state; struct drm_plane *scanout_plane = output->scanout_plane; struct drm_property_info *damage_info = &scanout_plane->props[WDRM_PLANE_FB_DAMAGE_CLIPS]; - struct drm_backend *b = to_drm_backend(c); + struct drm_backend *b = device->backend; struct drm_fb *fb; pixman_region32_t scanout_damage; pixman_box32_t *rects; @@ -414,27 +423,14 @@ drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) pixman_region32_init(&scanout_damage); pixman_region32_copy(&scanout_damage, damage); - if (output->base.zoom.active) { - pixman_region32_t clip; - - weston_matrix_transform_region(&scanout_damage, - &output->base.matrix, - &scanout_damage); - pixman_region32_init_rect(&clip, 0, 0, - output->base.width, - output->base.height); - pixman_region32_intersect(&scanout_damage, &scanout_damage, &clip); - pixman_region32_fini(&clip); - } else { - pixman_region32_translate(&scanout_damage, - -output->base.x, -output->base.y); - weston_transformed_region(output->base.width, - output->base.height, - output->base.transform, - output->base.current_scale, - &scanout_damage, - &scanout_damage); - } + pixman_region32_translate(&scanout_damage, + -output->base.x, -output->base.y); + weston_transformed_region(output->base.width, + output->base.height, + output->base.transform, + output->base.current_scale, + &scanout_damage, + &scanout_damage); assert(scanout_state->damage_blob_id == 0); @@ -446,7 +442,7 @@ drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) * that it will consider the whole plane damaged. While this may * affect efficiency, it should still produce correct results. */ - drmModeCreatePropertyBlob(b->drm.fd, rects, + drmModeCreatePropertyBlob(device->drm.fd, rects, sizeof(*rects) * n_rects, &scanout_state->damage_blob_id); @@ -454,17 +450,20 @@ drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) } static int -drm_output_repaint(struct weston_output *output_base, - pixman_region32_t *damage, - void *repaint_data) +drm_output_repaint(struct weston_output *output_base, pixman_region32_t *damage) { - struct drm_pending_state *pending_state = repaint_data; struct drm_output *output = to_drm_output(output_base); struct drm_output_state *state = NULL; struct drm_plane_state *scanout_state; + struct drm_pending_state *pending_state; + struct drm_device *device; + assert(output); assert(!output->virtual); + device = output->device; + pending_state = device->repaint_data; + if (output->disable_pending || output->destroy_pending) goto err; @@ -485,6 +484,9 @@ drm_output_repaint(struct weston_output *output_base, else state->protection = WESTON_HDCP_DISABLE; + if (drm_output_ensure_hdr_output_metadata_blob(output) < 0) + goto err; + drm_output_render(state, damage); scanout_state = drm_output_state_get_plane(state, output->scanout_plane); @@ -529,8 +531,9 @@ drm_output_start_repaint_loop(struct weston_output *output_base) struct drm_output *output = to_drm_output(output_base); struct drm_pending_state *pending_state; struct drm_plane *scanout_plane = output->scanout_plane; - struct drm_backend *backend = - to_drm_backend(output_base->compositor); + struct drm_device *device = output->device; + struct drm_backend *backend = device->backend; + struct weston_compositor *compositor = backend->compositor; struct timespec ts, tnow; struct timespec vbl2now; int64_t refresh_nsec; @@ -552,14 +555,14 @@ drm_output_start_repaint_loop(struct weston_output *output_base) /* Need to smash all state in from scratch; current timings might not * be what we want, page flip might not work, etc. */ - if (backend->state_invalid) + if (device->state_invalid) goto finish_frame; assert(scanout_plane->state_cur->output == output); /* Try to get current msc and timestamp via instant query */ vbl.request.type |= drm_waitvblank_pipe(output->crtc); - ret = drmWaitVBlank(backend->drm.fd, &vbl); + ret = drmWaitVBlank(device->drm.fd, &vbl); /* Error ret or zero timestamp means failure to get valid timestamp */ if ((ret == 0) && (vbl.reply.tval_sec > 0 || vbl.reply.tval_usec > 0)) { @@ -570,7 +573,7 @@ drm_output_start_repaint_loop(struct weston_output *output_base) * Stale ts could happen on Linux 3.17+, so make sure it * is not older than 1 refresh duration since now. */ - weston_compositor_read_presentation_clock(backend->compositor, + weston_compositor_read_presentation_clock(compositor, &tnow); timespec_sub(&vbl2now, &tnow, &ts); refresh_nsec = @@ -590,7 +593,7 @@ drm_output_start_repaint_loop(struct weston_output *output_base) assert(!output->page_flip_pending); assert(!output->state_last); - pending_state = drm_pending_state_alloc(backend); + pending_state = drm_pending_state_alloc(device); drm_output_state_duplicate(output->state_cur, pending_state, DRM_OUTPUT_STATE_PRESERVE_PLANES); @@ -598,8 +601,8 @@ drm_output_start_repaint_loop(struct weston_output *output_base) if (ret != 0) { weston_log("applying repaint-start state failed: %s\n", strerror(errno)); - if (ret == -EACCES) - return -1; + if (ret == -EACCES || ret == -EBUSY) + return ret; goto finish_frame; } @@ -619,24 +622,23 @@ finish_frame: * a new pending_state structure to own any output state created by individual * output repaint functions until the repaint is flushed or cancelled. */ -static void * +static void drm_repaint_begin(struct weston_compositor *compositor) { struct drm_backend *b = to_drm_backend(compositor); - struct drm_pending_state *ret; + struct drm_device *device = b->drm; + struct drm_pending_state *pending_state; - ret = drm_pending_state_alloc(b); - b->repaint_data = ret; + pending_state = drm_pending_state_alloc(device); + device->repaint_data = pending_state; if (weston_log_scope_is_enabled(b->debug)) { char *dbg = weston_compositor_print_scene_graph(compositor); drm_debug(b, "[repaint] Beginning repaint; pending_state %p\n", - ret); + device->repaint_data); drm_debug(b, "%s", dbg); free(dbg); } - - return ret; } /** @@ -649,10 +651,11 @@ drm_repaint_begin(struct weston_compositor *compositor) * state will be freed. */ static int -drm_repaint_flush(struct weston_compositor *compositor, void *repaint_data) +drm_repaint_flush(struct weston_compositor *compositor) { struct drm_backend *b = to_drm_backend(compositor); - struct drm_pending_state *pending_state = repaint_data; + struct drm_device *device = b->drm; + struct drm_pending_state *pending_state = device->repaint_data; int ret; ret = drm_pending_state_apply(pending_state); @@ -660,9 +663,9 @@ drm_repaint_flush(struct weston_compositor *compositor, void *repaint_data) weston_log("repaint-flush failed: %s\n", strerror(errno)); drm_debug(b, "[repaint] flushed pending_state %p\n", pending_state); - b->repaint_data = NULL; + device->repaint_data = NULL; - return (ret == -EACCES) ? -1 : 0; + return (ret == -EACCES || ret == -EBUSY) ? ret : 0; } /** @@ -672,14 +675,15 @@ drm_repaint_flush(struct weston_compositor *compositor, void *repaint_data) * held across the repaint cycle should be discarded. */ static void -drm_repaint_cancel(struct weston_compositor *compositor, void *repaint_data) +drm_repaint_cancel(struct weston_compositor *compositor) { struct drm_backend *b = to_drm_backend(compositor); - struct drm_pending_state *pending_state = repaint_data; + struct drm_device *device = b->drm; + struct drm_pending_state *pending_state = device->repaint_data; drm_pending_state_free(pending_state); drm_debug(b, "[repaint] cancel pending_state %p\n", pending_state); - b->repaint_data = NULL; + device->repaint_data = NULL; } static int @@ -691,9 +695,11 @@ static int drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mode) { struct drm_output *output = to_drm_output(output_base); - struct drm_backend *b = to_drm_backend(output_base->compositor); - struct drm_mode *drm_mode = drm_output_choose_mode(output, mode); + struct drm_mode *drm_mode; + + assert(output); + drm_mode = drm_output_choose_mode(output, mode); if (!drm_mode) { weston_log("%s: invalid resolution %dx%d\n", output_base->name, mode->width, mode->height); @@ -709,13 +715,27 @@ drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mo output->base.current_mode->flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; + if (output->page_flip_pending || output->atomic_complete_pending) { + output->mode_switch_pending = true; + return 0; + } + + return drm_output_apply_mode(output); +} + +static int +drm_output_apply_mode(struct drm_output *output) +{ + struct drm_device *device = output->device; + struct drm_backend *b = device->backend; + /* XXX: This drops our current buffer too early, before we've started * displaying it. Ideally this should be much more atomic and * integrated with a full repaint cycle, rather than doing a * sledgehammer modeswitch first, and only later showing new * content. */ - b->state_invalid = true; + device->state_invalid = true; if (b->use_pixman) { drm_output_fini_pixman(output); @@ -754,13 +774,15 @@ init_pixman(struct drm_backend *b) * Call drm_plane_destroy to clean up the plane. * * @sa drm_output_find_special_plane - * @param b DRM compositor backend + * @param device DRM device * @param kplane DRM plane to create */ static struct drm_plane * -drm_plane_create(struct drm_backend *b, const drmModePlane *kplane) +drm_plane_create(struct drm_device *device, const drmModePlane *kplane) { - struct drm_plane *plane; + struct drm_backend *b = device->backend; + struct weston_compositor *compositor = b->compositor; + struct drm_plane *plane, *tmp; drmModeObjectProperties *props; uint64_t *zpos_range_values; @@ -770,8 +792,7 @@ drm_plane_create(struct drm_backend *b, const drmModePlane *kplane) return NULL; } - plane->backend = b; - plane->plane_idx = b->next_plane_idx++; + plane->device = device; plane->state_cur = drm_plane_state_alloc(NULL, plane); plane->state_cur->complete = true; plane->possible_crtcs = kplane->possible_crtcs; @@ -779,14 +800,14 @@ drm_plane_create(struct drm_backend *b, const drmModePlane *kplane) weston_drm_format_array_init(&plane->formats); - props = drmModeObjectGetProperties(b->drm.fd, kplane->plane_id, + props = drmModeObjectGetProperties(device->drm.fd, kplane->plane_id, DRM_MODE_OBJECT_PLANE); if (!props) { weston_log("couldn't get plane properties\n"); goto err; } - drm_property_info_populate(b, plane_props, plane->props, + drm_property_info_populate(device, plane_props, plane->props, WDRM_PLANE__COUNT, props); plane->type = drm_property_get_value(&plane->props[WDRM_PLANE_TYPE], @@ -806,7 +827,7 @@ drm_plane_create(struct drm_backend *b, const drmModePlane *kplane) } if (drm_plane_populate_formats(plane, kplane, props, - b->fb_modifiers) < 0) { + device->fb_modifiers) < 0) { drmModeFreeObjectProperties(props); goto err; } @@ -816,8 +837,16 @@ drm_plane_create(struct drm_backend *b, const drmModePlane *kplane) if (plane->type == WDRM_PLANE_TYPE__COUNT) goto err_props; - weston_plane_init(&plane->base, b->compositor, 0, 0); - wl_list_insert(&b->plane_list, &plane->link); + weston_plane_init(&plane->base, compositor, 0, 0); + + wl_list_for_each(tmp, &device->plane_list, link) { + if (tmp->zpos_max < plane->zpos_max) { + wl_list_insert(tmp->link.prev, &plane->link); + break; + } + } + if (plane->link.next == NULL) + wl_list_insert(device->plane_list.prev, &plane->link); return plane; @@ -833,18 +862,20 @@ err: /** * Find, or create, a special-purpose plane * - * @param b DRM backend + * @param device DRM device * @param output Output to use for plane * @param type Type of plane */ static struct drm_plane * -drm_output_find_special_plane(struct drm_backend *b, struct drm_output *output, +drm_output_find_special_plane(struct drm_device *device, + struct drm_output *output, enum wdrm_plane_type type) { + struct drm_backend *b = device->backend; struct drm_plane *plane; - wl_list_for_each(plane, &b->plane_list, link) { - struct drm_output *tmp; + wl_list_for_each(plane, &device->plane_list, link) { + struct weston_output *base; bool found_elsewhere = false; if (plane->type != type) @@ -855,7 +886,11 @@ drm_output_find_special_plane(struct drm_backend *b, struct drm_output *output, /* On some platforms, primary/cursor planes can roam * between different CRTCs, so make sure we don't claim the * same plane for two outputs. */ - wl_list_for_each(tmp, &b->compositor->output_list, base.link) { + wl_list_for_each(base, &b->compositor->output_list, link) { + struct drm_output *tmp = to_drm_output(base); + if (!tmp) + continue; + if (tmp->cursor_plane == plane || tmp->scanout_plane == plane) { found_elsewhere = true; @@ -884,8 +919,10 @@ drm_output_find_special_plane(struct drm_backend *b, struct drm_output *output, static void drm_plane_destroy(struct drm_plane *plane) { + struct drm_device *device = plane->device; + if (plane->type == WDRM_PLANE_TYPE_OVERLAY) - drmModeSetPlane(plane->backend->drm.fd, plane->plane_id, + drmModeSetPlane(device->drm.fd, plane->plane_id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); drm_plane_state_free(plane->state_cur, true); drm_property_info_free(plane->props, WDRM_PLANE__COUNT); @@ -902,16 +939,19 @@ drm_plane_destroy(struct drm_plane *plane) * * Call destroy_sprites to free these planes. * - * @param b DRM compositor backend + * @param device DRM device */ static void -create_sprites(struct drm_backend *b) +create_sprites(struct drm_device *device) { + struct drm_backend *b = device->backend; drmModePlaneRes *kplane_res; drmModePlane *kplane; struct drm_plane *drm_plane; uint32_t i; - kplane_res = drmModeGetPlaneResources(b->drm.fd); + uint32_t next_plane_idx = 0; + kplane_res = drmModeGetPlaneResources(device->drm.fd); + if (!kplane_res) { weston_log("failed to get plane resources: %s\n", strerror(errno)); @@ -919,11 +959,11 @@ create_sprites(struct drm_backend *b) } for (i = 0; i < kplane_res->count_planes; i++) { - kplane = drmModeGetPlane(b->drm.fd, kplane_res->planes[i]); + kplane = drmModeGetPlane(device->drm.fd, kplane_res->planes[i]); if (!kplane) continue; - drm_plane = drm_plane_create(b, kplane); + drm_plane = drm_plane_create(device, kplane); drmModeFreePlane(kplane); if (!drm_plane) continue; @@ -934,6 +974,9 @@ create_sprites(struct drm_backend *b) &b->compositor->primary_plane); } + wl_list_for_each (drm_plane, &device->plane_list, link) + drm_plane->plane_idx = next_plane_idx++; + drmModeFreePlaneResources(kplane_res); } @@ -942,14 +985,14 @@ create_sprites(struct drm_backend *b) * * The counterpart to create_sprites. * - * @param b DRM compositor backend + * @param device DRM device */ static void -destroy_sprites(struct drm_backend *b) +destroy_sprites(struct drm_device *device) { struct drm_plane *plane, *next; - wl_list_for_each_safe(plane, next, &b->plane_list, link) + wl_list_for_each_safe(plane, next, &device->plane_list, link) drm_plane_destroy(plane); } @@ -1032,11 +1075,12 @@ static void drm_set_dpms(struct weston_output *output_base, enum dpms_enum level) { struct drm_output *output = to_drm_output(output_base); - struct drm_backend *b = to_drm_backend(output_base->compositor); - struct drm_pending_state *pending_state = b->repaint_data; + struct drm_device *device = output->device; + struct drm_pending_state *pending_state = device->repaint_data; struct drm_output_state *state; int ret; + assert(output); assert(!output->virtual); if (output->state_cur->dpms == level) @@ -1081,7 +1125,7 @@ drm_set_dpms(struct weston_output *output_base, enum dpms_enum level) return; } - pending_state = drm_pending_state_alloc(b); + pending_state = drm_pending_state_alloc(device); drm_output_get_disable_state(pending_state, output); ret = drm_pending_state_apply_sync(pending_state); if (ret != 0) @@ -1140,6 +1184,7 @@ make_connector_name(const drmModeConnector *con) static int drm_output_init_pixman(struct drm_output *output, struct drm_backend *b) { + struct drm_device *device = output->device; int w = output->base.current_mode->width; int h = output->base.current_mode->height; uint32_t format = output->gbm_format; @@ -1163,7 +1208,7 @@ drm_output_init_pixman(struct drm_output *output, struct drm_backend *b) /* FIXME error checking */ for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { - output->dumb[i] = drm_fb_create_dumb(b, w, h, format); + output->dumb[i] = drm_fb_create_dumb(device, w, h, format); if (!output->dumb[i]) goto err; @@ -1253,10 +1298,15 @@ drm_output_attach_head(struct weston_output *output_base, struct weston_head *head_base) { struct drm_backend *b = to_drm_backend(output_base->compositor); + struct drm_device *device = b->drm; + struct drm_head *head = to_drm_head(head_base); if (wl_list_length(&output_base->head_list) >= MAX_CLONED_CONNECTORS) return -1; + wl_list_remove(&head->disable_head_link); + wl_list_init(&head->disable_head_link); + if (!output_base->enabled) return 0; @@ -1269,7 +1319,7 @@ drm_output_attach_head(struct weston_output *output_base, /* XXX: Doing it globally, what guarantees another output's update * will not clear the flag before this output is updated? */ - b->state_invalid = true; + device->state_invalid = true; weston_output_schedule_repaint(output_base); @@ -1280,17 +1330,14 @@ static void drm_output_detach_head(struct weston_output *output_base, struct weston_head *head_base) { - struct drm_backend *b = to_drm_backend(output_base->compositor); + struct drm_output *output = to_drm_output(output_base); + struct drm_head *head = to_drm_head(head_base); if (!output_base->enabled) return; - /* Need to go through modeset to drop connectors that should no longer - * be driven. */ - /* XXX: Ideally we'd do this per-output, not globally. */ - b->state_invalid = true; - - weston_output_schedule_repaint(output_base); + /* Drop connectors that should no longer be driven on next repaint. */ + wl_list_insert(&output->disable_head, &head->disable_head_link); } int @@ -1318,9 +1365,9 @@ parse_gbm_format(const char *s, uint32_t default_value, uint32_t *gbm_format) } static int -drm_head_read_current_setup(struct drm_head *head, struct drm_backend *backend) +drm_head_read_current_setup(struct drm_head *head, struct drm_device *device) { - int drm_fd = backend->drm.fd; + int drm_fd = device->drm.fd; drmModeConnector *conn = head->connector.conn; drmModeEncoder *encoder; drmModeCrtc *crtc; @@ -1341,6 +1388,12 @@ drm_head_read_current_setup(struct drm_head *head, struct drm_backend *backend) drmModeFreeCrtc(crtc); } + /* Get the current max_bpc that's currently configured to + * this connector. */ + head->inherited_max_bpc = drm_property_get_value( + &head->connector.props[WDRM_CONNECTOR_MAX_BPC], + head->connector.props_drm, 0); + return 0; } @@ -1349,10 +1402,10 @@ drm_output_set_gbm_format(struct weston_output *base, const char *gbm_format) { struct drm_output *output = to_drm_output(base); - struct drm_backend *b = to_drm_backend(base->compositor); - if (parse_gbm_format(gbm_format, b->gbm_format, &output->gbm_format) == -1) - output->gbm_format = b->gbm_format; + if (parse_gbm_format(gbm_format, + DRM_FORMAT_INVALID, &output->gbm_format) == -1) + output->gbm_format = DRM_FORMAT_INVALID; } static void @@ -1366,15 +1419,26 @@ drm_output_set_seat(struct weston_output *base, seat ? seat : ""); } +static void +drm_output_set_max_bpc(struct weston_output *base, unsigned max_bpc) +{ + struct drm_output *output = to_drm_output(base); + + assert(output); + assert(!output->base.enabled); + + output->max_bpc = max_bpc; +} + static int drm_output_init_gamma_size(struct drm_output *output) { - struct drm_backend *backend = to_drm_backend(output->base.compositor); + struct drm_device *device = output->device; drmModeCrtc *crtc; assert(output->base.compositor); assert(output->crtc); - crtc = drmModeGetCrtc(backend->drm.fd, output->crtc->crtc_id); + crtc = drmModeGetCrtc(device->drm.fd, output->crtc->crtc_id); if (!crtc) return -1; @@ -1388,13 +1452,14 @@ drm_output_init_gamma_size(struct drm_output *output) static uint32_t drm_connector_get_possible_crtcs_mask(struct drm_connector *connector) { + struct drm_device *device = connector->device; uint32_t possible_crtcs = 0; drmModeConnector *conn = connector->conn; drmModeEncoder *encoder; int i; for (i = 0; i < conn->count_encoders; i++) { - encoder = drmModeGetEncoder(connector->backend->drm.fd, + encoder = drmModeGetEncoder(device->drm.fd, conn->encoders[i]); if (!encoder) continue; @@ -1414,7 +1479,9 @@ drm_connector_get_possible_crtcs_mask(struct drm_connector *connector) static struct drm_crtc * drm_output_pick_crtc(struct drm_output *output) { - struct drm_backend *backend; + struct drm_device *device = output->device; + struct drm_backend *backend = device->backend; + struct weston_compositor *compositor = backend->compositor; struct weston_head *base; struct drm_head *head; struct drm_crtc *crtc; @@ -1427,8 +1494,6 @@ drm_output_pick_crtc(struct drm_output *output) unsigned int i; bool match; - backend = to_drm_backend(output->base.compositor); - /* This algorithm ignores drmModeEncoder::possible_clones restriction, * because it is more often set wrong than not in the kernel. */ @@ -1441,12 +1506,12 @@ drm_output_pick_crtc(struct drm_output *output) crtc_id = head->inherited_crtc_id; if (crtc_id > 0 && n < ARRAY_LENGTH(existing_crtc)) - existing_crtc[n++] = drm_crtc_find(backend, crtc_id); + existing_crtc[n++] = drm_crtc_find(device, crtc_id); } /* Find a crtc that could drive each connector individually at least, * and prefer existing routings. */ - wl_list_for_each(crtc, &backend->crtc_list, link) { + wl_list_for_each(crtc, &device->crtc_list, link) { /* Could the crtc not drive each connector? */ if (!(possible_crtcs & (1 << crtc->pipe))) @@ -1469,9 +1534,10 @@ drm_output_pick_crtc(struct drm_output *output) * If they did, this is not the best CRTC as it might be needed * for another output we haven't enabled yet. */ match = false; - wl_list_for_each(base, &backend->compositor->head_list, - compositor_link) { + wl_list_for_each(base, &compositor->head_list, compositor_link) { head = to_drm_head(base); + if (!head) + continue; if (head->base.output == &output->base) continue; @@ -1509,7 +1575,7 @@ drm_output_pick_crtc(struct drm_output *output) } /* Otherwise pick any available crtc. */ - wl_list_for_each(crtc, &backend->crtc_list, link) { + wl_list_for_each(crtc, &device->crtc_list, link) { if (!crtc->output) return crtc; } @@ -1521,12 +1587,12 @@ drm_output_pick_crtc(struct drm_output *output) * all, it adds the object to the DRM-backend CRTC list. */ static struct drm_crtc * -drm_crtc_create(struct drm_backend *b, uint32_t crtc_id, uint32_t pipe) +drm_crtc_create(struct drm_device *device, uint32_t crtc_id, uint32_t pipe) { struct drm_crtc *crtc; drmModeObjectPropertiesPtr props; - props = drmModeObjectGetProperties(b->drm.fd, crtc_id, + props = drmModeObjectGetProperties(device->drm.fd, crtc_id, DRM_MODE_OBJECT_CRTC); if (!props) { weston_log("failed to get CRTC properties\n"); @@ -1537,15 +1603,15 @@ drm_crtc_create(struct drm_backend *b, uint32_t crtc_id, uint32_t pipe) if (!crtc) goto ret; - drm_property_info_populate(b, crtc_props, crtc->props_crtc, + drm_property_info_populate(device, crtc_props, crtc->props_crtc, WDRM_CRTC__COUNT, props); - crtc->backend = b; + crtc->device = device; crtc->crtc_id = crtc_id; crtc->pipe = pipe; crtc->output = NULL; /* Add it to the last position of the DRM-backend CRTC list */ - wl_list_insert(b->crtc_list.prev, &crtc->link); + wl_list_insert(device->crtc_list.prev, &crtc->link); ret: drmModeFreeObjectProperties(props); @@ -1576,12 +1642,12 @@ drm_crtc_destroy(struct drm_crtc *crtc) * The CRTCs are saved in a list of the drm_backend and will keep there until * the fd gets closed. * - * @param b The DRM-backend structure. + * @param device The DRM device structure. * @param resources The DRM resources, it is taken with drmModeGetResources * @return 0 on success (at least one CRTC in the list), -1 on failure. */ static int -drm_backend_create_crtc_list(struct drm_backend *b, drmModeRes *resources) +drm_backend_create_crtc_list(struct drm_device *device, drmModeRes *resources) { struct drm_crtc *crtc, *crtc_tmp; int i; @@ -1590,7 +1656,7 @@ drm_backend_create_crtc_list(struct drm_backend *b, drmModeRes *resources) for (i = 0; i < resources->count_crtcs; i++) { /* Let's create an object for the CRTC and add it to the list */ - crtc = drm_crtc_create(b, resources->crtcs[i], i); + crtc = drm_crtc_create(device, resources->crtcs[i], i); if (!crtc) goto err; } @@ -1598,7 +1664,7 @@ drm_backend_create_crtc_list(struct drm_backend *b, drmModeRes *resources) return 0; err: - wl_list_for_each_safe(crtc, crtc_tmp, &b->crtc_list, link) + wl_list_for_each_safe(crtc, crtc_tmp, &device->crtc_list, link) drm_crtc_destroy(crtc); return -1; } @@ -1611,9 +1677,10 @@ static int drm_output_init_planes(struct drm_output *output) { struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_device *device = output->device; output->scanout_plane = - drm_output_find_special_plane(b, output, + drm_output_find_special_plane(device, output, WDRM_PLANE_TYPE_PRIMARY); if (!output->scanout_plane) { weston_log("Failed to find primary plane for output %s\n", @@ -1628,7 +1695,7 @@ drm_output_init_planes(struct drm_output *output) /* Failing to find a cursor plane is not fatal, as we'll fall back * to software cursor. */ output->cursor_plane = - drm_output_find_special_plane(b, output, + drm_output_find_special_plane(device, output, WDRM_PLANE_TYPE_CURSOR); if (output->cursor_plane) @@ -1636,7 +1703,7 @@ drm_output_init_planes(struct drm_output *output) &output->cursor_plane->base, NULL); else - b->cursors_are_broken = true; + device->cursors_are_broken = true; return 0; } @@ -1648,6 +1715,7 @@ static void drm_output_deinit_planes(struct drm_output *output) { struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_device *device = output->device; /* If the compositor is already shutting down, the planes have already * been destroyed. */ @@ -1659,7 +1727,7 @@ drm_output_deinit_planes(struct drm_output *output) wl_list_remove(&output->cursor_plane->base.link); wl_list_init(&output->cursor_plane->base.link); /* Turn off hardware cursor */ - drmModeSetCursor(b->drm.fd, output->crtc->crtc_id, 0, 0, 0); + drmModeSetCursor(device->drm.fd, output->crtc->crtc_id, 0, 0, 0); } /* With universal planes, the planes are allocated at startup, @@ -1678,9 +1746,9 @@ drm_output_deinit_planes(struct drm_output *output) } static struct weston_drm_format_array * -get_scanout_formats(struct drm_backend *b) +get_scanout_formats(struct drm_device *device) { - struct weston_compositor *ec = b->compositor; + struct weston_compositor *ec = device->backend->compositor; const struct weston_drm_format_array *renderer_formats; struct weston_drm_format_array *scanout_formats, union_planes_formats; struct drm_plane *plane; @@ -1701,7 +1769,7 @@ get_scanout_formats(struct drm_backend *b) weston_drm_format_array_init(scanout_formats); /* Compute the union of the format/modifiers of the KMS planes */ - wl_list_for_each(plane, &b->plane_list, link) { + wl_list_for_each(plane, &device->plane_list, link) { /* The scanout formats are used by the dma-buf feedback. But for * now cursor planes do not support dma-buf buffers, only wl_shm * buffers. So we skip cursor planes here. */ @@ -1769,25 +1837,30 @@ drm_output_attach_crtc(struct drm_output *output) static void drm_output_detach_crtc(struct drm_output *output) { - struct drm_backend *b = output->backend; struct drm_crtc *crtc = output->crtc; crtc->output = NULL; output->crtc = NULL; - - /* Force resetting unused CRTCs */ - b->state_invalid = true; } static int drm_output_enable(struct weston_output *base) { struct drm_output *output = to_drm_output(base); - struct drm_backend *b = to_drm_backend(base->compositor); + struct drm_device *device = output->device; + struct drm_backend *b = device->backend; int ret; + assert(output); assert(!output->virtual); + if (output->gbm_format == DRM_FORMAT_INVALID) { + if (output->base.eotf_mode != WESTON_EOTF_MODE_SDR) + output->gbm_format = DRM_FORMAT_XRGB2101010; + else + output->gbm_format = b->gbm_format; + } + ret = drm_output_attach_crtc(output); if (ret < 0) return -1; @@ -1839,6 +1912,14 @@ drm_output_deinit(struct weston_output *base) { struct drm_output *output = to_drm_output(base); struct drm_backend *b = to_drm_backend(base->compositor); + struct drm_device *device = b->drm; + struct drm_pending_state *pending; + + if (!b->shutting_down) { + pending = drm_pending_state_alloc(device); + drm_output_get_disable_state(pending, output); + drm_pending_state_apply_sync(pending); + } if (b->use_pixman) drm_output_fini_pixman(output); @@ -1847,17 +1928,21 @@ drm_output_deinit(struct weston_output *base) drm_output_deinit_planes(output); drm_output_detach_crtc(output); -} -static void -drm_head_destroy(struct drm_head *head); + if (output->hdr_output_metadata_blob_id) { + drmModeDestroyPropertyBlob(device->drm.fd, + output->hdr_output_metadata_blob_id); + output->hdr_output_metadata_blob_id = 0; + } +} -static void +void drm_output_destroy(struct weston_output *base) { struct drm_output *output = to_drm_output(base); - struct drm_backend *b = to_drm_backend(base->compositor); + struct drm_device *device = output->device; + assert(output); assert(!output->virtual); if (output->page_flip_pending || output->atomic_complete_pending) { @@ -1871,7 +1956,7 @@ drm_output_destroy(struct weston_output *base) if (output->base.enabled) drm_output_deinit(&output->base); - drm_mode_list_destroy(b, &output->base.mode_list); + drm_mode_list_destroy(device, &output->base.mode_list); if (output->pageflip_timer) wl_event_source_remove(output->pageflip_timer); @@ -1881,6 +1966,8 @@ drm_output_destroy(struct weston_output *base) assert(!output->state_last); drm_output_state_free(output->state_cur); + assert(output->hdr_output_metadata_blob_id == 0); + free(output); } @@ -1889,6 +1976,7 @@ drm_output_disable(struct weston_output *base) { struct drm_output *output = to_drm_output(base); + assert(output); assert(!output->virtual); if (output->page_flip_pending || output->atomic_complete_pending) { @@ -1987,9 +2075,10 @@ drm_head_get_current_protection(struct drm_head *head) static int drm_connector_update_properties(struct drm_connector *connector) { + struct drm_device *device = connector->device; drmModeObjectProperties *props; - props = drmModeObjectGetProperties(connector->backend->drm.fd, + props = drmModeObjectGetProperties(device->drm.fd, connector->connector_id, DRM_MODE_OBJECT_CONNECTOR); if (!props) { @@ -2017,6 +2106,8 @@ static int drm_connector_assign_connector_info(struct drm_connector *connector, drmModeConnector *conn) { + struct drm_device *device = connector->device; + assert(connector->conn != conn); assert(connector->connector_id == conn->connector_id); @@ -2028,17 +2119,16 @@ drm_connector_assign_connector_info(struct drm_connector *connector, connector->conn = conn; drm_property_info_free(connector->props, WDRM_CONNECTOR__COUNT); - drm_property_info_populate(connector->backend, connector_props, - connector->props, + drm_property_info_populate(device, connector_props, connector->props, WDRM_CONNECTOR__COUNT, connector->props_drm); return 0; } static void -drm_connector_init(struct drm_backend *b, struct drm_connector *connector, +drm_connector_init(struct drm_device *device, struct drm_connector *connector, uint32_t connector_id) { - connector->backend = b; + connector->device = device; connector->connector_id = connector_id; connector->conn = NULL; connector->props_drm = NULL; @@ -2055,12 +2145,21 @@ drm_connector_fini(struct drm_connector *connector) static void drm_head_log_info(struct drm_head *head, const char *msg) { + char *eotf_list; + if (head->base.connected) { weston_log("DRM: head '%s' %s, connector %d is connected, " "EDID make '%s', model '%s', serial '%s'\n", head->base.name, msg, head->connector.connector_id, head->base.make, head->base.model, head->base.serial_number ?: ""); + eotf_list = weston_eotf_mask_to_str(head->base.supported_eotf_mask); + if (eotf_list) { + weston_log_continue(STAMP_SPACE + "Supported EOTF modes: %s\n", + eotf_list); + } + free(eotf_list); } else { weston_log("DRM: head '%s' %s, connector %d is disconnected.\n", head->base.name, msg, head->connector.connector_id); @@ -2119,7 +2218,7 @@ drm_writeback_update_info(struct drm_writeback *writeback, drmModeConnector *con * Given a DRM connector, create a matching drm_head structure and add it * to Weston's head list. * - * @param backend Weston backend structure + * @param device DRM device structure * @param conn DRM connector object * @param drm_device udev device pointer * @returns 0 on success, -1 on failure @@ -2127,9 +2226,10 @@ drm_writeback_update_info(struct drm_writeback *writeback, drmModeConnector *con * Takes ownership of @c connector on success, not on failure. */ static int -drm_head_create(struct drm_backend *backend, drmModeConnector *conn, +drm_head_create(struct drm_device *device, drmModeConnector *conn, struct udev_device *drm_device) { + struct drm_backend *backend = device->backend; struct drm_head *head; char *name; int ret; @@ -2138,9 +2238,7 @@ drm_head_create(struct drm_backend *backend, drmModeConnector *conn, if (!head) return -1; - head->backend = backend; - - drm_connector_init(backend, &head->connector, conn->connector_id); + drm_connector_init(device, &head->connector, conn->connector_id); name = make_connector_name(conn); if (!name) @@ -2149,6 +2247,10 @@ drm_head_create(struct drm_backend *backend, drmModeConnector *conn, weston_head_init(&head->base, name); free(name); + head->base.backend_id = drm_head_destroy; + + wl_list_init(&head->disable_head_link); + ret = drm_head_update_info(head, conn); if (ret < 0) goto err_update; @@ -2159,7 +2261,7 @@ drm_head_create(struct drm_backend *backend, drmModeConnector *conn, conn->connector_type == DRM_MODE_CONNECTOR_eDP) weston_head_set_internal(&head->base); - if (drm_head_read_current_setup(head, backend) < 0) { + if (drm_head_read_current_setup(head, device) < 0) { weston_log("Failed to retrieve current mode from connector %d.\n", head->connector.connector_id); /* Not fatal. */ @@ -2178,9 +2280,13 @@ err: return -1; } -static void -drm_head_destroy(struct drm_head *head) +void +drm_head_destroy(struct weston_head *base) { + struct drm_head *head = to_drm_head(base); + + assert(head); + weston_head_release(&head->base); drm_connector_fini(&head->connector); @@ -2208,15 +2314,20 @@ static struct weston_output * drm_output_create(struct weston_compositor *compositor, const char *name) { struct drm_backend *b = to_drm_backend(compositor); + struct drm_device *device = b->drm; struct drm_output *output; output = zalloc(sizeof *output); if (output == NULL) return NULL; - output->backend = b; + output->device = device; output->crtc = NULL; + wl_list_init(&output->disable_head); + + output->max_bpc = 16; + output->gbm_format = DRM_FORMAT_INVALID; #ifdef BUILD_DRM_GBM output->gbm_bo_flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING; #endif @@ -2245,14 +2356,14 @@ drm_output_create(struct weston_compositor *compositor, const char *name) * Given a DRM connector of type writeback, create a matching drm_writeback * structure and add it to Weston's writeback list. * - * @param b Weston backend structure + * @param device DRM device structure * @param conn DRM connector object of type writeback * @returns 0 on success, -1 on failure * * Takes ownership of @c connector on success, not on failure. */ static int -drm_writeback_create(struct drm_backend *b, drmModeConnector *conn) +drm_writeback_create(struct drm_device *device, drmModeConnector *conn) { struct drm_writeback *writeback; int ret; @@ -2260,15 +2371,15 @@ drm_writeback_create(struct drm_backend *b, drmModeConnector *conn) writeback = zalloc(sizeof *writeback); assert(writeback); - writeback->backend = b; + writeback->device = device; - drm_connector_init(b, &writeback->connector, conn->connector_id); + drm_connector_init(device, &writeback->connector, conn->connector_id); ret = drm_writeback_update_info(writeback, conn); if (ret < 0) goto err; - wl_list_insert(&b->writeback_connector_list, &writeback->link); + wl_list_insert(&device->writeback_connector_list, &writeback->link); return 0; err: @@ -2291,24 +2402,24 @@ drm_writeback_destroy(struct drm_writeback *writeback) * * The object is then added to the DRM-backend list of heads or writebacks. * - * @param b The DRM-backend structure + * @param device The DRM device structure * @param conn The DRM connector object * @param drm_device udev device pointer * @return 0 on success, -1 on failure */ static int -drm_backend_add_connector(struct drm_backend *b, drmModeConnector *conn, +drm_backend_add_connector(struct drm_device *device, drmModeConnector *conn, struct udev_device *drm_device) { int ret; if (conn->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) { - ret = drm_writeback_create(b, conn); + ret = drm_writeback_create(device, conn); if (ret < 0) weston_log("DRM: failed to create writeback for connector %d.\n", conn->connector_id); } else { - ret = drm_head_create(b, conn, drm_device); + ret = drm_head_create(device, conn, drm_device); if (ret < 0) weston_log("DRM: failed to create head for connector %d.\n", conn->connector_id); @@ -2322,31 +2433,32 @@ drm_backend_add_connector(struct drm_backend *b, drmModeConnector *conn, * * These objects are added to the DRM-backend lists of heads and writebacks. * - * @param b The DRM-backend structure + * @param device The DRM device structure * @param drm_device udev device pointer * @param resources The DRM resources, it is taken with drmModeGetResources * @return 0 on success, -1 on failure */ static int -drm_backend_discover_connectors(struct drm_backend *b, struct udev_device *drm_device, +drm_backend_discover_connectors(struct drm_device *device, + struct udev_device *drm_device, drmModeRes *resources) { drmModeConnector *conn; int i, ret; - b->min_width = resources->min_width; - b->max_width = resources->max_width; - b->min_height = resources->min_height; - b->max_height = resources->max_height; + device->min_width = resources->min_width; + device->max_width = resources->max_width; + device->min_height = resources->min_height; + device->max_height = resources->max_height; for (i = 0; i < resources->count_connectors; i++) { uint32_t connector_id = resources->connectors[i]; - conn = drmModeGetConnector(b->drm.fd, connector_id); + conn = drmModeGetConnector(device->drm.fd, connector_id); if (!conn) continue; - ret = drm_backend_add_connector(b, conn, drm_device); + ret = drm_backend_add_connector(device, conn, drm_device); if (ret < 0) drmModeFreeConnector(conn); } @@ -2366,8 +2478,10 @@ resources_has_connector(drmModeRes *resources, uint32_t connector_id) } static void -drm_backend_update_connectors(struct drm_backend *b, struct udev_device *drm_device) +drm_backend_update_connectors(struct drm_device *device, + struct udev_device *drm_device) { + struct drm_backend *b = device->backend; drmModeRes *resources; drmModeConnector *conn; struct weston_head *base, *base_next; @@ -2376,7 +2490,7 @@ drm_backend_update_connectors(struct drm_backend *b, struct udev_device *drm_dev uint32_t connector_id; int i, ret; - resources = drmModeGetResources(b->drm.fd); + resources = drmModeGetResources(device->drm.fd); if (!resources) { weston_log("drmModeGetResources failed\n"); return; @@ -2386,7 +2500,7 @@ drm_backend_update_connectors(struct drm_backend *b, struct udev_device *drm_dev for (i = 0; i < resources->count_connectors; i++) { connector_id = resources->connectors[i]; - conn = drmModeGetConnector(b->drm.fd, connector_id); + conn = drmModeGetConnector(device->drm.fd, connector_id); if (!conn) continue; @@ -2402,7 +2516,7 @@ drm_backend_update_connectors(struct drm_backend *b, struct udev_device *drm_dev else if (writeback) ret = drm_writeback_update_info(writeback, conn); else - ret = drm_backend_add_connector(b, conn, drm_device); + ret = drm_backend_add_connector(b->drm, conn, drm_device); if (ret < 0) drmModeFreeConnector(conn); @@ -2413,20 +2527,25 @@ drm_backend_update_connectors(struct drm_backend *b, struct udev_device *drm_dev wl_list_for_each_safe(base, base_next, &b->compositor->head_list, compositor_link) { head = to_drm_head(base); + if (!head) + continue; connector_id = head->connector.connector_id; + if (head->connector.device != device) + continue; + if (resources_has_connector(resources, connector_id)) continue; weston_log("DRM: head '%s' (connector %d) disappeared.\n", head->base.name, connector_id); - drm_head_destroy(head); + drm_head_destroy(base); } /* Destroy writeback objects of writeback connectors that have * disappeared. */ wl_list_for_each_safe(writeback, writeback_next, - &b->writeback_connector_list, link) { + &b->drm->writeback_connector_list, link) { connector_id = writeback->connector.connector_id; if (resources_has_connector(resources, connector_id)) @@ -2487,16 +2606,16 @@ drm_backend_update_conn_props(struct drm_backend *b, } static int -udev_event_is_hotplug(struct drm_backend *b, struct udev_device *device) +udev_event_is_hotplug(struct drm_device *device, struct udev_device *udev_device) { const char *sysnum; const char *val; - sysnum = udev_device_get_sysnum(device); - if (!sysnum || atoi(sysnum) != b->drm.id) + sysnum = udev_device_get_sysnum(udev_device); + if (!sysnum || atoi(sysnum) != device->drm.id) return 0; - val = udev_device_get_property_value(device, "HOTPLUG"); + val = udev_device_get_property_value(udev_device, "HOTPLUG"); if (!val) return 0; @@ -2505,7 +2624,7 @@ udev_event_is_hotplug(struct drm_backend *b, struct udev_device *device) static int udev_event_is_conn_prop_change(struct drm_backend *b, - struct udev_device *device, + struct udev_device *udev_device, uint32_t *connector_id, uint32_t *property_id) @@ -2513,13 +2632,13 @@ udev_event_is_conn_prop_change(struct drm_backend *b, const char *val; int id; - val = udev_device_get_property_value(device, "CONNECTOR"); + val = udev_device_get_property_value(udev_device, "CONNECTOR"); if (!val || !safe_strtoint(val, &id)) return 0; else *connector_id = id; - val = udev_device_get_property_value(device, "PROPERTY"); + val = udev_device_get_property_value(udev_device, "PROPERTY"); if (!val || !safe_strtoint(val, &id)) return 0; else @@ -2537,11 +2656,11 @@ udev_drm_event(int fd, uint32_t mask, void *data) event = udev_monitor_receive_device(b->udev_monitor); - if (udev_event_is_hotplug(b, event)) { + if (udev_event_is_hotplug(b->drm, event)) { if (udev_event_is_conn_prop_change(b, event, &conn_id, &prop_id)) drm_backend_update_conn_props(b, conn_id, prop_id); else - drm_backend_update_connectors(b, event); + drm_backend_update_connectors(b->drm, event); } udev_device_unref(event); @@ -2553,6 +2672,7 @@ static void drm_destroy(struct weston_compositor *ec) { struct drm_backend *b = to_drm_backend(ec); + struct drm_device *device = b->drm; struct weston_head *base, *next; struct drm_crtc *crtc, *crtc_tmp; struct drm_writeback *writeback, *writeback_tmp; @@ -2564,20 +2684,22 @@ drm_destroy(struct weston_compositor *ec) b->shutting_down = true; - destroy_sprites(b); + destroy_sprites(b->drm); weston_log_scope_destroy(b->debug); b->debug = NULL; weston_compositor_shutdown(ec); - wl_list_for_each_safe(crtc, crtc_tmp, &b->crtc_list, link) + wl_list_for_each_safe(crtc, crtc_tmp, &b->drm->crtc_list, link) drm_crtc_destroy(crtc); - wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) - drm_head_destroy(to_drm_head(base)); + wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) { + if (to_drm_head(base)) + drm_head_destroy(base); + } wl_list_for_each_safe(writeback, writeback_tmp, - &b->writeback_connector_list, link) + &b->drm->writeback_connector_list, link) drm_writeback_destroy(writeback); #ifdef BUILD_DRM_GBM @@ -2588,10 +2710,11 @@ drm_destroy(struct weston_compositor *ec) udev_monitor_unref(b->udev_monitor); udev_unref(b->udev); - weston_launcher_close(ec->launcher, b->drm.fd); + weston_launcher_close(ec->launcher, device->drm.fd); weston_launcher_destroy(ec->launcher); - free(b->drm.filename); + free(device->drm.filename); + free(device); free(b); } @@ -2600,15 +2723,14 @@ session_notify(struct wl_listener *listener, void *data) { struct weston_compositor *compositor = data; struct drm_backend *b = to_drm_backend(compositor); - struct drm_plane *plane; - struct drm_output *output; - struct drm_crtc *crtc; + struct drm_device *device = b->drm; + struct weston_output *output; if (compositor->session_active) { weston_log("activating session\n"); weston_compositor_wake(compositor); weston_compositor_damage_all(compositor); - b->state_invalid = true; + device->state_invalid = true; udev_input_enable(&b->input); } else { weston_log("deactivating session\n"); @@ -2624,24 +2746,9 @@ session_notify(struct wl_listener *listener, void *data) * back, we schedule a repaint, which will process * pending frame callbacks. */ - wl_list_for_each(output, &compositor->output_list, base.link) { - crtc = output->crtc; - output->base.repaint_needed = false; - if (output->cursor_plane) - drmModeSetCursor(b->drm.fd, crtc->crtc_id, - 0, 0, 0); - } - - output = container_of(compositor->output_list.next, - struct drm_output, base.link); - crtc = output->crtc; - - wl_list_for_each(plane, &b->plane_list, link) { - if (plane->type != WDRM_PLANE_TYPE_OVERLAY) - continue; - drmModeSetPlane(b->drm.fd, plane->plane_id, crtc->crtc_id, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - } + wl_list_for_each(output, &compositor->output_list, link) + if (to_drm_output(output)) + output->repaint_needed = false; } } @@ -2653,16 +2760,17 @@ session_notify(struct wl_listener *listener, void *data) * the compositor session. * * @param compositor The compositor instance. - * @param device The device being added/removed. + * @param devnum The device being added/removed. * @param added Whether the device is being added (or removed) */ static void drm_device_changed(struct weston_compositor *compositor, - dev_t device, bool added) + dev_t devnum, bool added) { struct drm_backend *b = to_drm_backend(compositor); + struct drm_device *device = b->drm; - if (b->drm.fd < 0 || b->drm.devnum != device || + if (device->drm.fd < 0 || device->drm.devnum != devnum || compositor->session_active == added) return; @@ -2675,18 +2783,20 @@ drm_device_changed(struct weston_compositor *compositor, * sets b->drm.fd and b->drm.filename to the opened device. */ static bool -drm_device_is_kms(struct drm_backend *b, struct udev_device *device) +drm_device_is_kms(struct drm_backend *b, struct drm_device *device, + struct udev_device *udev_device) { - const char *filename = udev_device_get_devnode(device); - const char *sysnum = udev_device_get_sysnum(device); - dev_t devnum = udev_device_get_devnum(device); + struct weston_compositor *compositor = b->compositor; + const char *filename = udev_device_get_devnode(udev_device); + const char *sysnum = udev_device_get_sysnum(udev_device); + dev_t devnum = udev_device_get_devnum(udev_device); drmModeRes *res; int id = -1, fd; if (!filename) return false; - fd = weston_launcher_open(b->compositor->launcher, filename, O_RDWR); + fd = weston_launcher_open(compositor->launcher, filename, O_RDWR); if (fd < 0) return false; @@ -2707,14 +2817,14 @@ drm_device_is_kms(struct drm_backend *b, struct udev_device *device) /* We can be called successfully on multiple devices; if we have, * clean up old entries. */ - if (b->drm.fd >= 0) - weston_launcher_close(b->compositor->launcher, b->drm.fd); - free(b->drm.filename); + if (device->drm.fd >= 0) + weston_launcher_close(compositor->launcher, device->drm.fd); + free(device->drm.filename); - b->drm.fd = fd; - b->drm.id = id; - b->drm.filename = strdup(filename); - b->drm.devnum = devnum; + device->drm.fd = fd; + device->drm.id = id; + device->drm.filename = strdup(filename); + device->drm.devnum = devnum; drmModeFreeResources(res); @@ -2740,10 +2850,11 @@ out_fd: static struct udev_device* find_primary_gpu(struct drm_backend *b, const char *seat) { + struct drm_device *device = b->drm; struct udev_enumerate *e; struct udev_list_entry *entry; const char *path, *device_seat, *id; - struct udev_device *device, *drm_device, *pci; + struct udev_device *dev, *drm_device, *pci; e = udev_enumerate_new(b->udev); udev_enumerate_add_match_subsystem(e, "drm"); @@ -2755,18 +2866,18 @@ find_primary_gpu(struct drm_backend *b, const char *seat) bool is_boot_vga = false; path = udev_list_entry_get_name(entry); - device = udev_device_new_from_syspath(b->udev, path); - if (!device) + dev = udev_device_new_from_syspath(b->udev, path); + if (!dev) continue; - device_seat = udev_device_get_property_value(device, "ID_SEAT"); + device_seat = udev_device_get_property_value(dev, "ID_SEAT"); if (!device_seat) device_seat = default_seat; if (strcmp(device_seat, seat)) { - udev_device_unref(device); + udev_device_unref(dev); continue; } - pci = udev_device_get_parent_with_subsystem_devtype(device, + pci = udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL); if (pci) { id = udev_device_get_sysattr_value(pci, "boot_vga"); @@ -2778,15 +2889,15 @@ find_primary_gpu(struct drm_backend *b, const char *seat) * device isn't our boot-VGA device, we aren't going to use * it. */ if (!is_boot_vga && drm_device) { - udev_device_unref(device); + udev_device_unref(dev); continue; } /* Make sure this device is actually capable of modesetting; - * if this call succeeds, b->drm.{fd,filename} will be set, + * if this call succeeds, device->drm.{fd,filename} will be set, * and any old values freed. */ - if (!drm_device_is_kms(b, device)) { - udev_device_unref(device); + if (!drm_device_is_kms(b, b->drm, dev)) { + udev_device_unref(dev); continue; } @@ -2795,7 +2906,7 @@ find_primary_gpu(struct drm_backend *b, const char *seat) if (is_boot_vga) { if (drm_device) udev_device_unref(drm_device); - drm_device = device; + drm_device = dev; break; } @@ -2803,39 +2914,40 @@ find_primary_gpu(struct drm_backend *b, const char *seat) * trump existing saved devices with boot-VGA devices, so if * we end up here, this must be the first device we've seen. */ assert(!drm_device); - drm_device = device; + drm_device = dev; } /* If we're returning a device to use, we must have an open FD for * it. */ - assert(!!drm_device == (b->drm.fd >= 0)); + assert(!!drm_device == (device->drm.fd >= 0)); udev_enumerate_unref(e); return drm_device; } static struct udev_device * -open_specific_drm_device(struct drm_backend *b, const char *name) +open_specific_drm_device(struct drm_backend *b, struct drm_device *device, + const char *name) { - struct udev_device *device; + struct udev_device *udev_device; - device = udev_device_new_from_subsystem_sysname(b->udev, "drm", name); - if (!device) { + udev_device = udev_device_new_from_subsystem_sysname(b->udev, "drm", name); + if (!udev_device) { weston_log("ERROR: could not open DRM device '%s'\n", name); return NULL; } - if (!drm_device_is_kms(b, device)) { - udev_device_unref(device); + if (!drm_device_is_kms(b, device, udev_device)) { + udev_device_unref(udev_device); weston_log("ERROR: DRM device '%s' is not a KMS device.\n", name); return NULL; } /* If we're returning a device to use, we must have an open FD for * it. */ - assert(b->drm.fd >= 0); + assert(device->drm.fd >= 0); - return device; + return udev_device; } static void @@ -2843,15 +2955,16 @@ planes_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct drm_backend *b = data; + struct drm_device *device = b->drm; switch (key) { case KEY_C: - b->cursors_are_broken ^= true; + device->cursors_are_broken ^= true; break; case KEY_V: /* We don't support overlay-plane usage with legacy KMS. */ - if (b->atomic_modeset) - b->sprites_are_broken ^= true; + if (device->atomic_modeset) + device->sprites_are_broken ^= true; break; default: break; @@ -2875,17 +2988,17 @@ static void recorder_frame_notify(struct wl_listener *listener, void *data) { struct drm_output *output; - struct drm_backend *b; + struct drm_device *device; int fd, ret; output = container_of(listener, struct drm_output, recorder_frame_listener); - b = to_drm_backend(output->base.compositor); + device = output->device; if (!output->recorder) return; - ret = drmPrimeHandleToFD(b->drm.fd, + ret = drmPrimeHandleToFD(device->drm.fd, output->scanout_plane->state_cur->fb->handles[0], DRM_CLOEXEC, &fd); if (ret) { @@ -2906,15 +3019,16 @@ static void * create_recorder(struct drm_backend *b, int width, int height, const char *filename) { + struct drm_device *device = b->drm; int fd; drm_magic_t magic; - fd = open(b->drm.filename, O_RDWR | O_CLOEXEC); + fd = open(device->drm.filename, O_RDWR | O_CLOEXEC); if (fd < 0) return NULL; drmGetMagic(fd, &magic); - drmAuthMagic(b->drm.fd, magic); + drmAuthMagic(device->drm.fd, magic); return vaapi_recorder_create(fd, width, height, filename); } @@ -2924,11 +3038,15 @@ recorder_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) { struct drm_backend *b = data; + struct weston_output *base_output; struct drm_output *output; int width, height; - output = container_of(b->compositor->output_list.next, - struct drm_output, base.link); + wl_list_for_each(base_output, &b->compositor->output_list, link) { + output = to_drm_output(base_output); + if (output) + break; + } if (!output->recorder) { if (output->gbm_format != DRM_FORMAT_XRGB8888) { @@ -2974,6 +3092,7 @@ static const struct weston_drm_output_api api = { drm_output_set_mode, drm_output_set_gbm_format, drm_output_set_seat, + drm_output_set_max_bpc, }; static struct drm_backend * @@ -2981,6 +3100,7 @@ drm_backend_create(struct weston_compositor *compositor, struct weston_drm_backend_config *config) { struct drm_backend *b; + struct drm_device *device; struct udev_device *drm_device; struct wl_event_loop *loop; const char *seat_id = default_seat; @@ -3002,8 +3122,14 @@ drm_backend_create(struct weston_compositor *compositor, if (b == NULL) return NULL; - b->state_invalid = true; - b->drm.fd = -1; + device = zalloc(sizeof *device); + if (device == NULL) + return NULL; + device->state_invalid = true; + device->drm.fd = -1; + device->backend = b; + + b->drm = device; b->compositor = compositor; b->use_pixman = config->use_pixman; @@ -3019,13 +3145,11 @@ drm_backend_create(struct weston_compositor *compositor, if (parse_gbm_format(config->gbm_format, DRM_FORMAT_XRGB8888, &b->gbm_format) < 0) goto err_compositor; - /* Check if we run drm-backend using weston-launch */ - compositor->launcher = weston_launcher_connect(compositor, config->tty, - seat_id, true); + /* Check if we run drm-backend using a compatible launcher */ + compositor->launcher = weston_launcher_connect(compositor, seat_id, true); if (compositor->launcher == NULL) { - weston_log("fatal: drm backend should be run using " - "weston-launch binary, or your system should " - "provide the logind D-Bus API.\n"); + weston_log("fatal: your system should either provide the " + "logind D-Bus API, or use seatd.\n"); goto err_compositor; } @@ -3039,7 +3163,8 @@ drm_backend_create(struct weston_compositor *compositor, wl_signal_add(&compositor->session_signal, &b->session_listener); if (config->specific_device) - drm_device = open_specific_drm_device(b, config->specific_device); + drm_device = open_specific_drm_device(b, device, + config->specific_device); else drm_device = find_primary_gpu(b, seat_id); if (drm_device == NULL) { @@ -3047,7 +3172,7 @@ drm_backend_create(struct weston_compositor *compositor, goto err_udev; } - if (init_kms_caps(b) < 0) { + if (init_kms_caps(device) < 0) { weston_log("failed to initialize kms\n"); goto err_udev_dev; } @@ -3074,20 +3199,20 @@ drm_backend_create(struct weston_compositor *compositor, weston_setup_vt_switch_bindings(compositor); - res = drmModeGetResources(b->drm.fd); + res = drmModeGetResources(b->drm->drm.fd); if (!res) { weston_log("Failed to get drmModeRes\n"); goto err_udev_dev; } - wl_list_init(&b->crtc_list); - if (drm_backend_create_crtc_list(b, res) == -1) { + wl_list_init(&b->drm->crtc_list); + if (drm_backend_create_crtc_list(b->drm, res) == -1) { weston_log("Failed to create CRTC list for DRM-backend\n"); goto err_create_crtc_list; } - wl_list_init(&b->plane_list); - create_sprites(b); + wl_list_init(&device->plane_list); + create_sprites(b->drm); if (udev_input_init(&b->input, compositor, b->udev, seat_id, @@ -3096,9 +3221,9 @@ drm_backend_create(struct weston_compositor *compositor, goto err_sprite; } - wl_list_init(&b->writeback_connector_list); - if (drm_backend_discover_connectors(b, drm_device, res) < 0) { - weston_log("Failed to create heads for %s\n", b->drm.filename); + wl_list_init(&b->drm->writeback_connector_list); + if (drm_backend_discover_connectors(b->drm, drm_device, res) < 0) { + weston_log("Failed to create heads for %s\n", b->drm->drm.filename); goto err_udev_input; } @@ -3109,13 +3234,13 @@ drm_backend_create(struct weston_compositor *compositor, /* A this point we have some idea of whether or not we have a working * cursor plane. */ - if (!b->cursors_are_broken) + if (!device->cursors_are_broken) compositor->capabilities |= WESTON_CAP_CURSOR_PLANE; loop = wl_display_get_event_loop(compositor->wl_display); b->drm_source = - wl_event_loop_add_fd(loop, b->drm.fd, - WL_EVENT_READABLE, on_drm_input, b); + wl_event_loop_add_fd(loop, b->drm->drm.fd, + WL_EVENT_READABLE, on_drm_input, b->drm); b->udev_monitor = udev_monitor_new_from_netlink(b->udev, "udev"); if (b->udev_monitor == NULL) { @@ -3144,8 +3269,6 @@ drm_backend_create(struct weston_compositor *compositor, planes_binding, b); weston_compositor_add_debug_binding(compositor, KEY_Q, recorder_binding, b); - weston_compositor_add_debug_binding(compositor, KEY_W, - renderer_switch_binding, b); if (compositor->renderer->import_dmabuf) { if (linux_dmabuf_setup(compositor) < 0) @@ -3157,7 +3280,7 @@ drm_backend_create(struct weston_compositor *compositor, * table was already created and populated with * renderer's format/modifier pairs. So now we must * compute the scanout formats indices in the table */ - scanout_formats = get_scanout_formats(b); + scanout_formats = get_scanout_formats(b->drm); if (!scanout_formats) goto err_udev_monitor; ret = weston_dmabuf_feedback_format_table_set_scanout_indices(compositor->dmabuf_feedback_format_table, @@ -3178,7 +3301,7 @@ drm_backend_create(struct weston_compositor *compositor, " synchronization support failed.\n"); } - if (b->atomic_modeset) + if (device->atomic_modeset) if (weston_compositor_enable_content_protection(compositor) < 0) weston_log("Error: initializing content-protection " "support failed.\n"); @@ -3207,7 +3330,7 @@ err_drm_source: err_udev_input: udev_input_destroy(&b->input); err_sprite: - destroy_sprites(b); + destroy_sprites(b->drm); err_create_crtc_list: drmModeFreeResources(res); err_udev_dev: diff --git a/libweston/backend-drm/fb.c b/libweston/backend-drm/fb.c index ba0c177e..05b4988f 100644 --- a/libweston/backend-drm/fb.c +++ b/libweston/backend-drm/fb.c @@ -69,7 +69,7 @@ drm_fb_destroy_dumb(struct drm_fb *fb) } static int -drm_fb_addfb(struct drm_backend *b, struct drm_fb *fb) +drm_fb_addfb(struct drm_device *device, struct drm_fb *fb) { int ret = -EINVAL; uint64_t mods[4] = { }; @@ -77,7 +77,7 @@ drm_fb_addfb(struct drm_backend *b, struct drm_fb *fb) /* If we have a modifier set, we must only use the WithModifiers * entrypoint; we cannot import it through legacy ioctls. */ - if (b->fb_modifiers && fb->modifier != DRM_FORMAT_MOD_INVALID) { + if (device->fb_modifiers && fb->modifier != DRM_FORMAT_MOD_INVALID) { /* KMS demands that if a modifier is set, it must be the same * for all planes. */ for (i = 0; i < ARRAY_LENGTH(mods) && fb->handles[i]; i++) @@ -98,7 +98,7 @@ drm_fb_addfb(struct drm_backend *b, struct drm_fb *fb) /* Legacy AddFB can't always infer the format from depth/bpp alone, so * check if our format is one of the lucky ones. */ - if (!fb->format->depth || !fb->format->bpp) + if (!fb->format->addfb_legacy_depth || !fb->format->bpp) return ret; /* Cannot fall back to AddFB for multi-planar formats either. */ @@ -106,13 +106,13 @@ drm_fb_addfb(struct drm_backend *b, struct drm_fb *fb) return ret; ret = drmModeAddFB(fb->fd, fb->width, fb->height, - fb->format->depth, fb->format->bpp, + fb->format->addfb_legacy_depth, fb->format->bpp, fb->strides[0], fb->handles[0], &fb->fb_id); return ret; } struct drm_fb * -drm_fb_create_dumb(struct drm_backend *b, int width, int height, +drm_fb_create_dumb(struct drm_device *device, int width, int height, uint32_t format) { struct drm_fb *fb; @@ -134,7 +134,7 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, goto err_fb; } - if (!fb->format->depth || !fb->format->bpp) { + if (!fb->format->addfb_legacy_depth || !fb->format->bpp) { weston_log("format 0x%lx is not compatible with dumb buffers\n", (unsigned long) format); goto err_fb; @@ -145,7 +145,7 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, create_arg.width = width; create_arg.height = height; - ret = drmIoctl(b->drm.fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_arg); + ret = drmIoctl(device->drm.fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_arg); if (ret) goto err_fb; @@ -157,9 +157,9 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, fb->size = create_arg.size; fb->width = width; fb->height = height; - fb->fd = b->drm.fd; + fb->fd = device->drm.fd; - if (drm_fb_addfb(b, fb) != 0) { + if (drm_fb_addfb(device, fb) != 0) { weston_log("failed to create kms fb: %s\n", strerror(errno)); goto err_bo; } @@ -171,18 +171,18 @@ drm_fb_create_dumb(struct drm_backend *b, int width, int height, goto err_add_fb; fb->map = mmap(NULL, fb->size, PROT_WRITE, - MAP_SHARED, b->drm.fd, map_arg.offset); + MAP_SHARED, device->drm.fd, map_arg.offset); if (fb->map == MAP_FAILED) goto err_add_fb; return fb; err_add_fb: - drmModeRmFB(b->drm.fd, fb->fb_id); + drmModeRmFB(device->drm.fd, fb->fb_id); err_bo: memset(&destroy_arg, 0, sizeof(destroy_arg)); destroy_arg.handle = create_arg.handle; - drmIoctl(b->drm.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_arg); + drmIoctl(device->drm.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_arg); err_fb: free(fb); return NULL; @@ -218,7 +218,7 @@ drm_fb_destroy_dmabuf(struct drm_fb *fb) static struct drm_fb * drm_fb_get_from_dmabuf(struct linux_dmabuf_buffer *dmabuf, - struct drm_backend *backend, bool is_opaque, + struct drm_device *device, bool is_opaque, uint32_t *try_view_on_plane_failure_reasons) { #ifndef HAVE_GBM_FD_IMPORT @@ -227,6 +227,7 @@ drm_fb_get_from_dmabuf(struct linux_dmabuf_buffer *dmabuf, * of GBM_BO_IMPORT_FD_MODIFIER. */ return NULL; #else + struct drm_backend *backend = device->backend; struct drm_fb *fb; int i; struct gbm_import_fd_modifier_data import_mod = { @@ -287,7 +288,7 @@ drm_fb_get_from_dmabuf(struct linux_dmabuf_buffer *dmabuf, fb->height = dmabuf->attributes.height; fb->modifier = dmabuf->attributes.modifier[0]; fb->size = 0; - fb->fd = backend->drm.fd; + fb->fd = device->drm.fd; ARRAY_COPY(fb->strides, dmabuf->attributes.stride); ARRAY_COPY(fb->offsets, dmabuf->attributes.offset); @@ -302,10 +303,10 @@ drm_fb_get_from_dmabuf(struct linux_dmabuf_buffer *dmabuf, if (is_opaque) fb->format = pixel_format_get_opaque_substitute(fb->format); - if (backend->min_width > fb->width || - fb->width > backend->max_width || - backend->min_height > fb->height || - fb->height > backend->max_height) { + if (device->min_width > fb->width || + fb->width > device->max_width || + device->min_height > fb->height || + fb->height > device->max_height) { weston_log("bo geometry out of bounds\n"); goto err_free; } @@ -315,12 +316,15 @@ drm_fb_get_from_dmabuf(struct linux_dmabuf_buffer *dmabuf, union gbm_bo_handle handle; handle = gbm_bo_get_handle_for_plane(fb->bo, i); - if (handle.s32 == -1) + if (handle.s32 == -1) { + *try_view_on_plane_failure_reasons |= + FAILURE_REASONS_GBM_BO_GET_HANDLE_FAILED; goto err_free; + } fb->handles[i] = handle.u32; } - if (drm_fb_addfb(backend, fb) != 0) { + if (drm_fb_addfb(device, fb) != 0) { if (try_view_on_plane_failure_reasons) *try_view_on_plane_failure_reasons |= FAILURE_REASONS_ADD_FB_FAILED; @@ -336,7 +340,7 @@ err_free: } struct drm_fb * -drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, +drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_device *device, bool is_opaque, enum drm_fb_type type) { struct drm_fb *fb = gbm_bo_get_user_data(bo); @@ -356,7 +360,7 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, fb->type = type; fb->refcnt = 1; fb->bo = bo; - fb->fd = backend->drm.fd; + fb->fd = device->drm.fd; fb->width = gbm_bo_get_width(bo); fb->height = gbm_bo_get_height(bo); @@ -389,15 +393,15 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, if (is_opaque) fb->format = pixel_format_get_opaque_substitute(fb->format); - if (backend->min_width > fb->width || - fb->width > backend->max_width || - backend->min_height > fb->height || - fb->height > backend->max_height) { + if (device->min_width > fb->width || + fb->width > device->max_width || + device->min_height > fb->height || + fb->height > device->max_height) { weston_log("bo geometry out of bounds\n"); goto err_free; } - if (drm_fb_addfb(backend, fb) != 0) { + if (drm_fb_addfb(device, fb) != 0) { if (type == BUFFER_GBM_SURFACE) weston_log("failed to create kms fb: %s\n", strerror(errno)); @@ -453,22 +457,25 @@ drm_can_scanout_dmabuf(struct weston_compositor *ec, { struct drm_fb *fb; struct drm_backend *b = to_drm_backend(ec); + struct drm_device *device = b->drm; bool ret = false; + uint32_t try_reason = 0x0; - fb = drm_fb_get_from_dmabuf(dmabuf, b, true, NULL); + fb = drm_fb_get_from_dmabuf(dmabuf, device, true, &try_reason); if (fb) ret = true; drm_fb_unref(fb); - drm_debug(b, "[dmabuf] dmabuf %p, import test %s\n", dmabuf, - ret ? "succeeded" : "failed"); + drm_debug(b, "[dmabuf] dmabuf %p, import test %s, with reason 0x%x\n", dmabuf, + ret ? "succeeded" : "failed", try_reason); return ret; } static bool drm_fb_compatible_with_plane(struct drm_fb *fb, struct drm_plane *plane) { - struct drm_backend *b = plane->backend; + struct drm_device *device = plane->device; + struct drm_backend *b = device->backend; struct weston_drm_format *fmt; /* Check whether the format is supported */ @@ -508,6 +515,8 @@ drm_fb_handle_buffer_destroy(struct wl_listener *listener, void *data) struct drm_buffer_fb *buf_fb = container_of(listener, struct drm_buffer_fb, buffer_destroy_listener); + wl_list_remove(&buf_fb->buffer_destroy_listener.link); + if (buf_fb->fb) { assert(buf_fb->fb->type == BUFFER_CLIENT || buf_fb->fb->type == BUFFER_DMABUF); @@ -523,25 +532,36 @@ drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev, { struct drm_output *output = state->output; struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_device *device = output->device; struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; struct drm_buffer_fb *buf_fb; bool is_opaque = weston_view_is_opaque(ev, &ev->transform.boundingbox); - struct linux_dmabuf_buffer *dmabuf; struct drm_fb *fb; struct drm_plane *plane; - if (ev->alpha != 1.0f) + if (ev->alpha != 1.0f) { + *try_view_on_plane_failure_reasons |= + FAILURE_REASONS_GLOBAL_ALPHA; return NULL; + } - if (!drm_view_transform_supported(ev, &output->base)) + if (!drm_view_transform_supported(ev, &output->base)) { + *try_view_on_plane_failure_reasons |= + FAILURE_REASONS_INCOMPATIBLE_TRANSFORM; return NULL; + } if (ev->surface->protection_mode == WESTON_SURFACE_PROTECTION_MODE_ENFORCED && - ev->surface->desired_protection > output->base.current_protection) + ev->surface->desired_protection > output->base.current_protection) { + *try_view_on_plane_failure_reasons |= + FAILURE_REASONS_INADEQUATE_CONTENT_PROTECTION; return NULL; + } - if (!buffer) + if (!buffer) { + *try_view_on_plane_failure_reasons |= FAILURE_REASONS_NO_BUFFER; return NULL; + } if (buffer->backend_private) { buf_fb = buffer->backend_private; @@ -554,20 +574,18 @@ drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev, buf_fb->buffer_destroy_listener.notify = drm_fb_handle_buffer_destroy; wl_signal_add(&buffer->destroy_signal, &buf_fb->buffer_destroy_listener); - if (wl_shm_buffer_get(buffer->resource)) - goto unsuitable; - /* GBM is used for dmabuf import as well as from client wl_buffer. */ - if (!b->gbm) + if (!b->gbm) { + *try_view_on_plane_failure_reasons |= FAILURE_REASONS_NO_GBM; goto unsuitable; + } - dmabuf = linux_dmabuf_buffer_get(buffer->resource); - if (dmabuf) { - fb = drm_fb_get_from_dmabuf(dmabuf, b, is_opaque, + if (buffer->type == WESTON_BUFFER_DMABUF) { + fb = drm_fb_get_from_dmabuf(buffer->dmabuf, device, is_opaque, &buf_fb->failure_reasons); if (!fb) goto unsuitable; - } else { + } else if (buffer->type == WESTON_BUFFER_RENDERER_OPAQUE) { struct gbm_bo *bo; bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_WL_BUFFER, @@ -575,16 +593,24 @@ drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev, if (!bo) goto unsuitable; - fb = drm_fb_get_from_bo(bo, b, is_opaque, BUFFER_CLIENT); + fb = drm_fb_get_from_bo(bo, device, is_opaque, BUFFER_CLIENT); if (!fb) { + *try_view_on_plane_failure_reasons |= + (1 << FAILURE_REASONS_ADD_FB_FAILED); gbm_bo_destroy(bo); goto unsuitable; } + } else { + *try_view_on_plane_failure_reasons |= FAILURE_REASONS_BUFFER_TYPE; + goto unsuitable; } /* Check if this buffer can ever go on any planes. If it can't, we have * no reason to ever have a drm_fb, so we fail it here. */ - wl_list_for_each(plane, &b->plane_list, link) { + wl_list_for_each(plane, &device->plane_list, link) { + /* only SHM buffers can go into cursor planes */ + if (plane->type == WDRM_PLANE_TYPE_CURSOR) + continue; if (drm_fb_compatible_with_plane(fb, plane)) fb->plane_mask |= (1 << plane->plane_idx); } diff --git a/libweston/backend-drm/kms-color.c b/libweston/backend-drm/kms-color.c new file mode 100644 index 00000000..610b2442 --- /dev/null +++ b/libweston/backend-drm/kms-color.c @@ -0,0 +1,178 @@ +/* + * Copyright 2021-2022 Collabora, Ltd. + * + * 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. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "drm-internal.h" + +static inline uint16_t +color_xy_to_u16(float v) +{ + assert(v >= 0.0f); + assert(v <= 1.0f); + /* + * CTA-861-G + * 6.9.1 Static Metadata Type 1 + * chromaticity coordinate encoding + */ + return (uint16_t)round(v * 50000.0); +} + +static inline uint16_t +nits_to_u16(float nits) +{ + assert(nits >= 1.0f); + assert(nits <= 65535.0f); + /* + * CTA-861-G + * 6.9.1 Static Metadata Type 1 + * max display mastering luminance, max content light level, + * max frame-average light level + */ + return (uint16_t)round(nits); +} + +static inline uint16_t +nits_to_u16_dark(float nits) +{ + assert(nits >= 0.0001f); + assert(nits <= 6.5535f); + /* + * CTA-861-G + * 6.9.1 Static Metadata Type 1 + * min display mastering luminance + */ + return (uint16_t)round(nits * 10000.0); +} + +static void +weston_hdr_metadata_type1_to_kms(struct hdr_metadata_infoframe *dst, + const struct weston_hdr_metadata_type1 *src) +{ + if (src->group_mask & WESTON_HDR_METADATA_TYPE1_GROUP_PRIMARIES) { + unsigned i; + + for (i = 0; i < 3; i++) { + dst->display_primaries[i].x = color_xy_to_u16(src->primary[i].x); + dst->display_primaries[i].y = color_xy_to_u16(src->primary[i].y); + } + } + + if (src->group_mask & WESTON_HDR_METADATA_TYPE1_GROUP_WHITE) { + dst->white_point.x = color_xy_to_u16(src->white.x); + dst->white_point.y = color_xy_to_u16(src->white.y); + } + + if (src->group_mask & WESTON_HDR_METADATA_TYPE1_GROUP_MAXDML) + dst->max_display_mastering_luminance = nits_to_u16(src->maxDML); + + if (src->group_mask & WESTON_HDR_METADATA_TYPE1_GROUP_MINDML) + dst->min_display_mastering_luminance = nits_to_u16_dark(src->minDML); + + if (src->group_mask & WESTON_HDR_METADATA_TYPE1_GROUP_MAXCLL) + dst->max_cll = nits_to_u16(src->maxCLL); + + if (src->group_mask & WESTON_HDR_METADATA_TYPE1_GROUP_MAXFALL) + dst->max_fall = nits_to_u16(src->maxFALL); +} + +int +drm_output_ensure_hdr_output_metadata_blob(struct drm_output *output) +{ + struct drm_device *device = output->device; + const struct weston_hdr_metadata_type1 *src; + struct hdr_output_metadata meta; + uint32_t blob_id = 0; + int ret; + + if (output->hdr_output_metadata_blob_id && + output->ackd_color_outcome_serial == output->base.color_outcome_serial) + return 0; + + src = weston_output_get_hdr_metadata_type1(&output->base); + + /* + * Set up the data for Dynamic Range and Mastering InfoFrame, + * CTA-861-G, a.k.a the static HDR metadata. + */ + + memset(&meta, 0, sizeof meta); + + meta.metadata_type = 0; /* Static Metadata Type 1 */ + + /* Duplicated field in UABI struct */ + meta.hdmi_metadata_type1.metadata_type = meta.metadata_type; + + switch (output->base.eotf_mode) { + case WESTON_EOTF_MODE_NONE: + assert(0 && "bad eotf_mode: none"); + return -1; + case WESTON_EOTF_MODE_SDR: + /* + * Do not send any static HDR metadata. Video sinks should + * respond by switching to traditional SDR mode. If they + * do not, the kernel should fix that up. + */ + assert(output->hdr_output_metadata_blob_id == 0); + return 0; + case WESTON_EOTF_MODE_TRADITIONAL_HDR: + meta.hdmi_metadata_type1.eotf = 1; /* from CTA-861-G */ + break; + case WESTON_EOTF_MODE_ST2084: + meta.hdmi_metadata_type1.eotf = 2; /* from CTA-861-G */ + weston_hdr_metadata_type1_to_kms(&meta.hdmi_metadata_type1, src); + break; + case WESTON_EOTF_MODE_HLG: + meta.hdmi_metadata_type1.eotf = 3; /* from CTA-861-G */ + break; + } + + if (meta.hdmi_metadata_type1.eotf == 0) { + assert(0 && "bad eotf_mode"); + return -1; + } + + ret = drmModeCreatePropertyBlob(device->drm.fd, + &meta, sizeof meta, &blob_id); + if (ret != 0) { + weston_log("Error: failed to create KMS blob for HDR metadata on output '%s': %s\n", + output->base.name, strerror(-ret)); + return -1; + } + + drmModeDestroyPropertyBlob(device->drm.fd, + output->hdr_output_metadata_blob_id); + + output->hdr_output_metadata_blob_id = blob_id; + output->ackd_color_outcome_serial = output->base.color_outcome_serial; + + return 0; +} diff --git a/libweston/backend-drm/kms.c b/libweston/backend-drm/kms.c index 780d0070..b6c7fe5f 100644 --- a/libweston/backend-drm/kms.c +++ b/libweston/backend-drm/kms.c @@ -143,6 +143,10 @@ const struct drm_property_info connector_props[] = { .enum_values = panel_orientation_enums, .num_enum_values = WDRM_PANEL_ORIENTATION__COUNT, }, + [WDRM_CONNECTOR_HDR_OUTPUT_METADATA] = { + .name = "HDR_OUTPUT_METADATA", + }, + [WDRM_CONNECTOR_MAX_BPC] = { .name = "max bpc", }, }; const struct drm_property_info crtc_props[] = { @@ -272,14 +276,14 @@ drm_property_get_range_values(struct drm_property_info *info, * The values given in enum_names are searched for, and stored in the * same-indexed field of the map array. * - * @param b DRM backend object + * @param device DRM device object * @param src DRM property info array to source from * @param info DRM property info array to copy into * @param num_infos Number of entries in the source array * @param props DRM object properties for the object */ void -drm_property_info_populate(struct drm_backend *b, +drm_property_info_populate(struct drm_device *device, const struct drm_property_info *src, struct drm_property_info *info, unsigned int num_infos, @@ -311,7 +315,7 @@ drm_property_info_populate(struct drm_backend *b, for (i = 0; i < props->count_props; i++) { unsigned int k; - prop = drmModeGetProperty(b->drm.fd, props->props[i]); + prop = drmModeGetProperty(device->drm.fd, props->props[i]); if (!prop) continue; @@ -411,19 +415,6 @@ drm_property_info_free(struct drm_property_info *info, int num_props) memset(info, 0, sizeof(*info) * num_props); } -static inline uint32_t * -formats_ptr(struct drm_format_modifier_blob *blob) -{ - return (uint32_t *)(((char *)blob) + blob->formats_offset); -} - -static inline struct drm_format_modifier * -modifiers_ptr(struct drm_format_modifier_blob *blob) -{ - return (struct drm_format_modifier *) - (((char *)blob) + blob->modifiers_offset); -} - /** * Populates the plane's formats array, using either the IN_FORMATS blob * property (if available), or the plane's format list if not. @@ -433,13 +424,11 @@ drm_plane_populate_formats(struct drm_plane *plane, const drmModePlane *kplane, const drmModeObjectProperties *props, const bool use_modifiers) { - unsigned i, j; + struct drm_device *device = plane->device; + uint32_t i, blob_id, fmt_prev = DRM_FORMAT_INVALID; + drmModeFormatModifierIterator drm_iter = {0}; + struct weston_drm_format *fmt = NULL; drmModePropertyBlobRes *blob = NULL; - struct drm_format_modifier_blob *fmt_mod_blob; - struct drm_format_modifier *blob_modifiers; - uint32_t *blob_formats; - uint32_t blob_id; - struct weston_drm_format *fmt; int ret = 0; if (!use_modifiers) @@ -451,39 +440,26 @@ drm_plane_populate_formats(struct drm_plane *plane, const drmModePlane *kplane, if (blob_id == 0) goto fallback; - blob = drmModeGetPropertyBlob(plane->backend->drm.fd, blob_id); + blob = drmModeGetPropertyBlob(device->drm.fd, blob_id); if (!blob) goto fallback; - fmt_mod_blob = blob->data; - blob_formats = formats_ptr(fmt_mod_blob); - blob_modifiers = modifiers_ptr(fmt_mod_blob); - - assert(kplane->count_formats == fmt_mod_blob->count_formats); + while (drmModeFormatModifierBlobIterNext(blob, &drm_iter)) { + if (fmt_prev != drm_iter.fmt) { + fmt = weston_drm_format_array_add_format(&plane->formats, + drm_iter.fmt); + if (!fmt) { + ret = -1; + goto out; + } - for (i = 0; i < fmt_mod_blob->count_formats; i++) { - fmt = weston_drm_format_array_add_format(&plane->formats, - blob_formats[i]); - if (!fmt) { - ret = -1; - goto out; + fmt_prev = drm_iter.fmt; } - for (j = 0; j < fmt_mod_blob->count_modifiers; j++) { - struct drm_format_modifier *mod = &blob_modifiers[j]; - - if ((i < mod->offset) || (i > mod->offset + 63)) - continue; - if (!(mod->formats & (1 << (i - mod->offset)))) - continue; - - ret = weston_drm_format_add_modifier(fmt, mod->modifier); - if (ret < 0) - goto out; - } + ret = weston_drm_format_add_modifier(fmt, drm_iter.mod); + if (ret < 0) + goto out; - if (fmt->modifiers.size == 0) - weston_drm_format_array_remove_latest_format(&plane->formats); } out: @@ -510,14 +486,15 @@ drm_output_set_gamma(struct weston_output *output_base, { int rc; struct drm_output *output = to_drm_output(output_base); - struct drm_backend *backend = - to_drm_backend(output->base.compositor); + struct drm_device *device = output->device; + + assert(output); /* check */ if (output_base->gamma_size != size) return; - rc = drmModeCrtcSetGamma(backend->drm.fd, + rc = drmModeCrtcSetGamma(device->drm.fd, output->crtc->crtc_id, size, r, g, b); if (rc) @@ -535,7 +512,8 @@ drm_output_assign_state(struct drm_output_state *state, enum drm_state_apply_mode mode) { struct drm_output *output = state->output; - struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_device *device = output->device; + struct drm_backend *b = device->backend; struct drm_plane_state *plane_state; struct drm_head *head; @@ -552,13 +530,13 @@ drm_output_assign_state(struct drm_output_state *state, output->state_cur = state; - if (b->atomic_modeset && mode == DRM_STATE_APPLY_ASYNC) { + if (device->atomic_modeset && mode == DRM_STATE_APPLY_ASYNC) { drm_debug(b, "\t[CRTC:%u] setting pending flip\n", output->crtc->crtc_id); output->atomic_complete_pending = true; } - if (b->atomic_modeset && + if (device->atomic_modeset && state->protection == WESTON_HDCP_DISABLE) wl_list_for_each(head, &output->base.head_list, base.output_link) weston_head_set_content_protection_status(&head->base, @@ -580,7 +558,7 @@ drm_output_assign_state(struct drm_output_state *state, continue; } - if (b->atomic_modeset) + if (device->atomic_modeset) continue; assert(plane->type != WDRM_PLANE_TYPE_OVERLAY); @@ -593,7 +571,7 @@ static void drm_output_set_cursor(struct drm_output_state *output_state) { struct drm_output *output = output_state->output; - struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_device *device = output->device; struct drm_crtc *crtc = output->crtc; struct drm_plane *plane = output->cursor_plane; struct drm_plane_state *state; @@ -609,7 +587,7 @@ drm_output_set_cursor(struct drm_output_state *output_state) if (!state->fb) { pixman_region32_fini(&plane->base.damage); pixman_region32_init(&plane->base.damage); - drmModeSetCursor(b->drm.fd, crtc->crtc_id, 0, 0, 0); + drmModeSetCursor(device->drm.fd, crtc->crtc_id, 0, 0, 0); return; } @@ -618,8 +596,8 @@ drm_output_set_cursor(struct drm_output_state *output_state) handle = output->gbm_cursor_handle[output->current_cursor]; if (plane->state_cur->fb != state->fb) { - if (drmModeSetCursor(b->drm.fd, crtc->crtc_id, handle, - b->cursor_width, b->cursor_height)) { + if (drmModeSetCursor(device->drm.fd, crtc->crtc_id, handle, + device->cursor_width, device->cursor_height)) { weston_log("failed to set cursor: %s\n", strerror(errno)); goto err; @@ -629,7 +607,7 @@ drm_output_set_cursor(struct drm_output_state *output_state) pixman_region32_fini(&plane->base.damage); pixman_region32_init(&plane->base.damage); - if (drmModeMoveCursor(b->drm.fd, crtc->crtc_id, + if (drmModeMoveCursor(device->drm.fd, crtc->crtc_id, state->dest_x, state->dest_y)) { weston_log("failed to move cursor: %s\n", strerror(errno)); goto err; @@ -638,15 +616,16 @@ drm_output_set_cursor(struct drm_output_state *output_state) return; err: - b->cursors_are_broken = true; - drmModeSetCursor(b->drm.fd, crtc->crtc_id, 0, 0, 0); + device->cursors_are_broken = true; + drmModeSetCursor(device->drm.fd, crtc->crtc_id, 0, 0, 0); } static int drm_output_apply_state_legacy(struct drm_output_state *state) { struct drm_output *output = state->output; - struct drm_backend *backend = to_drm_backend(output->base.compositor); + struct drm_device *device = output->device; + struct drm_backend *backend = device->backend; struct drm_plane *scanout_plane = output->scanout_plane; struct drm_crtc *crtc = output->crtc; struct drm_property_info *dpms_prop; @@ -678,14 +657,14 @@ drm_output_apply_state_legacy(struct drm_output_state *state) if (state->dpms != WESTON_DPMS_ON) { if (output->cursor_plane) { - ret = drmModeSetCursor(backend->drm.fd, crtc->crtc_id, + ret = drmModeSetCursor(device->drm.fd, crtc->crtc_id, 0, 0, 0); if (ret) weston_log("drmModeSetCursor failed disable: %s\n", strerror(errno)); } - ret = drmModeSetCrtc(backend->drm.fd, crtc->crtc_id, 0, 0, 0, + ret = drmModeSetCrtc(device->drm.fd, crtc->crtc_id, 0, 0, 0, NULL, 0, NULL); if (ret) weston_log("drmModeSetCrtc failed disabling: %s\n", @@ -719,12 +698,12 @@ drm_output_apply_state_legacy(struct drm_output_state *state) assert(scanout_state->in_fence_fd == -1); mode = to_drm_mode(output->base.current_mode); - if (backend->state_invalid || + if (device->state_invalid || !scanout_plane->state_cur->fb || scanout_plane->state_cur->fb->strides[0] != scanout_state->fb->strides[0]) { - ret = drmModeSetCrtc(backend->drm.fd, crtc->crtc_id, + ret = drmModeSetCrtc(device->drm.fd, crtc->crtc_id, scanout_state->fb->fb_id, 0, 0, connectors, n_conn, @@ -740,7 +719,7 @@ drm_output_apply_state_legacy(struct drm_output_state *state) crtc->crtc_id, scanout_state->plane->plane_id, pinfo ? pinfo->drm_format_name : "UNKNOWN"); - if (drmModePageFlip(backend->drm.fd, crtc->crtc_id, + if (drmModePageFlip(device->drm.fd, crtc->crtc_id, scanout_state->fb->fb_id, DRM_MODE_PAGE_FLIP_EVENT, output) < 0) { weston_log("queueing pageflip failed: %s\n", strerror(errno)); @@ -761,7 +740,7 @@ drm_output_apply_state_legacy(struct drm_output_state *state) if (dpms_prop->prop_id == 0) continue; - ret = drmModeConnectorSetProperty(backend->drm.fd, + ret = drmModeConnectorSetProperty(device->drm.fd, head->connector.connector_id, dpms_prop->prop_id, state->dpms); @@ -786,6 +765,8 @@ static int crtc_add_prop(drmModeAtomicReq *req, struct drm_crtc *crtc, enum wdrm_crtc_property prop, uint64_t val) { + struct drm_device *device = crtc->device; + struct drm_backend *b = device->backend; struct drm_property_info *info = &crtc->props_crtc[prop]; int ret; @@ -794,7 +775,7 @@ crtc_add_prop(drmModeAtomicReq *req, struct drm_crtc *crtc, ret = drmModeAtomicAddProperty(req, crtc->crtc_id, info->prop_id, val); - drm_debug(crtc->backend, "\t\t\t[CRTC:%lu] %lu (%s) -> %llu (0x%llx)\n", + drm_debug(b, "\t\t\t[CRTC:%lu] %lu (%s) -> %llu (0x%llx)\n", (unsigned long) crtc->crtc_id, (unsigned long) info->prop_id, info->name, (unsigned long long) val, (unsigned long long) val); @@ -805,6 +786,8 @@ static int connector_add_prop(drmModeAtomicReq *req, struct drm_connector *connector, enum wdrm_connector_property prop, uint64_t val) { + struct drm_device *device = connector->device; + struct drm_backend *b = device->backend; struct drm_property_info *info = &connector->props[prop]; uint32_t connector_id = connector->connector_id; int ret; @@ -813,7 +796,7 @@ connector_add_prop(drmModeAtomicReq *req, struct drm_connector *connector, return -1; ret = drmModeAtomicAddProperty(req, connector_id, info->prop_id, val); - drm_debug(connector->backend, "\t\t\t[CONN:%lu] %lu (%s) -> %llu (0x%llx)\n", + drm_debug(b, "\t\t\t[CONN:%lu] %lu (%s) -> %llu (0x%llx)\n", (unsigned long) connector_id, (unsigned long) info->prop_id, info->name, (unsigned long long) val, (unsigned long long) val); @@ -824,6 +807,8 @@ static int plane_add_prop(drmModeAtomicReq *req, struct drm_plane *plane, enum wdrm_plane_property prop, uint64_t val) { + struct drm_device *device = plane->device; + struct drm_backend *b = device->backend; struct drm_property_info *info = &plane->props[prop]; int ret; @@ -832,7 +817,7 @@ plane_add_prop(drmModeAtomicReq *req, struct drm_plane *plane, ret = drmModeAtomicAddProperty(req, plane->plane_id, info->prop_id, val); - drm_debug(plane->backend, "\t\t\t[PLANE:%lu] %lu (%s) -> %llu (0x%llx)\n", + drm_debug(b, "\t\t\t[PLANE:%lu] %lu (%s) -> %llu (0x%llx)\n", (unsigned long) plane->plane_id, (unsigned long) info->prop_id, info->name, (unsigned long long) val, (unsigned long long) val); @@ -921,17 +906,52 @@ drm_connector_set_hdcp_property(struct drm_connector *connector, assert(ret == 0); } +static int +drm_connector_set_max_bpc(struct drm_connector *connector, + struct drm_output *output, + drmModeAtomicReq *req) +{ + const struct drm_property_info *info; + struct drm_head *head; + struct drm_backend *backend = output->device->backend; + uint64_t max_bpc; + uint64_t a, b; + + if (!drm_connector_has_prop(connector, WDRM_CONNECTOR_MAX_BPC)) + return 0; + + if (output->max_bpc == 0) { + /* A value of 0 means that the current max_bpc must be programmed. */ + head = drm_head_find_by_connector(backend, connector->connector_id); + max_bpc = head->inherited_max_bpc; + } else { + info = &connector->props[WDRM_CONNECTOR_MAX_BPC]; + assert(info->flags & DRM_MODE_PROP_RANGE); + assert(info->num_range_values == 2); + a = info->range_values[0]; + b = info->range_values[1]; + assert(a <= b); + + max_bpc = MAX(a, MIN(output->max_bpc, b)); + } + + return connector_add_prop(req, connector, + WDRM_CONNECTOR_MAX_BPC, max_bpc); +} + static int drm_output_apply_state_atomic(struct drm_output_state *state, drmModeAtomicReq *req, uint32_t *flags) { struct drm_output *output = state->output; - struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_device *device = output->device; + struct drm_backend *b = device->backend; struct drm_crtc *crtc = output->crtc; struct drm_plane_state *plane_state; struct drm_mode *current_mode = to_drm_mode(output->base.current_mode); struct drm_head *head; + struct drm_head *tmp; int ret = 0; drm_debug(b, "\t\t[atomic] %s output %lu (%s) state\n", @@ -944,7 +964,7 @@ drm_output_apply_state_atomic(struct drm_output_state *state, } if (state->dpms == WESTON_DPMS_ON) { - ret = drm_mode_ensure_blob(b, current_mode); + ret = drm_mode_ensure_blob(device, current_mode); if (ret != 0) return ret; @@ -968,12 +988,30 @@ drm_output_apply_state_atomic(struct drm_output_state *state, wl_list_for_each(head, &output->base.head_list, base.output_link) ret |= connector_add_prop(req, &head->connector, WDRM_CONNECTOR_CRTC_ID, 0); + + wl_list_for_each_safe(head, tmp, &output->disable_head, + disable_head_link) { + ret |= connector_add_prop(req, &head->connector, + WDRM_CONNECTOR_CRTC_ID, 0); + wl_list_remove(&head->disable_head_link); + wl_list_init(&head->disable_head_link); + } } - wl_list_for_each(head, &output->base.head_list, base.output_link) + wl_list_for_each(head, &output->base.head_list, base.output_link) { drm_connector_set_hdcp_property(&head->connector, state->protection, req); + if (drm_connector_has_prop(&head->connector, + WDRM_CONNECTOR_HDR_OUTPUT_METADATA)) { + ret |= connector_add_prop(req, &head->connector, + WDRM_CONNECTOR_HDR_OUTPUT_METADATA, + output->hdr_output_metadata_blob_id); + } + + ret |= drm_connector_set_max_bpc(&head->connector, output, req); + } + if (ret != 0) { weston_log("couldn't set atomic CRTC/connector state\n"); return ret; @@ -1010,9 +1048,9 @@ drm_output_apply_state_atomic(struct drm_output_state *state, if (plane_state->fb && plane_state->fb->format) pinfo = plane_state->fb->format; - drm_debug(plane->backend, "\t\t\t[PLANE:%lu] FORMAT: %s\n", - (unsigned long) plane->plane_id, - pinfo ? pinfo->drm_format_name : "UNKNOWN"); + drm_debug(b, "\t\t\t[PLANE:%lu] FORMAT: %s\n", + (unsigned long) plane->plane_id, + pinfo ? pinfo->drm_format_name : "UNKNOWN"); if (plane_state->in_fence_fd >= 0) { ret |= plane_add_prop(req, plane, @@ -1044,7 +1082,8 @@ static int drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, enum drm_state_apply_mode mode) { - struct drm_backend *b = pending_state->backend; + struct drm_device *device = pending_state->device; + struct drm_backend *b = device->backend; struct drm_output_state *output_state, *tmp; struct drm_plane *plane; drmModeAtomicReq *req = drmModeAtomicAlloc(); @@ -1066,7 +1105,7 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, break; } - if (b->state_invalid) { + if (device->state_invalid) { struct weston_head *head_base; struct drm_head *head; struct drm_crtc *crtc; @@ -1082,12 +1121,16 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, wl_list_for_each(head_base, &b->compositor->head_list, compositor_link) { struct drm_property_info *info; + head = to_drm_head(head_base); + if (!head) + continue; if (weston_head_is_enabled(head_base)) continue; - head = to_drm_head(head_base); connector_id = head->connector.connector_id; + if (head->connector.device != device) + continue; drm_debug(b, "\t\t[atomic] disabling inactive head %s\n", head_base->name); @@ -1103,7 +1146,7 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, ret = -1; } - wl_list_for_each(crtc, &b->crtc_list, link) { + wl_list_for_each(crtc, &device->crtc_list, link) { struct drm_property_info *info; drmModeObjectProperties *props; uint64_t active; @@ -1116,7 +1159,7 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, * off, as the kernel will refuse to generate an event * for an off->off state and fail the commit. */ - props = drmModeObjectGetProperties(b->drm.fd, + props = drmModeObjectGetProperties(device->drm.fd, crtc->crtc_id, DRM_MODE_OBJECT_CRTC); if (!props) { @@ -1139,7 +1182,7 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, /* Disable all the planes; planes which are being used will * override this state in the output-state application. */ - wl_list_for_each(plane, &b->plane_list, link) { + wl_list_for_each(plane, &device->plane_list, link) { drm_debug(b, "\t\t[atomic] starting with plane %lu disabled\n", (unsigned long) plane->plane_id); plane_add_prop(req, plane, WDRM_PLANE_CRTC_ID, 0); @@ -1162,7 +1205,7 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, goto out; } - ret = drmModeAtomicCommit(b->drm.fd, req, flags, b); + ret = drmModeAtomicCommit(device->drm.fd, req, flags, device); drm_debug(b, "[atomic] drmModeAtomicCommit\n"); /* Test commits do not take ownership of the state; return @@ -1182,7 +1225,7 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, link) drm_output_assign_state(output_state, mode); - b->state_invalid = false; + device->state_invalid = false; assert(wl_list_empty(&pending_state->output_list)); @@ -1213,9 +1256,9 @@ out: int drm_pending_state_test(struct drm_pending_state *pending_state) { - struct drm_backend *b = pending_state->backend; + struct drm_device *device = pending_state->device; - if (b->atomic_modeset) + if (device->atomic_modeset) return drm_pending_state_apply_atomic(pending_state, DRM_STATE_TEST_ONLY); @@ -1234,24 +1277,25 @@ drm_pending_state_test(struct drm_pending_state *pending_state) int drm_pending_state_apply(struct drm_pending_state *pending_state) { - struct drm_backend *b = pending_state->backend; + struct drm_device *device = pending_state->device; + struct drm_backend *b = device->backend; struct drm_output_state *output_state, *tmp; struct drm_crtc *crtc; - if (b->atomic_modeset) + if (device->atomic_modeset) return drm_pending_state_apply_atomic(pending_state, DRM_STATE_APPLY_ASYNC); - if (b->state_invalid) { + if (device->state_invalid) { /* If we need to reset all our state (e.g. because we've * just started, or just been VT-switched in), explicitly * disable all the CRTCs we aren't using. This also disables * all connectors on these CRTCs, so we don't need to do that * separately with the pre-atomic API. */ - wl_list_for_each(crtc, &b->crtc_list, link) { + wl_list_for_each(crtc, &device->crtc_list, link) { if (crtc->output) continue; - drmModeSetCrtc(b->drm.fd, crtc->crtc_id, 0, 0, 0, + drmModeSetCrtc(device->drm.fd, crtc->crtc_id, 0, 0, 0, NULL, 0, NULL); } } @@ -1274,7 +1318,7 @@ drm_pending_state_apply(struct drm_pending_state *pending_state) weston_output_repaint_failed(&output->base); drm_output_state_free(output->state_cur); output->state_cur = drm_output_state_alloc(output, NULL); - b->state_invalid = true; + device->state_invalid = true; if (!b->use_pixman) { drm_output_fini_egl(output); drm_output_init_egl(output, b); @@ -1282,7 +1326,7 @@ drm_pending_state_apply(struct drm_pending_state *pending_state) } } - b->state_invalid = false; + device->state_invalid = false; assert(wl_list_empty(&pending_state->output_list)); @@ -1301,24 +1345,24 @@ drm_pending_state_apply(struct drm_pending_state *pending_state) int drm_pending_state_apply_sync(struct drm_pending_state *pending_state) { - struct drm_backend *b = pending_state->backend; + struct drm_device *device = pending_state->device; struct drm_output_state *output_state, *tmp; struct drm_crtc *crtc; - if (b->atomic_modeset) + if (device->atomic_modeset) return drm_pending_state_apply_atomic(pending_state, DRM_STATE_APPLY_SYNC); - if (b->state_invalid) { + if (device->state_invalid) { /* If we need to reset all our state (e.g. because we've * just started, or just been VT-switched in), explicitly * disable all the CRTCs we aren't using. This also disables * all connectors on these CRTCs, so we don't need to do that * separately with the pre-atomic API. */ - wl_list_for_each(crtc, &b->crtc_list, link) { + wl_list_for_each(crtc, &device->crtc_list, link) { if (crtc->output) continue; - drmModeSetCrtc(b->drm.fd, crtc->crtc_id, 0, 0, 0, + drmModeSetCrtc(device->drm.fd, crtc->crtc_id, 0, 0, 0, NULL, 0, NULL); } } @@ -1335,7 +1379,7 @@ drm_pending_state_apply_sync(struct drm_pending_state *pending_state) } } - b->state_invalid = false; + device->state_invalid = false; assert(wl_list_empty(&pending_state->output_list)); @@ -1360,14 +1404,14 @@ page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { struct drm_output *output = data; - struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_device *device = output->device; uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_VSYNC | WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; drm_output_update_msc(output, frame); - assert(!b->atomic_modeset); + assert(!device->atomic_modeset); assert(output->page_flip_pending); output->page_flip_pending = false; @@ -1378,14 +1422,15 @@ static void atomic_flip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, unsigned int crtc_id, void *data) { - struct drm_backend *b = data; + struct drm_device *device = data; + struct drm_backend *b = device->backend; struct drm_crtc *crtc; struct drm_output *output; uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_VSYNC | WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION | WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; - crtc = drm_crtc_find(b, crtc_id); + crtc = drm_crtc_find(device, crtc_id); assert(crtc); output = crtc->output; @@ -1399,7 +1444,7 @@ atomic_flip_handler(int fd, unsigned int frame, unsigned int sec, drm_output_update_msc(output, frame); drm_debug(b, "[atomic][CRTC:%u] flip processing started\n", crtc_id); - assert(b->atomic_modeset); + assert(device->atomic_modeset); assert(output->atomic_complete_pending); output->atomic_complete_pending = false; @@ -1410,12 +1455,12 @@ atomic_flip_handler(int fd, unsigned int frame, unsigned int sec, int on_drm_input(int fd, uint32_t mask, void *data) { - struct drm_backend *b = data; + struct drm_device *device = data; drmEventContext evctx; memset(&evctx, 0, sizeof evctx); evctx.version = 3; - if (b->atomic_modeset) + if (device->atomic_modeset) evctx.page_flip_handler2 = atomic_flip_handler; else evctx.page_flip_handler = page_flip_handler; @@ -1425,61 +1470,63 @@ on_drm_input(int fd, uint32_t mask, void *data) } int -init_kms_caps(struct drm_backend *b) +init_kms_caps(struct drm_device *device) { + struct drm_backend *b = device->backend; + struct weston_compositor *compositor = b->compositor; uint64_t cap; int ret; - weston_log("using %s\n", b->drm.filename); + weston_log("using %s\n", device->drm.filename); - ret = drmGetCap(b->drm.fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap); + ret = drmGetCap(device->drm.fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap); if (ret != 0 || cap != 1) { weston_log("Error: kernel DRM KMS does not support DRM_CAP_TIMESTAMP_MONOTONIC.\n"); return -1; } - if (weston_compositor_set_presentation_clock(b->compositor, CLOCK_MONOTONIC) < 0) { + if (weston_compositor_set_presentation_clock(compositor, CLOCK_MONOTONIC) < 0) { weston_log("Error: failed to set presentation clock to CLOCK_MONOTONIC.\n"); return -1; } - ret = drmGetCap(b->drm.fd, DRM_CAP_CURSOR_WIDTH, &cap); + ret = drmGetCap(device->drm.fd, DRM_CAP_CURSOR_WIDTH, &cap); if (ret == 0) - b->cursor_width = cap; + device->cursor_width = cap; else - b->cursor_width = 64; + device->cursor_width = 64; - ret = drmGetCap(b->drm.fd, DRM_CAP_CURSOR_HEIGHT, &cap); + ret = drmGetCap(device->drm.fd, DRM_CAP_CURSOR_HEIGHT, &cap); if (ret == 0) - b->cursor_height = cap; + device->cursor_height = cap; else - b->cursor_height = 64; + device->cursor_height = 64; - ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + ret = drmSetClientCap(device->drm.fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); if (ret) { weston_log("Error: drm card doesn't support universal planes!\n"); return -1; } if (!getenv("WESTON_DISABLE_ATOMIC")) { - ret = drmGetCap(b->drm.fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap); + ret = drmGetCap(device->drm.fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap); if (ret != 0) cap = 0; - ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_ATOMIC, 1); - b->atomic_modeset = ((ret == 0) && (cap == 1)); + ret = drmSetClientCap(device->drm.fd, DRM_CLIENT_CAP_ATOMIC, 1); + device->atomic_modeset = ((ret == 0) && (cap == 1)); } weston_log("DRM: %s atomic modesetting\n", - b->atomic_modeset ? "supports" : "does not support"); + device->atomic_modeset ? "supports" : "does not support"); if (!getenv("WESTON_DISABLE_GBM_MODIFIERS")) { - ret = drmGetCap(b->drm.fd, DRM_CAP_ADDFB2_MODIFIERS, &cap); + ret = drmGetCap(device->drm.fd, DRM_CAP_ADDFB2_MODIFIERS, &cap); if (ret == 0) - b->fb_modifiers = cap; + device->fb_modifiers = cap; } weston_log("DRM: %s GBM modifiers\n", - b->fb_modifiers ? "supports" : "does not support"); + device->fb_modifiers ? "supports" : "does not support"); - drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_WRITEBACK_CONNECTORS, 1); + drmSetClientCap(device->drm.fd, DRM_CLIENT_CAP_WRITEBACK_CONNECTORS, 1); /* * KMS support for hardware planes cannot properly synchronize @@ -1489,13 +1536,13 @@ init_kms_caps(struct drm_backend *b) * to a fraction. For cursors, it's not so bad, so they are * enabled. */ - if (!b->atomic_modeset || getenv("WESTON_FORCE_RENDERER")) - b->sprites_are_broken = true; + if (!device->atomic_modeset || getenv("WESTON_FORCE_RENDERER")) + device->sprites_are_broken = true; - ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_ASPECT_RATIO, 1); - b->aspect_ratio_supported = (ret == 0); + ret = drmSetClientCap(device->drm.fd, DRM_CLIENT_CAP_ASPECT_RATIO, 1); + device->aspect_ratio_supported = (ret == 0); weston_log("DRM: %s picture aspect ratio\n", - b->aspect_ratio_supported ? "supports" : "does not support"); + device->aspect_ratio_supported ? "supports" : "does not support"); return 0; } diff --git a/libweston/backend-drm/libbacklight.c b/libweston/backend-drm/libbacklight.c index 4bbc6db4..ca7f2d68 100644 --- a/libweston/backend-drm/libbacklight.c +++ b/libweston/backend-drm/libbacklight.c @@ -53,8 +53,10 @@ static long backlight_get(struct backlight *backlight, char *node) int fd, value; long ret; - if (asprintf(&path, "%s/%s", backlight->path, node) < 0) + str_printf(&path, "%s/%s", backlight->path, node); + if (!path) return -ENOMEM; + fd = open(path, O_RDONLY); if (fd < 0) { ret = -1; @@ -67,6 +69,9 @@ static long backlight_get(struct backlight *backlight, char *node) goto out; } + if (buffer[ret - 1] == '\n') + buffer[ret - 1] = '\0'; + if (!safe_strtoint(buffer, &value)) { ret = -1; goto out; @@ -103,7 +108,8 @@ long backlight_set_brightness(struct backlight *backlight, long brightness) int fd; long ret; - if (asprintf(&path, "%s/%s", backlight->path, "brightness") < 0) + str_printf(&path, "%s/%s", backlight->path, "brightness"); + if (!path) return -ENOMEM; fd = open(path, O_RDWR); @@ -118,7 +124,8 @@ long backlight_set_brightness(struct backlight *backlight, long brightness) goto out; } - if (asprintf(&buffer, "%ld", brightness) < 0) { + str_printf(&buffer, "%ld", brightness); + if (!buffer) { ret = -1; goto out; } @@ -171,7 +178,8 @@ struct backlight *backlight_init(struct udev_device *drm_device, if (!syspath) return NULL; - if (asprintf(&path, "%s/%s", syspath, "device") < 0) + str_printf(&path, "%s/%s", syspath, "device"); + if (!path) return NULL; ret = readlink(path, buffer, sizeof(buffer) - 1); @@ -214,11 +222,13 @@ struct backlight *backlight_init(struct udev_device *drm_device, if (entry->d_name[0] == '.') continue; - if (asprintf(&backlight_path, "%s/%s", "/sys/class/backlight", - entry->d_name) < 0) + str_printf(&backlight_path, "%s/%s", "/sys/class/backlight", + entry->d_name); + if (!backlight_path) goto err; - if (asprintf(&path, "%s/%s", backlight_path, "type") < 0) { + str_printf(&path, "%s/%s", backlight_path, "type"); + if (!path) { free(backlight_path); goto err; } @@ -255,7 +265,8 @@ struct backlight *backlight_init(struct udev_device *drm_device, free (path); - if (asprintf(&path, "%s/%s", backlight_path, "device") < 0) + str_printf(&path, "%s/%s", backlight_path, "device"); + if (!path) goto err; ret = readlink(path, buffer, sizeof(buffer) - 1); diff --git a/libweston/backend-drm/meson.build b/libweston/backend-drm/meson.build index b7af834b..bf8dbb0a 100644 --- a/libweston/backend-drm/meson.build +++ b/libweston/backend-drm/meson.build @@ -24,6 +24,7 @@ srcs_drm = [ 'fb.c', 'modes.c', 'kms.c', + 'kms-color.c', 'state-helpers.c', 'state-propose.c', linux_dmabuf_unstable_v1_protocol_c, @@ -32,6 +33,7 @@ srcs_drm = [ ] deps_drm = [ + dep_libm, dep_libdl, dep_libweston_private, dep_session_helper, diff --git a/libweston/backend-drm/modes.c b/libweston/backend-drm/modes.c index a071375b..ae3a35d8 100644 --- a/libweston/backend-drm/modes.c +++ b/libweston/backend-drm/modes.c @@ -98,14 +98,15 @@ drm_subpixel_to_wayland(int drm_value) } int -drm_mode_ensure_blob(struct drm_backend *backend, struct drm_mode *mode) +drm_mode_ensure_blob(struct drm_device *device, struct drm_mode *mode) { + struct drm_backend *backend = device->backend; int ret; if (mode->blob_id) return 0; - ret = drmModeCreatePropertyBlob(backend->drm.fd, + ret = drmModeCreatePropertyBlob(device->drm.fd, &mode->mode_info, sizeof(mode->mode_info), &mode->blob_id); @@ -304,6 +305,8 @@ edid_parse(struct drm_edid *edid, const uint8_t *data, size_t length) * \param[out] make The monitor make (PNP ID). * \param[out] model The monitor model (name). * \param[out] serial_number The monitor serial number. + * \param[out] eotf_mask The monitor supported EOTF modes, combination of + * enum weston_eotf_mode bits. * * Each of \c *make, \c *model and \c *serial_number are set only if the * information is found in the EDID. The pointers they are set to must not @@ -315,8 +318,10 @@ find_and_parse_output_edid(struct drm_head *head, drmModeObjectPropertiesPtr props, const char **make, const char **model, - const char **serial_number) + const char **serial_number, + uint32_t *eotf_mask) { + struct drm_device *device = head->connector.device; drmModePropertyBlobPtr edid_blob = NULL; uint32_t blob_id; int rc; @@ -328,7 +333,7 @@ find_and_parse_output_edid(struct drm_head *head, if (!blob_id) return; - edid_blob = drmModeGetPropertyBlob(head->backend->drm.fd, blob_id); + edid_blob = drmModeGetPropertyBlob(device->drm.fd, blob_id); if (!edid_blob) return; @@ -344,6 +349,21 @@ find_and_parse_output_edid(struct drm_head *head, *serial_number = head->edid.serial_number; } drmModeFreePropertyBlob(edid_blob); + + /* TODO: parse this from EDID */ + *eotf_mask = WESTON_EOTF_MODE_ALL_MASK; +} + +static void +prune_eotf_modes_by_kms_support(struct drm_head *head, uint32_t *eotf_mask) +{ + const struct drm_property_info *info; + + /* Without the KMS property, cannot do anything but SDR. */ + + info = &head->connector.props[WDRM_CONNECTOR_HDR_OUTPUT_METADATA]; + if (!head->connector.device->atomic_modeset || info->prop_id == 0) + *eotf_mask = WESTON_EOTF_MODE_SDR; } static uint32_t @@ -406,26 +426,26 @@ drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info) * Destroys a mode, and removes it from the list. */ static void -drm_output_destroy_mode(struct drm_backend *backend, struct drm_mode *mode) +drm_output_destroy_mode(struct drm_device *device, struct drm_mode *mode) { if (mode->blob_id) - drmModeDestroyPropertyBlob(backend->drm.fd, mode->blob_id); + drmModeDestroyPropertyBlob(device->drm.fd, mode->blob_id); wl_list_remove(&mode->base.link); free(mode); } /** Destroy a list of drm_modes * - * @param backend The backend for releasing mode property blobs. + * @param device The device for releasing mode property blobs. * @param mode_list The list linked by drm_mode::base.link. */ void -drm_mode_list_destroy(struct drm_backend *backend, struct wl_list *mode_list) +drm_mode_list_destroy(struct drm_device *device, struct wl_list *mode_list) { struct drm_mode *mode, *next; wl_list_for_each_safe(mode, next, mode_list, base.link) - drm_output_destroy_mode(backend, mode); + drm_output_destroy_mode(device, mode); } void @@ -469,16 +489,16 @@ drm_output_choose_mode(struct drm_output *output, struct drm_mode *tmp_mode = NULL, *mode_fall_back = NULL, *mode; enum weston_mode_aspect_ratio src_aspect = WESTON_MODE_PIC_AR_NONE; enum weston_mode_aspect_ratio target_aspect = WESTON_MODE_PIC_AR_NONE; - struct drm_backend *b; + struct drm_device *device; - b = to_drm_backend(output->base.compositor); + device = output->device; target_aspect = target_mode->aspect_ratio; src_aspect = output->base.current_mode->aspect_ratio; if (output->base.current_mode->width == target_mode->width && output->base.current_mode->height == target_mode->height && (output->base.current_mode->refresh == target_mode->refresh || target_mode->refresh == 0)) { - if (!b->aspect_ratio_supported || src_aspect == target_aspect) + if (!device->aspect_ratio_supported || src_aspect == target_aspect) return to_drm_mode(output->base.current_mode); } @@ -489,7 +509,7 @@ drm_output_choose_mode(struct drm_output *output, mode->mode_info.vdisplay == target_mode->height) { if (mode->base.refresh == target_mode->refresh || target_mode->refresh == 0) { - if (!b->aspect_ratio_supported || + if (!device->aspect_ratio_supported || src_aspect == target_aspect) return mode; else if (!mode_fall_back) @@ -515,9 +535,12 @@ update_head_from_connector(struct drm_head *head) const char *make = "unknown"; const char *model = "unknown"; const char *serial_number = "unknown"; + uint32_t eotf_mask = WESTON_EOTF_MODE_SDR; - find_and_parse_output_edid(head, props, &make, &model, &serial_number); + find_and_parse_output_edid(head, props, &make, &model, &serial_number, &eotf_mask); weston_head_set_monitor_strings(&head->base, make, model, serial_number); + prune_eotf_modes_by_kms_support(head, &eotf_mask); + weston_head_set_supported_eotf_mask(&head->base, eotf_mask); weston_head_set_non_desktop(&head->base, check_non_desktop(connector, props)); weston_head_set_subpixel(&head->base, @@ -539,7 +562,7 @@ update_head_from_connector(struct drm_head *head) * Find the most suitable mode to use for initial setup (or reconfiguration on * hotplug etc) for a DRM output. * - * @param backend the DRM backend + * @param device the DRM device * @param output DRM output to choose mode for * @param mode Strategy and preference to use when choosing mode * @param modeline Manually-entered mode (may be NULL) @@ -547,7 +570,7 @@ update_head_from_connector(struct drm_head *head) * @returns A mode from the output's mode list, or NULL if none available */ static struct drm_mode * -drm_output_choose_initial_mode(struct drm_backend *backend, +drm_output_choose_initial_mode(struct drm_device *device, struct drm_output *output, enum weston_drm_backend_output_mode mode, const char *modeline, @@ -571,7 +594,7 @@ drm_output_choose_initial_mode(struct drm_backend *backend, if (mode == WESTON_DRM_BACKEND_OUTPUT_PREFERRED && modeline) { n = sscanf(modeline, "%dx%d@%d %u:%u", &width, &height, &refresh, &aspect_width, &aspect_height); - if (backend->aspect_ratio_supported && n == 5) { + if (device->aspect_ratio_supported && n == 5) { if (aspect_width == 4 && aspect_height == 3) aspect_ratio = WESTON_MODE_PIC_AR_4_3; else if (aspect_width == 16 && aspect_height == 9) @@ -602,7 +625,7 @@ drm_output_choose_initial_mode(struct drm_backend *backend, if (width == drm_mode->base.width && height == drm_mode->base.height && (refresh == 0 || refresh == drm_mode->mode_info.vrefresh)) { - if (!backend->aspect_ratio_supported || + if (!device->aspect_ratio_supported || aspect_ratio == drm_mode->base.aspect_ratio) configured = drm_mode; else @@ -714,7 +737,7 @@ drm_output_try_add_mode(struct drm_output *output, const drmModeModeInfo *info) { struct weston_mode *base; struct drm_mode *mode = NULL; - struct drm_backend *backend; + struct drm_device *device = output->device; const drmModeModeInfo *chosen = NULL; assert(info); @@ -728,8 +751,7 @@ drm_output_try_add_mode(struct drm_output *output, const drmModeModeInfo *info) if (chosen == info) { assert(mode); - backend = to_drm_backend(output->base.compositor); - drm_output_destroy_mode(backend, mode); + drm_output_destroy_mode(device, mode); chosen = NULL; } @@ -756,7 +778,7 @@ drm_output_try_add_mode(struct drm_output *output, const drmModeModeInfo *info) static int drm_output_update_modelist_from_heads(struct drm_output *output) { - struct drm_backend *backend = to_drm_backend(output->base.compositor); + struct drm_device *device = output->device; struct weston_head *head_base; struct drm_head *head; drmModeConnector *conn; @@ -765,7 +787,7 @@ drm_output_update_modelist_from_heads(struct drm_output *output) assert(!output->base.enabled); - drm_mode_list_destroy(backend, &output->base.mode_list); + drm_mode_list_destroy(device, &output->base.mode_list); wl_list_for_each(head_base, &output->base.head_list, output_link) { head = to_drm_head(head_base); @@ -786,7 +808,7 @@ drm_output_set_mode(struct weston_output *base, const char *modeline) { struct drm_output *output = to_drm_output(base); - struct drm_backend *b = to_drm_backend(base->compositor); + struct drm_device *device = output->device; struct drm_head *head = to_drm_head(weston_output_get_first_head(base)); struct drm_mode *current; @@ -797,7 +819,7 @@ drm_output_set_mode(struct weston_output *base, if (drm_output_update_modelist_from_heads(output) < 0) return -1; - current = drm_output_choose_initial_mode(b, output, mode, modeline, + current = drm_output_choose_initial_mode(device, output, mode, modeline, &head->inherited_mode); if (!current) return -1; diff --git a/libweston/backend-drm/state-helpers.c b/libweston/backend-drm/state-helpers.c index 75a2a18c..4077c8aa 100644 --- a/libweston/backend-drm/state-helpers.c +++ b/libweston/backend-drm/state-helpers.c @@ -73,6 +73,8 @@ drm_plane_state_alloc(struct drm_output_state *state_output, void drm_plane_state_free(struct drm_plane_state *state, bool force) { + struct drm_device *device; + if (!state) return; @@ -86,14 +88,17 @@ drm_plane_state_free(struct drm_plane_state *state, bool force) * by the kernel, which means we can safely discard it. */ if (state->damage_blob_id != 0) { - drmModeDestroyPropertyBlob(state->plane->backend->drm.fd, + device = state->plane->device; + + drmModeDestroyPropertyBlob(device->drm.fd, state->damage_blob_id); state->damage_blob_id = 0; } if (force || state != state->plane->state_cur) { drm_fb_unref(state->fb); - weston_buffer_reference(&state->fb_ref.buffer, NULL); + weston_buffer_reference(&state->fb_ref.buffer, NULL, + BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference(&state->fb_ref.release, NULL); free(state); } @@ -135,10 +140,20 @@ drm_plane_state_duplicate(struct drm_output_state *state_output, * buffer, then we must also transfer the reference on the client * buffer. */ if (src->fb) { + struct weston_buffer *buffer; + dst->fb = drm_fb_ref(src->fb); memset(&dst->fb_ref, 0, sizeof(dst->fb_ref)); - weston_buffer_reference(&dst->fb_ref.buffer, - src->fb_ref.buffer.buffer); + + if (src->fb->type == BUFFER_CLIENT || + src->fb->type == BUFFER_DMABUF) { + buffer = src->fb_ref.buffer.buffer; + } else { + buffer = NULL; + } + weston_buffer_reference(&dst->fb_ref.buffer, buffer, + buffer ? BUFFER_MAY_BE_ACCESSED : + BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference(&dst->fb_ref.release, src->fb_ref.release.buffer_release); } else { @@ -421,11 +436,11 @@ drm_output_state_free(struct drm_output_state *state) * Allocate a new, empty, 'pending state' structure to be used across a * repaint cycle or similar. * - * @param backend DRM backend + * @param device DRM device * @returns Newly-allocated pending state structure */ struct drm_pending_state * -drm_pending_state_alloc(struct drm_backend *backend) +drm_pending_state_alloc(struct drm_device *device) { struct drm_pending_state *ret; @@ -433,7 +448,7 @@ drm_pending_state_alloc(struct drm_backend *backend) if (!ret) return NULL; - ret->backend = backend; + ret->device = device; wl_list_init(&ret->output_list); return ret; diff --git a/libweston/backend-drm/state-propose.c b/libweston/backend-drm/state-propose.c index 7b350aa4..967b6bd0 100644 --- a/libweston/backend-drm/state-propose.c +++ b/libweston/backend-drm/state-propose.c @@ -41,6 +41,7 @@ #include "color.h" #include "linux-dmabuf.h" #include "presentation-time-server-protocol.h" +#include "linux-dmabuf-unstable-v1-server-protocol.h" enum drm_output_propose_state_mode { DRM_OUTPUT_PROPOSE_STATE_MIXED, /**< mix renderer & planes */ @@ -63,55 +64,6 @@ drm_propose_state_mode_to_string(enum drm_output_propose_state_mode mode) return drm_output_propose_state_mode_as_string[mode]; } -static void -drm_output_add_zpos_plane(struct drm_plane *plane, struct wl_list *planes) -{ - struct drm_backend *b = plane->backend; - struct drm_plane_zpos *h_plane; - struct drm_plane_zpos *plane_zpos; - - plane_zpos = zalloc(sizeof(*plane_zpos)); - if (!plane_zpos) - return; - - plane_zpos->plane = plane; - - drm_debug(b, "\t\t\t\t[plane] plane %d added to candidate list\n", - plane->plane_id); - - if (wl_list_empty(planes)) { - wl_list_insert(planes, &plane_zpos->link); - return; - } - - h_plane = wl_container_of(planes->next, h_plane, link); - if (h_plane->plane->zpos_max >= plane->zpos_max) { - wl_list_insert(planes->prev, &plane_zpos->link); - } else { - struct drm_plane_zpos *p_zpos = NULL; - - if (wl_list_length(planes) == 1) { - wl_list_insert(planes->prev, &plane_zpos->link); - return; - } - - wl_list_for_each(p_zpos, planes, link) { - if (p_zpos->plane->zpos_max > - plane_zpos->plane->zpos_max) - break; - } - - wl_list_insert(p_zpos->link.prev, &plane_zpos->link); - } -} - -static void -drm_output_destroy_zpos_plane(struct drm_plane_zpos *plane_zpos) -{ - wl_list_remove(&plane_zpos->link); - free(plane_zpos); -} - static bool drm_output_check_plane_has_view_assigned(struct drm_plane *plane, struct drm_output_state *output_state) @@ -125,87 +77,79 @@ drm_output_check_plane_has_view_assigned(struct drm_plane *plane, } static struct drm_plane_state * -drm_output_prepare_overlay_view(struct drm_plane *plane, - struct drm_output_state *output_state, - struct weston_view *ev, - enum drm_output_propose_state_mode mode, - struct drm_fb *fb, uint64_t zpos) +drm_output_try_view_on_plane(struct drm_plane *plane, + struct drm_output_state *output_state, + struct weston_view *ev, + enum drm_output_propose_state_mode mode, + struct drm_fb *fb, uint64_t zpos) { struct drm_output *output = output_state->output; - struct weston_compositor *ec = output->base.compositor; - struct drm_backend *b = to_drm_backend(ec); + struct weston_surface *surface = ev->surface; + struct drm_device *device = output->device; + struct drm_backend *b = device->backend; struct drm_plane_state *state = NULL; - int ret; - assert(!b->sprites_are_broken); - assert(b->atomic_modeset); - - if (!fb) { - drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay: " - " couldn't get fb\n", ev); - return NULL; - } + assert(!device->sprites_are_broken); + assert(device->atomic_modeset); + assert(fb); + assert(mode == DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY || + (mode == DRM_OUTPUT_PROPOSE_STATE_MIXED && + plane->type == WDRM_PLANE_TYPE_OVERLAY)); state = drm_output_state_get_plane(output_state, plane); /* we can't have a 'pending' framebuffer as never set one before reaching here */ assert(!state->fb); - - state->ev = ev; state->output = output; if (!drm_plane_state_coords_for_view(state, ev, zpos)) { - drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay: " + drm_debug(b, "\t\t\t\t[view] not placing view %p on plane: " "unsuitable transform\n", ev); - drm_plane_state_put_back(state); - state = NULL; goto out; } - /* If the surface buffer has an in-fence fd, but the plane - * doesn't support fences, we can't place the buffer on this - * plane. */ - if (ev->surface->acquire_fence_fd >= 0 && - plane->props[WDRM_PLANE_IN_FENCE_FD].prop_id == 0) { - drm_debug(b, "\t\t\t\t[overlay] not placing view %p on overlay: " - "no in-fence support\n", ev); - drm_plane_state_put_back(state); - state = NULL; - goto out; + /* Should've been ensured by weston_view_matches_entire_output. */ + if (plane->type == WDRM_PLANE_TYPE_PRIMARY) { + assert(state->dest_x == 0 && state->dest_y == 0 && + state->dest_w == (unsigned) output->base.current_mode->width && + state->dest_h == (unsigned) output->base.current_mode->height); } /* We hold one reference for the lifetime of this function; from * calling drm_fb_get_from_view() in drm_output_prepare_plane_view(), * so, we take another reference here to live within the state. */ + state->ev = ev; state->fb = drm_fb_ref(fb); - state->in_fence_fd = ev->surface->acquire_fence_fd; /* In planes-only mode, we don't have an incremental state to * test against, so we just hope it'll work. */ - if (mode == DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY) { - drm_debug(b, "\t\t\t[overlay] provisionally placing " - "view %p on overlay %lu in planes-only mode\n", + if (mode != DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY && + drm_pending_state_test(output_state->pending_state) != 0) { + drm_debug(b, "\t\t\t[view] not placing view %p on plane %lu: " + "atomic test failed\n", ev, (unsigned long) plane->plane_id); goto out; } - ret = drm_pending_state_test(output_state->pending_state); - if (ret == 0) { - drm_debug(b, "\t\t\t[overlay] provisionally placing " - "view %p on overlay %d in mixed mode\n", - ev, plane->plane_id); - goto out; - } - - drm_debug(b, "\t\t\t[overlay] not placing view %p on overlay %lu " - "in mixed mode: kernel test failed\n", + drm_debug(b, "\t\t\t[view] provisionally placing view %p on plane %lu\n", ev, (unsigned long) plane->plane_id); - drm_plane_state_put_back(state); - state = NULL; + /* Take a reference on the buffer so that we don't release it + * back to the client until we're done with it; cursor buffers + * don't require a reference since we copy them. */ + assert(state->fb_ref.buffer.buffer == NULL); + assert(state->fb_ref.release.buffer_release == NULL); + weston_buffer_reference(&state->fb_ref.buffer, + surface->buffer_ref.buffer, + BUFFER_MAY_BE_ACCESSED); + weston_buffer_release_reference(&state->fb_ref.release, + surface->buffer_release_ref.buffer_release); -out: return state; + +out: + drm_plane_state_put_back(state); + return NULL; } #ifdef BUILD_DRM_GBM @@ -218,18 +162,18 @@ out: static void cursor_bo_update(struct drm_plane_state *plane_state, struct weston_view *ev) { - struct drm_backend *b = plane_state->plane->backend; + struct drm_output *output = plane_state->output; + struct drm_device *device = output->device; struct gbm_bo *bo = plane_state->fb->bo; struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; - uint32_t buf[b->cursor_width * b->cursor_height]; + uint32_t buf[device->cursor_width * device->cursor_height]; int32_t stride; uint8_t *s; int i; assert(buffer && buffer->shm_buffer); - assert(buffer->shm_buffer == wl_shm_buffer_get(buffer->resource)); - assert(buffer->width <= b->cursor_width); - assert(buffer->height <= b->cursor_height); + assert(buffer->width <= device->cursor_width); + assert(buffer->height <= device->cursor_height); memset(buf, 0, sizeof buf); stride = wl_shm_buffer_get_stride(buffer->shm_buffer); @@ -237,7 +181,7 @@ cursor_bo_update(struct drm_plane_state *plane_state, struct weston_view *ev) wl_shm_buffer_begin_access(buffer->shm_buffer); for (i = 0; i < buffer->height; i++) - memcpy(buf + i * b->cursor_width, + memcpy(buf + i * device->cursor_width, s + i * stride, buffer->width * 4); wl_shm_buffer_end_access(buffer->shm_buffer); @@ -251,32 +195,23 @@ drm_output_prepare_cursor_view(struct drm_output_state *output_state, struct weston_view *ev, uint64_t zpos) { struct drm_output *output = output_state->output; - struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_device *device = output->device; + struct drm_backend *b = device->backend; struct drm_plane *plane = output->cursor_plane; struct drm_plane_state *plane_state; bool needs_update = false; - struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; const char *p_name = drm_output_get_plane_type_name(plane); - assert(!b->cursors_are_broken); - - if (!plane) - return NULL; - - if (!plane->state_cur->complete) - return NULL; - - if (plane->state_cur->output && plane->state_cur->output != output) - return NULL; + assert(!device->cursors_are_broken); + assert(plane); + assert(plane->state_cur->complete); + assert(!plane->state_cur->output || plane->state_cur->output == output); /* We use GBM to import SHM buffers. */ - if (b->gbm == NULL) - return NULL; + assert(b->gbm); plane_state = drm_output_state_get_plane(output_state, plane); - - if (plane_state && plane_state->fb) - return NULL; + assert(!plane_state->fb); /* We can't scale with the legacy API, and we don't try to account for * simple cropping/translation in cursor_bo_update. */ @@ -287,19 +222,9 @@ drm_output_prepare_cursor_view(struct drm_output_state *output_state, goto err; } - if (buffer->width > b->cursor_width || - buffer->height > b->cursor_height) { - drm_debug(b, "\t\t\t\t[%s] not assigning view %p to %s plane " - "(surface buffer (%dx%d) larger than permitted" - " (%dx%d))\n", p_name, ev, p_name, - buffer->width, buffer->height, - b->cursor_width, b->cursor_height); - goto err; - } - if (plane_state->src_x != 0 || plane_state->src_y != 0 || - plane_state->src_w > (unsigned) b->cursor_width << 16 || - plane_state->src_h > (unsigned) b->cursor_height << 16 || + plane_state->src_w > (unsigned) device->cursor_width << 16 || + plane_state->src_h > (unsigned) device->cursor_height << 16 || plane_state->src_w != plane_state->dest_w << 16 || plane_state->src_h != plane_state->dest_h << 16) { drm_debug(b, "\t\t\t\t[%s] not assigning view %p to %s plane " @@ -338,10 +263,10 @@ drm_output_prepare_cursor_view(struct drm_output_state *output_state, * a buffer which is always cursor_width x cursor_height, even if the * surface we want to promote is actually smaller than this. Manually * mangle the plane state to deal with this. */ - plane_state->src_w = b->cursor_width << 16; - plane_state->src_h = b->cursor_height << 16; - plane_state->dest_w = b->cursor_width; - plane_state->dest_h = b->cursor_height; + plane_state->src_w = device->cursor_width << 16; + plane_state->src_h = device->cursor_height << 16; + plane_state->dest_w = device->cursor_width; + plane_state->dest_h = device->cursor_height; drm_debug(b, "\t\t\t\t[%s] provisionally assigned view %p to cursor\n", p_name, ev); @@ -361,190 +286,6 @@ drm_output_prepare_cursor_view(struct drm_output_state *output_state, } #endif -static struct drm_plane_state * -drm_output_prepare_scanout_view(struct drm_output_state *output_state, - struct weston_view *ev, - enum drm_output_propose_state_mode mode, - struct drm_fb *fb, uint64_t zpos) -{ - struct drm_output *output = output_state->output; - struct drm_backend *b = to_drm_backend(output->base.compositor); - struct drm_plane *scanout_plane = output->scanout_plane; - struct drm_plane_state *state; - const char *p_name = drm_output_get_plane_type_name(scanout_plane); - - assert(!b->sprites_are_broken); - assert(b->atomic_modeset); - assert(mode == DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY); - - /* Check the view spans exactly the output size, calculated in the - * logical co-ordinate space. */ - if (!weston_view_matches_output_entirely(ev, &output->base)) { - drm_debug(b, "\t\t\t\t[%s] not placing view %p on %s: " - " view does not match output entirely\n", - p_name, ev, p_name); - return NULL; - } - - /* If the surface buffer has an in-fence fd, but the plane doesn't - * support fences, we can't place the buffer on this plane. */ - if (ev->surface->acquire_fence_fd >= 0 && - scanout_plane->props[WDRM_PLANE_IN_FENCE_FD].prop_id == 0) { - drm_debug(b, "\t\t\t\t[%s] not placing view %p on %s: " - "no in-fence support\n", p_name, ev, p_name); - return NULL; - } - - if (!fb) { - drm_debug(b, "\t\t\t\t[%s] not placing view %p on %s: " - " couldn't get fb\n", p_name, ev, p_name); - return NULL; - } - - state = drm_output_state_get_plane(output_state, scanout_plane); - - /* The only way we can already have a buffer in the scanout plane is - * if we are in mixed mode, or if a client buffer has already been - * placed into scanout. The former case will never call into here, - * and in the latter case, the view must have been marked as occluded, - * meaning we should never have ended up here. */ - assert(!state->fb); - - /* take another reference here to live within the state */ - state->fb = drm_fb_ref(fb); - state->ev = ev; - state->output = output; - if (!drm_plane_state_coords_for_view(state, ev, zpos)) { - drm_debug(b, "\t\t\t\t[%s] not placing view %p on %s: " - "unsuitable transform\n", p_name, ev, p_name); - goto err; - } - - if (state->dest_x != 0 || state->dest_y != 0 || - state->dest_w != (unsigned) output->base.current_mode->width || - state->dest_h != (unsigned) output->base.current_mode->height) { - drm_debug(b, "\t\t\t\t[%s] not placing view %p on %s: " - " invalid plane state\n", p_name, ev, p_name); - goto err; - } - - state->in_fence_fd = ev->surface->acquire_fence_fd; - - /* In plane-only mode, we don't need to test the state now, as we - * will only test it once at the end. */ - return state; - -err: - drm_plane_state_put_back(state); - return NULL; -} - -static struct drm_plane_state * -drm_output_try_view_on_plane(struct drm_plane *plane, - struct drm_output_state *state, - struct weston_view *ev, - enum drm_output_propose_state_mode mode, - struct drm_fb *fb, uint64_t zpos) -{ - struct drm_backend *b = state->pending_state->backend; - struct weston_output *wet_output = &state->output->base; - bool view_matches_entire_output, scanout_has_view_assigned; - struct drm_plane *scanout_plane = state->output->scanout_plane; - struct drm_plane_state *ps = NULL; - const char *p_name = drm_output_get_plane_type_name(plane); - struct weston_surface *surface = ev->surface; - enum { - NO_PLANES, /* generic err-handle */ - NO_PLANES_ACCEPTED, - PLACED_ON_PLANE, - } availability = NO_PLANES; - - /* sanity checks in case we over/underflow zpos or pass incorrect - * values */ - assert(zpos <= plane->zpos_max || - zpos != DRM_PLANE_ZPOS_INVALID_PLANE); - - switch (plane->type) { - case WDRM_PLANE_TYPE_CURSOR: - if (b->cursors_are_broken) { - availability = NO_PLANES_ACCEPTED; - goto out; - } - - ps = drm_output_prepare_cursor_view(state, ev, zpos); - if (ps) - availability = PLACED_ON_PLANE; - break; - case WDRM_PLANE_TYPE_OVERLAY: - /* do not attempt to place it in the overlay if we don't have - * anything in the scanout/primary and the view doesn't cover - * the entire output */ - view_matches_entire_output = - weston_view_matches_output_entirely(ev, wet_output); - scanout_has_view_assigned = - drm_output_check_plane_has_view_assigned(scanout_plane, - state); - - if (view_matches_entire_output && !scanout_has_view_assigned) { - availability = NO_PLANES_ACCEPTED; - goto out; - } - - ps = drm_output_prepare_overlay_view(plane, state, ev, mode, - fb, zpos); - if (ps) - availability = PLACED_ON_PLANE; - break; - case WDRM_PLANE_TYPE_PRIMARY: - if (mode != DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY) { - availability = NO_PLANES_ACCEPTED; - goto out; - } - - ps = drm_output_prepare_scanout_view(state, ev, mode, - fb, zpos); - if (ps) - availability = PLACED_ON_PLANE; - break; - default: - assert(0); - break; - } - -out: - switch (availability) { - case NO_PLANES: - /* set initial to this catch-all case, such that - * prepare_cursor/overlay/scanout() should have/contain the - * reason for failling */ - break; - case NO_PLANES_ACCEPTED: - drm_debug(b, "\t\t\t\t[plane] plane %d refusing to " - "place view %p in %s\n", - plane->plane_id, ev, p_name); - break; - case PLACED_ON_PLANE: - /* Take a reference on the buffer so that we don't release it - * back to the client until we're done with it; cursor buffers - * don't require a reference since we copy them. */ - assert(ps->fb_ref.buffer.buffer == NULL); - assert(ps->fb_ref.release.buffer_release == NULL); - if (ps->plane->type == WDRM_PLANE_TYPE_CURSOR) { - assert(ps->fb->type == BUFFER_CURSOR); - } else if (fb->type == BUFFER_CLIENT || fb->type == BUFFER_DMABUF) { - assert(ps->fb == fb); - weston_buffer_reference(&ps->fb_ref.buffer, - surface->buffer_ref.buffer); - weston_buffer_release_reference(&ps->fb_ref.release, - surface->buffer_release_ref.buffer_release); - } - break; - } - - - return ps; -} - static void drm_output_check_zpos_plane_states(struct drm_output_state *state) { @@ -585,12 +326,13 @@ drm_output_check_zpos_plane_states(struct drm_output_state *state) } static bool -dmabuf_feedback_maybe_update(struct drm_backend *b, struct weston_view *ev, +dmabuf_feedback_maybe_update(struct drm_device *device, struct weston_view *ev, uint32_t try_view_on_plane_failure_reasons) { struct weston_dmabuf_feedback *dmabuf_feedback = ev->surface->dmabuf_feedback; struct weston_dmabuf_feedback_tranche *scanout_tranche; - dev_t scanout_dev = b->drm.devnum; + struct drm_backend *b = device->backend; + dev_t scanout_dev = device->drm.devnum; uint32_t scanout_flags = ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT; uint32_t action_needed = ACTION_NEEDED_NONE; struct timespec current_time, delta_time; @@ -603,7 +345,8 @@ dmabuf_feedback_maybe_update(struct drm_backend *b, struct weston_view *ev, (FAILURE_REASONS_ADD_FB_FAILED | FAILURE_REASONS_FB_FORMAT_INCOMPATIBLE | FAILURE_REASONS_DMABUF_MODIFIER_INVALID | - FAILURE_REASONS_GBM_BO_IMPORT_FAILED)) + FAILURE_REASONS_GBM_BO_IMPORT_FAILED | + FAILURE_REASONS_GBM_BO_GET_HANDLE_FAILED)) action_needed |= ACTION_NEEDED_ADD_SCANOUT_TRANCHE; assert(action_needed != (ACTION_NEEDED_REMOVE_SCANOUT_TRANCHE | @@ -681,58 +424,161 @@ dmabuf_feedback_maybe_update(struct drm_backend *b, struct weston_view *ev, } static struct drm_plane_state * -drm_output_prepare_plane_view(struct drm_output_state *state, - struct weston_view *ev, - enum drm_output_propose_state_mode mode, - struct drm_plane_state *scanout_state, - uint64_t current_lowest_zpos, - uint32_t *try_view_on_plane_failure_reasons) +drm_output_find_plane_for_view(struct drm_output_state *state, + struct weston_paint_node *pnode, + enum drm_output_propose_state_mode mode, + struct drm_plane_state *scanout_state, + uint64_t current_lowest_zpos) { struct drm_output *output = state->output; - struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_device *device = output->device; + struct drm_backend *b = device->backend; struct drm_plane_state *ps = NULL; struct drm_plane *plane; - struct drm_plane_zpos *p_zpos, *p_zpos_next; - struct wl_list zpos_candidate_list; + struct weston_view *ev = pnode->view; struct weston_buffer *buffer; - struct wl_shm_buffer *shmbuf; - struct drm_fb *fb; + struct drm_fb *fb = NULL; + + bool view_matches_entire_output, scanout_has_view_assigned; + uint32_t possible_plane_mask = 0; - wl_list_init(&zpos_candidate_list); + pnode->try_view_on_plane_failure_reasons = FAILURE_REASONS_NONE; /* check view for valid buffer, doesn't make sense to even try */ - if (!weston_view_has_valid_buffer(ev)) - return ps; + if (!weston_view_has_valid_buffer(ev)) { + pnode->try_view_on_plane_failure_reasons |= + FAILURE_REASONS_FB_FORMAT_INCOMPATIBLE; + return NULL; + } buffer = ev->surface->buffer_ref.buffer; - shmbuf = wl_shm_buffer_get(buffer->resource); - fb = drm_fb_get_from_view(state, ev, try_view_on_plane_failure_reasons); - if (!shmbuf && !fb) + if (buffer->type == WESTON_BUFFER_SOLID) { + pnode->try_view_on_plane_failure_reasons |= + FAILURE_REASONS_FB_FORMAT_INCOMPATIBLE; return NULL; + } else if (buffer->type == WESTON_BUFFER_SHM) { + if (!output->cursor_plane || device->cursors_are_broken) { + pnode->try_view_on_plane_failure_reasons |= + FAILURE_REASONS_FB_FORMAT_INCOMPATIBLE; + return NULL; + } + + /* Even though this is a SHM buffer, pixel_format stores the + * format code as DRM FourCC */ + if (buffer->pixel_format->format != DRM_FORMAT_ARGB8888) { + drm_debug(b, "\t\t\t\t[view] not placing view %p on " + "plane; SHM buffers must be ARGB8888 for " + "cursor view\n", ev); + pnode->try_view_on_plane_failure_reasons |= + FAILURE_REASONS_FB_FORMAT_INCOMPATIBLE; + return NULL; + } + + if (buffer->width > device->cursor_width || + buffer->height > device->cursor_height) { + drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " + "(buffer (%dx%d) too large for cursor plane)\n", + ev, buffer->width, buffer->height); + pnode->try_view_on_plane_failure_reasons |= + FAILURE_REASONS_FB_FORMAT_INCOMPATIBLE; + return NULL; + } + + possible_plane_mask = (1 << output->cursor_plane->plane_idx); + } else { + if (mode == DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY) { + drm_debug(b, "\t\t\t\t[view] not assigning view %p " + "to plane: renderer-only mode\n", ev); + return NULL; + } + + fb = drm_fb_get_from_view(state, ev, + &pnode->try_view_on_plane_failure_reasons); + if (!fb) { + drm_debug(b, "\t\t\t[view] couldn't get FB for view: 0x%lx\n", + (unsigned long) pnode->try_view_on_plane_failure_reasons); + return NULL; + } + + possible_plane_mask = fb->plane_mask; + } + + view_matches_entire_output = + weston_view_matches_output_entirely(ev, &output->base); + scanout_has_view_assigned = + drm_output_check_plane_has_view_assigned(output->scanout_plane, + state); /* assemble a list with possible candidates */ - wl_list_for_each(plane, &b->plane_list, link) { + wl_list_for_each(plane, &device->plane_list, link) { + const char *p_name = drm_output_get_plane_type_name(plane); + uint64_t zpos; + + if (possible_plane_mask == 0) + break; + + if (!(possible_plane_mask & (1 << plane->plane_idx))) + continue; + + possible_plane_mask &= ~(1 << plane->plane_idx); + + switch (plane->type) { + case WDRM_PLANE_TYPE_CURSOR: + assert(buffer->shm_buffer); + assert(plane == output->cursor_plane); + break; + case WDRM_PLANE_TYPE_PRIMARY: + assert(fb); + if (plane != output->scanout_plane) + continue; + if (mode != DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY) + continue; + if (!view_matches_entire_output) + continue; + break; + case WDRM_PLANE_TYPE_OVERLAY: + assert(fb); + assert(mode != DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY); + /* if the view covers the whole output, put it in the + * scanout plane, not overlay */ + if (view_matches_entire_output && + !scanout_has_view_assigned) + continue; + break; + default: + assert(false && "unknown plane type"); + } + if (!drm_plane_is_available(plane, output)) continue; if (drm_output_check_plane_has_view_assigned(plane, state)) { - drm_debug(b, "\t\t\t\t[plane] not adding plane %d to" - " candidate list: view already assigned " - "to a plane\n", plane->plane_id); + drm_debug(b, "\t\t\t\t[plane] not trying plane %d: " + "another view already assigned\n", + plane->plane_id); continue; } if (plane->zpos_min >= current_lowest_zpos) { - drm_debug(b, "\t\t\t\t[plane] not adding plane %d to " - "candidate list: minimum zpos (%"PRIu64") " - "plane's above current lowest zpos " - "(%"PRIu64")\n", plane->plane_id, - plane->zpos_min, current_lowest_zpos); + drm_debug(b, "\t\t\t\t[plane] not trying plane %d: " + "plane's minimum zpos (%"PRIu64") above " + "current lowest zpos (%"PRIu64")\n", + plane->plane_id, plane->zpos_min, + current_lowest_zpos); continue; } + /* If the surface buffer has an in-fence fd, but the plane doesn't + * support fences, we can't place the buffer on this plane. */ + if (ev->surface->acquire_fence_fd >= 0 && + plane->props[WDRM_PLANE_IN_FENCE_FD].prop_id == 0) { + drm_debug(b, "\t\t\t\t[%s] not placing view %p on %s: " + "no in-fence support\n", p_name, ev, p_name); + return NULL; + } + if (mode == DRM_OUTPUT_PROPOSE_STATE_MIXED) { assert(scanout_state != NULL); if (scanout_state->zpos >= plane->zpos_max) { @@ -746,49 +592,6 @@ drm_output_prepare_plane_view(struct drm_output_state *state, } } - if (mode == DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY && - (plane->type == WDRM_PLANE_TYPE_OVERLAY || - plane->type == WDRM_PLANE_TYPE_PRIMARY)) { - drm_debug(b, "\t\t\t\t[plane] not adding plane %d to " - "candidate list: renderer-only mode\n", - plane->plane_id); - continue; - } - - if (plane->type == WDRM_PLANE_TYPE_CURSOR && - (!shmbuf || wl_shm_buffer_get_format(shmbuf) != WL_SHM_FORMAT_ARGB8888)) { - drm_debug(b, "\t\t\t\t[plane] not adding plane %d, type cursor to " - "candidate list: cursor planes only support ARGB8888" - "wl_shm buffers and the view buffer is of another type\n", - plane->plane_id); - continue; - } - - if (plane->type != WDRM_PLANE_TYPE_CURSOR && - (!fb || !(fb->plane_mask & (1 << plane->plane_idx)))) { - *try_view_on_plane_failure_reasons |= - FAILURE_REASONS_FB_FORMAT_INCOMPATIBLE; - drm_debug(b, "\t\t\t\t[plane] not adding plane %d to " - "candidate list: invalid pixel format\n", - plane->plane_id); - continue; - } - - drm_output_add_zpos_plane(plane, &zpos_candidate_list); - } - - /* go over the potential candidate list and try to find a possible - * plane suitable for \c ev; start with the highest zpos value of a - * plane to maximize our chances, but do note we pass the zpos value - * based on current tracked value by \c current_lowest_zpos_in_use */ - while (!wl_list_empty(&zpos_candidate_list)) { - struct drm_plane_zpos *head_p_zpos = - wl_container_of(zpos_candidate_list.next, - head_p_zpos, link); - struct drm_plane *plane = head_p_zpos->plane; - const char *p_name = drm_output_get_plane_type_name(plane); - uint64_t zpos; - if (current_lowest_zpos == DRM_PLANE_ZPOS_INVALID_PLANE) zpos = plane->zpos_max; else @@ -798,20 +601,32 @@ drm_output_prepare_plane_view(struct drm_output_state *state, "from candidate list, type: %s\n", plane->plane_id, p_name); - ps = drm_output_try_view_on_plane(plane, state, ev, - mode, fb, zpos); - drm_output_destroy_zpos_plane(head_p_zpos); + if (plane->type == WDRM_PLANE_TYPE_CURSOR) { + ps = drm_output_prepare_cursor_view(state, ev, zpos); + } else { + ps = drm_output_try_view_on_plane(plane, state, ev, + mode, fb, zpos); + } + if (ps) { drm_debug(b, "\t\t\t\t[view] view %p has been placed to " "%s plane with computed zpos %"PRIu64"\n", ev, p_name, zpos); break; } + + pnode->try_view_on_plane_failure_reasons |= + FAILURE_REASONS_PLANES_REJECTED; } - wl_list_for_each_safe(p_zpos, p_zpos_next, &zpos_candidate_list, link) - drm_output_destroy_zpos_plane(p_zpos); + if (!ps && + pnode->try_view_on_plane_failure_reasons == FAILURE_REASONS_NONE) { + pnode->try_view_on_plane_failure_reasons |= + FAILURE_REASONS_NO_PLANES_AVAILABLE; + } + /* if we have a plane state, it has its own ref to the fb; if not then + * we drop ours here */ drm_fb_unref(fb); return ps; } @@ -822,7 +637,8 @@ drm_output_propose_state(struct weston_output *output_base, enum drm_output_propose_state_mode mode) { struct drm_output *output = to_drm_output(output_base); - struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_device *device = output->device; + struct drm_backend *b = device->backend; struct weston_paint_node *pnode; struct drm_output_state *state; struct drm_plane_state *scanout_state = NULL; @@ -957,12 +773,29 @@ drm_output_propose_state(struct weston_output *output_base, force_renderer = true; } + if (!b->gbm) { + drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " + "(GBM not available)\n", ev); + force_renderer = true; + } + if (!weston_view_has_valid_buffer(ev)) { drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " "(no buffer available)\n", ev); force_renderer = true; } + /* We can support this with the 'CRTC background colour' property, + * if it is fullscreen (i.e. we disable the primary plane), and + * opaque (as it is only shown in the absence of any covering + * plane, not as a replacement for the primary plane per se). */ + if (ev->surface->buffer_ref.buffer && + ev->surface->buffer_ref.buffer->type == WESTON_BUFFER_SOLID) { + drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " + "(solid-colour surface)\n", ev); + force_renderer = true; + } + if (pnode->surf_xform.transform != NULL || !pnode->surf_xform.identity_pipeline) { drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " @@ -996,15 +829,9 @@ drm_output_propose_state(struct weston_output *output_base, if (!force_renderer) { drm_debug(b, "\t\t\t[plane] started with zpos %"PRIu64"\n", current_lowest_zpos); - ps = drm_output_prepare_plane_view(state, ev, mode, - scanout_state, - current_lowest_zpos, - &pnode->try_view_on_plane_failure_reasons); - /* If we were able to place the view in a plane, set - * failure reasons to none. */ - if (ps) - pnode->try_view_on_plane_failure_reasons = - FAILURE_REASONS_NONE; + ps = drm_output_find_plane_for_view(state, pnode, mode, + scanout_state, + current_lowest_zpos); } else { /* We are forced to place the view in the renderer, set * the failure reason accordingly. */ @@ -1088,21 +915,24 @@ err: } void -drm_assign_planes(struct weston_output *output_base, void *repaint_data) +drm_assign_planes(struct weston_output *output_base) { - struct drm_backend *b = to_drm_backend(output_base->compositor); - struct drm_pending_state *pending_state = repaint_data; struct drm_output *output = to_drm_output(output_base); + struct drm_device *device = output->device; + struct drm_backend *b = device->backend; + struct drm_pending_state *pending_state = device->repaint_data; struct drm_output_state *state = NULL; struct drm_plane_state *plane_state; struct weston_paint_node *pnode; struct weston_plane *primary = &output_base->compositor->primary_plane; enum drm_output_propose_state_mode mode = DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY; + assert(output); + drm_debug(b, "\t[repaint] preparing state for output %s (%lu)\n", output_base->name, (unsigned long) output_base->id); - if (!b->sprites_are_broken && !output->virtual) { + if (!device->sprites_are_broken && !output->virtual && b->gbm) { drm_debug(b, "\t[repaint] trying planes-only build state\n"); state = drm_output_propose_state(output_base, pending_state, mode); if (!state) { @@ -1144,26 +974,24 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) /* Update dmabuf-feedback if needed */ if (ev->surface->dmabuf_feedback) - dmabuf_feedback_maybe_update(b, ev, + dmabuf_feedback_maybe_update(device, ev, pnode->try_view_on_plane_failure_reasons); pnode->try_view_on_plane_failure_reasons = FAILURE_REASONS_NONE; /* Test whether this buffer can ever go into a plane: - * non-shm, or small enough to be a cursor. - * - * Also, keep a reference when using the pixman renderer. - * That makes it possible to do a seamless switch to the GL - * renderer and since the pixman renderer keeps a reference - * to the buffer anyway, there is no side effects. - */ - if (b->use_pixman || - (weston_view_has_valid_buffer(ev) && - (!wl_shm_buffer_get(ev->surface->buffer_ref.buffer->resource) || - (ev->surface->width <= b->cursor_width && - ev->surface->height <= b->cursor_height)))) - ev->surface->keep_buffer = true; - else - ev->surface->keep_buffer = false; + * non-shm, or small enough to be a cursor. */ + ev->surface->keep_buffer = false; + if (weston_view_has_valid_buffer(ev)) { + struct weston_buffer *buffer = + ev->surface->buffer_ref.buffer; + if (buffer->type == WESTON_BUFFER_DMABUF || + buffer->type == WESTON_BUFFER_RENDERER_OPAQUE) + ev->surface->keep_buffer = true; + else if (buffer->type == WESTON_BUFFER_SHM && + (ev->surface->width <= device->cursor_width && + ev->surface->height <= device->cursor_height)) + ev->surface->keep_buffer = true; + } /* This is a bit unpleasant, but lacking a temporary place to * hang a plane off the view, we have to do a nested walk. diff --git a/libweston/backend-fbdev/fbdev.c b/libweston/backend-fbdev/fbdev.c deleted file mode 100644 index 7dfea6fb..00000000 --- a/libweston/backend-fbdev/fbdev.c +++ /dev/null @@ -1,998 +0,0 @@ -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2011 Intel Corporation - * Copyright © 2012 Raspberry Pi Foundation - * Copyright © 2013 Philip Withnall - * - * 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. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "shared/helpers.h" -#include -#include -#include "launcher-util.h" -#include "pixman-renderer.h" -#include "libinput-seat.h" -#include "presentation-time-server-protocol.h" - -struct fbdev_backend { - struct weston_backend base; - struct weston_compositor *compositor; - uint32_t prev_state; - - struct udev *udev; - struct udev_input input; - uint32_t output_transform; - struct wl_listener session_listener; -}; - -struct fbdev_screeninfo { - unsigned int x_resolution; /* pixels, visible area */ - unsigned int y_resolution; /* pixels, visible area */ - unsigned int width_mm; /* visible screen width in mm */ - unsigned int height_mm; /* visible screen height in mm */ - unsigned int bits_per_pixel; - - size_t buffer_length; /* length of frame buffer memory in bytes */ - size_t line_length; /* length of a line in bytes */ - char id[16]; /* screen identifier */ - - pixman_format_code_t pixel_format; /* frame buffer pixel format */ - unsigned int refresh_rate; /* Hertz */ -}; - -struct fbdev_head { - struct weston_head base; - - /* Frame buffer details. */ - char *device; - struct fbdev_screeninfo fb_info; -}; - -struct fbdev_output { - struct fbdev_backend *backend; - struct weston_output base; - - struct weston_mode mode; - struct wl_event_source *finish_frame_timer; - - /* framebuffer mmap details */ - size_t buffer_length; - void *fb; - - /* pixman details. */ - pixman_image_t *hw_surface; -}; - -static const char default_seat[] = "seat0"; - -static inline struct fbdev_head * -to_fbdev_head(struct weston_head *base) -{ - return container_of(base, struct fbdev_head, base); -} - -static inline struct fbdev_output * -to_fbdev_output(struct weston_output *base) -{ - return container_of(base, struct fbdev_output, base); -} - -static inline struct fbdev_backend * -to_fbdev_backend(struct weston_compositor *base) -{ - return container_of(base->backend, struct fbdev_backend, base); -} - -static struct fbdev_head * -fbdev_output_get_head(struct fbdev_output *output) -{ - if (wl_list_length(&output->base.head_list) != 1) - return NULL; - - return container_of(output->base.head_list.next, - struct fbdev_head, base.output_link); -} - -static int -fbdev_output_start_repaint_loop(struct weston_output *output) -{ - struct timespec ts; - - weston_compositor_read_presentation_clock(output->compositor, &ts); - weston_output_finish_frame(output, &ts, WP_PRESENTATION_FEEDBACK_INVALID); - - return 0; -} - -static int -fbdev_output_repaint(struct weston_output *base, pixman_region32_t *damage, - void *repaint_data) -{ - struct fbdev_output *output = to_fbdev_output(base); - struct weston_compositor *ec = output->base.compositor; - - /* Repaint the damaged region onto the back buffer. */ - pixman_renderer_output_set_buffer(base, output->hw_surface); - ec->renderer->repaint_output(base, damage); - - /* Update the damage region. */ - pixman_region32_subtract(&ec->primary_plane.damage, - &ec->primary_plane.damage, damage); - - /* Schedule the end of the frame. We do not sync this to the frame - * buffer clock because users who want that should be using the DRM - * compositor. FBIO_WAITFORVSYNC blocks and FB_ACTIVATE_VBL requires - * panning, which is broken in most kernel drivers. - * - * Finish the frame synchronised to the specified refresh rate. The - * refresh rate is given in mHz and the interval in ms. */ - wl_event_source_timer_update(output->finish_frame_timer, - 1000000 / output->mode.refresh); - - return 0; -} - -static int -finish_frame_handler(void *data) -{ - struct fbdev_output *output = data; - struct timespec ts; - - weston_compositor_read_presentation_clock(output->base.compositor, &ts); - weston_output_finish_frame(&output->base, &ts, 0); - - return 1; -} - -static pixman_format_code_t -calculate_pixman_format(struct fb_var_screeninfo *vinfo, - struct fb_fix_screeninfo *finfo) -{ - /* Calculate the pixman format supported by the frame buffer from the - * buffer's metadata. Return 0 if no known pixman format is supported - * (since this has depth 0 it's guaranteed to not conflict with any - * actual pixman format). - * - * Documentation on the vinfo and finfo structures: - * http://www.mjmwired.net/kernel/Documentation/fb/api.txt - * - * TODO: Try a bit harder to support other formats, including setting - * the preferred format in the hardware. */ - int type; - - weston_log("Calculating pixman format from:\n" - STAMP_SPACE " - type: %i (aux: %i)\n" - STAMP_SPACE " - visual: %i\n" - STAMP_SPACE " - bpp: %i (grayscale: %i)\n" - STAMP_SPACE " - red: offset: %i, length: %i, MSB: %i\n" - STAMP_SPACE " - green: offset: %i, length: %i, MSB: %i\n" - STAMP_SPACE " - blue: offset: %i, length: %i, MSB: %i\n" - STAMP_SPACE " - transp: offset: %i, length: %i, MSB: %i\n", - finfo->type, finfo->type_aux, finfo->visual, - vinfo->bits_per_pixel, vinfo->grayscale, - vinfo->red.offset, vinfo->red.length, vinfo->red.msb_right, - vinfo->green.offset, vinfo->green.length, - vinfo->green.msb_right, - vinfo->blue.offset, vinfo->blue.length, - vinfo->blue.msb_right, - vinfo->transp.offset, vinfo->transp.length, - vinfo->transp.msb_right); - - /* We only handle packed formats at the moment. */ - if (finfo->type != FB_TYPE_PACKED_PIXELS) - return 0; - - /* We only handle true-colour frame buffers at the moment. */ - switch(finfo->visual) { - case FB_VISUAL_TRUECOLOR: - case FB_VISUAL_DIRECTCOLOR: - if (vinfo->grayscale != 0) - return 0; - break; - default: - return 0; - } - - /* We only support formats with MSBs on the left. */ - if (vinfo->red.msb_right != 0 || vinfo->green.msb_right != 0 || - vinfo->blue.msb_right != 0) - return 0; - - /* Work out the format type from the offsets. We only support RGBA, ARGB - * and ABGR at the moment. */ - type = PIXMAN_TYPE_OTHER; - - if ((vinfo->transp.offset >= vinfo->red.offset || - vinfo->transp.length == 0) && - vinfo->red.offset >= vinfo->green.offset && - vinfo->green.offset >= vinfo->blue.offset) - type = PIXMAN_TYPE_ARGB; - else if (vinfo->red.offset >= vinfo->green.offset && - vinfo->green.offset >= vinfo->blue.offset && - vinfo->blue.offset >= vinfo->transp.offset) - type = PIXMAN_TYPE_RGBA; - else if (vinfo->transp.offset >= vinfo->blue.offset && - vinfo->blue.offset >= vinfo->green.offset && - vinfo->green.offset >= vinfo->red.offset) - type = PIXMAN_TYPE_ABGR; - - if (type == PIXMAN_TYPE_OTHER) - return 0; - - /* Build the format. */ - return PIXMAN_FORMAT(vinfo->bits_per_pixel, type, - vinfo->transp.length, - vinfo->red.length, - vinfo->green.length, - vinfo->blue.length); -} - -static int -calculate_refresh_rate(struct fb_var_screeninfo *vinfo) -{ - uint64_t quot; - - /* Calculate monitor refresh rate. Default is 60 Hz. Units are mHz. */ - quot = (vinfo->upper_margin + vinfo->lower_margin + vinfo->yres); - quot *= (vinfo->left_margin + vinfo->right_margin + vinfo->xres); - quot *= vinfo->pixclock; - - if (quot > 0) { - uint64_t refresh_rate; - - refresh_rate = 1000000000000000LLU / quot; - if (refresh_rate > 200000) - refresh_rate = 200000; /* cap at 200 Hz */ - - if (refresh_rate >= 1000) /* at least 1 Hz */ - return refresh_rate; - } - - return 60 * 1000; /* default to 60 Hz */ -} - -static int -fbdev_query_screen_info(int fd, struct fbdev_screeninfo *info) -{ - struct fb_var_screeninfo varinfo; - struct fb_fix_screeninfo fixinfo; - - /* Probe the device for screen information. */ - if (ioctl(fd, FBIOGET_FSCREENINFO, &fixinfo) < 0 || - ioctl(fd, FBIOGET_VSCREENINFO, &varinfo) < 0) { - return -1; - } - - /* Store the pertinent data. */ - info->x_resolution = varinfo.xres; - info->y_resolution = varinfo.yres; - info->width_mm = varinfo.width; - info->height_mm = varinfo.height; - info->bits_per_pixel = varinfo.bits_per_pixel; - - info->buffer_length = fixinfo.smem_len; - info->line_length = fixinfo.line_length; - strncpy(info->id, fixinfo.id, sizeof(info->id)); - info->id[sizeof(info->id)-1] = '\0'; - - info->pixel_format = calculate_pixman_format(&varinfo, &fixinfo); - info->refresh_rate = calculate_refresh_rate(&varinfo); - - if (info->pixel_format == 0) { - weston_log("Frame buffer uses an unsupported format.\n"); - return -1; - } - - return 1; -} - -static int -fbdev_set_screen_info(int fd, struct fbdev_screeninfo *info) -{ - struct fb_var_screeninfo varinfo; - - /* Grab the current screen information. */ - if (ioctl(fd, FBIOGET_VSCREENINFO, &varinfo) < 0) { - return -1; - } - - /* Update the information. */ - varinfo.xres = info->x_resolution; - varinfo.yres = info->y_resolution; - varinfo.width = info->width_mm; - varinfo.height = info->height_mm; - varinfo.bits_per_pixel = info->bits_per_pixel; - - /* Try to set up an ARGB (x8r8g8b8) pixel format. */ - varinfo.grayscale = 0; - varinfo.transp.offset = 24; - varinfo.transp.length = 0; - varinfo.transp.msb_right = 0; - varinfo.red.offset = 16; - varinfo.red.length = 8; - varinfo.red.msb_right = 0; - varinfo.green.offset = 8; - varinfo.green.length = 8; - varinfo.green.msb_right = 0; - varinfo.blue.offset = 0; - varinfo.blue.length = 8; - varinfo.blue.msb_right = 0; - - /* Set the device's screen information. */ - if (ioctl(fd, FBIOPUT_VSCREENINFO, &varinfo) < 0) { - return -1; - } - - return 1; -} - -static int -fbdev_wakeup_screen(int fd, struct fbdev_screeninfo *info) -{ - struct fb_var_screeninfo varinfo; - - /* Grab the current screen information. */ - if (ioctl(fd, FBIOGET_VSCREENINFO, &varinfo) < 0) { - return -1; - } - - /* force the framebuffer to wake up */ - varinfo.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE; - - /* Set the device's screen information. */ - if (ioctl(fd, FBIOPUT_VSCREENINFO, &varinfo) < 0) { - return -1; - } - - return 1; -} - -/* Returns an FD for the frame buffer device. */ -static int -fbdev_frame_buffer_open(const char *fb_dev, - struct fbdev_screeninfo *screen_info) -{ - int fd = -1; - - weston_log("Opening fbdev frame buffer.\n"); - - /* Open the frame buffer device. */ - fd = open(fb_dev, O_RDWR | O_CLOEXEC); - if (fd < 0) { - weston_log("Failed to open frame buffer device ‘%s’: %s\n", - fb_dev, strerror(errno)); - return -1; - } - - /* Grab the screen info. */ - if (fbdev_query_screen_info(fd, screen_info) < 0) { - weston_log("Failed to get frame buffer info: %s\n", - strerror(errno)); - - close(fd); - return -1; - } - - /* Attempt to wake up the framebuffer device, needed for secondary - * framebuffer devices */ - if (fbdev_wakeup_screen(fd, screen_info) < 0) { - weston_log("Failed to activate framebuffer display. " - "Attempting to open output anyway.\n"); - } - - - return fd; -} - -/* Closes the FD on success or failure. */ -static int -fbdev_frame_buffer_map(struct fbdev_output *output, int fd) -{ - struct fbdev_head *head; - int retval = -1; - - head = fbdev_output_get_head(output); - - weston_log("Mapping fbdev frame buffer.\n"); - - /* Map the frame buffer. Write-only mode, since we don't want to read - * anything back (because it's slow). */ - output->buffer_length = head->fb_info.buffer_length; - output->fb = mmap(NULL, output->buffer_length, - PROT_WRITE, MAP_SHARED, fd, 0); - if (output->fb == MAP_FAILED) { - weston_log("Failed to mmap frame buffer: %s\n", - strerror(errno)); - output->fb = NULL; - goto out_close; - } - - /* Create a pixman image to wrap the memory mapped frame buffer. */ - output->hw_surface = - pixman_image_create_bits(head->fb_info.pixel_format, - head->fb_info.x_resolution, - head->fb_info.y_resolution, - output->fb, - head->fb_info.line_length); - if (output->hw_surface == NULL) { - weston_log("Failed to create surface for frame buffer.\n"); - goto out_unmap; - } - - /* Success! */ - retval = 0; - -out_unmap: - if (retval != 0 && output->fb != NULL) { - munmap(output->fb, output->buffer_length); - output->fb = NULL; - } - -out_close: - if (fd >= 0) - close(fd); - - return retval; -} - -static void -fbdev_frame_buffer_unmap(struct fbdev_output *output) -{ - if (!output->fb) { - assert(!output->hw_surface); - return; - } - - weston_log("Unmapping fbdev frame buffer.\n"); - - if (output->hw_surface) - pixman_image_unref(output->hw_surface); - output->hw_surface = NULL; - - if (munmap(output->fb, output->buffer_length) < 0) - weston_log("Failed to munmap frame buffer: %s\n", - strerror(errno)); - - output->fb = NULL; -} - - -static int -fbdev_output_attach_head(struct weston_output *output_base, - struct weston_head *head_base) -{ - struct fbdev_output *output = to_fbdev_output(output_base); - struct fbdev_head *head = to_fbdev_head(head_base); - - /* Clones not supported. */ - if (!wl_list_empty(&output->base.head_list)) - return -1; - - /* only one static mode in list */ - output->mode.flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; - output->mode.width = head->fb_info.x_resolution; - output->mode.height = head->fb_info.y_resolution; - output->mode.refresh = head->fb_info.refresh_rate; - wl_list_init(&output->base.mode_list); - wl_list_insert(&output->base.mode_list, &output->mode.link); - output->base.current_mode = &output->mode; - - return 0; -} - -static void fbdev_output_destroy(struct weston_output *base); - -static int -fbdev_output_enable(struct weston_output *base) -{ - struct fbdev_output *output = to_fbdev_output(base); - struct fbdev_backend *backend = to_fbdev_backend(base->compositor); - struct fbdev_head *head; - int fb_fd; - struct wl_event_loop *loop; - const struct pixman_renderer_output_options options = { - .use_shadow = true, - }; - - head = fbdev_output_get_head(output); - - /* Create the frame buffer. */ - fb_fd = fbdev_frame_buffer_open(head->device, &head->fb_info); - if (fb_fd < 0) { - weston_log("Creating frame buffer failed.\n"); - return -1; - } - - if (fbdev_frame_buffer_map(output, fb_fd) < 0) { - weston_log("Mapping frame buffer failed.\n"); - return -1; - } - - output->base.start_repaint_loop = fbdev_output_start_repaint_loop; - output->base.repaint = fbdev_output_repaint; - - if (pixman_renderer_output_create(&output->base, &options) < 0) - goto out_hw_surface; - - loop = wl_display_get_event_loop(backend->compositor->wl_display); - output->finish_frame_timer = - wl_event_loop_add_timer(loop, finish_frame_handler, output); - - weston_log("fbdev output %d×%d px\n", - output->mode.width, output->mode.height); - weston_log_continue(STAMP_SPACE "guessing %d Hz and 96 dpi\n", - output->mode.refresh / 1000); - - return 0; - -out_hw_surface: - fbdev_frame_buffer_unmap(output); - - return -1; -} - -static int -fbdev_output_disable(struct weston_output *base) -{ - struct fbdev_output *output = to_fbdev_output(base); - - if (!base->enabled) - return 0; - - wl_event_source_remove(output->finish_frame_timer); - output->finish_frame_timer = NULL; - - pixman_renderer_output_destroy(&output->base); - fbdev_frame_buffer_unmap(output); - - return 0; -} - -static struct fbdev_head * -fbdev_head_create(struct fbdev_backend *backend, const char *device) -{ - struct fbdev_head *head; - int fb_fd; - - head = zalloc(sizeof *head); - if (!head) - return NULL; - - head->device = strdup(device); - - /* Create the frame buffer. */ - fb_fd = fbdev_frame_buffer_open(head->device, &head->fb_info); - if (fb_fd < 0) { - weston_log("Creating frame buffer head failed.\n"); - goto out_free; - } - close(fb_fd); - - weston_head_init(&head->base, "fbdev"); - weston_head_set_connection_status(&head->base, true); - weston_head_set_monitor_strings(&head->base, "unknown", - head->fb_info.id, NULL); - weston_head_set_subpixel(&head->base, WL_OUTPUT_SUBPIXEL_UNKNOWN); - weston_head_set_physical_size(&head->base, head->fb_info.width_mm, - head->fb_info.height_mm); - - weston_compositor_add_head(backend->compositor, &head->base); - - weston_log("Created head '%s' for device %s (%s)\n", - head->base.name, head->device, head->base.model); - - return head; - -out_free: - free(head->device); - free(head); - - return NULL; -} - -static void -fbdev_head_destroy(struct fbdev_head *head) -{ - weston_head_release(&head->base); - free(head->device); - free(head); -} - -static struct weston_output * -fbdev_output_create(struct weston_compositor *compositor, - const char *name) -{ - struct fbdev_output *output; - - weston_log("Creating fbdev output.\n"); - - output = zalloc(sizeof *output); - if (output == NULL) - return NULL; - - output->backend = to_fbdev_backend(compositor); - - weston_output_init(&output->base, compositor, name); - - output->base.destroy = fbdev_output_destroy; - output->base.disable = fbdev_output_disable; - output->base.enable = fbdev_output_enable; - output->base.attach_head = fbdev_output_attach_head; - - weston_compositor_add_pending_output(&output->base, compositor); - - return &output->base; -} - -static void -fbdev_output_destroy(struct weston_output *base) -{ - struct fbdev_output *output = to_fbdev_output(base); - - weston_log("Destroying fbdev output.\n"); - - fbdev_output_disable(base); - - /* Remove the output. */ - weston_output_release(&output->base); - - free(output); -} - -/* strcmp()-style return values. */ -static int -compare_screen_info (const struct fbdev_screeninfo *a, - const struct fbdev_screeninfo *b) -{ - if (a->x_resolution == b->x_resolution && - a->y_resolution == b->y_resolution && - a->width_mm == b->width_mm && - a->height_mm == b->height_mm && - a->bits_per_pixel == b->bits_per_pixel && - a->pixel_format == b->pixel_format && - a->refresh_rate == b->refresh_rate) - return 0; - - return 1; -} - -static int -fbdev_output_reenable(struct fbdev_backend *backend, - struct weston_output *base) -{ - struct fbdev_output *output = to_fbdev_output(base); - struct fbdev_head *head; - struct fbdev_screeninfo new_screen_info; - int fb_fd; - - head = fbdev_output_get_head(output); - - weston_log("Re-enabling fbdev output.\n"); - assert(output->base.enabled); - - /* Create the frame buffer. */ - fb_fd = fbdev_frame_buffer_open(head->device, &new_screen_info); - if (fb_fd < 0) { - weston_log("Creating frame buffer failed.\n"); - return -1; - } - - /* Check whether the frame buffer details have changed since we were - * disabled. */ - if (compare_screen_info(&head->fb_info, &new_screen_info) != 0) { - /* Perform a mode-set to restore the old mode. */ - if (fbdev_set_screen_info(fb_fd, &head->fb_info) < 0) { - weston_log("Failed to restore mode settings. " - "Attempting to re-open output anyway.\n"); - } - - close(fb_fd); - - /* Disable and enable the output so that resources depending on - * the frame buffer X/Y resolution (such as the shadow buffer) - * are re-initialised. */ - fbdev_output_disable(&output->base); - return fbdev_output_enable(&output->base); - } - - /* Map the device if it has the same details as before. */ - if (fbdev_frame_buffer_map(output, fb_fd) < 0) { - weston_log("Mapping frame buffer failed.\n"); - return -1; - } - - return 0; -} - -static void -fbdev_backend_destroy(struct weston_compositor *base) -{ - struct fbdev_backend *backend = to_fbdev_backend(base); - struct weston_head *head, *next; - - udev_input_destroy(&backend->input); - - /* Destroy the output. */ - weston_compositor_shutdown(base); - - wl_list_for_each_safe(head, next, &base->head_list, compositor_link) - fbdev_head_destroy(to_fbdev_head(head)); - - /* Chain up. */ - weston_launcher_destroy(base->launcher); - - udev_unref(backend->udev); - - free(backend); -} - -static void -session_notify(struct wl_listener *listener, void *data) -{ - struct weston_compositor *compositor = data; - struct fbdev_backend *backend = to_fbdev_backend(compositor); - struct weston_output *output; - - if (compositor->session_active) { - weston_log("entering VT\n"); - compositor->state = backend->prev_state; - - wl_list_for_each(output, &compositor->output_list, link) { - fbdev_output_reenable(backend, output); - } - - weston_compositor_damage_all(compositor); - - udev_input_enable(&backend->input); - } else { - weston_log("leaving VT\n"); - udev_input_disable(&backend->input); - - wl_list_for_each(output, &compositor->output_list, link) { - fbdev_frame_buffer_unmap(to_fbdev_output(output)); - } - - backend->prev_state = compositor->state; - weston_compositor_offscreen(compositor); - - /* If we have a repaint scheduled (from the idle handler), make - * sure we cancel that so we don't try to pageflip when we're - * vt switched away. The OFFSCREEN state will prevent - * further attempts at repainting. When we switch - * back, we schedule a repaint, which will process - * pending frame callbacks. */ - - wl_list_for_each(output, - &compositor->output_list, link) { - output->repaint_needed = false; - } - } -} - -static char * -find_framebuffer_device(struct fbdev_backend *b, const char *seat) -{ - struct udev_enumerate *e; - struct udev_list_entry *entry; - const char *path, *device_seat, *id; - char *fb_device_path = NULL; - struct udev_device *device, *fb_device, *pci; - - e = udev_enumerate_new(b->udev); - udev_enumerate_add_match_subsystem(e, "graphics"); - udev_enumerate_add_match_sysname(e, "fb[0-9]*"); - - udev_enumerate_scan_devices(e); - fb_device = NULL; - udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { - bool is_boot_vga = false; - - path = udev_list_entry_get_name(entry); - device = udev_device_new_from_syspath(b->udev, path); - if (!device) - continue; - device_seat = udev_device_get_property_value(device, "ID_SEAT"); - if (!device_seat) - device_seat = default_seat; - if (strcmp(device_seat, seat)) { - udev_device_unref(device); - continue; - } - - pci = udev_device_get_parent_with_subsystem_devtype(device, - "pci", NULL); - if (pci) { - id = udev_device_get_sysattr_value(pci, "boot_vga"); - if (id && !strcmp(id, "1")) - is_boot_vga = true; - } - - /* If a framebuffer device was found, and this device isn't - * the boot-VGA device, don't use it. */ - if (!is_boot_vga && fb_device) { - udev_device_unref(device); - continue; - } - - /* There can only be one boot_vga device. Try to use it - * at all costs. */ - if (is_boot_vga) { - if (fb_device) - udev_device_unref(fb_device); - fb_device = device; - break; - } - - /* Per the (!is_boot_vga && fb_device) test above, only - * trump existing saved devices with boot-VGA devices, so if - * the test ends up here, this must be the first device seen. */ - assert(!fb_device); - fb_device = device; - } - - udev_enumerate_unref(e); - - if (fb_device) { - fb_device_path = strdup(udev_device_get_devnode(fb_device)); - udev_device_unref(fb_device); - } - - return fb_device_path; -} - -static struct fbdev_backend * -fbdev_backend_create(struct weston_compositor *compositor, - struct weston_fbdev_backend_config *param) -{ - struct fbdev_backend *backend; - const char *seat_id = default_seat; - const char *session_seat; - - session_seat = getenv("XDG_SEAT"); - if (session_seat) - seat_id = session_seat; - if (param->seat_id) - seat_id = param->seat_id; - - weston_log("initializing fbdev backend\n"); - weston_log("warning: the fbdev backend is deprecated, please migrate " - "to the DRM backend\n"); - - backend = zalloc(sizeof *backend); - if (backend == NULL) - return NULL; - - backend->compositor = compositor; - compositor->backend = &backend->base; - if (weston_compositor_set_presentation_clock_software( - compositor) < 0) - goto out_compositor; - - backend->udev = udev_new(); - if (backend->udev == NULL) { - weston_log("Failed to initialize udev context.\n"); - goto out_compositor; - } - - if (!param->device) - param->device = find_framebuffer_device(backend, seat_id); - if (!param->device) { - weston_log("fatal: no framebuffer devices detected.\n"); - goto out_udev; - } - - /* Set up the TTY. */ - backend->session_listener.notify = session_notify; - wl_signal_add(&compositor->session_signal, - &backend->session_listener); - compositor->launcher = - weston_launcher_connect(compositor, param->tty, seat_id, false); - if (!compositor->launcher) { - weston_log("fatal: fbdev backend should be run using " - "weston-launch binary, or your system should " - "provide the logind D-Bus API.\n"); - goto out_udev; - } - - backend->base.destroy = fbdev_backend_destroy; - backend->base.create_output = fbdev_output_create; - - backend->prev_state = WESTON_COMPOSITOR_ACTIVE; - - weston_setup_vt_switch_bindings(compositor); - - if (pixman_renderer_init(compositor) < 0) - goto out_launcher; - - if (!fbdev_head_create(backend, param->device)) - goto out_launcher; - - free(param->device); - - udev_input_init(&backend->input, compositor, backend->udev, - seat_id, param->configure_device); - - return backend; - -out_launcher: - free(param->device); - weston_launcher_destroy(compositor->launcher); - -out_udev: - udev_unref(backend->udev); - -out_compositor: - weston_compositor_shutdown(compositor); - free(backend); - - return NULL; -} - -static void -config_init_to_defaults(struct weston_fbdev_backend_config *config) -{ - config->tty = 0; /* default to current tty */ - config->device = NULL; - config->seat_id = NULL; -} - -WL_EXPORT int -weston_backend_init(struct weston_compositor *compositor, - struct weston_backend_config *config_base) -{ - struct fbdev_backend *b; - struct weston_fbdev_backend_config config = {{ 0, }}; - - if (config_base == NULL || - config_base->struct_version != WESTON_FBDEV_BACKEND_CONFIG_VERSION || - config_base->struct_size > sizeof(struct weston_fbdev_backend_config)) { - weston_log("fbdev backend config structure is invalid\n"); - return -1; - } - - config_init_to_defaults(&config); - memcpy(&config, config_base, config_base->struct_size); - - b = fbdev_backend_create(compositor, &config); - if (b == NULL) - return -1; - return 0; -} diff --git a/libweston/backend-fbdev/meson.build b/libweston/backend-fbdev/meson.build deleted file mode 100644 index 563ad6c5..00000000 --- a/libweston/backend-fbdev/meson.build +++ /dev/null @@ -1,33 +0,0 @@ -if not get_option('deprecated-backend-fbdev') - subdir_done() -endif - -warning('Support for the deprecated fbdev backend is enabled.') -warning('This feature will be removed in a future version.') - -config_h.set('BUILD_FBDEV_COMPOSITOR', '1') - -srcs_fbdev = [ - 'fbdev.c', - presentation_time_server_protocol_h, -] - -deps_fbdev = [ - dep_libweston_private, - dep_session_helper, - dep_libinput_backend, - dependency('libudev', version: '>= 136'), -] - -plugin_fbdev = shared_library( - 'fbdev-backend', - srcs_fbdev, - include_directories: common_inc, - dependencies: deps_fbdev, - name_prefix: '', - install: true, - install_dir: dir_module_libweston -) -env_modmap += 'fbdev-backend.so=@0@;'.format(plugin_fbdev.full_path()) - -install_headers(backend_fbdev_h, subdir: dir_include_libweston_install) diff --git a/libweston/backend-headless/headless.c b/libweston/backend-headless/headless.c index 5c4e4ccb..83de39dc 100644 --- a/libweston/backend-headless/headless.c +++ b/libweston/backend-headless/headless.c @@ -37,6 +37,7 @@ #include #include "shared/helpers.h" #include "linux-explicit-synchronization.h" +#include "pixel-formats.h" #include "pixman-renderer.h" #include "renderer-gl/gl-renderer.h" #include "shared/weston-drm-fourcc.h" @@ -70,24 +71,33 @@ struct headless_output { struct weston_mode mode; struct wl_event_source *finish_frame_timer; - uint32_t *image_buf; pixman_image_t *image; }; static const uint32_t headless_formats[] = { - DRM_FORMAT_XRGB8888, + DRM_FORMAT_XRGB8888, /* default for pixman-renderer */ DRM_FORMAT_ARGB8888, }; +static void +headless_head_destroy(struct weston_head *base); + static inline struct headless_head * to_headless_head(struct weston_head *base) { + if (base->backend_id != headless_head_destroy) + return NULL; return container_of(base, struct headless_head, base); } +static void +headless_output_destroy(struct weston_output *base); + static inline struct headless_output * to_headless_output(struct weston_output *base) { + if (base->destroy != headless_output_destroy) + return NULL; return container_of(base, struct headless_output, base); } @@ -122,11 +132,14 @@ finish_frame_handler(void *data) static int headless_output_repaint(struct weston_output *output_base, - pixman_region32_t *damage, - void *repaint_data) + pixman_region32_t *damage) { struct headless_output *output = to_headless_output(output_base); - struct weston_compositor *ec = output->base.compositor; + struct weston_compositor *ec; + + assert(output); + + ec = output->base.compositor; ec->renderer->repaint_output(&output->base, damage); @@ -152,18 +165,21 @@ headless_output_disable_pixman(struct headless_output *output) { pixman_renderer_output_destroy(&output->base); pixman_image_unref(output->image); - free(output->image_buf); } static int headless_output_disable(struct weston_output *base) { struct headless_output *output = to_headless_output(base); - struct headless_backend *b = to_headless_backend(base->compositor); + struct headless_backend *b; + + assert(output); if (!output->base.enabled) return 0; + b = to_headless_backend(base->compositor); + wl_event_source_remove(output->finish_frame_timer); switch (b->renderer_type) { @@ -185,6 +201,8 @@ headless_output_destroy(struct weston_output *base) { struct headless_output *output = to_headless_output(base); + assert(output); + headless_output_disable(&output->base); weston_output_release(&output->base); @@ -214,20 +232,20 @@ headless_output_enable_gl(struct headless_output *output) static int headless_output_enable_pixman(struct headless_output *output) { + const struct pixel_format_info *pfmt; const struct pixman_renderer_output_options options = { .use_shadow = true, }; - output->image_buf = malloc(output->base.current_mode->width * - output->base.current_mode->height * 4); - if (!output->image_buf) - return -1; + pfmt = pixel_format_get_info(headless_formats[0]); - output->image = pixman_image_create_bits(PIXMAN_x8r8g8b8, - output->base.current_mode->width, - output->base.current_mode->height, - output->image_buf, - output->base.current_mode->width * 4); + output->image = + pixman_image_create_bits_no_clear(pfmt->pixman_format, + output->base.current_mode->width, + output->base.current_mode->height, + NULL, 0); + if (!output->image) + return -1; if (pixman_renderer_output_create(&output->base, &options) < 0) goto err_renderer; @@ -238,7 +256,6 @@ headless_output_enable_pixman(struct headless_output *output) err_renderer: pixman_image_unref(output->image); - free(output->image_buf); return -1; } @@ -247,10 +264,14 @@ static int headless_output_enable(struct weston_output *base) { struct headless_output *output = to_headless_output(base); - struct headless_backend *b = to_headless_backend(base->compositor); + struct headless_backend *b; struct wl_event_loop *loop; int ret = 0; + assert(output); + + b = to_headless_backend(base->compositor); + loop = wl_display_get_event_loop(b->compositor->wl_display); output->finish_frame_timer = wl_event_loop_add_timer(loop, finish_frame_handler, output); @@ -287,6 +308,9 @@ headless_output_set_size(struct weston_output *base, struct weston_head *head; int output_width, output_height; + if (!output) + return -1; + /* We can only be called once. */ assert(!output->base.current_mode); @@ -361,7 +385,12 @@ headless_head_create(struct weston_compositor *compositor, return -1; weston_head_init(&head->base, name); + + head->base.backend_id = headless_head_destroy; + weston_head_set_connection_status(&head->base, true); + weston_head_set_supported_eotf_mask(&head->base, + WESTON_EOTF_MODE_ALL_MASK); /* Ideally all attributes of the head would be set here, so that the * user has all the information when deciding to create outputs. @@ -374,8 +403,12 @@ headless_head_create(struct weston_compositor *compositor, } static void -headless_head_destroy(struct headless_head *head) +headless_head_destroy(struct weston_head *base) { + struct headless_head *head = to_headless_head(base); + + assert(head); + weston_head_release(&head->base); free(head); } @@ -388,8 +421,10 @@ headless_destroy(struct weston_compositor *ec) weston_compositor_shutdown(ec); - wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) - headless_head_destroy(to_headless_head(base)); + wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) { + if (to_headless_head(base)) + headless_head_destroy(base); + } free(b); } diff --git a/libweston/backend-rdp/meson.build b/libweston/backend-rdp/meson.build index 4f34fdbb..2c057066 100644 --- a/libweston/backend-rdp/meson.build +++ b/libweston/backend-rdp/meson.build @@ -4,24 +4,36 @@ endif config_h.set('BUILD_RDP_COMPOSITOR', '1') -dep_frdp = dependency('freerdp2', version: '>= 2.2.0', required: false) +dep_frdp = dependency('freerdp2', version: '>= 2.3.0', required: false) if not dep_frdp.found() - error('RDP-backend requires freerdp >= 2.2.0 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') + error('RDP-backend requires freerdp >= 2.3.0 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') endif -dep_wpr = dependency('winpr2', version: '>= 2.2.0', required: false) +dep_frdp_server = dependency('freerdp-server2', version: '>= 2.3.0', required: false) +if not dep_frdp_server.found() + error('RDP-backend requires freerdp-server2 >= 2.3.0 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') +endif + +dep_wpr = dependency('winpr2', version: '>= 2.3.0', required: false) if not dep_wpr.found() - error('RDP-backend requires winpr >= 2.2.0 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') + error('RDP-backend requires winpr >= 2.3.0 which was not found. Or, you can use \'-Dbackend-rdp=false\'.') endif deps_rdp = [ dep_libweston_private, dep_frdp, + dep_frdp_server, dep_wpr, ] +srcs_rdp = [ + 'rdp.c', + 'rdpclip.c', + 'rdputil.c', +] + plugin_rdp = shared_library( 'rdp-backend', - 'rdp.c', + srcs_rdp, include_directories: common_inc, dependencies: deps_rdp, name_prefix: '', diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index 8e333f4c..894ae5d2 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -31,104 +31,27 @@ #include #include #include +#include + +#include "rdp.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include -#include "shared/helpers.h" #include "shared/timespec-util.h" #include #include #include "pixman-renderer.h" -#define MAX_FREERDP_FDS 32 -#define DEFAULT_AXIS_STEP_DISTANCE 10 -#define RDP_MODE_FREQ 60 * 1000 -#define DEFAULT_PIXEL_FORMAT PIXEL_FORMAT_BGRA32 - -struct rdp_output; - -struct rdp_backend { - struct weston_backend base; - struct weston_compositor *compositor; - - freerdp_listener *listener; - struct wl_event_source *listener_events[MAX_FREERDP_FDS]; - struct rdp_output *output; - - char *server_cert; - char *server_key; - char *rdp_key; - int tls_enabled; - int no_clients_resize; - int force_no_compression; -}; - -enum peer_item_flags { - RDP_PEER_ACTIVATED = (1 << 0), - RDP_PEER_OUTPUT_ENABLED = (1 << 1), -}; - -struct rdp_peers_item { - int flags; - freerdp_peer *peer; - struct weston_seat *seat; - - struct wl_list link; -}; - -struct rdp_head { - struct weston_head base; -}; - -struct rdp_output { - struct weston_output base; - struct wl_event_source *finish_frame_timer; - pixman_image_t *shadow_surface; - - struct wl_list peers; -}; - -struct rdp_peer_context { - rdpContext _p; - - struct rdp_backend *rdpBackend; - struct wl_event_source *events[MAX_FREERDP_FDS]; - RFX_CONTEXT *rfx_context; - wStream *encode_stream; - RFX_RECT *rfx_rects; - NSC_CONTEXT *nsc_context; - - struct rdp_peers_item item; -}; -typedef struct rdp_peer_context RdpPeerContext; - -static inline struct rdp_head * -to_rdp_head(struct weston_head *base) -{ - return container_of(base, struct rdp_head, base); -} - -static inline struct rdp_output * -to_rdp_output(struct weston_output *base) -{ - return container_of(base, struct rdp_output, base); -} +/* These can be removed when we bump FreeRDP dependency past 3.0.0 in the future */ +#ifndef KBD_PERSIAN +#define KBD_PERSIAN 0x50429 +#endif +#ifndef KBD_HEBREW_STANDARD +#define KBD_HEBREW_STANDARD 0x2040D +#endif -static inline struct rdp_backend * -to_rdp_backend(struct weston_compositor *base) -{ - return container_of(base->backend, struct rdp_backend, base); -} +extern PWtsApiFunctionTable FreeRDP_InitWtsApi(void); static void rdp_peer_refresh_rfx(pixman_region32_t *damage, pixman_image_t *image, freerdp_peer *peer) @@ -137,7 +60,7 @@ rdp_peer_refresh_rfx(pixman_region32_t *damage, pixman_image_t *image, freerdp_p pixman_box32_t *region, *rects; uint32_t *ptr; RFX_RECT *rfxRect; - rdpUpdate *update = peer->update; + rdpUpdate *update = peer->context->update; SURFACE_BITS_COMMAND cmd = { 0 }; RdpPeerContext *context = (RdpPeerContext *)peer->context; @@ -154,7 +77,7 @@ rdp_peer_refresh_rfx(pixman_region32_t *damage, pixman_image_t *image, freerdp_p cmd.destRight = damage->extents.x2; cmd.destBottom = damage->extents.y2; cmd.bmp.bpp = 32; - cmd.bmp.codecID = peer->settings->RemoteFxCodecId; + cmd.bmp.codecID = peer->context->settings->RemoteFxCodecId; cmd.bmp.width = width; cmd.bmp.height = height; @@ -191,7 +114,7 @@ rdp_peer_refresh_nsc(pixman_region32_t *damage, pixman_image_t *image, freerdp_p { int width, height; uint32_t *ptr; - rdpUpdate *update = peer->update; + rdpUpdate *update = peer->context->update; SURFACE_BITS_COMMAND cmd = { 0 }; RdpPeerContext *context = (RdpPeerContext *)peer->context; @@ -208,7 +131,7 @@ rdp_peer_refresh_nsc(pixman_region32_t *damage, pixman_image_t *image, freerdp_p cmd.destRight = damage->extents.x2; cmd.destBottom = damage->extents.y2; cmd.bmp.bpp = 32; - cmd.bmp.codecID = peer->settings->NSCodecId; + cmd.bmp.codecID = peer->context->settings->NSCodecId; cmd.bmp.width = width; cmd.bmp.height = height; @@ -242,7 +165,7 @@ pixman_image_flipped_subrect(const pixman_box32_t *rect, pixman_image_t *img, BY static void rdp_peer_refresh_raw(pixman_region32_t *region, pixman_image_t *image, freerdp_peer *peer) { - rdpUpdate *update = peer->update; + rdpUpdate *update = peer->context->update; SURFACE_BITS_COMMAND cmd = { 0 }; SURFACE_FRAME_MARKER marker; pixman_box32_t *rect, subrect; @@ -267,7 +190,7 @@ rdp_peer_refresh_raw(pixman_region32_t *region, pixman_image_t *image, freerdp_p cmd.destRight = rect->x2; cmd.bmp.width = (rect->x2 - rect->x1); - heightIncrement = peer->settings->MultifragMaxRequestSize / (16 + cmd.bmp.width * 4); + heightIncrement = peer->context->settings->MultifragMaxRequestSize / (16 + cmd.bmp.width * 4); remainingHeight = rect->y2 - rect->y1; top = rect->y1; @@ -304,7 +227,7 @@ rdp_peer_refresh_region(pixman_region32_t *region, freerdp_peer *peer) { RdpPeerContext *context = (RdpPeerContext *)peer->context; struct rdp_output *output = context->rdpBackend->output; - rdpSettings *settings = peer->settings; + rdpSettings *settings = peer->context->settings; if (settings->RemoteFxCodec) rdp_peer_refresh_rfx(region, output->shadow_surface, peer); @@ -326,22 +249,42 @@ rdp_output_start_repaint_loop(struct weston_output *output) } static int -rdp_output_repaint(struct weston_output *output_base, pixman_region32_t *damage, - void *repaint_data) +rdp_output_repaint(struct weston_output *output_base, pixman_region32_t *damage) { struct rdp_output *output = container_of(output_base, struct rdp_output, base); struct weston_compositor *ec = output->base.compositor; - struct rdp_peers_item *outputPeer; + struct rdp_backend *b = to_rdp_backend(ec); + struct rdp_peers_item *peer; + struct timespec now, target; + int refresh_nsec = millihz_to_nsec(output_base->current_mode->refresh); + int refresh_msec = refresh_nsec / 1000000; + int next_frame_delta; + + /* Calculate the time we should complete this frame such that frames + are spaced out by the specified monitor refresh. Note that our timer + mechanism only has msec precision, so we won't exactly hit our + target refresh rate. + */ + weston_compositor_read_presentation_clock(ec, &now); + + timespec_add_nsec(&target, &output_base->frame_time, refresh_nsec); + + next_frame_delta = (int)timespec_sub_to_msec(&target, &now); + if (next_frame_delta < 1 || next_frame_delta > refresh_msec) { + next_frame_delta = refresh_msec; + } + + assert(output); pixman_renderer_output_set_buffer(output_base, output->shadow_surface); ec->renderer->repaint_output(&output->base, damage); if (pixman_region32_not_empty(damage)) { - wl_list_for_each(outputPeer, &output->peers, link) { - if ((outputPeer->flags & RDP_PEER_ACTIVATED) && - (outputPeer->flags & RDP_PEER_OUTPUT_ENABLED)) + wl_list_for_each(peer, &b->peers, link) { + if ((peer->flags & RDP_PEER_ACTIVATED) && + (peer->flags & RDP_PEER_OUTPUT_ENABLED)) { - rdp_peer_refresh_region(damage, outputPeer->peer); + rdp_peer_refresh_region(damage, peer->peer); } } } @@ -349,7 +292,7 @@ rdp_output_repaint(struct weston_output *output_base, pixman_region32_t *damage, pixman_region32_subtract(&ec->primary_plane.damage, &ec->primary_plane.damage, damage); - wl_event_source_timer_update(output->finish_frame_timer, 16); + wl_event_source_timer_update(output->finish_frame_timer, next_frame_delta); return 0; } @@ -382,6 +325,7 @@ rdp_insert_new_mode(struct weston_output *output, int width, int height, int rat static struct weston_mode * ensure_matching_mode(struct weston_output *output, struct weston_mode *target) { + struct rdp_backend *b = to_rdp_backend(output->compositor); struct weston_mode *local; wl_list_for_each(local, &output->mode_list, link) { @@ -389,22 +333,25 @@ ensure_matching_mode(struct weston_output *output, struct weston_mode *target) return local; } - return rdp_insert_new_mode(output, target->width, target->height, RDP_MODE_FREQ); + return rdp_insert_new_mode(output, target->width, target->height, b->rdp_monitor_refresh_rate); } static int rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) { struct rdp_output *rdpOutput = container_of(output, struct rdp_output, base); + struct rdp_backend *rdpBackend = to_rdp_backend(output->compositor); struct rdp_peers_item *rdpPeer; rdpSettings *settings; pixman_image_t *new_shadow_buffer; struct weston_mode *local_mode; const struct pixman_renderer_output_options options = { .use_shadow = true, }; + assert(output); + local_mode = ensure_matching_mode(output, target_mode); if (!local_mode) { - weston_log("mode %dx%d not available\n", target_mode->width, target_mode->height); + rdp_debug(rdpBackend, "mode %dx%d not available\n", target_mode->width, target_mode->height); return -ENOENT; } @@ -426,8 +373,8 @@ rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) pixman_image_unref(rdpOutput->shadow_surface); rdpOutput->shadow_surface = new_shadow_buffer; - wl_list_for_each(rdpPeer, &rdpOutput->peers, link) { - settings = rdpPeer->peer->settings; + wl_list_for_each(rdpPeer, &rdpBackend->peers, link) { + settings = rdpPeer->peer->context->settings; if (settings->DesktopWidth == (UINT32)target_mode->width && settings->DesktopHeight == (UINT32)target_mode->height) continue; @@ -438,7 +385,7 @@ rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) } else { settings->DesktopWidth = target_mode->width; settings->DesktopHeight = target_mode->height; - rdpPeer->peer->update->DesktopResize(rdpPeer->peer->context); + rdpPeer->peer->context->update->DesktopResize(rdpPeer->peer->context); } } return 0; @@ -449,10 +396,13 @@ rdp_output_set_size(struct weston_output *base, int width, int height) { struct rdp_output *output = to_rdp_output(base); + struct rdp_backend *rdpBackend = to_rdp_backend(base->compositor); struct weston_head *head; struct weston_mode *currentMode; struct weston_mode initMode; + assert(output); + /* We can only be called once. */ assert(!output->base.current_mode); @@ -464,13 +414,10 @@ rdp_output_set_size(struct weston_output *base, weston_head_set_physical_size(head, 0, 0); } - wl_list_init(&output->peers); - initMode.flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; initMode.width = width; initMode.height = height; - initMode.refresh = RDP_MODE_FREQ; - + initMode.refresh = rdpBackend->rdp_monitor_refresh_rate; currentMode = ensure_matching_mode(&output->base, &initMode); if (!currentMode) return -1; @@ -491,12 +438,16 @@ static int rdp_output_enable(struct weston_output *base) { struct rdp_output *output = to_rdp_output(base); - struct rdp_backend *b = to_rdp_backend(base->compositor); + struct rdp_backend *b; struct wl_event_loop *loop; const struct pixman_renderer_output_options options = { .use_shadow = true, }; + assert(output); + + b = to_rdp_backend(base->compositor); + output->shadow_surface = pixman_image_create_bits(PIXMAN_x8r8g8b8, output->base.current_mode->width, output->base.current_mode->height, @@ -524,7 +475,11 @@ static int rdp_output_disable(struct weston_output *base) { struct rdp_output *output = to_rdp_output(base); - struct rdp_backend *b = to_rdp_backend(base->compositor); + struct rdp_backend *b; + + assert(output); + + b = to_rdp_backend(base->compositor); if (!output->base.enabled) return 0; @@ -538,11 +493,13 @@ rdp_output_disable(struct weston_output *base) return 0; } -static void +void rdp_output_destroy(struct weston_output *base) { struct rdp_output *output = to_rdp_output(base); + assert(output); + rdp_output_disable(&output->base); weston_output_release(&output->base); @@ -580,15 +537,22 @@ rdp_head_create(struct weston_compositor *compositor, const char *name) return -1; weston_head_init(&head->base, name); + + head->base.backend_id = rdp_head_destroy; + weston_head_set_connection_status(&head->base, true); weston_compositor_add_head(compositor, &head->base); return 0; } -static void -rdp_head_destroy(struct rdp_head *head) +void +rdp_head_destroy(struct weston_head *base) { + struct rdp_head *head = to_rdp_head(base); + + assert(head); + weston_head_release(&head->base); free(head); } @@ -601,7 +565,7 @@ rdp_destroy(struct weston_compositor *ec) struct rdp_peers_item *rdp_peer, *tmp; int i; - wl_list_for_each_safe(rdp_peer, tmp, &b->output->peers, link) { + wl_list_for_each_safe(rdp_peer, tmp, &b->peers, link) { freerdp_peer* client = rdp_peer->peer; client->Disconnect(client); @@ -613,10 +577,32 @@ rdp_destroy(struct weston_compositor *ec) if (b->listener_events[i]) wl_event_source_remove(b->listener_events[i]); + if (b->clipboard_debug) { + weston_log_scope_destroy(b->clipboard_debug); + b->clipboard_debug = NULL; + } + + if (b->clipboard_verbose) { + weston_log_scope_destroy(b->clipboard_verbose); + b->clipboard_verbose = NULL; + } + + if (b->debug) { + weston_log_scope_destroy(b->debug); + b->debug = NULL; + } + + if (b->verbose) { + weston_log_scope_destroy(b->verbose); + b->verbose = NULL; + } + weston_compositor_shutdown(ec); - wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) - rdp_head_destroy(to_rdp_head(base)); + wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) { + if (to_rdp_head(base)) + rdp_head_destroy(base); + } freerdp_listener_free(b->listener); @@ -644,18 +630,19 @@ static int rdp_implant_listener(struct rdp_backend *b, freerdp_listener* instance) { int i, fd; - int rcount = 0; - void* rfds[MAX_FREERDP_FDS]; + int handle_count = 0; + HANDLE handles[MAX_FREERDP_FDS]; struct wl_event_loop *loop; - if (!instance->GetFileDescriptor(instance, rfds, &rcount)) { - weston_log("Failed to get FreeRDP file descriptor\n"); + handle_count = instance->GetEventHandles(instance, handles, MAX_FREERDP_FDS); + if (!handle_count) { + weston_log("Failed to get FreeRDP handles\n"); return -1; } loop = wl_display_get_event_loop(b->compositor->wl_display); - for (i = 0; i < rcount; i++) { - fd = (int)(long)(rfds[i]); + for (i = 0; i < handle_count; i++) { + fd = GetEventFileDescriptor(handles[i]); b->listener_events[i] = wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, rdp_listener_activity, instance); } @@ -672,13 +659,17 @@ rdp_peer_context_new(freerdp_peer* client, RdpPeerContext* context) context->item.peer = client; context->item.flags = RDP_PEER_OUTPUT_ENABLED; + context->loop_task_event_source_fd = -1; + context->loop_task_event_source = NULL; + wl_list_init(&context->loop_task_list); + context->rfx_context = rfx_context_new(TRUE); if (!context->rfx_context) return FALSE; context->rfx_context->mode = RLGR3; - context->rfx_context->width = client->settings->DesktopWidth; - context->rfx_context->height = client->settings->DesktopHeight; + context->rfx_context->width = client->context->settings->DesktopWidth; + context->rfx_context->height = client->context->settings->DesktopHeight; rfx_context_set_pixel_format(context->rfx_context, DEFAULT_PIXEL_FORMAT); context->nsc_context = nsc_context_new(); @@ -702,16 +693,34 @@ out_error_stream: static void rdp_peer_context_free(freerdp_peer* client, RdpPeerContext* context) { - int i; + struct rdp_backend *b; + unsigned i; + if (!context) return; + b = context->rdpBackend; + wl_list_remove(&context->item.link); - for (i = 0; i < MAX_FREERDP_FDS; i++) { + + for (i = 0; i < ARRAY_LENGTH(context->events); i++) { if (context->events[i]) wl_event_source_remove(context->events[i]); } + if (context->audio_in_private) + b->audio_in_teardown(context->audio_in_private); + + if (context->audio_out_private) + b->audio_out_teardown(context->audio_out_private); + + rdp_clipboard_destroy(context); + + if (context->vcm) + WTSCloseServer(context->vcm); + + rdp_destroy_dispatch_task_event_source(context); + if (context->item.flags & RDP_PEER_ACTIVATED) { weston_seat_release_keyboard(context->item.seat); weston_seat_release_pointer(context->item.seat); @@ -730,11 +739,21 @@ static int rdp_client_activity(int fd, uint32_t mask, void *data) { freerdp_peer* client = (freerdp_peer *)data; + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; if (!client->CheckFileDescriptor(client)) { weston_log("unable to checkDescriptor for %p\n", client); goto out_clean; } + + if (peerCtx && peerCtx->vcm) + { + if (!WTSVirtualChannelManagerCheckFileDescriptor(peerCtx->vcm)) { + weston_log("failed to check FreeRDP WTS VC file descriptor for %p\n", client); + goto out_clean; + } + } + return 0; out_clean: @@ -774,19 +793,21 @@ struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { {KBD_GREEK_319, "gr", "extended"}, {KBD_GREEK_POLYTONIC, "gr", "polytonic"}, {KBD_US, "us", 0}, + {KBD_UNITED_STATES_INTERNATIONAL, "us", "intl"}, {KBD_US_ENGLISH_TABLE_FOR_IBM_ARABIC_238_L, "ara", "buckwalter"}, {KBD_SPANISH, "es", 0}, {KBD_SPANISH_VARIATION, "es", "nodeadkeys"}, {KBD_FINNISH, "fi", 0}, {KBD_FRENCH, "fr", 0}, {KBD_HEBREW, "il", 0}, + {KBD_HEBREW_STANDARD, "il", "basic"}, {KBD_HUNGARIAN, "hu", 0}, {KBD_HUNGARIAN_101_KEY, "hu", "standard"}, {KBD_ICELANDIC, "is", 0}, {KBD_ITALIAN, "it", 0}, {KBD_ITALIAN_142, "it", "nodeadkeys"}, {KBD_JAPANESE, "jp", 0}, - {KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002, "jp", "kana"}, + {KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002, "jp", 0}, {KBD_KOREAN, "kr", 0}, {KBD_KOREAN_INPUT_SYSTEM_IME_2000, "kr", "kr104"}, {KBD_DUTCH, "nl", 0}, @@ -812,7 +833,8 @@ struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { {KBD_ESTONIAN, "ee", 0}, {KBD_LATVIAN, "lv", 0}, {KBD_LITHUANIAN_IBM, "lt", "ibm"}, - {KBD_FARSI, "af", 0}, + {KBD_FARSI, "ir", "pes"}, + {KBD_PERSIAN, "af", "basic"}, {KBD_VIETNAMESE, "vn", 0}, {KBD_ARMENIAN_EASTERN, "am", 0}, {KBD_AZERI_LATIN, 0, 0}, @@ -864,24 +886,57 @@ struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { {KBD_IRISH, 0, 0}, {KBD_BOSNIAN_CYRILLIC, "ba", "us"}, {KBD_UNITED_STATES_DVORAK, "us", "dvorak"}, - {KBD_PORTUGUESE_BRAZILIAN_ABNT2, "br", "nativo"}, + {KBD_PORTUGUESE_BRAZILIAN_ABNT2, "br", "abnt2"}, {KBD_CANADIAN_MULTILINGUAL_STANDARD, "ca", "multix"}, {KBD_GAELIC, "ie", "CloGaelach"}, {0x00000000, 0, 0}, }; -/* taken from 2.2.7.1.6 Input Capability Set (TS_INPUT_CAPABILITYSET) */ -static const char *rdp_keyboard_types[] = { - "", /* 0: unused */ - "", /* 1: IBM PC/XT or compatible (83-key) keyboard */ - "", /* 2: Olivetti "ICO" (102-key) keyboard */ - "", /* 3: IBM PC/AT (84-key) or similar keyboard */ - "pc102",/* 4: IBM enhanced (101- or 102-key) keyboard */ - "", /* 5: Nokia 1050 and similar keyboards */ - "", /* 6: Nokia 9140 and similar keyboards */ - "" /* 7: Japanese keyboard */ -}; +void +convert_rdp_keyboard_to_xkb_rule_names(UINT32 KeyboardType, + UINT32 KeyboardSubType, + UINT32 KeyboardLayout, + struct xkb_rule_names *xkbRuleNames) +{ + int i; + + memset(xkbRuleNames, 0, sizeof(*xkbRuleNames)); + xkbRuleNames->model = "pc105"; + for (i = 0; rdp_keyboards[i].rdpLayoutCode; i++) { + if (rdp_keyboards[i].rdpLayoutCode == KeyboardLayout) { + xkbRuleNames->layout = rdp_keyboards[i].xkbLayout; + xkbRuleNames->variant = rdp_keyboards[i].xkbVariant; + break; + } + } + + /* Korean keyboard support (KeyboardType 8, LangID 0x412) */ + if (KeyboardType == KBD_TYPE_KOREAN && ((KeyboardLayout & 0xFFFF) == 0x412)) { + /* TODO: PC/AT 101 Enhanced Korean Keyboard (Type B) and (Type C) are not supported yet + because default Xkb settings for Korean layout don't have corresponding + configuration. + (Type B): KeyboardSubType:4: rctrl_hangul/ratl_hanja + (Type C): KeyboardSubType:5: shift_space_hangul/crtl_space_hanja + */ + if (KeyboardSubType == 0 || + KeyboardSubType == 3) /* PC/AT 101 Enhanced Korean Keyboard (Type A) */ + xkbRuleNames->variant = "kr104"; /* kr(ralt_hangul)/kr(rctrl_hanja) */ + else if (KeyboardSubType == 6) /* PC/AT 103 Enhanced Korean Keyboard */ + xkbRuleNames->variant = "kr106"; /* kr(hw_keys) */ + } else if (KeyboardType != KBD_TYPE_JAPANESE && ((KeyboardLayout & 0xFFFF) == 0x411)) { + /* when Japanese keyboard layout is used without a Japanese 106/109 + * keyboard (keyboard type 7), use the "us" layout, since the "jp" + * layout in xkb expects the Japanese 106/109 keyboard layout. + */ + xkbRuleNames->layout = "us"; + xkbRuleNames->variant = 0; + } + + weston_log("%s: matching model=%s layout=%s variant=%s\n", + __func__, xkbRuleNames->model, xkbRuleNames->layout, + xkbRuleNames->variant); +} static BOOL xf_peer_activate(freerdp_peer* client) @@ -895,7 +950,6 @@ xf_peer_activate(freerdp_peer* client) struct xkb_rule_names xkbRuleNames; struct xkb_keymap *keymap; struct weston_output *weston_output; - int i; pixman_box32_t box; pixman_region32_t damage; char seat_name[50]; @@ -905,7 +959,7 @@ xf_peer_activate(freerdp_peer* client) b = peerCtx->rdpBackend; peersItem = &peerCtx->item; output = b->output; - settings = client->settings; + settings = client->context->settings; if (!settings->SurfaceCommandsEnabled) { weston_log("client doesn't support required SurfaceCommands\n"); @@ -913,10 +967,28 @@ xf_peer_activate(freerdp_peer* client) } if (b->force_no_compression && settings->CompressionEnabled) { - weston_log("Forcing compression off\n"); + rdp_debug(b, "Forcing compression off\n"); settings->CompressionEnabled = FALSE; } + settings->AudioPlayback = b->audio_out_setup && b->audio_out_teardown; + settings->AudioCapture = b->audio_in_setup && b->audio_in_teardown; + + if (settings->RedirectClipboard || + settings->AudioPlayback || + settings->AudioCapture) { + if (!peerCtx->vcm) { + weston_log("Virtual channel is required for clipboard, audio playback/capture\n"); + goto error_exit; + } + /* Audio setup will return NULL on failure, and we'll proceed without audio */ + if (settings->AudioPlayback) + peerCtx->audio_out_private = b->audio_out_setup(b->compositor, peerCtx->vcm); + + if (settings->AudioCapture) + peerCtx->audio_in_private = b->audio_in_setup(b->compositor, peerCtx->vcm); + } + if (output->base.width != (int)settings->DesktopWidth || output->base.height != (int)settings->DesktopHeight) { @@ -929,7 +1001,7 @@ xf_peer_activate(freerdp_peer* client) } else { settings->DesktopWidth = output->base.width; settings->DesktopHeight = output->base.height; - client->update->DesktopResize(client->context); + client->context->update->DesktopResize(client->context); } } else { /* ask weston to adjust size */ @@ -956,22 +1028,14 @@ xf_peer_activate(freerdp_peer* client) return TRUE; /* when here it's the first reactivation, we need to setup a little more */ - weston_log("kbd_layout:0x%x kbd_type:0x%x kbd_subType:0x%x kbd_functionKeys:0x%x\n", + rdp_debug(b, "kbd_layout:0x%x kbd_type:0x%x kbd_subType:0x%x kbd_functionKeys:0x%x\n", settings->KeyboardLayout, settings->KeyboardType, settings->KeyboardSubType, settings->KeyboardFunctionKey); - memset(&xkbRuleNames, 0, sizeof(xkbRuleNames)); - if (settings->KeyboardType <= 7) - xkbRuleNames.model = rdp_keyboard_types[settings->KeyboardType]; - for (i = 0; rdp_keyboards[i].rdpLayoutCode; i++) { - if (rdp_keyboards[i].rdpLayoutCode == settings->KeyboardLayout) { - xkbRuleNames.layout = rdp_keyboards[i].xkbLayout; - xkbRuleNames.variant = rdp_keyboards[i].xkbVariant; - weston_log("%s: matching layout=%s variant=%s\n", __FUNCTION__, - xkbRuleNames.layout, xkbRuleNames.variant); - break; - } - } + convert_rdp_keyboard_to_xkb_rule_names(settings->KeyboardType, + settings->KeyboardSubType, + settings->KeyboardLayout, + &xkbRuleNames); keymap = NULL; if (xkbRuleNames.layout) { @@ -996,10 +1060,15 @@ xf_peer_activate(freerdp_peer* client) xkb_keymap_unref(keymap); weston_seat_init_pointer(peersItem->seat); + /* Initialize RDP clipboard after seat is initialized */ + if (settings->RedirectClipboard) + if (rdp_clipboard_init(client) != 0) + goto error_exit; + peersItem->flags |= RDP_PEER_ACTIVATED; /* disable pointer on the client side */ - pointer = client->update->pointer; + pointer = client->context->update->pointer; pointer_system.type = SYSPTR_NULL; pointer->PointerSystem(client->context, &pointer_system); @@ -1015,6 +1084,18 @@ xf_peer_activate(freerdp_peer* client) pixman_region32_fini(&damage); return TRUE; + +error_exit: + + rdp_clipboard_destroy(peerCtx); + + if (settings->AudioPlayback && peerCtx->audio_out_private) + b->audio_out_teardown(peerCtx->audio_out_private); + + if (settings->AudioCapture && peerCtx->audio_in_private) + b->audio_in_teardown(peerCtx->audio_in_private); + + return FALSE; } static BOOL @@ -1023,6 +1104,140 @@ xf_peer_post_connect(freerdp_peer *client) return TRUE; } +static void +dump_mouseinput(RdpPeerContext *peerContext, UINT16 flags, UINT16 x, UINT16 y, bool is_ex) +{ + struct rdp_backend *b = peerContext->rdpBackend; + + rdp_debug_verbose(b, "RDP mouse input%s: (%d, %d): flags:%x: ", is_ex ? "_ex" : "", x, y, flags); + if (is_ex) { + if (flags & PTR_XFLAGS_DOWN) + rdp_debug_verbose_continue(b, "DOWN "); + if (flags & PTR_XFLAGS_BUTTON1) + rdp_debug_verbose_continue(b, "XBUTTON1 "); + if (flags & PTR_XFLAGS_BUTTON2) + rdp_debug_verbose_continue(b, "XBUTTON2 "); + } else { + if (flags & PTR_FLAGS_WHEEL) + rdp_debug_verbose_continue(b, "WHEEL "); + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + rdp_debug_verbose_continue(b, "WHEEL_NEGATIVE "); + if (flags & PTR_FLAGS_HWHEEL) + rdp_debug_verbose_continue(b, "HWHEEL "); + if (flags & PTR_FLAGS_MOVE) + rdp_debug_verbose_continue(b, "MOVE "); + if (flags & PTR_FLAGS_DOWN) + rdp_debug_verbose_continue(b, "DOWN "); + if (flags & PTR_FLAGS_BUTTON1) + rdp_debug_verbose_continue(b, "BUTTON1 "); + if (flags & PTR_FLAGS_BUTTON2) + rdp_debug_verbose_continue(b, "BUTTON2 "); + if (flags & PTR_FLAGS_BUTTON3) + rdp_debug_verbose_continue(b, "BUTTON3 "); + } + rdp_debug_verbose_continue(b, "\n"); +} + +static void +rdp_validate_button_state(RdpPeerContext *peerContext, bool pressed, uint32_t *button) +{ + struct rdp_backend *b = peerContext->rdpBackend; + uint32_t index; + + if (*button < BTN_LEFT || *button > BTN_EXTRA) { + weston_log("RDP client posted invalid button event\n"); + goto ignore; + } + + index = *button - BTN_LEFT; + assert(index < ARRAY_LENGTH(peerContext->button_state)); + + if (pressed == peerContext->button_state[index]) { + rdp_debug_verbose(b, "%s: inconsistent button state button:%d (index:%d) pressed:%d\n", + __func__, *button, index, pressed); + goto ignore; + } else { + peerContext->button_state[index] = pressed; + } + return; +ignore: + /* ignore button input */ + *button = 0; +} + +static bool +rdp_notify_wheel_scroll(RdpPeerContext *peerContext, UINT16 flags, uint32_t axis) +{ + struct weston_pointer_axis_event weston_event; + struct rdp_backend *b = peerContext->rdpBackend; + int ivalue; + double value; + struct timespec time; + int *accumWheelRotationPrecise; + int *accumWheelRotationDiscrete; + + /* + * The RDP specs says the lower bits of flags contains the "the number of rotation + * units the mouse wheel was rotated". + */ + ivalue = ((int)flags & 0x000000ff); + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + ivalue = (0xff - ivalue) * -1; + + /* + * Flip the scroll direction as the RDP direction is inverse of X/Wayland + * for vertical scroll + */ + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + ivalue *= -1; + + accumWheelRotationPrecise = &peerContext->verticalAccumWheelRotationPrecise; + accumWheelRotationDiscrete = &peerContext->verticalAccumWheelRotationDiscrete; + } + else { + accumWheelRotationPrecise = &peerContext->horizontalAccumWheelRotationPrecise; + accumWheelRotationDiscrete = &peerContext->horizontalAccumWheelRotationDiscrete; + } + + /* + * Accumulate the wheel increments. + * + * Every 12 wheel increments, we will send an update to our Wayland + * clients with an updated value for the wheel for smooth scrolling. + * + * Every 120 wheel increments, we tick one discrete wheel click. + * + * https://devblogs.microsoft.com/oldnewthing/20130123-00/?p=5473 explains the 120 value + */ + *accumWheelRotationPrecise += ivalue; + *accumWheelRotationDiscrete += ivalue; + rdp_debug_verbose(b, "wheel: rawValue:%d accumPrecise:%d accumDiscrete %d\n", + ivalue, *accumWheelRotationPrecise, *accumWheelRotationDiscrete); + + if (abs(*accumWheelRotationPrecise) >= 12) { + value = (double)(*accumWheelRotationPrecise / 12); + + weston_event.axis = axis; + weston_event.value = value; + weston_event.discrete = *accumWheelRotationDiscrete / 120; + weston_event.has_discrete = true; + + rdp_debug_verbose(b, "wheel: value:%f discrete:%d\n", + weston_event.value, weston_event.discrete); + + weston_compositor_get_time(&time); + + notify_axis(peerContext->item.seat, &time, &weston_event); + + *accumWheelRotationPrecise %= 12; + *accumWheelRotationDiscrete %= 120; + + return true; + } + + return false; +} + static BOOL xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) { @@ -1032,7 +1247,15 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) bool need_frame = false; struct timespec time; - if (flags & PTR_FLAGS_MOVE) { + dump_mouseinput(peerContext, flags, x, y, false); + + /* Per RDP spec, the x,y position is valid on all input mouse messages, + * except for PTR_FLAGS_WHEEL and PTR_FLAGS_HWHEEL event. Take the opportunity + * to resample our x,y position even when PTR_FLAGS_MOVE isn't explicitly set, + * for example a button down/up only notification, to ensure proper sync with + * the RDP client. + */ + if (!(flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL))) { output = peerContext->rdpBackend->output; if (x < output->base.width && y < output->base.height) { weston_compositor_get_time(&time); @@ -1049,6 +1272,12 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) else if (flags & PTR_FLAGS_BUTTON3) button = BTN_MIDDLE; + if (button) { + rdp_validate_button_state(peerContext, + flags & PTR_FLAGS_DOWN ? true : false, + &button); + } + if (button) { weston_compositor_get_time(&time); notify_button(peerContext->item.seat, &time, button, @@ -1057,29 +1286,15 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) need_frame = true; } + /* Per RDP spec, if both PTRFLAGS_WHEEL and PTRFLAGS_HWHEEL are specified + * then PTRFLAGS_WHEEL takes precedent + */ if (flags & PTR_FLAGS_WHEEL) { - struct weston_pointer_axis_event weston_event; - double value; - - /* DEFAULT_AXIS_STEP_DISTANCE is stolen from compositor-x11.c - * The RDP specs says the lower bits of flags contains the "the number of rotation - * units the mouse wheel was rotated". - * - * https://blogs.msdn.microsoft.com/oldnewthing/20130123-00/?p=5473 explains the 120 value - */ - value = -(flags & 0xff) / 120.0; - if (flags & PTR_FLAGS_WHEEL_NEGATIVE) - value = -value; - - weston_event.axis = WL_POINTER_AXIS_VERTICAL_SCROLL; - weston_event.value = DEFAULT_AXIS_STEP_DISTANCE * value; - weston_event.discrete = (int)value; - weston_event.has_discrete = true; - - weston_compositor_get_time(&time); - - notify_axis(peerContext->item.seat, &time, &weston_event); - need_frame = true; + if (rdp_notify_wheel_scroll(peerContext, flags, WL_POINTER_AXIS_VERTICAL_SCROLL)) + need_frame = true; + } else if (flags & PTR_FLAGS_HWHEEL) { + if (rdp_notify_wheel_scroll(peerContext, flags, WL_POINTER_AXIS_HORIZONTAL_SCROLL)) + need_frame = true; } if (need_frame) @@ -1092,15 +1307,41 @@ static BOOL xf_extendedMouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) { RdpPeerContext *peerContext = (RdpPeerContext *)input->context; + uint32_t button = 0; + bool need_frame = false; struct rdp_output *output; struct timespec time; + dump_mouseinput(peerContext, flags, x, y, true); + + if (flags & PTR_XFLAGS_BUTTON1) + button = BTN_SIDE; + else if (flags & PTR_XFLAGS_BUTTON2) + button = BTN_EXTRA; + + if (button) { + rdp_validate_button_state(peerContext, + flags & PTR_XFLAGS_DOWN ? true : false, + &button); + } + + if (button) { + weston_compositor_get_time(&time); + notify_button(peerContext->item.seat, &time, button, + (flags & PTR_XFLAGS_DOWN) ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED); + need_frame = true; + } + output = peerContext->rdpBackend->output; if (x < output->base.width && y < output->base.height) { weston_compositor_get_time(&time); notify_motion_absolute(peerContext->item.seat, &time, x, y); + need_frame = true; } + if (need_frame) + notify_pointer_frame(peerContext->item.seat); + return TRUE; } @@ -1110,10 +1351,32 @@ xf_input_synchronize_event(rdpInput *input, UINT32 flags) { freerdp_peer *client = input->context->peer; RdpPeerContext *peerCtx = (RdpPeerContext *)input->context; + struct rdp_backend *b = peerCtx->rdpBackend; struct rdp_output *output = peerCtx->rdpBackend->output; + struct weston_keyboard *keyboard; pixman_box32_t box; pixman_region32_t damage; + rdp_debug_verbose(b, "RDP backend: %s ScrLk:%d, NumLk:%d, CapsLk:%d, KanaLk:%d\n", + __func__, + flags & KBD_SYNC_SCROLL_LOCK ? 1 : 0, + flags & KBD_SYNC_NUM_LOCK ? 1 : 0, + flags & KBD_SYNC_CAPS_LOCK ? 1 : 0, + flags & KBD_SYNC_KANA_LOCK ? 1 : 0); + + keyboard = weston_seat_get_keyboard(peerCtx->item.seat); + if (keyboard) { + uint32_t value = 0; + + if (flags & KBD_SYNC_NUM_LOCK) + value |= WESTON_NUM_LOCK; + if (flags & KBD_SYNC_CAPS_LOCK) + value |= WESTON_CAPS_LOCK; + weston_keyboard_set_locks(keyboard, + WESTON_NUM_LOCK | WESTON_CAPS_LOCK, + value); + } + /* sends a full refresh */ box.x1 = 0; box.y1 = 0; @@ -1133,7 +1396,9 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) { uint32_t scan_code, vk_code, full_code; enum wl_keyboard_key_state keyState; + freerdp_peer *client = input->context->peer; RdpPeerContext *peerContext = (RdpPeerContext *)input->context; + bool send_release_key = false; int notify = 0; struct timespec time; @@ -1153,9 +1418,37 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) if (flags & KBD_FLAGS_EXTENDED) full_code |= KBD_FLAGS_EXTENDED; - vk_code = GetVirtualKeyCodeFromVirtualScanCode(full_code, 4); - if (flags & KBD_FLAGS_EXTENDED) - vk_code |= KBDEXT; + /* Korean keyboard support: + * WinPR's GetVirtualKeyCodeFromVirtualScanCode() can't handle hangul/hanja keys + * hanja and hangeul keys are only present on Korean 103 keyboard (Type 8:SubType 6) */ + if (client->context->settings->KeyboardType == 8 && + client->context->settings->KeyboardSubType == 6 && + ((full_code == (KBD_FLAGS_EXTENDED | ATKBD_RET_HANJA)) || + (full_code == (KBD_FLAGS_EXTENDED | ATKBD_RET_HANGEUL)))) { + if (full_code == (KBD_FLAGS_EXTENDED | ATKBD_RET_HANJA)) + vk_code = VK_HANJA; + else if (full_code == (KBD_FLAGS_EXTENDED | ATKBD_RET_HANGEUL)) + vk_code = VK_HANGUL; + /* From Linux's keyboard driver at drivers/input/keyboard/atkbd.c */ + /* + * HANGEUL and HANJA keys do not send release events so we need to + * generate such events ourselves + */ + /* Similarly, for RDP there is no release for those 2 Korean keys, + * thus generate release right after press. */ + if (keyState != WL_KEYBOARD_KEY_STATE_PRESSED) { + weston_log("RDP: Received invalid key release\n"); + return TRUE; + } + send_release_key = true; + } else { + vk_code = GetVirtualKeyCodeFromVirtualScanCode(full_code, client->context->settings->KeyboardType); + } + /* Korean keyboard support */ + /* WinPR's GetKeycodeFromVirtualKeyCode() expects no extended bit for VK_HANGUL and VK_HANJA */ + if (vk_code != VK_HANGUL && vk_code != VK_HANJA) + if (flags & KBD_FLAGS_EXTENDED) + vk_code |= KBDEXT; scan_code = GetKeycodeFromVirtualKeyCode(vk_code, KEYCODE_TYPE_EVDEV); @@ -1164,6 +1457,13 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) weston_compositor_get_time(&time); notify_key(peerContext->item.seat, &time, scan_code - 8, keyState, STATE_UPDATE_AUTOMATIC); + + if (send_release_key) { + notify_key(peerContext->item.seat, &time, + scan_code - 8, + WL_KEYBOARD_KEY_STATE_RELEASED, + STATE_UPDATE_AUTOMATIC); + } } return TRUE; @@ -1172,7 +1472,11 @@ xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) static BOOL xf_input_unicode_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) { - weston_log("Client sent a unicode keyboard event (flags:0x%X code:0x%X)\n", flags, code); + RdpPeerContext *peerContext = (RdpPeerContext *)input->context; + struct rdp_backend *b = peerContext->rdpBackend; + + rdp_debug(b, "Client sent a unicode keyboard event (flags:0x%X code:0x%X)\n", flags, code); + return TRUE; } @@ -1193,8 +1497,8 @@ xf_suppress_output(rdpContext *context, BYTE allow, const RECTANGLE_16 *area) static int rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) { - int rcount = 0; - void *rfds[MAX_FREERDP_FDS]; + int handle_count = 0; + HANDLE handles[MAX_FREERDP_FDS + 1]; /* +1 for virtual channel */ int i, fd; struct wl_event_loop *loop; rdpSettings *settings; @@ -1209,7 +1513,7 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) peerCtx = (RdpPeerContext *) client->context; peerCtx->rdpBackend = b; - settings = client->settings; + settings = client->context->settings; /* configure security settings */ if (b->rdp_key) settings->RdpKeyFile = strdup(b->rdp_key); @@ -1230,42 +1534,71 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) settings->OsMinorType = OSMINORTYPE_PSEUDO_XSERVER; settings->ColorDepth = 32; settings->RefreshRect = TRUE; - settings->RemoteFxCodec = TRUE; + settings->RemoteFxCodec = b->remotefx_codec; settings->NSCodec = TRUE; settings->FrameMarkerCommandEnabled = TRUE; settings->SurfaceFrameMarkerEnabled = TRUE; + settings->RedirectClipboard = TRUE; + settings->HasExtendedMouseEvent = TRUE; + settings->HasHorizontalWheel = TRUE; client->Capabilities = xf_peer_capabilities; client->PostConnect = xf_peer_post_connect; client->Activate = xf_peer_activate; - client->update->SuppressOutput = (pSuppressOutput)xf_suppress_output; + client->context->update->SuppressOutput = (pSuppressOutput)xf_suppress_output; - input = client->input; + input = client->context->input; input->SynchronizeEvent = xf_input_synchronize_event; input->MouseEvent = xf_mouseEvent; input->ExtendedMouseEvent = xf_extendedMouseEvent; input->KeyboardEvent = xf_input_keyboard_event; input->UnicodeKeyboardEvent = xf_input_unicode_keyboard_event; - if (!client->GetFileDescriptor(client, rfds, &rcount)) { - weston_log("unable to retrieve client fds\n"); + handle_count = client->GetEventHandles(client, handles, MAX_FREERDP_FDS); + if (!handle_count) { + weston_log("unable to retrieve client handles\n"); goto error_initialize; } + PWtsApiFunctionTable fn = FreeRDP_InitWtsApi(); + WTSRegisterWtsApiFunctionTable(fn); + peerCtx->vcm = WTSOpenServerA((LPSTR)peerCtx); + if (peerCtx->vcm) { + handles[handle_count++] = WTSVirtualChannelManagerGetEventHandle(peerCtx->vcm); + } else { + weston_log("WTSOpenServer is failed! continue without virtual channel.\n"); + } + loop = wl_display_get_event_loop(b->compositor->wl_display); - for (i = 0; i < rcount; i++) { - fd = (int)(long)(rfds[i]); + for (i = 0; i < handle_count; i++) { + fd = GetEventFileDescriptor(handles[i]); peerCtx->events[i] = wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, rdp_client_activity, client); } - for ( ; i < MAX_FREERDP_FDS; i++) + for ( ; i < (int)ARRAY_LENGTH(peerCtx->events); i++) peerCtx->events[i] = 0; - wl_list_insert(&b->output->peers, &peerCtx->item.link); + wl_list_insert(&b->peers, &peerCtx->item.link); + + if (!rdp_initialize_dispatch_task_event_source(peerCtx)) + goto error_dispatch_initialize; + return 0; +error_dispatch_initialize: + for (i = 0; i < (int)ARRAY_LENGTH(peerCtx->events); i++) { + if (peerCtx->events[i]) { + wl_event_source_remove(peerCtx->events[i]); + peerCtx->events[i] = NULL; + } + } + if (peerCtx->vcm) { + WTSCloseServer(peerCtx->vcm); + peerCtx->vcm = NULL; + } + error_initialize: client->Close(client); return -1; @@ -1303,25 +1636,69 @@ rdp_backend_create(struct weston_compositor *compositor, if (b == NULL) return NULL; + b->compositor_tid = gettid(); b->compositor = compositor; b->base.destroy = rdp_destroy; b->base.create_output = rdp_output_create; b->rdp_key = config->rdp_key ? strdup(config->rdp_key) : NULL; b->no_clients_resize = config->no_clients_resize; b->force_no_compression = config->force_no_compression; + b->remotefx_codec = config->remotefx_codec; + b->audio_in_setup = config->audio_in_setup; + b->audio_in_teardown = config->audio_in_teardown; + b->audio_out_setup = config->audio_out_setup; + b->audio_out_teardown = config->audio_out_teardown; + + b->debug = weston_compositor_add_log_scope(compositor, + "rdp-backend", + "Debug messages from RDP backend\n", + NULL, NULL, NULL); + b->verbose = weston_compositor_add_log_scope(compositor, + "rdp-backend-verbose", + "Verbose debug messages from RDP backend\n", + NULL, NULL, NULL); + + /* After here, rdp_debug() is ready to be used */ + + b->rdp_monitor_refresh_rate = config->refresh_rate * 1000; + rdp_debug(b, "RDP backend: WESTON_RDP_MONITOR_REFRESH_RATE: %d\n", b->rdp_monitor_refresh_rate); + + b->clipboard_debug = weston_log_ctx_add_log_scope(b->compositor->weston_log_ctx, + "rdp-backend-clipboard", + "Debug messages from RDP backend clipboard\n", + NULL, NULL, NULL); + b->clipboard_verbose = weston_log_ctx_add_log_scope(b->compositor->weston_log_ctx, + "rdp-backend-clipboard-verbose", + "Debug messages from RDP backend clipboard\n", + NULL, NULL, NULL); compositor->backend = &b->base; - /* activate TLS only if certificate/key are available */ if (config->server_cert && config->server_key) { - weston_log("TLS support activated\n"); b->server_cert = strdup(config->server_cert); b->server_key = strdup(config->server_key); if (!b->server_cert || !b->server_key) goto err_free_strings; - b->tls_enabled = 1; } + /* if we are listening for client connections on an external listener + * fd, we don't need to enforce TLS or RDP security, since FreeRDP + * will consider it to be a local connection */ + fd = config->external_listener_fd; + if (fd < 0) { + if (!b->rdp_key && (!b->server_cert || !b->server_key)) { + weston_log("the RDP compositor requires keys and an optional certificate for RDP or TLS security (" + "--rdp4-key or --rdp-tls-cert/--rdp-tls-key)\n"); + goto err_free_strings; + } + if (b->server_cert && b->server_key) { + b->tls_enabled = 1; + rdp_debug(b, "TLS support activated\n"); + } + } + + wl_list_init(&b->peers); + if (weston_compositor_set_presentation_clock_software(compositor) < 0) goto err_compositor; @@ -1337,13 +1714,22 @@ rdp_backend_create(struct weston_compositor *compositor, b->listener = freerdp_listener_new(); b->listener->PeerAccepted = rdp_incoming_peer; b->listener->param4 = b; - if (!b->listener->Open(b->listener, config->bind_address, config->port)) { - weston_log("unable to bind rdp socket\n"); - goto err_listener; + if (fd >= 0) { + rdp_debug(b, "Using external fd for incoming connections: %d\n", fd); + + if (!b->listener->OpenFromSocket(b->listener, fd)) { + weston_log("RDP unable to use external listener fd: %d\n", fd); + goto err_listener; + } + } else { + if (!b->listener->Open(b->listener, config->bind_address, config->port)) { + weston_log("RDP unable to bind socket\n"); + goto err_listener; + } } if (rdp_implant_listener(b, b->listener) < 0) - goto err_compositor; + goto err_listener; } else { /* get the socket from RDP_FD var */ fd_str = getenv("RDP_FD"); @@ -1374,11 +1760,21 @@ err_output: if (b->output) weston_output_release(&b->output->base); err_compositor: - wl_list_for_each_safe(base, next, &compositor->head_list, compositor_link) - rdp_head_destroy(to_rdp_head(base)); + wl_list_for_each_safe(base, next, &compositor->head_list, compositor_link) { + if (to_rdp_head(base)) + rdp_head_destroy(base); + } weston_compositor_shutdown(compositor); err_free_strings: + if (b->clipboard_debug) + weston_log_scope_destroy(b->clipboard_debug); + if (b->clipboard_verbose) + weston_log_scope_destroy(b->clipboard_verbose); + if (b->debug) + weston_log_scope_destroy(b->debug); + if (b->verbose) + weston_log_scope_destroy(b->verbose); free(b->rdp_key); free(b->server_cert); free(b->server_key); @@ -1397,6 +1793,13 @@ config_init_to_defaults(struct weston_rdp_backend_config *config) config->env_socket = 0; config->no_clients_resize = 0; config->force_no_compression = 0; + config->remotefx_codec = true; + config->external_listener_fd = -1; + config->refresh_rate = RDP_DEFAULT_FREQ; + config->audio_in_setup = NULL; + config->audio_in_teardown = NULL; + config->audio_out_setup = NULL; + config->audio_out_teardown = NULL; } WL_EXPORT int @@ -1423,12 +1826,6 @@ weston_backend_init(struct weston_compositor *compositor, config_init_to_defaults(&config); memcpy(&config, config_base, config_base->struct_size); - if (!config.rdp_key && (!config.server_cert || !config.server_key)) { - weston_log("the RDP compositor requires keys and an optional certificate for RDP or TLS security (" - "--rdp4-key or --rdp-tls-cert/--rdp-tls-key)\n"); - return -1; - } - b = rdp_backend_create(compositor, &config); if (b == NULL) return -1; diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h new file mode 100644 index 00000000..210ddab4 --- /dev/null +++ b/libweston/backend-rdp/rdp.h @@ -0,0 +1,263 @@ +/* + * Copyright © 2013 Hardening + * Copyright © 2020 Microsoft + * + * 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 RDP_H +#define RDP_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "backend.h" + +#include "shared/helpers.h" +#include "shared/string-helpers.h" + +#define MAX_FREERDP_FDS 32 +#define DEFAULT_AXIS_STEP_DISTANCE 10 +#define DEFAULT_PIXEL_FORMAT PIXEL_FORMAT_BGRA32 + +/* https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getkeyboardtype + * defines a keyboard type that isn't currently defined in FreeRDP, but is + * available for RDP connections */ +#ifndef KBD_TYPE_KOREAN +#define KBD_TYPE_KOREAN 8 +#endif + +/* WinPR's GetVirtualKeyCodeFromVirtualScanCode() can't handle hangul/hanja keys */ +/* 0x1f1 and 0x1f2 keys are only exists on Korean 103 keyboard (Type 8:SubType 6) */ + +/* From Linux's keyboard driver at drivers/input/keyboard/atkbd.c */ +#define ATKBD_RET_HANJA 0xf1 +#define ATKBD_RET_HANGEUL 0xf2 + +struct rdp_output; + +struct rdp_backend { + struct weston_backend base; + struct weston_compositor *compositor; + + freerdp_listener *listener; + struct wl_event_source *listener_events[MAX_FREERDP_FDS]; + struct rdp_output *output; + struct weston_log_scope *debug; + struct weston_log_scope *verbose; + + struct weston_log_scope *clipboard_debug; + struct weston_log_scope *clipboard_verbose; + + struct wl_list peers; + + char *server_cert; + char *server_key; + char *rdp_key; + int tls_enabled; + int no_clients_resize; + int force_no_compression; + bool remotefx_codec; + int external_listener_fd; + int rdp_monitor_refresh_rate; + pid_t compositor_tid; + + rdp_audio_in_setup audio_in_setup; + rdp_audio_in_teardown audio_in_teardown; + rdp_audio_out_setup audio_out_setup; + rdp_audio_out_teardown audio_out_teardown; +}; + +enum peer_item_flags { + RDP_PEER_ACTIVATED = (1 << 0), + RDP_PEER_OUTPUT_ENABLED = (1 << 1), +}; + +struct rdp_peers_item { + int flags; + freerdp_peer *peer; + struct weston_seat *seat; + + struct wl_list link; +}; + +struct rdp_head { + struct weston_head base; +}; + +struct rdp_output { + struct weston_output base; + struct wl_event_source *finish_frame_timer; + pixman_image_t *shadow_surface; +}; + +struct rdp_peer_context { + rdpContext _p; + + struct rdp_backend *rdpBackend; + struct wl_event_source *events[MAX_FREERDP_FDS + 1]; /* +1 for WTSVirtualChannelManagerGetFileDescriptor */ + RFX_CONTEXT *rfx_context; + wStream *encode_stream; + RFX_RECT *rfx_rects; + NSC_CONTEXT *nsc_context; + + struct rdp_peers_item item; + + bool button_state[5]; + + int verticalAccumWheelRotationPrecise; + int verticalAccumWheelRotationDiscrete; + int horizontalAccumWheelRotationPrecise; + int horizontalAccumWheelRotationDiscrete; + + HANDLE vcm; + + /* list of outstanding event_source sent from FreeRDP thread to display loop.*/ + int loop_task_event_source_fd; + struct wl_event_source *loop_task_event_source; + pthread_mutex_t loop_task_list_mutex; + struct wl_list loop_task_list; /* struct rdp_loop_task::link */ + + /* Clipboard support */ + CliprdrServerContext *clipboard_server_context; + + void *audio_in_private; + void *audio_out_private; + + struct rdp_clipboard_data_source *clipboard_client_data_source; + struct rdp_clipboard_data_source *clipboard_inflight_client_data_source; + + struct wl_listener clipboard_selection_listener; +}; + +typedef struct rdp_peer_context RdpPeerContext; + +typedef void (*rdp_loop_task_func_t)(bool freeOnly, void *data); + +struct rdp_loop_task { + struct wl_list link; + RdpPeerContext *peerCtx; + rdp_loop_task_func_t func; +}; + +#define rdp_debug_verbose(b, ...) \ + rdp_debug_print(b->verbose, false, __VA_ARGS__) +#define rdp_debug_verbose_continue(b, ...) \ + rdp_debug_print(b->verbose, true, __VA_ARGS__) +#define rdp_debug(b, ...) \ + rdp_debug_print(b->debug, false, __VA_ARGS__) +#define rdp_debug_continue(b, ...) \ + rdp_debug_print(b->debug, true, __VA_ARGS__) + +#define rdp_debug_clipboard_verbose(b, ...) \ + rdp_debug_print(b->clipboard_verbose, false, __VA_ARGS__) +#define rdp_debug_clipboard_verbose_continue(b, ...) \ + rdp_debug_print(b->clipboard_verbose, true, __VA_ARGS__) +#define rdp_debug_clipboard(b, ...) \ + rdp_debug_print(b->clipboard_debug, false, __VA_ARGS__) +#define rdp_debug_clipboard_continue(b, ...) \ + rdp_debug_print(b->clipboard_debug, true, __VA_ARGS__) + +/* rdputil.c */ +void +rdp_debug_print(struct weston_log_scope *log_scope, bool cont, char *fmt, ...); + +int +rdp_wl_array_read_fd(struct wl_array *array, int fd); + +void +convert_rdp_keyboard_to_xkb_rule_names(UINT32 KeyboardType, UINT32 KeyboardSubType, UINT32 KeyboardLayout, struct xkb_rule_names *xkbRuleNames); + +void +assert_compositor_thread(struct rdp_backend *b); + +void +assert_not_compositor_thread(struct rdp_backend *b); + +bool +rdp_event_loop_add_fd(struct wl_event_loop *loop, + int fd, uint32_t mask, + wl_event_loop_fd_func_t func, + void *data, + struct wl_event_source **event_source); + +void +rdp_dispatch_task_to_display_loop(RdpPeerContext *peerCtx, + rdp_loop_task_func_t func, + struct rdp_loop_task *task); + +bool +rdp_initialize_dispatch_task_event_source(RdpPeerContext *peerCtx); + +void +rdp_destroy_dispatch_task_event_source(RdpPeerContext *peerCtx); + +/* rdpclip.c */ +int +rdp_clipboard_init(freerdp_peer *client); + +void +rdp_clipboard_destroy(RdpPeerContext *peerCtx); + +void +rdp_head_destroy(struct weston_head *base); + +static inline struct rdp_head * +to_rdp_head(struct weston_head *base) +{ + if (base->backend_id != rdp_head_destroy) + return NULL; + return container_of(base, struct rdp_head, base); +} + +void +rdp_output_destroy(struct weston_output *base); + +static inline struct rdp_output * +to_rdp_output(struct weston_output *base) +{ + if (base->destroy != rdp_output_destroy) + return NULL; + return container_of(base, struct rdp_output, base); +} + +static inline struct rdp_backend * +to_rdp_backend(struct weston_compositor *base) +{ + return container_of(base->backend, struct rdp_backend, base); +} + +#endif diff --git a/libweston/backend-rdp/rdpclip.c b/libweston/backend-rdp/rdpclip.c new file mode 100644 index 00000000..e08f4ca2 --- /dev/null +++ b/libweston/backend-rdp/rdpclip.c @@ -0,0 +1,1754 @@ +/* + * Copyright © 2020 Microsoft + * + * 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. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rdp.h" + +#include "libweston-internal.h" + +/* From MSDN, RegisterClipboardFormat API. + Registered clipboard formats are identified by values in the range 0xC000 through 0xFFFF. */ +#define CF_PRIVATE_RTF 49309 /* fake format ID for "Rich Text Format". */ +#define CF_PRIVATE_HTML 49405 /* fake format ID for "HTML Format".*/ + + /* 1 2 3 4 5 6 7 8 */ + /*01234567890 1 2345678901234 5 67890123456 7 89012345678901234567890 1 234567890123456789012 3 4*/ +static const char rdp_clipboard_html_header[] = "Version:0.9\r\nStartHTML:-1\r\nEndHTML:-1\r\nStartFragment:00000000\r\nEndFragment:00000000\r\n"; +#define RDP_CLIPBOARD_FRAGMENT_START_OFFSET (53) //---------------------------------------------------------+ | +#define RDP_CLIPBOARD_FRAGMENT_END_OFFSET (75) //-----------------------------------------------------------------------------------+ + +/* + * https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format + * + * The fragment should be preceded and followed by the HTML comments and + * (no space allowed between the !-- and the text) to conveniently + * indicate where the fragment starts and ends. + */ +static const char rdp_clipboard_html_fragment_start[] = "\r\n"; +static const char rdp_clipboard_html_fragment_end[] = "\r\n"; + +struct rdp_clipboard_data_source; + +typedef bool (*pfn_process_data)(struct rdp_clipboard_data_source *source, bool is_send); + +struct rdp_clipboard_supported_format { + uint32_t format_id; + char *format_name; + char *mime_type; + pfn_process_data pfn; +}; + +static bool +clipboard_process_text_utf8(struct rdp_clipboard_data_source *source, bool is_send); + +static bool +clipboard_process_text_raw(struct rdp_clipboard_data_source *source, bool is_send); + +static bool +clipboard_process_bmp(struct rdp_clipboard_data_source *source , bool is_send); + +static bool +clipboard_process_html(struct rdp_clipboard_data_source *source, bool is_send); + +/* TODO: need to support to 1:n or m:n format conversion. + * For example, CF_UNICODETEXT to "UTF8_STRING" as well as "text/plain;charset=utf-8". + */ +struct rdp_clipboard_supported_format clipboard_supported_formats[] = { + { CF_UNICODETEXT, NULL, "text/plain;charset=utf-8", clipboard_process_text_utf8 }, + { CF_TEXT, NULL, "STRING", clipboard_process_text_raw }, + { CF_DIB, NULL, "image/bmp", clipboard_process_bmp }, + { CF_PRIVATE_RTF, "Rich Text Format", "text/rtf", clipboard_process_text_raw }, + { CF_PRIVATE_HTML, "HTML Format", "text/html", clipboard_process_html }, +}; +#define RDP_NUM_CLIPBOARD_FORMATS ARRAY_LENGTH(clipboard_supported_formats) + +enum rdp_clipboard_data_source_state { + RDP_CLIPBOARD_SOURCE_ALLOCATED = 0, + RDP_CLIPBOARD_SOURCE_FORMATLIST_READY, /* format list obtained from provider */ + RDP_CLIPBOARD_SOURCE_PUBLISHED, /* availablity of some or no clipboard data notified to consumer */ + RDP_CLIPBOARD_SOURCE_REQUEST_DATA, /* data request sent to provider */ + RDP_CLIPBOARD_SOURCE_RECEIVED_DATA, /* data was received from provider, waiting data to be dispatched to consumer */ + RDP_CLIPBOARD_SOURCE_TRANSFERING, /* transfering data to consumer */ + RDP_CLIPBOARD_SOURCE_TRANSFERRED, /* completed transfering data to consumer */ + RDP_CLIPBOARD_SOURCE_CANCEL_PENDING, /* data transfer cancel requested */ + RDP_CLIPBOARD_SOURCE_CANCELED, /* data transfer canceled */ + RDP_CLIPBOARD_SOURCE_RETRY, /* retry later */ + RDP_CLIPBOARD_SOURCE_FAILED, /* failure occured */ +}; + +struct rdp_clipboard_data_source { + struct weston_data_source base; + struct rdp_loop_task task_base; + struct wl_event_source *transfer_event_source; /* used for read/write with pipe */ + struct wl_array data_contents; + void *context; + int refcount; + int data_source_fd; + int format_index; + enum rdp_clipboard_data_source_state state; + uint32_t data_response_fail_count; + uint32_t inflight_write_count; + void *inflight_data_to_write; + size_t inflight_data_size; + bool is_data_processed; + void *processed_data_start; + uint32_t processed_data_size; + bool processed_data_is_send; + bool is_canceled; + uint32_t client_format_id_table[RDP_NUM_CLIPBOARD_FORMATS]; +}; + +struct rdp_clipboard_data_request { + struct rdp_loop_task task_base; + RdpPeerContext *ctx; + uint32_t requested_format_index; +}; + +static char * +clipboard_data_source_state_to_string(struct rdp_clipboard_data_source *source) +{ + if (!source) + return "null"; + + switch (source->state) { + case RDP_CLIPBOARD_SOURCE_ALLOCATED: + return "allocated"; + case RDP_CLIPBOARD_SOURCE_FORMATLIST_READY: + return "format list ready"; + case RDP_CLIPBOARD_SOURCE_PUBLISHED: + return "published"; + case RDP_CLIPBOARD_SOURCE_REQUEST_DATA: + return "request data"; + case RDP_CLIPBOARD_SOURCE_RECEIVED_DATA: + return "received data"; + case RDP_CLIPBOARD_SOURCE_TRANSFERING: + return "transferring"; + case RDP_CLIPBOARD_SOURCE_TRANSFERRED: + return "transferred"; + case RDP_CLIPBOARD_SOURCE_CANCEL_PENDING: + return "cancel pending"; + case RDP_CLIPBOARD_SOURCE_CANCELED: + return "canceled"; + case RDP_CLIPBOARD_SOURCE_RETRY: + return "retry"; + case RDP_CLIPBOARD_SOURCE_FAILED: + return "failed"; + } + assert(false); + return "unknown"; +} + +static bool +clipboard_process_text_utf8(struct rdp_clipboard_data_source *source, bool is_send) +{ + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + struct wl_array data_contents; + + wl_array_init(&data_contents); + + assert(!source->is_data_processed); + + if (is_send) { + char *data = source->data_contents.data; + size_t data_size, data_size_in_char; + + /* Linux to Windows (convert utf-8 to UNICODE) */ + /* Include terminating NULL in size */ + assert((source->data_contents.size + 1) <= source->data_contents.alloc); + data[source->data_contents.size] = '\0'; + source->data_contents.size++; + + /* obtain size in UNICODE */ + data_size = MultiByteToWideChar(CP_UTF8, 0, + data, + source->data_contents.size, + NULL, 0); + if (data_size < 1) + goto error_return; + + data_size *= 2; /* convert to size in bytes. */ + if (!wl_array_add(&data_contents, data_size)) + goto error_return; + + /* convert to UNICODE */ + data_size_in_char = MultiByteToWideChar(CP_UTF8, 0, + data, + source->data_contents.size, + data_contents.data, + data_size); + assert(data_contents.size == (data_size_in_char * 2)); + } else { + /* Windows to Linux (UNICODE to utf-8) */ + size_t data_size; + LPWSTR data = source->data_contents.data; + size_t data_size_in_char = source->data_contents.size / 2; + + /* Windows's data has trailing chars, which Linux doesn't expect. */ + while (data_size_in_char && + ((data[data_size_in_char-1] == L'\0') || (data[data_size_in_char-1] == L'\n'))) + data_size_in_char -= 1; + if (!data_size_in_char) + goto error_return; + + /* obtain size in utf-8 */ + data_size = WideCharToMultiByte(CP_UTF8, 0, + source->data_contents.data, + data_size_in_char, + NULL, 0, + NULL, NULL); + if (data_size < 1) + goto error_return; + + if (!wl_array_add(&data_contents, data_size)) + goto error_return; + + /* convert to utf-8 */ + data_size = WideCharToMultiByte(CP_UTF8, 0, + source->data_contents.data, + data_size_in_char, + data_contents.data, + data_size, + NULL, NULL); + assert(data_contents.size == data_size); + } + + /* swap the data_contents with new one */ + wl_array_release(&source->data_contents); + source->data_contents = data_contents; + source->is_data_processed = true; + source->processed_data_start = source->data_contents.data; + source->processed_data_size = source->data_contents.size; + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s): %s (%u bytes)\n", + __func__, source, + clipboard_data_source_state_to_string(source), + is_send ? "send" : "receive", + (uint32_t)source->data_contents.size); + + return true; + +error_return: + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + weston_log("RDP %s FAILED (%p:%s): %s (%u bytes)\n", + __func__, source, + clipboard_data_source_state_to_string(source), + is_send ? "send" : "receive", + (uint32_t)source->data_contents.size); + + wl_array_release(&data_contents); + + return false; +} + +static bool +clipboard_process_text_raw(struct rdp_clipboard_data_source *source, bool is_send) +{ + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + char *data = source->data_contents.data; + size_t data_size = source->data_contents.size; + + assert(!source->is_data_processed); + + if (is_send) { + /* Linux to Windows */ + /* Include terminating NULL in size */ + assert(data_size + 1 <= source->data_contents.alloc); + data[data_size] = '\0'; + source->data_contents.size++; + } else { + /* Windows to Linux */ + /* Windows's data has trailing chars, which Linux doesn't expect. */ + while (data_size && ((data[data_size-1] == '\0') || (data[data_size-1] == '\n'))) + data_size -= 1; + source->data_contents.size = data_size; + } + source->is_data_processed = true; + source->processed_data_start = source->data_contents.data; + source->processed_data_size = source->data_contents.size; + rdp_debug_clipboard_verbose(b, "RDP %s (%p): %s (%u bytes)\n", + __func__, source, + is_send ? "send" : "receive", + (uint32_t)source->data_contents.size); + + return true; +} + +/* based off sample code at https://docs.microsoft.com/en-us/troubleshoot/cpp/add-html-code-clipboard + But this missing a lot of corner cases, it must be rewritten with use of proper HTML parser */ +/* TODO: This doesn't work for converting HTML from Firefox in Wayland mode to Windows in certain cases, + because Firefox sends "...", thus + this needs to property strip meta header and convert to the Windows clipboard style HTML. */ +static bool +clipboard_process_html(struct rdp_clipboard_data_source *source, bool is_send) +{ + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + struct wl_array data_contents; + char *cur = source->data_contents.data; + + assert(!source->is_data_processed); + + /* We're treating the contents as a string for now, so null + * terminate it so strstr can't run off the end. However, we + * don't increase data_contents.size because we don't want + * to affect the content. */ + assert(source->data_contents.size + 1 <= source->data_contents.alloc); + ((char *)(source->data_contents.data))[source->data_contents.size] = '\0'; + + wl_array_init(&data_contents); + cur = strstr(cur, "data_contents.size - + (cur - (char *)source->data_contents.data); + + /* Windows's data has trailing chars, which Linux doesn't expect. */ + while (data_size && ((cur[data_size-1] == '\0') || (cur[data_size-1] == '\n'))) + data_size -= 1; + + if (!data_size) + goto error_return; + + if (!wl_array_add(&data_contents, data_size+1)) /* +1 for null */ + goto error_return; + + memcpy(data_contents.data, cur, data_size); + ((char *)(data_contents.data))[data_size] = '\0'; + data_contents.size = data_size; + } else { + /* Linux to Windows */ + char *last, *buf; + uint32_t fragment_start, fragment_end; + + if (!wl_array_add(&data_contents, source->data_contents.size+200)) + goto error_return; + + buf = data_contents.data; + strcpy(buf, rdp_clipboard_html_header); + last = cur; + cur = strstr(cur, "' */ + strncat(buf, last, cur-last); + last = cur; + fragment_start = strlen(buf); + strcat(buf, rdp_clipboard_html_fragment_start); + cur = strstr(cur, "data_contents); + source->data_contents = data_contents; + source->is_data_processed = true; + source->processed_data_start = source->data_contents.data; + source->processed_data_size = source->data_contents.size; + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s): %s (%u bytes)\n", + __func__, source, clipboard_data_source_state_to_string(source), + is_send ? "send" : "receive", (uint32_t)source->data_contents.size); + + return true; + +error_return: + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + weston_log("RDP %s FAILED (%p:%s): %s (%u bytes)\n", + __func__, source, clipboard_data_source_state_to_string(source), + is_send ? "send" : "receive", (uint32_t)source->data_contents.size); + + wl_array_release(&data_contents); + + return false; +} + +#define DIB_HEADER_MARKER ((WORD) ('M' << 8) | 'B') +#define DIB_WIDTH_BYTES(bits) ((((bits) + 31) & ~31) >> 3) + +static bool +clipboard_process_bmp(struct rdp_clipboard_data_source *source, bool is_send) +{ + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + BITMAPFILEHEADER *bmfh = NULL; + BITMAPINFOHEADER *bmih = NULL; + uint32_t color_table_size = 0; + struct wl_array data_contents; + + assert(!source->is_data_processed); + + wl_array_init(&data_contents); + + if (is_send) { + /* Linux to Windows (remove BITMAPFILEHEADER) */ + if (source->data_contents.size <= sizeof(*bmfh)) + goto error_return; + + bmfh = source->data_contents.data; + bmih = (BITMAPINFOHEADER *)(bmfh + 1); + + source->is_data_processed = true; + source->processed_data_start = bmih; + source->processed_data_size = source->data_contents.size - sizeof(*bmfh); + } else { + /* Windows to Linux (insert BITMAPFILEHEADER) */ + BITMAPFILEHEADER _bmfh = {}; + + if (source->data_contents.size <= sizeof(*bmih)) + goto error_return; + + bmih = source->data_contents.data; + bmfh = &_bmfh; + if (bmih->biCompression == BI_BITFIELDS) + color_table_size = sizeof(RGBQUAD) * 3; + else + color_table_size = sizeof(RGBQUAD) * bmih->biClrUsed; + + bmfh->bfType = DIB_HEADER_MARKER; + bmfh->bfOffBits = sizeof(*bmfh) + bmih->biSize + color_table_size; + if (bmih->biSizeImage) + bmfh->bfSize = bmfh->bfOffBits + bmih->biSizeImage; + else if (bmih->biCompression == BI_BITFIELDS || bmih->biCompression == BI_RGB) + bmfh->bfSize = bmfh->bfOffBits + + (DIB_WIDTH_BYTES(bmih->biWidth * bmih->biBitCount) * abs(bmih->biHeight)); + else + goto error_return; + + /* source data must have enough size as described in its own bitmap header */ + if (source->data_contents.size < (bmfh->bfSize - sizeof(*bmfh))) + goto error_return; + + if (!wl_array_add(&data_contents, bmfh->bfSize)) + goto error_return; + assert(data_contents.size == bmfh->bfSize); + + /* copy generated BITMAPFILEHEADER */ + memcpy(data_contents.data, bmfh, sizeof(*bmfh)); + /* copy rest of bitmap data from source */ + memcpy((char *)data_contents.data + sizeof(*bmfh), + source->data_contents.data, bmfh->bfSize - sizeof(*bmfh)); + + /* swap the data_contents with new one */ + wl_array_release(&source->data_contents); + source->data_contents = data_contents; + source->is_data_processed = true; + source->processed_data_start = source->data_contents.data; + source->processed_data_size = source->data_contents.size; + } + + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s): %s (%d bytes)\n", + __func__, source, + clipboard_data_source_state_to_string(source), + is_send ? "send" : "receive", + (uint32_t)source->data_contents.size); + + return true; + +error_return: + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + weston_log("RDP %s FAILED (%p:%s): %s (%d bytes)\n", + __func__, source, clipboard_data_source_state_to_string(source), + is_send ? "send" : "receive", (uint32_t)source->data_contents.size); + + wl_array_release(&data_contents); + + return false; +} + +static char * +clipboard_format_id_to_string(UINT32 formatId, bool is_server_format_id) +{ + switch (formatId) { + case CF_RAW: + return "CF_RAW"; + case CF_TEXT: + return "CF_TEXT"; + case CF_BITMAP: + return "CF_BITMAP"; + case CF_METAFILEPICT: + return "CF_METAFILEPICT"; + case CF_SYLK: + return "CF_SYLK"; + case CF_DIF: + return "CF_DIF"; + case CF_TIFF: + return "CF_TIFF"; + case CF_OEMTEXT: + return "CF_OEMTEXT"; + case CF_DIB: + return "CF_DIB"; + case CF_PALETTE: + return "CF_PALETTE"; + case CF_PENDATA: + return "CF_PENDATA"; + case CF_RIFF: + return "CF_RIFF"; + case CF_WAVE: + return "CF_WAVE"; + case CF_UNICODETEXT: + return "CF_UNICODETEXT"; + case CF_ENHMETAFILE: + return "CF_ENHMETAFILE"; + case CF_HDROP: + return "CF_HDROP"; + case CF_LOCALE: + return "CF_LOCALE"; + case CF_DIBV5: + return "CF_DIBV5"; + + case CF_OWNERDISPLAY: + return "CF_OWNERDISPLAY"; + case CF_DSPTEXT: + return "CF_DSPTEXT"; + case CF_DSPBITMAP: + return "CF_DSPBITMAP"; + case CF_DSPMETAFILEPICT: + return "CF_DSPMETAFILEPICT"; + case CF_DSPENHMETAFILE: + return "CF_DSPENHMETAFILE"; + } + + if (formatId >= CF_PRIVATEFIRST && formatId <= CF_PRIVATELAST) + return "CF_PRIVATE"; + + if (formatId >= CF_GDIOBJFIRST && formatId <= CF_GDIOBJLAST) + return "CF_GDIOBJ"; + + if (is_server_format_id) { + if (formatId == CF_PRIVATE_HTML) + return "CF_PRIVATE_HTML"; + + if (formatId == CF_PRIVATE_RTF) + return "CF_PRIVATE_RTF"; + } else { + /* From MSDN, RegisterClipboardFormat API. + Registered clipboard formats are identified by values in the range 0xC000 through 0xFFFF. */ + if (formatId >= 0xC000 && formatId <= 0xFFFF) + return "Client side Registered Clipboard Format"; + } + + return "Unknown format"; +} + +/* find supported index in supported format table by format id from client */ +static int +clipboard_find_supported_format_by_format_id(UINT32 format_id) +{ + unsigned int i; + + for (i = 0; i < RDP_NUM_CLIPBOARD_FORMATS; i++) { + struct rdp_clipboard_supported_format *format = &clipboard_supported_formats[i]; + + if (format_id == format->format_id) + return i; + } + return -1; +} + +/* find supported index in supported format table by format id and name from client */ +static int +clipboard_find_supported_format_by_format_id_and_name(UINT32 format_id, const char *format_name) +{ + unsigned int i; + + for (i = 0; i < RDP_NUM_CLIPBOARD_FORMATS; i++) { + struct rdp_clipboard_supported_format *format = &clipboard_supported_formats[i]; + + /* when our supported format table has format name, only format name must match, + format id provided from client is ignored (but it may be saved by caller for future use. + When our supported format table doesn't have format name, only format id must match, + format name (if provided from client) is ignored */ + if ((format->format_name == NULL && format_id == format->format_id) || + (format->format_name && format_name && strcmp(format_name, format->format_name) == 0)) + return i; + } + return -1; +} + +/* find supported index in supported format table by mime */ +static int +clipboard_find_supported_format_by_mime_type(const char *mime_type) +{ + unsigned int i; + + for (i = 0; i < RDP_NUM_CLIPBOARD_FORMATS; i++) { + struct rdp_clipboard_supported_format *format = &clipboard_supported_formats[i]; + + if (strcmp(mime_type, format->mime_type) == 0) + return i; + } + return -1; +} + +static bool +clipboard_process_source(struct rdp_clipboard_data_source *source, bool is_send) +{ + if (source->is_data_processed) { + assert(source->processed_data_is_send == is_send); + return true; + } + + source->processed_data_start = NULL; + source->processed_data_size = 0; + + if (clipboard_supported_formats[source->format_index].pfn) + return clipboard_supported_formats[source->format_index].pfn(source, is_send); + + /* No processor, so just set up pointer and length for raw data */ + source->is_data_processed = true; + source->processed_data_start = source->data_contents.data; + source->processed_data_size = source->data_contents.size; + source->processed_data_is_send = is_send; + return true; +} + +static void +clipboard_data_source_unref(struct rdp_clipboard_data_source *source) +{ + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + char **p; + + assert_compositor_thread(b); + + assert(source->refcount); + source->refcount--; + + rdp_debug_clipboard(b, "RDP %s (%p:%s): refcount:%d\n", + __func__, source, + clipboard_data_source_state_to_string(source), + source->refcount); + + if (source->refcount > 0) + return; + + if (source->transfer_event_source) + wl_event_source_remove(source->transfer_event_source); + + if (source->data_source_fd != -1) + close(source->data_source_fd); + + if (!wl_list_empty(&source->base.destroy_signal.listener_list)) + wl_signal_emit(&source->base.destroy_signal, + &source->base); + + wl_array_release(&source->data_contents); + + wl_array_for_each(p, &source->base.mime_types) + free(*p); + + wl_array_release(&source->base.mime_types); + + free(source); +} + +/******************************************\ + * FreeRDP format data response functions * +\******************************************/ + +/* Inform client data request is succeeded with data */ +static void +clipboard_client_send_format_data_response(RdpPeerContext *ctx, struct rdp_clipboard_data_source *source) +{ + struct rdp_backend *b = ctx->rdpBackend; + CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = {}; + + assert(source->is_data_processed); + rdp_debug_clipboard(b, "Client: %s (%p:%s) format_index:%d %s (%d bytes)\n", + __func__, source, + clipboard_data_source_state_to_string(source), + source->format_index, + clipboard_supported_formats[source->format_index].mime_type, + source->processed_data_size); + + formatDataResponse.msgType = CB_FORMAT_DATA_RESPONSE; + formatDataResponse.msgFlags = CB_RESPONSE_OK; + formatDataResponse.dataLen = source->processed_data_size; + formatDataResponse.requestedFormatData = source->processed_data_start; + ctx->clipboard_server_context->ServerFormatDataResponse(ctx->clipboard_server_context, &formatDataResponse); + /* if here failed to send response, what can we do ? */ +} + +/* Inform client data request has failed */ +static void +clipboard_client_send_format_data_response_fail(RdpPeerContext *ctx, struct rdp_clipboard_data_source *source) +{ + struct rdp_backend *b = ctx->rdpBackend; + CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = {}; + + rdp_debug_clipboard(b, "Client: %s (%p:%s)\n", + __func__, source, + clipboard_data_source_state_to_string(source)); + + if (source) { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + source->data_response_fail_count++; + } + + formatDataResponse.msgType = CB_FORMAT_DATA_RESPONSE; + formatDataResponse.msgFlags = CB_RESPONSE_FAIL; + formatDataResponse.dataLen = 0; + formatDataResponse.requestedFormatData = NULL; + ctx->clipboard_server_context->ServerFormatDataResponse(ctx->clipboard_server_context, &formatDataResponse); + /* if here failed to send response, what can we do ? */ +} + +/***************************************\ + * Compositor file descritor callbacks * +\***************************************/ + +/* Send server clipboard data to client when server side application sent them via pipe. */ +static int +clipboard_data_source_read(int fd, uint32_t mask, void *arg) +{ + struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)arg; + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + int len; + bool failed = true; + + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) fd:%d\n", + __func__, source, + clipboard_data_source_state_to_string(source), fd); + + assert_compositor_thread(b); + + assert(source->data_source_fd == fd); + assert(source->refcount == 1); + + /* event source is not removed here, but it will be removed when read is completed, + until it's completed this function will be called whenever next chunk of data is + available for read in pipe. */ + assert(source->transfer_event_source); + + source->state = RDP_CLIPBOARD_SOURCE_TRANSFERING; + + len = rdp_wl_array_read_fd(&source->data_contents, fd); + if (len < 0) { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + weston_log("RDP %s (%p:%s) read failed (%s)\n", + __func__, source, + clipboard_data_source_state_to_string(source), + strerror(errno)); + goto error_exit; + } + + if (len > 0) { + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) read (%zu bytes)\n", + __func__, source, + clipboard_data_source_state_to_string(source), + source->data_contents.size); + /* continue to read next batch */ + return 0; + } + + /* len == 0, all data from source is read, so completed. */ + source->state = RDP_CLIPBOARD_SOURCE_TRANSFERRED; + rdp_debug_clipboard(b, "RDP %s (%p:%s): read completed (%ld bytes)\n", + __func__, source, + clipboard_data_source_state_to_string(source), + source->data_contents.size); + if (!source->data_contents.size) + goto error_exit; + /* process data before sending to client */ + if (!clipboard_process_source(source, true)) + goto error_exit; + + clipboard_client_send_format_data_response(ctx, source); + failed = false; + +error_exit: + if (failed) + clipboard_client_send_format_data_response_fail(ctx, source); + + /* make sure this is the last reference, so event source is removed at unref */ + assert(source->refcount == 1); + clipboard_data_source_unref(source); + return 0; +} + +/* client's reply with error for data request, clean up */ +static int +clipboard_data_source_fail(int fd, uint32_t mask, void *arg) +{ + struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)arg; + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) fd:%d\n", __func__, + source, clipboard_data_source_state_to_string(source), fd); + + assert_compositor_thread(b); + + assert(source->data_source_fd == fd); + /* this data source must be tracked as inflight */ + assert(source == ctx->clipboard_inflight_client_data_source); + + wl_event_source_remove(source->transfer_event_source); + source->transfer_event_source = NULL; + + /* if data was received, but failed for another reason then keep data + * and format index for future request, otherwise data is purged at + * last reference release. */ + if (!source->data_contents.size) { + /* data has been never received, thus must be empty. */ + assert(source->data_contents.size == 0); + assert(source->data_contents.alloc == 0); + assert(source->data_contents.data == NULL); + /* clear previous requested format so it can be requested later again. */ + source->format_index = -1; + } + + /* data has never been sent to write(), thus must be no inflight write. */ + assert(source->inflight_write_count == 0); + assert(source->inflight_data_to_write == NULL); + assert(source->inflight_data_size == 0); + /* data never has been sent to write(), so must not be processed. */ + assert(source->is_data_processed == FALSE); + /* close fd to server clipboard stop pulling data. */ + close(source->data_source_fd); + source->data_source_fd = -1; + /* clear inflight data source from client to server. */ + ctx->clipboard_inflight_client_data_source = NULL; + clipboard_data_source_unref(source); + + return 0; +} + +/* Send client's clipboard data to the requesting application at server side */ +static int +clipboard_data_source_write(int fd, uint32_t mask, void *arg) +{ + struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)arg; + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + void *data_to_write; + size_t data_size; + ssize_t size; + + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) fd:%d\n", __func__, + source, + clipboard_data_source_state_to_string(source), + fd); + + assert_compositor_thread(b); + + assert(source->data_source_fd == fd); + /* this data source must be tracked as inflight */ + assert(source == ctx->clipboard_inflight_client_data_source); + + if (source->is_canceled) { + /* if source is being canceled, this must be the last reference */ + assert(source->refcount == 1); + source->state = RDP_CLIPBOARD_SOURCE_CANCELED; + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) canceled\n", + __func__, source, + clipboard_data_source_state_to_string(source)); + goto fail; + } + + if (!source->data_contents.data || !source->data_contents.size) { + assert(source->refcount > 1); + weston_log("RDP %s (%p:%s) no data received from client\n", + __func__, source, + clipboard_data_source_state_to_string(source)); + goto fail; + } + + assert(source->refcount > 1); + if (source->inflight_data_to_write) { + assert(source->inflight_data_size); + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) transfer in chunck, count:%d\n", + __func__, source, + clipboard_data_source_state_to_string(source), + source->inflight_write_count); + data_to_write = source->inflight_data_to_write; + data_size = source->inflight_data_size; + } else { + fcntl(source->data_source_fd, F_SETFL, O_WRONLY | O_NONBLOCK); + clipboard_process_source(source, false); + data_to_write = source->processed_data_start; + data_size = source->processed_data_size; + } + while (data_to_write && data_size) { + source->state = RDP_CLIPBOARD_SOURCE_TRANSFERING; + do { + size = write(source->data_source_fd, data_to_write, data_size); + } while (size == -1 && errno == EINTR); + + if (size <= 0) { + if (errno != EAGAIN) { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + weston_log("RDP %s (%p:%s) write failed %s\n", + __func__, source, + clipboard_data_source_state_to_string(source), + strerror(errno)); + break; + } + /* buffer is full, wait until data_source_fd is writable again */ + source->inflight_data_to_write = data_to_write; + source->inflight_data_size = data_size; + source->inflight_write_count++; + return 0; + } else { + assert(data_size >= (size_t)size); + data_size -= size; + data_to_write = (char *)data_to_write + size; + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) wrote %ld bytes, remaining %ld bytes\n", + __func__, source, + clipboard_data_source_state_to_string(source), + size, data_size); + if (!data_size) { + source->state = RDP_CLIPBOARD_SOURCE_TRANSFERRED; + rdp_debug_clipboard(b, "RDP %s (%p:%s) write completed (%ld bytes)\n", + __func__, source, + clipboard_data_source_state_to_string(source), + source->data_contents.size); + } + } + } + +fail: + /* Here write is either completed, canceled or failed, so close the pipe. */ + close(source->data_source_fd); + source->data_source_fd = -1; + /* and remove the event source */ + wl_event_source_remove(source->transfer_event_source); + source->transfer_event_source = NULL; + /* and reset the inflight transfer state. */ + source->inflight_write_count = 0; + source->inflight_data_to_write = NULL; + source->inflight_data_size = 0; + ctx->clipboard_inflight_client_data_source = NULL; + clipboard_data_source_unref(source); + + return 0; +} + +/***********************************\ + * Clipboard data-device callbacks * +\***********************************/ + +/* data-device informs the given data format is accepted */ +static void +clipboard_data_source_accept(struct weston_data_source *base, + uint32_t time, const char *mime_type) +{ + struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)base; + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + + rdp_debug_clipboard(b, "RDP %s (%p:%s) mime-type:\"%s\"\n", + __func__, source, + clipboard_data_source_state_to_string(source), + mime_type); +} + +/* data-device informs the application requested the specified format data + * in given data_source (= client's clipboard) */ +static void +clipboard_data_source_send(struct weston_data_source *base, + const char *mime_type, int32_t fd) +{ + struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)base; + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + struct weston_seat *seat = ctx->item.seat; + struct wl_event_loop *loop = wl_display_get_event_loop(seat->compositor->wl_display); + CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest = {}; + int index; + + rdp_debug_clipboard(b, "RDP %s (%p:%s) fd:%d, mime-type:\"%s\"\n", + __func__, source, + clipboard_data_source_state_to_string(source), + fd, mime_type); + + assert_compositor_thread(b); + + if (ctx->clipboard_inflight_client_data_source) { + /* Here means server side (Linux application) request clipboard data, + but server hasn't completed with previous request yet. + If this happens, punt to idle loop and reattempt. */ + weston_log("\n\n\nRDP %s new (%p:%s:fd %d) vs prev (%p:%s:fd %d): outstanding RDP data request (client to server)\n\n\n", + __func__, source, clipboard_data_source_state_to_string(source), fd, + ctx->clipboard_inflight_client_data_source, + clipboard_data_source_state_to_string(ctx->clipboard_inflight_client_data_source), + ctx->clipboard_inflight_client_data_source->data_source_fd); + if (source == ctx->clipboard_inflight_client_data_source) { + /* when new source and previous source is same, update fd with new one and retry */ + source->state = RDP_CLIPBOARD_SOURCE_RETRY; + ctx->clipboard_inflight_client_data_source->data_source_fd = fd; + return; + } else { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + goto error_return_close_fd; + } + } + + if (source->base.mime_types.size == 0) { + source->state = RDP_CLIPBOARD_SOURCE_TRANSFERRED; + rdp_debug_clipboard(b, "RDP %s (%p:%s) source has no data\n", + __func__, source, clipboard_data_source_state_to_string(source)); + goto error_return_close_fd; + } + + index = clipboard_find_supported_format_by_mime_type(mime_type); + if (index >= 0 && /* check supported by this RDP bridge */ + source->client_format_id_table[index]) { /* check supported by current data source from client */ + ctx->clipboard_inflight_client_data_source = source; + source->refcount++; /* reference while request inflight. */ + source->data_source_fd = fd; + assert(source->inflight_write_count == 0); + assert(source->inflight_data_to_write == NULL); + assert(source->inflight_data_size == 0); + if (index == source->format_index) { + bool ret; + + /* data is already in data_contents, no need to pull from client */ + assert(source->transfer_event_source == NULL); + source->state = RDP_CLIPBOARD_SOURCE_RECEIVED_DATA; + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) data in cache \"%s\" index:%d formatId:%d %s\n", + __func__, source, + clipboard_data_source_state_to_string(source), + mime_type, index, + source->client_format_id_table[index], + clipboard_format_id_to_string(source->client_format_id_table[index], + false)); + + ret = rdp_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, + clipboard_data_source_write, source, + &source->transfer_event_source); + if (!ret) { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + weston_log("RDP %s (%p:%s) rdp_event_loop_add_fd failed\n", + __func__, source, + clipboard_data_source_state_to_string(source)); + goto error_return_unref_source; + } + } else { + /* purge cached data */ + wl_array_release(&source->data_contents); + wl_array_init(&source->data_contents); + source->is_data_processed = false; + /* update requesting format property */ + source->format_index = index; + /* request clipboard data from client */ + formatDataRequest.msgType = CB_FORMAT_DATA_REQUEST; + formatDataRequest.dataLen = 4; + formatDataRequest.requestedFormatId = source->client_format_id_table[index]; + source->state = RDP_CLIPBOARD_SOURCE_REQUEST_DATA; + rdp_debug_clipboard(b, "RDP %s (%p:%s) request data \"%s\" index:%d formatId:%d %s\n", + __func__, source, clipboard_data_source_state_to_string(source), mime_type, index, + formatDataRequest.requestedFormatId, + clipboard_format_id_to_string(formatDataRequest.requestedFormatId, false)); + if (ctx->clipboard_server_context->ServerFormatDataRequest(ctx->clipboard_server_context, &formatDataRequest) != 0) + goto error_return_unref_source; + } + } else { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + weston_log("RDP %s (%p:%s) specified format \"%s\" index:%d is not supported by client\n", + __func__, source, clipboard_data_source_state_to_string(source), + mime_type, index); + goto error_return_close_fd; + } + + return; + +error_return_unref_source: + source->data_source_fd = -1; + assert(source->inflight_write_count == 0); + assert(source->inflight_data_to_write == NULL); + assert(source->inflight_data_size == 0); + assert(ctx->clipboard_inflight_client_data_source == source); + ctx->clipboard_inflight_client_data_source = NULL; + clipboard_data_source_unref(source); + +error_return_close_fd: + close(fd); +} + +/* data-device informs the given data source is not longer referenced by compositor */ +static void +clipboard_data_source_cancel(struct weston_data_source *base) +{ + struct rdp_clipboard_data_source *source = (struct rdp_clipboard_data_source *)base; + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + + rdp_debug_clipboard(b, "RDP %s (%p:%s)\n", + __func__, source, + clipboard_data_source_state_to_string(source)); + + assert_compositor_thread(b); + + if (source == ctx->clipboard_inflight_client_data_source) { + source->is_canceled = true; + source->state = RDP_CLIPBOARD_SOURCE_CANCEL_PENDING; + rdp_debug_clipboard(b, "RDP %s (%p:%s): still inflight - refcount:%d\n", + __func__, source, + clipboard_data_source_state_to_string(source), + source->refcount); + assert(source->refcount > 1); + return; + } + /* everything outside of the base has to be cleaned up */ + source->state = RDP_CLIPBOARD_SOURCE_CANCELED; + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) - refcount:%d\n", + __func__, source, + clipboard_data_source_state_to_string(source), + source->refcount); + assert(source->refcount == 1); + assert(source->transfer_event_source == NULL); + wl_array_release(&source->data_contents); + wl_array_init(&source->data_contents); + source->is_data_processed = false; + source->format_index = -1; + memset(source->client_format_id_table, 0, sizeof(source->client_format_id_table)); + source->inflight_write_count = 0; + source->inflight_data_to_write = NULL; + source->inflight_data_size = 0; + if (source->data_source_fd != -1) { + close(source->data_source_fd); + source->data_source_fd = -1; + } +} + +/**********************************\ + * Compositor idle loop callbacks * +\**********************************/ + +/* Publish client's available clipboard formats to compositor (make them visible to applications in server) */ +static void +clipboard_data_source_publish(bool freeOnly, void *arg) +{ + struct rdp_clipboard_data_source *source = wl_container_of(arg, source, task_base); + freerdp_peer *client = (freerdp_peer *)source->context; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + struct rdp_clipboard_data_source *source_prev; + + rdp_debug_clipboard(b, "RDP %s (%p:%s)\n", + __func__, source, clipboard_data_source_state_to_string(source)); + + assert_compositor_thread(b); + + /* here is going to publish new data, if previous data from client is still referenced, + unref it after selection */ + source_prev = ctx->clipboard_client_data_source; + if (!freeOnly) { + ctx->clipboard_client_data_source = source; + source->transfer_event_source = NULL; + source->base.accept = clipboard_data_source_accept; + source->base.send = clipboard_data_source_send; + source->base.cancel = clipboard_data_source_cancel; + source->state = RDP_CLIPBOARD_SOURCE_PUBLISHED; + weston_seat_set_selection(ctx->item.seat, &source->base, + wl_display_next_serial(b->compositor->wl_display)); + } else { + ctx->clipboard_client_data_source = NULL; + clipboard_data_source_unref(source); + } + + if (source_prev) + clipboard_data_source_unref(source_prev); +} + +/* Request the specified clipboard data from data-device at server side */ +static void +clipboard_data_source_request(bool freeOnly, void *arg) +{ + struct rdp_clipboard_data_request *request = wl_container_of(arg, request, task_base); + RdpPeerContext *ctx = request->ctx; + struct rdp_backend *b = ctx->rdpBackend; + struct weston_seat *seat = ctx->item.seat; + struct weston_data_source *selection_data_source = seat->selection_data_source; + struct wl_event_loop *loop = wl_display_get_event_loop(seat->compositor->wl_display); + struct rdp_clipboard_data_source *source = NULL; + int p[2] = {}; + const char *requested_mime_type, **mime_type; + int index; + bool found_requested_format; + bool ret; + + assert_compositor_thread(b); + + if (freeOnly) + goto error_exit_free_request; + + index = request->requested_format_index; + assert(index >= 0 && index < (int)RDP_NUM_CLIPBOARD_FORMATS); + requested_mime_type = clipboard_supported_formats[index].mime_type; + rdp_debug_clipboard(b, "RDP %s (base:%p) requested mime type:\"%s\"\n", + __func__, selection_data_source, requested_mime_type); + + found_requested_format = FALSE; + wl_array_for_each(mime_type, &selection_data_source->mime_types) { + rdp_debug_clipboard(b, "RDP %s (base:%p) available formats: %s\n", + __func__, selection_data_source, *mime_type); + if (strcmp(requested_mime_type, *mime_type) == 0) { + found_requested_format = true; + break; + } + } + if (!found_requested_format) { + rdp_debug_clipboard(b, "RDP %s (base:%p) requested format not found format:\"%s\"\n", + __func__, selection_data_source, requested_mime_type); + goto error_exit_response_fail; + } + + source = zalloc(sizeof *source); + if (!source) + goto error_exit_response_fail; + + /* By now, the server side data availablity is already notified + to client by clipboard_set_selection(). */ + source->state = RDP_CLIPBOARD_SOURCE_PUBLISHED; + rdp_debug_clipboard(b, "RDP %s (%p:%s) for (base:%p)\n", + __func__, source, + clipboard_data_source_state_to_string(source), + selection_data_source); + wl_signal_init(&source->base.destroy_signal); + wl_array_init(&source->base.mime_types); + wl_array_init(&source->data_contents); + source->is_data_processed = false; + source->context = ctx->item.peer; + source->refcount = 1; /* decremented when data sent to client. */ + source->data_source_fd = -1; + source->format_index = index; + + if (pipe2(p, O_CLOEXEC) == -1) + goto error_exit_free_source; + + source->data_source_fd = p[0]; + + rdp_debug_clipboard_verbose(b, "RDP %s (%p:%s) pipe write:%d -> read:%d\n", + __func__, source, + clipboard_data_source_state_to_string(source), + p[1], p[0]); + + /* Request data from data source */ + source->state = RDP_CLIPBOARD_SOURCE_REQUEST_DATA; + selection_data_source->send(selection_data_source, requested_mime_type, p[1]); + /* p[1] should be closed by data source */ + + ret = rdp_event_loop_add_fd(loop, p[0], WL_EVENT_READABLE, + clipboard_data_source_read, source, + &source->transfer_event_source); + if (!ret) { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + weston_log("RDP %s (%p:%s) rdp_event_loop_add_fd failed.\n", + __func__, source, + clipboard_data_source_state_to_string(source)); + goto error_exit_free_source; + } + + free(request); + + return; + +error_exit_free_source: + assert(source->refcount == 1); + clipboard_data_source_unref(source); +error_exit_response_fail: + clipboard_client_send_format_data_response_fail(ctx, NULL); +error_exit_free_request: + free(request); +} + +/*************************************\ + * Compositor notification callbacks * +\*************************************/ + +/* Compositor notify new clipboard data is going to be copied to clipboard, and its supported formats */ +static void +clipboard_set_selection(struct wl_listener *listener, void *data) +{ + RdpPeerContext *ctx = + container_of(listener, RdpPeerContext, clipboard_selection_listener); + struct rdp_backend *b = ctx->rdpBackend; + struct weston_seat *seat = data; + struct weston_data_source *selection_data_source = seat->selection_data_source; + struct rdp_clipboard_data_source *data_source; + CLIPRDR_FORMAT_LIST formatList = {}; + CLIPRDR_FORMAT format[RDP_NUM_CLIPBOARD_FORMATS] = {}; + const char **mime_type; + int index, num_supported_format = 0, num_avail_format = 0; + + rdp_debug_clipboard(b, "RDP %s (base:%p)\n", __func__, selection_data_source); + + assert_compositor_thread(b); + + if (selection_data_source == NULL) { + return; + } + + if (selection_data_source->accept == clipboard_data_source_accept) { + /* Callback for our data source. */ + return; + } + + /* another data source (from server side) gets selected, + no longer need previous data from client. */ + if (ctx->clipboard_client_data_source) { + data_source = ctx->clipboard_client_data_source; + ctx->clipboard_client_data_source = NULL; + clipboard_data_source_unref(data_source); + } + + wl_array_for_each(mime_type, &selection_data_source->mime_types) { + rdp_debug_clipboard(b, "RDP %s (base:%p) available formats[%d]: %s\n", + __func__, selection_data_source, num_avail_format, *mime_type); + num_avail_format++; + } + + /* check supported clipboard formats */ + wl_array_for_each(mime_type, &selection_data_source->mime_types) { + index = clipboard_find_supported_format_by_mime_type(*mime_type); + if (index >= 0) { + CLIPRDR_FORMAT *f = &format[num_supported_format]; + + f->formatId = clipboard_supported_formats[index].format_id; + f->formatName = clipboard_supported_formats[index].format_name; + rdp_debug_clipboard(b, "RDP %s (base:%p) supported formats[%d]: %d: %s\n", + __func__, + selection_data_source, + num_supported_format, + f->formatId, + f->formatName ? + f->formatName : + clipboard_format_id_to_string(f->formatId, true)); + num_supported_format++; + } + } + + if (num_supported_format) { + /* let client knows formats are available in server clipboard */ + formatList.msgType = CB_FORMAT_LIST; + formatList.numFormats = num_supported_format; + formatList.formats = &format[0]; + ctx->clipboard_server_context->ServerFormatList(ctx->clipboard_server_context, &formatList); + } else { + rdp_debug_clipboard(b, "RDP %s (base:%p) no supported formats\n", __func__, selection_data_source); + } +} + +/*********************\ + * FreeRDP callbacks * +\*********************/ + +/* client reports the path of temp folder */ +static UINT +clipboard_client_temp_directory(CliprdrServerContext *context, const CLIPRDR_TEMP_DIRECTORY *tempDirectory) +{ + freerdp_peer *client = (freerdp_peer *)context->custom; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + + rdp_debug_clipboard(b, "Client: %s %s\n", __func__, tempDirectory->szTempDir); + return 0; +} + +/* client reports thier clipboard capabilities */ +static UINT +clipboard_client_capabilities(CliprdrServerContext *context, const CLIPRDR_CAPABILITIES *capabilities) +{ + freerdp_peer *client = (freerdp_peer *)context->custom; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + + rdp_debug_clipboard(b, "Client: clipboard capabilities: cCapabilitiesSet:%d\n", capabilities->cCapabilitiesSets); + for (uint32_t i = 0; i < capabilities->cCapabilitiesSets; i++) { + CLIPRDR_CAPABILITY_SET *capabilitySets = &capabilities->capabilitySets[i]; + CLIPRDR_GENERAL_CAPABILITY_SET *generalCapabilitySet = (CLIPRDR_GENERAL_CAPABILITY_SET *)capabilitySets; + + switch (capabilitySets->capabilitySetType) { + case CB_CAPSTYPE_GENERAL: + rdp_debug_clipboard(b, "Client: clipboard capabilities[%d]: General\n", i); + rdp_debug_clipboard(b, " Version:%d\n", generalCapabilitySet->version); + rdp_debug_clipboard(b, " GeneralFlags:0x%x\n", generalCapabilitySet->generalFlags); + if (generalCapabilitySet->generalFlags & CB_USE_LONG_FORMAT_NAMES) + rdp_debug_clipboard(b, " CB_USE_LONG_FORMAT_NAMES\n"); + if (generalCapabilitySet->generalFlags & CB_STREAM_FILECLIP_ENABLED) + rdp_debug_clipboard(b, " CB_STREAM_FILECLIP_ENABLED\n"); + if (generalCapabilitySet->generalFlags & CB_FILECLIP_NO_FILE_PATHS) + rdp_debug_clipboard(b, " CB_FILECLIP_NO_FILE_PATHS\n"); + if (generalCapabilitySet->generalFlags & CB_CAN_LOCK_CLIPDATA) + rdp_debug_clipboard(b, " CB_CAN_LOCK_CLIPDATA\n"); + break; + default: + return -1; + } + } + return 0; +} + +/* client reports the supported format list in client's clipboard */ +static UINT +clipboard_client_format_list(CliprdrServerContext *context, const CLIPRDR_FORMAT_LIST *formatList) +{ + CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = {}; + freerdp_peer *client = (freerdp_peer *)context->custom; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + struct rdp_clipboard_data_source *source = NULL; + char **p, *s; + + assert_not_compositor_thread(b); + + rdp_debug_clipboard(b, "Client: %s clipboard format list: numFormats:%d\n", __func__, formatList->numFormats); + for (uint32_t i = 0; i < formatList->numFormats; i++) { + CLIPRDR_FORMAT *format = &formatList->formats[i]; + + rdp_debug_clipboard(b, "Client: %s clipboard formats[%d]: formatId:%d, formatName:%s\n", + __func__, i, format->formatId, + format->formatName ? format->formatName : clipboard_format_id_to_string(format->formatId, false)); + } + + source = zalloc(sizeof *source); + if (!source) + goto fail; + + source->state = RDP_CLIPBOARD_SOURCE_ALLOCATED; + rdp_debug_clipboard(b, "Client: %s (%p:%s) allocated\n", + __func__, source, + clipboard_data_source_state_to_string(source)); + wl_signal_init(&source->base.destroy_signal); + wl_array_init(&source->base.mime_types); + wl_array_init(&source->data_contents); + source->context = client; + source->refcount = 1; /* decremented when another source is selected. */ + source->data_source_fd = -1; + source->format_index = -1; + + for (uint32_t i = 0; i < formatList->numFormats; i++) { + CLIPRDR_FORMAT *format = &formatList->formats[i]; + int index = clipboard_find_supported_format_by_format_id_and_name(format->formatId, format->formatName); + + if (index >= 0) { + /* save format id given from client, client can handle its own format id for private format. */ + source->client_format_id_table[index] = format->formatId; + s = strdup(clipboard_supported_formats[index].mime_type); + if (s) { + p = wl_array_add(&source->base.mime_types, sizeof *p); + if (p) { + rdp_debug_clipboard(b, "Client: %s (%p:%s) mine_type:\"%s\" index:%d formatId:%d\n", + __func__, source, + clipboard_data_source_state_to_string(source), + s, index, format->formatId); + *p = s; + } else { + rdp_debug_clipboard(b, "Client: %s (%p:%s) wl_array_add failed\n", + __func__, source, + clipboard_data_source_state_to_string(source)); + free(s); + } + } else { + rdp_debug_clipboard(b, "Client: %s (%p:%s) strdup failed\n", + __func__, source, + clipboard_data_source_state_to_string(source)); + } + } + } + + if (formatList->numFormats != 0 && + source->base.mime_types.size == 0) { + rdp_debug_clipboard(b, "Client: %s (%p:%s) no formats are supported\n", + __func__, source, + clipboard_data_source_state_to_string(source)); + } + + source->state = RDP_CLIPBOARD_SOURCE_FORMATLIST_READY; + rdp_dispatch_task_to_display_loop(ctx, clipboard_data_source_publish, &source->task_base); + +fail: + formatListResponse.msgType = CB_FORMAT_LIST_RESPONSE; + formatListResponse.msgFlags = source ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; + formatListResponse.dataLen = 0; + if (ctx->clipboard_server_context->ServerFormatListResponse(ctx->clipboard_server_context, &formatListResponse) != 0) { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + weston_log("Client: %s (%p:%s) ServerFormatListResponse failed\n", + __func__, source, + clipboard_data_source_state_to_string(source)); + return -1; + } + return 0; +} + +/* client responded with clipboard data asked by server */ +static UINT +clipboard_client_format_data_response(CliprdrServerContext *context, const CLIPRDR_FORMAT_DATA_RESPONSE *formatDataResponse) +{ + freerdp_peer *client = (freerdp_peer *)context->custom; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + struct wl_event_loop *loop = wl_display_get_event_loop(b->compositor->wl_display); + struct rdp_clipboard_data_source *source = ctx->clipboard_inflight_client_data_source; + bool success = false; + bool ret; + + rdp_debug_clipboard(b, "Client: %s (%p:%s) flags:%d dataLen:%d\n", + __func__, source, + clipboard_data_source_state_to_string(source), + formatDataResponse->msgFlags, + formatDataResponse->dataLen); + + assert_not_compositor_thread(b); + + if (!source) { + rdp_debug_clipboard(b, "Client: %s client send data without server asking. protocol error", __func__); + return -1; + } + + if (source->transfer_event_source || (source->inflight_write_count != 0)) { + /* here means client responded more than once for single data request */ + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + weston_log("Client: %s (%p:%s) middle of write loop:%p, %d\n", + __func__, source, clipboard_data_source_state_to_string(source), + source->transfer_event_source, source->inflight_write_count); + return -1; + } + + if (formatDataResponse->msgFlags == CB_RESPONSE_OK) { + /* Recieved data from client, cache to data source */ + if (wl_array_add(&source->data_contents, formatDataResponse->dataLen+1)) { + memcpy(source->data_contents.data, + formatDataResponse->requestedFormatData, + formatDataResponse->dataLen); + source->data_contents.size = formatDataResponse->dataLen; + /* regardless data type, make sure it ends with NULL */ + ((char *)source->data_contents.data)[source->data_contents.size] = '\0'; + /* data is ready, waiting to be written to destination */ + source->state = RDP_CLIPBOARD_SOURCE_RECEIVED_DATA; + success = true; + } else { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + } + } else { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + source->data_response_fail_count++; + } + rdp_debug_clipboard_verbose(b, "Client: %s (%p:%s) fail count:%d\n", + __func__, source, + clipboard_data_source_state_to_string(source), + source->data_response_fail_count); + + assert(source->transfer_event_source == NULL); + ret = rdp_event_loop_add_fd(loop, source->data_source_fd, WL_EVENT_WRITABLE, + success ? clipboard_data_source_write : clipboard_data_source_fail, + source, &source->transfer_event_source); + if (!ret) { + source->state = RDP_CLIPBOARD_SOURCE_FAILED; + weston_log("Client: %s (%p:%s) rdp_event_loop_add_fd failed\n", + __func__, source, clipboard_data_source_state_to_string(source)); + return -1; + } + + return 0; +} + +/* client responded on the format list sent by server */ +static UINT +clipboard_client_format_list_response(CliprdrServerContext *context, + const CLIPRDR_FORMAT_LIST_RESPONSE *formatListResponse) +{ + freerdp_peer *client = (freerdp_peer *)context->custom; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + + rdp_debug_clipboard(b, "Client: %s msgFlags:0x%x\n", __func__, formatListResponse->msgFlags); + assert_not_compositor_thread(b); + return 0; +} + +/* client requested the data of specificed format in server clipboard */ +static UINT +clipboard_client_format_data_request(CliprdrServerContext *context, + const CLIPRDR_FORMAT_DATA_REQUEST *formatDataRequest) +{ + freerdp_peer *client = (freerdp_peer *)context->custom; + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + struct rdp_clipboard_data_request *request; + int index; + + rdp_debug_clipboard(b, "Client: %s requestedFormatId:%d - %s\n", + __func__, formatDataRequest->requestedFormatId, + clipboard_format_id_to_string(formatDataRequest->requestedFormatId, true)); + + assert_not_compositor_thread(b); + + /* Make sure clients requested the format we knew */ + index = clipboard_find_supported_format_by_format_id(formatDataRequest->requestedFormatId); + if (index < 0) { + weston_log("Client: %s client requests data format the server never reported in format list response. protocol error.\n", __func__); + goto error_return; + } + + request = zalloc(sizeof(*request)); + if (!request) { + weston_log("zalloc failed\n"); + goto error_return; + } + request->ctx = ctx; + request->requested_format_index = index; + rdp_dispatch_task_to_display_loop(ctx, clipboard_data_source_request, &request->task_base); + + return 0; + +error_return: + /* send FAIL response to client */ + clipboard_client_send_format_data_response_fail(ctx, NULL); + return 0; +} + +/********************\ + * Public functions * +\********************/ + +int +rdp_clipboard_init(freerdp_peer *client) +{ + RdpPeerContext *ctx = (RdpPeerContext *)client->context; + struct rdp_backend *b = ctx->rdpBackend; + struct weston_seat *seat = ctx->item.seat; + CliprdrServerContext *clip_ctx; + + assert(seat); + + assert_compositor_thread(b); + + ctx->clipboard_server_context = cliprdr_server_context_new(ctx->vcm); + if (!ctx->clipboard_server_context) + goto error; + + clip_ctx = ctx->clipboard_server_context; + clip_ctx->custom = (void *)client; + clip_ctx->TempDirectory = clipboard_client_temp_directory; + clip_ctx->ClientCapabilities = clipboard_client_capabilities; + clip_ctx->ClientFormatList = clipboard_client_format_list; + clip_ctx->ClientFormatListResponse = clipboard_client_format_list_response; + /* clip_ctx->ClientLockClipboardData */ + /* clip_ctx->ClientUnlockClipboardData */ + clip_ctx->ClientFormatDataRequest = clipboard_client_format_data_request; + clip_ctx->ClientFormatDataResponse = clipboard_client_format_data_response; + /* clip_ctxClientFileContentsRequest */ + /* clip_ctx->ClientFileContentsResponse */ + clip_ctx->useLongFormatNames = FALSE; /* ASCII8 format name only (No Windows-style 2 bytes Unicode). */ + clip_ctx->streamFileClipEnabled = FALSE; + clip_ctx->fileClipNoFilePaths = FALSE; + clip_ctx->canLockClipData = TRUE; + if (clip_ctx->Start(ctx->clipboard_server_context) != 0) + goto error; + + ctx->clipboard_selection_listener.notify = clipboard_set_selection; + wl_signal_add(&seat->selection_signal, + &ctx->clipboard_selection_listener); + + return 0; + +error: + if (ctx->clipboard_server_context) { + cliprdr_server_context_free(ctx->clipboard_server_context); + ctx->clipboard_server_context = NULL; + } + + return -1; +} + +void +rdp_clipboard_destroy(RdpPeerContext *ctx) +{ + struct rdp_clipboard_data_source *data_source; + struct rdp_backend *b = ctx->rdpBackend; + + assert_compositor_thread(b); + + if (ctx->clipboard_selection_listener.notify) { + wl_list_remove(&ctx->clipboard_selection_listener.link); + ctx->clipboard_selection_listener.notify = NULL; + } + + if (ctx->clipboard_inflight_client_data_source) { + data_source = ctx->clipboard_inflight_client_data_source; + ctx->clipboard_inflight_client_data_source = NULL; + clipboard_data_source_unref(data_source); + } + + if (ctx->clipboard_client_data_source) { + data_source = ctx->clipboard_client_data_source; + ctx->clipboard_client_data_source = NULL; + clipboard_data_source_unref(data_source); + } + + if (ctx->clipboard_server_context) { + ctx->clipboard_server_context->Stop(ctx->clipboard_server_context); + cliprdr_server_context_free(ctx->clipboard_server_context); + ctx->clipboard_server_context = NULL; + } +} diff --git a/libweston/backend-rdp/rdputil.c b/libweston/backend-rdp/rdputil.c new file mode 100644 index 00000000..9aca3139 --- /dev/null +++ b/libweston/backend-rdp/rdputil.c @@ -0,0 +1,262 @@ +/* + * Copyright © 2020 Microsoft + * + * 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. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rdp.h" + +static int cached_tm_mday = -1; + +void rdp_debug_print(struct weston_log_scope *log_scope, bool cont, char *fmt, ...) +{ + char timestr[128]; + int len_va; + char *str; + + if (!log_scope || !weston_log_scope_is_enabled(log_scope)) + return; + + va_list ap; + va_start(ap, fmt); + + if (cont) { + weston_log_scope_vprintf(log_scope, fmt, ap); + goto end; + } + + weston_log_timestamp(timestr, sizeof(timestr), &cached_tm_mday); + len_va = vasprintf(&str, fmt, ap); + if (len_va >= 0) { + weston_log_scope_printf(log_scope, "%s %s", + timestr, str); + free(str); + } else { + const char *oom = "Out of memory"; + + weston_log_scope_printf(log_scope, "%s %s", + timestr, oom); + } +end: + va_end(ap); +} + +void +assert_compositor_thread(struct rdp_backend *b) +{ + assert(b->compositor_tid == gettid()); +} + +void +assert_not_compositor_thread(struct rdp_backend *b) +{ + assert(b->compositor_tid != gettid()); +} + +bool +rdp_event_loop_add_fd(struct wl_event_loop *loop, + int fd, uint32_t mask, + wl_event_loop_fd_func_t func, + void *data, struct wl_event_source **event_source) +{ + *event_source = wl_event_loop_add_fd(loop, fd, 0, func, data); + if (!*event_source) { + weston_log("%s: wl_event_loop_add_fd failed.\n", __func__); + return false; + } + + wl_event_source_fd_update(*event_source, mask); + return true; +} + +void +rdp_dispatch_task_to_display_loop(RdpPeerContext *peerCtx, + rdp_loop_task_func_t func, + struct rdp_loop_task *task) +{ + /* this function is ONLY used to queue the task from FreeRDP thread, + * and the task to be processed at wayland display loop thread. */ + assert_not_compositor_thread(peerCtx->rdpBackend); + + task->peerCtx = peerCtx; + task->func = func; + + pthread_mutex_lock(&peerCtx->loop_task_list_mutex); + /* this inserts at head */ + wl_list_insert(&peerCtx->loop_task_list, &task->link); + pthread_mutex_unlock(&peerCtx->loop_task_list_mutex); + + eventfd_write(peerCtx->loop_task_event_source_fd, 1); +} + +static int +rdp_dispatch_task(int fd, uint32_t mask, void *arg) +{ + RdpPeerContext *peerCtx = (RdpPeerContext *)arg; + struct rdp_loop_task *task, *tmp; + eventfd_t dummy; + + /* this must be called back at wayland display loop thread */ + assert_compositor_thread(peerCtx->rdpBackend); + + eventfd_read(peerCtx->loop_task_event_source_fd, &dummy); + + pthread_mutex_lock(&peerCtx->loop_task_list_mutex); + /* dequeue the first task which is at last, so use reverse. */ + assert(!wl_list_empty(&peerCtx->loop_task_list)); + wl_list_for_each_reverse_safe(task, tmp, &peerCtx->loop_task_list, link) { + wl_list_remove(&task->link); + break; + } + pthread_mutex_unlock(&peerCtx->loop_task_list_mutex); + + /* Dispatch and task will be freed by caller. */ + task->func(false, task); + + return 0; +} + +bool +rdp_initialize_dispatch_task_event_source(RdpPeerContext *peerCtx) +{ + struct rdp_backend *b = peerCtx->rdpBackend; + struct wl_event_loop *loop; + bool ret; + + if (pthread_mutex_init(&peerCtx->loop_task_list_mutex, NULL) == -1) { + weston_log("%s: pthread_mutex_init failed. %s\n", __func__, strerror(errno)); + goto error_mutex; + } + + assert(peerCtx->loop_task_event_source_fd == -1); + peerCtx->loop_task_event_source_fd = eventfd(0, EFD_SEMAPHORE | EFD_CLOEXEC); + if (peerCtx->loop_task_event_source_fd == -1) { + weston_log("%s: eventfd(EFD_SEMAPHORE) failed. %s\n", __func__, strerror(errno)); + goto error_event_source_fd; + } + + assert(wl_list_empty(&peerCtx->loop_task_list)); + + loop = wl_display_get_event_loop(b->compositor->wl_display); + assert(peerCtx->loop_task_event_source == NULL); + + ret = rdp_event_loop_add_fd(loop, + peerCtx->loop_task_event_source_fd, + WL_EVENT_READABLE, rdp_dispatch_task, + peerCtx, + &peerCtx->loop_task_event_source); + if (!ret) + goto error_event_loop_add_fd; + + return true; + +error_event_loop_add_fd: + close(peerCtx->loop_task_event_source_fd); + peerCtx->loop_task_event_source_fd = -1; + +error_event_source_fd: + pthread_mutex_destroy(&peerCtx->loop_task_list_mutex); + +error_mutex: + return false; +} + +void +rdp_destroy_dispatch_task_event_source(RdpPeerContext *peerCtx) +{ + struct rdp_loop_task *task, *tmp; + + /* This function must be called all virtual channel thread at FreeRDP is terminated, + * that ensures no more incoming tasks. */ + + if (peerCtx->loop_task_event_source) { + wl_event_source_remove(peerCtx->loop_task_event_source); + peerCtx->loop_task_event_source = NULL; + } + + wl_list_for_each_reverse_safe(task, tmp, &peerCtx->loop_task_list, link) { + wl_list_remove(&task->link); + /* inform caller task is not really scheduled prior to context destruction, + * inform them to clean them up. */ + task->func(true /* freeOnly */, task); + } + assert(wl_list_empty(&peerCtx->loop_task_list)); + + if (peerCtx->loop_task_event_source_fd != -1) { + close(peerCtx->loop_task_event_source_fd); + peerCtx->loop_task_event_source_fd = -1; + } + + pthread_mutex_destroy(&peerCtx->loop_task_list_mutex); +} + +/* This is a little tricky - it makes sure there's always at least + * one spare byte in the array in case the caller needs to add a + * null terminator to it. We can't just null terminate the array + * here, because some callers won't want that - and some won't + * like having an odd number of bytes. + */ +int +rdp_wl_array_read_fd(struct wl_array *array, int fd) +{ + int len, size; + char *data; + + /* Make sure we have at least 1024 bytes of space left */ + if (array->alloc - array->size < 1024) { + if (!wl_array_add(array, 1024)) { + errno = ENOMEM; + return -1; + } + array->size -= 1024; + } + data = (char *)array->data + array->size; + /* Leave one char in case the caller needs space for a + * null terminator */ + size = array->alloc - array->size - 1; + do { + len = read(fd, data, size); + } while (len == -1 && errno == EINTR); + + if (len == -1) + return -1; + + array->size += len; + + return len; +} diff --git a/libweston/backend-x11/x11.c b/libweston/backend-x11/x11.c index 5c82584b..34820a06 100644 --- a/libweston/backend-x11/x11.c +++ b/libweston/backend-x11/x11.c @@ -119,6 +119,7 @@ struct x11_backend { xcb_atom_t cardinal; xcb_atom_t xkb_names; } atom; + xcb_generic_event_t *prev_event; }; struct x11_head { @@ -151,15 +152,25 @@ struct window_delete_data { struct gl_renderer_interface *gl_renderer; +static void +x11_head_destroy(struct weston_head *base); + static inline struct x11_head * to_x11_head(struct weston_head *base) { + if (base->backend_id != x11_head_destroy) + return NULL; return container_of(base, struct x11_head, base); } +static void +x11_output_destroy(struct weston_output *base); + static inline struct x11_output * to_x11_output(struct weston_output *base) { + if (base->destroy != x11_output_destroy) + return NULL; return container_of(base, struct x11_output, base); } @@ -417,11 +428,14 @@ x11_output_start_repaint_loop(struct weston_output *output) static int x11_output_repaint_gl(struct weston_output *output_base, - pixman_region32_t *damage, - void *repaint_data) + pixman_region32_t *damage) { struct x11_output *output = to_x11_output(output_base); - struct weston_compositor *ec = output->base.compositor; + struct weston_compositor *ec; + + assert(output); + + ec = output->base.compositor; ec->renderer->repaint_output(output_base, damage); @@ -436,8 +450,8 @@ static void set_clip_for_output(struct weston_output *output_base, pixman_region32_t *region) { struct x11_output *output = to_x11_output(output_base); - struct weston_compositor *ec = output->base.compositor; - struct x11_backend *b = to_x11_backend(ec); + struct weston_compositor *ec; + struct x11_backend *b; pixman_region32_t transformed_region; pixman_box32_t *rects; xcb_rectangle_t *output_rects; @@ -445,6 +459,12 @@ set_clip_for_output(struct weston_output *output_base, pixman_region32_t *region int nrects, i; xcb_generic_error_t *err; + if (!output) + return; + + ec = output->base.compositor; + b = to_x11_backend(ec); + pixman_region32_init(&transformed_region); pixman_region32_copy(&transformed_region, region); pixman_region32_translate(&transformed_region, @@ -486,15 +506,19 @@ set_clip_for_output(struct weston_output *output_base, pixman_region32_t *region static int x11_output_repaint_shm(struct weston_output *output_base, - pixman_region32_t *damage, - void *repaint_data) + pixman_region32_t *damage) { struct x11_output *output = to_x11_output(output_base); - struct weston_compositor *ec = output->base.compositor; - struct x11_backend *b = to_x11_backend(ec); + struct weston_compositor *ec; + struct x11_backend *b; xcb_void_cookie_t cookie; xcb_generic_error_t *err; + assert(output); + + ec = output->base.compositor; + b = to_x11_backend(ec); + pixman_renderer_output_set_buffer(output_base, output->hw_surface); ec->renderer->repaint_output(output_base, damage); @@ -567,13 +591,13 @@ x11_output_set_wm_protocols(struct x11_backend *b, } struct wm_normal_hints { - uint32_t flags; + uint32_t flags; uint32_t pad[4]; int32_t min_width, min_height; int32_t max_width, max_height; - int32_t width_inc, height_inc; - int32_t min_aspect_x, min_aspect_y; - int32_t max_aspect_x, max_aspect_y; + int32_t width_inc, height_inc; + int32_t min_aspect_x, min_aspect_y; + int32_t max_aspect_x, max_aspect_y; int32_t base_width, base_height; int32_t win_gravity; }; @@ -803,12 +827,13 @@ static int x11_output_switch_mode(struct weston_output *base, struct weston_mode *mode) { struct x11_backend *b; - struct x11_output *output; + struct x11_output *output = to_x11_output(base); static uint32_t values[2]; int ret; + assert(output); + b = to_x11_backend(base->compositor); - output = to_x11_output(base); if (mode->width == output->mode.width && mode->height == output->mode.height) @@ -880,7 +905,11 @@ static int x11_output_disable(struct weston_output *base) { struct x11_output *output = to_x11_output(base); - struct x11_backend *backend = to_x11_backend(base->compositor); + struct x11_backend *backend; + + assert(output); + + backend = to_x11_backend(base->compositor); if (!output->base.enabled) return 0; @@ -905,6 +934,8 @@ x11_output_destroy(struct weston_output *base) { struct x11_output *output = to_x11_output(base); + assert(output); + x11_output_disable(&output->base); weston_output_release(&output->base); @@ -915,7 +946,12 @@ static int x11_output_enable(struct weston_output *base) { struct x11_output *output = to_x11_output(base); - struct x11_backend *b = to_x11_backend(base->compositor); + const struct weston_mode *mode = output->base.current_mode; + struct x11_backend *b; + + assert(output); + + b = to_x11_backend(base->compositor); static const char name[] = "Weston Compositor"; static const char class[] = "weston-1\0Weston Compositor"; @@ -954,8 +990,7 @@ x11_output_enable(struct weston_output *base) output->window, screen->root, 0, 0, - output->base.current_mode->width, - output->base.current_mode->height, + mode->width, mode->height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, @@ -1020,8 +1055,7 @@ x11_output_enable(struct weston_output *base) .use_shadow = true, }; if (x11_output_init_shm(b, output, - output->base.current_mode->width, - output->base.current_mode->height) < 0) { + mode->width, mode->height) < 0) { weston_log("Failed to initialize SHM for the X11 output\n"); goto err; } @@ -1062,9 +1096,7 @@ x11_output_enable(struct weston_output *base) wl_event_loop_add_timer(loop, finish_frame_handler, output); weston_log("x11 output %dx%d, window id %d\n", - output->base.current_mode->width, - output->base.current_mode->height, - output->window); + mode->width, mode->height, output->window); return 0; @@ -1079,11 +1111,17 @@ static int x11_output_set_size(struct weston_output *base, int width, int height) { struct x11_output *output = to_x11_output(base); - struct x11_backend *b = to_x11_backend(base->compositor); + struct x11_backend *b; struct weston_head *head; - xcb_screen_t *scrn = b->screen; + xcb_screen_t *scrn; int output_width, output_height; + if (!output) + return -1; + + b = to_x11_backend(base->compositor); + scrn = b->screen; + /* We can only be called once. */ assert(!output->base.current_mode); @@ -1165,6 +1203,9 @@ x11_head_create(struct weston_compositor *compositor, const char *name) return -1; weston_head_init(&head->base, name); + + head->base.backend_id = x11_head_destroy; + weston_head_set_connection_status(&head->base, true); weston_compositor_add_head(compositor, &head->base); @@ -1172,8 +1213,12 @@ x11_head_create(struct weston_compositor *compositor, const char *name) } static void -x11_head_destroy(struct x11_head *head) +x11_head_destroy(struct weston_head *base) { + struct x11_head *head = to_x11_head(base); + + assert(head); + weston_head_release(&head->base); free(head); } @@ -1450,7 +1495,7 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) { struct x11_backend *b = data; struct x11_output *output; - xcb_generic_event_t *event, *prev; + xcb_generic_event_t *event; xcb_client_message_event_t *client_message; xcb_enter_notify_event_t *enter_notify; xcb_key_press_event_t *key_press, *key_release; @@ -1466,16 +1511,15 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) int count; struct timespec time; - prev = NULL; count = 0; while (x11_backend_next_event(b, &event, mask)) { response_type = event->response_type & ~0x80; - switch (prev ? prev->response_type & ~0x80 : 0x80) { + switch (b->prev_event ? b->prev_event->response_type & ~0x80 : 0x80) { case XCB_KEY_RELEASE: /* Suppress key repeat events; this is only used if we * don't have XCB XKB support. */ - key_release = (xcb_key_press_event_t *) prev; + key_release = (xcb_key_press_event_t *) b->prev_event; key_press = (xcb_key_press_event_t *) event; if (response_type == XCB_KEY_PRESS && key_release->time == key_press->time && @@ -1483,8 +1527,8 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) /* Don't deliver the held key release * event or the new key press event. */ free(event); - free(prev); - prev = NULL; + free(b->prev_event); + b->prev_event = NULL; continue; } else { /* Deliver the held key release now @@ -1497,8 +1541,8 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) key_release->detail - 8, WL_KEYBOARD_KEY_STATE_RELEASED, STATE_UPDATE_AUTOMATIC); - free(prev); - prev = NULL; + free(b->prev_event); + b->prev_event = NULL; break; } @@ -1522,8 +1566,8 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) notify_keyboard_focus_in(&b->core_seat, &b->keys, STATE_UPDATE_AUTOMATIC); - free(prev); - prev = NULL; + free(b->prev_event); + b->prev_event = NULL; break; default: @@ -1548,7 +1592,7 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) /* If we don't have XKB, we need to use the lame * autorepeat detection above. */ if (!b->has_xkb) { - prev = event; + b->prev_event = event; break; } key_release = (xcb_key_press_event_t *) event; @@ -1643,7 +1687,7 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) if (focus_in->mode == XCB_NOTIFY_MODE_WHILE_GRABBED) break; - prev = event; + b->prev_event = event; break; case XCB_FOCUS_OUT: @@ -1677,13 +1721,13 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) #endif count++; - if (prev != event) - free (event); + if (b->prev_event != event) + free(event); } - switch (prev ? prev->response_type & ~0x80 : 0x80) { + switch (b->prev_event ? b->prev_event->response_type & ~0x80 : 0x80) { case XCB_KEY_RELEASE: - key_release = (xcb_key_press_event_t *) prev; + key_release = (xcb_key_press_event_t *) b->prev_event; update_xkb_state_from_core(b, key_release->state); weston_compositor_get_time(&time); notify_key(&b->core_seat, @@ -1691,8 +1735,8 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) key_release->detail - 8, WL_KEYBOARD_KEY_STATE_RELEASED, STATE_UPDATE_AUTOMATIC); - free(prev); - prev = NULL; + free(b->prev_event); + b->prev_event = NULL; break; default: break; @@ -1792,8 +1836,10 @@ x11_destroy(struct weston_compositor *ec) weston_compositor_shutdown(ec); /* destroys outputs, too */ - wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) - x11_head_destroy(to_x11_head(base)); + wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) { + if (to_x11_head(base)) + x11_head_destroy(base); + } XCloseDisplay(backend->dpy); free(backend); diff --git a/libweston/backend.h b/libweston/backend.h index 3ae59a6c..2374b17b 100644 --- a/libweston/backend.h +++ b/libweston/backend.h @@ -32,6 +32,8 @@ #ifndef LIBWESTON_BACKEND_INTERNAL_H #define LIBWESTON_BACKEND_INTERNAL_H +struct weston_hdr_metadata_type1; + struct weston_backend { void (*destroy)(struct weston_compositor *compositor); @@ -46,27 +48,21 @@ struct weston_backend { * Returns an opaque pointer, which the backend may use as private * data referring to the repaint cycle. */ - void * (*repaint_begin)(struct weston_compositor *compositor); + void (*repaint_begin)(struct weston_compositor *compositor); /** Cancel a repaint sequence * * Cancels a repaint sequence, when an error has occurred during * one output's repaint; see repaint_begin. - * - * @param repaint_data Data returned by repaint_begin */ - void (*repaint_cancel)(struct weston_compositor *compositor, - void *repaint_data); + void (*repaint_cancel)(struct weston_compositor *compositor); /** Conclude a repaint sequence * * Called on successful completion of a repaint sequence; see * repaint_begin. - * - * @param repaint_data Data returned by repaint_begin */ - int (*repaint_flush)(struct weston_compositor *compositor, - void *repaint_data); + int (*repaint_flush)(struct weston_compositor *compositor); /** Allocate a new output * @@ -142,6 +138,10 @@ weston_head_set_subpixel(struct weston_head *head, void weston_head_set_transform(struct weston_head *head, uint32_t transform); +void +weston_head_set_supported_eotf_mask(struct weston_head *head, + uint32_t eotf_mask); + /* weston_output */ void @@ -154,9 +154,6 @@ weston_output_damage(struct weston_output *output); void weston_output_release(struct weston_output *output); -void -weston_output_init_zoom(struct weston_output *output); - void weston_output_finish_frame(struct weston_output *output, const struct timespec *stamp, @@ -178,6 +175,9 @@ void weston_output_region_from_global(struct weston_output *output, pixman_region32_t *region); +const struct weston_hdr_metadata_type1 * +weston_output_get_hdr_metadata_type1(const struct weston_output *output); + /* weston_seat */ void diff --git a/libweston/bindings.c b/libweston/bindings.c index 2ca999a3..526379ab 100644 --- a/libweston/bindings.c +++ b/libweston/bindings.c @@ -310,8 +310,8 @@ weston_compositor_run_key_binding(struct weston_compositor *compositor, /* If this was a key binding and it didn't * install a keyboard grab, install one now to * swallow the key press. */ - if (keyboard->grab == - &keyboard->default_grab) + if (keyboard->grab == &keyboard->default_grab || + keyboard->grab == &keyboard->input_method_grab) install_binding_grab(keyboard, time, key, diff --git a/libweston/clipboard.c b/libweston/clipboard.c index 7d60351a..a82d8fe8 100644 --- a/libweston/clipboard.c +++ b/libweston/clipboard.c @@ -110,7 +110,7 @@ clipboard_source_data(int fd, uint32_t mask, void *data) static void clipboard_source_accept(struct weston_data_source *source, - uint32_t time, const char *mime_type) + uint32_t serial, const char *mime_type) { } diff --git a/libweston/color-lcms/color-lcms.c b/libweston/color-lcms/color-lcms.c index 18340a39..97e6d6ab 100644 --- a/libweston/color-lcms/color-lcms.c +++ b/libweston/color-lcms/color-lcms.c @@ -27,12 +27,37 @@ #include "config.h" #include +#include #include #include "color.h" #include "color-lcms.h" #include "shared/helpers.h" +static cmsUInt32Number +cmlcms_get_render_intent(enum cmlcms_category cat, + struct weston_surface *surface, + struct weston_output *output) +{ + /* + * TODO: Take into account client provided content profile, + * output profile, and the category of the wanted color + * transformation. + */ + cmsUInt32Number intent = INTENT_RELATIVE_COLORIMETRIC; + return intent; +} + +static struct cmlcms_color_profile * +get_cprof_or_stock_sRGB(struct weston_color_manager_lcms *cm, + struct weston_color_profile *cprof_base) +{ + if (cprof_base) + return get_cprof(cprof_base); + else + return cm->sRGB_profile; +} + static void cmlcms_destroy_color_transform(struct weston_color_transform *xform_base) { @@ -48,47 +73,52 @@ cmlcms_get_surface_color_transform(struct weston_color_manager *cm_base, struct weston_surface_color_transform *surf_xform) { struct weston_color_manager_lcms *cm = get_cmlcms(cm_base); - struct cmlcms_color_transform_search_param param = { - /* - * Assumes both content and output color spaces are sRGB SDR. - * This defines the blending space as optical sRGB SDR. - */ - .type = CMLCMS_TYPE_EOTF_sRGB, - }; struct cmlcms_color_transform *xform; - /* TODO: use output color profile */ - if (output->color_profile) - return false; + /* TODO: take weston_output::eotf_mode into account */ + + struct cmlcms_color_transform_search_param param = { + .category = CMLCMS_CATEGORY_INPUT_TO_BLEND, + .input_profile = get_cprof_or_stock_sRGB(cm, NULL /* TODO: surface->color_profile */), + .output_profile = get_cprof_or_stock_sRGB(cm, output->color_profile), + }; + param.intent_output = cmlcms_get_render_intent(param.category, + surface, output); xform = cmlcms_color_transform_get(cm, ¶m); if (!xform) return false; surf_xform->transform = &xform->base; - surf_xform->identity_pipeline = true; + /* + * When we introduce LCMS plug-in we can precisely answer this question + * by examining the color pipeline using precision parameters. For now + * we just compare if it is same pointer or not. + */ + if (xform->search_key.input_profile == xform->search_key.output_profile) + surf_xform->identity_pipeline = true; + else + surf_xform->identity_pipeline = false; return true; } static bool -cmlcms_get_output_color_transform(struct weston_color_manager *cm_base, - struct weston_output *output, - struct weston_color_transform **xform_out) +cmlcms_get_blend_to_output_color_transform(struct weston_color_manager_lcms *cm, + struct weston_output *output, + struct weston_color_transform **xform_out) { - struct weston_color_manager_lcms *cm = get_cmlcms(cm_base); - struct cmlcms_color_transform_search_param param = { - /* - * Assumes blending space is optical sRGB SDR and - * output color space is sRGB SDR. - */ - .type = CMLCMS_TYPE_EOTF_sRGB_INV, - }; struct cmlcms_color_transform *xform; - /* TODO: use output color profile */ - if (output->color_profile) - return false; + /* TODO: take weston_output::eotf_mode into account */ + + struct cmlcms_color_transform_search_param param = { + .category = CMLCMS_CATEGORY_BLEND_TO_OUTPUT, + .input_profile = NULL, + .output_profile = get_cprof_or_stock_sRGB(cm, output->color_profile), + }; + param.intent_output = cmlcms_get_render_intent(param.category, + NULL, output); xform = cmlcms_color_transform_get(cm, ¶m); if (!xform) @@ -99,37 +129,54 @@ cmlcms_get_output_color_transform(struct weston_color_manager *cm_base, } static bool -cmlcms_get_sRGB_to_output_color_transform(struct weston_color_manager *cm_base, +cmlcms_get_sRGB_to_output_color_transform(struct weston_color_manager_lcms *cm, struct weston_output *output, struct weston_color_transform **xform_out) { - /* Assumes output color space is sRGB SDR */ + struct cmlcms_color_transform *xform; - /* TODO: use output color profile */ - if (output->color_profile) - return false; + /* TODO: take weston_output::eotf_mode into account */ - /* Identity transform */ - *xform_out = NULL; + struct cmlcms_color_transform_search_param param = { + .category = CMLCMS_CATEGORY_INPUT_TO_OUTPUT, + .input_profile = cm->sRGB_profile, + .output_profile = get_cprof_or_stock_sRGB(cm, output->color_profile), + }; + param.intent_output = cmlcms_get_render_intent(param.category, + NULL, output); + + /* + * Create a color transformation when output profile is not stock + * sRGB profile. + */ + if (param.output_profile != cm->sRGB_profile) { + xform = cmlcms_color_transform_get(cm, ¶m); + if (!xform) + return false; + *xform_out = &xform->base; + } else { + *xform_out = NULL; /* Identity transform */ + } return true; } static bool -cmlcms_get_sRGB_to_blend_color_transform(struct weston_color_manager *cm_base, +cmlcms_get_sRGB_to_blend_color_transform(struct weston_color_manager_lcms *cm, struct weston_output *output, struct weston_color_transform **xform_out) { - struct weston_color_manager_lcms *cm = get_cmlcms(cm_base); - struct cmlcms_color_transform_search_param param = { - /* Assumes blending space is optical sRGB SDR */ - .type = CMLCMS_TYPE_EOTF_sRGB, - }; struct cmlcms_color_transform *xform; - /* TODO: use output color profile */ - if (output->color_profile) - return false; + /* TODO: take weston_output::eotf_mode into account */ + + struct cmlcms_color_transform_search_param param = { + .category = CMLCMS_CATEGORY_INPUT_TO_BLEND, + .input_profile = cm->sRGB_profile, + .output_profile = get_cprof_or_stock_sRGB(cm, output->color_profile), + }; + param.intent_output = cmlcms_get_render_intent(param.category, + NULL, output); xform = cmlcms_color_transform_get(cm, ¶m); if (!xform) @@ -139,6 +186,145 @@ cmlcms_get_sRGB_to_blend_color_transform(struct weston_color_manager *cm_base, return true; } +static float +meta_clamp(float value, const char *valname, float min, float max, + struct weston_output *output) +{ + float ret = value; + + /* Paranoia against NaN */ + if (!(ret >= min)) + ret = min; + + if (!(ret <= max)) + ret = max; + + if (ret != value) { + weston_log("output '%s' clamping %s value from %f to %f.\n", + output->name, valname, value, ret); + } + + return ret; +} + +static bool +cmlcms_get_hdr_meta(struct weston_output *output, + struct weston_hdr_metadata_type1 *hdr_meta) +{ + const struct weston_color_characteristics *cc; + + hdr_meta->group_mask = 0; + + /* Only SMPTE ST 2084 mode uses HDR Static Metadata Type 1 */ + if (weston_output_get_eotf_mode(output) != WESTON_EOTF_MODE_ST2084) + return true; + + /* ICC profile overrides color characteristics */ + if (output->color_profile) { + /* + * TODO: extract characteristics from profile? + * Get dynamic range from weston_color_characteristics? + */ + + return true; + } + + cc = weston_output_get_color_characteristics(output); + + /* Target content chromaticity */ + if (cc->group_mask & WESTON_COLOR_CHARACTERISTICS_GROUP_PRIMARIES) { + unsigned i; + + for (i = 0; i < 3; i++) { + hdr_meta->primary[i].x = meta_clamp(cc->primary[i].x, + "primary", 0.0, 1.0, + output); + hdr_meta->primary[i].y = meta_clamp(cc->primary[i].y, + "primary", 0.0, 1.0, + output); + } + hdr_meta->group_mask |= WESTON_HDR_METADATA_TYPE1_GROUP_PRIMARIES; + } + + /* Target content white point */ + if (cc->group_mask & WESTON_COLOR_CHARACTERISTICS_GROUP_WHITE) { + hdr_meta->white.x = meta_clamp(cc->white.x, "white", + 0.0, 1.0, output); + hdr_meta->white.y = meta_clamp(cc->white.y, "white", + 0.0, 1.0, output); + hdr_meta->group_mask |= WESTON_HDR_METADATA_TYPE1_GROUP_WHITE; + } + + /* Target content peak and max mastering luminance */ + if (cc->group_mask & WESTON_COLOR_CHARACTERISTICS_GROUP_MAXL) { + hdr_meta->maxDML = meta_clamp(cc->max_luminance, "maxDML", + 1.0, 65535.0, output); + hdr_meta->maxCLL = meta_clamp(cc->max_luminance, "maxCLL", + 1.0, 65535.0, output); + hdr_meta->group_mask |= WESTON_HDR_METADATA_TYPE1_GROUP_MAXDML; + hdr_meta->group_mask |= WESTON_HDR_METADATA_TYPE1_GROUP_MAXCLL; + } + + /* Target content min mastering luminance */ + if (cc->group_mask & WESTON_COLOR_CHARACTERISTICS_GROUP_MINL) { + hdr_meta->minDML = meta_clamp(cc->min_luminance, "minDML", + 0.0001, 6.5535, output); + hdr_meta->group_mask |= WESTON_HDR_METADATA_TYPE1_GROUP_MINDML; + } + + /* Target content max frame-average luminance */ + if (cc->group_mask & WESTON_COLOR_CHARACTERISTICS_GROUP_MAXFALL) { + hdr_meta->maxFALL = meta_clamp(cc->maxFALL, "maxFALL", + 1.0, 65535.0, output); + hdr_meta->group_mask |= WESTON_HDR_METADATA_TYPE1_GROUP_MAXFALL; + } + + return true; +} + +static struct weston_output_color_outcome * +cmlcms_create_output_color_outcome(struct weston_color_manager *cm_base, + struct weston_output *output) +{ + struct weston_color_manager_lcms *cm = get_cmlcms(cm_base); + struct weston_output_color_outcome *co; + + co = zalloc(sizeof *co); + if (!co) + return NULL; + + if (!cmlcms_get_hdr_meta(output, &co->hdr_meta)) + goto out_fail; + + /* + * TODO: if output->color_profile is NULL, maybe manufacture a + * profile from weston_color_characteristics if it has enough + * information? + * Or let the frontend decide to call a "create a profile from + * characteristics" API? + */ + + /* TODO: take container color space into account */ + + if (!cmlcms_get_blend_to_output_color_transform(cm, output, + &co->from_blend_to_output)) + goto out_fail; + + if (!cmlcms_get_sRGB_to_blend_color_transform(cm, output, + &co->from_sRGB_to_blend)) + goto out_fail; + + if (!cmlcms_get_sRGB_to_output_color_transform(cm, output, + &co->from_sRGB_to_output)) + goto out_fail; + + return co; + +out_fail: + weston_output_color_outcome_destroy(&co); + return NULL; +} + static void lcms_error_logger(cmsContext context_id, cmsUInt32Number error_code, @@ -165,6 +351,10 @@ cmlcms_init(struct weston_color_manager *cm_base) cmsSetLogErrorHandlerTHR(cm->lcms_ctx, lcms_error_logger); + if (!cmlcms_create_stock_profile(cm)) { + weston_log("color-lcms: error: cmlcms_create_stock_profile failed\n"); + return false; + } weston_log("LittleCMS %d initialized.\n", cmsGetEncodedCMMversion()); return true; @@ -175,6 +365,8 @@ cmlcms_destroy(struct weston_color_manager *cm_base) { struct weston_color_manager_lcms *cm = get_cmlcms(cm_base); + if (cm->sRGB_profile) + cmlcms_color_profile_destroy(cm->sRGB_profile); assert(wl_list_empty(&cm->color_transform_list)); assert(wl_list_empty(&cm->color_profile_list)); @@ -199,13 +391,8 @@ weston_color_manager_create(struct weston_compositor *compositor) cm->base.destroy_color_profile = cmlcms_destroy_color_profile; cm->base.get_color_profile_from_icc = cmlcms_get_color_profile_from_icc; cm->base.destroy_color_transform = cmlcms_destroy_color_transform; - cm->base.get_surface_color_transform = - cmlcms_get_surface_color_transform; - cm->base.get_output_color_transform = cmlcms_get_output_color_transform; - cm->base.get_sRGB_to_output_color_transform = - cmlcms_get_sRGB_to_output_color_transform; - cm->base.get_sRGB_to_blend_color_transform = - cmlcms_get_sRGB_to_blend_color_transform; + cm->base.get_surface_color_transform = cmlcms_get_surface_color_transform; + cm->base.create_output_color_outcome = cmlcms_create_output_color_outcome; wl_list_init(&cm->color_transform_list); wl_list_init(&cm->color_profile_list); diff --git a/libweston/color-lcms/color-lcms.h b/libweston/color-lcms/color-lcms.h index dd5c38fc..b7291764 100644 --- a/libweston/color-lcms/color-lcms.h +++ b/libweston/color-lcms/color-lcms.h @@ -39,6 +39,7 @@ struct weston_color_manager_lcms { struct wl_list color_transform_list; /* cmlcms_color_transform::link */ struct wl_list color_profile_list; /* cmlcms_color_profile::link */ + struct cmlcms_color_profile *sRGB_profile; /* stock profile */ }; static inline struct weston_color_manager_lcms * @@ -59,6 +60,58 @@ struct cmlcms_color_profile { cmsHPROFILE profile; struct cmlcms_md5_sum md5sum; + + /** + * If the profile does support being an output profile and it is used as an + * output then this field represents a light linearizing transfer function + * and it can not be null. The field is null only if the profile is not + * usable as an output profile. The field is set when cmlcms_color_profile + * is created. + */ + cmsToneCurve *output_eotf[3]; + + /** + * If the profile does support being an output profile and it is used as an + * output then this field represents a concatenation of inverse EOTF + VCGT, + * if the tag exists and it can not be null. + * VCGT is part of monitor calibration which means: even though we must + * apply VCGT in the compositor, we pretend that it happens inside the + * monitor. This is how the classic color management and ICC profiles work. + * The ICC profile (ignoring the VCGT tag) characterizes the output which + * is VCGT + monitor behavior. The field is null only if the profile is not + * usable as an output profile. The field is set when cmlcms_color_profile + * is created. + */ + cmsToneCurve *output_inv_eotf_vcgt[3]; + + /** + * VCGT tag cached from output profile, it could be null if not exist + */ + cmsToneCurve *vcgt[3]; +}; + +/** + * Type of LCMS transforms + */ +enum cmlcms_category { + /** + * Uses combination of input profile with output profile, but + * without INV EOTF or with additional EOTF in the transform pipeline + * input→blend = input profile + output profile + output EOTF + */ + CMLCMS_CATEGORY_INPUT_TO_BLEND = 0, + + /** + * Uses INV EOTF only concatenated with VCGT tag if present + * blend→output = output inverse EOTF + VCGT + */ + CMLCMS_CATEGORY_BLEND_TO_OUTPUT, + + /** + * Transform uses input profile and output profile as is + * input→output = input profile + output profile + VCGT + */ + CMLCMS_CATEGORY_INPUT_TO_OUTPUT, }; static inline struct cmlcms_color_profile * @@ -78,18 +131,12 @@ cmlcms_get_color_profile_from_icc(struct weston_color_manager *cm, void cmlcms_destroy_color_profile(struct weston_color_profile *cprof_base); -/* - * Perhaps a placeholder, until we get actual color spaces involved and - * see how this would work better. - */ -enum cmlcms_color_transform_type { - CMLCMS_TYPE_EOTF_sRGB = 0, - CMLCMS_TYPE_EOTF_sRGB_INV, - CMLCMS_TYPE__END, -}; struct cmlcms_color_transform_search_param { - enum cmlcms_color_transform_type type; + enum cmlcms_category category; + struct cmlcms_color_profile *input_profile; + struct cmlcms_color_profile *output_profile; + cmsUInt32Number intent_output; /* selected intent from output profile */ }; struct cmlcms_color_transform { @@ -100,8 +147,15 @@ struct cmlcms_color_transform { struct cmlcms_color_transform_search_param search_key; - /* for EOTF types */ - cmsToneCurve *curve; + /** + * 3D LUT color mapping part of the transformation, if needed. + * For category CMLCMS_CATEGORY_INPUT_TO_OUTPUT it includes pre-curve and + * post-curve. + * For category CMLCMS_CATEGORY_INPUT_TO_BLEND it includes pre-curve. + * For category CMLCMS_CATEGORY_BLEND_TO_OUTPUT and when identity it is + * not used + */ + cmsHTRANSFORM cmap_3dlut; }; static inline struct cmlcms_color_transform * @@ -117,4 +171,27 @@ cmlcms_color_transform_get(struct weston_color_manager_lcms *cm, void cmlcms_color_transform_destroy(struct cmlcms_color_transform *xform); +struct cmlcms_color_profile * +ref_cprof(struct cmlcms_color_profile *cprof); + +void +unref_cprof(struct cmlcms_color_profile *cprof); + +bool +cmlcms_create_stock_profile(struct weston_color_manager_lcms *cm); + +void +cmlcms_color_profile_destroy(struct cmlcms_color_profile *cprof); + +bool +retrieve_eotf_and_output_inv_eotf(cmsContext lcms_ctx, + cmsHPROFILE hProfile, + cmsToneCurve *output_eotf[3], + cmsToneCurve *output_inv_eotf_vcgt[3], + cmsToneCurve *vcgt[3], + unsigned int num_points); + +unsigned int +cmlcms_reasonable_1D_points(void); + #endif /* WESTON_COLOR_LCMS_H */ diff --git a/libweston/color-lcms/color-profile.c b/libweston/color-lcms/color-profile.c index 8884a071..b03bd0e6 100644 --- a/libweston/color-lcms/color-profile.c +++ b/libweston/color-lcms/color-profile.c @@ -36,6 +36,223 @@ #include "shared/helpers.h" #include "shared/string-helpers.h" +struct xyz_arr_flt { + float v[3]; +}; + +static double +xyz_dot_prod(const struct xyz_arr_flt a, const struct xyz_arr_flt b) +{ + return (double)a.v[0] * b.v[0] + + (double)a.v[1] * b.v[1] + + (double)a.v[2] * b.v[2]; +} + +/** + * Graeme sketched a linearization method there: + * https://lists.freedesktop.org/archives/wayland-devel/2019-March/040171.html + */ +static bool +build_eotf_from_clut_profile(cmsContext lcms_ctx, + cmsHPROFILE profile, + cmsToneCurve *output_eotf[3], + int num_points) +{ + int ch, point; + float *curve_array[3]; + float *red = NULL; + cmsHPROFILE xyz_profile = NULL; + cmsHTRANSFORM transform_rgb_to_xyz = NULL; + bool ret = false; + const float div = num_points - 1; + + red = malloc(sizeof(float) * num_points * 3); + if (!red) + goto release; + + curve_array[0] = red; + curve_array[1] = red + num_points; + curve_array[2] = red + 2 * num_points; + + xyz_profile = cmsCreateXYZProfileTHR(lcms_ctx); + if (!xyz_profile) + goto release; + + transform_rgb_to_xyz = cmsCreateTransformTHR(lcms_ctx, profile, + TYPE_RGB_FLT, xyz_profile, + TYPE_XYZ_FLT, + INTENT_ABSOLUTE_COLORIMETRIC, + 0); + if (!transform_rgb_to_xyz) + goto release; + + for (ch = 0; ch < 3; ch++) { + struct xyz_arr_flt prim_xyz_max; + struct xyz_arr_flt prim_xyz; + double xyz_square_magnitude; + float rgb[3] = { 0.0f, 0.0f, 0.0f }; + + rgb[ch] = 1.0f; + cmsDoTransform(transform_rgb_to_xyz, rgb, prim_xyz_max.v, 1); + + /** + * Calculate xyz square of magnitude uses single channel 100% and + * others are zero. + */ + xyz_square_magnitude = xyz_dot_prod(prim_xyz_max, prim_xyz_max); + /** + * Build rgb tone curves + */ + for (point = 0; point < num_points; point++) { + rgb[ch] = (float)point / div; + cmsDoTransform(transform_rgb_to_xyz, rgb, prim_xyz.v, 1); + curve_array[ch][point] = xyz_dot_prod(prim_xyz, + prim_xyz_max) / + xyz_square_magnitude; + } + + /** + * Create LCMS object of rgb tone curves and validate whether + * monotonic + */ + output_eotf[ch] = cmsBuildTabulatedToneCurveFloat(lcms_ctx, + num_points, + curve_array[ch]); + if (!output_eotf[ch]) + goto release; + if (!cmsIsToneCurveMonotonic(output_eotf[ch])) { + /** + * It is interesting to see how this profile was created. + * We assume that such a curve could not be used for linearization + * of arbitrary profile. + */ + goto release; + } + } + ret = true; + +release: + if (transform_rgb_to_xyz) + cmsDeleteTransform(transform_rgb_to_xyz); + if (xyz_profile) + cmsCloseProfile(xyz_profile); + free(red); + if (ret == false) + cmsFreeToneCurveTriple(output_eotf); + + return ret; +} + +/** + * Concatenation of two monotonic tone curves. + * LCMS API cmsJoinToneCurve does y = Y^-1(X(t)), + * but want to have y = Y^(X(t)) + */ +static cmsToneCurve * +lcmsJoinToneCurve(cmsContext context_id, const cmsToneCurve *X, + const cmsToneCurve *Y, unsigned int resulting_points) +{ + cmsToneCurve *out = NULL; + float t, x; + float *res = NULL; + unsigned int i; + + res = zalloc(resulting_points * sizeof(float)); + if (res == NULL) + goto error; + + for (i = 0; i < resulting_points; i++) { + t = (float)i / (resulting_points - 1); + x = cmsEvalToneCurveFloat(X, t); + res[i] = cmsEvalToneCurveFloat(Y, x); + } + + out = cmsBuildTabulatedToneCurveFloat(context_id, resulting_points, res); + +error: + if (res != NULL) + free(res); + + return out; +} +/** + * Extract EOTF from matrix-shaper and cLUT profiles, + * then invert and concatenate with 'vcgt' curve if it + * is available. + */ +bool +retrieve_eotf_and_output_inv_eotf(cmsContext lcms_ctx, + cmsHPROFILE hProfile, + cmsToneCurve *output_eotf[3], + cmsToneCurve *output_inv_eotf_vcgt[3], + cmsToneCurve *vcgt[3], + unsigned int num_points) +{ + cmsToneCurve *curve = NULL; + const cmsToneCurve * const *vcgt_curves; + unsigned i; + cmsTagSignature tags[] = { + cmsSigRedTRCTag, cmsSigGreenTRCTag, cmsSigBlueTRCTag + }; + + if (cmsIsMatrixShaper(hProfile)) { + /** + * Optimization for matrix-shaper profile + * May have 1DLUT->3x3->3x3->1DLUT, 1DLUT->3x3->1DLUT + */ + for (i = 0 ; i < 3; i++) { + curve = cmsReadTag(hProfile, tags[i]); + if (!curve) + goto fail; + output_eotf[i] = cmsDupToneCurve(curve); + if (!output_eotf[i]) + goto fail; + } + } else { + /** + * Linearization of cLUT profile may have 1DLUT->3DLUT->1DLUT, + * 1DLUT->3DLUT, 3DLUT + */ + if (!build_eotf_from_clut_profile(lcms_ctx, hProfile, + output_eotf, num_points)) + goto fail; + } + /** + * If the caller looking for eotf only then return early. + * It could be used for input profile when identity case: EOTF + INV_EOTF + * in pipeline only. + */ + if (output_inv_eotf_vcgt == NULL) + return true; + + for (i = 0; i < 3; i++) { + curve = cmsReverseToneCurve(output_eotf[i]); + if (!curve) + goto fail; + output_inv_eotf_vcgt[i] = curve; + } + vcgt_curves = cmsReadTag(hProfile, cmsSigVcgtTag); + if (vcgt_curves && vcgt_curves[0] && vcgt_curves[1] && vcgt_curves[2]) { + for (i = 0; i < 3; i++) { + curve = lcmsJoinToneCurve(lcms_ctx, + output_inv_eotf_vcgt[i], + vcgt_curves[i], num_points); + if (!curve) + goto fail; + cmsFreeToneCurve(output_inv_eotf_vcgt[i]); + output_inv_eotf_vcgt[i] = curve; + if (vcgt) + vcgt[i] = cmsDupToneCurve(vcgt_curves[i]); + } + } + return true; + +fail: + cmsFreeToneCurveTriple(output_eotf); + cmsFreeToneCurveTriple(output_inv_eotf_vcgt); + return false; +} + /* FIXME: sync with spec! */ static bool validate_icc_profile(cmsHPROFILE profile, char **errmsg) @@ -102,15 +319,37 @@ cmlcms_color_profile_create(struct weston_color_manager_lcms *cm, return cprof; } -static void +void cmlcms_color_profile_destroy(struct cmlcms_color_profile *cprof) { wl_list_remove(&cprof->link); + cmsFreeToneCurveTriple(cprof->vcgt); + cmsFreeToneCurveTriple(cprof->output_eotf); + cmsFreeToneCurveTriple(cprof->output_inv_eotf_vcgt); cmsCloseProfile(cprof->profile); free(cprof->base.description); free(cprof); } +struct cmlcms_color_profile * +ref_cprof(struct cmlcms_color_profile *cprof) +{ + if (!cprof) + return NULL; + + weston_color_profile_ref(&cprof->base); + return cprof; +} + +void +unref_cprof(struct cmlcms_color_profile *cprof) +{ + if (!cprof) + return; + + weston_color_profile_unref(&cprof->base); +} + static char * make_icc_file_description(cmsHPROFILE profile, const struct cmlcms_md5_sum *md5sum, @@ -131,6 +370,52 @@ make_icc_file_description(cmsHPROFILE profile, return desc; } +/** + * + * Build stock profile which available for clients unaware of color management + */ +bool +cmlcms_create_stock_profile(struct weston_color_manager_lcms *cm) +{ + cmsHPROFILE profile; + struct cmlcms_md5_sum md5sum; + char *desc = NULL; + + profile = cmsCreate_sRGBProfileTHR(cm->lcms_ctx); + if (!profile) { + weston_log("color-lcms: error: cmsCreate_sRGBProfileTHR failed\n"); + return false; + } + if (!cmsMD5computeID(profile)) { + weston_log("Failed to compute MD5 for ICC profile\n"); + goto err_close; + } + + cmsGetHeaderProfileID(profile, md5sum.bytes); + desc = make_icc_file_description(profile, &md5sum, "sRGB stock"); + if (!desc) + goto err_close; + + cm->sRGB_profile = cmlcms_color_profile_create(cm, profile, desc, NULL); + if (!cm->sRGB_profile) + goto err_close; + + if (!retrieve_eotf_and_output_inv_eotf(cm->lcms_ctx, + cm->sRGB_profile->profile, + cm->sRGB_profile->output_eotf, + cm->sRGB_profile->output_inv_eotf_vcgt, + cm->sRGB_profile->vcgt, + cmlcms_reasonable_1D_points())) + goto err_close; + + return true; + +err_close: + free(desc); + cmsCloseProfile(profile); + return false; +} + bool cmlcms_get_color_profile_from_icc(struct weston_color_manager *cm_base, const void *icc_data, diff --git a/libweston/color-lcms/color-transform.c b/libweston/color-lcms/color-transform.c index 713be189..c7b45639 100644 --- a/libweston/color-lcms/color-transform.c +++ b/libweston/color-lcms/color-transform.c @@ -33,48 +33,98 @@ #include "color-lcms.h" #include "shared/helpers.h" -/* Arguments to cmsBuildParametricToneCurve() */ -struct tone_curve_def { - cmsInt32Number cmstype; - cmsFloat64Number params[5]; -}; - -/* - * LCMS uses the required number of 'params' based on 'cmstype', the parametric - * tone curve number. LCMS honors negative 'cmstype' as inverse function. - * These are LCMS built-in parametric tone curves. +/** + * The method is used in linearization of an arbitrary color profile + * when EOTF is retrieved we want to know a generic way to decide the number + * of points */ -static const struct tone_curve_def predefined_eotf_curves[] = { - [CMLCMS_TYPE_EOTF_sRGB] = { - .cmstype = 4, - .params = { 2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045 }, - }, - [CMLCMS_TYPE_EOTF_sRGB_INV] = { - .cmstype = -4, - .params = { 2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045 }, - }, -}; +unsigned int +cmlcms_reasonable_1D_points(void) +{ + return 1024; +} + +static unsigned int +cmlcms_reasonable_3D_points(void) +{ + return 33; +} static void -cmlcms_fill_in_tone_curve(struct weston_color_transform *xform_base, - float *values, unsigned len) +fill_in_curves(cmsToneCurve *curves[3], float *values, unsigned len) { - struct cmlcms_color_transform *xform = get_xform(xform_base); float *R_lut = values; float *G_lut = R_lut + len; float *B_lut = G_lut + len; unsigned i; - cmsFloat32Number x, y; - - assert(xform->curve != NULL); - assert(len > 1); + cmsFloat32Number x; for (i = 0; i < len; i++) { x = (double)i / (len - 1); - y = cmsEvalToneCurveFloat(xform->curve, x); - R_lut[i] = y; - G_lut[i] = y; - B_lut[i] = y; + R_lut[i] = cmsEvalToneCurveFloat(curves[0], x); + G_lut[i] = cmsEvalToneCurveFloat(curves[1], x); + B_lut[i] = cmsEvalToneCurveFloat(curves[2], x); + } +} + +static void +cmlcms_fill_in_pre_curve(struct weston_color_transform *xform_base, + float *values, unsigned len) +{ + struct cmlcms_color_transform *xform = get_xform(xform_base); + + assert(xform->search_key.category == CMLCMS_CATEGORY_BLEND_TO_OUTPUT); + + assert(len > 1); + + fill_in_curves(xform->search_key.output_profile->output_inv_eotf_vcgt, + values, len); +} + +/** + * Clamp value to [0.0, 1.0], except pass NaN through. + * + * This function is not intended for hiding NaN. + */ +static float +ensure_unorm(float v) +{ + if (v <= 0.0f) + return 0.0f; + if (v > 1.0f) + return 1.0f; + return v; +} + +static void +cmlcms_fill_in_3dlut(struct weston_color_transform *xform_base, + float *lut, unsigned int len) +{ + struct cmlcms_color_transform *xform = get_xform(xform_base); + float rgb_in[3]; + float rgb_out[3]; + unsigned int index; + unsigned int value_b, value_r, value_g; + float divider = len - 1; + + assert(xform->search_key.category == CMLCMS_CATEGORY_INPUT_TO_BLEND || + xform->search_key.category == CMLCMS_CATEGORY_INPUT_TO_OUTPUT); + + for (value_b = 0; value_b < len; value_b++) { + for (value_g = 0; value_g < len; value_g++) { + for (value_r = 0; value_r < len; value_r++) { + rgb_in[0] = (float)value_r / divider; + rgb_in[1] = (float)value_g / divider; + rgb_in[2] = (float)value_b / divider; + + cmsDoTransform(xform->cmap_3dlut, rgb_in, rgb_out, 1); + + index = 3 * (value_r + len * (value_g + len * value_b)); + lut[index ] = ensure_unorm(rgb_out[0]); + lut[index + 1] = ensure_unorm(rgb_out[1]); + lut[index + 2] = ensure_unorm(rgb_out[2]); + } + } } } @@ -82,55 +132,135 @@ void cmlcms_color_transform_destroy(struct cmlcms_color_transform *xform) { wl_list_remove(&xform->link); - if (xform->curve) - cmsFreeToneCurve(xform->curve); + + if (xform->cmap_3dlut) + cmsDeleteTransform(xform->cmap_3dlut); + + unref_cprof(xform->search_key.input_profile); + unref_cprof(xform->search_key.output_profile); free(xform); } +static bool +xform_set_cmap_3dlut(struct cmlcms_color_transform *xform, + cmsHPROFILE input_profile, + cmsHPROFILE output_profile, + cmsToneCurve *curves[3], + cmsUInt32Number intent) +{ + struct weston_color_manager_lcms *cm = get_cmlcms(xform->base.cm); + cmsHPROFILE arr_prof[3] = { input_profile, output_profile, NULL }; + int num_profiles = 2; + + if (curves[0]) { + arr_prof[2] = cmsCreateLinearizationDeviceLinkTHR(cm->lcms_ctx, + cmsSigRgbData, + curves); + if (!arr_prof[2]) + return false; + + num_profiles = 3; + } + + xform->cmap_3dlut = cmsCreateMultiprofileTransformTHR(cm->lcms_ctx, + arr_prof, + num_profiles, + TYPE_RGB_FLT, + TYPE_RGB_FLT, + intent, + 0); + if (!xform->cmap_3dlut) { + cmsCloseProfile(arr_prof[2]); + weston_log("color-lcms error: fail cmsCreateMultiprofileTransformTHR.\n"); + return false; + } + xform->base.mapping.type = WESTON_COLOR_MAPPING_TYPE_3D_LUT; + xform->base.mapping.u.lut3d.fill_in = cmlcms_fill_in_3dlut; + xform->base.mapping.u.lut3d.optimal_len = + cmlcms_reasonable_3D_points(); + cmsCloseProfile(arr_prof[2]); + + return true; +} + static struct cmlcms_color_transform * cmlcms_color_transform_create(struct weston_color_manager_lcms *cm, - const struct cmlcms_color_transform_search_param *param) + const struct cmlcms_color_transform_search_param *search_param) { + struct cmlcms_color_profile *input_profile = search_param->input_profile; + struct cmlcms_color_profile *output_profile = search_param->output_profile; struct cmlcms_color_transform *xform; - const struct tone_curve_def *tonedef; - - if (param->type < 0 || param->type >= CMLCMS_TYPE__END) { - weston_log("color-lcms error: bad color transform type in %s.\n", - __func__); - return NULL; - } - tonedef = &predefined_eotf_curves[param->type]; + bool ok = false; xform = zalloc(sizeof *xform); if (!xform) return NULL; - xform->curve = cmsBuildParametricToneCurve(cm->lcms_ctx, - tonedef->cmstype, - tonedef->params); - if (xform->curve == NULL) { - weston_log("color-lcms error: failed to build parametric tone curve.\n"); - free(xform); - return NULL; + weston_color_transform_init(&xform->base, &cm->base); + wl_list_init(&xform->link); + xform->search_key = *search_param; + xform->search_key.input_profile = ref_cprof(input_profile); + xform->search_key.output_profile = ref_cprof(output_profile); + + /* Ensure the linearization etc. have been extracted. */ + if (!output_profile->output_eotf[0]) { + if (!retrieve_eotf_and_output_inv_eotf(cm->lcms_ctx, + output_profile->profile, + output_profile->output_eotf, + output_profile->output_inv_eotf_vcgt, + output_profile->vcgt, + cmlcms_reasonable_1D_points())) + goto error; } - weston_color_transform_init(&xform->base, &cm->base); - xform->search_key = *param; + switch (search_param->category) { + case CMLCMS_CATEGORY_INPUT_TO_BLEND: + /* Use EOTF to linearize the result. */ + ok = xform_set_cmap_3dlut(xform, input_profile->profile, + output_profile->profile, + output_profile->output_eotf, + search_param->intent_output); + break; - xform->base.pre_curve.type = WESTON_COLOR_CURVE_TYPE_LUT_3x1D; - xform->base.pre_curve.u.lut_3x1d.fill_in = cmlcms_fill_in_tone_curve; - xform->base.pre_curve.u.lut_3x1d.optimal_len = 256; + case CMLCMS_CATEGORY_INPUT_TO_OUTPUT: + /* Apply also VCGT if it exists. */ + ok = xform_set_cmap_3dlut(xform, input_profile->profile, + output_profile->profile, + output_profile->vcgt, + search_param->intent_output); + break; - wl_list_insert(&cm->color_transform_list, &xform->link); + case CMLCMS_CATEGORY_BLEND_TO_OUTPUT: + xform->base.pre_curve.type = WESTON_COLOR_CURVE_TYPE_LUT_3x1D; + xform->base.pre_curve.u.lut_3x1d.fill_in = cmlcms_fill_in_pre_curve; + xform->base.pre_curve.u.lut_3x1d.optimal_len = + cmlcms_reasonable_1D_points(); + ok = true; + break; + } + if (!ok) + goto error; + + wl_list_insert(&cm->color_transform_list, &xform->link); return xform; + +error: + cmlcms_color_transform_destroy(xform); + weston_log("CM cmlcms_color_transform_create failed\n"); + return NULL; } static bool transform_matches_params(const struct cmlcms_color_transform *xform, const struct cmlcms_color_transform_search_param *param) { - if (xform->search_key.type != param->type) + if (xform->search_key.category != param->category) + return false; + + if (xform->search_key.intent_output != param->intent_output || + xform->search_key.output_profile != param->output_profile || + xform->search_key.input_profile != param->input_profile) return false; return true; diff --git a/libweston/color-lcms/meson.build b/libweston/color-lcms/meson.build index 86e2871f..4aefd4b3 100644 --- a/libweston/color-lcms/meson.build +++ b/libweston/color-lcms/meson.build @@ -2,7 +2,6 @@ if not get_option('color-management-lcms') subdir_done() endif -dep_lcms2 = dependency('lcms2', version: '>= 2.9', required: false) if not dep_lcms2.found() error('color-lcms plugin requires lcms2 which was not found. Or, you can use \'-Dcolor-management-lcms=false\'.') endif diff --git a/libweston/color-noop.c b/libweston/color-noop.c index 4b0a0c38..4bd1e762 100644 --- a/libweston/color-noop.c +++ b/libweston/color-noop.c @@ -35,6 +35,18 @@ struct weston_color_manager_noop { struct weston_color_manager base; }; +static bool +check_output_eotf_mode(struct weston_output *output) +{ + if (output->eotf_mode == WESTON_EOTF_MODE_SDR) + return true; + + weston_log("Error: color manager no-op does not support EOTF mode %s of output %s.\n", + weston_eotf_mode_to_str(output->eotf_mode), + output->name); + return false; +} + static struct weston_color_manager_noop * get_cmnoop(struct weston_color_manager *cm_base) { @@ -74,6 +86,9 @@ cmnoop_get_surface_color_transform(struct weston_color_manager *cm_base, /* TODO: Assert surface has no colorspace set */ assert(output->color_profile == NULL); + if (!check_output_eotf_mode(output)) + return false; + /* Identity transform */ surf_xform->transform = NULL; surf_xform->identity_pipeline = true; @@ -81,43 +96,29 @@ cmnoop_get_surface_color_transform(struct weston_color_manager *cm_base, return true; } -static bool -cmnoop_get_output_color_transform(struct weston_color_manager *cm_base, - struct weston_output *output, - struct weston_color_transform **xform_out) +static struct weston_output_color_outcome * +cmnoop_create_output_color_outcome(struct weston_color_manager *cm_base, + struct weston_output *output) { - assert(output->color_profile == NULL); - - /* Identity transform */ - *xform_out = NULL; - - return true; -} + struct weston_output_color_outcome *co; -static bool -cmnoop_get_sRGB_to_output_color_transform(struct weston_color_manager *cm_base, - struct weston_output *output, - struct weston_color_transform **xform_out) -{ assert(output->color_profile == NULL); - /* Identity transform */ - *xform_out = NULL; + if (!check_output_eotf_mode(output)) + return NULL; - return true; -} + co = zalloc(sizeof *co); + if (!co) + return NULL; -static bool -cmnoop_get_sRGB_to_blend_color_transform(struct weston_color_manager *cm_base, - struct weston_output *output, - struct weston_color_transform **xform_out) -{ - assert(output->color_profile == NULL); + /* Identity transform on everything */ + co->from_blend_to_output = NULL; + co->from_sRGB_to_blend = NULL; + co->from_sRGB_to_output = NULL; - /* Identity transform */ - *xform_out = NULL; + co->hdr_meta.group_mask = 0; - return true; + return co; } static bool @@ -153,13 +154,8 @@ weston_color_manager_noop_create(struct weston_compositor *compositor) cm->base.destroy_color_profile = cmnoop_destroy_color_profile; cm->base.get_color_profile_from_icc = cmnoop_get_color_profile_from_icc; cm->base.destroy_color_transform = cmnoop_destroy_color_transform; - cm->base.get_surface_color_transform = - cmnoop_get_surface_color_transform; - cm->base.get_output_color_transform = cmnoop_get_output_color_transform; - cm->base.get_sRGB_to_output_color_transform = - cmnoop_get_sRGB_to_output_color_transform; - cm->base.get_sRGB_to_blend_color_transform = - cmnoop_get_sRGB_to_blend_color_transform; + cm->base.get_surface_color_transform = cmnoop_get_surface_color_transform; + cm->base.create_output_color_outcome = cmnoop_create_output_color_outcome; return &cm->base; } diff --git a/libweston/color.c b/libweston/color.c index eb1d45eb..27991901 100644 --- a/libweston/color.c +++ b/libweston/color.c @@ -297,3 +297,55 @@ out_close: close(fd); return cprof; } + +/** Get a string naming the EOTF mode + * + * \internal + */ +WL_EXPORT const char * +weston_eotf_mode_to_str(enum weston_eotf_mode e) +{ + switch (e) { + case WESTON_EOTF_MODE_NONE: return "(none)"; + case WESTON_EOTF_MODE_SDR: return "SDR"; + case WESTON_EOTF_MODE_TRADITIONAL_HDR: return "traditional gamma HDR"; + case WESTON_EOTF_MODE_ST2084: return "ST2084"; + case WESTON_EOTF_MODE_HLG: return "HLG"; + } + return "???"; +} + +/** A list of EOTF modes as a string + * + * \param eotf_mask Bitwise-or'd enum weston_eotf_mode values. + * \return Comma separated names of the listed EOTF modes. Must be free()'d by + * the caller. + */ +WL_EXPORT char * +weston_eotf_mask_to_str(uint32_t eotf_mask) +{ + FILE *fp; + char *str = NULL; + size_t size = 0; + unsigned i; + const char *sep = ""; + + fp = open_memstream(&str, &size); + if (!fp) + return NULL; + + for (i = 0; eotf_mask; i++) { + uint32_t bitmask = 1u << i; + + if (eotf_mask & bitmask) { + fprintf(fp, "%s%s", sep, + weston_eotf_mode_to_str(bitmask)); + sep = ", "; + } + + eotf_mask &= ~bitmask; + } + fclose(fp); + + return str; +} diff --git a/libweston/color.h b/libweston/color.h index d2137af8..b8b30d5b 100644 --- a/libweston/color.h +++ b/libweston/color.h @@ -1,5 +1,6 @@ /* * Copyright 2021 Collabora, Ltd. + * Copyright 2021 Advanced Micro Devices, Inc. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -98,6 +99,79 @@ struct weston_color_curve { } u; }; +/** Type or formula for a color mapping */ +enum weston_color_mapping_type { + /** Identity function, no-op */ + WESTON_COLOR_MAPPING_TYPE_IDENTITY = 0, + + /** 3D-dimensional look-up table */ + WESTON_COLOR_MAPPING_TYPE_3D_LUT, +}; + +/** + * A three-dimensional look-up table + * + * A 3D LUT is a three-dimensional array where each element is an RGB triplet. + * A 3D LUT is usually an approximation of some arbitrary color mapping + * function that cannot be represented in any simpler form. The array contains + * samples from the approximated function, and values between samples are + * estimated by interpolation. The array is accessed with three indices, one + * for each input dimension (color channel). + * + * Color channel values in the range [0.0, 1.0] are mapped linearly to + * 3D LUT indices such that 0.0 maps exactly to the first element and 1.0 maps + * exactly to the last element in each dimension. + * + * This object represents a 3D LUT and offers an interface for realizing it + * as a data array with a custom size. + */ +struct weston_color_mapping_3dlut { + /** + * Create a 3D LUT data array + * + * \param xform This color transformation object. + * \param values Memory to hold the resulting data array. + * \param len The number of elements in each dimension. + * + * The array \c values must be at least 3 * len * len * len elements + * in size. + * + * Given the red index ri, green index gi and blue index bi, the + * corresponding array element index + * + * i = 3 * (len * len * bi + len * gi + ri) + c + * + * where + * + * c = 0 for red output value, + * c = 1 for green output value, and + * c = 2 for blue output value + */ + void + (*fill_in)(struct weston_color_transform *xform, + float *values, unsigned len); + + /** Optimal 3D LUT size along each dimension */ + unsigned optimal_len; +}; + +/** + * Color mapping function + * + * This object can represent a 3D LUT to do a color space conversion + * + */ +struct weston_color_mapping { + /** Which member of 'u' defines the color mapping type */ + enum weston_color_mapping_type type; + + /** Parameters for the color mapping function */ + union { + /* identity: no parameters */ + struct weston_color_mapping_3dlut lut3d; + } u; +}; + /** * Describes a color transformation formula * @@ -124,7 +198,7 @@ struct weston_color_transform { struct weston_color_curve pre_curve; /** Step 3: color mapping */ - /* TBD: e.g. a 3D LUT or a matrix */ + struct weston_color_mapping mapping; /** Step 4: color curve after color mapping */ /* struct weston_color_curve post_curve; */ @@ -223,53 +297,19 @@ struct weston_color_manager { struct weston_output *output, struct weston_surface_color_transform *surf_xform); - /** Get output's blending space to output transformation + /** Compute derived color properties for an output * * \param cm The color manager. - * \param output The output for the destination color space. - * \param xform_out Pointer for storing the weston_color_transform. - * \return True on success, false on failure. + * \param output The output. + * \return A new color_outcome object on success, NULL on failure. * - * The callee is responsible for increasing the reference count on the - * weston_color_transform it stores via xform_out. On failure, xform_out - * is untouched. - */ - bool - (*get_output_color_transform)(struct weston_color_manager *cm, - struct weston_output *output, - struct weston_color_transform **xform_out); - - /** Get sRGB to output transformation - * - * \param cm The color manager. - * \param output The output for the destination color space. - * \param xform_out Pointer for storing the weston_color_transform. - * \return True on success, false on failure. - * - * The callee is responsible for increasing the reference count on the - * weston_color_transform it stores via xform_out. On failure, xform_out - * is untouched. + * The callee (color manager) must inspect the weston_output (color + * profile, EOTF mode, etc.) and create a fully populated + * weston_output_color_outcome object. */ - bool - (*get_sRGB_to_output_color_transform)(struct weston_color_manager *cm, - struct weston_output *output, - struct weston_color_transform **xform_out); - - /** Get sRGB to output's blending space transformation - * - * \param cm The color manager. - * \param output The output for the destination blending color space. - * \param xform_out Pointer for storing the weston_color_transform. - * \return True on success, false on failure. - * - * The callee is responsible for increasing the reference count on the - * weston_color_transform it stores via xform_out. On failure, xform_out - * is untouched. - */ - bool - (*get_sRGB_to_blend_color_transform)(struct weston_color_manager *cm, - struct weston_output *output, - struct weston_color_transform **xform_out); + struct weston_output_color_outcome * + (*create_output_color_outcome)(struct weston_color_manager *cm, + struct weston_output *output); }; void @@ -305,4 +345,13 @@ weston_color_manager_noop_create(struct weston_compositor *compositor); struct weston_color_manager * weston_color_manager_create(struct weston_compositor *compositor); +const char * +weston_eotf_mode_to_str(enum weston_eotf_mode e); + +char * +weston_eotf_mask_to_str(uint32_t eotf_mask); + +void +weston_output_color_outcome_destroy(struct weston_output_color_outcome **pco); + #endif /* WESTON_COLOR_H */ diff --git a/libweston/compositor.c b/libweston/compositor.c index b194bc40..b6c16d6f 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -52,17 +52,20 @@ #include #include #include +#include #include "timeline.h" #include #include #include "linux-dmabuf.h" +#include "linux-dmabuf-unstable-v1-server-protocol.h" #include "viewporter-server-protocol.h" #include "presentation-time-server-protocol.h" #include "xdg-output-unstable-v1-server-protocol.h" #include "linux-explicit-synchronization-unstable-v1-server-protocol.h" #include "linux-explicit-synchronization.h" +#include "single-pixel-buffer-v1-server-protocol.h" #include "shared/fd-util.h" #include "shared/helpers.h" #include "shared/os-compatibility.h" @@ -630,14 +633,6 @@ weston_surface_create(struct weston_compositor *compositor) return surface; } -WL_EXPORT void -weston_surface_set_color(struct weston_surface *surface, - float red, float green, float blue, float alpha) -{ - surface->compositor->renderer->surface_set_color(surface, red, green, blue, alpha); - surface->is_opaque = !(alpha < 1.0); -} - WL_EXPORT void weston_view_to_global_float(struct weston_view *view, float sx, float sy, float *x, float *y) @@ -1306,6 +1301,14 @@ weston_view_set_output(struct weston_view *view, struct weston_output *output) } } +static struct weston_layer * +get_view_layer(struct weston_view *view) +{ + if (view->parent_view) + return get_view_layer(view->parent_view); + return view->layer_link.layer; +} + /** Recalculate which output(s) the surface has views displayed on * * \param es The surface to remap to outputs @@ -1331,7 +1334,9 @@ weston_surface_assign_output(struct weston_surface *es) mask = 0; pixman_region32_init(®ion); wl_list_for_each(view, &es->views, surface_link) { - if (!view->output) + /* Only views that are visible on some layer participate in + * output_mask calculations. */ + if (!view->output || !get_view_layer(view)) continue; pixman_region32_intersect(®ion, &view->transform.boundingbox, @@ -1521,15 +1526,20 @@ weston_view_update_transform_disable(struct weston_view *view) view->geometry.x, view->geometry.y); if (view->alpha == 1.0) { - pixman_region32_copy(&view->transform.opaque, - &view->surface->opaque); - if (view->geometry.scissor_enabled) - pixman_region32_intersect(&view->transform.opaque, - &view->transform.opaque, - &view->geometry.scissor); - pixman_region32_translate(&view->transform.opaque, - view->geometry.x, - view->geometry.y); + if (view->surface->is_opaque) { + pixman_region32_copy(&view->transform.opaque, + &view->transform.boundingbox); + } else { + pixman_region32_copy(&view->transform.opaque, + &view->surface->opaque); + if (view->geometry.scissor_enabled) + pixman_region32_intersect(&view->transform.opaque, + &view->transform.opaque, + &view->geometry.scissor); + pixman_region32_translate(&view->transform.opaque, + view->geometry.x, + view->geometry.y); + } } } @@ -1572,32 +1582,40 @@ weston_view_update_transform_enable(struct weston_view *view) surfbox = pixman_region32_extents(&surfregion); view_compute_bbox(view, surfbox, &view->transform.boundingbox); - pixman_region32_fini(&surfregion); if (view->alpha == 1.0 && matrix->type == WESTON_MATRIX_TRANSFORM_TRANSLATE) { + if (view->surface->is_opaque) { + pixman_region32_copy(&view->transform.opaque, + &view->transform.boundingbox); + } else { + pixman_region32_copy(&view->transform.opaque, + &view->surface->opaque); + if (view->geometry.scissor_enabled) + pixman_region32_intersect(&view->transform.opaque, + &view->transform.opaque, + &view->geometry.scissor); + pixman_region32_translate(&view->transform.opaque, + matrix->d[12], + matrix->d[13]); + } + } else if (view->alpha == 1.0 && + matrix->type < WESTON_MATRIX_TRANSFORM_ROTATE && + pixman_region32_n_rects(&surfregion) == 1 && + (pixman_region32_equal(&surfregion, &view->surface->opaque) || + view->surface->is_opaque)) { + /* The whole surface is opaque and it is only translated and + * scaled and after applying the scissor, the result is still + * a single rectangle. In this case the boundingbox matches the + * view exactly and can be used as opaque area. */ pixman_region32_copy(&view->transform.opaque, - &view->surface->opaque); - if (view->geometry.scissor_enabled) - pixman_region32_intersect(&view->transform.opaque, - &view->transform.opaque, - &view->geometry.scissor); - pixman_region32_translate(&view->transform.opaque, - matrix->d[12], - matrix->d[13]); + &view->transform.boundingbox); } + pixman_region32_fini(&surfregion); return 0; } -static struct weston_layer * -get_view_layer(struct weston_view *view) -{ - if (view->parent_view) - return get_view_layer(view->parent_view); - return view->layer_link.layer; -} - WL_EXPORT void weston_view_update_transform(struct weston_view *view) { @@ -1810,6 +1828,7 @@ transform_parent_handle_parent_destroy(struct wl_listener *listener, geometry.parent_destroy_listener); weston_view_set_transform_parent(view, NULL); + view->parent_view = NULL; } WL_EXPORT void @@ -1983,7 +2002,11 @@ weston_view_is_opaque(struct weston_view *ev, pixman_region32_t *region) WL_EXPORT bool weston_view_has_valid_buffer(struct weston_view *ev) { - return ev->surface->buffer_ref.buffer != NULL; + if (!ev->surface->buffer_ref.buffer) + return false; + if (!ev->surface->buffer_ref.buffer->resource) + return false; + return true; } /** Check if the view matches the entire output @@ -2104,7 +2127,7 @@ weston_surface_calculate_size_from_buffer(struct weston_surface *surface) { struct weston_buffer_viewport *vp = &surface->buffer_viewport; - if (!surface->buffer_ref.buffer) { + if (!weston_surface_has_content(surface)) { surface->width_from_buffer = 0; surface->height_from_buffer = 0; return; @@ -2244,6 +2267,12 @@ weston_view_unmap(struct weston_view *view) weston_signal_emit_mutable(&view->unmap_signal, view); } +WL_EXPORT void +weston_surface_map(struct weston_surface *surface) +{ + surface->is_mapped = true; +} + WL_EXPORT void weston_surface_unmap(struct weston_surface *surface) { @@ -2259,8 +2288,6 @@ static void weston_surface_reset_pending_buffer(struct weston_surface *surface) { weston_surface_state_set_buffer(&surface->pending, NULL); - surface->pending.sx = 0; - surface->pending.sy = 0; surface->pending.newly_attached = 0; surface->pending.buffer_viewport.changed = 0; } @@ -2299,8 +2326,18 @@ weston_view_destroy(struct weston_view *view) free(view); } +WL_EXPORT struct weston_surface * +weston_surface_ref(struct weston_surface *surface) +{ + assert(surface->ref_count < INT32_MAX && + surface->ref_count > 0); + + surface->ref_count++; + return surface; +} + WL_EXPORT void -weston_surface_destroy(struct weston_surface *surface) +weston_surface_unref(struct weston_surface *surface) { struct wl_resource *cb, *next; struct weston_view *ev, *nv; @@ -2334,7 +2371,8 @@ weston_surface_destroy(struct weston_surface *surface) weston_surface_state_fini(&surface->pending); - weston_buffer_reference(&surface->buffer_ref, NULL); + weston_buffer_reference(&surface->buffer_ref, NULL, + BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference(&surface->buffer_release_ref, NULL); pixman_region32_fini(&surface->damage); @@ -2365,7 +2403,7 @@ destroy_surface(struct wl_resource *resource) /* Set the resource to NULL, since we don't want to leave a * dangling pointer if the surface was refcounted and survives - * the weston_surface_destroy() call. */ + * the weston_surface_unref() call. */ surface->resource = NULL; if (surface->viewport_resource) @@ -2376,24 +2414,37 @@ destroy_surface(struct wl_resource *resource) NULL); } - weston_surface_destroy(surface); + weston_surface_unref(surface); } +static struct weston_solid_buffer_values * +single_pixel_buffer_get(struct wl_resource *resource); + static void weston_buffer_destroy_handler(struct wl_listener *listener, void *data) { struct weston_buffer *buffer = container_of(listener, struct weston_buffer, destroy_listener); + buffer->resource = NULL; + buffer->shm_buffer = NULL; + + if (buffer->busy_count + buffer->passive_count > 0) + return; + weston_signal_emit_mutable(&buffer->destroy_signal, buffer); free(buffer); } WL_EXPORT struct weston_buffer * -weston_buffer_from_resource(struct wl_resource *resource) +weston_buffer_from_resource(struct weston_compositor *ec, + struct wl_resource *resource) { struct weston_buffer *buffer; + struct wl_shm_buffer *shm; + struct linux_dmabuf_buffer *dmabuf; struct wl_listener *listener; + struct weston_solid_buffer_values *solid; listener = wl_resource_get_destroy_listener(resource, weston_buffer_destroy_handler); @@ -2409,45 +2460,127 @@ weston_buffer_from_resource(struct wl_resource *resource) buffer->resource = resource; wl_signal_init(&buffer->destroy_signal); buffer->destroy_listener.notify = weston_buffer_destroy_handler; - buffer->y_inverted = 1; wl_resource_add_destroy_listener(resource, &buffer->destroy_listener); - return buffer; -} + if ((shm = wl_shm_buffer_get(buffer->resource))) { + buffer->type = WESTON_BUFFER_SHM; + buffer->shm_buffer = shm; + buffer->width = wl_shm_buffer_get_width(shm); + buffer->height = wl_shm_buffer_get_height(shm); + buffer->buffer_origin = ORIGIN_TOP_LEFT; + /* wl_shm might create a buffer with an unknown format, so check + * and reject */ + buffer->pixel_format = + pixel_format_get_info_shm(wl_shm_buffer_get_format(shm)); + buffer->format_modifier = DRM_FORMAT_MOD_LINEAR; + + if (!buffer->pixel_format || buffer->pixel_format->hide_from_clients) + goto fail; + } else if ((dmabuf = linux_dmabuf_buffer_get(buffer->resource))) { + buffer->type = WESTON_BUFFER_DMABUF; + buffer->dmabuf = dmabuf; + buffer->direct_display = dmabuf->direct_display; + buffer->width = dmabuf->attributes.width; + buffer->height = dmabuf->attributes.height; + buffer->pixel_format = + pixel_format_get_info(dmabuf->attributes.format); + /* dmabuf import should assure we don't create a buffer with an + * unknown format */ + assert(buffer->pixel_format && !buffer->pixel_format->hide_from_clients); + buffer->format_modifier = dmabuf->attributes.modifier[0]; + if (dmabuf->attributes.flags & ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT) + buffer->buffer_origin = ORIGIN_BOTTOM_LEFT; + else + buffer->buffer_origin = ORIGIN_TOP_LEFT; + } else if ((solid = single_pixel_buffer_get(buffer->resource))) { + buffer->type = WESTON_BUFFER_SOLID; + buffer->solid = *solid; + buffer->width = 1; + buffer->height = 1; + if (buffer->solid.a == 1.0) { + buffer->pixel_format = + pixel_format_get_info(DRM_FORMAT_XRGB8888); + } else { + buffer->pixel_format = + pixel_format_get_info(DRM_FORMAT_ARGB8888); + } + buffer->format_modifier = DRM_FORMAT_MOD_LINEAR; + } else { + /* Only taken for legacy EGL buffers */ + if (!ec->renderer->fill_buffer_info || + !ec->renderer->fill_buffer_info(ec, buffer)) { + goto fail; + } + buffer->type = WESTON_BUFFER_RENDERER_OPAQUE; + } -static void -weston_buffer_reference_handle_destroy(struct wl_listener *listener, - void *data) -{ - struct weston_buffer_reference *ref = - container_of(listener, struct weston_buffer_reference, - destroy_listener); + /* Don't accept any formats we can't reason about: the importer should + * make sure this never happens */ + assert(buffer->pixel_format); + + return buffer; - assert((struct weston_buffer *)data == ref->buffer); - ref->buffer = NULL; +fail: + wl_list_remove(&buffer->destroy_listener.link); + free(buffer); + return NULL; } WL_EXPORT void weston_buffer_reference(struct weston_buffer_reference *ref, - struct weston_buffer *buffer) + struct weston_buffer *buffer, + enum weston_buffer_reference_type type) { - if (ref->buffer && buffer != ref->buffer) { - ref->buffer->busy_count--; - if (ref->buffer->busy_count == 0) { - assert(wl_resource_get_client(ref->buffer->resource)); - wl_buffer_send_release(ref->buffer->resource); - } - wl_list_remove(&ref->destroy_listener.link); - } + struct weston_buffer_reference old_ref = *ref; + + assert(buffer != NULL || type == BUFFER_WILL_NOT_BE_ACCESSED); - if (buffer && buffer != ref->buffer) { - buffer->busy_count++; - wl_signal_add(&buffer->destroy_signal, - &ref->destroy_listener); + if (buffer == ref->buffer && type == ref->type) + return; + + /* First ref the incoming buffer, so we keep positive refcount */ + if (buffer) { + if (type == BUFFER_MAY_BE_ACCESSED) + buffer->busy_count++; + else + buffer->passive_count++; } ref->buffer = buffer; - ref->destroy_listener.notify = weston_buffer_reference_handle_destroy; + ref->type = type; + + /* Now drop refs to the old buffer, if any */ + if (!old_ref.buffer) + return; + + ref = NULL; /* will no longer be accessed */ + + if (old_ref.type == BUFFER_MAY_BE_ACCESSED) { + assert(old_ref.buffer->busy_count > 0); + old_ref.buffer->busy_count--; + + /* If the wl_buffer lives, then hold on to the weston_buffer, + * but send a release event to the client */ + if (old_ref.buffer->busy_count == 0 && + old_ref.buffer->resource) { + assert(wl_resource_get_client(old_ref.buffer->resource)); + wl_buffer_send_release(old_ref.buffer->resource); + } + } else if (old_ref.type == BUFFER_WILL_NOT_BE_ACCESSED) { + assert(old_ref.buffer->passive_count > 0); + old_ref.buffer->passive_count--; + } else { + assert(!"unknown buffer ref type"); + } + + /* If the wl_buffer has gone and this was the last ref, destroy the + * weston_buffer, since we'll never need it again */ + if (old_ref.buffer->busy_count + old_ref.buffer->passive_count == 0 && + !old_ref.buffer->resource) { + weston_signal_emit_mutable(&old_ref.buffer->destroy_signal, + old_ref.buffer); + free(old_ref.buffer); + } } static void @@ -2512,11 +2645,180 @@ weston_buffer_release_move(struct weston_buffer_release_reference *dest, weston_buffer_release_reference(src, NULL); } +WL_EXPORT struct weston_buffer_reference * +weston_buffer_create_solid_rgba(struct weston_compositor *compositor, + float r, float g, float b, float a) +{ + struct weston_buffer_reference *ret = zalloc(sizeof(*ret)); + struct weston_buffer *buffer; + + if (!ret) + return NULL; + + buffer = zalloc(sizeof(*buffer)); + if (!buffer) { + free(ret); + return NULL; + } + + wl_signal_init(&buffer->destroy_signal); + buffer->type = WESTON_BUFFER_SOLID; + buffer->width = 1; + buffer->height = 1; + buffer->buffer_origin = ORIGIN_TOP_LEFT; + buffer->solid.r = r; + buffer->solid.g = g; + buffer->solid.b = b; + buffer->solid.a = a; + + if (a == 1.0) { + buffer->pixel_format = + pixel_format_get_info_shm(WL_SHM_FORMAT_XRGB8888); + } else { + buffer->pixel_format = + pixel_format_get_info_shm(WL_SHM_FORMAT_ARGB8888); + } + buffer->format_modifier = DRM_FORMAT_MOD_LINEAR; + + weston_buffer_reference(ret, buffer, BUFFER_MAY_BE_ACCESSED); + + return ret; +} + +WL_EXPORT void +weston_surface_attach_solid(struct weston_surface *surface, + struct weston_buffer_reference *buffer_ref, + int w, int h) +{ + struct weston_buffer *buffer = buffer_ref->buffer; + + assert(buffer); + assert(buffer->type == WESTON_BUFFER_SOLID); + weston_buffer_reference(&surface->buffer_ref, buffer, + BUFFER_MAY_BE_ACCESSED); + surface->compositor->renderer->attach(surface, buffer); + + weston_surface_set_size(surface, w, h); + + pixman_region32_fini(&surface->opaque); + if (buffer->solid.a == 1.0) { + surface->is_opaque = true; + pixman_region32_init_rect(&surface->opaque, 0, 0, w, h); + } else { + surface->is_opaque = false; + pixman_region32_init(&surface->opaque); + } +} + +WL_EXPORT void +weston_buffer_destroy_solid(struct weston_buffer_reference *buffer_ref) +{ + assert(buffer_ref); + assert(buffer_ref->buffer); + assert(buffer_ref->type == BUFFER_MAY_BE_ACCESSED); + assert(buffer_ref->buffer->type == WESTON_BUFFER_SOLID); + weston_buffer_reference(buffer_ref, NULL, BUFFER_WILL_NOT_BE_ACCESSED); + free(buffer_ref); +} + +static void +single_pixel_buffer_destroy(struct wl_resource *resource) +{ + struct weston_solid_buffer_values *solid = + wl_resource_get_user_data(resource); + free(solid); +} + +static void +single_pixel_buffer_handle_buffer_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct wl_buffer_interface single_pixel_buffer_implementation = { + single_pixel_buffer_handle_buffer_destroy, +}; + +static struct weston_solid_buffer_values * +single_pixel_buffer_get(struct wl_resource *resource) +{ + if (!resource) + return NULL; + + if (!wl_resource_instance_of(resource, &wl_buffer_interface, + &single_pixel_buffer_implementation)) + return NULL; + + return wl_resource_get_user_data(resource); +} + +static void +single_pixel_buffer_manager_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +single_pixel_buffer_create(struct wl_client *client, struct wl_resource *resource, + uint32_t id, uint32_t r, uint32_t g, uint32_t b, uint32_t a) +{ + struct weston_solid_buffer_values *solid = zalloc(sizeof(*solid)); + struct wl_resource *buffer; + + if (!solid) { + wl_client_post_no_memory(client); + return; + } + + solid->r = r / (double) 0xffffffff; + solid->g = g / (double) 0xffffffff; + solid->b = b / (double) 0xffffffff; + solid->a = a / (double) 0xffffffff; + + buffer = wl_resource_create(client, &wl_buffer_interface, 1, id); + if (!buffer) { + wl_client_post_no_memory(client); + free(solid); + return; + } + wl_resource_set_implementation(buffer, + &single_pixel_buffer_implementation, + solid, single_pixel_buffer_destroy); +} + +static const struct wp_single_pixel_buffer_manager_v1_interface +single_pixel_buffer_manager_implementation = { + single_pixel_buffer_manager_destroy, + single_pixel_buffer_create, +}; + +static void +bind_single_pixel_buffer(struct wl_client *client, void *data, uint32_t version, + uint32_t id) +{ + struct wl_resource *resource; + + resource = wl_resource_create(client, + &wp_single_pixel_buffer_manager_v1_interface, 1, + id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, + &single_pixel_buffer_manager_implementation, + NULL, NULL); +} + static void weston_surface_attach(struct weston_surface *surface, struct weston_buffer *buffer) { - weston_buffer_reference(&surface->buffer_ref, buffer); + weston_buffer_reference(&surface->buffer_ref, buffer, + buffer ? BUFFER_MAY_BE_ACCESSED : + BUFFER_WILL_NOT_BE_ACCESSED); if (!buffer) { if (weston_surface_is_mapped(surface)) @@ -2527,6 +2829,9 @@ weston_surface_attach(struct weston_surface *surface, weston_surface_calculate_size_from_buffer(surface); weston_presentation_feedback_discard_list(&surface->feedback_list); + + if (buffer) + surface->is_opaque = pixel_format_is_opaque(buffer->pixel_format); } /** weston_compositor_damage_all @@ -2555,12 +2860,27 @@ weston_output_damage(struct weston_output *output) weston_output_schedule_repaint(output); } +/* FIXME: note that we don't flush any damage when the core wants us to + * do so as it will sometimes ask for a flush not necessarily at the + * right time. + * + * A (more) proper way is to handle correctly damage whenever there's + * compositor side damage. See the comment for weston_surface_damage(). + */ +static bool +buffer_can_be_accessed_BANDAID_XXX(struct weston_buffer_reference buffer_ref) +{ + return buffer_ref.type == BUFFER_MAY_BE_ACCESSED; +} + static void surface_flush_damage(struct weston_surface *surface) { - if (surface->buffer_ref.buffer && - wl_shm_buffer_get(surface->buffer_ref.buffer->resource)) - surface->compositor->renderer->flush_damage(surface); + struct weston_buffer *buffer = surface->buffer_ref.buffer; + + if (buffer->type == WESTON_BUFFER_SHM && + buffer_can_be_accessed_BANDAID_XXX(surface->buffer_ref)) + surface->compositor->renderer->flush_damage(surface, buffer); if (pixman_region32_not_empty(&surface->damage)) TL_POINT(surface->compositor, "core_flush_damage", TLP_SURFACE(surface), @@ -2652,7 +2972,9 @@ output_accumulate_damage(struct weston_output *output) * clients to use single-buffering. */ if (!pnode->surface->keep_buffer) { - weston_buffer_reference(&pnode->surface->buffer_ref, NULL); + weston_buffer_reference(&pnode->surface->buffer_ref, + pnode->surface->buffer_ref.buffer, + BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference( &pnode->surface->buffer_release_ref, NULL); } @@ -2795,6 +3117,26 @@ view_list_add(struct weston_compositor *compositor, struct weston_subsurface *sub; weston_view_update_transform(view); + + /* It is possible for a view to appear in the layer list even though + * the view or the surface is unmapped. This is erroneous but difficult + * to fix. */ + if (!weston_surface_is_mapped(view->surface) || + !weston_view_is_mapped(view) || + !weston_surface_has_content(view->surface)) { + if (!compositor->warned_about_unmapped_surface_or_view) { + weston_log("Detected an unmapped surface or view in " + "the layer list, which should not occur.\n"); + compositor->warned_about_unmapped_surface_or_view = true; + } + + pnode = weston_view_find_paint_node(view, output); + if (pnode) + weston_paint_node_destroy(pnode); + + return; + } + pnode = view_ensure_paint_node(view, output); if (wl_list_empty(&view->surface->subsurface_list)) { @@ -2870,7 +3212,7 @@ weston_output_take_feedback_list(struct weston_output *output, } static int -weston_output_repaint(struct weston_output *output, void *repaint_data) +weston_output_repaint(struct weston_output *output) { struct weston_compositor *ec = output->compositor; struct weston_paint_node *pnode; @@ -2910,10 +3252,14 @@ weston_output_repaint(struct weston_output *output, void *repaint_data) output->desired_protection = highest_requested; if (output->assign_planes && !output->disable_planes) { - output->assign_planes(output, repaint_data); + output->assign_planes(output); } else { wl_list_for_each(pnode, &output->paint_node_z_order_list, z_order_link) { + /* TODO: turn this into assert once z_order_list is pruned. */ + if ((pnode->view->output_mask & (1u << output->id)) == 0) + continue; + weston_view_move_to_plane(pnode->view, &ec->primary_plane); pnode->view->psf_flags = 0; } @@ -2945,7 +3291,7 @@ weston_output_repaint(struct weston_output *output, void *repaint_data) if (output->dirty) weston_output_update_matrix(output); - r = output->repaint(output, &output_damage, repaint_data); + r = output->repaint(output, &output_damage); pixman_region32_fini(&output_damage); @@ -2981,8 +3327,7 @@ weston_output_schedule_repaint_reset(struct weston_output *output) } static int -weston_output_maybe_repaint(struct weston_output *output, struct timespec *now, - void *repaint_data) +weston_output_maybe_repaint(struct weston_output *output, struct timespec *now) { struct weston_compositor *compositor = output->compositor; int ret = 0; @@ -3012,7 +3357,7 @@ weston_output_maybe_repaint(struct weston_output *output, struct timespec *now, * something schedules a successful repaint later. As repainting may * take some time, re-read our clock as a courtesy to the next * output. */ - ret = weston_output_repaint(output, repaint_data); + ret = weston_output_repaint(output); weston_compositor_read_presentation_clock(compositor, now); if (ret != 0) goto err; @@ -3064,41 +3409,56 @@ output_repaint_timer_arm(struct weston_compositor *compositor) wl_event_source_timer_update(compositor->repaint_timer, msec_to_next); } +static void +weston_output_schedule_repaint_restart(struct weston_output *output) +{ + assert(output->repaint_status == REPAINT_AWAITING_COMPLETION); + /* The device was busy so try again one frame later */ + timespec_add_nsec(&output->next_repaint, &output->next_repaint, + millihz_to_nsec(output->current_mode->refresh)); + output->repaint_status = REPAINT_SCHEDULED; + TL_POINT(output->compositor, "core_repaint_restart", + TLP_OUTPUT(output), TLP_END); + output_repaint_timer_arm(output->compositor); + weston_output_damage(output); +} + static int output_repaint_timer_handler(void *data) { struct weston_compositor *compositor = data; struct weston_output *output; struct timespec now; - void *repaint_data = NULL; int ret = 0; weston_compositor_read_presentation_clock(compositor, &now); compositor->last_repaint_start = now; if (compositor->backend->repaint_begin) - repaint_data = compositor->backend->repaint_begin(compositor); + compositor->backend->repaint_begin(compositor); wl_list_for_each(output, &compositor->output_list, link) { - ret = weston_output_maybe_repaint(output, &now, repaint_data); + ret = weston_output_maybe_repaint(output, &now); if (ret) break; } if (ret == 0) { if (compositor->backend->repaint_flush) - ret = compositor->backend->repaint_flush(compositor, - repaint_data); + ret = compositor->backend->repaint_flush(compositor); } else { if (compositor->backend->repaint_cancel) - compositor->backend->repaint_cancel(compositor, - repaint_data); + compositor->backend->repaint_cancel(compositor); } if (ret != 0) { wl_list_for_each(output, &compositor->output_list, link) { - if (output->repainted) - weston_output_schedule_repaint_reset(output); + if (output->repainted) { + if (ret == -EBUSY) + weston_output_schedule_repaint_restart(output); + else + weston_output_schedule_repaint_reset(output); + } } } @@ -3245,7 +3605,9 @@ idle_repaint(void *data) output->repaint_status = REPAINT_AWAITING_COMPLETION; output->idle_repaint_source = NULL; ret = output->start_repaint_loop(output); - if (ret != 0) + if (ret == -EBUSY) + weston_output_schedule_repaint_restart(output); + else if (ret != 0) weston_output_schedule_repaint_reset(output); } @@ -3423,6 +3785,16 @@ weston_compositor_schedule_repaint(struct weston_compositor *compositor) weston_output_schedule_repaint(output); } +/** + * Returns true if a surface has a buffer attached to it and thus valid + * content available. + */ +WL_EXPORT bool +weston_surface_has_content(struct weston_surface *surface) +{ + return !!surface->buffer_ref.buffer; +} + static void surface_destroy(struct wl_client *client, struct wl_resource *resource) { @@ -3435,16 +3807,25 @@ surface_attach(struct wl_client *client, struct wl_resource *buffer_resource, int32_t sx, int32_t sy) { struct weston_surface *surface = wl_resource_get_user_data(resource); + struct weston_compositor *ec = surface->compositor; struct weston_buffer *buffer = NULL; if (buffer_resource) { - buffer = weston_buffer_from_resource(buffer_resource); + buffer = weston_buffer_from_resource(ec, buffer_resource); if (buffer == NULL) { wl_client_post_no_memory(client); return; } } + if (wl_resource_get_version(resource) >= WL_SURFACE_OFFSET_SINCE_VERSION && + (sx != 0 || sy != 0)) { + wl_resource_post_error(resource, + WL_SURFACE_ERROR_INVALID_OFFSET, + "Can't attach with an offset"); + return; + } + /* Attach, attach, without commit in between does not send * wl_buffer.release. */ weston_surface_state_set_buffer(&surface->pending, buffer); @@ -3833,7 +4214,8 @@ weston_surface_commit_state(struct weston_surface *surface, weston_matrix_invert(&surface->buffer_to_surface_matrix, &surface->surface_to_buffer_matrix); - if (state->newly_attached || state->buffer_viewport.changed) { + if (state->newly_attached || state->buffer_viewport.changed || + state->sx != 0 || state->sy != 0) { weston_surface_update_size(surface); if (surface->committed) surface->committed(surface, state->sx, state->sy); @@ -3956,14 +4338,7 @@ surface_commit(struct wl_client *client, struct wl_resource *resource) return; } - /* We support fences for both wp_linux_dmabuf and opaque EGL - * buffers, as mandated by minor version 2 of the - * zwp_linux_explicit_synchronization_v1 protocol. Since - * renderers that support fences currently only support these - * two buffer types plus SHM buffers, we can just check for the - * SHM buffer case here. - */ - if (wl_shm_buffer_get(surface->pending.buffer->resource)) { + if (surface->pending.buffer->type == WESTON_BUFFER_SHM) { fd_clear(&surface->pending.acquire_fence_fd); wl_resource_post_error(surface->synchronization_resource, ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_UNSUPPORTED_BUFFER, @@ -4036,6 +4411,18 @@ surface_set_buffer_scale(struct wl_client *client, surface->pending.buffer_viewport.changed = 1; } +static void +surface_offset(struct wl_client *client, + struct wl_resource *resource, + int32_t sx, + int32_t sy) +{ + struct weston_surface *surface = wl_resource_get_user_data(resource); + + surface->pending.sx = sx; + surface->pending.sy = sy; +} + static const struct wl_surface_interface surface_interface = { surface_destroy, surface_attach, @@ -4046,7 +4433,8 @@ static const struct wl_surface_interface surface_interface = { surface_commit, surface_set_buffer_transform, surface_set_buffer_scale, - surface_damage_buffer + surface_damage_buffer, + surface_offset, }; static void @@ -4073,7 +4461,7 @@ compositor_create_surface(struct wl_client *client, return; err_res: - weston_surface_destroy(surface); + weston_surface_unref(surface); err: wl_resource_post_no_memory(resource); } @@ -4157,7 +4545,8 @@ weston_subsurface_commit_from_cache(struct weston_subsurface *sub) struct weston_surface *surface = sub->surface; weston_surface_commit_state(surface, &sub->cached); - weston_buffer_reference(&sub->cached_buffer_ref, NULL); + weston_buffer_reference(&sub->cached_buffer_ref, NULL, + BUFFER_WILL_NOT_BE_ACCESSED); weston_surface_commit_subsurface_order(surface); @@ -4194,7 +4583,10 @@ weston_subsurface_commit_to_cache(struct weston_subsurface *sub) weston_surface_state_set_buffer(&sub->cached, surface->pending.buffer); weston_buffer_reference(&sub->cached_buffer_ref, - surface->pending.buffer); + surface->pending.buffer, + surface->pending.buffer ? + BUFFER_MAY_BE_ACCESSED : + BUFFER_WILL_NOT_BE_ACCESSED); weston_presentation_feedback_discard_list( &sub->cached.feedback_list); /* zwp_surface_synchronization_v1.set_acquire_fence */ @@ -4220,6 +4612,9 @@ weston_subsurface_commit_to_cache(struct weston_subsurface *sub) weston_surface_reset_pending_buffer(surface); + surface->pending.sx = 0; + surface->pending.sy = 0; + pixman_region32_copy(&sub->cached.opaque, &surface->pending.opaque); pixman_region32_copy(&sub->cached.input, &surface->pending.input); @@ -4335,22 +4730,21 @@ subsurface_committed(struct weston_surface *surface, int32_t dx, int32_t dy) * mapped, parent is not in a visible layer, so this sub-surface * will not be drawn either. */ - - if (!weston_surface_is_mapped(surface)) { - surface->is_mapped = surface->buffer_ref.buffer != NULL; - - /* Cannot call weston_view_update_transform(), - * because that would call it also for the parent surface, - * which might not be mapped yet. That would lead to - * inconsistent state, where the window could never be - * mapped. - * - * Instead just force the is_mapped flag on, to make - * weston_surface_is_mapped() return true, so that when the - * parent surface does get mapped, this one will get - * included, too. See view_list_add(). - */ - } + if (!weston_surface_is_mapped(surface) && + weston_surface_has_content(surface)) { + weston_surface_map(surface); + } + + /* Cannot call weston_view_update_transform() here, because that would + * call it also for the parent surface, which might not be mapped yet. + * That would lead to inconsistent state, where the window could never + * be mapped. + * + * Instead just force the child surface to appear mapped, to make + * weston_surface_is_mapped() return true, so that when the parent + * surface does get mapped, this one will get included, too. See + * view_list_add(). + */ } static struct weston_subsurface * @@ -4422,8 +4816,7 @@ weston_surface_set_label_func(struct weston_surface *surface, * * Retrieves the raw surface content size in pixels for the given surface. * This is the whole content size in buffer pixels. If the surface - * has no content or the renderer does not implement this feature, - * zeroes are returned. + * has no content, zeroes are returned. * * This function is used to determine the buffer size needed for * a weston_surface_copy_content() call. @@ -4432,15 +4825,15 @@ WL_EXPORT void weston_surface_get_content_size(struct weston_surface *surface, int *width, int *height) { - struct weston_renderer *rer = surface->compositor->renderer; + struct weston_buffer *buffer = surface->buffer_ref.buffer; - if (!rer->surface_get_content_size) { + if (buffer) { + *width = buffer->width; + *height = buffer->height; + } else { *width = 0; *height = 0; - return; } - - rer->surface_get_content_size(surface, width, height); } /** Get the bounding box of a surface and its subsurfaces @@ -4776,7 +5169,8 @@ weston_subsurface_destroy(struct weston_subsurface *sub) weston_subsurface_unlink_parent(sub); weston_surface_state_fini(&sub->cached); - weston_buffer_reference(&sub->cached_buffer_ref, NULL); + weston_buffer_reference(&sub->cached_buffer_ref, NULL, + BUFFER_WILL_NOT_BE_ACCESSED); sub->surface->committed = NULL; sub->surface->committed_private = NULL; @@ -5260,6 +5654,7 @@ weston_head_init(struct weston_head *head, const char *name) wl_list_init(&head->resource_list); wl_list_init(&head->xdg_output_resource_list); head->name = strdup(name); + head->supported_eotf_mask = WESTON_EOTF_MODE_SDR; head->current_protection = WESTON_HDCP_DISABLE; } @@ -5459,6 +5854,33 @@ weston_output_iterate_heads(struct weston_output *output, return container_of(node, struct weston_head, output_link); } +static void +weston_output_compute_protection(struct weston_output *output) +{ + struct weston_head *head; + enum weston_hdcp_protection op_protection; + bool op_protection_valid = false; + struct weston_compositor *wc = output->compositor; + + wl_list_for_each(head, &output->head_list, output_link) { + if (!op_protection_valid) { + op_protection = head->current_protection; + op_protection_valid = true; + } + if (head->current_protection < op_protection) + op_protection = head->current_protection; + } + + if (!op_protection_valid) + op_protection = WESTON_HDCP_DISABLE; + + if (output->current_protection != op_protection) { + output->current_protection = op_protection; + weston_output_damage(output); + weston_schedule_surface_protection_update(wc); + } +} + /** Attach a head to an output * * \param output The output to attach to. @@ -5498,6 +5920,8 @@ weston_output_attach_head(struct weston_output *output, head->output = output; wl_list_insert(output->head_list.prev, &head->output_link); + weston_output_compute_protection(output); + if (output->enabled) { weston_head_add_global(head); @@ -5785,31 +6209,29 @@ weston_head_set_connection_status(struct weston_head *head, bool connected) weston_head_set_device_changed(head); } -static void -weston_output_compute_protection(struct weston_output *output) +/** Store the set of supported EOTF modes + * + * \param head The head to modify. + * \param eotf_mask A bit mask with the possible bits or'ed together from + * enum weston_eotf_mode. + * + * This may set the device_changed flag. + * + * \ingroup head + * \internal + */ +WL_EXPORT void +weston_head_set_supported_eotf_mask(struct weston_head *head, + uint32_t eotf_mask) { - struct weston_head *head; - enum weston_hdcp_protection op_protection; - bool op_protection_valid = false; - struct weston_compositor *wc = output->compositor; + assert((eotf_mask & ~WESTON_EOTF_MODE_ALL_MASK) == 0); - wl_list_for_each(head, &output->head_list, output_link) { - if (!op_protection_valid) { - op_protection = head->current_protection; - op_protection_valid = true; - } - if (head->current_protection < op_protection) - op_protection = head->current_protection; - } + if (head->supported_eotf_mask == eotf_mask) + return; - if (!op_protection_valid) - op_protection = WESTON_HDCP_DISABLE; + head->supported_eotf_mask = eotf_mask; - if (output->current_protection != op_protection) { - output->current_protection = op_protection; - weston_output_damage(output); - weston_schedule_surface_protection_update(wc); - } + weston_head_set_device_changed(head); } WL_EXPORT void @@ -6007,6 +6429,9 @@ weston_head_get_destroy_listener(struct weston_head *head, return wl_signal_get(&head->destroy_signal, notify); } +static void +weston_output_set_position(struct weston_output *output, int x, int y); + /* Move other outputs when one is resized so the space remains contiguous. */ static void weston_compositor_reflow_outputs(struct weston_compositor *compositor, @@ -6015,6 +6440,9 @@ weston_compositor_reflow_outputs(struct weston_compositor *compositor, struct weston_output *output; bool start_resizing = false; + if (compositor->output_flow_dirty) + return; + if (!delta_width) return; @@ -6025,7 +6453,7 @@ weston_compositor_reflow_outputs(struct weston_compositor *compositor, } if (start_resizing) { - weston_output_move(output, output->x + delta_width, output->y); + weston_output_set_position(output, output->x + delta_width, output->y); output->dirty = 1; } } @@ -6037,11 +6465,8 @@ weston_compositor_reflow_outputs(struct weston_compositor *compositor, * \param region The region to be transformed in-place. * * This takes a region in the global coordinate system, and takes into account - * output position, transform and scale, and the zoom, and converts the region - * into output pixel coordinates in the framebuffer. - * - * Uses floating-point operations if zoom is active, which may round to expand - * the region. + * output position, transform and scale, and converts the region into output + * pixel coordinates in the framebuffer. * * \internal * \ingroup output @@ -6050,34 +6475,19 @@ WL_EXPORT void weston_output_region_from_global(struct weston_output *output, pixman_region32_t *region) { - if (output->zoom.active) { - weston_matrix_transform_region(region, &output->matrix, region); - } else { - pixman_region32_translate(region, -output->x, -output->y); - weston_transformed_region(output->width, output->height, - output->transform, - output->current_scale, - region, region); - } + pixman_region32_translate(region, -output->x, -output->y); + weston_transformed_region(output->width, output->height, + output->transform, + output->current_scale, + region, region); } static void weston_output_update_matrix(struct weston_output *output) { - float magnification; - weston_matrix_init(&output->matrix); weston_matrix_translate(&output->matrix, -output->x, -output->y, 0); - if (output->zoom.active) { - magnification = 1 / (1 - output->zoom.spring_z.current); - weston_output_update_zoom(output); - weston_matrix_translate(&output->matrix, -output->zoom.trans_x, - -output->zoom.trans_y, 0); - weston_matrix_scale(&output->matrix, magnification, - magnification, 1.0); - } - switch (output->transform) { case WL_OUTPUT_TRANSFORM_FLIPPED: case WL_OUTPUT_TRANSFORM_FLIPPED_90: @@ -6149,13 +6559,19 @@ weston_output_init_geometry(struct weston_output *output, int x, int y) /** * \ingroup output */ -WL_EXPORT void -weston_output_move(struct weston_output *output, int x, int y) +static void +weston_output_set_position(struct weston_output *output, int x, int y) { struct weston_head *head; struct wl_resource *resource; int ver; + if (!output->enabled) { + output->x = x; + output->y = y; + return; + } + output->move_x = x - output->x; output->move_y = y - output->y; @@ -6196,6 +6612,28 @@ weston_output_move(struct weston_output *output, int x, int y) } } +/** + * \ingroup output + */ +WL_EXPORT void +weston_output_move(struct weston_output *output, int x, int y) +{ + /* XXX: we should probably perform some sanity checking here + * as we do for weston_output_enable, and allow moves to fail. + * + * However, while a front-end is rearranging outputs it may + * pass through indeterminate states where outputs overlap + * or are discontinuous, and this may be ok as long as no + * input processing or rendering occurs at that time. + * + * Ultimately, we probably need a way to pass complete output + * config atomically to libweston. + */ + + output->compositor->output_flow_dirty = true; + weston_output_set_position(output, x, y); +} + /** Signal that a pending output is taken into use. * * Removes the output from the pending list and adds it to the compositor's @@ -6278,51 +6716,107 @@ weston_output_transform_coordinate(struct weston_output *output, *y = p.f[1] / p.f[3]; } -static void -weston_output_reset_color_transforms(struct weston_output *output) +static bool +validate_float_range(float val, float min, float max) { - weston_color_transform_unref(output->from_sRGB_to_output); - output->from_sRGB_to_output = NULL; - weston_color_transform_unref(output->from_sRGB_to_blend); - output->from_sRGB_to_blend = NULL; - weston_color_transform_unref(output->from_blend_to_output); - output->from_blend_to_output = NULL; + return val >= min && val <= max; } +/* Based on CTA-861-G, HDR static metadata type 1 */ static bool -weston_output_set_color_transforms(struct weston_output *output) +weston_hdr_metadata_type1_validate(const struct weston_hdr_metadata_type1 *md) +{ + unsigned i; + + if (md->group_mask & WESTON_HDR_METADATA_TYPE1_GROUP_PRIMARIES) { + for (i = 0; i < ARRAY_LENGTH(md->primary); i++) { + if (!validate_float_range(md->primary[i].x, 0.0, 1.0)) + return false; + if (!validate_float_range(md->primary[i].y, 0.0, 1.0)) + return false; + } + } + + if (md->group_mask & WESTON_HDR_METADATA_TYPE1_GROUP_WHITE) { + if (!validate_float_range(md->white.x, 0.0, 1.0)) + return false; + if (!validate_float_range(md->white.y, 0.0, 1.0)) + return false; + } + + if (md->group_mask & WESTON_HDR_METADATA_TYPE1_GROUP_MAXDML) { + if (!validate_float_range(md->maxDML, 1.0, 65535.0)) + return false; + } + + if (md->group_mask & WESTON_HDR_METADATA_TYPE1_GROUP_MINDML) { + if (!validate_float_range(md->minDML, 0.0001, 6.5535)) + return false; + } + + if (md->group_mask & WESTON_HDR_METADATA_TYPE1_GROUP_MAXCLL) { + if (!validate_float_range(md->maxCLL, 1.0, 65535.0)) + return false; + } + + if (md->group_mask & WESTON_HDR_METADATA_TYPE1_GROUP_MAXFALL) { + if (!validate_float_range(md->maxFALL, 1.0, 65535.0)) + return false; + } + + return true; +} + +WL_EXPORT void +weston_output_color_outcome_destroy(struct weston_output_color_outcome **pco) +{ + struct weston_output_color_outcome *co = *pco; + + if (!co) + return; + + weston_color_transform_unref(co->from_sRGB_to_output); + weston_color_transform_unref(co->from_sRGB_to_blend); + weston_color_transform_unref(co->from_blend_to_output); + + free(co); + *pco = NULL; +} + +WESTON_EXPORT_FOR_TESTS bool +weston_output_set_color_outcome(struct weston_output *output) { struct weston_color_manager *cm = output->compositor->color_manager; - struct weston_color_transform *blend_to_output = NULL; - struct weston_color_transform *sRGB_to_output = NULL; - struct weston_color_transform *sRGB_to_blend = NULL; - bool ok; - - ok = cm->get_output_color_transform(cm, output, &blend_to_output); - ok = ok && cm->get_sRGB_to_output_color_transform(cm, output, - &sRGB_to_output); - ok = ok && cm->get_sRGB_to_blend_color_transform(cm, output, - &sRGB_to_blend); - if (!ok) { + struct weston_output_color_outcome *colorout; + + colorout = cm->create_output_color_outcome(cm, output); + if (!colorout) { weston_log("Creating color transformation for output \"%s\" failed.\n", output->name); - weston_color_transform_unref(blend_to_output); - weston_color_transform_unref(sRGB_to_output); - weston_color_transform_unref(sRGB_to_blend); - return false; } - weston_output_reset_color_transforms(output); - output->from_blend_to_output = blend_to_output; + if (!weston_hdr_metadata_type1_validate(&colorout->hdr_meta)) { + weston_log("Internal color manager error creating Metadata Type 1 for output \"%s\".\n", + output->name); + goto out_error; + } + + weston_output_color_outcome_destroy(&output->color_outcome); + output->color_outcome = colorout; + output->color_outcome_serial++; + output->from_blend_to_output_by_backend = false; - output->from_sRGB_to_output = sRGB_to_output; - output->from_sRGB_to_blend = sRGB_to_blend; weston_log("Output '%s' using color profile: %s\n", output->name, weston_color_profile_get_description(output->color_profile)); return true; + +out_error: + weston_output_color_outcome_destroy(&colorout); + + return false; } /** Removes output from compositor's list of enabled outputs @@ -6367,6 +6861,11 @@ weston_compositor_remove_output(struct weston_output *output) assert(output->destroying); assert(output->enabled); + if (output->idle_repaint_source) { + wl_event_source_remove(output->idle_repaint_source); + output->idle_repaint_source = NULL; + } + wl_list_for_each_safe(pnode, pntmp, &output->paint_node_list, output_link) { weston_paint_node_destroy(pnode); @@ -6382,7 +6881,7 @@ weston_compositor_remove_output(struct weston_output *output) weston_view_assign_output(view); } - weston_output_reset_color_transforms(output); + weston_output_color_outcome_destroy(&output->color_outcome); weston_presentation_feedback_discard_list(&output->feedback_list); @@ -6536,7 +7035,7 @@ weston_output_set_color_profile(struct weston_output *output, output->color_profile = weston_color_profile_ref(cprof); if (output->enabled) { - if (!weston_output_set_color_transforms(output)) { + if (!weston_output_set_color_outcome(output)) { /* Failed, roll back */ weston_color_profile_unref(output->color_profile); output->color_profile = old; @@ -6555,6 +7054,111 @@ weston_output_set_color_profile(struct weston_output *output, return true; } +/** Set EOTF mode on an output + * + * \param output The output to modify, must be in disabled state. + * \param eotf_mode The EOTF mode to set. + * + * Setting the output EOTF mode is used for turning HDR on/off. There are + * multiple modes for HDR on, see enum weston_eotf_mode. This is the high level + * choice on how to drive a video sink (monitor), either in the traditional + * SDR mode or in one of the HDR modes. + * + * After attaching heads to an output, you can find out the possibly supported + * EOTF modes with weston_output_get_supported_eotf_modes(). + * + * This function does not check whether the given eotf_mode is actually + * supported on the output. Enabling an output with an unsupported EOTF mode + * has undefined visual results. + * + * The initial EOTF mode is SDR. + * + * \ingroup output + */ +WL_EXPORT void +weston_output_set_eotf_mode(struct weston_output *output, + enum weston_eotf_mode eotf_mode) +{ + assert(!output->enabled); + + output->eotf_mode = eotf_mode; +} + +/** Get EOTF mode of an output + * + * \param output The output to query. + * \return The EOTF mode. + * + * \sa weston_output_set_eotf_mode + * \ingroup output + */ +WL_EXPORT enum weston_eotf_mode +weston_output_get_eotf_mode(const struct weston_output *output) +{ + return output->eotf_mode; +} + +/** Get HDR static metadata type 1 + * + * \param output The output to query. + * \return Pointer to the metadata stored in weston_output. + * + * This function is meant to be used by libweston backends. + * + * \ingroup output + * \internal + */ +WL_EXPORT const struct weston_hdr_metadata_type1 * +weston_output_get_hdr_metadata_type1(const struct weston_output *output) +{ + assert(output->color_outcome); + return &output->color_outcome->hdr_meta; +} + +/** Set display or monitor basic color characteristics + * + * \param output The output to modify, must be in disabled state. + * \param cc The new characteristics to set, or NULL to unset everything. + * + * This sets the metadata that describes the color characteristics of the + * output in a very simple manner. If a non-NULL color profile is set for the + * output, that will always take precedence. + * + * The initial value has everything unset. + * + * This function is meant to be used by compositor frontends. + * + * \ingroup output + * \sa weston_output_set_color_profile + */ +WL_EXPORT void +weston_output_set_color_characteristics(struct weston_output *output, + const struct weston_color_characteristics *cc) +{ + assert(!output->enabled); + + if (cc) + output->color_characteristics = *cc; + else + output->color_characteristics.group_mask = 0; +} + +/** Get display or monitor basic color characteristics + * + * \param output The output to query. + * \return Pointer to the metadata stored in weston_output. + * + * This function is meant to be used by color manager modules. + * + * \ingroup output + * \sa weston_output_set_color_characteristics + */ +WL_EXPORT const struct weston_color_characteristics * +weston_output_get_color_characteristics(struct weston_output *output) +{ + return &output->color_characteristics; +} + /** Initializes a weston_output object with enough data so ** an output can be configured. * @@ -6582,6 +7186,7 @@ weston_output_init(struct weston_output *output, wl_list_init(&output->link); wl_signal_init(&output->user_destroy_signal); output->enabled = false; + output->eotf_mode = WESTON_EOTF_MODE_SDR; output->desired_protection = WESTON_HDCP_DISABLE; output->allow_protection = true; @@ -6647,6 +7252,45 @@ weston_output_create_heads_string(struct weston_output *output) return str; } +static bool +weston_outputs_overlap(struct weston_output *a, struct weston_output *b) +{ + bool overlap; + pixman_region32_t intersection; + + pixman_region32_init(&intersection); + pixman_region32_intersect(&intersection, &a->region, &b->region); + overlap = pixman_region32_not_empty(&intersection); + pixman_region32_fini(&intersection); + + return overlap; +} + +/* This only works if the output region is current! + * + * That means we shouldn't expect it to return usable results unless + * the output is at least undergoing enabling. + */ +static bool +weston_output_placement_ok(struct weston_output *output) +{ + struct weston_compositor *c = output->compositor; + struct weston_output *iter; + + wl_list_for_each(iter, &c->output_list, link) { + if (!iter->enabled) + continue; + + if (weston_outputs_overlap(iter, output)) { + weston_log("Error: output '%s' overlaps enabled output '%s'.\n", + output->name, iter->name); + return false; + } + } + + return true; +} + /** Constructs a weston_output object that can be used by the compositor. * * \param output The weston_output object that needs to be enabled. Must not @@ -6655,8 +7299,8 @@ weston_output_create_heads_string(struct weston_output *output) * Output coordinates are calculated and each new output is by default * assigned to the right of previous one. * - * Sets up the transformation, zoom, and geometry of the output using - * the properties that need to be configured by the compositor. + * Sets up the transformation, and geometry of the output using the + * properties that need to be configured by the compositor. * * Establishes a repaint timer for the output with the relevant display * object's event loop. See output_repaint_timer_handler(). @@ -6685,11 +7329,8 @@ weston_output_create_heads_string(struct weston_output *output) WL_EXPORT int weston_output_enable(struct weston_output *output) { - struct weston_compositor *c = output->compositor; - struct weston_output *iterator; struct weston_head *head; char *head_names; - int x = 0, y = 0; if (output->enabled) { weston_log("Error: attempt to enable an enabled output '%s'\n", @@ -6714,20 +7355,12 @@ weston_output_enable(struct weston_output *output) assert(head->model); } - iterator = container_of(c->output_list.prev, - struct weston_output, link); - - if (!wl_list_empty(&c->output_list)) - x = iterator->x + iterator->width; - /* Make sure the scale is set up */ assert(output->scale); /* Make sure we have a transform set */ assert(output->transform != UINT32_MAX); - output->x = x; - output->y = y; output->dirty = 1; output->original_scale = output->scale; @@ -6735,9 +7368,13 @@ weston_output_enable(struct weston_output *output) wl_signal_init(&output->destroy_signal); weston_output_transform_scale_init(output, output->transform, output->scale); - weston_output_init_zoom(output); - weston_output_init_geometry(output, x, y); + weston_output_init_geometry(output, output->x, output->y); + + /* At this point we have a valid region so we can check placement. */ + if (!weston_output_placement_ok(output)) + return -1; + weston_output_damage(output); wl_list_init(&output->animation_list); @@ -6745,7 +7382,10 @@ weston_output_enable(struct weston_output *output) wl_list_init(&output->paint_node_list); wl_list_init(&output->paint_node_z_order_list); - if (!weston_output_set_color_transforms(output)) + weston_log("Output '%s' attempts EOTF mode: %s\n", output->name, + weston_eotf_mode_to_str(output->eotf_mode)); + + if (!weston_output_set_color_outcome(output)) return -1; /* Enable the output (set up the crtc or create a @@ -6754,7 +7394,7 @@ weston_output_enable(struct weston_output *output) */ if (output->enable(output) < 0) { weston_log("Enabling output \"%s\" failed.\n", output->name); - weston_output_reset_color_transforms(output); + weston_output_color_outcome_destroy(&output->color_outcome); return -1; } @@ -6907,13 +7547,11 @@ weston_output_release(struct weston_output *output) weston_signal_emit_mutable(&output->user_destroy_signal, output); - if (output->idle_repaint_source) - wl_event_source_remove(output->idle_repaint_source); - if (output->enabled) weston_compositor_remove_output(output); weston_color_profile_unref(output->color_profile); + assert(output->color_outcome == NULL); pixman_region32_fini(&output->region); wl_list_remove(&output->link); @@ -6949,13 +7587,15 @@ weston_compositor_find_output_by_name(struct weston_compositor *compositor, return NULL; } -/** Create a named output +/** Create a named output for an unused head * * \param compositor The compositor. + * \param head The head to attach to the output. * \param name The name for the output. * \return A new \c weston_output, or NULL on failure. * - * This creates a new weston_output that starts with no heads attached. + * This creates a new weston_output that starts with the given head attached. + * The head must not be already attached to another output. * * An output must be configured and it must have at least one head before * it can be enabled. @@ -6964,8 +7604,11 @@ weston_compositor_find_output_by_name(struct weston_compositor *compositor, */ WL_EXPORT struct weston_output * weston_compositor_create_output(struct weston_compositor *compositor, + struct weston_head *head, const char *name) { + struct weston_output *output; + assert(compositor->backend->create_output); if (weston_compositor_find_output_by_name(compositor, name)) { @@ -6974,34 +7617,11 @@ weston_compositor_create_output(struct weston_compositor *compositor, return NULL; } - return compositor->backend->create_output(compositor, name); -} - -/** Create an output for an unused head - * - * \param compositor The compositor. - * \param head The head to attach to the output. - * \return A new \c weston_output, or NULL on failure. - * - * This creates a new weston_output that starts with the given head attached. - * The output inherits the name of the head. The head must not be already - * attached to another output. - * - * An output must be configured before it can be enabled. - * - * \ingroup compositor - */ -WL_EXPORT struct weston_output * -weston_compositor_create_output_with_head(struct weston_compositor *compositor, - struct weston_head *head) -{ - struct weston_output *output; - - output = weston_compositor_create_output(compositor, head->name); + output = compositor->backend->create_output(compositor, name); if (!output) return NULL; - if (weston_output_attach_head(output, head) < 0) { + if (head && weston_output_attach_head(output, head) < 0) { weston_output_destroy(output); return NULL; } @@ -7064,6 +7684,34 @@ weston_output_allow_protection(struct weston_output *output, output->allow_protection = allow_protection; } +/** Get supported EOTF modes as a bit mask + * + * \param output The output to query. + * \return A bit mask with values from enum weston_eotf_mode or'ed together. + * + * Returns the bit mask of the EOTF modes that all the currently attached + * heads claim to support. Adding or removing heads may change the result. + * An output can be queried regrdless of whether it is enabled or disabled. + * + * If no heads are attached, no EOTF modes are deemed supported. + * + * \ingroup output + */ +WL_EXPORT uint32_t +weston_output_get_supported_eotf_modes(struct weston_output *output) +{ + uint32_t eotf_modes = WESTON_EOTF_MODE_ALL_MASK; + struct weston_head *head; + + if (wl_list_empty(&output->head_list)) + return WESTON_EOTF_MODE_NONE; + + wl_list_for_each(head, &output->head_list, output_link) + eotf_modes = eotf_modes & head->supported_eotf_mask; + + return eotf_modes; +} + static void xdg_output_unlist(struct wl_resource *resource) { @@ -7438,43 +8086,60 @@ static void debug_scene_view_print_buffer(FILE *fp, struct weston_view *view) { struct weston_buffer *buffer = view->surface->buffer_ref.buffer; - struct wl_shm_buffer *shm; - struct linux_dmabuf_buffer *dmabuf; - const struct pixel_format_info *pixel_info = NULL; + char *modifier_name; if (!buffer) { fprintf(fp, "\t\t[buffer not available]\n"); return; } - shm = wl_shm_buffer_get(buffer->resource); - if (shm) { - uint32_t _format = wl_shm_buffer_get_format(shm); - pixel_info = pixel_format_get_info_shm(_format); + switch (buffer->type) { + case WESTON_BUFFER_SHM: fprintf(fp, "\t\tSHM buffer\n"); - fprintf(fp, "\t\t\tformat: 0x%lx %s\n", - (unsigned long) _format, - pixel_info ? pixel_info->drm_format_name : "UNKNOWN"); - return; + break; + case WESTON_BUFFER_DMABUF: + fprintf(fp, "\t\tdmabuf buffer\n"); + break; + case WESTON_BUFFER_SOLID: + fprintf(fp, "\t\tsolid-colour buffer\n"); + fprintf(fp, "\t\t\t[R %f, G %f, B %f, A %f]\n", + buffer->solid.r, buffer->solid.g, buffer->solid.b, + buffer->solid.a); + break; + case WESTON_BUFFER_RENDERER_OPAQUE: + fprintf(fp, "\t\tEGL buffer:\n"); + fprintf(fp, "\t\t\t[format may be inaccurate]\n"); + break; } - dmabuf = linux_dmabuf_buffer_get(buffer->resource); - if (dmabuf) { - uint64_t modifier = dmabuf->attributes.modifier[0]; - char *modifier_name = pixel_format_get_modifier(modifier); - pixel_info = pixel_format_get_info(dmabuf->attributes.format); - fprintf(fp, "\t\tdmabuf buffer\n"); - fprintf(fp, "\t\t\tformat: 0x%lx %s\n", - (unsigned long) dmabuf->attributes.format, - pixel_info ? pixel_info->drm_format_name : "UNKNOWN"); + if (buffer->busy_count > 0) { + fprintf(fp, "\t\t\t[%d references may use buffer content]\n", + buffer->busy_count); + } else { + fprintf(fp, "\t\t\t[buffer has been released to client]\n"); + } - fprintf(fp, "\t\t\tmodifier: %s\n", modifier_name ? modifier_name : - "Failed to convert to a modifier name"); - free(modifier_name); - return; + if (buffer->pixel_format) { + fprintf(fp, "\t\t\tformat: 0x%lx %s\n", + (unsigned long) buffer->pixel_format->format, + buffer->pixel_format->drm_format_name); + } else { + fprintf(fp, "\t\t\t[unknown format]\n"); } - fprintf(fp, "\t\tEGL buffer\n"); + modifier_name = pixel_format_get_modifier(buffer->format_modifier); + fprintf(fp, "\t\t\tmodifier: %s\n", + modifier_name ? + modifier_name : "Failed to convert to a modifier name"); + free(modifier_name); + + fprintf(fp, "\t\t\twidth: %d, height: %d\n", + buffer->width, buffer->height); + if (buffer->buffer_origin == ORIGIN_BOTTOM_LEFT) + fprintf(fp, "\t\t\tbottom-left origin\n"); + + if (buffer->direct_display) + fprintf(fp, "\t\t\tdirect-display buffer (no renderer access)\n"); } static void @@ -7490,7 +8155,7 @@ debug_scene_view_print(FILE *fp, struct weston_view *view, int view_idx) if (view->surface->resource) { struct wl_resource *resource = view->surface->resource; wl_client_get_credentials(wl_resource_get_client(resource), - &pid, NULL, NULL); + &pid, NULL, NULL); surface_id = wl_resource_get_id(view->surface->resource); } @@ -7502,6 +8167,11 @@ debug_scene_view_print(FILE *fp, struct weston_view *view, int view_idx) view_idx, view->surface->role_name, pid, surface_id, desc, view); + if (!weston_view_is_mapped(view)) + fprintf(fp, "\t[view is not mapped!]\n"); + if (!weston_surface_is_mapped(view->surface)) + fprintf(fp, "\t[surface is not mapped!]\n"); + box = pixman_region32_extents(&view->transform.boundingbox); fprintf(fp, "\t\tposition: (%d, %d) -> (%d, %d)\n", box->x1, box->y1, box->x2, box->y2); @@ -7755,7 +8425,7 @@ weston_compositor_create(struct wl_display *display, ec->content_protection = NULL; - if (!wl_global_create(ec->wl_display, &wl_compositor_interface, 4, + if (!wl_global_create(ec->wl_display, &wl_compositor_interface, 5, ec, compositor_bind)) goto fail; @@ -7775,6 +8445,11 @@ weston_compositor_create(struct wl_display *display, ec, bind_presentation)) goto fail; + if (!wl_global_create(ec->wl_display, + &wp_single_pixel_buffer_manager_v1_interface, 1, + NULL, bind_single_pixel_buffer)) + goto fail; + if (weston_input_init(ec) != 0) goto fail; @@ -7826,6 +8501,10 @@ weston_compositor_create(struct wl_display *display, weston_timeline_create_subscription, weston_timeline_destroy_subscription, ec); + ec->libseat_debug = + weston_compositor_add_log_scope(ec, "libseat-debug", + "libseat debug messages\n", + NULL, NULL, NULL); return ec; fail: @@ -7844,6 +8523,9 @@ weston_compositor_shutdown(struct weston_compositor *ec) wl_event_source_remove(ec->idle_source); wl_event_source_remove(ec->repaint_timer); + if (ec->touch_calibration) + weston_compositor_destroy_touch_calibrator(ec); + /* Destroy all outputs associated with this compositor */ wl_list_for_each_safe(output, next, &ec->output_list, link) output->destroy(output); @@ -8207,6 +8889,9 @@ weston_compositor_destroy(struct weston_compositor *compositor) weston_log_scope_destroy(compositor->timeline); compositor->timeline = NULL; + weston_log_scope_destroy(compositor->libseat_debug); + compositor->libseat_debug = NULL; + if (compositor->default_dmabuf_feedback) { weston_dmabuf_feedback_destroy(compositor->default_dmabuf_feedback); weston_dmabuf_feedback_format_table_destroy(compositor->dmabuf_feedback_format_table); @@ -8246,7 +8931,6 @@ weston_compositor_get_user_data(struct weston_compositor *compositor) static const char * const backend_map[] = { [WESTON_BACKEND_DRM] = "drm-backend.so", - [WESTON_BACKEND_FBDEV] = "fbdev-backend.so", [WESTON_BACKEND_HEADLESS] = "headless-backend.so", [WESTON_BACKEND_RDP] = "rdp-backend.so", [WESTON_BACKEND_WAYLAND] = "wayland-backend.so", diff --git a/libweston/data-device.c b/libweston/data-device.c index 6ac6f65f..2042cce4 100644 --- a/libweston/data-device.c +++ b/libweston/data-device.c @@ -420,7 +420,7 @@ drag_surface_configure(struct weston_drag *drag, assert((pointer != NULL && touch == NULL) || (pointer == NULL && touch != NULL)); - if (!weston_surface_is_mapped(es) && es->buffer_ref.buffer) { + if (!weston_surface_is_mapped(es) && weston_surface_has_content(es)) { if (pointer && pointer->sprite && weston_view_is_mapped(pointer->sprite)) list = &pointer->sprite->layer_link; @@ -431,7 +431,7 @@ drag_surface_configure(struct weston_drag *drag, weston_layer_entry_insert(list, &drag->icon->layer_link); weston_view_update_transform(drag->icon); pixman_region32_clear(&es->pending.input); - es->is_mapped = true; + weston_surface_map(es); drag->icon->is_mapped = true; } @@ -1057,13 +1057,17 @@ data_device_start_drag(struct wl_client *client, struct wl_resource *resource, touch->focus && touch->focus->surface == origin; - if (!is_pointer_grab && !is_touch_grab) - return; - /* FIXME: Check that the data source type array isn't empty. */ if (source_resource) source = wl_resource_get_user_data(source_resource); + + if (!is_pointer_grab && !is_touch_grab) { + if (source) + wl_data_source_send_cancelled(source->resource); + return; + } + if (icon_resource) icon = wl_resource_get_user_data(icon_resource); @@ -1235,7 +1239,7 @@ destroy_data_source(struct wl_resource *resource) static void client_source_accept(struct weston_data_source *source, - uint32_t time, const char *mime_type) + uint32_t serial, const char *mime_type) { wl_data_source_send_target(source->resource, mime_type); } diff --git a/libweston-desktop/client.c b/libweston/desktop/client.c similarity index 100% rename from libweston-desktop/client.c rename to libweston/desktop/client.c diff --git a/libweston-desktop/internal.h b/libweston/desktop/internal.h similarity index 97% rename from libweston-desktop/internal.h rename to libweston/desktop/internal.h index 2606d279..1d035d5c 100644 --- a/libweston-desktop/internal.h +++ b/libweston/desktop/internal.h @@ -86,6 +86,11 @@ weston_desktop_api_set_xwayland_position(struct weston_desktop *desktop, struct weston_desktop_surface *surface, int32_t x, int32_t y); +void +weston_desktop_api_get_position(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + int32_t *x, int32_t *y); + struct weston_desktop_seat * weston_desktop_seat_from_seat(struct weston_seat *wseat); @@ -100,6 +105,8 @@ struct weston_desktop_surface_implementation { void *user_data, bool resizing); void (*set_size)(struct weston_desktop_surface *surface, void *user_data, int32_t width, int32_t height); + void (*set_orientation)(struct weston_desktop_surface *surface, + void *user_data, enum weston_top_level_tiled_orientation tiled_orientation); void (*committed)(struct weston_desktop_surface *surface, void *user_data, int32_t sx, int32_t sy); void (*update_position)(struct weston_desktop_surface *surface, @@ -234,10 +241,6 @@ weston_desktop_xdg_wm_base_create(struct weston_desktop *desktop, struct wl_global * weston_desktop_xdg_shell_v6_create(struct weston_desktop *desktop, struct wl_display *display); -struct wl_global * -weston_desktop_wl_shell_create(struct weston_desktop *desktop, - struct wl_display *display); - void weston_desktop_xwayland_init(struct weston_desktop *desktop); void diff --git a/libweston-desktop/libweston-desktop.c b/libweston/desktop/libweston-desktop.c similarity index 87% rename from libweston-desktop/libweston-desktop.c rename to libweston/desktop/libweston-desktop.c index fdfe21bf..0be9d717 100644 --- a/libweston-desktop/libweston-desktop.c +++ b/libweston/desktop/libweston-desktop.c @@ -42,7 +42,6 @@ struct weston_desktop { void *user_data; struct wl_global *xdg_wm_base; /* Stable protocol xdg_shell replaces xdg_shell_unstable_v6 */ struct wl_global *xdg_shell_v6; /* Unstable xdg_shell_unstable_v6 protocol. */ - struct wl_global *wl_shell; }; void @@ -77,22 +76,6 @@ weston_desktop_create(struct weston_compositor *compositor, return NULL; } -#ifdef HAVE_DEPRECATED_WL_SHELL - weston_log("Warning: support for deprecated wl_shell interface is " - "enabled. Please migrate legacy clients to xdg-shell.\n"); - desktop->wl_shell = - weston_desktop_wl_shell_create(desktop, display); - if (desktop->wl_shell == NULL) { - weston_desktop_destroy(desktop); - return NULL; - } -#else - weston_log("Note: support for the deprecated wl_shell interface is " - "disabled. If a legacy client still needs it, it can be " - "re-enabled by passing -Ddeprecated-wl-shell=true to Meson " - "when building Weston.\n"); -#endif - weston_desktop_xwayland_init(desktop); return desktop; @@ -106,8 +89,6 @@ weston_desktop_destroy(struct weston_desktop *desktop) weston_desktop_xwayland_fini(desktop); - if (desktop->wl_shell != NULL) - wl_global_destroy(desktop->wl_shell); if (desktop->xdg_shell_v6 != NULL) wl_global_destroy(desktop->xdg_shell_v6); if (desktop->xdg_wm_base != NULL) @@ -189,6 +170,12 @@ weston_desktop_api_show_window_menu(struct weston_desktop *desktop, desktop->user_data); } +bool +weston_desktop_window_menu_supported(struct weston_desktop *desktop) +{ + return desktop->api.show_window_menu != NULL; +} + void weston_desktop_api_set_parent(struct weston_desktop *desktop, struct weston_desktop_surface *surface, @@ -207,6 +194,12 @@ weston_desktop_api_move(struct weston_desktop *desktop, desktop->api.move(surface, seat, serial, desktop->user_data); } +bool +weston_desktop_move_supported(struct weston_desktop *desktop) +{ + return desktop->api.move != NULL; +} + void weston_desktop_api_resize(struct weston_desktop *desktop, struct weston_desktop_surface *surface, @@ -218,6 +211,12 @@ weston_desktop_api_resize(struct weston_desktop *desktop, desktop->user_data); } +bool +weston_desktop_resize_supported(struct weston_desktop *desktop) +{ + return desktop->api.resize != NULL; +} + void weston_desktop_api_fullscreen_requested(struct weston_desktop *desktop, struct weston_desktop_surface *surface, @@ -229,6 +228,12 @@ weston_desktop_api_fullscreen_requested(struct weston_desktop *desktop, desktop->user_data); } +bool +weston_desktop_fullscreen_supported(struct weston_desktop *desktop) +{ + return desktop->api.fullscreen_requested != NULL; +} + void weston_desktop_api_maximized_requested(struct weston_desktop *desktop, struct weston_desktop_surface *surface, @@ -239,6 +244,12 @@ weston_desktop_api_maximized_requested(struct weston_desktop *desktop, desktop->user_data); } +bool +weston_desktop_maximize_supported(struct weston_desktop *desktop) +{ + return desktop->api.maximized_requested != NULL; +} + void weston_desktop_api_minimized_requested(struct weston_desktop *desktop, struct weston_desktop_surface *surface) @@ -247,6 +258,12 @@ weston_desktop_api_minimized_requested(struct weston_desktop *desktop, desktop->api.minimized_requested(surface, desktop->user_data); } +bool +weston_desktop_minimize_supported(struct weston_desktop *desktop) +{ + return desktop->api.minimized_requested != NULL; +} + void weston_desktop_api_set_xwayland_position(struct weston_desktop *desktop, struct weston_desktop_surface *surface, @@ -256,3 +273,14 @@ weston_desktop_api_set_xwayland_position(struct weston_desktop *desktop, desktop->api.set_xwayland_position(surface, x, y, desktop->user_data); } + +void +weston_desktop_api_get_position(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + int32_t *x, int32_t *y) +{ + if (!desktop->api.get_position) + return; + + desktop->api.get_position(surface, x, y, desktop->user_data); +} diff --git a/libweston/desktop/meson.build b/libweston/desktop/meson.build new file mode 100644 index 00000000..4588ad10 --- /dev/null +++ b/libweston/desktop/meson.build @@ -0,0 +1,16 @@ +srcs_libweston += files([ + 'libweston-desktop.c', + 'client.c', + 'seat.c', + 'surface.c', + 'xwayland.c', + 'xdg-shell.c', + 'xdg-shell-v6.c', +]) + +srcs_libweston += [ + xdg_shell_unstable_v6_server_protocol_h, + xdg_shell_unstable_v6_protocol_c, + xdg_shell_server_protocol_h, + xdg_shell_protocol_c, +] diff --git a/libweston-desktop/seat.c b/libweston/desktop/seat.c similarity index 100% rename from libweston-desktop/seat.c rename to libweston/desktop/seat.c diff --git a/libweston-desktop/surface.c b/libweston/desktop/surface.c similarity index 98% rename from libweston-desktop/surface.c rename to libweston/desktop/surface.c index 433f08ae..6b3f4aeb 100644 --- a/libweston-desktop/surface.c +++ b/libweston/desktop/surface.c @@ -506,6 +506,16 @@ weston_desktop_surface_set_size(struct weston_desktop_surface *surface, int32_t width, height); } +WL_EXPORT void +weston_desktop_surface_set_orientation(struct weston_desktop_surface *surface, + enum weston_top_level_tiled_orientation tile_orientation) +{ + if (surface->implementation->set_orientation != NULL) + surface->implementation->set_orientation(surface, + surface->implementation_data, + tile_orientation); +} + WL_EXPORT void weston_desktop_surface_close(struct weston_desktop_surface *surface) { diff --git a/libweston-desktop/xdg-shell-v6.c b/libweston/desktop/xdg-shell-v6.c similarity index 98% rename from libweston-desktop/xdg-shell-v6.c rename to libweston/desktop/xdg-shell-v6.c index 955fccad..13f4213d 100644 --- a/libweston-desktop/xdg-shell-v6.c +++ b/libweston/desktop/xdg-shell-v6.c @@ -645,11 +645,11 @@ weston_desktop_xdg_toplevel_committed(struct weston_desktop_xdg_toplevel *toplev struct weston_surface *wsurface = weston_desktop_surface_get_surface(toplevel->base.desktop_surface); - if (!wsurface->buffer_ref.buffer && !toplevel->added) { + if (!weston_surface_has_content(wsurface) && !toplevel->added) { weston_desktop_xdg_toplevel_ensure_added(toplevel); return; } - if (!wsurface->buffer_ref.buffer) + if (!weston_surface_has_content(wsurface)) return; struct weston_geometry geometry = @@ -841,6 +841,14 @@ weston_desktop_xdg_popup_committed(struct weston_desktop_xdg_popup *popup) popup->committed = true; weston_desktop_xdg_popup_update_position(popup->base.desktop_surface, popup); + + if (!weston_surface_is_mapped(wsurface) && + weston_surface_has_content(wsurface)) { + weston_surface_map(wsurface); + } else if (weston_surface_is_mapped(wsurface) && + !weston_surface_has_content(wsurface)) { + weston_surface_unmap(wsurface); + } } static void @@ -1209,7 +1217,7 @@ weston_desktop_xdg_surface_committed(struct weston_desktop_surface *dsurface, struct weston_surface *wsurface = weston_desktop_surface_get_surface (dsurface); - if (wsurface->buffer_ref.buffer && !surface->configured) { + if (weston_surface_has_content(wsurface) && !surface->configured) { wl_resource_post_error(surface->resource, ZXDG_SURFACE_V6_ERROR_UNCONFIGURED_BUFFER, "xdg_surface has never been configured"); @@ -1397,7 +1405,7 @@ weston_desktop_xdg_shell_protocol_get_xdg_surface(struct wl_client *wl_client, if (surface->resource == NULL) return; - if (wsurface->buffer_ref.buffer != NULL) { + if (weston_surface_has_content(wsurface)) { wl_resource_post_error(surface->resource, ZXDG_SURFACE_V6_ERROR_UNCONFIGURED_BUFFER, "xdg_surface must not have a buffer at creation"); diff --git a/libweston-desktop/xdg-shell.c b/libweston/desktop/xdg-shell.c similarity index 94% rename from libweston-desktop/xdg-shell.c rename to libweston/desktop/xdg-shell.c index 6cbf55e4..ca9862c1 100644 --- a/libweston-desktop/xdg-shell.c +++ b/libweston/desktop/xdg-shell.c @@ -45,7 +45,7 @@ * implements the older unstable xdg shell v6 protocol. ************************************************************************************/ -#define WD_XDG_SHELL_PROTOCOL_VERSION 3 +#define WD_XDG_SHELL_PROTOCOL_VERSION 5 static const char *weston_desktop_xdg_toplevel_role = "xdg_toplevel"; static const char *weston_desktop_xdg_popup_role = "xdg_popup"; @@ -94,6 +94,7 @@ struct weston_desktop_xdg_toplevel_state { bool fullscreen; bool resizing; bool activated; + uint32_t tiled_orientation; }; struct weston_desktop_xdg_toplevel_configure { @@ -625,6 +626,30 @@ weston_desktop_xdg_toplevel_send_configure(struct weston_desktop_xdg_toplevel *t *s = XDG_TOPLEVEL_STATE_ACTIVATED; } + if (toplevel->pending.state.tiled_orientation & + WESTON_TOP_LEVEL_TILED_ORIENTATION_LEFT) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = XDG_TOPLEVEL_STATE_TILED_LEFT; + } + + if (toplevel->pending.state.tiled_orientation & + WESTON_TOP_LEVEL_TILED_ORIENTATION_RIGHT) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = XDG_TOPLEVEL_STATE_TILED_RIGHT; + } + + if (toplevel->pending.state.tiled_orientation & + WESTON_TOP_LEVEL_TILED_ORIENTATION_TOP) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = XDG_TOPLEVEL_STATE_TILED_TOP; + } + + if (toplevel->pending.state.tiled_orientation & + WESTON_TOP_LEVEL_TILED_ORIENTATION_BOTTOM) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = XDG_TOPLEVEL_STATE_TILED_BOTTOM; + } + xdg_toplevel_send_configure(toplevel->resource, toplevel->pending.size.width, toplevel->pending.size.height, @@ -686,6 +711,16 @@ weston_desktop_xdg_toplevel_set_size(struct weston_desktop_surface *dsurface, weston_desktop_xdg_surface_schedule_configure(&toplevel->base); } +static void +weston_desktop_xdg_toplevel_set_orientation(struct weston_desktop_surface *surface, void *user_data, + enum weston_top_level_tiled_orientation tiled_orientation) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.tiled_orientation = tiled_orientation; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + static void weston_desktop_xdg_toplevel_committed(struct weston_desktop_xdg_toplevel *toplevel, int32_t sx, int32_t sy) @@ -693,11 +728,11 @@ weston_desktop_xdg_toplevel_committed(struct weston_desktop_xdg_toplevel *toplev struct weston_surface *wsurface = weston_desktop_surface_get_surface(toplevel->base.desktop_surface); - if (!wsurface->buffer_ref.buffer && !toplevel->added) { + if (!weston_surface_has_content(wsurface) && !toplevel->added) { weston_desktop_xdg_toplevel_ensure_added(toplevel); return; } - if (!wsurface->buffer_ref.buffer) + if (!weston_surface_has_content(wsurface)) return; struct weston_geometry geometry = @@ -962,6 +997,14 @@ weston_desktop_xdg_popup_committed(struct weston_desktop_xdg_popup *popup) popup->committed = true; weston_desktop_xdg_popup_update_position(popup->base.desktop_surface, popup); + + if (!weston_surface_is_mapped(wsurface) && + weston_surface_has_content(wsurface)) { + weston_surface_map(wsurface); + } else if (weston_surface_is_mapped(wsurface) && + !weston_surface_has_content(wsurface)) { + weston_surface_unmap(wsurface); + } } static void @@ -1096,6 +1139,9 @@ weston_desktop_xdg_toplevel_state_compare(struct weston_desktop_xdg_toplevel *to return false; if (toplevel->pending.state.resizing != configured.state.resizing) return false; + if (toplevel->pending.state.tiled_orientation != + configured.state.tiled_orientation) + return false; if (toplevel->pending.size.width == configured.size.width && toplevel->pending.size.height == configured.size.height) @@ -1154,6 +1200,7 @@ weston_desktop_xdg_surface_protocol_get_toplevel(struct wl_client *wl_client, weston_desktop_surface_get_surface(dsurface); struct weston_desktop_xdg_toplevel *toplevel = weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop *desktop = toplevel->base.desktop; if (weston_surface_set_role(wsurface, weston_desktop_xdg_toplevel_role, resource, XDG_WM_BASE_ERROR_ROLE) < 0) @@ -1168,6 +1215,35 @@ weston_desktop_xdg_surface_protocol_get_toplevel(struct wl_client *wl_client, return; toplevel->base.role = WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL; + + if (wl_resource_get_version(toplevel->resource) >= + XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) { + struct wl_array caps; + uint32_t *cap; + + wl_array_init(&caps); + + if (weston_desktop_window_menu_supported(desktop)) { + cap = wl_array_add(&caps, sizeof(*cap)); + *cap = XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU; + } + if (weston_desktop_maximize_supported(desktop)) { + cap = wl_array_add(&caps, sizeof(*cap)); + *cap = XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE; + } + if (weston_desktop_fullscreen_supported(desktop)) { + cap = wl_array_add(&caps, sizeof(*cap)); + *cap = XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN; + } + if (weston_desktop_minimize_supported(desktop)) { + cap = wl_array_add(&caps, sizeof(*cap)); + *cap = XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE; + } + + xdg_toplevel_send_wm_capabilities(toplevel->resource, &caps); + + wl_array_release(&caps); + } } static void @@ -1351,7 +1427,7 @@ weston_desktop_xdg_surface_committed(struct weston_desktop_surface *dsurface, struct weston_surface *wsurface = weston_desktop_surface_get_surface (dsurface); - if (wsurface->buffer_ref.buffer && !surface->configured) { + if (weston_surface_has_content(wsurface) && !surface->configured) { wl_resource_post_error(surface->resource, XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER, "xdg_surface has never been configured"); @@ -1440,6 +1516,7 @@ static const struct weston_desktop_surface_implementation weston_desktop_xdg_sur .set_resizing = weston_desktop_xdg_toplevel_set_resizing, .set_activated = weston_desktop_xdg_toplevel_set_activated, .set_size = weston_desktop_xdg_toplevel_set_size, + .set_orientation = weston_desktop_xdg_toplevel_set_orientation, .get_maximized = weston_desktop_xdg_toplevel_get_maximized, .get_fullscreen = weston_desktop_xdg_toplevel_get_fullscreen, @@ -1518,7 +1595,7 @@ weston_desktop_xdg_shell_protocol_get_xdg_surface(struct wl_client *wl_client, return; } - if (wsurface->buffer_ref.buffer != NULL) { + if (weston_surface_has_content(wsurface)) { wl_resource_post_error(resource, XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER, "xdg_surface must not have a buffer at creation"); diff --git a/libweston-desktop/xwayland.c b/libweston/desktop/xwayland.c similarity index 89% rename from libweston-desktop/xwayland.c rename to libweston/desktop/xwayland.c index cdb65dfc..0b795648 100644 --- a/libweston-desktop/xwayland.c +++ b/libweston/desktop/xwayland.c @@ -64,6 +64,7 @@ struct weston_desktop_xwayland_surface { bool committed; bool added; enum weston_desktop_xwayland_surface_state state; + enum weston_desktop_xwayland_surface_state prev_state; }; static void @@ -122,7 +123,7 @@ weston_desktop_xwayland_surface_change_state(struct weston_desktop_xwayland_surf weston_layer_entry_insert(&surface->xwayland->layer.view_list, &surface->view->layer_link); surface->view->is_mapped = true; - wsurface->is_mapped = true; + weston_surface_map(wsurface); } surface->state = state; @@ -150,8 +151,16 @@ weston_desktop_xwayland_surface_committed(struct weston_desktop_surface *dsurfac if (surface->has_next_geometry) { oldgeom = weston_desktop_surface_get_geometry(surface->surface); - sx -= surface->next_geometry.x - oldgeom.x; - sy -= surface->next_geometry.y - oldgeom.x; + /* If we're transitioning away from fullscreen or maximized + * we've moved to old saved co-ordinates that were saved + * with window geometry in place, so avoid adajusting by + * the geometry in those cases. + */ + if (surface->state == surface->prev_state) { + sx -= surface->next_geometry.x - oldgeom.x; + sy -= surface->next_geometry.y - oldgeom.y; + } + surface->prev_state = surface->state; surface->has_next_geometry = false; weston_desktop_surface_set_geometry(surface->surface, @@ -391,12 +400,31 @@ set_maximized(struct weston_desktop_xwayland_surface *surface) surface->surface, true); } +static void +set_minimized(struct weston_desktop_xwayland_surface *surface) +{ + weston_desktop_api_minimized_requested(surface->desktop, + surface->surface); +} + static void set_pid(struct weston_desktop_xwayland_surface *surface, pid_t pid) { weston_desktop_surface_set_pid(surface->surface, pid); } +static void +get_position(struct weston_desktop_xwayland_surface *surface, + int32_t *x, int32_t *y) +{ + if (!surface->surface) { + *x = 0; + *y = 0; + return; + } + weston_desktop_api_get_position(surface->desktop, surface->surface, x, y); +} + static const struct weston_desktop_xwayland_interface weston_desktop_xwayland_interface = { .create_surface = create_surface, .set_toplevel = set_toplevel, @@ -410,7 +438,9 @@ static const struct weston_desktop_xwayland_interface weston_desktop_xwayland_in .set_title = set_title, .set_window_geometry = set_window_geometry, .set_maximized = set_maximized, + .set_minimized = set_minimized, .set_pid = set_pid, + .get_position = get_position, }; void @@ -427,10 +457,21 @@ weston_desktop_xwayland_init(struct weston_desktop *desktop) xwayland->client = weston_desktop_client_create(desktop, NULL, NULL, NULL, NULL, 0, 0); weston_layer_init(&xwayland->layer, compositor); - /* We put this layer on top of regular shell surfaces, but hopefully - * below any UI the shell would add */ + /* This is the layer we use for override redirect "windows", which + * ends up used for tooltips and drop down menus, among other things. + * Previously this was WESTON_LAYER_POSITION_NORMAL + 1, but this is + * below the fullscreen layer, so fullscreen apps would be above their + * menus and tooltips. + * + * Moving this to just below the TOP_UI layer ensures visibility at all + * times, with the minor drawback that they could be rendered above + * DESKTOP_UI. + * + * For tooltips with no transient window hints, this is probably the best + * we can do. + */ weston_layer_set_position(&xwayland->layer, - WESTON_LAYER_POSITION_NORMAL + 1); + WESTON_LAYER_POSITION_TOP_UI - 1); compositor->xwayland = xwayland; compositor->xwayland_interface = &weston_desktop_xwayland_interface; diff --git a/libweston/input.c b/libweston/input.c index ec25271a..cc6cb00d 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -75,6 +75,15 @@ struct border { enum motion_direction blocking_dir; }; +struct pending_touch { + struct weston_touch_device *touch_device; + bool pending_focus_touch_reset; + + struct wl_list touch_link; +}; + +static struct wl_list pending_touch_list; + static void maybe_warp_confined_pointer(struct weston_pointer_constraint *constraint); @@ -143,6 +152,7 @@ weston_touch_create_touch_device(struct weston_touch *touch, const struct weston_touch_device_ops *ops) { struct weston_touch_device *device; + struct pending_touch *pt; assert(syspath); if (ops) { @@ -164,20 +174,55 @@ weston_touch_create_touch_device(struct weston_touch *touch, return NULL; } + pt = zalloc(sizeof(*pt)); + if (!pt) { + free(device); + return NULL; + } + + device->backend_data = backend_data; device->ops = ops; + pt->touch_device = device; + pt->pending_focus_touch_reset = false; + device->aggregate = touch; wl_list_insert(touch->device_list.prev, &device->link); + wl_list_insert(&pending_touch_list, &pt->touch_link); return device; } +static struct pending_touch * +weston_touch_get_pending(struct weston_touch_device *dev) +{ + struct pending_touch *pt_iter = NULL; + struct pending_touch *pt = NULL; + + wl_list_for_each(pt_iter, &pending_touch_list, touch_link) { + if (pt_iter->touch_device == dev) { + pt = pt_iter; + break; + } + } + + return pt; +} + /** Destroy the touch device. */ WL_EXPORT void weston_touch_device_destroy(struct weston_touch_device *device) { + struct pending_touch *pt = NULL; + wl_list_remove(&device->link); + pt = weston_touch_get_pending(device); + + assert(pt); + wl_list_remove(&pt->touch_link); + free(pt); + wl_signal_emit(&device->destroy_signal, device); free(device->syspath); free(device); @@ -2197,12 +2242,6 @@ notify_key(struct weston_seat *seat, const struct timespec *time, uint32_t key, struct weston_keyboard_grab *grab = keyboard->grab; uint32_t *k, *end; - if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { - weston_compositor_idle_inhibit(compositor); - } else { - weston_compositor_idle_release(compositor); - } - end = keyboard->keys.data + keyboard->keys.size; for (k = keyboard->keys.data; k < end; k++) { if (*k == key) { @@ -2218,6 +2257,12 @@ notify_key(struct weston_seat *seat, const struct timespec *time, uint32_t key, *k = key; } + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + weston_compositor_idle_inhibit(compositor); + } else { + weston_compositor_idle_release(compositor); + } + if (grab == &keyboard->default_grab || grab == &keyboard->input_method_grab) { weston_compositor_run_key_binding(compositor, keyboard, time, @@ -2329,6 +2374,7 @@ notify_keyboard_focus_out(struct weston_seat *seat) if (focus) { seat->use_saved_kbd_focus = true; seat->saved_kbd_focus = focus; + assert(seat->saved_kbd_focus_listener.notify == NULL); seat->saved_kbd_focus_listener.notify = destroy_device_saved_kbd_focus; wl_signal_add(&focus->destroy_signal, @@ -2387,6 +2433,7 @@ process_touch_normal(struct weston_touch_device *device, struct weston_touch_grab *grab = device->aggregate->grab; struct weston_compositor *ec = device->aggregate->seat->compositor; struct weston_view *ev; + struct pending_touch *pt = NULL; wl_fixed_t sx, sy; wl_fixed_t x = wl_fixed_from_double(double_x); wl_fixed_t y = wl_fixed_from_double(double_y); @@ -2437,8 +2484,9 @@ process_touch_normal(struct weston_touch_device *device, break; case WL_TOUCH_UP: grab->interface->up(grab, time, touch_id); - if (touch->num_tp == 0) - weston_touch_set_focus(touch, NULL); + pt = weston_touch_get_pending(device); + assert(pt); + pt->pending_focus_touch_reset = true; break; } } @@ -2627,12 +2675,22 @@ WL_EXPORT void notify_touch_frame(struct weston_touch_device *device) { struct weston_touch_grab *grab; + struct pending_touch *pt; switch (weston_touch_device_get_mode(device)) { case WESTON_TOUCH_MODE_NORMAL: case WESTON_TOUCH_MODE_PREP_CALIB: grab = device->aggregate->grab; grab->interface->frame(grab); + + pt = weston_touch_get_pending(device); + assert(pt); + + if (pt->pending_focus_touch_reset) { + if (grab->touch->num_tp == 0) + weston_touch_set_focus(grab->touch, NULL); + pt->pending_focus_touch_reset = false; + } break; case WESTON_TOUCH_MODE_CALIB: case WESTON_TOUCH_MODE_PREP_NORMAL: @@ -2697,7 +2755,7 @@ pointer_cursor_surface_committed(struct weston_surface *es, weston_layer_entry_insert(&es->compositor->cursor_layer.view_list, &pointer->sprite->layer_link); weston_view_update_transform(pointer->sprite); - es->is_mapped = true; + weston_surface_map(es); pointer->sprite->is_mapped = true; } } @@ -5058,6 +5116,8 @@ bind_input_timestamps_manager(struct wl_client *client, void *data, int weston_input_init(struct weston_compositor *compositor) { + wl_list_init(&pending_touch_list); + if (!wl_global_create(compositor->wl_display, &zwp_relative_pointer_manager_v1_interface, 1, compositor, bind_relative_pointer_manager)) diff --git a/libweston/launcher-direct.c b/libweston/launcher-direct.c deleted file mode 100644 index c04ba857..00000000 --- a/libweston/launcher-direct.c +++ /dev/null @@ -1,370 +0,0 @@ -/* - * Copyright © 2012 Benjamin Franzke - * Copyright © 2013 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. - */ - -#include "config.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "launcher-impl.h" - -#define DRM_MAJOR 226 - -#ifndef KDSKBMUTE -#define KDSKBMUTE 0x4B51 -#endif - -/* major()/minor() */ -#ifdef MAJOR_IN_MKDEV -#include -#endif -#ifdef MAJOR_IN_SYSMACROS -#include -#endif - -#ifdef BUILD_DRM_COMPOSITOR - -#include - -static inline int -is_drm_master(int drm_fd) -{ - drm_magic_t magic; - - return drmGetMagic(drm_fd, &magic) == 0 && - drmAuthMagic(drm_fd, magic) == 0; -} - -#else - -static inline int -drmDropMaster(int drm_fd) -{ - return 0; -} - -static inline int -drmSetMaster(int drm_fd) -{ - return 0; -} - -static inline int -is_drm_master(int drm_fd) -{ - return 0; -} - -#endif - -struct launcher_direct { - struct weston_launcher base; - struct weston_compositor *compositor; - int kb_mode, tty, drm_fd; - struct wl_event_source *vt_source; -}; - -static int -vt_handler(int signal_number, void *data) -{ - struct launcher_direct *launcher = data; - struct weston_compositor *compositor = launcher->compositor; - - if (compositor->session_active) { - compositor->session_active = false; - wl_signal_emit(&compositor->session_signal, compositor); - drmDropMaster(launcher->drm_fd); - ioctl(launcher->tty, VT_RELDISP, 1); - } else { - ioctl(launcher->tty, VT_RELDISP, VT_ACKACQ); - drmSetMaster(launcher->drm_fd); - compositor->session_active = true; - wl_signal_emit(&compositor->session_signal, compositor); - } - - return 1; -} - -static int -setup_tty(struct launcher_direct *launcher, int tty) -{ - struct wl_event_loop *loop; - struct vt_mode mode = { 0 }; - struct stat buf; - char tty_device[32] =""; - int ret, kd_mode; - - if (geteuid() != 0) - return -1; - - if (tty == 0) { - launcher->tty = dup(tty); - if (launcher->tty == -1) { - weston_log("couldn't dup stdin: %s\n", - strerror(errno)); - return -1; - } - } else { - snprintf(tty_device, sizeof tty_device, "/dev/tty%d", tty); - launcher->tty = open(tty_device, O_RDWR | O_CLOEXEC); - if (launcher->tty == -1) { - weston_log("couldn't open tty %s: %s\n", tty_device, - strerror(errno)); - return -1; - } - } - - if (fstat(launcher->tty, &buf) == -1 || - major(buf.st_rdev) != TTY_MAJOR || minor(buf.st_rdev) == 0) { - weston_log("%s not a vt\n", tty_device); - weston_log("if running weston from ssh, " - "use --tty to specify a tty\n"); - goto err_close; - } - - ret = ioctl(launcher->tty, KDGETMODE, &kd_mode); - if (ret) { - weston_log("failed to get VT mode: %s\n", strerror(errno)); - return -1; - } - if (kd_mode != KD_TEXT) { - weston_log("%s is already in graphics mode, " - "is another display server running?\n", tty_device); - } - - ioctl(launcher->tty, VT_ACTIVATE, minor(buf.st_rdev)); - ioctl(launcher->tty, VT_WAITACTIVE, minor(buf.st_rdev)); - - if (ioctl(launcher->tty, KDGKBMODE, &launcher->kb_mode)) { - weston_log("failed to read keyboard mode: %s\n", - strerror(errno)); - goto err_close; - } - - if (ioctl(launcher->tty, KDSKBMUTE, 1) && - ioctl(launcher->tty, KDSKBMODE, K_OFF)) { - weston_log("failed to set K_OFF keyboard mode: %s\n", - strerror(errno)); - goto err_close; - } - - ret = ioctl(launcher->tty, KDSETMODE, KD_GRAPHICS); - if (ret) { - weston_log("failed to set KD_GRAPHICS mode on tty: %s\n", - strerror(errno)); - goto err_close; - } - - /* - * SIGRTMIN is used as global VT-acquire+release signal. Note that - * SIGRT* must be tested on runtime, as their exact values are not - * known at compile-time. POSIX requires 32 of them to be available. - */ - if (SIGRTMIN > SIGRTMAX) { - weston_log("not enough RT signals available: %u-%u\n", - SIGRTMIN, SIGRTMAX); - ret = -EINVAL; - goto err_close; - } - - mode.mode = VT_PROCESS; - mode.relsig = SIGRTMIN; - mode.acqsig = SIGRTMIN; - if (ioctl(launcher->tty, VT_SETMODE, &mode) < 0) { - weston_log("failed to take control of vt handling\n"); - goto err_close; - } - - loop = wl_display_get_event_loop(launcher->compositor->wl_display); - launcher->vt_source = - wl_event_loop_add_signal(loop, SIGRTMIN, vt_handler, launcher); - if (!launcher->vt_source) { - weston_log("failed to add SIGRTMIN signal\n"); - goto err_close; - } - - return 0; - - err_close: - close(launcher->tty); - return -1; -} - -static int -launcher_direct_open(struct weston_launcher *launcher_base, const char *path, int flags) -{ - struct launcher_direct *launcher = wl_container_of(launcher_base, launcher, base); - struct stat s; - int fd; - - fd = open(path, flags | O_CLOEXEC); - if (fd == -1) { - weston_log("couldn't open: %s! error=%s\n", path, strerror(errno)); - return -1; - } - - if (geteuid() != 0) { - weston_log("WARNING! Succeeded opening %s as non-root user." - " This implies your device can be spied on.\n", - path); - } - - if (fstat(fd, &s) == -1) { - weston_log("couldn't fstat: %s! error=%s\n", path, strerror(errno)); - close(fd); - return -1; - } - - if (major(s.st_rdev) == DRM_MAJOR) { - launcher->drm_fd = fd; - if (!is_drm_master(fd)) { - weston_log("drm fd not master\n"); - close(fd); - return -1; - } - } - - return fd; -} - -static void -launcher_direct_close(struct weston_launcher *launcher_base, int fd) -{ - close(fd); -} - -static void -launcher_direct_restore(struct weston_launcher *launcher_base) -{ - struct launcher_direct *launcher = wl_container_of(launcher_base, launcher, base); - struct vt_mode mode = { 0 }; - - if (ioctl(launcher->tty, KDSKBMUTE, 0) && - ioctl(launcher->tty, KDSKBMODE, launcher->kb_mode)) - weston_log("failed to restore kb mode: %s\n", - strerror(errno)); - - if (ioctl(launcher->tty, KDSETMODE, KD_TEXT)) - weston_log("failed to set KD_TEXT mode on tty: %s\n", - strerror(errno)); - - /* We have to drop master before we switch the VT back in - * VT_AUTO, so we don't risk switching to a VT with another - * display server, that will then fail to set drm master. */ - drmDropMaster(launcher->drm_fd); - - mode.mode = VT_AUTO; - if (ioctl(launcher->tty, VT_SETMODE, &mode) < 0) - weston_log("could not reset vt handling! error=%s\n", - strerror(errno)); -} - -static int -launcher_direct_activate_vt(struct weston_launcher *launcher_base, int vt) -{ - struct launcher_direct *launcher = wl_container_of(launcher_base, launcher, base); - return ioctl(launcher->tty, VT_ACTIVATE, vt); -} - -static int -launcher_direct_connect(struct weston_launcher **out, struct weston_compositor *compositor, - int tty, const char *seat_id, bool sync_drm) -{ - struct launcher_direct *launcher; - struct stat buf; - - launcher = zalloc(sizeof(*launcher)); - if (launcher == NULL) { - weston_log("failed to alloc for launcher\n"); - return -ENOMEM; - } - - launcher->base.iface = &launcher_direct_iface; - launcher->compositor = compositor; - - /* Checking the existance of /dev/tty0 and verifying it's a TTY - * device, as kernels compiled with CONFIG_VT=0 do not create these - * devices. */ - if (stat("/dev/tty0", &buf) == 0 && - strcmp("seat0", seat_id) == 0 && major(buf.st_rdev) == TTY_MAJOR) { - if (setup_tty(launcher, tty) == -1) { - free(launcher); - return -1; - } - } else { - launcher->tty = -1; - } - - * (struct launcher_direct **) out = launcher; - return 0; -} - -static void -launcher_direct_destroy(struct weston_launcher *launcher_base) -{ - struct launcher_direct *launcher = wl_container_of(launcher_base, launcher, base); - - if (launcher->tty >= 0) { - launcher_direct_restore(&launcher->base); - wl_event_source_remove(launcher->vt_source); - close(launcher->tty); - } - - free(launcher); -} - -static int -launcher_direct_get_vt(struct weston_launcher *base) -{ - struct launcher_direct *launcher = wl_container_of(base, launcher, base); - struct stat s; - if (fstat(launcher->tty, &s) < 0) { - weston_log("couldn't fstat launcher tty: %s\n", strerror(errno)); - return -1; - } - - return minor(s.st_rdev); -} - -const struct launcher_interface launcher_direct_iface = { - .name = "direct", - .connect = launcher_direct_connect, - .destroy = launcher_direct_destroy, - .open = launcher_direct_open, - .close = launcher_direct_close, - .activate_vt = launcher_direct_activate_vt, - .get_vt = launcher_direct_get_vt, -}; diff --git a/libweston/launcher-impl.h b/libweston/launcher-impl.h index 6bcbbc54..954dc6de 100644 --- a/libweston/launcher-impl.h +++ b/libweston/launcher-impl.h @@ -32,7 +32,7 @@ struct weston_launcher; struct launcher_interface { char *name; int (* connect) (struct weston_launcher **launcher_out, struct weston_compositor *compositor, - int tty, const char *seat_id, bool sync_drm); + const char *seat_id, bool sync_drm); void (* destroy) (struct weston_launcher *launcher); int (* open) (struct weston_launcher *launcher, const char *path, int flags); void (* close) (struct weston_launcher *launcher, int fd); @@ -47,5 +47,3 @@ struct weston_launcher { extern const struct launcher_interface launcher_libseat_iface; extern const struct launcher_interface launcher_logind_iface; -extern const struct launcher_interface launcher_weston_launch_iface; -extern const struct launcher_interface launcher_direct_iface; diff --git a/libweston/launcher-libseat.c b/libweston/launcher-libseat.c index 6aa8ad0f..8c0abb9a 100644 --- a/libweston/launcher-libseat.c +++ b/libweston/launcher-libseat.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,8 @@ #include #include +#include +#include "weston-log-internal.h" #include "backend.h" #include "dbus.h" #include "launcher-impl.h" @@ -60,6 +63,11 @@ struct launcher_libseat { struct wl_list devices; }; +/* debug messages go into a dedicated libseat-debug scope, while info and err + * log level messages go into the log_scope, which the compositor has a + * subscription by default*/ +static struct weston_log_scope *libseat_debug_scope = NULL; + static struct launcher_libseat_device * find_device_by_fd(struct launcher_libseat *wl, int fd) { @@ -179,9 +187,47 @@ libseat_event(int fd, uint32_t mask, void *data) return 1; } +static void +log_libseat_info_err(const char *fmt, va_list ap) +{ + /* these all have been set-up by the compositor and use the 'log' scope */ + weston_vlog(fmt, ap); + weston_log_continue("\n"); +} + +static void +log_libseat_debug(const char *fmt, va_list ap) +{ + int len_va; + char *str; + const char *oom = "Out of memory"; + + if (!weston_log_scope_is_enabled(libseat_debug_scope)) + return; + + len_va = vasprintf(&str, fmt, ap); + if (len_va >= 0) { + weston_log_scope_printf(libseat_debug_scope, "%s\n", str); + free(str); + } else { + weston_log_scope_printf(libseat_debug_scope, "%s\n", oom); + } +} + +static void log_libseat(enum libseat_log_level level, + const char *fmt, va_list ap) +{ + if (level == LIBSEAT_LOG_LEVEL_DEBUG) { + log_libseat_debug(fmt, ap); + return; + } + + log_libseat_info_err(fmt, ap); +} + static int seat_open(struct weston_launcher **out, struct weston_compositor *compositor, - int tty, const char *seat_id, bool sync_drm) + const char *seat_id, bool sync_drm) { struct launcher_libseat *wl; struct wl_event_loop *event_loop; @@ -195,6 +241,13 @@ seat_open(struct weston_launcher **out, struct weston_compositor *compositor, wl->compositor = compositor; wl_list_init(&wl->devices); + libseat_debug_scope = compositor->libseat_debug; + assert(libseat_debug_scope); + libseat_set_log_handler(log_libseat); + + /* includes (all) other log levels available <= LOG_LEVEL_DEBUG */ + libseat_set_log_level(LIBSEAT_LOG_LEVEL_DEBUG); + wl->seat = libseat_open_seat(&seat_listener, wl); if (wl->seat == NULL) { weston_log("libseat: could not open seat\n"); @@ -231,6 +284,9 @@ seat_close(struct weston_launcher *launcher) { struct launcher_libseat *wl = wl_container_of(launcher, wl, base); + libseat_debug_scope = NULL; + libseat_set_log_handler(NULL); + if (wl->seat != NULL) { libseat_close_seat(wl->seat); } diff --git a/libweston/launcher-logind.c b/libweston/launcher-logind.c index 3fca1dff..2b3efecd 100644 --- a/libweston/launcher-logind.c +++ b/libweston/launcher-logind.c @@ -695,29 +695,6 @@ launcher_logind_release_control(struct launcher_logind *wl) } } -static int -weston_sd_session_get_vt(const char *sid, unsigned int *out) -{ -#ifdef HAVE_SYSTEMD_LOGIN_209 - return sd_session_get_vt(sid, out); -#else - int r; - char *tty; - - r = sd_session_get_tty(sid, &tty); - if (r < 0) - return r; - - r = sscanf(tty, "tty%u", out); - free(tty); - - if (r != 1) - return -EINVAL; - - return 0; -#endif -} - static int launcher_logind_activate(struct launcher_logind *wl) { @@ -759,7 +736,7 @@ launcher_logind_get_session(char **session) static int launcher_logind_connect(struct weston_launcher **out, struct weston_compositor *compositor, - int tty, const char *seat_id, bool sync_drm) + const char *seat_id, bool sync_drm) { struct launcher_logind *wl; struct wl_event_loop *loop; @@ -803,15 +780,10 @@ launcher_logind_connect(struct weston_launcher **out, struct weston_compositor * r = sd_seat_can_tty(t); free(t); if (r > 0) { - r = weston_sd_session_get_vt(wl->sid, &wl->vtnr); + r = sd_session_get_vt(wl->sid, &wl->vtnr); if (r < 0) { weston_log("logind: session not running on a VT\n"); goto err_session; - } else if (tty > 0 && wl->vtnr != (unsigned int )tty) { - weston_log("logind: requested VT --tty=%d differs from real session VT %u\n", - tty, wl->vtnr); - r = -EINVAL; - goto err_session; } } else if (r < 0) { weston_log("logind: could not determine if seat %s has ttys or not", t); @@ -881,9 +853,6 @@ static int launcher_logind_get_vt(struct weston_launcher *launcher) { struct launcher_logind *wl = wl_container_of(launcher, wl, base); - if (wl->vtnr <= 0) { - return -EINVAL; - } return wl->vtnr; } diff --git a/libweston/launcher-util.c b/libweston/launcher-util.c index b2219b68..b277606d 100644 --- a/libweston/launcher-util.c +++ b/libweston/launcher-util.c @@ -43,13 +43,11 @@ static const struct launcher_interface *ifaces[] = { #ifdef HAVE_SYSTEMD_LOGIN &launcher_logind_iface, #endif - &launcher_weston_launch_iface, - &launcher_direct_iface, NULL, }; WL_EXPORT struct weston_launcher * -weston_launcher_connect(struct weston_compositor *compositor, int tty, +weston_launcher_connect(struct weston_compositor *compositor, const char *seat_id, bool sync_drm) { const struct launcher_interface **it; @@ -59,7 +57,7 @@ weston_launcher_connect(struct weston_compositor *compositor, int tty, struct weston_launcher *launcher; weston_log("Trying %s launcher...\n", iface->name); - if (iface->connect(&launcher, compositor, tty, seat_id, sync_drm) == 0) + if (iface->connect(&launcher, compositor, seat_id, sync_drm) == 0) return launcher; } diff --git a/libweston/launcher-util.h b/libweston/launcher-util.h index dd7b7702..2ee7ceb5 100644 --- a/libweston/launcher-util.h +++ b/libweston/launcher-util.h @@ -33,7 +33,7 @@ struct weston_launcher; struct weston_launcher * -weston_launcher_connect(struct weston_compositor *compositor, int tty, +weston_launcher_connect(struct weston_compositor *compositor, const char *seat_id, bool sync_drm); void diff --git a/libweston/launcher-weston-launch.c b/libweston/launcher-weston-launch.c deleted file mode 100644 index 109ac486..00000000 --- a/libweston/launcher-weston-launch.c +++ /dev/null @@ -1,396 +0,0 @@ -/* - * Copyright © 2012 Benjamin Franzke - * Copyright © 2013 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. - */ - -#include "config.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "weston-launch.h" -#include "launcher-impl.h" -#include "shared/string-helpers.h" - -#define DRM_MAJOR 226 - -#ifndef KDSKBMUTE -#define KDSKBMUTE 0x4B51 -#endif - -#ifdef BUILD_DRM_COMPOSITOR - -#include - -#else - -static inline int -drmDropMaster(int drm_fd) -{ - return 0; -} - -static inline int -drmSetMaster(int drm_fd) -{ - return 0; -} - -#endif - -/* major()/minor() */ -#ifdef MAJOR_IN_MKDEV -#include -#endif -#ifdef MAJOR_IN_SYSMACROS -#include -#endif - -union cmsg_data { unsigned char b[4]; int fd; }; - -struct launcher_weston_launch { - struct weston_launcher base; - struct weston_compositor *compositor; - int fd; - struct wl_event_source *source; - - int kb_mode, tty, drm_fd; - int deferred_deactivate; -}; - -static ssize_t -launcher_weston_launch_send(int sockfd, void *buf, size_t buflen) -{ - ssize_t len; - - do { - len = send(sockfd, buf, buflen, 0); - } while (len < 0 && errno == EINTR); - - return len; -} - -static void -handle_deactivate(struct launcher_weston_launch *launcher) -{ - int reply; - - launcher->compositor->session_active = false; - wl_signal_emit(&launcher->compositor->session_signal, - launcher->compositor); - - reply = WESTON_LAUNCHER_DEACTIVATE_DONE; - launcher_weston_launch_send(launcher->fd, &reply, sizeof reply); -} - -static void -idle_deactivate(void *data) -{ - struct launcher_weston_launch *launcher = data; - - if (launcher->deferred_deactivate) { - launcher->deferred_deactivate = 0; - handle_deactivate((struct launcher_weston_launch*)data); - } -} - -static int -launcher_weston_launch_open(struct weston_launcher *launcher_base, - const char *path, int flags) -{ - struct launcher_weston_launch *launcher = wl_container_of(launcher_base, launcher, base); - int n; - struct msghdr msg; - struct cmsghdr *cmsg; - struct iovec iov; - union cmsg_data *data; - char control[CMSG_SPACE(sizeof data->fd)]; - ssize_t len; - struct weston_launcher_open *message; - struct { int id; int ret; } event; - - n = sizeof(*message) + strlen(path) + 1; - message = malloc(n); - if (!message) - return -1; - - message->header.opcode = WESTON_LAUNCHER_OPEN; - message->flags = flags; - strcpy(message->path, path); - - launcher_weston_launch_send(launcher->fd, message, n); - free(message); - - memset(&msg, 0, sizeof msg); - iov.iov_base = &event; - iov.iov_len = sizeof event; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_control = control; - - while (1) { - msg.msg_controllen = sizeof control; - - do { - len = recvmsg(launcher->fd, &msg, MSG_CMSG_CLOEXEC); - } while (len < 0 && errno == EINTR); - - // Only OPEN_REPLY and up to one DEACTIVATE message should be possible here - if ((len == sizeof event) && (event.id == WESTON_LAUNCHER_OPEN_REPLY)) - break; - - if ((len == sizeof event.id) && (event.id == WESTON_LAUNCHER_DEACTIVATE) && (launcher->deferred_deactivate == 0)) { - wl_event_loop_add_idle(wl_display_get_event_loop(launcher->compositor->wl_display), idle_deactivate, launcher); - launcher->deferred_deactivate = 1; - } else { - weston_log("unexpected event %d (len=%zd) from weston-launch\n", event.id, len); - return -1; - } - } - - if (event.ret < 0) - return -1; - - cmsg = CMSG_FIRSTHDR(&msg); - if (!cmsg || - cmsg->cmsg_level != SOL_SOCKET || - cmsg->cmsg_type != SCM_RIGHTS) { - fprintf(stderr, "invalid control message\n"); - return -1; - } - - data = (union cmsg_data *) CMSG_DATA(cmsg); - if (data->fd == -1) { - fprintf(stderr, "missing drm fd in socket request\n"); - return -1; - } - - return data->fd; -} - -static void -launcher_weston_launch_close(struct weston_launcher *launcher_base, int fd) -{ - close(fd); -} - -static void -launcher_weston_launch_restore(struct weston_launcher *launcher_base) -{ - struct launcher_weston_launch *launcher = wl_container_of(launcher_base, launcher, base); - struct vt_mode mode = { 0 }; - - if (ioctl(launcher->tty, KDSKBMUTE, 0) && - ioctl(launcher->tty, KDSKBMODE, launcher->kb_mode)) - weston_log("failed to restore kb mode: %s\n", - strerror(errno)); - - if (ioctl(launcher->tty, KDSETMODE, KD_TEXT)) - weston_log("failed to set KD_TEXT mode on tty: %s\n", - strerror(errno)); - - /* We have to drop master before we switch the VT back in - * VT_AUTO, so we don't risk switching to a VT with another - * display server, that will then fail to set drm master. */ - drmDropMaster(launcher->drm_fd); - - mode.mode = VT_AUTO; - if (ioctl(launcher->tty, VT_SETMODE, &mode) < 0) - weston_log("could not reset vt handling\n"); -} - -static int -launcher_weston_launch_data(int fd, uint32_t mask, void *data) -{ - struct launcher_weston_launch *launcher = data; - int len, ret; - - if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { - weston_log("launcher socket closed, exiting\n"); - /* Normally the weston-launch will reset the tty, but - * in this case it died or something, so do it here so - * we don't end up with a stuck vt. */ - launcher_weston_launch_restore(&launcher->base); - exit(-1); - } - - if (launcher->deferred_deactivate) { - launcher->deferred_deactivate = 0; - handle_deactivate(launcher); - return 1; - } - - do { - len = recv(launcher->fd, &ret, sizeof ret, 0); - } while (len < 0 && errno == EINTR); - - switch (ret) { - case WESTON_LAUNCHER_ACTIVATE: - launcher->compositor->session_active = true; - wl_signal_emit(&launcher->compositor->session_signal, - launcher->compositor); - break; - case WESTON_LAUNCHER_DEACTIVATE: - handle_deactivate(launcher); - break; - default: - weston_log("unexpected event from weston-launch\n"); - break; - } - - return 1; -} - -static int -launcher_weston_launch_activate_vt(struct weston_launcher *launcher_base, int vt) -{ - struct launcher_weston_launch *launcher = wl_container_of(launcher_base, launcher, base); - return ioctl(launcher->tty, VT_ACTIVATE, vt); -} - -static int -launcher_weston_environment_get_fd(const char *env) -{ - char *e; - int fd, flags; - - e = getenv(env); - if (!e || !safe_strtoint(e, &fd)) { - weston_log("could not get launcher fd from env\n"); - return -1; - } - - flags = fcntl(fd, F_GETFD); - if (flags == -1) { - weston_log("could not get fd flags!, env: %s, error: %s\n", - env, strerror(errno)); - return -1; - } - - fcntl(fd, F_SETFD, flags | FD_CLOEXEC); - unsetenv(env); - - return fd; -} - - -static int -launcher_weston_launch_connect(struct weston_launcher **out, struct weston_compositor *compositor, - int tty, const char *seat_id, bool sync_drm) -{ - struct launcher_weston_launch *launcher; - struct wl_event_loop *loop; - - launcher = malloc(sizeof *launcher); - if (launcher == NULL) - return -ENOMEM; - - launcher->base.iface = &launcher_weston_launch_iface; - launcher->compositor = compositor; - launcher->drm_fd = -1; - launcher->deferred_deactivate = 0; - launcher->fd = launcher_weston_environment_get_fd("WESTON_LAUNCHER_SOCK"); - if (launcher->fd == -1) { - free(launcher); - return -1; - } - - launcher->tty = launcher_weston_environment_get_fd("WESTON_TTY_FD"); - /* We don't get a chance to read out the original kb - * mode for the tty, so just hard code K_UNICODE here - * in case we have to clean if weston-launch dies. */ - launcher->kb_mode = K_UNICODE; - - loop = wl_display_get_event_loop(compositor->wl_display); - launcher->source = wl_event_loop_add_fd(loop, launcher->fd, - WL_EVENT_READABLE, - launcher_weston_launch_data, - launcher); - if (launcher->source == NULL) { - free(launcher); - weston_log("failed to get weston-launcher socket fd event source\n"); - return -ENOMEM; - } - - * (struct launcher_weston_launch **) out = launcher; - - return 0; -} - -static void -launcher_weston_launch_destroy(struct weston_launcher *launcher_base) -{ - struct launcher_weston_launch *launcher = wl_container_of(launcher_base, launcher, base); - - if (launcher->fd != -1) { - close(launcher->fd); - wl_event_source_remove(launcher->source); - } else { - launcher_weston_launch_restore(&launcher->base); - } - - if (launcher->tty >= 0) - close(launcher->tty); - - free(launcher); -} - -static int -launcher_weston_launch_get_vt(struct weston_launcher *base) -{ - struct launcher_weston_launch *launcher = wl_container_of(base, launcher, base); - struct stat s; - if (fstat(launcher->tty, &s) < 0) { - weston_log("could not fstat launcher tty: %s\n", strerror(errno)); - return -1; - } - - return minor(s.st_rdev); -} - -const struct launcher_interface launcher_weston_launch_iface = { - .name = "weston_launch", - .connect = launcher_weston_launch_connect, - .destroy = launcher_weston_launch_destroy, - .open = launcher_weston_launch_open, - .close = launcher_weston_launch_close, - .activate_vt = launcher_weston_launch_activate_vt, - .get_vt = launcher_weston_launch_get_vt, -}; diff --git a/libweston/libweston-internal.h b/libweston/libweston-internal.h index 7c30706f..8bdac337 100644 --- a/libweston/libweston-internal.h +++ b/libweston/libweston-internal.h @@ -43,6 +43,37 @@ #include #include "color.h" +/* compositor <-> renderer interface */ + +struct weston_renderer { + int (*read_pixels)(struct weston_output *output, + const struct pixel_format_info *format, void *pixels, + uint32_t x, uint32_t y, + uint32_t width, uint32_t height); + void (*repaint_output)(struct weston_output *output, + pixman_region32_t *output_damage); + void (*flush_damage)(struct weston_surface *surface, + struct weston_buffer *buffer); + void (*attach)(struct weston_surface *es, struct weston_buffer *buffer); + void (*destroy)(struct weston_compositor *ec); + + /** See weston_surface_copy_content() */ + int (*surface_copy_content)(struct weston_surface *surface, + void *target, size_t size, + int src_x, int src_y, + int width, int height); + + /** See weston_compositor_import_dmabuf() */ + bool (*import_dmabuf)(struct weston_compositor *ec, + struct linux_dmabuf_buffer *buffer); + + const struct weston_drm_format_array * + (*get_supported_formats)(struct weston_compositor *ec); + + bool (*fill_buffer_info)(struct weston_compositor *ec, + struct weston_buffer *buffer); +}; + /* weston_buffer */ void @@ -50,7 +81,8 @@ weston_buffer_send_server_error(struct weston_buffer *buffer, const char *msg); void weston_buffer_reference(struct weston_buffer_reference *ref, - struct weston_buffer *buffer); + struct weston_buffer *buffer, + enum weston_buffer_reference_type type); void weston_buffer_release_move(struct weston_buffer_release_reference *dest, @@ -389,6 +421,8 @@ const uint64_t * weston_drm_format_get_modifiers(const struct weston_drm_format *format, unsigned int *count_out); +void +weston_compositor_destroy_touch_calibrator(struct weston_compositor *compositor); /** * paint node * @@ -428,4 +462,9 @@ weston_view_find_paint_node(struct weston_view *view, int wl_data_device_manager_init(struct wl_display *display); +/* Exclusively for unit tests */ + +bool +weston_output_set_color_outcome(struct weston_output *output); + #endif diff --git a/libweston/linux-dmabuf.c b/libweston/linux-dmabuf.c index 21de498d..b73508e3 100644 --- a/libweston/linux-dmabuf.c +++ b/libweston/linux-dmabuf.c @@ -35,6 +35,7 @@ #include #include "linux-dmabuf.h" +#include "linux-dmabuf-unstable-v1-server-protocol.h" #include "shared/os-compatibility.h" #include "libweston-internal.h" #include "shared/weston-drm-fourcc.h" diff --git a/libweston/linux-dmabuf.h b/libweston/linux-dmabuf.h index 7cae93c5..32a7cb56 100644 --- a/libweston/linux-dmabuf.h +++ b/libweston/linux-dmabuf.h @@ -27,7 +27,6 @@ #define WESTON_LINUX_DMABUF_H #include -#include "linux-dmabuf-unstable-v1-server-protocol.h" #define MAX_DMABUF_PLANES 4 diff --git a/libweston/meson.build b/libweston/meson.build index 257d6950..313a84f6 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -35,7 +35,6 @@ srcs_libweston = [ 'weston-log-flight-rec.c', 'weston-log.c', 'weston-direct-display.c', - 'zoom.c', linux_dmabuf_unstable_v1_protocol_c, linux_dmabuf_unstable_v1_server_protocol_h, linux_explicit_synchronization_unstable_v1_protocol_c, @@ -52,6 +51,8 @@ srcs_libweston = [ relative_pointer_unstable_v1_server_protocol_h, weston_screenshooter_protocol_c, weston_screenshooter_server_protocol_h, + single_pixel_buffer_v1_protocol_c, + single_pixel_buffer_v1_server_protocol_h, text_cursor_position_protocol_c, text_cursor_position_server_protocol_h, text_input_unstable_v1_protocol_c, @@ -70,6 +71,8 @@ srcs_libweston = [ weston_direct_display_server_protocol_h, ] +subdir('desktop') + if get_option('renderer-gl') dep_egl = dependency('egl', required: false) if not dep_egl.found() @@ -131,6 +134,20 @@ pkgconfig.generate( subdirs: dir_include_libweston ) +if version_weston.version_compare('>= 11.0.90') + error('Remove libweston-desktop.pc for Weston 12.x') +endif + +pkgconfig.generate( + lib_weston, + filebase: 'libweston-desktop-@0@'.format(libweston_major), + name: 'libweston-desktop', + version: version_weston, + description: 'Desktop shell abstraction library for libweston compositors', + requires_private: deps_for_libweston_users, + subdirs: dir_include_libweston +) + pkgconfig.generate( filebase: 'libweston-@0@-protocols'.format(libweston_major), name: 'libWeston Protocols', @@ -144,9 +161,7 @@ pkgconfig.generate( ) srcs_session_helper = [ - 'launcher-direct.c', 'launcher-util.c', - 'launcher-weston-launch.c', ] deps_session_helper = [ dep_libweston_private_h ] @@ -157,13 +172,8 @@ endif systemd_dep = dependency('', required: false) if get_option('launcher-logind') systemd_dep = dependency('libsystemd', version: '>= 209', required: false) - if systemd_dep.found() - config_h.set('HAVE_SYSTEMD_LOGIN_209', '1') - else - systemd_dep = dependency('libsystemd-login', version: '>= 198', required: false) - if not systemd_dep.found() - error('logind support requires libsystemd or libsystemd-login but neither was found. Or, you can use \'-Dlauncher-logind=false\'') - endif + if not systemd_dep.found() + error('logind support requires libsystemd >= 209. Or, you can use \'-Dlauncher-logind=false\'') endif dbus_dep = dependency('dbus-1', version: '>= 1.6', required: false) @@ -228,29 +238,9 @@ dep_vertex_clipping = declare_dependency( include_directories: include_directories('.') ) -if get_option('deprecated-weston-launch') - warning('weston-launch is deprecated and will be removed in a future release. Please migrate to libseat and seatd-launch.') - dep_pam = cc.find_library('pam') - - if not cc.has_function('pam_open_session', dependencies: dep_pam) - error('pam_open_session not found for weston-launch') - endif - - executable( - 'weston-launch', - 'weston-launch.c', - dependencies: [dep_pam, systemd_dep, dep_libdrm], - include_directories: common_inc, - install: true - ) - - meson.add_install_script('echo', 'REMINDER: You are installing weston-launch, please make it setuid-root.') -endif - subdir('color-lcms') subdir('renderer-gl') subdir('backend-drm') -subdir('backend-fbdev') subdir('backend-headless') subdir('backend-rdp') subdir('backend-wayland') diff --git a/libweston/noop-renderer.c b/libweston/noop-renderer.c index d86e7f0b..f99a3130 100644 --- a/libweston/noop-renderer.c +++ b/libweston/noop-renderer.c @@ -31,11 +31,16 @@ #include #include "libweston-internal.h" +struct noop_renderer { + struct weston_renderer base; + unsigned char seed; /* see comment in attach() */ +}; + static int noop_renderer_read_pixels(struct weston_output *output, - pixman_format_code_t format, void *pixels, - uint32_t x, uint32_t y, - uint32_t width, uint32_t height) + const struct pixel_format_info *format, void *pixels, + uint32_t x, uint32_t y, + uint32_t width, uint32_t height) { return 0; } @@ -47,32 +52,40 @@ noop_renderer_repaint_output(struct weston_output *output, } static void -noop_renderer_flush_damage(struct weston_surface *surface) +noop_renderer_flush_damage(struct weston_surface *surface, + struct weston_buffer *buffer) { } static void noop_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) { + struct noop_renderer *renderer = + wl_container_of(es->compositor->renderer, renderer, base); struct wl_shm_buffer *shm_buffer; uint8_t *data; - uint32_t size, i, width, height, stride; - volatile unsigned char unused = 0; /* volatile so it's not optimized out */ + uint32_t size, i, height, stride; + unsigned char unused = 0; if (!buffer) return; - shm_buffer = wl_shm_buffer_get(buffer->resource); - - if (!shm_buffer) { + switch (buffer->type) { + case WESTON_BUFFER_SOLID: + /* no-op, early exit */ + return; + case WESTON_BUFFER_SHM: + /* fine */ + break; + default: weston_log("No-op renderer supports only SHM buffers\n"); return; } + shm_buffer = buffer->shm_buffer; data = wl_shm_buffer_get_data(shm_buffer); stride = wl_shm_buffer_get_stride(shm_buffer); - width = wl_shm_buffer_get_width(shm_buffer); - height = wl_shm_buffer_get_height(shm_buffer); + height = buffer->height; size = stride * height; /* Access the buffer data to make sure the buffer's client gets killed @@ -84,20 +97,18 @@ noop_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) unused ^= data[i]; wl_shm_buffer_end_access(shm_buffer); - buffer->shm_buffer = shm_buffer; - buffer->width = width; - buffer->height = height; -} - -static void -noop_renderer_surface_set_color(struct weston_surface *surface, - float red, float green, float blue, float alpha) -{ + /* Make sure that our unused is actually used, otherwise the compiler + * is free to notice that our reads have no effect and elide them. */ + renderer->seed = unused; } static void noop_renderer_destroy(struct weston_compositor *ec) { + struct noop_renderer *renderer = + wl_container_of(ec->renderer, renderer, base); + + weston_log("no-op renderer SHM seed: %d\n", renderer->seed); free(ec->renderer); ec->renderer = NULL; } @@ -105,19 +116,18 @@ noop_renderer_destroy(struct weston_compositor *ec) WL_EXPORT int noop_renderer_init(struct weston_compositor *ec) { - struct weston_renderer *renderer; + struct noop_renderer *renderer; renderer = zalloc(sizeof *renderer); if (renderer == NULL) return -1; - renderer->read_pixels = noop_renderer_read_pixels; - renderer->repaint_output = noop_renderer_repaint_output; - renderer->flush_damage = noop_renderer_flush_damage; - renderer->attach = noop_renderer_attach; - renderer->surface_set_color = noop_renderer_surface_set_color; - renderer->destroy = noop_renderer_destroy; - ec->renderer = renderer; + renderer->base.read_pixels = noop_renderer_read_pixels; + renderer->base.repaint_output = noop_renderer_repaint_output; + renderer->base.flush_damage = noop_renderer_flush_damage; + renderer->base.attach = noop_renderer_attach; + renderer->base.destroy = noop_renderer_destroy; + ec->renderer = &renderer->base; return 0; } diff --git a/libweston/pixel-formats.c b/libweston/pixel-formats.c index 9064cb66..fc33997d 100644 --- a/libweston/pixel-formats.c +++ b/libweston/pixel-formats.c @@ -48,6 +48,7 @@ #include #include #include +#include #define GL_FORMAT(fmt) .gl_format = (fmt) #define GL_TYPE(type) .gl_type = (type) #define SAMPLER_TYPE(type) .sampler_type = (type) @@ -64,6 +65,12 @@ .bits.b = b_, \ .bits.a = a_, \ .component_type = PIXEL_COMPONENT_TYPE_FIXED +#define BITS_RGBA_FLOAT(r_, g_, b_, a_) \ + .bits.r = r_, \ + .bits.g = g_, \ + .bits.b = b_, \ + .bits.a = a_, \ + .component_type = PIXEL_COMPONENT_TYPE_FLOAT #define PIXMAN_FMT(fmt) .pixman_format = (PIXMAN_ ## fmt) @@ -75,27 +82,48 @@ * colour channels, are not supported. */ static const struct pixel_format_info pixel_format_table[] = { + { + DRM_FORMAT(R8), + BITS_RGBA_FIXED(8, 0, 0, 0), + .bpp = 8, + .hide_from_clients = true, + GL_FORMAT(GL_R8_EXT), + GL_TYPE(GL_UNSIGNED_BYTE), + }, + { + DRM_FORMAT(GR88), + BITS_RGBA_FIXED(8, 8, 0, 0), + .bpp = 16, + .hide_from_clients = true, + GL_FORMAT(GL_RG8_EXT), + GL_TYPE(GL_UNSIGNED_BYTE), + }, { DRM_FORMAT(XRGB4444), BITS_RGBA_FIXED(4, 4, 4, 0), + .bpp = 16, }, { DRM_FORMAT(ARGB4444), BITS_RGBA_FIXED(4, 4, 4, 4), + .bpp = 16, .opaque_substitute = DRM_FORMAT_XRGB4444, }, { DRM_FORMAT(XBGR4444), BITS_RGBA_FIXED(4, 4, 4, 0), + .bpp = 16, }, { DRM_FORMAT(ABGR4444), BITS_RGBA_FIXED(4, 4, 4, 4), + .bpp = 16, .opaque_substitute = DRM_FORMAT_XBGR4444, }, { DRM_FORMAT(RGBX4444), BITS_RGBA_FIXED(4, 4, 4, 0), + .bpp = 16, # if __BYTE_ORDER == __LITTLE_ENDIAN GL_FORMAT(GL_RGBA), GL_TYPE(GL_UNSIGNED_SHORT_4_4_4_4), @@ -104,6 +132,7 @@ static const struct pixel_format_info pixel_format_table[] = { { DRM_FORMAT(RGBA4444), BITS_RGBA_FIXED(4, 4, 4, 4), + .bpp = 16, .opaque_substitute = DRM_FORMAT_RGBX4444, # if __BYTE_ORDER == __LITTLE_ENDIAN GL_FORMAT(GL_RGBA), @@ -113,35 +142,41 @@ static const struct pixel_format_info pixel_format_table[] = { { DRM_FORMAT(BGRX4444), BITS_RGBA_FIXED(4, 4, 4, 0), + .bpp = 16, }, { DRM_FORMAT(BGRA4444), BITS_RGBA_FIXED(4, 4, 4, 4), + .bpp = 16, .opaque_substitute = DRM_FORMAT_BGRX4444, }, { DRM_FORMAT(XRGB1555), BITS_RGBA_FIXED(5, 5, 5, 0), - .depth = 15, + .addfb_legacy_depth = 15, .bpp = 16, }, { DRM_FORMAT(ARGB1555), BITS_RGBA_FIXED(5, 5, 5, 1), + .bpp = 16, .opaque_substitute = DRM_FORMAT_XRGB1555, }, { DRM_FORMAT(XBGR1555), BITS_RGBA_FIXED(5, 5, 5, 0), + .bpp = 16, }, { DRM_FORMAT(ABGR1555), BITS_RGBA_FIXED(5, 5, 5, 1), + .bpp = 16, .opaque_substitute = DRM_FORMAT_XBGR1555, }, { DRM_FORMAT(RGBX5551), BITS_RGBA_FIXED(5, 5, 5, 0), + .bpp = 16, # if __BYTE_ORDER == __LITTLE_ENDIAN GL_FORMAT(GL_RGBA), GL_TYPE(GL_UNSIGNED_SHORT_5_5_5_1), @@ -150,6 +185,7 @@ static const struct pixel_format_info pixel_format_table[] = { { DRM_FORMAT(RGBA5551), BITS_RGBA_FIXED(5, 5, 5, 1), + .bpp = 16, .opaque_substitute = DRM_FORMAT_RGBX5551, # if __BYTE_ORDER == __LITTLE_ENDIAN GL_FORMAT(GL_RGBA), @@ -159,16 +195,18 @@ static const struct pixel_format_info pixel_format_table[] = { { DRM_FORMAT(BGRX5551), BITS_RGBA_FIXED(5, 5, 5, 0), + .bpp = 16, }, { DRM_FORMAT(BGRA5551), BITS_RGBA_FIXED(5, 5, 5, 1), + .bpp = 16, .opaque_substitute = DRM_FORMAT_BGRX5551, }, { DRM_FORMAT(RGB565), BITS_RGBA_FIXED(5, 6, 5, 0), - .depth = 16, + .addfb_legacy_depth = 16, .bpp = 16, # if __BYTE_ORDER == __LITTLE_ENDIAN GL_FORMAT(GL_RGB), @@ -179,21 +217,24 @@ static const struct pixel_format_info pixel_format_table[] = { { DRM_FORMAT(BGR565), BITS_RGBA_FIXED(5, 6, 5, 0), + .bpp = 16, }, { DRM_FORMAT(RGB888), BITS_RGBA_FIXED(8, 8, 8, 0), + .bpp = 24, }, { DRM_FORMAT(BGR888), BITS_RGBA_FIXED(8, 8, 8, 0), + .bpp = 24, GL_FORMAT(GL_RGB), GL_TYPE(GL_UNSIGNED_BYTE), }, { DRM_FORMAT(XRGB8888), BITS_RGBA_FIXED(8, 8, 8, 0), - .depth = 24, + .addfb_legacy_depth = 24, .bpp = 32, GL_FORMAT(GL_BGRA_EXT), GL_TYPE(GL_UNSIGNED_BYTE), @@ -207,7 +248,7 @@ static const struct pixel_format_info pixel_format_table[] = { DRM_FORMAT(ARGB8888), BITS_RGBA_FIXED(8, 8, 8, 8), .opaque_substitute = DRM_FORMAT_XRGB8888, - .depth = 32, + .addfb_legacy_depth = 32, .bpp = 32, GL_FORMAT(GL_BGRA_EXT), GL_TYPE(GL_UNSIGNED_BYTE), @@ -220,6 +261,7 @@ static const struct pixel_format_info pixel_format_table[] = { { DRM_FORMAT(XBGR8888), BITS_RGBA_FIXED(8, 8, 8, 0), + .bpp = 32, GL_FORMAT(GL_RGBA), GL_TYPE(GL_UNSIGNED_BYTE), #if __BYTE_ORDER == __LITTLE_ENDIAN @@ -231,6 +273,7 @@ static const struct pixel_format_info pixel_format_table[] = { { DRM_FORMAT(ABGR8888), BITS_RGBA_FIXED(8, 8, 8, 8), + .bpp = 32, .opaque_substitute = DRM_FORMAT_XBGR8888, GL_FORMAT(GL_RGBA), GL_TYPE(GL_UNSIGNED_BYTE), @@ -243,6 +286,7 @@ static const struct pixel_format_info pixel_format_table[] = { { DRM_FORMAT(RGBX8888), BITS_RGBA_FIXED(8, 8, 8, 0), + .bpp = 32, #if __BYTE_ORDER == __LITTLE_ENDIAN PIXMAN_FMT(r8g8b8x8), #else @@ -252,6 +296,7 @@ static const struct pixel_format_info pixel_format_table[] = { { DRM_FORMAT(RGBA8888), BITS_RGBA_FIXED(8, 8, 8, 8), + .bpp = 32, .opaque_substitute = DRM_FORMAT_RGBX8888, #if __BYTE_ORDER == __LITTLE_ENDIAN PIXMAN_FMT(r8g8b8a8), @@ -262,6 +307,7 @@ static const struct pixel_format_info pixel_format_table[] = { { DRM_FORMAT(BGRX8888), BITS_RGBA_FIXED(8, 8, 8, 0), + .bpp = 32, #if __BYTE_ORDER == __LITTLE_ENDIAN PIXMAN_FMT(b8g8r8x8), #else @@ -271,6 +317,7 @@ static const struct pixel_format_info pixel_format_table[] = { { DRM_FORMAT(BGRA8888), BITS_RGBA_FIXED(8, 8, 8, 8), + .bpp = 32, .opaque_substitute = DRM_FORMAT_BGRX8888, #if __BYTE_ORDER == __LITTLE_ENDIAN PIXMAN_FMT(b8g8r8a8), @@ -281,7 +328,7 @@ static const struct pixel_format_info pixel_format_table[] = { { DRM_FORMAT(XRGB2101010), BITS_RGBA_FIXED(10, 10, 10, 0), - .depth = 30, + .addfb_legacy_depth = 30, .bpp = 32, #if __BYTE_ORDER == __LITTLE_ENDIAN PIXMAN_FMT(x2r10g10b10), @@ -290,6 +337,7 @@ static const struct pixel_format_info pixel_format_table[] = { { DRM_FORMAT(ARGB2101010), BITS_RGBA_FIXED(10, 10, 10, 2), + .bpp = 32, .opaque_substitute = DRM_FORMAT_XRGB2101010, #if __BYTE_ORDER == __LITTLE_ENDIAN PIXMAN_FMT(a2r10g10b10), @@ -298,6 +346,7 @@ static const struct pixel_format_info pixel_format_table[] = { { DRM_FORMAT(XBGR2101010), BITS_RGBA_FIXED(10, 10, 10, 0), + .bpp = 32, # if __BYTE_ORDER == __LITTLE_ENDIAN GL_FORMAT(GL_RGBA), GL_TYPE(GL_UNSIGNED_INT_2_10_10_10_REV_EXT), @@ -307,6 +356,7 @@ static const struct pixel_format_info pixel_format_table[] = { { DRM_FORMAT(ABGR2101010), BITS_RGBA_FIXED(10, 10, 10, 2), + .bpp = 32, .opaque_substitute = DRM_FORMAT_XBGR2101010, # if __BYTE_ORDER == __LITTLE_ENDIAN GL_FORMAT(GL_RGBA), @@ -317,21 +367,74 @@ static const struct pixel_format_info pixel_format_table[] = { { DRM_FORMAT(RGBX1010102), BITS_RGBA_FIXED(10, 10, 10, 0), + .bpp = 32, }, { DRM_FORMAT(RGBA1010102), BITS_RGBA_FIXED(10, 10, 10, 2), + .bpp = 32, .opaque_substitute = DRM_FORMAT_RGBX1010102, }, { DRM_FORMAT(BGRX1010102), BITS_RGBA_FIXED(10, 10, 10, 0), + .bpp = 32, }, { DRM_FORMAT(BGRA1010102), BITS_RGBA_FIXED(10, 10, 10, 2), + .bpp = 32, .opaque_substitute = DRM_FORMAT_BGRX1010102, }, + { + DRM_FORMAT(XBGR16161616), + BITS_RGBA_FIXED(16, 16, 16, 0), + .bpp = 64, +#if __BYTE_ORDER__ == __LITTLE_ENDIAN + GL_FORMAT(GL_RGBA16_EXT), + GL_TYPE(GL_UNSIGNED_SHORT), +#endif + }, + { + DRM_FORMAT(ABGR16161616), + BITS_RGBA_FIXED(16, 16, 16, 16), + .bpp = 64, + .opaque_substitute = DRM_FORMAT_XBGR16161616, +#if __BYTE_ORDER__ == __LITTLE_ENDIAN + GL_FORMAT(GL_RGBA16_EXT), + GL_TYPE(GL_UNSIGNED_SHORT), +#endif + }, + { + DRM_FORMAT(XBGR16161616F), + BITS_RGBA_FLOAT(16, 16, 16, 0), + .bpp = 64, +#if __BYTE_ORDER__ == __LITTLE_ENDIAN + GL_FORMAT(GL_RGBA16F), + GL_TYPE(GL_HALF_FLOAT), +#endif + }, + { + DRM_FORMAT(ABGR16161616F), + BITS_RGBA_FLOAT(16, 16, 16, 16), + .bpp = 64, + .opaque_substitute = DRM_FORMAT_XBGR16161616F, +#if __BYTE_ORDER__ == __LITTLE_ENDIAN + GL_FORMAT(GL_RGBA16F), + GL_TYPE(GL_HALF_FLOAT), +#endif + }, + { + DRM_FORMAT(XRGB16161616F), + BITS_RGBA_FLOAT(16, 16, 16, 0), + .bpp = 64, + }, + { + DRM_FORMAT(ARGB16161616F), + BITS_RGBA_FLOAT(16, 16, 16, 16), + .bpp = 64, + .opaque_substitute = DRM_FORMAT_XRGB16161616F, + }, { DRM_FORMAT(YUYV), SAMPLER_TYPE(EGL_TEXTURE_Y_XUXV_WL), @@ -472,6 +575,10 @@ static const struct pixel_format_info pixel_format_table[] = { .num_planes = 3, .chroma_order = ORDER_VU, }, + { + DRM_FORMAT(XYUV8888), + .bpp = 32, + }, }; WL_EXPORT const struct pixel_format_info * @@ -529,6 +636,19 @@ pixel_format_get_info_by_drm_name(const char *drm_format_name) return NULL; } +WL_EXPORT const struct pixel_format_info * +pixel_format_get_info_by_pixman(pixman_format_code_t pixman_format) +{ + unsigned int i; + + for (i = 0; i < ARRAY_LENGTH(pixel_format_table); i++) { + if (pixel_format_table[i].pixman_format == pixman_format) + return &pixel_format_table[i]; + } + + return NULL; +} + WL_EXPORT unsigned int pixel_format_get_plane_count(const struct pixel_format_info *info) { @@ -563,16 +683,34 @@ pixel_format_get_info_by_opaque_substitute(uint32_t format) return NULL; } +WL_EXPORT unsigned int +pixel_format_hsub(const struct pixel_format_info *info, + unsigned int plane) +{ + /* We don't support any formats where the first plane is subsampled. */ + if (plane == 0 || info->hsub == 0) + return 1; + + return info->hsub; +} + +WL_EXPORT unsigned int +pixel_format_vsub(const struct pixel_format_info *info, + unsigned int plane) +{ + /* We don't support any formats where the first plane is subsampled. */ + if (plane == 0 || info->vsub == 0) + return 1; + + return info->vsub; +} + WL_EXPORT unsigned int pixel_format_width_for_plane(const struct pixel_format_info *info, unsigned int plane, unsigned int width) { - /* We don't support any formats where the first plane is subsampled. */ - if (plane == 0 || !info->hsub) - return width; - - return width / info->hsub; + return width / pixel_format_hsub(info, plane); } WL_EXPORT unsigned int @@ -580,14 +718,9 @@ pixel_format_height_for_plane(const struct pixel_format_info *info, unsigned int plane, unsigned int height) { - /* We don't support any formats where the first plane is subsampled. */ - if (plane == 0 || !info->vsub) - return height; - - return height / info->vsub; + return height / pixel_format_vsub(info, plane); } -#ifdef HAVE_HUMAN_FORMAT_MODIFIER WL_EXPORT char * pixel_format_get_modifier(uint64_t modifier) { @@ -627,12 +760,3 @@ pixel_format_get_modifier(uint64_t modifier) return mod_str; } -#else -WL_EXPORT char * -pixel_format_get_modifier(uint64_t modifier) -{ - char *mod_str; - str_printf(&mod_str, "0x%llx", (unsigned long long) modifier); - return mod_str; -} -#endif diff --git a/libweston/pixel-formats.h b/libweston/pixel-formats.h index 0b1a5f58..849cc8a2 100644 --- a/libweston/pixel-formats.h +++ b/libweston/pixel-formats.h @@ -42,6 +42,10 @@ struct pixel_format_info { /** The DRM format name without the DRM_FORMAT_ prefix. */ const char *drm_format_name; + /** If true, is only for internal use and should not be advertised to + * clients to allow them to create buffers of this format. */ + bool hide_from_clients; + /** If non-zero, number of planes in base (non-modified) format. */ int num_planes; @@ -76,9 +80,10 @@ struct pixel_format_info { /** If set, this format can be used with the legacy drmModeAddFB() * function (not AddFB2), using this and the bpp member. */ - int depth; + int addfb_legacy_depth; - /** See 'depth' member above. */ + /** Number of bits required to store a single pixel, for + * single-planar formats. */ int bpp; /** Horizontal subsampling; if non-zero, divide the width by this @@ -185,6 +190,19 @@ pixel_format_get_info_count(void); const struct pixel_format_info * pixel_format_get_info_by_drm_name(const char *drm_format_name); +/** + * Get pixel format information for a Pixman format code + * + * Given a Pixman format code, return a pixel format info structure describing + * the properties of that format. + * + * @param pixman_format Pixman format code to get info for + * @returns A pixel format structure (must not be freed), or NULL if the + * format could not be found + */ +const struct pixel_format_info * +pixel_format_get_info_by_pixman(pixman_format_code_t pixman_format); + /** * Get number of planes used by a pixel format * @@ -247,6 +265,36 @@ pixel_format_get_opaque_substitute(const struct pixel_format_info *format); const struct pixel_format_info * pixel_format_get_info_by_opaque_substitute(uint32_t format); +/** + * Return the horizontal subsampling factor for a given plane + * + * When horizontal subsampling is effective, a sampler bound to a secondary + * plane must bind the sampler with a smaller effective width. This function + * returns the subsampling factor to use for the given plane. + * + * @param format Pixel format info structure + * @param plane Zero-indexed plane number + * @returns Horizontal subsampling factor for the given plane + */ +unsigned int +pixel_format_hsub(const struct pixel_format_info *format, + unsigned int plane); + +/** + * Return the vertical subsampling factor for a given plane + * + * When vertical subsampling is effective, a sampler bound to a secondary + * plane must bind the sampler with a smaller effective height. This function + * returns the subsampling factor to use for the given plane. + * + * @param format Pixel format info structure + * @param plane Zero-indexed plane number + * @returns Vertical subsampling factor for the given plane + */ +unsigned int +pixel_format_vsub(const struct pixel_format_info *format, + unsigned int plane); + /** * Return the effective sampling width for a given plane * diff --git a/libweston/pixman-renderer.c b/libweston/pixman-renderer.c index 754adce2..62c2cb15 100644 --- a/libweston/pixman-renderer.c +++ b/libweston/pixman-renderer.c @@ -36,11 +36,11 @@ #include "color.h" #include "pixel-formats.h" #include "shared/helpers.h" +#include "shared/signal.h" #include struct pixman_output_state { - void *shadow_buffer; pixman_image_t *shadow_image; pixman_image_t *hw_buffer; pixman_region32_t *hw_extra_damage; @@ -94,9 +94,9 @@ get_renderer(struct weston_compositor *ec) static int pixman_renderer_read_pixels(struct weston_output *output, - pixman_format_code_t format, void *pixels, - uint32_t x, uint32_t y, - uint32_t width, uint32_t height) + const struct pixel_format_info *format, void *pixels, + uint32_t x, uint32_t y, + uint32_t width, uint32_t height) { struct pixman_output_state *po = get_output_state(output); pixman_image_t *out_buf; @@ -106,11 +106,11 @@ pixman_renderer_read_pixels(struct weston_output *output, return -1; } - out_buf = pixman_image_create_bits(format, + out_buf = pixman_image_create_bits(format->pixman_format, width, height, pixels, - (PIXMAN_FORMAT_BPP(format) / 8) * width); + (PIXMAN_FORMAT_BPP(format->pixman_format) / 8) * width); pixman_image_composite32(PIXMAN_OP_SRC, po->hw_buffer, /* src */ @@ -489,6 +489,19 @@ draw_paint_node(struct weston_paint_node *pnode, if (!ps->image) return; + /* if we still have a reference, but the underlying buffer is no longer + * available signal that we should unref image_t as well. This happens + * when using close animations, with the reference surviving the + * animation while the underlying buffer went away as the client was + * terminated. This is a particular use-case and should probably be + * refactored to provide some analogue with the GL-renderer (as in, to + * still maintain the buffer and let the compositor dispose of it). */ + if (ps->buffer_ref.buffer && !ps->buffer_ref.buffer->shm_buffer) { + pixman_image_unref(ps->image); + ps->image = NULL; + return; + } + pixman_region32_init(&repaint); pixman_region32_intersect(&repaint, &pnode->view->transform.boundingbox, damage); @@ -568,7 +581,7 @@ pixman_renderer_repaint_output(struct weston_output *output, pixman_region32_t hw_damage; assert(output->from_blend_to_output_by_backend || - output->from_blend_to_output == NULL); + output->color_outcome->from_blend_to_output == NULL); if (!po->hw_buffer) { po->hw_extra_damage = NULL; @@ -598,7 +611,8 @@ pixman_renderer_repaint_output(struct weston_output *output, } static void -pixman_renderer_flush_damage(struct weston_surface *surface) +pixman_renderer_flush_damage(struct weston_surface *surface, + struct weston_buffer *buffer) { /* No-op for pixman renderer */ } @@ -619,6 +633,26 @@ buffer_state_handle_buffer_destroy(struct wl_listener *listener, void *data) ps->buffer_destroy_listener.notify = NULL; } +static void +pixman_renderer_surface_set_color(struct weston_surface *es, + float red, float green, float blue, float alpha) +{ + struct pixman_surface_state *ps = get_surface_state(es); + pixman_color_t color; + + color.red = red * 0xffff; + color.green = green * 0xffff; + color.blue = blue * 0xffff; + color.alpha = alpha * 0xffff; + + if (ps->image) { + pixman_image_unref(ps->image); + ps->image = NULL; + } + + ps->image = pixman_image_create_solid_fill(&color); +} + static void pixman_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) { @@ -626,7 +660,9 @@ pixman_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) struct wl_shm_buffer *shm_buffer; const struct pixel_format_info *pixel_info; - weston_buffer_reference(&ps->buffer_ref, buffer); + weston_buffer_reference(&ps->buffer_ref, buffer, + buffer ? BUFFER_MAY_BE_ACCESSED : + BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference(&ps->buffer_release_ref, es->buffer_release_ref.buffer_release); @@ -643,32 +679,40 @@ pixman_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) if (!buffer) return; - shm_buffer = wl_shm_buffer_get(buffer->resource); + if (buffer->type == WESTON_BUFFER_SOLID) { + pixman_renderer_surface_set_color(es, + buffer->solid.r, + buffer->solid.g, + buffer->solid.b, + buffer->solid.a); + weston_buffer_reference(&ps->buffer_ref, NULL, + BUFFER_WILL_NOT_BE_ACCESSED); + weston_buffer_release_reference(&ps->buffer_release_ref, NULL); + return; + } - if (! shm_buffer) { + if (buffer->type != WESTON_BUFFER_SHM) { weston_log("Pixman renderer supports only SHM buffers\n"); - weston_buffer_reference(&ps->buffer_ref, NULL); + weston_buffer_reference(&ps->buffer_ref, NULL, + BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference(&ps->buffer_release_ref, NULL); return; } + shm_buffer = buffer->shm_buffer; + pixel_info = pixel_format_get_info_shm(wl_shm_buffer_get_format(shm_buffer)); if (!pixel_info || !pixman_format_supported_source(pixel_info->pixman_format)) { weston_log("Unsupported SHM buffer format 0x%x\n", wl_shm_buffer_get_format(shm_buffer)); - weston_buffer_reference(&ps->buffer_ref, NULL); + weston_buffer_reference(&ps->buffer_ref, NULL, + BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference(&ps->buffer_release_ref, NULL); weston_buffer_send_server_error(buffer, "disconnecting due to unhandled buffer type"); return; } - es->is_opaque = pixel_format_is_opaque(pixel_info); - - buffer->shm_buffer = shm_buffer; - buffer->width = wl_shm_buffer_get_width(shm_buffer); - buffer->height = wl_shm_buffer_get_height(shm_buffer); - ps->image = pixman_image_create_bits(pixel_info->pixman_format, buffer->width, buffer->height, wl_shm_buffer_get_data(shm_buffer), @@ -696,7 +740,8 @@ pixman_renderer_surface_state_destroy(struct pixman_surface_state *ps) pixman_image_unref(ps->image); ps->image = NULL; } - weston_buffer_reference(&ps->buffer_ref, NULL); + weston_buffer_reference(&ps->buffer_ref, NULL, + BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference(&ps->buffer_release_ref, NULL); free(ps); } @@ -750,26 +795,6 @@ pixman_renderer_create_surface(struct weston_surface *surface) return 0; } -static void -pixman_renderer_surface_set_color(struct weston_surface *es, - float red, float green, float blue, float alpha) -{ - struct pixman_surface_state *ps = get_surface_state(es); - pixman_color_t color; - - color.red = red * 0xffff; - color.green = green * 0xffff; - color.blue = blue * 0xffff; - color.alpha = alpha * 0xffff; - - if (ps->image) { - pixman_image_unref(ps->image); - ps->image = NULL; - } - - ps->image = pixman_image_create_solid_fill(&color); -} - static void pixman_renderer_destroy(struct weston_compositor *ec) { @@ -782,21 +807,6 @@ pixman_renderer_destroy(struct weston_compositor *ec) ec->renderer = NULL; } -static void -pixman_renderer_surface_get_content_size(struct weston_surface *surface, - int *width, int *height) -{ - struct pixman_surface_state *ps = get_surface_state(surface); - - if (ps->image) { - *width = pixman_image_get_width(ps->image); - *height = pixman_image_get_height(ps->image); - } else { - *width = 0; - *height = 0; - } -} - static int pixman_renderer_surface_copy_content(struct weston_surface *surface, void *target, size_t size, @@ -867,10 +877,7 @@ pixman_renderer_init(struct weston_compositor *ec) renderer->base.repaint_output = pixman_renderer_repaint_output; renderer->base.flush_damage = pixman_renderer_flush_damage; renderer->base.attach = pixman_renderer_attach; - renderer->base.surface_set_color = pixman_renderer_surface_set_color; renderer->base.destroy = pixman_renderer_destroy; - renderer->base.surface_get_content_size = - pixman_renderer_surface_get_content_size; renderer->base.surface_copy_content = pixman_renderer_surface_copy_content; ec->renderer = &renderer->base; @@ -907,13 +914,16 @@ pixman_renderer_output_set_buffer(struct weston_output *output, pixman_image_t *buffer) { struct pixman_output_state *po = get_output_state(output); + pixman_format_code_t pixman_format; if (po->hw_buffer) pixman_image_unref(po->hw_buffer); po->hw_buffer = buffer; if (po->hw_buffer) { - output->compositor->read_format = pixman_image_get_format(po->hw_buffer); + pixman_format = pixman_image_get_format(po->hw_buffer); + output->compositor->read_format = + pixel_format_get_info_by_pixman(pixman_format); pixman_image_ref(po->hw_buffer); } } @@ -943,19 +953,11 @@ pixman_renderer_output_create(struct weston_output *output, w = output->current_mode->width; h = output->current_mode->height; - po->shadow_buffer = malloc(w * h * 4); - - if (!po->shadow_buffer) { - free(po); - return -1; - } - po->shadow_image = - pixman_image_create_bits(PIXMAN_x8r8g8b8, w, h, - po->shadow_buffer, w * 4); + pixman_image_create_bits_no_clear(PIXMAN_x8r8g8b8, + w, h, NULL, 0); if (!po->shadow_image) { - free(po->shadow_buffer); free(po); return -1; } @@ -977,9 +979,6 @@ pixman_renderer_output_destroy(struct weston_output *output) if (po->hw_buffer) pixman_image_unref(po->hw_buffer); - free(po->shadow_buffer); - - po->shadow_buffer = NULL; po->shadow_image = NULL; po->hw_buffer = NULL; diff --git a/libweston/renderer-gl/egl-glue.c b/libweston/renderer-gl/egl-glue.c index 013172af..8a0381c4 100644 --- a/libweston/renderer-gl/egl-glue.c +++ b/libweston/renderer-gl/egl-glue.c @@ -31,6 +31,7 @@ #include "shared/helpers.h" #include "shared/platform.h" +#include "shared/string-helpers.h" #include "gl-renderer.h" #include "gl-renderer-internal.h" @@ -477,7 +478,7 @@ gl_renderer_set_egl_device(struct gl_renderer *gr) return; } - gl_renderer_log_extensions("EGL device extensions", extensions); + gl_renderer_log_extensions(gr, "EGL device extensions", extensions); /* Try to query the render node using EGL_DRM_RENDER_NODE_FILE_EXT */ if (weston_check_egl_extension(extensions, "EGL_EXT_device_drm_render_node")) @@ -490,8 +491,10 @@ gl_renderer_set_egl_device(struct gl_renderer *gr) gr->drm_device = gr->query_device_string(gr->egl_device, EGL_DRM_DEVICE_FILE_EXT); - if (!gr->drm_device) - weston_log("failed to query DRM device from EGL\n"); + if (gr->drm_device) + weston_log("Using rendering device: %s\n", gr->drm_device); + else + weston_log("warning: failed to query rendering device from EGL\n"); } int @@ -573,8 +576,7 @@ gl_renderer_setup_egl_client_extensions(struct gl_renderer *gr) return 0; } - gl_renderer_log_extensions("EGL client extensions", - extensions); + gl_renderer_log_extensions(gr, "EGL client extensions", extensions); if (weston_check_egl_extension(extensions, "EGL_EXT_device_query")) { gr->query_display_attrib = @@ -594,8 +596,10 @@ gl_renderer_setup_egl_client_extensions(struct gl_renderer *gr) weston_log("warning: EGL_EXT_platform_base not supported.\n"); /* Surfaceless is unusable without platform_base extension */ - if (gr->platform == EGL_PLATFORM_SURFACELESS_MESA) + if (gr->platform == EGL_PLATFORM_SURFACELESS_MESA) { + weston_log("Error: EGL surfaceless platform cannot be used.\n"); return -1; + } return 0; } @@ -615,6 +619,7 @@ gl_renderer_setup_egl_client_extensions(struct gl_renderer *gr) /* at this point we definitely have some platform extensions but * haven't found the supplied platform, so chances are it's * not supported. */ + weston_log("Error: EGL does not support %s platform.\n", extension_suffix); return -1; } @@ -739,5 +744,25 @@ gl_renderer_setup_egl_extensions(struct weston_compositor *ec) "to missing EGL_KHR_wait_sync extension\n"); } + weston_log("EGL features:\n"); + weston_log_continue(STAMP_SPACE "EGL Wayland extension: %s\n", + yesno(gr->has_bind_display)); + weston_log_continue(STAMP_SPACE "context priority: %s\n", + yesno(gr->has_context_priority)); + weston_log_continue(STAMP_SPACE "buffer age: %s\n", + yesno(gr->has_egl_buffer_age)); + weston_log_continue(STAMP_SPACE "partial update: %s\n", + yesno(gr->has_egl_partial_update)); + weston_log_continue(STAMP_SPACE "swap buffers with damage: %s\n", + yesno(gr->swap_buffers_with_damage)); + weston_log_continue(STAMP_SPACE "configless context: %s\n", + yesno(gr->has_configless_context)); + weston_log_continue(STAMP_SPACE "surfaceless context: %s\n", + yesno(gr->has_surfaceless_context)); + weston_log_continue(STAMP_SPACE "dmabuf support: %s\n", + gr->has_dmabuf_import ? + (gr->has_dmabuf_import_modifiers ? "modifiers" : "legacy") : + "no"); + return 0; } diff --git a/libweston/renderer-gl/fragment.glsl b/libweston/renderer-gl/fragment.glsl index cfadb885..7de8d984 100644 --- a/libweston/renderer-gl/fragment.glsl +++ b/libweston/renderer-gl/fragment.glsl @@ -2,6 +2,7 @@ * Copyright 2012 Intel Corporation * Copyright 2015,2019,2021 Collabora, Ltd. * Copyright 2016 NVIDIA Corporation + * Copyright 2021 Advanced Micro Devices, Inc. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -46,10 +47,18 @@ #define SHADER_COLOR_CURVE_IDENTITY 0 #define SHADER_COLOR_CURVE_LUT_3x1D 1 +/* enum gl_shader_color_mapping */ +#define SHADER_COLOR_MAPPING_IDENTITY 0 +#define SHADER_COLOR_MAPPING_3DLUT 1 + #if DEF_VARIANT == SHADER_VARIANT_EXTERNAL #extension GL_OES_EGL_image_external : require #endif +#if DEF_COLOR_MAPPING == SHADER_COLOR_MAPPING_3DLUT +#extension GL_OES_texture_3D : require +#endif + #ifdef GL_FRAGMENT_PRECISION_HIGH #define HIGHPRECISION highp #else @@ -66,9 +75,11 @@ compile_const int c_variant = DEF_VARIANT; compile_const bool c_input_is_premult = DEF_INPUT_IS_PREMULT; compile_const bool c_green_tint = DEF_GREEN_TINT; compile_const int c_color_pre_curve = DEF_COLOR_PRE_CURVE; +compile_const int c_color_mapping = DEF_COLOR_MAPPING; compile_const bool c_need_color_pipeline = - c_color_pre_curve != SHADER_COLOR_CURVE_IDENTITY; + c_color_pre_curve != SHADER_COLOR_CURVE_IDENTITY || + c_color_mapping != SHADER_COLOR_MAPPING_IDENTITY; vec4 yuva2rgba(vec4 yuva) @@ -108,11 +119,16 @@ uniform sampler2D tex; varying vec2 v_texcoord; uniform sampler2D tex1; uniform sampler2D tex2; -uniform float alpha; +uniform float view_alpha; uniform vec4 unicolor; uniform HIGHPRECISION sampler2D color_pre_curve_lut_2d; uniform HIGHPRECISION vec2 color_pre_curve_lut_scale_offset; +#if DEF_COLOR_MAPPING == SHADER_COLOR_MAPPING_3DLUT +uniform HIGHPRECISION sampler3D color_mapping_lut_3d; +uniform HIGHPRECISION vec2 color_mapping_lut_scale_offset; +#endif + vec4 sample_input_texture() { @@ -171,6 +187,12 @@ lut_texcoord(float x, vec2 scale_offset) return x * scale_offset.s + scale_offset.t; } +vec3 +lut_texcoord(vec3 pos, vec2 scale_offset) +{ + return pos * scale_offset.s + scale_offset.t; +} + /* * Sample a 1D LUT which is a single row of a 2D texture. The 2D texture has * four rows so that the centers of texels have precise y-coordinates. @@ -202,10 +224,41 @@ color_pre_curve(vec3 color) } } +vec3 +sample_color_mapping_lut_3d(vec3 color) +{ + vec3 pos, ret = vec3(0.0, 0.0, 0.0); +#if DEF_COLOR_MAPPING == SHADER_COLOR_MAPPING_3DLUT + pos = lut_texcoord(color, color_mapping_lut_scale_offset); + ret = texture3D(color_mapping_lut_3d, pos).rgb; +#endif + return ret; +} + +vec3 +color_mapping(vec3 color) +{ + if (c_color_mapping == SHADER_COLOR_MAPPING_IDENTITY) + return color; + else if (c_color_mapping == SHADER_COLOR_MAPPING_3DLUT) + return sample_color_mapping_lut_3d(color); + else /* Never reached, bad c_color_mapping. */ + return vec3(1.0, 0.3, 1.0); +} + vec4 color_pipeline(vec4 color) { + /* Ensure straight alpha */ + if (c_input_is_premult) { + if (color.a == 0.0) + color.rgb = vec3(0, 0, 0); + else + color.rgb *= 1.0 / color.a; + } + color.rgb = color_pre_curve(color.rgb); + color.rgb = color_mapping(color.rgb); return color; } @@ -218,35 +271,14 @@ main() /* Electrical (non-linear) RGBA values, may be premult or not */ color = sample_input_texture(); - if (c_need_color_pipeline) { - /* Ensure straight alpha */ - if (c_input_is_premult) { - if (color.a == 0.0) - color.rgb = vec3(0, 0, 0); - else - color.rgb *= 1.0 / color.a; - } - - color = color_pipeline(color); - - /* View alpha (opacity) */ - color.a *= alpha; + if (c_need_color_pipeline) + color = color_pipeline(color); /* Produces straight alpha */ - /* pre-multiply for blending */ + /* Ensure pre-multiplied for blending */ + if (!c_input_is_premult || c_need_color_pipeline) color.rgb *= color.a; - } else { - /* Fast path for disabled color management */ - - if (c_input_is_premult) { - /* View alpha (opacity) */ - color *= alpha; - } else { - /* View alpha (opacity) */ - color.a *= alpha; - /* pre-multiply for blending */ - color.rgb *= color.a; - } - } + + color *= view_alpha; if (c_green_tint) color = vec4(0.0, 0.3, 0.0, 0.2) + color * 0.8; diff --git a/libweston/renderer-gl/gl-renderer-internal.h b/libweston/renderer-gl/gl-renderer-internal.h index 72101b47..d84d9d37 100644 --- a/libweston/renderer-gl/gl-renderer-internal.h +++ b/libweston/renderer-gl/gl-renderer-internal.h @@ -2,6 +2,7 @@ * Copyright © 2019 Collabora, Ltd. * Copyright © 2019 Harish Krupo * Copyright © 2019 Intel Corporation + * Copyright 2021 Advanced Micro Devices, Inc. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -56,6 +57,12 @@ enum gl_shader_color_curve { SHADER_COLOR_CURVE_LUT_3x1D, }; +/* Keep the following in sync with fragment.glsl. */ +enum gl_shader_color_mapping { + SHADER_COLOR_MAPPING_IDENTITY = 0, + SHADER_COLOR_MAPPING_3DLUT, +}; + /** GL shader requirements key * * This structure is used as a binary blob key for building and searching @@ -70,13 +77,14 @@ struct gl_shader_requirements unsigned variant:4; /* enum gl_shader_texture_variant */ bool input_is_premult:1; bool green_tint:1; - unsigned color_pre_curve:1; /* enum gl_shader_color_curve */ + unsigned color_pre_curve:1; /* enum gl_shader_color_curve */ + unsigned color_mapping:1; /* enum gl_shader_color_mapping */ /* * The total size of all bitfields plus pad_bits_ must fill up exactly * how many bytes the compiler allocates for them together. */ - unsigned pad_bits_:25; + unsigned pad_bits_:24; }; static_assert(sizeof(struct gl_shader_requirements) == 4 /* total bitfield size in bytes */, @@ -96,11 +104,18 @@ struct gl_shader_config { GLuint input_tex[GL_SHADER_INPUT_TEX_MAX]; GLuint color_pre_curve_lut_tex; GLfloat color_pre_curve_lut_scale_offset[2]; + union { + struct { + GLuint tex; + GLfloat scale_offset[2]; + } lut3d; + } color_mapping; }; struct gl_renderer { struct weston_renderer base; struct weston_compositor *compositor; + struct weston_log_scope *renderer_scope; bool fragment_shader_debug; bool fan_debug; @@ -156,6 +171,7 @@ struct gl_renderer { bool has_texture_type_2_10_10_10_rev; bool has_gl_texture_rg; + bool has_texture_norm16; struct gl_shader *current_shader; struct gl_shader *fallback_shader; @@ -198,7 +214,8 @@ void gl_renderer_print_egl_error_state(void); void -gl_renderer_log_extensions(const char *name, const char *extensions); +gl_renderer_log_extensions(struct gl_renderer *gr, + const char *name, const char *extensions); void log_egl_config_info(EGLDisplay egldpy, EGLConfig eglconfig); diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index a5f5eae4..0f2dab5a 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -57,6 +57,7 @@ #include "shared/fd-util.h" #include "shared/helpers.h" #include "shared/platform.h" +#include "shared/string-helpers.h" #include "shared/timespec-util.h" #include "shared/weston-drm-fourcc.h" #include "shared/weston-egl-ext.h" @@ -106,37 +107,8 @@ struct gl_output_state { struct gl_fbo_texture shadow; }; -enum buffer_type { - BUFFER_TYPE_NULL, - BUFFER_TYPE_SOLID, /* internal solid color surfaces without a buffer */ - BUFFER_TYPE_SHM, - BUFFER_TYPE_EGL -}; - struct gl_renderer; -struct egl_image { - struct gl_renderer *renderer; - EGLImageKHR image; - int refcount; -}; - -enum import_type { - IMPORT_TYPE_INVALID, - IMPORT_TYPE_DIRECT, - IMPORT_TYPE_GL_CONVERSION -}; - -struct dmabuf_image { - struct linux_dmabuf_buffer *dmabuf; - int num_images; - struct egl_image *images[3]; - struct wl_list link; - - enum import_type import_type; - enum gl_shader_texture_variant shader_variant; -}; - struct dmabuf_format { uint32_t format; struct wl_list link; @@ -146,61 +118,62 @@ struct dmabuf_format { int num_modifiers; }; +/* + * yuv_format_descriptor and yuv_plane_descriptor describe the translation + * between YUV and RGB formats. When native YUV sampling is not available, we + * bind each YUV plane as one or more RGB plane and convert in the shader. + * This structure describes the mapping: output_planes is the number of + * RGB images we need to bind, each of which has a yuv_plane_descriptor + * describing the GL format and the input (YUV) plane index to bind. + * + * The specified shader_variant is then used to sample. + */ struct yuv_plane_descriptor { - int width_divisor; - int height_divisor; uint32_t format; int plane_index; }; -enum texture_type { - TEXTURE_Y_XUXV_WL, - TEXTURE_Y_UV_WL, - TEXTURE_Y_U_V_WL, - TEXTURE_XYUV_WL -}; - struct yuv_format_descriptor { uint32_t format; - int input_planes; int output_planes; - enum texture_type texture_type; - struct yuv_plane_descriptor plane[4]; + enum gl_shader_texture_variant shader_variant; + struct yuv_plane_descriptor plane[3]; }; -struct gl_surface_state { +struct gl_buffer_state { + struct gl_renderer *gr; + GLfloat color[4]; - GLuint textures[3]; - int num_textures; bool needs_full_upload; pixman_region32_t texture_damage; - /* These are only used by SHM surfaces to detect when we need - * to do a full upload to specify a new internal texture - * format */ - GLenum gl_format[3]; + /* Only needed between attach() and flush_damage() */ + int pitch; /* plane 0 pitch in pixels */ GLenum gl_pixel_type; + GLenum gl_format[3]; + int offset[3]; /* per-plane pitch in bytes */ - struct egl_image* images[3]; + EGLImageKHR images[3]; int num_images; enum gl_shader_texture_variant shader_variant; - struct weston_buffer_reference buffer_ref; - struct weston_buffer_release_reference buffer_release_ref; - enum buffer_type buffer_type; - int pitch; /* in pixels */ - int height; /* in pixels */ - bool y_inverted; - bool direct_display; + GLuint textures[3]; + int num_textures; - /* Extension needed for SHM YUV texture */ - int offset[3]; /* offset per plane */ - int hsub[3]; /* horizontal subsampling per plane */ - int vsub[3]; /* vertical subsampling per plane */ + struct wl_listener destroy_listener; +}; +struct gl_surface_state { struct weston_surface *surface; + struct gl_buffer_state *buffer; + + /* These buffer references should really be attached to paint nodes + * rather than either buffer or surface state */ + struct weston_buffer_reference buffer_ref; + struct weston_buffer_release_reference buffer_release_ref; + /* Whether this surface was used in the current output repaint. Used only in the context of a gl_renderer_repaint_output call. */ bool used_in_output_repaint; @@ -275,6 +248,69 @@ shadow_exists(const struct gl_output_state *go) return go->shadow.fbo != 0; } +struct yuv_format_descriptor yuv_formats[] = { + { + .format = DRM_FORMAT_YUYV, + .output_planes = 2, + .shader_variant = SHADER_VARIANT_Y_XUXV, + {{ + .format = DRM_FORMAT_GR88, + .plane_index = 0 + }, { + .format = DRM_FORMAT_ARGB8888, + .plane_index = 0 + }} + }, { + .format = DRM_FORMAT_NV12, + .output_planes = 2, + .shader_variant = SHADER_VARIANT_Y_UV, + {{ + .format = DRM_FORMAT_R8, + .plane_index = 0 + }, { + .format = DRM_FORMAT_GR88, + .plane_index = 1 + }} + }, { + .format = DRM_FORMAT_YUV420, + .output_planes = 3, + .shader_variant = SHADER_VARIANT_Y_U_V, + {{ + .format = DRM_FORMAT_R8, + .plane_index = 0 + }, { + .format = DRM_FORMAT_R8, + .plane_index = 1 + }, { + .format = DRM_FORMAT_R8, + .plane_index = 2 + }} + }, { + .format = DRM_FORMAT_YUV444, + .output_planes = 3, + .shader_variant = SHADER_VARIANT_Y_U_V, + {{ + .format = DRM_FORMAT_R8, + .plane_index = 0 + }, { + .format = DRM_FORMAT_R8, + .plane_index = 1 + }, { + .format = DRM_FORMAT_R8, + .plane_index = 2 + }} + }, { + .format = DRM_FORMAT_XYUV8888, + .output_planes = 1, + .shader_variant = SHADER_VARIANT_XYUV, + {{ + .format = DRM_FORMAT_XBGR8888, + .plane_index = 0 + }} + } +}; + + static void timeline_render_point_destroy(struct timeline_render_point *trp) { @@ -358,87 +394,6 @@ timeline_submit_render_sync(struct gl_renderer *gr, wl_list_insert(&go->timeline_render_point_list, &trp->link); } -static struct egl_image* -egl_image_create(struct gl_renderer *gr, EGLenum target, - EGLClientBuffer buffer, const EGLint *attribs) -{ - struct egl_image *img; - - img = zalloc(sizeof *img); - img->renderer = gr; - img->refcount = 1; - img->image = gr->create_image(gr->egl_display, EGL_NO_CONTEXT, - target, buffer, attribs); - - if (img->image == EGL_NO_IMAGE_KHR) { - free(img); - return NULL; - } - - return img; -} - -static struct egl_image* -egl_image_ref(struct egl_image *image) -{ - image->refcount++; - - return image; -} - -static int -egl_image_unref(struct egl_image *image) -{ - struct gl_renderer *gr; - - /* in multi-planar cases, egl_image_create() might fail on an - * intermediary step resulting in egl_image being NULL. In order to go - * over all successful ones, and avoid leaking one of them (the last - * one), we'll have to guard against it -- until we'll have a correct - * way of disposing of any previous created images. - */ - if (!image) - return 0; - - gr = image->renderer; - assert(image->refcount > 0); - - image->refcount--; - if (image->refcount > 0) - return image->refcount; - - gr->destroy_image(gr->egl_display, image->image); - free(image); - - return 0; -} - -static struct dmabuf_image* -dmabuf_image_create(void) -{ - struct dmabuf_image *img; - - img = zalloc(sizeof *img); - wl_list_init(&img->link); - - return img; -} - -static void -dmabuf_image_destroy(struct dmabuf_image *image) -{ - int i; - - for (i = 0; i < image->num_images; ++i) - egl_image_unref(image->images[i]); - - if (image->dmabuf) - linux_dmabuf_buffer_set_user_data(image->dmabuf, NULL, NULL); - - wl_list_remove(&image->link); - free(image); -} - #define max(a, b) (((a) > (b)) ? (a) : (b)) #define min(a, b) (((a) > (b)) ? (b) : (a)) @@ -568,6 +523,7 @@ texture_region(struct weston_view *ev, pixman_region32_t *surf_region) { struct gl_surface_state *gs = get_surface_state(ev->surface); + struct weston_buffer *buffer = gs->buffer_ref.buffer; struct weston_compositor *ec = ev->surface->compositor; struct gl_renderer *gr = get_renderer(ec); GLfloat *v, inv_width, inv_height; @@ -593,8 +549,8 @@ texture_region(struct weston_view *ev, v = wl_array_add(&gr->vertices, nrects * nsurf * 8 * 4 * sizeof *v); vtxcnt = wl_array_add(&gr->vtxcnt, nrects * nsurf * sizeof *vtxcnt); - inv_width = 1.0 / gs->pitch; - inv_height = 1.0 / gs->height; + inv_width = 1.0 / buffer->width; + inv_height = 1.0 / buffer->height; for (i = 0; i < nrects; i++) { pixman_box32_t *rect = &rects[i]; @@ -634,10 +590,10 @@ texture_region(struct weston_view *ev, sx, sy, &bx, &by); *(v++) = bx * inv_width; - if (gs->y_inverted) { + if (buffer->buffer_origin == ORIGIN_TOP_LEFT) { *(v++) = by * inv_height; } else { - *(v++) = (gs->height - by) * inv_height; + *(v++) = (buffer->height - by) * inv_height; } } @@ -740,6 +696,7 @@ triangle_fan_debug(struct gl_renderer *gr, static int color_idx = 0; struct gl_shader_config alt; const GLfloat *col; + struct weston_color_transform *ctransf; static const GLfloat color[][4] = { { 1.0, 0.0, 0.0, 1.0 }, { 0.0, 1.0, 0.0, 1.0 }, @@ -758,8 +715,8 @@ triangle_fan_debug(struct gl_renderer *gr, .unicolor = { col[0], col[1], col[2], col[3] }, }; - if (!gl_shader_config_set_color_transform(&alt, - output->from_sRGB_to_blend)) { + ctransf = output->color_outcome->from_sRGB_to_blend; + if (!gl_shader_config_set_color_transform(&alt, ctransf)) { weston_log("GL-renderer: %s failed to generate a color transformation.\n", __func__); return; @@ -888,7 +845,7 @@ ensure_surface_buffer_is_ready(struct gl_renderer *gr, assert(gr->has_native_fence_sync); /* We should only get a fence for non-SHM buffers, since surface * commit would have failed otherwise. */ - assert(wl_shm_buffer_get(buffer->resource) == NULL); + assert(buffer->type != WESTON_BUFFER_SHM); attribs[1] = dup(surface->acquire_fence_fd); if (attribs[1] == -1) { @@ -932,6 +889,7 @@ static void censor_override(struct gl_shader_config *sconf, struct weston_output *output) { + struct weston_color_transform *ctransf; struct gl_shader_config alt = { .req = { .variant = SHADER_VARIANT_SOLID, @@ -942,8 +900,8 @@ censor_override(struct gl_shader_config *sconf, .unicolor = { 0.40, 0.0, 0.0, 1.0 }, }; - if (!gl_shader_config_set_color_transform(&alt, - output->from_sRGB_to_blend)) { + ctransf = output->color_outcome->from_sRGB_to_blend; + if (!gl_shader_config_set_color_transform(&alt, ctransf)) { weston_log("GL-renderer: %s failed to generate a color transformation.\n", __func__); } @@ -965,6 +923,7 @@ maybe_censor_override(struct gl_shader_config *sconf, struct weston_view *ev) { struct gl_surface_state *gs = get_surface_state(ev->surface); + struct weston_buffer *buffer = gs->buffer_ref.buffer; bool recording_censor = (output->disable_planes > 0) && (ev->surface->desired_protection > WESTON_HDCP_DISABLE); @@ -972,7 +931,7 @@ maybe_censor_override(struct gl_shader_config *sconf, bool unprotected_censor = (ev->surface->desired_protection > output->current_protection); - if (gs->direct_display) { + if (buffer->direct_display) { censor_override(sconf, output); return; } @@ -991,18 +950,19 @@ static void gl_shader_config_set_input_textures(struct gl_shader_config *sconf, struct gl_surface_state *gs) { + struct gl_buffer_state *gb = gs->buffer; int i; - sconf->req.variant = gs->shader_variant; + sconf->req.variant = gb->shader_variant; sconf->req.input_is_premult = - gl_shader_texture_variant_can_be_premult(gs->shader_variant); + gl_shader_texture_variant_can_be_premult(gb->shader_variant); for (i = 0; i < 4; i++) - sconf->unicolor[i] = gs->color[i]; + sconf->unicolor[i] = gb->color[i]; - assert(gs->num_textures <= GL_SHADER_INPUT_TEX_MAX); - for (i = 0; i < gs->num_textures; i++) - sconf->input_tex[i] = gs->textures[i]; + assert(gb->num_textures <= GL_SHADER_INPUT_TEX_MAX); + for (i = 0; i < gb->num_textures; i++) + sconf->input_tex[i] = gb->textures[i]; for (; i < GL_SHADER_INPUT_TEX_MAX; i++) sconf->input_tex[i] = 0; } @@ -1040,6 +1000,8 @@ draw_paint_node(struct weston_paint_node *pnode, { struct gl_renderer *gr = get_renderer(pnode->surface->compositor); struct gl_surface_state *gs = get_surface_state(pnode->surface); + struct gl_buffer_state *gb = gs->buffer; + struct weston_buffer *buffer = gs->buffer_ref.buffer; /* repaint bounding region in global coordinates: */ pixman_region32_t repaint; /* opaque region in surface coordinates: */ @@ -1049,10 +1011,8 @@ draw_paint_node(struct weston_paint_node *pnode, GLint filter; struct gl_shader_config sconf; - /* In case of a runtime switch of renderers, we may not have received - * an attach for this surface since the switch. In that case we don't - * have a valid buffer or a proper shader set up so skip rendering. */ - if (gs->shader_variant == SHADER_VARIANT_NONE && !gs->direct_display) + if (gb->shader_variant == SHADER_VARIANT_NONE && + !buffer->direct_display) return; pixman_region32_init(&repaint); @@ -1068,7 +1028,7 @@ draw_paint_node(struct weston_paint_node *pnode, glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - if (pnode->view->transform.enabled || pnode->output->zoom.active || + if (pnode->view->transform.enabled || pnode->output->current_scale != pnode->surface->buffer_viewport.buffer.scale) filter = GL_LINEAR; else @@ -1307,6 +1267,7 @@ draw_output_borders(struct weston_output *output, }, .view_alpha = 1.0f, }; + struct weston_color_transform *ctransf; struct gl_output_state *go = get_output_state(output); struct gl_renderer *gr = get_renderer(output->compositor); struct gl_border_image *top, *bottom, *left, *right; @@ -1315,7 +1276,8 @@ draw_output_borders(struct weston_output *output, if (border_status == BORDER_STATUS_CLEAN) return; /* Clean. Nothing to do. */ - if (!gl_shader_config_set_color_transform(&sconf, output->from_sRGB_to_output)) { + ctransf = output->color_outcome->from_sRGB_to_output; + if (!gl_shader_config_set_color_transform(&sconf, ctransf)) { weston_log("GL-renderer: %s failed to generate a color transformation.\n", __func__); return; } @@ -1550,13 +1512,15 @@ blit_shadow_to_output(struct weston_output *output, struct gl_renderer *gr = get_renderer(output->compositor); double width = output->current_mode->width; double height = output->current_mode->height; + struct weston_color_transform *ctransf; pixman_box32_t *rects; int n_rects; int i; pixman_region32_t translated_damage; GLfloat verts[4 * 2]; - if (!gl_shader_config_set_color_transform(&sconf, output->from_blend_to_output)) { + ctransf = output->color_outcome->from_blend_to_output; + if (!gl_shader_config_set_color_transform(&sconf, ctransf)) { weston_log("GL-renderer: %s failed to generate a color transformation.\n", __func__); return; } @@ -1624,7 +1588,8 @@ gl_renderer_repaint_output(struct weston_output *output, struct weston_paint_node *pnode; assert(output->from_blend_to_output_by_backend || - output->from_blend_to_output == NULL || shadow_exists(go)); + output->color_outcome->from_blend_to_output == NULL || + shadow_exists(go)); if (use_output(output) < 0) return; @@ -1783,33 +1748,24 @@ gl_renderer_repaint_output(struct weston_output *output, static int gl_renderer_read_pixels(struct weston_output *output, - pixman_format_code_t format, void *pixels, + const struct pixel_format_info *format, void *pixels, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { - GLenum gl_format; struct gl_output_state *go = get_output_state(output); x += go->borders[GL_RENDERER_BORDER_LEFT].width; y += go->borders[GL_RENDERER_BORDER_BOTTOM].height; - switch (format) { - case PIXMAN_a8r8g8b8: - gl_format = GL_BGRA_EXT; - break; - case PIXMAN_a8b8g8r8: - gl_format = GL_RGBA; - break; - default: + if (format->gl_format == 0 || format->gl_type == 0) return -1; - } if (use_output(output) < 0) return -1; glPixelStorei(GL_PACK_ALIGNMENT, 1); - glReadPixels(x, y, width, height, gl_format, - GL_UNSIGNED_BYTE, pixels); + glReadPixels(x, y, width, height, format->gl_format, + format->gl_type, pixels); return 0; } @@ -1822,6 +1778,7 @@ gl_format_from_internal(GLenum internal_format) return GL_RED_EXT; case GL_RG8_EXT: return GL_RG_EXT; + case GL_RGBA16_EXT: case GL_RGBA16F: case GL_RGB10_A2: return GL_RGBA; @@ -1831,22 +1788,28 @@ gl_format_from_internal(GLenum internal_format) } static void -gl_renderer_flush_damage(struct weston_surface *surface) +gl_renderer_flush_damage(struct weston_surface *surface, + struct weston_buffer *buffer) { const struct weston_testsuite_quirks *quirks = &surface->compositor->test_data.test_quirks; struct gl_surface_state *gs = get_surface_state(surface); - struct weston_buffer *buffer = gs->buffer_ref.buffer; + struct gl_buffer_state *gb = gs->buffer; struct weston_view *view; bool texture_used; pixman_box32_t *rectangles; uint8_t *data; int i, j, n; - pixman_region32_union(&gs->texture_damage, - &gs->texture_damage, &surface->damage); + assert(buffer && gb); - if (!buffer) + pixman_region32_union(&gb->texture_damage, + &gb->texture_damage, &surface->damage); + + /* This can happen if a SHM wl_buffer gets destroyed before we flush + * damage, because wayland-server just nukes the wl_shm_buffer from + * underneath us */ + if (!buffer->shm_buffer) return; /* Avoid upload, if the texture won't be used this time. @@ -1864,378 +1827,439 @@ gl_renderer_flush_damage(struct weston_surface *surface) if (!texture_used) return; - if (!pixman_region32_not_empty(&gs->texture_damage) && - !gs->needs_full_upload) + if (!pixman_region32_not_empty(&gb->texture_damage) && + !gb->needs_full_upload) goto done; data = wl_shm_buffer_get_data(buffer->shm_buffer); glActiveTexture(GL_TEXTURE0); - if (gs->needs_full_upload || quirks->gl_force_full_upload) { + if (gb->needs_full_upload || quirks->gl_force_full_upload) { glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0); glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0); wl_shm_buffer_begin_access(buffer->shm_buffer); - for (j = 0; j < gs->num_textures; j++) { - glBindTexture(GL_TEXTURE_2D, gs->textures[j]); + + for (j = 0; j < gb->num_textures; j++) { + int hsub = pixel_format_hsub(buffer->pixel_format, j); + int vsub = pixel_format_vsub(buffer->pixel_format, j); + + glBindTexture(GL_TEXTURE_2D, gb->textures[j]); glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, - gs->pitch / gs->hsub[j]); + gb->pitch / hsub); glTexImage2D(GL_TEXTURE_2D, 0, - gs->gl_format[j], - gs->pitch / gs->hsub[j], - buffer->height / gs->vsub[j], + gb->gl_format[j], + buffer->width / hsub, + buffer->height / vsub, 0, - gl_format_from_internal(gs->gl_format[j]), - gs->gl_pixel_type, - data + gs->offset[j]); + gl_format_from_internal(gb->gl_format[j]), + gb->gl_pixel_type, + data + gb->offset[j]); } wl_shm_buffer_end_access(buffer->shm_buffer); goto done; } - rectangles = pixman_region32_rectangles(&gs->texture_damage, &n); + rectangles = pixman_region32_rectangles(&gb->texture_damage, &n); wl_shm_buffer_begin_access(buffer->shm_buffer); for (i = 0; i < n; i++) { pixman_box32_t r; r = weston_surface_to_buffer_rect(surface, rectangles[i]); - for (j = 0; j < gs->num_textures; j++) { - glBindTexture(GL_TEXTURE_2D, gs->textures[j]); + for (j = 0; j < gb->num_textures; j++) { + int hsub = pixel_format_hsub(buffer->pixel_format, j); + int vsub = pixel_format_vsub(buffer->pixel_format, j); + + glBindTexture(GL_TEXTURE_2D, gb->textures[j]); glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, - gs->pitch / gs->hsub[j]); - glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, - r.x1 / gs->hsub[j]); - glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, - r.y1 / gs->hsub[j]); + gb->pitch / hsub); + glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, r.x1 / hsub); + glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, r.y1 / vsub); glTexSubImage2D(GL_TEXTURE_2D, 0, - r.x1 / gs->hsub[j], - r.y1 / gs->vsub[j], - (r.x2 - r.x1) / gs->hsub[j], - (r.y2 - r.y1) / gs->vsub[j], - gl_format_from_internal(gs->gl_format[j]), - gs->gl_pixel_type, - data + gs->offset[j]); + r.x1 / hsub, + r.y1 / vsub, + (r.x2 - r.x1) / hsub, + (r.y2 - r.y1) / vsub, + gl_format_from_internal(gb->gl_format[j]), + gb->gl_pixel_type, + data + gb->offset[j]); } } wl_shm_buffer_end_access(buffer->shm_buffer); done: - pixman_region32_fini(&gs->texture_damage); - pixman_region32_init(&gs->texture_damage); - gs->needs_full_upload = false; + pixman_region32_fini(&gb->texture_damage); + pixman_region32_init(&gb->texture_damage); + gb->needs_full_upload = false; - weston_buffer_reference(&gs->buffer_ref, NULL); + weston_buffer_reference(&gs->buffer_ref, buffer, + BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference(&gs->buffer_release_ref, NULL); } static void -ensure_textures(struct gl_surface_state *gs, GLenum target, int num_textures) +destroy_buffer_state(struct gl_buffer_state *gb) { int i; - if (num_textures <= gs->num_textures) { - glDeleteTextures(gs->num_textures - num_textures, &gs->textures[num_textures]); - gs->num_textures = num_textures; - return; - } + glDeleteTextures(gb->num_textures, gb->textures); + + for (i = 0; i < gb->num_images; i++) + gb->gr->destroy_image(gb->gr->egl_display, gb->images[i]); + + pixman_region32_fini(&gb->texture_damage); + wl_list_remove(&gb->destroy_listener.link); + + free(gb); +} + +static void +handle_buffer_destroy(struct wl_listener *listener, void *data) +{ + struct weston_buffer *buffer = data; + struct gl_buffer_state *gb = + container_of(listener, struct gl_buffer_state, destroy_listener); + + assert(gb == buffer->renderer_private); + buffer->renderer_private = NULL; + + destroy_buffer_state(gb); +} + +static void +ensure_textures(struct gl_buffer_state *gb, GLenum target, int num_textures) +{ + int i; + + assert(gb->num_textures == 0); glActiveTexture(GL_TEXTURE0); - for (i = gs->num_textures; i < num_textures; i++) { - glGenTextures(1, &gs->textures[i]); - glBindTexture(target, gs->textures[i]); + for (i = 0; i < num_textures; i++) { + glGenTextures(1, &gb->textures[i]); + glBindTexture(target, gb->textures[i]); glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } - gs->num_textures = num_textures; + gb->num_textures = num_textures; glBindTexture(target, 0); } -static void -gl_renderer_attach_shm(struct weston_surface *es, struct weston_buffer *buffer, - struct wl_shm_buffer *shm_buffer) +static bool +gl_renderer_attach_shm(struct weston_surface *es, struct weston_buffer *buffer) { struct weston_compositor *ec = es->compositor; struct gl_renderer *gr = get_renderer(ec); struct gl_surface_state *gs = get_surface_state(es); + struct gl_buffer_state *gb; + struct wl_shm_buffer *shm_buffer = buffer->shm_buffer; + struct weston_buffer *old_buffer = gs->buffer_ref.buffer; GLenum gl_format[3] = {0, 0, 0}; GLenum gl_pixel_type; + enum gl_shader_texture_variant shader_variant; int pitch; - int num_planes; + int offset[3] = { 0, 0, 0 }; + unsigned int num_planes; + unsigned int i; bool using_glesv2 = gr->gl_version < gr_gl_version(3, 0); + const struct yuv_format_descriptor *yuv = NULL; + + /* When sampling YUV input textures and converting to RGB by hand, we + * have to bind to each plane separately, with a different format. For + * example, YUYV will have a single wl_shm input plane, but be bound as + * two planes within gl-renderer, one as GR88 and one as ARGB8888. + * + * The yuv_formats array gives us this translation. + */ + for (i = 0; i < ARRAY_LENGTH(yuv_formats); ++i) { + if (yuv_formats[i].format == buffer->pixel_format->format) { + yuv = &yuv_formats[i]; + break; + } + } - buffer->shm_buffer = shm_buffer; - buffer->width = wl_shm_buffer_get_width(shm_buffer); - buffer->height = wl_shm_buffer_get_height(shm_buffer); + if (yuv) { + unsigned int out; + unsigned int shm_plane_count; + int shm_offset[3] = { 0 }; + int bpp = buffer->pixel_format->bpp; - num_planes = 1; - gs->offset[0] = 0; - gs->hsub[0] = 1; - gs->vsub[0] = 1; + /* XXX: Pitch here is given in pixel units, whereas offset is + * given in byte units. This is fragile and will break with + * new formats. + */ + if (!bpp) + bpp = pixel_format_get_info(yuv->plane[0].format)->bpp; + pitch = wl_shm_buffer_get_stride(shm_buffer) / (bpp / 8); - switch (wl_shm_buffer_get_format(shm_buffer)) { - case WL_SHM_FORMAT_XRGB8888: - gs->shader_variant = SHADER_VARIANT_RGBX; - pitch = wl_shm_buffer_get_stride(shm_buffer) / 4; - gl_format[0] = GL_BGRA_EXT; + /* well, they all are so far ... */ gl_pixel_type = GL_UNSIGNED_BYTE; - es->is_opaque = true; - break; - case WL_SHM_FORMAT_ARGB8888: - gs->shader_variant = SHADER_VARIANT_RGBA; - pitch = wl_shm_buffer_get_stride(shm_buffer) / 4; - gl_format[0] = GL_BGRA_EXT; - gl_pixel_type = GL_UNSIGNED_BYTE; - es->is_opaque = false; - break; - case WL_SHM_FORMAT_RGB565: - gs->shader_variant = SHADER_VARIANT_RGBX; - pitch = wl_shm_buffer_get_stride(shm_buffer) / 2; - gl_format[0] = GL_RGB; - gl_pixel_type = GL_UNSIGNED_SHORT_5_6_5; - es->is_opaque = true; - break; -#if __BYTE_ORDER == __LITTLE_ENDIAN - case WL_SHM_FORMAT_ABGR2101010: - if (!gr->has_texture_type_2_10_10_10_rev) { - goto unsupported; - } - gs->shader_variant = SHADER_VARIANT_RGBA; - pitch = wl_shm_buffer_get_stride(shm_buffer) / 4; - gl_format[0] = using_glesv2 ? GL_RGBA : GL_RGB10_A2; - gl_pixel_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT; - es->is_opaque = false; - break; - case WL_SHM_FORMAT_XBGR2101010: - if (!gr->has_texture_type_2_10_10_10_rev) { - goto unsupported; + shader_variant = yuv->shader_variant; + + /* pre-compute all plane offsets in shm buffer */ + shm_plane_count = pixel_format_get_plane_count(buffer->pixel_format); + assert(shm_plane_count <= ARRAY_LENGTH(shm_offset)); + for (i = 1; i < shm_plane_count; i++) { + int hsub, vsub; + + hsub = pixel_format_hsub(buffer->pixel_format, i - 1); + vsub = pixel_format_vsub(buffer->pixel_format, i - 1); + shm_offset[i] = shm_offset[i - 1] + + ((pitch / hsub) * (buffer->height / vsub)); } - gs->shader_variant = SHADER_VARIANT_RGBX; - pitch = wl_shm_buffer_get_stride(shm_buffer) / 4; - gl_format[0] = using_glesv2 ? GL_RGBA : GL_RGB10_A2; - gl_pixel_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT; - es->is_opaque = true; - break; - case WL_SHM_FORMAT_ABGR16161616F: - if (!gr->gl_supports_color_transforms) - goto unsupported; - gs->shader_variant = SHADER_VARIANT_RGBA; - pitch = wl_shm_buffer_get_stride(shm_buffer) / 8; - gl_format[0] = GL_RGBA16F; - gl_pixel_type = GL_HALF_FLOAT; - es->is_opaque = false; - break; - case WL_SHM_FORMAT_XBGR16161616F: - if (!gr->gl_supports_color_transforms) - goto unsupported; - gs->shader_variant = SHADER_VARIANT_RGBX; - pitch = wl_shm_buffer_get_stride(shm_buffer) / 8; - gl_format[0] = GL_RGBA16F; - gl_pixel_type = GL_HALF_FLOAT; - es->is_opaque = true; - break; -#endif - case WL_SHM_FORMAT_YUV420: - gs->shader_variant = SHADER_VARIANT_Y_U_V; - pitch = wl_shm_buffer_get_stride(shm_buffer); - gl_pixel_type = GL_UNSIGNED_BYTE; - num_planes = 3; - gs->offset[1] = gs->offset[0] + (pitch / gs->hsub[0]) * - (buffer->height / gs->vsub[0]); - gs->hsub[1] = 2; - gs->vsub[1] = 2; - gs->offset[2] = gs->offset[1] + (pitch / gs->hsub[1]) * - (buffer->height / gs->vsub[1]); - gs->hsub[2] = 2; - gs->vsub[2] = 2; - if (gr->has_gl_texture_rg) { - gl_format[0] = GL_R8_EXT; - gl_format[1] = GL_R8_EXT; - gl_format[2] = GL_R8_EXT; - } else { - gl_format[0] = GL_LUMINANCE; - gl_format[1] = GL_LUMINANCE; - gl_format[2] = GL_LUMINANCE; - } - es->is_opaque = true; - break; - case WL_SHM_FORMAT_NV12: - pitch = wl_shm_buffer_get_stride(shm_buffer); - gl_pixel_type = GL_UNSIGNED_BYTE; - num_planes = 2; - gs->offset[1] = gs->offset[0] + (pitch / gs->hsub[0]) * - (buffer->height / gs->vsub[0]); - gs->hsub[1] = 2; - gs->vsub[1] = 2; - if (gr->has_gl_texture_rg) { - gs->shader_variant = SHADER_VARIANT_Y_UV; - gl_format[0] = GL_R8_EXT; - gl_format[1] = GL_RG8_EXT; - } else { - gs->shader_variant = SHADER_VARIANT_Y_XUXV; - gl_format[0] = GL_LUMINANCE; - gl_format[1] = GL_LUMINANCE_ALPHA; + + num_planes = yuv->output_planes; + for (out = 0; out < num_planes; out++) { + const struct pixel_format_info *sub_info = + pixel_format_get_info(yuv->plane[out].format); + + assert(sub_info); + assert(yuv->plane[out].plane_index < (int) shm_plane_count); + + gl_format[out] = sub_info->gl_format; + offset[out] = shm_offset[yuv->plane[out].plane_index]; } - es->is_opaque = true; - break; - case WL_SHM_FORMAT_YUYV: - gs->shader_variant = SHADER_VARIANT_Y_XUXV; - pitch = wl_shm_buffer_get_stride(shm_buffer) / 2; - gl_pixel_type = GL_UNSIGNED_BYTE; - num_planes = 2; - gs->offset[1] = 0; - gs->hsub[1] = 2; - gs->vsub[1] = 1; - if (gr->has_gl_texture_rg) - gl_format[0] = GL_RG8_EXT; + } else { + int bpp = buffer->pixel_format->bpp; + + assert(pixel_format_get_plane_count(buffer->pixel_format) == 1); + num_planes = 1; + + if (pixel_format_is_opaque(buffer->pixel_format)) + shader_variant = SHADER_VARIANT_RGBX; else - gl_format[0] = GL_LUMINANCE_ALPHA; - gl_format[1] = GL_BGRA_EXT; - es->is_opaque = true; - break; - case WL_SHM_FORMAT_XYUV8888: - /* - * [31:0] X:Y:Cb:Cr 8:8:8:8 little endian - * a:b: g: r in SHADER_VARIANT_XYUV - */ - gs->shader_variant = SHADER_VARIANT_XYUV; - pitch = wl_shm_buffer_get_stride(shm_buffer) / 4; - gl_format[0] = GL_RGBA; - gl_pixel_type = GL_UNSIGNED_BYTE; - es->is_opaque = true; - break; - default: -unsupported: - weston_log("warning: unknown or unsupported shm buffer format: %08x\n", - wl_shm_buffer_get_format(shm_buffer)); - return; + shader_variant = SHADER_VARIANT_RGBA; + + assert(bpp > 0 && !(bpp & 7)); + pitch = wl_shm_buffer_get_stride(shm_buffer) / (bpp / 8); + + gl_format[0] = buffer->pixel_format->gl_format; + gl_pixel_type = buffer->pixel_format->gl_type; } - /* Only allocate a texture if it doesn't match existing one. - * If a switch from DRM allocated buffer to a SHM buffer is - * happening, we need to allocate a new texture buffer. */ - if (pitch != gs->pitch || - buffer->height != gs->height || - gl_format[0] != gs->gl_format[0] || - gl_format[1] != gs->gl_format[1] || - gl_format[2] != gs->gl_format[2] || - gl_pixel_type != gs->gl_pixel_type || - gs->buffer_type != BUFFER_TYPE_SHM) { - gs->pitch = pitch; - gs->height = buffer->height; - gs->gl_format[0] = gl_format[0]; - gs->gl_format[1] = gl_format[1]; - gs->gl_format[2] = gl_format[2]; - gs->gl_pixel_type = gl_pixel_type; - gs->buffer_type = BUFFER_TYPE_SHM; - gs->needs_full_upload = true; - gs->y_inverted = true; - gs->direct_display = false; + for (i = 0; i < ARRAY_LENGTH(gb->gl_format); i++) { + /* Fall back to GL_RGBA for 10bpc formats on ES2 */ + if (using_glesv2 && gl_format[i] == GL_RGB10_A2) { + assert(gl_pixel_type == GL_UNSIGNED_INT_2_10_10_10_REV_EXT); + gl_format[i] = GL_RGBA; + } - gs->surface = es; + /* Fall back to old luminance-based formats if we don't have + * GL_EXT_texture_rg, which requires different sampling for + * two-component formats. */ + if (!gr->has_gl_texture_rg && gl_format[i] == GL_R8_EXT) { + assert(gl_pixel_type == GL_UNSIGNED_BYTE); + assert(shader_variant == SHADER_VARIANT_Y_U_V || + shader_variant == SHADER_VARIANT_Y_UV); + gl_format[i] = GL_LUMINANCE; + } + if (!gr->has_gl_texture_rg && gl_format[i] == GL_RG8_EXT) { + assert(gl_pixel_type == GL_UNSIGNED_BYTE); + assert(shader_variant == SHADER_VARIANT_Y_UV || + shader_variant == SHADER_VARIANT_Y_XUXV); + shader_variant = SHADER_VARIANT_Y_XUXV; + gl_format[i] = GL_LUMINANCE_ALPHA; + } + } - ensure_textures(gs, GL_TEXTURE_2D, num_planes); + /* If this surface previously had a SHM buffer, its gl_buffer_state will + * be speculatively retained. Check to see if we can reuse it rather + * than allocating a new one. */ + assert(!gs->buffer || + (old_buffer && old_buffer->type == WESTON_BUFFER_SHM)); + if (gs->buffer && + buffer->width == old_buffer->width && + buffer->height == old_buffer->height && + buffer->pixel_format == old_buffer->pixel_format) { + gs->buffer->pitch = pitch; + memcpy(gs->buffer->offset, offset, sizeof(offset)); + return true; } + + if (gs->buffer) + destroy_buffer_state(gs->buffer); + gs->buffer = NULL; + + gb = zalloc(sizeof(*gb)); + if (!gb) + return false; + gb->gr = gr; + + wl_list_init(&gb->destroy_listener.link); + pixman_region32_init(&gb->texture_damage); + + gb->pitch = pitch; + gb->shader_variant = shader_variant; + ARRAY_COPY(gb->offset, offset); + ARRAY_COPY(gb->gl_format, gl_format); + gb->gl_pixel_type = gl_pixel_type; + gb->needs_full_upload = true; + + gs->buffer = gb; + gs->surface = es; + + ensure_textures(gb, GL_TEXTURE_2D, num_planes); + + return true; } -static void -gl_renderer_attach_egl(struct weston_surface *es, struct weston_buffer *buffer, - uint32_t format) +static bool +gl_renderer_fill_buffer_info(struct weston_compositor *ec, + struct weston_buffer *buffer) { - struct weston_compositor *ec = es->compositor; struct gl_renderer *gr = get_renderer(ec); - struct gl_surface_state *gs = get_surface_state(es); - EGLint attribs[5]; + struct gl_buffer_state *gb = zalloc(sizeof(*gb)); + EGLint format; + uint32_t fourcc; GLenum target; - int i, num_planes; + EGLint y_inverted; + bool ret = true; + int i; + + if (!gb) + return false; + + gb->gr = gr; + pixman_region32_init(&gb->texture_damage); buffer->legacy_buffer = (struct wl_buffer *)buffer->resource; - gr->query_buffer(gr->egl_display, buffer->legacy_buffer, - EGL_WIDTH, &buffer->width); - gr->query_buffer(gr->egl_display, buffer->legacy_buffer, - EGL_HEIGHT, &buffer->height); - gr->query_buffer(gr->egl_display, buffer->legacy_buffer, - EGL_WAYLAND_Y_INVERTED_WL, &buffer->y_inverted); - - for (i = 0; i < gs->num_images; i++) { - egl_image_unref(gs->images[i]); - gs->images[i] = NULL; - } - es->is_opaque = false; + ret &= gr->query_buffer(gr->egl_display, buffer->legacy_buffer, + EGL_WIDTH, &buffer->width); + ret &= gr->query_buffer(gr->egl_display, buffer->legacy_buffer, + EGL_HEIGHT, &buffer->height); + ret &= gr->query_buffer(gr->egl_display, buffer->legacy_buffer, + EGL_TEXTURE_FORMAT, &format); + if (!ret) { + weston_log("eglQueryWaylandBufferWL failed\n"); + gl_renderer_print_egl_error_state(); + goto err_free; + } + + /* The legacy EGL buffer interface only describes the channels we can + * sample from; not their depths or order. Take a stab at something + * which might be representative. Pessimise extremely hard for + * TEXTURE_EXTERNAL_OES. */ switch (format) { case EGL_TEXTURE_RGB: - es->is_opaque = true; - /* fallthrough */ + fourcc = DRM_FORMAT_XRGB8888; + gb->num_images = 1; + gb->shader_variant = SHADER_VARIANT_RGBA; + break; case EGL_TEXTURE_RGBA: - default: - num_planes = 1; - gs->shader_variant = SHADER_VARIANT_RGBA; + fourcc = DRM_FORMAT_ARGB8888; + gb->num_images = 1; + gb->shader_variant = SHADER_VARIANT_RGBA; break; case EGL_TEXTURE_EXTERNAL_WL: - num_planes = 1; - gs->shader_variant = SHADER_VARIANT_EXTERNAL; + fourcc = DRM_FORMAT_ARGB8888; + gb->num_images = 1; + gb->shader_variant = SHADER_VARIANT_EXTERNAL; + break; + case EGL_TEXTURE_Y_XUXV_WL: + fourcc = DRM_FORMAT_YUYV; + gb->num_images = 2; + gb->shader_variant = SHADER_VARIANT_Y_XUXV; break; case EGL_TEXTURE_Y_UV_WL: - num_planes = 2; - gs->shader_variant = SHADER_VARIANT_Y_UV; - es->is_opaque = true; + fourcc = DRM_FORMAT_NV12; + gb->num_images = 2; + gb->shader_variant = SHADER_VARIANT_Y_UV; break; case EGL_TEXTURE_Y_U_V_WL: - num_planes = 3; - gs->shader_variant = SHADER_VARIANT_Y_U_V; - es->is_opaque = true; - break; - case EGL_TEXTURE_Y_XUXV_WL: - num_planes = 2; - gs->shader_variant = SHADER_VARIANT_Y_XUXV; - es->is_opaque = true; + fourcc = DRM_FORMAT_YUV420; + gb->num_images = 3; + gb->shader_variant = SHADER_VARIANT_Y_U_V; break; + default: + assert(0 && "not reached"); } - gs->num_images = num_planes; - target = gl_shader_texture_variant_get_target(gs->shader_variant); - ensure_textures(gs, target, num_planes); - for (i = 0; i < num_planes; i++) { - attribs[0] = EGL_WAYLAND_PLANE_WL; - attribs[1] = i; - attribs[2] = EGL_IMAGE_PRESERVED_KHR; - attribs[3] = EGL_TRUE; - attribs[4] = EGL_NONE; + buffer->pixel_format = pixel_format_get_info(fourcc); + assert(buffer->pixel_format); + buffer->format_modifier = DRM_FORMAT_MOD_INVALID; + + /* Assume scanout co-ordinate space i.e. (0,0) is top-left + * if the query fails */ + ret = gr->query_buffer(gr->egl_display, buffer->legacy_buffer, + EGL_WAYLAND_Y_INVERTED_WL, &y_inverted); + if (!ret || y_inverted) + buffer->buffer_origin = ORIGIN_TOP_LEFT; + else + buffer->buffer_origin = ORIGIN_BOTTOM_LEFT; + + for (i = 0; i < gb->num_images; i++) { + const EGLint attribs[] = { + EGL_WAYLAND_PLANE_WL, i, + EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, + EGL_NONE + }; - gs->images[i] = egl_image_create(gr, + gb->images[i] = gr->create_image(gr->egl_display, + EGL_NO_CONTEXT, EGL_WAYLAND_BUFFER_WL, buffer->legacy_buffer, attribs); - if (!gs->images[i]) { - weston_log("failed to create img for plane %d\n", i); - continue; + if (gb->images[i] == EGL_NO_IMAGE_KHR) { + weston_log("couldn't create EGLImage for plane %d\n", i); + goto err_img; } + } + + target = gl_shader_texture_variant_get_target(gb->shader_variant); + ensure_textures(gb, target, gb->num_images); + buffer->renderer_private = gb; + gb->destroy_listener.notify = handle_buffer_destroy; + wl_signal_add(&buffer->destroy_signal, &gb->destroy_listener); + return true; + +err_img: + while (--i >= 0) + gr->destroy_image(gb->gr->egl_display, gb->images[i]); +err_free: + free(gb); + return false; +} + +static bool +gl_renderer_attach_egl(struct weston_surface *es, struct weston_buffer *buffer) +{ + struct weston_compositor *ec = es->compositor; + struct gl_renderer *gr = get_renderer(ec); + struct gl_surface_state *gs = get_surface_state(es); + struct gl_buffer_state *gb = buffer->renderer_private; + GLenum target; + int i; + + assert(gb); + + gs->buffer = gb; + + target = gl_shader_texture_variant_get_target(gb->shader_variant); + for (i = 0; i < gb->num_images; i++) { glActiveTexture(GL_TEXTURE0 + i); - glBindTexture(target, gs->textures[i]); - gr->image_target_texture_2d(target, gs->images[i]->image); + glBindTexture(target, gb->textures[i]); + gr->image_target_texture_2d(target, gb->images[i]); } - gs->pitch = buffer->width; - gs->height = buffer->height; - gs->buffer_type = BUFFER_TYPE_EGL; - gs->y_inverted = buffer->y_inverted; + return true; } static void gl_renderer_destroy_dmabuf(struct linux_dmabuf_buffer *dmabuf) { - struct dmabuf_image *image = linux_dmabuf_buffer_get_user_data(dmabuf); + struct gl_buffer_state *gb = + linux_dmabuf_buffer_get_user_data(dmabuf); - dmabuf_image_destroy(image); + linux_dmabuf_buffer_set_user_data(dmabuf, NULL, NULL); + destroy_buffer_state(gb); } -static struct egl_image * +static EGLImageKHR import_simple_dmabuf(struct gl_renderer *gr, struct dmabuf_attributes *attributes) { - struct egl_image *image; EGLint attribs[52]; int atti = 0; bool has_modifier; @@ -2327,112 +2351,25 @@ import_simple_dmabuf(struct gl_renderer *gr, attribs[atti++] = EGL_NONE; - image = egl_image_create(gr, EGL_LINUX_DMA_BUF_EXT, NULL, - attribs); - - return image; + return gr->create_image(gr->egl_display, EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, NULL, attribs); } -struct yuv_format_descriptor yuv_formats[] = { - { - .format = DRM_FORMAT_YUYV, - .input_planes = 1, - .output_planes = 2, - .texture_type = TEXTURE_Y_XUXV_WL, - {{ - .width_divisor = 1, - .height_divisor = 1, - .format = DRM_FORMAT_GR88, - .plane_index = 0 - }, { - .width_divisor = 2, - .height_divisor = 1, - .format = DRM_FORMAT_ARGB8888, - .plane_index = 0 - }} - }, { - .format = DRM_FORMAT_NV12, - .input_planes = 2, - .output_planes = 2, - .texture_type = TEXTURE_Y_UV_WL, - {{ - .width_divisor = 1, - .height_divisor = 1, - .format = DRM_FORMAT_R8, - .plane_index = 0 - }, { - .width_divisor = 2, - .height_divisor = 2, - .format = DRM_FORMAT_GR88, - .plane_index = 1 - }} - }, { - .format = DRM_FORMAT_YUV420, - .input_planes = 3, - .output_planes = 3, - .texture_type = TEXTURE_Y_U_V_WL, - {{ - .width_divisor = 1, - .height_divisor = 1, - .format = DRM_FORMAT_R8, - .plane_index = 0 - }, { - .width_divisor = 2, - .height_divisor = 2, - .format = DRM_FORMAT_R8, - .plane_index = 1 - }, { - .width_divisor = 2, - .height_divisor = 2, - .format = DRM_FORMAT_R8, - .plane_index = 2 - }} - }, { - .format = DRM_FORMAT_YUV444, - .input_planes = 3, - .output_planes = 3, - .texture_type = TEXTURE_Y_U_V_WL, - {{ - .width_divisor = 1, - .height_divisor = 1, - .format = DRM_FORMAT_R8, - .plane_index = 0 - }, { - .width_divisor = 1, - .height_divisor = 1, - .format = DRM_FORMAT_R8, - .plane_index = 1 - }, { - .width_divisor = 1, - .height_divisor = 1, - .format = DRM_FORMAT_R8, - .plane_index = 2 - }} - }, { - .format = DRM_FORMAT_XYUV8888, - .input_planes = 1, - .output_planes = 1, - .texture_type = TEXTURE_XYUV_WL, - {{ - .width_divisor = 1, - .height_divisor = 1, - .format = DRM_FORMAT_XBGR8888, - .plane_index = 0 - }} - } -}; - -static struct egl_image * +static EGLImageKHR import_dmabuf_single_plane(struct gl_renderer *gr, + const struct pixel_format_info *info, + int idx, const struct dmabuf_attributes *attributes, struct yuv_plane_descriptor *descriptor) { struct dmabuf_attributes plane; - struct egl_image *image; + EGLImageKHR image; char fmt[4]; + int hsub = pixel_format_hsub(info, idx); + int vsub = pixel_format_vsub(info, idx); - plane.width = attributes->width / descriptor->width_divisor; - plane.height = attributes->height / descriptor->height_divisor; + plane.width = attributes->width / hsub; + plane.height = attributes->height / vsub; plane.format = descriptor->format; plane.n_planes = 1; plane.fd[0] = attributes->fd[descriptor->plane_index]; @@ -2441,7 +2378,7 @@ import_dmabuf_single_plane(struct gl_renderer *gr, plane.modifier[0] = attributes->modifier[descriptor->plane_index]; image = import_simple_dmabuf(gr, &plane); - if (!image) { + if (image == EGL_NO_IMAGE_KHR) { weston_log("Failed to import plane %d as %.4s\n", descriptor->plane_index, dump_format(descriptor->format, fmt)); @@ -2452,14 +2389,15 @@ import_dmabuf_single_plane(struct gl_renderer *gr, } static bool -import_yuv_dmabuf(struct gl_renderer *gr, - struct dmabuf_image *image) +import_yuv_dmabuf(struct gl_renderer *gr, struct gl_buffer_state *gb, + struct dmabuf_attributes *attributes) { unsigned i; int j; - int ret; struct yuv_format_descriptor *format = NULL; - struct dmabuf_attributes *attributes = &image->dmabuf->attributes; + const struct pixel_format_info *info; + int plane_count; + GLenum target; char fmt[4]; for (i = 0; i < ARRAY_LENGTH(yuv_formats); ++i) { @@ -2476,45 +2414,37 @@ import_yuv_dmabuf(struct gl_renderer *gr, return false; } - if (attributes->n_planes != format->input_planes) { + info = pixel_format_get_info(attributes->format); + assert(info); + plane_count = pixel_format_get_plane_count(info); + + if (attributes->n_planes != plane_count) { weston_log("%.4s dmabuf must contain %d plane%s (%d provided)\n", dump_format(format->format, fmt), - format->input_planes, - (format->input_planes > 1) ? "s" : "", + plane_count, + (plane_count > 1) ? "s" : "", attributes->n_planes); return false; } for (j = 0; j < format->output_planes; ++j) { - image->images[j] = import_dmabuf_single_plane(gr, attributes, - &format->plane[j]); - if (!image->images[j]) { - while (j) { - ret = egl_image_unref(image->images[--j]); - assert(ret == 0); + gb->images[j] = import_dmabuf_single_plane(gr, info, j, attributes, + &format->plane[j]); + if (gb->images[j] == EGL_NO_IMAGE_KHR) { + while (--j >= 0) { + gr->destroy_image(gb->gr->egl_display, + gb->images[j]); + gb->images[j] = NULL; } return false; } } - image->num_images = format->output_planes; + gb->num_images = format->output_planes; + gb->shader_variant = format->shader_variant; - switch (format->texture_type) { - case TEXTURE_Y_XUXV_WL: - image->shader_variant = SHADER_VARIANT_Y_XUXV; - break; - case TEXTURE_Y_UV_WL: - image->shader_variant = SHADER_VARIANT_Y_UV; - break; - case TEXTURE_Y_U_V_WL: - image->shader_variant = SHADER_VARIANT_Y_U_V; - break; - case TEXTURE_XYUV_WL: - image->shader_variant = SHADER_VARIANT_XYUV; - break; - default: - assert(false); - } + target = gl_shader_texture_variant_get_target(gb->shader_variant); + ensure_textures(gb, target, gb->num_images); return true; } @@ -2580,7 +2510,7 @@ choose_texture_target(struct gl_renderer *gr, for (i = 0; i < format->num_modifiers; ++i) { if (format->modifiers[i] == attributes->modifier[0]) { - if(format->external_only[i]) + if (format->external_only[i]) return GL_TEXTURE_EXTERNAL_OES; else return GL_TEXTURE_2D; @@ -2588,9 +2518,6 @@ choose_texture_target(struct gl_renderer *gr, } } - if (attributes->n_planes > 1) - return GL_TEXTURE_EXTERNAL_OES; - switch (attributes->format & ~DRM_FORMAT_BIG_ENDIAN) { case DRM_FORMAT_YUYV: case DRM_FORMAT_YVYU: @@ -2604,40 +2531,50 @@ choose_texture_target(struct gl_renderer *gr, } } -static struct dmabuf_image * +static struct gl_buffer_state * import_dmabuf(struct gl_renderer *gr, struct linux_dmabuf_buffer *dmabuf) { - struct egl_image *egl_image; - struct dmabuf_image *image; - GLenum target; + EGLImageKHR egl_image; + struct gl_buffer_state *gb; + + if (!pixel_format_get_info(dmabuf->attributes.format)) + return NULL; + + gb = zalloc(sizeof(*gb)); + if (!gb) + return NULL; - image = dmabuf_image_create(); - image->dmabuf = dmabuf; + gb->gr = gr; + pixman_region32_init(&gb->texture_damage); + wl_list_init(&gb->destroy_listener.link); egl_image = import_simple_dmabuf(gr, &dmabuf->attributes); - if (egl_image) { - image->num_images = 1; - image->images[0] = egl_image; - image->import_type = IMPORT_TYPE_DIRECT; - target = choose_texture_target(gr, &dmabuf->attributes); + if (egl_image != EGL_NO_IMAGE_KHR) { + GLenum target = choose_texture_target(gr, &dmabuf->attributes); + + gb->num_images = 1; + gb->images[0] = egl_image; switch (target) { case GL_TEXTURE_2D: - image->shader_variant = SHADER_VARIANT_RGBA; + gb->shader_variant = SHADER_VARIANT_RGBA; break; default: - image->shader_variant = SHADER_VARIANT_EXTERNAL; - } - } else { - if (!import_yuv_dmabuf(gr, image)) { - dmabuf_image_destroy(image); - return NULL; + gb->shader_variant = SHADER_VARIANT_EXTERNAL; } - image->import_type = IMPORT_TYPE_GL_CONVERSION; + + ensure_textures(gb, target, gb->num_images); + + return gb; } - return image; + if (!import_yuv_dmabuf(gr, gb, &dmabuf->attributes)) { + destroy_buffer_state(gb); + return NULL; + } + + return gb; } static void @@ -2746,7 +2683,7 @@ gl_renderer_import_dmabuf(struct weston_compositor *ec, struct linux_dmabuf_buffer *dmabuf) { struct gl_renderer *gr = get_renderer(ec); - struct dmabuf_image *image; + struct gl_buffer_state *gb; int i; assert(gr->has_dmabuf_import); @@ -2767,98 +2704,102 @@ gl_renderer_import_dmabuf(struct weston_compositor *ec, if (dmabuf->attributes.flags & ~ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT) return false; - image = import_dmabuf(gr, dmabuf); - if (!image) + gb = import_dmabuf(gr, dmabuf); + if (!gb) return false; - wl_list_insert(&gr->dmabuf_images, &image->link); - linux_dmabuf_buffer_set_user_data(dmabuf, image, + linux_dmabuf_buffer_set_user_data(dmabuf, gb, gl_renderer_destroy_dmabuf); return true; } -static bool -dmabuf_is_opaque(struct linux_dmabuf_buffer *dmabuf) +static struct gl_buffer_state * +ensure_renderer_gl_buffer_state(struct weston_surface *surface, + struct weston_buffer *buffer) { - const struct pixel_format_info *info; + struct gl_renderer *gr = get_renderer(surface->compositor); + struct gl_surface_state *gs = get_surface_state(surface); + struct gl_buffer_state *gb = buffer->renderer_private; - info = pixel_format_get_info(dmabuf->attributes.format & - ~DRM_FORMAT_BIG_ENDIAN); - if (!info) - return false; + if (gb) { + gs->buffer = gb; + return gb; + } + + gb = zalloc(sizeof(*gb)); + gb->gr = gr; + pixman_region32_init(&gb->texture_damage); + buffer->renderer_private = gb; + gb->destroy_listener.notify = handle_buffer_destroy; + wl_signal_add(&buffer->destroy_signal, &gb->destroy_listener); + + gs->buffer = gb; - return pixel_format_is_opaque(info); + return gb; } static void +attach_direct_display_censor_placeholder(struct weston_surface *surface, + struct weston_buffer *buffer) +{ + struct gl_buffer_state *gb; + + gb = ensure_renderer_gl_buffer_state(surface, buffer); + + /* uses the same color as the content-protection placeholder */ + gb->color[0] = 0.40f; + gb->color[1] = 0.0f; + gb->color[2] = 0.0f; + gb->color[3] = 1.0f; + + gb->shader_variant = SHADER_VARIANT_SOLID; +} + + +static bool gl_renderer_attach_dmabuf(struct weston_surface *surface, - struct weston_buffer *buffer, - struct linux_dmabuf_buffer *dmabuf) + struct weston_buffer *buffer) { struct gl_renderer *gr = get_renderer(surface->compositor); struct gl_surface_state *gs = get_surface_state(surface); - struct dmabuf_image *image; + struct gl_buffer_state *gb; + struct linux_dmabuf_buffer *dmabuf = buffer->dmabuf; GLenum target; int i; - if (!gr->has_dmabuf_import) { - linux_dmabuf_buffer_send_server_error(dmabuf, - "EGL dmabuf import not supported"); - return; + if (buffer->direct_display) { + attach_direct_display_censor_placeholder(surface, buffer); + return true; } - buffer->width = dmabuf->attributes.width; - buffer->height = dmabuf->attributes.height; - - /* - * GL-renderer uses the OpenGL convention of texture coordinates, where - * the origin is at bottom-left. Because dmabuf buffers have the origin - * at top-left, we must invert the Y_INVERT flag to get the image right. - */ - buffer->y_inverted = - !(dmabuf->attributes.flags & ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT); - - for (i = 0; i < gs->num_images; i++) - egl_image_unref(gs->images[i]); - gs->num_images = 0; - - gs->pitch = buffer->width; - gs->height = buffer->height; - gs->buffer_type = BUFFER_TYPE_EGL; - gs->y_inverted = buffer->y_inverted; - gs->direct_display = dmabuf->direct_display; - surface->is_opaque = dmabuf_is_opaque(dmabuf); - - /* - * We try to always hold an imported EGLImage from the dmabuf - * to prevent the client from preventing re-imports. But, we also - * need to re-import every time the contents may change because - * GL driver's caching may need flushing. - * - * Here we release the cache reference which has to be final. - */ - if (dmabuf->direct_display) - return; - - image = linux_dmabuf_buffer_get_user_data(dmabuf); + /* Thanks to linux-dmabuf being totally independent of libweston, + * the first time a dmabuf is attached, the gl_buffer_state will + * only be set as userdata on the dmabuf, not on the weston_buffer. + * When this happens, steal it away into the weston_buffer. */ + if (!buffer->renderer_private) { + gb = linux_dmabuf_buffer_get_user_data(dmabuf); + assert(gb); + linux_dmabuf_buffer_set_user_data(dmabuf, NULL, NULL); + buffer->renderer_private = gb; + gb->destroy_listener.notify = handle_buffer_destroy; + wl_signal_add(&buffer->destroy_signal, &gb->destroy_listener); + } - /* The dmabuf_image should have been created during the import */ - assert(image != NULL); + assert(buffer->renderer_private); + assert(linux_dmabuf_buffer_get_user_data(dmabuf) == NULL); + gb = buffer->renderer_private; - gs->num_images = image->num_images; - for (i = 0; i < gs->num_images; ++i) - gs->images[i] = egl_image_ref(image->images[i]); + gs->buffer = gb; - target = gl_shader_texture_variant_get_target(image->shader_variant); - ensure_textures(gs, target, gs->num_images); - for (i = 0; i < gs->num_images; ++i) { + target = gl_shader_texture_variant_get_target(gb->shader_variant); + for (i = 0; i < gb->num_images; ++i) { glActiveTexture(GL_TEXTURE0 + i); - glBindTexture(target, gs->textures[i]); - gr->image_target_texture_2d(target, gs->images[i]->image); + glBindTexture(target, gb->textures[i]); + gr->image_target_texture_2d(target, gb->images[i]); } - gs->shader_variant = image->shader_variant; + return true; } static const struct weston_drm_format_array * @@ -2887,6 +2828,12 @@ populate_supported_formats(struct weston_compositor *ec, return 0; for (i = 0; i < num_formats; i++) { + const struct pixel_format_info *info = + pixel_format_get_info(formats[i]); + + if (!info || info->hide_from_clients) + continue; + fmt = weston_drm_format_array_add_format(supported_formats, formats[i]); if (!fmt) { @@ -2923,92 +2870,84 @@ out: return ret; } +static bool +gl_renderer_attach_solid(struct weston_surface *surface, + struct weston_buffer *buffer) +{ + struct gl_buffer_state *gb; + + gb = ensure_renderer_gl_buffer_state(surface, buffer); + + gb->color[0] = buffer->solid.r; + gb->color[1] = buffer->solid.g; + gb->color[2] = buffer->solid.b; + gb->color[3] = buffer->solid.a; + + gb->shader_variant = SHADER_VARIANT_SOLID; + + return true; +} + static void gl_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) { - struct weston_compositor *ec = es->compositor; - struct gl_renderer *gr = get_renderer(ec); struct gl_surface_state *gs = get_surface_state(es); - struct wl_shm_buffer *shm_buffer; - struct linux_dmabuf_buffer *dmabuf; - EGLint format; - int i; - - weston_buffer_reference(&gs->buffer_ref, buffer); - weston_buffer_release_reference(&gs->buffer_release_ref, - es->buffer_release_ref.buffer_release); - - if (!buffer) { - for (i = 0; i < gs->num_images; i++) { - egl_image_unref(gs->images[i]); - gs->images[i] = NULL; + bool ret = false; + + /* SHM buffers are a little special in that they are allocated + * per-surface rather than per-buffer, because we keep a shadow + * copy of the SHM data in a GL texture; for these we need to + * destroy the buffer state when we're switching to another + * buffer type. For all the others, the gl_buffer_state comes + * from the weston_buffer itself, and will only be destroyed + * along with it. */ + if (gs->buffer && gs->buffer_ref.buffer->type == WESTON_BUFFER_SHM) { + if (!buffer || buffer->type != WESTON_BUFFER_SHM) { + destroy_buffer_state(gs->buffer); + gs->buffer = NULL; } - gs->num_images = 0; - glDeleteTextures(gs->num_textures, gs->textures); - gs->num_textures = 0; - gs->buffer_type = BUFFER_TYPE_NULL; - gs->y_inverted = true; - gs->direct_display = false; - es->is_opaque = false; - return; + } else { + gs->buffer = NULL; } - shm_buffer = wl_shm_buffer_get(buffer->resource); + if (!buffer) + goto out; - if (shm_buffer) - gl_renderer_attach_shm(es, buffer, shm_buffer); - else if (gr->has_bind_display && - gr->query_buffer(gr->egl_display, (void *)buffer->resource, - EGL_TEXTURE_FORMAT, &format)) - gl_renderer_attach_egl(es, buffer, format); - else if ((dmabuf = linux_dmabuf_buffer_get(buffer->resource))) - gl_renderer_attach_dmabuf(es, buffer, dmabuf); - else { + switch (buffer->type) { + case WESTON_BUFFER_SHM: + ret = gl_renderer_attach_shm(es, buffer); + break; + case WESTON_BUFFER_DMABUF: + ret = gl_renderer_attach_dmabuf(es, buffer); + break; + case WESTON_BUFFER_RENDERER_OPAQUE: + ret = gl_renderer_attach_egl(es, buffer); + break; + case WESTON_BUFFER_SOLID: + ret = gl_renderer_attach_solid(es, buffer); + break; + default: + break; + } + + if (!ret) { weston_log("unhandled buffer type!\n"); - if (gr->has_bind_display) { - weston_log("eglQueryWaylandBufferWL failed\n"); - gl_renderer_print_egl_error_state(); - } - weston_buffer_reference(&gs->buffer_ref, NULL); - weston_buffer_release_reference(&gs->buffer_release_ref, NULL); - gs->buffer_type = BUFFER_TYPE_NULL; - gs->y_inverted = true; - es->is_opaque = false; weston_buffer_send_server_error(buffer, "disconnecting due to unhandled buffer type"); + goto out; } -} - -static void -gl_renderer_surface_set_color(struct weston_surface *surface, - float red, float green, float blue, float alpha) -{ - struct gl_surface_state *gs = get_surface_state(surface); - - gs->color[0] = red; - gs->color[1] = green; - gs->color[2] = blue; - gs->color[3] = alpha; - gs->buffer_type = BUFFER_TYPE_SOLID; - gs->pitch = 1; - gs->height = 1; - gs->shader_variant = SHADER_VARIANT_SOLID; -} - -static void -gl_renderer_surface_get_content_size(struct weston_surface *surface, - int *width, int *height) -{ - struct gl_surface_state *gs = get_surface_state(surface); + weston_buffer_reference(&gs->buffer_ref, buffer, + BUFFER_MAY_BE_ACCESSED); + weston_buffer_release_reference(&gs->buffer_release_ref, + es->buffer_release_ref.buffer_release); + return; - if (gs->buffer_type == BUFFER_TYPE_NULL) { - *width = 0; - *height = 0; - } else { - *width = gs->pitch; - *height = gs->height; - } +out: + assert(!gs->buffer); + weston_buffer_reference(&gs->buffer_ref, NULL, + BUFFER_WILL_NOT_BE_ACCESSED); + weston_buffer_release_reference(&gs->buffer_release_ref, NULL); } static uint32_t @@ -3061,24 +3000,28 @@ gl_renderer_surface_copy_content(struct weston_surface *surface, const GLenum gl_format = GL_RGBA; /* PIXMAN_a8b8g8r8 little-endian */ struct gl_renderer *gr = get_renderer(surface->compositor); struct gl_surface_state *gs = get_surface_state(surface); + struct gl_buffer_state *gb = gs->buffer; + struct weston_buffer *buffer = gs->buffer_ref.buffer; int cw, ch; GLuint fbo; GLuint tex; GLenum status; int ret = -1; - gl_renderer_surface_get_content_size(surface, &cw, &ch); + assert(buffer); - switch (gs->buffer_type) { - case BUFFER_TYPE_NULL: - return -1; - case BUFFER_TYPE_SOLID: - *(uint32_t *)target = pack_color(format, gs->color); + cw = buffer->width; + ch = buffer->height; + + switch (buffer->type) { + case WESTON_BUFFER_SOLID: + *(uint32_t *)target = pack_color(format, gb->color); return 0; - case BUFFER_TYPE_SHM: - gl_renderer_flush_damage(surface); + case WESTON_BUFFER_SHM: + gl_renderer_flush_damage(surface, buffer); /* fall through */ - case BUFFER_TYPE_EGL: + case WESTON_BUFFER_DMABUF: + case WESTON_BUFFER_RENDERER_OPAQUE: break; } @@ -3104,7 +3047,7 @@ gl_renderer_surface_copy_content(struct weston_surface *surface, glViewport(0, 0, cw, ch); glDisable(GL_BLEND); - if (gs->y_inverted) + if (buffer->buffer_origin == ORIGIN_TOP_LEFT) ARRAY_COPY(sconf.projection.d, projmat_normal); else ARRAY_COPY(sconf.projection.d, projmat_yinvert); @@ -3142,21 +3085,19 @@ out: static void surface_state_destroy(struct gl_surface_state *gs, struct gl_renderer *gr) { - int i; - wl_list_remove(&gs->surface_destroy_listener.link); wl_list_remove(&gs->renderer_destroy_listener.link); gs->surface->renderer_state = NULL; - glDeleteTextures(gs->num_textures, gs->textures); - - for (i = 0; i < gs->num_images; i++) - egl_image_unref(gs->images[i]); + if (gs->buffer && gs->buffer_ref.buffer->type == WESTON_BUFFER_SHM) + destroy_buffer_state(gs->buffer); + gs->buffer = NULL; - weston_buffer_reference(&gs->buffer_ref, NULL); + weston_buffer_reference(&gs->buffer_ref, NULL, + BUFFER_WILL_NOT_BE_ACCESSED); weston_buffer_release_reference(&gs->buffer_release_ref, NULL); - pixman_region32_fini(&gs->texture_damage); + free(gs); } @@ -3202,13 +3143,8 @@ gl_renderer_create_surface(struct weston_surface *surface) * they still go through texcoord computations. Do not divide * by zero there. */ - gs->pitch = 1; - gs->y_inverted = true; - gs->direct_display = false; - gs->surface = surface; - pixman_region32_init(&gs->texture_damage); surface->renderer_state = gs; gs->surface_destroy_listener.notify = @@ -3223,37 +3159,46 @@ gl_renderer_create_surface(struct weston_surface *surface) if (surface->buffer_ref.buffer) { gl_renderer_attach(surface, surface->buffer_ref.buffer); - gl_renderer_flush_damage(surface); + if (surface->buffer_ref.buffer->type == WESTON_BUFFER_SHM) { + gl_renderer_flush_damage(surface, + surface->buffer_ref.buffer); + } } return 0; } void -gl_renderer_log_extensions(const char *name, const char *extensions) +gl_renderer_log_extensions(struct gl_renderer *gr, + const char *name, const char *extensions) { const char *p, *end; int l; int len; - l = weston_log("%s:", name); + if (!weston_log_scope_is_enabled(gr->renderer_scope)) + return; + + l = weston_log_scope_printf(gr->renderer_scope, "%s:", name); p = extensions; while (*p) { end = strchrnul(p, ' '); len = end - p; - if (l + len > 78) - l = weston_log_continue("\n" STAMP_SPACE "%.*s", - len, p); - else - l += weston_log_continue(" %.*s", len, p); + if (l + len > 78) { + l = weston_log_scope_printf(gr->renderer_scope, + "\n %.*s", len, p); + } else { + l += weston_log_scope_printf(gr->renderer_scope, + " %.*s", len, p); + } for (p = end; isspace(*p); p++) ; } - weston_log_continue("\n"); + weston_log_scope_printf(gr->renderer_scope, "\n"); } static void -log_egl_info(EGLDisplay egldpy) +log_egl_info(struct gl_renderer *gr, EGLDisplay egldpy) { const char *str; @@ -3267,11 +3212,11 @@ log_egl_info(EGLDisplay egldpy) weston_log("EGL client APIs: %s\n", str ? str : "(null)"); str = eglQueryString(egldpy, EGL_EXTENSIONS); - gl_renderer_log_extensions("EGL extensions", str ? str : "(null)"); + gl_renderer_log_extensions(gr, "EGL extensions", str ? str : "(null)"); } static void -log_gl_info(void) +log_gl_info(struct gl_renderer *gr) { const char *str; @@ -3288,7 +3233,7 @@ log_gl_info(void) weston_log("GL renderer: %s\n", str ? str : "(null)"); str = (char *)glGetString(GL_EXTENSIONS); - gl_renderer_log_extensions("GL extensions", str ? str : "(null)"); + gl_renderer_log_extensions(gr, "GL extensions", str ? str : "(null)"); } static void @@ -3376,7 +3321,7 @@ gl_renderer_output_create(struct weston_output *output, go->begin_render_sync = EGL_NO_SYNC_KHR; go->end_render_sync = EGL_NO_SYNC_KHR; - if ((output->from_blend_to_output != NULL && + if ((output->color_outcome->from_blend_to_output != NULL && output->from_blend_to_output_by_backend == false) || quirks->gl_force_full_redraw_of_shadow_fb) { assert(gr->gl_supports_color_transforms); @@ -3536,7 +3481,6 @@ static void gl_renderer_destroy(struct weston_compositor *ec) { struct gl_renderer *gr = get_renderer(ec); - struct dmabuf_image *image, *next; struct dmabuf_format *format, *next_format; wl_signal_emit(&gr->destroy_signal, gr); @@ -3553,9 +3497,6 @@ gl_renderer_destroy(struct weston_compositor *ec) EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - wl_list_for_each_safe(image, next, &gr->dmabuf_images, link) - dmabuf_image_destroy(image); - wl_list_for_each_safe(format, next_format, &gr->dmabuf_formats, link) dmabuf_format_destroy(format); @@ -3577,6 +3518,7 @@ gl_renderer_destroy(struct weston_compositor *ec) weston_binding_destroy(gr->fan_binding); weston_log_scope_destroy(gr->shader_scope); + weston_log_scope_destroy(gr->renderer_scope); free(gr); } @@ -3659,6 +3601,11 @@ gl_renderer_display_create(struct weston_compositor *ec, wl_list_init(&gr->shader_list); gr->platform = options->egl_platform; + gr->renderer_scope = weston_compositor_add_log_scope(ec, "gl-renderer", + "GL-renderer verbose messages\n", NULL, NULL, gr); + if (!gr->renderer_scope) + goto fail; + gr->shader_scope = gl_shader_scope_create(gr); if (!gr->shader_scope) goto fail; @@ -3670,18 +3617,16 @@ gl_renderer_display_create(struct weston_compositor *ec, gr->base.repaint_output = gl_renderer_repaint_output; gr->base.flush_damage = gl_renderer_flush_damage; gr->base.attach = gl_renderer_attach; - gr->base.surface_set_color = gl_renderer_surface_set_color; gr->base.destroy = gl_renderer_destroy; - gr->base.surface_get_content_size = - gl_renderer_surface_get_content_size; gr->base.surface_copy_content = gl_renderer_surface_copy_content; + gr->base.fill_buffer_info = gl_renderer_fill_buffer_info; if (gl_renderer_setup_egl_display(gr, options->egl_native_display) < 0) goto fail; weston_drm_format_array_init(&gr->supported_formats); - log_egl_info(gr->egl_display); + log_egl_info(gr, gr->egl_display); ec->renderer = &gr->base; @@ -3711,7 +3656,6 @@ gl_renderer_display_create(struct weston_compositor *ec, if (gr->has_native_fence_sync && gr->has_wait_sync) ec->capabilities |= WESTON_CAP_EXPLICIT_SYNC; - wl_list_init(&gr->dmabuf_images); if (gr->has_dmabuf_import) { gr->base.import_dmabuf = gl_renderer_import_dmabuf; gr->base.get_supported_formats = gl_renderer_get_supported_formats; @@ -3733,12 +3677,8 @@ gl_renderer_display_create(struct weston_compositor *ec, wl_list_init(&gr->dmabuf_formats); if (gr->has_surfaceless_context) { - weston_log("EGL_KHR_surfaceless_context available\n"); gr->dummy_surface = EGL_NO_SURFACE; } else { - weston_log("EGL_KHR_surfaceless_context unavailable. " - "Trying PbufferSurface\n"); - if (gl_renderer_create_pbuffer_surface(gr) < 0) goto fail_with_error; } @@ -3754,6 +3694,7 @@ gl_renderer_display_create(struct weston_compositor *ec, wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_RGB565); wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_YUV420); + wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_YUV444); wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_NV12); wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_YUYV); wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_XYUV8888); @@ -3766,6 +3707,10 @@ gl_renderer_display_create(struct weston_compositor *ec, wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_ABGR16161616F); wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_XBGR16161616F); } + if (gr->has_texture_norm16) { + wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_ABGR16161616); + wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_XBGR16161616); + } #endif if (gr->gl_supports_color_transforms) @@ -3789,6 +3734,7 @@ fail_terminate: eglTerminate(gr->egl_display); fail: weston_log_scope_destroy(gr->shader_scope); + weston_log_scope_destroy(gr->renderer_scope); free(gr); ec->renderer = NULL; return -1; @@ -3911,7 +3857,7 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) } gr->gl_version = get_gl_version(); - log_gl_info(); + log_gl_info(gr); gr->image_target_texture_2d = (void *) eglGetProcAddress("glEGLImageTargetTexture2DOES"); @@ -3928,9 +3874,9 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) } if (weston_check_egl_extension(extensions, "GL_EXT_read_format_bgra")) - ec->read_format = PIXMAN_a8r8g8b8; + ec->read_format = pixel_format_get_info_by_pixman(PIXMAN_a8r8g8b8); else - ec->read_format = PIXMAN_a8b8g8r8; + ec->read_format = pixel_format_get_info_by_pixman(PIXMAN_a8b8g8r8); if (gr->gl_version < gr_gl_version(3, 0) && !weston_check_egl_extension(extensions, "GL_EXT_unpack_subimage")) { @@ -3942,6 +3888,9 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) weston_check_egl_extension(extensions, "GL_EXT_texture_type_2_10_10_10_REV")) gr->has_texture_type_2_10_10_10_rev = true; + if (weston_check_egl_extension(extensions, "GL_EXT_texture_norm16")) + gr->has_texture_norm16 = true; + if (gr->gl_version >= gr_gl_version(3, 0) || weston_check_egl_extension(extensions, "GL_EXT_texture_rg")) gr->has_gl_texture_rg = true; @@ -3951,7 +3900,8 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) if (gr->gl_version >= gr_gl_version(3, 0) && weston_check_egl_extension(extensions, "GL_OES_texture_float_linear") && - weston_check_egl_extension(extensions, "GL_EXT_color_buffer_half_float")) { + weston_check_egl_extension(extensions, "GL_EXT_color_buffer_half_float") && + weston_check_egl_extension(extensions, "GL_OES_texture_3D")) { gr->gl_supports_color_transforms = true; } @@ -3976,9 +3926,17 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) gr_gl_version_major(gr->gl_version), gr_gl_version_minor(gr->gl_version)); weston_log_continue(STAMP_SPACE "read-back format: %s\n", - ec->read_format == PIXMAN_a8r8g8b8 ? "BGRA" : "RGBA"); - weston_log_continue(STAMP_SPACE "EGL Wayland extension: %s\n", - gr->has_bind_display ? "yes" : "no"); + ec->read_format->drm_format_name); + weston_log_continue(STAMP_SPACE "wl_shm 10 bpc formats: %s\n", + yesno(gr->has_texture_type_2_10_10_10_rev)); + weston_log_continue(STAMP_SPACE "wl_shm 16 bpc formats: %s\n", + yesno(gr->has_texture_norm16)); + weston_log_continue(STAMP_SPACE "wl_shm half-float formats: %s\n", + yesno(gr->gl_supports_color_transforms)); + weston_log_continue(STAMP_SPACE "internal R and RG formats: %s\n", + yesno(gr->has_gl_texture_rg)); + weston_log_continue(STAMP_SPACE "OES_EGL_image_external: %s\n", + yesno(gr->has_egl_image_external)); return 0; } diff --git a/libweston/renderer-gl/gl-shader-config-color-transformation.c b/libweston/renderer-gl/gl-shader-config-color-transformation.c index 21a45653..7f858dd1 100644 --- a/libweston/renderer-gl/gl-shader-config-color-transformation.c +++ b/libweston/renderer-gl/gl-shader-config-color-transformation.c @@ -1,5 +1,6 @@ /* * Copyright 2021 Collabora, Ltd. + * Copyright 2021 Advanced Micro Devices, Inc. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -44,11 +45,22 @@ struct gl_renderer_color_curve { float offset; }; +struct gl_renderer_color_mapping { + enum gl_shader_color_mapping type; + union { + struct { + GLuint tex3d; + float scale; + float offset; + } lut3d; + }; +} ; + struct gl_renderer_color_transform { struct weston_color_transform *owner; struct wl_listener destroy_listener; - struct gl_renderer_color_curve pre_curve; + struct gl_renderer_color_mapping mapping; }; static void @@ -58,10 +70,19 @@ gl_renderer_color_curve_fini(struct gl_renderer_color_curve *gl_curve) glDeleteTextures(1, &gl_curve->tex); } +static void +gl_renderer_color_mapping_fini(struct gl_renderer_color_mapping *gl_mapping) +{ + if (gl_mapping->type == SHADER_COLOR_MAPPING_3DLUT && + gl_mapping->lut3d.tex3d) + glDeleteTextures(1, &gl_mapping->lut3d.tex3d); +} + static void gl_renderer_color_transform_destroy(struct gl_renderer_color_transform *gl_xform) { gl_renderer_color_curve_fini(&gl_xform->pre_curve); + gl_renderer_color_mapping_fini(&gl_xform->mapping); wl_list_remove(&gl_xform->destroy_listener.link); free(gl_xform); } @@ -152,6 +173,47 @@ gl_color_curve_lut_3x1d(struct gl_renderer_color_curve *gl_curve, return true; } +static bool +gl_3d_lut(struct gl_renderer_color_transform *gl_xform, + struct weston_color_transform *xform) +{ + + GLuint tex3d; + float *lut; + const unsigned dim_size = xform->mapping.u.lut3d.optimal_len; + + lut = calloc(3 * dim_size * dim_size * dim_size, sizeof *lut); + if (!lut) + return false; + + xform->mapping.u.lut3d.fill_in(xform, lut, dim_size); + + glActiveTexture(GL_TEXTURE0); + glGenTextures(1, &tex3d); + glBindTexture(GL_TEXTURE_3D, tex3d); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0); + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); + glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB32F, dim_size, dim_size, dim_size, 0, + GL_RGB, GL_FLOAT, lut); + + glBindTexture(GL_TEXTURE_3D, 0); + gl_xform->mapping.type = SHADER_COLOR_MAPPING_3DLUT; + gl_xform->mapping.lut3d.tex3d = tex3d; + gl_xform->mapping.lut3d.scale = (float)(dim_size - 1) / dim_size; + gl_xform->mapping.lut3d.offset = 0.5f / dim_size; + + free(lut); + + return true; +} + + static const struct gl_renderer_color_transform * gl_renderer_color_transform_from(struct weston_color_transform *xform) { @@ -160,6 +222,7 @@ gl_renderer_color_transform_from(struct weston_color_transform *xform) .pre_curve.tex = 0, .pre_curve.scale = 0.0f, .pre_curve.offset = 0.0f, + .mapping.type = SHADER_COLOR_MAPPING_IDENTITY, }; struct gl_renderer_color_transform *gl_xform; bool ok = false; @@ -190,6 +253,19 @@ gl_renderer_color_transform_from(struct weston_color_transform *xform) break; } + if (!ok) { + gl_renderer_color_transform_destroy(gl_xform); + return NULL; + } + switch (xform->mapping.type) { + case WESTON_COLOR_MAPPING_TYPE_IDENTITY: + gl_xform->mapping = no_op_gl_xform.mapping; + ok = true; + break; + case WESTON_COLOR_MAPPING_TYPE_3D_LUT: + ok = gl_3d_lut(gl_xform, xform); + break; + } if (!ok) { gl_renderer_color_transform_destroy(gl_xform); return NULL; @@ -203,6 +279,7 @@ gl_shader_config_set_color_transform(struct gl_shader_config *sconf, struct weston_color_transform *xform) { const struct gl_renderer_color_transform *gl_xform; + bool ret = false; gl_xform = gl_renderer_color_transform_from(xform); if (!gl_xform) @@ -213,5 +290,22 @@ gl_shader_config_set_color_transform(struct gl_shader_config *sconf, sconf->color_pre_curve_lut_scale_offset[0] = gl_xform->pre_curve.scale; sconf->color_pre_curve_lut_scale_offset[1] = gl_xform->pre_curve.offset; - return true; + sconf->req.color_mapping = gl_xform->mapping.type; + switch (gl_xform->mapping.type) { + case SHADER_COLOR_MAPPING_3DLUT: + sconf->color_mapping.lut3d.tex = gl_xform->mapping.lut3d.tex3d; + sconf->color_mapping.lut3d.scale_offset[0] = + gl_xform->mapping.lut3d.scale; + sconf->color_mapping.lut3d.scale_offset[1] = + gl_xform->mapping.lut3d.offset; + assert(sconf->color_mapping.lut3d.scale_offset[0] > 0.0); + assert(sconf->color_mapping.lut3d.scale_offset[1] > 0.0); + ret = true; + break; + case SHADER_COLOR_MAPPING_IDENTITY: + ret = true; + break; + } + + return ret; } diff --git a/libweston/renderer-gl/gl-shaders.c b/libweston/renderer-gl/gl-shaders.c index 97f288c0..66850e71 100644 --- a/libweston/renderer-gl/gl-shaders.c +++ b/libweston/renderer-gl/gl-shaders.c @@ -4,6 +4,7 @@ * Copyright 2016 NVIDIA Corporation * Copyright 2019 Harish Krupo * Copyright 2019 Intel Corporation + * Copyright 2021 Advanced Micro Devices, Inc. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -56,10 +57,16 @@ struct gl_shader { GLuint vertex_shader, fragment_shader; GLint proj_uniform; GLint tex_uniforms[3]; - GLint alpha_uniform; + GLint view_alpha_uniform; GLint color_uniform; GLint color_pre_curve_lut_2d_uniform; GLint color_pre_curve_lut_scale_offset_uniform; + union { + struct { + GLint tex_uniform; + GLint scale_offset_uniform; + } lut3d; + } color_mapping; struct wl_list link; /* gl_renderer::shader_list */ struct timespec last_used; }; @@ -97,6 +104,19 @@ gl_shader_color_curve_to_string(enum gl_shader_color_curve kind) return "!?!?"; /* never reached */ } +static const char * +gl_shader_color_mapping_to_string(enum gl_shader_color_mapping kind) +{ + switch (kind) { +#define CASERET(x) case x: return #x; + CASERET(SHADER_COLOR_MAPPING_IDENTITY) + CASERET(SHADER_COLOR_MAPPING_3DLUT) +#undef CASERET + } + + return "!?!?"; /* never reached */ +} + static void dump_program_with_line_numbers(int count, const char **sources) { @@ -162,9 +182,10 @@ create_shader_description_string(const struct gl_shader_requirements *req) int size; char *str; - size = asprintf(&str, "%s %s %cinput_is_premult %cgreen", + size = asprintf(&str, "%s %s %s %cinput_is_premult %cgreen", gl_shader_texture_variant_to_string(req->variant), gl_shader_color_curve_to_string(req->color_pre_curve), + gl_shader_color_mapping_to_string(req->color_mapping), req->input_is_premult ? '+' : '-', req->green_tint ? '+' : '-'); if (size < 0) @@ -182,10 +203,12 @@ create_shader_config_string(const struct gl_shader_requirements *req) "#define DEF_GREEN_TINT %s\n" "#define DEF_INPUT_IS_PREMULT %s\n" "#define DEF_COLOR_PRE_CURVE %s\n" + "#define DEF_COLOR_MAPPING %s\n" "#define DEF_VARIANT %s\n", req->green_tint ? "true" : "false", req->input_is_premult ? "true" : "false", gl_shader_color_curve_to_string(req->color_pre_curve), + gl_shader_color_mapping_to_string(req->color_mapping), gl_shader_texture_variant_to_string(req->variant)); if (size < 0) return NULL; @@ -260,7 +283,7 @@ gl_shader_create(struct gl_renderer *gr, shader->tex_uniforms[0] = glGetUniformLocation(shader->program, "tex"); shader->tex_uniforms[1] = glGetUniformLocation(shader->program, "tex1"); shader->tex_uniforms[2] = glGetUniformLocation(shader->program, "tex2"); - shader->alpha_uniform = glGetUniformLocation(shader->program, "alpha"); + shader->view_alpha_uniform = glGetUniformLocation(shader->program, "view_alpha"); shader->color_uniform = glGetUniformLocation(shader->program, "unicolor"); shader->color_pre_curve_lut_2d_uniform = @@ -268,6 +291,16 @@ gl_shader_create(struct gl_renderer *gr, shader->color_pre_curve_lut_scale_offset_uniform = glGetUniformLocation(shader->program, "color_pre_curve_lut_scale_offset"); + switch(requirements->color_mapping) { + case SHADER_COLOR_MAPPING_3DLUT: + shader->color_mapping.lut3d.tex_uniform = + glGetUniformLocation(shader->program, "color_mapping_lut_3d"); + shader->color_mapping.lut3d.scale_offset_uniform = + glGetUniformLocation(shader->program,"color_mapping_lut_scale_offset"); + break; + case SHADER_COLOR_MAPPING_IDENTITY: + break; + } free(conf); wl_list_insert(&gr->shader_list, &shader->link); @@ -376,6 +409,7 @@ gl_renderer_create_fallback_shader(struct gl_renderer *gr) .variant = SHADER_VARIANT_SOLID, .input_is_premult = true, .color_pre_curve = SHADER_COLOR_CURVE_IDENTITY, + .color_mapping = SHADER_COLOR_MAPPING_IDENTITY, }; struct gl_shader *shader; @@ -481,7 +515,7 @@ gl_shader_load_config(struct gl_shader *shader, glUniformMatrix4fv(shader->proj_uniform, 1, GL_FALSE, sconf->projection.d); glUniform4fv(shader->color_uniform, 1, sconf->unicolor); - glUniform1f(shader->alpha_uniform, sconf->view_alpha); + glUniform1f(shader->view_alpha_uniform, sconf->view_alpha); in_tgt = gl_shader_texture_variant_get_target(sconf->req.variant); for (i = 0; i < GL_SHADER_INPUT_TEX_MAX; i++) { @@ -497,9 +531,8 @@ gl_shader_load_config(struct gl_shader *shader, glTexParameteri(in_tgt, GL_TEXTURE_MAG_FILTER, in_filter); } - /* Fixed texture unit for color_pre_curve LUT */ + /* Fixed texture unit for color_pre_curve LUT if it is available */ i = GL_SHADER_INPUT_TEX_MAX; - glActiveTexture(GL_TEXTURE0 + i); switch (sconf->req.color_pre_curve) { case SHADER_COLOR_CURVE_IDENTITY: assert(sconf->color_pre_curve_lut_tex == 0); @@ -508,13 +541,32 @@ gl_shader_load_config(struct gl_shader *shader, assert(sconf->color_pre_curve_lut_tex != 0); assert(shader->color_pre_curve_lut_2d_uniform != -1); assert(shader->color_pre_curve_lut_scale_offset_uniform != -1); - + glActiveTexture(GL_TEXTURE0 + i); glBindTexture(GL_TEXTURE_2D, sconf->color_pre_curve_lut_tex); glUniform1i(shader->color_pre_curve_lut_2d_uniform, i); + i++; glUniform2fv(shader->color_pre_curve_lut_scale_offset_uniform, 1, sconf->color_pre_curve_lut_scale_offset); break; } + + switch (sconf->req.color_mapping) { + case SHADER_COLOR_MAPPING_IDENTITY: + break; + case SHADER_COLOR_MAPPING_3DLUT: + assert(shader->color_mapping.lut3d.tex_uniform != -1); + assert(sconf->color_mapping.lut3d.tex != 0); + assert(shader->color_mapping.lut3d.scale_offset_uniform != -1); + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_3D, sconf->color_mapping.lut3d.tex); + glUniform1i(shader->color_mapping.lut3d.tex_uniform, i); + glUniform2fv(shader->color_mapping.lut3d.scale_offset_uniform, + 1, sconf->color_mapping.lut3d.scale_offset); + break; + default: + assert(false); + break; + } } bool @@ -540,7 +592,7 @@ gl_renderer_use_program(struct gl_renderer *gr, shader = gr->fallback_shader; glUseProgram(shader->program); glUniform4fv(shader->color_uniform, 1, fallback_shader_color); - glUniform1f(shader->alpha_uniform, 1.0f); + glUniform1f(shader->view_alpha_uniform, 1.0f); return false; } diff --git a/libweston/screenshooter.c b/libweston/screenshooter.c index fedc4805..aaa8de5c 100644 --- a/libweston/screenshooter.c +++ b/libweston/screenshooter.c @@ -40,11 +40,13 @@ #include "shared/timespec-util.h" #include "backend.h" #include "libweston-internal.h" +#include "pixel-formats.h" #include "wcap/wcap-decode.h" struct screenshooter_frame_listener { - struct wl_listener listener; + struct wl_listener frame_listener; + struct wl_listener buffer_destroy_listener; struct weston_buffer *buffer; struct weston_output *output; weston_screenshooter_done_func_t done; @@ -119,15 +121,20 @@ screenshooter_frame_notify(struct wl_listener *listener, void *data) { struct screenshooter_frame_listener *l = container_of(listener, - struct screenshooter_frame_listener, listener); + struct screenshooter_frame_listener, + frame_listener); struct weston_output *output = l->output; struct weston_compositor *compositor = output->compositor; + const pixman_format_code_t pixman_format = + compositor->read_format->pixman_format; int32_t stride; uint8_t *pixels, *d, *s; weston_output_disable_planes_decr(output); wl_list_remove(&listener->link); - stride = l->buffer->width * (PIXMAN_FORMAT_BPP(compositor->read_format) / 8); + wl_list_remove(&l->buffer_destroy_listener.link); + + stride = l->buffer->width * (PIXMAN_FORMAT_BPP(pixman_format) / 8); pixels = malloc(stride * l->buffer->height); if (pixels == NULL) { @@ -148,7 +155,7 @@ screenshooter_frame_notify(struct wl_listener *listener, void *data) wl_shm_buffer_begin_access(l->buffer->shm_buffer); - switch (compositor->read_format) { + switch (pixman_format) { case PIXMAN_a8r8g8b8: case PIXMAN_x8r8g8b8: if (compositor->capabilities & WESTON_CAP_CAPTURE_YFLIP) @@ -174,6 +181,22 @@ screenshooter_frame_notify(struct wl_listener *listener, void *data) free(l); } +static void +buffer_destroy_handle(struct wl_listener *listener, void *data) +{ + struct screenshooter_frame_listener *l = + container_of(listener, + struct screenshooter_frame_listener, + buffer_destroy_listener); + + weston_output_disable_planes_decr(l->output); + wl_list_remove(&listener->link); + wl_list_remove(&l->frame_listener.link); + l->done(l->data, WESTON_SCREENSHOOTER_BAD_BUFFER); + + free(l); +} + WL_EXPORT int weston_screenshooter_shoot(struct weston_output *output, struct weston_buffer *buffer, @@ -181,15 +204,11 @@ weston_screenshooter_shoot(struct weston_output *output, { struct screenshooter_frame_listener *l; - if (!wl_shm_buffer_get(buffer->resource)) { + if (buffer->type != WESTON_BUFFER_SHM) { done(data, WESTON_SCREENSHOOTER_BAD_BUFFER); return -1; } - buffer->shm_buffer = wl_shm_buffer_get(buffer->resource); - buffer->width = wl_shm_buffer_get_width(buffer->shm_buffer); - buffer->height = wl_shm_buffer_get_height(buffer->shm_buffer); - if (buffer->width < output->current_mode->width || buffer->height < output->current_mode->height) { done(data, WESTON_SCREENSHOOTER_BAD_BUFFER); @@ -206,8 +225,13 @@ weston_screenshooter_shoot(struct weston_output *output, l->output = output; l->done = done; l->data = data; - l->listener.notify = screenshooter_frame_notify; - wl_signal_add(&output->frame_signal, &l->listener); + + l->frame_listener.notify = screenshooter_frame_notify; + wl_signal_add(&output->frame_signal, &l->frame_listener); + + l->buffer_destroy_listener.notify = buffer_destroy_handle; + wl_signal_add(&buffer->destroy_signal, &l->buffer_destroy_listener); + weston_output_disable_planes_incr(output); weston_output_schedule_repaint(output); @@ -418,7 +442,7 @@ weston_recorder_create(struct weston_output *output, const char *filename) header.magic = WCAP_HEADER_MAGIC; - switch (compositor->read_format) { + switch (compositor->read_format->pixman_format) { case PIXMAN_x8r8g8b8: case PIXMAN_a8r8g8b8: header.format = WCAP_FORMAT_XRGB8888; diff --git a/libweston/touch-calibration.c b/libweston/touch-calibration.c index 9dd99bba..72a114a6 100644 --- a/libweston/touch-calibration.c +++ b/libweston/touch-calibration.c @@ -206,7 +206,7 @@ map_calibrator(struct weston_touch_calibrator *calibrator) calibrator->view->is_mapped = true; calibrator->surface->output = calibrator->output; - calibrator->surface->is_mapped = true; + weston_surface_map(calibrator->surface); weston_output_schedule_repaint(calibrator->output); @@ -674,6 +674,16 @@ bind_touch_calibration(struct wl_client *client, } } +void +weston_compositor_destroy_touch_calibrator(struct weston_compositor *ec) +{ + /* TODO: handle weston_compositor::touch_calibrator destruction + * see + * https://gitlab.freedesktop.org/wayland/weston/-/merge_requests/819#note_1345191 + */ + weston_layer_fini(&ec->calibrator_layer); +} + /** Advertise touch_calibration support * * \param compositor The compositor to init for. diff --git a/libweston/vertex-clipping.c b/libweston/vertex-clipping.c index a71e7336..3a05c7bd 100644 --- a/libweston/vertex-clipping.c +++ b/libweston/vertex-clipping.c @@ -26,9 +26,10 @@ #include #include +#include "shared/helpers.h" #include "vertex-clipping.h" -float +WESTON_EXPORT_FOR_TESTS float float_difference(float a, float b) { /* http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/ */ @@ -282,7 +283,7 @@ clip_polygon_bottom(struct clip_context *ctx, const struct polygon8 *src, #define min(a, b) (((a) > (b)) ? (b) : (a)) #define clip(x, a, b) min(max(x, a), b) -int +WESTON_EXPORT_FOR_TESTS int clip_simple(struct clip_context *ctx, struct polygon8 *surf, float *ex, @@ -296,7 +297,7 @@ clip_simple(struct clip_context *ctx, return surf->n; } -int +WESTON_EXPORT_FOR_TESTS int clip_transformed(struct clip_context *ctx, struct polygon8 *surf, float *ex, diff --git a/libweston/weston-launch.c b/libweston/weston-launch.c deleted file mode 100644 index c43ed7b2..00000000 --- a/libweston/weston-launch.c +++ /dev/null @@ -1,917 +0,0 @@ -/* - * Copyright © 2012 Benjamin Franzke - * - * 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. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -#ifdef HAVE_SYSTEMD_LOGIN -#include -#endif - -#include "weston-launch.h" - -#define DRM_MAJOR 226 - -#ifndef KDSKBMUTE -#define KDSKBMUTE 0x4B51 -#endif - -#ifndef EVIOCREVOKE -#define EVIOCREVOKE _IOW('E', 0x91, int) -#endif - -#define MAX_ARGV_SIZE 256 - -#ifdef BUILD_DRM_COMPOSITOR - -#include - -#else - -static inline int -drmDropMaster(int drm_fd) -{ - return 0; -} - -static inline int -drmSetMaster(int drm_fd) -{ - return 0; -} - -#endif - -/* major()/minor() */ -#ifdef MAJOR_IN_MKDEV -# include -#endif -#ifdef MAJOR_IN_SYSMACROS -# include -#endif - -struct weston_launch { - struct pam_conv pc; - pam_handle_t *ph; - int tty; - int ttynr; - int sock[2]; - int drm_fd; - int last_input_fd; - int kb_mode; - struct passwd *pw; - - int signalfd; - - pid_t child; - int verbose; - char *new_user; -}; - -union cmsg_data { unsigned char b[4]; int fd; }; - -static gid_t * -read_groups(int *ngroups) -{ - int n; - gid_t *groups; - - n = getgroups(0, NULL); - - if (n < 0) { - fprintf(stderr, "Unable to retrieve groups: %s\n", - strerror(errno)); - return NULL; - } - - groups = malloc(n * sizeof(gid_t)); - if (!groups) - return NULL; - - if (getgroups(n, groups) < 0) { - fprintf(stderr, "Unable to retrieve groups: %s\n", - strerror(errno)); - free(groups); - return NULL; - } - - *ngroups = n; - return groups; -} - -static bool -weston_launch_allowed(struct weston_launch *wl) -{ - struct group *gr; - gid_t *groups; - int ngroups; -#ifdef HAVE_SYSTEMD_LOGIN - char *session, *seat; - int err; -#endif - - if (getuid() == 0) - return true; - - gr = getgrnam("weston-launch"); - if (gr) { - groups = read_groups(&ngroups); - if (groups && ngroups > 0) { - while (ngroups--) { - if (groups[ngroups] == gr->gr_gid) { - free(groups); - return true; - } - } - free(groups); - } - } - -#ifdef HAVE_SYSTEMD_LOGIN - err = sd_pid_get_session(getpid(), &session); - if (err == 0 && session) { - if (sd_session_is_active(session) && - sd_session_get_seat(session, &seat) == 0) { - free(seat); - free(session); - return true; - } - free(session); - } -#endif - - return false; -} - -static int -pam_conversation_fn(int msg_count, - const struct pam_message **messages, - struct pam_response **responses, - void *user_data) -{ - return PAM_SUCCESS; -} - -static int -setup_pam(struct weston_launch *wl) -{ - int err; - - wl->pc.conv = pam_conversation_fn; - wl->pc.appdata_ptr = wl; - - err = pam_start("login", wl->pw->pw_name, &wl->pc, &wl->ph); - if (err != PAM_SUCCESS) { - fprintf(stderr, "failed to start pam transaction: %d: %s\n", - err, pam_strerror(wl->ph, err)); - return -1; - } - - err = pam_set_item(wl->ph, PAM_TTY, ttyname(wl->tty)); - if (err != PAM_SUCCESS) { - fprintf(stderr, "failed to set PAM_TTY item: %d: %s\n", - err, pam_strerror(wl->ph, err)); - return -1; - } - - err = pam_open_session(wl->ph, 0); - if (err != PAM_SUCCESS) { - fprintf(stderr, "failed to open pam session: %d: %s\n", - err, pam_strerror(wl->ph, err)); - return -1; - } - - return 0; -} - -static int -setup_launcher_socket(struct weston_launch *wl) -{ - if (socketpair(AF_LOCAL, SOCK_SEQPACKET, 0, wl->sock) < 0) { - fprintf(stderr, "weston: socketpair failed: %s\n", - strerror(errno)); - return -1; - } - - if (fcntl(wl->sock[0], F_SETFD, FD_CLOEXEC) < 0) { - fprintf(stderr, "weston: fcntl failed: %s\n", - strerror(errno)); - return -1; - } - - return 0; -} - -static int -setup_signals(struct weston_launch *wl) -{ - int ret; - sigset_t mask; - struct sigaction sa; - - memset(&sa, 0, sizeof sa); - sa.sa_handler = SIG_DFL; - sa.sa_flags = SA_NOCLDSTOP | SA_RESTART; - ret = sigaction(SIGCHLD, &sa, NULL); - assert(ret == 0); - - sa.sa_handler = SIG_IGN; - sa.sa_flags = 0; - sigaction(SIGHUP, &sa, NULL); - - ret = sigemptyset(&mask); - assert(ret == 0); - sigaddset(&mask, SIGCHLD); - sigaddset(&mask, SIGINT); - sigaddset(&mask, SIGTERM); - sigaddset(&mask, SIGUSR1); - sigaddset(&mask, SIGUSR2); - ret = sigprocmask(SIG_BLOCK, &mask, NULL); - assert(ret == 0); - - wl->signalfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); - if (wl->signalfd < 0) - return -errno; - - return 0; -} - -static void -setenv_fd(const char *env, int fd) -{ - char buf[32]; - - snprintf(buf, sizeof buf, "%d", fd); - setenv(env, buf, 1); -} - -static int -open_tty_by_number(int ttynr) -{ - int ret; - char filename[16]; - - ret = snprintf(filename, sizeof filename, "/dev/tty%d", ttynr); - if (ret < 0) - return -1; - - return open(filename, O_RDWR | O_NOCTTY); -} - -static int -send_reply(struct weston_launch *wl, int reply) -{ - int len; - - do { - len = send(wl->sock[0], &reply, sizeof reply, 0); - } while (len < 0 && errno == EINTR); - - return len; -} - -static int -handle_open(struct weston_launch *wl, struct msghdr *msg, ssize_t len) -{ - int fd = -1, ret = -1; - char control[CMSG_SPACE(sizeof(fd))]; - struct cmsghdr *cmsg; - struct stat s; - struct msghdr nmsg; - struct iovec iov; - struct weston_launcher_open *message; - union cmsg_data *data; - - message = msg->msg_iov->iov_base; - if ((size_t)len < sizeof(*message)) - goto err0; - - /* Ensure path is null-terminated */ - ((char *) message)[len-1] = '\0'; - - fd = open(message->path, message->flags); - if (fd < 0) { - fprintf(stderr, "Error opening device %s: %s\n", - message->path, strerror(errno)); - goto err0; - } - - if (fstat(fd, &s) < 0) { - close(fd); - fd = -1; - fprintf(stderr, "Failed to stat %s\n", message->path); - goto err0; - } - - if (major(s.st_rdev) != INPUT_MAJOR && - major(s.st_rdev) != DRM_MAJOR) { - close(fd); - fd = -1; - fprintf(stderr, "Device %s is not an input or drm device\n", - message->path); - goto err0; - } - -err0: - memset(&nmsg, 0, sizeof nmsg); - nmsg.msg_iov = &iov; - nmsg.msg_iovlen = 1; - if (fd != -1) { - nmsg.msg_control = control; - nmsg.msg_controllen = sizeof control; - cmsg = CMSG_FIRSTHDR(&nmsg); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); - data = (union cmsg_data *) CMSG_DATA(cmsg); - data->fd = fd; - nmsg.msg_controllen = cmsg->cmsg_len; - ret = 0; - } - struct { int reply_id; int ret; } reply_iov_data = { WESTON_LAUNCHER_OPEN_REPLY, ret }; - iov.iov_base = &reply_iov_data; - iov.iov_len = sizeof reply_iov_data; - - if (wl->verbose) - fprintf(stderr, "weston-launch: opened %s: ret: %d, fd: %d\n", - message->path, ret, fd); - do { - len = sendmsg(wl->sock[0], &nmsg, 0); - } while (len < 0 && errno == EINTR); - - if (len < 0) - return -1; - - if (fd != -1 && major(s.st_rdev) == DRM_MAJOR) - wl->drm_fd = fd; - if (fd != -1 && major(s.st_rdev) == INPUT_MAJOR && - wl->last_input_fd < fd) - wl->last_input_fd = fd; - - return 0; -} - -static void -close_input_fds(struct weston_launch *wl) -{ - struct stat s; - int fd; - - for (fd = 3; fd <= wl->last_input_fd; fd++) { - if (fstat(fd, &s) == 0 && major(s.st_rdev) == INPUT_MAJOR) { - /* EVIOCREVOKE may fail if the kernel doesn't - * support it, but all we can do is ignore it. */ - ioctl(fd, EVIOCREVOKE, 0); - close(fd); - } - } -} - -static int -handle_socket_msg(struct weston_launch *wl) -{ - char control[CMSG_SPACE(sizeof(int))]; - char buf[BUFSIZ]; - struct msghdr msg; - struct iovec iov; - int ret = -1; - ssize_t len; - struct weston_launcher_message *message; - - memset(&msg, 0, sizeof(msg)); - iov.iov_base = buf; - iov.iov_len = sizeof buf; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_control = control; - msg.msg_controllen = sizeof control; - - do { - len = recvmsg(wl->sock[0], &msg, 0); - } while (len < 0 && errno == EINTR); - - if (len < 1) - return -1; - - message = (void *) buf; - switch (message->opcode) { - case WESTON_LAUNCHER_OPEN: - ret = handle_open(wl, &msg, len); - break; - case WESTON_LAUNCHER_DEACTIVATE_DONE: - close_input_fds(wl); - drmDropMaster(wl->drm_fd); - ioctl(wl->tty, VT_RELDISP, 1); - break; - } - - return ret; -} - -static void -quit(struct weston_launch *wl, int status) -{ - struct vt_mode mode = { 0 }; - int err; - int oldtty; - - close(wl->signalfd); - close(wl->sock[0]); - - if (wl->new_user) { - err = pam_close_session(wl->ph, 0); - if (err) - fprintf(stderr, "pam_close_session failed: %d: %s\n", - err, pam_strerror(wl->ph, err)); - pam_end(wl->ph, err); - } - - /* - * Get a fresh handle to the tty as the previous one is in - * hang-up state since weston (the controlling process for - * the tty) exit at this point. Reopen before closing the - * file descriptor to avoid a potential race condition. - * - * A similar fix exists in logind, see: - * https://github.com/systemd/systemd/pull/990 - */ - oldtty = wl->tty; - wl->tty = open_tty_by_number(wl->ttynr); - close(oldtty); - - if (ioctl(wl->tty, KDSKBMUTE, 0) && - ioctl(wl->tty, KDSKBMODE, wl->kb_mode)) - fprintf(stderr, "failed to restore keyboard mode: %s\n", - strerror(errno)); - - if (ioctl(wl->tty, KDSETMODE, KD_TEXT)) - fprintf(stderr, "failed to set KD_TEXT mode on tty: %s\n", - strerror(errno)); - - /* We have to drop master before we switch the VT back in - * VT_AUTO, so we don't risk switching to a VT with another - * display server, that will then fail to set drm master. */ - drmDropMaster(wl->drm_fd); - - mode.mode = VT_AUTO; - if (ioctl(wl->tty, VT_SETMODE, &mode) < 0) - fprintf(stderr, "could not reset vt handling\n"); - - if (wl->tty != STDIN_FILENO) - close(wl->tty); - - exit(status); -} - -static int -handle_signal(struct weston_launch *wl) -{ - struct signalfd_siginfo sig; - int pid, status, ret; - - if (read(wl->signalfd, &sig, sizeof sig) != sizeof sig) { - fprintf(stderr, "weston: reading signalfd failed: %s\n", - strerror(errno)); - return -1; - } - - switch (sig.ssi_signo) { - case SIGCHLD: - pid = waitpid(-1, &status, 0); - if (pid == wl->child) { - wl->child = 0; - if (WIFEXITED(status)) - ret = WEXITSTATUS(status); - else if (WIFSIGNALED(status)) - /* - * If weston dies because of signal N, we - * return 10+N. This is distinct from - * weston-launch dying because of a signal - * (128+N). - */ - ret = 10 + WTERMSIG(status); - else - ret = 0; - quit(wl, ret); - } - break; - case SIGTERM: - case SIGINT: - if (!wl->child) - break; - - if (wl->verbose) - fprintf(stderr, "weston-launch: sending %s to pid %d\n", - strsignal(sig.ssi_signo), wl->child); - - kill(wl->child, sig.ssi_signo); - break; - case SIGUSR1: - send_reply(wl, WESTON_LAUNCHER_DEACTIVATE); - break; - case SIGUSR2: - ioctl(wl->tty, VT_RELDISP, VT_ACKACQ); - drmSetMaster(wl->drm_fd); - send_reply(wl, WESTON_LAUNCHER_ACTIVATE); - break; - default: - return -1; - } - - return 0; -} - -static int -setup_tty(struct weston_launch *wl, const char *tty) -{ - struct stat buf; - struct vt_mode mode = { 0 }; - char *t; - - if (!wl->new_user) { - wl->tty = STDIN_FILENO; - } else if (tty) { - t = ttyname(STDIN_FILENO); - if (t && strcmp(t, tty) == 0) - wl->tty = STDIN_FILENO; - else - wl->tty = open(tty, O_RDWR | O_NOCTTY); - } else { - int tty0 = open("/dev/tty0", O_WRONLY | O_CLOEXEC); - - if (tty0 < 0) { - fprintf(stderr, "weston: could not open tty0: %s\n", - strerror(errno)); - return -1; - } - - if (ioctl(tty0, VT_OPENQRY, &wl->ttynr) < 0 || wl->ttynr == -1) - { - fprintf(stderr, "weston: failed to find non-opened console: %s\n", - strerror(errno)); - return -1; - } - - wl->tty = open_tty_by_number(wl->ttynr); - close(tty0); - } - - if (wl->tty < 0) { - fprintf(stderr, "weston: failed to open tty: %s\n", - strerror(errno)); - return -1; - } - - if (fstat(wl->tty, &buf) == -1 || - major(buf.st_rdev) != TTY_MAJOR || minor(buf.st_rdev) == 0) { - fprintf(stderr, "weston: weston-launch must be run from a virtual terminal\n"); - return -1; - } - - if (!wl->new_user || tty) { - if (fstat(wl->tty, &buf) < 0) { - fprintf(stderr, "weston: stat %s failed: %s\n", tty, - strerror(errno)); - return -1; - } - - if (major(buf.st_rdev) != TTY_MAJOR) { - fprintf(stderr, - "weston: invalid tty device: %s\n", tty); - return -1; - } - - wl->ttynr = minor(buf.st_rdev); - } - - if (ioctl(wl->tty, VT_ACTIVATE, wl->ttynr) < 0) { - fprintf(stderr, - "weston: failed to activate VT: %s\n", - strerror(errno)); - return -1; - } - - if (ioctl(wl->tty, VT_WAITACTIVE, wl->ttynr) < 0) { - fprintf(stderr, - "weston: failed to wait for VT to be active: %s\n", - strerror(errno)); - return -1; - } - - if (ioctl(wl->tty, KDGKBMODE, &wl->kb_mode)) { - fprintf(stderr, - "weston: failed to get current keyboard mode: %s\n", - strerror(errno)); - return -1; - } - - if (ioctl(wl->tty, KDSKBMUTE, 1) && - ioctl(wl->tty, KDSKBMODE, K_OFF)) { - fprintf(stderr, - "weston: failed to set K_OFF keyboard mode: %s\n", - strerror(errno)); - return -1; - } - - if (ioctl(wl->tty, KDSETMODE, KD_GRAPHICS)) { - fprintf(stderr, - "weston: failed to set KD_GRAPHICS mode on tty: %s\n", - strerror(errno)); - return -1; - } - - mode.mode = VT_PROCESS; - mode.relsig = SIGUSR1; - mode.acqsig = SIGUSR2; - if (ioctl(wl->tty, VT_SETMODE, &mode) < 0) { - fprintf(stderr, - "weston: failed to take control of vt handling %s\n", - strerror(errno)); - return -1; - } - - return 0; -} - -static int -setup_session(struct weston_launch *wl, char **child_argv) -{ - char **env; - char *term; - int i; - - if (wl->tty != STDIN_FILENO) { - if (setsid() < 0) { - fprintf(stderr, "weston: setsid failed %s\n", - strerror(errno)); - exit(EXIT_FAILURE); - } - if (ioctl(wl->tty, TIOCSCTTY, 0) < 0) { - fprintf(stderr, "TIOCSCTTY failed - tty is in use %s\n", - strerror(errno)); - exit(EXIT_FAILURE); - } - } - - term = getenv("TERM"); - clearenv(); - if (term) - setenv("TERM", term, 1); - setenv("USER", wl->pw->pw_name, 1); - setenv("LOGNAME", wl->pw->pw_name, 1); - setenv("HOME", wl->pw->pw_dir, 1); - setenv("SHELL", wl->pw->pw_shell, 1); - - env = pam_getenvlist(wl->ph); - if (env) { - for (i = 0; env[i]; ++i) { - if (putenv(env[i]) != 0) - fprintf(stderr, "putenv %s failed\n", env[i]); - } - free(env); - } - - /* - * We open a new session, so it makes sense - * to run a new login shell - */ - child_argv[0] = "/bin/sh"; - child_argv[1] = "-l"; - child_argv[2] = "-c"; - child_argv[3] = "exec " BINDIR "/weston \"$@\""; - child_argv[4] = "weston"; - return 5; -} - -static void -drop_privileges(struct weston_launch *wl) -{ - if (setgid(wl->pw->pw_gid) < 0 || -#ifdef HAVE_INITGROUPS - initgroups(wl->pw->pw_name, wl->pw->pw_gid) < 0 || -#endif - setuid(wl->pw->pw_uid) < 0) { - fprintf(stderr, "weston: dropping privileges failed %s\n", - strerror(errno)); - exit(EXIT_FAILURE); - } -} - -static void -launch_compositor(struct weston_launch *wl, int argc, char *argv[]) -{ - char *child_argv[MAX_ARGV_SIZE]; - sigset_t mask; - int o, i; - - if (wl->verbose) - printf("weston-launch: spawned weston with pid: %d\n", getpid()); - if (wl->new_user) { - o = setup_session(wl, child_argv); - } else { - child_argv[0] = BINDIR "/weston"; - o = 1; - } - for (i = 0; i < argc; ++i) - child_argv[o + i] = argv[i]; - child_argv[o + i] = NULL; - - if (geteuid() == 0) - drop_privileges(wl); - - setenv_fd("WESTON_TTY_FD", wl->tty); - setenv_fd("WESTON_LAUNCHER_SOCK", wl->sock[1]); - - unsetenv("DISPLAY"); - - /* Do not give our signal mask to the new process. */ - sigemptyset(&mask); - sigaddset(&mask, SIGTERM); - sigaddset(&mask, SIGCHLD); - sigaddset(&mask, SIGINT); - sigprocmask(SIG_UNBLOCK, &mask, NULL); - - - execv(child_argv[0], child_argv); - fprintf(stderr, "weston: exec failed: %s\n", strerror(errno)); - exit(EXIT_FAILURE); -} - -static void -help(const char *name) -{ - fprintf(stderr, "Usage: %s [args...] [-- [weston args..]]\n", name); - fprintf(stderr, " -u, --user Start session as specified username,\n" - " e.g. -u joe, requires root.\n"); - fprintf(stderr, " -t, --tty Start session on alternative tty,\n" - " e.g. -t /dev/tty4, requires -u option.\n"); - fprintf(stderr, " -v, --verbose Be verbose\n"); - fprintf(stderr, " -h, --help Display this help message\n"); -} - -int -main(int argc, char *argv[]) -{ - struct weston_launch wl; - int i, c; - char *tty = NULL; - struct option opts[] = { - { "user", required_argument, NULL, 'u' }, - { "tty", required_argument, NULL, 't' }, - { "verbose", no_argument, NULL, 'v' }, - { "help", no_argument, NULL, 'h' }, - { 0, 0, NULL, 0 } - }; - - memset(&wl, 0, sizeof wl); - - while ((c = getopt_long(argc, argv, "u:t:vh", opts, &i)) != -1) { - switch (c) { - case 'u': - wl.new_user = optarg; - if (getuid() != 0) { - fprintf(stderr, "weston: Permission denied. -u allowed for root only\n"); - exit(EXIT_FAILURE); - } - break; - case 't': - tty = optarg; - break; - case 'v': - wl.verbose = 1; - break; - case 'h': - help("weston-launch"); - exit(EXIT_FAILURE); - default: - exit(EXIT_FAILURE); - } - } - - if ((argc - optind) > (MAX_ARGV_SIZE - 6)) { - fprintf(stderr, - "weston: Too many arguments to pass to weston: %s\n", - strerror(E2BIG)); - exit(EXIT_FAILURE); - } - - if (tty && !wl.new_user) { - fprintf(stderr, "weston: -t/--tty option requires -u/--user option as well\n"); - exit(EXIT_FAILURE); - } - - if (wl.new_user) - wl.pw = getpwnam(wl.new_user); - else - wl.pw = getpwuid(getuid()); - if (wl.pw == NULL) { - fprintf(stderr, "weston: failed to get username: %s\n", - strerror(errno)); - exit(EXIT_FAILURE); - } - - if (!weston_launch_allowed(&wl)) { - fprintf(stderr, "Permission denied. You should either:\n" -#ifdef HAVE_SYSTEMD_LOGIN - " - run from an active and local (systemd) session.\n" -#else - " - enable systemd session support for weston-launch.\n" -#endif - " - or add yourself to the 'weston-launch' group.\n"); - exit(EXIT_FAILURE); - } - - if (setup_tty(&wl, tty) < 0) - exit(EXIT_FAILURE); - - if (wl.new_user && setup_pam(&wl) < 0) - exit(EXIT_FAILURE); - - if (setup_launcher_socket(&wl) < 0) - exit(EXIT_FAILURE); - - if (setup_signals(&wl) < 0) - exit(EXIT_FAILURE); - - wl.child = fork(); - if (wl.child == -1) { - fprintf(stderr, "weston: fork failed %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } - - if (wl.child == 0) - launch_compositor(&wl, argc - optind, argv + optind); - - close(wl.sock[1]); - - while (1) { - struct pollfd fds[2]; - int n; - - fds[0].fd = wl.sock[0]; - fds[0].events = POLLIN; - fds[1].fd = wl.signalfd; - fds[1].events = POLLIN; - - n = poll(fds, 2, -1); - if (n < 0) { - fprintf(stderr, "poll failed: %s\n", strerror(errno)); - return -1; - } - if (fds[0].revents & POLLIN) - handle_socket_msg(&wl); - if (fds[1].revents) - handle_signal(&wl); - } - - return 0; -} diff --git a/libweston/weston-log.c b/libweston/weston-log.c index 276fde26..93f95c9a 100644 --- a/libweston/weston-log.c +++ b/libweston/weston-log.c @@ -238,8 +238,6 @@ weston_log_subscription_get_data(struct weston_log_subscription *sub) * subscription * @param scope the scope in order to add the subscription to the scope's * subscription list - * @returns a weston_log_subscription object in case of success, or NULL - * otherwise * * @sa weston_log_subscription_destroy, weston_log_subscription_remove, * weston_log_subscription_add @@ -913,6 +911,56 @@ weston_log_scope_timestamp(struct weston_log_scope *scope, return buf; } +/** Returns a timestamp useful for adding it to a log scope. + * + * @example + * char timestr[128]; + * static int cached_dm = -1; + * char *time_buff = weston_log_timestamp(timestr, sizeof(timestr), &cached_dm); + * weston_log_scope_printf(log_scope, "%s %s", time_buff, other_data); + * + * @param buf a user-supplied buffer + * @param len user-supplied length of the buffer + * @param cached_tm_mday a cached day of the month, as an integer. Setting this + * pointer different from NULL, to an integer value other than was retrieved as + * current day of the month, would add an additional line under the form of + * 'Date: Y-m-d Z\n'. Setting the pointer to NULL would not print any date, nor + * if the value matches the current day of month. Helps identify logs that + * spawn multiple days, while still having a shorter time stamp format. + * @ingroup log + */ +WL_EXPORT char * +weston_log_timestamp(char *buf, size_t len, int *cached_tm_mday) +{ + struct timeval tv; + struct tm *brokendown_time; + char datestr[128]; + char timestr[128]; + + gettimeofday(&tv, NULL); + + brokendown_time = localtime(&tv.tv_sec); + if (brokendown_time == NULL) { + snprintf(buf, len, "%s", "[(NULL)localtime] "); + return buf; + } + + memset(datestr, 0, sizeof(datestr)); + if (cached_tm_mday && brokendown_time->tm_mday != *cached_tm_mday) { + strftime(datestr, sizeof(datestr), "Date: %Y-%m-%d %Z\n", + brokendown_time); + *cached_tm_mday = brokendown_time->tm_mday; + } + + strftime(timestr, sizeof(timestr), "%H:%M:%S", brokendown_time); + /* if datestr is empty it prints only timestr*/ + snprintf(buf, len, "%s[%s.%03li]", datestr, + timestr, (tv.tv_usec / 1000)); + + return buf; +} + + void weston_log_subscriber_release(struct weston_log_subscriber *subscriber) { diff --git a/libweston/zoom.c b/libweston/zoom.c deleted file mode 100644 index 064d6a84..00000000 --- a/libweston/zoom.c +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright © 2012 Scott Moreau - * - * 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. - */ - -#include "config.h" - -#include -#include -#include -#include - -#include -#include "backend.h" -#include "libweston-internal.h" -#include "text-cursor-position-server-protocol.h" -#include "shared/helpers.h" - -static void -weston_zoom_frame_z(struct weston_animation *animation, - struct weston_output *output, - const struct timespec *time) -{ - if (animation->frame_counter <= 1) - output->zoom.spring_z.timestamp = *time; - - weston_spring_update(&output->zoom.spring_z, time); - - if (output->zoom.spring_z.current > output->zoom.max_level) - output->zoom.spring_z.current = output->zoom.max_level; - else if (output->zoom.spring_z.current < 0.0) - output->zoom.spring_z.current = 0.0; - - if (weston_spring_done(&output->zoom.spring_z)) { - if (output->zoom.active && output->zoom.level <= 0.0) { - output->zoom.active = false; - output->zoom.seat = NULL; - weston_output_disable_planes_decr(output); - wl_list_remove(&output->zoom.motion_listener.link); - } - output->zoom.spring_z.current = output->zoom.level; - wl_list_remove(&animation->link); - wl_list_init(&animation->link); - } - - output->dirty = 1; - weston_output_damage(output); -} - -static void -zoom_area_center_from_point(struct weston_output *output, - double *x, double *y) -{ - float level = output->zoom.spring_z.current; - - *x = (*x - output->x) * level + output->width / 2.; - *y = (*y - output->y) * level + output->height / 2.; -} - -static void -weston_output_update_zoom_transform(struct weston_output *output) -{ - double x = output->zoom.current.x; /* global pointer coords */ - double y = output->zoom.current.y; - float level; - - level = output->zoom.spring_z.current; - - if (!output->zoom.active || level > output->zoom.max_level || - level == 0.0f) - return; - - zoom_area_center_from_point(output, &x, &y); - - output->zoom.trans_x = x - output->width / 2; - output->zoom.trans_y = y - output->height / 2; - - if (output->zoom.trans_x < 0) - output->zoom.trans_x = 0; - if (output->zoom.trans_y < 0) - output->zoom.trans_y = 0; - if (output->zoom.trans_x > level * output->width) - output->zoom.trans_x = level * output->width; - if (output->zoom.trans_y > level * output->height) - output->zoom.trans_y = level * output->height; -} - -static void -weston_zoom_transition(struct weston_output *output) -{ - if (output->zoom.level != output->zoom.spring_z.current) { - output->zoom.spring_z.target = output->zoom.level; - if (wl_list_empty(&output->zoom.animation_z.link)) { - output->zoom.animation_z.frame_counter = 0; - wl_list_insert(output->animation_list.prev, - &output->zoom.animation_z.link); - } - } - - output->dirty = 1; - weston_output_damage(output); -} - -WL_EXPORT void -weston_output_update_zoom(struct weston_output *output) -{ - struct weston_seat *seat = output->zoom.seat; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - if (!pointer) - return; - - assert(output->zoom.active); - - output->zoom.current.x = wl_fixed_to_double(pointer->x); - output->zoom.current.y = wl_fixed_to_double(pointer->y); - - weston_zoom_transition(output); - weston_output_update_zoom_transform(output); -} - -static void -motion(struct wl_listener *listener, void *data) -{ - struct weston_output_zoom *zoom = - container_of(listener, struct weston_output_zoom, motion_listener); - struct weston_output *output = - container_of(zoom, struct weston_output, zoom); - - weston_output_update_zoom(output); -} - -WL_EXPORT void -weston_output_activate_zoom(struct weston_output *output, - struct weston_seat *seat) -{ - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - if (!pointer || output->zoom.active) - return; - - output->zoom.active = true; - output->zoom.seat = seat; - weston_output_disable_planes_incr(output); - wl_signal_add(&pointer->motion_signal, - &output->zoom.motion_listener); -} - -WL_EXPORT void -weston_output_init_zoom(struct weston_output *output) -{ - output->zoom.active = false; - output->zoom.seat = NULL; - output->zoom.increment = 0.07; - output->zoom.max_level = 0.95; - output->zoom.level = 0.0; - output->zoom.trans_x = 0.0; - output->zoom.trans_y = 0.0; - weston_spring_init(&output->zoom.spring_z, 250.0, 0.0, 0.0); - output->zoom.spring_z.friction = 1000; - output->zoom.animation_z.frame = weston_zoom_frame_z; - wl_list_init(&output->zoom.animation_z.link); - output->zoom.motion_listener.notify = motion; -} diff --git a/man/weston-bindings.man b/man/weston-bindings.man index e88a9e85..d528a807 100644 --- a/man/weston-bindings.man +++ b/man/weston-bindings.man @@ -33,29 +33,29 @@ Kill active window Maximize active window .P .RE -.B mod + PageUp, mod + PageDown +.B mod + Shift + KEY_LEFT .RS 4 -Zoom desktop in (or out) +Make the active window tiled left. .P .RE -.B mod + Tab +.B mod + Shift + KEY_RIGHT .RS 4 -Switch active window +Make the active window tiled right. .P .RE -.B mod + Up, mod + Down +.B mod + Shift + KEY_UP .RS 4 -Increment/decrement active workspace number, if there are multiple +Make the active window tiled top. .P .RE -.B mod + Shift + Up, mod + Shift + Down +.B mod + Shift + KEY_DOWN .RS 4 -Move active window to the succeeding/preceding workspace, if possible +Make the active window tiled bottom. .P .RE -.B mod + F1/F2/F3/F4/F5/F6 +.B mod + Tab .RS 4 -Jump to the numbered workspace, if it exists +Switch active window .P .RE .B Ctrl + Alt + Backspace @@ -126,11 +126,41 @@ The combination \fBmod + Shift + Space\fR begins a debug binding. Debug bindings are completed by pressing an additional key. For example, pressing F may toggle texture mesh wireframes with the GL renderer. (In fact, most debug effects can be disabled again by repeating the command.) -Debug bindings are often tied to specific backends. +Debug bindings are often tied to specific backends. Below are the debug bindings available. + +.RS +- KEY_D : +.RS 4 +Subscribe for flight recorder. +.RE +- KEY_C : +.RS 4 +Enable/Disable cursor planes. +.RE +- KEY_V : +.RS 4 +Enable/Disable overlay planes. +.RE +- KEY_Q : +.RS 4 +Start VAAPI recorder. +.RE +- KEY_S : +.RS 4 +Enable fragment debugging for gl-renderer. +.RE +- KEY_F : +.RS 4 +Enable fan debugging for gl-renderer. +.RE +- KEY_R : +.RS 4 +Enable repaint debugging for Pixman. +.RE +.RE .SH "SEE ALSO" .BR weston (1), -.BR weston-launch (1), .BR weston-drm (7), .BR weston.ini (5), .BR xkeyboard-config (7) diff --git a/man/weston-drm.man b/man/weston-drm.man index 01a336e1..383715d3 100644 --- a/man/weston-drm.man +++ b/man/weston-drm.man @@ -2,8 +2,6 @@ .SH NAME weston-drm \- the DRM backend for Weston .SH SYNOPSIS -.B weston-launch -.LP .B weston --backend=drm-backend.so . .\" *************************************************************** @@ -33,19 +31,6 @@ the first DRM device returned by .BR udev (7). Combining multiple graphics devices is not supported yet. -The DRM backend relies on -.B weston-launch -for managing input device access and DRM master status, so that -.B weston -can be run without root privileges. On switching away from the -virtual terminal (VT) hosting Weston, all input devices are closed and -the DRM master capability is dropped, so that other servers, -including -.BR Xorg (1), -can run on other VTs. On switching back to Weston's VT, input devices -and DRM master are re-acquired through the parent process -.BR weston-launch . - The DRM backend also supports virtual outputs that are transmitted over an RTP session as a series of JPEG images (RTP payload type 26) to a remote client. Virtual outputs are configured in the @@ -58,6 +43,31 @@ section of . The DRM backend uses the following entries from .BR weston.ini . +.SS Section core +.TP +\fBgbm-format\fR=\fIformat\fR +Sets the default pixel format for DRM KMS framebuffers. +.IR Format " can be" +.BR xrgb8888 ", " xrgb2101010 ", " rgb565 +or others. Weston recognizes the names of most pixel formats defined by +the kernel DRM subsystem in +.B drm_fourcc.h +header without the DRM_FORMAT_ prefix. +The actually supported pixel formats depend on the DRM driver and hardware, +and the renderer used. Using Pixman-renderer, DRM-backend supports only +.BR xrgb8888 " and " rgb565 . +The formats supported with GL-renderer depend on the EGL and OpenGL ES 2 or 3 +implementations. The names are case-insensitive. This setting applies only to +.RB "outputs in SDR mode, see " eotf-mode " in " weston.ini (5). + +.RB "If not specified, " xrgb8888 " is used. See also " gbm-format " in" +.BR output " section." +.TP +\fBpageflip-timeout\fR=\fImilliseconds\fR +sets Weston's pageflip timeout in milliseconds. This sets a timer to exit +gracefully with a log message and an exit code of 1 in case the DRM driver is +non-responsive. Setting it to 0 disables this feature. + .SS Section output .TP \fBname\fR=\fIconnector\fR @@ -123,6 +133,14 @@ and possibly flipped. Possible values are .BR flipped ", " flipped-rotate-90 ", " flipped-rotate-180 ", and " .BR flipped-rotate-270 . .TP +\fBgbm-format\fR=\fIformat\fR +Set the DRM KMS framebuffer format for this specific output. If not set, +.RB "the value from " gbm-format " option in " core " section is used" +.RB "for SDR mode outputs and " xrgb2101010 " for other modes." +.RI "For the possible values for " format " see " +.BR gbm-format " option in " core " section." +.RB "For SDR mode, see " eotf-mode " in " weston.ini (7). +.TP \fBpixman-shadow\fR=\fIboolean\fR If using the Pixman-renderer, use shadow framebuffers. Defaults to .BR true . @@ -146,6 +164,15 @@ Defaults to false. Note that When a connector is disconnected, there is no EDID information to provide a list of video modes. Therefore a forced output should also have a detailed mode line specified. +.TP +\fBmax-bpc\fR=\fIN\fR +.RB "Set \(dq" "max bpc" "\(dq KMS property to value" +.IR N , +silenty clamped to the hardware driver supported range. This artificially +limits the driver chosen link bits-per-channel which may be useful for working +around sink hardware (e.g. monitor) limitations. The default is 16 which is +practically unlimited. If you need to work around hardware issues, try a lower +value like 8. A value of 0 means that the current max bpc will be reprogrammed. .SS Section remote-output .TP @@ -201,11 +228,6 @@ Use graphics and input devices designated for seat instead of the seat defined in the environment variable .BR XDG_SEAT ". If neither is specified, seat0 will be assumed." .TP -\fB\-\-tty\fR=\fIx\fR -Launch Weston on tty -.I x -instead of using the current tty. -.TP .B \-\-continue\-without\-input Allow Weston to start without input devices. Used for testing purposes. . @@ -218,23 +240,10 @@ The minimum libinput verbosity level to be printed to Weston's log. Valid values are .BR debug ", " info ", and " error ". Default is " info . .TP -.B WESTON_TTY_FD -The file descriptor (integer) of the opened tty where -.B weston -will run. Set by -.BR weston-launch . -.TP -.B WESTON_LAUNCHER_SOCK -The file descriptor (integer) where -.B weston-launch -is listening. Automatically set by -.BR weston-launch . -.TP .B XDG_SEAT The seat Weston will start on, unless overridden on the command line. . .\" *************************************************************** .SH "SEE ALSO" .BR weston (1) -.\".BR weston-launch (1), .\".BR weston.ini (5) diff --git a/man/weston-rdp.man b/man/weston-rdp.man index f6cdd1de..a596826f 100644 --- a/man/weston-rdp.man +++ b/man/weston-rdp.man @@ -24,6 +24,19 @@ backend will announce security options based on which files have been given. The RDP backend is multi-seat aware, so if two clients connect on the backend, they will get their own seat. +.\" *************************************************************** +.SH CONFIGURATION +. +The RDP backend uses the following entries from +.BR weston.ini . +.SS Section rdp +.TP +\fBrefresh-rate\fR=\fIrate\fR +Specifies the desktop redraw rate in Hz. If unspecified, the default is 60Hz. Changing +this may be useful if you have a faster than 60Hz display, or if you want to reduce updates to +reduce network traffic. + + .\" *************************************************************** .SH OPTIONS . @@ -43,6 +56,11 @@ By default when a client connects on the RDP backend, it will instruct weston to resize to the dimensions of the client's announced resolution. When this option is set, weston will force the client to resize to its own resolution. .TP +\fB\-\-no-remotefx-codec +The RemoteFX compression codec is enabled by default, but it may be necessary +to disable it to work around incompatabilities between implementations. This +option may be removed in the future when all known issues are resolved. +.TP \fB\-\-rdp4\-key\fR=\fIfile\fR The file containing the RSA key for doing RDP security. As RDP security is known to be insecure, this option should be avoided in production. @@ -54,6 +72,12 @@ to ship a file containing a certificate. \fB\-\-rdp\-tls\-cert\fR=\fIfile\fR The file containing the certificate for doing TLS security. To have TLS security you also need to ship a key file. +.TP +\fB\-\-external\-listener\-fd\fR=\fIfd\fR +Specifies a file descriptor inherited from the process that launched weston +to be listened on for client connections. Only local (such as AF_VSOCK) +sockets should be used, as this will be considered to be a local connection +by the RDP backend, and TLS and RDP security will be bypassed. .\" *************************************************************** diff --git a/man/weston.ini.man b/man/weston.ini.man index 5877425c..179e0882 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -1,15 +1,18 @@ .\" shorthand for double quote that works everywhere. .ds q \N'34' .TH weston.ini 5 "2019-03-26" "Weston @version@" +.\"--------------------------------------------------------------------- .SH NAME weston.ini \- configuration file for .B Weston \- the reference Wayland compositor +.\"--------------------------------------------------------------------- .SH INTRODUCTION .B Weston obtains configuration from its command line parameters and the configuration file described here. +.\"--------------------------------------------------------------------- .SH DESCRIPTION .B Weston uses a configuration file called @@ -87,7 +90,7 @@ integer, and boolean. Strings must not be quoted, do not support any escape sequences, and run till the end of the line. Integers can be given in decimal (e.g. 123), octal (e.g. 0173), and hexadecimal (e.g. 0x7b) form. Boolean values can be only 'true' or 'false'. -.RE +.\"--------------------------------------------------------------------- .SH "CORE SECTION" The .B core @@ -108,7 +111,6 @@ directory are: .TP 7 .BI "xwayland=" true ask Weston to load the XWayland module (boolean). -.RE .TP 7 .BI "modules=" cms-colord.so,screen-share.so specifies the modules to load (string). Available modules in the @@ -130,7 +132,6 @@ directory are: .RS 10 .nf .BR drm-backend.so -.BR fbdev-backend.so .BR headless-backend.so .BR rdp-backend.so .BR wayland-backend.so @@ -148,16 +149,6 @@ target vertical blank, increasing output latency. The default value is 7 milliseconds. The allowed range is from -10 to 1000 milliseconds. Using a negative value will force the compositor to always miss the target vblank. .TP 7 -.BI "gbm-format="format -sets the GBM format used for the framebuffer for the GBM backend. Can be -.B xrgb8888, -.B xrgb2101010, -.B rgb565. -By default, xrgb8888 is used. -.RS -.PP -.RE -.TP 7 .BI "idle-time="seconds sets Weston's idle timeout in seconds. This idle timeout is the time after which Weston will enter an "inactive" mode and screen will fade to @@ -170,18 +161,10 @@ means that if both weston.ini and command line define this idle-timeout time, the one specified in the command-line will be used. On the other hand, if none of these sets the value, default idle timeout will be set to 300 seconds. -.RS -.PP -.RE .TP 7 .BI "require-input=" true require an input device for launch .TP 7 -.BI "pageflip-timeout="milliseconds -sets Weston's pageflip timeout in milliseconds. This sets a timer to exit -gracefully with a log message and an exit code of 1 in case the DRM driver is -non-responsive. Setting it to 0 disables this feature. -.TP 7 .BI "wait-for-debugger=" true Raises SIGSTOP before initializing the compositor. This allows the user to attach with a debugger and continue execution by sending SIGCONT. This is @@ -208,7 +191,6 @@ Enables pixman-based rendering for all outputs on backends that support it. Boolean, defaults to .BR false . There is also a command line option to do the same. -.RE .TP 7 .BI "color-management=" true Enables color management and requires using GL-renderer. @@ -222,7 +204,7 @@ spaces and perform monitor profiling, and tone mapping required to enable HDR video modes. This extended functionality comes at the cost of heavier image processing and sometimes a loss of some hardware off-loading features like composite-bypass. - +.\"--------------------------------------------------------------------- .SH "LIBINPUT SECTION" The .B libinput @@ -331,18 +313,18 @@ Weston will use the new calibration immediately. The program is invoked as: .PP .RS 10 +.nf .I calibration_helper syspath m1 m2 m3 m4 m5 m6 +.fi .RE -.RS -.PP +.IP .RI "where " syspath is the udev sys path for the device and .IR m1 " through " m6 are the calibration matrix elements in libinput's .BR LIBINPUT_CALIBRATION_MATRIX " udev property format." The sys path is an absolute path and starts with the sys mount point. -.RE - +.\"--------------------------------------------------------------------- .SH "SHELL SECTION" The .B shell @@ -422,7 +404,7 @@ sets the effect used when closing windows (string). Can be By default, the fade animation is used. .TP 7 .BI "startup-animation=" fade -sets the effect used for opening new windows (string). Can be +sets the effect used by desktop-shell when starting up (string). Can be .B fade, .B none. By default, the fade animation is used. @@ -444,23 +426,21 @@ for windows, controlling the backlight and zooming the desktop. See .BR weston-bindings (7). Possible values: none, ctrl, alt, super (default) .TP 7 -.BI "num-workspaces=" 6 -defines the number of workspaces (unsigned integer). The user can switch -workspaces by using the -binding+F1, F2 keys. If this key is not set, fall back to one workspace. -.TP 7 .BI "cursor-theme=" theme sets the cursor theme (string). .TP 7 .BI "cursor-size=" 24 sets the cursor size (unsigned integer). -.RE +.\"--------------------------------------------------------------------- .SH "LAUNCHER SECTION" There can be multiple launcher sections, one for each launcher. .TP 7 .BI "icon=" icon sets the path to icon image (string). Svg images are not currently supported. .TP 7 +.BI "displayname=" displayname +sets the display name of the launcher that appears in the tooltip. +.TP 7 .BI "path=" program sets the path to the program that is run by clicking on this launcher (string). It is possible to pass arguments and environment variables to the program. For @@ -471,7 +451,7 @@ example: path=GDK_BACKEND=wayland gnome-terminal --full-screen .in .fi -.PP +.\"--------------------------------------------------------------------- .SH "OUTPUT SECTION" There can be multiple output sections, each corresponding to one output. It is currently only recognized by the drm and x11 backends. @@ -479,10 +459,7 @@ currently only recognized by the drm and x11 backends. .BI "name=" name sets a name for the output (string). The backend uses the name to identify the output. All X11 output names start with a letter X. All -Wayland output names start with the letters WL. The available -output names for DRM backend are listed in the -.B "weston-launch(1)" -output. +Wayland output names start with the letters WL. Examples of usage: .PP .RS 10 @@ -493,12 +470,10 @@ Examples of usage: .BR "WL1 " "Wayland backend, Wayland window no.1" .fi .RE -.RS -.PP +.IP See .B "weston-drm(7)" for more details. -.RE .TP 7 .BI "mode=" mode sets the output mode (string). The mode parameter is handled differently @@ -509,7 +484,6 @@ The DRM backend accepts different modes, along with an option of a modeline stri See .B "weston-drm(7)" for examples of modes-formats supported by DRM backend. -.RE .TP 7 .BI "transform=" normal How you have rotated your monitor from its normal orientation (string). @@ -538,12 +512,9 @@ multiplier, to make them readable. Applications that do support their own output scaling can draw their content in high resolution, in which case they avoid compositor scaling. Weston will not scale the output of such applications, and they are not affected by this multiplier. -.RE -.RS -.PP +.IP An integer, 1 by default, typically configured as 2 or higher when needed, denoting the scaling multiplier for the output. -.RE .TP 7 .BI "icc_profile=" file If option @@ -551,7 +522,6 @@ If option is true, load the given ICC file as the output color profile. This works only on DRM, headless, wayland, and x11 backends, and for remoting and pipewire outputs. -.RE .TP 7 .BI "seat=" name The logical seat name that this output should be associated with. If this @@ -560,7 +530,6 @@ set on it. The expectation is that this functionality will be used in a multiheaded environment with a single compositor for multiple output and input configurations. The default seat is called "default" and will always be present. This seat can be constrained like any other. -.RE .TP 7 .BI "allow_hdcp=" true Allows HDCP support for this output. If set to true, HDCP can be tried for the @@ -569,82 +538,87 @@ default, HDCP support is always allowed for an output. The content-protection can actually be realized, only if the hardware (source and sink) support HDCP, and the backend has the implementation of content-protection protocol. Currently, HDCP is supported by drm-backend. -.RE .TP 7 .BI "app-ids=" app-id[,app_id]* A comma separated list of the IDs of applications to place on this output. These IDs should match the application IDs as set with the xdg_shell.set_app_id request. Currently, this option is supported by kiosk-shell. +.TP 7 +.BI "eotf-mode=" sdr +Sets the EOTF mode on the output. This is used for choosing between standard +dynamic range (SDR) mode and the various high dynamic range (HDR) modes. The +display driver, the graphics card, and the video sink (monitor) need to support +the chosen mode, otherwise the result is undefined. +The mode can be one of the following strings: +.PP +.RS 10 +.nf +.BR "sdr " "traditional gamma, SDR" +.BR "hdr-gamma " "traditional gamma, HDR" +.BR "st2084 " "SMPTE ST 2084, a.k.a Perceptual Quantizer" +.BR "hlg " "Hybrid Log-Gamma (ITU-R BT.2100)" +.fi .RE +.IP +Defaults to +.BR sdr ". Non-SDR modes require " "color-management=true" . +.TP 7 +.BI "color_characteristics=" name +Sets the basic output color characteristics by loading the parameters from the +.B color_characteristics +section with the key +.BI "name=" name +\&. If an ICC profile is also set, the ICC profile takes precedence. +.\"--------------------------------------------------------------------- .SH "INPUT-METHOD SECTION" .TP 7 .BI "path=" "@weston_libexecdir@/weston-keyboard" sets the path of the on screen keyboard input method (string). -.RE -.RE .TP 7 .BI "overlay-keyboard=" false sets weston-keyboard as overlay panel. -.RE -.RE +.\"--------------------------------------------------------------------- .SH "KEYBOARD SECTION" This section contains the following keys: .TP 7 .BI "keymap_rules=" "evdev" sets the keymap rules file (string). Used to map layout and model to input device. -.RE -.RE .TP 7 .BI "keymap_model=" "pc105" sets the keymap model (string). See the Models section in .B "xkeyboard-config(7)." -.RE -.RE .TP 7 .BI "keymap_layout=" "us,de,gb" sets the comma separated list of keyboard layout codes (string). See the Layouts section in .B "xkeyboard-config(7)." -.RE -.RE .TP 7 .BI "keymap_variant=" "euro,,intl" sets the comma separated list of keyboard layout variants (string). The number of variants must be the same as the number of layouts above. See the Layouts section in .B "xkeyboard-config(7)." -.RE -.RE .TP 7 .BI "keymap_options=" "grp:alt_shift_toggle,grp_led:scroll" sets the keymap options (string). See the Options section in .B "xkeyboard-config(7)." -.RE -.RE .TP 7 .BI "repeat-rate=" "40" sets the rate of repeating keys in characters per second (unsigned integer) -.RE -.RE .TP 7 .BI "repeat-delay=" "400" sets the delay in milliseconds since key down until repeating starts (unsigned integer) -.RE -.RE .TP 7 .BI "numlock-on=" "false" sets the default state of the numlock on weston startup for the backends which support it. -.RE -.RE .TP 7 .BI "vt-switching=" "true" Whether to allow the use of Ctrl+Alt+Fn key combinations to switch away from the compositor's virtual console. -.RE -.RE +.\"--------------------------------------------------------------------- .SH "TERMINAL SECTION" Contains settings for the weston terminal application (weston-terminal). It allows to customize the font and shell of the command line interface. @@ -652,35 +626,33 @@ allows to customize the font and shell of the command line interface. .BI "font=" "DejaVu Sans Mono" sets the font of the terminal (string). For a good experience it is recommended to use monospace fonts. In case the font is not found, the default one is used. -.RE -.RE .TP 7 .BI "font-size=" "14" sets the size of the terminal font (unsigned integer). -.RE -.RE .TP 7 .BI "term=" "xterm-256color" The terminal shell (string). Sets the $TERM variable. -.RE -.RE +.\"--------------------------------------------------------------------- .SH "XWAYLAND SECTION" .TP 7 .BI "path=" "@xserver_path@" sets the path to the xserver to run (string). -.RE -.RE +.\"--------------------------------------------------------------------- .SH "SCREEN-SHARE SECTION" .TP 7 .BI "command=" "@weston_bindir@/weston --backend=rdp-backend.so \ ---shell=fullscreen-shell.so --no-clients-resize" +--shell=fullscreen-shell.so --no-clients-resize --no-config" sets the command to start a fullscreen-shell server for screen sharing (string). -.RE -.RE .TP 7 .BI "start-on-startup=" "false" If set to true, start screen sharing of all outputs available on Weston startup. Set to false by default. +.\"--------------------------------------------------------------------- +Set to false by default. When using this option make sure you enable --no-config +to avoid re-loading the screen-share module and implictly trigger screen-sharing +for the RDP output already performing the screen share. Alternatively, you could +also supply a different configuration file, by using --config /path/to/config/file, +and make sure that the configuration file doesn't load the screen-share module. .RE .RE .SH "AUTOLAUNCH SECTION" @@ -688,13 +660,74 @@ Set to false by default. .BI "path=" "/usr/bin/echo" Path to an executable file to run after startup. This file is executed in parallel to Weston, so it does not have to immediately exit. Defaults to empty. -.RE .TP 7 .BI "watch=" "false" If set to true, quit Weston after the auto-launched executable exits. Set to false by default. -.RE -.RE +.\"--------------------------------------------------------------------- +.SH "COLOR_CHARACTERISTICS SECTION" +Each +.B color_characteristics +section records one set of basic display or monitor color characterisation +parameters. The parameters are defined in CTA-861-H specification as Static +Metadata Type 1, and they can also be found in EDID. The parameters are +divided into groups. Each group must be given either fully or not at all. +.PP +Each section should be named with +.B name +key by which it can be referenced from other sections. A metadata section is +just a collection of parameter values and does nothing on its own. It has an +effect only when referenced from elsewhere. +.PP +See +.BR output " section key " color_characteristics . +.TP 7 +.BI "name=" name +An arbitrary name for this section. You can choose any name you want as long as +it does not contain the colon +.RB ( : ) +character. Names with at least one colon are reserved. +.SS Primaries group +.TP 7 +.BI "red_x=" x +.TQ +.BI "red_y=" y +.TQ +.BI "green_x=" x +.TQ +.BI "green_y=" y +.TQ +.BI "blue_x=" x +.TQ +.BI "blue_y=" y +The CIE 1931 xy chromaticity coordinates of the display primaries. +These floating point values must reside between 0.0 and 1.0, inclusive. +.SS White point group +.TP 7 +.BI "white_x=" x +.TQ +.BI "white_y=" y +The CIE 1931 xy chromaticity coordinates of the display white point. +These floating point values must reside between 0.0 and 1.0, inclusive. +.SS Independent parameters +Each parameter listed here has its own group and therefore can be given +alone. +.TP 7 +.BI "max_L=" L +Display's desired maximum content luminance (peak) +.IR L \~cd/m², +a floating point value in the range 0.0\(en100000.0. +.TP 7 +.BI "min_L=" L +Display's desired minimum content luminance +.IR L \~cd/m², +a floating point value in the range 0.0\(en100000.0. +.TP 7 +.BI "maxFALL=" L +Display's desired maximum frame-average light level +.IR L \~cd/m², +a floating point value in the range 0.0\(en100000.0. +.\"--------------------------------------------------------------------- .SH "SEE ALSO" .BR weston (1), .BR weston-bindings (7), diff --git a/man/weston.man b/man/weston.man index c453a7d3..654c0dae 100644 --- a/man/weston.man +++ b/man/weston.man @@ -14,17 +14,9 @@ modesetting via DRM), as an X client, or inside another Wayland server instance. Weston supports fundamentally different graphical user interface paradigms via -shell plugins. Two plugins are provided: the desktop shell, and the tablet +shell plugins. Two plugins are provided: the desktop shell, and the kiosk shell. -When weston is started as the first windowing system (i.e. not under X nor -under another Wayland server), it should be done with the command -.B weston-launch -to set up proper privileged access to devices. If your system supports -the logind D-Bus API and the support has been built into weston as well, -it is possible to start weston with just -.BR weston . - Weston also supports X clients via .BR XWayland ", see below." . @@ -361,9 +353,9 @@ http://wayland.freedesktop.org/ .\" *************************************************************** .SH EXAMPLES .IP "Launch Weston with the DRM backend on a VT" -weston-launch +weston .IP "Launch Weston with the DRM backend and XWayland support" -weston-launch -- --xwayland +weston --xwayland .IP "Launch Weston (wayland-1) nested in another Weston instance (wayland-0)" WAYLAND_DISPLAY=wayland-0 weston -Swayland-1 .IP "From an X terminal, launch Weston with the x11 backend" diff --git a/meson.build b/meson.build index 8b3d46e7..1418267b 100644 --- a/meson.build +++ b/meson.build @@ -1,16 +1,16 @@ project('weston', 'c', - version: '10.0.5', + version: '11.0.3', default_options: [ 'warning_level=3', 'c_std=gnu99', 'b_lundef=true', ], - meson_version: '>= 0.52.1', + meson_version: '>= 0.54.0', license: 'MIT/Expat', ) -libweston_major = 10 +libweston_major = 11 # libweston_revision is manufactured to follow the autotools build's # library file naming, thanks to libtool @@ -27,6 +27,10 @@ else error('Bad versions in meson.build: libweston_major is too low') endif +if not (get_option('launcher-logind') or get_option('launcher-libseat')) + error('At least one launcher must be enabled') +endif + dir_prefix = get_option('prefix') dir_bin = join_paths(dir_prefix, get_option('bindir')) dir_data = join_paths(dir_prefix, get_option('datadir')) @@ -117,8 +121,6 @@ config_h.set_quoted('LIBEXECDIR', dir_libexec) config_h.set_quoted('MODULEDIR', dir_module_weston) config_h.set_quoted('LIBWESTON_MODULEDIR', dir_module_libweston) -config_h.set10('TEST_GL_RENDERER', get_option('test-gl-renderer')) - backend_default = get_option('backend-default') if backend_default == 'auto' foreach b : [ 'headless', 'x11', 'wayland', 'drm' ] @@ -139,28 +141,18 @@ if dep_xkbcommon.version().version_compare('>= 0.5.0') config_h.set('HAVE_XKBCOMMON_COMPOSE', '1') endif -if get_option('deprecated-wl-shell') - warning('Support for the deprecated wl_shell interface is enabled.') - warning('This feature will be removed in a future version.') - config_h.set('HAVE_DEPRECATED_WL_SHELL', '1') -endif - -dep_wayland_server = dependency('wayland-server', version: '>= 1.18.0') -dep_wayland_client = dependency('wayland-client', version: '>= 1.18.0') +dep_wayland_server = dependency('wayland-server', version: '>= 1.20.0') +dep_wayland_client = dependency('wayland-client', version: '>= 1.20.0') dep_pixman = dependency('pixman-1', version: '>= 0.25.2') dep_libinput = dependency('libinput', version: '>= 0.8.0') dep_libevdev = dependency('libevdev') dep_libm = cc.find_library('m') dep_libdl = cc.find_library('dl') -dep_libdrm = dependency('libdrm', version: '>= 2.4.95') +dep_libdrm = dependency('libdrm', version: '>= 2.4.108') dep_libdrm_headers = dep_libdrm.partial_dependency(compile_args: true) dep_threads = dependency('threads') -dep_libdrm_version = dep_libdrm.version() -if dep_libdrm_version.version_compare('>=2.4.107') - message('Found libdrm with human format modifier support.') - config_h.set('HAVE_HUMAN_FORMAT_MODIFIER', '1') -endif +dep_lcms2 = dependency('lcms2', version: '>= 2.9', required: false) prog_python = import('python').find_installation('python3') files_xxd_py = files('tools/xxd.py') @@ -170,8 +162,8 @@ subdir('include') subdir('protocol') subdir('shared') subdir('libweston') -subdir('libweston-desktop') subdir('xwayland') +subdir('shell-utils') subdir('compositor') subdir('desktop-shell') subdir('fullscreen-shell') diff --git a/meson_options.txt b/meson_options.txt index 8a527d74..055457d5 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -44,16 +44,10 @@ option( value: true, description: 'Weston backend: X11 (nested)' ) -option( - 'deprecated-backend-fbdev', - type: 'boolean', - value: false, - description: 'Weston backend: fbdev (deprecated)' -) option( 'backend-default', type: 'combo', - choices: [ 'auto', 'drm', 'wayland', 'x11', 'fbdev', 'headless' ], + choices: [ 'auto', 'drm', 'wayland', 'x11', 'headless', 'rdp' ], value: 'drm', description: 'Default backend when no parent display server detected' ) @@ -65,13 +59,6 @@ option( description: 'Weston renderer: EGL / OpenGL ES 2.x' ) -option( - 'deprecated-weston-launch', - type: 'boolean', - value: false, - description: 'Deprecated weston launcher for systems without logind' -) - option( 'xwayland', type: 'boolean', @@ -138,13 +125,6 @@ option( description: 'Weston desktop shell: default helper client selection' ) -option( - 'deprecated-wl-shell', - type: 'boolean', - value: false, - description: 'Enable the deprecated wl_shell protocol' -) - option( 'color-management-lcms', type: 'boolean', @@ -152,10 +132,16 @@ option( description: 'Compositor color management: Little CMS' ) option( - 'color-management-colord', + 'deprecated-color-management-static', type: 'boolean', - value: true, - description: 'Compositor color management: colord (requires lcms)' + value: false, + description: 'DEPRECATED: color management plugin cms-static' +) +option( + 'deprecated-color-management-colord', + type: 'boolean', + value: false, + description: 'DEPRECATED: color management plugin cms-colord (requires cms-static)' ) option( @@ -168,7 +154,7 @@ option( option( 'launcher-libseat', type: 'boolean', - value: false, + value: true, description: 'Compositor: support libseat' ) @@ -230,12 +216,6 @@ option( value: false, description: 'Tests: consider skip to be a failure' ) -option( - 'test-gl-renderer', - type: 'boolean', - value: true, - description: 'Tests: allow running with GL-renderer' -) option( 'doc', type: 'boolean', diff --git a/pipewire/pipewire-plugin.c b/pipewire/pipewire-plugin.c index b194f4c6..748ba492 100644 --- a/pipewire/pipewire-plugin.c +++ b/pipewire/pipewire-plugin.c @@ -29,6 +29,7 @@ #include "backend.h" #include "libweston-internal.h" #include "shared/timespec-util.h" +#include "shared/string-helpers.h" #include #include @@ -66,7 +67,6 @@ struct weston_pipewire { struct pipewire_output { struct weston_output *output; - void (*saved_destroy)(struct weston_output *output); int (*saved_enable)(struct weston_output *output); int (*saved_disable)(struct weston_output *output); int (*saved_start_repaint_loop)(struct weston_output *output); @@ -149,6 +149,16 @@ lookup_pipewire_output(struct weston_output *base_output) struct weston_pipewire *pipewire = weston_pipewire_get(c); struct pipewire_output *output; + /* XXX: This could happen on the compositor shutdown path with our + * destroy listener being removed, and pipewire_output_destroy() being + * called as a virtual destructor. + * + * See https://gitlab.freedesktop.org/wayland/weston/-/issues/591 for + * an alternative to the shutdown sequence. + */ + if (!pipewire) + return NULL; + wl_list_for_each(output, &pipewire->output_list, link) { if (output->output == base_output) return output; @@ -310,17 +320,19 @@ pipewire_output_destroy(struct weston_output *base_output) struct pipewire_output *output = lookup_pipewire_output(base_output); struct weston_mode *mode, *next; + if (!output) + return; + + weston_head_release(output->head); + wl_list_for_each_safe(mode, next, &base_output->mode_list, link) { wl_list_remove(&mode->link); free(mode); } - output->saved_destroy(base_output); - pw_stream_destroy(output->stream); wl_list_remove(&output->link); - weston_head_release(output->head); free(output->head); free(output); } @@ -535,14 +547,12 @@ pipewire_output_create(struct weston_compositor *c, char *name) pw_stream_add_listener(output->stream, &output->stream_listener, &stream_events, output); - output->output = api->create_output(c, name); + output->output = api->create_output(c, name, pipewire_output_destroy); if (!output->output) { weston_log("Cannot create virtual output\n"); goto err; } - output->saved_destroy = output->output->destroy; - output->output->destroy = pipewire_output_destroy; output->saved_enable = output->output->enable; output->output->enable = pipewire_output_enable; output->saved_disable = output->output->disable; @@ -550,7 +560,7 @@ pipewire_output_create(struct weston_compositor *c, char *name) output->pipewire = pipewire; wl_list_insert(pipewire->output_list.prev, &output->link); - asprintf(&remoting_name, "%s-%s", connector_name, name); + str_printf(&remoting_name, "%s-%s", connector_name, name); weston_head_init(head, remoting_name); weston_head_set_subpixel(head, WL_OUTPUT_SUBPIXEL_NONE); weston_head_set_monitor_strings(head, make, model, serial_number); @@ -634,13 +644,19 @@ weston_pipewire_destroy(struct wl_listener *l, void *data) { struct weston_pipewire *pipewire = wl_container_of(l, pipewire, destroy_listener); + struct pipewire_output *p_output, *p_output_next; weston_log_scope_destroy(pipewire->debug); pipewire->debug = NULL; + wl_list_for_each_safe(p_output, p_output_next, &pipewire->output_list, link) + pipewire_output_destroy(p_output->output); + wl_event_source_remove(pipewire->loop_source); pw_loop_leave(pipewire->loop); pw_loop_destroy(pipewire->loop); + + free(pipewire); } static struct weston_pipewire * diff --git a/protocol/meson.build b/protocol/meson.build index 7d869dad..ba52089b 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -1,11 +1,13 @@ dep_scanner = dependency('wayland-scanner', native: true) prog_scanner = find_program(dep_scanner.get_pkgconfig_variable('wayland_scanner')) -dep_wp = dependency('wayland-protocols', version: '>= 1.24') -dir_wp_base = dep_wp.get_pkgconfig_variable('pkgdatadir') +dep_wp = dependency('wayland-protocols', version: '>= 1.26', + fallback: ['wayland-protocols', 'wayland_protocols']) +dir_wp_base = dep_wp.get_variable(pkgconfig: 'pkgdatadir', internal: 'pkgdatadir') install_data( [ + 'weston-content-protection.xml', 'weston-debug.xml', 'weston-direct-display.xml', ], @@ -13,19 +15,20 @@ install_data( ) generated_protocols = [ - [ 'input-method', 'v1' ], - [ 'input-timestamps', 'v1' ], + [ 'input-method', 'unstable', 'v1' ], + [ 'input-timestamps', 'unstable', 'v1' ], [ 'ivi-application', 'internal' ], [ 'ivi-hmi-controller', 'internal' ], - [ 'fullscreen-shell', 'v1' ], - [ 'linux-dmabuf', 'v1' ], - [ 'linux-explicit-synchronization', 'v1' ], + [ 'fullscreen-shell', 'unstable', 'v1' ], + [ 'linux-dmabuf', 'unstable', 'v1' ], + [ 'linux-explicit-synchronization', 'unstable', 'v1' ], [ 'presentation-time', 'stable' ], - [ 'pointer-constraints', 'v1' ], - [ 'relative-pointer', 'v1' ], - [ 'tablet', 'v2' ], + [ 'pointer-constraints', 'unstable', 'v1' ], + [ 'relative-pointer', 'unstable', 'v1' ], + [ 'single-pixel-buffer', 'staging', 'v1' ], + [ 'tablet', 'unstable', 'v2' ], [ 'text-cursor-position', 'internal' ], - [ 'text-input', 'v1' ], + [ 'text-input', 'unstable', 'v1' ], [ 'viewporter', 'stable' ], [ 'weston-debug', 'internal' ], [ 'weston-desktop-shell', 'internal' ], @@ -34,8 +37,8 @@ generated_protocols = [ [ 'weston-test', 'internal' ], [ 'weston-touch-calibration', 'internal' ], [ 'weston-direct-display', 'internal' ], - [ 'xdg-output', 'v1' ], - [ 'xdg-shell', 'v6' ], + [ 'xdg-output', 'unstable', 'v1' ], + [ 'xdg-shell', 'unstable', 'v6' ], [ 'xdg-shell', 'stable' ], ] @@ -47,9 +50,12 @@ foreach proto: generated_protocols elif proto[1] == 'stable' base_file = proto_name xml_path = '@0@/stable/@1@/@1@.xml'.format(dir_wp_base, base_file) - else - base_file = '@0@-unstable-@1@'.format(proto_name, proto[1]) + elif proto[1] == 'unstable' + base_file = '@0@-unstable-@1@'.format(proto_name, proto[2]) xml_path = '@0@/unstable/@1@/@2@.xml'.format(dir_wp_base, proto_name, base_file) + elif proto[1] == 'staging' + base_file = '@0@-@1@'.format(proto_name, proto[2]) + xml_path = '@0@/staging/@1@/@2@.xml'.format(dir_wp_base, proto_name, base_file) endif foreach output_type: [ 'client-header', 'server-header', 'private-code' ] diff --git a/remoting/remoting-plugin.c b/remoting/remoting-plugin.c index e5f5ca4a..4d4dea7e 100644 --- a/remoting/remoting-plugin.c +++ b/remoting/remoting-plugin.c @@ -47,6 +47,7 @@ #include "shared/helpers.h" #include "shared/timespec-util.h" #include "shared/weston-drm-fourcc.h" +#include "shared/string-helpers.h" #include "backend.h" #include "libweston-internal.h" @@ -94,7 +95,6 @@ static const struct remoted_output_support_gbm_format supported_formats[] = { struct remoted_output { struct weston_output *output; - void (*saved_destroy)(struct weston_output *output); int (*saved_enable)(struct weston_output *output); int (*saved_disable)(struct weston_output *output); int (*saved_start_repaint_loop)(struct weston_output *output); @@ -512,6 +512,16 @@ lookup_remoted_output(struct weston_output *output) struct weston_remoting *remoting = weston_remoting_get(c); struct remoted_output *remoted_output; + /* XXX: This could happen on the compositor shutdown path with our + * destroy listener being removed, and remoting_output_destroy() being + * called as a virtual destructor. + * + * See https://gitlab.freedesktop.org/wayland/weston/-/issues/591 for + * an alternative to the shutdown sequence. + */ + if (!remoting) + return NULL; + wl_list_for_each(remoted_output, &remoting->output_list, link) { if (remoted_output->output == output) return remoted_output; @@ -636,13 +646,16 @@ remoting_output_destroy(struct weston_output *output) struct remoted_output *remoted_output = lookup_remoted_output(output); struct weston_mode *mode, *next; + if (!remoted_output) + return; + + weston_head_release(remoted_output->head); + wl_list_for_each_safe(mode, next, &output->mode_list, link) { wl_list_remove(&mode->link); free(mode); } - remoted_output->saved_destroy(output); - remoting_gst_pipeline_deinit(remoted_output); remoting_gstpipe_release(&remoted_output->gstpipe); @@ -652,7 +665,6 @@ remoting_output_destroy(struct weston_output *output) free(remoted_output->gst_pipeline); wl_list_remove(&remoted_output->link); - weston_head_release(remoted_output->head); free(remoted_output->head); free(remoted_output); } @@ -762,14 +774,12 @@ remoting_output_create(struct weston_compositor *c, char *name) goto err; } - output->output = api->create_output(c, name); + output->output = api->create_output(c, name, remoting_output_destroy); if (!output->output) { weston_log("Can not create virtual output\n"); goto err; } - output->saved_destroy = output->output->destroy; - output->output->destroy = remoting_output_destroy; output->saved_enable = output->output->enable; output->output->enable = remoting_output_enable; output->saved_disable = output->output->disable; @@ -777,7 +787,7 @@ remoting_output_create(struct weston_compositor *c, char *name) output->remoting = remoting; wl_list_insert(remoting->output_list.prev, &output->link); - asprintf(&remoting_name, "%s-%s", connector_name, name); + str_printf(&remoting_name, "%s-%s", connector_name, name); weston_head_init(head, remoting_name); weston_head_set_subpixel(head, WL_OUTPUT_SUBPIXEL_NONE); weston_head_set_monitor_strings(head, make, model, serial_number); diff --git a/shared/cairo-util.c b/shared/cairo-util.c index 3bdf9654..30670939 100644 --- a/shared/cairo-util.c +++ b/shared/cairo-util.c @@ -40,6 +40,7 @@ #include #ifdef HAVE_PANGO +#include #include #endif @@ -275,7 +276,6 @@ tile_source(cairo_t *cr, cairo_surface_t *surface, pattern = cairo_pattern_create_for_surface (surface); cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST); cairo_set_source(cr, pattern); - cairo_pattern_destroy(pattern); for (i = 0; i < 4; i++) { fx = i & 1; @@ -328,6 +328,9 @@ tile_source(cairo_t *cr, cairo_surface_t *surface, cairo_rectangle(cr, x + width - margin, y + top_margin, margin, height - margin - top_margin); cairo_fill(cr); + + cairo_pattern_destroy(pattern); + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); } void @@ -488,16 +491,26 @@ theme_destroy(struct theme *t) static PangoLayout * create_layout(cairo_t *cr, const char *title) { + PangoFontMap *fontmap; + PangoContext *context; PangoLayout *layout; PangoFontDescription *desc; - layout = pango_cairo_create_layout(cr); + fontmap = pango_cairo_font_map_new(); + context = pango_font_map_create_context(fontmap); + g_object_unref(fontmap); + pango_cairo_font_map_set_default(NULL); + pango_cairo_update_context(cr, context); + layout = pango_layout_new(context); + g_object_unref(context); + if (title) { pango_layout_set_text(layout, title, -1); desc = pango_font_description_from_string("sans-serif Bold 10"); pango_layout_set_font_description(layout, desc); pango_font_description_free(desc); } + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT); pango_layout_set_auto_dir (layout, FALSE); @@ -565,6 +578,8 @@ theme_render_frame(struct theme *t, PangoLayout *title_layout; PangoRectangle logical; + cairo_save(cr); + title_layout = create_layout(cr, title); pango_layout_get_pixel_extents (title_layout, NULL, &logical); @@ -606,6 +621,11 @@ theme_render_frame(struct theme *t, cairo_set_source_rgb(cr, 0.4, 0.4, 0.4); SHOW_TEXT(cr); } + +#ifdef HAVE_PANGO + cairo_restore(cr); + g_object_unref(title_layout); +#endif } } @@ -662,3 +682,17 @@ theme_get_location(struct theme *t, int x, int y, return location; } + +/** Cleanup static Cairo/Pango data + * + * Using Cairo, Pango, PangoCairo, and fontconfig, ends up leaving a trail of + * thread-cached data behind us. Clean up what we can. + */ +void +cleanup_after_cairo(void) +{ + cairo_debug_reset_static_data(); +#ifdef HAVE_PANGO + FcFini(); +#endif +} diff --git a/shared/cairo-util.h b/shared/cairo-util.h index 6fd11f6b..a7d4329c 100644 --- a/shared/cairo-util.h +++ b/shared/cairo-util.h @@ -161,6 +161,10 @@ frame_width(struct frame *frame); int32_t frame_height(struct frame *frame); +void +frame_decoration_sizes(struct frame *frame, int32_t *top, int32_t *bottom, + int32_t *left, int32_t *right); + void frame_interior(struct frame *frame, int32_t *x, int32_t *y, int32_t *width, int32_t *height); @@ -230,4 +234,7 @@ frame_double_touch_up(struct frame *frame, void *data, int32_t id); void frame_repaint(struct frame *frame, cairo_t *cr); +void +cleanup_after_cairo(void); + #endif diff --git a/shared/config-parser.c b/shared/config-parser.c index ed120d50..30779ae4 100644 --- a/shared/config-parser.c +++ b/shared/config-parser.c @@ -129,8 +129,7 @@ config_section_get_entry(struct weston_config_section *section, return NULL; } -WL_EXPORT -struct weston_config_section * +WL_EXPORT struct weston_config_section * weston_config_get_section(struct weston_config *config, const char *section, const char *key, const char *value) { @@ -152,8 +151,7 @@ weston_config_get_section(struct weston_config *config, const char *section, return NULL; } -WL_EXPORT -int +WL_EXPORT int weston_config_section_get_int(struct weston_config_section *section, const char *key, int32_t *value, int32_t default_value) @@ -175,8 +173,7 @@ weston_config_section_get_int(struct weston_config_section *section, return 0; } -WL_EXPORT -int +WL_EXPORT int weston_config_section_get_uint(struct weston_config_section *section, const char *key, uint32_t *value, uint32_t default_value) @@ -212,8 +209,7 @@ weston_config_section_get_uint(struct weston_config_section *section, return 0; } -WL_EXPORT -int +WL_EXPORT int weston_config_section_get_color(struct weston_config_section *section, const char *key, uint32_t *color, uint32_t default_color) @@ -250,8 +246,7 @@ weston_config_section_get_color(struct weston_config_section *section, return 0; } -WL_EXPORT -int +WL_EXPORT int weston_config_section_get_double(struct weston_config_section *section, const char *key, double *value, double default_value) @@ -276,8 +271,7 @@ weston_config_section_get_double(struct weston_config_section *section, return 0; } -WL_EXPORT -int +WL_EXPORT int weston_config_section_get_string(struct weston_config_section *section, const char *key, char **value, const char *default_value) @@ -299,8 +293,7 @@ weston_config_section_get_string(struct weston_config_section *section, return 0; } -WL_EXPORT -int +WL_EXPORT int weston_config_section_get_bool(struct weston_config_section *section, const char *key, bool *value, bool default_value) @@ -327,8 +320,7 @@ weston_config_section_get_bool(struct weston_config_section *section, return 0; } -WL_EXPORT -const char * +WL_EXPORT const char * weston_config_get_name_from_env(void) { const char *name; @@ -389,42 +381,15 @@ section_add_entry(struct weston_config_section *section, return entry; } -WL_EXPORT -struct weston_config * -weston_config_parse(const char *name) +static bool +weston_config_parse_internal(struct weston_config *config, FILE *fp) { - FILE *fp; - char line[512], *p; - struct stat filestat; - struct weston_config *config; struct weston_config_section *section = NULL; - int i, fd; - - config = zalloc(sizeof *config); - if (config == NULL) - return NULL; + char line[512], *p; + int i; wl_list_init(&config->section_list); - fd = open_config_file(config, name); - if (fd == -1) { - free(config); - return NULL; - } - - if (fstat(fd, &filestat) < 0 || - !S_ISREG(filestat.st_mode)) { - close(fd); - free(config); - return NULL; - } - - fp = fdopen(fd, "r"); - if (fp == NULL) { - free(config); - return NULL; - } - while (fgets(line, sizeof line, fp)) { switch (line[0]) { case '#': @@ -435,9 +400,7 @@ weston_config_parse(const char *name) if (!p || p[1] != '\n') { fprintf(stderr, "malformed " "section header: %s\n", line); - fclose(fp); - weston_config_destroy(config); - return NULL; + return false; } p[0] = '\0'; section = config_add_section(config, &line[1]); @@ -447,9 +410,7 @@ weston_config_parse(const char *name) if (!p || p == line || !section) { fprintf(stderr, "malformed " "config line: %s\n", line); - fclose(fp); - weston_config_destroy(config); - return NULL; + return false; } p[0] = '\0'; @@ -466,20 +427,77 @@ weston_config_parse(const char *name) } } + return true; +} + +WESTON_EXPORT_FOR_TESTS struct weston_config * +weston_config_parse_fp(FILE *file) +{ + struct weston_config *config = zalloc(sizeof(*config)); + + if (config == NULL) + return NULL; + + if (!weston_config_parse_internal(config, file)) { + weston_config_destroy(config); + return NULL; + } + + return config; +} + +WL_EXPORT struct weston_config * +weston_config_parse(const char *name) +{ + FILE *fp; + struct stat filestat; + struct weston_config *config; + int fd; + bool ret; + + config = zalloc(sizeof *config); + if (config == NULL) + return NULL; + + fd = open_config_file(config, name); + if (fd == -1) { + free(config); + return NULL; + } + + if (fstat(fd, &filestat) < 0 || + !S_ISREG(filestat.st_mode)) { + close(fd); + free(config); + return NULL; + } + + fp = fdopen(fd, "r"); + if (fp == NULL) { + close(fd); + free(config); + return NULL; + } + + ret = weston_config_parse_internal(config, fp); + fclose(fp); + if (!ret) { + weston_config_destroy(config); + return NULL; + } + return config; } -WL_EXPORT -const char * +WL_EXPORT const char * weston_config_get_full_path(struct weston_config *config) { return config == NULL ? NULL : config->path; } -WL_EXPORT -int +WL_EXPORT int weston_config_next_section(struct weston_config *config, struct weston_config_section **section, const char **name) @@ -502,8 +520,7 @@ weston_config_next_section(struct weston_config *config, return 1; } -WL_EXPORT -void +WL_EXPORT void weston_config_destroy(struct weston_config *config) { struct weston_config_section *s, *next_s; diff --git a/shared/frame.c b/shared/frame.c index e8a5cad6..cf58d66b 100644 --- a/shared/frame.c +++ b/shared/frame.c @@ -493,27 +493,40 @@ frame_resize(struct frame *frame, int32_t width, int32_t height) } void -frame_resize_inside(struct frame *frame, int32_t width, int32_t height) +frame_decoration_sizes(struct frame *frame, int32_t *top, int32_t *bottom, + int32_t *left, int32_t *right) { struct theme *t = frame->theme; - int decoration_width, decoration_height, titlebar_height; + /* Top may have a titlebar */ if (frame->title || !wl_list_empty(&frame->buttons)) - titlebar_height = t->titlebar_height; + *top = t->titlebar_height; else - titlebar_height = t->width; + *top = t->width; - if (frame->flags & FRAME_FLAG_MAXIMIZED) { - decoration_width = t->width * 2; - decoration_height = t->width + titlebar_height; - } else { - decoration_width = (t->width + t->margin) * 2; - decoration_height = t->width + - titlebar_height + t->margin * 2; - } + /* All other sides have the basic frame thickness */ + *bottom = t->width; + *right = t->width; + *left = t->width; + + if (frame->flags & FRAME_FLAG_MAXIMIZED) + return; + + /* Not maximized, add shadows */ + *top += t->margin; + *bottom += t->margin; + *left += t->margin; + *right += t->margin; +} + +void +frame_resize_inside(struct frame *frame, int32_t width, int32_t height) +{ + int32_t top, bottom, left, right; - frame_resize(frame, width + decoration_width, - height + decoration_height); + frame_decoration_sizes(frame, &top, &bottom, &left, &right); + frame_resize(frame, width + left + right, + height + top + bottom); } int32_t diff --git a/shared/helpers.h b/shared/helpers.h index 1688b8ea..7b722096 100644 --- a/shared/helpers.h +++ b/shared/helpers.h @@ -159,6 +159,18 @@ do { \ tmp___; }) #endif +/** Private symbol export for tests + * + * Symbols tagged with this are private libweston functions that are exported + * only for the test suite to allow unit testing. Nothing else internal or + * external to libweston is allowed to use these exports. + * + * Therefore, the ABI exported with this tag is completely unversioned, and + * is allowed to break at any time without any indication or version bump. + * This may happen in all git branches, including stable release branches. + */ +#define WESTON_EXPORT_FOR_TESTS __attribute__ ((visibility("default"))) + #ifdef __cplusplus } #endif diff --git a/shared/matrix.c b/shared/matrix.c index 4e8d6b40..0df22840 100644 --- a/shared/matrix.c +++ b/shared/matrix.c @@ -31,12 +31,7 @@ #include #include -#ifdef UNIT_TEST -#define WL_EXPORT -#else #include -#endif - #include @@ -169,7 +164,7 @@ find_pivot(double *column, unsigned k) * LU decomposition, forward and back substitution: Chapter 3. */ -MATRIX_TEST_EXPORT inline int +static int matrix_invert(double *A, unsigned *p, const struct weston_matrix *matrix) { unsigned i, j, k; @@ -204,7 +199,7 @@ matrix_invert(double *A, unsigned *p, const struct weston_matrix *matrix) return 0; } -MATRIX_TEST_EXPORT inline void +static void inverse_transform(const double *LU, const unsigned *p, float *v) { /* Solve A * x = v, when we have P * A = L * U. diff --git a/shared/meson.build b/shared/meson.build index 9a4af530..8b71fb87 100644 --- a/shared/meson.build +++ b/shared/meson.build @@ -4,7 +4,7 @@ srcs_libshared = [ 'signal.c', 'file-util.c', 'os-compatibility.c', - 'xalloc.c', + 'process-util.c', ] deps_libshared = [dep_wayland_client, dep_wayland_server] @@ -22,6 +22,26 @@ dep_libshared = declare_dependency( dependencies: deps_libshared ) +xcb_dep = dependency('xcb', required: false) + +xcb_xwayland_srcs = [ + 'xcb-xwayland.c', +] + +lib_xcb_xwayland = static_library( + 'xcb-xwayland', + xcb_xwayland_srcs, + include_directories: common_inc, + dependencies: [ xcb_dep ], + install: false, + build_by_default: false, +) + +dep_xcb_xwayland = declare_dependency( + link_with: lib_xcb_xwayland, + include_directories: public_inc, +) + srcs_cairo_shared = [ 'image-loader.c', 'cairo-util.c', @@ -38,10 +58,11 @@ deps_cairo_shared = [ dep_pango = dependency('pango', required: false) dep_pangocairo = dependency('pangocairo', required: false) +dep_fontconfig = dependency('fontconfig', required: false) dep_glib = dependency('glib-2.0', version: '>= 2.36', required: false) -if dep_pango.found() and dep_pangocairo.found() and dep_glib.found() - deps_cairo_shared += [ dep_pango, dep_pangocairo, dep_glib ] +if dep_pango.found() and dep_pangocairo.found() and dep_fontconfig.found() and dep_glib.found() + deps_cairo_shared += [ dep_pango, dep_pangocairo, dep_fontconfig, dep_glib ] config_h.set('HAVE_PANGO', '1') endif diff --git a/shared/os-compatibility.c b/shared/os-compatibility.c index f687f92f..a9d91c52 100644 --- a/shared/os-compatibility.c +++ b/shared/os-compatibility.c @@ -40,10 +40,25 @@ #define READONLY_SEALS (F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE) +int +os_fd_clear_cloexec(int fd) +{ + int flags; + + flags = fcntl(fd, F_GETFD); + if (flags == -1) + return -1; + + if (fcntl(fd, F_SETFD, flags & ~(int)FD_CLOEXEC) == -1) + return -1; + + return 0; +} + int os_fd_set_cloexec(int fd) { - long flags; + int flags; if (fd == -1) return -1; diff --git a/shared/os-compatibility.h b/shared/os-compatibility.h index 6aaa2688..85f65854 100644 --- a/shared/os-compatibility.h +++ b/shared/os-compatibility.h @@ -30,6 +30,9 @@ #include +int +os_fd_clear_cloexec(int fd); + int os_fd_set_cloexec(int fd); diff --git a/shared/process-util.c b/shared/process-util.c new file mode 100644 index 00000000..e36c6470 --- /dev/null +++ b/shared/process-util.c @@ -0,0 +1,270 @@ +/* + * Copyright 2022 Collabora, Ltd. + * + * 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. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "helpers.h" +#include "os-compatibility.h" +#include "process-util.h" +#include "string-helpers.h" + +extern char **environ; /* defined by libc */ + +void +fdstr_update_str1(struct fdstr *s) +{ + snprintf(s->str1, sizeof(s->str1), "%d", s->fds[1]); +} + +void +fdstr_set_fd1(struct fdstr *s, int fd) +{ + s->fds[0] = -1; + s->fds[1] = fd; + fdstr_update_str1(s); +} + +bool +fdstr_clear_cloexec_fd1(struct fdstr *s) +{ + return os_fd_clear_cloexec(s->fds[1]) >= 0; +} + +void +fdstr_close_all(struct fdstr *s) +{ + unsigned i; + + for (i = 0; i < ARRAY_LENGTH(s->fds); i++) { + close(s->fds[i]); + s->fds[i] = -1; + } +} + +void +custom_env_init_from_environ(struct custom_env *env) +{ + char **it; + char **ep; + + wl_array_init(&env->envp); + env->env_finalized = false; + wl_array_init(&env->argp); + env->arg_finalized = false; + + for (it = environ; *it; it++) { + ep = wl_array_add(&env->envp, sizeof *ep); + assert(ep); + *ep = strdup(*it); + assert(*ep); + } +} + +void +custom_env_fini(struct custom_env *env) +{ + char **p; + + wl_array_for_each(p, &env->envp) + free(*p); + wl_array_release(&env->envp); + + wl_array_for_each(p, &env->argp) + free(*p); + wl_array_release(&env->argp); +} + +static char ** +custom_env_get_env_var(struct custom_env *env, const char *name) +{ + char **ep; + size_t name_len = strlen(name); + + wl_array_for_each(ep, &env->envp) { + char *entry = *ep; + + if (strncmp(entry, name, name_len) == 0 && + entry[name_len] == '=') { + return ep; + } + } + + return NULL; +} + +void +custom_env_add_arg(struct custom_env *env, const char *arg) +{ + char **ap; + + assert(!env->arg_finalized); + + ap = wl_array_add(&env->argp, sizeof *ap); + assert(ap); + + *ap = strdup(arg); + assert(*ap); +} + +void +custom_env_set_env_var(struct custom_env *env, const char *name, const char *value) +{ + char **ep; + + assert(strchr(name, '=') == NULL); + assert(!env->env_finalized); + + ep = custom_env_get_env_var(env, name); + if (ep) + free(*ep); + else + ep = wl_array_add(&env->envp, sizeof *ep); + assert(ep); + + str_printf(ep, "%s=%s", name, value); + assert(*ep); +} + +/** + * Add information from a parsed exec string to a custom_env + * + * An 'exec string' is a string in the format: + * ENVFOO=bar ENVBAR=baz /path/to/exec --arg anotherarg + * + * This function will parse such a string and add the specified environment + * variables (in the format KEY=value) up until it sees a non-environment + * string, after which point every entry will be interpreted as a new + * argument. + * + * Entries are space-separated; there is no support for quoting. + */ +void +custom_env_add_from_exec_string(struct custom_env *env, const char *exec_str) +{ + char *dup_path = strdup(exec_str); + char *start = dup_path; + + assert(dup_path); + + /* Build the environment array (if any) by handling any number of + * equal-separated key=value at the start of the string, split by + * spaces; uses "foo=bar baz=quux meh argh" as the example, where + * "foo=bar" and "baz=quux" should go into the environment, and + * "meh" should be executed with "argh" as its first argument */ + while (*start) { + char *k = NULL, *v = NULL; + char *p; + + /* Leaves us with "foo\0bar baz=quux meh argh", with k pointing + * to "foo" and v pointing to "bar baz=quux meh argh" */ + for (p = start; *p && !isspace(*p); p++) { + if (*p == '=') { + *p++ = '\0'; + k = start; + v = p; + break; + } + } + + if (!v) + break; + + /* Walk to the next space or NUL, filling any trailing spaces + * with NUL, to give us "foo\0bar\0\0baz=quux meh argh". + * k will point to "foo", v will point to "bar", and + * start will point to "baz=quux meh argh". */ + while (*p && !isspace(*p)) + p++; + while (*p && isspace(*p)) + *p++ = '\0'; + start = p; + + custom_env_set_env_var(env, k, v); + } + + /* Now build the argv array by splitting on spaces */ + while (*start) { + char *p; + bool valid = false; + + for (p = start; *p && !isspace(*p); p++) + valid = true; + + if (!valid) + break; + + while (*p && isspace(*p)) + *p++ = '\0'; + + custom_env_add_arg(env, start); + start = p; + } + + free(dup_path); +} + +char *const * +custom_env_get_envp(struct custom_env *env) +{ + char **ep; + + assert(!env->env_finalized); + + /* add terminating NULL */ + ep = wl_array_add(&env->envp, sizeof *ep); + assert(ep); + *ep = NULL; + + env->env_finalized = true; + + return env->envp.data; +} + +char *const * +custom_env_get_argp(struct custom_env *env) +{ + char **ap; + + assert(!env->arg_finalized); + + /* add terminating NULL */ + ap = wl_array_add(&env->argp, sizeof *ap); + assert(ap); + *ap = NULL; + + env->arg_finalized = true; + + return env->argp.data; +} diff --git a/shared/process-util.h b/shared/process-util.h new file mode 100644 index 00000000..05543f6f --- /dev/null +++ b/shared/process-util.h @@ -0,0 +1,94 @@ +/* + * Copyright 2022 Collabora, Ltd. + * + * 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. + */ + +#include +#include +#include +#include + +#include + +#include "os-compatibility.h" +#include "string-helpers.h" + +/** + * A container for file descriptors and their string representations, designed + * to be used when forking child processes. + * + * fds[0] is generally used as the file descriptor held within the parent + * process to communicate with the child, and fds[1] as the child's counterpart. + * + * str1[] is used as a string representation of fds[1]. + */ +struct fdstr { + char str1[12]; + int fds[2]; +}; + +void +fdstr_update_str1(struct fdstr *s); + +void +fdstr_set_fd1(struct fdstr *s, int fd); + +bool +fdstr_clear_cloexec_fd1(struct fdstr *s); + +void +fdstr_close_all(struct fdstr *s); + + +/** + * A container for environment variables and/or process arguments, designed to + * be used when forking child processes, as setenv() and anything which + * allocates memory cannot be used between fork() and exec(). + */ +struct custom_env { + struct wl_array envp; + bool env_finalized; + struct wl_array argp; + bool arg_finalized; +}; + +void +custom_env_init_from_environ(struct custom_env *env); + +void +custom_env_fini(struct custom_env *env); + +void +custom_env_set_env_var(struct custom_env *env, const char *name, const char *value); + +void +custom_env_add_arg(struct custom_env *env, const char *arg); + +void +custom_env_add_from_exec_string(struct custom_env *env, const char *exec_str); + +char *const * +custom_env_get_envp(struct custom_env *env); + +char *const * +custom_env_get_argp(struct custom_env *env); diff --git a/shared/string-helpers.h b/shared/string-helpers.h index e7174b21..302cfa81 100644 --- a/shared/string-helpers.h +++ b/shared/string-helpers.h @@ -95,4 +95,10 @@ str_printf(char **str_out, const char *fmt, ...) *str_out = NULL; } +static inline const char * +yesno(bool cond) +{ + return cond ? "yes" : "no"; +} + #endif /* WESTON_STRING_HELPERS_H */ diff --git a/shared/weston-drm-fourcc.h b/shared/weston-drm-fourcc.h index 41b86ed8..0a013f79 100644 --- a/shared/weston-drm-fourcc.h +++ b/shared/weston-drm-fourcc.h @@ -38,4 +38,12 @@ #define DRM_FORMAT_XYUV8888 fourcc_code('X', 'Y', 'U', 'V') /* [31:0] X:Y:Cb:Cr 8:8:8:8 little endian */ #endif +#ifndef DRM_FORMAT_XBGR16161616 +#define DRM_FORMAT_XBGR16161616 fourcc_code('X', 'B', '4', '8') /* [63:0] x:B:G:R 16:16:16:16 little endian */ +#endif + +#ifndef DRM_FORMAT_ABGR16161616 +#define DRM_FORMAT_ABGR16161616 fourcc_code('A', 'B', '4', '8') /* [63:0] A:B:G:R 16:16:16:16 little endian */ +#endif + #endif diff --git a/shared/xalloc.h b/shared/xalloc.h index cd39dd8b..a2747f9c 100644 --- a/shared/xalloc.h +++ b/shared/xalloc.h @@ -1,5 +1,6 @@ /* * Copyright © 2008 Kristian Høgsberg + * Copyright 2022 Collabora, Ltd. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -30,19 +31,32 @@ extern "C" { #endif -#include +#include #include +#include #include -#include +static inline void * +abort_oom_if_null(void *p) +{ + static const char oommsg[] = ": out of memory\n"; + size_t written __attribute__((unused)); -void * -fail_on_null(void *p, size_t size, char *file, int32_t line); + if (p) + return p; -#define xmalloc(s) (fail_on_null(malloc(s), (s), __FILE__, __LINE__)) -#define xzalloc(s) (fail_on_null(zalloc(s), (s), __FILE__, __LINE__)) -#define xstrdup(s) (fail_on_null(strdup(s), 0, __FILE__, __LINE__)) -#define xrealloc(p, s) (fail_on_null(realloc(p, s), (s), __FILE__, __LINE__)) + written = write(STDERR_FILENO, program_invocation_short_name, + strlen(program_invocation_short_name)); + written = write(STDERR_FILENO, oommsg, strlen(oommsg)); + + abort(); +} + +#define xmalloc(s) (abort_oom_if_null(malloc(s))) +#define xzalloc(s) (abort_oom_if_null(calloc(1, s))) +#define xcalloc(n, s) (abort_oom_if_null(calloc(n, s))) +#define xstrdup(s) (abort_oom_if_null(strdup(s))) +#define xrealloc(p, s) (abort_oom_if_null(realloc(p, s))) #ifdef __cplusplus } diff --git a/shared/xcb-xwayland.c b/shared/xcb-xwayland.c new file mode 100644 index 00000000..bd64e4c9 --- /dev/null +++ b/shared/xcb-xwayland.c @@ -0,0 +1,161 @@ +/* + * Copyright © 2011 Intel Corporation + * Copyright © 2021 Collabora, Ltd. + * + * 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. + */ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "shared/helpers.h" +#include "xcb-xwayland.h" + +const char * +get_atom_name(xcb_connection_t *c, xcb_atom_t atom) +{ + xcb_get_atom_name_cookie_t cookie; + xcb_get_atom_name_reply_t *reply; + xcb_generic_error_t *e; + static char buffer[64]; + + if (atom == XCB_ATOM_NONE) + return "None"; + + cookie = xcb_get_atom_name(c, atom); + reply = xcb_get_atom_name_reply(c, cookie, &e); + + if (reply) { + snprintf(buffer, sizeof buffer, "%.*s", + xcb_get_atom_name_name_length(reply), + xcb_get_atom_name_name(reply)); + } else { + snprintf(buffer, sizeof buffer, "(atom %u)", atom); + } + + free(reply); + + return buffer; +} + +void +x11_get_atoms(xcb_connection_t *connection, struct atom_x11 *atom) +{ + unsigned int i; + +#define F(field) offsetof(struct atom_x11, field) + + static const struct { const char *name; int offset; } atoms[] = { + { "WM_PROTOCOLS", F(wm_protocols) }, + { "WM_NORMAL_HINTS", F(wm_normal_hints) }, + { "WM_TAKE_FOCUS", F(wm_take_focus) }, + { "WM_DELETE_WINDOW", F(wm_delete_window) }, + { "WM_STATE", F(wm_state) }, + { "WM_S0", F(wm_s0) }, + { "WM_CLIENT_MACHINE", F(wm_client_machine) }, + { "WM_CHANGE_STATE", F(wm_change_state) }, + { "_NET_FRAME_EXTENTS", F(net_frame_extents) }, + { "_NET_WM_CM_S0", F(net_wm_cm_s0) }, + { "_NET_WM_NAME", F(net_wm_name) }, + { "_NET_WM_PID", F(net_wm_pid) }, + { "_NET_WM_ICON", F(net_wm_icon) }, + { "_NET_WM_STATE", F(net_wm_state) }, + { "_NET_WM_STATE_MAXIMIZED_VERT", F(net_wm_state_maximized_vert) }, + { "_NET_WM_STATE_MAXIMIZED_HORZ", F(net_wm_state_maximized_horz) }, + { "_NET_WM_STATE_FULLSCREEN", F(net_wm_state_fullscreen) }, + { "_NET_WM_USER_TIME", F(net_wm_user_time) }, + { "_NET_WM_ICON_NAME", F(net_wm_icon_name) }, + { "_NET_WM_DESKTOP", F(net_wm_desktop) }, + { "_NET_WM_WINDOW_TYPE", F(net_wm_window_type) }, + + { "_NET_WM_WINDOW_TYPE_DESKTOP", F(net_wm_window_type_desktop) }, + { "_NET_WM_WINDOW_TYPE_DOCK", F(net_wm_window_type_dock) }, + { "_NET_WM_WINDOW_TYPE_TOOLBAR", F(net_wm_window_type_toolbar) }, + { "_NET_WM_WINDOW_TYPE_MENU", F(net_wm_window_type_menu) }, + { "_NET_WM_WINDOW_TYPE_UTILITY", F(net_wm_window_type_utility) }, + { "_NET_WM_WINDOW_TYPE_SPLASH", F(net_wm_window_type_splash) }, + { "_NET_WM_WINDOW_TYPE_DIALOG", F(net_wm_window_type_dialog) }, + { "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", F(net_wm_window_type_dropdown) }, + { "_NET_WM_WINDOW_TYPE_POPUP_MENU", F(net_wm_window_type_popup) }, + { "_NET_WM_WINDOW_TYPE_TOOLTIP", F(net_wm_window_type_tooltip) }, + { "_NET_WM_WINDOW_TYPE_NOTIFICATION", F(net_wm_window_type_notification) }, + { "_NET_WM_WINDOW_TYPE_COMBO", F(net_wm_window_type_combo) }, + { "_NET_WM_WINDOW_TYPE_DND", F(net_wm_window_type_dnd) }, + { "_NET_WM_WINDOW_TYPE_NORMAL", F(net_wm_window_type_normal) }, + + { "_NET_WM_MOVERESIZE", F(net_wm_moveresize) }, + { "_NET_SUPPORTING_WM_CHECK", F(net_supporting_wm_check) }, + + { "_NET_SUPPORTED", F(net_supported) }, + { "_NET_ACTIVE_WINDOW", F(net_active_window) }, + { "_MOTIF_WM_HINTS", F(motif_wm_hints) }, + { "CLIPBOARD", F(clipboard) }, + { "CLIPBOARD_MANAGER", F(clipboard_manager) }, + { "TARGETS", F(targets) }, + { "UTF8_STRING", F(utf8_string) }, + { "_WL_SELECTION", F(wl_selection) }, + { "INCR", F(incr) }, + { "TIMESTAMP", F(timestamp) }, + { "MULTIPLE", F(multiple) }, + { "UTF8_STRING" , F(utf8_string) }, + { "COMPOUND_TEXT", F(compound_text) }, + { "TEXT", F(text) }, + { "STRING", F(string) }, + { "WINDOW", F(window) }, + { "text/plain;charset=utf-8", F(text_plain_utf8) }, + { "text/plain", F(text_plain) }, + { "XdndSelection", F(xdnd_selection) }, + { "XdndAware", F(xdnd_aware) }, + { "XdndEnter", F(xdnd_enter) }, + { "XdndLeave", F(xdnd_leave) }, + { "XdndDrop", F(xdnd_drop) }, + { "XdndStatus", F(xdnd_status) }, + { "XdndFinished", F(xdnd_finished) }, + { "XdndTypeList", F(xdnd_type_list) }, + { "XdndActionCopy", F(xdnd_action_copy) }, + { "_XWAYLAND_ALLOW_COMMITS", F(allow_commits) }, + { "WL_SURFACE_ID", F(wl_surface_id) }, + { "_WESTON_FOCUS_PING", F(weston_focus_ping) }, + }; + + xcb_intern_atom_cookie_t cookies[ARRAY_LENGTH(atoms)]; + + for (i = 0; i < ARRAY_LENGTH(atoms); i++) + cookies[i] = + xcb_intern_atom(connection, 0, strlen(atoms[i].name), atoms[i].name); + + for (i = 0; i < ARRAY_LENGTH(atoms); i++) { + xcb_intern_atom_reply_t *reply_atom; + reply_atom = xcb_intern_atom_reply(connection, cookies[i], NULL); + assert(reply_atom); + + xcb_atom_t rr_atom = reply_atom->atom; + *(xcb_atom_t *) ((char *) atom + atoms[i].offset) = rr_atom; + + free(reply_atom); + } +} diff --git a/shared/xcb-xwayland.h b/shared/xcb-xwayland.h new file mode 100644 index 00000000..38eea82a --- /dev/null +++ b/shared/xcb-xwayland.h @@ -0,0 +1,107 @@ +/* + * Copyright © 2011 Intel Corporation + * Copyright © 2021 Collabora, Ltd. + * + * 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. + */ + +#pragma once + +#include + +#define SEND_EVENT_MASK (0x80) +#define EVENT_TYPE(event) ((event)->response_type & ~SEND_EVENT_MASK) + +struct atom_x11 { + xcb_atom_t wm_protocols; + xcb_atom_t wm_normal_hints; + xcb_atom_t wm_take_focus; + xcb_atom_t wm_delete_window; + xcb_atom_t wm_state; + xcb_atom_t wm_s0; + xcb_atom_t wm_client_machine; + xcb_atom_t wm_change_state; + xcb_atom_t net_frame_extents; + xcb_atom_t net_wm_cm_s0; + xcb_atom_t net_wm_name; + xcb_atom_t net_wm_pid; + xcb_atom_t net_wm_icon; + xcb_atom_t net_wm_state; + xcb_atom_t net_wm_state_maximized_vert; + xcb_atom_t net_wm_state_maximized_horz; + xcb_atom_t net_wm_state_fullscreen; + xcb_atom_t net_wm_user_time; + xcb_atom_t net_wm_icon_name; + xcb_atom_t net_wm_desktop; + xcb_atom_t net_wm_window_type; + xcb_atom_t net_wm_window_type_desktop; + xcb_atom_t net_wm_window_type_dock; + xcb_atom_t net_wm_window_type_toolbar; + xcb_atom_t net_wm_window_type_menu; + xcb_atom_t net_wm_window_type_utility; + xcb_atom_t net_wm_window_type_splash; + xcb_atom_t net_wm_window_type_dialog; + xcb_atom_t net_wm_window_type_dropdown; + xcb_atom_t net_wm_window_type_popup; + xcb_atom_t net_wm_window_type_tooltip; + xcb_atom_t net_wm_window_type_notification; + xcb_atom_t net_wm_window_type_combo; + xcb_atom_t net_wm_window_type_dnd; + xcb_atom_t net_wm_window_type_normal; + xcb_atom_t net_wm_moveresize; + xcb_atom_t net_supporting_wm_check; + xcb_atom_t net_supported; + xcb_atom_t net_active_window; + xcb_atom_t motif_wm_hints; + xcb_atom_t clipboard; + xcb_atom_t clipboard_manager; + xcb_atom_t targets; + xcb_atom_t utf8_string; + xcb_atom_t wl_selection; + xcb_atom_t incr; + xcb_atom_t timestamp; + xcb_atom_t multiple; + xcb_atom_t compound_text; + xcb_atom_t text; + xcb_atom_t string; + xcb_atom_t window; + xcb_atom_t text_plain_utf8; + xcb_atom_t text_plain; + xcb_atom_t xdnd_selection; + xcb_atom_t xdnd_aware; + xcb_atom_t xdnd_enter; + xcb_atom_t xdnd_leave; + xcb_atom_t xdnd_drop; + xcb_atom_t xdnd_status; + xcb_atom_t xdnd_finished; + xcb_atom_t xdnd_type_list; + xcb_atom_t xdnd_action_copy; + xcb_atom_t wl_surface_id; + xcb_atom_t allow_commits; + xcb_atom_t weston_focus_ping; +}; + +const char * +get_atom_name(xcb_connection_t *c, xcb_atom_t atom); + +void +x11_get_atoms(xcb_connection_t *connection, struct atom_x11 *atom); diff --git a/shell-utils/meson.build b/shell-utils/meson.build new file mode 100644 index 00000000..88aaca8c --- /dev/null +++ b/shell-utils/meson.build @@ -0,0 +1,5 @@ +dep_shell_utils = declare_dependency( + sources: 'shell-utils.c', + include_directories: include_directories('.'), + dependencies: dep_libweston_public, +) diff --git a/shared/shell-utils.c b/shell-utils/shell-utils.c similarity index 65% rename from shared/shell-utils.c rename to shell-utils/shell-utils.c index a301eefc..25978734 100644 --- a/shared/shell-utils.c +++ b/shell-utils/shell-utils.c @@ -25,7 +25,7 @@ */ #include "config.h" -#include "shared/shell-utils.h" +#include "shell-utils.h" #include struct weston_output * @@ -138,38 +138,108 @@ surface_get_label(struct weston_surface *surface, char *buf, size_t len) c ? " of " : "", c ?: ""); } -struct weston_view * -create_solid_color_surface(struct weston_compositor *compositor, - struct weston_solid_color_surface *ss, - float x, float y, int w, int h) +struct weston_curtain * +weston_curtain_create(struct weston_compositor *compositor, + struct weston_curtain_params *params) { + struct weston_curtain *curtain; struct weston_surface *surface = NULL; + struct weston_buffer_reference *buffer_ref; struct weston_view *view; + curtain = zalloc(sizeof(*curtain)); + if (curtain == NULL) + goto err; + surface = weston_surface_create(compositor); - if (surface == NULL) { - weston_log("no memory\n"); - return NULL; - } + if (surface == NULL) + goto err_curtain; + view = weston_view_create(surface); - if (view == NULL) { - weston_log("no memory\n"); - weston_surface_destroy(surface); - return NULL; - } + if (view == NULL) + goto err_surface; - surface->committed = ss->surface_committed; - surface->committed_private = ss->surface_private; + buffer_ref = weston_buffer_create_solid_rgba(compositor, + params->r, + params->g, + params->b, + params->a); + if (buffer_ref == NULL) + goto err_view; + + curtain->view = view; + curtain->buffer_ref = buffer_ref; + + weston_surface_set_label_func(surface, params->get_label); + surface->committed = params->surface_committed; + surface->committed_private = params->surface_private; + + weston_surface_attach_solid(surface, buffer_ref, params->width, + params->height); - weston_surface_set_color(surface, ss->r, ss->g, ss->b, 1.0); - weston_surface_set_label_func(surface, ss->get_label); - pixman_region32_fini(&surface->opaque); - pixman_region32_init_rect(&surface->opaque, 0, 0, w, h); pixman_region32_fini(&surface->input); - pixman_region32_init_rect(&surface->input, 0, 0, w, h); + if (params->capture_input) { + pixman_region32_init_rect(&surface->input, 0, 0, + params->width, params->height); + } else { + pixman_region32_init(&surface->input); + } - weston_surface_set_size(surface, w, h); - weston_view_set_position(view, x, y); + weston_surface_map(surface); - return view; + weston_view_set_position(view, params->x, params->y); + + return curtain; + +err_view: + weston_view_destroy(view); +err_surface: + weston_surface_unref(surface); +err_curtain: + free(curtain); +err: + weston_log("no memory\n"); + return NULL; +} + +void +weston_curtain_destroy(struct weston_curtain *curtain) +{ + struct weston_surface *surface = curtain->view->surface; + + weston_view_destroy(curtain->view); + weston_surface_unref(surface); + weston_buffer_destroy_solid(curtain->buffer_ref); + free(curtain); +} + +uint32_t +weston_shell_get_binding_modifier(struct weston_config *config, + uint32_t default_mod) +{ + struct weston_config_section *shell_section = NULL; + char *mod_string = NULL; + uint32_t mod = default_mod; + + if (config) + shell_section = weston_config_get_section(config, "shell", NULL, NULL); + + if (shell_section) + weston_config_section_get_string(shell_section, + "binding-modifier", &mod_string, "super"); + + if (!mod_string || !strcmp(mod_string, "none")) + mod = default_mod; + else if (!strcmp(mod_string, "super")) + mod = MODIFIER_SUPER; + else if (!strcmp(mod_string, "alt")) + mod = MODIFIER_ALT; + else if (!strcmp(mod_string, "ctrl")) + mod = MODIFIER_CTRL; + else if (!strcmp(mod_string, "shift")) + mod = MODIFIER_SHIFT; + + free(mod_string); + + return mod; } diff --git a/shared/shell-utils.h b/shell-utils/shell-utils.h similarity index 74% rename from shared/shell-utils.h rename to shell-utils/shell-utils.h index b25de1a0..b7a1df9a 100644 --- a/shared/shell-utils.h +++ b/shell-utils/shell-utils.h @@ -26,12 +26,22 @@ #include "shared/helpers.h" #include -/* parameter for create_solid_color_surface() */ -struct weston_solid_color_surface { +#include +#include + +/* parameter for weston_curtain_create() */ +struct weston_curtain_params { int (*get_label)(struct weston_surface *es, char *buf, size_t len); void (*surface_committed)(struct weston_surface *es, int32_t sx, int32_t sy); void *surface_private; - float r, g, b; + float r, g, b, a; + int x, y, width, height; + bool capture_input; +}; + +struct weston_curtain { + struct weston_view *view; + struct weston_buffer_reference *buffer_ref; }; struct weston_output * @@ -50,9 +60,12 @@ surface_subsurfaces_boundingbox(struct weston_surface *surface, int32_t *x, int surface_get_label(struct weston_surface *surface, char *buf, size_t len); -/* helper to create a view w/ a color - */ -struct weston_view * -create_solid_color_surface(struct weston_compositor *compositor, - struct weston_solid_color_surface *ss, - float x, float y, int w, int h); +/* helper to create a view w/ a color */ +struct weston_curtain * +weston_curtain_create(struct weston_compositor *compositor, + struct weston_curtain_params *params); +void +weston_curtain_destroy(struct weston_curtain *curtain); + +uint32_t +weston_shell_get_binding_modifier(struct weston_config *config, uint32_t default_mod); diff --git a/tests/alpha-blending-test.c b/tests/alpha-blending-test.c index e2916be9..8ea9de36 100644 --- a/tests/alpha-blending-test.c +++ b/tests/alpha-blending-test.c @@ -27,9 +27,11 @@ #include "config.h" #include +#include #include "weston-test-client-helper.h" #include "weston-test-fixture-compositor.h" +#include "image-iter.h" #include "color_util.h" struct setup_args { @@ -80,19 +82,6 @@ fixture_setup(struct weston_test_harness *harness, const struct setup_args *arg) } DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, my_setup_args, meta); -static void -set_opaque_rect(struct client *client, - struct surface *surface, - const struct rectangle *rect) -{ - struct wl_region *region; - - region = wl_compositor_create_region(client->wl_compositor); - wl_region_add(region, rect->x, rect->y, rect->width, rect->height); - wl_surface_set_opaque_region(surface->wl_surface, region); - wl_region_destroy(region); -} - static uint32_t premult_color(uint32_t a, uint32_t r, uint32_t g, uint32_t b) { @@ -106,39 +95,17 @@ premult_color(uint32_t a, uint32_t r, uint32_t g, uint32_t b) return c; } -static void -unpremult_float(struct color_float *cf) -{ - if (cf->a == 0.0f) { - cf->r = 0.0f; - cf->g = 0.0f; - cf->b = 0.0f; - } else { - cf->r /= cf->a; - cf->g /= cf->a; - cf->b /= cf->a; - } -} - static void fill_alpha_pattern(struct buffer *buf) { - void *pixels; - int stride_bytes; - int w, h; + struct image_header ih = image_header_from(buf->image); int y; - assert(pixman_image_get_format(buf->image) == PIXMAN_a8r8g8b8); - - pixels = pixman_image_get_data(buf->image); - stride_bytes = pixman_image_get_stride(buf->image); - w = pixman_image_get_width(buf->image); - h = pixman_image_get_height(buf->image); - - assert(w == BLOCK_WIDTH * ALPHA_STEPS); + assert(ih.pixman_format == PIXMAN_a8r8g8b8); + assert(ih.width == BLOCK_WIDTH * ALPHA_STEPS); - for (y = 0; y < h; y++) { - uint32_t *row = pixels + y * stride_bytes; + for (y = 0; y < ih.height; y++) { + uint32_t *row = image_header_get_row_u32(&ih, y); uint32_t step; for (step = 0; step < (uint32_t)ALPHA_STEPS; step++) { @@ -153,98 +120,38 @@ fill_alpha_pattern(struct buffer *buf) } } - -static bool -compare_float(float ref, float dst, int x, const char *chan, float *max_diff) -{ -#if 0 - /* - * This file can be loaded in Octave for visualization. - * - * S = load('compare_float_dump.txt'); - * - * rvec = S(S(:,1)==114, 2:3); - * gvec = S(S(:,1)==103, 2:3); - * bvec = S(S(:,1)==98, 2:3); - * - * figure - * subplot(3, 1, 1); - * plot(rvec(:,1), rvec(:,2) .* 255, 'r'); - * subplot(3, 1, 2); - * plot(gvec(:,1), gvec(:,2) .* 255, 'g'); - * subplot(3, 1, 3); - * plot(bvec(:,1), bvec(:,2) .* 255, 'b'); - */ - static FILE *fp = NULL; - - if (!fp) - fp = fopen("compare_float_dump.txt", "w"); - fprintf(fp, "%d %d %f\n", chan[0], x, dst - ref); - fflush(fp); -#endif - - float diff = fabsf(ref - dst); - - if (diff > *max_diff) - *max_diff = diff; - - /* - * Allow for +/- 1.5 code points of error in non-linear 8-bit channel - * value. This is necessary for the BLEND_LINEAR case. - * - * With llvmpipe, we could go as low as +/- 0.65 code points of error - * and still pass. - * - * AMD Polaris 11 would be ok with +/- 1.0 code points error threshold - * if not for one particular case of blending (a=254, r=0) into r=255, - * which results in error of 1.29 code points. - */ - if (diff < 1.5f / 255.f) - return true; - - testlog("x=%d %s: ref %f != dst %f, delta %f\n", - x, chan, ref, dst, dst - ref); - - return false; -} - enum blend_space { BLEND_NONLINEAR, BLEND_LINEAR, }; -static bool -verify_sRGB_blend_a8r8g8b8(uint32_t bg32, uint32_t fg32, uint32_t dst32, - int x, struct color_float *max_diff, - enum blend_space space) +static void +compare_sRGB_blend_a8r8g8b8(uint32_t bg32, uint32_t fg32, uint32_t dst32, + struct rgb_diff_stat *diffstat, + enum blend_space space) { struct color_float bg = a8r8g8b8_to_float(bg32); struct color_float fg = a8r8g8b8_to_float(fg32); struct color_float dst = a8r8g8b8_to_float(dst32); struct color_float ref; - bool ok = true; + int i; - unpremult_float(&bg); - unpremult_float(&fg); - unpremult_float(&dst); + bg = color_float_unpremult(bg); + fg = color_float_unpremult(fg); + dst = color_float_unpremult(dst); if (space == BLEND_LINEAR) { sRGB_linearize(&bg); sRGB_linearize(&fg); } - ref.r = (1.0f - fg.a) * bg.r + fg.a * fg.r; - ref.g = (1.0f - fg.a) * bg.g + fg.a * fg.g; - ref.b = (1.0f - fg.a) * bg.b + fg.a * fg.b; + for (i = 0; i < COLOR_CHAN_NUM; i++) + ref.rgb[i] = (1.0f - fg.a) * bg.rgb[i] + fg.a * fg.rgb[i]; if (space == BLEND_LINEAR) sRGB_delinearize(&ref); - ok = compare_float(ref.r, dst.r, x, "r", &max_diff->r) && ok; - ok = compare_float(ref.g, dst.g, x, "g", &max_diff->g) && ok; - ok = compare_float(ref.b, dst.b, x, "b", &max_diff->b) && ok; - - return ok; + rgb_diff_stat_update(diffstat, &ref, &dst, &fg); } static uint8_t @@ -280,26 +187,46 @@ pixels_monotonic(const uint32_t *row, int x) static void * get_middle_row(struct buffer *buf) { - const int y = (BLOCK_WIDTH - 1) / 2; /* middle row */ - void *pixels; - int stride_bytes; + struct image_header ih = image_header_from(buf->image); - assert(pixman_image_get_width(buf->image) >= BLOCK_WIDTH * ALPHA_STEPS); - assert(pixman_image_get_height(buf->image) >= BLOCK_WIDTH); + assert(ih.width >= BLOCK_WIDTH * ALPHA_STEPS); + assert(ih.height >= BLOCK_WIDTH); - pixels = pixman_image_get_data(buf->image); - stride_bytes = pixman_image_get_stride(buf->image); - return pixels + y * stride_bytes; + return image_header_get_row_u32(&ih, (BLOCK_WIDTH - 1) / 2); } static bool check_blend_pattern(struct buffer *bg, struct buffer *fg, struct buffer *shot, enum blend_space space) { + FILE *dump = NULL; +#if 0 + /* + * This file can be loaded in Octave for visualization. Find the script + * in tests/visualization/weston_plot_rgb_diff_stat.m and call it with + * + * weston_plot_rgb_diff_stat('alpha_blend-f01-dump.txt', 255, 8) + */ + dump = fopen_dump_file("dump"); +#endif + + /* + * Allow for +/- 1.5 code points of error in non-linear 8-bit channel + * value. This is necessary for the BLEND_LINEAR case. + * + * With llvmpipe, we could go as low as +/- 0.65 code points of error + * and still pass. + * + * AMD Polaris 11 would be ok with +/- 1.0 code points error threshold + * if not for one particular case of blending (a=254, r=0) into r=255, + * which results in error of 1.29 code points. + */ + const float tolerance = 1.5f / 255.f; + uint32_t *bg_row = get_middle_row(bg); uint32_t *fg_row = get_middle_row(fg); uint32_t *shot_row = get_middle_row(shot); - struct color_float max_diff = { 0.0f, 0.0f, 0.0f, 0.0f }; + struct rgb_diff_stat diffstat = { .dump = dump, }; bool ret = true; int x; @@ -307,14 +234,17 @@ check_blend_pattern(struct buffer *bg, struct buffer *fg, struct buffer *shot, if (!pixels_monotonic(shot_row, x)) ret = false; - if (!verify_sRGB_blend_a8r8g8b8(bg_row[x], fg_row[x], - shot_row[x], x, &max_diff, - space)) - ret = false; + compare_sRGB_blend_a8r8g8b8(bg_row[x], fg_row[x], shot_row[x], + &diffstat, space); } - testlog("%s max diff: r=%f, g=%f, b=%f\n", - __func__, max_diff.r, max_diff.g, max_diff.b); + if (diffstat.two_norm.max > tolerance) + ret = false; + + rgb_diff_stat_print(&diffstat, __func__, 8); + + if (dump) + fclose(dump); return ret; } @@ -398,8 +328,8 @@ TEST(alpha_blend) client->surface->width = width; client->surface->height = height; client->surface->buffer = bg; /* pass ownership */ - set_opaque_rect(client, client->surface, - &(struct rectangle){ 0, 0, width, height }); + surface_set_opaque_rect(client->surface, + &(struct rectangle){ 0, 0, width, height }); /* foreground blended content */ fg = create_shm_buffer_a8r8g8b8(client, width, height); diff --git a/tests/color-icc-output-test.c b/tests/color-icc-output-test.c new file mode 100644 index 00000000..63fbfd1f --- /dev/null +++ b/tests/color-icc-output-test.c @@ -0,0 +1,847 @@ +/* + * Copyright 2021 Advanced Micro Devices, Inc. + * Copyright 2020, 2022 Collabora, Ltd. + * + * 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. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include + +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" +#include "color_util.h" +#include "image-iter.h" +#include "lcms_util.h" + +struct lcms_pipeline { + /** + * Color space name + */ + const char *color_space; + /** + * Chromaticities for output profile + */ + cmsCIExyYTRIPLE prim_output; + /** + * tone curve enum + */ + enum transfer_fn pre_fn; + /** + * Transform matrix from sRGB to target chromaticities in prim_output + */ + struct lcmsMAT3 mat; + /** + * matrix from prim_output to XYZ, for example matrix conversion + * sRGB->XYZ, adobeRGB->XYZ, bt2020->XYZ + */ + struct lcmsMAT3 mat2XYZ; + /** + * tone curve enum + */ + enum transfer_fn post_fn; +}; + +static const int WINDOW_WIDTH = 256; +static const int WINDOW_HEIGHT = 24; + +static cmsCIExyY wp_d65 = { 0.31271, 0.32902, 1.0 }; + +enum profile_type { + PTYPE_MATRIX_SHAPER, + PTYPE_CLUT, +}; + +/* + * Using currently destination gamut bigger than source. + * Using https://www.colour-science.org/ we can extract conversion matrix: + * import colour + * colour.matrix_RGB_to_RGB(colour.RGB_COLOURSPACES['sRGB'], colour.RGB_COLOURSPACES['Adobe RGB (1998)'], None) + * colour.matrix_RGB_to_RGB(colour.RGB_COLOURSPACES['sRGB'], colour.RGB_COLOURSPACES['ITU-R BT.2020'], None) + */ + +const struct lcms_pipeline pipeline_sRGB = { + .color_space = "sRGB", + .prim_output = { + .Red = { 0.640, 0.330, 1.0 }, + .Green = { 0.300, 0.600, 1.0 }, + .Blue = { 0.150, 0.060, 1.0 } + }, + .pre_fn = TRANSFER_FN_SRGB_EOTF, + .mat = LCMSMAT3(1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0), + .mat2XYZ = LCMSMAT3(0.436037, 0.385124, 0.143039, + 0.222482, 0.716913, 0.060605, + 0.013922, 0.097078, 0.713899), + .post_fn = TRANSFER_FN_SRGB_EOTF_INVERSE +}; + +const struct lcms_pipeline pipeline_adobeRGB = { + .color_space = "adobeRGB", + .prim_output = { + .Red = { 0.640, 0.330, 1.0 }, + .Green = { 0.210, 0.710, 1.0 }, + .Blue = { 0.150, 0.060, 1.0 } + }, + .pre_fn = TRANSFER_FN_SRGB_EOTF, + .mat = LCMSMAT3( 0.715127, 0.284868, 0.000005, + 0.000001, 0.999995, 0.000004, + -0.000003, 0.041155, 0.958848), + .mat2XYZ = LCMSMAT3(0.609740, 0.205279, 0.149181, + 0.311111, 0.625681, 0.063208, + 0.019469, 0.060879, 0.744552), + .post_fn = TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE +}; + +const struct lcms_pipeline pipeline_BT2020 = { + .color_space = "bt2020", + .prim_output = { + .Red = { 0.708, 0.292, 1.0 }, + .Green = { 0.170, 0.797, 1.0 }, + .Blue = { 0.131, 0.046, 1.0 } + }, + .pre_fn = TRANSFER_FN_SRGB_EOTF, + .mat = LCMSMAT3(0.627402, 0.329292, 0.043306, + 0.069095, 0.919544, 0.011360, + 0.016394, 0.088028, 0.895578), + /* this is equivalent to BT.1886 with zero black level */ + .post_fn = TRANSFER_FN_POWER2_4_EOTF_INVERSE, +}; + +struct setup_args { + struct fixture_metadata meta; + int ref_image_index; + const struct lcms_pipeline *pipeline; + + /** + * Two-norm color error tolerance in units of 1.0/255, computed in + * output electrical space. + * + * Tolerance depends more on the 1D LUT used for the + * inv EOTF than the tested 3D LUT size: + * 9x9x9, 17x17x17, 33x33x33, 127x127x127 + * + * TODO: when we add power-law in the curve enumeration + * in GL-renderer, then we should fix the tolerance + * as the error should reduce a lot. + */ + float tolerance; + + /** + * 3DLUT dimension size + */ + int dim_size; + enum profile_type type; + + /** Two-norm error limit for cLUT DToB->BToD roundtrip */ + float clut_roundtrip_tolerance; +}; + +static const struct setup_args my_setup_args[] = { + /* name, ref img, pipeline, tolerance, dim, profile type, clut tolerance */ + { { "sRGB->sRGB" }, 0, &pipeline_sRGB, 0.0, 0, PTYPE_MATRIX_SHAPER }, + { { "sRGB->adobeRGB" }, 1, &pipeline_adobeRGB, 1.4, 0, PTYPE_MATRIX_SHAPER }, + { { "sRGB->BT2020" }, 2, &pipeline_BT2020, 4.5, 0, PTYPE_MATRIX_SHAPER }, + { { "sRGB->sRGB" }, 0, &pipeline_sRGB, 0.0, 17, PTYPE_CLUT, 0.0005 }, + { { "sRGB->adobeRGB" }, 1, &pipeline_adobeRGB, 1.8, 17, PTYPE_CLUT, 0.0065 }, +}; + +static void +test_roundtrip(uint8_t r, uint8_t g, uint8_t b, cmsPipeline *pip, + struct rgb_diff_stat *stat) +{ + struct color_float in = { .rgb = { r / 255.0, g / 255.0, b / 255.0 } }; + struct color_float out = {}; + + cmsPipelineEvalFloat(in.rgb, out.rgb, pip); + rgb_diff_stat_update(stat, &in, &out, &in); +} + +/* + * Roundtrip verification tests that converting device -> PCS -> device + * results in the original color values close enough. + * + * This ensures that the two pipelines are probably built correctly, and we + * do not have problems with unexpected value clamping or with representing + * (inverse) EOTF curves. + */ +static void +roundtrip_verification(cmsPipeline *DToB, cmsPipeline *BToD, float tolerance) +{ + unsigned r, g, b; + struct rgb_diff_stat stat = {}; + cmsPipeline *pip; + + pip = cmsPipelineDup(DToB); + cmsPipelineCat(pip, BToD); + + /* + * Inverse-EOTF is known to have precision problems near zero, so + * sample near zero densely, the rest can be more sparse to run faster. + */ + for (r = 0; r < 256; r += (r < 15) ? 1 : 8) { + for (g = 0; g < 256; g += (g < 15) ? 1 : 8) { + for (b = 0; b < 256; b += (b < 15) ? 1 : 8) + test_roundtrip(r, g, b, pip, &stat); + } + } + + cmsPipelineFree(pip); + + rgb_diff_stat_print(&stat, "DToB->BToD roundtrip", 8); + assert(stat.two_norm.max < tolerance); +} + +static cmsInt32Number +sampler_matrix(const float src[], float dst[], void *cargo) +{ + const struct lcmsMAT3 *mat = cargo; + struct color_float in = { .r = src[0], .g = src[1], .b = src[2] }; + struct color_float cf; + unsigned i; + + cf = color_float_apply_matrix(mat, in); + + for (i = 0; i < COLOR_CHAN_NUM; i++) + dst[i] = cf.rgb[i]; + + return 1; +} + +static cmsStage * +create_cLUT_from_matrix(cmsContext context_id, const struct lcmsMAT3 *mat, int dim_size) +{ + cmsStage *cLUT_stage; + + cLUT_stage = cmsStageAllocCLutFloat(context_id, dim_size, 3, 3, NULL); + cmsStageSampleCLutFloat(cLUT_stage, sampler_matrix, (void *)mat, 0); + + return cLUT_stage; +} + +/* + * Originally the cLUT profile test attempted to use the AToB/BToA tags. Those + * come with serious limitations though: at most uint16 representation for + * values in a LUT which means LUT entry precision is limited and range is + * [0.0, 1.0]. This poses difficulties such as: + * - for AToB, the resulting PCS XYZ values may need to be > 1.0 + * - for BToA, it is easy to fall outside of device color volume meaning that + * out-of-range values are needed in the 3D LUT + * Working around these could require offsetting and scaling of values + * before and after the 3D LUT, and even that may not always be possible. + * + * DToB/BToD tags do not have most of these problems, because there pipelines + * use float32 representation throughout. We have much more precision, and + * we can mostly use negative and greater than 1.0 values. LUT elements + * still clamp their input to [0.0, 1.0] before applying the LUT. This type of + * pipeline is called multiProcessElement (MPE). + * + * MPE also allows us to represent curves in a few analytical forms. These are + * just enough to represent the EOTF curves we have and their inverses, but + * they do not allow encoding extended EOTF curves or their inverses + * (defined for all real numbers by extrapolation, and mirroring for negative + * inputs). Using MPE curves we avoid the precision problems that arise from + * attempting to represent an inverse-EOTF as a LUT. For the precision issue, + * see: https://gitlab.freedesktop.org/pq/color-and-hdr/-/merge_requests/9 + * + * MPE is not a complete remedy, because 3D LUT inputs are still always clamped + * to [0.0, 1.0]. Therefore a 3D LUT cannot represent the inverse of a matrix + * that can produce negative or greater than 1.0 values without further tricks + * (scaling and offsetting) in the pipeline. Rather than implementing that + * complication, we decided to just not test with such matrices. Therefore + * BT.2020 color space is not used in the cLUT test. AdobeRGB is enough. + */ +static cmsHPROFILE +build_lcms_clut_profile_output(cmsContext context_id, + const struct setup_args *arg) +{ + enum transfer_fn inv_eotf_fn = arg->pipeline->post_fn; + enum transfer_fn eotf_fn = transfer_fn_invert(inv_eotf_fn); + cmsHPROFILE hRGB; + cmsPipeline *DToB0, *BToD0; + cmsStage *stage; + cmsStage *stage_inv_eotf; + cmsStage *stage_eotf; + struct lcmsMAT3 mat2XYZ_inv; + + lcmsMAT3_invert(&mat2XYZ_inv, &arg->pipeline->mat2XYZ); + + hRGB = cmsCreateProfilePlaceholder(context_id); + cmsSetProfileVersion(hRGB, 4.3); + cmsSetDeviceClass(hRGB, cmsSigDisplayClass); + cmsSetColorSpace(hRGB, cmsSigRgbData); + cmsSetPCS(hRGB, cmsSigXYZData); + SetTextTags(hRGB, L"cLut profile"); + + stage_eotf = build_MPE_curve_stage(context_id, eotf_fn); + stage_inv_eotf = build_MPE_curve_stage(context_id, inv_eotf_fn); + + /* + * Pipeline from PCS (optical) to device (electrical) + */ + BToD0 = cmsPipelineAlloc(context_id, 3, 3); + + stage = create_cLUT_from_matrix(context_id, &mat2XYZ_inv, arg->dim_size); + cmsPipelineInsertStage(BToD0, cmsAT_END, stage); + cmsPipelineInsertStage(BToD0, cmsAT_END, cmsStageDup(stage_inv_eotf)); + + cmsWriteTag(hRGB, cmsSigBToD0Tag, BToD0); + cmsLinkTag(hRGB, cmsSigBToD1Tag, cmsSigBToD0Tag); + cmsLinkTag(hRGB, cmsSigBToD2Tag, cmsSigBToD0Tag); + cmsLinkTag(hRGB, cmsSigBToD3Tag, cmsSigBToD0Tag); + + /* + * Pipeline from device (electrical) to PCS (optical) + */ + DToB0 = cmsPipelineAlloc(context_id, 3, 3); + + cmsPipelineInsertStage(DToB0, cmsAT_END, cmsStageDup(stage_eotf)); + stage = create_cLUT_from_matrix(context_id, &arg->pipeline->mat2XYZ, arg->dim_size); + cmsPipelineInsertStage(DToB0, cmsAT_END, stage); + + cmsWriteTag(hRGB, cmsSigDToB0Tag, DToB0); + cmsLinkTag(hRGB, cmsSigDToB1Tag, cmsSigDToB0Tag); + cmsLinkTag(hRGB, cmsSigDToB2Tag, cmsSigDToB0Tag); + cmsLinkTag(hRGB, cmsSigDToB3Tag, cmsSigDToB0Tag); + + roundtrip_verification(DToB0, BToD0, arg->clut_roundtrip_tolerance); + + cmsPipelineFree(BToD0); + cmsPipelineFree(DToB0); + cmsStageFree(stage_eotf); + cmsStageFree(stage_inv_eotf); + + return hRGB; +} + +static cmsHPROFILE +build_lcms_matrix_shaper_profile_output(cmsContext context_id, + const struct lcms_pipeline *pipeline) +{ + cmsToneCurve *arr_curves[3]; + cmsHPROFILE hRGB; + int type_inverse_tone_curve; + double inverse_tone_curve_param[5]; + + assert(find_tone_curve_type(pipeline->post_fn, &type_inverse_tone_curve, + inverse_tone_curve_param)); + + /* + * We are creating output profile and therefore we can use the following: + * calling semantics: + * cmsBuildParametricToneCurve(type_inverse_tone_curve, inverse_tone_curve_param) + * The function find_tone_curve_type sets the type of curve positive if it + * is tone curve and negative if it is inverse. When we create an ICC + * profile we should use a tone curve, the inversion is done by LCMS + * when the profile is used for output. + */ + + arr_curves[0] = arr_curves[1] = arr_curves[2] = + cmsBuildParametricToneCurve(context_id, + (-1) * type_inverse_tone_curve, + inverse_tone_curve_param); + + assert(arr_curves[0]); + hRGB = cmsCreateRGBProfileTHR(context_id, &wp_d65, + &pipeline->prim_output, arr_curves); + assert(hRGB); + + cmsFreeToneCurve(arr_curves[0]); + return hRGB; +} + +static cmsHPROFILE +build_lcms_profile_output(cmsContext context_id, const struct setup_args *arg) +{ + switch (arg->type) { + case PTYPE_MATRIX_SHAPER: + return build_lcms_matrix_shaper_profile_output(context_id, + arg->pipeline); + case PTYPE_CLUT: + return build_lcms_clut_profile_output(context_id, arg); + } + + return NULL; +} + +static char * +build_output_icc_profile(const struct setup_args *arg) +{ + char *profile_name = NULL; + cmsHPROFILE profile = NULL; + char *wd; + int ret; + bool saved; + + wd = realpath(".", NULL); + assert(wd); + if (arg->type == PTYPE_MATRIX_SHAPER) + ret = asprintf(&profile_name, "%s/matrix-shaper-test-%s.icm", wd, + arg->pipeline->color_space); + else + ret = asprintf(&profile_name, "%s/cLUT-test-%s.icm", wd, + arg->pipeline->color_space); + assert(ret > 0); + + profile = build_lcms_profile_output(NULL, arg); + assert(profile); + + saved = cmsSaveProfileToFile(profile, profile_name); + assert(saved); + + cmsCloseProfile(profile); + free(wd); + + return profile_name; +} + +static void +test_lcms_error_logger(cmsContext context_id, + cmsUInt32Number error_code, + const char *text) +{ + testlog("LittleCMS error: %s\n", text); +} + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness, const struct setup_args *arg) +{ + struct compositor_setup setup; + char *file_name; + + cmsSetLogErrorHandler(test_lcms_error_logger); + + compositor_setup_defaults(&setup); + setup.renderer = RENDERER_GL; + setup.backend = WESTON_BACKEND_HEADLESS; + setup.width = WINDOW_WIDTH; + setup.height = WINDOW_HEIGHT; + setup.shell = SHELL_TEST_DESKTOP; + + file_name = build_output_icc_profile(arg); + if (!file_name) + return RESULT_HARD_ERROR; + + weston_ini_setup(&setup, + cfgln("[core]"), + cfgln("color-management=true"), + cfgln("[output]"), + cfgln("name=headless"), + cfgln("icc_profile=%s", file_name)); + + free(file_name); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, my_setup_args, meta); + +static void +gen_ramp_rgb(pixman_image_t *image, int bitwidth, int width_bar) +{ + static const int hue[][COLOR_CHAN_NUM] = { + { 1, 1, 1 }, /* White */ + { 1, 1, 0 }, /* Yellow */ + { 0, 1, 1 }, /* Cyan */ + { 0, 1, 0 }, /* Green */ + { 1, 0, 1 }, /* Magenta */ + { 1, 0, 0 }, /* Red */ + { 0, 0, 1 }, /* Blue */ + }; + const int num_hues = ARRAY_LENGTH(hue); + + struct image_header ih = image_header_from(image); + float val_max; + int x, y; + int hue_index; + int chan; + float value; + unsigned char r, g, b; + uint32_t *pixel; + + float n_steps = width_bar - 1; + + val_max = (1 << bitwidth) - 1; + + for (y = 0; y < ih.height; y++) { + hue_index = (y * num_hues) / (ih.height - 1); + hue_index = MIN(hue_index, num_hues - 1); + + pixel = image_header_get_row_u32(&ih, y); + for (x = 0; x < ih.width; x++, pixel++) { + struct color_float rgb = { .rgb = { 0, 0, 0 } }; + + value = (float)x / (float)(ih.width - 1); + + if (width_bar > 1) + value = floor(value * n_steps) / n_steps; + + for (chan = 0; chan < COLOR_CHAN_NUM; chan++) { + if (hue[hue_index][chan]) + rgb.rgb[chan] = value; + } + + sRGB_delinearize(&rgb); + + r = round(rgb.r * val_max); + g = round(rgb.g * val_max); + b = round(rgb.b * val_max); + + *pixel = (255U << 24) | (r << 16) | (g << 8) | b; + } + } +} + +static bool +process_pipeline_comparison(const struct buffer *src_buf, + const struct buffer *shot_buf, + const struct setup_args * arg) +{ + FILE *dump = NULL; +#if 0 + /* + * This file can be loaded in Octave for visualization. Find the script + * in tests/visualization/weston_plot_rgb_diff_stat.m and call it with + * + * weston_plot_rgb_diff_stat('opaque_pixel_conversion-f05-dump.txt') + */ + dump = fopen_dump_file("dump"); +#endif + + struct image_header ih_src = image_header_from(src_buf->image); + struct image_header ih_shot = image_header_from(shot_buf->image); + int y, x; + struct color_float pix_src; + struct color_float pix_src_pipeline; + struct color_float pix_shot; + struct rgb_diff_stat diffstat = { .dump = dump }; + bool ok; + + /* no point to compare different images */ + assert(ih_src.width == ih_shot.width); + assert(ih_src.height == ih_shot.height); + + for (y = 0; y < ih_src.height; y++) { + uint32_t *row_ptr = image_header_get_row_u32(&ih_src, y); + uint32_t *row_ptr_shot = image_header_get_row_u32(&ih_shot, y); + + for (x = 0; x < ih_src.width; x++) { + pix_src = a8r8g8b8_to_float(row_ptr[x]); + pix_shot = a8r8g8b8_to_float(row_ptr_shot[x]); + + process_pixel_using_pipeline(arg->pipeline->pre_fn, + &arg->pipeline->mat, + arg->pipeline->post_fn, + &pix_src, &pix_src_pipeline); + + rgb_diff_stat_update(&diffstat, + &pix_src_pipeline, &pix_shot, + &pix_src); + } + } + + ok = diffstat.two_norm.max <= arg->tolerance / 255.0f; + + testlog("%s %s %s tolerance %f %s\n", __func__, + ok ? "SUCCESS" : "FAILURE", + arg->meta.name, arg->tolerance, + arg->type == PTYPE_MATRIX_SHAPER ? "matrix-shaper" : "cLUT"); + + rgb_diff_stat_print(&diffstat, __func__, 8); + + if (dump) + fclose(dump); + + return ok; +} + +/* + * Test that opaque client pixels produce the expected output when converted + * from the implicit sRGB input to ICC profile described output. + * + * The groundtruth conversion comes from the struct lcms_pipeline definitions. + * The first error source is converting those to ICC files. The second error + * source is Weston. + */ +TEST(opaque_pixel_conversion) +{ + int seq_no = get_test_fixture_index(); + const struct setup_args *arg = &my_setup_args[seq_no]; + const int width = WINDOW_WIDTH; + const int height = WINDOW_HEIGHT; + const int bitwidth = 8; + const int width_bar = 32; + + struct client *client; + struct buffer *buf; + struct buffer *shot; + struct wl_surface *surface; + bool match; + + client = create_client_and_test_surface(0, 0, width, height); + assert(client); + surface = client->surface->wl_surface; + + buf = create_shm_buffer_a8r8g8b8(client, width, height); + gen_ramp_rgb(buf->image, bitwidth, width_bar); + + wl_surface_attach(surface, buf->proxy, 0, 0); + wl_surface_damage(surface, 0, 0, width, height); + wl_surface_commit(surface); + + shot = capture_screenshot_of_output(client); + assert(shot); + + match = verify_image(shot, "shaper_matrix", arg->ref_image_index, + NULL, seq_no); + assert(process_pipeline_comparison(buf, shot, arg)); + assert(match); + buffer_destroy(shot); + buffer_destroy(buf); + client_destroy(client); +} + +static struct color_float +convert_to_blending_space(const struct lcms_pipeline *pip, + struct color_float cf) +{ + /* Blending space is the linearized output space, + * or simply output space without the non-linear encoding + */ + cf = color_float_apply_curve(pip->pre_fn, cf); + return color_float_apply_matrix(&pip->mat, cf); +} + +static void +compare_blend(const struct lcms_pipeline *pip, + struct color_float bg, + struct color_float fg, + const struct color_float *shot, + struct rgb_diff_stat *diffstat) +{ + struct color_float ref; + unsigned i; + + /* convert sources to straight alpha */ + assert(bg.a == 1.0f); + fg = color_float_unpremult(fg); + + bg = convert_to_blending_space(pip, bg); + fg = convert_to_blending_space(pip, fg); + + /* blend */ + for (i = 0; i < COLOR_CHAN_NUM; i++) + ref.rgb[i] = (1.0f - fg.a) * bg.rgb[i] + fg.a * fg.rgb[i]; + + /* non-linear encoding for output */ + ref = color_float_apply_curve(pip->post_fn, ref); + + rgb_diff_stat_update(diffstat, &ref, shot, &fg); +} + +/* Alpha blending test pattern parameters */ +static const int ALPHA_STEPS = 256; +static const int BLOCK_WIDTH = 1; + +static void * +get_middle_row(struct buffer *buf) +{ + struct image_header ih = image_header_from(buf->image); + + assert(ih.width >= BLOCK_WIDTH * ALPHA_STEPS); + assert(ih.height >= BLOCK_WIDTH); + + return image_header_get_row_u32(&ih, (BLOCK_WIDTH - 1) / 2); +} + +static bool +check_blend_pattern(struct buffer *bg_buf, + struct buffer *fg_buf, + struct buffer *shot_buf, + const struct setup_args *arg) +{ + FILE *dump = NULL; +#if 0 + /* + * This file can be loaded in Octave for visualization. Find the script + * in tests/visualization/weston_plot_rgb_diff_stat.m and call it with + * + * weston_plot_rgb_diff_stat('output_icc_alpha_blend-f01-dump.txt', 255, 8) + */ + dump = fopen_dump_file("dump"); +#endif + + uint32_t *bg_row = get_middle_row(bg_buf); + uint32_t *fg_row = get_middle_row(fg_buf); + uint32_t *shot_row = get_middle_row(shot_buf); + struct rgb_diff_stat diffstat = { .dump = dump }; + int x; + + for (x = 0; x < BLOCK_WIDTH * ALPHA_STEPS; x++) { + struct color_float bg = a8r8g8b8_to_float(bg_row[x]); + struct color_float fg = a8r8g8b8_to_float(fg_row[x]); + struct color_float shot = a8r8g8b8_to_float(shot_row[x]); + + compare_blend(arg->pipeline, bg, fg, &shot, &diffstat); + } + + rgb_diff_stat_print(&diffstat, "Blending", 8); + + if (dump) + fclose(dump); + + /* Test success condition: */ + return diffstat.two_norm.max < 1.5f / 255.0f; +} + +static uint32_t +premult_color(uint32_t a, uint32_t r, uint32_t g, uint32_t b) +{ + uint32_t c = 0; + + c |= a << 24; + c |= (a * r / 255) << 16; + c |= (a * g / 255) << 8; + c |= a * b / 255; + + return c; +} + +static void +fill_alpha_pattern(struct buffer *buf) +{ + struct image_header ih = image_header_from(buf->image); + int y; + + assert(ih.pixman_format == PIXMAN_a8r8g8b8); + assert(ih.width == BLOCK_WIDTH * ALPHA_STEPS); + + for (y = 0; y < ih.height; y++) { + uint32_t *row = image_header_get_row_u32(&ih, y); + uint32_t step; + + for (step = 0; step < (uint32_t)ALPHA_STEPS; step++) { + uint32_t alpha = step * 255 / (ALPHA_STEPS - 1); + uint32_t color; + int i; + + color = premult_color(alpha, 0, 255 - alpha, 255); + for (i = 0; i < BLOCK_WIDTH; i++) + *row++ = color; + } + } +} + +/* + * Test that alpha blending is correct when an output ICC profile is installed. + * + * The background is a constant color. On top of that, there is an + * alpha-blended gradient with ramps in both alpha and color. Sub-surface + * ensures the correct positioning and stacking. + * + * The gradient consists of ALPHA_STEPS number of blocks. Block size is + * BLOCK_WIDTH x BLOCK_WIDTH and a block has a uniform color. + * + * In the blending result over x axis: + * - red goes from 1.0 to 0.0, monotonic + * - green is not monotonic + * - blue goes from 0.0 to 1.0, monotonic + * + * The test has sRGB encoded input pixels (non-linear). These are converted to + * linear light (optical) values in output color space, blended, and converted + * to non-linear (electrical) values according to the output ICC profile. + * + * Specifically, this test exercises the linearization of output ICC profiles, + * retrieve_eotf_and_output_inv_eotf(). + */ +TEST(output_icc_alpha_blend) +{ + const int width = BLOCK_WIDTH * ALPHA_STEPS; + const int height = BLOCK_WIDTH; + const pixman_color_t background_color = { + .red = 0xffff, + .green = 0x8080, + .blue = 0x0000, + .alpha = 0xffff + }; + int seq_no = get_test_fixture_index(); + const struct setup_args *arg = &my_setup_args[seq_no]; + struct client *client; + struct buffer *bg; + struct buffer *fg; + struct wl_subcompositor *subco; + struct wl_surface *surf; + struct wl_subsurface *sub; + struct buffer *shot; + bool match; + + client = create_client(); + subco = bind_to_singleton_global(client, &wl_subcompositor_interface, 1); + + /* background window content */ + bg = create_shm_buffer_a8r8g8b8(client, width, height); + fill_image_with_color(bg->image, &background_color); + + /* background window, main surface */ + client->surface = create_test_surface(client); + client->surface->width = width; + client->surface->height = height; + client->surface->buffer = bg; /* pass ownership */ + surface_set_opaque_rect(client->surface, + &(struct rectangle){ 0, 0, width, height }); + + /* foreground blended content */ + fg = create_shm_buffer_a8r8g8b8(client, width, height); + fill_alpha_pattern(fg); + + /* foreground window, sub-surface */ + surf = wl_compositor_create_surface(client->wl_compositor); + sub = wl_subcompositor_get_subsurface(subco, surf, client->surface->wl_surface); + /* sub-surface defaults to position 0, 0, top-most, synchronized */ + wl_surface_attach(surf, fg->proxy, 0, 0); + wl_surface_damage(surf, 0, 0, width, height); + wl_surface_commit(surf); + + /* attach, damage, commit background window */ + move_client(client, 0, 0); + + shot = capture_screenshot_of_output(client); + assert(shot); + match = verify_image(shot, "output_icc_alpha_blend", arg->ref_image_index, + NULL, seq_no); + assert(check_blend_pattern(bg, fg, shot, arg)); + assert(match); + + buffer_destroy(shot); + + wl_subsurface_destroy(sub); + wl_surface_destroy(surf); + buffer_destroy(fg); + wl_subcompositor_destroy(subco); + client_destroy(client); /* destroys bg */ +} diff --git a/tests/color-metadata-errors-test.c b/tests/color-metadata-errors-test.c new file mode 100644 index 00000000..61ce33de --- /dev/null +++ b/tests/color-metadata-errors-test.c @@ -0,0 +1,338 @@ +/* + * Copyright 2022 Collabora, Ltd. + * + * 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. + */ + +#include "config.h" + +#include +#include +#include + +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +#include "weston-private.h" +#include "libweston-internal.h" +#include "backend.h" +#include "color.h" + +struct config_testcase { + bool has_characteristics_key; + const char *output_characteristics_name; + const char *characteristics_name; + const char *red_x; + const char *green_y; + const char *white_y; + const char *min_L; + int expected_retval; + const char *expected_error; +}; + +static const struct config_testcase config_cases[] = { + { + false, "fred", "fred", "red_x=0.9", "green_y=0.8", "white_y=0.323", "min_L=1e-4", 0, + "" + }, + { + true, "fred", "fred", "red_x=0.9", "green_y= 0.8 ", "white_y=0.323", "min_L=1e-4", 0, + "" + }, + { + true, "fred", "fred", "red_x=0.9", "green_y= 0.8 ", "white_y=0.323", "", 0, + "" + }, + { + true, "notexisting", "fred", "red_x=0.9", "green_y=0.8", "white_y=0.323", "min_L=1e-4", -1, + "Config error in weston.ini, output mockoutput: no [color_characteristics] section with 'name=notexisting' found.\n" + }, + { + true, "fr:ed", "fr:ed", "red_x=0.9", "green_y=0.8", "white_y=0.323", "min_L=1e-4", -1, + "Config error in weston.ini [color_characteristics] name=fr:ed: reserved name. Do not use ':' character in the name.\n" + }, + { + true, "fred", "fred", "red_x=-5", "green_y=1.01", "white_y=0.323", "min_L=1e-4", -1, + "Config error in weston.ini [color_characteristics] name=fred: red_x value -5.000000 is outside of the range 0.000000 - 1.000000.\n" + "Config error in weston.ini [color_characteristics] name=fred: green_y value 1.010000 is outside of the range 0.000000 - 1.000000.\n" + }, + { + true, "fred", "fred", "red_x=haahaa", "green_y=-", "white_y=0.323", "min_L=1e-4", -1, + "Config error in weston.ini [color_characteristics] name=fred: failed to parse the value of key red_x.\n" + "Config error in weston.ini [color_characteristics] name=fred: failed to parse the value of key green_y.\n" + }, + { + true, "fred", "fred", "", "", "white_y=0.323", "min_L=1e-4", -1, + "Config error in weston.ini [color_characteristics] name=fred: group 1 key red_x is missing. You must set either none or all keys of a group.\n" + "Config error in weston.ini [color_characteristics] name=fred: group 1 key red_y is set. You must set either none or all keys of a group.\n" + "Config error in weston.ini [color_characteristics] name=fred: group 1 key green_x is set. You must set either none or all keys of a group.\n" + "Config error in weston.ini [color_characteristics] name=fred: group 1 key green_y is missing. You must set either none or all keys of a group.\n" + "Config error in weston.ini [color_characteristics] name=fred: group 1 key blue_x is set. You must set either none or all keys of a group.\n" + "Config error in weston.ini [color_characteristics] name=fred: group 1 key blue_y is set. You must set either none or all keys of a group.\n" + }, + { + true, "fred", "fred", "red_x=0.9", "green_y=0.8", "", "min_L=1e-4", -1, + "Config error in weston.ini [color_characteristics] name=fred: group 2 key white_x is set. You must set either none or all keys of a group.\n" + "Config error in weston.ini [color_characteristics] name=fred: group 2 key white_y is missing. You must set either none or all keys of a group.\n" + }, +}; + +static FILE *logfile; + +static int +logger(const char *fmt, va_list arg) +{ + return vfprintf(logfile, fmt, arg); +} + +static int +no_logger(const char *fmt, va_list arg) +{ + return 0; +} + +static struct weston_config * +create_config(const struct config_testcase *t) +{ + struct compositor_setup setup; + struct weston_config *wc; + + compositor_setup_defaults(&setup); + weston_ini_setup(&setup, + cfgln("[output]"), + cfgln("name=mockoutput"), + t->has_characteristics_key ? + cfgln("color_characteristics=%s", t->output_characteristics_name) : + cfgln(""), + cfgln("eotf-mode=st2084"), + + cfgln("[color_characteristics]"), + cfgln("name=%s", t->characteristics_name), + cfgln("maxFALL=1000"), + cfgln("%s", t->red_x), + cfgln("red_y=0.3"), + cfgln("blue_x=0.1"), + cfgln("blue_y=0.11"), + cfgln("green_x=0.1771"), + cfgln("%s", t->green_y), + cfgln("white_x=0.313"), + cfgln("%s", t->white_y), + cfgln("%s", t->min_L), + cfgln("max_L=65535.0"), + + cfgln("[core]"), + cfgln("color-management=true")); + + wc = weston_config_parse(setup.config_file); + free(setup.config_file); + + return wc; +} + +/* + * Manufacture various weston.ini and check what + * wet_output_set_color_characteristics() says. Tests for the return value and + * the error messages logged. + */ +TEST_P(color_characteristics_config_error, config_cases) +{ + const struct config_testcase *t = data; + struct weston_config *wc; + struct weston_config_section *section; + int retval; + char *logbuf; + size_t logsize; + struct weston_output mock_output = {}; + + weston_output_init(&mock_output, NULL, "mockoutput"); + + logfile = open_memstream(&logbuf, &logsize); + weston_log_set_handler(logger, logger); + + wc = create_config(t); + section = weston_config_get_section(wc, "output", "name", "mockoutput"); + assert(section); + + retval = wet_output_set_color_characteristics(&mock_output, wc, section); + + assert(fclose(logfile) == 0); + logfile = NULL; + + testlog("retval %d, logs:\n%s\n", retval, logbuf); + + assert(retval == t->expected_retval); + assert(strcmp(logbuf, t->expected_error) == 0); + + weston_config_destroy(wc); + free(logbuf); + weston_output_release(&mock_output); +} + +/* Setting NULL resets group_mask */ +TEST(weston_output_set_color_characteristics_null) +{ + struct weston_output mock_output = {}; + + weston_output_init(&mock_output, NULL, "mockoutput"); + + mock_output.color_characteristics.group_mask = 1; + weston_output_set_color_characteristics(&mock_output, NULL); + assert(mock_output.color_characteristics.group_mask == 0); + + weston_output_release(&mock_output); +} + +struct value_testcase { + unsigned field_index; + float value; + bool retval; +}; + +static const struct value_testcase value_cases[] = { + { 0, 0.0, true }, + { 0, 1.0, true }, + { 0, -0.001, false }, + { 0, 1.01, false }, + { 0, NAN, false }, + { 0, HUGE_VALF, false }, + { 0, -HUGE_VALF, false }, + { 1, -1.0, false }, + { 2, 2.0, false }, + { 3, 2.0, false }, + { 4, 2.0, false }, + { 5, 2.0, false }, + { 6, 2.0, false }, + { 7, 2.0, false }, + { 8, 0.99, false }, + { 8, 65535.1, false }, + { 9, 0.000099, false }, + { 9, 6.55351, false }, + { 10, 0.99, false }, + { 10, 65535.1, false }, + { 11, 0.99, false }, + { 11, 65535.1, false }, +}; + +struct mock_color_manager { + struct weston_color_manager base; + struct weston_hdr_metadata_type1 *test_hdr_meta; +}; + +static struct weston_output_color_outcome * +mock_create_output_color_outcome(struct weston_color_manager *cm_base, + struct weston_output *output) +{ + struct mock_color_manager *cm = container_of(cm_base, typeof(*cm), base); + struct weston_output_color_outcome *co; + + co = zalloc(sizeof *co); + assert(co); + + co->hdr_meta = *cm->test_hdr_meta; + + return co; +} + +/* + * Modify one value in a known good metadata structure, and see how + * validation reacts to it. + */ +TEST_P(hdr_metadata_type1_errors, value_cases) +{ + struct value_testcase *t = data; + struct weston_hdr_metadata_type1 meta = { + .group_mask = WESTON_HDR_METADATA_TYPE1_GROUP_ALL_MASK, + .primary[0] = { 0.6650, 0.3261 }, + .primary[1] = { 0.2890, 0.6435 }, + .primary[2] = { 0.1491, 0.0507 }, + .white = { 0.3134, 0.3291 }, + .maxDML = 600.0, + .minDML = 0.0001, + .maxCLL = 600.0, + .maxFALL = 400.0, + }; + float *fields[] = { + &meta.primary[0].x, &meta.primary[0].y, + &meta.primary[1].x, &meta.primary[1].y, + &meta.primary[2].x, &meta.primary[2].y, + &meta.white.x, &meta.white.y, + &meta.maxDML, &meta.minDML, + &meta.maxCLL, &meta.maxFALL, + }; + struct mock_color_manager mock_cm = { + .base.create_output_color_outcome = mock_create_output_color_outcome, + .test_hdr_meta = &meta, + }; + struct weston_compositor mock_compositor = { + .color_manager = &mock_cm.base, + }; + struct weston_output mock_output = {}; + bool ret; + + weston_log_set_handler(no_logger, no_logger); + + weston_output_init(&mock_output, &mock_compositor, "mockoutput"); + + assert(t->field_index < ARRAY_LENGTH(fields)); + *fields[t->field_index] = t->value; + ret = weston_output_set_color_outcome(&mock_output); + assert(ret == t->retval); + + weston_output_color_outcome_destroy(&mock_output.color_outcome); + weston_output_release(&mock_output); +} + +/* Unflagged members are ignored in validity check */ +TEST(hdr_metadata_type1_ignore_unflagged) +{ + /* All values invalid, but also empty mask so none actually used. */ + struct weston_hdr_metadata_type1 meta = { + .group_mask = 0, + .primary[0] = { -1.0, -1.0 }, + .primary[1] = { -1.0, -1.0 }, + .primary[2] = { -1.0, -1.0 }, + .white = { -1.0, -1.0 }, + .maxDML = -1.0, + .minDML = -1.0, + .maxCLL = -1.0, + .maxFALL = -1.0, + }; + struct mock_color_manager mock_cm = { + .base.create_output_color_outcome = mock_create_output_color_outcome, + .test_hdr_meta = &meta, + }; + struct weston_compositor mock_compositor = { + .color_manager = &mock_cm.base, + }; + struct weston_output mock_output = {}; + bool ret; + + weston_log_set_handler(no_logger, no_logger); + + weston_output_init(&mock_output, &mock_compositor, "mockoutput"); + + ret = weston_output_set_color_outcome(&mock_output); + assert(ret); + + weston_output_color_outcome_destroy(&mock_output.color_outcome); + weston_output_release(&mock_output); +} diff --git a/tests/color-metadata-parsing-test.c b/tests/color-metadata-parsing-test.c new file mode 100644 index 00000000..cc1325f1 --- /dev/null +++ b/tests/color-metadata-parsing-test.c @@ -0,0 +1,120 @@ +/* + * Copyright 2022 Collabora, Ltd. + * + * 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. + */ + +#include "config.h" + +#include +#include + +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" +#include "backend.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.renderer = RENDERER_GL; + setup.shell = SHELL_TEST_DESKTOP; + + weston_ini_setup(&setup, + cfgln("[output]"), + cfgln("name=headless"), + cfgln("color_characteristics=my-awesome-color"), + cfgln("eotf-mode=st2084"), + + cfgln("[color_characteristics]"), + cfgln("name=my-awesome-color"), + cfgln("maxFALL=1000"), + cfgln("red_x=0.9999"), + cfgln("red_y=0.3"), + cfgln("blue_x=0.1"), + cfgln("blue_y=0.11"), + cfgln("green_x=0.1771"), + cfgln("green_y=0.80001"), + cfgln("white_x=0.313"), + cfgln("white_y=0.323"), + cfgln("min_L=0.0001"), + cfgln("max_L=65535.0"), + + cfgln("[core]"), + cfgln("color-management=true")); + + return weston_test_harness_execute_as_plugin(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +PLUGIN_TEST(color_characteristics_from_weston_ini) +{ + struct weston_output *output = NULL; + struct weston_output *it; + enum weston_eotf_mode mode; + const struct weston_color_characteristics *cc; + const struct weston_hdr_metadata_type1 *hdr_meta; + + wl_list_for_each(it, &compositor->output_list, link) { + if (strcmp(it->name, "headless") == 0) { + output = it; + break; + } + } + + assert(output); + + mode = weston_output_get_eotf_mode(output); + assert(mode == WESTON_EOTF_MODE_ST2084); + + cc = weston_output_get_color_characteristics(output); + assert(cc->group_mask == WESTON_COLOR_CHARACTERISTICS_GROUP_ALL_MASK); + assert(cc->primary[0].x == 0.9999f); + assert(cc->primary[0].y == 0.3f); + assert(cc->primary[1].x == 0.1771f); + assert(cc->primary[1].y == 0.80001f); + assert(cc->primary[2].x == 0.1f); + assert(cc->primary[2].y == 0.11f); + assert(cc->white.x == 0.313f); + assert(cc->white.y == 0.323f); + assert(cc->min_luminance == 0.0001f); + assert(cc->max_luminance == 65535.0f); + assert(cc->maxFALL == 1000.0f); + + /* The below is color manager policy. */ + hdr_meta = weston_output_get_hdr_metadata_type1(output); + assert(hdr_meta->group_mask == WESTON_HDR_METADATA_TYPE1_GROUP_ALL_MASK); + assert(hdr_meta->primary[0].x == 0.9999f); + assert(hdr_meta->primary[0].y == 0.3f); + assert(hdr_meta->primary[1].x == 0.1771f); + assert(hdr_meta->primary[1].y == 0.80001f); + assert(hdr_meta->primary[2].x == 0.1f); + assert(hdr_meta->primary[2].y == 0.11f); + assert(hdr_meta->white.x == 0.313f); + assert(hdr_meta->white.y == 0.323f); + assert(hdr_meta->minDML == 0.0001f); + assert(hdr_meta->maxDML == 65535.0f); + assert(hdr_meta->maxCLL == 65535.0f); + assert(hdr_meta->maxFALL == 1000.0f); +} diff --git a/tests/color_util.c b/tests/color_util.c index 52250781..fc406d87 100644 --- a/tests/color_util.c +++ b/tests/color_util.c @@ -25,16 +25,149 @@ */ #include "config.h" + #include -#include "color_util.h" #include +#include +#include +#include + +#include +#include "color_util.h" +#include "weston-test-runner.h" +#include "shared/helpers.h" + +static_assert(sizeof(struct color_float) == 4 * sizeof(float), + "unexpected padding in struct color_float"); +static_assert(offsetof(struct color_float, r) == offsetof(struct color_float, rgb[COLOR_CHAN_R]), + "unexpected offset for struct color_float::r"); +static_assert(offsetof(struct color_float, g) == offsetof(struct color_float, rgb[COLOR_CHAN_G]), + "unexpected offset for struct color_float::g"); +static_assert(offsetof(struct color_float, b) == offsetof(struct color_float, rgb[COLOR_CHAN_B]), + "unexpected offset for struct color_float::b"); + +struct color_tone_curve { + enum transfer_fn fn; + enum transfer_fn inv_fn; + + /* LCMS2 API */ + int internal_type; + double param[5]; +}; + +/* Mapping from enum transfer_fn to LittleCMS curve parameters. */ +const struct color_tone_curve arr_curves[] = { + { + .fn = TRANSFER_FN_SRGB_EOTF, + .inv_fn = TRANSFER_FN_SRGB_EOTF_INVERSE, + .internal_type = 4, + .param = { 2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045 }, + }, + { + .fn = TRANSFER_FN_ADOBE_RGB_EOTF, + .inv_fn = TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE, + .internal_type = 1, + .param = { 563./256., 0.0, 0.0, 0.0 , 0.0 }, + }, + { + .fn = TRANSFER_FN_POWER2_4_EOTF, + .inv_fn = TRANSFER_FN_POWER2_4_EOTF_INVERSE, + .internal_type = 1, + .param = { 2.4, 0.0, 0.0, 0.0 , 0.0 }, + } +}; + +bool +find_tone_curve_type(enum transfer_fn fn, int *type, double params[5]) +{ + const int size_arr = ARRAY_LENGTH(arr_curves); + const struct color_tone_curve *curve; + + for (curve = &arr_curves[0]; curve < &arr_curves[size_arr]; curve++ ) { + if (curve->fn == fn ) + *type = curve->internal_type; + else if (curve->inv_fn == fn) + *type = -curve->internal_type; + else + continue; + + memcpy(params, curve->param, sizeof(curve->param)); + return true; + } + + return false; +} + +enum transfer_fn +transfer_fn_invert(enum transfer_fn fn) +{ + switch (fn) { + case TRANSFER_FN_ADOBE_RGB_EOTF: + return TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE; + case TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE: + return TRANSFER_FN_ADOBE_RGB_EOTF; + case TRANSFER_FN_IDENTITY: + return TRANSFER_FN_IDENTITY; + case TRANSFER_FN_POWER2_4_EOTF: + return TRANSFER_FN_POWER2_4_EOTF_INVERSE; + case TRANSFER_FN_POWER2_4_EOTF_INVERSE: + return TRANSFER_FN_POWER2_4_EOTF; + case TRANSFER_FN_SRGB_EOTF: + return TRANSFER_FN_SRGB_EOTF_INVERSE; + case TRANSFER_FN_SRGB_EOTF_INVERSE: + return TRANSFER_FN_SRGB_EOTF; + } + assert(0 && "bad transfer_fn"); + return 0; +} + +const char * +transfer_fn_name(enum transfer_fn fn) +{ + switch (fn) { + case TRANSFER_FN_ADOBE_RGB_EOTF: + return "AdobeRGB EOTF"; + case TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE: + return "inverse AdobeRGB EOTF"; + case TRANSFER_FN_IDENTITY: + return "identity"; + case TRANSFER_FN_POWER2_4_EOTF: + return "power 2.4"; + case TRANSFER_FN_POWER2_4_EOTF_INVERSE: + return "inverse power 2.4"; + case TRANSFER_FN_SRGB_EOTF: + return "sRGB EOTF"; + case TRANSFER_FN_SRGB_EOTF_INVERSE: + return "inverse sRGB EOTF"; + } + assert(0 && "bad transfer_fn"); + return 0; +} +/** + * NaN comes out as is + *This function is not intended for hiding NaN. + */ static float -sRGB_EOTF(float e) +ensure_unit_range(float v) { - assert(e >= 0.0f); - assert(e <= 1.0f); + const float tol = 1e-5f; + const float lim_lo = -tol; + const float lim_hi = 1.0f + tol; + assert(v >= lim_lo); + if (v < 0.0f) + return 0.0f; + assert(v <= lim_hi); + if (v > 1.0f) + return 1.0f; + return v; +} + +static float +sRGB_EOTF(float e) +{ + e = ensure_unit_range(e); if (e <= 0.04045) return e / 12.92; else @@ -44,30 +177,71 @@ sRGB_EOTF(float e) static float sRGB_EOTF_inv(float o) { - assert(o >= 0.0f); - assert(o <= 1.0f); - + o = ensure_unit_range(o); if (o <= 0.04045 / 12.92) return o * 12.92; else return pow(o, 1.0 / 2.4) * 1.055 - 0.055; } +static float +AdobeRGB_EOTF(float e) +{ + e = ensure_unit_range(e); + return pow(e, 563./256.); +} -void -sRGB_linearize(struct color_float *cf) +static float +AdobeRGB_EOTF_inv(float o) { - cf->r = sRGB_EOTF(cf->r); - cf->g = sRGB_EOTF(cf->g); - cf->b = sRGB_EOTF(cf->b); + o = ensure_unit_range(o); + return pow(o, 256./563.); } -void -sRGB_delinearize(struct color_float *cf) +static float +Power2_4_EOTF(float e) +{ + e = ensure_unit_range(e); + return pow(e, 2.4); +} + +static float +Power2_4_EOTF_inv(float o) { - cf->r = sRGB_EOTF_inv(cf->r); - cf->g = sRGB_EOTF_inv(cf->g); - cf->b = sRGB_EOTF_inv(cf->b); + o = ensure_unit_range(o); + return pow(o, 1./2.4); +} + +float +apply_tone_curve(enum transfer_fn fn, float r) +{ + float ret = 0; + + switch(fn) { + case TRANSFER_FN_IDENTITY: + ret = r; + break; + case TRANSFER_FN_SRGB_EOTF: + ret = sRGB_EOTF(r); + break; + case TRANSFER_FN_SRGB_EOTF_INVERSE: + ret = sRGB_EOTF_inv(r); + break; + case TRANSFER_FN_ADOBE_RGB_EOTF: + ret = AdobeRGB_EOTF(r); + break; + case TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE: + ret = AdobeRGB_EOTF_inv(r); + break; + case TRANSFER_FN_POWER2_4_EOTF: + ret = Power2_4_EOTF(r); + break; + case TRANSFER_FN_POWER2_4_EOTF_INVERSE: + ret = Power2_4_EOTF_inv(r); + break; + } + + return ret; } struct color_float @@ -82,3 +256,283 @@ a8r8g8b8_to_float(uint32_t v) return cf; } + +struct color_float +color_float_apply_curve(enum transfer_fn fn, struct color_float c) +{ + unsigned i; + + for (i = 0; i < COLOR_CHAN_NUM; i++) + c.rgb[i] = apply_tone_curve(fn, c.rgb[i]); + + return c; +} + +void +sRGB_linearize(struct color_float *cf) +{ + *cf = color_float_apply_curve(TRANSFER_FN_SRGB_EOTF, *cf); +} + +void +sRGB_delinearize(struct color_float *cf) +{ + *cf = color_float_apply_curve(TRANSFER_FN_SRGB_EOTF_INVERSE, *cf); +} + +struct color_float +color_float_unpremult(struct color_float in) +{ + static const struct color_float transparent = { + .r = 0.0f, .g = 0.0f, .b = 0.0f, .a = 0.0f, + }; + struct color_float out; + int i; + + if (in.a == 0.0f) + return transparent; + + for (i = 0; i < COLOR_CHAN_NUM; i++) + out.rgb[i] = in.rgb[i] / in.a; + out.a = in.a; + return out; +} + +/* + * Returns the result of the matrix-vector multiplication mat * c. + */ +struct color_float +color_float_apply_matrix(const struct lcmsMAT3 *mat, struct color_float c) +{ + struct color_float result; + unsigned i, j; + + /* + * The matrix has an array of columns, hence i indexes to rows and + * j indexes to columns. + */ + for (i = 0; i < 3; i++) { + result.rgb[i] = 0.0f; + for (j = 0; j < 3; j++) + result.rgb[i] += mat->v[j].n[i] * c.rgb[j]; + } + + result.a = c.a; + return result; +} + +void +process_pixel_using_pipeline(enum transfer_fn pre_curve, + const struct lcmsMAT3 *mat, + enum transfer_fn post_curve, + const struct color_float *in, + struct color_float *out) +{ + struct color_float cf; + + cf = color_float_apply_curve(pre_curve, *in); + cf = color_float_apply_matrix(mat, cf); + *out = color_float_apply_curve(post_curve, cf); +} + +static void +weston_matrix_from_lcmsMAT3(struct weston_matrix *w, const struct lcmsMAT3 *m) +{ + unsigned r, c; + + /* column-major */ + weston_matrix_init(w); + + for (c = 0; c < 3; c++) { + for (r = 0; r < 3; r++) + w->d[c * 4 + r] = m->v[c].n[r]; + } +} + +static void +lcmsMAT3_from_weston_matrix(struct lcmsMAT3 *m, const struct weston_matrix *w) +{ + unsigned r, c; + + for (c = 0; c < 3; c++) { + for (r = 0; r < 3; r++) + m->v[c].n[r] = w->d[c * 4 + r]; + } +} + +void +lcmsMAT3_invert(struct lcmsMAT3 *result, const struct lcmsMAT3 *mat) +{ + struct weston_matrix inv; + struct weston_matrix w; + int ret; + + weston_matrix_from_lcmsMAT3(&w, mat); + ret = weston_matrix_invert(&inv, &w); + assert(ret == 0); + lcmsMAT3_from_weston_matrix(result, &inv); +} + +/** Update scalar statistics + * + * \param stat The statistics structure to update. + * \param val A sample of the variable whose statistics you are collecting. + * \param pos The "position" that generated the current value. + * + * Accumulates min, max, sum and count statistics with the given value. + * Stores the position related to the current max and min each. + * + * To use this, declare a variable of type struct scalar_stat and + * zero-initialize it. Repeatedly call scalar_stat_update() to accumulate + * statistics. Then either directly read out what you are interested in from + * the structure, or use the related accessor or printing functions. + * + * If you also want to collect a debug log of all calls to this function, + * initialize the .dump member to a writable file handle. This is easiest + * with fopen_dump_file(). Remember to fclose() the handle after you have + * no more samples to add. + */ +void +scalar_stat_update(struct scalar_stat *stat, + double val, + const struct color_float *pos) +{ + if (stat->count == 0 || stat->min > val) { + stat->min = val; + stat->min_pos = *pos; + } + + if (stat->count == 0 || stat->max < val) { + stat->max = val; + stat->max_pos = *pos; + } + + stat->sum += val; + stat->count++; + + if (stat->dump) { + fprintf(stat->dump, "%.8g %.5g %.5g %.5g %.5g\n", + val, pos->r, pos->g, pos->b, pos->a); + } +} + +/** Return the average of the previously seen values. */ +float +scalar_stat_avg(const struct scalar_stat *stat) +{ + return stat->sum / stat->count; +} + +/** Print scalar statistics with pos.r only */ +void +scalar_stat_print_float(const struct scalar_stat *stat) +{ + testlog(" min %11.5g at %.5f\n", stat->min, stat->min_pos.r); + testlog(" max %11.5g at %.5f\n", stat->max, stat->max_pos.r); + testlog(" avg %11.5g\n", scalar_stat_avg(stat)); +} + +static void +print_stat_at_pos(const char *lim, double val, struct color_float pos, double scale) +{ + testlog(" %s %8.5f at rgb(%7.2f, %7.2f, %7.2f)\n", + lim, val * scale, pos.r * scale, pos.g * scale, pos.b * scale); +} + +static void +print_rgb_at_pos(const struct scalar_stat *stat, double scale) +{ + print_stat_at_pos("min", stat->min, stat->min_pos, scale); + print_stat_at_pos("max", stat->max, stat->max_pos, scale); + testlog(" avg %8.5f\n", scalar_stat_avg(stat) * scale); +} + +/** Print min/max/avg for each R/G/B/two-norm statistics + * + * \param stat The statistics to print. + * \param title A custom title to include in the heading which shall be printed + * like "%s error statistics:". + * \param scaling_bits Determines a scaling factor for the printed numbers as + * 2^scaling_bits - 1. + * + * Usually RGB values are stored in unsigned integer representation. 8-bit + * integer range is [0, 255] for example. Passing scaling_bits=8 will multiply + * all values (differences, two-norm errors, and position values) by + * 2^8 - 1 = 255. This makes interpreting the recorded errors more intuitive + * through the integer encoding precision perspective. + */ +void +rgb_diff_stat_print(const struct rgb_diff_stat *stat, + const char *title, unsigned scaling_bits) +{ + const char *const chan_name[COLOR_CHAN_NUM] = { "r", "g", "b" }; + float scale = exp2f(scaling_bits) - 1.0f; + unsigned i; + + assert(scaling_bits > 0); + + testlog("%s error statistics, %u samples, value range 0.0 - %.1f:\n", + title, stat->two_norm.count, scale); + for (i = 0; i < COLOR_CHAN_NUM; i++) { + testlog(" ch %s (signed):\n", chan_name[i]); + print_rgb_at_pos(&stat->rgb[i], scale); + } + testlog(" rgb two-norm:\n"); + print_rgb_at_pos(&stat->two_norm, scale); +} + +/** Update RGB difference statistics + * + * \param stat The statistics structure to update. + * \param ref The reference color to compare to. + * \param val The color produced by the algorithm under test; a sample. + * \param pos The position to be recorded with extremes. + * + * Computes the RGB difference by subtracting the reference color from the + * sample. This signed difference is tracked separately for each color channel + * in a scalar_stat to find the min, max, and average signed difference. The + * two-norm (Euclidean length) of the RGB difference vector is tracked in + * another scalar_stat. + * + * The position is stored separately for each of the eight min/max + * R/G/B/two-norm values recorded. A good way to use position is to record + * the algorithm input color. + * + * To use this, declare a variable of type struct rgb_diff_stat and + * zero-initalize it. Repeatedly call rgb_diff_stat_update() to accumulate + * statistics. Then either directly read out what you are interested in from + * the structure or use rgb_diff_stat_print(). + * + * If you also want to collect a debug log of all calls to this function, + * initialize the .dump member to a writable file handle. This is easiest + * with fopen_dump_file(). Remember to fclose() the handle after you have + * no more samples to add. + */ +void +rgb_diff_stat_update(struct rgb_diff_stat *stat, + const struct color_float *ref, + const struct color_float *val, + const struct color_float *pos) +{ + unsigned i; + double ssd = 0.0; + double diff[COLOR_CHAN_NUM]; + double two_norm; + + for (i = 0; i < COLOR_CHAN_NUM; i++) { + diff[i] = val->rgb[i] - ref->rgb[i]; + + scalar_stat_update(&stat->rgb[i], diff[i], pos); + ssd += diff[i] * diff[i]; + } + two_norm = sqrt(ssd); + + scalar_stat_update(&stat->two_norm, two_norm, pos); + + if (stat->dump) { + fprintf(stat->dump, "%.8g %.8g %.8g %.8g %.5g %.5g %.5g %.5g\n", + two_norm, + diff[COLOR_CHAN_R], diff[COLOR_CHAN_G], diff[COLOR_CHAN_B], + pos->r, pos->g, pos->b, pos->a); + } +} diff --git a/tests/color_util.h b/tests/color_util.h index a9cfd02d..a5003fa8 100644 --- a/tests/color_util.h +++ b/tests/color_util.h @@ -24,19 +24,172 @@ * SOFTWARE. */ +#pragma once + #include +#include +#include +enum color_chan_index { + COLOR_CHAN_R = 0, + COLOR_CHAN_G, + COLOR_CHAN_B, + COLOR_CHAN_NUM +}; +/* column vector when used in linear algebra */ struct color_float { - float r, g, b, a; + union { + float rgb[COLOR_CHAN_NUM]; + struct { + float r, g, b; + }; + }; + float a; +}; + +/* column vector */ +struct lcmsVEC3 { + float n[3]; +}; + +struct lcmsMAT3 { + /* array of columns */ + struct lcmsVEC3 v[3]; }; +enum transfer_fn { + TRANSFER_FN_IDENTITY, + TRANSFER_FN_SRGB_EOTF, + TRANSFER_FN_SRGB_EOTF_INVERSE, + TRANSFER_FN_ADOBE_RGB_EOTF, + TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE, + TRANSFER_FN_POWER2_4_EOTF, + TRANSFER_FN_POWER2_4_EOTF_INVERSE, +}; + +/* + * A helper to lay out a matrix in the natural writing order in code + * instead of needing to transpose in your mind every time you read it. + * The matrix is laid out as written: + * ⎡ a11 a12 a13 ⎤ + * ⎢ a21 a22 a23 ⎥ + * ⎣ a31 a32 a33 ⎦ + * where the first digit is row and the second digit is column. + */ +#define LCMSMAT3(a11, a12, a13, \ + a21, a22, a23, \ + a31, a32, a33) ((struct lcmsMAT3) \ + { /* Each vector is a column => looks like a transpose */ \ + .v[0] = { .n = { a11, a21, a31} }, \ + .v[1] = { .n = { a12, a22, a32} }, \ + .v[2] = { .n = { a13, a23, a33} }, \ + }) + void sRGB_linearize(struct color_float *cf); void sRGB_delinearize(struct color_float *cf); - struct color_float a8r8g8b8_to_float(uint32_t v); + +bool +find_tone_curve_type(enum transfer_fn fn, int *type, double params[5]); + +float +apply_tone_curve(enum transfer_fn fn, float r); + +void +process_pixel_using_pipeline(enum transfer_fn pre_curve, + const struct lcmsMAT3 *mat, + enum transfer_fn post_curve, + const struct color_float *in, + struct color_float *out); + +struct color_float +color_float_unpremult(struct color_float in); + +struct color_float +color_float_apply_curve(enum transfer_fn fn, struct color_float c); + +struct color_float +color_float_apply_matrix(const struct lcmsMAT3 *mat, struct color_float c); + +enum transfer_fn +transfer_fn_invert(enum transfer_fn fn); + +const char * +transfer_fn_name(enum transfer_fn fn); + +void +lcmsMAT3_invert(struct lcmsMAT3 *result, const struct lcmsMAT3 *mat); + +/** Scalar statistics + * + * See scalar_stat_update(). + */ +struct scalar_stat { + double min; + struct color_float min_pos; + + double max; + struct color_float max_pos; + + double sum; + unsigned count; + + /** Debug dump into file + * + * Initialize this to a writable file to get a record of all values + * ever fed through this statistics accumulator. The file shall be + * text with one value and its position per line: + * val pos.r pos.g pos.b pos.a + * + * Set to NULL to not record. + */ + FILE *dump; +}; + +/** RGB difference statistics + * + * See rgb_diff_stat_update(). + */ +struct rgb_diff_stat { + struct scalar_stat rgb[COLOR_CHAN_NUM]; + struct scalar_stat two_norm; + + /** Debug dump into file + * + * Initialize this to a writable file to get a record of all values + * ever fed through this statistics accumulator. The file shall be + * text with the two-norm error, the rgb difference, and their position + * per line: + * norm diff.r diff.g diff.b pos.r pos.g pos.b pos.a + * + * Set to NULL to not record. + */ + FILE *dump; +}; + +void +scalar_stat_update(struct scalar_stat *stat, + double val, + const struct color_float *pos); + +float +scalar_stat_avg(const struct scalar_stat *stat); + +void +scalar_stat_print_float(const struct scalar_stat *stat); + +void +rgb_diff_stat_update(struct rgb_diff_stat *stat, + const struct color_float *ref, + const struct color_float *val, + const struct color_float *pos); + +void +rgb_diff_stat_print(const struct rgb_diff_stat *stat, + const char *title, unsigned scaling_bits); diff --git a/tests/config-parser-test.c b/tests/config-parser-test.c index 583c83f2..33ad5d0b 100644 --- a/tests/config-parser-test.c +++ b/tests/config-parser-test.c @@ -32,6 +32,7 @@ #include #include #include +#include #include @@ -47,23 +48,25 @@ static struct weston_config * load_config(const char *text) { struct weston_config *config = NULL; - int len = 0; - int fd = -1; - char file[] = "/tmp/weston-config-parser-test-XXXXXX"; + char *content = NULL; + size_t file_len = 0; + int write_len; + FILE *file; - ZUC_ASSERTG_NOT_NULL(text, out); + file = open_memstream(&content, &file_len); + ZUC_ASSERTG_NOT_NULL(file, out); - fd = mkstemp(file); - ZUC_ASSERTG_NE(-1, fd, out); + write_len = fwrite(text, 1, strlen(text), file); + ZUC_ASSERTG_EQ((int)strlen(text), write_len, out_close); - len = write(fd, text, strlen(text)); - ZUC_ASSERTG_EQ((int)strlen(text), len, out_close); + ZUC_ASSERTG_EQ(fflush(file), 0, out_close); + fseek(file, 0L, SEEK_SET); - config = weston_config_parse(file); + config = weston_config_parse_fp(file); out_close: - close(fd); - unlink(file); + fclose(file); + free(content); out: return config; } @@ -119,6 +122,13 @@ static struct zuc_fixture config_test_t1 = { "zero=0\n" "negative=-42\n" "flag=false\n" + "real=4.667\n" + "negreal=-3.2\n" + "expval=24.687E+15\n" + "negexpval=-3e-2\n" + "notanumber=nan\n" + "empty=\n" + "tiny=0.0000000000000000000000000000000000000063548\n" "\n" "[colors]\n" "none=0x00000000\n" @@ -600,6 +610,197 @@ ZUC_TEST_F(config_test_t1, test027, data) ZUC_ASSERT_EQ(ERANGE, errno); } +ZUC_TEST_F(config_test_t1, get_double_number, data) +{ + int r; + double n; + struct weston_config_section *section; + struct weston_config *config = data; + + errno = 0; + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_double(section, "number", &n, 600.0); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_TRUE(5252.0 == n); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, get_double_missing, data) +{ + int r; + double n; + struct weston_config_section *section; + struct weston_config *config = data; + + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_double(section, "+++", &n, 600.0); + + ZUC_ASSERT_EQ(-1, r); + ZUC_ASSERT_TRUE(600.0 == n); + ZUC_ASSERT_EQ(ENOENT, errno); +} + +ZUC_TEST_F(config_test_t1, get_double_zero, data) +{ + int r; + double n; + struct weston_config_section *section; + struct weston_config *config = data; + + errno = 0; + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_double(section, "zero", &n, 600.0); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_TRUE(0.0 == n); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, get_double_negative, data) +{ + int r; + double n; + struct weston_config_section *section; + struct weston_config *config = data; + + errno = 0; + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_double(section, "negative", &n, 600.0); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_TRUE(-42.0 == n); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, get_double_flag, data) +{ + int r; + double n; + struct weston_config_section *section; + struct weston_config *config = data; + + errno = 0; + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_double(section, "flag", &n, 600.0); + + ZUC_ASSERT_EQ(-1, r); + ZUC_ASSERT_TRUE(600.0 == n); + ZUC_ASSERT_EQ(EINVAL, errno); +} + +ZUC_TEST_F(config_test_t1, get_double_real, data) +{ + int r; + double n; + struct weston_config_section *section; + struct weston_config *config = data; + + errno = 0; + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_double(section, "real", &n, 600.0); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_TRUE(4.667 == n); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, get_double_negreal, data) +{ + int r; + double n; + struct weston_config_section *section; + struct weston_config *config = data; + + errno = 0; + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_double(section, "negreal", &n, 600.0); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_TRUE(-3.2 == n); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, get_double_expval, data) +{ + int r; + double n; + struct weston_config_section *section; + struct weston_config *config = data; + + errno = 0; + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_double(section, "expval", &n, 600.0); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_TRUE(24.687e+15 == n); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, get_double_negexpval, data) +{ + int r; + double n; + struct weston_config_section *section; + struct weston_config *config = data; + + errno = 0; + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_double(section, "negexpval", &n, 600.0); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_TRUE(-3e-2 == n); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, get_double_notanumber, data) +{ + int r; + double n; + struct weston_config_section *section; + struct weston_config *config = data; + + errno = 0; + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_double(section, "notanumber", &n, 600.0); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_TRUE(isnan(n)); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, get_double_empty, data) +{ + int r; + double n; + struct weston_config_section *section; + struct weston_config *config = data; + + errno = 0; + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_double(section, "empty", &n, 600.0); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_TRUE(0.0 == n); + ZUC_ASSERT_EQ(0, errno); +} + +ZUC_TEST_F(config_test_t1, get_double_tiny, data) +{ + int r; + double n; + struct weston_config_section *section; + struct weston_config *config = data; + + errno = 0; + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_double(section, "tiny", &n, 600.0); + + ZUC_ASSERT_EQ(0, r); + ZUC_ASSERT_TRUE(6.3548e-39 == n); + ZUC_ASSERT_EQ(0, errno); +} + ZUC_TEST_F(config_test_t2, doesnt_parse, data) { struct weston_config *config = data; diff --git a/tests/custom-env-test.c b/tests/custom-env-test.c new file mode 100644 index 00000000..3a516190 --- /dev/null +++ b/tests/custom-env-test.c @@ -0,0 +1,158 @@ +/* + * Copyright 2022 Collabora, Ltd. + * + * 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. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include + +#include "shared/helpers.h" +#include "shared/os-compatibility.h" +#include "shared/process-util.h" +#include "shared/string-helpers.h" + +#include "weston-test-runner.h" + +#define ASSERT_STR_MATCH(_as, _bs) do { \ + const char *as = _as; \ + const char *bs = _bs; \ + assert(!!as == !!bs); \ + assert(!as || strcmp(as, bs) == 0); \ +} while (0) + +#define ASSERT_STR_ARRAY_MATCH(_name, _aa, _ba) do { \ + char * const *aa = _aa; \ + char * const *ba = _ba; \ + testlog("\tcomparing " _name ":\n"); \ + for (int _i = 0; aa[_i] || ba[_i]; _i++) { \ + testlog("\t\t[%d] '%s' == '%s'?\n", _i, aa[_i], ba[_i]); \ + ASSERT_STR_MATCH(aa[_i], ba[_i]); \ + } \ + testlog("\tsuccessfully compared " _name "\n"); \ +} while (0) + +static enum test_result_code +setup_env(struct weston_test_harness *harness) +{ + /* as this is a standalone test, we can clear the environment here */ + clearenv(); + + putenv("ENV1=one"); + setenv("ENV2", "two", 1); + setenv("ENV3", "three", 1); + + return weston_test_harness_execute_standalone(harness); +} + +DECLARE_FIXTURE_SETUP(setup_env); + +#define DEFAULT_ENVP (char * const []) { "ENV1=one", "ENV2=two", "ENV3=three", NULL } + +TEST(basic_env) +{ + struct custom_env env; + char *const envp[] = { "ENV1=one", "ENV2=two", "ENV3=four", "ENV5=five", NULL }; + + custom_env_init_from_environ(&env); + custom_env_set_env_var(&env, "ENV5", "five"); + custom_env_set_env_var(&env, "ENV3", "four"); + ASSERT_STR_ARRAY_MATCH("envp", custom_env_get_envp(&env), envp); + assert(env.env_finalized); + custom_env_fini(&env); +} + +TEST(basic_env_arg) +{ + struct custom_env env; + char *const argp[] = { "arg1", "arg2", "arg3", NULL }; + + custom_env_init_from_environ(&env); + custom_env_add_arg(&env, "arg1"); + custom_env_add_arg(&env, "arg2"); + custom_env_add_arg(&env, "arg3"); + + ASSERT_STR_ARRAY_MATCH("envp", custom_env_get_envp(&env), DEFAULT_ENVP); + assert(env.env_finalized); + ASSERT_STR_ARRAY_MATCH("argp", custom_env_get_argp(&env), argp); + assert(env.arg_finalized); + custom_env_fini(&env); +} + +struct test_str { + const char *exec_str; + char * const *envp; + char * const *argp; +}; + +static struct test_str str_tests[] = { + { + .exec_str = "ENV1=1 ENV2=owt two-arghs", + .envp = (char * const []) { "ENV1=1", "ENV2=owt", "ENV3=three", NULL }, + .argp = (char * const []) { "two-arghs", NULL }, + }, + { + .exec_str = "ENV2=owt one-argh", + .envp = (char * const []) { "ENV1=one", "ENV2=owt", "ENV3=three", NULL }, + .argp = (char * const []) { "one-argh", NULL }, + }, + { + .exec_str = "FOO=bar one-argh-again", + .envp = (char * const []) { "ENV1=one", "ENV2=two", "ENV3=three", "FOO=bar", NULL }, + .argp = (char * const []) { "one-argh-again", NULL }, + }, + { + .exec_str = "ENV1=number=7 one-argh-eq", + .envp = (char * const []) { "ENV1=number=7", "ENV2=two", "ENV3=three", NULL }, + .argp = (char * const []) { "one-argh-eq", NULL }, + }, + { + .exec_str = "no-arg-h", + .envp = DEFAULT_ENVP, + .argp = (char * const []) { "no-arg-h", NULL }, + }, + { + .exec_str = "argh-w-arg argequals=thing plainarg ", + .envp = DEFAULT_ENVP, + .argp = (char * const []) { "argh-w-arg", "argequals=thing", "plainarg", NULL }, + }, +}; + +TEST_P(env_parse_string, str_tests) +{ + struct custom_env env; + struct test_str *test = data; + + testlog("checking exec_str '%s'\n", test->exec_str); + custom_env_init_from_environ(&env); + custom_env_add_from_exec_string(&env, test->exec_str); + ASSERT_STR_ARRAY_MATCH("envp", custom_env_get_envp(&env), test->envp); + ASSERT_STR_ARRAY_MATCH("argp", custom_env_get_argp(&env), test->argp); + custom_env_fini(&env); +} diff --git a/tests/image-iter.h b/tests/image-iter.h new file mode 100644 index 00000000..ab9b0830 --- /dev/null +++ b/tests/image-iter.h @@ -0,0 +1,75 @@ +/* + * Copyright 2021 Advanced Micro Devices, Inc. + * Copyright 2022 Collabora, Ltd. + * + * 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. + */ + +#pragma once + +#include +#include +#include + +/** A collection of basic information extracted from a pixman_image_t */ +struct image_header { + int width; + int height; + pixman_format_code_t pixman_format; + + int stride_bytes; + unsigned char *data; +}; + +/** Populate image_header from pixman_image_t */ +static inline struct image_header +image_header_from(pixman_image_t *image) +{ + struct image_header h; + + h.width = pixman_image_get_width(image); + h.height = pixman_image_get_height(image); + h.pixman_format = pixman_image_get_format(image); + h.stride_bytes = pixman_image_get_stride(image); + h.data = (void *)pixman_image_get_data(image); + + return h; +} + +/** Get pointer to the beginning of the row + * + * \param header Header describing the Pixman image. + * \param y Index of the desired row, starting from zero. + * \return Pointer to the first pixel of the row. + * + * Asserts that y is within image height, and that pixel format uses 32 bits + * per pixel. + */ +static inline uint32_t * +image_header_get_row_u32(const struct image_header *header, int y) +{ + assert(y >= 0); + assert(y < header->height); + assert(PIXMAN_FORMAT_BPP(header->pixman_format) == 32); + + return (uint32_t *)(header->data + y * header->stride_bytes); +} diff --git a/tests/internal-screenshot-test.c b/tests/internal-screenshot-test.c index e6e2962f..1c85660c 100644 --- a/tests/internal-screenshot-test.c +++ b/tests/internal-screenshot-test.c @@ -30,6 +30,7 @@ #include "weston-test-client-helper.h" #include "weston-test-fixture-compositor.h" +#include "image-iter.h" #include "test-config.h" static enum test_result_code @@ -55,30 +56,20 @@ DECLARE_FIXTURE_SETUP(fixture_setup); static void draw_stuff(pixman_image_t *image) { - int w, h; - int stride; /* bytes */ + struct image_header ih = image_header_from(image); int x, y; uint32_t r, g, b; - uint32_t *pixels; - uint32_t *pixel; - pixman_format_code_t fmt; - fmt = pixman_image_get_format(image); - w = pixman_image_get_width(image); - h = pixman_image_get_height(image); - stride = pixman_image_get_stride(image); - pixels = pixman_image_get_data(image); + for (y = 0; y < ih.height; y++) { + uint32_t *pixel = image_header_get_row_u32(&ih, y); - assert(PIXMAN_FORMAT_BPP(fmt) == 32); - - for (x = 0; x < w; x++) - for (y = 0; y < h; y++) { + for (x = 0; x < ih.width; x++, pixel++) { b = x; g = x + y; r = y; - pixel = pixels + (y * stride / 4) + x; *pixel = (255U << 24) | (r << 16) | (g << 8) | b; } + } } TEST(internal_screenshot) diff --git a/tests/lcms-util-test.c b/tests/lcms-util-test.c new file mode 100644 index 00000000..e7d8b69e --- /dev/null +++ b/tests/lcms-util-test.c @@ -0,0 +1,85 @@ +/* + * Copyright 2022 Collabora, Ltd. + * + * 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. + */ + +#include "config.h" + +#include +#include + +#include "weston-test-client-helper.h" +#include "color_util.h" +#include "lcms_util.h" + +static void +compare_pipeline_to_transfer_fn(cmsPipeline *pipeline, enum transfer_fn fn, + struct scalar_stat *stat) +{ + const unsigned N = 100000; + unsigned i; + + for (i = 0; i < N; i++) { + float x = (double)i / N; + float ref = apply_tone_curve(fn, x); + float y; + + cmsPipelineEvalFloat(&x, &y, pipeline); + scalar_stat_update(stat, y - ref, &(struct color_float){ .r = x }); + } +} + +static const enum transfer_fn build_MPE_curves_test_set[] = { + TRANSFER_FN_SRGB_EOTF, + TRANSFER_FN_SRGB_EOTF_INVERSE, + TRANSFER_FN_ADOBE_RGB_EOTF, + TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE, + TRANSFER_FN_POWER2_4_EOTF, + TRANSFER_FN_POWER2_4_EOTF_INVERSE, +}; + +TEST_P(build_MPE_curves, build_MPE_curves_test_set) +{ + const enum transfer_fn *fn = data; + const cmsContext ctx = 0; + cmsToneCurve *curve; + cmsStage *stage; + cmsPipeline *pipeline; + struct scalar_stat stat = {}; + + curve = build_MPE_curve(ctx, *fn); + stage = cmsStageAllocToneCurves(ctx, 1, &curve); + cmsFreeToneCurve(curve); + + pipeline = cmsPipelineAlloc(ctx, 1, 1); + cmsPipelineInsertStage(pipeline, cmsAT_END, stage); + + compare_pipeline_to_transfer_fn(pipeline, *fn, &stat); + testlog("Transfer function %s as a segmented curve element, error:\n", + transfer_fn_name(*fn)); + scalar_stat_print_float(&stat); + assert(fabs(stat.max) < 1e-7); + assert(fabs(stat.min) < 1e-7); + + cmsPipelineFree(pipeline); +} diff --git a/tests/lcms_util.c b/tests/lcms_util.c new file mode 100644 index 00000000..4e304634 --- /dev/null +++ b/tests/lcms_util.c @@ -0,0 +1,231 @@ +/* + * Copyright 2022 Collabora, Ltd. + * Copyright (c) 1998-2022 Marti Maria Saguer + * + * 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. + */ + +#include "config.h" + +#include +#include +#include + +#include "shared/helpers.h" +#include "color_util.h" +#include "lcms_util.h" + +/* + * MPE tone curves can only use LittleCMS parametric curve types 6-8 and not + * inverses. + * type 6: Y = (aX + b)^g + c; params [g, a, b, c] + * type 7: Y = a log(bX^g + c) + d; params [g, a, b, c, d] + * type 8: Y = a b^(cX + d) + e; params [a, b, c, d, e] + * Additionally, type 0 is sampled segment. + * + * cmsCurveSegment.x1 is the breakpoint stored in ICC files, except for the + * last segment. First segment always begins at -Inf, and last segment always + * ends at Inf. + */ + +static cmsToneCurve * +build_MPE_curve_sRGB(cmsContext ctx) +{ + cmsCurveSegment segments[] = { + { + /* Constant zero segment */ + .x0 = -HUGE_VAL, + .x1 = 0.0, + .Type = 6, + .Params = { 1.0, 0.0, 0.0, 0.0 }, + }, + { + /* Linear segment y = x / 12.92 */ + .x0 = 0.0, + .x1 = 0.04045, + .Type = 0, + .nGridPoints = 2, + .SampledPoints = (float[]){ 0.0, 0.04045 / 12.92 }, + }, + { + /* Power segment y = ((x + 0.055) / 1.055)^2.4 + * which is translated to + * y = (1/1.055 * x + 0.055 / 1.055)^2.4 + 0.0 + */ + .x0 = 0.04045, + .x1 = 1.0, + .Type = 6, + .Params = { 2.4, 1.0 / 1.055, 0.055 / 1.055, 0.0 }, + }, + { + /* Constant one segment */ + .x0 = 1.0, + .x1 = HUGE_VAL, + .Type = 6, + .Params = { 1.0, 0.0, 0.0, 1.0 }, + } + }; + + return cmsBuildSegmentedToneCurve(ctx, ARRAY_LENGTH(segments), segments); +} + +static cmsToneCurve * +build_MPE_curve_sRGB_inv(cmsContext ctx) +{ + cmsCurveSegment segments[] = { + { + /* Constant zero segment */ + .x0 = -HUGE_VAL, + .x1 = 0.0, + .Type = 6, + .Params = { 1.0, 0.0, 0.0, 0.0 }, + }, + { + /* Linear segment y = x * 12.92 */ + .x0 = 0.0, + .x1 = 0.04045 / 12.92, + .Type = 0, + .nGridPoints = 2, + .SampledPoints = (float[]){ 0.0, 0.04045 }, + }, + { + /* Power segment y = 1.055 * x^(1/2.4) - 0.055 + * which is translated to + * y = (1.055^2.4 * x + 0.0)^(1/2.4) - 0.055 + */ + .x0 = 0.04045 / 12.92, + .x1 = 1.0, + .Type = 6, + .Params = { 1.0 / 2.4, pow(1.055, 2.4), 0.0, -0.055 }, + }, + { + /* Constant one segment */ + .x0 = 1.0, + .x1 = HUGE_VAL, + .Type = 6, + .Params = { 1.0, 0.0, 0.0, 1.0 }, + } + }; + + return cmsBuildSegmentedToneCurve(ctx, ARRAY_LENGTH(segments), segments); +} + +static cmsToneCurve * +build_MPE_curve_power(cmsContext ctx, double exponent) +{ + cmsCurveSegment segments[] = { + { + /* Constant zero segment */ + .x0 = -HUGE_VAL, + .x1 = 0.0, + .Type = 6, + .Params = { 1.0, 0.0, 0.0, 0.0 }, + }, + { + /* Power segment y = x^exponent + * which is translated to + * y = (1.0 * x + 0.0)^exponent + 0.0 + */ + .x0 = 0.0, + .x1 = 1.0, + .Type = 6, + .Params = { exponent, 1.0, 0.0, 0.0 }, + }, + { + /* Constant one segment */ + .x0 = 1.0, + .x1 = HUGE_VAL, + .Type = 6, + .Params = { 1.0, 0.0, 0.0, 1.0 }, + } + }; + + return cmsBuildSegmentedToneCurve(ctx, ARRAY_LENGTH(segments), segments); +} + +cmsToneCurve * +build_MPE_curve(cmsContext ctx, enum transfer_fn fn) +{ + switch (fn) { + case TRANSFER_FN_ADOBE_RGB_EOTF: + return build_MPE_curve_power(ctx, 563.0 / 256.0); + case TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE: + return build_MPE_curve_power(ctx, 256.0 / 563.0); + case TRANSFER_FN_POWER2_4_EOTF: + return build_MPE_curve_power(ctx, 2.4); + case TRANSFER_FN_POWER2_4_EOTF_INVERSE: + return build_MPE_curve_power(ctx, 1.0 / 2.4); + case TRANSFER_FN_SRGB_EOTF: + return build_MPE_curve_sRGB(ctx); + case TRANSFER_FN_SRGB_EOTF_INVERSE: + return build_MPE_curve_sRGB_inv(ctx); + default: + assert(0 && "unimplemented MPE curve"); + } + + return NULL; +} + +cmsStage * +build_MPE_curve_stage(cmsContext context_id, enum transfer_fn fn) +{ + cmsToneCurve *c; + cmsStage *stage; + + c = build_MPE_curve(context_id, fn); + stage = cmsStageAllocToneCurves(context_id, 3, + (cmsToneCurve *[3]){ c, c, c }); + assert(stage); + cmsFreeToneCurve(c); + + return stage; +} + +/* This function is taken from LittleCMS, pardon the odd style */ +cmsBool +SetTextTags(cmsHPROFILE hProfile, const wchar_t* Description) +{ + cmsMLU *DescriptionMLU, *CopyrightMLU; + cmsBool rc = FALSE; + cmsContext ContextID = cmsGetProfileContextID(hProfile); + + DescriptionMLU = cmsMLUalloc(ContextID, 1); + CopyrightMLU = cmsMLUalloc(ContextID, 1); + + if (DescriptionMLU == NULL || CopyrightMLU == NULL) goto Error; + + if (!cmsMLUsetWide(DescriptionMLU, "en", "US", Description)) goto Error; + if (!cmsMLUsetWide(CopyrightMLU, "en", "US", L"No copyright, use freely")) goto Error; + + if (!cmsWriteTag(hProfile, cmsSigProfileDescriptionTag, DescriptionMLU)) goto Error; + if (!cmsWriteTag(hProfile, cmsSigCopyrightTag, CopyrightMLU)) goto Error; + + rc = TRUE; + +Error: + + if (DescriptionMLU) + cmsMLUfree(DescriptionMLU); + if (CopyrightMLU) + cmsMLUfree(CopyrightMLU); + return rc; +} diff --git a/libweston/weston-launch.h b/tests/lcms_util.h similarity index 69% rename from libweston/weston-launch.h rename to tests/lcms_util.h index 72954019..8808b641 100644 --- a/libweston/weston-launch.h +++ b/tests/lcms_util.h @@ -1,5 +1,5 @@ /* - * Copyright © 2012 Benjamin Franzke + * Copyright 2022 Collabora, Ltd. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -23,29 +23,17 @@ * SOFTWARE. */ -#ifndef _WESTON_LAUNCH_H_ -#define _WESTON_LAUNCH_H_ +#pragma once -enum weston_launcher_opcode { - WESTON_LAUNCHER_OPEN, -}; +#include -enum weston_launcher_event { - WESTON_LAUNCHER_ACTIVATE, - WESTON_LAUNCHER_DEACTIVATE, - WESTON_LAUNCHER_DEACTIVATE_DONE, - // This event is followed by an fd handle - WESTON_LAUNCHER_OPEN_REPLY, -}; +#include "color_util.h" -struct weston_launcher_message { - int opcode; -}; +cmsToneCurve * +build_MPE_curve(cmsContext ctx, enum transfer_fn fn); -struct weston_launcher_open { - struct weston_launcher_message header; - int flags; - char path[0]; -}; +cmsStage * +build_MPE_curve_stage(cmsContext context_id, enum transfer_fn fn); -#endif +cmsBool +SetTextTags(cmsHPROFILE hProfile, const wchar_t* Description); diff --git a/tests/matrix-test.c b/tests/matrix-test.c index 03b92162..11c840da 100644 --- a/tests/matrix-test.c +++ b/tests/matrix-test.c @@ -1,5 +1,5 @@ /* - * Copyright © 2012 Collabora, Ltd. + * Copyright 2022 Collabora, Ltd. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,132 +25,52 @@ #include "config.h" -#include -#include #include -#include -#include -#include - #include +#include "weston-test-client-helper.h" -struct inverse_matrix { - double LU[16]; /* column-major */ - unsigned perm[4]; /* permutation */ -}; - -static struct timespec begin_time; - -static void -reset_timer(void) -{ - clock_gettime(CLOCK_MONOTONIC, &begin_time); -} - -static double -read_timer(void) -{ - struct timespec t; - - clock_gettime(CLOCK_MONOTONIC, &t); - return (double)(t.tv_sec - begin_time.tv_sec) + - 1e-9 * (t.tv_nsec - begin_time.tv_nsec); -} - -static double -det3x3(const float *c0, const float *c1, const float *c2) -{ - return (double) - c0[0] * c1[1] * c2[2] + - c1[0] * c2[1] * c0[2] + - c2[0] * c0[1] * c1[2] - - c0[2] * c1[1] * c2[0] - - c1[2] * c2[1] * c0[0] - - c2[2] * c0[1] * c1[0]; -} - -static double -determinant(const struct weston_matrix *m) -{ - double det = 0; -#if 1 - /* develop on last row */ - det -= m->d[3 + 0 * 4] * det3x3(&m->d[4], &m->d[8], &m->d[12]); - det += m->d[3 + 1 * 4] * det3x3(&m->d[0], &m->d[8], &m->d[12]); - det -= m->d[3 + 2 * 4] * det3x3(&m->d[0], &m->d[4], &m->d[12]); - det += m->d[3 + 3 * 4] * det3x3(&m->d[0], &m->d[4], &m->d[8]); -#else - /* develop on first row */ - det += m->d[0 + 0 * 4] * det3x3(&m->d[5], &m->d[9], &m->d[13]); - det -= m->d[0 + 1 * 4] * det3x3(&m->d[1], &m->d[9], &m->d[13]); - det += m->d[0 + 2 * 4] * det3x3(&m->d[1], &m->d[5], &m->d[13]); - det -= m->d[0 + 3 * 4] * det3x3(&m->d[1], &m->d[5], &m->d[9]); -#endif - return det; -} - -static void -print_permutation_matrix(const struct inverse_matrix *m) -{ - const unsigned *p = m->perm; - const char *row[4] = { - "1 0 0 0\n", - "0 1 0 0\n", - "0 0 1 0\n", - "0 0 0 1\n" - }; - - printf(" P =\n%s%s%s%s", row[p[0]], row[p[1]], row[p[2]], row[p[3]]); -} - -static void -print_LU_decomposition(const struct inverse_matrix *m) -{ - unsigned r, c; - - printf(" L " - " U\n"); - for (r = 0; r < 4; ++r) { - double v; - - for (c = 0; c < 4; ++c) { - if (c < r) - v = m->LU[r + c * 4]; - else if (c == r) - v = 1.0; - else - v = 0.0; - printf(" %12.6f", v); - } - - printf(" | "); - - for (c = 0; c < 4; ++c) { - if (c >= r) - v = m->LU[r + c * 4]; - else - v = 0.0; - printf(" %12.6f", v); - } - printf("\n"); - } -} +/* + * A helper to lay out a matrix in the natural writing order in code + * instead of needing to transpose in your mind every time you read it. + * The matrix is laid out as written: + * ⎡ a11 a12 a13 a14 ⎤ + * ⎢ a21 a22 a23 a24 ⎥ + * ⎢ a31 a32 a33 a34 ⎥ + * ⎣ a41 a42 a43 a44 ⎦ + * where the first digit is row and the second digit is column. + * + * The type field is set to the most pessimistic case possible so that if + * weston_matrix_invert() ever gets special-case code paths, we don't take + * them. + */ +#define MAT(a11, a12, a13, a14, \ + a21, a22, a23, a24, \ + a31, a32, a33, a34, \ + a41, a42, a43, a44) ((struct weston_matrix) \ + { \ + .d[0] = a11, .d[4] = a12, .d[ 8] = a13, .d[12] = a14, \ + .d[1] = a21, .d[5] = a22, .d[ 9] = a23, .d[13] = a24, \ + .d[2] = a31, .d[6] = a32, .d[10] = a33, .d[14] = a34, \ + .d[3] = a41, .d[7] = a42, .d[11] = a43, .d[15] = a44, \ + .type = WESTON_MATRIX_TRANSFORM_TRANSLATE | \ + WESTON_MATRIX_TRANSFORM_SCALE | \ + WESTON_MATRIX_TRANSFORM_ROTATE | \ + WESTON_MATRIX_TRANSFORM_OTHER, \ + }) + +static const struct weston_matrix IDENTITY = + MAT(1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); static void -print_inverse_data_matrix(const struct inverse_matrix *m) +subtract_matrix(struct weston_matrix *from, const struct weston_matrix *what) { - unsigned r, c; - - for (r = 0; r < 4; ++r) { - for (c = 0; c < 4; ++c) - printf(" %12.6f", m->LU[r + c * 4]); - printf("\n"); - } + unsigned i; - printf("permutation: "); - for (r = 0; r < 4; ++r) - printf(" %u", m->perm[r]); - printf("\n"); + for (i = 0; i < ARRAY_LENGTH(from->d); i++) + from->d[i] -= what->d[i]; } static void @@ -160,265 +80,192 @@ print_matrix(const struct weston_matrix *m) for (r = 0; r < 4; ++r) { for (c = 0; c < 4; ++c) - printf(" %14.6e", m->d[r + c * 4]); - printf("\n"); + testlog(" %14.6e", m->d[r + c * 4]); + testlog("\n"); } } -static double -frand(void) -{ - double r = random(); - return r / (double)(RAND_MAX / 2) - 1.0f; -} - -static void -randomize_matrix(struct weston_matrix *m) -{ - unsigned i; - for (i = 0; i < 16; ++i) -#if 1 - m->d[i] = frand() * exp(10.0 * frand()); -#else - m->d[i] = frand(); -#endif -} - -/* Take a matrix, compute inverse, multiply together - * and subtract the identity matrix to get the error matrix. - * Return the largest absolute value from the error matrix. +/* + * Matrix infinity norm + * http://www.netlib.org/lapack/lug/node75.html */ static double -test_inverse(struct weston_matrix *m) -{ - unsigned i; - struct inverse_matrix q; - double errsup = 0.0; - - if (matrix_invert(q.LU, q.perm, m) != 0) - return INFINITY; - - for (i = 0; i < 4; ++i) - inverse_transform(q.LU, q.perm, &m->d[i * 4]); - - m->d[0] -= 1.0f; - m->d[5] -= 1.0f; - m->d[10] -= 1.0f; - m->d[15] -= 1.0f; - - for (i = 0; i < 16; ++i) { - double err = fabs(m->d[i]); - if (err > errsup) - errsup = err; - } - - return errsup; -} - -enum { - TEST_OK, - TEST_NOT_INVERTIBLE_OK, - TEST_FAIL, - TEST_COUNT -}; - -static int -test(void) -{ - struct weston_matrix m; - double det, errsup; - - randomize_matrix(&m); - det = determinant(&m); - - errsup = test_inverse(&m); - if (errsup < 1e-6) - return TEST_OK; - - if (fabs(det) < 1e-5 && isinf(errsup)) - return TEST_NOT_INVERTIBLE_OK; - - printf("test fail, det: %g, error sup: %g\n", det, errsup); - - return TEST_FAIL; -} - -static int running; -static void -stopme(int n) -{ - running = 0; -} - -static void -test_loop_precision(void) -{ - int counts[TEST_COUNT] = { 0 }; - - printf("\nRunning a test loop for 10 seconds...\n"); - running = 1; - alarm(10); - while (running) { - counts[test()]++; - } - - printf("tests: %d ok, %d not invertible but ok, %d failed.\n" - "Total: %d iterations.\n", - counts[TEST_OK], counts[TEST_NOT_INVERTIBLE_OK], - counts[TEST_FAIL], - counts[TEST_OK] + counts[TEST_NOT_INVERTIBLE_OK] + - counts[TEST_FAIL]); -} - -static void __attribute__((noinline)) -test_loop_speed_matrixvector(void) -{ - struct weston_matrix m; - struct weston_vector v = { { 0.5, 0.5, 0.5, 1.0 } }; - unsigned long count = 0; - double t; - - printf("\nRunning 3 s test on weston_matrix_transform()...\n"); - - weston_matrix_init(&m); - - running = 1; - alarm(3); - reset_timer(); - while (running) { - weston_matrix_transform(&m, &v); - count++; - } - t = read_timer(); - - printf("%lu iterations in %f seconds, avg. %.1f ns/iter.\n", - count, t, 1e9 * t / count); -} - -static void __attribute__((noinline)) -test_loop_speed_inversetransform(void) +matrix_inf_norm(const struct weston_matrix *mat) { - struct weston_matrix m; - struct inverse_matrix inv; - struct weston_vector v = { { 0.5, 0.5, 0.5, 1.0 } }; - unsigned long count = 0; - double t; + unsigned row; + double infnorm = -1.0; - printf("\nRunning 3 s test on inverse_transform()...\n"); + for (row = 0; row < 4; row++) { + unsigned col; + double sum = 0.0; - weston_matrix_init(&m); - matrix_invert(inv.LU, inv.perm, &m); + for (col = 0; col < 4; col++) + sum += fabs(mat->d[col * 4 + row]); - running = 1; - alarm(3); - reset_timer(); - while (running) { - inverse_transform(inv.LU, inv.perm, v.f); - count++; + if (infnorm < sum) + infnorm = sum; } - t = read_timer(); - printf("%lu iterations in %f seconds, avg. %.1f ns/iter.\n", - count, t, 1e9 * t / count); + return infnorm; } -static void __attribute__((noinline)) -test_loop_speed_invert(void) -{ - struct weston_matrix m; - struct inverse_matrix inv; - unsigned long count = 0; - double t; - - printf("\nRunning 3 s test on matrix_invert()...\n"); - - weston_matrix_init(&m); +struct test_matrix { + /* the matrix to test */ + struct weston_matrix M; - running = 1; - alarm(3); - reset_timer(); - while (running) { - matrix_invert(inv.LU, inv.perm, &m); - count++; - } - t = read_timer(); + /* + * Residual error limit; inf norm(M * inv(M) - I) < err_limit + * The residual error as calculated here represents the relative + * error added by transforming a vector with inv(M). + * + * Since weston_matrix stores the inverse matrix in 32-bit floats, + * that limits the precision considerably. + */ + double err_limit; +}; - printf("%lu iterations in %f seconds, avg. %.1f ns/iter.\n", - count, t, 1e9 * t / count); -} +static const struct test_matrix matrices[] = { + /* A very trivial case. */ + { + .M = MAT(1, 0, 0, 0, + 0, 2, 0, 0, + 0, 0, 3, 0, + 0, 0, 0, 4), + .err_limit = 0.0, + }, + + /* + * A very likely case in a compositor, being a matrix applying + * just a translation. Surprisingly, fourbyfour-analyze says: + * + * ------------------------------------------------------------------- + * $ ./fourbyfour-analyse 1 0 0 1980 0 1 0 1080 + * Your input matrix A is + * 1 0 0 1980 + * 0 1 0 1080 + * 0 0 1 0 + * 0 0 0 1 + * + * The singular values of A are: 2255.39, 1, 1, 0.000443382 + * The condition number according to 2-norm of A is 5.087e+06. + * + * This means that if you were to solve the linear system Ax=b for vector x, + * in the worst case you would lose 6.7 digits (22.3 bits) of precision. + * The condition number is how much errors in vector b would be amplified + * when solving x even with infinite computational precision. + * + * Compare this to the precision of vectors b and x: + * + * - Single precision floating point has 7.2 digits (24 bits) of precision, + * leaving your result with no correct digits. + * Single precision, matrix A has rank 3 which means that the solution space + * for x has 1 dimension and therefore has many solutions. + * + * - Double precision floating point has 16.0 digits (53 bits) of precision, + * leaving your result with 9.2 correct digits (30 correct bits). + * Double precision, matrix A has full rank which means the solution x is + * unique. + * + * NOTE! The above gives you only an upper limit on errors. + * If the upper limit is low, you can be confident of your computations. But, + * if the upper limit is high, it does not necessarily imply that your + * computations will be doomed. + * ------------------------------------------------------------------- + * + * This is one example where the condition number is highly pessimistic, + * while the actual inversion results in no error at all. + * + * https://gitlab.freedesktop.org/pq/fourbyfour + */ + { + .M = MAT(1, 0, 0, 1980, + 0, 1, 0, 1080, + 0, 0, 1, 0, + 0, 0, 0, 1), + .err_limit = 0.0, + }, + + /* + * The following matrices have been generated with + * fourbyfour-generate using parameters out of a hat as listed below. + * + * If you want to verify the matrices in Octave, type this: + * M = [ ] + * mat = reshape(M, 4, 4) + * det(mat) + * cond(mat) + */ + + /* cond = 1e3 */ + { + .M = MAT(-4.12798022231678357619e-02, -7.93301899046665176529e-02, 2.49367040174418935772e-01, -2.22400462135059429070e-01, + 2.02416121867255743849e-01, -2.25754422240346010187e-02, -2.91283152417864787953e-01, 1.49354988316431153139e-01, + 6.18473094065821293874e-01, 5.81511312950217934548e-02, -1.18363610818063924590e+00, 8.00087538947595322547e-01, + 1.25723127083294305972e-01, 7.72723720984487272290e-02, -3.76023220287807879991e-01, 2.82473279931768073148e-01), + .err_limit = 1e-5, + }, + + /* cond = 1e3, abs det = 15 */ + { + .M = MAT(6.84154939885726509630e+00, -6.87241565273813304060e+00, -2.56772939909334070308e+01, -2.52185055099662420730e+01, + 2.04511561406330022450e+00, -3.67551043874248994925e+00, -1.96421641406619129633e+00, -2.40644091603848320204e+00, + 5.83631095663641819016e+00, -9.31051765621826277197e+00, -1.80402129629135217215e+01, -1.78475057662460052654e+01, + -9.88588496379959025262e+00, 1.49790516545410774540e+01, 2.64975800675967363418e+01, 2.65795891678410747261e+01), + .err_limit = 1e-4, + }, + + /* cond = 700, abs det = 1e-6, invertible regardless of det */ + { + .M = MAT(1.32125189257677579449e-03, -1.67411409720826992453e-01, 1.07940907587735196449e-01, -1.22163309792902186057e-01, + -5.42113793774764013422e-02, 5.30455105336593901733e-01, -2.59607412684229155175e-01, 4.36480803188117993940e-01, + 2.88175168292948129939e-03, -1.85262537685181277736e-01, 1.46265858042118279680e-01, -9.41398969709369287662e-02, + -2.88900393087768159184e-03, 1.57987202530630227448e-01, -1.20781192010860280450e-01, 8.95194304475115387731e-02), + .err_limit = 1e-4, + }, + + /* cond = 1e6, this is a little more challenging */ + { + .M = MAT(-4.41851445093878913983e-01, -5.16386185043831491548e-01, 2.86186055948129847160e-01, -5.79440137716940473211e-01, + 2.49798696238173301154e-01, 2.84965614532234345901e-01, -1.65729639683955931595e-01, 3.12568045963485974248e-01, + 3.15253213984537428161e-01, 3.71270066781250074328e-01, -2.02675623845341434937e-01, 4.19969870491003371971e-01, + 5.60818677658178832424e-01, 6.45373659426444201692e-01, -3.68902466471524526082e-01, 7.13785795079988516498e-01), + .err_limit = 0.02, + }, + + /* cond = 15, abs det = 1e-9, should be well invertible */ + { + .M = MAT(-5.37536200142514660589e-05, 7.92552373388843642288e-03, -3.90554524958281433500e-03, 2.68892064500873568395e-03, + -9.72329428437283989350e-03, 8.32075145342783470404e-03, 6.52648485926096092596e-03, 1.06707947887298994737e-03, + 1.04453728969657322345e-02, -1.03627268579679666927e-02, -3.56835980207569763989e-03, -3.95935925157862422114e-03, + 5.37160838929722633805e-03, 6.13466744624343262009e-05, -1.23695935407398946090e-04, 8.21231194921675112380e-04), + .err_limit = 1e-6, + }, +}; -static void __attribute__((noinline)) -test_loop_speed_invert_explicit(void) +TEST_P(matrix_inversion_precision, matrices) { - struct weston_matrix m; - unsigned long count = 0; - double t; - - printf("\nRunning 3 s test on weston_matrix_invert()...\n"); - - weston_matrix_init(&m); - - running = 1; - alarm(3); - reset_timer(); - while (running) { - weston_matrix_invert(&m, &m); - count++; + const struct test_matrix *tm = data; + struct weston_matrix rr; + double err; + + /* Compute rr = M * inv(M) */ + weston_matrix_invert(&rr, &tm->M); + weston_matrix_multiply(&rr, &tm->M); + + /* Residual: subtract identity matrix (expected result) */ + subtract_matrix(&rr, &IDENTITY); + + /* + * Infinity norm of the residual is our measure. + * See https://gitlab.freedesktop.org/pq/fourbyfour/-/blob/master/README.d/precision_testing.md + */ + err = matrix_inf_norm(&rr); + testlog("Residual error %g (%.1f bits precision), limit %g.\n", + err, -log2(err), tm->err_limit); + + if (err > tm->err_limit) { + testlog("Error is too high for matrix\n"); + print_matrix(&tm->M); + assert(0); } - t = read_timer(); - - printf("%lu iterations in %f seconds, avg. %.1f ns/iter.\n", - count, t, 1e9 * t / count); -} - -int main(void) -{ - struct sigaction ding; - struct weston_matrix M; - struct inverse_matrix Q; - int ret; - double errsup; - double det; - - ding.sa_handler = stopme; - sigemptyset(&ding.sa_mask); - ding.sa_flags = 0; - sigaction(SIGALRM, &ding, NULL); - - srandom(13); - - M.d[0] = 3.0; M.d[4] = 17.0; M.d[8] = 10.0; M.d[12] = 0.0; - M.d[1] = 2.0; M.d[5] = 4.0; M.d[9] = -2.0; M.d[13] = 0.0; - M.d[2] = 6.0; M.d[6] = 18.0; M.d[10] = -12; M.d[14] = 0.0; - M.d[3] = 0.0; M.d[7] = 0.0; M.d[11] = 0.0; M.d[15] = 1.0; - - ret = matrix_invert(Q.LU, Q.perm, &M); - printf("ret = %d\n", ret); - printf("det = %g\n\n", determinant(&M)); - - if (ret != 0) - return 1; - - print_inverse_data_matrix(&Q); - printf("P * A = L * U\n"); - print_permutation_matrix(&Q); - print_LU_decomposition(&Q); - - - printf("a random matrix:\n"); - randomize_matrix(&M); - det = determinant(&M); - print_matrix(&M); - errsup = test_inverse(&M); - printf("\nThe matrix multiplied by its inverse, error:\n"); - print_matrix(&M); - printf("max abs error: %g, original determinant %g\n", errsup, det); - - test_loop_precision(); - test_loop_speed_matrixvector(); - test_loop_speed_inversetransform(); - test_loop_speed_invert(); - test_loop_speed_invert_explicit(); - - return 0; } diff --git a/tests/meson.build b/tests/meson.build index d8e96e77..cc35934d 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -2,7 +2,7 @@ plugin_test_shell_desktop = shared_library( 'weston-test-desktop-shell', 'weston-test-desktop-shell.c', include_directories: common_inc, - dependencies: [ dep_lib_desktop, dep_libweston_public, dep_libexec_weston ], + dependencies: [ dep_libweston_public, dep_libexec_weston, dep_shell_utils ], name_prefix: '', install: false ) @@ -14,6 +14,7 @@ lib_test_runner = static_library( dependencies: [ dep_libweston_private_h_deps, dep_wayland_client, + dep_libdl, ], include_directories: common_inc, install: false, @@ -29,8 +30,9 @@ lib_test_client = static_library( 'weston-test-client-helper.c', 'weston-test-fixture-compositor.c', weston_test_client_protocol_h, - weston_screenshooter_protocol_c, weston_test_protocol_c, + weston_screenshooter_client_protocol_h, + weston_screenshooter_protocol_c, viewporter_client_protocol_h, viewporter_protocol_c, 'color_util.h', @@ -59,6 +61,21 @@ dep_test_client = declare_dependency( ] ) +lib_lcms_util = static_library( + 'lib_lcms_util', + [ 'lcms_util.c' ], + include_directories: common_inc, + dependencies: [ + dep_lcms2, dep_libm + ], + build_by_default: false, + install: false, +) +dep_lcms_util = declare_dependency( + link_with: lib_lcms_util, + dependencies: [ dep_lcms2 ] +) + exe_plugin_test = shared_library( 'test-plugin', 'weston-test.c', @@ -90,8 +107,6 @@ lib_zuc = static_library( '../tools/zunitc/inc/zunitc/zunitc_impl.h', '../tools/zunitc/src/zuc_base_logger.c', '../tools/zunitc/src/zuc_base_logger.h', - '../tools/zunitc/src/zuc_collector.c', - '../tools/zunitc/src/zuc_collector.h', '../tools/zunitc/src/zuc_context.h', '../tools/zunitc/src/zuc_event.h', '../tools/zunitc/src/zuc_event_listener.h', @@ -126,7 +141,12 @@ tests = [ }, { 'name': 'bad-buffer', }, { 'name': 'buffer-transforms', }, + { + 'name': 'color-metadata-errors', + 'dep_objs': dep_libexec_weston, + }, { 'name': 'color-manager', }, + { 'name': 'custom-env', }, { 'name': 'devices', }, { 'name': 'drm-formats', @@ -152,6 +172,10 @@ tests = [ linux_explicit_synchronization_unstable_v1_protocol_c, ], }, + { + 'name': 'matrix', + 'dep_objs': [ dep_libm ] + }, { 'name': 'output-damage', }, { 'name': 'output-transforms', }, { 'name': 'plugin-registry', }, @@ -181,6 +205,14 @@ tests = [ xdg_shell_protocol_c, ], }, + { + 'name': 'single-pixel-buffer', + 'sources': [ + 'single-pixel-buffer-test.c', + single_pixel_buffer_v1_client_protocol_h, + single_pixel_buffer_v1_protocol_c, + ] + }, { 'name': 'string', }, { 'name': 'subsurface', }, { 'name': 'subsurface-shot', }, @@ -203,10 +235,6 @@ tests = [ input_timestamps_unstable_v1_protocol_c, ], }, - { - 'name': 'vertex-clip', - 'dep_objs': dep_vertex_clipping, - }, { 'name': 'viewporter', }, { 'name': 'viewporter-shot', }, { @@ -220,15 +248,39 @@ tests = [ { 'name': 'safe-signal-output-removal', 'sources': [ 'safe-signal-output-removal-test.c', - '../shared/shell-utils.c', ], - 'dep_objs': [ dep_lib_desktop ] + 'dep_objs': [ dep_shell_utils ] }, ] +if get_option('renderer-gl') + tests += { + 'name': 'vertex-clip', + 'link_with': plugin_gl, + } + +endif + +if get_option('color-management-lcms') + if not dep_lcms2.found() + error('color-management-lcms tests require lcms2 which was not found. Or, you can use \'-Dcolor-management-lcms=false\'.') + endif + tests += [ + { + 'name': 'color-icc-output', + 'dep_objs': [ dep_libm, dep_lcms_util ] + }, + { 'name': 'color-metadata-parsing' }, + { + 'name': 'lcms-util', + 'dep_objs': [ dep_lcms_util ] + }, + ] +endif + + tests_standalone = [ ['config-parser', [], [ dep_zucmain ]], - ['matrix', [], [ dep_libm, dep_matrix_c ]], ['timespec', [], [ dep_zucmain ]], ['zuc', [ @@ -319,12 +371,12 @@ foreach t : tests t_name, t_sources, c_args: [ - '-DUNIT_TEST', '-DTHIS_TEST_NAME="' + t_name + '"', ], build_by_default: true, include_directories: common_inc, dependencies: t_deps, + link_with: t.get('link_with', []), install: false, ) @@ -362,7 +414,6 @@ foreach t : tests_standalone exe_t = executable( 'test-@0@'.format(t.get(0)), srcs_t, - c_args: [ '-DUNIT_TEST' ], build_by_default: true, include_directories: common_inc, dependencies: deps_t, diff --git a/tests/reference/output_icc_alpha_blend-00.png b/tests/reference/output_icc_alpha_blend-00.png new file mode 100644 index 00000000..beec77b8 Binary files /dev/null and b/tests/reference/output_icc_alpha_blend-00.png differ diff --git a/tests/reference/output_icc_alpha_blend-01.png b/tests/reference/output_icc_alpha_blend-01.png new file mode 100644 index 00000000..45250ecc Binary files /dev/null and b/tests/reference/output_icc_alpha_blend-01.png differ diff --git a/tests/reference/output_icc_alpha_blend-02.png b/tests/reference/output_icc_alpha_blend-02.png new file mode 100644 index 00000000..7714b103 Binary files /dev/null and b/tests/reference/output_icc_alpha_blend-02.png differ diff --git a/tests/reference/shaper_matrix-00.png b/tests/reference/shaper_matrix-00.png new file mode 100644 index 00000000..3671b0ee Binary files /dev/null and b/tests/reference/shaper_matrix-00.png differ diff --git a/tests/reference/shaper_matrix-01.png b/tests/reference/shaper_matrix-01.png new file mode 100644 index 00000000..6f600083 Binary files /dev/null and b/tests/reference/shaper_matrix-01.png differ diff --git a/tests/reference/shaper_matrix-02.png b/tests/reference/shaper_matrix-02.png new file mode 100644 index 00000000..0abca0be Binary files /dev/null and b/tests/reference/shaper_matrix-02.png differ diff --git a/tests/reference/single-pixel-buffer-00.png b/tests/reference/single-pixel-buffer-00.png new file mode 100644 index 00000000..2fa7f594 Binary files /dev/null and b/tests/reference/single-pixel-buffer-00.png differ diff --git a/tests/safe-signal-output-removal-test.c b/tests/safe-signal-output-removal-test.c index 0a747307..596ca498 100644 --- a/tests/safe-signal-output-removal-test.c +++ b/tests/safe-signal-output-removal-test.c @@ -30,7 +30,7 @@ #include #include "../shared/signal.h" -#include "../shared/shell-utils.h" +#include "shell-utils.h" #include "weston-test-client-helper.h" #include "weston-test-fixture-compositor.h" @@ -38,7 +38,7 @@ struct test_output { struct weston_compositor *compositor; struct weston_output *output; struct wl_listener output_destroy_listener; - struct weston_view *view; + struct weston_curtain *curtain; }; static enum test_result_code @@ -60,8 +60,8 @@ output_destroy(struct test_output *t_output) t_output->output = NULL; t_output->output_destroy_listener.notify = NULL; - if (t_output->view) - weston_surface_destroy(t_output->view->surface); + if (t_output->curtain) + weston_curtain_destroy(t_output->curtain); wl_list_remove(&t_output->output_destroy_listener.link); free(t_output); @@ -79,20 +79,18 @@ notify_output_destroy(struct wl_listener *listener, void *data) static void output_create_view(struct test_output *t_output) { - struct weston_solid_color_surface solid_surface = {}; - - solid_surface.r = 0.5; - solid_surface.g = 0.5; - solid_surface.b = 0.5; - - solid_surface.get_label = NULL; - solid_surface.surface_committed = NULL; - solid_surface.surface_private = NULL; - - t_output->view = - create_solid_color_surface(t_output->compositor, - &solid_surface, 0, 0, 320, 240); - weston_view_set_output(t_output->view, t_output->output); + struct weston_curtain_params curtain_params = { + .r = 0.5, .g = 0.5, .b = 0.5, .a = 1.0, + .x = 0, .y = 0, + .width = 320, .height = 240, + .get_label = NULL, + .surface_committed = NULL, + .surface_private = NULL, + }; + + t_output->curtain = weston_curtain_create(t_output->compositor, + &curtain_params); + weston_view_set_output(t_output->curtain->view, t_output->output); /* weston_compositor_remove_output() has to be patched with * weston_signal_emit_mutable() to avoid signal corruption */ diff --git a/tests/safe-signal-test.c b/tests/safe-signal-test.c index 3248f0f7..4b5ac643 100644 --- a/tests/safe-signal-test.c +++ b/tests/safe-signal-test.c @@ -88,4 +88,5 @@ TEST(real_usecase_standalone) add_destroy_listener(st_new); destroy_test_surface(st); + destroy_test_surface(st_new); } diff --git a/tests/single-pixel-buffer-test.c b/tests/single-pixel-buffer-test.c new file mode 100644 index 00000000..ba013232 --- /dev/null +++ b/tests/single-pixel-buffer-test.c @@ -0,0 +1,111 @@ +/* + * Copyright © 2020 Collabora, Ltd. + * + * 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. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" +#include "single-pixel-buffer-v1-client-protocol.h" +#include "shared/os-compatibility.h" +#include "shared/xalloc.h" + +struct setup_args { + struct fixture_metadata meta; + enum renderer_type renderer; +}; + +static const struct setup_args my_setup_args[] = { + { + .renderer = RENDERER_PIXMAN, + .meta.name = "pixman" + }, + { + .renderer = RENDERER_GL, + .meta.name = "GL" + }, +}; + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness, const struct setup_args *arg) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.renderer = arg->renderer; + setup.width = 320; + setup.height = 240; + setup.shell = SHELL_TEST_DESKTOP; + setup.logging_scopes = "log,test-harness-plugin"; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, my_setup_args, meta); + +TEST(solid_buffer_argb_u32) +{ + struct client *client; + struct wp_single_pixel_buffer_manager_v1 *mgr; + struct wp_viewport *viewport; + struct wl_buffer *buffer; + int done; + bool match; + + client = create_client(); + client->surface = create_test_surface(client); + viewport = client_create_viewport(client); + wp_viewport_set_destination(viewport, 128, 128); + + mgr = bind_to_singleton_global(client, + &wp_single_pixel_buffer_manager_v1_interface, + 1); + buffer = wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer(mgr, + 0xcfffffff, /* r */ + 0x8fffffff, /* g */ + 0x4fffffff, /* b */ + 0xffffffff /* a */); + assert(buffer); + + weston_test_move_surface(client->test->weston_test, + client->surface->wl_surface, + 64, 64); + wl_surface_attach(client->surface->wl_surface, buffer, 0, 0); + wl_surface_damage_buffer(client->surface->wl_surface, 0, 0, 1, 1); + frame_callback_set(client->surface->wl_surface, &done); + wl_surface_commit(client->surface->wl_surface); + frame_callback_wait(client, &done); + + match = verify_screen_content(client, "single-pixel-buffer", 0, NULL, 0); + assert(match); + + wl_buffer_destroy(buffer); + wp_viewport_destroy(viewport); + client_destroy(client); +} diff --git a/tests/subsurface-shot-test.c b/tests/subsurface-shot-test.c index 16534497..df7d0d3c 100644 --- a/tests/subsurface-shot-test.c +++ b/tests/subsurface-shot-test.c @@ -287,6 +287,7 @@ TEST(subsurface_empty_mapping) struct wl_subcompositor *subco; struct wp_viewporter *viewporter; struct buffer *bufs[3] = { 0 }; + struct buffer *buf_tmp; struct wl_surface *surf[3] = { 0 }; struct wl_subsurface *sub[3] = { 0 }; struct wp_viewport *viewport; @@ -385,7 +386,9 @@ TEST(subsurface_empty_mapping) fail += check_screen(client, "subsurface_empty_mapping", 0, &clip, 10); /* remap middle surface to ensure recursive mapping */ + buf_tmp = bufs[1]; bufs[1] = surface_commit_color(client, surf[1], &blue, 100, 100); + buffer_destroy(buf_tmp); fail += check_screen(client, "subsurface_empty_mapping", 1, &clip, 11); diff --git a/tests/surface-global-test.c b/tests/surface-global-test.c index a9e4ff9d..63393986 100644 --- a/tests/surface-global-test.c +++ b/tests/surface-global-test.c @@ -90,5 +90,5 @@ PLUGIN_TEST(surface_to_from_global) assert(ix == 0 && iy == 0); /* Destroys all views too. */ - weston_surface_destroy(surface); + weston_surface_unref(surface); } diff --git a/tests/surface-test.c b/tests/surface-test.c index 68288a86..1a1db287 100644 --- a/tests/surface-test.c +++ b/tests/surface-test.c @@ -70,5 +70,5 @@ PLUGIN_TEST(surface_transform) assert(x == 200 && y == 340); /* Destroys all views too. */ - weston_surface_destroy(surface); + weston_surface_unref(surface); } diff --git a/tests/visualization/weston_plot_rgb_diff_stat.m b/tests/visualization/weston_plot_rgb_diff_stat.m new file mode 100644 index 00000000..b2546a31 --- /dev/null +++ b/tests/visualization/weston_plot_rgb_diff_stat.m @@ -0,0 +1,77 @@ +% -- weston_plot_rgb_diff_stat (fname) +% -- weston_plot_rgb_diff_stat (fname, scale) +% -- weston_plot_rgb_diff_stat (fname, scale, x_column) +% Plot an rgb_diff_stat dump file +% +% Creates a new figure and draws four sub-plots: R difference, +% G difference, B difference, and two-norm error. +% +% Scale defaults to 255. It is used to multiply both x and y values +% in all plots. Note that R, G and B plots will contain horizontal lines +% at y = +/- 0.5 to help you see the optimal rounding error range for +% the integer encoding [0, scale]. Two-norm plot contains a horizontal +% line at y = sqrt(0.75) which represents an error sphere with the radius +% equal to the two-norm of RGB error (0.5, 0.5, 0.5). +% +% By default, x-axis is sample index, not multiplied by scale. If +% x_column is given, it is a column index in the dump file to be used as +% the x-axis values, multiplied by scale. Indices start from 1, not 0. + +% SPDX-FileCopyrightText: 2022 Collabora, Ltd. +% SPDX-License-Identifier: MIT + +function weston_plot_rgb_diff_stat(fname, scale, x_column); + +S = load(fname); + +if nargin < 2 + scale = 255; +end +if nargin < 3 + x = 1:size(S, 1); +else + x = S(:, x_column) .* scale; +end + +x_lim = [min(x) max(x)]; + +evec = S(:, 1) .* scale; # two-norm error +rvec = S(:, 2) .* scale; # r diff +gvec = S(:, 3) .* scale; # g diff +bvec = S(:, 4) .* scale; # b diff + +figure + +subplot(4, 1, 1); +plot(x, rvec, 'r'); +plus_minus_half_lines(x_lim); +title(fname, "Interpreter", "none"); +ylabel('R diff'); +axis("tight"); + +subplot(4, 1, 2); +plot(x, gvec, 'g'); +plus_minus_half_lines(x_lim); +ylabel('G diff'); +axis("tight"); + +subplot(4, 1, 3); +plot(x, bvec, 'b'); +plus_minus_half_lines(x_lim); +ylabel('B diff'); +axis("tight"); + +subplot(4, 1, 4); +plot(x, evec, 'k'); +hold on; +plot(x_lim, [1 1] .* sqrt(0.75), 'k:'); +ylabel('Two-norm'); +axis("tight"); + +max_abs_err = [max(abs(rvec)) max(abs(gvec)) max(abs(bvec))] + +function plus_minus_half_lines(x_lim); + +hold on; +plot(x_lim, [0.5 -0.5; 0.5 -0.5], 'k:'); + diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c index 01c4b804..ca49a04d 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -38,9 +38,11 @@ #include "test-config.h" #include "shared/os-compatibility.h" +#include "shared/string-helpers.h" #include "shared/xalloc.h" #include #include "weston-test-client-helper.h" +#include "image-iter.h" #define max(a, b) (((a) > (b)) ? (a) : (b)) #define min(a, b) (((a) > (b)) ? (b) : (a)) @@ -870,22 +872,6 @@ static const struct wl_registry_listener registry_listener = { handle_global_remove, }; -void -skip(const char *fmt, ...) -{ - va_list argp; - - va_start(argp, fmt); - vfprintf(stderr, fmt, argp); - va_end(argp); - - /* automake tests uses exit code 77. weston-test-runner will see - * this and use it, and then weston-test's sigchld handler (in the - * weston process) will use that as an exit status, which is what - * ninja will see in the end. */ - exit(77); -} - void expect_protocol_error(struct client *client, const struct wl_interface *intf, @@ -992,6 +978,7 @@ create_test_surface(struct client *client) surface = xzalloc(sizeof *surface); + surface->client = client; surface->wl_surface = wl_compositor_create_surface(client->wl_compositor); assert(surface->wl_surface); @@ -1014,6 +1001,17 @@ surface_destroy(struct surface *surface) free(surface); } +void +surface_set_opaque_rect(struct surface *surface, const struct rectangle *rect) +{ + struct wl_region *region; + + region = wl_compositor_create_region(surface->client->wl_compositor); + wl_region_add(region, rect->x, rect->y, rect->width, rect->height); + wl_surface_set_opaque_region(surface->wl_surface, region); + wl_region_destroy(region); +} + struct client * create_client_and_test_surface(int x, int y, int width, int height) { @@ -1147,6 +1145,36 @@ image_filename(const char *basename) return filename; } +/** Open a writable file + * + * \param suffix Custom file name suffix. + * \return FILE pointer, or NULL on failure. + * + * The file name consists of output path, test name, and the given suffix. + * If environment variable WESTON_TEST_OUTPUT_PATH is set, it is used as the + * directory path, otherwise the current directory is used. + * + * The file will be writable. If it exists, it is truncated, otherwise it is + * created. Failures are logged. + */ +FILE * +fopen_dump_file(const char *suffix) +{ + char *fname; + FILE *fp; + + str_printf(&fname, "%s/%s-%s.txt", output_path(), + get_test_name(), suffix); + fp = fopen(fname, "w"); + if (!fp) { + testlog("Error: failed to open file '%s' for writing: %s\n", + fname, strerror(errno)); + } + free(fname); + + return fp; +} + struct format_map_entry { cairo_format_t cairo; pixman_format_code_t pixman; @@ -1204,8 +1232,8 @@ range_get(const struct range *r) /** * Compute the ROI for image comparisons * - * \param img_a An image. - * \param img_b Another image. + * \param ih_a A header for an image. + * \param ih_b A header for another image. * \param clip_rect Explicit ROI, or NULL for using the whole * image area. * @@ -1219,21 +1247,12 @@ range_get(const struct range *r) * The ROI is given as pixman_box32_t, where x2,y2 are non-inclusive. */ static pixman_box32_t -image_check_get_roi(pixman_image_t *img_a, pixman_image_t *img_b, - const struct rectangle *clip_rect) +image_check_get_roi(const struct image_header *ih_a, + const struct image_header *ih_b, + const struct rectangle *clip_rect) { - int width_a; - int width_b; - int height_a; - int height_b; pixman_box32_t box; - width_a = pixman_image_get_width(img_a); - height_a = pixman_image_get_height(img_a); - - width_b = pixman_image_get_width(img_b); - height_b = pixman_image_get_height(img_b); - if (clip_rect) { box.x1 = clip_rect->x; box.y1 = clip_rect->y; @@ -1242,45 +1261,22 @@ image_check_get_roi(pixman_image_t *img_a, pixman_image_t *img_b, } else { box.x1 = 0; box.y1 = 0; - box.x2 = max(width_a, width_b); - box.y2 = max(height_a, height_b); + box.x2 = max(ih_a->width, ih_b->width); + box.y2 = max(ih_a->height, ih_b->height); } assert(box.x1 >= 0); assert(box.y1 >= 0); assert(box.x2 > box.x1); assert(box.y2 > box.y1); - assert(box.x2 <= width_a); - assert(box.x2 <= width_b); - assert(box.y2 <= height_a); - assert(box.y2 <= height_b); + assert(box.x2 <= ih_a->width); + assert(box.x2 <= ih_b->width); + assert(box.y2 <= ih_a->height); + assert(box.y2 <= ih_b->height); return box; } -struct image_iterator { - char *data; - int stride; /* bytes */ -}; - -static void -image_iter_init(struct image_iterator *it, pixman_image_t *image) -{ - pixman_format_code_t fmt; - - it->stride = pixman_image_get_stride(image); - it->data = (void *)pixman_image_get_data(image); - - fmt = pixman_image_get_format(image); - assert(PIXMAN_FORMAT_BPP(fmt) == 32); -} - -static uint32_t * -image_iter_get_row(struct image_iterator *it, int y) -{ - return (uint32_t *)(it->data + y * it->stride); -} - struct pixel_diff_stat { struct pixel_diff_stat_channel { int min_diff; @@ -1351,21 +1347,18 @@ check_images_match(pixman_image_t *img_a, pixman_image_t *img_b, { struct range fuzz = range_get(prec); struct pixel_diff_stat diffstat = {}; - struct image_iterator it_a; - struct image_iterator it_b; + struct image_header ih_a = image_header_from(img_a); + struct image_header ih_b = image_header_from(img_b); pixman_box32_t box; int x, y; uint32_t *pix_a; uint32_t *pix_b; - box = image_check_get_roi(img_a, img_b, clip_rect); - - image_iter_init(&it_a, img_a); - image_iter_init(&it_b, img_b); + box = image_check_get_roi(&ih_a, &ih_b, clip_rect); for (y = box.y1; y < box.y2; y++) { - pix_a = image_iter_get_row(&it_a, y) + box.x1; - pix_b = image_iter_get_row(&it_b, y) + box.x1; + pix_a = image_header_get_row_u32(&ih_a, y) + box.x1; + pix_b = image_header_get_row_u32(&ih_b, y) + box.x1; for (x = box.x1; x < box.x2; x++) { if (!fuzzy_match_pixels(*pix_a, *pix_b, @@ -1435,11 +1428,9 @@ visualize_image_difference(pixman_image_t *img_a, pixman_image_t *img_b, struct pixel_diff_stat diffstat = {}; pixman_image_t *diffimg; pixman_image_t *shade; - struct image_iterator it_a; - struct image_iterator it_b; - struct image_iterator it_d; - int width; - int height; + struct image_header ih_a = image_header_from(img_a); + struct image_header ih_b = image_header_from(img_b); + struct image_header ih_d; pixman_box32_t box; int x, y; uint32_t *pix_a; @@ -1447,32 +1438,28 @@ visualize_image_difference(pixman_image_t *img_a, pixman_image_t *img_b, uint32_t *pix_d; pixman_color_t shade_color = { 0, 0, 0, 32768 }; - width = pixman_image_get_width(img_a); - height = pixman_image_get_height(img_a); - box = image_check_get_roi(img_a, img_b, clip_rect); + box = image_check_get_roi(&ih_a, &ih_b, clip_rect); diffimg = pixman_image_create_bits_no_clear(PIXMAN_x8r8g8b8, - width, height, NULL, 0); + ih_a.width, ih_a.height, + NULL, 0); + ih_d = image_header_from(diffimg); /* Fill diffimg with a black-shaded copy of img_a, and then fill * the clip_rect area with original img_a. */ shade = pixman_image_create_solid_fill(&shade_color); pixman_image_composite32(PIXMAN_OP_SRC, img_a, shade, diffimg, - 0, 0, 0, 0, 0, 0, width, height); + 0, 0, 0, 0, 0, 0, ih_a.width, ih_a.height); pixman_image_unref(shade); pixman_image_composite32(PIXMAN_OP_SRC, img_a, NULL, diffimg, box.x1, box.y1, 0, 0, box.x1, box.y1, box.x2 - box.x1, box.y2 - box.y1); - image_iter_init(&it_a, img_a); - image_iter_init(&it_b, img_b); - image_iter_init(&it_d, diffimg); - for (y = box.y1; y < box.y2; y++) { - pix_a = image_iter_get_row(&it_a, y) + box.x1; - pix_b = image_iter_get_row(&it_b, y) + box.x1; - pix_d = image_iter_get_row(&it_d, y) + box.x1; + pix_a = image_header_get_row_u32(&ih_a, y) + box.x1; + pix_b = image_header_get_row_u32(&ih_b, y) + box.x1; + pix_d = image_header_get_row_u32(&ih_d, y) + box.x1; for (x = box.x1; x < box.x2; x++) { if (fuzzy_match_pixels(*pix_a, *pix_b, @@ -1508,16 +1495,12 @@ write_image_as_png(pixman_image_t *image, const char *fname) { cairo_surface_t *cairo_surface; cairo_status_t status; - cairo_format_t fmt; - - fmt = format_pixman2cairo(pixman_image_get_format(image)); + struct image_header ih = image_header_from(image); + cairo_format_t fmt = format_pixman2cairo(ih.pixman_format); - cairo_surface = cairo_image_surface_create_for_data( - (void *)pixman_image_get_data(image), - fmt, - pixman_image_get_width(image), - pixman_image_get_height(image), - pixman_image_get_stride(image)); + cairo_surface = cairo_image_surface_create_for_data(ih.data, fmt, + ih.width, ih.height, + ih.stride_bytes); status = cairo_surface_write_to_png(cairo_surface, fname); if (status != CAIRO_STATUS_SUCCESS) { @@ -1536,21 +1519,17 @@ static pixman_image_t * image_convert_to_a8r8g8b8(pixman_image_t *image) { pixman_image_t *ret; - int width; - int height; + struct image_header ih = image_header_from(image); - if (pixman_image_get_format(image) == PIXMAN_a8r8g8b8) + if (ih.pixman_format == PIXMAN_a8r8g8b8) return pixman_image_ref(image); - width = pixman_image_get_width(image); - height = pixman_image_get_height(image); - - ret = pixman_image_create_bits_no_clear(PIXMAN_a8r8g8b8, width, height, - NULL, 0); + ret = pixman_image_create_bits_no_clear(PIXMAN_a8r8g8b8, + ih.width, ih.height, NULL, 0); assert(ret); pixman_image_composite32(PIXMAN_OP_SRC, image, NULL, ret, - 0, 0, 0, 0, 0, 0, width, height); + 0, 0, 0, 0, 0, 0, ih.width, ih.height); return ret; } diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index 8e1505d4..34183249 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -172,6 +172,8 @@ struct buffer { }; struct surface { + struct client *client; /* not owned */ + struct wl_surface *wl_surface; struct output *output; /* not owned */ int x; @@ -205,6 +207,9 @@ create_test_surface(struct client *client); void surface_destroy(struct surface *surface); +void +surface_set_opaque_rect(struct surface *surface, const struct rectangle *rect); + struct client * create_client_and_test_surface(int x, int y, int width, int height); @@ -232,9 +237,6 @@ frame_callback_wait_nofail(struct client *client, int *done); #define frame_callback_wait(c, d) assert(frame_callback_wait_nofail((c), (d))) -void -skip(const char *fmt, ...); - void expect_protocol_error(struct client *client, const struct wl_interface *intf, uint32_t code); @@ -248,6 +250,9 @@ screenshot_reference_filename(const char *basename, uint32_t seq); char * image_filename(const char *basename); +FILE * +fopen_dump_file(const char *suffix); + bool check_images_match(pixman_image_t *img_a, pixman_image_t *img_b, const struct rectangle *clip, diff --git a/tests/weston-test-desktop-shell.c b/tests/weston-test-desktop-shell.c index 91654baa..eb231e0d 100644 --- a/tests/weston-test-desktop-shell.c +++ b/tests/weston-test-desktop-shell.c @@ -39,14 +39,14 @@ #include "compositor/weston.h" #include #include "shared/helpers.h" +#include "shell-utils.h" #include struct desktest_shell { struct wl_listener compositor_destroy_listener; struct weston_desktop *desktop; struct weston_layer background_layer; - struct weston_surface *background_surface; - struct weston_view *background_view; + struct weston_curtain *background; struct weston_layer layer; struct weston_view *view; }; @@ -92,7 +92,7 @@ desktop_surface_committed(struct weston_desktop_surface *desktop_surface, if (weston_surface_is_mapped(surface)) return; - surface->is_mapped = true; + weston_surface_map(surface); weston_layer_entry_insert(&dts->layer.view_list, &dts->view->layer_link); weston_view_set_position(dts->view, 0 - geometry.x, 0 - geometry.y); weston_view_update_transform(dts->view); @@ -174,8 +174,7 @@ shell_destroy(struct wl_listener *listener, void *data) wl_list_remove(&dts->compositor_destroy_listener.link); weston_desktop_destroy(dts->desktop); - weston_view_destroy(dts->background_view); - weston_surface_destroy(dts->background_surface); + weston_curtain_destroy(dts->background); weston_layer_fini(&dts->layer); weston_layer_fini(&dts->background_layer); @@ -188,6 +187,14 @@ wet_shell_init(struct weston_compositor *ec, int *argc, char *argv[]) { struct desktest_shell *dts; + struct weston_curtain_params background_params = { + .r = 0.16, .g = 0.32, .b = 0.48, .a = 1.0, + .x = 0, .y = 0, .width = 2000, .height = 2000, + .capture_input = true, + .surface_committed = NULL, + .get_label = background_get_label, + .surface_private = NULL, + }; dts = zalloc(sizeof *dts); if (!dts) @@ -208,28 +215,17 @@ wet_shell_init(struct weston_compositor *ec, weston_layer_set_position(&dts->background_layer, WESTON_LAYER_POSITION_BACKGROUND); - dts->background_surface = weston_surface_create(ec); - if (dts->background_surface == NULL) + dts->background = weston_curtain_create(ec, &background_params); + if (dts->background == NULL) goto out_free; - - dts->background_view = weston_view_create(dts->background_surface); - if (dts->background_view == NULL) - goto out_surface; - - weston_surface_set_role(dts->background_surface, + weston_surface_set_role(dts->background->view->surface, "test-desktop background", NULL, 0); - weston_surface_set_label_func(dts->background_surface, - background_get_label); - weston_surface_set_color(dts->background_surface, 0.16, 0.32, 0.48, 1.); - pixman_region32_fini(&dts->background_surface->opaque); - pixman_region32_init_rect(&dts->background_surface->opaque, 0, 0, 2000, 2000); - pixman_region32_fini(&dts->background_surface->input); - pixman_region32_init_rect(&dts->background_surface->input, 0, 0, 2000, 2000); - - weston_surface_set_size(dts->background_surface, 2000, 2000); - weston_view_set_position(dts->background_view, 0, 0); - weston_layer_entry_insert(&dts->background_layer.view_list, &dts->background_view->layer_link); - weston_view_update_transform(dts->background_view); + + weston_view_set_position(dts->background->view, 0, 0); + weston_layer_entry_insert(&dts->background_layer.view_list, + &dts->background->view->layer_link); + weston_view_update_transform(dts->background->view); + dts->background->view->is_mapped = true; dts->desktop = weston_desktop_create(ec, &shell_desktop_api, dts); if (dts->desktop == NULL) @@ -240,10 +236,7 @@ wet_shell_init(struct weston_compositor *ec, return 0; out_view: - weston_view_destroy(dts->background_view); - -out_surface: - weston_surface_destroy(dts->background_surface); + weston_curtain_destroy(dts->background); out_free: wl_list_remove(&dts->compositor_destroy_listener.link); diff --git a/tests/weston-test-fixture-compositor.c b/tests/weston-test-fixture-compositor.c index d4ad369d..2e9333a2 100644 --- a/tests/weston-test-fixture-compositor.c +++ b/tests/weston-test-fixture-compositor.c @@ -38,6 +38,7 @@ #include #include "shared/helpers.h" +#include "shared/string-helpers.h" #include "weston-test-fixture-compositor.h" #include "weston.h" #include "test-config.h" @@ -92,6 +93,11 @@ prog_args_fini(struct prog_args *p) { int i; + /* If our args have never been saved, then we haven't called the + * compositor, but we still need to free the args, not leak them. */ + if (!p->saved) + prog_args_save(p); + if (p->saved) { for (i = 0; i < p->argc; i++) free(p->saved[i]); @@ -116,7 +122,8 @@ get_lock_path(void) return NULL; } - if (asprintf(&lock_path, "%s/%s", env_path, suffix) == -1) + str_printf(&lock_path, "%s/%s", env_path, suffix); + if (!lock_path) return NULL; return lock_path; @@ -200,7 +207,6 @@ backend_to_str(enum weston_compositor_backend b) { static const char * const names[] = { [WESTON_BACKEND_DRM] = "drm-backend.so", - [WESTON_BACKEND_FBDEV] = "fbdev-backend.so", [WESTON_BACKEND_HEADLESS] = "headless-backend.so", [WESTON_BACKEND_RDP] = "rdp-backend.so", [WESTON_BACKEND_WAYLAND] = "wayland-backend.so", @@ -285,70 +291,16 @@ execute_compositor(const struct compositor_setup *setup, struct prog_args args; char *tmp; const char *ctmp, *drm_device; - int ret, lock_fd = -1; - - if (setenv("WESTON_MODULE_MAP", WESTON_MODULE_MAP, 0) < 0 || - setenv("WESTON_DATA_DIR", WESTON_DATA_DIR, 0) < 0) { - fprintf(stderr, "Error: environment setup failed.\n"); - return RESULT_HARD_ERROR; - } - -#ifndef BUILD_DRM_COMPOSITOR - if (setup->backend == WESTON_BACKEND_DRM) { - fprintf(stderr, "DRM-backend required but not built, skipping.\n"); - return RESULT_SKIP; - } -#endif - -#ifndef BUILD_FBDEV_COMPOSITOR - if (setup->backend == WESTON_BACKEND_FBDEV) { - fprintf(stderr, "fbdev-backend required but not built, skipping.\n"); - return RESULT_SKIP; - } -#endif - -#ifndef BUILD_RDP_COMPOSITOR - if (setup->backend == WESTON_BACKEND_RDP) { - fprintf(stderr, "RDP-backend required but not built, skipping.\n"); - return RESULT_SKIP; - } -#endif - -#ifndef BUILD_WAYLAND_COMPOSITOR - if (setup->backend == WESTON_BACKEND_WAYLAND) { - fprintf(stderr, "wayland-backend required but not built, skipping.\n"); - return RESULT_SKIP; - } -#endif - -#ifndef BUILD_X11_COMPOSITOR - if (setup->backend == WESTON_BACKEND_X11) { - fprintf(stderr, "X11-backend required but not built, skipping.\n"); - return RESULT_SKIP; - } -#endif - -#ifndef ENABLE_EGL - if (setup->renderer == RENDERER_GL) { - fprintf(stderr, "GL-renderer required but not built, skipping.\n"); - return RESULT_SKIP; - } -#endif - -#if !TEST_GL_RENDERER - if (setup->renderer == RENDERER_GL) { - fprintf(stderr, "GL-renderer disabled for tests, skipping.\n"); - return RESULT_SKIP; - } -#endif + int lock_fd = -1; + int ret = RESULT_OK; prog_args_init(&args); /* argv[0] */ - asprintf(&tmp, "weston-%s", setup->testset_name); + str_printf(&tmp, "weston-%s", setup->testset_name); prog_args_take(&args, tmp); - asprintf(&tmp, "--backend=%s", backend_to_str(setup->backend)); + str_printf(&tmp, "--backend=%s", backend_to_str(setup->backend)); prog_args_take(&args, tmp); if (setup->backend == WESTON_BACKEND_DRM) { @@ -362,7 +314,7 @@ execute_compositor(const struct compositor_setup *setup, ret = RESULT_SKIP; goto out; } - asprintf(&tmp, "--drm-device=%s", drm_device); + str_printf(&tmp, "--drm-device=%s", drm_device); prog_args_take(&args, tmp); prog_args_take(&args, strdup("--seat=weston-test-seat")); @@ -379,36 +331,35 @@ execute_compositor(const struct compositor_setup *setup, /* Test suite needs the debug protocol to be able to take screenshots */ prog_args_take(&args, strdup("--debug")); - asprintf(&tmp, "--socket=%s", setup->testset_name); + str_printf(&tmp, "--socket=%s", setup->testset_name); prog_args_take(&args, tmp); - asprintf(&tmp, "--modules=%s%s%s", TESTSUITE_PLUGIN_PATH, - setup->extra_module ? "," : "", - setup->extra_module ? setup->extra_module : ""); + str_printf(&tmp, "--modules=%s%s%s", TESTSUITE_PLUGIN_PATH, + setup->extra_module ? "," : "", + setup->extra_module ? setup->extra_module : ""); prog_args_take(&args, tmp); - if (setup->backend != WESTON_BACKEND_DRM && - setup->backend != WESTON_BACKEND_FBDEV) { - asprintf(&tmp, "--width=%d", setup->width); + if (setup->backend != WESTON_BACKEND_DRM) { + str_printf(&tmp, "--width=%d", setup->width); prog_args_take(&args, tmp); - asprintf(&tmp, "--height=%d", setup->height); + str_printf(&tmp, "--height=%d", setup->height); prog_args_take(&args, tmp); } if (setup->scale != 1) { - asprintf(&tmp, "--scale=%d", setup->scale); + str_printf(&tmp, "--scale=%d", setup->scale); prog_args_take(&args, tmp); } if (setup->transform != WL_OUTPUT_TRANSFORM_NORMAL) { - asprintf(&tmp, "--transform=%s", - transform_to_str(setup->transform)); + str_printf(&tmp, "--transform=%s", + transform_to_str(setup->transform)); prog_args_take(&args, tmp); } if (setup->config_file) { - asprintf(&tmp, "--config=%s", setup->config_file); + str_printf(&tmp, "--config=%s", setup->config_file); prog_args_take(&args, tmp); free(setup->config_file); } else { @@ -419,11 +370,11 @@ execute_compositor(const struct compositor_setup *setup, if (ctmp) prog_args_take(&args, strdup(ctmp)); - asprintf(&tmp, "--shell=%s", shell_to_str(setup->shell)); + str_printf(&tmp, "--shell=%s", shell_to_str(setup->shell)); prog_args_take(&args, tmp); if (setup->logging_scopes) { - asprintf(&tmp, "--logger-scopes=%s", setup->logging_scopes); + str_printf(&tmp, "--logger-scopes=%s", setup->logging_scopes); prog_args_take(&args, tmp); } @@ -433,7 +384,50 @@ execute_compositor(const struct compositor_setup *setup, test_data.test_quirks = setup->test_quirks; test_data.test_private_data = data; prog_args_save(&args); - ret = wet_main(args.argc, args.argv, &test_data); + + if (setenv("WESTON_MODULE_MAP", WESTON_MODULE_MAP, 0) < 0 || + setenv("WESTON_DATA_DIR", WESTON_DATA_DIR, 0) < 0) { + fprintf(stderr, "Error: environment setup failed.\n"); + ret = RESULT_HARD_ERROR; + } + +#ifndef BUILD_DRM_COMPOSITOR + if (setup->backend == WESTON_BACKEND_DRM) { + fprintf(stderr, "DRM-backend required but not built, skipping.\n"); + ret = RESULT_SKIP; + } +#endif + +#ifndef BUILD_RDP_COMPOSITOR + if (setup->backend == WESTON_BACKEND_RDP) { + fprintf(stderr, "RDP-backend required but not built, skipping.\n"); + ret = RESULT_SKIP; + } +#endif + +#ifndef BUILD_WAYLAND_COMPOSITOR + if (setup->backend == WESTON_BACKEND_WAYLAND) { + fprintf(stderr, "wayland-backend required but not built, skipping.\n"); + ret = RESULT_SKIP; + } +#endif + +#ifndef BUILD_X11_COMPOSITOR + if (setup->backend == WESTON_BACKEND_X11) { + fprintf(stderr, "X11-backend required but not built, skipping.\n"); + ret = RESULT_SKIP; + } +#endif + +#ifndef ENABLE_EGL + if (setup->renderer == RENDERER_GL) { + fprintf(stderr, "GL-renderer required but not built, skipping.\n"); + ret = RESULT_SKIP; + } +#endif + + if (ret == RESULT_OK) + ret = wet_main(args.argc, args.argv, &test_data); out: prog_args_fini(&args); @@ -472,7 +466,8 @@ open_ini_file(struct compositor_setup *setup) wd = realpath(".", NULL); assert(wd); - if (asprintf(&tmp_path, "%s/%s.ini", wd, setup->testset_name) == -1) { + str_printf(&tmp_path, "%s/%s.ini", wd, setup->testset_name); + if (!tmp_path) { fprintf(stderr, "Fail formatting Weston.ini file name.\n"); goto out; } diff --git a/tests/weston-test-runner.c b/tests/weston-test-runner.c index 0ab7bc1b..6bea6705 100644 --- a/tests/weston-test-runner.c +++ b/tests/weston-test-runner.c @@ -34,12 +34,18 @@ #include #include #include +#include #include "test-config.h" #include "weston-test-runner.h" #include "weston-testsuite-data.h" #include "shared/string-helpers.h" +/* This is a glibc extension; if we can't use it then make it harmless. */ +#ifndef RTLD_NODELETE +#define RTLD_NODELETE 0 +#endif + /** * \defgroup testharness Test harness * \defgroup testharness_private Test harness private @@ -627,9 +633,23 @@ main(int argc, char *argv[]) enum test_result_code ret; enum test_result_code result = RESULT_OK; const struct fixture_setup_array *fsa; + const char *leak_dl_handle; int fi; int fi_end; + /* This is horrific, but it gives us working leak checking. If we + * actually unload llvmpipe, then we also unload LLVM, and some global + * setup it's done - which llvmpipe can't tear down because the actual + * client might be using LLVM instead. + * + * Turns out if llvmpipe is always live, then the pointers are always + * reachable, so LeakSanitizer just tells us about our own code rather + * than LLVM's, so ... + */ + leak_dl_handle = getenv("WESTON_CI_LEAK_DL_HANDLE"); + if (leak_dl_handle) + (void) dlopen(leak_dl_handle, RTLD_LAZY | RTLD_GLOBAL | RTLD_NODELETE); + harness = weston_test_harness_create(argc, argv); fsa = fixture_setup_array_get_(); diff --git a/tests/weston-test.c b/tests/weston-test.c index f8db286b..58f27a04 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -164,7 +164,7 @@ test_surface_committed(struct weston_surface *surface, int32_t sx, int32_t sy) weston_view_update_transform(test_surface->view); - test_surface->surface->is_mapped = true; + weston_surface_map(test_surface->surface); test_surface->view->is_mapped = true; } diff --git a/tests/yuv-buffer-test.c b/tests/yuv-buffer-test.c index ad0b298b..d7de5367 100644 --- a/tests/yuv-buffer-test.c +++ b/tests/yuv-buffer-test.c @@ -33,6 +33,7 @@ #include "weston-test-client-helper.h" #include "weston-test-fixture-compositor.h" +#include "image-iter.h" #include "shared/os-compatibility.h" #include "shared/weston-drm-fourcc.h" #include "shared/xalloc.h" @@ -150,20 +151,18 @@ x8r8g8b8_to_ycbcr8_bt601(uint32_t xrgb, * plane 0: Y plane, [7:0] Y * plane 1: Cb plane, [7:0] Cb * plane 2: Cr plane, [7:0] Cr - * 2x2 subsampled Cb (1) and Cr (2) planes + * YUV420: 2x2 subsampled Cb (1) and Cr (2) planes + * YUV444: no subsampling */ static struct yuv_buffer * -yuv420_create_buffer(struct client *client, - uint32_t drm_format, - pixman_image_t *rgb_image) +y_u_v_create_buffer(struct client *client, + uint32_t drm_format, + pixman_image_t *rgb_image) { + struct image_header rgb = image_header_from(rgb_image); struct yuv_buffer *buf; size_t bytes; - int width; - int height; int x, y; - void *rgb_pixels; - int rgb_stride_bytes; uint32_t *rgb_row; uint8_t *y_base; uint8_t *u_base; @@ -172,29 +171,28 @@ yuv420_create_buffer(struct client *client, uint8_t *u_row; uint8_t *v_row; uint32_t argb; + int sub = (drm_format == DRM_FORMAT_YUV420) ? 2 : 1; - assert(drm_format == DRM_FORMAT_YUV420); + assert(drm_format == DRM_FORMAT_YUV420 || + drm_format == DRM_FORMAT_YUV444); - width = pixman_image_get_width(rgb_image); - height = pixman_image_get_height(rgb_image); - rgb_pixels = pixman_image_get_data(rgb_image); - rgb_stride_bytes = pixman_image_get_stride(rgb_image); - - /* Full size Y, quarter U and V */ - bytes = width * height + (width / 2) * (height / 2) * 2; - buf = yuv_buffer_create(client, bytes, width, height, width, drm_format); + /* Full size Y plus quarter U and V */ + bytes = rgb.width * rgb.height + + (rgb.width / sub) * (rgb.height / sub) * 2; + buf = yuv_buffer_create(client, bytes, rgb.width, rgb.height, + rgb.width, drm_format); y_base = buf->data; - u_base = y_base + width * height; - v_base = u_base + (width / 2) * (height / 2); + u_base = y_base + rgb.width * rgb.height; + v_base = u_base + (rgb.width / sub) * (rgb.height / sub); - for (y = 0; y < height; y++) { - rgb_row = rgb_pixels + (y / 2 * 2) * rgb_stride_bytes; - y_row = y_base + y * width; - u_row = u_base + (y / 2) * (width / 2); - v_row = v_base + (y / 2) * (width / 2); + for (y = 0; y < rgb.height; y++) { + rgb_row = image_header_get_row_u32(&rgb, y / 2 * 2); + y_row = y_base + y * rgb.width; + u_row = u_base + (y / sub) * (rgb.width / sub); + v_row = v_base + (y / sub) * (rgb.width / sub); - for (x = 0; x < width; x++) { + for (x = 0; x < rgb.width; x++) { /* * Sub-sample the source image instead, so that U and V * sub-sampling does not require proper @@ -207,10 +205,10 @@ yuv420_create_buffer(struct client *client, * do the necessary filtering/averaging/siting or * alternate Cb/Cr rows. */ - if ((y & 1) == 0 && (x & 1) == 0) { + if ((y & (sub - 1)) == 0 && (x & (sub - 1)) == 0) { x8r8g8b8_to_ycbcr8_bt601(argb, y_row + x, - u_row + x / 2, - v_row + x / 2); + u_row + x / sub, + v_row + x / sub); } else { x8r8g8b8_to_ycbcr8_bt601(argb, y_row + x, NULL, NULL); @@ -232,13 +230,10 @@ nv12_create_buffer(struct client *client, uint32_t drm_format, pixman_image_t *rgb_image) { + struct image_header rgb = image_header_from(rgb_image); struct yuv_buffer *buf; size_t bytes; - int width; - int height; int x, y; - void *rgb_pixels; - int rgb_stride_bytes; uint32_t *rgb_row; uint8_t *y_base; uint16_t *uv_base; @@ -250,24 +245,21 @@ nv12_create_buffer(struct client *client, assert(drm_format == DRM_FORMAT_NV12); - width = pixman_image_get_width(rgb_image); - height = pixman_image_get_height(rgb_image); - rgb_pixels = pixman_image_get_data(rgb_image); - rgb_stride_bytes = pixman_image_get_stride(rgb_image); - /* Full size Y, quarter UV */ - bytes = width * height + (width / 2) * (height / 2) * sizeof(uint16_t); - buf = yuv_buffer_create(client, bytes, width, height, width, drm_format); + bytes = rgb.width * rgb.height + + (rgb.width / 2) * (rgb.height / 2) * sizeof(uint16_t); + buf = yuv_buffer_create(client, bytes, rgb.width, rgb.height, + rgb.width, drm_format); y_base = buf->data; - uv_base = (uint16_t *)(y_base + width * height); + uv_base = (uint16_t *)(y_base + rgb.width * rgb.height); - for (y = 0; y < height; y++) { - rgb_row = rgb_pixels + (y / 2 * 2) * rgb_stride_bytes; - y_row = y_base + y * width; - uv_row = uv_base + (y / 2) * (width / 2); + for (y = 0; y < rgb.height; y++) { + rgb_row = image_header_get_row_u32(&rgb, y / 2 * 2); + y_row = y_base + y * rgb.width; + uv_row = uv_base + (y / 2) * (rgb.width / 2); - for (x = 0; x < width; x++) { + for (x = 0; x < rgb.width; x++) { /* * Sub-sample the source image instead, so that U and V * sub-sampling does not require proper @@ -304,13 +296,10 @@ yuyv_create_buffer(struct client *client, uint32_t drm_format, pixman_image_t *rgb_image) { + struct image_header rgb = image_header_from(rgb_image); struct yuv_buffer *buf; size_t bytes; - int width; - int height; int x, y; - void *rgb_pixels; - int rgb_stride_bytes; uint32_t *rgb_row; uint32_t *yuv_base; uint32_t *yuv_row; @@ -320,22 +309,18 @@ yuyv_create_buffer(struct client *client, assert(drm_format == DRM_FORMAT_YUYV); - width = pixman_image_get_width(rgb_image); - height = pixman_image_get_height(rgb_image); - rgb_pixels = pixman_image_get_data(rgb_image); - rgb_stride_bytes = pixman_image_get_stride(rgb_image); - /* Full size Y, horizontally subsampled UV, 2 pixels in 32 bits */ - bytes = width / 2 * height * sizeof(uint32_t); - buf = yuv_buffer_create(client, bytes, width, height, width / 2 * sizeof(uint32_t), drm_format); + bytes = rgb.width / 2 * rgb.height * sizeof(uint32_t); + buf = yuv_buffer_create(client, bytes, rgb.width, rgb.height, + rgb.width / 2 * sizeof(uint32_t), drm_format); yuv_base = buf->data; - for (y = 0; y < height; y++) { - rgb_row = rgb_pixels + (y / 2 * 2) * rgb_stride_bytes; - yuv_row = yuv_base + y * (width / 2); + for (y = 0; y < rgb.height; y++) { + rgb_row = image_header_get_row_u32(&rgb, y / 2 * 2); + yuv_row = yuv_base + y * (rgb.width / 2); - for (x = 0; x < width; x += 2) { + for (x = 0; x < rgb.width; x += 2) { /* * Sub-sample the source image instead, so that U and V * sub-sampling does not require proper @@ -364,13 +349,10 @@ xyuv8888_create_buffer(struct client *client, uint32_t drm_format, pixman_image_t *rgb_image) { + struct image_header rgb = image_header_from(rgb_image); struct yuv_buffer *buf; size_t bytes; - int width; - int height; int x, y; - void *rgb_pixels; - int rgb_stride_bytes; uint32_t *rgb_row; uint32_t *yuv_base; uint32_t *yuv_row; @@ -380,22 +362,18 @@ xyuv8888_create_buffer(struct client *client, assert(drm_format == DRM_FORMAT_XYUV8888); - width = pixman_image_get_width(rgb_image); - height = pixman_image_get_height(rgb_image); - rgb_pixels = pixman_image_get_data(rgb_image); - rgb_stride_bytes = pixman_image_get_stride(rgb_image); - /* Full size, 32 bits per pixel */ - bytes = width * height * sizeof(uint32_t); - buf = yuv_buffer_create(client, bytes, width, height, width * sizeof(uint32_t), drm_format); + bytes = rgb.width * rgb.height * sizeof(uint32_t); + buf = yuv_buffer_create(client, bytes, rgb.width, rgb.height, + rgb.width * sizeof(uint32_t), drm_format); yuv_base = buf->data; - for (y = 0; y < height; y++) { - rgb_row = rgb_pixels + (y / 2 * 2) * rgb_stride_bytes; - yuv_row = yuv_base + y * width; + for (y = 0; y < rgb.height; y++) { + rgb_row = image_header_get_row_u32(&rgb, y / 2 * 2); + yuv_row = yuv_base + y * rgb.width; - for (x = 0; x < width; x++) { + for (x = 0; x < rgb.width; x++) { /* * 2x2 sub-sample the source image to get the same * result as the other YUV variants, so we can use the @@ -435,7 +413,8 @@ show_window_with_yuv(struct client *client, struct yuv_buffer *buf) static const struct yuv_case yuv_cases[] = { #define FMT(x) DRM_FORMAT_ ##x, #x - { FMT(YUV420), yuv420_create_buffer }, + { FMT(YUV420), y_u_v_create_buffer }, + { FMT(YUV444), y_u_v_create_buffer }, { FMT(NV12), nv12_create_buffer }, { FMT(YUYV), yuyv_create_buffer }, { FMT(XYUV8888), xyuv8888_create_buffer }, diff --git a/tools/zunitc/inc/zunitc/zunitc.h b/tools/zunitc/inc/zunitc/zunitc.h index 16b211ba..d285c59d 100644 --- a/tools/zunitc/inc/zunitc/zunitc.h +++ b/tools/zunitc/inc/zunitc/zunitc.h @@ -239,16 +239,6 @@ zuc_set_repeat(int repeat); void zuc_set_random(int random); -/** - * Controls whether or not to run the tests as forked child processes. - * Defaults to true. - * - * @param spawn true to spawn each test in a forked child process, - * false to run tests directly. - */ -void -zuc_set_spawn(bool spawn); - /** * Enables output in the JUnit XML format. * Defaults to false. diff --git a/tools/zunitc/src/zuc_collector.c b/tools/zunitc/src/zuc_collector.c deleted file mode 100644 index 035c9ce7..00000000 --- a/tools/zunitc/src/zuc_collector.c +++ /dev/null @@ -1,427 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * 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. - */ - -#include "config.h" - -#include "zuc_collector.h" - -#include -#include -#include -#include - -#include -#include "zuc_event_listener.h" -#include "zunitc/zunitc_impl.h" - -#include -#include -#include - -/** - * @file - * General handling of collecting events during testing to pass back to - * main tracking of fork()'d tests. - * - * @note implementation of zuc_process_message() is included here so that - * all child->parent IPC is in a single source file for easier maintenance - * and updating. - */ - -/** - * Internal data struct for processing. - */ -struct collector_data -{ - int *fd; /**< file descriptor to output to. */ - struct zuc_test *test; /**< current test, or NULL. */ -}; - -/** - * Stores an int32_t into the given buffer. - * - * @param ptr the buffer to store to. - * @param val the value to store. - * @return a pointer to the position in the buffer after the stored value. - */ -static char * -pack_int32(char *ptr, int32_t val); - -/** - * Stores an intptr_t into the given buffer. - * - * @param ptr the buffer to store to. - * @param val the value to store. - * @return a pointer to the position in the buffer after the stored value. - */ -static char * -pack_intptr_t(char *ptr, intptr_t val); - -/** - * Extracts a int32_t from the given buffer. - * - * @param ptr the buffer to extract from. - * @param val the value to set. - * @return a pointer to the position in the buffer after the extracted - * value. - */ -static char const * -unpack_int32(char const *ptr, int32_t *val); - -/** - * Extracts a intptr_t from the given buffer. - * - * @param ptr the buffer to extract from. - * @param val the value to set. - * @return a pointer to the position in the buffer after the extracted - * value. - */ -static char const * -unpack_intptr_t(char const *ptr, intptr_t *val); - -/** - * Extracts a length-prefixed string from the given buffer. - * - * @param ptr the buffer to extract from. - * @param str the value to set. - * @return a pointer to the position in the buffer after the extracted - * value. - */ -static char const * -unpack_string(char const *ptr, char **str); - -/** - * Extracts an event from the given buffer. - * - * @param ptr the buffer to extract from. - * @param len the length of the given buffer. - * @return an event that was packed in the buffer - */ -static struct zuc_event * -unpack_event(char const *ptr, int32_t len); - -/** - * Handles an event by either attaching it directly or sending it over IPC - * as needed. - */ -static void -store_event(struct collector_data *cdata, - enum zuc_event_type event_type, char const *file, int line, - enum zuc_fail_state state, enum zuc_check_op op, - enum zuc_check_valtype valtype, - intptr_t val1, intptr_t val2, const char *expr1, const char *expr2); - -static void -destroy(void *data); - -static void -test_started(void *data, struct zuc_test *test); - -static void -test_ended(void *data, struct zuc_test *test); - -static void -check_triggered(void *data, char const *file, int line, - enum zuc_fail_state state, enum zuc_check_op op, - enum zuc_check_valtype valtype, - intptr_t val1, intptr_t val2, - const char *expr1, const char *expr2); - -static void -collect_event(void *data, char const *file, int line, const char *expr1); - -struct zuc_event_listener * -zuc_collector_create(int *pipe_fd) -{ - struct zuc_event_listener *listener = - zalloc(sizeof(struct zuc_event_listener)); - - listener->data = zalloc(sizeof(struct collector_data)); - ((struct collector_data *)listener->data)->fd = pipe_fd; - listener->destroy = destroy; - listener->test_started = test_started; - listener->test_ended = test_ended; - listener->check_triggered = check_triggered; - listener->collect_event = collect_event; - - return listener; -} - -char * -pack_int32(char *ptr, int32_t val) -{ - memcpy(ptr, &val, sizeof(val)); - return ptr + sizeof(val); -} - -char * -pack_intptr_t(char *ptr, intptr_t val) -{ - memcpy(ptr, &val, sizeof(val)); - return ptr + sizeof(val); -} - -static char * -pack_cstr(char *ptr, intptr_t val, int len) -{ - if (val == 0) { /* a null pointer */ - ptr = pack_int32(ptr, -1); - } else { - ptr = pack_int32(ptr, len); - memcpy(ptr, (const char *)val, len); - ptr += len; - } - return ptr; -} - -void -destroy(void *data) -{ - free(data); -} - -void -test_started(void *data, struct zuc_test *test) -{ - struct collector_data *cdata = data; - cdata->test = test; -} - -void -test_ended(void *data, struct zuc_test *test) -{ - struct collector_data *cdata = data; - cdata->test = NULL; -} - -void -check_triggered(void *data, char const *file, int line, - enum zuc_fail_state state, enum zuc_check_op op, - enum zuc_check_valtype valtype, - intptr_t val1, intptr_t val2, - const char *expr1, const char *expr2) -{ - struct collector_data *cdata = data; - if (op != ZUC_OP_TRACEPOINT) - store_event(cdata, ZUC_EVENT_IMMEDIATE, file, line, state, op, - valtype, - val1, val2, expr1, expr2); -} - -void -collect_event(void *data, char const *file, int line, const char *expr1) -{ - struct collector_data *cdata = data; - store_event(cdata, ZUC_EVENT_DEFERRED, file, line, ZUC_CHECK_OK, - ZUC_OP_TRACEPOINT, ZUC_VAL_INT, - 0, 0, expr1, ""); -} - -void -store_event(struct collector_data *cdata, - enum zuc_event_type event_type, char const *file, int line, - enum zuc_fail_state state, enum zuc_check_op op, - enum zuc_check_valtype valtype, - intptr_t val1, intptr_t val2, const char *expr1, const char *expr2) -{ - struct zuc_event *event = zalloc(sizeof(*event)); - event->file = strdup(file); - event->line = line; - event->state = state; - event->op = op; - event->valtype = valtype; - event->val1 = val1; - event->val2 = val2; - if (valtype == ZUC_VAL_CSTR) { - if (val1) - event->val1 = (intptr_t)strdup((const char *)val1); - if (val2) - event->val2 = (intptr_t)strdup((const char *)val2); - } - event->expr1 = strdup(expr1); - event->expr2 = strdup(expr2); - - zuc_attach_event(cdata->test, event, event_type, false); - - if (*cdata->fd == -1) { - } else { - /* Need to pass it back */ - int sent; - int count; - int expr1_len = strlen(expr1); - int expr2_len = strlen(expr2); - int val1_len = - ((valtype == ZUC_VAL_CSTR) && val1) ? - strlen((char *)val1) : 0; - int val2_len = - ((valtype == ZUC_VAL_CSTR) && val2) ? - strlen((char *)val2) : 0; - int file_len = strlen(file); - int len = (sizeof(int32_t) * 9) + file_len - + (sizeof(intptr_t) * 2) - + ((valtype == ZUC_VAL_CSTR) ? - (sizeof(int32_t) * 2) + val1_len + val2_len : 0) - + expr1_len + expr2_len; - char *buf = zalloc(len); - - char *ptr = pack_int32(buf, len - 4); - ptr = pack_int32(ptr, event_type); - ptr = pack_int32(ptr, file_len); - memcpy(ptr, file, file_len); - ptr += file_len; - ptr = pack_int32(ptr, line); - ptr = pack_int32(ptr, state); - ptr = pack_int32(ptr, op); - ptr = pack_int32(ptr, valtype); - if (valtype == ZUC_VAL_CSTR) { - ptr = pack_cstr(ptr, val1, val1_len); - ptr = pack_cstr(ptr, val2, val2_len); - } else { - ptr = pack_intptr_t(ptr, val1); - ptr = pack_intptr_t(ptr, val2); - } - - ptr = pack_int32(ptr, expr1_len); - if (expr1_len) { - memcpy(ptr, expr1, expr1_len); - ptr += expr1_len; - } - ptr = pack_int32(ptr, expr2_len); - if (expr2_len) { - memcpy(ptr, expr2, expr2_len); - ptr += expr2_len; - } - - - sent = 0; - while (sent < len) { - count = write(*cdata->fd, buf, len); - if (count == -1) - break; - sent += count; - } - - free(buf); - } -} - -char const * -unpack_int32(char const *ptr, int32_t *val) -{ - memcpy(val, ptr, sizeof(*val)); - return ptr + sizeof(*val); -} - -char const * -unpack_intptr_t(char const *ptr, intptr_t *val) -{ - memcpy(val, ptr, sizeof(*val)); - return ptr + sizeof(*val); -} - -char const * -unpack_string(char const *ptr, char **str) -{ - int32_t len = 0; - ptr = unpack_int32(ptr, &len); - *str = zalloc(len + 1); - if (len) - memcpy(*str, ptr, len); - ptr += len; - return ptr; -} - -static char const * -unpack_cstr(char const *ptr, char **str) -{ - int32_t len = 0; - ptr = unpack_int32(ptr, &len); - if (len < 0) { - *str = NULL; - } else { - *str = zalloc(len + 1); - if (len) - memcpy(*str, ptr, len); - ptr += len; - } - return ptr; -} - -struct zuc_event * -unpack_event(char const *ptr, int32_t len) -{ - int32_t val = 0; - struct zuc_event *evt = zalloc(sizeof(*evt)); - char const *tmp = unpack_string(ptr, &evt->file); - tmp = unpack_int32(tmp, &evt->line); - - tmp = unpack_int32(tmp, &val); - evt->state = val; - tmp = unpack_int32(tmp, &val); - evt->op = val; - tmp = unpack_int32(tmp, &val); - evt->valtype = val; - - if (evt->valtype == ZUC_VAL_CSTR) { - char *ptr = NULL; - tmp = unpack_cstr(tmp, &ptr); - evt->val1 = (intptr_t)ptr; - tmp = unpack_cstr(tmp, &ptr); - evt->val2 = (intptr_t)ptr; - } else { - tmp = unpack_intptr_t(tmp, &evt->val1); - tmp = unpack_intptr_t(tmp, &evt->val2); - } - - tmp = unpack_string(tmp, &evt->expr1); - tmp = unpack_string(tmp, &evt->expr2); - - return evt; -} - -int -zuc_process_message(struct zuc_test *test, int fd) -{ - char buf[4] = {}; - int got = read(fd, buf, 4); - if (got == 4) { - enum zuc_event_type event_type = ZUC_EVENT_IMMEDIATE; - int32_t val = 0; - int32_t len = 0; - const char *tmp = NULL; - char *raw = NULL; - unpack_int32(buf, &len); - raw = zalloc(len); - got = read(fd, raw, len); - - tmp = unpack_int32(raw, &val); - event_type = val; - - struct zuc_event *evt = unpack_event(tmp, len - (tmp - raw)); - zuc_attach_event(test, evt, event_type, true); - free(raw); - } - return got; -} diff --git a/tools/zunitc/src/zuc_collector.h b/tools/zunitc/src/zuc_collector.h deleted file mode 100644 index 56f8a5fc..00000000 --- a/tools/zunitc/src/zuc_collector.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * 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 ZUC_COLLECTOR_H -#define ZUC_COLLECTOR_H - -struct zuc_event_listener; -struct zuc_test; - -/** - * Creates a new instance of an event collector that will attach events - * to the current test directly or via connection from child to parent. - * - * @param pipe_fd pointer to the file descriptor to use for communication if - * needed. If the value is -1 the events will be attached directly to the - * current test. Otherwise events will be passed back via IPC over this - * pipe with the expectation that the payload will be handled in the parent - * process via zuc_process_message(). - * @return a new collector instance. - * @see zuc_process_message() - */ -struct zuc_event_listener * -zuc_collector_create(int *pipe_fd); - -/** - * Reads events from the given pipe and processes them. - * - * @param test the currently active test to attach events for. - * @param pipe_fd the file descriptor of the pipe to read from. - * @return a positive value if a message was received, 0 if the end has - * been reached and -1 if an error has occurred. - */ -int -zuc_process_message(struct zuc_test *test, int pipe_fd); - -#endif /* ZUC_COLLECTOR_H */ diff --git a/tools/zunitc/src/zuc_context.h b/tools/zunitc/src/zuc_context.h index 609f34bd..c476f5e1 100644 --- a/tools/zunitc/src/zuc_context.h +++ b/tools/zunitc/src/zuc_context.h @@ -42,11 +42,9 @@ struct zuc_context { int repeat; int random; unsigned int seed; - bool spawn; bool break_on_failure; bool output_tap; bool output_junit; - int fds[2]; char *filter; struct zuc_slinked *listeners; diff --git a/tools/zunitc/src/zunitc_impl.c b/tools/zunitc/src/zunitc_impl.c index 395bdd74..18f03015 100644 --- a/tools/zunitc/src/zunitc_impl.c +++ b/tools/zunitc/src/zunitc_impl.c @@ -41,7 +41,6 @@ #include "zunitc/zunitc.h" #include "zuc_base_logger.h" -#include "zuc_collector.h" #include "zuc_context.h" #include "zuc_event_listener.h" #include "zuc_junit_reporter.h" @@ -82,9 +81,7 @@ static struct zuc_context g_ctx = { .fatal = false, .repeat = 0, .random = 0, - .spawn = true, .break_on_failure = false, - .fds = {-1, -1}, .listeners = NULL, @@ -141,12 +138,6 @@ zuc_set_random(int random) g_ctx.random = random; } -void -zuc_set_spawn(bool spawn) -{ - g_ctx.spawn = spawn; -} - void zuc_set_break_on_failure(bool break_on_failure) { @@ -525,7 +516,6 @@ zuc_initialize(int *argc, char *argv[], bool *help_flagged) { int rc = EXIT_FAILURE; bool opt_help = false; - bool opt_nofork = false; bool opt_list = false; int opt_repeat = 0; int opt_random = 0; @@ -537,7 +527,6 @@ zuc_initialize(int *argc, char *argv[], bool *help_flagged) int argc_in = *argc; const struct weston_option options[] = { - { WESTON_OPTION_BOOLEAN, "zuc-nofork", 0, &opt_nofork }, { WESTON_OPTION_BOOLEAN, "zuc-list-tests", 0, &opt_list }, { WESTON_OPTION_INTEGER, "zuc-repeat", 0, &opt_repeat }, { WESTON_OPTION_INTEGER, "zuc-random", 0, &opt_random }, @@ -633,7 +622,6 @@ zuc_initialize(int *argc, char *argv[], bool *help_flagged) " --zuc-break-on-failure\n" " --zuc-filter=FILTER\n" " --zuc-list-tests\n" - " --zuc-nofork\n" #if ENABLE_JUNIT_XML " --zuc-output-xml\n" #endif @@ -650,7 +638,6 @@ zuc_initialize(int *argc, char *argv[], bool *help_flagged) } else { zuc_set_repeat(opt_repeat); zuc_set_random(opt_random); - zuc_set_spawn(!opt_nofork); zuc_set_break_on_failure(opt_break_on_failure); zuc_set_output_junit(opt_junit); rc = EXIT_SUCCESS; @@ -937,11 +924,6 @@ zuc_cleanup(void) free(g_ctx.filter); g_ctx.filter = 0; - for (i = 0; i < 2; ++i) - if (g_ctx.fds[i] != -1) { - close(g_ctx.fds[i]); - g_ctx.fds[i] = -1; - } if (g_ctx.listeners) { struct zuc_slinked *curr = g_ctx.listeners; @@ -1017,108 +999,9 @@ zuc_list_tests(void) } } -static void -spawn_test(struct zuc_test *test, void *test_data, - void (*cleanup_fn)(void *data), void *cleanup_data) -{ - pid_t pid = -1; - - if (!test || (!test->fn && !test->fn_f)) - return; - - if (pipe2(g_ctx.fds, O_CLOEXEC)) { - printf("%s:%d: error: Unable to create pipe: %d\n", - __FILE__, __LINE__, errno); - mark_failed(test, ZUC_CHECK_ERROR); - return; - } - - fflush(NULL); /* important. avoid duplication of output */ - pid = fork(); - switch (pid) { - case -1: /* Error forking */ - printf("%s:%d: error: Problem with fork: %d\n", - __FILE__, __LINE__, errno); - mark_failed(test, ZUC_CHECK_ERROR); - close(g_ctx.fds[0]); - g_ctx.fds[0] = -1; - close(g_ctx.fds[1]); - g_ctx.fds[1] = -1; - break; - case 0: { /* child */ - int rc = EXIT_SUCCESS; - close(g_ctx.fds[0]); - g_ctx.fds[0] = -1; - - if (test->fn_f) - test->fn_f(test_data); - else - test->fn(); - - if (test_has_failure(test)) - rc = EXIT_FAILURE; - else if (test_has_skip(test)) - rc = ZUC_EXIT_SKIP; - - /* Avoid confusing memory tools like valgrind */ - if (cleanup_fn) - cleanup_fn(cleanup_data); - - zuc_cleanup(); - exit(rc); - } - default: { /* parent */ - ssize_t rc = 0; - siginfo_t info = {}; - - close(g_ctx.fds[1]); - g_ctx.fds[1] = -1; - - do { - rc = zuc_process_message(g_ctx.curr_test, - g_ctx.fds[0]); - } while (rc > 0); - close(g_ctx.fds[0]); - g_ctx.fds[0] = -1; - - if (waitid(P_ALL, 0, &info, WEXITED)) { - printf("%s:%d: error: waitid failed. (%d)\n", - __FILE__, __LINE__, errno); - mark_failed(test, ZUC_CHECK_ERROR); - } else { - switch (info.si_code) { - case CLD_EXITED: { - int exit_code = info.si_status; - switch(exit_code) { - case EXIT_SUCCESS: - break; - case ZUC_EXIT_SKIP: - if (!test_has_skip(g_ctx.curr_test) && - !test_has_failure(g_ctx.curr_test)) - ZUC_SKIP("Child exited SKIP"); - break; - default: - /* unexpected failure */ - if (!test_has_failure(g_ctx.curr_test)) - ZUC_ASSERT_EQ(0, exit_code); - } - break; - } - case CLD_KILLED: - case CLD_DUMPED: - printf("%s:%d: error: signaled: %d\n", - __FILE__, __LINE__, info.si_status); - mark_failed(test, ZUC_CHECK_ERROR); - break; - } - } - } - } -} - static void run_single_test(struct zuc_test *test,const struct zuc_fixture *fxt, - void *case_data, bool spawn) + void *case_data) { long elapsed = 0; struct timespec begin; @@ -1146,15 +1029,10 @@ run_single_test(struct zuc_test *test,const struct zuc_fixture *fxt, /* Need to re-check these, as fixtures might have changed test state. */ if (!test->fatal && !test->skipped) { - if (spawn) { - spawn_test(test, test_data, - cleanup_fn, cleanup_data); - } else { - if (test->fn_f) - test->fn_f(test_data); - else - test->fn(); - } + if (test->fn_f) + test->fn_f(test_data); + else + test->fn(); } clock_gettime(TARGET_TIMER, &end); @@ -1204,8 +1082,7 @@ run_single_case(struct zuc_case *test_case) if (curr->disabled) { dispatch_test_disabled(&g_ctx, curr); } else { - run_single_test(curr, fxt, case_data, - g_ctx.spawn); + run_single_test(curr, fxt, case_data); if (curr->skipped) test_case->skipped++; if (curr->failed) @@ -1313,7 +1190,6 @@ zucimpl_run_tests(void) return EXIT_FAILURE; if (g_ctx.listeners == NULL) { - zuc_add_event_listener(zuc_collector_create(&(g_ctx.fds[1]))); zuc_add_event_listener(zuc_base_logger_create()); if (g_ctx.output_junit) zuc_add_event_listener(zuc_junit_reporter_create()); diff --git a/weston.ini.in b/weston.ini.in index 011b1942..bb53f377 100644 --- a/weston.ini.in +++ b/weston.ini.in @@ -24,18 +24,22 @@ startup-animation=fade [launcher] icon=/usr/share/icons/gnome/24x24/apps/utilities-terminal.png path=/usr/bin/gnome-terminal +displayname=Gnome Terminal [launcher] icon=/usr/share/icons/gnome/24x24/apps/utilities-terminal.png path=@bindir@/weston-terminal +displayname=Weston Terminal [launcher] icon=/usr/share/icons/hicolor/24x24/apps/google-chrome.png path=/usr/bin/google-chrome +displayname=Google Chome [launcher] icon=/usr/share/icons/gnome/24x24/apps/arts.png path=@bindir@/weston-flower +displayname=Weston Flower [input-method] path=@libexecdir@/weston-keyboard diff --git a/xwayland/dnd.c b/xwayland/dnd.c index aea0845f..01918e16 100644 --- a/xwayland/dnd.c +++ b/xwayland/dnd.c @@ -51,7 +51,7 @@ struct dnd_data_source { static void data_source_accept(struct weston_data_source *base, - uint32_t time, const char *mime_type) + uint32_t serial, const char *mime_type) { struct dnd_data_source *source = (struct dnd_data_source *) base; xcb_client_message_event_t client_message; diff --git a/xwayland/meson.build b/xwayland/meson.build index d8482a46..59a78287 100644 --- a/xwayland/meson.build +++ b/xwayland/meson.build @@ -26,7 +26,7 @@ dep_names_xwayland = [ 'cairo-xcb', ] -deps_xwayland = [ dep_libweston_public ] +deps_xwayland = [ dep_libweston_public, dep_xcb_xwayland ] foreach name : dep_names_xwayland d = dependency(name, required: false) diff --git a/xwayland/selection.c b/xwayland/selection.c index c4845f20..6476c0d4 100644 --- a/xwayland/selection.c +++ b/xwayland/selection.c @@ -152,7 +152,7 @@ struct x11_data_source { static void data_source_accept(struct weston_data_source *source, - uint32_t time, const char *mime_type) + uint32_t serial, const char *mime_type) { } @@ -199,6 +199,9 @@ weston_wm_get_selection_targets(struct weston_wm *wm) char *logstr; size_t logsize; + if (!seat) + return; + cookie = xcb_get_property(wm->conn, 1, /* delete */ wm->selection_window, @@ -513,7 +516,6 @@ weston_wm_send_data(struct weston_wm *wm, xcb_atom_t target, const char *mime_ty source = seat->selection_data_source; source->send(source, mime_type, p[1]); - close(p[1]); } static void @@ -632,6 +634,9 @@ weston_wm_handle_xfixes_selection_notify(struct weston_wm *wm, xfixes_selection_notify->owner); if (xfixes_selection_notify->owner == XCB_WINDOW_NONE) { + if (!seat) + return 1; + if (wm->selection_owner != wm->selection_window) { /* A real X client selection went away, not our * proxy selection. Clear the wayland selection. */ @@ -715,6 +720,8 @@ weston_wm_set_selection(struct wl_listener *listener, void *data) wm->selection_window, wm->atom.clipboard, XCB_TIME_CURRENT_TIME); + + xcb_flush(wm->conn); } void diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index ef8d92b0..aa3edab3 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -48,6 +48,7 @@ #include "shared/cairo-util.h" #include "hash.h" #include "shared/helpers.h" +#include "shared/xcb-xwayland.h" struct wm_size_hints { uint32_t flags; @@ -170,9 +171,14 @@ struct weston_wm_window { int delete_window; int maximized_vert; int maximized_horz; + int take_focus; struct wm_size_hints size_hints; struct motif_wm_hints motif_hints; struct wl_list link; + int decor_top; + int decor_bottom; + int decor_left; + int decor_right; }; static void @@ -268,32 +274,6 @@ wm_lookup_window(struct weston_wm *wm, xcb_window_t hash, return false; } -const char * -get_atom_name(xcb_connection_t *c, xcb_atom_t atom) -{ - xcb_get_atom_name_cookie_t cookie; - xcb_get_atom_name_reply_t *reply; - xcb_generic_error_t *e; - static char buffer[64]; - - if (atom == XCB_ATOM_NONE) - return "None"; - - cookie = xcb_get_atom_name (c, atom); - reply = xcb_get_atom_name_reply (c, cookie, &e); - - if (reply) { - snprintf(buffer, sizeof buffer, "%.*s", - xcb_get_atom_name_name_length (reply), - xcb_get_atom_name_name (reply)); - } else { - snprintf(buffer, sizeof buffer, "(atom %u)", atom); - } - - free(reply); - - return buffer; -} static xcb_cursor_t xcb_cursor_image_load_cursor(struct weston_wm *wm, const XcursorImage *img) @@ -346,6 +326,7 @@ xcb_cursor_library_load_cursor(struct weston_wm *wm, const char *file) xcb_cursor_t cursor; XcursorImages *images; char *v = NULL; + char *theme; int size = 0; if (!file) @@ -358,7 +339,9 @@ xcb_cursor_library_load_cursor(struct weston_wm *wm, const char *file) if (!size) size = 32; - images = XcursorLibraryLoadImages (file, NULL, size); + theme = getenv("XCURSOR_THEME"); + + images = XcursorLibraryLoadImages(file, theme, size); if (!images) return -1; @@ -548,6 +531,7 @@ weston_wm_window_read_properties(struct weston_wm_window *window) window->size_hints.flags = 0; window->motif_hints.flags = 0; window->delete_window = 0; + window->take_focus = 0; for (i = 0; i < ARRAY_LENGTH(props); i++) { reply = xcb_get_property_reply(wm->conn, cookie[i], NULL); @@ -590,13 +574,18 @@ weston_wm_window_read_properties(struct weston_wm_window *window) for (i = 0; i < reply->value_len; i++) if (atom[i] == wm->atom.wm_delete_window) { window->delete_window = 1; - break; + } else if (atom[i] == wm->atom.wm_take_focus) { + window->take_focus = 1; } break; case TYPE_WM_NORMAL_HINTS: + /* WM_NORMAL_HINTS can be either 15 or 18 CARD32s */ + memset(&window->size_hints, 0, + sizeof(window->size_hints)); memcpy(&window->size_hints, xcb_get_property_value(reply), - sizeof window->size_hints); + MIN(sizeof(window->size_hints), + reply->value_len * 4)); break; case TYPE_NET_WM_STATE: window->fullscreen = 0; @@ -693,15 +682,33 @@ weston_wm_window_send_configure_notify(struct weston_wm_window *window) xcb_configure_notify_event_t configure_notify; struct weston_wm *wm = window->wm; int x, y; + int32_t dx = 0, dy = 0; + const struct weston_desktop_xwayland_interface *xwayland_api = + wm->server->compositor->xwayland_interface; + + if (window->override_redirect) { + /* Some clever application has changed the override redirect + * flag on an existing window. We didn't see it at map time, + * so have no idea what to do with it now. Log and leave. + */ + wm_printf(wm, "XWM warning: Can't send XCB_CONFIGURE_NOTIFY to" + " window %d which was mapped override redirect\n", + window->id); + return; + } weston_wm_window_get_child_position(window, &x, &y); + /* Synthetic ConfigureNotify events must be relative to the root + * window, so get our offset if we're mapped. */ + if (window->shsurf) + xwayland_api->get_position(window->shsurf, &dx, &dy); configure_notify.response_type = XCB_CONFIGURE_NOTIFY; configure_notify.pad0 = 0; configure_notify.event = window->id; configure_notify.window = window->id; configure_notify.above_sibling = XCB_WINDOW_NONE; - configure_notify.x = x; - configure_notify.y = y; + configure_notify.x = x + dx; + configure_notify.y = y + dy; configure_notify.width = window->width; configure_notify.height = window->height; configure_notify.border_width = 0; @@ -790,6 +797,13 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev if (!wm_lookup_window(wm, configure_request->window, &window)) return; + /* If we see this, a window's override_redirect state has changed + * after it was mapped, and we don't really know what to do about + * that. + */ + if (window->override_redirect) + return; + if (window->fullscreen) { weston_wm_window_send_configure_notify(window); return; @@ -825,6 +839,7 @@ weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *ev weston_wm_configure_window(wm, window->id, mask, values); weston_wm_window_configure_frame(window); + weston_wm_window_send_configure_notify(window); weston_wm_window_schedule_repaint(window); } @@ -916,24 +931,23 @@ static void weston_wm_send_focus_window(struct weston_wm *wm, struct weston_wm_window *window) { - xcb_client_message_event_t client_message; - if (window) { uint32_t values[1]; if (window->override_redirect) return; - client_message.response_type = XCB_CLIENT_MESSAGE; - client_message.format = 32; - client_message.window = window->id; - client_message.type = wm->atom.wm_protocols; - client_message.data.data32[0] = wm->atom.wm_take_focus; - client_message.data.data32[1] = XCB_TIME_CURRENT_TIME; - - xcb_send_event(wm->conn, 0, window->id, - XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, - (char *) &client_message); + if (window->take_focus) { + /* Set a property to get a roundtrip + * with a timestamp for WM_TAKE_FOCUS */ + xcb_change_property(wm->conn, + XCB_PROP_MODE_REPLACE, + window->id, + wm->atom.weston_focus_ping, + XCB_ATOM_STRING, + 8, /* format */ + 0, NULL); + } xcb_set_input_focus (wm->conn, XCB_INPUT_FOCUS_POINTER_ROOT, window->id, XCB_TIME_CURRENT_TIME); @@ -963,6 +977,9 @@ weston_wm_window_activate(struct wl_listener *listener, void *data) window = get_wm_window(surface); } + if (wm->focus_window == window) + return; + if (window) { weston_wm_set_net_active_window(wm, window->id); } else { @@ -1044,6 +1061,38 @@ weston_wm_window_set_wm_state(struct weston_wm_window *window, int32_t state) 2, property); } +static void +weston_wm_window_set_net_frame_extents(struct weston_wm_window *window) +{ + struct weston_wm *wm = window->wm; + uint32_t property[4]; + int top = 0, bottom = 0, left = 0, right = 0; + + if (!window->fullscreen) + frame_decoration_sizes(window->frame, &top, &bottom, &left, &right); + + if (window->decor_top == top && window->decor_bottom == bottom && + window->decor_left == left && window->decor_right == right) + return; + + window->decor_top = top; + window->decor_bottom = bottom; + window->decor_left = left; + window->decor_right = right; + + property[0] = left; + property[1] = right; + property[2] = top; + property[3] = bottom; + xcb_change_property(wm->conn, + XCB_PROP_MODE_REPLACE, + window->id, + wm->atom.net_frame_extents, + XCB_ATOM_CARDINAL, + 32, /* format */ + 4, property); +} + static void weston_wm_window_set_net_wm_state(struct weston_wm_window *window) { @@ -1079,6 +1128,9 @@ weston_wm_window_create_frame(struct weston_wm_window *window) if (window->decorate & MWM_DECOR_MAXIMIZE) buttons |= FRAME_BUTTON_MAXIMIZE; + if (window->decorate & MWM_DECOR_MINIMIZE) + buttons |= FRAME_BUTTON_MINIMIZE; + window->frame = frame_create(window->wm->theme, window->width, window->height, buttons, window->name, NULL); @@ -1132,6 +1184,7 @@ weston_wm_window_create_frame(struct weston_wm_window *window) width, height); hash_table_insert(wm->window_hash, window->frame_id, window); + weston_wm_window_send_configure_notify(window); } /* @@ -1381,6 +1434,7 @@ weston_wm_window_do_repaint(void *data) weston_wm_window_read_properties(window); weston_wm_window_draw_decoration(window); + weston_wm_window_set_net_frame_extents(window); weston_wm_window_set_pending_state(window); weston_wm_window_set_allow_commits(window, true); } @@ -1445,6 +1499,35 @@ weston_wm_handle_property_notify(struct weston_wm *wm, xcb_generic_event_t *even if (!wm_lookup_window(wm, property_notify->window, &window)) return; + /* We set the weston_focus_ping property on this window to + * get a timestamp to send a WM_TAKE_FOCUS... send it now, + * or just return if this is confirming we deleted the + * property. + */ + if (property_notify->atom == wm->atom.weston_focus_ping) { + xcb_client_message_event_t client_message; + + if (property_notify->state == XCB_PROPERTY_DELETE) + return; + + /* delete our ping property */ + xcb_delete_property(window->wm->conn, + window->id, + window->wm->atom.weston_focus_ping); + + client_message.response_type = XCB_CLIENT_MESSAGE; + client_message.format = 32; + client_message.window = window->id; + client_message.type = wm->atom.wm_protocols; + client_message.data.data32[0] = wm->atom.wm_take_focus; + client_message.data.data32[1] = property_notify->time; + xcb_send_event(wm->conn, 0, window->id, + XCB_EVENT_MASK_NO_EVENT, + (char *) &client_message); + + return; + } + window->properties_dirty = 1; if (wm_debug_is_enabled(wm)) @@ -1506,11 +1589,21 @@ weston_wm_window_create(struct weston_wm *wm, window->override_redirect = override; window->width = width; window->height = height; + /* Completely arbitrary defaults in case something starts + * maximized and we unmaximize it later - at which point 0 x 0 + * would not be the most useful size. + */ + window->saved_width = 512; + window->saved_height = 512; window->x = x; window->y = y; window->pos_dirty = false; window->map_request_x = INT_MIN; /* out of range for valid positions */ window->map_request_y = INT_MIN; /* out of range for valid positions */ + window->decor_top = -1; + window->decor_bottom = -1; + window->decor_left = -1; + window->decor_right = -1; weston_output_weak_ref_init(&window->legacy_fullscreen_output); geometry_reply = xcb_get_geometry_reply(wm->conn, geometry_cookie, NULL); @@ -1751,10 +1844,12 @@ weston_wm_window_set_toplevel(struct weston_wm_window *window) xwayland_interface->set_toplevel(window->shsurf); window->width = window->saved_width; window->height = window->saved_height; - if (window->frame) + if (window->frame) { + frame_unset_flag(window->frame, FRAME_FLAG_MAXIMIZED); frame_resize_inside(window->frame, window->width, window->height); + } weston_wm_window_configure(window); } @@ -1817,6 +1912,27 @@ weston_wm_window_handle_state(struct weston_wm_window *window, } } +static void +weston_wm_window_handle_iconic_state(struct weston_wm_window *window, + xcb_client_message_event_t *client_message) +{ + struct weston_wm *wm = window->wm; + const struct weston_desktop_xwayland_interface *xwayland_interface = + wm->server->compositor->xwayland_interface; + uint32_t iconic_state; + + if (!window->shsurf) + return; + + iconic_state = client_message->data.data32[0]; + + if (iconic_state == ICCCM_ICONIC_STATE) { + window->saved_height = window->height; + window->saved_width = window->width; + xwayland_interface->set_minimized(window->shsurf); + } +} + static void surface_destroy(struct wl_listener *listener, void *data) { @@ -1894,6 +2010,8 @@ weston_wm_handle_client_message(struct weston_wm *wm, weston_wm_window_handle_state(window, client_message); else if (client_message->type == wm->atom.wl_surface_id) weston_wm_window_handle_surface_id(window, client_message); + else if (client_message->type == wm->atom.wm_change_state) + weston_wm_window_handle_iconic_state(window, client_message); } enum cursor_type { @@ -2167,6 +2285,7 @@ weston_wm_handle_button(struct weston_wm *wm, xcb_generic_event_t *event) if (frame_status(window->frame) & FRAME_STATUS_MAXIMIZE) { window->maximized_horz = !window->maximized_horz; window->maximized_vert = !window->maximized_vert; + weston_wm_window_set_net_wm_state(window); if (weston_wm_window_is_maximized(window)) { window->saved_width = window->width; window->saved_height = window->height; @@ -2176,6 +2295,13 @@ weston_wm_handle_button(struct weston_wm *wm, xcb_generic_event_t *event) } frame_status_clear(window->frame, FRAME_STATUS_MAXIMIZE); } + + if (frame_status(window->frame) & FRAME_STATUS_MINIMIZE) { + window->saved_width = window->width; + window->saved_height = window->height; + xwayland_interface->set_minimized(window->shsurf); + frame_status_clear(window->frame, FRAME_STATUS_MINIMIZE); + } } static void @@ -2240,6 +2366,7 @@ weston_wm_handle_leave(struct weston_wm *wm, xcb_generic_event_t *event) static void weston_wm_handle_focus_in(struct weston_wm *wm, xcb_generic_event_t *event) { + struct weston_wm_window *window; xcb_focus_in_event_t *focus = (xcb_focus_in_event_t *) event; /* Do not interfere with grabs */ @@ -2247,6 +2374,17 @@ weston_wm_handle_focus_in(struct weston_wm *wm, xcb_generic_event_t *event) focus->mode == XCB_NOTIFY_MODE_UNGRAB) return; + if (!wm_lookup_window(wm, focus->event, &window)) + return; + + /* Sometimes apps like to focus their own windows, and we don't + * want to prevent that - but we'd like to at least prevent any + * attempt to focus a toplevel that isn't the currently activated + * toplevel. + */ + if (!window->frame) + 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 (!wm->focus_window || focus->event != wm->focus_window->id) @@ -2375,84 +2513,8 @@ weston_wm_get_visual_and_colormap(struct weston_wm *wm) static void weston_wm_get_resources(struct weston_wm *wm) { - -#define F(field) offsetof(struct weston_wm, field) - - static const struct { const char *name; int offset; } atoms[] = { - { "WM_PROTOCOLS", F(atom.wm_protocols) }, - { "WM_NORMAL_HINTS", F(atom.wm_normal_hints) }, - { "WM_TAKE_FOCUS", F(atom.wm_take_focus) }, - { "WM_DELETE_WINDOW", F(atom.wm_delete_window) }, - { "WM_STATE", F(atom.wm_state) }, - { "WM_S0", F(atom.wm_s0) }, - { "WM_CLIENT_MACHINE", F(atom.wm_client_machine) }, - { "_NET_WM_CM_S0", F(atom.net_wm_cm_s0) }, - { "_NET_WM_NAME", F(atom.net_wm_name) }, - { "_NET_WM_PID", F(atom.net_wm_pid) }, - { "_NET_WM_ICON", F(atom.net_wm_icon) }, - { "_NET_WM_STATE", F(atom.net_wm_state) }, - { "_NET_WM_STATE_MAXIMIZED_VERT", F(atom.net_wm_state_maximized_vert) }, - { "_NET_WM_STATE_MAXIMIZED_HORZ", F(atom.net_wm_state_maximized_horz) }, - { "_NET_WM_STATE_FULLSCREEN", F(atom.net_wm_state_fullscreen) }, - { "_NET_WM_USER_TIME", F(atom.net_wm_user_time) }, - { "_NET_WM_ICON_NAME", F(atom.net_wm_icon_name) }, - { "_NET_WM_DESKTOP", F(atom.net_wm_desktop) }, - { "_NET_WM_WINDOW_TYPE", F(atom.net_wm_window_type) }, - - { "_NET_WM_WINDOW_TYPE_DESKTOP", F(atom.net_wm_window_type_desktop) }, - { "_NET_WM_WINDOW_TYPE_DOCK", F(atom.net_wm_window_type_dock) }, - { "_NET_WM_WINDOW_TYPE_TOOLBAR", F(atom.net_wm_window_type_toolbar) }, - { "_NET_WM_WINDOW_TYPE_MENU", F(atom.net_wm_window_type_menu) }, - { "_NET_WM_WINDOW_TYPE_UTILITY", F(atom.net_wm_window_type_utility) }, - { "_NET_WM_WINDOW_TYPE_SPLASH", F(atom.net_wm_window_type_splash) }, - { "_NET_WM_WINDOW_TYPE_DIALOG", F(atom.net_wm_window_type_dialog) }, - { "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", F(atom.net_wm_window_type_dropdown) }, - { "_NET_WM_WINDOW_TYPE_POPUP_MENU", F(atom.net_wm_window_type_popup) }, - { "_NET_WM_WINDOW_TYPE_TOOLTIP", F(atom.net_wm_window_type_tooltip) }, - { "_NET_WM_WINDOW_TYPE_NOTIFICATION", F(atom.net_wm_window_type_notification) }, - { "_NET_WM_WINDOW_TYPE_COMBO", F(atom.net_wm_window_type_combo) }, - { "_NET_WM_WINDOW_TYPE_DND", F(atom.net_wm_window_type_dnd) }, - { "_NET_WM_WINDOW_TYPE_NORMAL", F(atom.net_wm_window_type_normal) }, - - { "_NET_WM_MOVERESIZE", F(atom.net_wm_moveresize) }, - { "_NET_SUPPORTING_WM_CHECK", - F(atom.net_supporting_wm_check) }, - { "_NET_SUPPORTED", F(atom.net_supported) }, - { "_NET_ACTIVE_WINDOW", F(atom.net_active_window) }, - { "_MOTIF_WM_HINTS", F(atom.motif_wm_hints) }, - { "CLIPBOARD", F(atom.clipboard) }, - { "CLIPBOARD_MANAGER", F(atom.clipboard_manager) }, - { "TARGETS", F(atom.targets) }, - { "UTF8_STRING", F(atom.utf8_string) }, - { "_WL_SELECTION", F(atom.wl_selection) }, - { "INCR", F(atom.incr) }, - { "TIMESTAMP", F(atom.timestamp) }, - { "MULTIPLE", F(atom.multiple) }, - { "UTF8_STRING" , F(atom.utf8_string) }, - { "COMPOUND_TEXT", F(atom.compound_text) }, - { "TEXT", F(atom.text) }, - { "STRING", F(atom.string) }, - { "WINDOW", F(atom.window) }, - { "text/plain;charset=utf-8", F(atom.text_plain_utf8) }, - { "text/plain", F(atom.text_plain) }, - { "XdndSelection", F(atom.xdnd_selection) }, - { "XdndAware", F(atom.xdnd_aware) }, - { "XdndEnter", F(atom.xdnd_enter) }, - { "XdndLeave", F(atom.xdnd_leave) }, - { "XdndDrop", F(atom.xdnd_drop) }, - { "XdndStatus", F(atom.xdnd_status) }, - { "XdndFinished", F(atom.xdnd_finished) }, - { "XdndTypeList", F(atom.xdnd_type_list) }, - { "XdndActionCopy", F(atom.xdnd_action_copy) }, - { "_XWAYLAND_ALLOW_COMMITS", F(atom.allow_commits) }, - { "WL_SURFACE_ID", F(atom.wl_surface_id) } - }; -#undef F - xcb_xfixes_query_version_cookie_t xfixes_cookie; xcb_xfixes_query_version_reply_t *xfixes_reply; - xcb_intern_atom_cookie_t cookies[ARRAY_LENGTH(atoms)]; - xcb_intern_atom_reply_t *reply; xcb_render_query_pict_formats_reply_t *formats_reply; xcb_render_query_pict_formats_cookie_t formats_cookie; xcb_render_pictforminfo_t *formats; @@ -2463,17 +2525,7 @@ weston_wm_get_resources(struct weston_wm *wm) formats_cookie = xcb_render_query_pict_formats(wm->conn); - for (i = 0; i < ARRAY_LENGTH(atoms); i++) - cookies[i] = xcb_intern_atom (wm->conn, 0, - strlen(atoms[i].name), - atoms[i].name); - - for (i = 0; i < ARRAY_LENGTH(atoms); i++) { - reply = xcb_intern_atom_reply (wm->conn, cookies[i], NULL); - *(xcb_atom_t *) ((char *) wm + atoms[i].offset) = reply->atom; - free(reply); - } - + x11_get_atoms(wm->conn, &wm->atom); wm->xfixes = xcb_get_extension_data(wm->conn, &xcb_xfixes_id); if (!wm->xfixes || !wm->xfixes->present) weston_log("xfixes not available\n"); @@ -2573,7 +2625,7 @@ weston_wm_create(struct weston_xserver *wxs, int fd) struct wl_event_loop *loop; xcb_screen_iterator_t s; uint32_t values[1]; - xcb_atom_t supported[6]; + xcb_atom_t supported[7]; wm = zalloc(sizeof *wm); if (wm == NULL) @@ -2627,6 +2679,7 @@ weston_wm_create(struct weston_xserver *wxs, int fd) supported[3] = wm->atom.net_wm_state_maximized_vert; supported[4] = wm->atom.net_wm_state_maximized_horz; supported[5] = wm->atom.net_active_window; + supported[6] = wm->atom.net_frame_extents; xcb_change_property(wm->conn, XCB_PROP_MODE_REPLACE, wm->screen->root, @@ -2730,6 +2783,7 @@ weston_wm_window_configure(void *data) values); weston_wm_window_configure_frame(window); + weston_wm_window_send_configure_notify(window); weston_wm_window_schedule_repaint(window); } @@ -2741,6 +2795,8 @@ send_configure(struct weston_surface *surface, int32_t width, int32_t height) struct theme *t; int new_width, new_height; int vborder, hborder; + bool use_saved_dimensions = false; + bool use_current_dimensions = false; if (!window || !window->wm) return; @@ -2755,21 +2811,50 @@ send_configure(struct weston_surface *surface, int32_t width, int32_t height) vborder = 0; } - if (width > hborder) - new_width = width - hborder; - else - new_width = 1; - - if (height > vborder) - new_height = height - vborder; - else - new_height = 1; + /* A config event with width == 0 or height == 0 is a hint to the client + * to choose its own dimensions. Since X11 clients don't support such + * hints we make a best guess here by trying to use the last saved + * dimensions or, as a fallback, the current dimensions. */ + if (width == 0 || height == 0) { + use_saved_dimensions = window->saved_width > 0 && + window->saved_height > 0; + use_current_dimensions = !use_saved_dimensions && + window->width > 0 && + window->height > 0; + } + + /* The saved or current dimensions are the plain window content + * dimensions without the borders, so we can use them directly for + * new_width and new_height below. */ + if (use_current_dimensions) { + new_width = window->width; + new_height = window->height; + } else if (use_saved_dimensions) { + new_width = window->saved_width; + new_height = window->saved_height; + } else { + new_width = (width > hborder) ? (width - hborder) : 1; + new_height = (height > vborder) ? (height - vborder) : 1; + } if (window->width != new_width || window->height != new_height) { window->width = new_width; window->height = new_height; + /* Save the toplevel size so that we can pick up a reasonable + * value when the compositor tell us to choose a size. We are + * already saving the size before going fullscreen/maximized, + * but this covers the case in which our size is changed but we + * continue on a normal state. */ + if (!weston_wm_window_is_maximized(window) && !window->fullscreen) { + window->saved_width = new_width; + window->saved_height = new_height; + } + if (window->frame) { + if (weston_wm_window_is_maximized(window)) + frame_set_flag(window->frame, FRAME_FLAG_MAXIMIZED); + frame_resize_inside(window->frame, window->width, window->height); } @@ -2816,6 +2901,7 @@ send_position(struct weston_surface *surface, int32_t x, int32_t y) mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; weston_wm_configure_window(wm, window->frame_id, mask, values); + weston_wm_window_send_configure_notify(window); xcb_flush(wm->conn); } } @@ -2879,6 +2965,9 @@ weston_wm_window_is_positioned(struct weston_wm_window *window) weston_log("XWM warning: win %d did not see map request\n", window->id); + if (window->size_hints.flags & (USPosition | PPosition)) + return true; + return window->map_request_x != 0 || window->map_request_y != 0; } @@ -2959,7 +3048,9 @@ xserver_map_shell_surface(struct weston_wm_window *window, } else if (window->override_redirect) { xwayland_interface->set_xwayland(window->shsurf, window->x, window->y); - } else if (window->transient_for && window->transient_for->surface) { + } else if (window->transient_for && + !window->transient_for->override_redirect && + window->transient_for->surface) { parent = window->transient_for; if (weston_wm_window_type_inactive(window)) { xwayland_interface->set_transient(window->shsurf, diff --git a/xwayland/xwayland-internal-interface.h b/xwayland/xwayland-internal-interface.h index c7dfd19b..0e732772 100644 --- a/xwayland/xwayland-internal-interface.h +++ b/xwayland/xwayland-internal-interface.h @@ -59,7 +59,11 @@ struct weston_desktop_xwayland_interface { int32_t x, int32_t y, int32_t width, int32_t height); void (*set_maximized)(struct weston_desktop_xwayland_surface *shsurf); + void (*set_minimized)(struct weston_desktop_xwayland_surface *shsurf); void (*set_pid)(struct weston_desktop_xwayland_surface *shsurf, pid_t pid); + void (*get_position)(struct weston_desktop_xwayland_surface *surface, + int32_t *x, int32_t *y); + }; #endif diff --git a/xwayland/xwayland.h b/xwayland/xwayland.h index 3ef0404f..df9cab83 100644 --- a/xwayland/xwayland.h +++ b/xwayland/xwayland.h @@ -33,9 +33,7 @@ #include #include #include - -#define SEND_EVENT_MASK (0x80) -#define EVENT_TYPE(event) ((event)->response_type & ~SEND_EVENT_MASK) +#include "shared/xcb-xwayland.h" struct weston_xserver { struct wl_display *wl_display; @@ -94,80 +92,13 @@ struct weston_wm { xcb_window_t dnd_window; xcb_window_t dnd_owner; - struct { - xcb_atom_t wm_protocols; - xcb_atom_t wm_normal_hints; - xcb_atom_t wm_take_focus; - xcb_atom_t wm_delete_window; - xcb_atom_t wm_state; - xcb_atom_t wm_s0; - xcb_atom_t wm_client_machine; - xcb_atom_t net_wm_cm_s0; - xcb_atom_t net_wm_name; - xcb_atom_t net_wm_pid; - xcb_atom_t net_wm_icon; - xcb_atom_t net_wm_state; - xcb_atom_t net_wm_state_maximized_vert; - xcb_atom_t net_wm_state_maximized_horz; - xcb_atom_t net_wm_state_fullscreen; - xcb_atom_t net_wm_user_time; - xcb_atom_t net_wm_icon_name; - xcb_atom_t net_wm_desktop; - xcb_atom_t net_wm_window_type; - xcb_atom_t net_wm_window_type_desktop; - xcb_atom_t net_wm_window_type_dock; - xcb_atom_t net_wm_window_type_toolbar; - xcb_atom_t net_wm_window_type_menu; - xcb_atom_t net_wm_window_type_utility; - xcb_atom_t net_wm_window_type_splash; - xcb_atom_t net_wm_window_type_dialog; - xcb_atom_t net_wm_window_type_dropdown; - xcb_atom_t net_wm_window_type_popup; - xcb_atom_t net_wm_window_type_tooltip; - xcb_atom_t net_wm_window_type_notification; - xcb_atom_t net_wm_window_type_combo; - xcb_atom_t net_wm_window_type_dnd; - xcb_atom_t net_wm_window_type_normal; - xcb_atom_t net_wm_moveresize; - xcb_atom_t net_supporting_wm_check; - xcb_atom_t net_supported; - xcb_atom_t net_active_window; - xcb_atom_t motif_wm_hints; - xcb_atom_t clipboard; - xcb_atom_t clipboard_manager; - xcb_atom_t targets; - xcb_atom_t utf8_string; - xcb_atom_t wl_selection; - xcb_atom_t incr; - xcb_atom_t timestamp; - xcb_atom_t multiple; - xcb_atom_t compound_text; - xcb_atom_t text; - xcb_atom_t string; - xcb_atom_t window; - xcb_atom_t text_plain_utf8; - xcb_atom_t text_plain; - xcb_atom_t xdnd_selection; - xcb_atom_t xdnd_aware; - xcb_atom_t xdnd_enter; - xcb_atom_t xdnd_leave; - xcb_atom_t xdnd_drop; - xcb_atom_t xdnd_status; - xcb_atom_t xdnd_finished; - xcb_atom_t xdnd_type_list; - xcb_atom_t xdnd_action_copy; - xcb_atom_t wl_surface_id; - xcb_atom_t allow_commits; - } atom; + struct atom_x11 atom; }; void dump_property(FILE *fp, struct weston_wm *wm, xcb_atom_t property, xcb_get_property_reply_t *reply); -const char * -get_atom_name(xcb_connection_t *c, xcb_atom_t atom); - void weston_wm_selection_init(struct weston_wm *wm); int