diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 55e6e8a1c..dc58f1c0b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,31 @@ Contributing to Weston ======================= +Sending patches +--------------- + +Patches should be sent via +[GitLab merge requests](https://docs.gitlab.com/ce/gitlab-basics/add-merge-request.html). +Weston is +[hosted on freedesktop.org's GitLab](https://gitlab.freedesktop.org/wayland/weston/): +in order to submit code, you should create an account on this GitLab instance, +fork the core Weston repository, push your changes to a branch in your new +repository, and then submit these patches for review through a merge request. + +### Forking & Permissions for new users + +Due to huge amounts of spam, freedesktop.org has disabled forking of existing +projects for new users. Please head to +[How can I contribute](https://gitlab.freedesktop.org/freedesktop/freedesktop/-/wikis/home#how-can-i-contribute-to-an-existing-project-or-create-a-new-one) +and verify whether you need to perform additional steps. + +### Do not send patches over email + +Weston formerly accepted patches via `git-send-email`, sent to +**wayland-devel\@lists.freedesktop.org**; these were +[tracked using Patchwork](https://patchwork.freedesktop.org/project/wayland/). +New email patches are no longer accepted. + Finding something to work on ---------------------------- @@ -25,23 +50,6 @@ long time (potentially some hours due to timezone differences), then you may want to send your question to the list or issue tracker instead. -Sending patches ---------------- - -Patches should be sent via -[GitLab merge requests](https://docs.gitlab.com/ce/gitlab-basics/add-merge-request.html). -Weston is -[hosted on freedesktop.org's GitLab](https://gitlab.freedesktop.org/wayland/weston/): -in order to submit code, you should create an account on this GitLab instance, -fork the core Weston repository, push your changes to a branch in your new -repository, and then submit these patches for review through a merge request. - -Weston formerly accepted patches via `git-send-email`, sent to -**wayland-devel\@lists.freedesktop.org**; these were -[tracked using Patchwork](https://patchwork.freedesktop.org/projects/wayland/). -New email patches are no longer accepted. - - Formatting and separating commits --------------------------------- @@ -208,6 +216,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/LICENSE b/LICENSE new file mode 100644 index 000000000..faefd8f49 --- /dev/null +++ b/LICENSE @@ -0,0 +1,30 @@ +Copyright © 2008-2012 Kristian Høgsberg +Copyright © 2010-2012 Intel Corporation +Copyright © 2010-2011 Benjamin Franzke +Copyright © 2011-2012 Collabora, Ltd. +Copyright © 2010 Red Hat + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +--- + +The above is the version of the MIT "Expat" License used by X.org: + + http://cgit.freedesktop.org/xorg/xserver/tree/COPYING diff --git a/README.md b/README.md index bbc93b459..9a93434e2 100644 --- a/README.md +++ b/README.md @@ -3,38 +3,17 @@ Weston ![screenshot of skeletal Weston desktop](doc/wayland-screenshot.jpg) -Weston is the reference implementation of a Wayland compositor, as well as a -useful environment in and of itself. +Weston is a Wayland compositor designed for correctness, reliability, +predictability, and performance. Out of the box, Weston provides a very basic desktop, or a full-featured environment for non-desktop uses such as automotive, embedded, in-flight, -industrial, kiosks, set-top boxes and TVs. It also provides a library allowing -other projects to build their own full-featured environments on top of Weston's -core. +industrial, kiosks, set-top boxes and TVs. -The core focus of Weston is correctness and reliability. Weston aims to be lean -and fast, but more importantly, to be predictable. Whilst Weston does have known -bugs and shortcomings, we avoid unknown or variable behaviour as much as -possible, including variable performance such as occasional spikes in frame -display time. +It also provides a library called [libweston](#libweston) which allows +users to build their own custom full-featured environments on top of +Weston's core. -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. - -If you are after a more mainline desktop experience, the -[GNOME](https://www.gnome.org) and [KDE](https://www.kde.org) projects provide -full-featured desktop environments built on the Wayland protocol. Many other -projects also exist providing Wayland clients and desktop environments: you are -not limited to just what you can find in Weston. - -Reporting issues and contributing -================================= - -Weston's development is -[hosted on freedesktop.org GitLab](https://gitlab.freedesktop.org/wayland/weston/). -Please also see [the contributing document](CONTRIBUTING.md), which details how -to make code or non-technical contributions to Weston. Building Weston =============== @@ -42,7 +21,7 @@ Building Weston Weston is built using [Meson](https://mesonbuild.com/). Weston often depends on the current release versions of [Wayland](https://gitlab.freedesktop.org/wayland/wayland) and -[wayland-protocols](https://cgit.freedesktop.org/wayland/wayland-protocols). +[wayland-protocols](https://gitlab.freedesktop.org/wayland/wayland-protocols). If necessary, the latest Meson can be installed as a user with: @@ -79,6 +58,7 @@ is available on the Wayland site. There are also more details on For building the documentation see [documentation](#documentation). + Running Weston ============== @@ -88,17 +68,58 @@ from a text console, it will take over that console. When launched from inside an existing Wayland or X11 session, it will start a 'nested' instance of Weston inside a window in that session. +By default, Weston will start with a skeletal desktop-like environment called +`desktop-shell`. Other shells are available; for example, to load the `kiosk` +shell designed for single-application environments, you can start with: + + $ weston --shell=kiosk + Help is available by running `weston --help`, or `man weston`, which will list 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. +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. + + +Using libweston +=============== + +libweston is designed to allow users to use Weston's core - its client support, +backends and renderers - whilst implementing their own user interface, policy, +configuration, and lifecycle. If you would like to implement your own window +manager or desktop environment, we recommend building your project using the +libweston API. + +Building and installing Weston will also install libweston's shared library +and development headers. libweston is both API-compatible and ABI-compatible +within a single stable release. It is parallel-installable, so multiple stable +releases can be installed and used side by side. + +Documentation for libweston's API can be found within the source (see the +[documentation](#documentation) section), and also on +[Weston's online documentation](https://wayland.pages.freedesktop.org/weston/) +for the current stable release. + + +Reporting issues and contributing +================================= + +Weston's development is +[hosted on freedesktop.org GitLab](https://gitlab.freedesktop.org/wayland/weston/). +Please also see [the contributing document](CONTRIBUTING.md), which details how +to make code or non-technical contributions to Weston. + +Weston and libweston are not suitable for severely 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). +This is only likely to occur if you have disabled your OS's 'overcommit' +functionality, and not in common cases. + Documentation ============= @@ -107,14 +128,12 @@ To read the Weston documentation online, head over to [the Weston website](https://wayland.pages.freedesktop.org/weston/). For documenting weston we use [sphinx](http://www.sphinx-doc.org/en/master/) -together with [breathe](https://breathe.readthedocs.io/en/latest/) that -understands XMLs databases generated by doxygen. So far, this is a compromise -until better tools are available in order to remove the doxygen -dependency. You should be able to install both sphinx and breathe extension -using pip3 command, or your package manager. -Doxygen should be available using your distribution package manager. - -Once those are set-up, run `meson` with `-Ddoc=true` option in order to enable +together with [breathe](https://breathe.readthedocs.io/en/latest/) to process +and augment code documentation from Doxygen. You should be able to install +both sphinx and the breathe extension using pip3 command, or your package +manager. Doxygen should be available using your distribution package manager. + +Once those are set up, run `meson` with `-Ddoc=true` option in order to enable building the documentation. Installation will place the documentation in the prefix's path under datadir (i.e., `share/doc`). @@ -138,226 +157,3 @@ $ ninja docs && ninja install # run 'docs' then install Improving/adding documentation can be done by modifying rST files under `doc/sphinx/` directory or by modifying the source code using doxygen directives. - -Libweston -========= - -Libweston is an effort to separate the re-usable parts of Weston into -a library. Libweston provides most of the boring and tedious bits of -correctly implementing core Wayland protocols and interfacing with -input and output systems, so that people who just want to write a new -"Wayland window manager" (WM) or a small desktop environment (DE) can -focus on the WM part. - -Libweston was first introduced in Weston 1.12, and is expected to -continue evolving through many Weston releases before it achieves a -stable API and feature completeness. - -Libweston's primary purpose is exporting an API for creating Wayland -compositors. Libweston's secondary purpose is to export the weston_config API -so that third party plugins and helper programs can read `weston.ini` if they -want to. However, these two scopes are orthogonal and independent. At no point -will the compositor functionality use or depend on the weston_config -functionality. - - -API/ABI (in)stability and parallel installability -------------------------------------------------- - -As libweston's API surface is huge, it is impossible to get it right -in one go. Therefore developers reserve the right to break the API/ABI and bump -the major version to signify that. For git snapshots of the master branch, the -API/ABI can break any time without warning. - -Libweston major can be bumped only once during a development cycle. This should -happen on the first patch that breaks the API or ABI. Further breaks before the -next Weston major.0.0 release do not cause a bump. This means that libweston -API and ABI are allowed to break also after an alpha release, up to the final -release. However, breaks after alpha should be judged by the usual practices -for allowing minor features, fixes only, or critical fixes only. - -To make things tolerable for libweston users despite API/ABI breakages, -different libweston major versions are designed to be perfectly -parallel-installable. This way external projects can easily depend on a -particular API/ABI-version. Thus they do not have to fight over which -ABI-version is installed in a user's system. This allows a user to install many -different compositors each requiring a different libweston ABI-version without -tricks or conflicts. - -Note, that versions of Weston itself will not be parallel-installable, -only libweston is. - -For more information about parallel installability, see -http://ometer.com/parallel.html - - -Versioning scheme ------------------ - -In order to provide consistent, easy to use versioning, libweston -follows the rules in the Apache Portable Runtime Project -http://apr.apache.org/versioning.html. - -The document provides the full details, with the gist summed below: - - Major - backward incompatible changes. - - Minor - new backward compatible features. - - Patch - internal (implementation specific) fixes. - -Weston and libweston have separate version numbers in meson.build. All -releases are made by the Weston version number. Libweston version number -matches the Weston version number in all releases except maybe pre-releases. -Pre-releases have the Weston micro version 91 or greater. - -A pre-release is allowed to install a libweston version greater than the Weston -version in case libweston major was bumped. In that case, the libweston version -must be Weston major + 1. - -Pkg-config files are named after libweston major, but carry the Weston version -number. This means that Weston pre-release 2.1.91 may install libweston-3.pc -for the future libweston 3.0.0, but the .pc file says the version is still -2.1.91. When a libweston user wants to depend on the fully stable API and ABI -of a libweston major, he should use (e.g. for major 3): - - PKG_CHECK_MODULES(LIBWESTON, [libweston-3 >= 3.0.0]) - -Depending only on libweston-3 without a specific version number still allows -pre-releases which might have different API or ABI. - - -Forward compatibility ---------------------- - -Inspired by ATK, Qt and KDE programs/libraries, libjpeg-turbo, GDK, -NetworkManager, js17, lz4 and many others, libweston uses a macro to restrict -the API visible to the developer - REQUIRE_LIBWESTON_API_VERSION. - -Note that different projects focus on different aspects - upper and/or lower -version check, default to visible/hidden old/new symbols and so on. - -libweston aims to guard all newly introduced API, in order to prevent subtle -breaks that a simple recompile (against a newer version) might cause. - -The macro is of the format 0x$MAJOR$MINOR and does not include PATCH version. -As mentioned in the Versioning scheme section, the latter does not reflect any -user visible API changes, thus should be not considered part of the API version. - -All new symbols should be guarded by the macro like the example given below: - -~~~~ -#if REQUIRE_LIBWESTON_API_VERSION >= 0x0101 - -bool -weston_ham_sandwich(void); - -#endif -~~~~ - -In order to use the said symbol, the one will have a similar code in their -configure.ac: - -~~~~ -PKG_CHECK_MODULES(LIBWESTON, [libweston-1 >= 1.1]) -AC_DEFINE(REQUIRE_LIBWESTON_API_VERSION, [0x0101]) -~~~~ - -If the user is _not_ interested in forward compatibility, they can use 0xffff -or similar high value. Yet doing so is not recommended. - - -Libweston design goals ----------------------- - -The high-level goal of libweston is to decouple the compositor from -the shell implementation (what used to be shell plugins). - -Thus, instead of launching 'weston' with various arguments to choose the -shell, one would launch the shell itself, e.g. 'weston-desktop', -'weston-ivi', 'orbital', etc. The main executable (the hosting program) -will implement the shell, while libweston will be used for a fundamental -compositor implementation. - -Libweston is also intended for use by other project developers who want -to create new "Wayland WMs". - -Details: - -- All configuration and user interfaces will be outside of libweston. - This includes command line parsing, configuration files, and runtime - (graphical) UI. - -- The hosting program (main executable) will be in full control of all - libweston options. Libweston should not have user settable options - that would work behind the hosting program's back, except perhaps - debugging features and such. - -- Signal handling will be outside of libweston. - -- Child process execution and management will be outside of libweston. - -- The different backends (drm, fbdev, 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 - available features. - -- Renderers will be libweston internal details too, though again the - hosting program may affect the choice of renderer if the backend - allows, and maybe set renderer-specific options. - -- plugin design ??? - -- 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: - -- libweston (minimal dependencies): - + headless backend - + wayland backend - -- gl-renderer (depends on GL libs etc.) - -- drm-backend (depends on libdrm, libgbm, libudev, libinput, ...) - -- x11-backend (depends of X11/xcb libs) - -- 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 - + screen-share - -- weston demos (not parallel-installable) - + weston-simple-* programs - + possibly all the programs we build but do not install by - default - -- and possibly more... - -Everything should be parallel-installable across libweston major -ABI-versions (libweston-1.so, libweston-2.so, etc.), except those -explicitly mentioned. - -Weston's build may not sanely allow this yet, but this is the -intention. diff --git a/SCR.txt b/SCR.txt new file mode 100644 index 000000000..493a37134 --- /dev/null +++ b/SCR.txt @@ -0,0 +1,9 @@ +Package: weston-imx.git +Version: 11.0.1.imx +Outgoing License: MIT +License File: COPYING +Type of Content: source +Description and comments: A reference implementation of a Wayland compositor +Release Location: https://github.com/nxp-imx/weston-imx -b lf-6.1.1-1.0.0 +Origin: NXP (MIT) + Weston (MIT) - http://github.com/wayland-project/weston/ diff --git a/clients/cliptest.c b/clients/cliptest.c index 6811e4e56..ada1ee77c 100644 --- a/clients/cliptest.c +++ b/clients/cliptest.c @@ -51,6 +51,7 @@ #include #include "libweston/vertex-clipping.h" +#include "shared/helpers.h" #include "shared/xalloc.h" #include "window.h" @@ -65,7 +66,11 @@ struct geometry { float phi; }; +struct weston_surface { +}; + struct weston_view { + struct weston_surface *surface; struct { int enabled; } transform; @@ -74,8 +79,8 @@ struct weston_view { }; static void -weston_view_to_global_float(struct weston_view *view, - float sx, float sy, float *x, float *y) +weston_view_to_global_double(struct weston_view *view, + double sx, double sy, double *x, double *y) { struct geometry *g = view->geometry; @@ -84,12 +89,21 @@ weston_view_to_global_float(struct weston_view *view, *y = -g->s * sx + g->c * sy; } +static struct weston_coord_global +weston_coord_surface_to_global(struct weston_view *view, struct weston_coord_surface pos) +{ + double gx, gy; + struct weston_coord_global g_pos; + + weston_view_to_global_double(view, pos.c.x, pos.c.y, &gx, &gy); + g_pos.c = weston_coord(gx, gy); + + return g_pos; +} + /* ---------------------- copied begins -----------------------*/ /* Keep this in sync with what is in gl-renderer.c! */ -#define max(a, b) (((a) > (b)) ? (a) : (b)) -#define min(a, b) (((a) > (b)) ? (b) : (a)) - /* * Compute the boundary vertices of the intersection of the global coordinate * aligned rectangle 'rect', and an arbitrary quadrilateral produced from @@ -101,17 +115,22 @@ weston_view_to_global_float(struct weston_view *view, */ static int calculate_edges(struct weston_view *ev, pixman_box32_t *rect, - pixman_box32_t *surf_rect, GLfloat *ex, GLfloat *ey) + pixman_box32_t *surf_rect, struct weston_coord *e) { struct clip_context ctx; int i, n; GLfloat min_x, max_x, min_y, max_y; - struct polygon8 surf = { - { surf_rect->x1, surf_rect->x2, surf_rect->x2, surf_rect->x1 }, - { surf_rect->y1, surf_rect->y1, surf_rect->y2, surf_rect->y2 }, - 4 + struct weston_surface *es = ev->surface; + struct weston_coord_surface tmp[4] = { + weston_coord_surface(surf_rect->x1, surf_rect->y1, es), + weston_coord_surface(surf_rect->x2, surf_rect->y1, es), + weston_coord_surface(surf_rect->x2, surf_rect->y2, es), + weston_coord_surface(surf_rect->x1, surf_rect->y2, es), }; + struct polygon8 surf; + + surf.n = 4; ctx.clip.x1 = rect->x1; ctx.clip.y1 = rect->y1; @@ -120,18 +139,17 @@ calculate_edges(struct weston_view *ev, pixman_box32_t *rect, /* transform surface to screen space: */ for (i = 0; i < surf.n; i++) - weston_view_to_global_float(ev, surf.x[i], surf.y[i], - &surf.x[i], &surf.y[i]); + surf.pos[i] = weston_coord_surface_to_global(ev, tmp[i]).c; /* find bounding box: */ - min_x = max_x = surf.x[0]; - min_y = max_y = surf.y[0]; + min_x = max_x = surf.pos[0].x; + min_y = max_y = surf.pos[0].y; for (i = 1; i < surf.n; i++) { - min_x = min(min_x, surf.x[i]); - max_x = max(max_x, surf.x[i]); - min_y = min(min_y, surf.y[i]); - max_y = max(max_y, surf.y[i]); + min_x = MIN(min_x, surf.pos[i].x); + max_x = MAX(max_x, surf.pos[i].x); + min_y = MIN(min_y, surf.pos[i].y); + max_y = MAX(max_y, surf.pos[i].y); } /* First, simple bounding box check to discard early transformed @@ -146,7 +164,7 @@ calculate_edges(struct weston_view *ev, pixman_box32_t *rect, * vertices to the clip rect bounds: */ if (!ev->transform.enabled) - return clip_simple(&ctx, &surf, ex, ey); + return clip_simple(&ctx, &surf, e); /* Transformed case: use a general polygon clipping algorithm to * clip the surface rectangle with each side of 'rect'. @@ -154,7 +172,7 @@ calculate_edges(struct weston_view *ev, pixman_box32_t *rect, * http://www.codeguru.com/cpp/misc/misc/graphics/article.php/c8965/Polygon-Clipping.htm * but without looking at any of that code. */ - n = clip_transformed(&ctx, &surf, ex, ey); + n = clip_transformed(&ctx, &surf, e); if (n < 3) return 0; @@ -162,7 +180,6 @@ calculate_edges(struct weston_view *ev, pixman_box32_t *rect, return n; } - /* ---------------------- copied ends -----------------------*/ static void @@ -206,35 +223,36 @@ struct cliptest { struct ui_state ui; struct geometry geometry; + struct weston_surface surface; struct weston_view view; }; static void -draw_polygon_closed(cairo_t *cr, GLfloat *x, GLfloat *y, int n) +draw_polygon_closed(cairo_t *cr, struct weston_coord *pos, int n) { int i; - cairo_move_to(cr, x[0], y[0]); + cairo_move_to(cr, pos[0].x, pos[0].y); for (i = 1; i < n; i++) - cairo_line_to(cr, x[i], y[i]); - cairo_line_to(cr, x[0], y[0]); + cairo_line_to(cr, pos[i].x, pos[i].y); + cairo_line_to(cr, pos[0].x, pos[0].y); } static void -draw_polygon_labels(cairo_t *cr, GLfloat *x, GLfloat *y, int n) +draw_polygon_labels(cairo_t *cr, struct weston_coord *pos, int n) { char str[16]; int i; for (i = 0; i < n; i++) { snprintf(str, 16, "%d", i); - cairo_move_to(cr, x[i], y[i]); + cairo_move_to(cr, pos[i].x, pos[i].y); cairo_show_text(cr, str); } } static void -draw_coordinates(cairo_t *cr, double ox, double oy, GLfloat *x, GLfloat *y, int n) +draw_coordinates(cairo_t *cr, double ox, double oy, struct weston_coord *pos, int n) { char str[64]; int i; @@ -242,7 +260,7 @@ draw_coordinates(cairo_t *cr, double ox, double oy, GLfloat *x, GLfloat *y, int cairo_font_extents(cr, &ext); for (i = 0; i < n; i++) { - snprintf(str, 64, "%d: %14.9f, %14.9f", i, x[i], y[i]); + snprintf(str, 64, "%d: %14.9f, %14.9f", i, pos[i].x, pos[i].y); cairo_move_to(cr, ox, oy + ext.height * (i + 1)); cairo_show_text(cr, str); } @@ -251,34 +269,34 @@ draw_coordinates(cairo_t *cr, double ox, double oy, GLfloat *x, GLfloat *y, int static void draw_box(cairo_t *cr, pixman_box32_t *box, struct weston_view *view) { - GLfloat x[4], y[4]; + struct weston_coord pos[4]; if (view) { - weston_view_to_global_float(view, box->x1, box->y1, &x[0], &y[0]); - weston_view_to_global_float(view, box->x2, box->y1, &x[1], &y[1]); - weston_view_to_global_float(view, box->x2, box->y2, &x[2], &y[2]); - weston_view_to_global_float(view, box->x1, box->y2, &x[3], &y[3]); + weston_view_to_global_double(view, box->x1, box->y1, &pos[0].x, &pos[0].y); + weston_view_to_global_double(view, box->x2, box->y1, &pos[1].x, &pos[1].y); + weston_view_to_global_double(view, box->x2, box->y2, &pos[2].x, &pos[2].y); + weston_view_to_global_double(view, box->x1, box->y2, &pos[3].x, &pos[3].y); } else { - x[0] = box->x1; y[0] = box->y1; - x[1] = box->x2; y[1] = box->y1; - x[2] = box->x2; y[2] = box->y2; - x[3] = box->x1; y[3] = box->y2; + pos[0] = weston_coord(box->x1, box->y1); + pos[1] = weston_coord(box->x2, box->y1); + pos[2] = weston_coord(box->x2, box->y2); + pos[3] = weston_coord(box->x1, box->y2); } - draw_polygon_closed(cr, x, y, 4); + draw_polygon_closed(cr, pos, 4); } static void draw_geometry(cairo_t *cr, struct weston_view *view, - GLfloat *ex, GLfloat *ey, int n) + struct weston_coord *e, int n) { struct geometry *g = view->geometry; - float cx, cy; + double cx, cy; draw_box(cr, &g->surf, view); cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.4); cairo_fill(cr); - weston_view_to_global_float(view, g->surf.x1 - 4, g->surf.y1 - 4, &cx, &cy); + weston_view_to_global_double(view, g->surf.x1 - 4, g->surf.y1 - 4, &cx, &cy); cairo_arc(cr, cx, cy, 1.5, 0.0, 2.0 * M_PI); if (view->transform.enabled == 0) cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.8); @@ -289,12 +307,12 @@ draw_geometry(cairo_t *cr, struct weston_view *view, cairo_fill(cr); if (n) { - draw_polygon_closed(cr, ex, ey, n); + draw_polygon_closed(cr, e, n); cairo_set_source_rgb(cr, 0.0, 1.0, 0.0); cairo_stroke(cr); cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 0.5); - draw_polygon_labels(cr, ex, ey, n); + draw_polygon_labels(cr, e, n); } } @@ -306,11 +324,10 @@ redraw_handler(struct widget *widget, void *data) struct rectangle allocation; cairo_t *cr; cairo_surface_t *surface; - GLfloat ex[8]; - GLfloat ey[8]; + struct weston_coord e[8]; int n; - n = calculate_edges(&cliptest->view, &g->clip, &g->surf, ex, ey); + n = calculate_edges(&cliptest->view, &g->clip, &g->surf, e); widget_get_allocation(cliptest->widget, &allocation); @@ -344,7 +361,7 @@ redraw_handler(struct widget *widget, void *data) cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size(cr, 5.0); - draw_geometry(cr, &cliptest->view, ex, ey, n); + draw_geometry(cr, &cliptest->view, e, n); cairo_pop_group_to_source(cr); cairo_paint(cr); @@ -352,7 +369,7 @@ redraw_handler(struct widget *widget, void *data) cairo_select_font_face(cr, "monospace", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, 12.0); - draw_coordinates(cr, 10.0, 10.0, ex, ey, n); + draw_coordinates(cr, 10.0, 10.0, e, n); cairo_destroy(cr); @@ -514,6 +531,7 @@ cliptest_create(struct display *display) struct cliptest *cliptest; cliptest = xzalloc(sizeof *cliptest); + cliptest->view.surface = &cliptest->surface; cliptest->view.geometry = &cliptest->geometry; cliptest->view.transform.enabled = 0; geometry_init(&cliptest->geometry); @@ -566,9 +584,10 @@ read_timer(void) static int benchmark(void) { + struct weston_surface surface; struct weston_view view; struct geometry geom; - GLfloat ex[8], ey[8]; + struct weston_coord e[8]; int i; double t; const int N = 1000000; @@ -585,13 +604,14 @@ benchmark(void) geometry_set_phi(&geom, 0.0); + view.surface = &surface; view.transform.enabled = 1; view.geometry = &geom; reset_timer(); for (i = 0; i < N; i++) { geometry_set_phi(&geom, (float)i / 360.0f); - calculate_edges(&view, &geom.clip, &geom.surf, ex, ey); + calculate_edges(&view, &geom.clip, &geom.surf, e); } t = read_timer(); diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c index fb53069e4..117d1115a 100644 --- a/clients/desktop-shell.c +++ b/clients/desktop-shell.c @@ -41,22 +41,24 @@ #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 "tablet-unstable-v2-client-protocol.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 +144,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 +215,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 +226,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 +278,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; } @@ -374,6 +375,56 @@ panel_launcher_touch_up_handler(struct widget *widget, struct input *input, panel_launcher_activate(launcher); } +static void +panel_launcher_tablet_tool_proximity_in_handler(struct widget *widget, + struct tablet_tool *tool, + struct tablet *tablet, void *data) +{ + struct panel_launcher *launcher; + + launcher = widget_get_user_data(widget); + launcher->focused = 1; + widget_schedule_redraw(widget); +} + +static void +panel_launcher_tablet_tool_proximity_out_handler(struct widget *widget, + struct tablet_tool *tool, void *data) +{ + struct panel_launcher *launcher; + + launcher = widget_get_user_data(widget); + launcher->focused = 0; + widget_schedule_redraw(widget); +} + +static void +panel_launcher_tablet_tool_up_handler(struct widget *widget, + struct tablet_tool *tool, + void *data) +{ + struct panel_launcher *launcher; + + launcher = widget_get_user_data(widget); + panel_launcher_activate(launcher); +} + +static void +panel_launcher_tablet_tool_button_handler(struct widget *widget, + struct tablet_tool *tool, + uint32_t button, + uint32_t state_w, + void *data) +{ + struct panel_launcher *launcher; + enum zwp_tablet_tool_v2_button_state state = state_w; + + launcher = widget_get_user_data(widget); + + if (state == ZWP_TABLET_TOOL_V2_BUTTON_STATE_RELEASED) + panel_launcher_activate(launcher); +} + static void clock_func(struct toytimer *tt) { @@ -575,10 +626,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 +729,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); @@ -745,6 +757,13 @@ panel_add_launcher(struct panel *panel, const char *icon, const char *path) panel_launcher_touch_down_handler); widget_set_touch_up_handler(launcher->widget, panel_launcher_touch_up_handler); + widget_set_tablet_tool_up_handler(launcher->widget, + panel_launcher_tablet_tool_up_handler); + widget_set_tablet_tool_proximity_handlers(launcher->widget, + panel_launcher_tablet_tool_proximity_in_handler, + panel_launcher_tablet_tool_proximity_out_handler); + widget_set_tablet_tool_button_handler(launcher->widget, + panel_launcher_tablet_tool_button_handler); widget_set_redraw_handler(launcher->widget, panel_launcher_redraw_handler); widget_set_motion_handler(launcher->widget, @@ -1447,7 +1466,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 +1478,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 +1491,7 @@ panel_add_launchers(struct panel *panel, struct desktop *desktop) free(icon); free(path); + free(displayname); } if (count == 0) { @@ -1477,7 +1500,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 1362db7ec..822cb98df 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/keyboard.c b/clients/keyboard.c index deddd78d9..e5fa3296a 100644 --- a/clients/keyboard.c +++ b/clients/keyboard.c @@ -506,7 +506,7 @@ delete_before_cursor(struct virtual_keyboard *keyboard) end = keyboard->surrounding_text + keyboard->surrounding_cursor; zwp_input_method_context_v1_delete_surrounding_text(keyboard->context, - (start - keyboard->surrounding_text) - keyboard->surrounding_cursor, + start - keyboard->surrounding_text, end - start); zwp_input_method_context_v1_commit_string(keyboard->context, keyboard->serial, diff --git a/clients/meson.build b/clients/meson.build index 362f7fe3f..2fe10956a 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -12,6 +12,8 @@ srcs_toytoolkit = [ relative_pointer_unstable_v1_protocol_c, pointer_constraints_unstable_v1_client_protocol_h, pointer_constraints_unstable_v1_protocol_c, + tablet_unstable_v2_client_protocol_h, + tablet_unstable_v2_protocol_c, ivi_application_client_protocol_h, ivi_application_protocol_c, viewporter_client_protocol_h, @@ -20,6 +22,7 @@ srcs_toytoolkit = [ deps_toytoolkit = [ dep_wayland_client, dep_lib_cairo_shared, + dep_matrix_c, dep_xkbcommon, dependency('wayland-cursor'), cc.find_library('util'), @@ -35,6 +38,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 +67,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, ], @@ -96,7 +105,8 @@ simple_clients = [ 'dep_objs': [ dep_wayland_client, dep_libdrm, - dep_libm + dep_libm, + dep_matrix_c, ], 'deps': [ 'egl', 'glesv2', 'gbm' ], 'options': [ 'renderer-gl' ] @@ -120,12 +130,23 @@ simple_clients = [ 'name': 'egl', 'sources': [ 'simple-egl.c', + fractional_scale_v1_client_protocol_h, + fractional_scale_v1_protocol_c, + tearing_control_v1_client_protocol_h, + tearing_control_v1_protocol_c, + viewporter_client_protocol_h, + viewporter_protocol_c, xdg_shell_client_protocol_h, xdg_shell_protocol_c, 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 +238,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' ], @@ -264,7 +270,7 @@ demo_clients = [ { 'basename': 'clickdot' }, { 'basename': 'cliptest', - 'dep_objs': dep_vertex_clipping + 'dep_objs': [ dep_vertex_clipping, dep_matrix_c ] }, { 'basename': 'confine' }, { @@ -324,6 +330,13 @@ demo_clients = [ 'basename': 'subsurfaces', 'deps': [ 'egl', 'glesv2', 'wayland-egl' ] }, + { + 'basename': 'tablet', + 'add_sources': [ + tablet_unstable_v2_client_protocol_h, + tablet_unstable_v2_protocol_c, + ], + }, { 'basename': 'transformed' }, ] @@ -364,23 +377,13 @@ if get_option('shell-desktop') ) env_modmap += 'weston-keyboard=@0@;'.format(exe_keyboard.full_path()) - exe_shooter = executable( - 'weston-screenshooter', - 'screenshot.c', - weston_screenshooter_client_protocol_h, - weston_screenshooter_protocol_c, - include_directories: common_inc, - dependencies: dep_toytoolkit, - install_dir: get_option('bindir'), - install: true - ) - env_modmap += 'weston-screenshooter=@0@;'.format(exe_shooter.full_path()) - exe_shell_desktop = executable( 'weston-desktop-shell', 'desktop-shell.c', weston_desktop_shell_client_protocol_h, weston_desktop_shell_protocol_c, + tablet_unstable_v2_client_protocol_h, + tablet_unstable_v2_protocol_c, include_directories: common_inc, dependencies: dep_toytoolkit, install_dir: get_option('libexecdir'), @@ -389,6 +392,23 @@ if get_option('shell-desktop') env_modmap += 'weston-desktop-shell=@0@;'.format(exe_shell_desktop.full_path()) endif +if get_option('shell-desktop') or get_option('shell-fullscreen') or get_option('shell-kiosk') or get_option('shell-ivi') + exe_shooter = executable( + 'weston-screenshooter', + 'screenshot.c', + weston_output_capture_client_protocol_h, + weston_output_capture_protocol_c, + include_directories: common_inc, + dependencies: [ + dep_toytoolkit, + dep_libweston_private, # for pixel-formats.h + dep_pixman, + ], + install_dir: get_option('bindir'), + install: true + ) + env_modmap += 'weston-screenshooter=@0@;'.format(exe_shooter.full_path()) +endif if get_option('shell-ivi') exe_shell_ivi_ui = executable( diff --git a/clients/presentation-shm.c b/clients/presentation-shm.c index d5d73a2a8..2bb0aa1ce 100644 --- a/clients/presentation-shm.c +++ b/clients/presentation-shm.c @@ -446,7 +446,7 @@ feedback_presented(void *data, struct feedback *feedback = data; struct window *window = feedback->window; struct feedback *prev_feedback = window->received_feedback; - uint64_t seq = ((uint64_t)seq_hi << 32) + seq_lo; + uint64_t seq = u64_from_u32s(seq_hi, seq_lo); const struct timespec *prevpresent; uint32_t commit, present; uint32_t f2c, c2p, f2p; @@ -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/scaler.c b/clients/scaler.c index ed9ae626d..3948fa22f 100644 --- a/clients/scaler.c +++ b/clients/scaler.c @@ -317,6 +317,11 @@ main(int argc, char *argv[]) display_set_user_data(box.display, &box); display_set_global_handler(box.display, global_handler); + if (box.mode != MODE_NO_VIEWPORT && !box.viewport) { + fprintf(stderr, "compositor doesn't support viewporter\n"); + return -1; + } + display_run(d); widget_destroy(box.widget); diff --git a/clients/screenshot.c b/clients/screenshot.c index ea0a2b2e5..a160406b2 100644 --- a/clients/screenshot.c +++ b/clients/screenshot.c @@ -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 "Software"), @@ -24,6 +25,7 @@ #include "config.h" #include +#include #include #include #include @@ -33,25 +35,49 @@ #include #include #include +#include #include +#include #include -#include "weston-screenshooter-client-protocol.h" +#include "weston-output-capture-client-protocol.h" #include "shared/os-compatibility.h" #include "shared/xalloc.h" #include "shared/file-util.h" +#include "pixel-formats.h" -/* The screenshooter is a good example of a custom object exposed by - * the compositor and serves as a test bed for implementing client - * side marshalling outside libwayland.so */ +struct screenshooter_app { + struct wl_registry *registry; + struct wl_shm *shm; + struct weston_capture_v1 *capture_factory; + struct wl_list output_list; /* struct screenshooter_output::link */ -struct screenshooter_output { - struct wl_output *output; - struct wl_buffer *buffer; - int width, height, offset_x, offset_y; + bool retry; + bool failed; + int waitcount; +}; + +struct screenshooter_buffer { + size_t len; void *data; - struct wl_list link; + struct wl_buffer *wl_buffer; + pixman_image_t *image; +}; + +struct screenshooter_output { + struct screenshooter_app *app; + struct wl_list link; /* struct screenshooter_app::output_list */ + + struct wl_output *wl_output; + int offset_x, offset_y; + + struct weston_capture_source_v1 *source; + + int buffer_width; + int buffer_height; + const struct pixel_format_info *fmt; + struct screenshooter_buffer *buffer; }; struct buffer_size { @@ -61,97 +87,204 @@ struct buffer_size { int max_x, max_y; }; -struct screenshooter_data { - struct wl_shm *shm; - struct wl_list output_list; +static struct screenshooter_buffer * +screenshot_create_shm_buffer(struct screenshooter_app *app, + size_t width, size_t height, + const struct pixel_format_info *fmt) +{ + struct screenshooter_buffer *buffer; + struct wl_shm_pool *pool; + int fd; + size_t bytes_pp; + size_t stride; - struct weston_screenshooter *screenshooter; - int buffer_copy_done; -}; + assert(width > 0); + assert(height > 0); + assert(fmt && fmt->bpp > 0); + assert(fmt->pixman_format); + + buffer = xzalloc(sizeof *buffer); + + bytes_pp = fmt->bpp / 8; + stride = width * bytes_pp; + buffer->len = stride * height; + + assert(width == stride / bytes_pp); + assert(height == buffer->len / stride); + + fd = os_create_anonymous_file(buffer->len); + if (fd < 0) { + fprintf(stderr, "creating a buffer file for %zd B failed: %s\n", + buffer->len, strerror(errno)); + free(buffer); + return NULL; + } + + buffer->data = mmap(NULL, buffer->len, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (buffer->data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); + close(fd); + free(buffer); + return NULL; + } + + pool = wl_shm_create_pool(app->shm, fd, buffer->len); + close(fd); + buffer->wl_buffer = + wl_shm_pool_create_buffer(pool, 0, width, height, stride, + pixel_format_get_shm_format(fmt)); + wl_shm_pool_destroy(pool); + buffer->image = pixman_image_create_bits(fmt->pixman_format, + width, height, + buffer->data, stride); + abort_oom_if_null(buffer->image); + + return buffer; +} static void -display_handle_geometry(void *data, - struct wl_output *wl_output, - int x, - int y, - int physical_width, - int physical_height, - int subpixel, - const char *make, - const char *model, - int transform) +screenshooter_buffer_destroy(struct screenshooter_buffer *buffer) { - struct screenshooter_output *output; + if (!buffer) + return; - output = wl_output_get_user_data(wl_output); + pixman_image_unref(buffer->image); + munmap(buffer->data, buffer->len); + wl_buffer_destroy(buffer->wl_buffer); + free(buffer); +} - if (wl_output == output->output) { - output->offset_x = x; - output->offset_y = y; - } +static void +capture_source_handle_format(void *data, + struct weston_capture_source_v1 *proxy, + uint32_t drm_format) +{ + struct screenshooter_output *output = data; + + assert(output->source == proxy); + + output->fmt = pixel_format_get_info(drm_format); } static void -display_handle_mode(void *data, - struct wl_output *wl_output, - uint32_t flags, - int width, - int height, - int refresh) +capture_source_handle_size(void *data, + struct weston_capture_source_v1 *proxy, + int32_t width, int32_t height) { - struct screenshooter_output *output; + struct screenshooter_output *output = data; - output = wl_output_get_user_data(wl_output); + assert(width > 0); + assert(height > 0); - if (wl_output == output->output && (flags & WL_OUTPUT_MODE_CURRENT)) { - output->width = width; - output->height = height; - } + output->buffer_width = width; + output->buffer_height = height; } -static const struct wl_output_listener output_listener = { - display_handle_geometry, - display_handle_mode -}; +static void +capture_source_handle_complete(void *data, + struct weston_capture_source_v1 *proxy) +{ + struct screenshooter_output *output = data; + + output->app->waitcount--; +} static void -screenshot_done(void *data, struct weston_screenshooter *screenshooter) +capture_source_handle_retry(void *data, + struct weston_capture_source_v1 *proxy) { - struct screenshooter_data *sh_data = data; - sh_data->buffer_copy_done = 1; + struct screenshooter_output *output = data; + + output->app->waitcount--; + output->app->retry = true; } -static const struct weston_screenshooter_listener screenshooter_listener = { - screenshot_done +static void +capture_source_handle_failed(void *data, + struct weston_capture_source_v1 *proxy, + const char *msg) +{ + struct screenshooter_output *output = data; + + output->app->waitcount--; + output->app->failed = true; + + if (msg) + fprintf(stderr, "Output capture error: %s\n", msg); +} + +static const struct weston_capture_source_v1_listener capture_source_handlers = { + .format = capture_source_handle_format, + .size = capture_source_handle_size, + .complete = capture_source_handle_complete, + .retry = capture_source_handle_retry, + .failed = capture_source_handle_failed, }; +static void +create_output(struct screenshooter_app *app, uint32_t output_name, uint32_t version) +{ + struct screenshooter_output *output; + + version = MIN(version, 4); + output = xzalloc(sizeof *output); + output->app = app; + output->wl_output = wl_registry_bind(app->registry, output_name, + &wl_output_interface, version); + abort_oom_if_null(output->wl_output); + + output->source = weston_capture_v1_create(app->capture_factory, + output->wl_output, + WESTON_CAPTURE_V1_SOURCE_FRAMEBUFFER); + abort_oom_if_null(output->source); + weston_capture_source_v1_add_listener(output->source, + &capture_source_handlers, output); + + wl_list_insert(&app->output_list, &output->link); +} + +static void +destroy_output(struct screenshooter_output *output) +{ + weston_capture_source_v1_destroy(output->source); + + if (wl_output_get_version(output->wl_output) >= WL_OUTPUT_RELEASE_SINCE_VERSION) + wl_output_release(output->wl_output); + else + wl_output_destroy(output->wl_output); + + screenshooter_buffer_destroy(output->buffer); + wl_list_remove(&output->link); + free(output); +} + static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { - static struct screenshooter_output *output; - struct screenshooter_data *sh_data = data; - - if (strcmp(interface, "wl_output") == 0) { - output = xmalloc(sizeof *output); - output->output = wl_registry_bind(registry, name, - &wl_output_interface, 1); - wl_list_insert(&sh_data->output_list, &output->link); - wl_output_add_listener(output->output, &output_listener, output); - } else if (strcmp(interface, "wl_shm") == 0) { - sh_data->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); - } else if (strcmp(interface, "weston_screenshooter") == 0) { - sh_data->screenshooter = wl_registry_bind(registry, name, - &weston_screenshooter_interface, - 1); + struct screenshooter_app *app = data; + + if (strcmp(interface, wl_output_interface.name) == 0) { + create_output(app, name, version); + } else if (strcmp(interface, wl_shm_interface.name) == 0) { + app->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + /* + * Not listening for format advertisements, + * weston_capture_source_v1.format event tells us what to use. + */ + } else if (strcmp(interface, weston_capture_v1_interface.name) == 0) { + app->capture_factory = wl_registry_bind(registry, name, + &weston_capture_v1_interface, + 1); } } static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { - /* XXX: unimplemented */ + /* Dynamic output removals will just fail the respective shot. */ } static const struct wl_registry_listener registry_listener = { @@ -159,80 +292,52 @@ static const struct wl_registry_listener registry_listener = { handle_global_remove }; -static struct wl_buffer * -screenshot_create_shm_buffer(int width, int height, void **data_out, - struct wl_shm *shm) +static void +screenshooter_output_capture(struct screenshooter_output *output) { - struct wl_shm_pool *pool; - struct wl_buffer *buffer; - int fd, size, stride; - void *data; - - stride = width * 4; - size = stride * height; - - fd = os_create_anonymous_file(size); - if (fd < 0) { - fprintf(stderr, "creating a buffer file for %d B failed: %s\n", - size, strerror(errno)); - return NULL; - } - - data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (data == MAP_FAILED) { - fprintf(stderr, "mmap failed: %s\n", strerror(errno)); - close(fd); - return NULL; - } - - pool = wl_shm_create_pool(shm, fd, size); - close(fd); - buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, - WL_SHM_FORMAT_XRGB8888); - wl_shm_pool_destroy(pool); - - *data_out = data; - - return buffer; + screenshooter_buffer_destroy(output->buffer); + output->buffer = screenshot_create_shm_buffer(output->app, + output->buffer_width, + output->buffer_height, + output->fmt); + abort_oom_if_null(output->buffer); + + weston_capture_source_v1_capture(output->source, + output->buffer->wl_buffer); + output->app->waitcount++; } static void screenshot_write_png(const struct buffer_size *buff_size, struct wl_list *output_list) { - int output_stride, buffer_stride, i; + pixman_image_t *shot; cairo_surface_t *surface; - void *data, *d, *s; - struct screenshooter_output *output, *next; + struct screenshooter_output *output; FILE *fp; char filepath[PATH_MAX]; - buffer_stride = buff_size->width * 4; - - data = xmalloc(buffer_stride * buff_size->height); - if (!data) - return; - - wl_list_for_each_safe(output, next, output_list, link) { - output_stride = output->width * 4; - s = output->data; - d = data + (output->offset_y - buff_size->min_y) * buffer_stride + - (output->offset_x - buff_size->min_x) * 4; + shot = pixman_image_create_bits(PIXMAN_a8r8g8b8, + buff_size->width, buff_size->height, + NULL, 0); + abort_oom_if_null(shot); - for (i = 0; i < output->height; i++) { - memcpy(d, s, output_stride); - d += buffer_stride; - s += output_stride; - } - - free(output); + wl_list_for_each(output, output_list, link) { + pixman_image_composite32(PIXMAN_OP_SRC, + output->buffer->image, /* src */ + NULL, /* mask */ + shot, /* dest */ + 0, 0, /* src x,y */ + 0, 0, /* mask x,y */ + output->offset_x, output->offset_y, /* dst x,y */ + output->buffer_width, output->buffer_height); } - surface = cairo_image_surface_create_for_data(data, + surface = cairo_image_surface_create_for_data((void *)pixman_image_get_data(shot), CAIRO_FORMAT_ARGB32, - buff_size->width, - buff_size->height, - buffer_stride); + pixman_image_get_width(shot), + pixman_image_get_height(shot), + pixman_image_get_stride(shot)); fp = file_create_dated(getenv("XDG_PICTURES_DIR"), "wayland-screenshot-", ".png", filepath, sizeof(filepath)); @@ -241,11 +346,12 @@ screenshot_write_png(const struct buffer_size *buff_size, cairo_surface_write_to_png(surface, filepath); } cairo_surface_destroy(surface); - free(data); + pixman_image_unref(shot); } static int -screenshot_set_buffer_size(struct buffer_size *buff_size, struct wl_list *output_list) +screenshot_set_buffer_size(struct buffer_size *buff_size, + struct wl_list *output_list) { struct screenshooter_output *output; buff_size->min_x = buff_size->min_y = INT_MAX; @@ -254,16 +360,16 @@ screenshot_set_buffer_size(struct buffer_size *buff_size, struct wl_list *output wl_list_for_each_reverse(output, output_list, link) { output->offset_x = position; - position += output->width; + position += output->buffer_width; } wl_list_for_each(output, output_list, link) { buff_size->min_x = MIN(buff_size->min_x, output->offset_x); buff_size->min_y = MIN(buff_size->min_y, output->offset_y); buff_size->max_x = - MAX(buff_size->max_x, output->offset_x + output->width); + MAX(buff_size->max_x, output->offset_x + output->buffer_width); buff_size->max_y = - MAX(buff_size->max_y, output->offset_y + output->height); + MAX(buff_size->max_y, output->offset_y + output->buffer_height); } if (buff_size->max_x <= buff_size->min_x || @@ -276,13 +382,16 @@ screenshot_set_buffer_size(struct buffer_size *buff_size, struct wl_list *output return 0; } -int main(int argc, char *argv[]) +int +main(int argc, char *argv[]) { struct wl_display *display; - struct wl_registry *registry; struct screenshooter_output *output; + struct screenshooter_output *tmp_output; struct buffer_size buff_size = {}; - struct screenshooter_data sh_data = {}; + struct screenshooter_app app = {}; + + wl_list_init(&app.output_list); display = wl_display_connect(NULL); if (display == NULL) { @@ -291,39 +400,52 @@ int main(int argc, char *argv[]) return -1; } - wl_list_init(&sh_data.output_list); - registry = wl_display_get_registry(display); - wl_registry_add_listener(registry, ®istry_listener, &sh_data); - wl_display_dispatch(display); + app.registry = wl_display_get_registry(display); + wl_registry_add_listener(app.registry, ®istry_listener, &app); + + /* Process wl_registry advertisements */ wl_display_roundtrip(display); - if (sh_data.screenshooter == NULL) { - fprintf(stderr, "display doesn't support screenshooter\n"); + + if (!app.shm) { + fprintf(stderr, "Error: display does not support wl_shm\n"); + return -1; + } + if (!app.capture_factory) { + fprintf(stderr, "Error: display does not support weston_capture_v1\n"); return -1; } - weston_screenshooter_add_listener(sh_data.screenshooter, - &screenshooter_listener, - &sh_data); + /* Process initial events for wl_output and weston_capture_source_v1 */ + wl_display_roundtrip(display); - if (screenshot_set_buffer_size(&buff_size, &sh_data.output_list)) - return -1; + do { + app.retry = false; + wl_list_for_each(output, &app.output_list, link) + screenshooter_output_capture(output); - wl_list_for_each(output, &sh_data.output_list, link) { - output->buffer = - screenshot_create_shm_buffer(output->width, - output->height, - &output->data, - sh_data.shm); - weston_screenshooter_take_shot(sh_data.screenshooter, - output->output, - output->buffer); - sh_data.buffer_copy_done = 0; - while (!sh_data.buffer_copy_done) - wl_display_roundtrip(display); + while (app.waitcount > 0 && !app.failed) { + if (wl_display_dispatch(display) < 0) + app.failed = true; + assert(app.waitcount >= 0); + } + } while (app.retry && !app.failed); + + if (!app.failed) { + if (screenshot_set_buffer_size(&buff_size, &app.output_list) < 0) + return -1; + screenshot_write_png(&buff_size, &app.output_list); + } else { + fprintf(stderr, "Error: screenshot or protocol failure\n"); } - screenshot_write_png(&buff_size, &sh_data.output_list); + wl_list_for_each_safe(output, tmp_output, &app.output_list, link) + destroy_output(output); + + weston_capture_v1_destroy(app.capture_factory); + wl_shm_destroy(app.shm); + wl_registry_destroy(app.registry); + wl_display_disconnect(display); return 0; } diff --git a/clients/simple-dmabuf-egl.c b/clients/simple-dmabuf-egl.c index ef0d9de6c..aed9b43cb 100644 --- a/clients/simple-dmabuf-egl.c +++ b/clients/simple-dmabuf-egl.c @@ -58,6 +58,7 @@ #include #include +#include #include "shared/weston-egl-ext.h" /* Possible options that affect the displayed image */ @@ -149,6 +150,7 @@ struct window { GLuint pos; GLuint color; GLuint offset_uniform; + GLuint reflection_uniform; } gl; bool render_mandelbrot; }; @@ -329,9 +331,7 @@ static int create_dmabuf_buffer(struct display *display, struct buffer *buffer, int width, int height, uint32_t opts) { - /* Y-Invert the buffer image, since we are going to renderer to the - * buffer through a FBO. */ - static uint32_t flags = ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; + static uint32_t flags = 0; struct zwp_linux_buffer_params_v1 *params; int i; @@ -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); } @@ -401,15 +411,8 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer, params = zwp_linux_dmabuf_v1_create_params(display->dmabuf); - if ((opts & OPT_DIRECT_DISPLAY) && display->direct_display) { + if ((opts & OPT_DIRECT_DISPLAY) && display->direct_display) weston_direct_display_v1_enable(display->direct_display, params); - /* turn off Y_INVERT otherwise linux-dmabuf will reject it and - * we need all dmabuf flags turned off */ - flags &= ~ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT; - - fprintf(stdout, "image is y-inverted as direct-display flag was set, " - "dmabuf y-inverted attribute flag was removed\n"); - } for (i = 0; i < buffer->plane_count; ++i) { zwp_linux_buffer_params_v1_add(params, @@ -493,11 +496,12 @@ static const struct xdg_toplevel_listener xdg_toplevel_listener = { static const char *vert_shader_text = "uniform float offset;\n" + "uniform mat4 reflection;\n" "attribute vec4 pos;\n" "attribute vec4 color;\n" "varying vec4 v_color;\n" "void main() {\n" - " gl_Position = pos + vec4(offset, offset, 0.0, 0.0);\n" + " gl_Position = reflection * (pos + vec4(offset, offset, 0.0, 0.0));\n" " v_color = color;\n" "}\n"; @@ -510,11 +514,12 @@ static const char *frag_shader_text = static const char *vert_shader_mandelbrot_text = "uniform float offset;\n" + "uniform mat4 reflection;\n" "attribute vec4 pos;\n" "varying vec2 v_pos;\n" "void main() {\n" " v_pos = pos.xy;\n" - " gl_Position = pos + vec4(offset, offset, 0.0, 0.0);\n" + " gl_Position = reflection * (pos + vec4(offset, offset, 0.0, 0.0));\n" "}\n"; @@ -614,6 +619,8 @@ window_set_up_gl(struct window *window) window->gl.offset_uniform = glGetUniformLocation(window->gl.program, "offset"); + window->gl.reflection_uniform = + glGetUniformLocation(window->gl.program, "reflection"); return window->gl.program != 0; } @@ -793,6 +800,7 @@ render(struct window *window, struct buffer *buffer) GLfloat offset; struct timeval tv; uint64_t time_ms; + struct weston_matrix reflection; gettimeofday(&tv, NULL); time_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000; @@ -801,12 +809,32 @@ render(struct window *window, struct buffer *buffer) * to offsets in the [-0.5, 0.5) range. */ offset = (time_ms % iteration_ms) / (float) iteration_ms - 0.5; + weston_matrix_init(&reflection); + /* perform a reflection about x-axis to keep the same orientation of + * the vertices colors, as outlined in the comment at the beginning + * of this function. + * + * We need to render upside-down, because rendering through an FBO + * causes the bottom of the image to be written to the top pixel row of + * the buffer, y-flipping the image. + * + * Reflection is a specialized version of scaling with the + * following matrix: + * + * [1, 0, 0] + * [0, -1, 0] + * [0, 0, 1] + */ + weston_matrix_scale(&reflection, 1, -1, 1); + /* Direct all GL draws to the buffer through the FBO */ glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); glViewport(0, 0, window->width, window->height); glUniform1f(window->gl.offset_uniform, offset); + glUniformMatrix4fv(window->gl.reflection_uniform, 1, GL_FALSE, + (GLfloat *) reflection.d); glClearColor(0.0,0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); @@ -836,6 +864,7 @@ render_mandelbrot(struct window *window, struct buffer *buffer) struct timeval tv; uint64_t time_ms; int i; + struct weston_matrix reflection; gettimeofday(&tv, NULL); time_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000; @@ -844,12 +873,17 @@ render_mandelbrot(struct window *window, struct buffer *buffer) * to offsets in the [-0.5, 0.5) range. */ offset = (time_ms % iteration_ms) / (float) iteration_ms - 0.5; + weston_matrix_init(&reflection); + weston_matrix_scale(&reflection, 1, -1, 1); + /* Direct all GL draws to the buffer through the FBO */ glBindFramebuffer(GL_FRAMEBUFFER, buffer->gl_fbo); glViewport(0, 0, window->width, window->height); glUniform1f(window->gl.offset_uniform, offset); + glUniformMatrix4fv(window->gl.reflection_uniform, 1, GL_FALSE, + (GLfloat *) reflection.d); glClearColor(0.6, 0.6, 0.6, 1.0); glClear(GL_COLOR_BUFFER_BIT); @@ -1000,7 +1034,7 @@ dmabuf_modifiers(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) { struct display *d = data; - uint64_t modifier = ((uint64_t)modifier_hi << 32) | modifier_lo; + uint64_t modifier = u64_from_u32s(modifier_hi, modifier_lo); if (format != d->format) { return; diff --git a/clients/simple-dmabuf-feedback.c b/clients/simple-dmabuf-feedback.c index 516cdbc8f..e5b7f9b02 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 @@ -62,7 +68,10 @@ static const char *vert_shader_text = "attribute vec4 color;\n" "varying vec4 v_color;\n" "void main() {\n" - " gl_Position = pos;\n" + " // We need to render upside-down, because rendering through an\n" + " // FBO causes the bottom of the image to be written to the top\n" + " // pixel row of the buffer, y-flipping the image.\n" + " gl_Position = vec4(1.0, -1.0, 1.0, 1.0) * pos;\n" " v_color = color;\n" "}\n"; @@ -110,6 +119,9 @@ struct output { int width, height; int scale; bool initialized; + struct { + int width, height; + } configure; }; struct egl { @@ -135,11 +147,14 @@ 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; }; struct buffer { + bool created; + bool valid; struct window *window; struct wl_buffer *buffer; enum { @@ -147,7 +162,6 @@ struct buffer { IN_USE, AVAILABLE } status; - bool recreate; int dmabuf_fds[4]; struct gbm_bo *bo; EGLImageKHR egl_image; @@ -166,12 +180,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]; }; @@ -448,26 +464,30 @@ buffer_free(struct buffer *buf) for (i = 0; i < buf->num_planes; i++) close(buf->dmabuf_fds[i]); + + buf->created = false; } 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) +buffer_recreate(struct buffer *buf, struct window *window) { - struct window *window = buf->window; - uint32_t width = buf->width; - uint32_t height = buf->height; + uint32_t width = window->display->output.width; + uint32_t height = window->display->output.height; + + if (buf->created) + buffer_free(buf); - buffer_free(buf); create_dmabuf_buffer(window, buf, width, height, window->format.format, window->format.modifiers.size / sizeof(uint64_t), - window->format.modifiers.data); - buf->recreate = false; + window->format.modifiers.data, window->bo_flags); + buf->created = true; + buf->valid = true; } static void @@ -476,9 +496,6 @@ buffer_release(void *data, struct wl_buffer *buffer) struct buffer *buf = data; buf->status = AVAILABLE; - - if (buf->recreate) - buffer_recreate(buf); } static const struct wl_buffer_listener buffer_listener = { @@ -516,7 +533,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 +548,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 +568,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; } @@ -580,23 +605,30 @@ window_next_buffer(struct window *window) { unsigned int i; - for (i = 0; i < NUM_BUFFERS; i++) - if (window->buffers[i].status == AVAILABLE) - return &window->buffers[i]; - - /* In this client, we sometimes have to recreate the buffers. As we are - * not using the create_immed request from zwp_linux_dmabuf_v1, we need - * to wait an event from the server (what leads to create_succeeded() - * being called in this client). So if all buffers are busy, it may be - * the case in which all the buffers were recreated but the server still - * didn't send the events. This is very unlikely to happen, but a - * roundtrip() guarantees that we receive and process the events. */ - wl_display_roundtrip(window->display->display); + for (i = 0; i < NUM_BUFFERS; i++) { + if (!window->buffers[i].created || + (!window->buffers[i].valid && + window->buffers[i].status == AVAILABLE)) + buffer_recreate(&window->buffers[i], window); + } for (i = 0; i < NUM_BUFFERS; i++) if (window->buffers[i].status == AVAILABLE) return &window->buffers[i]; + while (true) { + /* In this client, we create buffers lazily and also sometimes + * have to recreate the buffers. As we are not using the + * create_immed request from zwp_linux_dmabuf_v1, we need to wait + * for an event from the server (what leads to create_succeeded() + * being called in this client). */ + wl_display_roundtrip(window->display->display); + + for (i = 0; i < NUM_BUFFERS; i++) + if (window->buffers[i].status == AVAILABLE) + return &window->buffers[i]; + } + return NULL; } @@ -639,6 +671,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 +693,20 @@ 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); + window->presentation_feedback = NULL; + } + 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,11 +721,75 @@ 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); + window->presentation_feedback = NULL; +} + +static void presentation_feedback_handle_discarded(void *data, + struct wp_presentation_feedback *feedback) +{ + struct window *window = data; + wp_presentation_feedback_destroy(feedback); + window->presentation_feedback = NULL; +} + +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 +window_buffers_invalidate(struct window *window) +{ + unsigned int i; + + for (i = 0; i < NUM_BUFFERS; i++) + window->buffers[i].valid = false; +} + static void xdg_surface_handle_configure(void *data, struct xdg_surface *surface, uint32_t serial) { struct window *window = data; + struct output *output = &window->display->output; + + if (output->configure.width != output->width || + output->configure.height != output->height) { + output->width = output->configure.width; + output->height = output->configure.height; + + window_buffers_invalidate (window); + } xdg_surface_ack_configure(surface, serial); window->wait_for_configure = false; @@ -693,6 +804,11 @@ xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height, struct wl_array *states) { + struct window *window = data; + struct output *output = &window->display->output; + + output->configure.width = width; + output->configure.height = height; } static void @@ -810,6 +926,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) @@ -838,9 +956,6 @@ static struct window * create_window(struct display *display) { struct window *window; - uint32_t width = display->output.width; - uint32_t height = display->output.height; - unsigned int i; window = zalloc(sizeof *window); assert(window && "error: failed to allocate memory for window"); @@ -869,12 +984,6 @@ create_window(struct display *display) egl_setup(window); gl_setup(window); - for (i = 0; i < NUM_BUFFERS; i++) - create_dmabuf_buffer(window, &window->buffers[i], width, height, - window->format.format, - window->format.modifiers.size / sizeof(uint64_t), - window->format.modifiers.data); - window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base, window->surface); @@ -890,14 +999,13 @@ create_window(struct display *display) xdg_toplevel_set_title(window->xdg_toplevel, "simple-dmabuf-feedback"); xdg_toplevel_set_app_id(window->xdg_toplevel, "org.freedesktop.weston.simple-dmabuf-feedback"); + xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); window->wait_for_configure = true; wl_surface_commit(window->surface); wl_display_roundtrip(display->display); - xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); - assert(!window->wait_for_configure && "error: could not configure XDG surface"); @@ -1000,8 +1108,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 +1217,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 +1238,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 +1280,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 +1312,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; @@ -1214,9 +1325,8 @@ dmabuf_feedback_done(void *data, struct zwp_linux_dmabuf_feedback_v1 *dmabuf_fee struct window *window = data; struct dmabuf_feedback_tranche *tranche; 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 @@ -1229,8 +1339,7 @@ dmabuf_feedback_done(void *data, struct zwp_linux_dmabuf_feedback_v1 *dmabuf_fee if (tranche->is_scanout_tranche) { got_scanout_tranche = true; if (pick_format_from_scanout_tranche(window, tranche)) { - for (i = 0; i < NUM_BUFFERS; i++) - window->buffers[i].recreate = true; + window_buffers_invalidate(window); break; } } @@ -1276,12 +1385,6 @@ static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int width, int height, int refresh) { - struct output *output = data; - - if (flags & WL_OUTPUT_MODE_CURRENT) { - output->width = width; - output->height = height; - } } static void @@ -1344,6 +1447,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 +1475,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); @@ -1407,43 +1517,31 @@ create_display() } /* Simple client to test the dma-buf feedback implementation. This does not - * replace the need to implement a dma-buf feedback test that can be run in - * the CI. But as we still don't know exactly how to do this, this client - * can be helpful to run tests manually. - * - * In order to use this, we have to hack the DRM-backend to pretend that - * INITIAL_BUFFER_FORMAT is not supported by the planes of the underlying - * hardware. In Weston, we have to do this in - * drm_output_prepare_plane_view(), more specifically in the part where - * we call drm_output_plane_view_has_valid_format(). So we'd have something - * like this: - * - * // in this example, INITIAL_BUFFER_FORMAT == DRM_FORMAT_XRGB8888 + * replace the need to implement a dma-buf feedback test that can be run in the + * CI. But as we still don't know exactly how to do this, this client can be + * helpful to run tests manually. It can also be helpful to test other + * compositors. * - * bool fake_unsupported_format = false; - * if (fb && fb->format->format == DRM_FORMAT_XRGB8888) - * fake_unsupported_format = true; + * In order to use this client, we have to hack the DRM-backend of the + * compositor to pretend that INITIAL_BUFFER_FORMAT is not supported by the + * planes of the underlying hardware. * - * if (!drm_output_plane_view_has_valid_format(plane, state, ev, fb) || - * fake_unsupported_format) - * ... + * How this client works: * - * It creates a surface and buffers for it with the same resolution of the - * output mode in use. Also, it sets the surface to fullscreen. So we have - * everything set to allow the surface to be placed in a plane. But as - * these buffers are created with INITIAL_BUFFER_FORMAT, they are placed in - * the renderer. + * This client creates a surface and buffers for it with the same resolution of + * the output mode in use. Also, it sets the surface to fullscreen. So we have + * everything set to allow the surface to be placed in an overlay plane. But as + * these buffers are created with INITIAL_BUFFER_FORMAT, they are not eligible + * for direct scanout. * - * When the compositor creates the client surface, it adds only the - * renderer tranche to its dma-buf feedback object and send the feedback to - * the client. But as the repaint cycles start and Weston detects that the - * only reason why the surface has not been placed in a plane was the - * incompatibility between the framebuffer format and the ones supported by - * the planes of the underlying hardware, Weston adds a scanout tranche to - * the surface dma-buf feedback and resend them. In this tranche the client - * can find pairs of formats and modifiers supported by the planes, and so - * it can recreate its buffers using one of these pairs in order to - * increase the chances of its surface end up in a plane. */ + * When Weston creates a client's surface, it adds only the renderer tranche to + * its dma-buf feedback object and send the feedback to the client. But as the + * repaint cycles start and Weston detects that the view of the client is not + * eligible for direct scanout because of the incompatibility of the + * framebuffer's format/modifier pair and the KMS device, it adds a scanout + * tranche to the feedback and resend it. In this scanout tranche the client can + * find parameters to re-allocate its buffers and increase its chances of + * hitting direct scanout. */ int main(int argc, char **argv) { diff --git a/clients/simple-dmabuf-v4l.c b/clients/simple-dmabuf-v4l.c index a19570f9b..0f693d1a6 100644 --- a/clients/simple-dmabuf-v4l.c +++ b/clients/simple-dmabuf-v4l.c @@ -203,7 +203,6 @@ static unsigned int set_format(struct display *display, uint32_t format) { struct v4l2_format fmt; - char buf[4]; CLEAR(fmt); @@ -214,30 +213,33 @@ set_format(struct display *display, uint32_t format) return 0; } + /* NOTE: pix and pix_mp are in a union, pixelformat member maps between them. */ + const int format_matches = fmt.fmt.pix.pixelformat == format; + /* No need to set the format if it already is the one we want */ if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE && - fmt.fmt.pix.pixelformat == format) + format_matches) return 1; if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && - fmt.fmt.pix_mp.pixelformat == format) + format_matches) return fmt.fmt.pix_mp.num_planes; - if (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) - fmt.fmt.pix.pixelformat = format; - else - fmt.fmt.pix_mp.pixelformat = format; + fmt.fmt.pix.pixelformat = format; if (xioctl(display->v4l_fd, VIDIOC_S_FMT, &fmt) == -1) { perror("VIDIOC_S_FMT"); return 0; } - if ((display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE && - fmt.fmt.pix.pixelformat != format) || - (display->format.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && - fmt.fmt.pix_mp.pixelformat != format)) { - fprintf(stderr, "Failed to set format to %.4s\n", - dump_format(format, buf)); + const int format_was_set = fmt.fmt.pix.pixelformat == format; + if (!format_was_set) { + char want_name[4]; + char have_name[4]; + + dump_format(format, want_name); + dump_format(fmt.fmt.pix.pixelformat, have_name); + fprintf(stderr, "Tried to set format: %.4s but have: %.4s\n", + want_name, have_name); return 0; } @@ -379,7 +381,7 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer) struct zwp_linux_buffer_params_v1 *params; uint64_t modifier; uint32_t flags; - unsigned i; + int i; modifier = 0; flags = 0; @@ -399,7 +401,13 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer) } } - for (i = 0; i < display->format.num_planes; ++i) + const int num_planes = (int) display->format.num_planes; + + for (i = 0; i < num_planes; ++i) { + fprintf(stderr, "buffer %d, plane %d has dma fd %d and stride " + "%d and modifier %" PRIu64 "\n", + buffer->index, i, buffer->dmabuf_fds[i], + display->format.strides[i], modifier); zwp_linux_buffer_params_v1_add(params, buffer->dmabuf_fds[i], i, /* plane_idx */ @@ -407,8 +415,124 @@ create_dmabuf_buffer(struct display *display, struct buffer *buffer) display->format.strides[i], modifier >> 32, modifier & 0xffffffff); - zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, - buffer); + } + + /* Some v4l2 devices can output NV12, but will do so without the MPLANE + * api. Instead, it outputs both the luminance and chrominance planes + * in the same dma buffer. Here we account for that, and add an extra + * plane from the same buffer if necessary. If it needs an extra plane, + * set the stride of the chrominance plane. NOTE: Also handles cases + * where 3 planes are expected in 1 dma buffer (untested) + */ + enum plane_layout_t { + DISJOINT = 0, + CONTIGUOUS, + }; + enum chrom_packing_t { + CHROM_SEPARATE = 0, /* Cr/Cb are in their own planes. */ + CHROM_COMBINED, /* Cr/Cb are interleaved. */ + }; + + /* This table contains some planar formats we could fix-up and support. */ + const struct planar_layout_t { + /* Format identification. */ + uint32_t v4l_fourcc; + /* Disjoint or contigious planes? */ + enum plane_layout_t plane_layout; + /* Zero if Cb/Cr in separate planes. */ + enum chrom_packing_t chrom_packing; + /* Expected plane count. */ + int num_planes; + /* Horizontal sub-sampling for chroma. */ + int chroma_subsample_hori; + /* Vertical sub-sampling for chroma. */ + int chroma_subsample_vert; + } planar_layouts[] = { + { V4L2_PIX_FMT_NV12M, DISJOINT, CHROM_COMBINED, 2, 2,2 }, + { V4L2_PIX_FMT_NV21M, DISJOINT, CHROM_COMBINED, 2, 2,2 }, + { V4L2_PIX_FMT_NV16M, DISJOINT, CHROM_COMBINED, 2, 2,1 }, + { V4L2_PIX_FMT_NV61M, DISJOINT, CHROM_COMBINED, 2, 2,1 }, + { V4L2_PIX_FMT_NV12, CONTIGUOUS, CHROM_COMBINED, 2, 2,2 }, + { V4L2_PIX_FMT_NV21, CONTIGUOUS, CHROM_COMBINED, 2, 2,2 }, + { V4L2_PIX_FMT_NV16, CONTIGUOUS, CHROM_COMBINED, 2, 2,1 }, + { V4L2_PIX_FMT_NV61, CONTIGUOUS, CHROM_COMBINED, 2, 2,1 }, + { V4L2_PIX_FMT_NV24, CONTIGUOUS, CHROM_COMBINED, 2, 1,1 }, + { V4L2_PIX_FMT_NV42, CONTIGUOUS, CHROM_COMBINED, 2, 1,1 }, + { V4L2_PIX_FMT_YUV420, CONTIGUOUS, CHROM_SEPARATE, 3, 2,2 }, + { V4L2_PIX_FMT_YVU420, CONTIGUOUS, CHROM_SEPARATE, 3, 2,2 }, + { V4L2_PIX_FMT_YUV420M, DISJOINT, CHROM_SEPARATE, 3, 2,2 }, + { V4L2_PIX_FMT_YVU420M, DISJOINT, CHROM_SEPARATE, 3, 2,2 }, + { 0, 0, 0, 0, 0 }, + }; + + int layoutnr = 0; + int num_missing_planes = 0; /* Non-zero if format needs more planes in dma buf. */ + int stride_extra_plane = 0; + int vrtres_extra_plane = 0; + const uint32_t stride0 = display->format.strides[0]; + + /* Search the table. */ + while (planar_layouts[layoutnr].v4l_fourcc) { + const struct planar_layout_t *layout = + planar_layouts + layoutnr; + + if (layout->v4l_fourcc == display->format.format) { + /* If disjoint planes are missing, there is nothing to + * salvage. */ + if (layout->plane_layout == DISJOINT) + assert(num_planes == layout->num_planes); + + /* Is this a case where we need to add 1 or 2 missing + * planes? */ + num_missing_planes = layout->num_planes - num_planes; + if (num_missing_planes > 0) { + /* With this knowledge: + * - Stride for Y + * - Packing of chrominance + * - Horizontal subsampling ...we can compute + * the stride for Cr and Cb. + */ + const uint32_t num_chrom_parts = + layout->chrom_packing == CHROM_COMBINED ? 2 : 1; + stride_extra_plane = + stride0 * num_chrom_parts / + layout->chroma_subsample_hori; + vrtres_extra_plane = + display->format.height / + layout->chroma_subsample_vert; + break; + } + } + layoutnr += 1; + } + /* If we determined we need additional planes, add them. */ + int offset_in_buffer = buffer->data_offsets[0] + + display->format.height * stride0; + + for (i = 0; i < num_missing_planes; ++i) { + /* Add same dma buffer, but with offset for chromimance plane. */ + fprintf(stderr,"Adding additional chrominance plane.\n"); + zwp_linux_buffer_params_v1_add(params, + buffer->dmabuf_fds[0], + 1 + i, /* plane_idx */ + offset_in_buffer, + stride_extra_plane, + modifier >> 32, + modifier & 0xffffffff); + offset_in_buffer += vrtres_extra_plane * stride_extra_plane; + } + + zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, buffer); + + fprintf(stderr,"creating buffer of size %dx%d format %c%c%c%c flags %d\n", + display->format.width, + display->format.height, + (display->drm_format >> 0) & 0xff, + (display->drm_format >> 8) & 0xff, + (display->drm_format >> 16) & 0xff, + (display->drm_format >> 24) & 0xff, + flags + ); zwp_linux_buffer_params_v1_create(params, display->format.width, display->format.height, @@ -730,7 +854,7 @@ dmabuf_modifier(void *data, struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) { struct display *d = data; - uint64_t modifier = ((uint64_t) modifier_hi << 32 ) | modifier_lo; + uint64_t modifier = u64_from_u32s(modifier_hi, modifier_lo); if (format == d->drm_format && modifier == DRM_FORMAT_MOD_LINEAR) d->requested_format_found = true; @@ -900,8 +1024,10 @@ create_display(uint32_t requested_format, uint32_t opt_flags) wl_display_roundtrip(display->display); if (!display->requested_format_found) { - fprintf(stderr, "0x%lx requested DRM format not available\n", - (unsigned long) requested_format); + char want_name[4]; + + dump_format(requested_format, want_name); + fprintf(stderr, "Requested DRM format %4s not available\n", want_name); exit(1); } diff --git a/clients/simple-egl.c b/clients/simple-egl.c index 2c7059c00..9284f1656 100644 --- a/clients/simple-egl.c +++ b/clients/simple-egl.c @@ -42,13 +42,19 @@ #include #include +#include "fractional-scale-v1-client-protocol.h" +#include "viewporter-client-protocol.h" #include "xdg-shell-client-protocol.h" +#include "tearing-control-v1-client-protocol.h" + #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; @@ -66,6 +72,9 @@ struct display { struct wl_cursor_theme *cursor_theme; struct wl_cursor *default_cursor; struct wl_surface *cursor_surface; + struct wp_tearing_control_manager_v1 *tearing_manager; + struct wp_viewporter *viewporter; + struct wp_fractional_scale_manager_v1 *fractional_scale_manager; struct { EGLDisplay dpy; EGLContext ctx; @@ -73,6 +82,8 @@ struct display { } egl; struct window *window; + struct wl_list output_list; /* struct output::link */ + PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC swap_buffers_with_damage; }; @@ -82,22 +93,52 @@ 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; + double fractional_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; + struct wp_tearing_control_v1 *tear_control; + struct wp_viewport *viewport; + struct wp_fractional_scale_v1 *fractional_scale_obj; + bool tearing, toggled_tearing, tear_enabled; + bool vertical_bar; + bool fullscreen_ratio; 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 +196,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 +220,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 +234,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 +297,129 @@ 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; + struct geometry new_buffer_size; + struct geometry new_viewport_dest_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); + } + + 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; + } + + if (window->fractional_buffer_scale > 0.0) { + if (window->buffer_scale > 1) { + window->buffer_scale = 1; + wl_surface_set_buffer_scale(window->surface, + window->buffer_scale); + } + + new_buffer_size.width = ceil(new_buffer_size.width * + window->fractional_buffer_scale); + new_buffer_size.height = ceil(new_buffer_size.height * + window->fractional_buffer_scale); + } else { + int32_t new_buffer_scale; + + 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); + } + + new_buffer_size.width *= window->buffer_scale; + new_buffer_size.height *= window->buffer_scale; + } + + if (window->fullscreen && window->fullscreen_ratio) { + int new_buffer_size_min; + int new_viewport_dest_size_min; + + new_buffer_size_min = MIN(new_buffer_size.width, + new_buffer_size.height); + new_buffer_size.width = new_buffer_size_min; + new_buffer_size.height = new_buffer_size_min; + + new_viewport_dest_size_min = MIN(window->logical_size.width, + window->logical_size.height); + new_viewport_dest_size.width = new_viewport_dest_size_min; + new_viewport_dest_size.height = new_viewport_dest_size_min; + } else { + new_viewport_dest_size.width = window->logical_size.width; + new_viewport_dest_size.height = window->logical_size.height; + } + + 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); + } + + if (window->fractional_buffer_scale > 0.0) + wp_viewport_set_destination(window->viewport, + new_viewport_dest_size.width, + new_viewport_dest_size.height); + + window->needs_buffer_geometry_update = false; +} + static void init_gl(struct window *window) { @@ -264,9 +428,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 +443,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 +518,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 +538,218 @@ 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 +draw_triangle(struct window *window, EGLint buffer_age) +{ + struct display *display = window->display; + 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 } + }; + struct wl_region *region; + EGLint rect[4]; + + glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts); + glVertexAttribPointer(window->gl.col, 3, GL_FLOAT, GL_FALSE, 0, colors); + glEnableVertexAttribArray(window->gl.pos); + glEnableVertexAttribArray(window->gl.col); + + glDrawArrays(GL_TRIANGLES, 0, 3); + + glDisableVertexAttribArray(window->gl.pos); + glDisableVertexAttribArray(window->gl.col); + + usleep(window->delay); + + if (window->opaque || window->fullscreen) { + region = wl_compositor_create_region(window->display->compositor); + wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX); + wl_surface_set_opaque_region(window->surface, region); + wl_region_destroy(region); + } else { + wl_surface_set_opaque_region(window->surface, NULL); + } + + if (display->swap_buffers_with_damage && buffer_age > 0) { + 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); + } else { + eglSwapBuffers(display->egl.dpy, window->egl_surface); + } +} + +static void +draw_bar(struct window *window, EGLint buffer_age, struct weston_matrix *rotation) +{ + struct display *display = window->display; + GLfloat verts[4][2] = { + { -1, 1 }, + { -0.9, 1 }, + { -1, -1 }, + { -0.9, -1 } + }; + static const GLfloat colors[4][3] = { + { 1, 1, 1 }, + { 1, 1, 1 }, + { 1, 1, 1 }, + { 1, 1, 1 } + }; + struct wl_region *region; + int i; + const float delta = 0.01; + static float offset = 0; + + offset += delta; + if (offset > 2) + offset = 0; + + for (i = 0 ; i < 4; i++) { + verts[i][0] += offset; + } + glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts); + glVertexAttribPointer(window->gl.col, 3, GL_FLOAT, GL_FALSE, 0, colors); + glEnableVertexAttribArray(window->gl.pos); + glEnableVertexAttribArray(window->gl.col); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(window->gl.pos); + glDisableVertexAttribArray(window->gl.col); + + usleep(window->delay); + + if (window->opaque || window->fullscreen) { + region = wl_compositor_create_region(window->display->compositor); + wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX); + wl_surface_set_opaque_region(window->surface, region); + wl_region_destroy(region); + } else { + wl_surface_set_opaque_region(window->surface, NULL); + } + + eglSwapBuffers(display->egl.dpy, window->egl_surface); +} + +static void +set_tearing(struct window *window, bool enable) +{ + if (!window->tear_control) + return; + + if (enable) { + wp_tearing_control_v1_set_presentation_hint(window->tear_control, + WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC); + } else { + wp_tearing_control_v1_set_presentation_hint(window->tear_control, + WP_TEARING_CONTROL_V1_PRESENTATION_HINT_VSYNC); + } + window->tear_enabled = enable; +} + +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 fractional_scale_handle_preferred_scale(void *data, + struct wp_fractional_scale_v1 *info, + uint32_t wire_scale) { + struct window *window = data; + + window->fractional_buffer_scale = wire_scale / 120.0; + window->needs_buffer_geometry_update = true; +} + +static const struct wp_fractional_scale_v1_listener fractional_scale_listener = { + .preferred_scale = fractional_scale_handle_preferred_scale, +}; + 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); + + if (display->tearing_manager && window->tearing) { + window->tear_control = wp_tearing_control_manager_v1_get_tearing_control( + display->tearing_manager, + window->surface); + set_tearing(window, true); + } window->xdg_surface = xdg_wm_base_get_xdg_surface(display->wm_base, window->surface); @@ -397,11 +770,19 @@ create_surface(struct window *window) else if (window->maximized) xdg_toplevel_set_maximized(window->xdg_toplevel); + if (display->viewporter && display->fractional_scale_manager) { + window->viewport = wp_viewporter_get_viewport(display->viewporter, + window->surface); + window->fractional_scale_obj = + wp_fractional_scale_manager_v1_get_fractional_scale(display->fractional_scale_manager, + window->surface); + wp_fractional_scale_v1_add_listener(window->fractional_scale_obj, + &fractional_scale_listener, + window); + } + window->wait_for_configure = true; wl_surface_commit(window->surface); - - if (!window->frame_sync) - eglSwapInterval(display->egl.dpy, 0); } static void @@ -420,50 +801,33 @@ destroy_surface(struct window *window) xdg_toplevel_destroy(window->xdg_toplevel); if (window->xdg_surface) xdg_surface_destroy(window->xdg_surface); + if (window->viewport) + wp_viewport_destroy(window->viewport); + if (window->fractional_scale_obj) + wp_fractional_scale_v1_destroy(window->fractional_scale_obj); 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 }, - { 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 } - }; + 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, @@ -471,22 +835,60 @@ redraw(void *data, struct wl_callback *callback, uint32_t time) (float) window->frames / benchmark_interval); window->benchmark_time = time; window->frames = 0; + if (window->toggled_tearing) + set_tearing(window, window->tear_enabled ^ true); } - 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); + if (window->vertical_bar) { + angle = 0; + } else { + 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); @@ -494,40 +896,11 @@ redraw(void *data, struct wl_callback *callback, uint32_t time) glClearColor(0.0, 0.0, 0.0, 0.5); glClear(GL_COLOR_BUFFER_BIT); - glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts); - glVertexAttribPointer(window->gl.col, 3, GL_FLOAT, GL_FALSE, 0, colors); - glEnableVertexAttribArray(window->gl.pos); - glEnableVertexAttribArray(window->gl.col); - - glDrawArrays(GL_TRIANGLES, 0, 3); - - glDisableVertexAttribArray(window->gl.pos); - glDisableVertexAttribArray(window->gl.col); - - usleep(window->delay); - - 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_surface_set_opaque_region(window->surface, region); - wl_region_destroy(region); - } else { - wl_surface_set_opaque_region(window->surface, NULL); - } + if (window->vertical_bar) + draw_bar(window, buffer_age, &rotation); + else + draw_triangle(window, buffer_age); - 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; - display->swap_buffers_with_damage(display->egl.dpy, - window->egl_surface, - rect, 1); - } else { - eglSwapBuffers(display->egl.dpy, window->egl_surface); - } window->frames++; } @@ -745,6 +1118,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 +1237,21 @@ 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); + } else if (strcmp(interface, "wp_tearing_control_manager_v1") == 0) { + d->tearing_manager = wl_registry_bind(registry, name, + &wp_tearing_control_manager_v1_interface, + 1); + } else if (strcmp(interface, wp_viewporter_interface.name) == 0) { + d->viewporter = wl_registry_bind(registry, name, + &wp_viewporter_interface, + 1); + } else if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) { + d->fractional_scale_manager = + wl_registry_bind(registry, name, + &wp_fractional_scale_manager_v1_interface, + 1); } } @@ -785,6 +1259,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 = { @@ -804,10 +1287,14 @@ usage(int error_code) fprintf(stderr, "Usage: simple-egl [OPTIONS]\n\n" " -d \tBuffer swap delay in microseconds\n" " -f\tRun in fullscreen mode\n" + " -r\tUse fixed width/height ratio when run in fullscreen mode\n" " -m\tRun in maximized mode\n" " -o\tCreate an opaque surface\n" " -s\tUse a 16 bpp EGL config\n" " -b\tDon't sync to compositor redraw (eglSwapInterval 0)\n" + " -t\tEnable tearing via the tearing_control protocol\n" + " -T\tEnable and disable tearing every 5 seconds\n" + " -v\tDraw a moving vertical bar instead of a triangle\n" " -h\tThis help text\n\n"); exit(error_code); @@ -823,26 +1310,42 @@ 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; + window.fullscreen_ratio = false; + + 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]); else if (strcmp("-f", argv[i]) == 0) window.fullscreen = 1; + else if (strcmp("-r", argv[i]) == 0) + window.fullscreen_ratio = true; else if (strcmp("-m", argv[i]) == 0) window.maximized = 1; 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("-t", argv[i]) == 0) { + window.tearing = true; + } else if (strcmp("-T", argv[i]) == 0) { + window.tearing = true; + window.toggled_tearing = true; + } else if (strcmp("-v", argv[i]) == 0) + window.vertical_bar = true; else if (strcmp("-h", argv[i]) == 0) usage(EXIT_SUCCESS); else @@ -887,7 +1390,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 +1400,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); @@ -921,6 +1426,12 @@ main(int argc, char **argv) if (display.compositor) wl_compositor_destroy(display.compositor); + if (display.viewporter) + wp_viewporter_destroy(display.viewporter); + + if (display.fractional_scale_manager) + wp_fractional_scale_manager_v1_destroy(display.fractional_scale_manager); + wl_registry_destroy(display.registry); wl_display_flush(display.display); wl_display_disconnect(display.display); diff --git a/clients/simple-shm.c b/clients/simple-shm.c index 1c2bea9ee..6bcb45adf 100644 --- a/clients/simple-shm.c +++ b/clients/simple-shm.c @@ -35,38 +35,52 @@ #include #include +#include + #include #include "shared/os-compatibility.h" #include #include "xdg-shell-client-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" +#define MAX_BUFFER_ALLOC 2 + struct display { struct wl_display *display; struct wl_registry *registry; struct wl_compositor *compositor; struct xdg_wm_base *wm_base; struct zwp_fullscreen_shell_v1 *fshell; + struct wl_seat *seat; + struct wl_keyboard *keyboard; struct wl_shm *shm; bool has_xrgb; }; struct buffer { + struct window *window; struct wl_buffer *buffer; void *shm_data; int busy; + int width, height; + size_t size; /* width * 4 * height */ + struct wl_list buffer_link; /** window::buffer_list */ }; struct window { struct display *display; int width, height; + int init_width, init_height; struct wl_surface *surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; - struct buffer buffers[2]; + struct wl_list buffer_list; struct buffer *prev_buffer; struct wl_callback *callback; bool wait_for_configure; + bool maximized; + bool fullscreen; + bool needs_update_buffer; }; static int running = 1; @@ -74,6 +88,58 @@ static int running = 1; static void redraw(void *data, struct wl_callback *callback, uint32_t time); +static struct buffer * +alloc_buffer(struct window *window, int width, int height) +{ + struct buffer *buffer = calloc(1, sizeof(*buffer)); + + buffer->width = width; + buffer->height = height; + wl_list_insert(&window->buffer_list, &buffer->buffer_link); + + return buffer; +} + +static void +destroy_buffer(struct buffer *buffer) +{ + if (buffer->buffer) + wl_buffer_destroy(buffer->buffer); + + munmap(buffer->shm_data, buffer->size); + wl_list_remove(&buffer->buffer_link); + free(buffer); +} + +static struct buffer * +pick_free_buffer(struct window *window) +{ + struct buffer *b; + struct buffer *buffer = NULL; + + wl_list_for_each(b, &window->buffer_list, buffer_link) { + if (!b->busy) { + buffer = b; + break; + } + } + + return buffer; +} + +static void +prune_old_released_buffers(struct window *window) +{ + struct buffer *b, *b_next; + + wl_list_for_each_safe(b, b_next, + &window->buffer_list, buffer_link) { + if (!b->busy && (b->width != window->width || + b->height != window->height)) + destroy_buffer(b); + } +} + static void buffer_release(void *data, struct wl_buffer *buffer) { @@ -87,15 +153,19 @@ static const struct wl_buffer_listener buffer_listener = { }; static int -create_shm_buffer(struct display *display, struct buffer *buffer, - int width, int height, uint32_t format) +create_shm_buffer(struct window *window, struct buffer *buffer, uint32_t format) { struct wl_shm_pool *pool; int fd, size, stride; void *data; + int width, height; + struct display *display; + width = window->width; + height = window->height; stride = width * 4; size = stride * height; + display = window->display; fd = os_create_anonymous_file(size); if (fd < 0) { @@ -119,11 +189,77 @@ create_shm_buffer(struct display *display, struct buffer *buffer, wl_shm_pool_destroy(pool); close(fd); + buffer->size = size; buffer->shm_data = data; return 0; } +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) +{ + if (key == KEY_ESC && state) + running = 0; +} + +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 const struct wl_keyboard_listener keyboard_listener = { + keyboard_handle_keymap, + keyboard_handle_enter, + keyboard_handle_leave, + keyboard_handle_key, + keyboard_handle_modifiers, +}; + +static void +seat_handle_capabilities(void *data, struct wl_seat *seat, + enum wl_seat_capability caps) +{ + struct display *d = data; + + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !d->keyboard) { + d->keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(d->keyboard, &keyboard_listener, d); + } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && d->keyboard) { + wl_keyboard_destroy(d->keyboard); + d->keyboard = NULL; + } +} + +static const struct wl_seat_listener seat_listener = { + seat_handle_capabilities, +}; + static void handle_xdg_surface_configure(void *data, struct xdg_surface *surface, uint32_t serial) @@ -145,8 +281,39 @@ static const struct xdg_surface_listener xdg_surface_listener = { static void handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, - struct wl_array *state) + struct wl_array *states) { + struct window *window = data; + uint32_t *p; + + window->fullscreen = false; + window->maximized = false; + + wl_array_for_each(p, states) { + uint32_t state = *p; + switch (state) { + case XDG_TOPLEVEL_STATE_FULLSCREEN: + window->fullscreen = true; + break; + case XDG_TOPLEVEL_STATE_MAXIMIZED: + window->maximized = true; + break; + } + } + + if (width > 0 && height > 0) { + if (!window->fullscreen && !window->maximized) { + window->init_width = width; + window->init_height = height; + } + window->width = width; + window->height = height; + } else if (!window->fullscreen && !window->maximized) { + window->width = window->init_width; + window->height = window->init_height; + } + + window->needs_update_buffer = true; } static void @@ -164,6 +331,7 @@ static struct window * create_window(struct display *display, int width, int height) { struct window *window; + int i; window = zalloc(sizeof *window); if (!window) @@ -173,7 +341,11 @@ create_window(struct display *display, int width, int height) window->display = display; window->width = width; window->height = height; + window->init_width = width; + window->init_height = height; window->surface = wl_compositor_create_surface(display->compositor); + window->needs_update_buffer = false; + wl_list_init(&window->buffer_list); if (display->wm_base) { window->xdg_surface = @@ -204,19 +376,23 @@ create_window(struct display *display, int width, int height) assert(0); } + for (i = 0; i < MAX_BUFFER_ALLOC; i++) + alloc_buffer(window, window->width, window->height); + return window; } static void destroy_window(struct window *window) { + struct buffer *buffer, *buffer_next; + if (window->callback) wl_callback_destroy(window->callback); - if (window->buffers[0].buffer) - wl_buffer_destroy(window->buffers[0].buffer); - if (window->buffers[1].buffer) - wl_buffer_destroy(window->buffers[1].buffer); + wl_list_for_each_safe(buffer, buffer_next, + &window->buffer_list, buffer_link) + destroy_buffer(buffer); if (window->xdg_toplevel) xdg_toplevel_destroy(window->xdg_toplevel); @@ -229,20 +405,25 @@ destroy_window(struct window *window) static struct buffer * window_next_buffer(struct window *window) { - struct buffer *buffer; + struct buffer *buffer = NULL; int ret = 0; - if (!window->buffers[0].busy) - buffer = &window->buffers[0]; - else if (!window->buffers[1].busy) - buffer = &window->buffers[1]; - else + if (window->needs_update_buffer) { + int i; + + for (i = 0; i < MAX_BUFFER_ALLOC; i++) + alloc_buffer(window, window->width, window->height); + + window->needs_update_buffer = false; + } + + buffer = pick_free_buffer(window); + + if (!buffer) return NULL; if (!buffer->buffer) { - ret = create_shm_buffer(window->display, buffer, - window->width, window->height, - WL_SHM_FORMAT_XRGB8888); + ret = create_shm_buffer(window, buffer, WL_SHM_FORMAT_XRGB8888); if (ret < 0) return NULL; @@ -309,6 +490,8 @@ redraw(void *data, struct wl_callback *callback, uint32_t time) struct window *window = data; struct buffer *buffer; + prune_old_released_buffers(window); + buffer = window_next_buffer(window); if (!buffer) { fprintf(stderr, @@ -373,6 +556,10 @@ registry_handle_global(void *data, struct wl_registry *registry, d->wm_base = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1); xdg_wm_base_add_listener(d->wm_base, &xdg_wm_base_listener, d); + } else if (strcmp(interface, "wl_seat") == 0) { + d->seat = wl_registry_bind(registry, id, + &wl_seat_interface, 1); + wl_seat_add_listener(d->seat, &seat_listener, d); } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { d->fshell = wl_registry_bind(registry, id, &zwp_fullscreen_shell_v1_interface, 1); diff --git a/clients/simple-touch.c b/clients/simple-touch.c index 7c9ada646..e32013161 100644 --- a/clients/simple-touch.c +++ b/clients/simple-touch.c @@ -64,9 +64,13 @@ struct touch { struct xdg_toplevel *xdg_toplevel; struct buffer *buffer; int width, height; + int init_width, init_height; bool running; bool wait_for_configure; + bool needs_buffer_update; bool has_argb; + bool maximized; + bool fullscreen; }; static struct buffer * @@ -75,11 +79,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; @@ -113,7 +115,7 @@ create_shm_buffer(struct touch *touch) } static void -initial_redraw(void *data) +redraw(void *data) { struct touch *touch = data; struct buffer *buffer = NULL; @@ -121,6 +123,9 @@ initial_redraw(void *data) buffer = create_shm_buffer(touch); assert(buffer); + if (touch->buffer) + free(touch->buffer); + touch->buffer = buffer; /* paint the "work-area" */ @@ -285,9 +290,10 @@ handle_xdg_surface_configure(void *data, struct xdg_surface *surface, xdg_surface_ack_configure(surface, serial); - if (touch->wait_for_configure) { - initial_redraw(touch); + if (touch->wait_for_configure || touch->needs_buffer_update) { + redraw(touch); touch->wait_for_configure = false; + touch->needs_buffer_update = false; } } @@ -342,9 +348,40 @@ static const struct wl_registry_listener registry_listener = { static void handle_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, - int32_t width, int32_t height, - struct wl_array *state) + int32_t width, int32_t height, struct wl_array *states) { + struct touch *touch = data; + uint32_t *p; + + touch->fullscreen = false; + touch->maximized = false; + + wl_array_for_each(p, states) { + uint32_t state = *p; + switch (state) { + case XDG_TOPLEVEL_STATE_FULLSCREEN: + touch->fullscreen = true; + break; + case XDG_TOPLEVEL_STATE_MAXIMIZED: + touch->maximized = true; + break; + } + } + + if (width > 0 && height > 0) { + if (!touch->fullscreen && !touch->maximized) { + touch->init_width = width; + touch->init_width = height; + } + touch->width = width; + touch->height = height; + } else if (!touch->fullscreen && !touch->maximized) { + touch->width = touch->init_width; + touch->height = touch->init_height; + + } + + touch->needs_buffer_update = true; } static void @@ -373,6 +410,7 @@ touch_create(int width, int height) assert(touch->display); touch->has_argb = false; + touch->buffer = NULL; touch->registry = wl_display_get_registry(touch->display); wl_registry_add_listener(touch->registry, ®istry_listener, touch); wl_display_dispatch(touch->display); @@ -388,8 +426,8 @@ touch_create(int width, int height) exit(1); } - touch->width = width; - touch->height = height; + touch->init_width = width; + touch->init_height = height; touch->surface = wl_compositor_create_surface(touch->compositor); touch->xdg_surface = @@ -405,6 +443,7 @@ touch_create(int width, int height) xdg_toplevel_set_title(touch->xdg_toplevel, "simple-touch"); xdg_toplevel_set_app_id(touch->xdg_toplevel, "simple-touch"); touch->wait_for_configure = true; + touch->needs_buffer_update = false; wl_surface_commit(touch->surface); touch->running = true; diff --git a/clients/tablet.c b/clients/tablet.c new file mode 100644 index 000000000..112fc4b5c --- /dev/null +++ b/clients/tablet.c @@ -0,0 +1,254 @@ +/* + * Copyright © 2014 Lyude + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE + */ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "window.h" +#include "tablet-unstable-v2-client-protocol.h" + +struct display *display; +struct window *window; +struct widget *widget; + +cairo_surface_t *draw_buffer; + +int old_x, old_y; +int current_x, current_y; +enum zwp_tablet_tool_v2_type tool_type; + +bool tablet_is_down; + +double current_pressure; + +#define WL_TABLET_AXIS_MAX 65535 + +static void +redraw_handler(struct widget *widget, void *data) +{ + cairo_surface_t *surface; + cairo_t *window_cr, *drawing_cr; + struct rectangle allocation; + + widget_get_allocation(widget, &allocation); + + surface = window_get_surface(window); + + /* Setup the background */ + window_cr = cairo_create(surface); + cairo_set_operator(window_cr, CAIRO_OPERATOR_SOURCE); + cairo_rectangle(window_cr, + allocation.x, + allocation.y, + allocation.width, + allocation.height); + cairo_set_source_rgba(window_cr, 0, 0, 0, 0.8); + cairo_fill(window_cr); + + /* Update the drawing buffer */ + if (tablet_is_down) { + if (old_x != -1 && old_y != -1) { + drawing_cr = cairo_create(draw_buffer); + if (tool_type == ZWP_TABLET_TOOL_V2_TYPE_PEN) { + cairo_set_source_rgb(drawing_cr, 1, 1, 1); + cairo_set_line_width(drawing_cr, + current_pressure / + WL_TABLET_AXIS_MAX * 7 + 1); + } else if (tool_type == ZWP_TABLET_TOOL_V2_TYPE_ERASER) { + cairo_set_operator(drawing_cr, CAIRO_OPERATOR_CLEAR); + cairo_set_source_rgb(drawing_cr, 0, 0, 0); + cairo_set_line_width(drawing_cr, + current_pressure / + WL_TABLET_AXIS_MAX * 30 + 10); + } + + cairo_set_line_cap(drawing_cr, CAIRO_LINE_CAP_ROUND); + + cairo_translate(drawing_cr, + -allocation.x, + -allocation.y); + cairo_move_to(drawing_cr, old_x, old_y); + cairo_line_to(drawing_cr, current_x, current_y); + cairo_stroke(drawing_cr); + + cairo_destroy(drawing_cr); + } + + old_x = current_x; + old_y = current_y; + } + + /* Squash the drawing buffer onto the window's buffer */ + cairo_set_source_surface(window_cr, + draw_buffer, + allocation.x, + allocation.y); + cairo_set_operator(window_cr, CAIRO_OPERATOR_ADD); + cairo_rectangle(window_cr, + allocation.x, + allocation.y, + allocation.width, + allocation.height); + cairo_clip(window_cr); + cairo_paint(window_cr); + + cairo_destroy(window_cr); + + cairo_surface_destroy(surface); +} + +static void +resize_handler(struct widget *widget, + int32_t width, int32_t height, + void *data) +{ + cairo_surface_t *tmp_buffer; + cairo_t *cr; + + tmp_buffer = draw_buffer; + draw_buffer = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + width, height); + cr = cairo_create(draw_buffer); + cairo_set_source_rgba(cr, 0, 0, 0, 0); + cairo_rectangle(cr, 0, 0, width, height); + cairo_fill(cr); + + if (tmp_buffer) { + cairo_set_source_surface(cr, tmp_buffer, 0, 0); + cairo_rectangle(cr, 0, 0, width, height); + cairo_clip(cr); + cairo_paint(cr); + } + + cairo_destroy(cr); + + cairo_surface_destroy(tmp_buffer); +} + +static void +proximity_in_handler(struct widget *widget, struct tablet_tool *tool, + struct tablet *tablet, void *data) +{ + tool_type = tablet_tool_get_type(tool); +} + +static void +pressure_handler(struct widget *widget, struct tablet_tool *tool, + uint32_t pressure, void *data) +{ + current_pressure = pressure; +} + +static int +tablet_motion_handler(struct widget *widget, struct tablet_tool *tool, + float x, float y, void *data) +{ + int cursor; + + current_x = x; + current_y = y; + + if (tablet_is_down) { + widget_schedule_redraw(widget); + cursor = CURSOR_HAND1; + } else { + cursor = CURSOR_LEFT_PTR; + } + + return cursor; +} + +static void +tablet_down_handler(struct widget *widget, struct tablet_tool *tool, void *data) +{ + tablet_is_down = true; +} + +static void +tablet_up_handler(struct widget *widget, struct tablet_tool *tool, void *data) +{ + tablet_is_down = false; + old_x = -1; + old_y = -1; +} + +static void +init_globals(void) +{ + window = window_create(display); + widget = window_frame_create(window, NULL); + window_set_title(window, "Wayland Tablet Demo"); + old_x = -1; + old_y = -1; + + widget_set_tablet_tool_axis_handlers(widget, + tablet_motion_handler, + pressure_handler, + NULL, NULL, + NULL, NULL, NULL); + widget_set_tablet_tool_down_handler(widget, tablet_down_handler); + widget_set_tablet_tool_up_handler(widget, tablet_up_handler); + widget_set_tablet_tool_proximity_handlers(widget, + proximity_in_handler, + NULL); + widget_set_redraw_handler(widget, redraw_handler); + widget_set_resize_handler(widget, resize_handler); + + widget_schedule_resize(widget, 1000, 800); +} + +static void +cleanup(void) +{ + widget_destroy(widget); + window_destroy(window); +} + +int +main(int argc, char *argv[]) +{ + display = display_create(&argc, argv); + if (display == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + init_globals(); + + display_run(display); + + cleanup(); + + display_destroy(display); + + return 0; +} diff --git a/clients/terminal.c b/clients/terminal.c index b7537aadc..545ddd4f5 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/window.c b/clients/window.c index a0d988f45..30f64109e 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 @@ -78,8 +59,10 @@ typedef void *EGLContext; #include "text-cursor-position-client-protocol.h" #include "pointer-constraints-unstable-v1-client-protocol.h" #include "relative-pointer-unstable-v1-client-protocol.h" +#include "tablet-unstable-v2-client-protocol.h" #include "shared/os-compatibility.h" #include "shared/string-helpers.h" +#include "libweston/matrix.h" #include "window.h" #include "viewporter-client-protocol.h" @@ -107,12 +90,9 @@ struct display { struct wl_data_device_manager *data_device_manager; struct text_cursor_position *text_cursor_position; struct xdg_wm_base *xdg_shell; + struct zwp_tablet_manager_v2 *tablet_manager; 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; @@ -150,6 +130,39 @@ struct display { struct wp_viewporter *viewporter; }; +struct tablet { + struct zwp_tablet_v2 *tablet; + char *name; + int32_t vid; + int32_t pid; + + void *user_data; + + struct wl_list link; +}; + +struct tablet_tool { + struct zwp_tablet_tool_v2 *tool; + struct input *input; + void *user_data; + struct wl_list link; + struct tablet *current_tablet; + struct window *focus; + struct widget *focus_widget; + uint32_t enter_serial; + uint32_t cursor_serial; + int current_cursor; + struct wl_surface *cursor_surface; + uint32_t cursor_anim_start; + struct wl_callback *cursor_frame_cb; + + enum zwp_tablet_tool_v2_type type; + uint64_t serial; + uint64_t hwid; + + double sx, sy; +}; + struct window_output { struct output *output; struct wl_list link; @@ -178,18 +191,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 +241,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; @@ -312,10 +314,24 @@ struct widget { widget_axis_source_handler_t axis_source_handler; widget_axis_stop_handler_t axis_stop_handler; widget_axis_discrete_handler_t axis_discrete_handler; + widget_tablet_tool_motion_handler_t tablet_tool_motion_handler; + widget_tablet_tool_up_handler_t tablet_tool_up_handler; + widget_tablet_tool_down_handler_t tablet_tool_down_handler; + widget_tablet_tool_pressure_handler_t tablet_tool_pressure_handler; + widget_tablet_tool_distance_handler_t tablet_tool_distance_handler; + widget_tablet_tool_tilt_handler_t tablet_tool_tilt_handler; + widget_tablet_tool_rotation_handler_t tablet_tool_rotation_handler; + widget_tablet_tool_slider_handler_t tablet_tool_slider_handler; + widget_tablet_tool_wheel_handler_t tablet_tool_wheel_handler; + widget_tablet_tool_proximity_in_handler_t tablet_tool_prox_in_handler; + widget_tablet_tool_proximity_out_handler_t tablet_tool_prox_out_handler; + widget_tablet_tool_button_handler_t tablet_tool_button_handler; + widget_tablet_tool_frame_handler_t tablet_tool_frame_handler; void *user_data; int opaque; int tooltip_count; int default_cursor; + int default_tablet_cursor; /* If this is set to false then no cairo surface will be * created before redrawing the surface. This is useful if the * redraw handler is going to do completely custom rendering @@ -352,8 +368,11 @@ struct input { struct toytimer cursor_timer; bool cursor_timer_running; struct wl_surface *pointer_surface; + bool pointer_surface_has_role; + int hotspot_x, hotspot_y; uint32_t modifiers; uint32_t pointer_enter_serial; + uint32_t cursor_serial; float sx, sy; struct wl_list link; @@ -392,6 +411,10 @@ struct input { uint32_t repeat_key; uint32_t repeat_time; int seat_version; + + struct zwp_tablet_seat_v2 *tablet_seat; + struct wl_list tablet_list; + struct wl_list tablet_tool_list; }; struct output { @@ -542,167 +565,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; @@ -1156,17 +1018,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) { @@ -1189,8 +1040,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; @@ -1429,13 +1278,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 * @@ -1450,15 +1309,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, @@ -1681,6 +1531,7 @@ widget_create(struct window *window, struct surface *surface, void *data) widget->tooltip = NULL; widget->tooltip_count = 0; widget->default_cursor = CURSOR_LEFT_PTR; + widget->default_tablet_cursor = CURSOR_LEFT_PTR; widget->use_cairo = 1; widget->viewport_dest_width = -1; widget->viewport_dest_height = -1; @@ -1740,6 +1591,12 @@ widget_set_default_cursor(struct widget *widget, int cursor) widget->default_cursor = cursor; } +void +widget_set_default_tablet_cursor(struct widget *widget, int cursor) +{ + widget->default_tablet_cursor = cursor; +} + void widget_get_allocation(struct widget *widget, struct rectangle *allocation) { @@ -1796,78 +1653,16 @@ static void widget_cairo_update_transform(struct widget *widget, cairo_t *cr) { struct surface *surface = widget->surface; - double angle; + struct weston_matrix matrix; cairo_matrix_t m; - enum wl_output_transform transform; - int surface_width, surface_height; - int translate_x, translate_y; - int32_t scale; - - surface_width = surface->allocation.width; - surface_height = surface->allocation.height; - - transform = surface->buffer_transform; - scale = surface->buffer_scale; - - switch (transform) { - case WL_OUTPUT_TRANSFORM_FLIPPED: - case WL_OUTPUT_TRANSFORM_FLIPPED_90: - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - cairo_matrix_init(&m, -1, 0, 0, 1, 0, 0); - break; - default: - cairo_matrix_init_identity(&m); - break; - } - - switch (transform) { - case WL_OUTPUT_TRANSFORM_NORMAL: - default: - angle = 0; - translate_x = 0; - translate_y = 0; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED: - angle = 0; - translate_x = surface_width; - translate_y = 0; - break; - case WL_OUTPUT_TRANSFORM_90: - angle = M_PI + M_PI_2; - translate_x = 0; - translate_y = surface_width; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_90: - angle = M_PI + M_PI_2; - translate_x = 0; - translate_y = 0; - break; - case WL_OUTPUT_TRANSFORM_180: - angle = M_PI; - translate_x = surface_width; - translate_y = surface_height; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - angle = M_PI; - translate_x = 0; - translate_y = surface_height; - break; - case WL_OUTPUT_TRANSFORM_270: - angle = M_PI_2; - translate_x = surface_height; - translate_y = 0; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - angle = M_PI_2; - translate_x = surface_height; - translate_y = surface_width; - break; - } - cairo_scale(cr, scale, scale); - cairo_translate(cr, translate_x, translate_y); - cairo_rotate(cr, angle); + weston_matrix_init_transform(&matrix, surface->buffer_transform, + 0, 0, + surface->allocation.width, + surface->allocation.height, + surface->buffer_scale); + cairo_matrix_init(&m, matrix.d[0], matrix.d[1], matrix.d[4], + matrix.d[5], matrix.d[12], matrix.d[13]); cairo_transform(cr, &m); } @@ -2023,6 +1818,62 @@ widget_set_axis_handlers(struct widget *widget, widget->axis_discrete_handler = axis_discrete_handler; } +void +widget_set_tablet_tool_axis_handlers(struct widget *widget, + widget_tablet_tool_motion_handler_t motion, + widget_tablet_tool_pressure_handler_t pressure, + widget_tablet_tool_distance_handler_t distance, + widget_tablet_tool_tilt_handler_t tilt, + widget_tablet_tool_rotation_handler_t rotation, + widget_tablet_tool_slider_handler_t slider, + widget_tablet_tool_wheel_handler_t wheel) +{ + widget->tablet_tool_motion_handler = motion; + widget->tablet_tool_pressure_handler = pressure; + widget->tablet_tool_distance_handler = distance; + widget->tablet_tool_tilt_handler = tilt; + widget->tablet_tool_rotation_handler = rotation; + widget->tablet_tool_slider_handler = slider; + widget->tablet_tool_wheel_handler = wheel; +} + +void +widget_set_tablet_tool_up_handler(struct widget *widget, + widget_tablet_tool_up_handler_t handler) +{ + widget->tablet_tool_up_handler = handler; +} + +void +widget_set_tablet_tool_down_handler(struct widget *widget, + widget_tablet_tool_down_handler_t handler) +{ + widget->tablet_tool_down_handler = handler; +} + +void +widget_set_tablet_tool_proximity_handlers(struct widget *widget, + widget_tablet_tool_proximity_in_handler_t in_handler, + widget_tablet_tool_proximity_out_handler_t out_handler) +{ + widget->tablet_tool_prox_in_handler = in_handler; + widget->tablet_tool_prox_out_handler = out_handler; +} + +void +widget_set_tablet_tool_button_handler(struct widget *widget, + widget_tablet_tool_button_handler_t handler) +{ + widget->tablet_tool_button_handler = handler; +} + +void +widget_set_tablet_tool_frame_handler(struct widget *widget, + widget_tablet_tool_frame_handler_t handler) +{ + widget->tablet_tool_frame_handler = handler; +} + static void window_schedule_redraw_task(struct window *window); @@ -2564,6 +2415,54 @@ frame_touch_up_handler(struct widget *widget, frame_handle_status(frame, input, time, THEME_LOCATION_CLIENT_AREA); } +static int +frame_tablet_tool_motion_handler(struct widget *widget, + struct tablet_tool *tool, + float x, float y, + void *data) +{ + struct window_frame *frame = data; + enum theme_location location; + + location = frame_tablet_tool_motion(frame->frame, tool, x, y); + if (frame_status(frame->frame) & FRAME_STATUS_REPAINT) + widget_schedule_redraw(frame->widget); + + frame_get_pointer_image_for_location(data, location); + + return CURSOR_LEFT_PTR; +} + +static void +frame_tablet_tool_down_handler(struct widget *widget, + struct tablet_tool *tool, + void *data) +{ + struct window_frame *frame = data; + enum theme_location location; + uint32_t time = 0; /* FIXME: we should be doing this in the frame + handler where we have the timestamp */ + + /* Map a stylus touch to the left mouse button */ + location = frame_pointer_button(frame->frame, tool, BTN_LEFT, 1); + frame_handle_status(frame, tool->input, time, location); +} + +static void +frame_tablet_tool_up_handler(struct widget *widget, struct tablet_tool *tool, + void *data) +{ + struct window_frame *frame = data; + enum theme_location location; + uint32_t time = 0; /* FIXME: we should be doing this in the frame + handler where we have the timestamp */ + + /* Map the stylus leaving contact with the tablet as releasing the left + * mouse button */ + location = frame_pointer_button(frame->frame, tool, BTN_LEFT, 0); + frame_handle_status(frame, tool->input, time, location); +} + struct widget * window_frame_create(struct window *window, void *data) { @@ -2595,6 +2494,12 @@ window_frame_create(struct window *window, void *data) widget_set_button_handler(frame->widget, frame_button_handler); widget_set_touch_down_handler(frame->widget, frame_touch_down_handler); widget_set_touch_up_handler(frame->widget, frame_touch_up_handler); + widget_set_tablet_tool_axis_handlers(frame->widget, + frame_tablet_tool_motion_handler, + NULL, NULL, NULL, + NULL, NULL, NULL); + widget_set_tablet_tool_down_handler(frame->widget, frame_tablet_tool_down_handler); + widget_set_tablet_tool_up_handler(frame->widget, frame_tablet_tool_up_handler); window->frame = frame; @@ -2748,12 +2653,6 @@ input_remove_pointer_focus(struct input *input) input->pointer_focus = NULL; input->current_cursor = CURSOR_UNSET; cancel_pointer_image_update(input); - wl_surface_destroy(input->pointer_surface); - input->pointer_surface = NULL; - if (input->cursor_frame_cb) { - wl_callback_destroy(input->cursor_frame_cb); - input->cursor_frame_cb = NULL; - } } static void @@ -2782,6 +2681,16 @@ pointer_handle_enter(void *data, struct wl_pointer *pointer, input->pointer_enter_serial = serial; input->pointer_focus = window; + /* Some compositors advertise wl_seat before wl_compositor. This + * makes it potentially impossible to create the pointer surface + * when we bind the seat, so we need to create our pointer surface + * now instead. + */ + if (!input->pointer_surface) + input->pointer_surface = wl_compositor_create_surface(input->display->compositor); + + input->pointer_surface_has_role = false; + input->sx = sx; input->sy = sy; @@ -3793,8 +3702,7 @@ input_set_pointer_image_index(struct input *input, int index) struct wl_buffer *buffer; struct wl_cursor *cursor; struct wl_cursor_image *image; - struct wl_surface *prev_surface; - struct display *d = input->display; + int dx = 0, dy = 0; if (!input->pointer) return; @@ -3813,21 +3721,24 @@ input_set_pointer_image_index(struct input *input, int index) if (!buffer) return; - /* Don't re-use the previous surface, otherwise the new buffer and the - * new hotspot aren't applied atomically. */ - prev_surface = input->pointer_surface; - input->pointer_surface = wl_compositor_create_surface(d->compositor); - - wl_surface_attach(input->pointer_surface, buffer, 0, 0); + if (input->pointer_surface_has_role) { + dx = input->hotspot_x - image->hotspot_x; + dy = input->hotspot_y - image->hotspot_y; + } + wl_surface_attach(input->pointer_surface, buffer, dx, dy); wl_surface_damage(input->pointer_surface, 0, 0, image->width, image->height); wl_surface_commit(input->pointer_surface); - wl_pointer_set_cursor(input->pointer, input->pointer_enter_serial, - input->pointer_surface, - image->hotspot_x, image->hotspot_y); - if (prev_surface) - wl_surface_destroy(prev_surface); + if (!input->pointer_surface_has_role) { + wl_pointer_set_cursor(input->pointer, + input->pointer_enter_serial, + input->pointer_surface, + image->hotspot_x, image->hotspot_y); + input->pointer_surface_has_role = true; + } + input->hotspot_x = image->hotspot_x; + input->hotspot_y = image->hotspot_y; } static const struct wl_callback_listener pointer_surface_listener; @@ -3839,11 +3750,14 @@ input_set_pointer_special(struct input *input) wl_pointer_set_cursor(input->pointer, input->pointer_enter_serial, NULL, 0, 0); + input->pointer_surface_has_role = false; return true; } - if (input->current_cursor == CURSOR_UNSET) + if (input->current_cursor == CURSOR_UNSET) { + input->pointer_surface_has_role = false; return true; + } return false; } @@ -3883,7 +3797,6 @@ schedule_pointer_image_update(struct input *input, wl_callback_add_listener(input->cursor_frame_cb, &pointer_surface_listener, input); - wl_surface_commit(input->pointer_surface); } static void @@ -3933,11 +3846,11 @@ pointer_surface_frame_callback(void *data, struct wl_callback *callback, time - input->cursor_anim_start, &duration); - input_set_pointer_image_index(input, i); - if (cursor->image_count > 1) schedule_pointer_image_update(input, cursor, duration, force_frame); + + input_set_pointer_image_index(input, i); } static void @@ -3967,15 +3880,30 @@ static const struct wl_callback_listener pointer_surface_listener = { void input_set_pointer_image(struct input *input, int pointer) { + int force = 0; + if (!input->pointer) return; - if (pointer == input->current_cursor) + if (input->pointer_enter_serial > input->cursor_serial) + force = 1; + + if (!force && pointer == input->current_cursor) return; input->current_cursor = pointer; + input->cursor_serial = input->pointer_enter_serial; if (!input->cursor_frame_cb) pointer_surface_frame_callback(input, NULL, 0); + else if (force && !input_set_pointer_special(input)) { + /* The current frame callback may be stuck if, for instance, + * the set cursor request was processed by the server after + * this client lost the focus. In this case the cursor surface + * might not be mapped and the frame callback wouldn't ever + * complete. Send a set_cursor and attach to try to map the + * cursor surface again so that the callback will finish */ + input_set_pointer_image_index(input, 0); + } } struct wl_data_device * @@ -4413,9 +4341,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 @@ -4870,7 +4813,7 @@ relative_pointer_handle_motion(void *data, struct zwp_relative_pointer_v1 *point { struct input *input = data; struct window *window = input->pointer_focus; - uint32_t ms = (((uint64_t) utime_hi) << 32 | utime_lo) / 1000; + uint32_t ms = u64_from_u32s(utime_hi, utime_lo) / 1000; if (window->locked_pointer_motion_handler && window->pointer_locked) { @@ -5246,11 +5189,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; } @@ -5293,14 +5231,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); @@ -5416,6 +5354,16 @@ menu_touch_up_handler(struct widget *widget, menu_destroy(menu); } +static void +menu_tablet_tool_up_handler(struct widget *widget, struct tablet_tool *tool, + void *data) +{ + struct menu *menu = data; + + input_ungrab(tool->input); + menu_destroy(menu); +} + static void menu_redraw_handler(struct widget *widget, void *data) { @@ -5505,7 +5453,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; @@ -5522,6 +5470,7 @@ create_menu(struct display *display, widget_set_motion_handler(menu->widget, menu_motion_handler); widget_set_button_handler(menu->widget, menu_button_handler); widget_set_touch_up_handler(menu->widget, menu_touch_up_handler); + widget_set_tablet_tool_up_handler(menu->widget, menu_tablet_tool_up_handler); input_grab(input, menu->widget, 0); frame_resize_inside(menu->frame, 200, count * 20); @@ -5539,7 +5488,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, @@ -5584,7 +5533,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); @@ -5597,7 +5546,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)); @@ -5911,6 +5860,9 @@ display_add_input(struct display *d, uint32_t id, int display_seat_version) wl_list_init(&input->touch_point_list); wl_list_insert(d->input_list.prev, &input->link); + wl_list_init(&input->tablet_list); + wl_list_init(&input->tablet_tool_list); + wl_seat_add_listener(input->seat, &seat_listener, input); wl_seat_set_user_data(input->seat, input); @@ -5923,6 +5875,8 @@ display_add_input(struct display *d, uint32_t id, int display_seat_version) input); } + input->pointer_surface_has_role = false; + toytimer_init(&input->cursor_timer, CLOCK_MONOTONIC, d, cursor_timer_func); @@ -6011,24 +5965,545 @@ static const struct xdg_wm_base_listener wm_base_listener = { }; static void -global_destroy(struct display *disp, struct global *g) +tablet_handle_name(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, const char *name) { - if (disp->global_handler_remove) { - disp->global_handler_remove(disp, g->name, g->interface, - g->version, disp->user_data); - } + struct tablet *tablet = data; - wl_list_remove(&g->link); - free(g->interface); - free(g); + tablet->name = xstrdup(name); } static void -registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, - const char *interface, uint32_t version) +tablet_handle_id(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, + uint32_t vid, uint32_t pid) { - struct display *d = data; - struct global *global; + struct tablet *tablet = data; + + tablet->vid = vid; + tablet->pid = pid; +} + +static void +tablet_handle_path(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, const char *path) +{ +} + +static void +tablet_handle_done(void *data, struct zwp_tablet_v2 *zwp_tablet_v2) +{ +} + +static void +tablet_handle_removed(void *data, struct zwp_tablet_v2 *zwp_tablet_v2) +{ + struct tablet *tablet = data; + + zwp_tablet_v2_destroy(zwp_tablet_v2); + + wl_list_remove(&tablet->link); + free(tablet->name); + free(tablet); +} + +static const struct zwp_tablet_v2_listener tablet_listener = { + tablet_handle_name, + tablet_handle_id, + tablet_handle_path, + tablet_handle_done, + tablet_handle_removed, +}; + +static void +tablet_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat1, + struct zwp_tablet_v2 *id) +{ + struct input *input = data; + struct tablet *tablet; + + tablet = zalloc(sizeof *tablet); + + zwp_tablet_v2_add_listener(id, &tablet_listener, tablet); + wl_list_insert(&input->tablet_list, &tablet->link); + zwp_tablet_v2_set_user_data(id, tablet); +} + +uint32_t +tablet_tool_get_type(struct tablet_tool *tool) +{ + return tool->type; +} + +uint64_t +tablet_tool_get_serial(struct tablet_tool *tool) +{ + return tool->serial; +} + +uint64_t +tablet_tool_get_hwid(struct tablet_tool *tool) +{ + return tool->hwid; +} + +static void +tablet_tool_handle_type(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t tool_type) +{ + struct tablet_tool *tool = data; + + tool->type = tool_type; +} + +static void +tablet_tool_handle_serialid(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t serial_msb, uint32_t serial_lsb) +{ + struct tablet_tool *tool = data; + + tool->serial = ((uint64_t)serial_msb << 32) | serial_lsb; +} + +static void +tablet_tool_handle_hardware_id_wacom(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t hwid_msb, uint32_t hwid_lsb) +{ + struct tablet_tool *tool = data; + + tool->serial = ((uint64_t)hwid_msb << 32) | hwid_lsb; +} + +static void +tablet_tool_handle_capability(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t capability) +{ +} + +static void +tablet_tool_handle_done(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) +{ +} + +static void +tablet_tool_handle_removed(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) +{ + struct tablet_tool *tool = data; + + zwp_tablet_tool_v2_destroy(zwp_tablet_tool_v2); + wl_list_remove(&tool->link); + free(tool); +} + +static const struct wl_callback_listener tablet_tool_cursor_surface_listener; + +static void +tablet_tool_set_cursor_image_index(struct tablet_tool *tool, int index) +{ + struct wl_buffer *buffer; + struct wl_cursor *cursor; + struct wl_cursor_image *image; + + cursor = tool->input->display->cursors[tool->current_cursor]; + if (index >= (int)cursor->image_count) { + fprintf(stderr, "cursor index out of range\n"); + return; + } + + image = cursor->images[index]; + buffer = wl_cursor_image_get_buffer(image); + if (!buffer) + return; + + wl_surface_attach(tool->cursor_surface, buffer, 0, 0); + wl_surface_damage(tool->cursor_surface, 0, 0, + image->width, image->height); + wl_surface_commit(tool->cursor_surface); + zwp_tablet_tool_v2_set_cursor(tool->tool, tool->enter_serial, + tool->cursor_surface, + image->hotspot_x, image->hotspot_y); +} + +static void +tablet_tool_surface_frame_callback(void *data, struct wl_callback *callback, + uint32_t time) +{ + struct tablet_tool *tool = data; + struct wl_cursor *cursor; + int i; + + if (callback) { + assert(callback == tool->cursor_frame_cb); + wl_callback_destroy(callback); + tool->cursor_frame_cb = NULL; + } + + if (tool->current_cursor == CURSOR_BLANK) { + zwp_tablet_tool_v2_set_cursor(tool->tool, tool->enter_serial, + NULL, 0, 0); + return; + } + + if (tool->current_cursor == CURSOR_UNSET) + return; + + cursor = tool->input->display->cursors[tool->current_cursor]; + if (!cursor) + return; + + /* FIXME We don't have the current time on the first call so we set + * the animation start to the time of the first frame callback. */ + if (time == 0) + tool->cursor_anim_start = 0; + else if (tool->cursor_anim_start == 0) + tool->cursor_anim_start = time; + + if (time == 0 || tool->cursor_anim_start == 0) + i = 0; + else + i = wl_cursor_frame(cursor, time - tool->cursor_anim_start); + + if (cursor->image_count > 1) { + tool->cursor_frame_cb = + wl_surface_frame(tool->cursor_surface); + wl_callback_add_listener(tool->cursor_frame_cb, + &tablet_tool_cursor_surface_listener, + tool); + } + + tablet_tool_set_cursor_image_index(tool, i); +} + +static const struct wl_callback_listener tablet_tool_cursor_surface_listener = { + tablet_tool_surface_frame_callback, +}; + +void +tablet_tool_set_cursor_image(struct tablet_tool *tool, int cursor) +{ + bool force = false; + + if (tool->enter_serial > tool->cursor_serial) + force = true; + + if (!force && cursor == tool->current_cursor) + return; + + tool->current_cursor = cursor; + tool->cursor_serial = tool->enter_serial; + + if (!tool->cursor_frame_cb) { + tablet_tool_surface_frame_callback(tool, NULL, 0); + } else if (force) { + /* The current frame callback may be stuck if, for instance, + * the set cursor request was processed by the server after + * this client lost the focus. In this case the cursor surface + * might not be mapped and the frame callback wouldn't ever + * complete. Send a set_cursor and attach to try to map the + * cursor surface again so that the callback will finish + */ + tablet_tool_set_cursor_image_index(tool, 0); + } +} + +static void +tablet_tool_set_focus_widget(struct tablet_tool *tool, struct window *window, + wl_fixed_t sx, wl_fixed_t sy) +{ + struct widget *widget, *old; + + if (tool->input->grab) + widget = tool->input->grab; + else + widget = window_find_widget(window, sx, sy); + + if (tool->focus_widget == widget) + return; + + old = tool->focus_widget; + if (old && old->tablet_tool_prox_out_handler) + old->tablet_tool_prox_out_handler(old, tool, + widget_get_user_data(old)); + + if (widget && widget->tablet_tool_prox_in_handler) + widget->tablet_tool_prox_in_handler(widget, tool, + tool->current_tablet, + widget_get_user_data(widget)); + + tool->focus_widget = widget; +} + +static void +tablet_tool_handle_proximity_in(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t serial, + struct zwp_tablet_v2 *zwp_tablet_v2, + struct wl_surface *surface) +{ + struct tablet_tool *tool = data; + struct tablet *tablet = zwp_tablet_v2_get_user_data(zwp_tablet_v2); + struct window *window; + + window = wl_surface_get_user_data(surface); + if (surface != window->main_surface->surface) + return; + + tool->focus = window; + tool->current_tablet = tablet; + tool->enter_serial = serial; + + tablet_tool_set_cursor_image(tool, window->main_surface->widget->default_tablet_cursor); +} + +static void +tablet_tool_handle_proximity_out(void *data, + struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) +{ + struct tablet_tool *tool = data; + + tool->focus = NULL; + tool->current_tablet = NULL; +} + +static void +tablet_tool_handle_down(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t serial) +{ + struct tablet_tool *tool = data; + struct widget *focus = tool->focus_widget; + + tool->input->display->serial = serial; + + if (focus && focus->tablet_tool_down_handler) + focus->tablet_tool_down_handler(focus, tool, focus->user_data); +} + +static void +tablet_tool_handle_up(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) +{ + struct tablet_tool *tool = data; + struct widget *focus = tool->focus_widget; + + if (focus && focus->tablet_tool_up_handler) + focus->tablet_tool_up_handler(focus, tool, focus->user_data); +} + +static void +tablet_tool_handle_motion(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + wl_fixed_t x, wl_fixed_t y) +{ + struct tablet_tool *tool = data; + double sx = wl_fixed_to_double(x); + double sy = wl_fixed_to_double(y); + struct window *window = tool->focus; + struct widget *widget; + int cursor; + + if (!window) + return; + + tool->sx = sx; + tool->sy = sy; + + if (sx > window->main_surface->allocation.width || + sy > window->main_surface->allocation.height) + return; + + tablet_tool_set_focus_widget(tool, window, sx, sy); + widget = tool->focus_widget; + if (widget && widget->tablet_tool_motion_handler) { + cursor = widget->tablet_tool_motion_handler(widget, tool, + sx, sy, + widget->user_data); + } else { + cursor = widget->default_tablet_cursor; + } + + tablet_tool_set_cursor_image(tool, cursor); +} + +static void +tablet_tool_handle_pressure(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t pressure) +{ + struct tablet_tool *tool = data; + struct widget *widget = tool->focus_widget; + + if (widget && widget->tablet_tool_pressure_handler) + widget->tablet_tool_pressure_handler(widget, tool, pressure, + widget->user_data); +} + +static void +tablet_tool_handle_distance(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t distance) +{ + struct tablet_tool *tool = data; + struct widget *widget = tool->focus_widget; + + if (widget && widget->tablet_tool_distance_handler) + widget->tablet_tool_distance_handler(widget, tool, + distance, + widget->user_data); +} + +static void +tablet_tool_handle_tilt(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + int32_t tilt_x, int32_t tilt_y) +{ + struct tablet_tool *tool = data; + struct widget *widget = tool->focus_widget; + + if (widget && widget->tablet_tool_tilt_handler) + widget->tablet_tool_tilt_handler(widget, tool, + tilt_x, tilt_y, + widget->user_data); +} + +static void +tablet_tool_handle_rotation(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + int32_t rotation) +{ + struct tablet_tool *tool = data; + struct widget *widget = tool->focus_widget; + + if (widget && widget->tablet_tool_rotation_handler) + widget->tablet_tool_rotation_handler(widget, tool, + rotation, + widget->user_data); +} + +static void +tablet_tool_handle_slider(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + int32_t slider) +{ + struct tablet_tool *tool = data; + struct widget *widget = tool->focus_widget; + + if (widget && widget->tablet_tool_slider_handler) + widget->tablet_tool_slider_handler(widget, tool, + slider, + widget->user_data); +} + +static void +tablet_tool_handle_wheel(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + wl_fixed_t degrees, int32_t clicks) +{ + struct tablet_tool *tool = data; + struct widget *widget = tool->focus_widget; + + if (widget && widget->tablet_tool_wheel_handler) + widget->tablet_tool_wheel_handler(widget, tool, + degrees, clicks, + widget->user_data); +} + +static void +tablet_tool_handle_button(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t serial, uint32_t button, uint32_t state) +{ + struct tablet_tool *tool = data; + struct widget *focus = tool->focus_widget; + + tool->input->display->serial = serial; + + if (focus && focus->tablet_tool_button_handler) + focus->tablet_tool_button_handler(focus, tool, button, state, + focus->user_data); +} + +static void +tablet_tool_handle_frame(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, + uint32_t time) +{ + struct tablet_tool *tool = data; + struct widget *widget = tool->focus_widget; + + if (widget && widget->tablet_tool_frame_handler) + widget->tablet_tool_frame_handler(widget, tool, time, + widget->user_data); +} + +static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = { + tablet_tool_handle_type, + tablet_tool_handle_serialid, + tablet_tool_handle_hardware_id_wacom, + tablet_tool_handle_capability, + tablet_tool_handle_done, + tablet_tool_handle_removed, + tablet_tool_handle_proximity_in, + tablet_tool_handle_proximity_out, + tablet_tool_handle_down, + tablet_tool_handle_up, + tablet_tool_handle_motion, + tablet_tool_handle_pressure, + tablet_tool_handle_distance, + tablet_tool_handle_tilt, + tablet_tool_handle_rotation, + tablet_tool_handle_slider, + tablet_tool_handle_wheel, + tablet_tool_handle_button, + tablet_tool_handle_frame, +}; + +static void +tablet_tool_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat1, + struct zwp_tablet_tool_v2 *id) +{ + struct input *input = data; + struct tablet_tool *tool; + + tool = zalloc(sizeof *tool); + zwp_tablet_tool_v2_add_listener(id, &tablet_tool_listener, tool); + wl_list_insert(&input->tablet_tool_list, &tool->link); + + tool->tool = id; + tool->input = input; + tool->cursor_surface = + wl_compositor_create_surface(input->display->compositor); +} + +static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = { + tablet_added, + tablet_tool_added, +}; + +static void +display_bind_tablets(struct display *d, uint32_t id) +{ + struct input *input; + + d->tablet_manager = wl_registry_bind(d->registry, id, + &zwp_tablet_manager_v2_interface, 1); + + wl_list_for_each(input, &d->input_list, link) { + input->tablet_seat = + zwp_tablet_manager_v2_get_tablet_seat(d->tablet_manager, + input->seat); + zwp_tablet_seat_v2_add_listener(input->tablet_seat, + &tablet_seat_listener, + input); + } +} + +static void +global_destroy(struct display *disp, struct global *g) +{ + if (disp->global_handler_remove) { + disp->global_handler_remove(disp, g->name, g->interface, + g->version, disp->user_data); + } + + wl_list_remove(&g->link); + free(g->interface); + free(g); +} + +static void +registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, + const char *interface, uint32_t version) +{ + struct display *d = data; + struct global *global; global = xmalloc(sizeof *global); global->name = id; @@ -6061,7 +6536,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 = @@ -6075,6 +6551,8 @@ registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, d->viewporter = wl_registry_bind(registry, id, &wp_viewporter_interface, 1); + } else if (strcmp(interface, "zwp_tablet_manager_v2") == 0) { + display_bind_tablets(d, id); } if (d->global_handler) @@ -6112,90 +6590,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) { @@ -6301,12 +6695,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(); @@ -6365,10 +6753,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); @@ -6434,12 +6819,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) { @@ -6461,12 +6840,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) { @@ -6476,38 +6849,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 7cf82da16..1cb630d12 100644 --- a/clients/window.h +++ b/clients/window.h @@ -40,6 +40,8 @@ struct widget; struct display; struct input; struct output; +struct tablet; +struct tablet_tool; struct task { void (*run)(struct task *task, uint32_t events); @@ -71,9 +73,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 +113,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 @@ -284,6 +267,57 @@ typedef void (*widget_axis_handler_t)(struct widget *widget, uint32_t axis, wl_fixed_t value, void *data); +typedef int (*widget_tablet_tool_motion_handler_t)(struct widget *widget, + struct tablet_tool *tool, + float x, float y, + void *data); +typedef void (*widget_tablet_tool_down_handler_t)(struct widget *widget, + struct tablet_tool *tool, + void *data); +typedef void (*widget_tablet_tool_up_handler_t)(struct widget *widget, + struct tablet_tool *tool, + void *data); +typedef void (*widget_tablet_tool_pressure_handler_t)(struct widget *widget, + struct tablet_tool *tool, + uint32_t pressure, + void *data); +typedef void (*widget_tablet_tool_distance_handler_t)(struct widget *widget, + struct tablet_tool *tool, + uint32_t distance, + void *data); +typedef void (*widget_tablet_tool_tilt_handler_t)(struct widget *widget, + struct tablet_tool *tool, + int32_t tilt_x, int32_t tilt_y, + void *data); +typedef void (*widget_tablet_tool_rotation_handler_t)(struct widget *widget, + struct tablet_tool *tool, + int32_t rotation, + void *data); +typedef void (*widget_tablet_tool_slider_handler_t)(struct widget *widget, + struct tablet_tool *tool, + int32_t slider, + void *data); +typedef void (*widget_tablet_tool_wheel_handler_t)(struct widget *widget, + struct tablet_tool *tool, + wl_fixed_t degrees, + int32_t clicks, + void *data); +typedef void (*widget_tablet_tool_proximity_in_handler_t)(struct widget *widget, + struct tablet_tool *tool, + struct tablet *tablet, + void *data); +typedef void (*widget_tablet_tool_proximity_out_handler_t)(struct widget *widget, + struct tablet_tool *tool, + void *data); +typedef void (*widget_tablet_tool_button_handler_t)(struct widget *widget, + struct tablet_tool *tool, + uint32_t button, + uint32_t state, + void *data); +typedef void (*widget_tablet_tool_frame_handler_t)(struct widget *widget, + struct tablet_tool *tool, + uint32_t time, + void *data); typedef void (*widget_pointer_frame_handler_t)(struct widget *widget, struct input *input, @@ -416,7 +450,6 @@ struct wl_subsurface * widget_get_wl_subsurface(struct widget *widget); enum window_buffer_type { - WINDOW_BUFFER_TYPE_EGL_WINDOW, WINDOW_BUFFER_TYPE_SHM, }; @@ -526,6 +559,8 @@ widget_destroy(struct widget *widget); void widget_set_default_cursor(struct widget *widget, int cursor); void +widget_set_default_tablet_cursor(struct widget *widget, int cursor); +void widget_get_allocation(struct widget *widget, struct rectangle *allocation); void @@ -598,6 +633,31 @@ widget_set_axis_handlers(struct widget *widget, widget_axis_source_handler_t axis_source_handler, widget_axis_stop_handler_t axis_stop_handler, widget_axis_discrete_handler_t axis_discrete_handler); +void +widget_set_tablet_tool_axis_handlers(struct widget *widget, + widget_tablet_tool_motion_handler_t motion, + widget_tablet_tool_pressure_handler_t pressure, + widget_tablet_tool_distance_handler_t distance, + widget_tablet_tool_tilt_handler_t tilt, + widget_tablet_tool_rotation_handler_t rotation, + widget_tablet_tool_slider_handler_t slider, + widget_tablet_tool_wheel_handler_t wheel); +void +widget_set_tablet_tool_up_handler(struct widget *widget, + widget_tablet_tool_up_handler_t handler); +void +widget_set_tablet_tool_down_handler(struct widget *widget, + widget_tablet_tool_down_handler_t handler); +void +widget_set_tablet_tool_proximity_handlers(struct widget *widget, + widget_tablet_tool_proximity_in_handler_t in_handler, + widget_tablet_tool_proximity_out_handler_t out_handler); +void +widget_set_tablet_tool_button_handler(struct widget *widget, + widget_tablet_tool_button_handler_t handler); +void +widget_set_tablet_tool_frame_handler(struct widget *widget, + widget_tablet_tool_frame_handler_t handler); void window_inhibit_redraw(struct window *window); @@ -721,6 +781,18 @@ xkb_mod_mask_t keysym_modifiers_get_mask(struct wl_array *modifiers_map, const char *name); +uint32_t +tablet_tool_get_type(struct tablet_tool *tool); + +uint64_t +tablet_tool_get_serial(struct tablet_tool *tool); + +uint64_t +tablet_tool_get_hwid(struct tablet_tool *tool); + +void +tablet_tool_set_cursor_image(struct tablet_tool *tool, int cursor); + struct toytimer; typedef void (*toytimer_cb)(struct toytimer *); diff --git a/compositor/config-helpers.c b/compositor/config-helpers.c new file mode 100644 index 000000000..bd8a6d4e6 --- /dev/null +++ b/compositor/config-helpers.c @@ -0,0 +1,97 @@ +/* + * 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 "shared/helpers.h" +#include "weston-private.h" + +struct { + char *short_name; + char *long_name; + enum weston_compositor_backend backend; +} backend_name_map[] = { + { "drm", "drm-backend.so", WESTON_BACKEND_DRM }, + { "headless", "headless-backend.so", WESTON_BACKEND_HEADLESS }, + { "pipewire", "pipewire-backend.so", WESTON_BACKEND_PIPEWIRE }, + { "rdp", "rdp-backend.so", WESTON_BACKEND_RDP }, + { "vnc", "vnc-backend.so", WESTON_BACKEND_VNC }, + { "wayland", "wayland-backend.so", WESTON_BACKEND_WAYLAND }, + { "x11", "x11-backend.so", WESTON_BACKEND_X11 }, +}; + +bool +get_backend_from_string(const char *name, + enum weston_compositor_backend *backend) +{ + size_t i; + + for (i = 0; i < ARRAY_LENGTH(backend_name_map); i++) { + if (strcmp(name, backend_name_map[i].short_name) == 0 || + strcmp(name, backend_name_map[i].long_name) == 0) { + *backend = backend_name_map[i].backend; + return true; + } + } + + return false; +} + +struct { + char *name; + enum weston_renderer_type renderer; +} renderer_name_map[] = { + { "auto", WESTON_RENDERER_AUTO }, + { "gl", WESTON_RENDERER_GL }, +#if defined(ENABLE_IMXG2D) + { "g2d", WESTON_RENDERER_G2D }, +#endif + { "noop", WESTON_RENDERER_NOOP }, + { "pixman", WESTON_RENDERER_PIXMAN }, +}; + +bool +get_renderer_from_string(const char *name, + enum weston_renderer_type *renderer) +{ + size_t i; + + if (!name) + name = "auto"; + + for (i = 0; i < ARRAY_LENGTH(renderer_name_map); i++) { + if (strcmp(name, renderer_name_map[i].name) == 0) { + *renderer = renderer_name_map[i].renderer; + return true; + } + } + + return false; +} diff --git a/compositor/main.c b/compositor/main.c index 322f2ff57..ff85ff774 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,18 @@ #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 #include #include @@ -127,6 +131,7 @@ struct wet_compositor { pid_t autolaunch_pid; bool autolaunch_watch; bool use_color_manager; + struct wl_listener screenshot_auth; }; static FILE *weston_logfile = NULL; @@ -134,37 +139,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 +146,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 +192,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 +245,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 +258,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), @@ -376,12 +355,12 @@ sigchld_handler(int signal_number, void *data) break; } - if (&p->link == &wet->child_process_list) { - weston_log("unknown child process exited\n"); + /* An unknown child process exited. Oh well. */ + if (&p->link == &wet->child_process_list) continue; - } wl_list_remove(&p->link); + wl_list_init(&p->link); p->cleanup(p, status); } @@ -392,88 +371,93 @@ sigchld_handler(int signal_number, void *data) } static void -child_client_exec(int sockfd, const char *path) -{ - int clientfd; - char s[32]; +cleanup_for_child_process() { sigset_t allsigs; + /* 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(); + /* 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 * +WL_EXPORT bool weston_client_launch(struct weston_compositor *compositor, struct weston_process *proc, - const char *path, + struct custom_env *child_env, + int *no_cloexec_fds, + size_t num_no_cloexec_fds, weston_process_cleanup_func_t cleanup) { - int sv[2]; + const char *fail_cloexec = "Couldn't unset CLOEXEC on child FDs"; + const char *fail_seteuid = "Couldn't call seteuid"; + char *fail_exec; + char * const *argp; + char * const *envp; pid_t pid; - struct wl_client *client; + int err; + bool ret; + size_t i; + size_t written __attribute__((unused)); - weston_log("launching '%s'\n", path); + argp = custom_env_get_argp(child_env); + envp = custom_env_get_envp(child_env); - if (os_socketpair_cloexec(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { - weston_log("weston_client_launch: " - "socketpair failed while launching '%s': %s\n", - path, strerror(errno)); - return NULL; - } + weston_log("launching '%s'\n", argp[0]); + str_printf(&fail_exec, "Error: Couldn't launch client '%s'\n", argp[0]); 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: + cleanup_for_child_process(); + + /* 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); + } - if (pid == 0) { - child_client_exec(sv[1], path); - _exit(-1); - } + for (i = 0; i < num_no_cloexec_fds; i++) { + err = os_fd_clear_cloexec(no_cloexec_fds[i]); + if (err < 0) { + written = write(STDERR_FILENO, fail_cloexec, + strlen(fail_cloexec)); + _exit(EXIT_FAILURE); + } + } - close(sv[1]); + execve(argp[0], argp, envp); - client = wl_client_create(compositor->wl_display, sv[0]); - if (!client) { - close(sv[0]); + if (fail_exec) + written = write(STDERR_FILENO, fail_exec, + strlen(fail_exec)); + _exit(EXIT_FAILURE); + + default: + proc->pid = pid; + proc->cleanup = cleanup; + wet_watch_process(compositor, proc); + ret = true; + break; + + case -1: weston_log("weston_client_launch: " - "wl_client_create failed while launching '%s'.\n", - path); - return NULL; + "fork failed while launching '%s': %s\n", argp[0], + strerror(errno)); + ret = false; + break; } - proc->pid = pid; - proc->cleanup = cleanup; - wet_watch_process(compositor, proc); - - return client; + custom_env_fini(child_env); + free(fail_exec); + return ret; } WL_EXPORT void @@ -519,6 +503,11 @@ weston_client_start(struct weston_compositor *compositor, const char *path) { struct process_info *pinfo; struct wl_client *client; + struct custom_env child_env; + struct fdstr wayland_socket = FDSTR_INIT; + int no_cloexec_fds[1]; + size_t num_no_cloexec_fds = 0; + bool ret; pinfo = zalloc(sizeof *pinfo); if (!pinfo) @@ -528,18 +517,51 @@ weston_client_start(struct weston_compositor *compositor, const char *path) if (!pinfo->path) goto out_free; - client = weston_client_launch(compositor, &pinfo->proc, path, - process_handle_sigchld); - if (!client) - goto out_str; + if (os_socketpair_cloexec(AF_UNIX, SOCK_STREAM, 0, + wayland_socket.fds) < 0) { + weston_log("weston_client_start: " + "socketpair failed while launching '%s': %s\n", + path, strerror(errno)); + goto out_path; + } + + custom_env_init_from_environ(&child_env); + custom_env_add_from_exec_string(&child_env, path); + + fdstr_update_str1(&wayland_socket); + no_cloexec_fds[num_no_cloexec_fds++] = wayland_socket.fds[1]; + custom_env_set_env_var(&child_env, "WAYLAND_SOCKET", + wayland_socket.str1); + + assert(num_no_cloexec_fds <= ARRAY_LENGTH(no_cloexec_fds)); + + ret = weston_client_launch(compositor, &pinfo->proc, &child_env, + no_cloexec_fds, num_no_cloexec_fds, + process_handle_sigchld); + if (!ret) + goto out_path; + + client = wl_client_create(compositor->wl_display, + wayland_socket.fds[0]); + if (!client) { + weston_log("weston_client_start: " + "wl_client_create failed while launching '%s'.\n", + path); + /* We have no way of killing the process, so leave it hanging */ + goto out_sock; + } + + /* Close the child end of our socket which we no longer need */ + close(wayland_socket.fds[1]); return client; -out_str: +out_path: free(pinfo->path); - out_free: free(pinfo); +out_sock: + fdstr_close_all(&wayland_socket); return NULL; } @@ -642,26 +664,36 @@ usage(int error_code) "Core options:\n\n" " --version\t\tPrint weston version\n" - " -B, --backend=MODULE\tBackend module, one of\n" + " -B, --backend=BACKEND\tBackend module, one of\n" #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" + "\t\t\t\tdrm\n" #endif #if defined(BUILD_HEADLESS_COMPOSITOR) - "\t\t\t\theadless-backend.so\n" + "\t\t\t\theadless\n" +#endif +#if defined(BUILD_PIPEWIRE_COMPOSITOR) + "\t\t\t\tpipewire\n" #endif #if defined(BUILD_RDP_COMPOSITOR) - "\t\t\t\trdp-backend.so\n" + "\t\t\t\trdp\n" +#endif +#if defined(BUILD_VNC_COMPOSITOR) + "\t\t\t\tvnc\n" #endif #if defined(BUILD_WAYLAND_COMPOSITOR) - "\t\t\t\twayland-backend.so\n" + "\t\t\t\twayland\n" #endif #if defined(BUILD_X11_COMPOSITOR) - "\t\t\t\tx11-backend.so\n" + "\t\t\t\tx11\n" #endif - " --shell=MODULE\tShell module, defaults to desktop-shell.so\n" + " --renderer=NAME\tRenderer to use, one of\n" + "\t\t\t\tauto\tAutomatic selection of one of the below renderers\n" +#if defined(ENABLE_EGL) + "\t\t\t\tgl\tOpenGL ES\n" +#endif + "\t\t\t\tnoop\tNo-op renderer for testing only\n" + "\t\t\t\tpixman\tPixman software renderer\n" + " --shell=NAME\tShell to load, defaults to desktop\n" " -S, --socket=NAME\tName of socket to listen on\n" " -i, --idle-time=SECS\tIdle time in seconds\n" #if defined(BUILD_XWAYLAND) @@ -683,44 +715,46 @@ usage(int error_code) #if defined(BUILD_DRM_COMPOSITOR) fprintf(out, - "Options for drm-backend.so:\n\n" + "Options for drm:\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" + " --use-pixman\t\tUse the pixman (CPU) renderer (deprecated alias for --renderer=pixman)\n" +#if defined(ENABLE_IMXG2D) + " --use-g2d\t\tUse the G2D renderer (default: GL rendering)\n" +#endif " --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" + "Options for headless:\n\n" " --width=WIDTH\t\tWidth of memory surface\n" " --height=HEIGHT\tHeight of memory surface\n" " --scale=SCALE\t\tScale factor of output\n" " --transform=TR\tThe output transformation, TR is one of:\n" "\tnormal 90 180 270 flipped flipped-90 flipped-180 flipped-270\n" - " --use-pixman\t\tUse the pixman (CPU) renderer (default: no rendering)\n" - " --use-gl\t\tUse the GL renderer (default: no rendering)\n" + " --use-pixman\t\tUse the pixman (CPU) renderer (deprecated alias for --renderer=pixman)\n" + " --use-gl\t\tUse the GL renderer (deprecated alias for --renderer=gl)\n" " --no-outputs\t\tDo not create any virtual outputs\n" "\n"); #endif +#if defined(BUILD_PIPEWIRE_COMPOSITOR) + fprintf(out, + "Options for pipewire\n\n" + " --width=WIDTH\t\tWidth of desktop\n" + " --height=HEIGHT\tHeight of desktop\n" + "\n"); +#endif + #if defined(BUILD_RDP_COMPOSITOR) fprintf(out, - "Options for rdp-backend.so:\n\n" + "Options for rdp:\n\n" " --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" @@ -730,14 +764,25 @@ usage(int error_code) "\n"); #endif +#if defined(BUILD_VNC_COMPOSITOR) + fprintf(out, + "Options for vnc:\n\n" + " --width=WIDTH\t\tWidth of desktop\n" + " --height=HEIGHT\tHeight of desktop\n" + " --port=PORT\t\tThe port to listen on\n" + " --vnc-tls-cert=FILE\tThe file containing the certificate for TLS encryption\n" + " --vnc-tls-key=FILE\tThe file containing the private key for TLS encryption\n" + "\n"); +#endif + #if defined(BUILD_WAYLAND_COMPOSITOR) fprintf(out, - "Options for wayland-backend.so:\n\n" + "Options for wayland:\n\n" " --width=WIDTH\t\tWidth of Wayland surface\n" " --height=HEIGHT\tHeight of Wayland surface\n" " --scale=SCALE\t\tScale factor of output\n" " --fullscreen\t\tRun in fullscreen mode\n" - " --use-pixman\t\tUse the pixman (CPU) renderer\n" + " --use-pixman\t\tUse the pixman (CPU) renderer (deprecated alias for --renderer=pixman)\n" " --output-count=COUNT\tCreate multiple outputs\n" " --sprawl\t\tCreate one fullscreen output for every parent output\n" " --display=DISPLAY\tWayland display to connect to\n\n"); @@ -745,12 +790,12 @@ usage(int error_code) #if defined(BUILD_X11_COMPOSITOR) fprintf(out, - "Options for x11-backend.so:\n\n" + "Options for x11:\n\n" " --width=WIDTH\t\tWidth of X window\n" " --height=HEIGHT\tHeight of X window\n" " --scale=SCALE\t\tScale factor of output\n" " --fullscreen\t\tRun in fullscreen mode\n" - " --use-pixman\t\tUse the pixman (CPU) renderer\n" + " --use-pixman\t\tUse the pixman (CPU) renderer (deprecated alias for --renderer=pixman)\n" " --output-count=COUNT\tCreate multiple outputs\n" " --no-input\t\tDont create input devices\n\n"); #endif @@ -869,7 +914,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)) { @@ -894,53 +939,6 @@ weston_create_listening_socket(struct wl_display *display, const char *socket_na } } -WL_EXPORT void * -wet_load_module_entrypoint(const char *name, const char *entrypoint) -{ - char path[PATH_MAX]; - void *module, *init; - size_t len; - - if (name == NULL) - return NULL; - - if (name[0] != '/') { - len = weston_module_path_from_env(name, path, sizeof path); - if (len == 0) - len = snprintf(path, sizeof path, "%s/%s", MODULEDIR, - name); - } else { - len = snprintf(path, sizeof path, "%s", name); - } - - /* snprintf returns the length of the string it would've written, - * _excluding_ the NUL byte. So even being equal to the size of - * our buffer is an error here. */ - if (len >= sizeof path) - return NULL; - - module = dlopen(path, RTLD_NOW | RTLD_NOLOAD); - if (module) { - weston_log("Module '%s' already loaded\n", path); - } else { - weston_log("Loading module '%s'\n", path); - module = dlopen(path, RTLD_NOW); - if (!module) { - weston_log("Failed to load module: %s\n", dlerror()); - return NULL; - } - } - - init = dlsym(module, entrypoint); - if (!init) { - weston_log("Failed to lookup init function: %s\n", dlerror()); - dlclose(module); - return NULL; - } - - return init; -} - WL_EXPORT int wet_load_module(struct weston_compositor *compositor, const char *name, int *argc, char *argv[]) @@ -948,7 +946,7 @@ wet_load_module(struct weston_compositor *compositor, int (*module_init)(struct weston_compositor *ec, int *argc, char *argv[]); - module_init = wet_load_module_entrypoint(name, "wet_module_init"); + module_init = weston_load_module(name, "wet_module_init", MODULEDIR); if (!module_init) return -1; if (module_init(compositor, argc, argv) < 0) @@ -958,12 +956,21 @@ wet_load_module(struct weston_compositor *compositor, static int wet_load_shell(struct weston_compositor *compositor, - const char *name, int *argc, char *argv[]) + const char *_name, int *argc, char *argv[]) { + char *name; int (*shell_init)(struct weston_compositor *ec, int *argc, char *argv[]); - shell_init = wet_load_module_entrypoint(name, "wet_shell_init"); + if (strstr(_name, "-shell.so")) + name = strdup(_name); + else + str_printf(&name, "%s-shell.so", _name); + assert(name); + + shell_init = weston_load_module(name, "wet_shell_init", MODULEDIR); + free(name); + if (!shell_init) return -1; if (shell_init(compositor, argc, argv) < 0) @@ -1002,7 +1009,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 +1023,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; @@ -1165,9 +1172,9 @@ weston_choose_default_backend(void) char *backend = NULL; if (getenv("WAYLAND_DISPLAY") || getenv("WAYLAND_SOCKET")) - backend = strdup("wayland-backend.so"); + backend = strdup("wayland"); else if (getenv("DISPLAY")) - backend = strdup("x11-backend.so"); + backend = strdup("x11"); else backend = strdup(WESTON_NATIVE_BACKEND); @@ -1348,6 +1355,227 @@ 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); +#if !HAVE_LIBDISPLAY_INFO + weston_log_continue(STAMP_SPACE "Weston was built without libdisplay-info, " + "so HDR capabilities cannot be detected.\n"); +#endif + 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) @@ -1361,6 +1589,35 @@ allow_content_protection(struct weston_output *output, weston_output_allow_protection(output, allow_hdcp); } +static void +parse_simple_mode(struct weston_output *output, + struct weston_config_section *section, int *width, + int *height, struct wet_output_config *defaults, + struct wet_output_config *parsed_options) +{ + *width = defaults->width; + *height = defaults->height; + + if (section) { + char *mode; + + weston_config_section_get_string(section, "mode", &mode, NULL); + if (!mode || sscanf(mode, "%dx%d", width, height) != 2) { + weston_log("Invalid mode for output %s. Using defaults.\n", + output->name); + *width = defaults->width; + *height = defaults->height; + } + free(mode); + } + + if (parsed_options->width) + *width = parsed_options->width; + + if (parsed_options->height) + *height = parsed_options->height; +} + static int wet_configure_windowed_output_from_config(struct weston_output *output, struct wet_output_config *defaults) @@ -1372,8 +1629,8 @@ wet_configure_windowed_output_from_config(struct weston_output *output, struct weston_config_section *section = NULL; struct wet_compositor *compositor = to_wet_compositor(output->compositor); struct wet_output_config *parsed_options = compositor->parsed_options; - int width = defaults->width; - int height = defaults->height; + int width; + int height; assert(parsed_options); @@ -1384,28 +1641,11 @@ wet_configure_windowed_output_from_config(struct weston_output *output, section = weston_config_get_section(wc, "output", "name", output->name); - if (section) { - char *mode; - - weston_config_section_get_string(section, "mode", &mode, NULL); - if (!mode || sscanf(mode, "%dx%d", &width, - &height) != 2) { - weston_log("Invalid mode for output %s. Using defaults.\n", - output->name); - width = defaults->width; - height = defaults->height; - } - free(mode); - } + parse_simple_mode(output, section, &width, &height, defaults, + parsed_options); allow_content_protection(output, section); - if (parsed_options->width) - width = parsed_options->width; - - if (parsed_options->height) - height = parsed_options->height; - wet_output_set_scale(output, section, defaults->scale, parsed_options->scale); if (wet_output_set_transform(output, section, defaults->transform, parsed_options->transform) < 0) { @@ -1503,14 +1743,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 +1791,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,9 +2087,12 @@ 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; + char *content_type = NULL; char *seat = NULL; api = weston_drm_output_get_api(output->compositor); @@ -1825,12 +2102,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 +2127,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); @@ -1864,6 +2149,12 @@ drm_backend_output_configure(struct weston_output *output, api->set_gbm_format(output, gbm_format); free(gbm_format); + weston_config_section_get_string(section, + "content-type", &content_type, NULL); + if (api->set_content_type(output, content_type) < 0) + return -1; + free(content_type); + weston_config_section_get_string(section, "seat", &seat, ""); api->set_seat(output, seat); @@ -1871,6 +2162,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 +2256,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 +2268,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 +2421,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 +2442,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 +2540,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; @@ -2467,7 +2770,8 @@ load_remoting(struct weston_compositor *c, struct weston_config *wc) &module_name, "remoting-plugin.so"); module_init = weston_load_module(module_name, - "weston_module_init"); + "weston_module_init", + LIBWESTON_MODULEDIR); free(module_name); if (!module_init) { weston_log("Can't load remoting-plugin\n"); @@ -2597,7 +2901,8 @@ load_pipewire(struct weston_compositor *c, struct weston_config *wc) &module_name, "pipewire-plugin.so"); module_init = weston_load_module(module_name, - "weston_module_init"); + "weston_module_init", + LIBWESTON_MODULEDIR); free(module_name); if (!module_init) { weston_log("Can't load pipewire-plugin\n"); @@ -2617,33 +2922,87 @@ load_pipewire(struct weston_compositor *c, struct weston_config *wc) } } +static void +drm_backend_shell_configure(struct weston_compositor *c, + struct weston_drm_backend_config *config) +{ + struct weston_config_section *section; + section = weston_config_get_section(wet_get_config(c), + "shell", NULL, NULL); + + if (section) { + char *size; + int n; + uint32_t width = 0; + uint32_t height = 0; + + weston_config_section_get_string(section, "size", &size, NULL); + + if(size){ + n = sscanf(size, "%dx%d", &width, &height); + if (n == 2 && width > 0 && height > 0) { + config->shell_width = width; + config->shell_height = height; + } + free(size); + } + } +} + static int -load_drm_backend(struct weston_compositor *c, - int *argc, char **argv, struct weston_config *wc) +load_drm_backend(struct weston_compositor *c, int *argc, char **argv, + struct weston_config *wc, enum weston_renderer_type renderer) { struct weston_drm_backend_config config = {{ 0, }}; struct weston_config_section *section; struct wet_compositor *wet = to_wet_compositor(c); bool without_input = false; + bool force_pixman = false; int ret = 0; +#if defined(ENABLE_IMXG2D) + bool use_g2d = false; +#endif + uint32_t enable_overlay_view; wet->drm_use_current_mode = false; section = weston_config_get_section(wc, "core", NULL, NULL); - weston_config_section_get_bool(section, "use-pixman", &config.use_pixman, + + weston_config_section_get_bool(section, "use-pixman", &force_pixman, false); +#if defined(ENABLE_IMXG2D) + weston_config_section_get_bool(section, "use-g2d", &config.use_g2d, + use_g2d); +#endif + 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_STRING, "additional-devices", 0, &config.additional_devices}, { WESTON_OPTION_BOOLEAN, "current-mode", 0, &wet->drm_use_current_mode }, - { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, + { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &force_pixman }, +#if defined(ENABLE_IMXG2D) + { WESTON_OPTION_BOOLEAN, "use-g2d", 0, &config.use_g2d }, +#endif { WESTON_OPTION_BOOLEAN, "continue-without-input", false, &without_input } }; parse_options(options, ARRAY_LENGTH(options), argc, argv); + if (force_pixman && renderer != WESTON_RENDERER_AUTO) { + weston_log("error: conflicting renderer specification\n"); + return -1; + } else if (force_pixman) { + config.renderer = WESTON_RENDERER_PIXMAN; +#if defined(ENABLE_IMXG2D) + }else if (config.use_g2d) { + config.renderer = WESTON_RENDERER_G2D; +#endif + } else { + config.renderer = renderer; + } + section = weston_config_get_section(wc, "core", NULL, NULL); weston_config_section_get_string(section, "gbm-format", &config.gbm_format, @@ -2655,6 +3014,9 @@ load_drm_backend(struct weston_compositor *c, if (without_input) c->require_input = !without_input; + weston_config_section_get_uint(section, "enable-overlay-view", &enable_overlay_view, 0); + config.enable_overlay_view = enable_overlay_view; + config.base.struct_version = WESTON_DRM_BACKEND_CONFIG_VERSION; config.base.struct_size = sizeof(struct weston_drm_backend_config); config.configure_device = configure_input_device; @@ -2663,6 +3025,8 @@ load_drm_backend(struct weston_compositor *c, weston_compositor_add_heads_changed_listener(c, &wet->heads_changed_listener); + drm_backend_shell_configure(c, &config); + ret = weston_compositor_load_backend(c, WESTON_BACKEND_DRM, &config.base); @@ -2688,17 +3052,29 @@ 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); } static int load_headless_backend(struct weston_compositor *c, - int *argc, char **argv, struct weston_config *wc) + int *argc, char **argv, struct weston_config *wc, + enum weston_renderer_type renderer) { const struct weston_windowed_output_api *api; struct weston_headless_backend_config config = {{ 0, }}; struct weston_config_section *section; + bool force_pixman; + bool force_gl; bool no_outputs = false; int ret = 0; char *transform = NULL; @@ -2708,23 +3084,37 @@ load_headless_backend(struct weston_compositor *c, return -1; section = weston_config_get_section(wc, "core", NULL, NULL); - weston_config_section_get_bool(section, "use-pixman", &config.use_pixman, + weston_config_section_get_bool(section, "use-pixman", &force_pixman, + false); + weston_config_section_get_bool(section, "use-gl", &force_gl, false); - weston_config_section_get_bool(section, "use-gl", &config.use_gl, + weston_config_section_get_bool(section, "output-decorations", &config.decorate, false); const struct weston_option options[] = { { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, { WESTON_OPTION_INTEGER, "scale", 0, &parsed_options->scale }, - { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, - { WESTON_OPTION_BOOLEAN, "use-gl", 0, &config.use_gl }, + { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &force_pixman }, + { WESTON_OPTION_BOOLEAN, "use-gl", 0, &force_gl }, { WESTON_OPTION_STRING, "transform", 0, &transform }, { WESTON_OPTION_BOOLEAN, "no-outputs", 0, &no_outputs }, }; parse_options(options, ARRAY_LENGTH(options), argc, argv); + if ((force_pixman && force_gl) || + (renderer != WESTON_RENDERER_AUTO && (force_pixman || force_gl))) { + weston_log("Conflicting renderer specifications\n"); + return -1; + } else if (force_pixman) { + config.renderer = WESTON_RENDERER_PIXMAN; + } else if (force_gl) { + config.renderer = WESTON_RENDERER_GL; + } else { + config.renderer = renderer; + } + if (transform) { if (weston_parse_transform(transform, &parsed_options->transform) < 0) { weston_log("Invalid transform \"%s\"\n", transform); @@ -2753,7 +3143,7 @@ load_headless_backend(struct weston_compositor *c, return -1; } - if (api->create_head(c, "headless") < 0) + if (api->create_head(c->backend, "headless") < 0) return -1; } @@ -2761,63 +3151,208 @@ load_headless_backend(struct weston_compositor *c, } static int -rdp_backend_output_configure(struct weston_output *output) +pipewire_backend_output_configure(struct weston_output *output) { + struct wet_output_config defaults = { + .width = 640, + .height = 480, + }; struct wet_compositor *compositor = to_wet_compositor(output->compositor); struct wet_output_config *parsed_options = compositor->parsed_options; - const struct weston_rdp_output_api *api = weston_rdp_output_get_api(output->compositor); - int width = 640; - int height = 480; + const struct weston_pipewire_output_api *api = weston_pipewire_output_get_api(output->compositor); + struct weston_config *wc = wet_get_config(output->compositor); + struct weston_config_section *section; + char *gbm_format = NULL; + int width; + int height; assert(parsed_options); if (!api) { - weston_log("Cannot use weston_rdp_output_api.\n"); + weston_log("Cannot use weston_pipewire_output_api.\n"); return -1; } - if (parsed_options->width) - width = parsed_options->width; + section = weston_config_get_section(wc, "output", "name", output->name); - if (parsed_options->height) - height = parsed_options->height; + parse_simple_mode(output, section, &width, &height, &defaults, + parsed_options); + + if (section) + weston_config_section_get_string(section, "gbm-format", + &gbm_format, NULL); weston_output_set_scale(output, 1); weston_output_set_transform(output, WL_OUTPUT_TRANSFORM_NORMAL); + api->set_gbm_format(output, gbm_format); + free(gbm_format); + if (api->output_set_size(output, width, height) < 0) { - weston_log("Cannot configure output \"%s\" using weston_rdp_output_api.\n", + weston_log("Cannot configure output \"%s\" using weston_pipewire_output_api.\n", output->name); return -1; } + weston_log("pipewire_backend_output_configure.. Done\n"); return 0; } +static void +weston_pipewire_backend_config_init(struct weston_pipewire_backend_config *config) +{ + config->base.struct_version = WESTON_PIPEWIRE_BACKEND_CONFIG_VERSION; + config->base.struct_size = sizeof(struct weston_pipewire_backend_config); +} + +static int +load_pipewire_backend(struct weston_compositor *c, + int *argc, char *argv[], struct weston_config *wc, + enum weston_renderer_type renderer) +{ + struct weston_pipewire_backend_config config = {{ 0, }}; + struct weston_config_section *section; + struct wet_output_config *parsed_options = wet_init_parsed_options(c); + + if (!parsed_options) + return -1; + + weston_pipewire_backend_config_init(&config); + + const struct weston_option pipewire_options[] = { + { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, + { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, + }; + + parse_options(pipewire_options, ARRAY_LENGTH(pipewire_options), argc, argv); + + config.renderer = renderer; + + wet_set_simple_head_configurator(c, pipewire_backend_output_configure); + + section = weston_config_get_section(wc, "core", NULL, NULL); + weston_config_section_get_string(section, "gbm-format", + &config.gbm_format, NULL); + + section = weston_config_get_section(wc, "pipewire", NULL, NULL); + weston_config_section_get_int(section, "num-outputs", + &config.num_outputs, 1); + + return weston_compositor_load_backend(c, WESTON_BACKEND_PIPEWIRE, + &config.base); +} + static void weston_rdp_backend_config_init(struct weston_rdp_backend_config *config) { config->base.struct_version = WESTON_RDP_BACKEND_CONFIG_VERSION; config->base.struct_size = sizeof(struct weston_rdp_backend_config); + config->renderer = WESTON_RENDERER_AUTO; config->bind_address = NULL; config->port = 3389; config->rdp_key = NULL; 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 void +rdp_handle_layout(struct weston_compositor *ec) +{ + struct wet_compositor *wc = to_wet_compositor(ec); + struct wet_output_config *parsed_options = wc->parsed_options; + const struct weston_rdp_output_api *api = weston_rdp_output_get_api(ec); + struct weston_rdp_monitor config; + struct weston_head *head = NULL; + int width; + int height; + int scale = 1; + + while ((head = weston_compositor_iterate_heads(ec, head))) { + struct weston_coord_global pos; + struct weston_output *output = head->output; + struct weston_mode new_mode = {}; + + assert(output); + + api->head_get_monitor(head, &config); + + width = config.width; + height = config.height; + scale = config.desktop_scale / 100; + + /* If these are invalid, the backend is expecting + * us to provide defaults. + */ + width = width ? width : parsed_options->width; + height = height ? height : parsed_options->height; + scale = scale ? scale : parsed_options->scale; + + /* Fallback to 640 x 480 if we have nothing to use */ + width = width ? width : 640; + height = height ? height : 480; + scale = scale ? scale : 1; + + new_mode.width = width; + new_mode.height = height; + api->output_set_mode(output, &new_mode); + + weston_output_set_scale(output, scale); + weston_output_set_transform(output, + WL_OUTPUT_TRANSFORM_NORMAL); + pos.c = weston_coord(config.x, config.y); + weston_output_move(output, pos); + } +} + +static void +rdp_heads_changed(struct wl_listener *listener, void *arg) +{ + struct weston_compositor *compositor = arg; + struct wet_compositor *wet = to_wet_compositor(compositor); + struct weston_head *head = NULL; + + while ((head = weston_compositor_iterate_heads(compositor, head))) { + if (head->output) + continue; + + struct weston_output *out; + + out = weston_compositor_create_output(compositor, + head, head->name); + + wet_head_tracker_create(wet, head); + weston_output_attach_head(out, head); + } + + rdp_handle_layout(compositor); + + while ((head = weston_compositor_iterate_heads(compositor, head))) { + if (!head->output->enabled) + weston_output_enable(head->output); + + weston_head_reset_device_changed(head); + } } static int load_rdp_backend(struct weston_compositor *c, - int *argc, char *argv[], struct weston_config *wc) + int *argc, char *argv[], struct weston_config *wc, + enum weston_renderer_type renderer) { 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); + struct wet_compositor *wet = to_wet_compositor(c); + if (!parsed_options) return -1; @@ -2825,6 +3360,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 }, @@ -2833,12 +3369,23 @@ load_rdp_backend(struct weston_compositor *c, { WESTON_OPTION_STRING, "rdp4-key", 0, &config.rdp_key }, { WESTON_OPTION_STRING, "rdp-tls-cert", 0, &config.server_cert }, { WESTON_OPTION_STRING, "rdp-tls-key", 0, &config.server_key }, + { WESTON_OPTION_INTEGER, "scale", 0, &parsed_options->scale }, { 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; + config.renderer = renderer; + + section = weston_config_get_section(wc, "rdp", NULL, NULL); + weston_config_section_get_int(section, "refresh-rate", + &config.refresh_rate, + RDP_DEFAULT_FREQ); - wet_set_simple_head_configurator(c, rdp_backend_output_configure); + wet->heads_changed_listener.notify = rdp_heads_changed; + weston_compositor_add_heads_changed_listener(c, + &wet->heads_changed_listener); ret = weston_compositor_load_backend(c, WESTON_BACKEND_RDP, &config.base); @@ -2852,50 +3399,99 @@ load_rdp_backend(struct weston_compositor *c, } static int -fbdev_backend_output_configure(struct weston_output *output) +vnc_backend_output_configure(struct weston_output *output) { + struct wet_output_config defaults = { + .width = 640, + .height = 480, + }; + struct wet_compositor *compositor = to_wet_compositor(output->compositor); + struct wet_output_config *parsed_options = compositor->parsed_options; + const struct weston_vnc_output_api *api = weston_vnc_output_get_api(output->compositor); struct weston_config *wc = wet_get_config(output->compositor); struct weston_config_section *section; + int width; + int height; - section = weston_config_get_section(wc, "output", "name", "fbdev"); + assert(parsed_options); - if (wet_output_set_transform(output, section, - WL_OUTPUT_TRANSFORM_NORMAL, - UINT32_MAX) < 0) { + if (!api) { + weston_log("Cannot use weston_vnc_output_api.\n"); return -1; } + section = weston_config_get_section(wc, "output", "name", output->name); + + parse_simple_mode(output, section, &width, &height, &defaults, + compositor->parsed_options); + weston_output_set_scale(output, 1); + weston_output_set_transform(output, WL_OUTPUT_TRANSFORM_NORMAL); + + if (api->output_set_size(output, width, height) < 0) { + weston_log("Cannot configure output \"%s\" using weston_vnc_output_api.\n", + output->name); + return -1; + } + weston_log("vnc_backend_output_configure.. Done\n"); return 0; } + +static void +weston_vnc_backend_config_init(struct weston_vnc_backend_config *config) +{ + config->base.struct_version = WESTON_VNC_BACKEND_CONFIG_VERSION; + config->base.struct_size = sizeof(struct weston_vnc_backend_config); + + config->renderer = WESTON_RENDERER_AUTO; + config->bind_address = NULL; + config->port = 5900; + config->refresh_rate = VNC_DEFAULT_FREQ; +} + static int -load_fbdev_backend(struct weston_compositor *c, - int *argc, char **argv, struct weston_config *wc) +load_vnc_backend(struct weston_compositor *c, + int *argc, char *argv[], struct weston_config *wc, + enum weston_renderer_type renderer) { - struct weston_fbdev_backend_config config = {{ 0, }}; + struct weston_vnc_backend_config config = {{ 0, }}; + struct weston_config_section *section; 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 }, + struct wet_output_config *parsed_options = wet_init_parsed_options(c); + if (!parsed_options) + return -1; + + weston_vnc_backend_config_init(&config); + + const struct weston_option vnc_options[] = { + { WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width }, + { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, + { WESTON_OPTION_STRING, "address", 0, &config.bind_address }, + { WESTON_OPTION_INTEGER, "port", 0, &config.port }, + { WESTON_OPTION_STRING, "vnc-tls-cert", 0, &config.server_cert }, + { WESTON_OPTION_STRING, "vnc-tls-key", 0, &config.server_key }, }; - parse_options(fbdev_options, ARRAY_LENGTH(fbdev_options), argc, argv); + parse_options(vnc_options, ARRAY_LENGTH(vnc_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; + config.renderer = renderer; - wet_set_simple_head_configurator(c, fbdev_backend_output_configure); + wet_set_simple_head_configurator(c, vnc_backend_output_configure); + section = weston_config_get_section(wc, "vnc", NULL, NULL); + weston_config_section_get_int(section, "refresh-rate", + &config.refresh_rate, + VNC_DEFAULT_FREQ); - /* load the actual wayland backend and configure it */ - ret = weston_compositor_load_backend(c, WESTON_BACKEND_FBDEV, + ret = weston_compositor_load_backend(c, WESTON_BACKEND_VNC, &config.base); - free(config.device); + free(config.bind_address); + free(config.server_cert); + free(config.server_key); + return ret; } @@ -2914,12 +3510,14 @@ x11_backend_output_configure(struct weston_output *output) static int load_x11_backend(struct weston_compositor *c, - int *argc, char **argv, struct weston_config *wc) + int *argc, char **argv, struct weston_config *wc, + enum weston_renderer_type renderer) { char *default_output; const struct weston_windowed_output_api *api; struct weston_x11_backend_config config = {{ 0, }}; struct weston_config_section *section; + bool force_pixman; int ret = 0; int option_count = 1; int output_count = 0; @@ -2931,7 +3529,7 @@ load_x11_backend(struct weston_compositor *c, return -1; section = weston_config_get_section(wc, "core", NULL, NULL); - weston_config_section_get_bool(section, "use-pixman", &config.use_pixman, + weston_config_section_get_bool(section, "use-pixman", &force_pixman, false); const struct weston_option options[] = { @@ -2941,7 +3539,7 @@ load_x11_backend(struct weston_compositor *c, { WESTON_OPTION_BOOLEAN, "fullscreen", 'f', &config.fullscreen }, { WESTON_OPTION_INTEGER, "output-count", 0, &option_count }, { WESTON_OPTION_BOOLEAN, "no-input", 0, &config.no_input }, - { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, + { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &force_pixman }, }; parse_options(options, ARRAY_LENGTH(options), argc, argv); @@ -2949,6 +3547,15 @@ load_x11_backend(struct weston_compositor *c, config.base.struct_version = WESTON_X11_BACKEND_CONFIG_VERSION; config.base.struct_size = sizeof(struct weston_x11_backend_config); + if (force_pixman && renderer != WESTON_RENDERER_AUTO) { + weston_log("error: conflicting renderer specification\n"); + return -1; + } else if (force_pixman) { + config.renderer = WESTON_RENDERER_PIXMAN; + } else { + config.renderer = renderer; + } + wet_set_simple_head_configurator(c, x11_backend_output_configure); /* load the actual backend and configure it */ @@ -2982,7 +3589,7 @@ load_x11_backend(struct weston_compositor *c, continue; } - if (api->create_head(c, output_name) < 0) { + if (api->create_head(c->backend, output_name) < 0) { free(output_name); return -1; } @@ -2998,7 +3605,7 @@ load_x11_backend(struct weston_compositor *c, return -1; } - if (api->create_head(c, default_output) < 0) { + if (api->create_head(c->backend, default_output) < 0) { free(default_output); return -1; } @@ -3023,13 +3630,15 @@ wayland_backend_output_configure(struct weston_output *output) static int load_wayland_backend(struct weston_compositor *c, - int *argc, char **argv, struct weston_config *wc) + int *argc, char **argv, struct weston_config *wc, + enum weston_renderer_type renderer) { struct weston_wayland_backend_config config = {{ 0, }}; struct weston_config_section *section; const struct weston_windowed_output_api *api; const char *section_name; char *output_name = NULL; + bool force_pixman = false; int count = 1; int ret = 0; int i; @@ -3043,7 +3652,7 @@ load_wayland_backend(struct weston_compositor *c, config.display_name = NULL; section = weston_config_get_section(wc, "core", NULL, NULL); - weston_config_section_get_bool(section, "use-pixman", &config.use_pixman, + weston_config_section_get_bool(section, "use-pixman", &force_pixman, false); const struct weston_option wayland_options[] = { @@ -3051,7 +3660,7 @@ load_wayland_backend(struct weston_compositor *c, { WESTON_OPTION_INTEGER, "height", 0, &parsed_options->height }, { WESTON_OPTION_INTEGER, "scale", 0, &parsed_options->scale }, { WESTON_OPTION_STRING, "display", 0, &config.display_name }, - { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman }, + { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &force_pixman }, { WESTON_OPTION_INTEGER, "output-count", 0, &count }, { WESTON_OPTION_BOOLEAN, "fullscreen", 0, &config.fullscreen }, { WESTON_OPTION_BOOLEAN, "sprawl", 0, &config.sprawl }, @@ -3068,6 +3677,15 @@ load_wayland_backend(struct weston_compositor *c, config.base.struct_size = sizeof(struct weston_wayland_backend_config); config.base.struct_version = WESTON_WAYLAND_BACKEND_CONFIG_VERSION; + if (force_pixman && renderer != WESTON_RENDERER_AUTO) { + weston_log("error: conflicting renderer specification\n"); + return -1; + } else if (force_pixman) { + config.renderer = WESTON_RENDERER_PIXMAN; + } else { + config.renderer = renderer; + } + /* load the actual wayland backend and configure it */ ret = weston_compositor_load_backend(c, WESTON_BACKEND_WAYLAND, &config.base); @@ -3112,7 +3730,7 @@ load_wayland_backend(struct weston_compositor *c, continue; } - if (api->create_head(c, output_name) < 0) { + if (api->create_head(c->backend, output_name) < 0) { free(output_name); return -1; } @@ -3125,7 +3743,7 @@ load_wayland_backend(struct weston_compositor *c, if (asprintf(&output_name, "wayland%d", i) < 0) return -1; - if (api->create_head(c, output_name) < 0) { + if (api->create_head(c->backend, output_name) < 0) { free(output_name); return -1; } @@ -3137,24 +3755,48 @@ load_wayland_backend(struct weston_compositor *c, static int -load_backend(struct weston_compositor *compositor, const char *backend, - int *argc, char **argv, struct weston_config *config) -{ - if (strstr(backend, "headless-backend.so")) - 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")) - return load_x11_backend(compositor, argc, argv, config); - else if (strstr(backend, "wayland-backend.so")) - return load_wayland_backend(compositor, argc, argv, config); - - weston_log("Error: unknown backend \"%s\"\n", backend); - return -1; +load_backend(struct weston_compositor *compositor, const char *name, + int *argc, char **argv, struct weston_config *config, + const char *renderer_name) +{ + enum weston_compositor_backend backend; + enum weston_renderer_type renderer; + + if (!get_backend_from_string(name, &backend)) { + weston_log("Error: unknown backend \"%s\"\n", name); + return -1; + } + + if (!get_renderer_from_string(renderer_name, &renderer)) { + weston_log("Error: unknown renderer \"%s\"\n", renderer_name); + return -1; + } + + switch (backend) { + case WESTON_BACKEND_DRM: + return load_drm_backend(compositor, argc, argv, config, + renderer); + case WESTON_BACKEND_HEADLESS: + return load_headless_backend(compositor, argc, argv, config, + renderer); + case WESTON_BACKEND_PIPEWIRE: + return load_pipewire_backend(compositor, argc, argv, config, + renderer); + case WESTON_BACKEND_RDP: + return load_rdp_backend(compositor, argc, argv, config, + renderer); + case WESTON_BACKEND_VNC: + return load_vnc_backend(compositor, argc, argv, config, + renderer); + case WESTON_BACKEND_WAYLAND: + return load_wayland_backend(compositor, argc, argv, config, + renderer); + case WESTON_BACKEND_X11: + return load_x11_backend(compositor, argc, argv, config, + renderer); + default: + unreachable("unknown backend type in load_backend()"); + } } static char * @@ -3210,6 +3852,7 @@ execute_autolaunch(struct wet_compositor *wet, struct weston_config *config) weston_log("Failed to fork autolaunch process: %s\n", strerror(errno)); goto out; } else if (tmp_pid == 0) { + cleanup_for_child_process(); execl(autolaunch_path, autolaunch_path, NULL); /* execl shouldn't return */ fprintf(stderr, "Failed to execute autolaunch: %s\n", strerror(errno)); @@ -3266,16 +3909,46 @@ weston_log_subscribe_to_scopes(struct weston_log_context *log_ctx, weston_log_setup_scopes(log_ctx, flight_rec, flight_rec_scopes); } +static void +screenshot_allow_all(struct wl_listener *l, + struct weston_output_capture_attempt *att) +{ + /* + * The effect of --debug option: indiscriminately allow everyone to + * take screenshots of any output. + */ + att->authorized = true; +} + +static void +sigint_helper(int sig) +{ + raise(SIGUSR2); +} + +static void +wet_set_environment_variables(struct weston_compositor *c) +{ + struct weston_config_section *section; + + section = weston_config_get_section(wet_get_config(c), + "environment-variables", NULL, NULL); + if (section) { + weston_config_set_env(section); + } +} + 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; + char *renderer = NULL; char *shell = NULL; bool xwayland = false; char *modules = NULL; @@ -3302,12 +3975,14 @@ 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; const struct weston_option core_options[] = { { WESTON_OPTION_STRING, "backend", 'B', &backend }, + { WESTON_OPTION_STRING, "renderer", 0, &renderer }, { WESTON_OPTION_STRING, "shell", 0, &shell }, { WESTON_OPTION_STRING, "socket", 'S', &socket_name }, { WESTON_OPTION_INTEGER, "idle-time", 'i', &idle_time }, @@ -3392,16 +4067,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 @@ -3430,6 +4117,11 @@ wet_main(int argc, char *argv[], const struct weston_testsuite_data *test_data) raise(SIGSTOP); } + if (!renderer) { + weston_config_section_get_string(section, "renderer", + &renderer, NULL); + } + if (!backend) { weston_config_section_get_string(section, "backend", &backend, NULL); @@ -3451,8 +4143,12 @@ wet_main(int argc, char *argv[], const struct weston_testsuite_data *test_data) protologger = wl_display_add_protocol_logger(display, protocol_log_fn, NULL); - if (debug_protocol) + if (debug_protocol) { weston_compositor_enable_debug_protocol(wet.compositor); + weston_compositor_add_screenshot_authority(wet.compositor, + &wet.screenshot_auth, + screenshot_allow_all); + } if (flight_rec) weston_compositor_add_debug_binding(wet.compositor, KEY_D, @@ -3465,7 +4161,10 @@ wet_main(int argc, char *argv[], const struct weston_testsuite_data *test_data) weston_config_section_get_bool(section, "require-input", &wet.compositor->require_input, true); - if (load_backend(wet.compositor, backend, &argc, argv, config) < 0) { + wet_set_environment_variables(wet.compositor); + + if (load_backend(wet.compositor, backend, &argc, argv, config, + renderer) < 0) { weston_log("fatal: failed to create compositor backend\n"); goto out; } @@ -3517,18 +4216,15 @@ wet_main(int argc, char *argv[], const struct weston_testsuite_data *test_data) if (!shell) weston_config_section_get_string(section, "shell", &shell, - "desktop-shell.so"); + "desktop"); 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 +4234,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 +4276,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 +4283,7 @@ wet_main(int argc, char *argv[], const struct weston_testsuite_data *test_data) wl_protocol_logger_destroy(protologger); weston_compositor_destroy(wet.compositor); + wet_compositor_destroy_layout(&wet); weston_log_scope_destroy(protocol_scope); protocol_scope = NULL; @@ -3605,6 +4307,7 @@ wet_main(int argc, char *argv[], const struct weston_testsuite_data *test_data) weston_config_destroy(config); free(config_file); free(backend); + free(renderer); free(shell); free(socket_name); free(option_modules); diff --git a/compositor/meson.build b/compositor/meson.build index 8a54ea990..cf09150e2 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -2,13 +2,12 @@ srcs_weston = [ git_version_h, 'main.c', 'text-backend.c', + 'config-helpers.c', 'weston-screenshooter.c', text_input_unstable_v1_server_protocol_h, text_input_unstable_v1_protocol_c, input_method_unstable_v1_server_protocol_h, input_method_unstable_v1_protocol_c, - weston_screenshooter_server_protocol_h, - weston_screenshooter_protocol_c, ] deps_weston = [ dep_libshared, @@ -58,7 +57,6 @@ pkgconfig.generate( description: 'Header files for Weston plugin development', requires_private: [ lib_weston ], variables: [ - 'libexecdir=' + join_paths('${prefix}', get_option('libexecdir')), 'pkglibexecdir=${libexecdir}/weston' ], subdirs: 'weston' @@ -95,19 +93,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 +116,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 +132,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 +140,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 +155,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 8906c6ad7..783083029 100644 --- a/compositor/screen-share.c +++ b/compositor/screen-share.c @@ -43,23 +43,26 @@ #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 #include "fullscreen-shell-unstable-v1-client-protocol.h" struct shared_output { struct weston_output *output; struct wl_listener output_destroyed; struct wl_list seat_list; + struct wl_list output_link; /** screen_share::output_list */ struct { struct wl_display *display; 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 +117,8 @@ 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; + struct wl_list output_list; char *command; }; @@ -130,7 +132,7 @@ ss_seat_handle_pointer_enter(void *data, struct wl_pointer *pointer, /* No transformation of input position is required here because we are * always receiving the input in the same coordinates as the output. */ - notify_pointer_focus(&seat->base, NULL, 0, 0); + clear_pointer_focus(&seat->base); } static void @@ -139,7 +141,7 @@ ss_seat_handle_pointer_leave(void *data, struct wl_pointer *pointer, { struct ss_seat *seat = data; - notify_pointer_focus(&seat->base, NULL, 0, 0); + clear_pointer_focus(&seat->base); } static void @@ -148,14 +150,15 @@ ss_seat_handle_motion(void *data, struct wl_pointer *pointer, { struct ss_seat *seat = data; struct timespec ts; + struct weston_coord_global pos; timespec_from_msec(&ts, time); /* No transformation of input position is required here because we are * always receiving the input in the same coordinates as the output. */ - notify_motion_absolute(&seat->base, &ts, - wl_fixed_to_double(x), wl_fixed_to_double(y)); + pos.c = weston_coord_from_fixed(x, y); + notify_motion_absolute(&seat->base, &ts, pos); notify_pointer_frame(&seat->base); } @@ -368,7 +371,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 +705,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 = { @@ -820,14 +824,27 @@ shared_output_repainted(struct wl_listener *listener, void *data) { struct shared_output *so = container_of(listener, struct shared_output, frame_listener); - pixman_region32_t damage; - pixman_region32_t *current_damage = data; + pixman_region32_t sb_damage; + pixman_region32_t output_damage; + pixman_region32_t *global_output_damage; + struct weston_config *config; + struct weston_config_section *section; struct ss_shm_buffer *sb; int32_t x, y, width, height, stride; int i, nrects, do_yflip, y_orig; 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; + + uint32_t use_g2d; + + config = wet_get_config(so->output->compositor); + section = weston_config_get_section(config, "core", NULL, NULL); + + weston_config_section_get_uint(section, "use-g2d", &use_g2d, 0); width = so->output->current_mode->width; height = so->output->current_mode->height; @@ -846,49 +863,63 @@ shared_output_repainted(struct wl_listener *listener, void *data) if (!so->cache_image) goto err_shared_output; - pixman_region32_init_rect(&damage, 0, 0, width, height); + global_output_damage = &so->output->region; } else { - /* Damage in output coordinates */ - pixman_region32_init(&damage); - pixman_region32_intersect(&damage, &so->output->region, current_damage); - pixman_region32_translate(&damage, -so->output->x, -so->output->y); + global_output_damage = data; } + /* We want to calculate surface damage and store it for later. + * The buffers we use for the remote connection's surface are + * scale=1 and transform=normal, and cover the region the output + * covers in the compositor's global space. So if the output + * has a different scale or rotation, this is effectively undone + * (possibly by throwing away pixels in a later step). + * + * First, translate damage so the output's corner is the origin + * and store that in sb_damage. + */ + pixman_region32_init(&sb_damage); + pixman_region32_copy(&sb_damage, global_output_damage); + pixman_region32_translate(&sb_damage, -so->output->x, -so->output->y); /* Apply damage to all buffers */ wl_list_for_each(sb, &so->shm.buffers, link) - pixman_region32_union(&sb->damage, &sb->damage, &damage); + pixman_region32_union(&sb->damage, &sb->damage, &sb_damage); + + pixman_region32_fini(&sb_damage); - /* Transform to buffer coordinates */ - weston_transformed_region(so->output->width, so->output->height, - so->output->transform, - so->output->current_scale, - &damage, &damage); + /* Get damage in output coordinates */ + pixman_region32_init(&output_damage); + weston_region_global_to_output(&output_damage, so->output, + global_output_damage); - if (shared_output_ensure_tmp_data(so, &damage) < 0) + if (shared_output_ensure_tmp_data(so, &output_damage) < 0) goto err_pixman_init; do_yflip = !!(so->output->compositor->capabilities & WESTON_CAP_CAPTURE_YFLIP); - r = pixman_region32_rectangles(&damage, &nrects); + /* Create our cache image - a 1:1 copy of the output of interest's + * pixels from the output space. + */ + r = pixman_region32_rectangles(&output_damage, &nrects); for (i = 0; i < nrects; ++i) { x = r[i].x1; y = r[i].y1; width = r[i].x2 - r[i].x1; height = r[i].y2 - r[i].y1; - if (do_yflip) + if (do_yflip && !use_g2d) y_orig = so->output->current_mode->height - r[i].y2; else 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; @@ -917,19 +948,19 @@ shared_output_repainted(struct wl_listener *listener, void *data) so->cache_dirty = 1; - pixman_region32_fini(&damage); + pixman_region32_fini(&output_damage); shared_output_update(so); return; err_pixman_init: - pixman_region32_fini(&damage); + pixman_region32_fini(&output_damage); err_shared_output: shared_output_destroy(so); } static struct shared_output * -shared_output_create(struct weston_output *output, int parent_fd) +shared_output_create(struct weston_output *output, struct screen_share *ss, int parent_fd) { struct shared_output *so; struct wl_event_loop *loop; @@ -968,7 +999,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; @@ -1018,6 +1049,8 @@ shared_output_create(struct weston_output *output, int parent_fd) weston_output_disable_planes_incr(output); weston_output_damage(output); + wl_list_insert(&ss->output_list, &so->output_link); + return so; err_display: @@ -1035,6 +1068,7 @@ static void shared_output_destroy(struct shared_output *so) { struct ss_shm_buffer *buffer, *bnext; + struct ss_seat *seat, *tmp; weston_output_disable_planes_decr(so->output); @@ -1043,6 +1077,9 @@ shared_output_destroy(struct shared_output *so) wl_list_for_each_safe(buffer, bnext, &so->shm.free_buffers, free_link) ss_shm_buffer_destroy(buffer); + wl_list_for_each_safe(seat, tmp, &so->seat_list, link) + ss_seat_destroy(seat); + wl_display_disconnect(so->parent.display); wl_event_source_remove(so->event_source); @@ -1056,7 +1093,7 @@ shared_output_destroy(struct shared_output *so) } static struct shared_output * -weston_output_share(struct weston_output *output, const char* command) +weston_output_share(struct weston_output *output, struct screen_share *ss) { int sv[2]; char str[32]; @@ -1065,7 +1102,7 @@ weston_output_share(struct weston_output *output, const char* command) char *const argv[] = { "/bin/sh", "-c", - (char*)command, + (char*)ss->command, NULL }; @@ -1114,7 +1151,7 @@ weston_output_share(struct weston_output *output, const char* command) abort(); } else { close(sv[1]); - return shared_output_create(output, sv[0]); + return shared_output_create(output, ss, sv[0]); } return NULL; @@ -1125,12 +1162,9 @@ weston_output_find(struct weston_compositor *c, int32_t x, int32_t y) { struct weston_output *output; - wl_list_for_each(output, &c->output_list, link) { - if (x >= output->x && y >= output->y && - x < output->x + output->width && - y < output->y + output->height) + wl_list_for_each(output, &c->output_list, link) + if (weston_output_contains_point(output, x, y)) return output; - } return NULL; } @@ -1144,20 +1178,39 @@ 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, + pointer->pos.c.x, + pointer->pos.c.y); + } else { + output = weston_shell_utils_get_focused_output(keyboard->seat->compositor); + if (!output) + output = weston_shell_utils_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); + weston_output_share(output, ss); +} + +static void +compositor_destroy_listener(struct wl_listener *listener, void *data) +{ + struct shared_output *so, *so_next; + struct screen_share *ss = + wl_container_of(listener, ss, compositor_destroy_listener); + + wl_list_for_each_safe(so, so_next, &ss->output_list, output_link) + shared_output_destroy(so); + + wl_list_remove(&ss->compositor_destroy_listener.link); + + free(ss->command); + free(ss); } WL_EXPORT int @@ -1175,11 +1228,17 @@ wet_module_init(struct weston_compositor *compositor, return -1; ss->compositor = compositor; + wl_list_init(&ss->compositor_destroy_listener.link); + wl_list_init(&ss->output_list); + + 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, @@ -1189,7 +1248,7 @@ wet_module_init(struct weston_compositor *compositor, &start_on_startup, false); if (start_on_startup) { wl_list_for_each(output, &compositor->output_list, link) - weston_output_share(output, ss->command); + weston_output_share(output, ss); } return 0; diff --git a/compositor/text-backend.c b/compositor/text-backend.c index 5cd994cb1..6b8325b18 100644 --- a/compositor/text-backend.c +++ b/compositor/text-backend.c @@ -39,6 +39,7 @@ #include "input-method-unstable-v1-server-protocol.h" #include "shared/helpers.h" #include "shared/timespec-util.h" +#include "shared/xalloc.h" struct text_input_manager; struct input_method; @@ -141,6 +142,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) { @@ -406,9 +413,7 @@ static void text_input_manager_create_text_input(struct wl_client *client, wl_resource_get_user_data(resource); struct text_input *text_input; - text_input = zalloc(sizeof *text_input); - if (text_input == NULL) - return; + text_input = xzalloc(sizeof *text_input); text_input->resource = wl_resource_create(client, &zwp_text_input_v1_interface, 1, id); @@ -456,6 +461,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); } @@ -464,9 +471,7 @@ text_input_manager_create(struct weston_compositor *ec) { struct text_input_manager *text_input_manager; - text_input_manager = zalloc(sizeof *text_input_manager); - if (text_input_manager == NULL) - return; + text_input_manager = xzalloc(sizeof *text_input_manager); text_input_manager->ec = ec; @@ -806,9 +811,7 @@ input_method_context_create(struct text_input *input, if (!input_method->input_method_binding) return; - context = zalloc(sizeof *context); - if (context == NULL) - return; + context = xzalloc(sizeof *context); binding = input_method->input_method_binding; context->resource = @@ -949,7 +952,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 +992,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; @@ -1022,9 +1027,7 @@ text_backend_seat_created(struct text_backend *text_backend, struct input_method *input_method; struct weston_compositor *ec = seat->compositor; - input_method = zalloc(sizeof *input_method); - if (input_method == NULL) - return; + input_method = xzalloc(sizeof *input_method); input_method->seat = seat; input_method->input = NULL; @@ -1093,10 +1096,9 @@ 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) - return NULL; + text_backend = xzalloc(sizeof(*text_backend)); text_backend->compositor = ec; @@ -1110,7 +1112,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/compositor/weston-private.h b/compositor/weston-private.h new file mode 100644 index 000000000..066d07cf7 --- /dev/null +++ b/compositor/weston-private.h @@ -0,0 +1,42 @@ +/* + * 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 + +bool +get_backend_from_string(const char *name, + enum weston_compositor_backend *backend); + +bool +get_renderer_from_string(const char *name, + enum weston_renderer_type *renderer); + +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 18b2dca48..6c4012490 100644 --- a/compositor/weston-screenshooter.c +++ b/compositor/weston-screenshooter.c @@ -1,5 +1,6 @@ /* * Copyright © 2008-2011 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,91 +31,23 @@ #include #include "weston.h" -#include "weston-screenshooter-server-protocol.h" #include "shared/helpers.h" -#include struct screenshooter { struct weston_compositor *ec; - struct wl_global *global; struct wl_client *client; - struct weston_process process; - struct wl_listener destroy_listener; + struct wl_listener client_destroy_listener; + struct wl_listener compositor_destroy_listener; struct weston_recorder *recorder; + struct wl_listener authorization; }; static void -screenshooter_done(void *data, enum weston_screenshooter_outcome outcome) -{ - struct wl_resource *resource = data; - - switch (outcome) { - case WESTON_SCREENSHOOTER_SUCCESS: - weston_screenshooter_send_done(resource); - break; - case WESTON_SCREENSHOOTER_NO_MEMORY: - wl_resource_post_no_memory(resource); - break; - default: - break; - } -} - -static void -screenshooter_take_shot(struct wl_client *client, - struct wl_resource *resource, - struct wl_resource *output_resource, - struct wl_resource *buffer_resource) -{ - struct weston_output *output = - weston_head_from_resource(output_resource)->output; - struct weston_buffer *buffer = - weston_buffer_from_resource(buffer_resource); - - if (buffer == NULL) { - wl_resource_post_no_memory(resource); - return; - } - - weston_screenshooter_shoot(output, buffer, screenshooter_done, resource); -} - -struct weston_screenshooter_interface screenshooter_implementation = { - screenshooter_take_shot -}; - -static void -bind_shooter(struct wl_client *client, - void *data, uint32_t version, uint32_t id) -{ - struct screenshooter *shooter = data; - struct wl_resource *resource; - bool debug_enabled = - weston_compositor_is_debug_protocol_enabled(shooter->ec); - - resource = wl_resource_create(client, - &weston_screenshooter_interface, 1, id); - - if (!debug_enabled && !shooter->client) { - wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, - "screenshooter failed: permission denied. "\ - "Debug protocol must be enabled"); - return; - } else if (!debug_enabled && client != shooter->client) { - wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, - "screenshooter failed: permission denied."); - return; - } - - wl_resource_set_implementation(resource, &screenshooter_implementation, - data, NULL); -} - -static void -screenshooter_sigchld(struct weston_process *process, int status) +screenshooter_client_destroy(struct wl_listener *listener, void *data) { struct screenshooter *shooter = - container_of(process, struct screenshooter, process); + container_of(listener, struct screenshooter, + client_destroy_listener); shooter->client = NULL; } @@ -126,6 +59,9 @@ screenshooter_binding(struct weston_keyboard *keyboard, struct screenshooter *shooter = data; char *screenshooter_exe; + /* Don't start a screenshot whilst we already have one in progress */ + if (shooter->client) + return; screenshooter_exe = wet_get_bindir_path("weston-screenshooter"); if (!screenshooter_exe) { @@ -133,11 +69,16 @@ screenshooter_binding(struct weston_keyboard *keyboard, return; } - if (!shooter->client) - shooter->client = weston_client_launch(shooter->ec, - &shooter->process, - screenshooter_exe, screenshooter_sigchld); + shooter->client = weston_client_start(shooter->ec, + screenshooter_exe); free(screenshooter_exe); + + if (!shooter->client) + return; + + shooter->client_destroy_listener.notify = screenshooter_client_destroy; + wl_client_add_destroy_listener(shooter->client, + &shooter->client_destroy_listener); } static void @@ -164,15 +105,27 @@ recorder_binding(struct weston_keyboard *keyboard, const struct timespec *time, } } +static void +authorize_screenshooter(struct wl_listener *l, + struct weston_output_capture_attempt *att) +{ + struct screenshooter *shooter = wl_container_of(l, shooter, + authorization); + + if (shooter->client && att->who->client == shooter->client) + att->authorized = true; +} + static void screenshooter_destroy(struct wl_listener *listener, void *data) { struct screenshooter *shooter = - container_of(listener, struct screenshooter, destroy_listener); + container_of(listener, struct screenshooter, + compositor_destroy_listener); - wl_list_remove(&shooter->destroy_listener.link); + wl_list_remove(&shooter->compositor_destroy_listener.link); + wl_list_remove(&shooter->authorization.link); - wl_global_destroy(shooter->global); free(shooter); } @@ -187,14 +140,15 @@ screenshooter_create(struct weston_compositor *ec) shooter->ec = ec; - shooter->global = wl_global_create(ec->wl_display, - &weston_screenshooter_interface, 1, - shooter, bind_shooter); weston_compositor_add_key_binding(ec, KEY_S, MODIFIER_SUPER, screenshooter_binding, shooter); weston_compositor_add_key_binding(ec, KEY_R, MODIFIER_SUPER, recorder_binding, shooter); - shooter->destroy_listener.notify = screenshooter_destroy; - wl_signal_add(&ec->destroy_signal, &shooter->destroy_listener); + shooter->compositor_destroy_listener.notify = screenshooter_destroy; + wl_signal_add(&ec->destroy_signal, + &shooter->compositor_destroy_listener); + + weston_compositor_add_screenshot_authority(ec, &shooter->authorization, + authorize_screenshooter); } diff --git a/compositor/weston.h b/compositor/weston.h index 23f2b2c46..c068ae8e0 100644 --- a/compositor/weston.h +++ b/compositor/weston.h @@ -46,10 +46,14 @@ struct weston_process { struct wl_list link; }; -struct wl_client * +struct custom_env; + +bool weston_client_launch(struct weston_compositor *compositor, struct weston_process *proc, - const char *path, + struct custom_env *custom_env, + int *fds_no_cloexec, + size_t num_fds_no_cloexec, weston_process_cleanup_func_t cleanup); struct wl_client * @@ -62,9 +66,6 @@ wet_watch_process(struct weston_compositor *compositor, struct weston_config * wet_get_config(struct weston_compositor *compositor); -void * -wet_load_module_entrypoint(const char *name, const char *entrypoint); - int wet_shell_init(struct weston_compositor *ec, int *argc, char *argv[]); diff --git a/compositor/xwayland.c b/compositor/xwayland.c index 8cdf05556..44f020167 100644 --- a/compositor/xwayland.c +++ b/compositor/xwayland.c @@ -30,153 +30,203 @@ #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 void +xserver_cleanup(struct weston_process *process, int status) +{ + struct wet_xwayland *wxw = + container_of(process, struct wet_xwayland, process); + + wxw->api->xserver_exited(wxw->xwayland, status); + wxw->client = NULL; } static pid_t 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 = FDSTR_INIT; + struct fdstr x11_abstract_socket = FDSTR_INIT; + struct fdstr x11_unix_socket = FDSTR_INIT; + struct fdstr x11_wm_socket = FDSTR_INIT; + struct fdstr display_pipe = FDSTR_INIT; char *xserver = NULL; struct weston_config *config = wet_get_config(wxw->compositor); struct weston_config_section *section; + struct wl_event_loop *loop; + struct custom_env child_env; + int no_cloexec_fds[5]; + size_t num_no_cloexec_fds = 0; + int 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; + goto err; } + fdstr_update_str1(&wayland_socket); + no_cloexec_fds[num_no_cloexec_fds++] = wayland_socket.fds[1]; - 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; + goto err; } + fdstr_update_str1(&x11_wm_socket); + no_cloexec_fds[num_no_cloexec_fds++] = x11_wm_socket.fds[1]; - pid = fork(); - switch (pid) { - case 0: - /* 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); - - 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(wm[1]); - wxw->wm_fd = wm[0]; - - wxw->process.pid = pid; - wet_watch_process(wxw->compositor, &wxw->process); - break; - - case -1: - weston_log("Failed to fork to spawn xserver process\n"); - break; + if (pipe2(display_pipe.fds, O_CLOEXEC) < 0) { + weston_log("pipe creation for displayfd failed\n"); + goto err; + } + fdstr_update_str1(&display_pipe); + no_cloexec_fds[num_no_cloexec_fds++] = display_pipe.fds[1]; + + fdstr_set_fd1(&x11_abstract_socket, abstract_fd); + no_cloexec_fds[num_no_cloexec_fds++] = abstract_fd; + + fdstr_set_fd1(&x11_unix_socket, unix_fd); + no_cloexec_fds[num_no_cloexec_fds++] = unix_fd; + + assert(num_no_cloexec_fds <= ARRAY_LENGTH(no_cloexec_fds)); + + section = weston_config_get_section(config, "xwayland", NULL, NULL); + weston_config_section_get_string(section, "path", + &xserver, XSERVER_PATH); + 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"); + + ret = weston_client_launch(wxw->compositor, &wxw->process, &child_env, + no_cloexec_fds, num_no_cloexec_fds, + xserver_cleanup); + if (!ret) { + weston_log("Couldn't start Xwayland\n"); + goto err; + } + + wxw->client = wl_client_create(wxw->compositor->wl_display, + wayland_socket.fds[0]); + if (!wxw->client) { + weston_log("Couldn't create client for Xwayland\n"); + goto err; } - return pid; + wxw->wm_fd = x11_wm_socket.fds[0]; + + /* Now we can no longer fail, close the child end of our sockets */ + close(wayland_socket.fds[1]); + close(x11_wm_socket.fds[1]); + close(display_pipe.fds[1]); + + /* 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. */ + 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); + + free(xserver); + + return wxw->process.pid; + +err: + free(xserver); + fdstr_close_all(&display_pipe); + fdstr_close_all(&x11_wm_socket); + fdstr_close_all(&wayland_socket); + return -1; } static void -xserver_cleanup(struct weston_process *process, int status) +wxw_compositor_destroy(struct wl_listener *listener, void *data) { 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); + wl_container_of(listener, wxw, compositor_destroy_listener); - wxw->api->xserver_exited(wxw->xwayland, status); - wxw->sigusr1_source = wl_event_loop_add_signal(loop, SIGUSR1, - handle_sigusr1, wxw); - wxw->client = NULL; + 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 @@ -185,7 +235,6 @@ 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 +258,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/input-panel.c b/desktop-shell/input-panel.c index 0897ffdea..109f422ad 100644 --- a/desktop-shell/input-panel.c +++ b/desktop-shell/input-panel.c @@ -67,8 +67,10 @@ calc_input_panel_position(struct input_panel_surface *ip_surface, float *x, floa struct weston_view *view = get_default_view(shell->text_input.surface); if (view == NULL) return -1; - *x = view->geometry.x + shell->text_input.cursor_rectangle.x2; - *y = view->geometry.y + shell->text_input.cursor_rectangle.y2; + *x = view->geometry.pos_offset.x + + shell->text_input.cursor_rectangle.x2; + *y = view->geometry.pos_offset.y + + shell->text_input.cursor_rectangle.y2; } else { *x = ip_surface->output->x + (ip_surface->output->width - ip_surface->surface->width) / 2; *y = ip_surface->output->y + ip_surface->output->height - ip_surface->surface->height; @@ -103,8 +105,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) @@ -183,7 +185,8 @@ input_panel_get_label(struct weston_surface *surface, char *buf, size_t len) } static void -input_panel_committed(struct weston_surface *surface, int32_t sx, int32_t sy) +input_panel_committed(struct weston_surface *surface, + struct weston_coord_surface new_origin) { struct input_panel_surface *ip_surface = surface->committed_private; struct desktop_shell *shell = ip_surface->shell; diff --git a/desktop-shell/meson.build b/desktop-shell/meson.build index cc7215cb2..b0f1f9754 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,7 +13,6 @@ if get_option('shell-desktop') dep_libm, dep_libexec_weston, dep_libshared, - dep_lib_desktop, dep_libweston_public, ] plugin_shell_desktop = shared_library( diff --git a/desktop-shell/shell.c b/desktop-shell/shell.c index 63e143113..99ad3dfb0 100644 --- a/desktop-shell/shell.c +++ b/desktop-shell/shell.c @@ -41,9 +41,9 @@ #include "weston-desktop-shell-server-protocol.h" #include #include "shared/helpers.h" -#include "shared/shell-utils.h" #include "shared/timespec-util.h" -#include +#include +#include #define DEFAULT_NUM_WORKSPACES 1 #define DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH 200 @@ -110,11 +110,12 @@ struct shell_surface { struct wl_list children_list; struct wl_list children_link; - int32_t saved_x, saved_y; + struct weston_coord_global saved_pos; bool saved_position_valid; 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; @@ -162,6 +161,13 @@ struct shell_touch_grab { struct weston_touch *touch; }; +struct shell_tablet_tool_grab { + struct weston_tablet_tool_grab grab; + struct shell_surface *shsurf; + struct wl_listener shsurf_destroy_listener; + struct weston_tablet_tool *tool; +}; + struct weston_move_grab { struct shell_grab base; wl_fixed_t dx, dy; @@ -174,6 +180,11 @@ struct weston_touch_move_grab { wl_fixed_t dx, dy; }; +struct weston_tablet_tool_move_grab { + struct shell_tablet_tool_grab base; + wl_fixed_t dx, dy; +}; + struct rotate_grab { struct shell_grab base; struct weston_matrix rotation; @@ -191,10 +202,15 @@ struct shell_seat { struct wl_listener caps_changed_listener; struct wl_listener pointer_focus_listener; struct wl_listener keyboard_focus_listener; + struct wl_listener tablet_tool_added_listener; struct wl_list link; /** shell::seat_list */ }; +struct tablet_tool_listener { + struct wl_listener base; + struct wl_listener removed_listener; +}; static struct weston_view * shell_fade_create_fade_out_view(struct shell_surface *shsurf, @@ -262,6 +278,9 @@ desktop_shell_destroy_surface(struct shell_surface *shsurf) { struct shell_surface *shsurf_child, *tmp; + if (shsurf->fullscreen.black_view) + weston_shell_utils_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 +290,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); @@ -304,9 +323,7 @@ shell_grab_start(struct shell_grab *grab, weston_desktop_shell_send_grab_cursor(shell->child.desktop_shell, cursor); weston_pointer_set_focus(pointer, - get_default_view(shell->grab_surface), - wl_fixed_from_int(0), - wl_fixed_from_int(0)); + get_default_view(shell->grab_surface)); } } @@ -316,15 +333,20 @@ get_panel_size(struct desktop_shell *shell, int *width, int *height) { - float x1, y1; - float x2, y2; - weston_view_to_global_float(view, 0, 0, &x1, &y1); - weston_view_to_global_float(view, - view->surface->width, - view->surface->height, - &x2, &y2); - *width = (int)(x2 - x1); - *height = (int)(y2 - y1); + struct weston_coord_global a, b; + struct weston_coord_surface tmp_s; + + tmp_s = weston_coord_surface(0, 0, view->surface); + a = weston_coord_surface_to_global(view, tmp_s); + + tmp_s = weston_coord_surface(view->surface->width, + view->surface->height, + view->surface); + b = weston_coord_surface_to_global(view, tmp_s); + + a.c = weston_coord_sub(b.c, a.c); + *width = a.c.x; + *height = a.c.y; } static void @@ -441,22 +463,40 @@ shell_touch_grab_end(struct shell_touch_grab *grab) weston_touch_end_grab(grab->touch); } -static enum weston_keyboard_modifier -get_modifier(char *modifier) +static void +shell_tablet_tool_grab_start(struct shell_tablet_tool_grab *grab, + const struct weston_tablet_tool_grab_interface *interface, + struct shell_surface *shsurf, + struct weston_tablet_tool *tool) { - if (!modifier) - return MODIFIER_SUPER; + struct desktop_shell *shell = shsurf->shell; - 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; + weston_seat_break_desktop_grabs(tool->seat); + + grab->grab.interface = interface; + grab->shsurf = shsurf; + grab->shsurf_destroy_listener.notify = destroy_shell_grab_shsurf; + wl_signal_add(&shsurf->destroy_signal, &grab->shsurf_destroy_listener); + + grab->tool = tool; + shsurf->grabbed = 1; + + weston_tablet_tool_start_grab(tool, &grab->grab); + if (shell->child.desktop_shell) + weston_tablet_tool_set_focus(tool, + get_default_view(shell->grab_surface), + 0); +} + +static void +shell_tablet_tool_grab_end(struct shell_tablet_tool_grab *grab) +{ + if (grab->shsurf) { + wl_list_remove(&grab->shsurf_destroy_listener.link); + grab->shsurf->grabbed = 0; + } + + weston_tablet_tool_end_grab(grab->tool); } static enum animation_type @@ -479,11 +519,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 +534,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_config_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 +551,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 @@ -532,29 +562,15 @@ focus_surface_get_label(struct weston_surface *surface, char *buf, size_t len) /* no-op func for checking focus surface */ static void -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) +focus_surface_committed(struct weston_surface *es, + struct weston_coord_surface new_origin) { - 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 +578,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); + curtain_params.surface_private = fsurf; - 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); - - wl_list_init(&fsurf->workspace_transform.link); + fsurf->curtain = weston_shell_utils_curtain_create(ec, &curtain_params); + weston_view_set_output(fsurf->curtain->view, output); + fsurf->curtain->view->is_mapped = true; return fsurf; } @@ -607,7 +604,7 @@ create_focus_surface(struct weston_compositor *ec, static void focus_surface_destroy(struct focus_surface *fsurf) { - weston_surface_destroy(fsurf->surface); + weston_shell_utils_curtain_destroy(fsurf->curtain); free(fsurf); } @@ -682,8 +679,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 +790,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) @@ -830,24 +812,24 @@ animate_focus_change(struct desktop_shell *shell, struct workspace *ws, if (from == to || shell->focus_animation_type != ANIMATION_DIM_LAYER) return; - output = get_default_output(shell->compositor); + output = weston_shell_utils_get_default_output(shell->compositor); if (ws->fsurf_front == NULL && (from || to)) { 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 +839,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 +883,6 @@ workspace_destroy(struct workspace *ws) focus_surface_destroy(ws->fsurf_back); desktop_shell_destroy_layer(&ws->layer); - free(ws); } static void @@ -918,14 +899,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 +913,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 +941,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, @@ -1440,9 +1027,9 @@ surface_touch_move(struct shell_surface *shsurf, struct weston_touch *touch) return -1; move->active = 1; - move->dx = wl_fixed_from_double(shsurf->view->geometry.x) - + move->dx = wl_fixed_from_double(shsurf->view->geometry.pos_offset.x) - touch->grab_x; - move->dy = wl_fixed_from_double(shsurf->view->geometry.y) - + move->dy = wl_fixed_from_double(shsurf->view->geometry.pos_offset.y) - touch->grab_y; shell_touch_grab_start(&move->base, &touch_move_grab_interface, shsurf, @@ -1486,8 +1073,8 @@ constrain_position(struct weston_move_grab *move, int *cx, int *cy) pixman_rectangle32_t area; struct weston_geometry geometry; - x = wl_fixed_to_int(pointer->x + move->dx); - y = wl_fixed_to_int(pointer->y + move->dy); + x = pointer->pos.c.x + wl_fixed_to_double(move->dx); + y = pointer->pos.c.y + wl_fixed_to_double(move->dy); if (shsurf->shell->panel_position == WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP) { @@ -1574,6 +1161,7 @@ surface_move(struct shell_surface *shsurf, struct weston_pointer *pointer, bool client_initiated) { struct weston_move_grab *move; + struct weston_coord offset; if (!shsurf) return -1; @@ -1587,12 +1175,15 @@ surface_move(struct shell_surface *shsurf, struct weston_pointer *pointer, if (!move) return -1; - move->dx = wl_fixed_from_double(shsurf->view->geometry.x) - - pointer->grab_x; - move->dy = wl_fixed_from_double(shsurf->view->geometry.y) - - pointer->grab_y; + offset = weston_coord_sub(shsurf->view->geometry.pos_offset, + pointer->grab_pos.c); + move->dx = wl_fixed_from_double(offset.x); + move->dy = wl_fixed_from_double(offset.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); @@ -1605,6 +1196,148 @@ struct weston_resize_grab { int32_t width, height; }; +static void +tablet_tool_noop_grab_proximity_in(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + struct weston_tablet *tablet) +{ +} + +static void +tablet_tool_move_grab_proximity_out(struct weston_tablet_tool_grab *grab, + const struct timespec *time) +{ + struct weston_tablet_tool_move_grab *move = + (struct weston_tablet_tool_move_grab *)grab; + + shell_tablet_tool_grab_end(&move->base); + free(grab); +} + +static void +tablet_tool_move_grab_up(struct weston_tablet_tool_grab *grab, + const struct timespec *time) +{ + struct weston_tablet_tool_move_grab *move = + (struct weston_tablet_tool_move_grab *)grab; + + shell_tablet_tool_grab_end(&move->base); + free(grab); +} + +static void +tablet_tool_noop_grab_down(struct weston_tablet_tool_grab *grab, + const struct timespec *time) +{ +} + +static void +tablet_tool_move_grab_motion(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + struct weston_coord_global pos) +{ + struct weston_tablet_tool_move_grab *move = + (struct weston_tablet_tool_move_grab *)grab; + struct shell_surface *shsurf = move->base.shsurf; + struct weston_surface *es; + + weston_tablet_tool_cursor_move(grab->tool, pos); + + if (!shsurf) + return; + + es = weston_desktop_surface_get_surface(shsurf->desktop_surface); + weston_view_set_position(shsurf->view, + pos.c.x + wl_fixed_to_double(move->dx), + pos.c.y + wl_fixed_to_double(move->dy)); + weston_compositor_schedule_repaint(es->compositor); +} + +static void +tablet_tool_noop_grab_pressure(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + uint32_t pressure) +{ +} + +static void +tablet_tool_noop_grab_distance(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + uint32_t distance) +{ +} + +static void +tablet_tool_noop_grab_tilt(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + int32_t tilt_x, int32_t tilt_y) +{ +} + +static void tablet_tool_noop_grab_button(struct weston_tablet_tool_grab *grab, + const struct timespec *time, uint32_t button, + uint32_t state) +{ +} + +static void +tablet_tool_noop_grab_frame(struct weston_tablet_tool_grab *grab, + const struct timespec *time) +{ +} + +static void +tablet_tool_move_grab_cancel(struct weston_tablet_tool_grab *grab) +{ + struct weston_tablet_tool_move_grab *move = + (struct weston_tablet_tool_move_grab *)grab; + + shell_tablet_tool_grab_end(&move->base); + free(grab); +} + +static struct weston_tablet_tool_grab_interface tablet_tool_move_grab_interface = { + tablet_tool_noop_grab_proximity_in, + tablet_tool_move_grab_proximity_out, + tablet_tool_move_grab_motion, + tablet_tool_noop_grab_down, + tablet_tool_move_grab_up, + tablet_tool_noop_grab_pressure, + tablet_tool_noop_grab_distance, + tablet_tool_noop_grab_tilt, + tablet_tool_noop_grab_button, + tablet_tool_noop_grab_frame, + tablet_tool_move_grab_cancel, +}; + +static int +surface_tablet_tool_move(struct shell_surface *shsurf, struct weston_tablet_tool *tool) +{ + struct weston_tablet_tool_move_grab *move; + struct weston_coord offset; + + if (!shsurf) + return -1; + + if (shsurf->state.fullscreen || shsurf->state.maximized) + return 0; + + move = malloc(sizeof(*move)); + if (!move) + return -1; + + offset = weston_coord_sub(shsurf->view->geometry.pos_offset, + tool->grab_pos.c); + move->dx = wl_fixed_from_double(offset.x); + move->dy = wl_fixed_from_double(offset.y); + + shell_tablet_tool_grab_start(&move->base, &tablet_tool_move_grab_interface, + shsurf, tool); + + return 0; +} + + static void resize_grab_motion(struct weston_pointer_grab *grab, const struct timespec *time, @@ -1615,31 +1348,35 @@ resize_grab_motion(struct weston_pointer_grab *grab, struct shell_surface *shsurf = resize->base.shsurf; int32_t width, height; struct weston_size min_size, max_size; + struct weston_coord_surface tmp_s; wl_fixed_t from_x, from_y; wl_fixed_t to_x, to_y; weston_pointer_move(pointer, event); - if (!shsurf) + if (!shsurf || !shsurf->desktop_surface) return; - weston_view_from_global_fixed(shsurf->view, - pointer->grab_x, pointer->grab_y, - &from_x, &from_y); - weston_view_from_global_fixed(shsurf->view, - pointer->x, pointer->y, &to_x, &to_y); + weston_view_update_transform(shsurf->view); + + tmp_s = weston_coord_global_to_surface(shsurf->view, pointer->grab_pos); + from_x = wl_fixed_from_double(tmp_s.c.x); + from_y = wl_fixed_from_double(tmp_s.c.y); + tmp_s = weston_coord_global_to_surface(shsurf->view, pointer->pos); + to_x = wl_fixed_from_double(tmp_s.c.x); + to_y = wl_fixed_from_double(tmp_s.c.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 +1392,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 +1408,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 +1426,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 +1454,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 +1466,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 +1483,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); @@ -1757,11 +1499,9 @@ busy_cursor_grab_focus(struct weston_pointer_grab *base) struct weston_pointer *pointer = base->pointer; struct weston_desktop_surface *desktop_surface; struct weston_view *view; - wl_fixed_t sx, sy; view = weston_compositor_pick_view(pointer->seat->compositor, - pointer->x, pointer->y, - &sx, &sy); + pointer->pos); desktop_surface = weston_surface_get_desktop_surface(view->surface); if (!grab->shsurf || grab->shsurf->desktop_surface != desktop_surface) { @@ -1841,18 +1581,95 @@ handle_pointer_focus(struct wl_listener *listener, void *data) weston_desktop_client_ping(client); } +static void +has_keyboard_focused_child_callback(struct weston_desktop_surface *surface, + void *user_data); + +static void +has_keyboard_focused_child_callback(struct weston_desktop_surface *surface, + void *user_data) +{ + struct weston_surface *es = weston_desktop_surface_get_surface(surface); + struct shell_surface *shsurf = get_shell_surface(es); + bool *has_keyboard_focus = user_data; + + if (shsurf->focus_count > 0) { + *has_keyboard_focus = true; + return; + } + + weston_desktop_surface_foreach_child(shsurf->desktop_surface, + has_keyboard_focused_child_callback, + &has_keyboard_focus); +} + +static bool +has_keyboard_focused_child(struct shell_surface *shsurf) +{ + bool has_keyboard_focus = false; + + if (shsurf->focus_count > 0) + return true; + + weston_desktop_surface_foreach_child(shsurf->desktop_surface, + has_keyboard_focused_child_callback, + &has_keyboard_focus); + + return has_keyboard_focus; +} + +static void +sync_surface_activated_state(struct shell_surface *shsurf) +{ + struct weston_desktop_surface *surface = shsurf->desktop_surface; + struct weston_desktop_surface *parent; + struct weston_surface *parent_surface; + + parent = weston_desktop_surface_get_parent(surface); + if (parent) { + parent_surface = weston_desktop_surface_get_surface(parent); + sync_surface_activated_state(get_shell_surface(parent_surface)); + return; + } + + if (has_keyboard_focused_child(shsurf)) + weston_desktop_surface_set_activated(surface, true); + else + weston_desktop_surface_set_activated(surface, false); +} + +static void +handle_tablet_tool_focus(struct wl_listener *listener, void *data) +{ + struct weston_tablet_tool *tool = data; + struct weston_view *view = tool->focus; + struct shell_surface *shsurf; + struct weston_desktop_client *client; + + if (!view) + return; + + shsurf = get_shell_surface(view->surface); + if (!shsurf) + return; + + client = weston_desktop_surface_get_client(shsurf->desktop_surface); + + weston_desktop_client_ping(client); +} + static void shell_surface_deactivate(struct shell_surface *shsurf) { if (--shsurf->focus_count == 0) - weston_desktop_surface_set_activated(shsurf->desktop_surface, false); + sync_surface_activated_state(shsurf); } static void shell_surface_activate(struct shell_surface *shsurf) { if (shsurf->focus_count++ == 0) - weston_desktop_surface_set_activated(shsurf->desktop_surface, true); + sync_surface_activated_state(shsurf); } /* The surface will be inserted into the list immediately after the link @@ -1934,7 +1751,7 @@ shell_surface_set_output(struct shell_surface *shsurf, else if (es->output) shsurf->output = es->output; else - shsurf->output = get_default_output(es->compositor); + shsurf->output = weston_shell_utils_get_default_output(es->compositor); if (shsurf->output_destroy_listener.notify) { wl_list_remove(&shsurf->output_destroy_listener.link); @@ -1961,16 +1778,20 @@ 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_shell_utils_curtain_destroy(shsurf->fullscreen.black_view); shsurf->fullscreen.black_view = NULL; if (shsurf->saved_position_valid) weston_view_set_position(shsurf->view, - shsurf->saved_x, shsurf->saved_y); + shsurf->saved_pos.c.x, + shsurf->saved_pos.c.y); else 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); @@ -1985,15 +1806,20 @@ unset_maximized(struct shell_surface *shsurf) weston_desktop_surface_get_surface(shsurf->desktop_surface); /* undo all maximized things here */ - shell_surface_set_output(shsurf, get_default_output(surface->compositor)); + shell_surface_set_output(shsurf, + weston_shell_utils_get_default_output(surface->compositor)); if (shsurf->saved_position_valid) weston_view_set_position(shsurf->view, - shsurf->saved_x, shsurf->saved_y); + shsurf->saved_pos.c.x, + shsurf->saved_pos.c.y); else 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 +1895,22 @@ 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, + struct weston_coord_surface new_origin) { - 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 +1918,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_shell_utils_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; } @@ -2143,11 +1973,11 @@ shell_configure_fullscreen(struct shell_surface *shsurf) shell_ensure_fullscreen_black_view(shsurf); - surface_subsurfaces_boundingbox(surface, &surf_x, &surf_y, - &surf_width, &surf_height); + weston_shell_utils_subsurfaces_boundingbox(surface, &surf_x, &surf_y, + &surf_width, &surf_height); - if (surface->buffer_ref.buffer) - center_on_output(shsurf->view, shsurf->fullscreen_output); + if (weston_surface_has_content(surface)) + weston_shell_utils_center_on_output(shsurf->view, shsurf->fullscreen_output); } static void @@ -2164,11 +1994,43 @@ desktop_shell_destroy_seat(struct shell_seat *shseat) wl_list_remove(&shseat->caps_changed_listener.link); wl_list_remove(&shseat->pointer_focus_listener.link); wl_list_remove(&shseat->seat_destroy_listener.link); + wl_list_remove(&shseat->tablet_tool_added_listener.link); wl_list_remove(&shseat->link); free(shseat); } +static void +destroy_tablet_tool_listener(struct wl_listener *listener, void *data) +{ + struct tablet_tool_listener *tool_listener = + container_of(listener, struct tablet_tool_listener, removed_listener); + + wl_list_remove(&tool_listener->removed_listener.link); + wl_list_remove(&tool_listener->base.link); + free(tool_listener); +} + +static void +handle_tablet_tool_added(struct wl_listener *listener, void *data) +{ + struct weston_tablet_tool *tool = data; + struct tablet_tool_listener *tool_listener; + + tool_listener = malloc(sizeof *tool_listener); + if (!tool_listener) { + weston_log("no memory to allocate to shell seat tablet listener\n"); + return; + } + + tool_listener->removed_listener.notify = destroy_tablet_tool_listener; + wl_signal_add(&tool->removed_signal, + &tool_listener->removed_listener); + + tool_listener->base.notify = handle_tablet_tool_focus; + wl_signal_add(&tool->focus_signal, &tool_listener->base); +} + static void destroy_shell_seat(struct wl_listener *listener, void *data) { @@ -2202,6 +2064,7 @@ static struct shell_seat * create_shell_seat(struct desktop_shell *shell, struct weston_seat *seat) { struct shell_seat *shseat; + struct weston_tablet_tool *tool; shseat = calloc(1, sizeof *shseat); if (!shseat) { @@ -2220,6 +2083,25 @@ create_shell_seat(struct desktop_shell *shell, struct weston_seat *seat) shseat->pointer_focus_listener.notify = handle_pointer_focus; wl_list_init(&shseat->pointer_focus_listener.link); + shseat->tablet_tool_added_listener.notify = handle_tablet_tool_added; + wl_list_init(&shseat->tablet_tool_added_listener.link); + + wl_list_for_each(tool, &seat->tablet_tool_list, link) { + struct tablet_tool_listener *listener = malloc(sizeof *listener); + + if (!listener) { + weston_log("no memory to allocate to shell seat tablet listener\n"); + break; + } + + listener->removed_listener.notify = destroy_tablet_tool_listener; + wl_signal_add(&tool->removed_signal, + &listener->removed_listener); + + listener->base.notify = handle_tablet_tool_focus; + wl_signal_add(&tool->focus_signal, &listener->base); + } + shseat->caps_changed_listener.notify = shell_seat_caps_changed; wl_signal_add(&seat->updated_caps_signal, &shseat->caps_changed_listener); @@ -2308,7 +2190,7 @@ desktop_surface_added(struct weston_desktop_surface *desktop_surface, return; } - weston_surface_set_label_func(surface, surface_get_label); + weston_surface_set_label_func(surface, weston_shell_utils_surface_get_label); shsurf->shell = (struct desktop_shell *) shell; shsurf->unresponsive = 0; @@ -2320,7 +2202,7 @@ desktop_surface_added(struct weston_desktop_surface *desktop_surface, wl_list_init(&shsurf->fullscreen.transform.link); shell_surface_set_output( - shsurf, get_default_output(shsurf->shell->compositor)); + shsurf, weston_shell_utils_get_default_output(shsurf->shell->compositor)); wl_signal_init(&shsurf->destroy_signal); @@ -2328,8 +2210,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 +2245,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_shell_utils_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); @@ -2388,8 +2270,8 @@ desktop_surface_removed(struct weston_desktop_surface *desktop_surface, weston_view_set_output(shsurf->wview_anim_fade, shsurf->view->output); weston_view_set_position(shsurf->wview_anim_fade, - shsurf->view->geometry.x, - shsurf->view->geometry.y); + shsurf->view->geometry.pos_offset.x, + shsurf->view->geometry.pos_offset.y); weston_layer_entry_insert(&shsurf->view->layer_link, &shsurf->wview_anim_fade->layer_link); @@ -2444,8 +2326,7 @@ set_position_from_xwayland(struct shell_surface *shsurf) } static void -map(struct desktop_shell *shell, struct shell_surface *shsurf, - int32_t sx, int32_t sy) +map(struct desktop_shell *shell, struct shell_surface *shsurf) { struct weston_surface *surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); @@ -2454,7 +2335,8 @@ map(struct desktop_shell *shell, struct shell_surface *shsurf, /* initial positioning, see also configure() */ if (shsurf->state.fullscreen) { - center_on_output(shsurf->view, shsurf->fullscreen_output); + weston_shell_utils_center_on_output(shsurf->view, + shsurf->fullscreen_output); shell_map_fullscreen(shsurf); } else if (shsurf->state.maximized) { set_maximized_position(shell, shsurf); @@ -2474,6 +2356,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, @@ -2508,8 +2392,16 @@ desktop_surface_committed(struct weston_desktop_surface *desktop_surface, bool was_fullscreen; bool was_maximized; - if (surface->width == 0) + if (!weston_surface_has_content(surface) && + weston_surface_is_unmapping(surface) && + shsurf->state.fullscreen) { + unset_fullscreen(shsurf); + return; + } + + if (surface->width == 0) { return; + } was_fullscreen = shsurf->state.fullscreen; was_maximized = shsurf->state.maximized; @@ -2520,16 +2412,16 @@ desktop_surface_committed(struct weston_desktop_surface *desktop_surface, weston_desktop_surface_get_maximized(desktop_surface); if (!weston_surface_is_mapped(surface)) { - map(shell, shsurf, sx, sy); - surface->is_mapped = true; + map(shell, shsurf); /* 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; } @@ -2547,8 +2439,7 @@ desktop_surface_committed(struct weston_desktop_surface *desktop_surface, if ((shsurf->state.fullscreen || shsurf->state.maximized) && !shsurf->saved_position_valid) { - shsurf->saved_x = shsurf->view->geometry.x; - shsurf->saved_y = shsurf->view->geometry.y; + shsurf->saved_pos.c = shsurf->view->geometry.pos_offset; shsurf->saved_position_valid = true; if (!wl_list_empty(&shsurf->rotation.transform.link)) { @@ -2559,32 +2450,39 @@ desktop_surface_committed(struct weston_desktop_surface *desktop_surface, } } + weston_view_update_transform(shsurf->view); + if (shsurf->state.fullscreen) { shell_configure_fullscreen(shsurf); } else if (shsurf->state.maximized) { set_maximized_position(shell, shsurf); surface->output = shsurf->output; } else { - float from_x, from_y; - float to_x, to_y; - float x, y; + struct weston_coord_surface from_s, to_s; + struct weston_coord_global to_g, from_g; + struct weston_coord_global offset, pos; + + from_s = weston_coord_surface(0, 0, view->surface); if (shsurf->resize_edges) { sx = 0; 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); - weston_view_to_global_float(shsurf->view, sx, sy, &to_x, &to_y); - x = shsurf->view->geometry.x + to_x - from_x; - y = shsurf->view->geometry.y + to_y - from_y; + to_s = weston_coord_surface(sx, sy, view->surface); + + from_g = weston_coord_surface_to_global(view, from_s); + to_g = weston_coord_surface_to_global(view, to_s); - weston_view_set_position(shsurf->view, x, y); + offset.c = weston_coord_sub(to_g.c, from_g.c); + pos.c = weston_coord_add(view->geometry.pos_offset, offset.c); + + weston_view_set_position(shsurf->view, pos.c.x, pos.c.y); } shsurf->last_width = surface->width; @@ -2623,7 +2521,7 @@ set_fullscreen(struct shell_surface *shsurf, bool fullscreen, /* handle clients launching in fullscreen */ if (output == NULL && !weston_surface_is_mapped(surface)) { /* Set the output to the one that has focus currently. */ - output = get_focused_output(surface->compositor); + output = weston_shell_utils_get_focused_output(surface->compositor); } shell_surface_set_output(shsurf, output); @@ -2633,7 +2531,10 @@ set_fullscreen(struct shell_surface *shsurf, bool fullscreen, width = shsurf->output->width; height = shsurf->output->height; } - } else if (weston_desktop_surface_get_maximized(desktop_surface)) { + weston_desktop_surface_set_orientation(shsurf->desktop_surface, + WESTON_TOP_LEVEL_TILED_ORIENTATION_NONE); + } else if (weston_desktop_surface_get_maximized(desktop_surface) || + weston_desktop_surface_get_pending_maximized(desktop_surface)) { get_maximized_size(shsurf, &width, &height); } weston_desktop_surface_set_fullscreen(desktop_surface, fullscreen); @@ -2668,6 +2569,18 @@ desktop_surface_move(struct weston_desktop_surface *desktop_surface, if ((focus == surface) && (surface_touch_move(shsurf, touch) < 0)) wl_resource_post_no_memory(resource); + } else if (!wl_list_empty(&seat->tablet_tool_list)) { + struct weston_tablet_tool *tool; + + wl_list_for_each(tool, &seat->tablet_tool_list, link) { + if (tool->focus && tool->grab_serial == serial) { + focus = weston_surface_get_main_surface( + tool->focus->surface); + if (focus == surface && + surface_tablet_tool_move(shsurf, tool) < 0) + wl_resource_post_no_memory(resource); + } + } } } @@ -2738,17 +2651,23 @@ 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; if (!weston_surface_is_mapped(surface)) - output = get_focused_output(surface->compositor); + output = weston_shell_utils_get_focused_output(surface->compositor); else output = surface->output; 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 +2813,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.pos_offset.x; + *y = shsurf->view->geometry.pos_offset.y; +} + static const struct weston_desktop_api shell_desktop_api = { .struct_size = sizeof(struct weston_desktop_api), .surface_added = desktop_surface_added, @@ -2908,6 +2838,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,11 +2861,12 @@ 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)) { weston_layer_entry_insert(&layer->view_list, &ev->layer_link); + weston_view_geometry_dirty(ev); weston_compositor_schedule_repaint(ev->surface->compositor); } } @@ -2962,7 +2894,8 @@ background_get_label(struct weston_surface *surface, char *buf, size_t len) } static void -background_committed(struct weston_surface *es, int32_t sx, int32_t sy) +background_committed(struct weston_surface *es, + struct weston_coord_surface new_origin) { struct desktop_shell *shell = es->committed_private; struct weston_view *view; @@ -2983,6 +2916,37 @@ handle_background_surface_destroy(struct wl_listener *listener, void *data) output->background_surface = NULL; } +static void +desktop_shell_set_surface_size(struct desktop_shell *shell, + struct weston_surface *surface) +{ + struct weston_config_section *section; + if(surface->output && + surface->output->transform == WL_OUTPUT_TRANSFORM_NORMAL) { + section = weston_config_get_section(wet_get_config(shell->compositor), + "shell", NULL, NULL); + if (section) { + char *size; + int n; + int32_t width, height; + + weston_config_section_get_string(section, "size", &size, NULL); + + if(size){ + n = sscanf(size, "%dx%d", &width, &height); + if (n == 2) { + if (surface->output->width > width && + surface->output->height > height) { + surface->output->width = width; + surface->output->height = height; + } + } + free(size); + } + } + } +} + static void desktop_shell_set_background(struct wl_client *client, struct wl_resource *resource, @@ -3012,6 +2976,8 @@ desktop_shell_set_background(struct wl_client *client, surface->output = weston_head_from_resource(output_resource)->output; weston_view_set_output(view, surface->output); + desktop_shell_set_surface_size(shell, surface); + sh_output = find_shell_output_from_weston_output(shell, surface->output); if (sh_output->background_surface) { /* The output already has a background, tell our helper @@ -3042,7 +3008,8 @@ panel_get_label(struct weston_surface *surface, char *buf, size_t len) } static void -panel_committed(struct weston_surface *es, int32_t sx, int32_t sy) +panel_committed(struct weston_surface *es, + struct weston_coord_surface new_origin) { struct desktop_shell *shell = es->committed_private; struct weston_view *view; @@ -3051,17 +3018,16 @@ panel_committed(struct weston_surface *es, int32_t sx, int32_t sy) view = container_of(es->views.next, struct weston_view, surface_link); - get_panel_size(shell, view, &width, &height); switch (shell->panel_position) { case WESTON_DESKTOP_SHELL_PANEL_POSITION_TOP: break; case WESTON_DESKTOP_SHELL_PANEL_POSITION_BOTTOM: - y = view->output->height - height; + y = view->output->height - es->height; break; case WESTON_DESKTOP_SHELL_PANEL_POSITION_LEFT: break; case WESTON_DESKTOP_SHELL_PANEL_POSITION_RIGHT: - x = view->output->width - width; + x = view->output->width - es->width; break; } @@ -3109,6 +3075,8 @@ desktop_shell_set_panel(struct wl_client *client, surface->output = weston_head_from_resource(output_resource)->output; weston_view_set_output(view, surface->output); + desktop_shell_set_surface_size(shell, surface); + sh_output = find_shell_output_from_weston_output(shell, surface->output); if (sh_output->panel_surface) { /* The output already has a panel, tell our helper @@ -3136,7 +3104,8 @@ lock_surface_get_label(struct weston_surface *surface, char *buf, size_t len) } static void -lock_surface_committed(struct weston_surface *surface, int32_t sx, int32_t sy) +lock_surface_committed(struct weston_surface *surface, + struct weston_coord_surface new_origin) { struct desktop_shell *shell = surface->committed_private; struct weston_view *view; @@ -3146,13 +3115,14 @@ lock_surface_committed(struct weston_surface *surface, int32_t sx, int32_t sy) if (surface->width == 0) return; - center_on_output(view, get_default_output(shell->compositor)); + weston_shell_utils_center_on_output(view, + weston_shell_utils_get_default_output(shell->compositor)); if (!weston_surface_is_mapped(surface)) { 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 +3317,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) { @@ -3380,6 +3428,7 @@ resize_binding(struct weston_pointer *pointer, const struct timespec *time, uint32_t edges = 0; int32_t x, y; struct shell_surface *shsurf; + struct weston_coord_surface surf_pos; if (pointer->focus == NULL) return; @@ -3396,24 +3445,23 @@ resize_binding(struct weston_pointer *pointer, const struct timespec *time, weston_desktop_surface_get_maximized(shsurf->desktop_surface)) return; - weston_view_from_global(shsurf->view, - wl_fixed_to_int(pointer->grab_x), - wl_fixed_to_int(pointer->grab_y), - &x, &y); + surf_pos = weston_coord_global_to_surface(shsurf->view, pointer->grab_pos); + x = surf_pos.c.x; + y = surf_pos.c.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 +3497,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) @@ -3545,8 +3528,8 @@ rotate_grab_motion(struct weston_pointer_grab *grab, cx = 0.5f * surface->width; cy = 0.5f * surface->height; - dx = wl_fixed_to_double(pointer->x) - rotate->center.x; - dy = wl_fixed_to_double(pointer->y) - rotate->center.y; + dx = pointer->pos.c.x - rotate->center.x; + dy = pointer->pos.c.y - rotate->center.y; r = sqrtf(dx * dx + dy * dy); wl_list_remove(&shsurf->rotation.transform.link); @@ -3576,14 +3559,14 @@ rotate_grab_motion(struct weston_pointer_grab *grab, /* We need to adjust the position of the surface * in case it was resized in a rotated state before */ - cposx = shsurf->view->geometry.x + cx; - cposy = shsurf->view->geometry.y + cy; + cposx = shsurf->view->geometry.pos_offset.x + cx; + cposy = shsurf->view->geometry.pos_offset.y + cy; dposx = rotate->center.x - cposx; dposy = rotate->center.y - cposy; if (dposx != 0.0f || dposy != 0.0f) { weston_view_set_position(shsurf->view, - shsurf->view->geometry.x + dposx, - shsurf->view->geometry.y + dposy); + shsurf->view->geometry.pos_offset.x + dposx, + shsurf->view->geometry.pos_offset.y + dposy); } /* Repaint implies weston_view_update_transform(), which @@ -3639,6 +3622,8 @@ surface_rotate(struct shell_surface *shsurf, struct weston_pointer *pointer) struct weston_surface *surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); struct rotate_grab *rotate; + struct weston_coord_surface center; + struct weston_coord_global center_g; float dx, dy; float r; @@ -3646,13 +3631,16 @@ surface_rotate(struct shell_surface *shsurf, struct weston_pointer *pointer) if (!rotate) return; - weston_view_to_global_float(shsurf->view, - surface->width * 0.5f, - surface->height * 0.5f, - &rotate->center.x, &rotate->center.y); + center = weston_coord_surface(surface->width * 0.5f, + surface->height * 0.5f, + shsurf->view->surface); + center_g = weston_coord_surface_to_global(shsurf->view, center); - dx = wl_fixed_to_double(pointer->x) - rotate->center.x; - dy = wl_fixed_to_double(pointer->y) - rotate->center.y; + rotate->center.x = center_g.c.x; + rotate->center.y = center_g.c.y; + + dx = pointer->pos.c.x - rotate->center.x; + dy = pointer->pos.c.y - rotate->center.y; r = sqrtf(dx * dx + dy * dy); if (r > 20.0f) { struct weston_matrix inverse; @@ -3734,10 +3722,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 +3779,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 +3813,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, @@ -3894,6 +3864,20 @@ touch_to_activate_binding(struct weston_touch *touch, WESTON_ACTIVATE_FLAG_CONFIGURE); } +static void +tablet_tool_activate_binding(struct weston_tablet_tool *tool, + uint32_t button, void *data) +{ + if (tool->grab != &tool->default_grab) + return; + if (tool->focus == NULL) + return; + + activate_binding(tool->seat, data, tool->focus, + WESTON_ACTIVATE_FLAG_CLICKED | + WESTON_ACTIVATE_FLAG_CONFIGURE); +} + static void unfocus_all_seats(struct desktop_shell *shell) { @@ -3980,8 +3964,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_shell_utils_curtain_destroy(shell_output->fade.curtain); + shell_output->fade.curtain = NULL; break; case FADE_OUT: lock(shell); @@ -3991,33 +3975,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_shell_utils_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 * @@ -4031,13 +4027,13 @@ shell_fade_create_fade_out_view(struct shell_surface *shsurf, if (!view) return NULL; - woutput = get_focused_output(surface->compositor); + woutput = weston_shell_utils_get_focused_output(surface->compositor); /* set the initial position and output just in case we happen to not * move it around and just destroy it */ weston_view_set_output(view, woutput); weston_view_set_position(view, - shsurf->view->geometry.x, - shsurf->view->geometry.y); + shsurf->view->geometry.pos_offset.x, + shsurf->view->geometry.pos_offset.y); view->is_mapped = true; return view; @@ -4065,28 +4061,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_shell_utils_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 +4103,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_shell_utils_curtain_destroy(shell_output->fade.curtain); + shell_output->fade.curtain = NULL; } } } @@ -4158,18 +4155,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 +4211,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); @@ -4225,8 +4225,8 @@ transform_handler(struct wl_listener *listener, void *data) if (!weston_view_is_mapped(shsurf->view)) return; - x = shsurf->view->geometry.x; - y = shsurf->view->geometry.y; + x = shsurf->view->geometry.pos_offset.x; + y = shsurf->view->geometry.pos_offset.y; api->send_position(surface, x, y); } @@ -4252,14 +4252,14 @@ weston_view_set_initial_position(struct weston_view *view, struct weston_pointer *pointer = weston_seat_get_pointer(seat); if (pointer) { - ix = wl_fixed_to_int(pointer->x); - iy = wl_fixed_to_int(pointer->y); + ix = pointer->pos.c.x; + iy = pointer->pos.c.y; break; } } wl_list_for_each(output, &compositor->output_list, link) { - if (pixman_region32_contains_point(&output->region, ix, iy, NULL)) { + if (weston_output_contains_point(output, ix, iy)) { target_output = output; break; } @@ -4480,7 +4480,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 @@ -4606,7 +4606,7 @@ backlight_binding(struct weston_keyboard *keyboard, const struct timespec *time, * control on the primary display. We'd have to extend later if we * ever get support for setting backlights on random desktop LCD * panels though */ - output = get_default_output(compositor); + output = weston_shell_utils_get_default_output(compositor); if (!output) return; @@ -4654,86 +4654,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) { @@ -4746,21 +4666,24 @@ shell_reposition_view_on_output_change(struct weston_view *view) if (wl_list_empty(&ec->output_list)) return; - x = view->geometry.x; - y = view->geometry.y; + x = view->geometry.pos_offset.x; + y = view->geometry.pos_offset.y; /* At this point the destroyed output is not in the list anymore. * If the view is still visible somewhere, we leave where it is, * otherwise, move it to the first output. */ visible = 0; wl_list_for_each(output, &ec->output_list, link) { - if (pixman_region32_contains_point(&output->region, - x, y, NULL)) { + if (weston_output_contains_point(output, x, y)) { visible = 1; break; } } + shsurf = get_shell_surface(view->surface); + if (!shsurf) + return; + if (!visible) { first_output = container_of(ec->output_list.next, struct weston_output, link); @@ -4771,12 +4694,12 @@ shell_reposition_view_on_output_change(struct weston_view *view) weston_view_set_position(view, x, y); } else { weston_view_geometry_dirty(view); - } + if (shsurf->state.maximized || + shsurf->state.fullscreen) + return; + } - shsurf = get_shell_surface(view->surface); - if (!shsurf) - return; shsurf->saved_position_valid = false; set_maximized(shsurf, false); @@ -4787,16 +4710,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 +4742,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_shell_utils_curtain_destroy(shell_output->fade.curtain); if (shell_output->fade.startup_timer) wl_event_source_remove(shell_output->fade.startup_timer); @@ -4920,8 +4837,8 @@ handle_output_move_layer(struct desktop_shell *shell, if (view->output != output) continue; - x = view->geometry.x + output->move_x; - y = view->geometry.y + output->move_y; + x = view->geometry.pos_offset.x + output->move_x; + y = view->geometry.pos_offset.y + output->move_y; weston_view_set_position(view, x, y); } } @@ -4958,11 +4875,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 +4895,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 +4920,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 +4952,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 +4969,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, @@ -5061,16 +4985,13 @@ shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) weston_compositor_add_touch_binding(ec, 0, touch_to_activate_binding, shell); + weston_compositor_add_tablet_tool_binding(ec, BTN_TOUCH, 0, + tablet_tool_activate_binding, shell); weston_compositor_add_key_binding(ec, KEY_BRIGHTNESSDOWN, 0, backlight_binding, ec); 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 +5002,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 +5015,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 +5036,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 +5056,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,39 +5091,19 @@ 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) return -1; shell->text_backend = text_backend_init(ec); - if (!shell->text_backend) - return -1; 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 b06b90663..e9e123e98 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 bfe77e8e2..79bae0d91 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 @@ -789,6 +789,7 @@ WARN_LOGFILE = INPUT = @SRC_ROOT@/libweston \ @SRC_ROOT@/include/libweston \ + @SRC_ROOT@/shared/config-parser.c \ @SRC_ROOT@/tests # This tag can be used to specify the character encoding of the source files @@ -1486,17 +1487,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 +1622,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 +1711,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 +1889,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 +1909,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 +1921,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 +2121,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 +2135,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 8aa53b3ee..a47bccb93 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 88f09e2c3..1f5736241 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 000000000..87ff7a36c 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 000000000..c73a8c600 --- /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 000000000..42eb5fe59 --- /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.rst b/doc/sphinx/toc/libweston.rst index c8a2e62d4..cd56e0d10 100644 --- a/doc/sphinx/toc/libweston.rst +++ b/doc/sphinx/toc/libweston.rst @@ -6,6 +6,8 @@ Libweston :caption: Contents: libweston/compositor.rst + libweston/weston-config.rst + libweston/shell-utils.rst libweston/output-management.rst libweston/log.rst @@ -20,11 +22,12 @@ evolving through many Weston releases before it achieves a stable API and feature completeness. `Libweston`'s primary purpose is exporting an API for creating Wayland -compositors. Libweston's secondary purpose is to export the weston_config API +compositors. Libweston's secondary purpose is to export the :ref:`weston-config` API so that third party plugins and helper programs can read :file:`weston.ini` if they want to. However, these two scopes are orthogonal and independent. At no -point will the compositor functionality use or depend on the weston_config -functionality. +point will the compositor functionality use or depend on the :ref:`weston-config` +functionality. Additional helper functions are grouped together under +:ref:`shell-utils`, to ease out shell development. Further work ------------ diff --git a/doc/sphinx/toc/libweston/images/create_output.msc b/doc/sphinx/toc/libweston/images/create_output.msc index f20cdb4e5..b66b75482 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/libweston/meson.build b/doc/sphinx/toc/libweston/meson.build index 9fdd59f44..f72ca5f26 100644 --- a/doc/sphinx/toc/libweston/meson.build +++ b/doc/sphinx/toc/libweston/meson.build @@ -5,6 +5,8 @@ files = [ 'log.rst', 'output.rst', 'output-management.rst', + 'weston-config.rst', + 'shell-utils.rst', ] foreach file : files diff --git a/doc/sphinx/toc/libweston/shell-utils.rst b/doc/sphinx/toc/libweston/shell-utils.rst new file mode 100644 index 000000000..bf3906633 --- /dev/null +++ b/doc/sphinx/toc/libweston/shell-utils.rst @@ -0,0 +1,7 @@ +.. _shell-utils: + +Shell utils +=========== + +.. doxygengroup:: shell-utils + :content-only: diff --git a/doc/sphinx/toc/libweston/weston-config.rst b/doc/sphinx/toc/libweston/weston-config.rst new file mode 100644 index 000000000..e3ff9e876 --- /dev/null +++ b/doc/sphinx/toc/libweston/weston-config.rst @@ -0,0 +1,7 @@ +.. _weston-config: + +Weston config +============= + +.. doxygengroup:: weston-config + :content-only: diff --git a/doc/sphinx/toc/meson.build b/doc/sphinx/toc/meson.build index b7190f78a..4c6384118 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 e14ec55dd..f1e1d59a7 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 @@ -14,7 +14,7 @@ Weston on a machine that already has another graphical environment running, being either another wayland compositor (e.g. Weston) or on a X11 server. You should only specify the backend manually if you know that what Weston picks is not the best, or the one you intended to use is different than the one -loaded. In that case, the backend can be selected by using ``-B [backend.so]`` +loaded. In that case, the backend can be selected by using ``-B [backend]`` command line option. As each back-end uses a different way to get input and produce output, it means that the most suitable back-end depends on the environment being used. @@ -28,22 +28,19 @@ 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) +* **pipewire** -- run without input, output into a PipeWire node 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 compositing all surfaces into a single image, which is being handed out to a back-end, and finally, displayed on the screen. -libweston has a CPU-based type of renderer by making use of the -`Pixman `_ library, but also one that can make -use of the GPU to do that, which uses `OpenGL ES `_ -and it is simply called the GL-renderer. - -Most of the back-ends provide a command line option to disable the GL-renderer, -and use the CPU for doing that. That happens by appending to the command line -``--use-pixman`` when running Weston. One might use the CPU-based renderer -to exclude any other potential issues with the GL-renderer. +libweston provides two useful renderers. One uses +`OpenGL ES `_, which will often be accelerated +by your GPU when suitable drivers are installed. The other uses the +`Pixman `_ library which is entirely CPU (software) +rendered. You can select between these with the ``--renderer=gl`` and +``--renderer=pixman`` arguments when starting Weston. Additional set-up steps ----------------------- @@ -91,17 +88,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 +171,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 --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 f7803292f..650839881 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 6975f65e3..28fb58b75 100644 --- a/fullscreen-shell/fullscreen-shell.c +++ b/fullscreen-shell/fullscreen-shell.c @@ -37,14 +37,13 @@ #include "compositor/weston.h" #include "fullscreen-shell-unstable-v1-server-protocol.h" #include "shared/helpers.h" +#include struct fullscreen_shell { struct wl_client *client; struct wl_listener client_destroyed; + struct wl_listener destroy_listener; struct weston_compositor *compositor; - /* XXX: missing compositor destroy listener - * https://gitlab.freedesktop.org/wayland/weston/issues/299 - */ struct weston_layer layer; struct wl_list output_list; @@ -80,7 +79,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; @@ -222,41 +221,32 @@ seat_created(struct wl_listener *l, void *data) } static void -black_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) +black_surface_committed(struct weston_surface *es, + struct weston_coord_surface new_origin) { } -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_shell_utils_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 @@ -274,6 +264,7 @@ fs_output_destroy(struct fs_output *fsout) fs_output_set_surface(fsout, NULL, 0, 0, 0); fs_output_clear_pending(fsout); + weston_shell_utils_curtain_destroy(fsout->curtain); wl_list_remove(&fsout->link); if (fsout->output) @@ -311,8 +302,7 @@ pending_surface_destroyed(struct wl_listener *listener, void *data) } static void -configure_presented_surface(struct weston_surface *surface, int32_t sx, - int32_t sy); +configure_presented_surface_internal(struct weston_surface *surface); static struct fs_output * fs_output_create(struct fullscreen_shell *shell, struct weston_output *output) @@ -333,13 +323,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)) { @@ -347,7 +336,7 @@ fs_output_create(struct fullscreen_shell *shell, struct weston_output *output) struct fs_client_surface, link); fs_output_set_surface(fsout, surf->surface, surf->method, 0, 0); - configure_presented_surface(surf->surface, 0, 0); + configure_presented_surface_internal(surf->surface); } return fsout; @@ -373,57 +362,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) -{ - int32_t surf_x, surf_y, surf_width, surf_height; - float x, y; - struct weston_output *output = fsout->output; - - surface_subsurfaces_boundingbox(fsout->view->surface, &surf_x, &surf_y, - &surf_width, &surf_height); - - x = output->x + (output->width - surf_width) / 2 - surf_x / 2; - y = output->y + (output->height - surf_height) / 2 - surf_y / 2; - - weston_view_set_position(fsout->view, x, y); -} - static void fs_output_scale_view(struct fs_output *fsout, float width, float height) { @@ -433,8 +371,8 @@ fs_output_scale_view(struct fs_output *fsout, float width, float height) struct weston_view *view = fsout->view; struct weston_output *output = fsout->output; - surface_subsurfaces_boundingbox(view->surface, &surf_x, &surf_y, - &surf_width, &surf_height); + weston_shell_utils_subsurfaces_boundingbox(view->surface, &surf_x, &surf_y, + &surf_width, &surf_height); if (output->width == surf_width && output->height == surf_height) { weston_view_set_position(view, @@ -478,9 +416,9 @@ fs_output_configure_simple(struct fs_output *fsout, wl_list_remove(&fsout->transform.link); wl_list_init(&fsout->transform.link); - surface_subsurfaces_boundingbox(fsout->view->surface, - &surf_x, &surf_y, - &surf_width, &surf_height); + weston_shell_utils_subsurfaces_boundingbox(fsout->view->surface, + &surf_x, &surf_y, + &surf_width, &surf_height); output_aspect = (float) output->width / (float) output->height; surface_aspect = (float) surf_width / (float) surf_height; @@ -488,7 +426,7 @@ fs_output_configure_simple(struct fs_output *fsout, switch (fsout->method) { case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_DEFAULT: case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_CENTER: - fs_output_center_view(fsout); + weston_shell_utils_center_on_output(fsout->view, fsout->output); break; case ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_ZOOM: @@ -520,10 +458,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); } @@ -539,14 +477,14 @@ fs_output_configure_for_mode(struct fs_output *fsout, if (fsout->pending.surface != configured_surface) { /* Nothing to really reconfigure. We'll just recenter the * view in case they played with subsurfaces */ - fs_output_center_view(fsout); + weston_shell_utils_center_on_output(fsout->view, fsout->output); return; } /* We have a pending surface */ - surface_subsurfaces_boundingbox(fsout->pending.surface, - &surf_x, &surf_y, - &surf_width, &surf_height); + weston_shell_utils_subsurfaces_boundingbox(fsout->pending.surface, + &surf_x, &surf_y, + &surf_width, &surf_height); /* The actual output mode is in physical units. We need to * transform the surface size to physical unit size by flipping and @@ -623,8 +561,14 @@ fs_output_configure(struct fs_output *fsout, } static void -configure_presented_surface(struct weston_surface *surface, int32_t sx, - int32_t sy) +configure_presented_surface(struct weston_surface *surface, + struct weston_coord_surface new_origin) +{ + configure_presented_surface_internal(surface); +} + +static void +configure_presented_surface_internal(struct weston_surface *surface) { struct fullscreen_shell *shell = surface->committed_private; struct fs_output *fsout; @@ -670,11 +614,14 @@ 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); weston_layer_entry_insert(&fsout->shell->layer.view_list, &fsout->view->layer_link); + + weston_view_geometry_dirty(fsout->view); } fs_output_clear_pending(fsout); @@ -899,6 +846,28 @@ bind_fullscreen_shell(struct wl_client *client, void *data, uint32_t version, ZWP_FULLSCREEN_SHELL_V1_CAPABILITY_ARBITRARY_MODES); } +static void +fullscreen_shell_destroy(struct wl_listener *listener, void *data) +{ + struct fs_output *fs_output, *fs_output_next; + struct fs_client_surface *surf; + struct fullscreen_shell *shell = + container_of(listener, struct fullscreen_shell, destroy_listener); + + /* remove the curtain(s) */ + wl_list_for_each_safe(fs_output, fs_output_next, &shell->output_list, link) + fs_output_destroy(fs_output); + + if (!wl_list_empty(&shell->default_surface_list)) { + surf = container_of(shell->default_surface_list.prev, + struct fs_client_surface, link); + remove_default_surface(surf); + } + + weston_layer_fini(&shell->layer); + free(shell); +} + WL_EXPORT int wet_shell_init(struct weston_compositor *compositor, int *argc, char *argv[]) @@ -911,7 +880,16 @@ wet_shell_init(struct weston_compositor *compositor, if (shell == NULL) return -1; + shell->compositor = compositor; + + if (!weston_compositor_add_destroy_listener_once(compositor, + &shell->destroy_listener, + fullscreen_shell_destroy)) { + free(shell); + return 0; + } + wl_list_init(&shell->default_surface_list); shell->client_destroyed.notify = client_destroyed; diff --git a/fullscreen-shell/meson.build b/fullscreen-shell/meson.build index 02a6c6f16..05ba485eb 100644 --- a/fullscreen-shell/meson.build +++ b/fullscreen-shell/meson.build @@ -4,7 +4,7 @@ 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, ] diff --git a/include/libweston/backend-drm.h b/include/libweston/backend-drm.h index af2da4aa0..881db4fe3 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 6 struct libinput_device; @@ -78,6 +78,32 @@ 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); + + /** The content type primarily used on the output. Valid values are: + * - NULL or "no data" - No information is provided about the usage of the + * output + * - "graphics" + * - "photo" + * - "cinema" + * - "game" + */ + int (*set_content_type)(struct weston_output *output, + const char *content_type); }; static inline const struct weston_drm_output_api * @@ -90,7 +116,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 +127,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,11 +201,15 @@ 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; + /** Select the renderer type to use */ + enum weston_renderer_type renderer; - /** Whether to use the pixman renderer instead of the OpenGL ES renderer. */ - bool use_pixman; +#if defined(ENABLE_IMXG2D) + /** Whether to use the g2d renderer instead of the OpenGL ES renderer. */ + bool use_g2d; +#endif + + bool enable_overlay_view; /** The seat to be used for input and output. * @@ -191,6 +225,7 @@ struct weston_drm_backend_config { * Valid values are: * - NULL - The default format ("xrgb8888") will be used; * - "xrgb8888"; + * - "argb8888" * - "rgb565" * - "xrgb2101010" * The backend will take ownership of the format pointer and will free @@ -223,6 +258,18 @@ struct weston_drm_backend_config { /** Use shadow buffer if using Pixman-renderer. */ bool use_pixman_shadow; + + /** Additional DRM devices to open + * + * A comma-separated list of DRM devices names, like "card1", to open. + * The devices will be used as additional scanout devices, but not as a + * rendering device. + */ + char *additional_devices; + + /** Desktop shell size */ + uint32_t shell_width; + uint32_t shell_height; }; #ifdef __cplusplus diff --git a/include/libweston/backend-headless.h b/include/libweston/backend-headless.h index 1f53835e0..c84bd586d 100644 --- a/include/libweston/backend-headless.h +++ b/include/libweston/backend-headless.h @@ -34,16 +34,16 @@ extern "C" { #include -#define WESTON_HEADLESS_BACKEND_CONFIG_VERSION 2 +#define WESTON_HEADLESS_BACKEND_CONFIG_VERSION 3 struct weston_headless_backend_config { struct weston_backend_config base; - /** Whether to use the pixman renderer, default is no-op */ - bool use_pixman; + /** Select the renderer to use */ + enum weston_renderer_type renderer; - /** Whether to use the GL renderer, conflicts with use_pixman */ - bool use_gl; + /** Use output decorations, requires use_gl = true */ + bool decorate; }; #ifdef __cplusplus diff --git a/include/libweston/backend-pipewire.h b/include/libweston/backend-pipewire.h new file mode 100644 index 000000000..f8fe3777d --- /dev/null +++ b/include/libweston/backend-pipewire.h @@ -0,0 +1,108 @@ +/* + * Copyright © 2021-2023 Philipp Zabel + * + * 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_PIPEWIRE_H +#define WESTON_COMPOSITOR_PIPEWIRE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define WESTON_PIPEWIRE_OUTPUT_API_NAME "weston_pipewire_output_api_v2" + +struct pipewire_config { + int32_t width; + int32_t height; + uint32_t framerate; +}; + +struct weston_pipewire_output_api { + /** Create a new PipeWire head. + * + * \param backend The backend. + * \param name Desired name for the new head + * \param config The pipewire_config of the new head. + * + * Returns 0 on success, -1 on failure. + */ + void (*head_create)(struct weston_backend *backend, + const char *name, + const struct pipewire_config *config); + + /** Set the size of a PipeWire output to the specified width and height. + * + * If the width or height are set to -1, the size of the underlying + * PipeWire head will be used. + * + * \param output The weston output for which the size shall be set + * \param width Desired width of the output + * \param height Desired height of the output + * + * Returns 0 on success, -1 on failure. + */ + int (*output_set_size)(struct weston_output *output, + int width, int height); + + /** The pixel format to be used by the output. + * + * \param output The weston output for which the pixel format is set + * \param gbm_format String representation of the pixel format + * + * Valid values for the gbm_format are: + * - NULL - The format set at backend creation time will be used; + * - "xrgb8888"; + * - "rgb565"; + */ + void (*set_gbm_format)(struct weston_output *output, + const char *gbm_format); +}; + +static inline const struct weston_pipewire_output_api * +weston_pipewire_output_get_api(struct weston_compositor *compositor) +{ + const void *api; + api = weston_plugin_api_get(compositor, WESTON_PIPEWIRE_OUTPUT_API_NAME, + sizeof(struct weston_pipewire_output_api)); + + return (const struct weston_pipewire_output_api *)api; +} + +#define WESTON_PIPEWIRE_BACKEND_CONFIG_VERSION 1 + +struct weston_pipewire_backend_config { + struct weston_backend_config base; + enum weston_renderer_type renderer; + char *gbm_format; + int32_t num_outputs; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_COMPOSITOR_PIPEWIRE_H */ diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index b3542507a..00616507b 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -33,15 +33,26 @@ extern "C" { #include #include -#define WESTON_RDP_OUTPUT_API_NAME "weston_rdp_output_api_v1" +#define WESTON_RDP_OUTPUT_API_NAME "weston_rdp_output_api_v2" +#define RDP_DEFAULT_FREQ 60 + +struct weston_rdp_monitor { + int32_t x; + int32_t y; + int32_t width; + int32_t height; + uint32_t desktop_scale; +}; struct weston_rdp_output_api { - /** Initialize a RDP output with specified width and height. - * - * Returns 0 on success, -1 on failure. + /** Get config from RDP client when connected */ - int (*output_set_size)(struct weston_output *output, - int width, int height); + void (*head_get_monitor)(struct weston_head *head, + struct weston_rdp_monitor *monitor); + + /** Set mode for an output */ + void (*output_set_mode)(struct weston_output *base, + struct weston_mode *mode); }; static inline const struct weston_rdp_output_api * @@ -54,10 +65,16 @@ weston_rdp_output_get_api(struct weston_compositor *compositor) return (const struct weston_rdp_output_api *)api; } -#define WESTON_RDP_BACKEND_CONFIG_VERSION 2 +#define WESTON_RDP_BACKEND_CONFIG_VERSION 3 + +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; + enum weston_renderer_type renderer; char *bind_address; int port; char *rdp_key; @@ -66,6 +83,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/backend-vnc.h b/include/libweston/backend-vnc.h new file mode 100644 index 000000000..c8c299846 --- /dev/null +++ b/include/libweston/backend-vnc.h @@ -0,0 +1,74 @@ +/* + * Copyright © 2019 Stefan Agner + * + * 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_VNC_H +#define WESTON_COMPOSITOR_VNC_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define WESTON_VNC_OUTPUT_API_NAME "weston_vnc_output_api_v1" +#define VNC_DEFAULT_FREQ 60 + +struct weston_vnc_output_api { + /** Initialize a VNC output with specified width and height. + * + * Returns 0 on success, -1 on failure. + */ + int (*output_set_size)(struct weston_output *output, + int width, int height); +}; + +static inline const struct weston_vnc_output_api * +weston_vnc_output_get_api(struct weston_compositor *compositor) +{ + const void *api; + api = weston_plugin_api_get(compositor, WESTON_VNC_OUTPUT_API_NAME, + sizeof(struct weston_vnc_output_api)); + + return (const struct weston_vnc_output_api *)api; +} + +#define WESTON_VNC_BACKEND_CONFIG_VERSION 2 + +struct weston_vnc_backend_config { + struct weston_backend_config base; + enum weston_renderer_type renderer; + char *bind_address; + int port; + int refresh_rate; + char *server_cert; + char *server_key; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_COMPOSITOR_VNC_H */ diff --git a/include/libweston/backend-wayland.h b/include/libweston/backend-wayland.h index 7fe513a7f..b73ee01db 100644 --- a/include/libweston/backend-wayland.h +++ b/include/libweston/backend-wayland.h @@ -34,11 +34,11 @@ extern "C" { #include -#define WESTON_WAYLAND_BACKEND_CONFIG_VERSION 2 +#define WESTON_WAYLAND_BACKEND_CONFIG_VERSION 3 struct weston_wayland_backend_config { struct weston_backend_config base; - bool use_pixman; + enum weston_renderer_type renderer; bool sprawl; char *display_name; bool fullscreen; diff --git a/include/libweston/backend-x11.h b/include/libweston/backend-x11.h index 1556e8e77..3b38c49bf 100644 --- a/include/libweston/backend-x11.h +++ b/include/libweston/backend-x11.h @@ -34,7 +34,7 @@ extern "C" { #include -#define WESTON_X11_BACKEND_CONFIG_VERSION 2 +#define WESTON_X11_BACKEND_CONFIG_VERSION 3 struct weston_x11_backend_config { struct weston_backend_config base; @@ -42,8 +42,7 @@ struct weston_x11_backend_config { bool fullscreen; bool no_input; - /** Whether to use the pixman renderer instead of the OpenGL ES renderer. */ - bool use_pixman; + enum weston_renderer_type renderer; }; #ifdef __cplusplus diff --git a/include/libweston/config-parser.h b/include/libweston/config-parser.h index d82197bfd..d1cebad22 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,12 @@ weston_config_section_get_bool(struct weston_config_section *section, const char * weston_config_get_name_from_env(void); +void +weston_config_set_env(struct weston_config_section *section); + +struct weston_config * +weston_config_parse_fp(FILE *file); + struct weston_config * weston_config_parse(const char *name); @@ -121,10 +107,11 @@ int weston_config_next_section(struct weston_config *config, struct weston_config_section **section, const char **name); +uint32_t +weston_config_get_binding_modifier(struct weston_config *config, uint32_t default_mod); #ifdef __cplusplus } #endif #endif /* CONFIGPARSER_H */ - diff --git a/include/libweston/desktop.h b/include/libweston/desktop.h new file mode 100644 index 000000000..465c9f8fc --- /dev/null +++ b/include/libweston/desktop.h @@ -0,0 +1,245 @@ +/* + * 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. + */ + +#ifndef WESTON_DESKTOP_H +#define WESTON_DESKTOP_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum weston_desktop_surface_edge { + WESTON_DESKTOP_SURFACE_EDGE_NONE = 0, + WESTON_DESKTOP_SURFACE_EDGE_TOP = 1, + WESTON_DESKTOP_SURFACE_EDGE_BOTTOM = 2, + WESTON_DESKTOP_SURFACE_EDGE_LEFT = 4, + WESTON_DESKTOP_SURFACE_EDGE_TOP_LEFT = 5, + WESTON_DESKTOP_SURFACE_EDGE_BOTTOM_LEFT = 6, + WESTON_DESKTOP_SURFACE_EDGE_RIGHT = 8, + WESTON_DESKTOP_SURFACE_EDGE_TOP_RIGHT = 9, + 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; + +struct weston_desktop_api { + size_t struct_size; + void (*ping_timeout)(struct weston_desktop_client *client, + void *user_data); + void (*pong)(struct weston_desktop_client *client, + void *user_data); + + void (*surface_added)(struct weston_desktop_surface *surface, + void *user_data); + void (*surface_removed)(struct weston_desktop_surface *surface, + void *user_data); + void (*committed)(struct weston_desktop_surface *surface, + int32_t sx, int32_t sy, void *user_data); + void (*show_window_menu)(struct weston_desktop_surface *surface, + struct weston_seat *seat, int32_t x, int32_t y, + void *user_data); + void (*set_parent)(struct weston_desktop_surface *surface, + struct weston_desktop_surface *parent, + void *user_data); + void (*move)(struct weston_desktop_surface *surface, + struct weston_seat *seat, uint32_t serial, void *user_data); + void (*resize)(struct weston_desktop_surface *surface, + struct weston_seat *seat, uint32_t serial, + enum weston_desktop_surface_edge edges, void *user_data); + void (*fullscreen_requested)(struct weston_desktop_surface *surface, + bool fullscreen, + struct weston_output *output, + void *user_data); + void (*maximized_requested)(struct weston_desktop_surface *surface, + bool maximized, void *user_data); + void (*minimized_requested)(struct weston_desktop_surface *surface, + void *user_data); + + /** Position suggestion for an Xwayland window + * + * X11 applications assume they can position their windows as necessary, + * which is not possible in Wayland where positioning is driven by the + * shell alone. This function is used to relay absolute position wishes + * from Xwayland clients to the shell. + * + * This is particularly used for mapping windows at specified locations, + * e.g. via the commonly used '-geometry' command line option. In such + * case, a call to surface_added() is immediately followed by + * xwayland_position() if the X11 application specified a position. + * The committed() call that will map the window occurs later, so it + * is recommended to usually store and honour the given position for + * windows that are not yet mapped. + * + * Calls to this function may happen also at other times. + * + * The given coordinates are in the X11 window system coordinate frame + * relative to the X11 root window. Care should be taken to ensure the + * window gets mapped to coordinates that correspond to the proposed + * position from the X11 client perspective. + * + * \param surface The surface in question. + * \param x The absolute X11 coordinate for x. + * \param y The absolute X11 coordinate for y. + * \param user_data The user_data argument passed in to + * weston_desktop_create(). + * + * This callback can be NULL. + */ + 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 +weston_seat_break_desktop_grabs(struct weston_seat *seat); + +struct weston_desktop * +weston_desktop_create(struct weston_compositor *compositor, + const struct weston_desktop_api *api, void *user_data); +void +weston_desktop_destroy(struct weston_desktop *desktop); + +struct wl_client * +weston_desktop_client_get_client(struct weston_desktop_client *client); +void +weston_desktop_client_for_each_surface(struct weston_desktop_client *client, + void (*callback)(struct weston_desktop_surface *surface, void *user_data), + void *user_data); +int +weston_desktop_client_ping(struct weston_desktop_client *client); + +bool +weston_surface_is_desktop_surface(struct weston_surface *surface); +struct weston_desktop_surface * +weston_surface_get_desktop_surface(struct weston_surface *surface); + +void +weston_desktop_surface_set_user_data(struct weston_desktop_surface *self, + void *user_data); +struct weston_view * +weston_desktop_surface_create_view(struct weston_desktop_surface *surface); +void +weston_desktop_surface_unlink_view(struct weston_view *view); +void +weston_desktop_surface_propagate_layer(struct weston_desktop_surface *surface); +void +weston_desktop_surface_set_activated(struct weston_desktop_surface *surface, + bool activated); +void +weston_desktop_surface_set_fullscreen(struct weston_desktop_surface *surface, + bool fullscreen); +void +weston_desktop_surface_set_maximized(struct weston_desktop_surface *surface, + bool maximized); +void +weston_desktop_surface_set_resizing(struct weston_desktop_surface *surface, + bool resized); +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, + struct wl_listener *listener); + +void * +weston_desktop_surface_get_user_data(struct weston_desktop_surface *surface); +struct weston_desktop_client * +weston_desktop_surface_get_client(struct weston_desktop_surface *surface); +struct weston_surface * +weston_desktop_surface_get_surface(struct weston_desktop_surface *surface); +const char * +weston_desktop_surface_get_title(struct weston_desktop_surface *surface); +const char * +weston_desktop_surface_get_app_id(struct weston_desktop_surface *surface); +pid_t +weston_desktop_surface_get_pid(struct weston_desktop_surface *surface); +bool +weston_desktop_surface_get_activated(struct weston_desktop_surface *surface); +bool +weston_desktop_surface_get_maximized(struct weston_desktop_surface *surface); +bool +weston_desktop_surface_get_fullscreen(struct weston_desktop_surface *surface); +bool +weston_desktop_surface_get_pending_resizing(struct weston_desktop_surface *surface); +bool +weston_desktop_surface_get_pending_activated(struct weston_desktop_surface *surface); +bool +weston_desktop_surface_get_pending_maximized(struct weston_desktop_surface *surface); +bool +weston_desktop_surface_get_pending_fullscreen(struct weston_desktop_surface *surface); +bool +weston_desktop_surface_get_resizing(struct weston_desktop_surface *surface); +struct weston_geometry +weston_desktop_surface_get_geometry(struct weston_desktop_surface *surface); +struct weston_size +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); +struct weston_desktop_surface * +weston_desktop_surface_get_parent(struct weston_desktop_surface *surface); +void +weston_desktop_surface_foreach_child(struct weston_desktop_surface *surface, + void (* callback)(struct weston_desktop_surface *child, + void *user_data), + void *user_data); + + +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 + +#endif /* WESTON_DESKTOP_H */ diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index d99dc763a..538881321 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -44,6 +44,15 @@ extern "C" { #include #include +struct weston_log_pacer { + /** This must be set to zero before first use */ + bool initialized; + struct timespec burst_start; + unsigned int event_count; + unsigned int max_burst; + unsigned int reset_ms; +}; + struct weston_geometry { int32_t x, y; int32_t width, height; @@ -81,6 +90,9 @@ struct weston_pointer_constraint; struct ro_anonymous_file; struct weston_color_profile; struct weston_color_transform; +struct pixel_format_info; +struct weston_output_capture_info; +struct weston_tearing_control; enum weston_keyboard_modifier { MODIFIER_CTRL = (1 << 0), @@ -150,21 +162,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 +219,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 @@ -231,6 +376,7 @@ struct weston_testsuite_data { */ struct weston_head { struct weston_compositor *compositor; /**< owning compositor */ + struct weston_backend *backend; /**< owning backend */ struct wl_list compositor_link; /**< in weston_compositor::head_list */ struct wl_signal destroy_signal; /**< destroy callbacks */ @@ -258,11 +404,42 @@ 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; }; +/** 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; +}; + +enum weston_output_power_state { + /** No rendering and dpms off */ + WESTON_OUTPUT_POWER_FORCED_OFF = 0, + /** Normal rendering and dpms on */ + WESTON_OUTPUT_POWER_NORMAL +}; + /** Content producer for heads * * \rst @@ -324,8 +501,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 */ int move_x, move_y; @@ -334,6 +509,7 @@ struct weston_output { int disable_planes; int destroying; struct wl_list feedback_list; + struct weston_output_capture_info *capture_info; uint32_t transform; int32_t native_scale; @@ -351,12 +527,15 @@ struct weston_output { enum weston_hdcp_protection current_protection; bool allow_protection; + enum weston_output_power_state power_state; + + struct weston_log_pacer repaint_delay_pacer; + struct weston_log_pacer pixman_overdraw_pacer; + 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 +554,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); @@ -420,12 +601,9 @@ enum weston_pointer_motion_mask { struct weston_pointer_motion_event { uint32_t mask; struct timespec time; - double x; - double y; - double dx; - double dy; - double dx_unaccel; - double dy_unaccel; + struct weston_coord_global abs; + struct weston_coord rel; + struct weston_coord rel_unaccel; }; struct weston_pointer_axis_event { @@ -496,6 +674,46 @@ struct weston_touch_grab { struct weston_touch *touch; }; +struct weston_tablet; +struct weston_tablet_tool; +struct weston_tablet_tool_grab; +struct weston_tablet_tool_grab_interface { + void (*proximity_in)(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + struct weston_tablet *tablet); + void (*proximity_out)(struct weston_tablet_tool_grab *grab, + const struct timespec *time); + void (*motion)(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + struct weston_coord_global pos); + void (*down)(struct weston_tablet_tool_grab *grab, + const struct timespec *time); + void (*up)(struct weston_tablet_tool_grab *grab, + const struct timespec *time); + void (*pressure)(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + uint32_t pressure); + void (*distance)(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + uint32_t distance); + void (*tilt)(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + wl_fixed_t tilt_x, + wl_fixed_t tilt_y); + void (*button)(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + uint32_t button, + uint32_t state); + void (*frame)(struct weston_tablet_tool_grab *grab, + const struct timespec *time); + void (*cancel)(struct weston_tablet_tool_grab *grab); +}; + +struct weston_tablet_tool_grab { + const struct weston_tablet_tool_grab_interface *interface; + struct weston_tablet_tool *tool; +}; + struct weston_data_offer { struct wl_resource *resource; struct weston_data_source *source; @@ -548,16 +766,16 @@ struct weston_pointer { struct weston_view *sprite; struct wl_listener sprite_destroy_listener; - int32_t hotspot_x, hotspot_y; + struct weston_coord_surface hotspot; struct weston_pointer_grab *grab; struct weston_pointer_grab default_grab; - wl_fixed_t grab_x, grab_y; + struct weston_coord_global grab_pos; uint32_t grab_button; uint32_t grab_serial; struct timespec grab_time; - wl_fixed_t x, y; + struct weston_coord_global pos; wl_fixed_t sx, sy; uint32_t button_count; @@ -650,6 +868,7 @@ struct weston_touch { struct wl_listener focus_resource_listener; uint32_t focus_serial; struct wl_signal focus_signal; + bool pending_focus_reset; uint32_t num_tp; @@ -663,10 +882,61 @@ struct weston_touch { struct wl_list timestamps_list; }; -void +struct weston_tablet_tool { + struct weston_seat *seat; + uint32_t type; + struct weston_tablet *current_tablet; + + struct wl_list resource_list; + struct wl_list focus_resource_list; + struct weston_view *focus; + struct wl_listener focus_view_listener; + struct wl_listener focus_resource_listener; + uint32_t focus_serial; + uint32_t grab_serial; + + struct wl_list link; + + uint64_t serial; + uint64_t hwid; + uint32_t capabilities; + + struct weston_tablet_tool_grab *grab; + struct weston_tablet_tool_grab default_grab; + + int button_count; + bool tip_is_down; + + struct timespec frame_time; + + struct weston_view *sprite; + struct weston_coord_surface hotspot; + struct wl_listener sprite_destroy_listener; + + struct weston_coord_global pos; + struct weston_coord_global grab_pos; + + struct wl_signal focus_signal; + struct wl_signal removed_signal; +}; + +struct weston_tablet { + struct weston_seat *seat; + + struct wl_list resource_list; + struct wl_list tool_list; + + struct wl_list link; + + char *name; + uint32_t vid; + uint32_t pid; + const char *path; +}; + +struct weston_coord_global weston_pointer_motion_to_abs(struct weston_pointer *pointer, - struct weston_pointer_motion_event *event, - wl_fixed_t *x, wl_fixed_t *y); + struct weston_pointer_motion_event *event); void weston_pointer_send_motion(struct weston_pointer *pointer, @@ -677,21 +947,22 @@ weston_pointer_has_focus_resource(struct weston_pointer *pointer); void weston_pointer_send_button(struct weston_pointer *pointer, const struct timespec *time, - uint32_t button, uint32_t state_w); + uint32_t button, + enum wl_pointer_button_state state); void weston_pointer_send_axis(struct weston_pointer *pointer, const struct timespec *time, struct weston_pointer_axis_event *event); void weston_pointer_send_axis_source(struct weston_pointer *pointer, - uint32_t source); + enum wl_pointer_axis_source source); void weston_pointer_send_frame(struct weston_pointer *pointer); void weston_pointer_set_focus(struct weston_pointer *pointer, - struct weston_view *view, - wl_fixed_t sx, wl_fixed_t sy); + struct weston_view *view); + void weston_pointer_clear_focus(struct weston_pointer *pointer); void @@ -739,18 +1010,76 @@ weston_touch_end_grab(struct weston_touch *touch); void weston_touch_send_down(struct weston_touch *touch, const struct timespec *time, - int touch_id, wl_fixed_t x, wl_fixed_t y); + int touch_id, struct weston_coord_global pos); void weston_touch_send_up(struct weston_touch *touch, const struct timespec *time, int touch_id); void weston_touch_send_motion(struct weston_touch *touch, const struct timespec *time, int touch_id, - wl_fixed_t x, wl_fixed_t y); + struct weston_coord_global pos); void weston_touch_send_frame(struct weston_touch *touch); +void +weston_tablet_tool_set_focus(struct weston_tablet_tool *tool, + struct weston_view *view, + const struct timespec *time); + +void +weston_tablet_tool_start_grab(struct weston_tablet_tool *tool, + struct weston_tablet_tool_grab *grab); + +void +weston_tablet_tool_end_grab(struct weston_tablet_tool *tool); + +void +weston_tablet_tool_send_proximity_out(struct weston_tablet_tool *tool, + const struct timespec *time); + +void +weston_tablet_tool_send_motion(struct weston_tablet_tool *tool, + const struct timespec *time, + struct weston_coord_global pos); + +void +weston_tablet_tool_send_down(struct weston_tablet_tool *tool, + const struct timespec *time); + +void +weston_tablet_tool_send_up(struct weston_tablet_tool *tool, + const struct timespec *time); + +void +weston_tablet_tool_send_pressure(struct weston_tablet_tool *tool, + const struct timespec *time, + uint32_t pressure); + +void +weston_tablet_tool_send_distance(struct weston_tablet_tool *tool, + const struct timespec *time, + uint32_t distance); + +void +weston_tablet_tool_send_tilt(struct weston_tablet_tool *tool, + const struct timespec *time, + wl_fixed_t tilt_x, wl_fixed_t tilt_y); + +void +weston_tablet_tool_send_button(struct weston_tablet_tool *tool, + const struct timespec *time, + uint32_t button, uint32_t state); + +void +weston_tablet_tool_send_frame(struct weston_tablet_tool *tool, + const struct timespec *time); + +void +weston_tablet_tool_cursor_move(struct weston_tablet_tool *tool, + struct weston_coord_global pos); + + void weston_seat_set_selection(struct weston_seat *seat, struct weston_data_source *source, uint32_t serial); @@ -848,6 +1177,11 @@ struct weston_seat { struct input_method *input_method; char *seat_name; + + struct wl_list tablet_list; + struct wl_list tablet_tool_list; + struct wl_list tablet_seat_resource_list; + struct wl_signal tablet_tool_added_signal; }; enum { @@ -937,39 +1271,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, @@ -1056,6 +1357,7 @@ struct weston_debug_compositor; struct weston_color_manager; struct weston_dmabuf_feedback; struct weston_dmabuf_feedback_format_table; +struct weston_renderer; /** Main object, container-like structure which aggregates all other objects. * @@ -1109,6 +1411,7 @@ struct weston_compositor { struct wl_list modifier_binding_list; struct wl_list button_binding_list; struct wl_list touch_binding_list; + struct wl_list tablet_tool_binding_list; struct wl_list axis_binding_list; struct wl_list debug_binding_list; @@ -1126,7 +1429,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 +1440,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; @@ -1160,6 +1464,9 @@ struct weston_compositor { void *user_data; void (*exit)(struct weston_compositor *c); + struct wl_global *tablet_manager; + struct wl_list tablet_manager_resource_list; + /* Whether to let the compositor run without any input device. */ bool require_input; @@ -1182,8 +1489,22 @@ 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; + + struct weston_log_pacer unmapped_surface_or_view_pacer; + struct weston_log_pacer presentation_clock_failure_pacer; + + /** Screenshooting global state, see output-capture.c */ + struct { + struct wl_global *weston_capture_v1; + struct wl_signal ask_auth; + } output_capture; +}; + +struct weston_solid_buffer_values { + float r, g, b, a; }; struct weston_buffer { @@ -1191,19 +1512,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 { @@ -1289,6 +1636,7 @@ struct weston_view { struct weston_surface *surface; struct wl_list surface_link; struct wl_signal destroy_signal; + struct wl_signal unmap_signal; /* struct weston_paint_node::view_link */ struct wl_list paint_node_list; @@ -1310,7 +1658,11 @@ struct weston_view { * That includes the transformations referenced from the list. */ struct { - float x, y; /* surface translation on display */ + /* pos_offset is the surface's position in the global space + * if parent is NULL, or its offset in its parent's surface + * space if parent is set. + */ + struct weston_coord pos_offset; /* struct weston_transform */ struct wl_list transformation_list; @@ -1341,8 +1693,10 @@ struct weston_view { pixman_region32_t boundingbox; pixman_region32_t opaque; - /* matrix and inverse are used only if enabled = 1. - * If enabled = 0, use x, y, width, height directly. + /* Regardless of enabled status, matrix and inverse are + * updated by weston_view_update_transform(), and are + * used for coordinate conversion between the view + * and global spaces. */ int enabled; struct weston_matrix matrix; @@ -1369,6 +1723,7 @@ struct weston_view { uint32_t psf_flags; bool is_mapped; + struct weston_log_pacer subsurface_parent_log_pacer; }; struct weston_surface_state { @@ -1376,6 +1731,7 @@ struct weston_surface_state { int newly_attached; struct weston_buffer *buffer; struct wl_listener buffer_destroy_listener; + int32_t sx; int32_t sy; @@ -1441,7 +1797,7 @@ struct weston_pointer_constraint { bool hint_is_pending; struct wl_listener pointer_destroy_listener; - struct wl_listener surface_destroy_listener; + struct wl_listener view_unmap_listener; struct wl_listener surface_commit_listener; struct wl_listener surface_activate_listener; }; @@ -1510,10 +1866,11 @@ struct weston_surface { /* * If non-NULL, this function will be called on * wl_surface::commit after a new buffer has been set up for - * this surface. The integer params are the sx and sy - * parameters supplied to wl_surface::attach. + * this surface. The coordinate holds the buffer offset parameters + * supplied to wl_surface::attach or wl_surface::offset. */ - void (*committed)(struct weston_surface *es, int32_t sx, int32_t sy); + void (*committed)(struct weston_surface *es, + struct weston_coord_surface new_origin); void *committed_private; int (*get_label)(struct weston_surface *surface, char *buf, size_t len); @@ -1533,7 +1890,7 @@ struct weston_surface { */ const char *role_name; - bool is_mapped; + bool is_mapped, is_unmapping; bool is_opaque; /* An list of per seat pointer constraints. */ @@ -1549,6 +1906,8 @@ struct weston_surface { enum weston_hdcp_protection desired_protection; enum weston_hdcp_protection current_protection; enum weston_surface_protection_mode protection_mode; + + struct weston_tearing_control *tear_control; }; struct weston_subsurface { @@ -1565,9 +1924,8 @@ struct weston_subsurface { struct wl_list parent_link_pending; struct { - int32_t x; - int32_t y; - int set; + struct weston_coord_surface offset; + bool changed; } position; int has_cached_data; @@ -1623,17 +1981,17 @@ weston_view_update_transform(struct weston_view *view); void weston_view_geometry_dirty(struct weston_view *view); -void -weston_view_to_global_float(struct weston_view *view, - float sx, float sy, float *x, float *y); +struct weston_coord_global __attribute__ ((warn_unused_result)) +weston_coord_surface_to_global(const struct weston_view *view, + struct weston_coord_surface coord); -void -weston_view_from_global(struct weston_view *view, - int32_t x, int32_t y, int32_t *vx, int32_t *vy); -void -weston_view_from_global_fixed(struct weston_view *view, - wl_fixed_t x, wl_fixed_t y, - wl_fixed_t *vx, wl_fixed_t *vy); +struct weston_coord_surface __attribute__ ((warn_unused_result)) +weston_coord_global_to_surface(const struct weston_view *view, + struct weston_coord_global coord); + +struct weston_coord_buffer __attribute__ ((warn_unused_result)) +weston_coord_surface_to_buffer(const struct weston_surface *surface, + struct weston_coord_surface coord); void weston_view_activate_input(struct weston_view *view, @@ -1670,6 +2028,8 @@ weston_layer_mask_is_infinite(struct weston_layer *layer); /* An invalid flag in presented_flags to catch logic errors. */ #define WP_PRESENTATION_FEEDBACK_INVALID (1U << 31) +/* Steal another bit from presented_flags for tearing */ +#define WESTON_FINISH_FRAME_TEARING (1U << 30) void weston_output_schedule_repaint(struct weston_output *output); @@ -1683,9 +2043,7 @@ void weston_compositor_sleep(struct weston_compositor *compositor); struct weston_view * weston_compositor_pick_view(struct weston_compositor *compositor, - wl_fixed_t x, wl_fixed_t y, - wl_fixed_t *sx, wl_fixed_t *sy); - + struct weston_coord_global pos); struct weston_binding; typedef void (*weston_key_binding_handler_t)(struct weston_keyboard *keyboard, @@ -1733,6 +2091,16 @@ weston_compositor_add_touch_binding(struct weston_compositor *compositor, weston_touch_binding_handler_t binding, void *data); +typedef void (*weston_tablet_tool_binding_handler_t)(struct weston_tablet_tool *tool, + uint32_t button, + void *data); +struct weston_binding * +weston_compositor_add_tablet_tool_binding(struct weston_compositor *compositor, + uint32_t button, + enum weston_keyboard_modifier modifier, + weston_tablet_tool_binding_handler_t binding, + void *data); + typedef void (*weston_axis_binding_handler_t)(struct weston_pointer *pointer, const struct timespec *time, struct weston_pointer_axis_event *event, @@ -1761,9 +2129,28 @@ 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); +void +weston_view_set_rel_position(struct weston_view *view, + float x, float y); + void weston_view_set_position(struct weston_view *view, float x, float y); @@ -1788,6 +2175,9 @@ weston_view_schedule_repaint(struct weston_view *view); bool weston_surface_is_mapped(struct weston_surface *surface); +bool +weston_surface_is_unmapping(struct weston_surface *surface); + void weston_surface_set_size(struct weston_surface *surface, int32_t width, int32_t height); @@ -1801,6 +2191,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); @@ -1834,7 +2227,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); @@ -1857,13 +2251,24 @@ 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_PIPEWIRE, WESTON_BACKEND_RDP, + WESTON_BACKEND_VNC, WESTON_BACKEND_WAYLAND, WESTON_BACKEND_X11, }; +enum weston_renderer_type { + WESTON_RENDERER_AUTO = 0, + WESTON_RENDERER_NOOP = 1, + WESTON_RENDERER_PIXMAN = 2, + WESTON_RENDERER_GL = 3, +#if defined(ENABLE_IMXG2D) + WESTON_RENDERER_G2D = 4, +#endif +}; + int weston_compositor_load_backend(struct weston_compositor *compositor, enum weston_compositor_backend backend, @@ -1876,11 +2281,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 * @@ -1906,6 +2306,11 @@ int weston_log_continue(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); +void +weston_log_paced(struct weston_log_pacer *pacer, unsigned int max_burst, + unsigned int reset_ms, const char *fmt, ...) + __attribute__ ((format (printf, 4, 5))); + enum weston_screenshooter_outcome { WESTON_SCREENSHOOTER_SUCCESS, WESTON_SCREENSHOOTER_NO_MEMORY, @@ -1959,12 +2364,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, @@ -1980,7 +2384,7 @@ int weston_module_init(struct weston_compositor *compositor); void * -weston_load_module(const char *name, const char *entrypoint); +weston_load_module(const char *name, const char *entrypoint, const char *module_dir); size_t weston_module_path_from_env(const char *name, char *path, size_t path_len); @@ -2026,6 +2430,9 @@ weston_head_is_device_changed(struct weston_head *head); bool weston_head_is_non_desktop(struct weston_head *head); +void +weston_head_set_device_changed(struct weston_head *head); + void weston_head_reset_device_changed(struct weston_head *head); @@ -2067,11 +2474,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); @@ -2096,13 +2499,28 @@ 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, const char *name); void -weston_output_move(struct weston_output *output, int x, int y); +weston_output_move(struct weston_output *output, + struct weston_coord_global pos); int weston_output_enable(struct weston_output *output); @@ -2110,6 +2528,15 @@ 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_output_power_off(struct weston_output *output); + +void +weston_output_power_on(struct weston_output *output); + void weston_compositor_flush_heads_changed(struct weston_compositor *compositor); @@ -2123,6 +2550,10 @@ void weston_output_allow_protection(struct weston_output *output, bool allow_protection); +bool +weston_output_contains_point(struct weston_output *output, + int32_t x, int32_t y); + int weston_compositor_enable_touch_calibrator(struct weston_compositor *compositor, weston_touch_calibration_save_func save); @@ -2153,6 +2584,31 @@ struct weston_color_profile * weston_compositor_load_icc_file(struct weston_compositor *compositor, const char *path); +/** Describes who is trying to capture and which output */ +struct weston_output_capture_client { + struct wl_client *client; + struct weston_output *output; +}; + +/** Arguments asking to authorize a screenshot/capture + * + * \sa weston_compositor_add_screenshot_authority + */ +struct weston_output_capture_attempt { + const struct weston_output_capture_client *const who; + + /** Set to true to authorize the screenshot. */ + bool authorized; + /** Set to true to deny the screenshot. */ + bool denied; +}; + +void +weston_compositor_add_screenshot_authority(struct weston_compositor *compositor, + struct wl_listener *listener, + void (*auth)(struct wl_listener *l, + struct weston_output_capture_attempt *att)); + #ifdef __cplusplus } #endif diff --git a/include/libweston/matrix.h b/include/libweston/matrix.h index be4d4eb0d..842d96886 100644 --- a/include/libweston/matrix.h +++ b/include/libweston/matrix.h @@ -27,6 +27,11 @@ #ifndef WESTON_MATRIX_H #define WESTON_MATRIX_H +#include +#include + +#include + #ifdef __cplusplus extern "C" { #endif @@ -47,6 +52,28 @@ struct weston_vector { float f[4]; }; +/** Arbitrary coordinates in any space */ +struct weston_coord { + double x; + double y; +}; + +/** Coordinates in some weston_buffer (physical pixels) */ +struct weston_coord_buffer { + struct weston_coord c; +}; + +/** Coordinates in the global compositor space (logical pixels) */ +struct weston_coord_global { + struct weston_coord c; +}; + +/** surface-local coordinates on a specific surface */ +struct weston_coord_surface { + struct weston_coord c; + const struct weston_surface *coordinate_space_id; +}; + void weston_matrix_init(struct weston_matrix *matrix); void @@ -59,24 +86,85 @@ weston_matrix_translate(struct weston_matrix *matrix, void weston_matrix_rotate_xy(struct weston_matrix *matrix, float cos, float sin); void -weston_matrix_transform(struct weston_matrix *matrix, struct weston_vector *v); +weston_matrix_transform(const struct weston_matrix *matrix, + struct weston_vector *v); + +struct weston_coord +weston_matrix_transform_coord(const struct weston_matrix *matrix, + struct weston_coord coord); int weston_matrix_invert(struct weston_matrix *inverse, const struct weston_matrix *matrix); -#ifdef UNIT_TEST -# define MATRIX_TEST_EXPORT WL_EXPORT +bool +weston_matrix_needs_filtering(const struct weston_matrix *matrix); -int -matrix_invert(double *A, unsigned *p, const struct weston_matrix *matrix); +bool +weston_matrix_to_transform(const struct weston_matrix *mat, + enum wl_output_transform *transform); void -inverse_transform(const double *LU, const unsigned *p, float *v); +weston_matrix_init_transform(struct weston_matrix *matrix, + enum wl_output_transform transform, + int x, int y, int width, int height, + int scale); -#else -# define MATRIX_TEST_EXPORT static -#endif +static inline struct weston_coord __attribute__ ((warn_unused_result)) +weston_coord_from_fixed(wl_fixed_t x, wl_fixed_t y) +{ + struct weston_coord out; + + out.x = wl_fixed_to_double(x); + out.y = wl_fixed_to_double(y); + return out; +} + +static inline struct weston_coord __attribute__ ((warn_unused_result)) +weston_coord(double x, double y) +{ + return (struct weston_coord){ .x = x, .y = y }; +} + +static inline struct weston_coord_surface __attribute__ ((warn_unused_result)) +weston_coord_surface(double x, double y, const struct weston_surface *surface) +{ + struct weston_coord_surface out; + + assert(surface); + + out.c = weston_coord(x, y); + out.coordinate_space_id = surface; + + return out; +} + +static inline struct weston_coord_surface __attribute__ ((warn_unused_result)) +weston_coord_surface_from_fixed(wl_fixed_t x, wl_fixed_t y, + const struct weston_surface *surface) +{ + struct weston_coord_surface out; + + assert(surface); + + out.c.x = wl_fixed_to_double(x); + out.c.y = wl_fixed_to_double(y); + out.coordinate_space_id = surface; + + return out; +} + +static inline struct weston_coord __attribute__ ((warn_unused_result)) +weston_coord_add(struct weston_coord a, struct weston_coord b) +{ + return weston_coord(a.x + b.x, a.y + b.y); +} + +static inline struct weston_coord __attribute__ ((warn_unused_result)) +weston_coord_sub(struct weston_coord a, struct weston_coord b) +{ + return weston_coord(a.x - b.x, a.y - b.y); +} #ifdef __cplusplus } diff --git a/include/libweston/meson.build b/include/libweston/meson.build index 1ad459bb7..fc1070c5f 100644 --- a/include/libweston/meson.build +++ b/include/libweston/meson.build @@ -1,6 +1,7 @@ install_headers( 'config-parser.h', 'libweston.h', + 'desktop.h', 'matrix.h', 'plugin-registry.h', 'windowed-output-api.h', @@ -8,13 +9,15 @@ install_headers( 'zalloc.h', 'remoting-plugin.h', 'pipewire-plugin.h', + 'shell-utils.h', subdir: dir_include_libweston_install ) backend_drm_h = files('backend-drm.h') -backend_fbdev_h = files('backend-fbdev.h') backend_headless_h = files('backend-headless.h') +backend_pipewire_h = files('backend-pipewire.h') backend_rdp_h = files('backend-rdp.h') +backend_vnc_h = files('backend-vnc.h') backend_wayland_h = files('backend-wayland.h') backend_x11_h = files('backend-x11.h') diff --git a/include/libweston/shell-utils.h b/include/libweston/shell-utils.h new file mode 100644 index 000000000..988e1e822 --- /dev/null +++ b/include/libweston/shell-utils.h @@ -0,0 +1,78 @@ +/* + * 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. + * + */ +#pragma once + +#include "config.h" +#include "shared/helpers.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* 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, + struct weston_coord_surface new_origin); + void *surface_private; + 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 * +weston_shell_utils_get_default_output(struct weston_compositor *compositor); + +struct weston_output * +weston_shell_utils_get_focused_output(struct weston_compositor *compositor); + +void +weston_shell_utils_center_on_output(struct weston_view *view, + struct weston_output *output); + +void +weston_shell_utils_subsurfaces_boundingbox(struct weston_surface *surface, + int32_t *x, int32_t *y, + int32_t *w, int32_t *h); + +int +weston_shell_utils_surface_get_label(struct weston_surface *surface, + char *buf, size_t len); + +/* helper to create a view w/ a color */ +struct weston_curtain * +weston_shell_utils_curtain_create(struct weston_compositor *compositor, + struct weston_curtain_params *params); +void +weston_shell_utils_curtain_destroy(struct weston_curtain *curtain); + +#ifdef __cplusplus +} +#endif diff --git a/include/libweston/weston-log.h b/include/libweston/weston-log.h index aeb7768bf..1786dea02 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/include/libweston/windowed-output-api.h b/include/libweston/windowed-output-api.h index 42214d1b0..0f1bc0d74 100644 --- a/include/libweston/windowed-output-api.h +++ b/include/libweston/windowed-output-api.h @@ -35,7 +35,7 @@ extern "C" { struct weston_compositor; struct weston_output; -#define WESTON_WINDOWED_OUTPUT_API_NAME "weston_windowed_output_api_v1" +#define WESTON_WINDOWED_OUTPUT_API_NAME "weston_windowed_output_api_v2" struct weston_windowed_output_api { /** Assign a given width and height to an output. @@ -58,7 +58,7 @@ struct weston_windowed_output_api { /** Create a new windowed head. * - * \param compositor The compositor instance. + * \param backend The backend. * \param name Desired name for a new head, not NULL. * * Returns 0 on success, -1 on failure. @@ -74,7 +74,7 @@ struct weston_windowed_output_api { * \sa weston_compositor_set_heads_changed_cb(), * weston_compositor_create_output_with_head() */ - int (*create_head)(struct weston_compositor *compositor, + int (*create_head)(struct weston_backend *backend, const char *name); }; diff --git a/include/meson.build b/include/meson.build index 1ea6dd3d2..ef8298fb4 100644 --- a/include/meson.build +++ b/include/meson.build @@ -1,6 +1 @@ subdir('libweston') - -install_headers( - 'libweston-desktop/libweston-desktop.h', - subdir: join_paths(dir_include_libweston, 'libweston-desktop') -) diff --git a/ivi-shell/hmi-controller.c b/ivi-shell/hmi-controller.c index 230e788ba..4629b80f9 100644 --- a/ivi-shell/hmi-controller.c +++ b/ivi-shell/hmi-controller.c @@ -72,6 +72,7 @@ ****************************************************************************/ struct hmi_controller_layer { struct ivi_layout_layer *ivilayer; + struct weston_output *output; uint32_t id_layer; int32_t x; int32_t y; @@ -95,6 +96,7 @@ struct hmi_server_setting { uint32_t application_layer_id; uint32_t workspace_background_layer_id; uint32_t workspace_layer_id; + uint32_t input_panel_layer_id; uint32_t base_layer_id_offset; int32_t panel_height; uint32_t transition_duration; @@ -118,6 +120,8 @@ struct hmi_controller { /* List of struct hmi_controller_layer */ struct wl_list base_layer_list; struct wl_list application_layer_list; + struct wl_list input_panel_layer_list; + struct hmi_controller_layer *active_input_panel_layer; struct hmi_controller_layer workspace_background_layer; struct hmi_controller_layer workspace_layer; enum ivi_hmi_controller_layout_mode layout_mode; @@ -135,6 +139,11 @@ struct hmi_controller { struct wl_listener surface_configured; struct wl_listener desktop_surface_configured; + struct wl_listener input_panel_configured; + struct wl_listener input_panel_show; + struct wl_listener input_panel_hide; + struct wl_listener input_panel_update; + struct wl_client *user_interface; struct ui_setting ui_setting; @@ -153,13 +162,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, @@ -173,6 +175,11 @@ is_surf_in_ui_widget(struct hmi_controller *hmi_ctrl, return 1; } + const struct ivi_layout_surface_properties *props; + props = hmi_ctrl->interface->get_properties_of_surface(ivisurf); + if (props->surface_type == IVI_LAYOUT_SURFACE_TYPE_INPUT_PANEL) + return 1; + return 0; } @@ -222,8 +229,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 +304,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 +369,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 +419,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; @@ -445,8 +452,6 @@ mode_random_replace(struct hmi_controller *hmi_ctrl, surface_y, surface_width, surface_height); - - hmi_ctrl->interface->layer_add_surface(layers[layer_idx]->ivilayer, ivisurf); } free(layers); @@ -484,15 +489,13 @@ switch_mode(struct hmi_controller *hmi_ctrl, struct wl_list *layer = &hmi_ctrl->application_layer_list; struct ivi_layout_surface **pp_surface = NULL; int32_t surface_length = 0; - int32_t ret = 0; if (!hmi_ctrl->is_initialized) return; hmi_ctrl->layout_mode = layout_mode; - ret = hmi_ctrl->interface->get_surfaces(&surface_length, &pp_surface); - assert(!ret); + hmi_ctrl->interface->get_surfaces(&surface_length, &pp_surface); if (!has_application_surface(hmi_ctrl, pp_surface, surface_length)) { free(pp_surface); @@ -554,25 +557,21 @@ create_layer(struct weston_output *output, struct hmi_controller_layer *layer, struct hmi_controller *hmi_ctrl) { - int32_t ret = 0; - layer->ivilayer = hmi_ctrl->interface->layer_create_with_dimension(layer->id_layer, layer->width, layer->height); assert(layer->ivilayer != NULL); + layer->output = output; - ret = hmi_ctrl->interface->screen_add_layer(output, layer->ivilayer); - assert(!ret); + hmi_ctrl->interface->screen_add_layer(output, layer->ivilayer); - ret = hmi_ctrl->interface->layer_set_destination_rectangle(layer->ivilayer, + hmi_ctrl->interface->layer_set_destination_rectangle(layer->ivilayer, layer->x, layer->y, layer->width, layer->height); - assert(!ret); - ret = hmi_ctrl->interface->layer_set_visibility(layer->ivilayer, true); - assert(!ret); + hmi_ctrl->interface->layer_set_visibility(layer->ivilayer, true); } /** @@ -614,6 +613,8 @@ set_notification_configure_surface(struct wl_listener *listener, void *data) */ surface = hmi_ctrl->interface->surface_get_weston_surface(ivisurf); if (surface) { + if (!weston_surface_has_content(surface)) + return; hmi_ctrl->interface->surface_set_source_rectangle( ivisurf, 0, 0, surface->width, surface->height); @@ -658,14 +659,12 @@ set_notification_configure_desktop_surface(struct wl_listener *listener, void *d link); struct ivi_layout_layer *application_layer = layer_link->ivilayer; struct weston_surface *surface; - int32_t ret = 0; /* skip ui widgets */ if (is_surf_in_ui_widget(hmi_ctrl, ivisurf)) return; - ret = hmi_ctrl->interface->layer_add_surface(application_layer, ivisurf); - assert(!ret); + hmi_ctrl->interface->layer_add_surface(application_layer, ivisurf); /* * if application changes size of wl_buffer. The source rectangle shall be @@ -673,6 +672,8 @@ set_notification_configure_desktop_surface(struct wl_listener *listener, void *d */ surface = hmi_ctrl->interface->surface_get_weston_surface(ivisurf); if (surface) { + if (!weston_surface_has_content(surface)) + return; hmi_ctrl->interface->surface_set_source_rectangle(ivisurf, 0, 0, surface->width, surface->height); } @@ -681,6 +682,106 @@ set_notification_configure_desktop_surface(struct wl_listener *listener, void *d switch_mode(hmi_ctrl, hmi_ctrl->layout_mode); } +static void +set_notification_configure_input_panel_surface(struct wl_listener *listener, void *data) +{ + struct hmi_controller *hmi_ctrl = + wl_container_of(listener, hmi_ctrl, + input_panel_configured); + struct ivi_layout_surface *ivisurf = data; + struct weston_surface *surface; + struct hmi_controller_layer *layer_link; + + wl_list_for_each(layer_link, &hmi_ctrl->input_panel_layer_list, link) { + hmi_ctrl->interface->layer_add_surface(layer_link->ivilayer, ivisurf); + } + + surface = hmi_ctrl->interface->surface_get_weston_surface(ivisurf); + if (surface) + hmi_ctrl->interface->surface_set_source_rectangle(ivisurf, 0, + 0, surface->width, surface->height); +} + +static void +update_input_panel_position(struct hmi_controller *hmi_ctrl, + struct ivi_layout_text_input_state *state) +{ + struct weston_surface *surface; + int32_t x, y; + + surface = hmi_ctrl->interface->surface_get_weston_surface(state->input_panel); + + if (state->overlay_panel) { + const struct ivi_layout_surface_properties *props; + + props = hmi_ctrl->interface->get_properties_of_surface(state->surface); + x = props->dest_x + state->cursor_rectangle.x2; + y = props->dest_y + state->cursor_rectangle.y2; + } + else { + x = (hmi_ctrl->active_input_panel_layer->width - surface->width) / 2; + y = hmi_ctrl->active_input_panel_layer->height - surface->height; + } + + hmi_ctrl->interface->surface_set_destination_rectangle(state->input_panel, + x, y, + surface->width, + surface->height); + hmi_ctrl->interface->surface_set_visibility(state->input_panel, true); + hmi_ctrl->interface->commit_changes(); +} + +static void +set_notification_show_input_panel(struct wl_listener *listener, void *data) +{ + struct hmi_controller *hmi_ctrl = + wl_container_of(listener, hmi_ctrl, + input_panel_show); + struct ivi_layout_text_input_state *state = data; + struct hmi_controller_layer *layer_link; + struct weston_surface *focus; + + focus = hmi_ctrl->interface->surface_get_weston_surface(state->surface); + + wl_list_for_each(layer_link, &hmi_ctrl->input_panel_layer_list, link) { + if (layer_link->output != focus->output) + continue; + + hmi_ctrl->interface->layer_set_visibility(layer_link->ivilayer, true); + hmi_ctrl->active_input_panel_layer = layer_link; + update_input_panel_position(hmi_ctrl, state); + } +} + +static void +set_notification_hide_input_panel(struct wl_listener *listener, void *data) +{ + struct hmi_controller *hmi_ctrl = + wl_container_of(listener, hmi_ctrl, + input_panel_hide); + + if (!hmi_ctrl->active_input_panel_layer) + return; + + hmi_ctrl->interface->layer_set_visibility(hmi_ctrl->active_input_panel_layer->ivilayer, false); + hmi_ctrl->active_input_panel_layer = NULL; + hmi_ctrl->interface->commit_changes(); +} + +static void +set_notification_update_input_panel(struct wl_listener *listener, void *data) +{ + struct hmi_controller *hmi_ctrl = + wl_container_of(listener, hmi_ctrl, + input_panel_update); + struct ivi_layout_text_input_state *state = data; + + if (!hmi_ctrl->active_input_panel_layer) + return; + + update_input_panel_position(hmi_ctrl, state); +} + /** * A hmi_controller used 4 ivi_layers to manage ivi_surfaces. The IDs of * corresponding ivi_layer are defined in weston.ini. Default scene graph @@ -689,7 +790,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; @@ -711,6 +812,9 @@ hmi_server_setting_create(struct weston_compositor *ec) weston_config_section_get_uint(shell_section, "application-layer-id", &setting->application_layer_id, 4000); + weston_config_section_get_uint(shell_section, "input-panel-layer-id", + &setting->input_panel_layer_id, 5000); + weston_config_section_get_uint(shell_section, "base-layer-id-offset", &setting->base_layer_id_offset, 10000); @@ -745,6 +849,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); @@ -765,6 +871,17 @@ hmi_controller_destroy(struct wl_listener *listener, void *data) free(ctrl_layer_link); } + /* clear input_panel_layer_list */ + wl_list_for_each_safe(ctrl_layer_link, ctrl_layer_next, + &hmi_ctrl->input_panel_layer_list, link) { + wl_list_remove(&ctrl_layer_link->link); + free(ctrl_layer_link); + } + + wl_list_remove(&hmi_ctrl->surface_removed.link); + wl_list_remove(&hmi_ctrl->surface_configured.link); + wl_list_remove(&hmi_ctrl->desktop_surface_configured.link); + wl_array_release(&hmi_ctrl->ui_widgets); free(hmi_ctrl->hmi_setting); free(hmi_ctrl); @@ -794,6 +911,7 @@ hmi_controller_create(struct weston_compositor *ec) const struct ivi_layout_interface *interface; struct hmi_controller_layer *base_layer = NULL; struct hmi_controller_layer *application_layer = NULL; + struct hmi_controller_layer *input_panel_layer = NULL; struct weston_output *output; int32_t i; @@ -804,7 +922,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 +935,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 +955,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; @@ -870,9 +988,27 @@ hmi_controller_create(struct weston_compositor *ec) hmi_ctrl->interface->layer_set_visibility( hmi_ctrl->workspace_background_layer.ivilayer, false); + i = 0; + /* init input panel ivi_layer */ + wl_list_init(&hmi_ctrl->input_panel_layer_list); + wl_list_for_each(output, &ec->output_list, link) { + input_panel_layer = xzalloc(sizeof(struct hmi_controller_layer)); + input_panel_layer->x = 0; + input_panel_layer->y = 0; + input_panel_layer->width = output->current_mode->width; + input_panel_layer->height = output->current_mode->height - panel_height; + input_panel_layer->id_layer = + hmi_ctrl->hmi_setting->input_panel_layer_id + + (i * hmi_ctrl->hmi_setting->base_layer_id_offset); + wl_list_insert(&hmi_ctrl->input_panel_layer_list, &input_panel_layer->link); + + create_layer(output, input_panel_layer, hmi_ctrl); + hmi_ctrl->interface->layer_set_visibility(input_panel_layer->ivilayer, false); + i++; + } 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, @@ -887,9 +1023,24 @@ hmi_controller_create(struct weston_compositor *ec) hmi_ctrl->desktop_surface_configured.notify = set_notification_configure_desktop_surface; hmi_ctrl->interface->add_listener_configure_desktop_surface(&hmi_ctrl->desktop_surface_configured); - hmi_ctrl->destroy_listener.notify = hmi_controller_destroy; - wl_signal_add(&hmi_ctrl->compositor->destroy_signal, - &hmi_ctrl->destroy_listener); + hmi_ctrl->input_panel_configured.notify = set_notification_configure_input_panel_surface; + hmi_ctrl->interface->add_listener_configure_input_panel_surface(&hmi_ctrl->input_panel_configured); + + hmi_ctrl->input_panel_show.notify = set_notification_show_input_panel; + hmi_ctrl->interface->add_listener_show_input_panel(&hmi_ctrl->input_panel_show); + + hmi_ctrl->input_panel_hide.notify = set_notification_hide_input_panel; + hmi_ctrl->interface->add_listener_hide_input_panel(&hmi_ctrl->input_panel_hide); + + hmi_ctrl->input_panel_update.notify = set_notification_update_input_panel; + hmi_ctrl->interface->add_listener_update_input_panel(&hmi_ctrl->input_panel_update); + + if(hmi_ctrl->interface->shell_add_destroy_listener_once(&hmi_ctrl->destroy_listener, + hmi_controller_destroy) == IVI_FAILED){ + hmi_controller_destroy(&hmi_ctrl->destroy_listener, NULL); + return NULL; + } + return hmi_ctrl; } @@ -916,7 +1067,6 @@ ivi_hmi_controller_set_background(struct hmi_controller *hmi_ctrl, int32_t dsty; int32_t width; int32_t height; - int32_t ret = 0; int32_t i = 0; wl_list_for_each_reverse(base_layer, &hmi_ctrl->base_layer_list, link) { @@ -932,15 +1082,12 @@ ivi_hmi_controller_set_background(struct hmi_controller *hmi_ctrl, ivisurf = hmi_ctrl->interface->get_surface_from_id(*add_surface_id); assert(ivisurf != NULL); - ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); - assert(!ret); + hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); - ret = hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, + hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, dstx, dsty, width, height); - assert(!ret); - ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); - assert(!ret); + hmi_ctrl->interface->surface_set_visibility(ivisurf, true); i++; } @@ -961,7 +1108,6 @@ ivi_hmi_controller_set_panel(struct hmi_controller *hmi_ctrl, struct hmi_controller_layer *base_layer; struct ivi_layout_layer *ivilayer = NULL; int32_t width; - int32_t ret = 0; int32_t panel_height = hmi_ctrl->hmi_setting->panel_height; const int32_t dstx = 0; int32_t dsty = 0; @@ -976,18 +1122,15 @@ ivi_hmi_controller_set_panel(struct hmi_controller *hmi_ctrl, ivisurf = hmi_ctrl->interface->get_surface_from_id(*add_surface_id); assert(ivisurf != NULL); - ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); - assert(!ret); + hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); dsty = base_layer->height - panel_height; width = base_layer->width; - ret = hmi_ctrl->interface->surface_set_destination_rectangle( + hmi_ctrl->interface->surface_set_destination_rectangle( ivisurf, dstx, dsty, width, panel_height); - assert(!ret); - ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); - assert(!ret); + hmi_ctrl->interface->surface_set_visibility(ivisurf, true); i++; } @@ -1014,7 +1157,6 @@ ivi_hmi_controller_set_button(struct hmi_controller *hmi_ctrl, struct ivi_layout_layer *ivilayer = base_layer->ivilayer; const int32_t width = 48; const int32_t height = 48; - int32_t ret = 0; int32_t panel_height = 0; int32_t dstx = 0; int32_t dsty = 0; @@ -1025,20 +1167,17 @@ ivi_hmi_controller_set_button(struct hmi_controller *hmi_ctrl, ivisurf = hmi_ctrl->interface->get_surface_from_id(id_surface); assert(ivisurf != NULL); - ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); - assert(!ret); + hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); panel_height = hmi_ctrl->hmi_setting->panel_height; dstx = (60 * number) + 15; dsty = (base_layer->height - panel_height) + 5; - ret = hmi_ctrl->interface->surface_set_destination_rectangle( + hmi_ctrl->interface->surface_set_destination_rectangle( ivisurf,dstx, dsty, width, height); - assert(!ret); - ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); - assert(!ret); + hmi_ctrl->interface->surface_set_visibility(ivisurf, true); } /** @@ -1058,7 +1197,6 @@ ivi_hmi_controller_set_home_button(struct hmi_controller *hmi_ctrl, base_layer, link); struct ivi_layout_layer *ivilayer = base_layer->ivilayer; - int32_t ret = 0; int32_t size = 48; int32_t panel_height = hmi_ctrl->hmi_setting->panel_height; const int32_t dstx = (base_layer->width - size) / 2; @@ -1071,15 +1209,12 @@ ivi_hmi_controller_set_home_button(struct hmi_controller *hmi_ctrl, ivisurf = hmi_ctrl->interface->get_surface_from_id(id_surface); assert(ivisurf != NULL); - ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); - assert(!ret); + hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); - ret = hmi_ctrl->interface->surface_set_destination_rectangle( + hmi_ctrl->interface->surface_set_destination_rectangle( ivisurf, dstx, dsty, size, size); - assert(!ret); - ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); - assert(!ret); + hmi_ctrl->interface->surface_set_visibility(ivisurf, true); } /** @@ -1097,7 +1232,6 @@ ivi_hmi_controller_set_workspacebackground(struct hmi_controller *hmi_ctrl, struct ivi_layout_layer *ivilayer = NULL; const int32_t width = hmi_ctrl->workspace_background_layer.width; const int32_t height = hmi_ctrl->workspace_background_layer.height; - int32_t ret = 0; uint32_t *add_surface_id = wl_array_add(&hmi_ctrl->ui_widgets, sizeof(*add_surface_id)); @@ -1107,15 +1241,12 @@ ivi_hmi_controller_set_workspacebackground(struct hmi_controller *hmi_ctrl, ivisurf = hmi_ctrl->interface->get_surface_from_id(id_surface); assert(ivisurf != NULL); - ret = hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); - assert(!ret); + hmi_ctrl->interface->layer_add_surface(ivilayer, ivisurf); - ret = hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, + hmi_ctrl->interface->surface_set_destination_rectangle(ivisurf, 0, 0, width, height); - assert(!ret); - ret = hmi_ctrl->interface->surface_set_visibility(ivisurf, true); - assert(!ret); + hmi_ctrl->interface->surface_set_visibility(ivisurf, true); } /** @@ -1161,7 +1292,6 @@ ivi_hmi_controller_add_launchers(struct hmi_controller *hmi_ctrl, int32_t x = 0; int32_t y = 0; - int32_t ret = 0; struct ivi_layout_surface* layout_surface = NULL; uint32_t *add_surface_id = NULL; @@ -1239,9 +1369,8 @@ ivi_hmi_controller_add_launchers(struct hmi_controller *hmi_ctrl, hmi_ctrl->interface->get_surface_from_id(data->surface_id); assert(layout_surface); - ret = hmi_ctrl->interface->surface_set_destination_rectangle( + hmi_ctrl->interface->surface_set_destination_rectangle( layout_surface, x, y, icon_size, icon_size); - assert(!ret); nx++; @@ -1267,7 +1396,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); @@ -1278,12 +1407,10 @@ ivi_hmi_controller_add_launchers(struct hmi_controller *hmi_ctrl, hmi_ctrl->interface->get_surface_from_id(data->surface_id); assert(layout_surface); - ret = hmi_ctrl->interface->layer_add_surface(hmi_ctrl->workspace_layer.ivilayer, - layout_surface); - assert(!ret); + hmi_ctrl->interface->layer_add_surface(hmi_ctrl->workspace_layer.ivilayer, + layout_surface); - ret = hmi_ctrl->interface->surface_set_visibility(layout_surface, true); - assert(!ret); + hmi_ctrl->interface->surface_set_visibility(layout_surface, true); } wl_array_release(&launchers); @@ -1460,9 +1587,11 @@ pointer_move_workspace_grab_end(struct pointer_grab *grab) struct pointer_move_grab *pnt_move_grab = (struct pointer_move_grab *)grab; struct ivi_layout_layer *layer = pnt_move_grab->base.layer; + wl_fixed_t x; + x = wl_fixed_from_double(grab->grab.pointer->grab_pos.c.x); move_workspace_grab_end(&pnt_move_grab->move, grab->resource, - grab->grab.pointer->grab_x, layer); + x, layer); weston_pointer_end_grab(grab->grab.pointer); } @@ -1568,9 +1697,11 @@ pointer_move_grab_motion(struct weston_pointer_grab *grab, struct hmi_controller *hmi_ctrl = wl_resource_get_user_data(pnt_move_grab->base.resource); wl_fixed_t pointer_pos[2]; + struct weston_coord_global pos; - weston_pointer_motion_to_abs(grab->pointer, event, - &pointer_pos[0], &pointer_pos[1]); + pos = weston_pointer_motion_to_abs(grab->pointer, event); + pointer_pos[0] = wl_fixed_from_double(pos.c.x); + pointer_pos[1] = wl_fixed_from_double(pos.c.y); move_grab_update(&pnt_move_grab->move, pointer_pos); layer_set_pos(hmi_ctrl, pnt_move_grab->base.layer, pnt_move_grab->move.pos[0], pnt_move_grab->move.pos[1]); @@ -1756,11 +1887,13 @@ 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, - pointer->grab_y, resource); + move_grab_init_workspace(&pnt_move_grab->move, + wl_fixed_from_double(pointer->grab_pos.c.x), + wl_fixed_from_double(pointer->grab_pos.c.y), + resource); return pnt_move_grab; } @@ -1770,7 +1903,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; @@ -1981,10 +2114,6 @@ wet_module_init(struct weston_compositor *ec, struct hmi_controller *hmi_ctrl = NULL; struct wl_event_loop *loop = NULL; - /* ad hoc weston_compositor_add_destroy_listener_once() */ - if (wl_signal_get(&ec->destroy_signal, hmi_controller_destroy)) - return 0; - hmi_ctrl = hmi_controller_create(ec); if (hmi_ctrl == NULL) return -1; diff --git a/ivi-shell/ivi-layout-export.h b/ivi-shell/ivi-layout-export.h index 740e1ac0f..8f934608d 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,9 +64,14 @@ extern "C" { #define IVI_INVALID_ID UINT_MAX struct ivi_layout_layer; -struct ivi_layout_screen; struct ivi_layout_surface; +enum ivi_layout_surface_type { + IVI_LAYOUT_SURFACE_TYPE_IVI, + IVI_LAYOUT_SURFACE_TYPE_DESKTOP, + IVI_LAYOUT_SURFACE_TYPE_INPUT_PANEL, +}; + struct ivi_layout_surface_properties { wl_fixed_t opacity; @@ -90,6 +92,7 @@ struct ivi_layout_surface_properties int32_t transition_type; uint32_t transition_duration; uint32_t event_mask; + enum ivi_layout_surface_type surface_type; }; struct ivi_layout_layer_properties @@ -113,6 +116,14 @@ struct ivi_layout_layer_properties uint32_t event_mask; }; +struct ivi_layout_text_input_state +{ + bool overlay_panel; + struct ivi_layout_surface *surface; + struct ivi_layout_surface *input_panel; + pixman_box32_t cursor_rectangle; +}; + enum ivi_layout_notification_mask { IVI_NOTIFICATION_NONE = 0, IVI_NOTIFICATION_OPACITY = (1 << 1), @@ -156,6 +167,14 @@ struct ivi_layout_interface { */ int32_t (*commit_changes)(void); + /** + * \brief Rebuild view list without applying any new changes + * + * \return IVI_SUCCEEDED if the method call was successful + * \return IVI_FAILED if the method call was failed + */ + int32_t (*commit_current)(void); + /** * surface controller interface */ @@ -168,7 +187,7 @@ struct ivi_layout_interface { * The pointer of the created ivi_surface is sent as the void *data argument * to the wl_listener::notify callback function of the listener. */ - int32_t (*add_listener_create_surface)(struct wl_listener *listener); + void (*add_listener_create_surface)(struct wl_listener *listener); /** * \brief add a listener for notification when ivi_surface is removed @@ -178,7 +197,7 @@ struct ivi_layout_interface { * The pointer of the removed ivi_surface is sent as the void *data argument * to the wl_listener::notify callback function of the listener. */ - int32_t (*add_listener_remove_surface)(struct wl_listener *listener); + void (*add_listener_remove_surface)(struct wl_listener *listener); /** * \brief add a listener for notification when ivi_surface is configured @@ -188,7 +207,7 @@ struct ivi_layout_interface { * The pointer of the configured ivi_surface is sent as the void *data argument * to the wl_listener::notify callback function of the listener. */ - int32_t (*add_listener_configure_surface)(struct wl_listener *listener); + void (*add_listener_configure_surface)(struct wl_listener *listener); /** * \brief add a listener for notification when desktop_surface is configured @@ -198,16 +217,13 @@ struct ivi_layout_interface { * The pointer of the configured desktop_surface is sent as the void *data argument * to the wl_listener::notify callback function of the listener. */ - int32_t (*add_listener_configure_desktop_surface)(struct wl_listener *listener); + void (*add_listener_configure_desktop_surface)(struct wl_listener *listener); /** * \brief Get all ivi_surfaces which are currently registered and managed * by the services - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed */ - int32_t (*get_surfaces)(int32_t *pLength, struct ivi_layout_surface ***ppArray); + void (*get_surfaces)(int32_t *pLength, struct ivi_layout_surface ***ppArray); /** * \brief get id of ivi_surface from ivi_layout_surface @@ -239,56 +255,44 @@ struct ivi_layout_interface { /** * \brief Get all Surfaces which are currently registered to a given * layer and are managed by the services - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed */ - int32_t (*get_surfaces_on_layer)(struct ivi_layout_layer *ivilayer, - int32_t *pLength, - struct ivi_layout_surface ***ppArray); + void (*get_surfaces_on_layer)(struct ivi_layout_layer *ivilayer, + int32_t *pLength, + struct ivi_layout_surface ***ppArray); /** * \brief Set the visibility of a ivi_surface. * * If a surface is not visible it will not be rendered. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed */ - int32_t (*surface_set_visibility)(struct ivi_layout_surface *ivisurf, - bool newVisibility); + void (*surface_set_visibility)(struct ivi_layout_surface *ivisurf, + bool newVisibility); /** * \brief Set the opacity of a surface. * * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed + * \return IVI_FAILED if the specified opacity is out of bounds */ int32_t (*surface_set_opacity)(struct ivi_layout_surface *ivisurf, wl_fixed_t opacity); /** * \brief Set the area of a ivi_surface which should be used for the rendering. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed */ - int32_t (*surface_set_source_rectangle)(struct ivi_layout_surface *ivisurf, - int32_t x, int32_t y, - int32_t width, int32_t height); + void (*surface_set_source_rectangle)(struct ivi_layout_surface *ivisurf, + int32_t x, int32_t y, + int32_t width, int32_t height); /** * \brief Set the destination area of a ivi_surface within a ivi_layer * for rendering. * * The surface will be scaled to this rectangle for rendering. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed */ - int32_t (*surface_set_destination_rectangle)(struct ivi_layout_surface *ivisurf, - int32_t x, int32_t y, - int32_t width, int32_t height); + void (*surface_set_destination_rectangle)(struct ivi_layout_surface *ivisurf, + int32_t x, int32_t y, + int32_t width, int32_t height); /** * \brief add a listener to listen property changes of ivi_surface @@ -297,11 +301,8 @@ struct ivi_layout_interface { * signal is emitted to the listening controller plugins. * The pointer of the ivi_surface is sent as the void *data argument * to the wl_listener::notify callback function of the listener. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed */ - int32_t (*surface_add_listener)(struct ivi_layout_surface *ivisurf, + void (*surface_add_listener)(struct ivi_layout_surface *ivisurf, struct wl_listener *listener); /** @@ -313,16 +314,15 @@ struct ivi_layout_interface { /** * \brief set type of transition animation */ - int32_t (*surface_set_transition)(struct ivi_layout_surface *ivisurf, - enum ivi_layout_transition_type type, - uint32_t duration); + void (*surface_set_transition)(struct ivi_layout_surface *ivisurf, + enum ivi_layout_transition_type type, + uint32_t duration); /** * \brief set duration of transition animation */ - int32_t (*surface_set_transition_duration)( - struct ivi_layout_surface *ivisurf, - uint32_t duration); + void (*surface_set_transition_duration)(struct ivi_layout_surface *ivisurf, + uint32_t duration); /** * \brief set id of ivi_layout_surface @@ -342,7 +342,7 @@ struct ivi_layout_interface { * The pointer of the created ivi_layer is sent as the void *data argument * to the wl_listener::notify callback function of the listener. */ - int32_t (*add_listener_create_layer)(struct wl_listener *listener); + void (*add_listener_create_layer)(struct wl_listener *listener); /** * \brief add a listener for notification when ivi_layer is removed @@ -352,7 +352,7 @@ struct ivi_layout_interface { * The pointer of the removed ivi_layer is sent as the void *data argument * to the wl_listener::notify callback function of the listener. */ - int32_t (*add_listener_remove_layer)(struct wl_listener *listener); + void (*add_listener_remove_layer)(struct wl_listener *listener); /** * \brief Create a ivi_layer which should be managed by the service @@ -373,11 +373,8 @@ struct ivi_layout_interface { /** * \brief Get all ivi_layers which are currently registered and managed * by the services - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed */ - int32_t (*get_layers)(int32_t *pLength, struct ivi_layout_layer ***ppArray); + void (*get_layers)(int32_t *pLength, struct ivi_layout_layer ***ppArray); /** * \brief get id of ivi_layer from ivi_layout_layer @@ -411,39 +408,30 @@ struct ivi_layout_interface { * * This means all the ivi-layers the ivi-surface was added to. It has * no relation to geometric overlaps. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed */ - int32_t (*get_layers_under_surface)(struct ivi_layout_surface *ivisurf, - int32_t *pLength, + void (*get_layers_under_surface)(struct ivi_layout_surface *ivisurf, + int32_t *pLength, struct ivi_layout_layer ***ppArray); /** * \brief Get all Layers of the given weston_output - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed */ - int32_t (*get_layers_on_screen)(struct weston_output *output, - int32_t *pLength, - struct ivi_layout_layer ***ppArray); + void (*get_layers_on_screen)(struct weston_output *output, + int32_t *pLength, + struct ivi_layout_layer ***ppArray); /** * \brief Set the visibility of a ivi_layer. If a ivi_layer is not visible, * the ivi_layer and its ivi_surfaces will not be rendered. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed */ - int32_t (*layer_set_visibility)(struct ivi_layout_layer *ivilayer, - bool newVisibility); + void (*layer_set_visibility)(struct ivi_layout_layer *ivilayer, + bool newVisibility); /** * \brief Set the opacity of a ivi_layer. * * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed + * \return IVI_FAILED if the specified opacity is out of bounds */ int32_t (*layer_set_opacity)(struct ivi_layout_layer *ivilayer, wl_fixed_t opacity); @@ -452,34 +440,25 @@ struct ivi_layout_interface { * \brief Set the area of a ivi_layer which should be used for the rendering. * * Only this part will be visible. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed */ - int32_t (*layer_set_source_rectangle)(struct ivi_layout_layer *ivilayer, - int32_t x, int32_t y, - int32_t width, int32_t height); + void (*layer_set_source_rectangle)(struct ivi_layout_layer *ivilayer, + int32_t x, int32_t y, + int32_t width, int32_t height); /** * \brief Set the destination area on the display for a ivi_layer. * * The ivi_layer will be scaled and positioned to this rectangle * for rendering - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed */ - int32_t (*layer_set_destination_rectangle)(struct ivi_layout_layer *ivilayer, - int32_t x, int32_t y, - int32_t width, int32_t height); + void (*layer_set_destination_rectangle)(struct ivi_layout_layer *ivilayer, + int32_t x, int32_t y, + int32_t width, int32_t height); /** * \brief Add a ivi_surface to a ivi_layer which is currently managed by the service - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed */ - int32_t (*layer_add_surface)(struct ivi_layout_layer *ivilayer, + void (*layer_add_surface)(struct ivi_layout_layer *ivilayer, struct ivi_layout_surface *addsurf); /** @@ -490,13 +469,10 @@ struct ivi_layout_interface { /** * \brief Sets render order of ivi_surfaces within a ivi_layer - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed */ - int32_t (*layer_set_render_order)(struct ivi_layout_layer *ivilayer, - struct ivi_layout_surface **pSurface, - int32_t number); + void (*layer_set_render_order)(struct ivi_layout_layer *ivilayer, + struct ivi_layout_surface **pSurface, + int32_t number); /** * \brief add a listener to listen property changes of ivi_layer @@ -505,19 +481,16 @@ struct ivi_layout_interface { * signal is emitted to the listening controller plugins. * The pointer of the ivi_layer is sent as the void *data argument * to the wl_listener::notify callback function of the listener. - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed */ - int32_t (*layer_add_listener)(struct ivi_layout_layer *ivilayer, - struct wl_listener *listener); + void (*layer_add_listener)(struct ivi_layout_layer *ivilayer, + struct wl_listener *listener); /** * \brief set type of transition animation */ - int32_t (*layer_set_transition)(struct ivi_layout_layer *ivilayer, - enum ivi_layout_transition_type type, - uint32_t duration); + void (*layer_set_transition)(struct ivi_layout_layer *ivilayer, + enum ivi_layout_transition_type type, + uint32_t duration); /** * screen controller interface @@ -525,31 +498,22 @@ struct ivi_layout_interface { /** * \brief Get the weston_outputs under the given ivi_layer - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed */ - int32_t (*get_screens_under_layer)(struct ivi_layout_layer *ivilayer, - int32_t *pLength, - struct weston_output ***ppArray); + void (*get_screens_under_layer)(struct ivi_layout_layer *ivilayer, + int32_t *pLength, + struct weston_output ***ppArray); /** * \brief Add a ivi_layer to a weston_output which is currently managed * by the service - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed */ - int32_t (*screen_add_layer)(struct weston_output *output, + void (*screen_add_layer)(struct weston_output *output, struct ivi_layout_layer *addlayer); /** * \brief Sets render order of ivi_layers on a weston_output - * - * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed */ - int32_t (*screen_set_render_order)(struct weston_output *output, + void (*screen_set_render_order)(struct weston_output *output, struct ivi_layout_layer **pLayer, const int32_t number); @@ -557,16 +521,16 @@ struct ivi_layout_interface { * transition animation for layer */ void (*transition_move_layer_cancel)(struct ivi_layout_layer *layer); - int32_t (*layer_set_fade_info)(struct ivi_layout_layer* ivilayer, - uint32_t is_fade_in, - double start_alpha, double end_alpha); + void (*layer_set_fade_info)(struct ivi_layout_layer* ivilayer, + uint32_t is_fade_in, + double start_alpha, double end_alpha); /** * surface content dumping for debugging */ - int32_t (*surface_get_size)(struct ivi_layout_surface *ivisurf, - int32_t *width, int32_t *height, - int32_t *stride); + void (*surface_get_size)(struct ivi_layout_surface *ivisurf, + int32_t *width, int32_t *height, + int32_t *stride); int32_t (*surface_dump)(struct weston_surface *surface, void *target, size_t size, @@ -585,12 +549,78 @@ struct ivi_layout_interface { /** * \brief Remove a ivi_layer to a weston_output which is currently managed * by the service + */ + void (*screen_remove_layer)(struct weston_output *output, + struct ivi_layout_layer *removelayer); + + /** + * \brief Add a shell destroy listener only once. + * + * The begining of shell destroying, this signal is emitted + * to the listening controller plugins. + * The null pointer sent as the void *data argument + * to the wl_listener::notify callback function of the listener. * * \return IVI_SUCCEEDED if the method call was successful - * \return IVI_FAILED if the method call was failed + * \return IVI_FAILED if the method was called before + */ + int32_t (*shell_add_destroy_listener_once)(struct wl_listener *listener, + wl_notify_func_t destroy_handler); + + /** + * \brief add a listener for notification when input_panel_surface is + * configured + * + * When an input_panel_surface is configured, a signal is emitted + * to the listening controller plugins. + * The pointer of the configured input_panel_surface is sent as the void + * *data argument to the wl_listener::notify callback function of the + * listener. + */ + void (*add_listener_configure_input_panel_surface)(struct wl_listener *listener); + + /** + * \brief add a listener for notification when an input_panel_surface + * should be shown. + * + * When a client requests input panels, this signal is emitted for all + * available input panels. + * A pointer to a struct ivi_layout_text_input_state is sent as the void + * *data argument to the wl_listener::notify callback function of the + * listener. + * It contains the surface that requested the input panel, the + * input_panel_surface that should be shown and whether the input panel + * is a toplevel o overlay panel. + * For overlay panels, the relevant cursor_rectangle is filled with + * coordinates relative to the client surface. + */ + void (*add_listener_show_input_panel)(struct wl_listener *listener); + + /** + * \brief add a listener for notification when an input_panel_surface + * should be hidden. + * + * When a client requests that input panels are hidden, this signal is + * emitted for all available input panels. + * The pointer of the configured input_panel_surface is sent as the void + * *data argument to the wl_listener::notify callback function of the + * listener. + */ + void (*add_listener_hide_input_panel)(struct wl_listener *listener); + + /** + * \brief add a listener for notification when an input_panel_surface + * should be updated. + * + * When the input panels need to be updated in some way, this signal is + * called for available input panels. + * This happens for example when the cursor_rectangle changes. + * A pointer to a struct ivi_layout_text_input_state is sent as the void + * *data argument to the wl_listener::notify callback function of the + * listener. + * See add_listener_show_input_panel for more details. */ - int32_t (*screen_remove_layer)(struct weston_output *output, - struct ivi_layout_layer *removelayer); + void (*add_listener_update_input_panel)(struct wl_listener *listener); }; static inline const struct ivi_layout_interface * diff --git a/ivi-shell/ivi-layout-private.h b/ivi-shell/ivi-layout-private.h index 5a0119c63..21261e54f 100644 --- a/ivi-shell/ivi-layout-private.h +++ b/ivi-shell/ivi-layout-private.h @@ -30,7 +30,7 @@ #include #include "ivi-layout-export.h" -#include +#include struct ivi_layout_view { struct wl_list link; /* ivi_layout::view_list */ @@ -55,6 +55,8 @@ struct ivi_layout_surface { struct weston_surface *surface; struct weston_desktop_surface *weston_desktop_surface; + struct ivi_layout_view *ivi_view; + struct ivi_layout_surface_properties prop; struct { @@ -90,13 +92,17 @@ struct ivi_layout_layer { }; struct ivi_layout { - struct weston_compositor *compositor; + struct ivi_shell *shell; struct wl_list surface_list; /* ivi_layout_surface::link */ struct wl_list layer_list; /* ivi_layout_layer::link */ struct wl_list screen_list; /* ivi_layout_screen::link */ struct wl_list view_list; /* ivi_layout_view::link */ + struct { + struct wl_signal destroy_signal; + } shell_notification; + struct { struct wl_signal created; struct wl_signal removed; @@ -109,10 +115,25 @@ struct ivi_layout { struct wl_signal configure_desktop_changed; } surface_notification; + struct { + struct wl_signal configure_changed; + struct wl_signal show; + struct wl_signal hide; + struct wl_signal update; + } input_panel_notification; + struct weston_layer layout_layer; struct ivi_layout_transition_set *transitions; struct wl_list pending_transition_list; /* transition_node::link */ + + struct wl_listener output_created; + struct wl_listener output_destroyed; + + struct { + struct ivi_layout_surface *ivisurf; + pixman_box32_t cursor_rectangle; + } text_input; }; struct ivi_layout *get_instance(void); @@ -170,14 +191,14 @@ int32_t ivi_layout_commit_changes(void); uint32_t ivi_layout_get_id_of_surface(struct ivi_layout_surface *ivisurf); -int32_t +void ivi_layout_surface_set_destination_rectangle(struct ivi_layout_surface *ivisurf, int32_t x, int32_t y, int32_t width, int32_t height); int32_t ivi_layout_surface_set_opacity(struct ivi_layout_surface *ivisurf, wl_fixed_t opacity); -int32_t +void ivi_layout_surface_set_visibility(struct ivi_layout_surface *ivisurf, bool newVisibility); void @@ -188,14 +209,14 @@ ivi_layout_get_surface_from_id(uint32_t id_surface); int32_t ivi_layout_layer_set_opacity(struct ivi_layout_layer *ivilayer, wl_fixed_t opacity); -int32_t +void ivi_layout_layer_set_visibility(struct ivi_layout_layer *ivilayer, bool newVisibility); -int32_t +void ivi_layout_layer_set_destination_rectangle(struct ivi_layout_layer *ivilayer, int32_t x, int32_t y, int32_t width, int32_t height); -int32_t +void ivi_layout_layer_set_render_order(struct ivi_layout_layer *ivilayer, struct ivi_layout_surface **pSurface, int32_t number); diff --git a/ivi-shell/ivi-layout-shell.h b/ivi-shell/ivi-layout-shell.h index 63f66fa9d..83762fc0f 100644 --- a/ivi-shell/ivi-layout-shell.h +++ b/ivi-shell/ivi-layout-shell.h @@ -27,6 +27,8 @@ #define IVI_LAYOUT_SHELL_H #include +#include +#include /* * This is the interface that ivi-layout exposes to ivi-shell. @@ -38,13 +40,37 @@ struct weston_compositor; struct weston_view; struct weston_surface; struct ivi_layout_surface; +struct ivi_shell; void 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_input_panel_surface_configure(struct ivi_layout_surface *ivisurf, + int32_t width, int32_t height); + +void +ivi_layout_update_text_input_cursor(pixman_box32_t *cursor_rectangle); + +void +ivi_layout_show_input_panel(struct ivi_layout_surface *ivisurf, + struct ivi_layout_surface *target_ivisurf, + bool overlay_panel); + +void +ivi_layout_hide_input_panel(struct ivi_layout_surface *ivisurf); + +void +ivi_layout_update_input_panel(struct ivi_layout_surface *ivisurf, + bool overlay_panel); + +struct ivi_layout_surface* +ivi_layout_input_panel_surface_create(struct weston_surface *wl_surface); void ivi_layout_surface_configure(struct ivi_layout_surface *ivisurf, @@ -55,7 +81,7 @@ ivi_layout_surface_create(struct weston_surface *wl_surface, uint32_t id_surface); void -ivi_layout_init_with_compositor(struct weston_compositor *ec); +ivi_layout_init(struct weston_compositor *ec, struct ivi_shell *shell); void ivi_layout_fini(void); @@ -67,4 +93,7 @@ int load_controller_modules(struct weston_compositor *compositor, const char *modules, int *argc, char *argv[]); +void +ivi_layout_ivi_shell_destroy(void); + #endif /* IVI_LAYOUT_SHELL_H */ diff --git a/ivi-shell/ivi-layout.c b/ivi-shell/ivi-layout.c index 0f0f4f8a6..af204a1d2 100644 --- a/ivi-shell/ivi-layout.c +++ b/ivi-shell/ivi-layout.c @@ -28,7 +28,7 @@ * not updated until ivi_layout_commit_changes is called. An overview from * calling API for updating properties of ivi_surface/ivi_layer to asking * compositor to compose them by using weston_view_schedule_repaint, - * 0/ initialize this library by ivi_layout_init_with_compositor + * 0/ initialize this library by ivi_layout_init * with (struct weston_compositor *ec) from ivi-shell. * 1/ When an API for updating properties of ivi_surface/ivi_layer, it updates * pending prop of ivi_surface/ivi_layer/ivi_screen which are structure to @@ -70,6 +70,8 @@ #include "shared/helpers.h" #include "shared/os-compatibility.h" +#include "shared/signal.h" +#include "shared/xalloc.h" #define max(a, b) ((a) > (b) ? (a) : (b)) @@ -166,11 +168,7 @@ ivi_view_create(struct ivi_layout_layer *ivilayer, { struct ivi_layout_view *ivi_view; - ivi_view = calloc(1, sizeof *ivi_view); - if (ivi_view == NULL) { - weston_log("fails to allocate memory\n"); - return NULL; - } + ivi_view = xzalloc(sizeof *ivi_view); if (weston_surface_is_desktop_surface(ivisurf->surface)) { ivi_view->view = weston_desktop_surface_create_view( @@ -185,6 +183,8 @@ ivi_view_create(struct ivi_layout_layer *ivilayer, return NULL; } + ivisurf->ivi_view = ivi_view; + weston_matrix_init(&ivi_view->transform.matrix); wl_list_init(&ivi_view->transform.link); @@ -258,34 +258,78 @@ ivi_layout_surface_destroy(struct ivi_layout_surface *ivisurf) free(ivisurf); } -/** - * Internal API to initialize ivi_screens found from output_list of weston_compositor. - * Called by ivi_layout_init_with_compositor. - */ static void -create_screen(struct weston_compositor *ec) +destroy_screen(struct ivi_layout_screen *iviscrn) +{ + struct ivi_layout_layer *ivilayer, *layer_next; + + wl_list_for_each_safe(ivilayer, layer_next, + &iviscrn->pending.layer_list, pending.link) { + wl_list_remove(&ivilayer->pending.link); + wl_list_init(&ivilayer->pending.link); + } + + assert(wl_list_empty(&iviscrn->pending.layer_list)); + + wl_list_for_each_safe(ivilayer, layer_next, + &iviscrn->order.layer_list, order.link) { + wl_list_remove(&ivilayer->order.link); + wl_list_init(&ivilayer->order.link); + ivilayer->on_screen = NULL; + } + + assert(wl_list_empty(&iviscrn->order.layer_list)); + + wl_list_remove(&iviscrn->link); + free(iviscrn); +} + +static void +output_destroyed_event(struct wl_listener *listener, void *data) +{ + struct weston_output *destroyed_output = data; + struct ivi_layout_screen *iviscrn; + + iviscrn = get_screen_from_output(destroyed_output); + assert(iviscrn != NULL); + destroy_screen(iviscrn); + +} + +static void +add_screen(struct weston_output *output) { struct ivi_layout *layout = get_instance(); struct ivi_layout_screen *iviscrn = NULL; - struct weston_output *output = NULL; - wl_list_for_each(output, &ec->output_list, link) { - iviscrn = calloc(1, sizeof *iviscrn); - if (iviscrn == NULL) { - weston_log("fails to allocate memory\n"); - continue; - } + iviscrn = xzalloc(sizeof *iviscrn); - iviscrn->layout = layout; + iviscrn->layout = layout; + iviscrn->output = output; - iviscrn->output = output; + wl_list_init(&iviscrn->pending.layer_list); + wl_list_init(&iviscrn->order.layer_list); + wl_list_insert(&layout->screen_list, &iviscrn->link); +} - wl_list_init(&iviscrn->pending.layer_list); +static void +output_created_event(struct wl_listener *listener, void *data) +{ + struct weston_output *created_output = data; + add_screen(created_output); +} - wl_list_init(&iviscrn->order.layer_list); +/** + * Internal API to initialize ivi_screens found from output_list of weston_compositor. + * Called by ivi_layout_init. + */ +static void +create_screen(struct weston_compositor *ec) +{ + struct weston_output *output = NULL; - wl_list_insert(&layout->screen_list, &iviscrn->link); - } + wl_list_for_each(output, &ec->output_list, link) + add_screen(output); } /** @@ -434,19 +478,19 @@ calc_inverse_matrix_transform(const struct weston_matrix *matrix, weston_matrix_transform(&m, &bottom_right); if (top_left.f[0] < bottom_right.f[0]) { - rect_output->x = top_left.f[0]; - rect_output->width = bottom_right.f[0] - rect_output->x; + rect_output->x = floorf(top_left.f[0]); + rect_output->width = ceilf(bottom_right.f[0] - rect_output->x); } else { - rect_output->x = bottom_right.f[0]; - rect_output->width = top_left.f[0] - rect_output->x; + rect_output->x = floorf(bottom_right.f[0]); + rect_output->width = ceilf(top_left.f[0] - rect_output->x); } if (top_left.f[1] < bottom_right.f[1]) { - rect_output->y = top_left.f[1]; - rect_output->height = bottom_right.f[1] - rect_output->y; + rect_output->y = floorf(top_left.f[1]); + rect_output->height = ceilf(bottom_right.f[1] - rect_output->y); } else { - rect_output->y = bottom_right.f[1]; - rect_output->height = top_left.f[1] - rect_output->y; + rect_output->y = floorf(bottom_right.f[1]); + rect_output->height = ceilf(top_left.f[1] - rect_output->y); } ivi_rectangle_intersect(rect_output, boundingbox, rect_output); @@ -486,10 +530,6 @@ calc_surface_to_global_matrix_and_mask_to_weston_surface( const struct ivi_layout_surface_properties *sp = &ivisurf->prop; const struct ivi_layout_layer_properties *lp = &ivilayer->prop; struct weston_output *output = iviscrn->output; - struct ivi_rectangle weston_surface_rect = { 0, - 0, - ivisurf->surface->width, - ivisurf->surface->height }; struct ivi_rectangle surface_source_rect = { sp->source_x, sp->source_y, sp->source_width, @@ -515,7 +555,6 @@ calc_surface_to_global_matrix_and_mask_to_weston_surface( lp->dest_y + output->y, lp->dest_width, lp->dest_height }; - struct ivi_rectangle surface_result; struct ivi_rectangle layer_dest_rect_in_global_intersected; /* @@ -532,12 +571,6 @@ calc_surface_to_global_matrix_and_mask_to_weston_surface( weston_matrix_translate(m, output->x, output->y, 0.0f); - /* this intersected ivi_rectangle would be used for masking - * weston_surface - */ - ivi_rectangle_intersect(&surface_source_rect, &weston_surface_rect, - &surface_result); - /* * destination rectangle of layer in multi screens coordinate * is intersected to avoid displaying outside of an assigned screen. @@ -548,7 +581,7 @@ calc_surface_to_global_matrix_and_mask_to_weston_surface( /* calc masking area of weston_surface from m */ calc_inverse_matrix_transform(m, &layer_dest_rect_in_global_intersected, - &surface_result, + &surface_source_rect, result); } @@ -835,7 +868,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; } } @@ -905,93 +938,79 @@ clear_view_pending_list(struct ivi_layout_layer *ivilayer) * Exported APIs of ivi-layout library are implemented from here. * Brief of APIs is described in ivi-layout-export.h. */ -static int32_t +static void ivi_layout_add_listener_create_layer(struct wl_listener *listener) { struct ivi_layout *layout = get_instance(); - if (listener == NULL) { - weston_log("ivi_layout_add_listener_create_layer: invalid argument\n"); - return IVI_FAILED; - } + assert(listener); wl_signal_add(&layout->layer_notification.created, listener); - - return IVI_SUCCEEDED; } -static int32_t +static void ivi_layout_add_listener_remove_layer(struct wl_listener *listener) { struct ivi_layout *layout = get_instance(); - if (listener == NULL) { - weston_log("ivi_layout_add_listener_remove_layer: invalid argument\n"); - return IVI_FAILED; - } + assert(listener); wl_signal_add(&layout->layer_notification.removed, listener); - - return IVI_SUCCEEDED; } -static int32_t +static void ivi_layout_add_listener_create_surface(struct wl_listener *listener) { struct ivi_layout *layout = get_instance(); - if (listener == NULL) { - weston_log("ivi_layout_add_listener_create_surface: invalid argument\n"); - return IVI_FAILED; - } + assert(listener); wl_signal_add(&layout->surface_notification.created, listener); - - return IVI_SUCCEEDED; } -static int32_t +static void ivi_layout_add_listener_remove_surface(struct wl_listener *listener) { struct ivi_layout *layout = get_instance(); - if (listener == NULL) { - weston_log("ivi_layout_add_listener_remove_surface: invalid argument\n"); - return IVI_FAILED; - } + assert(listener); wl_signal_add(&layout->surface_notification.removed, listener); - - return IVI_SUCCEEDED; } -static int32_t +static void ivi_layout_add_listener_configure_surface(struct wl_listener *listener) { struct ivi_layout *layout = get_instance(); - if (listener == NULL) { - weston_log("ivi_layout_add_listener_configure_surface: invalid argument\n"); - return IVI_FAILED; - } + assert(listener); wl_signal_add(&layout->surface_notification.configure_changed, listener); - - return IVI_SUCCEEDED; } -static int32_t +static void ivi_layout_add_listener_configure_desktop_surface(struct wl_listener *listener) { struct ivi_layout *layout = get_instance(); - if (!listener) { - weston_log("ivi_layout_add_listener_configure_desktop_surface: invalid argument\n"); - return IVI_FAILED; - } + assert(listener); wl_signal_add(&layout->surface_notification.configure_desktop_changed, listener); +} +static int32_t +ivi_layout_shell_add_destroy_listener_once(struct wl_listener *listener, wl_notify_func_t destroy_handler) +{ + struct ivi_layout *layout = get_instance(); + + assert(listener); + assert(destroy_handler); + + if (wl_signal_get(&layout->shell_notification.destroy_signal, destroy_handler)) + return IVI_FAILED; + + listener->notify = destroy_handler; + wl_signal_add(&layout->shell_notification.destroy_signal, listener); return IVI_SUCCEEDED; } @@ -1037,32 +1056,25 @@ ivi_layout_get_surface_from_id(uint32_t id_surface) return NULL; } -static int32_t +static void ivi_layout_surface_add_listener(struct ivi_layout_surface *ivisurf, struct wl_listener *listener) { - if (ivisurf == NULL || listener == NULL) { - weston_log("ivi_layout_surface_add_listener: invalid argument\n"); - return IVI_FAILED; - } + assert(ivisurf); + assert(listener); wl_signal_add(&ivisurf->property_changed, listener); - - return IVI_SUCCEEDED; } static const struct ivi_layout_layer_properties * ivi_layout_get_properties_of_layer(struct ivi_layout_layer *ivilayer) { - if (ivilayer == NULL) { - weston_log("ivi_layout_get_properties_of_layer: invalid argument\n"); - return NULL; - } + assert(ivilayer); return &ivilayer->prop; } -static int32_t +static void ivi_layout_get_screens_under_layer(struct ivi_layout_layer *ivilayer, int32_t *pLength, struct weston_output ***ppArray) @@ -1070,31 +1082,24 @@ ivi_layout_get_screens_under_layer(struct ivi_layout_layer *ivilayer, int32_t length = 0; int32_t n = 0; - if (ivilayer == NULL || pLength == NULL || ppArray == NULL) { - weston_log("ivi_layout_get_screens_under_layer: invalid argument\n"); - return IVI_FAILED; - } + assert(ivilayer); + assert(pLength); + assert(ppArray); if (ivilayer->on_screen != NULL) length = 1; if (length != 0) { /* the Array must be free by module which called this function */ - *ppArray = calloc(length, sizeof(struct weston_output *)); - if (*ppArray == NULL) { - weston_log("fails to allocate memory\n"); - return IVI_FAILED; - } + *ppArray = xcalloc(length, sizeof(struct weston_output *)); (*ppArray)[n++] = ivilayer->on_screen->output; } *pLength = length; - - return IVI_SUCCEEDED; } -static int32_t +static void ivi_layout_get_layers(int32_t *pLength, struct ivi_layout_layer ***ppArray) { struct ivi_layout *layout = get_instance(); @@ -1102,20 +1107,14 @@ ivi_layout_get_layers(int32_t *pLength, struct ivi_layout_layer ***ppArray) int32_t length = 0; int32_t n = 0; - if (pLength == NULL || ppArray == NULL) { - weston_log("ivi_layout_get_layers: invalid argument\n"); - return IVI_FAILED; - } + assert(pLength); + assert(ppArray); length = wl_list_length(&layout->layer_list); if (length != 0) { /* the Array must be freed by module which called this function */ - *ppArray = calloc(length, sizeof(struct ivi_layout_layer *)); - if (*ppArray == NULL) { - weston_log("fails to allocate memory\n"); - return IVI_FAILED; - } + *ppArray = xcalloc(length, sizeof(struct ivi_layout_layer *)); wl_list_for_each(ivilayer, &layout->layer_list, link) { (*ppArray)[n++] = ivilayer; @@ -1123,11 +1122,9 @@ ivi_layout_get_layers(int32_t *pLength, struct ivi_layout_layer ***ppArray) } *pLength = length; - - return IVI_SUCCEEDED; } -static int32_t +static void ivi_layout_get_layers_on_screen(struct weston_output *output, int32_t *pLength, struct ivi_layout_layer ***ppArray) @@ -1137,21 +1134,16 @@ ivi_layout_get_layers_on_screen(struct weston_output *output, int32_t length = 0; int32_t n = 0; - if (output == NULL || pLength == NULL || ppArray == NULL) { - weston_log("ivi_layout_get_layers_on_screen: invalid argument\n"); - return IVI_FAILED; - } + assert(output); + assert(pLength); + assert(ppArray); iviscrn = get_screen_from_output(output); length = wl_list_length(&iviscrn->order.layer_list); if (length != 0) { /* the Array must be freed by module which called this function */ - *ppArray = calloc(length, sizeof(struct ivi_layout_layer *)); - if (*ppArray == NULL) { - weston_log("fails to allocate memory\n"); - return IVI_FAILED; - } + *ppArray = xcalloc(length, sizeof(struct ivi_layout_layer *)); wl_list_for_each(ivilayer, &iviscrn->order.layer_list, order.link) { (*ppArray)[n++] = ivilayer; @@ -1159,11 +1151,9 @@ ivi_layout_get_layers_on_screen(struct weston_output *output, } *pLength = length; - - return IVI_SUCCEEDED; } -static int32_t +static void ivi_layout_get_layers_under_surface(struct ivi_layout_surface *ivisurf, int32_t *pLength, struct ivi_layout_layer ***ppArray) @@ -1172,19 +1162,14 @@ ivi_layout_get_layers_under_surface(struct ivi_layout_surface *ivisurf, int32_t length = 0; int32_t n = 0; - if (ivisurf == NULL || pLength == NULL || ppArray == NULL) { - weston_log("ivi_layout_getLayers: invalid argument\n"); - return IVI_FAILED; - } + assert(ivisurf); + assert(pLength); + assert(ppArray); if (!wl_list_empty(&ivisurf->view_list)) { /* the Array must be free by module which called this function */ length = wl_list_length(&ivisurf->view_list); - *ppArray = calloc(length, sizeof(struct ivi_layout_layer *)); - if (*ppArray == NULL) { - weston_log("fails to allocate memory\n"); - return IVI_FAILED; - } + *ppArray = xcalloc(length, sizeof(struct ivi_layout_layer *)); wl_list_for_each_reverse(ivi_view, &ivisurf->view_list, surf_link) { if (ivi_view_is_rendered(ivi_view)) @@ -1192,20 +1177,16 @@ 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; } -static -int32_t +static void ivi_layout_get_surfaces(int32_t *pLength, struct ivi_layout_surface ***ppArray) { struct ivi_layout *layout = get_instance(); @@ -1213,20 +1194,14 @@ ivi_layout_get_surfaces(int32_t *pLength, struct ivi_layout_surface ***ppArray) int32_t length = 0; int32_t n = 0; - if (pLength == NULL || ppArray == NULL) { - weston_log("ivi_layout_get_surfaces: invalid argument\n"); - return IVI_FAILED; - } + assert(pLength); + assert(ppArray); length = wl_list_length(&layout->surface_list); if (length != 0) { /* the Array must be freed by module which called this function */ - *ppArray = calloc(length, sizeof(struct ivi_layout_surface *)); - if (*ppArray == NULL) { - weston_log("fails to allocate memory\n"); - return IVI_FAILED; - } + *ppArray = xcalloc(length, sizeof(struct ivi_layout_surface *)); wl_list_for_each(ivisurf, &layout->surface_list, link) { (*ppArray)[n++] = ivisurf; @@ -1234,11 +1209,9 @@ ivi_layout_get_surfaces(int32_t *pLength, struct ivi_layout_surface ***ppArray) } *pLength = length; - - return IVI_SUCCEEDED; } -static int32_t +static void ivi_layout_get_surfaces_on_layer(struct ivi_layout_layer *ivilayer, int32_t *pLength, struct ivi_layout_surface ***ppArray) @@ -1247,20 +1220,15 @@ ivi_layout_get_surfaces_on_layer(struct ivi_layout_layer *ivilayer, int32_t length = 0; int32_t n = 0; - if (ivilayer == NULL || pLength == NULL || ppArray == NULL) { - weston_log("ivi_layout_getSurfaceIDsOnLayer: invalid argument\n"); - return IVI_FAILED; - } + assert(ivilayer); + assert(pLength); + assert(ppArray); length = wl_list_length(&ivilayer->order.view_list); if (length != 0) { /* the Array must be freed by module which called this function */ - *ppArray = calloc(length, sizeof(struct ivi_layout_surface *)); - if (*ppArray == NULL) { - weston_log("fails to allocate memory\n"); - return IVI_FAILED; - } + *ppArray = xcalloc(length, sizeof(struct ivi_layout_surface *)); wl_list_for_each(ivi_view, &ivilayer->order.view_list, order_link) { (*ppArray)[n++] = ivi_view->ivisurf; @@ -1268,8 +1236,6 @@ ivi_layout_get_surfaces_on_layer(struct ivi_layout_layer *ivilayer, } *pLength = length; - - return IVI_SUCCEEDED; } static struct ivi_layout_layer * @@ -1286,11 +1252,7 @@ ivi_layout_layer_create_with_dimension(uint32_t id_layer, return ivilayer; } - ivilayer = calloc(1, sizeof *ivilayer); - if (ivilayer == NULL) { - weston_log("fails to allocate memory\n"); - return NULL; - } + ivilayer = xzalloc(sizeof *ivilayer); ivilayer->ref_count = 1; wl_signal_init(&ivilayer->property_changed); @@ -1319,10 +1281,7 @@ ivi_layout_layer_destroy(struct ivi_layout_layer *ivilayer) struct ivi_layout *layout = get_instance(); struct ivi_layout_view *ivi_view, *next; - if (ivilayer == NULL) { - weston_log("ivi_layout_layer_destroy: invalid argument\n"); - return; - } + assert(ivilayer); if (--ivilayer->ref_count > 0) return; @@ -1342,16 +1301,13 @@ ivi_layout_layer_destroy(struct ivi_layout_layer *ivilayer) free(ivilayer); } -int32_t +void ivi_layout_layer_set_visibility(struct ivi_layout_layer *ivilayer, bool newVisibility) { struct ivi_layout_layer_properties *prop = NULL; - if (ivilayer == NULL) { - weston_log("ivi_layout_layer_set_visibility: invalid argument\n"); - return IVI_FAILED; - } + assert(ivilayer); prop = &ivilayer->pending.prop; prop->visibility = newVisibility; @@ -1360,8 +1316,6 @@ ivi_layout_layer_set_visibility(struct ivi_layout_layer *ivilayer, prop->event_mask |= IVI_NOTIFICATION_VISIBILITY; else prop->event_mask &= ~IVI_NOTIFICATION_VISIBILITY; - - return IVI_SUCCEEDED; } int32_t @@ -1370,8 +1324,9 @@ ivi_layout_layer_set_opacity(struct ivi_layout_layer *ivilayer, { struct ivi_layout_layer_properties *prop = NULL; - if (ivilayer == NULL || - opacity < wl_fixed_from_double(0.0) || + assert(ivilayer); + + if (opacity < wl_fixed_from_double(0.0) || wl_fixed_from_double(1.0) < opacity) { weston_log("ivi_layout_layer_set_opacity: invalid argument\n"); return IVI_FAILED; @@ -1388,17 +1343,14 @@ ivi_layout_layer_set_opacity(struct ivi_layout_layer *ivilayer, return IVI_SUCCEEDED; } -static int32_t +static void ivi_layout_layer_set_source_rectangle(struct ivi_layout_layer *ivilayer, int32_t x, int32_t y, int32_t width, int32_t height) { struct ivi_layout_layer_properties *prop = NULL; - if (ivilayer == NULL) { - weston_log("ivi_layout_layer_set_source_rectangle: invalid argument\n"); - return IVI_FAILED; - } + assert(ivilayer); prop = &ivilayer->pending.prop; prop->source_x = x; @@ -1412,21 +1364,16 @@ ivi_layout_layer_set_source_rectangle(struct ivi_layout_layer *ivilayer, prop->event_mask |= IVI_NOTIFICATION_SOURCE_RECT; else prop->event_mask &= ~IVI_NOTIFICATION_SOURCE_RECT; - - return IVI_SUCCEEDED; } -int32_t +void ivi_layout_layer_set_destination_rectangle(struct ivi_layout_layer *ivilayer, int32_t x, int32_t y, int32_t width, int32_t height) { struct ivi_layout_layer_properties *prop = NULL; - if (ivilayer == NULL) { - weston_log("ivi_layout_layer_set_destination_rectangle: invalid argument\n"); - return IVI_FAILED; - } + assert(ivilayer); prop = &ivilayer->pending.prop; prop->dest_x = x; @@ -1440,11 +1387,9 @@ ivi_layout_layer_set_destination_rectangle(struct ivi_layout_layer *ivilayer, prop->event_mask |= IVI_NOTIFICATION_DEST_RECT; else prop->event_mask &= ~IVI_NOTIFICATION_DEST_RECT; - - return IVI_SUCCEEDED; } -int32_t +void ivi_layout_layer_set_render_order(struct ivi_layout_layer *ivilayer, struct ivi_layout_surface **pSurface, int32_t number) @@ -1452,10 +1397,7 @@ ivi_layout_layer_set_render_order(struct ivi_layout_layer *ivilayer, int32_t i = 0; struct ivi_layout_view * ivi_view; - if (ivilayer == NULL) { - weston_log("ivi_layout_layer_set_render_order: invalid argument\n"); - return IVI_FAILED; - } + assert(ivilayer); clear_view_pending_list(ivilayer); @@ -1471,20 +1413,15 @@ ivi_layout_layer_set_render_order(struct ivi_layout_layer *ivilayer, } ivilayer->order.dirty = 1; - - return IVI_SUCCEEDED; } -int32_t +void ivi_layout_surface_set_visibility(struct ivi_layout_surface *ivisurf, bool newVisibility) { struct ivi_layout_surface_properties *prop = NULL; - if (ivisurf == NULL) { - weston_log("ivi_layout_surface_set_visibility: invalid argument\n"); - return IVI_FAILED; - } + assert(ivisurf); prop = &ivisurf->pending.prop; prop->visibility = newVisibility; @@ -1493,8 +1430,6 @@ ivi_layout_surface_set_visibility(struct ivi_layout_surface *ivisurf, prop->event_mask |= IVI_NOTIFICATION_VISIBILITY; else prop->event_mask &= ~IVI_NOTIFICATION_VISIBILITY; - - return IVI_SUCCEEDED; } int32_t @@ -1503,8 +1438,9 @@ ivi_layout_surface_set_opacity(struct ivi_layout_surface *ivisurf, { struct ivi_layout_surface_properties *prop = NULL; - if (ivisurf == NULL || - opacity < wl_fixed_from_double(0.0) || + assert(ivisurf); + + if (opacity < wl_fixed_from_double(0.0) || wl_fixed_from_double(1.0) < opacity) { weston_log("ivi_layout_surface_set_opacity: invalid argument\n"); return IVI_FAILED; @@ -1521,17 +1457,14 @@ ivi_layout_surface_set_opacity(struct ivi_layout_surface *ivisurf, return IVI_SUCCEEDED; } -int32_t +void ivi_layout_surface_set_destination_rectangle(struct ivi_layout_surface *ivisurf, int32_t x, int32_t y, int32_t width, int32_t height) { struct ivi_layout_surface_properties *prop = NULL; - if (ivisurf == NULL) { - weston_log("ivi_layout_surface_set_destination_rectangle: invalid argument\n"); - return IVI_FAILED; - } + assert(ivisurf); prop = &ivisurf->pending.prop; prop->start_x = prop->dest_x; @@ -1549,33 +1482,36 @@ ivi_layout_surface_set_destination_rectangle(struct ivi_layout_surface *ivisurf, prop->event_mask |= IVI_NOTIFICATION_DEST_RECT; else prop->event_mask &= ~IVI_NOTIFICATION_DEST_RECT; - - return IVI_SUCCEEDED; } void ivi_layout_surface_set_size(struct ivi_layout_surface *ivisurf, int32_t width, int32_t height) { - if (weston_surface_is_desktop_surface(ivisurf->surface)) { + switch (ivisurf->prop.surface_type) { + case IVI_LAYOUT_SURFACE_TYPE_DESKTOP: weston_desktop_surface_set_size(ivisurf->weston_desktop_surface, width, height); - } else { + return; + case IVI_LAYOUT_SURFACE_TYPE_IVI: shell_surface_send_configure(ivisurf->surface, width, height); + return; + case IVI_LAYOUT_SURFACE_TYPE_INPUT_PANEL: + return; } + /* there should be no other surface type */ + assert(0); } -static int32_t +static void ivi_layout_screen_add_layer(struct weston_output *output, struct ivi_layout_layer *addlayer) { struct ivi_layout_screen *iviscrn; - if (output == NULL || addlayer == NULL) { - weston_log("ivi_layout_screen_add_layer: invalid argument\n"); - return IVI_FAILED; - } + assert(output); + assert(addlayer); iviscrn = get_screen_from_output(output); @@ -1588,20 +1524,16 @@ ivi_layout_screen_add_layer(struct weston_output *output, wl_list_insert(&iviscrn->pending.layer_list, &addlayer->pending.link); iviscrn->order.dirty = 1; - - return IVI_SUCCEEDED; } -static int32_t +static void ivi_layout_screen_remove_layer(struct weston_output *output, struct ivi_layout_layer *removelayer) { struct ivi_layout_screen *iviscrn; - if (output == NULL || removelayer == NULL) { - weston_log("ivi_layout_screen_remove_layer: invalid argument\n"); - return IVI_FAILED; - } + assert(output); + assert(removelayer); iviscrn = get_screen_from_output(output); @@ -1609,11 +1541,9 @@ ivi_layout_screen_remove_layer(struct weston_output *output, wl_list_init(&removelayer->pending.link); iviscrn->order.dirty = 1; - - return IVI_SUCCEEDED; } -static int32_t +static void ivi_layout_screen_set_render_order(struct weston_output *output, struct ivi_layout_layer **pLayer, const int32_t number) @@ -1623,10 +1553,7 @@ ivi_layout_screen_set_render_order(struct weston_output *output, struct ivi_layout_layer *next = NULL; int32_t i = 0; - if (output == NULL) { - weston_log("ivi_layout_screen_set_render_order: invalid argument\n"); - return IVI_FAILED; - } + assert(output); iviscrn = get_screen_from_output(output); @@ -1645,8 +1572,6 @@ ivi_layout_screen_set_render_order(struct weston_output *output, } iviscrn->order.dirty = 1; - - return IVI_SUCCEEDED; } /** @@ -1660,7 +1585,7 @@ ivi_layout_surface_get_weston_surface(struct ivi_layout_surface *ivisurf) return ivisurf != NULL ? ivisurf->surface : NULL; } -static int32_t +static void ivi_layout_surface_get_size(struct ivi_layout_surface *ivisurf, int32_t *width, int32_t *height, int32_t *stride) @@ -1669,10 +1594,7 @@ ivi_layout_surface_get_size(struct ivi_layout_surface *ivisurf, int32_t h; const size_t bytespp = 4; /* PIXMAN_a8b8g8r8 */ - if (ivisurf == NULL || ivisurf->surface == NULL) { - weston_log("%s: invalid argument\n", __func__); - return IVI_FAILED; - } + assert(ivisurf); weston_surface_get_content_size(ivisurf->surface, &w, &h); @@ -1684,45 +1606,34 @@ ivi_layout_surface_get_size(struct ivi_layout_surface *ivisurf, if (stride != NULL) *stride = w * bytespp; - - return IVI_SUCCEEDED; } -static int32_t +static void ivi_layout_layer_add_listener(struct ivi_layout_layer *ivilayer, struct wl_listener *listener) { - if (ivilayer == NULL || listener == NULL) { - weston_log("ivi_layout_layer_add_listener: invalid argument\n"); - return IVI_FAILED; - } + assert(ivilayer); + assert(listener); wl_signal_add(&ivilayer->property_changed, listener); - - return IVI_SUCCEEDED; } static const struct ivi_layout_surface_properties * ivi_layout_get_properties_of_surface(struct ivi_layout_surface *ivisurf) { - if (ivisurf == NULL) { - weston_log("ivi_layout_get_properties_of_surface: invalid argument\n"); - return NULL; - } + assert(ivisurf); return &ivisurf->prop; } -static int32_t +static void ivi_layout_layer_add_surface(struct ivi_layout_layer *ivilayer, struct ivi_layout_surface *addsurf) { struct ivi_layout_view *ivi_view; - if (ivilayer == NULL || addsurf == NULL) { - weston_log("ivi_layout_layer_add_surface: invalid argument\n"); - return IVI_FAILED; - } + assert(ivilayer); + assert(addsurf); ivi_view = get_ivi_view(ivilayer, addsurf); if (!ivi_view) @@ -1732,8 +1643,6 @@ ivi_layout_layer_add_surface(struct ivi_layout_layer *ivilayer, wl_list_insert(&ivilayer->pending.view_list, &ivi_view->pending_link); ivilayer->order.dirty = 1; - - return IVI_SUCCEEDED; } static void @@ -1756,17 +1665,14 @@ ivi_layout_layer_remove_surface(struct ivi_layout_layer *ivilayer, } } -static int32_t +static void ivi_layout_surface_set_source_rectangle(struct ivi_layout_surface *ivisurf, int32_t x, int32_t y, int32_t width, int32_t height) { struct ivi_layout_surface_properties *prop = NULL; - if (ivisurf == NULL) { - weston_log("ivi_layout_surface_set_source_rectangle: invalid argument\n"); - return IVI_FAILED; - } + assert(ivisurf); prop = &ivisurf->pending.prop; prop->source_x = x; @@ -1780,8 +1686,6 @@ ivi_layout_surface_set_source_rectangle(struct ivi_layout_surface *ivisurf, prop->event_mask |= IVI_NOTIFICATION_SOURCE_RECT; else prop->event_mask &= ~IVI_NOTIFICATION_SOURCE_RECT; - - return IVI_SUCCEEDED; } int32_t @@ -1803,52 +1707,48 @@ ivi_layout_commit_changes(void) } static int32_t +ivi_layout_commit_current(void) +{ + struct ivi_layout *layout = get_instance(); + build_view_list(layout); + commit_changes(layout); + send_prop(layout); + return IVI_SUCCEEDED; +} + +static void ivi_layout_layer_set_transition(struct ivi_layout_layer *ivilayer, enum ivi_layout_transition_type type, uint32_t duration) { - if (ivilayer == NULL) { - weston_log("%s: invalid argument\n", __func__); - return -1; - } + assert(ivilayer); ivilayer->pending.prop.transition_type = type; ivilayer->pending.prop.transition_duration = duration; - - return 0; } -static int32_t +static void ivi_layout_layer_set_fade_info(struct ivi_layout_layer* ivilayer, uint32_t is_fade_in, double start_alpha, double end_alpha) { - if (ivilayer == NULL) { - weston_log("%s: invalid argument\n", __func__); - return -1; - } + assert(ivilayer); ivilayer->pending.prop.is_fade_in = is_fade_in; ivilayer->pending.prop.start_alpha = start_alpha; ivilayer->pending.prop.end_alpha = end_alpha; - - return 0; } -static int32_t +static void ivi_layout_surface_set_transition_duration(struct ivi_layout_surface *ivisurf, uint32_t duration) { struct ivi_layout_surface_properties *prop; - if (ivisurf == NULL) { - weston_log("%s: invalid argument\n", __func__); - return -1; - } + assert(ivisurf); prop = &ivisurf->pending.prop; prop->transition_duration = duration*10; - return 0; } /* @@ -1864,10 +1764,7 @@ ivi_layout_surface_set_id(struct ivi_layout_surface *ivisurf, struct ivi_layout *layout = get_instance(); struct ivi_layout_surface *search_ivisurf = NULL; - if (!ivisurf) { - weston_log("%s: invalid argument\n", __func__); - return IVI_FAILED; - } + assert(ivisurf); if (ivisurf->id_surface != IVI_INVALID_ID) { weston_log("surface id can only be set once\n"); @@ -1882,29 +1779,24 @@ 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); return IVI_SUCCEEDED; } -static int32_t +static void ivi_layout_surface_set_transition(struct ivi_layout_surface *ivisurf, enum ivi_layout_transition_type type, uint32_t duration) { struct ivi_layout_surface_properties *prop; - if (ivisurf == NULL) { - weston_log("%s: invalid argument\n", __func__); - return -1; - } + assert(ivisurf); prop = &ivisurf->pending.prop; prop->transition_type = type; prop->transition_duration = duration; - return 0; } static int32_t @@ -1914,10 +1806,7 @@ ivi_layout_surface_dump(struct weston_surface *surface, { int result = 0; - if (surface == NULL) { - weston_log("%s: invalid argument\n", __func__); - return IVI_FAILED; - } + assert(surface); result = weston_surface_copy_content( surface, target, size, @@ -1931,21 +1820,13 @@ ivi_layout_surface_dump(struct weston_surface *surface, */ static struct ivi_layout_surface* -surface_create(struct weston_surface *wl_surface, uint32_t id_surface) +surface_create(struct weston_surface *wl_surface, uint32_t id_surface, + enum ivi_layout_surface_type surface_type) { struct ivi_layout *layout = get_instance(); struct ivi_layout_surface *ivisurf = NULL; - if (wl_surface == NULL) { - weston_log("ivi_layout_surface_create: invalid argument\n"); - return NULL; - } - - ivisurf = calloc(1, sizeof *ivisurf); - if (ivisurf == NULL) { - weston_log("fails to allocate memory\n"); - return NULL; - } + ivisurf = xzalloc(sizeof *ivisurf); wl_signal_init(&ivisurf->property_changed); ivisurf->id_surface = id_surface; @@ -1957,6 +1838,7 @@ surface_create(struct weston_surface *wl_surface, uint32_t id_surface) ivisurf->surface->height_from_buffer = 0; init_surface_properties(&ivisurf->prop); + ivisurf->prop.surface_type = surface_type; ivisurf->pending.prop = ivisurf->prop; @@ -1972,6 +1854,7 @@ ivi_layout_desktop_surface_configure(struct ivi_layout_surface *ivisurf, int32_t width, int32_t height) { struct ivi_layout *layout = get_instance(); + ivisurf->prop.event_mask |= IVI_NOTIFICATION_CONFIGURE; /* emit callback which is set by ivi-layout api user */ wl_signal_emit(&layout->surface_notification.configure_desktop_changed, @@ -1979,9 +1862,140 @@ 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) +{ + struct ivi_layout *layout = get_instance(); + struct ivi_layout_surface *ivisurf; + + ivisurf = surface_create(wl_surface, IVI_INVALID_ID, + IVI_LAYOUT_SURFACE_TYPE_DESKTOP); + + ivisurf->weston_desktop_surface = surface; + wl_signal_emit(&layout->surface_notification.created, ivisurf); + + return ivisurf; +} + +struct ivi_layout_surface* +ivi_layout_input_panel_surface_create(struct weston_surface *wl_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, + IVI_LAYOUT_SURFACE_TYPE_INPUT_PANEL); + + weston_signal_emit_mutable(&layout->surface_notification.created, + ivisurf); + + return ivisurf; +} + +void +ivi_layout_input_panel_surface_configure(struct ivi_layout_surface *ivisurf, + int32_t width, int32_t height) +{ + struct ivi_layout *layout = get_instance(); + + weston_signal_emit_mutable(&layout->input_panel_notification.configure_changed, + ivisurf); +} + +void +ivi_layout_update_text_input_cursor(pixman_box32_t *cursor_rectangle) +{ + struct ivi_layout *layout = get_instance(); + + memcpy(&layout->text_input.cursor_rectangle, cursor_rectangle, + sizeof(pixman_box32_t)); +} + +void +ivi_layout_show_input_panel(struct ivi_layout_surface *ivisurf, + struct ivi_layout_surface *target_ivisurf, + bool overlay_panel) +{ + struct ivi_layout *layout = get_instance(); + struct ivi_layout_text_input_state state = { + .overlay_panel = overlay_panel, + .input_panel = ivisurf, + .surface = target_ivisurf, + .cursor_rectangle = layout->text_input.cursor_rectangle + }; + layout->text_input.ivisurf = target_ivisurf; + + weston_signal_emit_mutable(&layout->input_panel_notification.show, + &state); +} + +void +ivi_layout_hide_input_panel(struct ivi_layout_surface *ivisurf) +{ + struct ivi_layout *layout = get_instance(); + + weston_signal_emit_mutable(&layout->input_panel_notification.hide, + ivisurf); +} + +void +ivi_layout_update_input_panel(struct ivi_layout_surface *ivisurf, + bool overlay_panel) +{ + struct ivi_layout *layout = get_instance(); + struct ivi_layout_text_input_state state = { + .overlay_panel = overlay_panel, + .input_panel = ivisurf, + .surface =layout->text_input.ivisurf, + .cursor_rectangle = layout->text_input.cursor_rectangle + }; + + weston_signal_emit_mutable(&layout->input_panel_notification.update, + &state); +} + +static void +ivi_layout_add_listener_configure_input_panel_surface(struct wl_listener *listener) +{ + struct ivi_layout *layout = get_instance(); + + assert(listener); + + wl_signal_add(&layout->input_panel_notification.configure_changed, listener); + shell_ensure_text_input(layout->shell); +} + +static void +ivi_layout_add_listener_show_input_panel(struct wl_listener *listener) +{ + struct ivi_layout *layout = get_instance(); + + assert(listener); + + wl_signal_add(&layout->input_panel_notification.show, listener); + shell_ensure_text_input(layout->shell); +} + +static void +ivi_layout_add_listener_hide_input_panel(struct wl_listener *listener) +{ + struct ivi_layout *layout = get_instance(); + + assert(listener); + + wl_signal_add(&layout->input_panel_notification.hide, listener); + shell_ensure_text_input(layout->shell); +} + +static void +ivi_layout_add_listener_update_input_panel(struct wl_listener *listener) +{ + struct ivi_layout *layout = get_instance(); + + assert(listener); + + wl_signal_add(&layout->input_panel_notification.update, listener); + shell_ensure_text_input(layout->shell); } void @@ -1989,6 +2003,7 @@ ivi_layout_surface_configure(struct ivi_layout_surface *ivisurf, int32_t width, int32_t height) { struct ivi_layout *layout = get_instance(); + ivisurf->prop.event_mask |= IVI_NOTIFICATION_CONFIGURE; /* emit callback which is set by ivi-layout api user */ wl_signal_emit(&layout->surface_notification.configure_changed, @@ -2008,7 +2023,8 @@ ivi_layout_surface_create(struct weston_surface *wl_surface, return NULL; } - ivisurf = surface_create(wl_surface, id_surface); + ivisurf = surface_create(wl_surface, id_surface, + IVI_LAYOUT_SURFACE_TYPE_IVI); if (ivisurf) wl_signal_emit(&layout->surface_notification.created, ivisurf); @@ -2016,14 +2032,23 @@ ivi_layout_surface_create(struct weston_surface *wl_surface, return ivisurf; } +void +ivi_layout_ivi_shell_destroy(void) +{ + struct ivi_layout *layout = get_instance(); + + /* emit callback which is set by ivi-layout api user */ + weston_signal_emit_mutable(&layout->shell_notification.destroy_signal, NULL); +} + static struct ivi_layout_interface ivi_layout_interface; void -ivi_layout_init_with_compositor(struct weston_compositor *ec) +ivi_layout_init(struct weston_compositor *ec, struct ivi_shell *shell) { struct ivi_layout *layout = get_instance(); - layout->compositor = ec; + layout->shell = shell; wl_list_init(&layout->surface_list); wl_list_init(&layout->layer_list); @@ -2038,6 +2063,13 @@ ivi_layout_init_with_compositor(struct weston_compositor *ec) wl_signal_init(&layout->surface_notification.configure_changed); wl_signal_init(&layout->surface_notification.configure_desktop_changed); + wl_signal_init(&layout->input_panel_notification.configure_changed); + wl_signal_init(&layout->input_panel_notification.show); + wl_signal_init(&layout->input_panel_notification.hide); + wl_signal_init(&layout->input_panel_notification.update); + + wl_signal_init(&layout->shell_notification.destroy_signal); + /* Add layout_layer at the last of weston_compositor.layer_list */ weston_layer_init(&layout->layout_layer, ec); weston_layer_set_position(&layout->layout_layer, @@ -2045,6 +2077,12 @@ ivi_layout_init_with_compositor(struct weston_compositor *ec) create_screen(ec); + layout->output_created.notify = output_created_event; + wl_signal_add(&ec->output_created_signal, &layout->output_created); + + layout->output_destroyed.notify = output_destroyed_event; + wl_signal_add(&ec->output_destroyed_signal, &layout->output_destroyed); + layout->transitions = ivi_layout_transition_set_create(ec); wl_list_init(&layout->pending_transition_list); @@ -2061,6 +2099,8 @@ ivi_layout_fini(void) weston_layer_fini(&layout->layout_layer); /* XXX: tear down everything else */ + wl_list_remove(&layout->output_created.link); + wl_list_remove(&layout->output_destroyed.link); } static struct ivi_layout_interface ivi_layout_interface = { @@ -2068,6 +2108,7 @@ static struct ivi_layout_interface ivi_layout_interface = { * commit all changes */ .commit_changes = ivi_layout_commit_changes, + .commit_current = ivi_layout_commit_current, /** * surface controller interfaces @@ -2134,4 +2175,17 @@ static struct ivi_layout_interface ivi_layout_interface = { */ .surface_get_size = ivi_layout_surface_get_size, .surface_dump = ivi_layout_surface_dump, + + /** + * shell interfaces + */ + .shell_add_destroy_listener_once = ivi_layout_shell_add_destroy_listener_once, + + /** + * input panel + */ + .add_listener_configure_input_panel_surface = ivi_layout_add_listener_configure_input_panel_surface, + .add_listener_show_input_panel = ivi_layout_add_listener_show_input_panel, + .add_listener_hide_input_panel = ivi_layout_add_listener_hide_input_panel, + .add_listener_update_input_panel = ivi_layout_add_listener_update_input_panel, }; diff --git a/ivi-shell/ivi-shell.c b/ivi-shell/ivi-shell.c index 5ec1349ef..9103f1d28 100644 --- a/ivi-shell/ivi-shell.c +++ b/ivi-shell/ivi-shell.c @@ -42,11 +42,14 @@ #include #include +#include "input-method-unstable-v1-server-protocol.h" #include "ivi-shell.h" #include "ivi-application-server-protocol.h" #include "ivi-layout-private.h" #include "ivi-layout-shell.h" +#include "libweston/libweston.h" #include "shared/helpers.h" +#include "shared/xalloc.h" #include "compositor/weston.h" /* Representation of ivi_surface protocol object. */ @@ -64,6 +67,31 @@ struct ivi_shell_surface int32_t width; int32_t height; + struct wl_list children_list; + struct wl_list children_link; + + struct wl_list link; +}; + +struct ivi_input_panel_surface +{ + struct wl_resource* resource; + struct ivi_shell *shell; + struct ivi_layout_surface *layout_surface; + + struct weston_surface *surface; + struct wl_listener surface_destroy_listener; + + int32_t width; + int32_t height; + + struct weston_output *output; + enum { + INPUT_PANEL_NONE, + INPUT_PANEL_TOPLEVEL, + INPUT_PANEL_OVERLAY, + } type; + struct wl_list link; }; @@ -72,21 +100,20 @@ struct ivi_shell_surface */ static void -ivi_shell_surface_committed(struct weston_surface *, int32_t, int32_t); +ivi_shell_surface_committed(struct weston_surface *, struct weston_coord_surface); static struct ivi_shell_surface * get_ivi_shell_surface(struct weston_surface *surface) { - struct ivi_shell_surface *shsurf; + struct weston_desktop_surface *desktop_surface + = weston_surface_get_desktop_surface(surface); + if (desktop_surface) + return weston_desktop_surface_get_user_data(desktop_surface); - if (surface->committed != ivi_shell_surface_committed) - return NULL; + if (surface->committed == ivi_shell_surface_committed) + return surface->committed_private; - shsurf = surface->committed_private; - assert(shsurf); - assert(shsurf->surface == surface); - - return shsurf; + return NULL; } struct ivi_layout_surface * @@ -108,8 +135,7 @@ shell_surface_send_configure(struct weston_surface *surface, struct ivi_shell_surface *shsurf; shsurf = get_ivi_shell_surface(surface); - if (!shsurf) - return; + assert(shsurf); if (shsurf->resource) ivi_surface_send_configure(shsurf->resource, width, height); @@ -117,16 +143,14 @@ shell_surface_send_configure(struct weston_surface *surface, static void ivi_shell_surface_committed(struct weston_surface *surface, - int32_t sx, int32_t sy) + struct weston_coord_surface new_origin) { struct ivi_shell_surface *ivisurf = get_ivi_shell_surface(surface); - assert(ivisurf); - if (!ivisurf) - return; - - if (surface->width == 0 || surface->height == 0) - return; + if (surface->width == 0 || surface->height == 0) { + if (!weston_surface_is_unmapping(surface)) + return; + } if (ivisurf->width != surface->width || ivisurf->height != surface->height) { @@ -145,9 +169,6 @@ ivi_shell_surface_get_label(struct weston_surface *surface, { struct ivi_shell_surface *shell_surf = get_ivi_shell_surface(surface); - if (!shell_surf) - return snprintf(buf, len, "unidentified window in ivi-shell"); - return snprintf(buf, len, "ivi-surface %#x", shell_surf->id_surface); } @@ -268,11 +289,7 @@ application_surface_create(struct wl_client *client, layout_surface->weston_desktop_surface = NULL; - ivisurf = zalloc(sizeof *ivisurf); - if (ivisurf == NULL) { - wl_resource_post_no_memory(resource); - return; - } + ivisurf = xzalloc(sizeof *ivisurf); wl_list_init(&ivisurf->link); wl_list_insert(&shell->ivi_surface_list, &ivisurf->link); @@ -284,6 +301,13 @@ application_surface_create(struct wl_client *client, ivisurf->height = 0; ivisurf->layout_surface = layout_surface; + /* + * initialize list as well as link. The latter allows to use + * wl_list_remove() event when this surface is not in another list. + */ + wl_list_init(&ivisurf->children_list); + wl_list_init(&ivisurf->children_link); + /* * The following code relies on wl_surface destruction triggering * immediateweston_surface destruction @@ -333,6 +357,9 @@ bind_ivi_application(struct wl_client *client, shell, NULL); } +void +input_panel_destroy(struct ivi_shell *shell); + /* * Called through the compositor's destroy signal. */ @@ -343,10 +370,15 @@ shell_destroy(struct wl_listener *listener, void *data) container_of(listener, struct ivi_shell, destroy_listener); struct ivi_shell_surface *ivisurf, *next; + ivi_layout_ivi_shell_destroy(); + wl_list_remove(&shell->destroy_listener.link); wl_list_remove(&shell->wake_listener.link); - weston_desktop_destroy(shell->desktop); + if (shell->text_backend) { + text_backend_destroy(shell->text_backend); + input_panel_destroy(shell); + } wl_list_for_each_safe(ivisurf, next, &shell->ivi_surface_list, link) { if (ivisurf->layout_surface != NULL) @@ -357,6 +389,7 @@ shell_destroy(struct wl_listener *listener, void *data) ivi_layout_fini(); + weston_desktop_destroy(shell->desktop); free(shell); } @@ -406,18 +439,43 @@ init_ivi_shell(struct weston_compositor *compositor, struct ivi_shell *shell) } } +static struct ivi_shell_surface * +get_last_child(struct ivi_shell_surface *ivisurf) +{ + struct ivi_shell_surface *ivisurf_child; + + wl_list_for_each_reverse(ivisurf_child, &ivisurf->children_list, + children_link) { + if (weston_surface_is_mapped(ivisurf_child->surface)) + return ivisurf_child; + } + return NULL; +} + static void activate_binding(struct weston_seat *seat, - struct weston_view *focus_view) + struct weston_view *focus_view, + uint32_t flags) { - struct weston_surface *focus = focus_view->surface; - struct weston_surface *main_surface = - weston_surface_get_main_surface(focus); + struct ivi_shell_surface *ivisurf, *ivisurf_child; + struct weston_surface *main_surface; + + main_surface = weston_surface_get_main_surface(focus_view->surface); + ivisurf = get_ivi_shell_surface(main_surface); + if (ivisurf == NULL) + return; - if (get_ivi_shell_surface(main_surface) == NULL) + ivisurf_child = get_last_child(ivisurf); + if (ivisurf_child) { + struct weston_view *view + = ivisurf_child->layout_surface->ivi_view->view; + activate_binding(seat, view, flags); return; + } - weston_seat_set_keyboard_focus(seat, focus); + /* FIXME: need to activate the surface like + kiosk_shell_surface_activate() */ + weston_view_activate_input(focus_view, seat, flags); } static void @@ -430,7 +488,8 @@ click_to_activate_binding(struct weston_pointer *pointer, if (pointer->focus == NULL) return; - activate_binding(pointer->seat, pointer->focus); + activate_binding(pointer->seat, pointer->focus, + WESTON_ACTIVATE_FLAG_CLICKED); } static void @@ -443,7 +502,7 @@ touch_to_activate_binding(struct weston_touch *touch, if (touch->focus == NULL) return; - activate_binding(touch->seat, touch->focus); + activate_binding(touch->seat, touch->focus, WESTON_ACTIVATE_FLAG_NONE); } static void @@ -489,17 +548,9 @@ 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); - if (!layout_surface) { - return; - } + layout_surface = ivi_layout_desktop_surface_create(weston_surf, surface); - layout_surface->weston_desktop_surface = surface; - - ivisurf = zalloc(sizeof *ivisurf); - if (!ivisurf) { - return; - } + ivisurf = xzalloc(sizeof *ivisurf); ivisurf->shell = shell; ivisurf->id_surface = IVI_INVALID_ID; @@ -509,6 +560,15 @@ 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); + + /* + * initialize list as well as link. The latter allows to use + * wl_list_remove() event when this surface is not in another list. + */ + wl_list_init(&ivisurf->children_list); + wl_list_init(&ivisurf->children_link); + weston_desktop_surface_set_user_data(surface, ivisurf); } @@ -518,11 +578,25 @@ desktop_surface_removed(struct weston_desktop_surface *surface, { struct ivi_shell_surface *ivisurf = (struct ivi_shell_surface *) weston_desktop_surface_get_user_data(surface); + struct ivi_shell_surface *ivisurf_child, *tmp; assert(ivisurf != NULL); + weston_desktop_surface_set_user_data(surface, NULL); + + wl_list_for_each_safe(ivisurf_child, tmp, &ivisurf->children_list, + children_link) { + wl_list_remove(&ivisurf_child->children_link); + wl_list_init(&ivisurf_child->children_link); + } + wl_list_remove(&ivisurf->children_link); + if (ivisurf->layout_surface) layout_surface_cleanup(ivisurf); + + wl_list_remove(&ivisurf->link); + + free(ivisurf); } static void @@ -537,8 +611,10 @@ desktop_surface_committed(struct weston_desktop_surface *surface, if(!ivisurf) return; - if (weston_surf->width == 0 || weston_surf->height == 0) - return; + if (weston_surf->width == 0 || weston_surf->height == 0) { + if (!weston_surface_is_unmapping(weston_surf)) + return; + } if (ivisurf->width != weston_surf->width || ivisurf->height != weston_surf->height) { @@ -566,6 +642,23 @@ desktop_surface_resize(struct weston_desktop_surface *surface, /* Not supported */ } +static void +desktop_surface_set_parent(struct weston_desktop_surface *desktop_surface, + struct weston_desktop_surface *parent, + void *shell) +{ + struct ivi_shell_surface *ivisurf = + weston_desktop_surface_get_user_data(desktop_surface); + struct ivi_shell_surface *ivisurf_parent; + + if (!parent) + return; + + ivisurf_parent = weston_desktop_surface_get_user_data(parent); + wl_list_insert(ivisurf_parent->children_list.prev, + &ivisurf->children_link); +} + static void desktop_surface_fullscreen_requested(struct weston_desktop_surface *surface, bool fullscreen, @@ -606,6 +699,7 @@ static const struct weston_desktop_api shell_desktop_api = { .move = desktop_surface_move, .resize = desktop_surface_resize, + .set_parent = desktop_surface_set_parent, .fullscreen_requested = desktop_surface_fullscreen_requested, .maximized_requested = desktop_surface_maximized_requested, .minimized_requested = desktop_surface_minimized_requested, @@ -616,6 +710,330 @@ static const struct weston_desktop_api shell_desktop_api = { * end of libweston-desktop */ +/* + * input panel + */ + +static void +maybe_show_input_panel(struct ivi_input_panel_surface *ipsurf, + struct ivi_shell_surface *target_ivisurf) +{ + if (ipsurf->surface->width == 0) + return; + + if (ipsurf->type == INPUT_PANEL_NONE) + return; + + ivi_layout_show_input_panel(ipsurf->layout_surface, + target_ivisurf->layout_surface, + ipsurf->type == INPUT_PANEL_OVERLAY); +} + +static void +show_input_panels(struct wl_listener *listener, void *data) +{ + struct ivi_shell *shell = container_of(listener, struct ivi_shell, + show_input_panel_listener); + struct ivi_shell_surface *target_ivisurf; + struct ivi_input_panel_surface *ipsurf; + + target_ivisurf = get_ivi_shell_surface(data); + if (!target_ivisurf) + return; + + if (shell->text_input_surface) + return; + + shell->text_input_surface = target_ivisurf; + + wl_list_for_each(ipsurf, &shell->input_panel.surfaces, link) + maybe_show_input_panel(ipsurf, target_ivisurf); +} + +static void +hide_input_panels(struct wl_listener *listener, void *data) +{ + struct ivi_shell *shell = container_of(listener, struct ivi_shell, + hide_input_panel_listener); + struct ivi_input_panel_surface *ipsurf; + + if (!shell->text_input_surface) + return; + + shell->text_input_surface = NULL; + + wl_list_for_each(ipsurf, &shell->input_panel.surfaces, link) + ivi_layout_hide_input_panel(ipsurf->layout_surface); +} + +static void +update_input_panels(struct wl_listener *listener, void *data) +{ + ivi_layout_update_text_input_cursor(data); +} + +static int +input_panel_get_label(struct weston_surface *surface, char *buf, size_t len) +{ + return snprintf(buf, len, "input panel"); +} + +static void +input_panel_committed(struct weston_surface *surface, + struct weston_coord_surface new_origin) +{ + struct ivi_input_panel_surface *ipsurf = surface->committed_private; + struct ivi_shell *shell = ipsurf->shell; + + if (surface->width == 0 || surface->height == 0) + return; + + if (ipsurf->width != surface->width || + ipsurf->height != surface->height) { + ipsurf->width = surface->width; + ipsurf->height = surface->height; + ivi_layout_input_panel_surface_configure(ipsurf->layout_surface, + surface->width, + surface->height); + } + + if (shell->text_input_surface) + maybe_show_input_panel(ipsurf, shell->text_input_surface); +} + +bool +shell_is_input_panel_surface(struct weston_surface *surface) +{ + return surface->committed == input_panel_committed; +} + +static struct ivi_input_panel_surface * +get_input_panel_surface(struct weston_surface *surface) +{ + if (shell_is_input_panel_surface(surface)) + return surface->committed_private; + else + return NULL; +} + +static void +input_panel_handle_surface_destroy(struct wl_listener *listener, void *data) +{ + struct ivi_input_panel_surface *ipsurf = + container_of(listener, struct ivi_input_panel_surface, + surface_destroy_listener); + + wl_resource_destroy(ipsurf->resource); +} + +static struct ivi_input_panel_surface * +create_input_panel_surface(struct ivi_shell *shell, + struct weston_surface *surface) +{ + struct ivi_input_panel_surface *ipsurf; + struct ivi_layout_surface *layout_surface; + + layout_surface = ivi_layout_input_panel_surface_create(surface); + + ipsurf = xzalloc(sizeof *ipsurf); + + surface->committed = input_panel_committed; + surface->committed_private = ipsurf; + weston_surface_set_label_func(surface, input_panel_get_label); + + wl_list_init(&ipsurf->link); + wl_list_insert(&shell->input_panel.surfaces, &ipsurf->link); + + ipsurf->shell = shell; + + ipsurf->width = 0; + ipsurf->height = 0; + ipsurf->layout_surface = layout_surface; + ipsurf->surface = surface; + + if (surface->width && surface->height) { + ipsurf->width = surface->width; + ipsurf->height = surface->height; + ivi_layout_input_panel_surface_configure(ipsurf->layout_surface, + surface->width, + surface->height); + } + + ipsurf->surface_destroy_listener.notify = input_panel_handle_surface_destroy; + wl_signal_add(&surface->destroy_signal, + &ipsurf->surface_destroy_listener); + + return ipsurf; +} + +static void +input_panel_surface_set_toplevel(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *output_resource, + uint32_t position) +{ + struct ivi_input_panel_surface *ipsurf = + wl_resource_get_user_data(resource); + struct weston_head *head; + + head = weston_head_from_resource(output_resource); + + ipsurf->type = INPUT_PANEL_TOPLEVEL; + ipsurf->output = head->output; +} + +static void +input_panel_surface_set_overlay_panel(struct wl_client *client, + struct wl_resource *resource) +{ + struct ivi_input_panel_surface *ipsurf = + wl_resource_get_user_data(resource); + + ipsurf->type = INPUT_PANEL_OVERLAY; +} + +static const struct zwp_input_panel_surface_v1_interface input_panel_surface_implementation = { + input_panel_surface_set_toplevel, + input_panel_surface_set_overlay_panel +}; + +static void +destroy_input_panel_surface_resource(struct wl_resource *resource) +{ + struct ivi_input_panel_surface *ipsurf = + wl_resource_get_user_data(resource); + + assert(ipsurf->resource == resource); + + ivi_layout_surface_destroy(ipsurf->layout_surface); + ipsurf->layout_surface = NULL; + + ipsurf->surface->committed = NULL; + ipsurf->surface->committed_private = NULL; + weston_surface_set_label_func(ipsurf->surface, NULL); + ipsurf->surface = NULL; + + wl_list_remove(&ipsurf->surface_destroy_listener.link); + wl_list_remove(&ipsurf->link); + + free(ipsurf); +} + +static void +input_panel_get_input_panel_surface(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface_resource) +{ + struct weston_surface *surface = + wl_resource_get_user_data(surface_resource); + struct ivi_shell *shell = wl_resource_get_user_data(resource); + struct ivi_input_panel_surface *ipsurf; + + if (get_input_panel_surface(surface)) { + wl_resource_post_error(surface_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "wl_input_panel::get_input_panel_surface already requested"); + return; + } + + ipsurf = create_input_panel_surface(shell, surface); + if (!ipsurf) { + wl_resource_post_error(surface_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "surface->committed already set"); + return; + } + + ipsurf->resource = + wl_resource_create(client, + &zwp_input_panel_surface_v1_interface, + 1, + id); + wl_resource_set_implementation(ipsurf->resource, + &input_panel_surface_implementation, + ipsurf, + destroy_input_panel_surface_resource); +} + +static const struct zwp_input_panel_v1_interface input_panel_implementation = { + input_panel_get_input_panel_surface +}; + +static void +unbind_input_panel(struct wl_resource *resource) +{ + struct ivi_shell *shell = wl_resource_get_user_data(resource); + + shell->input_panel.binding = NULL; +} + +static void +bind_input_panel(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct ivi_shell *shell = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, + &zwp_input_panel_v1_interface, 1, id); + + if (shell->input_panel.binding == NULL) { + wl_resource_set_implementation(resource, + &input_panel_implementation, + shell, unbind_input_panel); + shell->input_panel.binding = resource; + return; + } + + wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, + "interface object already bound"); +} + +void +input_panel_destroy(struct ivi_shell *shell) +{ + wl_list_remove(&shell->show_input_panel_listener.link); + wl_list_remove(&shell->hide_input_panel_listener.link); + wl_list_remove(&shell->update_input_panel_listener.link); +} + +static void +input_panel_setup(struct ivi_shell *shell) +{ + struct weston_compositor *ec = shell->compositor; + + shell->show_input_panel_listener.notify = show_input_panels; + wl_signal_add(&ec->show_input_panel_signal, + &shell->show_input_panel_listener); + shell->hide_input_panel_listener.notify = hide_input_panels; + wl_signal_add(&ec->hide_input_panel_signal, + &shell->hide_input_panel_listener); + shell->update_input_panel_listener.notify = update_input_panels; + wl_signal_add(&ec->update_input_panel_signal, + &shell->update_input_panel_listener); + + wl_list_init(&shell->input_panel.surfaces); + + abort_oom_if_null(wl_global_create(shell->compositor->wl_display, + &zwp_input_panel_v1_interface, 1, + shell, bind_input_panel)); +} + +void +shell_ensure_text_input(struct ivi_shell *shell) +{ + if (shell->text_backend) + return; + + shell->text_backend = text_backend_init(shell->compositor); + input_panel_setup(shell); +} + +/* + * end of input panel + */ + /* * Initialization of ivi-shell. */ @@ -625,9 +1043,7 @@ wet_shell_init(struct weston_compositor *compositor, { struct ivi_shell *shell; - shell = zalloc(sizeof *shell); - if (shell == NULL) - return -1; + shell = xzalloc(sizeof *shell); if (!weston_compositor_add_destroy_listener_once(compositor, &shell->destroy_listener, @@ -650,7 +1066,10 @@ wet_shell_init(struct weston_compositor *compositor, shell, bind_ivi_application) == NULL) goto err_desktop; - ivi_layout_init_with_compositor(compositor); + ivi_layout_init(compositor, shell); + + screenshooter_create(compositor); + shell_add_bindings(compositor, shell); return IVI_SUCCEEDED; diff --git a/ivi-shell/ivi-shell.h b/ivi-shell/ivi-shell.h index d7f1cdbde..fa206e6b5 100644 --- a/ivi-shell/ivi-shell.h +++ b/ivi-shell/ivi-shell.h @@ -30,17 +30,28 @@ #include #include -#include +#include struct ivi_shell { struct wl_listener destroy_listener; struct wl_listener wake_listener; + struct wl_listener show_input_panel_listener; + struct wl_listener hide_input_panel_listener; + struct wl_listener update_input_panel_listener; struct weston_compositor *compositor; struct weston_desktop *desktop; struct wl_list ivi_surface_list; /* struct ivi_shell_surface::link */ + + struct text_backend *text_backend; + + struct ivi_shell_surface *text_input_surface; + struct { + struct wl_resource *binding; + struct wl_list surfaces; + } input_panel; }; void @@ -52,4 +63,9 @@ struct ivi_layout_surface; struct ivi_layout_surface * shell_get_ivi_layout_surface(struct weston_surface *surface); +void +shell_ensure_text_input(struct ivi_shell *shell); +bool +shell_is_input_panel_surface(struct weston_surface *surface); + #endif /* WESTON_IVI_SHELL_H */ diff --git a/ivi-shell/meson.build b/ivi-shell/meson.build index b071ca632..13f990dab 100644 --- a/ivi-shell/meson.build +++ b/ivi-shell/meson.build @@ -15,8 +15,8 @@ if get_option('shell-ivi') dependencies: [ dep_libm, dep_libexec_weston, - dep_lib_desktop, - dep_libweston_public + dep_libweston_public, + dep_libshared ], name_prefix: '', install: true, diff --git a/kiosk-shell/kiosk-shell-grab.c b/kiosk-shell/kiosk-shell-grab.c index 3ea0156c1..1a4db85af 100644 --- a/kiosk-shell/kiosk-shell-grab.c +++ b/kiosk-shell/kiosk-shell-grab.c @@ -85,8 +85,8 @@ pointer_move_grab_motion(struct weston_pointer_grab *pointer_grab, surface = weston_desktop_surface_get_surface(shsurf->desktop_surface); - dx = wl_fixed_to_int(pointer->x + shgrab->dx); - dy = wl_fixed_to_int(pointer->y + shgrab->dy); + dx = pointer->pos.c.x + wl_fixed_to_double(shgrab->dx); + dy = pointer->pos.c.y + wl_fixed_to_double(shgrab->dy); weston_view_set_position(shsurf->view, dx, dy); @@ -238,6 +238,7 @@ kiosk_shell_grab_start_for_pointer_move(struct kiosk_shell_surface *shsurf, struct weston_pointer *pointer) { struct kiosk_shell_grab *shgrab; + struct weston_coord_global offset; if (!shsurf) return KIOSK_SHELL_GRAB_RESULT_ERROR; @@ -251,10 +252,10 @@ kiosk_shell_grab_start_for_pointer_move(struct kiosk_shell_surface *shsurf, if (!shgrab) return KIOSK_SHELL_GRAB_RESULT_ERROR; - shgrab->dx = wl_fixed_from_double(shsurf->view->geometry.x) - - pointer->grab_x; - shgrab->dy = wl_fixed_from_double(shsurf->view->geometry.y) - - pointer->grab_y; + offset.c = weston_coord_sub(shsurf->view->geometry.pos_offset, + pointer->grab_pos.c); + shgrab->dx = wl_fixed_from_double(offset.c.x); + shgrab->dy = wl_fixed_from_double(offset.c.y); shgrab->active = true; weston_seat_break_desktop_grabs(pointer->seat); @@ -283,9 +284,9 @@ kiosk_shell_grab_start_for_touch_move(struct kiosk_shell_surface *shsurf, if (!shgrab) return KIOSK_SHELL_GRAB_RESULT_ERROR; - shgrab->dx = wl_fixed_from_double(shsurf->view->geometry.x) - + shgrab->dx = wl_fixed_from_double(shsurf->view->geometry.pos_offset.x) - touch->grab_x; - shgrab->dy = wl_fixed_from_double(shsurf->view->geometry.y) - + shgrab->dy = wl_fixed_from_double(shsurf->view->geometry.pos_offset.y) - touch->grab_y; shgrab->active = true; diff --git a/kiosk-shell/kiosk-shell.c b/kiosk-shell/kiosk-shell.c index 14857eccc..19c32650f 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 #include @@ -90,7 +90,6 @@ transform_handler(struct wl_listener *listener, void *data) struct weston_surface *surface = data; struct kiosk_shell_surface *shsurf = get_kiosk_shell_surface(surface); const struct weston_xwayland_surface_api *api; - int x, y; if (!shsurf) return; @@ -107,10 +106,9 @@ transform_handler(struct wl_listener *listener, void *data) if (!weston_view_is_mapped(shsurf->view)) return; - x = shsurf->view->geometry.x; - y = shsurf->view->geometry.y; - - api->send_position(surface, x, y); + api->send_position(surface, + shsurf->view->geometry.pos_offset.x, + shsurf->view->geometry.pos_offset.y); } /* @@ -185,11 +183,11 @@ kiosk_shell_surface_find_best_output(struct kiosk_shell_surface *shsurf) if (root->output) return root->output; - output = get_focused_output(shsurf->shell->compositor); + output = weston_shell_utils_get_focused_output(shsurf->shell->compositor); if (output) return output; - output = get_default_output(shsurf->shell->compositor); + output = weston_shell_utils_get_default_output(shsurf->shell->compositor); if (output) return output; @@ -299,7 +297,7 @@ kiosk_shell_surface_reconfigure_for_output(struct kiosk_shell_surface *shsurf) shsurf->output->height); } - center_on_output(shsurf->view, shsurf->output); + weston_shell_utils_center_on_output(shsurf->view, shsurf->output); weston_view_update_transform(shsurf->view); } @@ -413,6 +411,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 +479,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_shell_utils_curtain_destroy(shoutput->curtain); if (!output) return; @@ -497,31 +497,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; + + curtain_params.capture_input = true; - solid_surface.get_label = kiosk_shell_background_surface_get_label; - solid_surface.surface_committed = NULL; - solid_surface.surface_private = NULL; + curtain_params.get_label = kiosk_shell_background_surface_get_label; + curtain_params.surface_committed = NULL; + curtain_params.surface_private = NULL; - shoutput->background_view = - create_solid_color_surface(shoutput->shell->compositor, - &solid_surface, - output->x, output->y, - output->width, - output->height); + shoutput->curtain = weston_shell_utils_curtain_create(ec, &curtain_params); - weston_surface_set_role(shoutput->background_view->surface, + 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 +532,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_shell_utils_curtain_destroy(shoutput->curtain); wl_list_remove(&shoutput->output_destroy_listener.link); wl_list_remove(&shoutput->link); @@ -632,7 +634,7 @@ desktop_surface_added(struct weston_desktop_surface *desktop_surface, if (!shsurf) return; - weston_surface_set_label_func(surface, surface_get_label); + weston_surface_set_label_func(surface, weston_shell_utils_surface_get_label); kiosk_shell_surface_set_fullscreen(shsurf, NULL); } @@ -776,7 +778,8 @@ desktop_surface_committed(struct weston_desktop_surface *desktop_surface, if (!weston_surface_is_mapped(surface) || (is_resized && is_fullscreen)) { if (is_fullscreen || !shsurf->xwayland.is_set) { - center_on_output(shsurf->view, shsurf->output); + weston_shell_utils_center_on_output(shsurf->view, + shsurf->output); } else { struct weston_geometry geometry = weston_desktop_surface_get_geometry(desktop_surface); @@ -795,7 +798,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) @@ -804,16 +807,22 @@ desktop_surface_committed(struct weston_desktop_surface *desktop_surface, } if (!is_fullscreen && (sx != 0 || sy != 0)) { - float from_x, from_y; - float to_x, to_y; - float x, y; - - weston_view_to_global_float(shsurf->view, 0, 0, &from_x, &from_y); - weston_view_to_global_float(shsurf->view, sx, sy, &to_x, &to_y); - x = shsurf->view->geometry.x + to_x - from_x; - y = shsurf->view->geometry.y + to_y - from_y; - - weston_view_set_position(shsurf->view, x, y); + struct weston_coord_surface from_s, to_s; + struct weston_coord_global from_g, to_g; + struct weston_coord_global offset, pos; + + from_s = weston_coord_surface(0, 0, + shsurf->view->surface); + to_s = weston_coord_surface(sx, sy, + shsurf->view->surface); + + from_g = weston_coord_surface_to_global(shsurf->view, from_s); + to_g = weston_coord_surface_to_global(shsurf->view, to_s); + offset.c = weston_coord_sub(to_g.c, from_g.c); + pos.c = weston_coord_add(shsurf->view->geometry.pos_offset, + offset.c); + + weston_view_set_position(shsurf->view, pos.c.x, pos.c.y); weston_view_update_transform(shsurf->view); } @@ -948,6 +957,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.pos_offset.x; + *y = shsurf->view->geometry.pos_offset.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 +982,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 +1070,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_config_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 +1083,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 @@ -1107,8 +1134,8 @@ kiosk_shell_handle_output_moved(struct wl_listener *listener, void *data) if (view->output != output) continue; weston_view_set_position(view, - view->geometry.x + output->move_x, - view->geometry.y + output->move_y); + view->geometry.pos_offset.x + output->move_x, + view->geometry.pos_offset.y + output->move_y); } wl_list_for_each(view, &shell->normal_layer.view_list.link, @@ -1116,8 +1143,8 @@ kiosk_shell_handle_output_moved(struct wl_listener *listener, void *data) if (view->output != output) continue; weston_view_set_position(view, - view->geometry.x + output->move_x, - view->geometry.y + output->move_y); + view->geometry.pos_offset.x + output->move_x, + view->geometry.pos_offset.y + output->move_y); } } diff --git a/kiosk-shell/kiosk-shell.h b/kiosk-shell/kiosk-shell.h index 070ba1ab4..99e783088 100644 --- a/kiosk-shell/kiosk-shell.h +++ b/kiosk-shell/kiosk-shell.h @@ -24,7 +24,7 @@ #ifndef WESTON_KIOSK_SHELL_H #define WESTON_KIOSK_SHELL_H -#include +#include #include #include @@ -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 c3a37da2b..2baaa4e5a 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,7 +9,6 @@ if get_option('shell-kiosk') dep_libm, dep_libexec_weston, dep_libshared, - dep_lib_desktop, dep_libweston_public, ] plugin_shell_kiosk = shared_library( diff --git a/libweston/auth.c b/libweston/auth.c new file mode 100644 index 000000000..2133abbe1 --- /dev/null +++ b/libweston/auth.c @@ -0,0 +1,116 @@ +/* + * Copyright © 2022 Philipp Zabel + * + * 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 "libweston-internal.h" + +#ifdef HAVE_PAM + +#include +#include + +static int +weston_pam_conv(int num_msg, const struct pam_message **msg, + struct pam_response **resp, void *appdata_ptr) +{ + const char *password = appdata_ptr; + struct pam_response *rsp; + int i; + + if (!num_msg) + return PAM_CONV_ERR; + + rsp = calloc(num_msg, sizeof(*rsp)); + if (!rsp) + return PAM_CONV_ERR; + + for (i = 0; i < num_msg; i++) { + switch (msg[i]->msg_style) { + case PAM_PROMPT_ECHO_OFF: + rsp[i].resp = strdup(password); + break; + case PAM_PROMPT_ECHO_ON: + break; + case PAM_ERROR_MSG: + weston_log("PAM error message: %s\n", msg[i]->msg); + break; + case PAM_TEXT_INFO: + weston_log("PAM info text: %s\n", msg[i]->msg); + break; + default: + free(rsp); + return PAM_CONV_ERR; + } + } + + *resp = rsp; + return PAM_SUCCESS; +} + +#endif + +WL_EXPORT bool +weston_authenticate_user(const char *username, const char *password) +{ + bool authenticated = false; +#ifdef HAVE_PAM + struct pam_conv conv = { + .conv = weston_pam_conv, + .appdata_ptr = strdup(password), + }; + struct pam_handle *pam; + int ret; + + conv.appdata_ptr = strdup(password); + + ret = pam_start("weston-remote-access", username, &conv, &pam); + if (ret != PAM_SUCCESS) { + weston_log("PAM: start failed\n"); + goto out; + } + + ret = pam_authenticate(pam, 0); + if (ret != PAM_SUCCESS) { + weston_log("PAM: authentication failed\n"); + goto out; + } + + ret = pam_acct_mgmt(pam, 0); + if (ret != PAM_SUCCESS) { + weston_log("PAM: account check failed\n"); + goto out; + } + + authenticated = true; +out: + ret = pam_end(pam, ret); + assert(ret == PAM_SUCCESS); + free(conv.appdata_ptr); +#endif + return authenticated; +} diff --git a/libweston/backend-drm/drm-gbm.c b/libweston/backend-drm/drm-gbm.c index d0a4c6ca4..187cd4e7a 100644 --- a/libweston/backend-drm/drm-gbm.c +++ b/libweston/backend-drm/drm-gbm.c @@ -44,18 +44,11 @@ #include "linux-dmabuf.h" #include "linux-explicit-synchronization.h" -struct gl_renderer_interface *gl_renderer; - static struct gbm_device * create_gbm_device(int fd) { struct gbm_device *gbm; - gl_renderer = weston_load_module("gl-renderer.so", - "gl_renderer_interface"); - if (!gl_renderer) - return NULL; - /* GBM will load a dri driver, but even though they need symbols from * libglapi, in some version of Mesa they are not linked to it. Since * only the gl-renderer module links to it, the call above won't make @@ -71,55 +64,48 @@ create_gbm_device(int fd) /* When initializing EGL, if the preferred buffer format isn't available * we may be able to substitute an ARGB format for an XRGB one. * - * This returns 0 if substitution isn't possible, but 0 might be a - * legitimate format for other EGL platforms, so the caller is - * responsible for checking for 0 before calling gl_renderer->create(). + * This returns NULL if substitution isn't possible. The caller is responsible + * for checking for NULL before calling gl_renderer->create(). * * This works around https://bugs.freedesktop.org/show_bug.cgi?id=89689 * but it's entirely possible we'll see this again on other implementations. */ -static uint32_t -fallback_format_for(uint32_t format) +static const struct pixel_format_info * +fallback_format_for(const struct pixel_format_info *format) { - const struct pixel_format_info *pf; - - pf = pixel_format_get_info_by_opaque_substitute(format); - if (!pf) - return 0; - - return pf->format; + return pixel_format_get_info_by_opaque_substitute(format->format); } static int drm_backend_create_gl_renderer(struct drm_backend *b) { - uint32_t format[3] = { - b->gbm_format, - fallback_format_for(b->gbm_format), - 0, + const struct pixel_format_info *format[3] = { + b->format, + fallback_format_for(b->format), + NULL, }; struct gl_renderer_display_options options = { .egl_platform = EGL_PLATFORM_GBM_KHR, .egl_native_display = b->gbm, .egl_surface_type = EGL_WINDOW_BIT, - .drm_formats = format, - .drm_formats_count = 2, + .formats = format, + .formats_count = 2, }; if (format[1]) - options.drm_formats_count = 3; - - if (gl_renderer->display_create(b->compositor, &options) < 0) - return -1; + options.formats_count = 3; - return 0; + return weston_compositor_init_renderer(b->compositor, + WESTON_RENDERER_GL, + &options.base); } 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; @@ -137,6 +123,9 @@ static void drm_output_fini_cursor_egl(struct drm_output *output) unsigned int i; for (i = 0; i < ARRAY_LENGTH(output->gbm_cursor_fb); i++) { + /* This cursor does not have a GBM device */ + if (output->gbm_cursor_fb[i] && !output->gbm_cursor_fb[i]->bo) + output->gbm_cursor_fb[i]->type = BUFFER_PIXMAN_DUMB; drm_fb_unref(output->gbm_cursor_fb[i]); output->gbm_cursor_fb[i] = NULL; } @@ -145,6 +134,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,26 +144,38 @@ 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, - 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); - if (!output->gbm_cursor_fb[i]) { - gbm_bo_destroy(bo); - goto err; + if (gbm_device_get_fd(b->gbm) != output->device->drm.fd) { + output->gbm_cursor_fb[i] = + drm_fb_create_dumb(output->device, + device->cursor_width, + device->cursor_height, + DRM_FORMAT_ARGB8888); + /* Override buffer type, since we know it is a cursor */ + output->gbm_cursor_fb[i]->type = BUFFER_CURSOR; + output->gbm_cursor_handle[i] = + output->gbm_cursor_fb[i]->handles[0]; + } else { + 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, device, false, BUFFER_CURSOR); + if (!output->gbm_cursor_fb[i]) { + gbm_bo_destroy(bo); + goto err; + } + output->gbm_cursor_handle[i] = gbm_bo_get_handle(bo).s32; } - output->gbm_cursor_handle[i] = gbm_bo_get_handle(bo).s32; } return 0; 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; } @@ -188,10 +190,11 @@ create_gbm_surface(struct gbm_device *gbm, struct drm_output *output) unsigned int num_modifiers; fmt = weston_drm_format_array_find_format(&plane->formats, - output->gbm_format); + output->format->format); if (!fmt) { - weston_log("format 0x%x not supported by output %s\n", - output->gbm_format, output->base.name); + weston_log("format %s not supported by output %s\n", + output->format->drm_format_name, + output->base.name); return; } @@ -201,11 +204,20 @@ create_gbm_surface(struct gbm_device *gbm, struct drm_output *output) output->gbm_surface = gbm_surface_create_with_modifiers(gbm, mode->width, mode->height, - output->gbm_format, + output->format->format, modifiers, num_modifiers); } #endif + /* + * If we cannot use modifiers to allocate the GBM surface and the GBM + * device differs from the KMS display device (because we are rendering + * on a different GPU), we have to use linear buffers to make sure that + * the allocated GBM surface is correctly displayed on the KMS device. + */ + if (gbm_device_get_fd(gbm) != output->device->drm.fd) + output->gbm_bo_flags |= GBM_BO_USE_LINEAR; + /* We may allocate with no modifiers in the following situations: * * 1. old GBM version, so HAVE_GBM_MODIFIERS is false; @@ -217,7 +229,7 @@ create_gbm_surface(struct gbm_device *gbm, struct drm_output *output) if (!output->gbm_surface) output->gbm_surface = gbm_surface_create(gbm, mode->width, mode->height, - output->gbm_format, + output->format->format, output->gbm_bo_flags); } @@ -225,13 +237,21 @@ create_gbm_surface(struct gbm_device *gbm, struct drm_output *output) int drm_output_init_egl(struct drm_output *output, struct drm_backend *b) { - uint32_t format[2] = { - output->gbm_format, - fallback_format_for(output->gbm_format), + const struct weston_renderer *renderer = b->compositor->renderer; + const struct weston_mode *mode = output->base.current_mode; + const struct pixel_format_info *format[2] = { + output->format, + fallback_format_for(output->format), }; struct gl_renderer_output_options options = { - .drm_formats = format, - .drm_formats_count = 1, + .formats = format, + .formats_count = 1, + .area.x = 0, + .area.y = 0, + .area.width = mode->width, + .area.height = mode->height, + .fb_size.width = mode->width, + .fb_size.height = mode->height, }; assert(output->gbm_surface == NULL); @@ -241,11 +261,11 @@ drm_output_init_egl(struct drm_output *output, struct drm_backend *b) return -1; } - if (options.drm_formats[1]) - options.drm_formats_count = 2; + if (options.formats[1]) + options.formats_count = 2; options.window_for_legacy = (EGLNativeWindowType) output->gbm_surface; options.window_for_platform = output->gbm_surface; - if (gl_renderer->output_window_create(&output->base, &options) < 0) { + if (renderer->gl->output_window_create(&output->base, &options) < 0) { weston_log("failed to create gl renderer output state\n"); gbm_surface_destroy(output->gbm_surface); output->gbm_surface = NULL; @@ -260,7 +280,8 @@ drm_output_init_egl(struct drm_output *output, struct drm_backend *b) void drm_output_fini_egl(struct drm_output *output) { - struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_backend *b = output->backend; + const struct weston_renderer *renderer = b->compositor->renderer; /* Destroying the GBM surface will destroy all our GBM buffers, * regardless of refcount. Ensure we destroy them here. */ @@ -270,7 +291,7 @@ drm_output_fini_egl(struct drm_output *output) drm_plane_reset_state(output->scanout_plane); } - gl_renderer->output_destroy(&output->base); + renderer->gl->output_destroy(&output->base); gbm_surface_destroy(output->gbm_surface); output->gbm_surface = NULL; drm_output_fini_cursor_egl(output); @@ -280,12 +301,14 @@ 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; + struct weston_paint_node *pnode; + bool have_through_hole = false; output->base.compositor->renderer->repaint_output(&output->base, - damage); + damage, NULL); bo = gbm_surface_lock_front_buffer(output->gbm_surface); if (!bo) { @@ -294,8 +317,13 @@ drm_output_render_gl(struct drm_output_state *state, pixman_region32_t *damage) return NULL; } - /* The renderer always produces an opaque image. */ - ret = drm_fb_get_from_bo(bo, b, true, BUFFER_GBM_SURFACE); + /* If there are through holes on the primary plane, the renderer needs to + * produces a non-opaque image, otherwise an opaque image. */ + wl_list_for_each_reverse(pnode, &output->base.paint_node_z_order_list, + z_order_link) + have_through_hole |= pnode->need_through_hole; + + ret = drm_fb_get_from_bo(bo, device, !have_through_hole, BUFFER_GBM_SURFACE); if (!ret) { weston_log("failed to get drm_fb for bo\n"); gbm_surface_release_buffer(output->gbm_surface, bo); @@ -306,67 +334,152 @@ 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) +#if defined(ENABLE_IMXG2D) +static int +drm_backend_create_g2d_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; + if (b->g2d_renderer->drm_display_create(b->compositor, + (void *)b->gbm) < 0) { + return -1; + } - weston_log("Switching to GL renderer\n"); + return 0; +} - b->gbm = create_gbm_device(b->drm.fd); - if (!b->gbm) { - weston_log("Failed to create gbm device. " - "Aborting renderer switch\n"); - return; +int +init_g2d(struct drm_backend *b) +{ + b->g2d_renderer = weston_load_module("g2d-renderer.so", + "g2d_renderer_interface", + LIBWESTON_MODULEDIR); + if (!b->g2d_renderer) { + weston_log("Could not load g2d renderer\n"); + return -1; } - wl_list_for_each(output, &b->compositor->output_list, base.link) - pixman_renderer_output_destroy(&output->base); + struct drm_device *device = b->drm; - b->compositor->renderer->destroy(b->compositor); + b->gbm = gbm_create_device(device->drm.fd); + if (!b->gbm) + return -1; - if (drm_backend_create_gl_renderer(b) < 0) { + if (drm_backend_create_g2d_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); + return -1; + } + + return 0; +} + +int +drm_output_init_g2d(struct drm_output *output, struct drm_backend *b) +{ + int w = output->base.current_mode->width; + int h = output->base.current_mode->height; + uint32_t format = output->format->format; + enum g2d_format g2dFormat; + uint32_t i = 0; + struct drm_device *device = b->drm; + + switch (format) { + case DRM_FORMAT_XRGB8888: + g2dFormat = G2D_BGRX8888; + break; + case DRM_FORMAT_ARGB8888: + g2dFormat = G2D_BGRA8888; + break; + case DRM_FORMAT_RGB565: + g2dFormat = G2D_RGB565; + break; + default: + weston_log("Unsupported pixman format 0x%x\n", format); + return -1; } - wl_list_for_each(output, &b->compositor->output_list, base.link) - drm_output_init_egl(output, b); + for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { + struct g2d_surfaceEx* g2dSurface = &(output->g2d_image[i]); + int ret; + output->dumb[i] = drm_fb_create_dumb(device, w, h, format); + if (!output->dumb[i]) + goto err; - b->use_pixman = 0; + ret = drmPrimeHandleToFD(device->drm.fd, output->dumb[i]->handles[0], DRM_CLOEXEC, + &output->dumb_dmafd[i]); + if(ret < 0) + goto err; - if (!dmabuf_support_inited && b->compositor->renderer->import_dmabuf) { - if (linux_dmabuf_setup(b->compositor) < 0) - weston_log("Error: initializing dmabuf " - "support failed.\n"); + ret = b->g2d_renderer->create_g2d_image(g2dSurface, g2dFormat, + output->dumb[i]->map, + w, h, + output->dumb[i]->strides[0], + output->dumb[i]->size, + output->dumb_dmafd[i]); + if (ret < 0) + goto err; } - 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"); + if (b->g2d_renderer->drm_output_create(&output->base) < 0) + goto err; + + drm_output_init_cursor_egl(output, b); + + return 0; + +err: + weston_log("drm_output_init_g2d failed.\n"); + for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { + if (output->dumb[i]) + drm_fb_unref(output->dumb[i]); + + output->dumb[i] = NULL; } + + return -1; } void -renderer_switch_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, void *data) +drm_output_fini_g2d(struct drm_output *output) +{ + unsigned int i; + struct drm_backend *b = to_drm_backend(output->base.compositor); + + pixman_region32_fini(&output->previous_damage); + + for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { + drm_fb_unref(output->dumb[i]); + output->dumb[i] = NULL; + close(output->dumb_dmafd[i]); + } + b->g2d_renderer->output_destroy(&output->base); +} + +struct drm_fb * +drm_output_render_g2d(struct drm_output_state *state, pixman_region32_t *damage) { - struct drm_backend *b = - to_drm_backend(keyboard->seat->compositor); + struct drm_output *output = state->output; + struct weston_compositor *ec = output->base.compositor; + struct drm_backend *b = to_drm_backend(output->base.compositor); + pixman_region32_t total_damage, previous_damage; + + pixman_region32_init(&total_damage); + pixman_region32_init(&previous_damage); + + pixman_region32_copy(&previous_damage, damage); + + pixman_region32_union(&total_damage, damage, &output->previous_damage); + pixman_region32_copy(&output->previous_damage, &previous_damage); + + output->current_image = (output->current_image + 1) % ARRAY_LENGTH(output->dumb); - switch_to_gl_renderer(b); + b->g2d_renderer->output_set_buffer(&output->base, + &output->g2d_image[output->current_image]); + + ec->renderer->repaint_output(&output->base, &total_damage, NULL); + + pixman_region32_fini(&total_damage); + pixman_region32_fini(&previous_damage); + + return drm_fb_ref(output->dumb[output->current_image]); } +#endif diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index 486008806..7b274afbd 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -42,6 +42,10 @@ #include #include +#if defined(ENABLE_IMXG2D) +#include +#include "renderer-g2d/g2d-renderer.h" +#endif #include #include @@ -54,6 +58,7 @@ #include #include #include +#include "output-capture.h" #include "shared/helpers.h" #include "shared/weston-drm-fourcc.h" #include "libinput-seat.h" @@ -72,6 +77,12 @@ #define DRM_PLANE_ZPOS_INVALID_PLANE 0xffffffffffffffffULL #endif +#ifndef DRM_PLANE_ALPHA_OPAQUE +#define DRM_PLANE_ALPHA_OPAQUE 0xffffUL +#endif + +#define ALIGNTO(a, b) ((a + (b-1)) & (~(b-1))) + /** * A small wrapper to print information into the 'drm-backend' debug scope. * @@ -162,6 +173,9 @@ enum wdrm_plane_property { WDRM_PLANE_IN_FENCE_FD, WDRM_PLANE_FB_DAMAGE_CLIPS, WDRM_PLANE_ZPOS, + WDRM_PLANE_ROTATION, + WDRM_PLANE_ALPHA, + WDRM_PLANE_DTRC_META, WDRM_PLANE__COUNT }; @@ -175,6 +189,19 @@ enum wdrm_plane_type { WDRM_PLANE_TYPE__COUNT }; +/** + * Possible values for the WDRM_PLANE_ROTATION property. + */ +enum wdrm_plane_rotation { + WDRM_PLANE_ROTATION_0 = 0, + WDRM_PLANE_ROTATION_90, + WDRM_PLANE_ROTATION_180, + WDRM_PLANE_ROTATION_270, + WDRM_PLANE_ROTATION_REFLECT_X, + WDRM_PLANE_ROTATION_REFLECT_Y, + WDRM_PLANE_ROTATION__COUNT, +}; + /** * List of properties attached to a DRM connector */ @@ -182,10 +209,16 @@ enum wdrm_connector_property { WDRM_CONNECTOR_EDID = 0, WDRM_CONNECTOR_DPMS, WDRM_CONNECTOR_CRTC_ID, + WDRM_CONNECTOR_WRITEBACK_PIXEL_FORMATS, + WDRM_CONNECTOR_WRITEBACK_FB_ID, + WDRM_CONNECTOR_WRITEBACK_OUT_FENCE_PTR, WDRM_CONNECTOR_NON_DESKTOP, WDRM_CONNECTOR_CONTENT_PROTECTION, WDRM_CONNECTOR_HDCP_CONTENT_TYPE, WDRM_CONNECTOR_PANEL_ORIENTATION, + WDRM_CONNECTOR_HDR_OUTPUT_METADATA, + WDRM_CONNECTOR_MAX_BPC, + WDRM_CONNECTOR_CONTENT_TYPE, WDRM_CONNECTOR__COUNT }; @@ -218,12 +251,27 @@ enum wdrm_panel_orientation { WDRM_PANEL_ORIENTATION__COUNT }; +enum wdrm_content_type { + WDRM_CONTENT_TYPE_NO_DATA = 0, + WDRM_CONTENT_TYPE_GRAPHICS, + WDRM_CONTENT_TYPE_PHOTO, + WDRM_CONTENT_TYPE_CINEMA, + WDRM_CONTENT_TYPE_GAME, + WDRM_CONTENT_TYPE__COUNT +}; + /** * List of properties attached to DRM CRTCs */ enum wdrm_crtc_property { WDRM_CRTC_MODE_ID = 0, WDRM_CRTC_ACTIVE, + WDRM_CRTC_CTM, + WDRM_CRTC_DEGAMMA_LUT, + WDRM_CRTC_DEGAMMA_LUT_SIZE, + WDRM_CRTC_GAMMA_LUT, + WDRM_CRTC_GAMMA_LUT_SIZE, + WDRM_CRTC_VRR_ENABLED, WDRM_CRTC__COUNT }; @@ -232,11 +280,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 +306,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,9 +315,41 @@ struct drm_backend { char *filename; dev_t devnum; } drm; - struct gbm_device *gbm; - struct wl_listener session_listener; - uint32_t gbm_format; + + /* Track the GEM handles if the device does not have a gbm device, which + * tracks the handles for us. + */ + struct hash_table *gem_handle_refcnt; + + /* drm_crtc::link */ + struct wl_list crtc_list; + + struct wl_list plane_list; + + /* drm_writeback::link */ + struct wl_list writeback_connector_list; + + bool state_invalid; + + bool atomic_modeset; + + bool tearing_supported; + + bool aspect_ratio_supported; + + int32_t cursor_width; + int32_t cursor_height; + + bool cursors_are_broken; + bool sprites_are_broken; + + void *repaint_data; + + bool fb_modifiers; + + /* hdr10 metadata blob id */ + unsigned int hdr_blob_id; + bool clean_hdr_blob; /* we need these parameters in order to not fail drmModeAddFB2() * due to out of bounds dimensions, and then mistakenly set @@ -276,41 +358,47 @@ struct drm_backend { int min_width, max_width; int min_height, max_height; - struct wl_list plane_list; - uint32_t next_plane_idx; - - void *repaint_data; + /* drm_backend::kms_list */ + struct wl_list link; +}; - bool state_invalid; +struct drm_backend { + struct weston_backend base; + struct weston_compositor *compositor; - /* drm_crtc::link */ - struct wl_list crtc_list; + struct udev *udev; + struct wl_event_source *drm_source; - /* drm_writeback::link */ - struct wl_list writeback_connector_list; + struct udev_monitor *udev_monitor; + struct wl_event_source *udev_drm_source; - bool sprites_are_broken; - bool cursors_are_broken; + struct drm_device *drm; + /* drm_device::link */ + struct wl_list kms_list; + struct gbm_device *gbm; + struct wl_listener session_listener; + const struct pixel_format_info *format; - bool atomic_modeset; +#if defined(ENABLE_IMXG2D) + bool use_g2d; + struct g2d_renderer_interface *g2d_renderer;; +#endif - bool use_pixman; bool use_pixman_shadow; - struct udev_input input; + bool enable_overlay_view; + uint32_t shell_width; + uint32_t shell_height; - int32_t cursor_width; - int32_t cursor_height; + struct udev_input input; uint32_t pageflip_timeout; bool shutting_down; - bool aspect_ratio_supported; - - bool fb_modifiers; - struct weston_log_scope *debug; + + struct weston_drm_format_array supported_formats; }; struct drm_mode { @@ -331,6 +419,8 @@ enum drm_fb_type { struct drm_fb { enum drm_fb_type type; + struct drm_device *scanout_device; + int refcnt; uint32_t fb_id, size; @@ -351,19 +441,20 @@ struct drm_fb { /* Used by dumb fbs */ void *map; + + uint64_t dtrc_meta; }; struct drm_buffer_fb { struct drm_fb *fb; enum try_view_on_plane_failure_reasons failure_reasons; - struct wl_listener buffer_destroy_listener; + struct drm_device *device; + struct wl_list link; }; -struct drm_edid { - char eisa_id[13]; - char monitor_name[13]; - char pnp_id[5]; - char serial_number[13]; +struct drm_fb_private { + struct wl_list buffer_fb_list; + struct wl_listener buffer_destroy_listener; }; /** @@ -373,7 +464,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; }; @@ -394,16 +485,7 @@ struct drm_output_state { enum dpms_enum dpms; enum weston_hdcp_protection protection; 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 */ + bool tear; }; /** @@ -431,7 +513,10 @@ struct drm_plane_state { int32_t dest_x, dest_y; uint32_t dest_w, dest_h; + uint32_t rotation; + uint64_t zpos; + uint16_t alpha; bool complete; @@ -461,29 +546,35 @@ struct drm_plane_state { struct drm_plane { struct weston_plane base; - struct drm_backend *backend; + struct drm_device *device; enum wdrm_plane_type type; uint32_t possible_crtcs; uint32_t plane_id; uint32_t plane_idx; + uint32_t crtc_id; struct drm_property_info props[WDRM_PLANE__COUNT]; + uint64_t dtrc_meta; + /* The last state submitted to the kernel for this plane. */ struct drm_plane_state *state_cur; uint64_t zpos_min; uint64_t zpos_max; + uint16_t alpha_min; + uint16_t alpha_max; + struct wl_list link; struct weston_drm_format_array formats; }; struct drm_connector { - struct drm_backend *backend; + struct drm_device *device; drmModeConnector *conn; uint32_t connector_id; @@ -494,31 +585,70 @@ struct drm_connector { struct drm_property_info props[WDRM_CONNECTOR__COUNT]; }; +enum writeback_screenshot_state { + /* No writeback connector screenshot ongoing. */ + DRM_OUTPUT_WB_SCREENSHOT_OFF, + /* Screenshot client just triggered a writeback connector screenshot. + * Now we need to prepare an atomic commit that will make DRM perform + * the writeback operation. */ + DRM_OUTPUT_WB_SCREENSHOT_PREPARE_COMMIT, + /* The atomic commit with writeback setup has been committed. After the + * commit is handled by DRM it will give us a sync fd that gets + * signalled when the writeback is done. */ + DRM_OUTPUT_WB_SCREENSHOT_CHECK_FENCE, + /* The atomic commit completed and we received the sync fd from the + * kernel. We've polled to check if the writeback was over, but it + * wasn't. Now we must stop the repaint loop and wait until the + * writeback is complete, because we can't commit with KMS objects + * (CRTC, planes, etc) that are in used by the writeback job. */ + DRM_OUTPUT_WB_SCREENSHOT_WAITING_SIGNAL, +}; + +struct drm_writeback_state { + struct drm_writeback *wb; + struct drm_output *output; + + enum writeback_screenshot_state state; + struct weston_capture_task *ct; + + struct drm_fb *fb; + int32_t out_fence_fd; + struct wl_event_source *wb_source; + + /* Reference to fb's being used by the writeback job. These are all the + * framebuffers in every drm_plane_state of the output state that we've + * used to request the writeback job */ + struct wl_array referenced_fbs; +}; + 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 weston_drm_format_array formats; }; struct drm_head { struct weston_head base; - struct drm_backend *backend; struct drm_connector connector; - struct drm_edid edid; - 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; @@ -533,13 +663,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]; @@ -549,9 +684,17 @@ struct drm_output { int current_cursor; struct gbm_surface *gbm_surface; - uint32_t gbm_format; + const struct pixel_format_info *format; uint32_t gbm_bo_flags; + uint32_t hdr_output_metadata_blob_id; + uint64_t ackd_color_outcome_serial; + + unsigned max_bpc; + + bool deprecated_gamma_is_set; + bool legacy_gamma_not_supported; + /* Plane being displayed directly on the CRTC */ struct drm_plane *scanout_plane; @@ -561,8 +704,15 @@ struct drm_output { * yet acknowledged completion of state_cur. */ struct drm_output_state *state_last; - struct drm_fb *dumb[2]; - pixman_image_t *image[2]; + /* only set when a writeback screenshot is ongoing */ + struct drm_writeback_state *wb_state; + + struct drm_fb *dumb[3]; + struct weston_renderbuffer *renderbuffer[3]; +#if defined(ENABLE_IMXG2D) + struct g2d_surfaceEx g2d_image[3]; + int dumb_dmafd[3]; +#endif int current_image; pixman_region32_t previous_damage; @@ -572,19 +722,51 @@ struct drm_output { struct wl_event_source *pageflip_timer; bool virtual; + void (*virtual_destroy)(struct weston_output *base); submit_frame_cb virtual_submit_frame; + + enum wdrm_content_type content_type; + + int (*surface_get_in_fence_fd)(struct gbm_surface *surface); }; +void +drm_destroy(struct weston_backend *backend); + static inline struct drm_head * to_drm_head(struct weston_head *base) { + if (base->backend->destroy != drm_destroy) + return NULL; return container_of(base, struct drm_head, base); } +void +drm_writeback_reference_planes(struct drm_writeback_state *state, + struct wl_list *plane_state_list); +bool +drm_writeback_should_wait_completion(struct drm_writeback_state *state); +void +drm_writeback_fail_screenshot(struct drm_writeback_state *state, + const char *err_msg); +enum writeback_screenshot_state +drm_output_get_writeback_state(struct drm_output *output); + +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,32 +799,31 @@ 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); +uint64_t +drm_rotation_from_output_transform(struct drm_plane *plane, + enum wl_output_transform ot); + static inline bool -drm_view_transform_supported(struct weston_view *ev, struct weston_output *output) +drm_paint_node_transform_supported(struct weston_paint_node *node, struct drm_plane *plane) { - struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; - - /* This will incorrectly disallow cases where the combination of - * buffer and view transformations match the output transform. - * Fixing this requires a full analysis of the transformation - * chain. */ - if (ev->transform.enabled && - ev->transform.matrix.type >= WESTON_MATRIX_TRANSFORM_ROTATE) + /* if false, the transform doesn't map to any of the standard + * (ie: 90 degree) output transformations. */ + if (!node->valid_transform) return false; - if (viewport->buffer.transform != output->transform) + if (drm_rotation_from_output_transform(plane, node->transform) == 0) return false; return true; } 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 +832,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 +843,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 +871,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,31 +898,35 @@ 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, - uint32_t *try_view_on_plane_failure_reasons); +drm_fb_get_from_paint_node(struct drm_output_state *state, + struct weston_paint_node *pnode); + extern bool -drm_can_scanout_dmabuf(struct weston_compositor *ec, +drm_can_scanout_dmabuf(struct weston_backend *backend, struct linux_dmabuf_buffer *dmabuf); #else static inline struct drm_fb * -drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev, - uint32_t *try_view_on_plane_failure_reasons) +drm_fb_get_from_paint_node(struct drm_output_state *state, + struct weston_paint_node *pnode) { return NULL; } static inline bool -drm_can_scanout_dmabuf(struct weston_compositor *ec, +drm_can_scanout_dmabuf(struct weston_backend *backend, struct linux_dmabuf_buffer *dmabuf) { return false; @@ -749,7 +934,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 * @@ -794,13 +979,14 @@ drm_plane_state_free(struct drm_plane_state *state, bool force); void drm_plane_state_put_back(struct drm_plane_state *state); bool -drm_plane_state_coords_for_view(struct drm_plane_state *state, - struct weston_view *ev, uint64_t zpos); +drm_plane_state_coords_for_paint_node(struct drm_plane_state *state, + struct weston_paint_node *node, + uint64_t zpos); 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); @@ -809,9 +995,8 @@ void drm_output_render(struct drm_output_state *state, pixman_region32_t *damage); int -parse_gbm_format(const char *s, uint32_t default_value, uint32_t *gbm_format); - -extern struct gl_renderer_interface *gl_renderer; +parse_gbm_format(const char *s, const struct pixel_format_info *default_format, + const struct pixel_format_info **format); #ifdef BUILD_DRM_VIRTUAL extern int @@ -836,10 +1021,23 @@ drm_output_fini_egl(struct drm_output *output); struct drm_fb * drm_output_render_gl(struct drm_output_state *state, pixman_region32_t *damage); +#if defined(ENABLE_IMXG2D) +int +init_g2d(struct drm_backend *b); + +int +drm_output_init_g2d(struct drm_output *output, struct drm_backend *b); void -renderer_switch_binding(struct weston_keyboard *keyboard, - const struct timespec *time, uint32_t key, void *data); +drm_output_fini_g2d(struct drm_output *output); + +struct drm_fb * +drm_output_render_g2d(struct drm_output_state *state, pixman_region32_t *damage); +#endif + +int +drm_fb_get_gbm_alignment(struct drm_fb *fb); + #else inline static int init_egl(struct drm_backend *b) @@ -864,11 +1062,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 597e71c17..fc228e5d1 100644 --- a/libweston/backend-drm/drm-virtual.c +++ b/libweston/backend-drm/drm-virtual.c @@ -36,6 +36,7 @@ #include #include "drm-internal.h" +#include "pixel-formats.h" #include "renderer-gl/gl-renderer.h" #define POISON_PTR ((void *)8) @@ -47,7 +48,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 +56,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 +82,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,19 +119,20 @@ 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; weston_drm_format_array_init(&plane->formats); - fmt = weston_drm_format_array_add_format(&plane->formats, output->gbm_format); + fmt = weston_drm_format_array_add_format(&plane->formats, + output->format->format); if (!fmt) goto err; /* 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; @@ -123,8 +140,10 @@ drm_virtual_plane_create(struct drm_backend *b, struct drm_output *output) if (weston_drm_format_add_modifier(fmt, mod) < 0) goto err; - weston_plane_init(&plane->base, b->compositor, 0, 0); - wl_list_insert(&b->plane_list, &plane->link); + weston_plane_init(&plane->base, b->compositor); + + plane->plane_idx = get_drm_plane_index_maximum(device) + 1; + wl_list_insert(&device->plane_list, &plane->link); return plane; @@ -163,11 +182,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 +203,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 +263,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 +277,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,11 +287,12 @@ 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); - if (b->use_pixman) { + if (output_base->compositor->renderer->type == WESTON_RENDERER_PIXMAN) { weston_log("Not support pixman renderer on Virtual output\n"); goto err; } @@ -277,7 +302,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 +345,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); @@ -345,6 +375,7 @@ drm_virtual_output_create(struct weston_compositor *c, char *name) output->base.disable = drm_virtual_output_disable; output->base.attach_head = NULL; + output->backend = b; output->state_cur = drm_output_state_alloc(output, NULL); weston_compositor_add_pending_output(&output->base, c); @@ -357,12 +388,13 @@ 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; + if (parse_gbm_format(gbm_format, b->format, &output->format) == -1) + output->format = b->format; - return output->gbm_format; + return output->format->format; } static void @@ -377,7 +409,10 @@ drm_virtual_output_set_submit_frame_cb(struct weston_output *output_base, static int drm_virtual_output_get_fence_fd(struct weston_output *output_base) { - return gl_renderer->create_fence_fd(output_base); + struct weston_compositor *compositor = output_base->compositor; + const struct weston_renderer *renderer = compositor->renderer; + + return renderer->gl->create_fence_fd(output_base); } static void diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 427877020..ea4d96bd4 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -50,11 +51,15 @@ #include #include #include +#include +#include "compositor/weston.h" #include "drm-internal.h" +#include "shared/hash.h" #include "shared/helpers.h" #include "shared/timespec-util.h" #include "shared/string-helpers.h" #include "shared/weston-drm-fourcc.h" +#include "output-capture.h" #include "pixman-renderer.h" #include "pixel-formats.h" #include "libbacklight.h" @@ -65,12 +70,14 @@ #include "linux-dmabuf.h" #include "linux-dmabuf-unstable-v1-server-protocol.h" #include "linux-explicit-synchronization.h" +#include "hdr10-metadata-unstable-v1-server-protocol.h" static const char default_seat[] = "seat0"; static void -drm_backend_create_faked_zpos(struct drm_backend *b) +drm_backend_create_faked_zpos(struct drm_device *device) { + struct drm_backend *b = device->backend; struct drm_plane *plane; uint64_t zpos = 0ULL; uint64_t zpos_min_primary; @@ -78,7 +85,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 +96,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 +112,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 +170,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 +196,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 +217,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 +231,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 +265,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 +277,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 +295,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) { @@ -310,6 +324,9 @@ drm_output_update_complete(struct drm_output *output, uint32_t flags, return; } + if (output->state_cur->tear) + flags |= WESTON_FINISH_FRAME_TEARING; + ts.tv_sec = sec; ts.tv_nsec = usec * 1000; @@ -334,14 +351,8 @@ drm_output_render_pixman(struct drm_output_state *state, output->current_image ^= 1; - pixman_renderer_output_set_buffer(&output->base, - output->image[output->current_image]); - pixman_renderer_output_set_hw_extra_damage(&output->base, - &output->previous_damage); - - ec->renderer->repaint_output(&output->base, damage); - - pixman_region32_copy(&output->previous_damage, damage); + ec->renderer->repaint_output(&output->base, damage, + output->renderbuffer[output->current_image]); return drm_fb_ref(output->dumb[output->current_image]); } @@ -350,13 +361,16 @@ 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; + uint32_t width; + uint32_t height; pixman_region32_t scanout_damage; pixman_box32_t *rects; int n_rects; @@ -374,15 +388,38 @@ drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) * area. But, we still have to call the renderer anyway if any screen * capture is pending, otherwise the capture will not complete. */ + +#ifdef HAVE_GBM_MODIFIERS + int gbm_aligned = drm_fb_get_gbm_alignment (scanout_plane->state_cur->fb); +#endif + if (!pixman_region32_not_empty(damage) && wl_list_empty(&output->base.frame_signal.listener_list) && + !weston_output_has_renderer_capture_tasks(&output->base) && scanout_plane->state_cur->fb && (scanout_plane->state_cur->fb->type == BUFFER_GBM_SURFACE || - scanout_plane->state_cur->fb->type == BUFFER_PIXMAN_DUMB)) { + scanout_plane->state_cur->fb->type == BUFFER_PIXMAN_DUMB) && +#ifdef HAVE_GBM_MODIFIERS + scanout_plane->state_cur->fb->width == + ALIGNTO(output->base.current_mode->width, gbm_aligned) && + scanout_plane->state_cur->fb->height == + ALIGNTO(output->base.current_mode->height, gbm_aligned)) { +#else + scanout_plane->state_cur->fb->width == + output->base.current_mode->width && + scanout_plane->state_cur->fb->height == + output->base.current_mode->height) { +#endif fb = drm_fb_ref(scanout_plane->state_cur->fb); - } else if (b->use_pixman) { + } else if (c->renderer->type == WESTON_RENDERER_PIXMAN) { fb = drm_output_render_pixman(state, damage); - } else { + } +#if defined(ENABLE_IMXG2D) + else if (b->use_g2d) { + fb = drm_output_render_g2d(state, damage); + } +#endif + else { fb = drm_output_render_gl(state, damage); } @@ -396,13 +433,25 @@ drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) scanout_state->src_x = 0; scanout_state->src_y = 0; - scanout_state->src_w = fb->width << 16; - scanout_state->src_h = fb->height << 16; + scanout_state->src_w = output->base.current_mode->width << 16; + scanout_state->src_h = output->base.current_mode->height << 16; scanout_state->dest_x = 0; scanout_state->dest_y = 0; - scanout_state->dest_w = output->base.current_mode->width; - scanout_state->dest_h = output->base.current_mode->height; + scanout_state->dest_w = scanout_state->src_w >> 16; + scanout_state->dest_h = scanout_state->src_h >> 16; + if ( output->base.transform == WL_OUTPUT_TRANSFORM_NORMAL && + b->shell_width > 0 && + b->shell_height > 0) { + width = b->shell_width << 16; + height = b->shell_height << 16; + if (scanout_state->src_w > width && scanout_state->src_h > width){ + scanout_state->src_w = width; + scanout_state->src_h = height; + } + } + + scanout_state->zpos = scanout_plane->zpos_min; pixman_region32_subtract(&c->primary_plane.damage, &c->primary_plane.damage, damage); @@ -412,29 +461,10 @@ drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) return; 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); - } + weston_region_global_to_output(&scanout_damage, + &output->base, + damage); assert(scanout_state->damage_blob_id == 0); @@ -446,25 +476,187 @@ 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); pixman_region32_fini(&scanout_damage); } +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(device->drm.fd, + conn->encoders[i]); + if (!encoder) + continue; + + possible_crtcs |= encoder->possible_crtcs; + drmModeFreeEncoder(encoder); + } + + return possible_crtcs; +} + +static struct drm_writeback * +drm_output_find_compatible_writeback(struct drm_output *output) +{ + struct drm_crtc *crtc; + struct drm_writeback *wb; + bool in_use; + uint32_t possible_crtcs; + + wl_list_for_each(wb, &output->device->writeback_connector_list, link) { + /* Another output may be using the writeback connector. */ + in_use = false; + wl_list_for_each(crtc, &output->device->crtc_list, link) { + if (crtc->output && crtc->output->wb_state && + crtc->output->wb_state->wb == wb) { + in_use = true; + break; + } + } + if (in_use) + continue; + + /* Is the writeback connector compatible with the CRTC? */ + possible_crtcs = + drm_connector_get_possible_crtcs_mask(&wb->connector); + if (!(possible_crtcs & (1 << output->crtc->pipe))) + continue; + + /* Does the writeback connector support the output gbm format? */ + if (!weston_drm_format_array_find_format(&wb->formats, + output->format->format)) + continue; + + return wb; + } + + return NULL; +} + +static struct drm_writeback_state * +drm_writeback_state_alloc(void) +{ + struct drm_writeback_state *state; + + state = zalloc(sizeof *state); + if (!state) + return NULL; + + state->state = DRM_OUTPUT_WB_SCREENSHOT_OFF; + state->out_fence_fd = -1; + wl_array_init(&state->referenced_fbs); + + return state; +} + +static void +drm_writeback_state_free(struct drm_writeback_state *state) +{ + struct drm_fb **fb; + + if (state->out_fence_fd >= 0) + close(state->out_fence_fd); + + /* Unref framebuffer that was given to save the content of the writeback */ + if (state->fb) + drm_fb_unref(state->fb); + + /* Unref framebuffers that were in use in the same commit of the one with + * the writeback setup */ + wl_array_for_each(fb, &state->referenced_fbs) + drm_fb_unref(*fb); + wl_array_release(&state->referenced_fbs); + + free(state); +} + +static void +drm_output_pick_writeback_capture_task(struct drm_output *output) +{ + struct weston_capture_task *ct; + struct weston_buffer *buffer; + struct drm_writeback *wb; + const char *msg; + int32_t width = output->base.current_mode->width; + int32_t height = output->base.current_mode->height; + uint32_t format = output->format->format; + + assert(output->device->atomic_modeset); + + ct = weston_output_pull_capture_task(&output->base, + WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK, + width, height, pixel_format_get_info(format)); + if (!ct) + return; + + if (output->base.disable_planes > 0) { + msg = "drm: KMS planes usage is disabled for now, so " \ + "writeback capture tasks are rejected"; + goto err; + } + + wb = drm_output_find_compatible_writeback(output); + if (!wb) { + msg = "drm: could not find writeback connector for output"; + goto err; + } + + buffer = weston_capture_task_get_buffer(ct); + assert(buffer->width == width); + assert(buffer->height == height); + assert(buffer->pixel_format->format == output->format->format); + + output->wb_state = drm_writeback_state_alloc(); + if (!output->wb_state) { + msg = "drm: failed to allocate memory for writeback state"; + goto err; + } + + output->wb_state->fb = drm_fb_create_dumb(output->device, width, height, format); + if (!output->wb_state->fb) { + msg = "drm: failed to create dumb buffer for writeback state"; + goto err_fb; + } + + output->wb_state->output = output; + output->wb_state->wb = wb; + output->wb_state->state = DRM_OUTPUT_WB_SCREENSHOT_PREPARE_COMMIT; + output->wb_state->ct = ct; + + return; + +err_fb: + drm_writeback_state_free(output->wb_state); + output->wb_state = NULL; +err: + weston_capture_task_retire_failed(ct, msg); +} + 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 +677,12 @@ 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; + + if (device->atomic_modeset) + drm_output_pick_writeback_capture_task(output); + drm_output_render(state, damage); scanout_state = drm_output_state_get_plane(state, output->scanout_plane); @@ -529,11 +727,13 @@ 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; + uint32_t flags = WP_PRESENTATION_FEEDBACK_INVALID; int ret; drmVBlank vbl = { .request.type = DRM_VBLANK_RELATIVE, @@ -552,14 +752,24 @@ 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); + /* If we're tearing, we've been generating timestamps from the + * presentation clock that don't line up with the msc timestamps, + * and could be more recent than the latest msc, which would cause + * an assert() later. + */ + if (output->state_cur->tear) { + flags |= WESTON_FINISH_FRAME_TEARING; + goto finish_frame; + } + /* 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,15 +780,14 @@ 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 = millihz_to_nsec(output->base.current_mode->refresh); if (timespec_to_nsec(&vbl2now) < refresh_nsec) { drm_output_update_msc(output, vbl.reply.sequence); - weston_output_finish_frame(output_base, &ts, - WP_PRESENTATION_FEEDBACK_INVALID); + weston_output_finish_frame(output_base, &ts, flags); return 0; } } @@ -590,7 +799,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 +807,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; } @@ -607,8 +816,7 @@ drm_output_start_repaint_loop(struct weston_output *output_base) finish_frame: /* if we cannot page-flip, immediately finish frame */ - weston_output_finish_frame(output_base, NULL, - WP_PRESENTATION_FEEDBACK_INVALID); + weston_output_finish_frame(output_base, NULL, flags); return 0; } @@ -619,24 +827,37 @@ drm_output_start_repaint_loop(struct weston_output *output_base) * 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 * -drm_repaint_begin(struct weston_compositor *compositor) +static void +drm_repaint_begin(struct weston_backend *backend) { - struct drm_backend *b = to_drm_backend(compositor); - struct drm_pending_state *ret; + struct drm_backend *b = container_of(backend, struct drm_backend, base); + struct drm_device *device; + struct drm_pending_state *pending_state; - ret = drm_pending_state_alloc(b); - b->repaint_data = ret; + device = b->drm; + 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); + char *dbg = weston_compositor_print_scene_graph(b->compositor); drm_debug(b, "[repaint] Beginning repaint; pending_state %p\n", - ret); + device->repaint_data); drm_debug(b, "%s", dbg); free(dbg); } - return ret; + wl_list_for_each(device, &b->kms_list, link) { + 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(b->compositor); + drm_debug(b, "[repaint] Beginning repaint; pending_state %p\n", + pending_state); + drm_debug(b, "%s", dbg); + free(dbg); + } + } } /** @@ -649,20 +870,33 @@ 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_backend *backend) { - struct drm_backend *b = to_drm_backend(compositor); - struct drm_pending_state *pending_state = repaint_data; + struct drm_backend *b = container_of(backend, struct drm_backend, base); + struct drm_device *device; + struct drm_pending_state *pending_state; int ret; + device = b->drm; + pending_state = device->repaint_data; ret = drm_pending_state_apply(pending_state); if (ret != 0) 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; + + wl_list_for_each(device, &b->kms_list, link) { + pending_state = device->repaint_data; + ret = drm_pending_state_apply(pending_state); + if (ret != 0) + weston_log("repaint-flush failed: %s\n", strerror(errno)); + + drm_debug(b, "[repaint] flushed pending_state %p\n", pending_state); + device->repaint_data = NULL; + } - return (ret == -EACCES) ? -1 : 0; + return (ret == -EACCES || ret == -EBUSY) ? ret : 0; } /** @@ -672,14 +906,24 @@ 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_backend *backend) { - struct drm_backend *b = to_drm_backend(compositor); - struct drm_pending_state *pending_state = repaint_data; + struct drm_backend *b = container_of(backend, struct drm_backend, base); + struct drm_device *device; + struct drm_pending_state *pending_state; + device = b->drm; + 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; + + wl_list_for_each(device, &b->kms_list, link) { + pending_state = device->repaint_data; + drm_pending_state_free(pending_state); + drm_debug(b, "[repaint] cancel pending_state %p\n", pending_state); + device->repaint_data = NULL; + } } static int @@ -691,9 +935,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,21 +955,44 @@ 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) { + if (b->compositor->renderer->type == WESTON_RENDERER_PIXMAN) { drm_output_fini_pixman(output); if (drm_output_init_pixman(output, b) < 0) { weston_log("failed to init output pixman state with " "new mode\n"); return -1; } +#if defined(ENABLE_IMXG2D) + } else if (b->use_g2d) { + drm_output_fini_g2d(output); + if (drm_output_init_g2d(output, b) < 0) { + weston_log("failed to init output g2d state with " + "new mode\n"); + return -1; + } +#endif } else { drm_output_fini_egl(output); if (drm_output_init_egl(output, b) < 0) { @@ -733,13 +1002,21 @@ drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mo } } + if (device->atomic_modeset) + weston_output_update_capture_info(&output->base, + WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK, + output->base.current_mode->width, + output->base.current_mode->height, + pixel_format_get_info(output->format->format)); + return 0; } static int init_pixman(struct drm_backend *b) { - return pixman_renderer_init(b->compositor); + return weston_compositor_init_renderer(b->compositor, + WESTON_RENDERER_PIXMAN, NULL); } /** @@ -754,15 +1031,18 @@ 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; + uint64_t *alpha_range_values; plane = zalloc(sizeof(*plane)); if (!plane) { @@ -770,23 +1050,23 @@ 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; plane->plane_id = kplane->plane_id; + plane->crtc_id = kplane->crtc_id; 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], @@ -805,8 +1085,20 @@ drm_plane_create(struct drm_backend *b, const drmModePlane *kplane) plane->zpos_max = DRM_PLANE_ZPOS_INVALID_PLANE; } + alpha_range_values = + drm_property_get_range_values(&plane->props[WDRM_PLANE_ALPHA], + props); + + if (alpha_range_values) { + plane->alpha_min = (uint16_t) alpha_range_values[0]; + plane->alpha_max = (uint16_t) alpha_range_values[1]; + } else { + plane->alpha_min = DRM_PLANE_ALPHA_OPAQUE; + plane->alpha_max = DRM_PLANE_ALPHA_OPAQUE; + } + if (drm_plane_populate_formats(plane, kplane, props, - b->fb_modifiers) < 0) { + device->fb_modifiers) < 0) { drmModeFreeObjectProperties(props); goto err; } @@ -816,8 +1108,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); + + 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 +1133,20 @@ drm_plane_create(struct drm_backend *b, const drmModePlane *kplane) /** * 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 +1157,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; @@ -866,6 +1172,14 @@ drm_output_find_special_plane(struct drm_backend *b, struct drm_output *output, if (found_elsewhere) continue; + /* If a plane already has a CRTC selected and it is not our + * output's CRTC, then do not select this plane. We cannot + * switch away a plane from a CTRC when active. */ + if ((type == WDRM_PLANE_TYPE_PRIMARY) && + (plane->crtc_id != 0) && + (plane->crtc_id != output->crtc->crtc_id)) + continue; + plane->possible_crtcs = (1 << output->crtc->pipe); return plane; } @@ -884,8 +1198,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 +1218,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 +1238,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 +1253,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 +1264,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 +1354,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 +1404,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,62 +1463,66 @@ make_connector_name(const drmModeConnector *con) static int drm_output_init_pixman(struct drm_output *output, struct drm_backend *b) { + struct weston_renderer *renderer = output->base.compositor->renderer; + const struct pixman_renderer_interface *pixman = renderer->pixman; + 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; - uint32_t pixman_format; unsigned int i; const struct pixman_renderer_output_options options = { .use_shadow = b->use_pixman_shadow, + .fb_size = { .width = w, .height = h }, + .format = output->format }; - switch (format) { - case DRM_FORMAT_XRGB8888: - pixman_format = PIXMAN_x8r8g8b8; - break; - case DRM_FORMAT_RGB565: - pixman_format = PIXMAN_r5g6b5; - break; - default: - weston_log("Unsupported pixman format 0x%x\n", format); - return -1; + assert(options.format); + + if (!options.format->pixman_format) { + weston_log("Unsupported pixel format %s\n", + options.format->drm_format_name); + return -1; } + if (pixman->output_create(&output->base, &options) < 0) + goto err; + /* 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, + options.format->format); if (!output->dumb[i]) goto err; - output->image[i] = - pixman_image_create_bits(pixman_format, w, h, - output->dumb[i]->map, - output->dumb[i]->strides[0]); - if (!output->image[i]) + output->renderbuffer[i] = + pixman->create_image_from_ptr(&output->base, + options.format, w, h, + output->dumb[i]->map, + output->dumb[i]->strides[0]); + if (!output->renderbuffer[i]) goto err; - } - if (pixman_renderer_output_create(&output->base, &options) < 0) - goto err; + pixman_region32_init_rect(&output->renderbuffer[i]->damage, + output->base.x, output->base.y, + output->base.width, + output->base.height); + } weston_log("DRM: output %s %s shadow framebuffer.\n", output->base.name, b->use_pixman_shadow ? "uses" : "does not use"); - pixman_region32_init_rect(&output->previous_damage, - output->base.x, output->base.y, output->base.width, output->base.height); - return 0; err: for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { if (output->dumb[i]) drm_fb_unref(output->dumb[i]); - if (output->image[i]) - pixman_image_unref(output->image[i]); + if (output->renderbuffer[i]) + weston_renderbuffer_unref(output->renderbuffer[i]); output->dumb[i] = NULL; - output->image[i] = NULL; + output->renderbuffer[i] = NULL; } + pixman->output_destroy(&output->base); return -1; } @@ -1203,7 +1530,8 @@ drm_output_init_pixman(struct drm_output *output, struct drm_backend *b) static void drm_output_fini_pixman(struct drm_output *output) { - struct drm_backend *b = to_drm_backend(output->base.compositor); + struct weston_renderer *renderer = output->base.compositor->renderer; + struct drm_backend *b = output->backend; unsigned int i; /* Destroying the Pixman surface will destroy all our buffers, @@ -1214,15 +1542,14 @@ drm_output_fini_pixman(struct drm_output *output) drm_plane_reset_state(output->scanout_plane); } - pixman_renderer_output_destroy(&output->base); - pixman_region32_fini(&output->previous_damage); - for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { - pixman_image_unref(output->image[i]); + weston_renderbuffer_unref(output->renderbuffer[i]); drm_fb_unref(output->dumb[i]); output->dumb[i] = NULL; - output->image[i] = NULL; + output->renderbuffer[i] = NULL; } + + renderer->pixman->output_destroy(&output->base); } static void @@ -1242,9 +1569,8 @@ setup_output_seat_constraint(struct drm_backend *b, pointer = weston_seat_get_pointer(&seat->base); if (pointer) - weston_pointer_clamp(pointer, - &pointer->x, - &pointer->y); + pointer->pos = weston_pointer_clamp(pointer, + pointer->pos); } } @@ -1252,11 +1578,17 @@ static int 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_output *output = to_drm_output(output_base); + struct drm_backend *b = output->backend; + 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 +1601,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,47 +1612,45 @@ 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 -parse_gbm_format(const char *s, uint32_t default_value, uint32_t *gbm_format) +parse_gbm_format(const char *s, const struct pixel_format_info *default_format, + const struct pixel_format_info **format) { - const struct pixel_format_info *pinfo; - if (s == NULL) { - *gbm_format = default_value; + *format = default_format; + + return 0; + }else if (strcmp(s, "argb8888") == 0) { + *format = pixel_format_get_info(DRM_FORMAT_ARGB8888); return 0; } - pinfo = pixel_format_get_info_by_drm_name(s); - if (!pinfo) { + /* GBM formats and DRM formats are identical. */ + *format = pixel_format_get_info_by_drm_name(s); + if (!*format) { weston_log("fatal: unrecognized pixel format: %s\n", s); return -1; } - /* GBM formats and DRM formats are identical. */ - *gbm_format = pinfo->format; - return 0; } 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 +1671,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 +1685,9 @@ 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, NULL, &output->format) == -1) + output->format = NULL; } static void @@ -1360,21 +1695,64 @@ drm_output_set_seat(struct weston_output *base, const char *seat) { struct drm_output *output = to_drm_output(base); - struct drm_backend *b = to_drm_backend(base->compositor); + struct drm_backend *b = output->backend; setup_output_seat_constraint(b, &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 const struct { const char *name; uint32_t token; } content_types[] = { + { "no data", WDRM_CONTENT_TYPE_NO_DATA }, + { "graphics", WDRM_CONTENT_TYPE_GRAPHICS }, + { "photo", WDRM_CONTENT_TYPE_PHOTO }, + { "cinema", WDRM_CONTENT_TYPE_CINEMA }, + { "game", WDRM_CONTENT_TYPE_GAME }, +}; + +static int +drm_output_set_content_type(struct weston_output *base, + const char *content_type) +{ + unsigned int i; + struct drm_output *output = to_drm_output(base); + + if (content_type == NULL) { + output->content_type = WDRM_CONTENT_TYPE_NO_DATA; + return 0; + } + + for (i = 0; i < ARRAY_LENGTH(content_types); i++) + if (strcmp(content_types[i].name, content_type) == 0) { + output->content_type = content_types[i].token; + return 0; + } + + weston_log("Error: unknown content-type for output %s: \"%s\"\n", + base->name, content_type); + output->content_type = WDRM_CONTENT_TYPE_NO_DATA; + return -1; +} + 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; @@ -1385,25 +1763,13 @@ drm_output_init_gamma_size(struct drm_output *output) return 0; } -static uint32_t -drm_connector_get_possible_crtcs_mask(struct drm_connector *connector) +enum writeback_screenshot_state +drm_output_get_writeback_state(struct drm_output *output) { - 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, - conn->encoders[i]); - if (!encoder) - continue; - - possible_crtcs |= encoder->possible_crtcs; - drmModeFreeEncoder(encoder); - } + if (!output->wb_state) + return DRM_OUTPUT_WB_SCREENSHOT_OFF; - return possible_crtcs; + return output->wb_state->state; } /** Pick a CRTC that might be able to drive all attached connectors @@ -1414,7 +1780,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 +1795,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 +1807,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 +1835,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 +1876,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 +1888,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 +1904,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 +1943,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 +1957,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 +1965,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; } @@ -1610,10 +1977,11 @@ drm_backend_create_crtc_list(struct drm_backend *b, drmModeRes *resources) static int drm_output_init_planes(struct drm_output *output) { - struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_backend *b = output->backend; + 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 +1996,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 +2004,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; } @@ -1647,7 +2015,8 @@ drm_output_init_planes(struct drm_output *output) static void drm_output_deinit_planes(struct drm_output *output) { - struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_backend *b = output->backend; + struct drm_device *device = output->device; /* If the compositor is already shutting down, the planes have already * been destroyed. */ @@ -1659,7 +2028,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 +2047,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 +2070,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 +2138,31 @@ 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->format) { + if (output->base.eotf_mode != WESTON_EOTF_MODE_SDR) + output->format = + pixel_format_get_info(DRM_FORMAT_XRGB2101010); + else + output->format = b->format; + } + ret = drm_output_attach_crtc(output); if (ret < 0) return -1; @@ -1802,11 +2177,18 @@ drm_output_enable(struct weston_output *base) if (b->pageflip_timeout) drm_output_pageflip_timer_create(output); - if (b->use_pixman) { + if (b->compositor->renderer->type == WESTON_RENDERER_PIXMAN) { if (drm_output_init_pixman(output, b) < 0) { weston_log("Failed to init output pixman state\n"); goto err_planes; } +#if defined(ENABLE_IMXG2D) + } else if (b->use_g2d) { + if (drm_output_init_g2d(output, b) < 0) { + weston_log("Failed to init output g2d state\n"); + goto err_planes; + } +#endif } else if (drm_output_init_egl(output, b) < 0) { weston_log("Failed to init output gl state\n"); goto err_planes; @@ -1821,6 +2203,12 @@ drm_output_enable(struct weston_output *base) output->base.switch_mode = drm_output_switch_mode; output->base.set_gamma = drm_output_set_gamma; + if (device->atomic_modeset) + weston_output_update_capture_info(base, WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK, + base->current_mode->width, + base->current_mode->height, + pixel_format_get_info(output->format->format)); + weston_log("Output %s (crtc %d) video modes:\n", output->base.name, output->crtc->crtc_id); drm_output_print_modes(output); @@ -1838,26 +2226,42 @@ static void 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_backend *b = output->backend; + 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) + if (b->compositor->renderer->type == WESTON_RENDERER_PIXMAN) drm_output_fini_pixman(output); +#if defined(ENABLE_IMXG2D) + else if (b->use_g2d) + drm_output_fini_g2d(output); +#endif else drm_output_fini_egl(output); 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 +2275,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 +2285,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 +2295,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 +2394,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 +2425,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 +2438,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 +2464,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); @@ -2089,9 +2507,6 @@ drm_head_update_info(struct drm_head *head, drmModeConnector *conn) weston_head_set_content_protection_status(&head->base, drm_head_get_current_protection(head)); - if (head->base.device_changed) - drm_head_log_info(head, "updated"); - return ret; } @@ -2119,7 +2534,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 +2542,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 +2554,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 +2563,10 @@ drm_head_create(struct drm_backend *backend, drmModeConnector *conn, weston_head_init(&head->base, name); free(name); + head->base.backend = &backend->base; + + wl_list_init(&head->disable_head_link); + ret = drm_head_update_info(head, conn); if (ret < 0) goto err_update; @@ -2159,7 +2577,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. */ @@ -2179,8 +2597,12 @@ drm_head_create(struct drm_backend *backend, drmModeConnector *conn, } static void -drm_head_destroy(struct drm_head *head) +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); @@ -2191,6 +2613,26 @@ drm_head_destroy(struct drm_head *head) free(head); } +static struct drm_device * +drm_device_find_by_output(struct weston_compositor *compositor, const char *name) +{ + struct drm_device *device = NULL; + struct weston_head *base = NULL; + struct drm_head *head; + const char *tmp; + + while ((base = weston_compositor_iterate_heads(compositor, base))) { + tmp = weston_head_get_name(base); + if (strcmp(name, tmp) != 0) + continue; + head = to_drm_head(base); + device = head->connector.device; + break; + } + + return device; +} + /** * Create a Weston output structure * @@ -2200,28 +2642,37 @@ drm_head_destroy(struct drm_head *head) * Creating an output is usually followed by drm_output_attach_head() * and drm_output_enable() to make use of it. * - * @param compositor The compositor instance. + * @param backend The backend instance. * @param name Name for the new output. * @returns The output, or NULL on failure. */ static struct weston_output * -drm_output_create(struct weston_compositor *compositor, const char *name) +drm_output_create(struct weston_backend *backend, const char *name) { - struct drm_backend *b = to_drm_backend(compositor); + struct drm_backend *b = container_of(backend, struct drm_backend, base); + struct drm_device *device; struct drm_output *output; + device = drm_device_find_by_output(b->compositor, name); + if (!device) + return NULL; + 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; #ifdef BUILD_DRM_GBM output->gbm_bo_flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING; + output->surface_get_in_fence_fd = weston_load_module("libgbm.so", "gbm_surface_get_in_fence_fd", LIBDIR); #endif - weston_output_init(&output->base, compositor, name); + weston_output_init(&output->base, b->compositor, name); output->base.enable = drm_output_enable; output->base.destroy = drm_output_destroy; @@ -2229,6 +2680,8 @@ drm_output_create(struct weston_compositor *compositor, const char *name) output->base.attach_head = drm_output_attach_head; output->base.detach_head = drm_output_detach_head; + output->backend = b; + output->destroy_pending = false; output->disable_pending = false; @@ -2239,20 +2692,210 @@ drm_output_create(struct weston_compositor *compositor, const char *name) return &output->base; } +static void +pixman_copy_screenshot(uint32_t *dst, uint32_t *src, int dst_stride, + int src_stride, int pixman_format, int width, int height) +{ + pixman_image_t *pixman_dst; + pixman_image_t *pixman_src; + + pixman_src = pixman_image_create_bits(pixman_format, + width, height, + src, src_stride); + pixman_dst = pixman_image_create_bits(pixman_format, + width, height, + dst, dst_stride); + assert(pixman_src); + assert(pixman_dst); + + pixman_image_composite32(PIXMAN_OP_SRC, + pixman_src, /* src */ + NULL, /* mask */ + pixman_dst, /* dst */ + 0, 0, /* src_x, src_y */ + 0, 0, /* mask_x, mask_y */ + 0, 0, /* dst_x, dst_y */ + width, height); /* width, height */ + + pixman_image_unref(pixman_src); + pixman_image_unref(pixman_dst); +} + +static void +drm_writeback_success_screenshot(struct drm_writeback_state *state) +{ + struct drm_output *output = state->output; + struct weston_buffer *buffer = + weston_capture_task_get_buffer(state->ct); + int width, height; + int dst_stride, src_stride; + uint32_t *src, *dst; + + src = state->fb->map; + src_stride = state->fb->strides[0]; + + dst = wl_shm_buffer_get_data(buffer->shm_buffer); + dst_stride = wl_shm_buffer_get_stride(buffer->shm_buffer); + + width = state->fb->width; + height = state->fb->height; + + wl_shm_buffer_begin_access(buffer->shm_buffer); + pixman_copy_screenshot(dst, src, dst_stride, src_stride, + buffer->pixel_format->pixman_format, + width, height); + wl_shm_buffer_end_access(buffer->shm_buffer); + + weston_capture_task_retire_complete(state->ct); + drm_writeback_state_free(state); + output->wb_state = NULL; +} + +void +drm_writeback_fail_screenshot(struct drm_writeback_state *state, + const char *err_msg) +{ + struct drm_output *output = state->output; + + weston_capture_task_retire_failed(state->ct, err_msg); + drm_writeback_state_free(state); + output->wb_state = NULL; +} + +static int +drm_writeback_save_callback(int fd, uint32_t mask, void *data) +{ + struct drm_writeback_state *state = data; + + wl_event_source_remove(state->wb_source); + close(fd); + + drm_writeback_success_screenshot(state); + + return 0; +} + +static bool +drm_writeback_has_finished(struct drm_writeback_state *state) +{ + struct pollfd pollfd; + int ret; + + pollfd.fd = state->out_fence_fd; + pollfd.events = POLLIN; + + while ((ret = poll(&pollfd, 1, 0)) == -1 && errno == EINTR) + continue; + + if (ret < 0) { + drm_writeback_fail_screenshot(state, "drm: polling wb fence failed"); + return true; + } else if (ret > 0) { + /* fence already signaled, simply save the screenshot */ + drm_writeback_success_screenshot(state); + return true; + } + + /* poll() returned 0, what means that out fence was not signalled yet */ + return false; +} + +bool +drm_writeback_should_wait_completion(struct drm_writeback_state *state) +{ + struct weston_compositor *ec = state->output->base.compositor; + struct wl_event_loop *event_loop; + + if (state->state == DRM_OUTPUT_WB_SCREENSHOT_WAITING_SIGNAL) + return true; + + if (state->state == DRM_OUTPUT_WB_SCREENSHOT_CHECK_FENCE) { + if (drm_writeback_has_finished(state)) + return false; + + /* The writeback has not finished yet. So add callback that gets + * called when the sync fd of the writeback job gets signalled. + * We need to wait for that to resume the repaint loop. */ + event_loop = wl_display_get_event_loop(ec->wl_display); + state->wb_source = + wl_event_loop_add_fd(event_loop, state->out_fence_fd, + WL_EVENT_READABLE, + drm_writeback_save_callback, state); + if (!state->wb_source) { + drm_writeback_fail_screenshot(state, "drm: out of memory"); + return false; + } + + state->state = DRM_OUTPUT_WB_SCREENSHOT_WAITING_SIGNAL; + + return true; + } + + return false; +} + +void +drm_writeback_reference_planes(struct drm_writeback_state *state, + struct wl_list *plane_state_list) +{ + struct drm_plane_state *plane_state; + struct drm_fb **fb; + + wl_list_for_each(plane_state, plane_state_list, link) { + if (!plane_state->fb) + continue; + fb = wl_array_add(&state->referenced_fbs, sizeof(*fb)); + *fb = drm_fb_ref(plane_state->fb); + } +} + +static int +drm_writeback_populate_formats(struct drm_writeback *wb) +{ + struct drm_property_info *info = wb->connector.props; + drmModeObjectProperties *props = wb->connector.props_drm; + uint64_t blob_id; + drmModePropertyBlobPtr blob; + uint32_t *blob_formats; + unsigned int i; + + blob_id = drm_property_get_value(&info[WDRM_CONNECTOR_WRITEBACK_PIXEL_FORMATS], + props, 0); + if (blob_id == 0) + return -1; + + blob = drmModeGetPropertyBlob(wb->device->drm.fd, blob_id); + if (!blob) + return -1; + + blob_formats = blob->data; + + for (i = 0; i < blob->length / sizeof(uint32_t); i++) + if (!weston_drm_format_array_add_format(&wb->formats, + blob_formats[i])) + goto err; + + return 0; + +err: + drmModeFreePropertyBlob(blob); + return -1; +} + /** * Create a Weston writeback for a writeback connector * * 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,17 +2903,24 @@ 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); + weston_drm_format_array_init(&writeback->formats); + ret = drm_writeback_populate_formats(writeback); + if (ret < 0) + goto err_formats; + + wl_list_insert(&device->writeback_connector_list, &writeback->link); return 0; +err_formats: + weston_drm_format_array_fini(&writeback->formats); err: drm_connector_fini(&writeback->connector); free(writeback); @@ -2281,6 +2931,7 @@ static void drm_writeback_destroy(struct drm_writeback *writeback) { drm_connector_fini(&writeback->connector); + weston_drm_format_array_fini(&writeback->formats); wl_list_remove(&writeback->link); free(writeback); @@ -2291,24 +2942,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 +2973,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 +3018,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 +3030,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 +3040,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; @@ -2397,12 +3051,15 @@ drm_backend_update_connectors(struct drm_backend *b, struct udev_device *drm_dev * one of the searches must fail. */ assert(head == NULL || writeback == NULL); - if (head) + if (head) { ret = drm_head_update_info(head, conn); - else if (writeback) + if (head->base.device_changed) + drm_head_log_info(head, "updated"); + } else if (writeback) { ret = drm_writeback_update_info(writeback, conn); - else - ret = drm_backend_add_connector(b, conn, drm_device); + } else { + ret = drm_backend_add_connector(b->drm, conn, drm_device); + } if (ret < 0) drmModeFreeConnector(conn); @@ -2413,20 +3070,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 +3149,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 +3167,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 +3175,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 @@ -2534,14 +3196,24 @@ udev_drm_event(int fd, uint32_t mask, void *data) struct drm_backend *b = data; struct udev_device *event; uint32_t conn_id, prop_id; + struct drm_device *device; 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); + } + + wl_list_for_each(device, &b->kms_list, link) { + if (udev_event_is_hotplug(device, 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(device, event); + } } udev_device_unref(event); @@ -2549,10 +3221,12 @@ udev_drm_event(int fd, uint32_t mask, void *data) return 1; } -static void -drm_destroy(struct weston_compositor *ec) +void +drm_destroy(struct weston_backend *backend) { - struct drm_backend *b = to_drm_backend(ec); + struct drm_backend *b = container_of(backend, struct drm_backend, base); + struct weston_compositor *ec = b->compositor; + 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 +3238,24 @@ 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); + } + + weston_drm_format_array_fini(&b->supported_formats); 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 +3266,21 @@ 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); + if(b->enable_overlay_view){ + /* remove enable-overlay-view */ + char *dir, *path; + dir = getenv("XDG_RUNTIME_DIR"); + path = malloc(strlen(dir) + 40); + strcpy(path, dir); + strcat(path, "/enable-overlay-view"); + remove(path); + free(path); + } + free(device->drm.filename); + free(device); free(b); } @@ -2599,16 +3288,16 @@ static void 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_backend *b = + container_of(listener, struct drm_backend, session_listener); + 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 +3313,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; } } @@ -2652,17 +3326,19 @@ session_notify(struct wl_listener *listener, void *data) * If the device being added/removed is the KMS device, we activate/deactivate * the compositor session. * - * @param compositor The compositor instance. - * @param device The device being added/removed. + * @param backend The DRM backend instance. + * @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) +drm_device_changed(struct weston_backend *backend, + dev_t devnum, bool added) { - struct drm_backend *b = to_drm_backend(compositor); + struct drm_backend *b = container_of(backend, struct drm_backend, base); + struct weston_compositor *compositor = b->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; @@ -2670,23 +3346,54 @@ drm_device_changed(struct weston_compositor *compositor, wl_signal_emit(&compositor->session_signal, compositor); } +static const struct weston_drm_format_array * +drm_get_supported_formats(struct weston_compositor *ec) +{ + struct drm_backend *b = to_drm_backend(ec); + + return &b->supported_formats; +} + +/* for drm backend, currently we only need expose overlay plane formats, + * because primary will been used by renderer */ +static int +populate_supported_formats(struct drm_backend *b) +{ + int ret = 0; + struct drm_plane *plane; + + wl_list_for_each(plane, &b->drm->plane_list, link) { + if (plane->type != WDRM_PLANE_TYPE_OVERLAY) + continue; + + ret = weston_drm_format_array_join(&b->supported_formats, + &plane->formats); + if (ret < 0) + break; + } + + return ret; +} + /** * Determines whether or not a device is capable of modesetting. If successful, * 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 +3414,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 +3447,11 @@ drm_device_is_kms(struct drm_backend *b, struct udev_device *device) 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 +3463,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 +3486,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 +3503,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 +3511,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 +3552,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 +3585,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 +3616,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,14 +3635,19 @@ 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) { + if (!output->format || + output->format->format != DRM_FORMAT_XRGB8888) { weston_log("failed to start vaapi recorder: " "output format not supported\n"); return; @@ -2969,18 +3685,244 @@ recorder_binding(struct weston_keyboard *keyboard, const struct timespec *time, } #endif +static struct drm_device * +drm_device_create(struct drm_backend *backend, const char *name) +{ + struct weston_compositor *compositor = backend->compositor; + struct udev_device *udev_device; + struct drm_device *device; + struct wl_event_loop *loop; + drmModeRes *res; + + device = zalloc(sizeof *device); + if (device == NULL) + return NULL; + device->state_invalid = true; + device->drm.fd = -1; + device->backend = backend; + device->gem_handle_refcnt = hash_table_create(); + + udev_device = open_specific_drm_device(backend, device, name); + if (!udev_device) { + free(device); + return NULL; + } + + if (init_kms_caps(device) < 0) { + weston_log("failed to initialize kms\n"); + goto err; + } + + res = drmModeGetResources(device->drm.fd); + if (!res) { + weston_log("Failed to get drmModeRes\n"); + goto err; + } + + wl_list_init(&device->crtc_list); + if (drm_backend_create_crtc_list(device, res) == -1) { + weston_log("Failed to create CRTC list for DRM-backend\n"); + goto err; + } + + loop = wl_display_get_event_loop(compositor->wl_display); + wl_event_loop_add_fd(loop, device->drm.fd, + WL_EVENT_READABLE, on_drm_input, device); + + wl_list_init(&device->plane_list); + create_sprites(device); + + wl_list_init(&device->writeback_connector_list); + if (drm_backend_discover_connectors(device, udev_device, res) < 0) { + weston_log("Failed to create heads for %s\n", device->drm.filename); + goto err; + } + + /* 'compute' faked zpos values in case HW doesn't expose any */ + drm_backend_create_faked_zpos(device); + + return device; +err: + return NULL; +} + +static void +open_additional_devices(struct drm_backend *backend, const char *cards) +{ + struct drm_device *device; + char *tokenize = strdup(cards); + char *card = strtok(tokenize, ","); + + while (card) { + device = drm_device_create(backend, card); + if (!device) { + weston_log("unable to use card %s\n", card); + goto next; + } + + weston_log("adding secondary device %s\n", + device->drm.filename); + wl_list_insert(&backend->kms_list, &device->link); + +next: + card = strtok(NULL, ","); + } + + free(tokenize); +} 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, + drm_output_set_content_type, }; +/** + * Test if drm driver can import dmabuf + * + * called by compositor when a dmabuf comes to test if this buffer + * can used by drm driver directly + */ +static bool +drm_import_dmabuf(struct weston_compositor *compositor, + struct linux_dmabuf_buffer *dmabuf) +{ + struct drm_backend *b = to_drm_backend(compositor); + struct drm_plane *p; + uint64_t has_prime; + int ret; + + ret = drmGetCap (b->drm->drm.fd, DRM_CAP_PRIME, &has_prime); + if (ret || !(bool) (has_prime & DRM_PRIME_CAP_IMPORT)) { + weston_log("drm backend not support import DMABUF\n"); + return false; + } + + wl_list_for_each(p, &b->drm->plane_list, link) { + if (p->type != WDRM_PLANE_TYPE_OVERLAY) + continue; + struct weston_drm_format * format = +#if USE_DRM_FORMAT_NV15 + weston_drm_format_array_find_format (&p->formats, DRM_FORMAT_NV15); +#else + weston_drm_format_array_find_format (&p->formats, DRM_FORMAT_NV12_10LE40); +#endif + +#if USE_DRM_FORMAT_NV15 + if (format && dmabuf->attributes.format == DRM_FORMAT_NV15) +#else + if (format && dmabuf->attributes.format == DRM_FORMAT_NV12_10LE40) +#endif + return true; + } + + return false; +} + +static void +hdr10_metadata_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +hdr10_metadata_set_metadata(struct wl_client *client, + struct wl_resource *resource, + uint32_t eotf, + uint32_t type, + uint32_t display_primaries_red, + uint32_t display_primaries_green, + uint32_t display_primaries_blue, + uint32_t white_point, + uint32_t mastering_display_luminance, + uint32_t max_cll, + uint32_t max_fall) +{ + struct weston_compositor *compositor = wl_resource_get_user_data(resource); + struct drm_backend *b = to_drm_backend(compositor); + struct hdr_output_metadata hdr_metadata; + + if (eotf == 0) { + b->drm->clean_hdr_blob = true; + return; + } + + hdr_metadata.metadata_type = 0; + hdr_metadata.hdmi_metadata_type1.eotf = eotf & 0xff; + hdr_metadata.hdmi_metadata_type1.metadata_type = type & 0xff; + hdr_metadata.hdmi_metadata_type1.display_primaries[0].x = (display_primaries_red >> 16) & 0xffff; + hdr_metadata.hdmi_metadata_type1.display_primaries[0].y = display_primaries_red & 0xffff; + hdr_metadata.hdmi_metadata_type1.display_primaries[1].x = (display_primaries_green >> 16) & 0xffff; + hdr_metadata.hdmi_metadata_type1.display_primaries[1].y = display_primaries_green & 0xffff; + hdr_metadata.hdmi_metadata_type1.display_primaries[2].x = (display_primaries_blue >> 16) & 0xffff; + hdr_metadata.hdmi_metadata_type1.display_primaries[2].y = display_primaries_blue & 0xffff; + hdr_metadata.hdmi_metadata_type1.white_point.x = (white_point >> 16) & 0xffff; + hdr_metadata.hdmi_metadata_type1.white_point.y = white_point & 0xffff; + hdr_metadata.hdmi_metadata_type1.max_display_mastering_luminance = + (mastering_display_luminance >> 16) & 0xffff; + hdr_metadata.hdmi_metadata_type1.min_display_mastering_luminance = + mastering_display_luminance & 0xffff; + hdr_metadata.hdmi_metadata_type1.max_cll = max_cll & 0xffff; + hdr_metadata.hdmi_metadata_type1.max_fall = max_fall & 0xffff; + + drmModeCreatePropertyBlob(b->drm->drm.fd, &hdr_metadata, sizeof(hdr_metadata), &b->drm->hdr_blob_id); +} + +static const struct zwp_hdr10_metadata_v1_interface hdr10_metadata_interface = { + hdr10_metadata_destroy, + hdr10_metadata_set_metadata, +}; + +static void +bind_hdr10_metadata(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct wl_resource *resource; + struct weston_compositor *compositor = data; + + resource = wl_resource_create(client, &zwp_hdr10_metadata_v1_interface, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &hdr10_metadata_interface, + compositor, NULL); +} + +static bool +drm_backend_is_hdr_supported(struct weston_compositor *compositor) +{ + struct drm_output *output; + struct drm_head *head; + + wl_list_for_each(output, &compositor->output_list, base.link) { + wl_list_for_each(head, &output->base.head_list, base.output_link) { + if (head->connector.props[WDRM_CONNECTOR_HDR_OUTPUT_METADATA].prop_id > 0) + return true; + } + } + + wl_list_for_each(output, &compositor->pending_output_list, base.link) { + wl_list_for_each(head, &output->base.head_list, base.output_link) { + if (head->connector.props[WDRM_CONNECTOR_HDR_OUTPUT_METADATA].prop_id > 0) + return true; + } + } + + return true; +} + static struct drm_backend * 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,11 +3944,24 @@ 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->clean_hdr_blob = false; + device->drm.fd = -1; + device->backend = b; + + b->drm = device; + wl_list_init(&b->kms_list); b->compositor = compositor; - b->use_pixman = config->use_pixman; +#if defined(ENABLE_IMXG2D) + b->use_g2d = config->use_g2d; +#endif + b->enable_overlay_view = config->enable_overlay_view; + b->shell_width = config->shell_width; + b->shell_height = config->shell_height; b->pageflip_timeout = config->pageflip_timeout; b->use_pixman_shadow = config->use_pixman_shadow; @@ -3016,16 +3971,16 @@ drm_backend_create(struct weston_compositor *compositor, compositor->backend = &b->base; - if (parse_gbm_format(config->gbm_format, DRM_FORMAT_XRGB8888, &b->gbm_format) < 0) + if (parse_gbm_format(config->gbm_format, + pixel_format_get_info(DRM_FORMAT_XRGB8888), + &b->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 +3994,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,21 +4003,50 @@ 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; } - if (b->use_pixman) { + if (config->additional_devices) + open_additional_devices(b, config->additional_devices); + + if (config->renderer == WESTON_RENDERER_AUTO) { +#ifdef BUILD_DRM_GBM + config->renderer = WESTON_RENDERER_GL; +#else + config->renderer = WESTON_RENDERER_PIXMAN; +#endif +#if defined(ENABLE_IMXG2D) + if (b->use_g2d) + config->renderer = WESTON_RENDERER_G2D; +#endif + } + + switch (config->renderer) { + case WESTON_RENDERER_PIXMAN: if (init_pixman(b) < 0) { weston_log("failed to initialize pixman renderer\n"); goto err_udev_dev; } - } else { + break; + case WESTON_RENDERER_GL: if (init_egl(b) < 0) { weston_log("failed to initialize egl\n"); goto err_udev_dev; } + break; +#if defined(ENABLE_IMXG2D) + case WESTON_RENDERER_G2D: + if (init_g2d(b) < 0) { + weston_log("failed to initialize g2d renderer\n"); + goto err_udev_dev; + } + break; +#endif + default: + weston_log("unsupported renderer for DRM backend\n"); + goto err_udev_dev; } b->base.destroy = drm_destroy; @@ -3071,51 +4056,57 @@ drm_backend_create(struct weston_compositor *compositor, b->base.create_output = drm_output_create; b->base.device_changed = drm_device_changed; b->base.can_scanout_dmabuf = drm_can_scanout_dmabuf; + b->base.get_supported_formats = drm_get_supported_formats; + b->base.import_dmabuf = drm_import_dmabuf; + + weston_drm_format_array_init(&b->supported_formats); 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); + ret = populate_supported_formats(b); + if (ret < 0) + goto err_sprite; if (udev_input_init(&b->input, compositor, b->udev, seat_id, config->configure_device) < 0) { weston_log("failed to create input devices\n"); - 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; } drmModeFreeResources(res); /* 'compute' faked zpos values in case HW doesn't expose any */ - drm_backend_create_faked_zpos(b); + drm_backend_create_faked_zpos(b->drm); /* 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 +4135,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 +4146,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,11 +4167,20 @@ 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"); + if (drm_backend_is_hdr_supported(compositor)) { + if (!wl_global_create(compositor->wl_display, &zwp_hdr10_metadata_v1_interface, 1, + compositor, bind_hdr10_metadata)) { + weston_log("Error: initializing hdr10 support failed\n"); + } + } else { + weston_log("info: HDR is not support\n"); + } + ret = weston_plugin_api_register(compositor, WESTON_DRM_OUTPUT_API_NAME, &api, sizeof(api)); @@ -3197,6 +4195,19 @@ drm_backend_create(struct weston_compositor *compositor, goto err_udev_monitor; } + if(b->enable_overlay_view){ + /* create enable-overlay-view*/ + char *dir, *path; + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + + dir = getenv("XDG_RUNTIME_DIR"); + path = malloc(strlen(dir) + 40); + strcpy(path, dir); + strcat(path, "/enable-overlay-view"); + close(open(path, O_CREAT | O_RDWR, mode)); + free(path); + } + return b; err_udev_monitor: @@ -3207,7 +4218,8 @@ drm_backend_create(struct weston_compositor *compositor, err_udev_input: udev_input_destroy(&b->input); err_sprite: - destroy_sprites(b); + weston_drm_format_array_fini(&b->supported_formats); + destroy_sprites(b->drm); err_create_crtc_list: drmModeFreeResources(res); err_udev_dev: @@ -3229,7 +4241,18 @@ drm_backend_create(struct weston_compositor *compositor, static void config_init_to_defaults(struct weston_drm_backend_config *config) { + config->renderer = WESTON_RENDERER_AUTO; config->use_pixman_shadow = true; +#if defined(ENABLE_IMXG2D) +#if !defined(BUILD_DRM_GBM) + config->use_g2d = true; +#else + config->use_g2d = false; +#endif +#endif + config->shell_width = 0; + config->shell_height = 0; + config->enable_overlay_view = 0; } WL_EXPORT int diff --git a/libweston/backend-drm/fb.c b/libweston/backend-drm/fb.c index ba0c177e9..c161500bc 100644 --- a/libweston/backend-drm/fb.c +++ b/libweston/backend-drm/fb.c @@ -38,6 +38,7 @@ #include #include #include +#include "shared/hash.h" #include "shared/helpers.h" #include "shared/weston-drm-fourcc.h" #include "drm-internal.h" @@ -68,21 +69,168 @@ drm_fb_destroy_dumb(struct drm_fb *fb) drm_fb_destroy(fb); } +#ifdef BUILD_DRM_GBM +static int gem_handle_get(struct drm_device *device, int handle) +{ + unsigned int *ref_count; + + ref_count = hash_table_lookup(device->gem_handle_refcnt, handle); + if (!ref_count) { + ref_count = zalloc(sizeof(*ref_count)); + hash_table_insert(device->gem_handle_refcnt, handle, ref_count); + } + (*ref_count)++; + + return handle; +} + +static void gem_handle_put(struct drm_device *device, int handle) +{ + unsigned int *ref_count; + + if (handle == 0) + return; + + ref_count = hash_table_lookup(device->gem_handle_refcnt, handle); + if (!ref_count) { + weston_log("failed to find GEM handle %d for device %s\n", + handle, device->drm.filename); + return; + } + (*ref_count)--; + + if (*ref_count == 0) { + hash_table_remove(device->gem_handle_refcnt, handle); + free(ref_count); + drmCloseBufferHandle(device->drm.fd, handle); + } +} + +static int +drm_fb_import_plane(struct drm_device *device, struct drm_fb *fb, int plane) +{ + int bo_fd; + uint32_t handle; + int ret; + + bo_fd = gbm_bo_get_fd_for_plane(fb->bo, plane); + if (bo_fd < 0) + return bo_fd; + + /* + * drmPrimeFDToHandle is dangerous, because the GEM handles are + * not reference counted by the kernel and user space needs a + * single reference counting implementation to avoid double + * closing of GEM handles. + * + * It is not desirable to use a GBM device here, because this + * requires a GBM device implementation, which might not be + * available for simple or custom DRM devices that only support + * scanout and no rendering. + * + * We are only importing the buffers from the render device to + * the scanout device if the devices are distinct, since + * otherwise no import is necessary. Therefore, we are the only + * instance using the handles and we can implement reference + * counting for the handles per device. See gem_handle_get and + * gem_handle_put for the implementation. + */ + ret = drmPrimeFDToHandle(fb->fd, bo_fd, &handle); + if (ret) + goto out; + + fb->handles[plane] = gem_handle_get(device, handle); + +out: + close(bo_fd); + return ret; +} +#endif + +/* + * If the fb is using a GBM surface, there is a possibility that the GBM + * surface has been created on a different device than the device which + * should be used for the fb. We have to import the fd of the GBM bo + * into the scanout device. + */ +static int +drm_fb_maybe_import(struct drm_device *device, struct drm_fb *fb) +{ +#ifndef BUILD_DRM_GBM + /* + * Without GBM support, the fb is always allocated on the scanout device + * and import is never necessary. + */ + return 0; +#else + struct gbm_device *gbm_device; + int ret = 0; + int plane; + + /* No import possible, if there is no gbm bo */ + if (!fb->bo) + return 0; + + /* No import necessary, if the gbm bo and the fb use the same device */ + gbm_device = gbm_bo_get_device(fb->bo); + if (gbm_device_get_fd(gbm_device) == fb->fd) + return 0; + + if (fb->fd != device->drm.fd) { + weston_log("fb was not allocated for scanout device %s\n", + device->drm.filename); + return -1; + } + + for (plane = 0; plane < gbm_bo_get_plane_count(fb->bo); plane++) { + ret = drm_fb_import_plane(device, fb, plane); + if (ret) + goto err; + } + + fb->scanout_device = device; + + return 0; +err: + for (; plane >= 0; plane--) { + gem_handle_put(device, fb->handles[plane]); + fb->handles[plane] = 0; + } + + return ret; +#endif +} + 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] = { }; + int width, height; size_t i; + ret = drm_fb_maybe_import(device, fb); + if (ret) + return ret; + /* 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++) mods[i] = fb->modifier; - ret = drmModeAddFB2WithModifiers(fb->fd, fb->width, fb->height, + if (fb->modifier == DRM_FORMAT_MOD_AMPHION_TILED) { + width = ALIGNTO (fb->width, 8); + height = ALIGNTO (fb->height, 256); + }else if(fb->modifier ==DRM_FORMAT_MOD_VIVANTE_SUPER_TILED){ + width = ALIGNTO (fb->width, 64); + height = ALIGNTO (fb->height, 64); + } else { + width = fb->width; + height = fb->height; + } + ret = drmModeAddFB2WithModifiers(fb->fd, width, height, fb->format->format, fb->handles, fb->strides, fb->offsets, mods, &fb->fb_id, @@ -98,7 +246,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 +254,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 +282,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 +293,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 +305,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 +319,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; @@ -209,16 +357,62 @@ drm_fb_destroy_gbm(struct gbm_bo *bo, void *data) static void drm_fb_destroy_dmabuf(struct drm_fb *fb) { + int i; + /* We deliberately do not close the GEM handles here; GBM manages * their lifetime through the BO. */ if (fb->bo) gbm_bo_destroy(fb->bo); + + /* + * If we imported the dmabuf into a scanout device, we are responsible + * for closing the GEM handle. + */ + for (i = 0; i < 4; i++) + if (fb->scanout_device && fb->handles[i] != 0) + gem_handle_put(fb->scanout_device, fb->handles[i]); + drm_fb_destroy(fb); } +#ifdef HAVE_GBM_MODIFIERS +int +drm_fb_get_gbm_alignment(struct drm_fb *fb) +{ + int gbm_aligned = 64; + + if (fb){ + switch(fb->modifier) { + case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED_FC: + case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED: + gbm_aligned = 64; + break; + default: + gbm_aligned = 1; + break; + } + } + return gbm_aligned; +} +#endif +static void +drm_close_gem_handle(struct linux_dmabuf_buffer *dmabuf) +{ + struct drm_backend *b = to_drm_backend(dmabuf->compositor); + int i; + + if (dmabuf->gem_handles[0] != 0) { + for (i = 0; i < dmabuf->attributes.n_planes; i++) { + struct drm_gem_close arg = { dmabuf->gem_handles[i], }; + drmIoctl (b->drm->drm.fd, DRM_IOCTL_GEM_CLOSE, &arg); + dmabuf->gem_handles[i] = 0; + } + } +} + 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,8 +421,10 @@ 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; + uint32_t gem_handle[MAX_DMABUF_PLANES] = {0}; struct gbm_import_fd_modifier_data import_mod = { .width = dmabuf->attributes.width, .height = dmabuf->attributes.height, @@ -269,25 +465,12 @@ drm_fb_get_from_dmabuf(struct linux_dmabuf_buffer *dmabuf, fb->refcnt = 1; fb->type = BUFFER_DMABUF; - - ARRAY_COPY(import_mod.fds, dmabuf->attributes.fd); - ARRAY_COPY(import_mod.strides, dmabuf->attributes.stride); - ARRAY_COPY(import_mod.offsets, dmabuf->attributes.offset); - - fb->bo = gbm_bo_import(backend->gbm, GBM_BO_IMPORT_FD_MODIFIER, - &import_mod, GBM_BO_USE_SCANOUT); - if (!fb->bo) { - if (try_view_on_plane_failure_reasons) - *try_view_on_plane_failure_reasons |= - FAILURE_REASONS_GBM_BO_IMPORT_FAILED; - goto err_free; - } - fb->width = dmabuf->attributes.width; fb->height = dmabuf->attributes.height; fb->modifier = dmabuf->attributes.modifier[0]; + fb->dtrc_meta = dmabuf->attributes.dtrc_meta; 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,25 +485,58 @@ 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; } + fb->num_planes = dmabuf->attributes.n_planes; + if (dmabuf->gem_handles[0] == 0) { + for (i = 0; i < dmabuf->attributes.n_planes; i++) { + int ret; + ret = drmPrimeFDToHandle (fb->fd, dmabuf->attributes.fd[i], &gem_handle[i]); + if (ret) { + weston_log ("got gem_handle %x\n", gem_handle[i]); + goto err_free; + } + fb->handles[i] = dmabuf->gem_handles[i] = gem_handle[i]; + } + linux_dmabuf_buffer_gem_handle_close_cb (dmabuf, drm_close_gem_handle); + } else { + for (i = 0; i < dmabuf->attributes.n_planes; i++) + fb->handles[i] = dmabuf->gem_handles[i]; + } + + if (fb->handles[0] != 0) + goto add_fb; + + ARRAY_COPY(import_mod.fds, dmabuf->attributes.fd); + ARRAY_COPY(import_mod.strides, dmabuf->attributes.stride); + ARRAY_COPY(import_mod.offsets, dmabuf->attributes.offset); + + fb->bo = gbm_bo_import(backend->gbm, GBM_BO_IMPORT_FD_MODIFIER, + &import_mod, GBM_BO_USE_SCANOUT); + if (!fb->bo) + goto err_free; + fb->num_planes = dmabuf->attributes.n_planes; for (i = 0; i < dmabuf->attributes.n_planes; i++) { 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) { +add_fb: + 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 +552,7 @@ drm_fb_get_from_dmabuf(struct linux_dmabuf_buffer *dmabuf, } 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); @@ -346,6 +562,22 @@ drm_fb_get_from_bo(struct gbm_bo *bo, struct drm_backend *backend, if (fb) { assert(fb->type == type); + + const struct pixel_format_info *target_format; + if(is_opaque) + target_format = pixel_format_get_opaque_substitute(fb->format); + else + target_format = pixel_format_get_info(gbm_bo_get_format(bo)); + + if (target_format->format != fb->format->format) { + fb->format = target_format; + if (drm_fb_addfb(device, fb) != 0) { + weston_log("failed to create kms fb: %s\n", + strerror(errno)); + goto err_free; + } + } + return drm_fb_ref(fb); } @@ -356,7 +588,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 +621,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)); @@ -448,27 +680,30 @@ drm_fb_unref(struct drm_fb *fb) #ifdef BUILD_DRM_GBM bool -drm_can_scanout_dmabuf(struct weston_compositor *ec, +drm_can_scanout_dmabuf(struct weston_backend *backend, struct linux_dmabuf_buffer *dmabuf) { + struct drm_backend *b = container_of(backend, struct drm_backend, base); 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 */ @@ -505,88 +740,117 @@ drm_fb_compatible_with_plane(struct drm_fb *fb, struct drm_plane *plane) static void 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); + struct drm_fb_private *private = + container_of(listener, struct drm_fb_private, buffer_destroy_listener); + struct drm_buffer_fb *buf_fb; + struct drm_buffer_fb *tmp; - if (buf_fb->fb) { - assert(buf_fb->fb->type == BUFFER_CLIENT || - buf_fb->fb->type == BUFFER_DMABUF); - drm_fb_unref(buf_fb->fb); + wl_list_remove(&private->buffer_destroy_listener.link); + + wl_list_for_each_safe(buf_fb, tmp, &private->buffer_fb_list, link) { + if (buf_fb->fb) { + assert(buf_fb->fb->type == BUFFER_CLIENT || + buf_fb->fb->type == BUFFER_DMABUF); + drm_fb_unref(buf_fb->fb); + } + wl_list_remove(&buf_fb->link); + free(buf_fb); } - free(buf_fb); + free(private); } struct drm_fb * -drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev, - uint32_t *try_view_on_plane_failure_reasons) +drm_fb_get_from_paint_node(struct drm_output_state *state, + struct weston_paint_node *pnode) { struct drm_output *output = state->output; - struct drm_backend *b = to_drm_backend(output->base.compositor); + struct drm_backend *b = output->backend; + struct drm_device *device = output->device; + struct weston_view *ev = pnode->view; struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; + struct drm_fb_private *private; 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) - return NULL; - - if (!drm_view_transform_supported(ev, &output->base)) - 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) { + pnode->try_view_on_plane_failure_reasons |= + FAILURE_REASONS_INADEQUATE_CONTENT_PROTECTION; return NULL; + } - if (!buffer) + if (!buffer) { + pnode->try_view_on_plane_failure_reasons |= FAILURE_REASONS_NO_BUFFER; return NULL; + } - if (buffer->backend_private) { - buf_fb = buffer->backend_private; - *try_view_on_plane_failure_reasons |= buf_fb->failure_reasons; - return buf_fb->fb ? drm_fb_ref(buf_fb->fb) : NULL; + if (!buffer->backend_private) { + private = zalloc(sizeof(*private)); + buffer->backend_private = private; + wl_list_init(&private->buffer_fb_list); + private->buffer_destroy_listener.notify = drm_fb_handle_buffer_destroy; + wl_signal_add(&buffer->destroy_signal, &private->buffer_destroy_listener); + } else { + private = buffer->backend_private; } - buf_fb = zalloc(sizeof(*buf_fb)); - buffer->backend_private = buf_fb; - buf_fb->buffer_destroy_listener.notify = drm_fb_handle_buffer_destroy; - wl_signal_add(&buffer->destroy_signal, &buf_fb->buffer_destroy_listener); + wl_list_for_each(buf_fb, &private->buffer_fb_list, link) { + if (buf_fb->device == device) { + pnode->try_view_on_plane_failure_reasons |= buf_fb->failure_reasons; + return buf_fb->fb ? drm_fb_ref(buf_fb->fb) : NULL; + } + } - if (wl_shm_buffer_get(buffer->resource)) - goto unsuitable; + buf_fb = zalloc(sizeof(*buf_fb)); + buf_fb->device = device; + wl_list_insert(&private->buffer_fb_list, &buf_fb->link); /* GBM is used for dmabuf import as well as from client wl_buffer. */ - if (!b->gbm) + if (!b->gbm) { + pnode->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 { - struct gbm_bo *bo; + } else if (buffer->type == WESTON_BUFFER_RENDERER_OPAQUE) { + struct gbm_bo *bo = NULL; - bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_WL_BUFFER, - buffer->resource, GBM_BO_USE_SCANOUT); + if(b->enable_overlay_view) + { + bo = gbm_bo_import(b->gbm, GBM_BO_IMPORT_WL_BUFFER, + buffer->resource, GBM_BO_USE_SCANOUT); + } 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) { + pnode->try_view_on_plane_failure_reasons |= + (1 << FAILURE_REASONS_ADD_FB_FAILED); gbm_bo_destroy(bo); goto unsuitable; } + } else { + pnode->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); + fb->plane_mask |= 1 << (plane->plane_idx); } if (fb->plane_mask == 0) { drm_fb_unref(fb); @@ -603,7 +867,7 @@ drm_fb_get_from_view(struct drm_output_state *state, struct weston_view *ev, return fb; unsuitable: - *try_view_on_plane_failure_reasons |= buf_fb->failure_reasons; + pnode->try_view_on_plane_failure_reasons |= buf_fb->failure_reasons; return NULL; } #endif diff --git a/libweston/backend-drm/kms-color.c b/libweston/backend-drm/kms-color.c new file mode 100644 index 000000000..610b24426 --- /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 780d00709..6621c678e 100644 --- a/libweston/backend-drm/kms.c +++ b/libweston/backend-drm/kms.c @@ -54,6 +54,27 @@ struct drm_property_enum_info plane_type_enums[] = { }, }; +struct drm_property_enum_info plane_rotation_enums[] = { + [WDRM_PLANE_ROTATION_0] = { + .name = "rotate-0", + }, + [WDRM_PLANE_ROTATION_90] = { + .name = "rotate-90", + }, + [WDRM_PLANE_ROTATION_180] = { + .name = "rotate-180", + }, + [WDRM_PLANE_ROTATION_270] = { + .name = "rotate-270", + }, + [WDRM_PLANE_ROTATION_REFLECT_X] = { + .name = "reflect-x", + }, + [WDRM_PLANE_ROTATION_REFLECT_Y] = { + .name = "reflect-y", + }, +}; + const struct drm_property_info plane_props[] = { [WDRM_PLANE_TYPE] = { .name = "type", @@ -74,6 +95,13 @@ const struct drm_property_info plane_props[] = { [WDRM_PLANE_IN_FENCE_FD] = { .name = "IN_FENCE_FD" }, [WDRM_PLANE_FB_DAMAGE_CLIPS] = { .name = "FB_DAMAGE_CLIPS" }, [WDRM_PLANE_ZPOS] = { .name = "zpos" }, + [WDRM_PLANE_ROTATION] = { + .name = "rotation", + .enum_values = plane_rotation_enums, + .num_enum_values = WDRM_PLANE_ROTATION__COUNT, + }, + [WDRM_PLANE_ALPHA] = { .name = "alpha" }, + [WDRM_PLANE_DTRC_META] = { .name = "dtrc_table_ofs" }, }; struct drm_property_enum_info dpms_state_enums[] = { @@ -119,6 +147,14 @@ struct drm_property_enum_info panel_orientation_enums[] = { [WDRM_PANEL_ORIENTATION_RIGHT_SIDE_UP] = { .name = "Right Side Up", }, }; +struct drm_property_enum_info content_type_enums[] = { + [WDRM_CONTENT_TYPE_NO_DATA] = { .name = "No Data", }, + [WDRM_CONTENT_TYPE_GRAPHICS] = { .name = "Graphics", }, + [WDRM_CONTENT_TYPE_PHOTO] = { .name = "Photo", }, + [WDRM_CONTENT_TYPE_CINEMA] = { .name = "Cinema", }, + [WDRM_CONTENT_TYPE_GAME] = { .name = "Game", }, +}; + const struct drm_property_info connector_props[] = { [WDRM_CONNECTOR_EDID] = { .name = "EDID" }, [WDRM_CONNECTOR_DPMS] = { @@ -127,6 +163,9 @@ const struct drm_property_info connector_props[] = { .num_enum_values = WDRM_DPMS_STATE__COUNT, }, [WDRM_CONNECTOR_CRTC_ID] = { .name = "CRTC_ID", }, + [WDRM_CONNECTOR_WRITEBACK_PIXEL_FORMATS] = { .name = "WRITEBACK_PIXEL_FORMATS", }, + [WDRM_CONNECTOR_WRITEBACK_FB_ID] = { .name = "WRITEBACK_FB_ID", }, + [WDRM_CONNECTOR_WRITEBACK_OUT_FENCE_PTR] = { .name = "WRITEBACK_OUT_FENCE_PTR", }, [WDRM_CONNECTOR_NON_DESKTOP] = { .name = "non-desktop", }, [WDRM_CONNECTOR_CONTENT_PROTECTION] = { .name = "Content Protection", @@ -143,11 +182,26 @@ 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", }, + [WDRM_CONNECTOR_CONTENT_TYPE] = { + .name = "content type", + .enum_values = content_type_enums, + .num_enum_values = WDRM_CONTENT_TYPE__COUNT, + }, }; const struct drm_property_info crtc_props[] = { [WDRM_CRTC_MODE_ID] = { .name = "MODE_ID", }, [WDRM_CRTC_ACTIVE] = { .name = "ACTIVE", }, + [WDRM_CRTC_CTM] = { .name = "CTM", }, + [WDRM_CRTC_DEGAMMA_LUT] = { .name = "DEGAMMA_LUT", }, + [WDRM_CRTC_DEGAMMA_LUT_SIZE] = { .name = "DEGAMMA_LUT_SIZE", }, + [WDRM_CRTC_GAMMA_LUT] = { .name = "GAMMA_LUT", }, + [WDRM_CRTC_GAMMA_LUT_SIZE] = { .name = "GAMMA_LUT_SIZE", }, + [WDRM_CRTC_VRR_ENABLED] = { .name = "VRR_ENABLED", }, }; @@ -247,6 +301,73 @@ drm_property_get_range_values(struct drm_property_info *info, return NULL; } +/* We use the fact that 0 is not a valid rotation here - if we return 0, + * the plane doesn't support the rotation requested. Otherwise the correct + * value to achieve the requested rotation on this plane is returned. + */ +uint64_t +drm_rotation_from_output_transform(struct drm_plane *plane, + enum wl_output_transform ot) +{ + struct drm_property_info *info = &plane->props[WDRM_PLANE_ROTATION]; + enum wdrm_plane_rotation drm_rotation; + enum wdrm_plane_rotation drm_reflection = 0; + uint64_t out = 0; + + if (info->prop_id == 0) { + if (ot == WL_OUTPUT_TRANSFORM_NORMAL) + return 1; + + return 0; + } + + switch (ot) { + case WL_OUTPUT_TRANSFORM_NORMAL: + drm_rotation = WDRM_PLANE_ROTATION_0; + break; + case WL_OUTPUT_TRANSFORM_90: + drm_rotation = WDRM_PLANE_ROTATION_90; + break; + case WL_OUTPUT_TRANSFORM_180: + drm_rotation = WDRM_PLANE_ROTATION_180; + break; + case WL_OUTPUT_TRANSFORM_270: + drm_rotation = WDRM_PLANE_ROTATION_270; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + drm_rotation = WDRM_PLANE_ROTATION_0; + drm_reflection = WDRM_PLANE_ROTATION_REFLECT_X; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + drm_rotation = WDRM_PLANE_ROTATION_90; + drm_reflection = WDRM_PLANE_ROTATION_REFLECT_X; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + drm_rotation = WDRM_PLANE_ROTATION_180; + drm_reflection = WDRM_PLANE_ROTATION_REFLECT_X; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + drm_rotation = WDRM_PLANE_ROTATION_270; + drm_reflection = WDRM_PLANE_ROTATION_REFLECT_X; + break; + default: + assert(0 && "bad output transform"); + } + + if (!info->enum_values[drm_rotation].valid) + return 0; + + out |= 1 << info->enum_values[drm_rotation].value; + + if (drm_reflection) { + if (!info->enum_values[drm_reflection].valid) + return 0; + out |= 1 << info->enum_values[drm_reflection].value; + } + + return out; +} + /** * Cache DRM property values * @@ -272,14 +393,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 +432,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; @@ -354,9 +475,11 @@ drm_property_info_populate(struct drm_backend *b, continue; } - if (!(prop->flags & DRM_MODE_PROP_ENUM)) { - weston_log("DRM: expected property %s to be an enum," - " but it is not; ignoring\n", prop->name); + if (!(prop->flags & DRM_MODE_PROP_ENUM) && + !(prop->flags & DRM_MODE_PROP_BITMASK)) { + weston_log("DRM: expected property %s to be an enum" + " or bitmask, but it is not; ignoring\n", + prop->name); drmModeFreeProperty(prop); info[j].prop_id = 0; continue; @@ -411,19 +534,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 +543,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 +559,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: @@ -497,7 +592,7 @@ drm_plane_populate_formats(struct drm_plane *plane, const drmModePlane *kplane, kplane->formats[i]); if (!fmt) return -1; - ret = weston_drm_format_add_modifier(fmt, DRM_FORMAT_MOD_INVALID); + ret = weston_drm_format_add_modifier(fmt, DRM_FORMAT_MOD_LINEAR); if (ret < 0) return -1; } @@ -510,14 +605,16 @@ 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, + output->deprecated_gamma_is_set = true; + rc = drmModeCrtcSetGamma(device->drm.fd, output->crtc->crtc_id, size, r, g, b); if (rc) @@ -535,7 +632,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 +650,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 +678,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 +691,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 +707,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 +716,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 +727,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 +736,51 @@ 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 void +drm_output_reset_legacy_gamma(struct drm_output *output) +{ + uint32_t len = output->base.gamma_size; + uint16_t *lut; + uint32_t i; + int ret; + + if (len == 0) + return; + + if (output->legacy_gamma_not_supported) + return; + + lut = calloc(len, sizeof(uint16_t)); + if (!lut) + return; + + /* Identity curve */ + for (i = 0; i < len; i++) + lut[i] = 0xffff * i / (len - 1); + + ret = drmModeCrtcSetGamma(output->device->drm.fd, + output->crtc->crtc_id, + len, lut, lut, lut); + if (ret == -EOPNOTSUPP || ret == -ENOSYS) + output->legacy_gamma_not_supported = true; + else if (ret < 0) { + weston_log("%s failed for %s: %s\n", __func__, + output->base.name, strerror(-ret)); + } + + free(lut); } 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 +812,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", @@ -707,24 +841,18 @@ drm_output_apply_state_legacy(struct drm_output_state *state) * legacy PageFlip API doesn't allow us to do clipping either. */ assert(scanout_state->src_x == 0); assert(scanout_state->src_y == 0); - assert(scanout_state->src_w == - (unsigned) (output->base.current_mode->width << 16)); - assert(scanout_state->src_h == - (unsigned) (output->base.current_mode->height << 16)); assert(scanout_state->dest_x == 0); assert(scanout_state->dest_y == 0); - assert(scanout_state->dest_w == scanout_state->src_w >> 16); - assert(scanout_state->dest_h == scanout_state->src_h >> 16); /* The legacy SetCrtc API doesn't support fences */ 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, @@ -733,6 +861,9 @@ drm_output_apply_state_legacy(struct drm_output_state *state) weston_log("set mode failed: %s\n", strerror(errno)); goto err; } + + if (!output->deprecated_gamma_is_set) + drm_output_reset_legacy_gamma(output); } pinfo = scanout_state->fb->format; @@ -740,7 +871,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 +892,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,37 +917,73 @@ 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; + 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); + if (info->prop_id == 0) return -1; 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", - (unsigned long) crtc->crtc_id, - (unsigned long) info->prop_id, info->name, - (unsigned long long) val, (unsigned long long) val); return (ret <= 0) ? -1 : 0; } +/** Set a CRTC property, allowing zero value for non-existing property + * + * \param req The atomic KMS request to append to. + * \param crtc The CRTC whose property to set. + * \param prop Which CRTC property to set. + * \param val The value, cast to u64, to set to the CRTC property. + * \return 0 on succcess, -1 on failure. + * + * If the property does not exist, attempting to set it to value + * zero is ok, because the property with value zero has the same + * KMS effect as the property not existing. + * + * However, trying to set a non-existing property to a non-zero value + * must fail, because that would not achieve the desired KMS effect. + * + * It is up to the caller to understand which KMS properties work + * like this and which do not. + */ +static int +crtc_add_prop_zero_ok(drmModeAtomicReq *req, struct drm_crtc *crtc, + enum wdrm_crtc_property prop, uint64_t val) +{ + struct drm_property_info *info = &crtc->props_crtc[prop]; + + if (info->prop_id == 0 && val == 0) + return 0; + + return crtc_add_prop(req, crtc, prop, val); +} + 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; + 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); + if (info->prop_id == 0) 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", - (unsigned long) connector_id, - (unsigned long) info->prop_id, info->name, - (unsigned long long) val, (unsigned long long) val); return (ret <= 0) ? -1 : 0; } @@ -824,18 +991,21 @@ 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; + 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); + if (info->prop_id == 0) return -1; 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", - (unsigned long) plane->plane_id, - (unsigned long) info->prop_id, info->name, - (unsigned long long) val, (unsigned long long) val); return (ret <= 0) ? -1 : 0; } @@ -878,6 +1048,28 @@ get_drm_protection_from_weston(enum weston_hdcp_protection weston_protection, } } +static int +drm_protection_from_weston_update(enum weston_hdcp_protection protection) +{ + enum weston_hdcp_protection current_protection; + static enum weston_hdcp_protection op_protection; + static bool op_protection_valid = false; + + current_protection = protection; + + if (!op_protection_valid) { + op_protection = current_protection; + op_protection_valid = true; + } + + if (current_protection != op_protection) { + op_protection = current_protection; + return 1; + } + + return 0; +} + static void drm_connector_set_hdcp_property(struct drm_connector *connector, enum weston_hdcp_protection protection, @@ -921,18 +1113,90 @@ 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_connector_set_content_type(struct drm_connector *connector, + enum wdrm_content_type content_type, + drmModeAtomicReq *req) +{ + struct drm_property_enum_info *enum_info; + uint64_t prop_val; + struct drm_property_info *props = connector->props; + + if (!drm_connector_has_prop(connector, WDRM_CONNECTOR_CONTENT_TYPE)) + return 0; + + enum_info = props[WDRM_CONNECTOR_CONTENT_TYPE].enum_values; + prop_val = enum_info[content_type].value; + return connector_add_prop(req, connector, + WDRM_CONNECTOR_CONTENT_TYPE, prop_val); +} + 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; + struct drm_writeback_state *wb_state = output->wb_state; + enum writeback_screenshot_state wb_screenshot_state = + drm_output_get_writeback_state(output); int ret = 0; + int in_fence_fd = -1; + int update = 0; + + if(output->gbm_surface) { + /* in_fence_fd was not created when + * the buffer_release was not exist or + * the buffer was not used in the output. + */ + if (output->surface_get_in_fence_fd) + in_fence_fd = output->surface_get_in_fence_fd(output->gbm_surface); + } +#if defined(ENABLE_IMXG2D) + else if(b->use_g2d && b->g2d_renderer) { + in_fence_fd = b->g2d_renderer->get_surface_fence_fd(&output->g2d_image[output->current_image]); + } +#endif drm_debug(b, "\t\t[atomic] %s output %lu (%s) state\n", (*flags & DRM_MODE_ATOMIC_TEST_ONLY) ? "testing" : "applying", @@ -943,8 +1207,13 @@ drm_output_apply_state_atomic(struct drm_output_state *state, *flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; } + if (wb_screenshot_state == DRM_OUTPUT_WB_SCREENSHOT_PREPARE_COMMIT) { + drm_debug(b, "\t\t\t[atomic] Writeback connector screenshot requested, modeset OK\n"); + *flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } + 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; @@ -952,6 +1221,15 @@ drm_output_apply_state_atomic(struct drm_output_state *state, current_mode->blob_id); ret |= crtc_add_prop(req, crtc, WDRM_CRTC_ACTIVE, 1); + if (!output->deprecated_gamma_is_set) { + ret |= crtc_add_prop_zero_ok(req, crtc, + WDRM_CRTC_GAMMA_LUT, 0); + ret |= crtc_add_prop_zero_ok(req, crtc, + WDRM_CRTC_DEGAMMA_LUT, 0); + } + ret |= crtc_add_prop_zero_ok(req, crtc, WDRM_CRTC_CTM, 0); + ret |= crtc_add_prop_zero_ok(req, crtc, WDRM_CRTC_VRR_ENABLED, 0); + /* No need for the DPMS property, since it is implicit in * routing and CRTC activity. */ wl_list_for_each(head, &output->base.head_list, base.output_link) { @@ -959,20 +1237,79 @@ drm_output_apply_state_atomic(struct drm_output_state *state, WDRM_CONNECTOR_CRTC_ID, crtc->crtc_id); } + + if (wb_screenshot_state == DRM_OUTPUT_WB_SCREENSHOT_PREPARE_COMMIT) { + ret |= connector_add_prop(req, &wb_state->wb->connector, + WDRM_CONNECTOR_CRTC_ID, + crtc->crtc_id); + ret |= connector_add_prop(req, &wb_state->wb->connector, + WDRM_CONNECTOR_WRITEBACK_FB_ID, + wb_state->fb->fb_id); + ret |= connector_add_prop(req, &wb_state->wb->connector, + WDRM_CONNECTOR_WRITEBACK_OUT_FENCE_PTR, + (uintptr_t)&wb_state->out_fence_fd); + if (!(*flags & DRM_MODE_ATOMIC_TEST_ONLY)) + wb_state->state = DRM_OUTPUT_WB_SCREENSHOT_CHECK_FENCE; + } + + if (device->hdr_blob_id > 0) { + wl_list_for_each(head, &output->base.head_list, base.output_link) { + /* checking if the output driver this head */ + if (head->base.output == &output->base) { + connector_add_prop(req, &head->connector, WDRM_CONNECTOR_HDR_OUTPUT_METADATA, + device->hdr_blob_id); + *flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } + } + } } else { ret |= crtc_add_prop(req, crtc, WDRM_CRTC_MODE_ID, 0); ret |= crtc_add_prop(req, crtc, WDRM_CRTC_ACTIVE, 0); + if (wb_screenshot_state == DRM_OUTPUT_WB_SCREENSHOT_PREPARE_COMMIT) { + drm_debug(b, "\t\t\t[atomic] Writeback connector screenshot requested but CRTC is off\n"); + drm_writeback_fail_screenshot(wb_state, "drm: CRTC is off"); + } + /* No need for the DPMS property, since it is implicit in * routing and CRTC activity. */ 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) - drm_connector_set_hdcp_property(&head->connector, - state->protection, req); + wl_list_for_each(head, &output->base.head_list, base.output_link) { + update = drm_protection_from_weston_update(state->protection); + if(update) { + drm_connector_set_hdcp_property(&head->connector, + state->protection, req); + /* checking if the output driver this head */ + if (head->base.output == &output->base) { + *flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } + } + + ret |= drm_connector_set_content_type(&head->connector, + output->content_type, req); + + if (drm_connector_has_prop(&head->connector, + WDRM_CONNECTOR_HDR_OUTPUT_METADATA) && device->clean_hdr_blob) { + ret |= connector_add_prop(req, &head->connector, + WDRM_CONNECTOR_HDR_OUTPUT_METADATA, + output->hdr_output_metadata_blob_id); + *flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } + + ret |= drm_connector_set_max_bpc(&head->connector, output, req); + } if (ret != 0) { weston_log("couldn't set atomic CRTC/connector state\n"); @@ -1010,16 +1347,24 @@ 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, WDRM_PLANE_IN_FENCE_FD, plane_state->in_fence_fd); + } else if (in_fence_fd >= 0 && plane->type == WDRM_PLANE_TYPE_PRIMARY && plane_state->fb) { + ret |= plane_add_prop(req, plane, + WDRM_PLANE_IN_FENCE_FD, + in_fence_fd); } + if (plane->props[WDRM_PLANE_ROTATION].prop_id != 0) + ret |= plane_add_prop(req, plane, WDRM_PLANE_ROTATION, + plane_state->rotation); + /* do note, that 'invented' zpos values are set as immutable */ if (plane_state->zpos != DRM_PLANE_ZPOS_INVALID_PLANE && plane_state->plane->zpos_min != plane_state->plane->zpos_max) @@ -1027,6 +1372,19 @@ drm_output_apply_state_atomic(struct drm_output_state *state, WDRM_PLANE_ZPOS, plane_state->zpos); + /*Plane-alpha support */ + if (plane->alpha_max != plane->alpha_min) + ret |= plane_add_prop(req, plane, + WDRM_PLANE_ALPHA, + plane_state->alpha); + + if (plane_state->fb && plane_state->fb->dtrc_meta != plane->dtrc_meta + && plane->type == WDRM_PLANE_TYPE_OVERLAY + && plane_state->fb->modifier != DRM_FORMAT_MOD_LINEAR) { + plane_add_prop(req, plane, WDRM_PLANE_DTRC_META, plane_state->fb->dtrc_meta); + plane->dtrc_meta = plane_state->fb->dtrc_meta; + } + if (ret != 0) { weston_log("couldn't set plane state\n"); return ret; @@ -1036,6 +1394,18 @@ drm_output_apply_state_atomic(struct drm_output_state *state, return 0; } +static void +drm_pending_state_clear_tearing(struct drm_pending_state *pending_state) +{ + struct drm_output_state *output_state; + + wl_list_for_each(output_state, &pending_state->output_list, link) { + if (output_state->output->virtual) + continue; + output_state->tear = false; + } +} + /** * Helper function used only by drm_pending_state_apply, with the same * guarantees and constraints as that function. @@ -1044,12 +1414,15 @@ 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(); - uint32_t flags; + uint32_t flags, tear_flag = 0; + bool may_tear = true; int ret = 0; + drm_magic_t magic; if (!req) return -1; @@ -1066,7 +1439,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 +1455,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 +1480,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 +1493,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 +1516,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); @@ -1154,6 +1531,7 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, continue; if (mode == DRM_STATE_APPLY_SYNC) assert(output_state->dpms == WESTON_DPMS_OFF); + may_tear &= output_state->tear; ret |= drm_output_apply_state_atomic(output_state, req, &flags); } @@ -1161,10 +1539,27 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, weston_log("atomic: couldn't compile atomic state\n"); goto out; } + if (may_tear) + tear_flag = DRM_MODE_PAGE_FLIP_ASYNC; - ret = drmModeAtomicCommit(b->drm.fd, req, flags, b); + /*drm master was set by systemd in PM test, try to set the master back.*/ + if (!(drmGetMagic(device->drm.fd, &magic) == 0 && + drmAuthMagic(device->drm.fd, magic) == 0)) { + drmSetMaster(device->drm.fd); + } + ret = drmModeAtomicCommit(device->drm.fd, req, flags | tear_flag, + device); drm_debug(b, "[atomic] drmModeAtomicCommit\n"); - + if (ret != 0 && may_tear && mode == DRM_STATE_TEST_ONLY) { + /* If we failed trying to set up a tearing commit, try again + * without tearing. If that succeeds, knock the tearing flag + * out of our state in case we were testing for a later commit. + */ + drm_debug(b, "[atomic] drmModeAtomicCommit (no tear fallback)\n"); + ret = drmModeAtomicCommit(device->drm.fd, req, flags, device); + if (ret == 0) + drm_pending_state_clear_tearing(pending_state); + } /* Test commits do not take ownership of the state; return * without freeing here. */ if (mode == DRM_STATE_TEST_ONLY) { @@ -1173,6 +1568,10 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, } if (ret != 0) { + wl_list_for_each(output_state, &pending_state->output_list, link) + if (drm_output_get_writeback_state(output_state->output) != DRM_OUTPUT_WB_SCREENSHOT_OFF) + drm_writeback_fail_screenshot(output_state->output->wb_state, + "drm: atomic commit failed"); weston_log("atomic: couldn't commit new state: %s\n", strerror(errno)); goto out; @@ -1182,11 +1581,16 @@ 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; + device->clean_hdr_blob = false; assert(wl_list_empty(&pending_state->output_list)); out: + if (device->hdr_blob_id > 0) { + drmModeDestroyPropertyBlob (device->drm.fd, device->hdr_blob_id); + device->hdr_blob_id = 0; + } drmModeAtomicFree(req); drm_pending_state_free(pending_state); return ret; @@ -1213,9 +1617,9 @@ drm_pending_state_apply_atomic(struct drm_pending_state *pending_state, 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 +1638,30 @@ 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 (wl_list_empty(&pending_state->output_list)) { + drm_pending_state_free(pending_state); + return 0; + } + + 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,15 +1684,15 @@ 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; - if (!b->use_pixman) { + device->state_invalid = true; + if (b->compositor->renderer->type == WESTON_RENDERER_GL) { drm_output_fini_egl(output); drm_output_init_egl(output, b); } } } - b->state_invalid = false; + device->state_invalid = false; assert(wl_list_empty(&pending_state->output_list)); @@ -1301,24 +1711,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 +1745,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)); @@ -1347,12 +1757,12 @@ drm_pending_state_apply_sync(struct drm_pending_state *pending_state) void drm_output_update_msc(struct drm_output *output, unsigned int seq) { - uint64_t msc_hi = output->base.msc >> 32; + uint32_t msc_hi = output->base.msc >> 32; if (seq < (output->base.msc & 0xffffffff)) msc_hi++; - output->base.msc = (msc_hi << 32) + seq; + output->base.msc = u64_from_u32s(msc_hi, seq); } static void @@ -1360,14 +1770,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 +1788,17 @@ 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 weston_compositor *ec = b->compositor; struct drm_crtc *crtc; struct drm_output *output; + struct timespec now; 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; @@ -1398,8 +1811,19 @@ atomic_flip_handler(int fd, unsigned int frame, unsigned int sec, drm_output_update_msc(output, frame); + if (output->state_cur->tear) { + /* When tearing we might not get accurate timestamps from + * the driver, so just use whatever time it is now. + * Note: This could actually be after a vblank that occured + * after entering this function. + */ + weston_compositor_read_presentation_clock(ec, &now); + sec = now.tv_sec; + usec = now.tv_nsec / 1000; + } + 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 +1834,27 @@ 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; + struct drm_writeback_state *state; + struct drm_crtc *crtc; + bool wait_wb_completion = false; drmEventContext evctx; + /* If we have a pending writeback job for this output, we can't continue + * with the repaint loop. The KMS UAPI docs says that we need to wait + * until the writeback is over before we send a new atomic commit that + * uses the KMS objects (CRTC, planes, etc) in use by the writeback. */ + wl_list_for_each(crtc, &device->crtc_list, link) { + state = crtc->output ? crtc->output->wb_state : NULL; + if (state && drm_writeback_should_wait_completion(state)) + wait_wb_completion = true; + } + if (wait_wb_completion) + return 1; + 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 +1864,77 @@ 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"); - - drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_WRITEBACK_CONNECTORS, 1); + device->fb_modifiers ? "supports" : "does not support"); + + drmSetClientCap(device->drm.fd, DRM_CLIENT_CAP_WRITEBACK_CONNECTORS, 1); + +#if 0 + /* FIXME: DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP isn't merged into mainline so + * we can't really use it at this point. Until then, make it so we + * don't support it. After it gets merged, we can flip this back such + * that we don't need to revert the entire tearing work, and we can + * still get it all back, when the capability is actually available in + * the kernel. */ + ret = drmGetCap(device->drm.fd, DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP, &cap); + if (ret != 0) + cap = 0; +#endif + device->tearing_supported = 0; + weston_log("DRM: does not support async page flipping\n"); /* * KMS support for hardware planes cannot properly synchronize @@ -1489,13 +1944,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 4bbc6db4a..ca7f2d680 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 23db9127f..27d488b11 100644 --- a/libweston/backend-drm/meson.build +++ b/libweston/backend-drm/meson.build @@ -2,6 +2,17 @@ if not get_option('backend-drm') subdir_done() endif +dep_libdisplay_info = dependency( + 'libdisplay-info', + version: ['>= 0.1.1', '< 0.2.0'], + fallback: ['display-info', 'di_dep'], + default_options: [ + 'werror=false', + ], + required: false, +) +config_h.set10('HAVE_LIBDISPLAY_INFO', dep_libdisplay_info.found()) + lib_backlight = static_library( 'backlight', 'libbacklight.c', @@ -24,25 +35,32 @@ srcs_drm = [ 'fb.c', 'modes.c', 'kms.c', + 'kms-color.c', 'state-helpers.c', 'state-propose.c', linux_dmabuf_unstable_v1_protocol_c, linux_dmabuf_unstable_v1_server_protocol_h, presentation_time_server_protocol_h, + hdr10_metadata_unstable_v1_protocol_c, + hdr10_metadata_unstable_v1_server_protocol_h, ] deps_drm = [ + dep_egl, # optional + dep_libm, dep_libdl, + dep_libshared, dep_libweston_private, dep_session_helper, dep_libdrm, dep_libinput_backend, dependency('libudev', version: '>= 136'), + dep_libdisplay_info, dep_backlight ] if get_option('renderer-gl') - dep_gbm = dependency('gbm', required: false) + dep_gbm = dependency('gbm', required: false, version: '>= 21.1.1') if not dep_gbm.found() error('drm-backend with GL renderer requires gbm which was not found. Or, you can use \'-Drenderer-gl=false\'.') endif @@ -53,10 +71,6 @@ if get_option('renderer-gl') config_h.set('HAVE_GBM_FD_IMPORT', '1') endif deps_drm += dep_gbm - if not dep_egl.found() - error('drm-backend + gl-renderer requires egl which was not found. Or, you can use \'-Dbackend-drm=false\' or \'-Drenderer-gl=false\'.') - endif - deps_drm += dep_egl srcs_drm += 'drm-gbm.c' config_h.set('BUILD_DRM_GBM', '1') endif diff --git a/libweston/backend-drm/modes.c b/libweston/backend-drm/modes.c index a071375b9..901372bb1 100644 --- a/libweston/backend-drm/modes.c +++ b/libweston/backend-drm/modes.c @@ -32,8 +32,33 @@ #include #include +#if HAVE_LIBDISPLAY_INFO +#include +#endif + #include "drm-internal.h" #include "shared/weston-drm-fourcc.h" +#include "shared/xalloc.h" + +struct drm_head_info { + char *make; /* The monitor make (PNP ID or company name). */ + char *model; /* The monitor model (product name). */ + char *serial_number; + + /* The monitor supported EOTF modes, combination of + * enum weston_eotf_mode bits. + */ + uint32_t eotf_mask; +}; + +static void +drm_head_info_fini(struct drm_head_info *dhi) +{ + free(dhi->make); + free(dhi->model); + free(dhi->serial_number); + *dhi = (struct drm_head_info){}; +} static const char *const aspect_ratio_as_string[] = { [WESTON_MODE_PIC_AR_NONE] = "", @@ -98,14 +123,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); @@ -199,6 +225,43 @@ parse_modeline(const char *s, drmModeModeInfo *mode) return 0; } +#if HAVE_LIBDISPLAY_INFO + +static void +drm_head_info_from_edid(struct drm_head_info *dhi, + const uint8_t *data, + size_t length) +{ + struct di_info *di_ctx; + const char *msg; + + di_ctx = di_info_parse_edid(data, length); + if (!di_ctx) + return; + + msg = di_info_get_failure_msg(di_ctx); + if (msg) + weston_log("DRM: EDID for the following head fails conformity:\n%s\n", msg); + + dhi->make = di_info_get_make(di_ctx); + dhi->model = di_info_get_model(di_ctx); + dhi->serial_number = di_info_get_serial(di_ctx); + + di_info_destroy(di_ctx); + + /* TODO: parse this from EDID */ + dhi->eotf_mask = WESTON_EOTF_MODE_ALL_MASK; +} + +#else /* HAVE_LIBDISPLAY_INFO */ + +struct drm_edid { + char eisa_id[13]; + char monitor_name[13]; + char pnp_id[5]; + char serial_number[13]; +}; + static void edid_parse_string(const uint8_t *data, char text[]) { @@ -297,29 +360,46 @@ edid_parse(struct drm_edid *edid, const uint8_t *data, size_t length) return 0; } +static void +drm_head_info_from_edid(struct drm_head_info *dhi, + const uint8_t *data, + size_t length) +{ + struct drm_edid edid = {}; + int rc; + + rc = edid_parse(&edid, data, length); + if (rc == 0) { + if (edid.pnp_id[0] != '\0') + dhi->make = xstrdup(edid.pnp_id); + if (edid.monitor_name[0] != '\0') + dhi->model = xstrdup(edid.monitor_name); + if (edid.serial_number[0] != '\0') + dhi->serial_number = xstrdup(edid.serial_number); + } + + /* This ad hoc code will never parse HDR data. */ + dhi->eotf_mask = WESTON_EOTF_MODE_SDR; +} + +#endif /* HAVE_LIBDISPLAY_INFO else */ + /** Parse monitor make, model and serial from EDID * * \param head The head whose \c drm_edid to fill in. * \param props The DRM connector properties to get the EDID from. - * \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] dhi Receives information from EDID. * - * 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 - * be free()'d explicitly, instead they get implicitly freed when the - * \c drm_head is destroyed. + * \c *dhi must be drm_head_info_fini()'d by the caller. */ static void find_and_parse_output_edid(struct drm_head *head, drmModeObjectPropertiesPtr props, - const char **make, - const char **model, - const char **serial_number) + struct drm_head_info *dhi) { + struct drm_device *device = head->connector.device; drmModePropertyBlobPtr edid_blob = NULL; uint32_t blob_id; - int rc; blob_id = drm_property_get_value( @@ -328,24 +408,27 @@ 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; - rc = edid_parse(&head->edid, - edid_blob->data, - edid_blob->length); - if (!rc) { - if (head->edid.pnp_id[0] != '\0') - *make = head->edid.pnp_id; - if (head->edid.monitor_name[0] != '\0') - *model = head->edid.monitor_name; - if (head->edid.serial_number[0] != '\0') - *serial_number = head->edid.serial_number; - } + drm_head_info_from_edid(dhi, edid_blob->data, edid_blob->length); + drmModeFreePropertyBlob(edid_blob); } +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 drm_refresh_rate_mHz(const drmModeModeInfo *info) { @@ -406,26 +489,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 +552,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 +572,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) @@ -512,12 +595,16 @@ update_head_from_connector(struct drm_head *head) struct drm_connector *connector = &head->connector; drmModeObjectProperties *props = connector->props_drm; drmModeConnector *conn = connector->conn; - const char *make = "unknown"; - const char *model = "unknown"; - const char *serial_number = "unknown"; + struct drm_head_info dhi = { .eotf_mask = WESTON_EOTF_MODE_SDR }; + + find_and_parse_output_edid(head, props, &dhi); - find_and_parse_output_edid(head, props, &make, &model, &serial_number); - weston_head_set_monitor_strings(&head->base, make, model, serial_number); + weston_head_set_monitor_strings(&head->base, dhi.make ?: "unknown", + dhi.model ?: "unknown", + dhi.serial_number ?: "unknown"); + + prune_eotf_modes_by_kms_support(head, &dhi.eotf_mask); + weston_head_set_supported_eotf_mask(&head->base, dhi.eotf_mask); weston_head_set_non_desktop(&head->base, check_non_desktop(connector, props)); weston_head_set_subpixel(&head->base, @@ -531,6 +618,8 @@ update_head_from_connector(struct drm_head *head) /* Unknown connection status is assumed disconnected. */ weston_head_set_connection_status(&head->base, conn->connection == DRM_MODE_CONNECTED); + + drm_head_info_fini(&dhi); } /** @@ -539,7 +628,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 +636,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 +660,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 +691,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 +803,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 +817,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 +844,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 +853,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 +874,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 +885,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 75a2a18c9..6e295de44 100644 --- a/libweston/backend-drm/state-helpers.c +++ b/libweston/backend-drm/state-helpers.c @@ -48,7 +48,12 @@ drm_plane_state_alloc(struct drm_output_state *state_output, state->output_state = state_output; state->plane = plane; state->in_fence_fd = -1; + state->rotation = drm_rotation_from_output_transform(plane, + WL_OUTPUT_TRANSFORM_NORMAL); + assert(state->rotation); state->zpos = DRM_PLANE_ZPOS_INVALID_PLANE; + state->alpha = (plane->alpha_max < DRM_PLANE_ALPHA_OPAQUE) ? + plane->alpha_max : DRM_PLANE_ALPHA_OPAQUE; /* Here we only add the plane state to the desired link, and not * set the member. Having an output pointer set means that the @@ -73,6 +78,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; @@ -81,19 +88,23 @@ drm_plane_state_free(struct drm_plane_state *state, bool force) state->output_state = NULL; state->in_fence_fd = -1; state->zpos = DRM_PLANE_ZPOS_INVALID_PLANE; + state->alpha = DRM_PLANE_ALPHA_OPAQUE; /* Once the damage blob has been submitted, it is refcounted internally * 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); } @@ -119,6 +130,10 @@ drm_plane_state_duplicate(struct drm_output_state *state_output, */ dst->damage_blob_id = 0; wl_list_init(&dst->link); + /* Don't copy the fence, it may no longer be valid and waiting for it + * again is not necessary + */ + dst->in_fence_fd = -1; wl_list_for_each_safe(old, tmp, &state_output->plane_list, link) { /* Duplicating a plane state into the same output state, so @@ -135,10 +150,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 { @@ -189,18 +214,45 @@ drm_plane_state_put_back(struct drm_plane_state *state) * a given plane. */ bool -drm_plane_state_coords_for_view(struct drm_plane_state *state, - struct weston_view *ev, uint64_t zpos) +drm_plane_state_coords_for_paint_node(struct drm_plane_state *state, + struct weston_paint_node *node, + uint64_t zpos) { struct drm_output *output = state->output; + struct weston_view *ev = node->view; struct weston_buffer *buffer = ev->surface->buffer_ref.buffer; - pixman_region32_t dest_rect, src_rect; - pixman_box32_t *box, tbox; + struct weston_buffer_viewport *viewport = &ev->surface->buffer_viewport; + pixman_region32_t dest_rect; + pixman_box32_t *box; + struct weston_coord corners[2]; float sxf1, syf1, sxf2, syf2; + uint16_t min_alpha = state->plane->alpha_min; + uint16_t max_alpha = state->plane->alpha_max; - if (!drm_view_transform_supported(ev, &output->base)) + float scale = 1.0; + struct weston_matrix scale_mat; + + if (!drm_paint_node_transform_supported(node, state->plane)) return false; + assert(node->valid_transform); + state->rotation = drm_rotation_from_output_transform(state->plane, node->transform); + + /* When buffer scale > 1, clients will provide higher resolution buffer data + * on high resolution output. In order to maintain the original output size + * and position, it needs to keep the mode scale and buffer scale equal. + * + * Here, check whether the buffer scale and mode scale are equal, if not, + * We need to judge whether secondary scaling is required during global->output + * coordinate transformation according to buffer scale and mode scale to maintain + * the original output size. */ + if (viewport->buffer.scale != output->base.current_scale) + scale = (float) MAX((viewport->buffer.scale / output->base.current_scale), 1); + + weston_matrix_init(&scale_mat); + /* Generate a scaling matrix using the scale parameter above. */ + weston_matrix_scale(&scale_mat, scale, scale, 1.0); + /* Update the base weston_plane co-ordinates. */ box = pixman_region32_extents(&ev->transform.boundingbox); state->plane->base.x = box->x1; @@ -212,42 +264,49 @@ drm_plane_state_coords_for_view(struct drm_plane_state *state, pixman_region32_init(&dest_rect); pixman_region32_intersect(&dest_rect, &ev->transform.boundingbox, &output->base.region); - pixman_region32_translate(&dest_rect, -output->base.x, -output->base.y); + weston_region_global_to_output(&dest_rect, &output->base, &dest_rect); + box = pixman_region32_extents(&dest_rect); - tbox = weston_transformed_rect(output->base.width, - output->base.height, - output->base.transform, - output->base.current_scale, - *box); - state->dest_x = tbox.x1; - state->dest_y = tbox.y1; - state->dest_w = tbox.x2 - tbox.x1; - state->dest_h = tbox.y2 - tbox.y1; + + /* After the coordinate transformation, do a second scaling to get + * back to the original output size. */ + corners[0] = weston_matrix_transform_coord(&scale_mat, + weston_coord(box->x1, box->y1)); + corners[1] = weston_matrix_transform_coord(&scale_mat, + weston_coord(box->x2, box->y2)); + state->dest_x = corners[0].x; + state->dest_y = corners[0].y; + state->dest_w = corners[1].x - corners[0].x; + state->dest_h = corners[1].y - corners[0].y; + + /* Now calculate the source rectangle, by transforming the destination + * rectangle by the output to buffer matrix. */ + corners[0] = weston_matrix_transform_coord( + &node->output_to_buffer_matrix, + weston_coord(box->x1, box->y1)); + corners[1] = weston_matrix_transform_coord( + &node->output_to_buffer_matrix, + weston_coord(box->x2, box->y2)); + sxf1 = corners[0].x; + syf1 = corners[0].y; + sxf2 = corners[1].x; + syf2 = corners[1].y; pixman_region32_fini(&dest_rect); - /* Now calculate the source rectangle, by finding the extents of the - * view, and working backwards to source co-ordinates. */ - pixman_region32_init(&src_rect); - pixman_region32_intersect(&src_rect, &ev->transform.boundingbox, - &output->base.region); - box = pixman_region32_extents(&src_rect); - weston_view_from_global_float(ev, box->x1, box->y1, &sxf1, &syf1); - weston_surface_to_buffer_float(ev->surface, sxf1, syf1, &sxf1, &syf1); - weston_view_from_global_float(ev, box->x2, box->y2, &sxf2, &syf2); - weston_surface_to_buffer_float(ev->surface, sxf2, syf2, &sxf2, &syf2); - pixman_region32_fini(&src_rect); - - /* Buffer transforms may mean that x2 is to the left of x1, and/or that - * y2 is above y1. */ - if (sxf2 < sxf1) { - double tmp = sxf1; + /* Make sure that our post-transform coordinates are in the + * right order. + */ + if (sxf1 > sxf2) { + float temp = sxf1; + sxf1 = sxf2; - sxf2 = tmp; + sxf2 = temp; } - if (syf2 < syf1) { - double tmp = syf1; + if (syf1 > syf2) { + float temp = syf1; + syf1 = syf2; - syf2 = tmp; + syf2 = temp; } /* Shift from S23.8 wl_fixed to U16.16 KMS fixed-point encoding. */ @@ -273,6 +332,12 @@ drm_plane_state_coords_for_view(struct drm_plane_state *state, /* apply zpos if available */ state->zpos = zpos; + /* The alpha of the view is normalized to alpha value range + * [min_alpha, max_alpha] that got from drm. The alpha value would + * never exceed max_alpha if ev->alpha <= 1.0. + */ + state->alpha = min_alpha + (uint16_t)round((max_alpha - min_alpha) * ev->alpha); + return true; } @@ -421,11 +486,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 +498,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 7b350aa42..42a68f7f6 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,81 @@ 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_paint_node_on_plane(struct drm_plane *plane, + struct drm_output_state *output_state, + struct weston_paint_node *node, + 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_view *ev = node->view; + 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: " + if (!drm_plane_state_coords_for_paint_node(state, node, zpos)) { + 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. */ + * calling drm_fb_get_from_paint_node() 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 +164,18 @@ drm_output_prepare_overlay_view(struct drm_plane *plane, 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,69 +183,57 @@ 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); - if (gbm_bo_write(bo, buf, sizeof buf) < 0) - weston_log("failed update cursor: %s\n", strerror(errno)); + if (bo) { + if (gbm_bo_write(bo, buf, sizeof buf) < 0) + weston_log("failed update cursor: %s\n", strerror(errno)); + } else { + memcpy(output->gbm_cursor_fb[output->current_cursor]->map, + buf, sizeof buf); + } } static struct drm_plane_state * -drm_output_prepare_cursor_view(struct drm_output_state *output_state, - struct weston_view *ev, uint64_t zpos) +drm_output_prepare_cursor_paint_node(struct drm_output_state *output_state, + struct weston_paint_node *node, + 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 weston_view *ev = node->view; 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. */ plane_state->output = output; - if (!drm_plane_state_coords_for_view(plane_state, ev, zpos)) { + if (!drm_plane_state_coords_for_paint_node(plane_state, node, 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 (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 +272,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); @@ -354,197 +288,14 @@ drm_output_prepare_cursor_view(struct drm_output_state *output_state, } #else static struct drm_plane_state * -drm_output_prepare_cursor_view(struct drm_output_state *output_state, - struct weston_view *ev, uint64_t zpos) +drm_output_prepare_cursor_paint_node(struct drm_output_state *output_state, + struct weston_paint_node *node, + uint64_t zpos) { return NULL; } #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) { @@ -584,31 +335,19 @@ 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, +static void +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; + enum actions_needed_dmabuf_feedback action_needed = ACTION_NEEDED_NONE; struct timespec current_time, delta_time; const time_t MAX_TIME_SECONDS = 2; - /* Find out what we need to do with the dma-buf feedback */ - if (try_view_on_plane_failure_reasons & FAILURE_REASONS_FORCE_RENDERER) - action_needed |= ACTION_NEEDED_REMOVE_SCANOUT_TRANCHE; - if (try_view_on_plane_failure_reasons & - (FAILURE_REASONS_ADD_FB_FAILED | - FAILURE_REASONS_FB_FORMAT_INCOMPATIBLE | - FAILURE_REASONS_DMABUF_MODIFIER_INVALID | - FAILURE_REASONS_GBM_BO_IMPORT_FAILED)) - action_needed |= ACTION_NEEDED_ADD_SCANOUT_TRANCHE; - - assert(action_needed != (ACTION_NEEDED_REMOVE_SCANOUT_TRANCHE | - ACTION_NEEDED_ADD_SCANOUT_TRANCHE)); - /* Look for scanout tranche. If not found, add it but in disabled mode * (we still don't know if we'll have to send it to clients). This * simplifies the code. */ @@ -619,19 +358,34 @@ dmabuf_feedback_maybe_update(struct drm_backend *b, struct weston_view *ev, scanout_tranche = weston_dmabuf_feedback_tranche_create(dmabuf_feedback, b->compositor->dmabuf_feedback_format_table, - scanout_dev, scanout_flags, - SCANOUT_PREF); + scanout_dev, scanout_flags, SCANOUT_PREF); scanout_tranche->active = false; } + /* Direct scanout won't happen even if client re-allocates using + * params from the scanout tranche, so keep only the renderer tranche. */ + if (try_view_on_plane_failure_reasons & (FAILURE_REASONS_FORCE_RENDERER | + FAILURE_REASONS_NO_PLANES_AVAILABLE)) { + action_needed = ACTION_NEEDED_REMOVE_SCANOUT_TRANCHE; + /* Direct scanout may be possible if client re-allocates using the + * params from the scanout tranche. */ + } else if (try_view_on_plane_failure_reasons & (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_GET_HANDLE_FAILED)) { + action_needed = ACTION_NEEDED_ADD_SCANOUT_TRANCHE; + /* Direct scanout is already possible, so include the scanout tranche. */ + } else if (try_view_on_plane_failure_reasons == FAILURE_REASONS_NONE) { + action_needed = ACTION_NEEDED_ADD_SCANOUT_TRANCHE; + } + /* No actions needed, so disarm timer and return */ if (action_needed == ACTION_NEEDED_NONE || - (action_needed == ACTION_NEEDED_ADD_SCANOUT_TRANCHE && - scanout_tranche->active) || - (action_needed == ACTION_NEEDED_REMOVE_SCANOUT_TRANCHE && - !scanout_tranche->active)) { + (action_needed == ACTION_NEEDED_ADD_SCANOUT_TRANCHE && scanout_tranche->active) || + (action_needed == ACTION_NEEDED_REMOVE_SCANOUT_TRANCHE && !scanout_tranche->active)) { dmabuf_feedback->action_needed = ACTION_NEEDED_NONE; - return false; + return; } /* We hit this if: @@ -646,7 +400,7 @@ dmabuf_feedback_maybe_update(struct drm_backend *b, struct weston_view *ev, dmabuf_feedback->action_needed != action_needed) { clock_gettime(CLOCK_MONOTONIC, &dmabuf_feedback->timer); dmabuf_feedback->action_needed = action_needed; - return false; + return; /* Timer is already on and the action needed when it was set to on does * not conflict with the most recent needed action we've detected. If * more than MAX_TIME_SECONDS has passed, we need to resend the dma-buf @@ -656,7 +410,7 @@ dmabuf_feedback_maybe_update(struct drm_backend *b, struct weston_view *ev, delta_time.tv_sec = current_time.tv_sec - dmabuf_feedback->timer.tv_sec; if (delta_time.tv_sec < MAX_TIME_SECONDS) - return false; + return; } /* If we got here it means that the timer has triggered, so we have @@ -676,118 +430,184 @@ dmabuf_feedback_maybe_update(struct drm_backend *b, struct weston_view *ev, /* Set the timer to off */ dmabuf_feedback->action_needed = ACTION_NEEDED_NONE; - - return true; } 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; + } - /* assemble a list with possible candidates */ - wl_list_for_each(plane, &b->plane_list, link) { - if (!drm_plane_is_available(plane, output)) - continue; + /* 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 (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); - continue; + 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; } - 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); - continue; + 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; } - if (mode == DRM_OUTPUT_PROPOSE_STATE_MIXED) { - assert(scanout_state != NULL); - if (scanout_state->zpos >= plane->zpos_max) { - drm_debug(b, "\t\t\t\t[plane] not adding plane %d to " - "candidate list: primary's zpos " - "value (%"PRIu64") higher than " - "plane's maximum value (%"PRIu64")\n", - plane->plane_id, scanout_state->zpos, - plane->zpos_max); + wl_list_for_each(plane, &device->plane_list, link) { + if (plane->type == WDRM_PLANE_TYPE_CURSOR) continue; - } + + if (drm_paint_node_transform_supported(pnode, plane)) + possible_plane_mask |= 1 << plane->plane_idx; } - 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); + if (!possible_plane_mask) { + pnode->try_view_on_plane_failure_reasons |= + FAILURE_REASONS_INCOMPATIBLE_TRANSFORM; + return NULL; + } + + fb = drm_fb_get_from_paint_node(state, pnode); + 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, &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 (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", + 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 trying plane %d: " + "another view already assigned\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", + /* if view has alpha check if this plane supports plane alpha */ + if (ev->alpha != 1.0f && plane->alpha_max == plane->alpha_min) { + drm_debug(b, "\t\t\t\t[plane] not trying plane %d:" + "plane-alpha not supported\n", plane->plane_id); continue; } - drm_output_add_zpos_plane(plane, &zpos_candidate_list); - } + if (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; + } - /* 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 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 (current_lowest_zpos == DRM_PLANE_ZPOS_INVALID_PLANE) zpos = plane->zpos_max; @@ -798,20 +618,41 @@ 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_paint_node(state, pnode, zpos); + } else { + ps = drm_output_try_paint_node_on_plane(plane, state, + pnode, 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); + /* Check if this ps is underlay plane, if so, the view + * needs through hole on primary plane. */ + if (mode == DRM_OUTPUT_PROPOSE_STATE_MIXED) { + assert(scanout_state != NULL); + if (scanout_state->zpos > ps->zpos) { + pnode->need_through_hole = true; + } + } 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,23 +663,37 @@ 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; - pixman_region32_t renderer_region; + pixman_region32_t bottom_region; pixman_region32_t occluded_region; bool renderer_ok = (mode != DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY); int ret; - uint64_t current_lowest_zpos = DRM_PLANE_ZPOS_INVALID_PLANE; + /* Record the current lowest zpos of the overlay planes */ + uint64_t current_lowest_zpos_overlay = DRM_PLANE_ZPOS_INVALID_PLANE; + /* Record the current lowest zpos of the underlay plane */ + uint64_t current_lowest_zpos_underlay = DRM_PLANE_ZPOS_INVALID_PLANE; + /* Record the current lowest zpos when finding planes for view */ + uint64_t *current_lowest_zpos = NULL; assert(!output->state_last); state = drm_output_state_duplicate(output->state_cur, pending_state, DRM_OUTPUT_STATE_CLEAR_PLANES); + /* Start with the assumption that we're going to do a tearing commit, + * if the hardware supports it and we're not compositing with the + * renderer. + * As soon as anything in the scene graph wants to be presented without + * tearing, or a test fails, drop the tear flag. */ + state->tear = device->tearing_supported && + mode == DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY; + /* We implement mixed mode by progressively creating and testing * incremental states, of scanout + overlay + cursor. Since we * walk our views top to bottom, the scanout plane is last, however @@ -862,9 +717,14 @@ drm_output_propose_state(struct weston_output *output_base, drm_output_state_free(state); return NULL; } - +#ifdef HAVE_GBM_MODIFIERS + int gbm_aligned = drm_fb_get_gbm_alignment (scanout_fb); + if (scanout_fb->width != ALIGNTO(output_base->current_mode->width, gbm_aligned) || + scanout_fb->height != ALIGNTO(output_base->current_mode->height, gbm_aligned)) { +#else if (scanout_fb->width != output_base->current_mode->width || scanout_fb->height != output_base->current_mode->height) { +#endif drm_debug(b, "\t\t[state] cannot propose mixed mode " "for output %s (%lu): previous fb has " "different size\n", @@ -878,6 +738,10 @@ drm_output_propose_state(struct weston_output *output_base, plane->state_cur); /* assign the primary the lowest zpos value */ scanout_state->zpos = plane->zpos_min; + /* Set the current lowest zpos of the underlay plane to + * scanout_state->zpos, the underlay planes need to look + * down from the scanout plane */ + current_lowest_zpos_underlay = scanout_state->zpos; drm_debug(b, "\t\t[state] using renderer FB ID %lu for mixed " "mode for output %s (%lu)\n", (unsigned long) scanout_fb->fb_id, output->base.name, @@ -886,8 +750,8 @@ drm_output_propose_state(struct weston_output *output_base, scanout_state->zpos); } - /* - renderer_region contains the total region which which will be - * covered by the renderer + /* - bottom_region contains the total region which which will be + * covered by the renderer and underlay region. * - occluded_region contains the total region which which will be * covered by the renderer and hardware planes, where the view's * visible-and-opaque region is added in both cases (the view's @@ -895,7 +759,7 @@ drm_output_propose_state(struct weston_output *output_base, * to skip the view, if it is completely occluded; includes the * situation where occluded_region covers entire output's region. */ - pixman_region32_init(&renderer_region); + pixman_region32_init(&bottom_region); pixman_region32_init(&occluded_region); wl_list_for_each(pnode, &output->base.paint_node_z_order_list, @@ -912,6 +776,10 @@ drm_output_propose_state(struct weston_output *output_base, ev, output->base.name, (unsigned long) output->base.id); + current_lowest_zpos = ¤t_lowest_zpos_overlay; + current_lowest_zpos_underlay = MIN(current_lowest_zpos_underlay, + current_lowest_zpos_overlay); + /* If this view doesn't touch our output at all, there's no * reason to do anything with it. */ /* TODO: turn this into assert once z_order_list is pruned. */ @@ -957,12 +825,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 " @@ -971,14 +856,15 @@ drm_output_propose_state(struct weston_output *output_base, } /* Since we process views from top to bottom, we know that if - * the view intersects the calculated renderer region, it must - * be part of, or occluded by, it, and cannot go on a plane. */ - pixman_region32_intersect(&surface_overlap, &renderer_region, + * the view intersects the calculated bottom region, it must + * be part of, or occluded by, it, and cannot go on an overlay + * plane. */ + pixman_region32_intersect(&surface_overlap, &bottom_region, &clipped_view); if (pixman_region32_not_empty(&surface_overlap)) { - drm_debug(b, "\t\t\t\t[view] not assigning view %p to plane " + drm_debug(b, "\t\t\t\t[view] assign view %p to underlay plane " "(occluded by renderer views)\n", ev); - force_renderer = true; + current_lowest_zpos = ¤t_lowest_zpos_underlay; } pixman_region32_fini(&surface_overlap); @@ -992,19 +878,18 @@ drm_output_propose_state(struct weston_output *output_base, force_renderer = true; } + if (pnode->view->surface->tear_control) + state->tear &= pnode->view->surface->tear_control->may_tear; + else + state->tear = 0; + /* Now try to place it on a plane if we can. */ 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; + *current_lowest_zpos); + 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. */ @@ -1013,9 +898,9 @@ drm_output_propose_state(struct weston_output *output_base, } if (ps) { - current_lowest_zpos = ps->zpos; + *current_lowest_zpos = ps->zpos; drm_debug(b, "\t\t\t[plane] next zpos to use %"PRIu64"\n", - current_lowest_zpos); + *current_lowest_zpos); } else if (!ps && !renderer_ok) { drm_debug(b, "\t\t[view] failing state generation: " "placing view %p to renderer not allowed\n", @@ -1023,14 +908,17 @@ drm_output_propose_state(struct weston_output *output_base, pixman_region32_fini(&clipped_view); goto err_region; } else if (!ps) { + drm_debug(b, "\t\t\t\t[view] view %p will be placed " + "on the renderer\n", ev); + } + + if (!ps || (mode == DRM_OUTPUT_PROPOSE_STATE_MIXED && + ps->zpos < scanout_state->zpos)) { /* clipped_view contains the area that's going to be * visible on screen; add this to the renderer region */ - pixman_region32_union(&renderer_region, - &renderer_region, + pixman_region32_union(&bottom_region, + &bottom_region, &clipped_view); - - drm_debug(b, "\t\t\t\t[view] view %p will be placed " - "on the renderer\n", ev); } /* Opaque areas of our clipped view occlude areas behind it; @@ -1049,7 +937,7 @@ drm_output_propose_state(struct weston_output *output_base, pixman_region32_fini(&clipped_view); } - pixman_region32_fini(&renderer_region); + pixman_region32_fini(&bottom_region); pixman_region32_fini(&occluded_region); /* In renderer-only mode, we can't test the state as we don't have a @@ -1080,7 +968,7 @@ drm_output_propose_state(struct weston_output *output_base, return state; err_region: - pixman_region32_fini(&renderer_region); + pixman_region32_fini(&bottom_region); pixman_region32_fini(&occluded_region); err: drm_output_state_free(state); @@ -1088,21 +976,25 @@ drm_output_propose_state(struct weston_output *output_base, } 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 drm_writeback_state *wb_state = output->wb_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) { @@ -1113,18 +1005,29 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) pending_state, mode); } - if (!state) { - drm_debug(b, "\t[repaint] could not build mixed-mode " - "state, trying renderer-only\n"); - } } else { drm_debug(b, "\t[state] no overlay plane support\n"); } + /* We can enter this block in two situations: + * 1. If we didn't enter the last block (for some reason we can't use planes) + * 2. If we entered but both the planes-only and the mixed modes didn't work */ if (!state) { + drm_debug(b, "\t[repaint] could not build state with planes, " + "trying renderer-only\n"); mode = DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY; state = drm_output_propose_state(output_base, pending_state, mode); + /* If renderer only mode failed and we are in a writeback + * screenshot, let's abort the writeback screenshot and try + * again. */ + if (!state && drm_output_get_writeback_state(output) != DRM_OUTPUT_WB_SCREENSHOT_OFF) { + drm_debug(b, "\t[repaint] could not build renderer-only " + "state, trying without writeback setup\n"); + drm_writeback_fail_screenshot(wb_state, "drm: failed to propose state"); + state = drm_output_propose_state(output_base, pending_state, + mode); + } } assert(state); @@ -1144,26 +1047,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. @@ -1187,6 +1088,7 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) drm_debug(b, "\t[repaint] view %p using renderer " "composition\n", ev); weston_view_move_to_plane(ev, primary); + pnode->need_through_hole = false; } if (!target_plane || @@ -1204,8 +1106,9 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) /* We rely on output->cursor_view being both an accurate reflection of * the cursor plane's state, but also being maintained across repaints * to avoid unnecessary damage uploads, per the comment in - * drm_output_prepare_cursor_view. In the event that we go from having - * a cursor view to not having a cursor view, we need to clear it. */ + * drm_output_prepare_cursor_paint_node. In the event that we go from + * having a cursor view to not having a cursor view, we need to clear + * it. */ if (output->cursor_view) { plane_state = drm_output_state_get_existing_plane(state, @@ -1213,6 +1116,9 @@ drm_assign_planes(struct weston_output *output_base, void *repaint_data) if (!plane_state || !plane_state->fb) drm_output_set_cursor_view(output, NULL); } + + if (drm_output_get_writeback_state(output) == DRM_OUTPUT_WB_SCREENSHOT_PREPARE_COMMIT) + drm_writeback_reference_planes(wb_state, &state->plane_list); } static void @@ -1229,7 +1135,7 @@ drm_output_handle_cursor_view_destroy(struct wl_listener *listener, void *data) * * Ensure the stored value will be properly cleared if the view is destroyed. * The stored cursor view helps avoid unnecessary uploads of cursor data to - * cursor plane buffer objects (see drm_output_prepare_cursor_view). + * cursor plane buffer objects (see drm_output_prepare_cursor_paint_node). */ void drm_output_set_cursor_view(struct drm_output *output, struct weston_view *ev) diff --git a/libweston/backend-headless/headless.c b/libweston/backend-headless/headless.c index 5c4e4ccba..b1d49f09e 100644 --- a/libweston/backend-headless/headless.c +++ b/libweston/backend-headless/headless.c @@ -1,6 +1,8 @@ /* * Copyright © 2010-2011 Benjamin Franzke * Copyright © 2012 Intel Corporation + * Copyright © 2013 Jason Ekstrand + * 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 @@ -37,28 +39,30 @@ #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 "gl-borders.h" #include "shared/weston-drm-fourcc.h" #include "shared/weston-egl-ext.h" +#include "shared/cairo-util.h" +#include "shared/xalloc.h" #include "linux-dmabuf.h" +#include "output-capture.h" #include "presentation-time-server-protocol.h" #include -enum headless_renderer_type { - HEADLESS_NOOP, - HEADLESS_PIXMAN, - HEADLESS_GL, -}; - struct headless_backend { struct weston_backend base; struct weston_compositor *compositor; struct weston_seat fake_seat; - enum headless_renderer_type renderer_type; - struct gl_renderer_interface *glri; + bool decorate; + struct theme *theme; + + const struct pixel_format_info **formats; + unsigned int formats_count; }; struct headless_head { @@ -67,34 +71,49 @@ struct headless_head { struct headless_output { struct weston_output base; + struct headless_backend *backend; struct weston_mode mode; struct wl_event_source *finish_frame_timer; - uint32_t *image_buf; - pixman_image_t *image; + struct weston_renderbuffer *renderbuffer; + + struct frame *frame; + struct { + struct weston_gl_borders borders; + } gl; }; static const uint32_t headless_formats[] = { - DRM_FORMAT_XRGB8888, + DRM_FORMAT_XRGB8888, /* default for pixman-renderer */ DRM_FORMAT_ARGB8888, }; +static void +headless_destroy(struct weston_backend *backend); + static inline struct headless_head * to_headless_head(struct weston_head *base) { + if (base->backend->destroy != headless_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); } static inline struct headless_backend * -to_headless_backend(struct weston_compositor *base) +to_headless_backend(struct weston_backend *base) { - return container_of(base->backend, struct headless_backend, base); + return container_of(base, struct headless_backend, base); } static int @@ -120,15 +139,33 @@ finish_frame_handler(void *data) return 1; } +static void +headless_output_update_gl_border(struct headless_output *output) +{ + if (!output->frame) + return; + if (!(frame_status(output->frame) & FRAME_STATUS_REPAINT)) + return; + + weston_gl_borders_update(&output->gl.borders, output->frame, + &output->base); +} + 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->renderer->repaint_output(&output->base, damage); + ec = output->base.compositor; + + headless_output_update_gl_border(output); + + ec->renderer->repaint_output(&output->base, damage, + output->renderbuffer); pixman_region32_subtract(&ec->primary_plane.damage, &ec->primary_plane.damage, damage); @@ -142,39 +179,54 @@ static void headless_output_disable_gl(struct headless_output *output) { struct weston_compositor *compositor = output->base.compositor; - struct headless_backend *b = to_headless_backend(compositor); + const struct weston_renderer *renderer = compositor->renderer; + + weston_gl_borders_fini(&output->gl.borders, &output->base); + + renderer->gl->output_destroy(&output->base); - b->glri->output_destroy(&output->base); + if (output->frame) { + frame_destroy(output->frame); + output->frame = NULL; + } } static void headless_output_disable_pixman(struct headless_output *output) { - pixman_renderer_output_destroy(&output->base); - pixman_image_unref(output->image); - free(output->image_buf); + struct weston_renderer *renderer = output->base.compositor->renderer; + + weston_renderbuffer_unref(output->renderbuffer); + output->renderbuffer = NULL; + renderer->pixman->output_destroy(&output->base); } 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 = output->backend; + wl_event_source_remove(output->finish_frame_timer); - switch (b->renderer_type) { - case HEADLESS_GL: + switch (b->compositor->renderer->type) { + case WESTON_RENDERER_GL: headless_output_disable_gl(output); break; - case HEADLESS_PIXMAN: + case WESTON_RENDERER_PIXMAN: headless_output_disable_pixman(output); break; - case HEADLESS_NOOP: + case WESTON_RENDERER_NOOP: break; + case WESTON_RENDERER_AUTO: + unreachable("cannot have auto renderer at runtime"); } return 0; @@ -185,26 +237,58 @@ 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); + assert(!output->frame); free(output); } static int headless_output_enable_gl(struct headless_output *output) { - struct weston_compositor *compositor = output->base.compositor; - struct headless_backend *b = to_headless_backend(compositor); - const struct gl_renderer_pbuffer_options options = { - .width = output->base.current_mode->width, - .height = output->base.current_mode->height, - .drm_formats = headless_formats, - .drm_formats_count = ARRAY_LENGTH(headless_formats), + struct headless_backend *b = output->backend; + const struct weston_renderer *renderer = b->compositor->renderer; + const struct weston_mode *mode = output->base.current_mode; + struct gl_renderer_pbuffer_options options = { + .formats = b->formats, + .formats_count = b->formats_count, }; - if (b->glri->output_pbuffer_create(&output->base, &options) < 0) { + if (b->decorate) { + /* + * Start with a dummy exterior size and then resize, because + * there is no frame_create() with interior size. + */ + output->frame = frame_create(b->theme, 100, 100, + FRAME_BUTTON_CLOSE, NULL, NULL); + if (!output->frame) { + weston_log("failed to create frame for output\n"); + return -1; + } + frame_resize_inside(output->frame, mode->width, mode->height); + + options.fb_size.width = frame_width(output->frame); + options.fb_size.height = frame_height(output->frame); + frame_interior(output->frame, &options.area.x, &options.area.y, + &options.area.width, &options.area.height); + } else { + options.area.x = 0; + options.area.y = 0; + options.area.width = mode->width; + options.area.height = mode->height; + options.fb_size.width = mode->width; + options.fb_size.height = mode->height; + } + + if (renderer->gl->output_pbuffer_create(&output->base, &options) < 0) { weston_log("failed to create gl renderer output state\n"); + if (output->frame) { + frame_destroy(output->frame); + output->frame = NULL; + } return -1; } @@ -214,31 +298,32 @@ headless_output_enable_gl(struct headless_output *output) static int headless_output_enable_pixman(struct headless_output *output) { + const struct pixman_renderer_interface *pixman; const struct pixman_renderer_output_options options = { .use_shadow = true, + .fb_size = { + .width = output->base.current_mode->width, + .height = output->base.current_mode->height + }, + .format = pixel_format_get_info(headless_formats[0]) }; - output->image_buf = malloc(output->base.current_mode->width * - output->base.current_mode->height * 4); - if (!output->image_buf) - return -1; + pixman = output->base.compositor->renderer->pixman; - 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); + if (pixman->output_create(&output->base, &options) < 0) + return -1; - if (pixman_renderer_output_create(&output->base, &options) < 0) + output->renderbuffer = + pixman->create_image(&output->base, options.format, + output->base.current_mode->width, + output->base.current_mode->height); + if (!output->renderbuffer) goto err_renderer; - pixman_renderer_output_set_buffer(&output->base, output->image); - return 0; err_renderer: - pixman_image_unref(output->image); - free(output->image_buf); + pixman->output_destroy(&output->base); return -1; } @@ -247,10 +332,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 = output->backend; + loop = wl_display_get_event_loop(b->compositor->wl_display); output->finish_frame_timer = wl_event_loop_add_timer(loop, finish_frame_handler, output); @@ -260,15 +349,17 @@ headless_output_enable(struct weston_output *base) return -1; } - switch (b->renderer_type) { - case HEADLESS_GL: + switch (b->compositor->renderer->type) { + case WESTON_RENDERER_GL: ret = headless_output_enable_gl(output); break; - case HEADLESS_PIXMAN: + case WESTON_RENDERER_PIXMAN: ret = headless_output_enable_pixman(output); break; - case HEADLESS_NOOP: + case WESTON_RENDERER_NOOP: break; + case WESTON_RENDERER_AUTO: + unreachable("cannot have auto renderer at runtime"); } if (ret < 0) { @@ -287,6 +378,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); @@ -324,8 +418,10 @@ headless_output_set_size(struct weston_output *base, } static struct weston_output * -headless_output_create(struct weston_compositor *compositor, const char *name) +headless_output_create(struct weston_backend *backend, const char *name) { + struct headless_backend *b = container_of(backend, struct headless_backend, base); + struct weston_compositor *compositor = b->compositor; struct headless_output *output; /* name can't be NULL. */ @@ -342,15 +438,18 @@ headless_output_create(struct weston_compositor *compositor, const char *name) output->base.enable = headless_output_enable; output->base.attach_head = NULL; + output->backend = b; + weston_compositor_add_pending_output(&output->base, compositor); return &output->base; } static int -headless_head_create(struct weston_compositor *compositor, +headless_head_create(struct weston_backend *base, const char *name) { + struct headless_backend *backend = to_headless_backend(base); struct headless_head *head; /* name can't be NULL. */ @@ -361,55 +460,55 @@ headless_head_create(struct weston_compositor *compositor, return -1; weston_head_init(&head->base, name); + + head->base.backend = &backend->base; + 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. * We do not have those until set_size() time through. */ - weston_compositor_add_head(compositor, &head->base); + weston_compositor_add_head(backend->compositor, &head->base); return 0; } 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); } static void -headless_destroy(struct weston_compositor *ec) +headless_destroy(struct weston_backend *backend) { - struct headless_backend *b = to_headless_backend(ec); + struct headless_backend *b = container_of(backend, struct headless_backend, base); + struct weston_compositor *ec = b->compositor; struct weston_head *base, *next; weston_compositor_shutdown(ec); - wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) - headless_head_destroy(to_headless_head(base)); - - free(b); -} + wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) { + if (to_headless_head(base)) + headless_head_destroy(base); + } -static int -headless_gl_renderer_init(struct headless_backend *b) -{ - const struct gl_renderer_display_options options = { - .egl_platform = EGL_PLATFORM_SURFACELESS_MESA, - .egl_native_display = NULL, - .egl_surface_type = EGL_PBUFFER_BIT, - .drm_formats = headless_formats, - .drm_formats_count = ARRAY_LENGTH(headless_formats), - }; + if (b->theme) + theme_destroy(b->theme); - b->glri = weston_load_module("gl-renderer.so", "gl_renderer_interface"); - if (!b->glri) - return -1; + free(b->formats); + free(b); - return b->glri->display_create(b->compositor, &options); + cleanup_after_cairo(); } static const struct weston_windowed_output_api api = { @@ -437,31 +536,52 @@ headless_backend_create(struct weston_compositor *compositor, b->base.destroy = headless_destroy; b->base.create_output = headless_output_create; - if (config->use_pixman && config->use_gl) { - weston_log("Error: cannot use both Pixman *and* GL renderers.\n"); - goto err_free; + b->decorate = config->decorate; + if (b->decorate) { + b->theme = theme_create(); + if (!b->theme) { + weston_log("Error: could not load decorations theme.\n"); + goto err_free; + } } - if (config->use_gl) - b->renderer_type = HEADLESS_GL; - else if (config->use_pixman) - b->renderer_type = HEADLESS_PIXMAN; - else - b->renderer_type = HEADLESS_NOOP; - - switch (b->renderer_type) { - case HEADLESS_GL: - ret = headless_gl_renderer_init(b); + b->formats_count = ARRAY_LENGTH(headless_formats); + b->formats = pixel_format_get_array(headless_formats, b->formats_count); + + switch (config->renderer) { + case WESTON_RENDERER_GL: { + const struct gl_renderer_display_options options = { + .egl_platform = EGL_PLATFORM_SURFACELESS_MESA, + .egl_native_display = NULL, + .egl_surface_type = EGL_PBUFFER_BIT, + .formats = b->formats, + .formats_count = b->formats_count, + }; + ret = weston_compositor_init_renderer(compositor, + WESTON_RENDERER_GL, + &options.base); break; - case HEADLESS_PIXMAN: - ret = pixman_renderer_init(compositor); + } + case WESTON_RENDERER_PIXMAN: + if (config->decorate) { + weston_log("Error: Pixman renderer does not support decorations.\n"); + goto err_input; + } + ret = weston_compositor_init_renderer(compositor, + WESTON_RENDERER_PIXMAN, + NULL); break; - case HEADLESS_NOOP: + case WESTON_RENDERER_AUTO: + case WESTON_RENDERER_NOOP: + if (config->decorate) { + weston_log("Error: no-op renderer does not support decorations.\n"); + goto err_input; + } ret = noop_renderer_init(compositor); break; default: - assert(0 && "invalid renderer type"); - ret = -1; + weston_log("Error: unsupported renderer\n"); + break; } if (ret < 0) @@ -490,6 +610,9 @@ headless_backend_create(struct weston_compositor *compositor, return b; err_input: + if (b->theme) + theme_destroy(b->theme); + weston_compositor_shutdown(compositor); err_free: free(b); diff --git a/libweston/backend-headless/meson.build b/libweston/backend-headless/meson.build index c603bb0bb..338fa08a2 100644 --- a/libweston/backend-headless/meson.build +++ b/libweston/backend-headless/meson.build @@ -12,7 +12,12 @@ plugin_headless = shared_library( 'headless-backend', srcs_headless, include_directories: common_inc, - dependencies: [ dep_libweston_private, dep_libdrm_headers ], + dependencies: [ + dep_libweston_private, + dep_libdrm_headers, + dep_lib_cairo_shared, + dep_lib_gl_borders, + ], name_prefix: '', install: true, install_dir: dir_module_libweston, diff --git a/libweston/backend-pipewire/meson.build b/libweston/backend-pipewire/meson.build new file mode 100644 index 000000000..267f5d366 --- /dev/null +++ b/libweston/backend-pipewire/meson.build @@ -0,0 +1,35 @@ +if not get_option('backend-pipewire') + subdir_done() +endif +user_hint = 'If you rather not build this, set \'-Dbackend-pipewire=false\'.' + +config_h.set('BUILD_PIPEWIRE_COMPOSITOR', '1') + +dep_libpipewire = dependency('libpipewire-0.3', required: false) +if not dep_libpipewire.found() + error('PipeWire backend requires libpipewire 0.3 which was not found. ' + user_hint) +endif + +dep_libspa = dependency('libspa-0.2', required: false) +if not dep_libspa.found() + error('Pipewire plugin requires libspa 0.2 which was not found. ' + user_hint) +endif + +deps_pipewire = [ + dep_libweston_private, + dep_libpipewire, + dep_libspa, + dep_libdrm_headers, +] + +plugin_pipewire = shared_library( + 'pipewire-backend', + [ 'pipewire.c' ], + include_directories: common_inc, + dependencies: deps_pipewire, + name_prefix: '', + install: true, + install_dir: dir_module_libweston +) +env_modmap += 'pipewire-backend.so=@0@;'.format(plugin_pipewire.full_path()) +install_headers(backend_pipewire_h, subdir: dir_include_libweston_install) diff --git a/libweston/backend-pipewire/pipewire.c b/libweston/backend-pipewire/pipewire.c new file mode 100644 index 000000000..5fce219c9 --- /dev/null +++ b/libweston/backend-pipewire/pipewire.c @@ -0,0 +1,1002 @@ +/* + * Copyright © 2021-2023 Pengutronix, Philipp Zabel + * based on backend-rdp: + * Copyright © 2013 Hardening + * and pipewire-plugin: + * Copyright © 2019 Pengutronix, Michael Olbrich + * + * 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 "shared/helpers.h" +#include "shared/timespec-util.h" +#include "shared/xalloc.h" +#include +#include +#include +#include "pixel-formats.h" +#include "pixman-renderer.h" + +struct pipewire_backend { + struct weston_backend base; + struct weston_compositor *compositor; + + const struct pixel_format_info *pixel_format; + + struct weston_log_scope *debug; + + struct pw_loop *loop; + struct wl_event_source *loop_source; + + struct pw_context *context; + struct pw_core *core; + struct spa_hook core_listener; +}; + +struct pipewire_output { + struct weston_output base; + struct pipewire_backend *backend; + + uint32_t seq; + struct pw_stream *stream; + struct spa_hook stream_listener; + + const struct pixel_format_info *pixel_format; + + struct wl_event_source *finish_frame_timer; + struct wl_list link; +}; + +struct pipewire_head { + struct weston_head base; + struct pipewire_config config; +}; + +struct pipewire_frame_data { + struct pipewire_output *output; + struct pw_buffer *buffer; + struct weston_renderbuffer *renderbuffer; +}; + +/* Pipewire default configuration for heads */ +static const struct pipewire_config default_config = { + .width = 640, + .height = 480, + .framerate = 30, +}; + +static void +pipewire_debug_impl(struct pipewire_backend *pipewire, + struct pipewire_output *output, + const char *fmt, va_list ap) +{ + FILE *fp; + char *logstr; + size_t logsize; + char timestr[128]; + + if (!weston_log_scope_is_enabled(pipewire->debug)) + return; + + fp = open_memstream(&logstr, &logsize); + if (!fp) + return; + + weston_log_scope_timestamp(pipewire->debug, timestr, sizeof timestr); + fprintf(fp, "%s", timestr); + + if (output) + fprintf(fp, "[%s]", output->base.name); + + fprintf(fp, " "); + vfprintf(fp, fmt, ap); + fprintf(fp, "\n"); + + if (fclose(fp) == 0) + weston_log_scope_write(pipewire->debug, logstr, logsize); + + free(logstr); +} + +static void +pipewire_output_debug(struct pipewire_output *output, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + pipewire_debug_impl(output->backend, output, fmt, ap); + va_end(ap); +} + +static inline struct pipewire_backend * +to_pipewire_backend(struct weston_backend *base) +{ + return container_of(base, struct pipewire_backend, base); +} + +static void +pipewire_output_destroy(struct weston_output *base); + +static inline struct pipewire_output * +to_pipewire_output(struct weston_output *base) +{ + if (base->destroy != pipewire_output_destroy) + return NULL; + return container_of(base, struct pipewire_output, base); +} + +static void +pipewire_head_destroy(struct weston_head *base); + +static void +pipewire_destroy(struct weston_backend *backend); + +static inline struct pipewire_head * +to_pipewire_head(struct weston_head *base) +{ + if (base->backend->destroy != pipewire_destroy) + return NULL; + return container_of(base, struct pipewire_head, base); +} + +static enum spa_video_format +spa_video_format_from_drm_fourcc(uint32_t fourcc) +{ + switch (fourcc) { + case DRM_FORMAT_XRGB8888: + return SPA_VIDEO_FORMAT_BGRx; + case DRM_FORMAT_RGB565: + return SPA_VIDEO_FORMAT_RGB16; + default: + return SPA_VIDEO_FORMAT_UNKNOWN; + } +} + +static int +pipewire_output_connect(struct pipewire_output *output) +{ + uint8_t buffer[1024]; + struct spa_pod_builder builder = + SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + const struct spa_pod *params[1]; + int framerate; + int width; + int height; + enum spa_video_format format; + int ret; + + framerate = output->base.current_mode->refresh / 1000; + width = output->base.width; + height = output->base.height; + + format = spa_video_format_from_drm_fourcc(output->pixel_format->format); + + params[0] = spa_pod_builder_add_object(&builder, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), + SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(width, height)), + SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION (0, 1)), + SPA_FORMAT_VIDEO_maxFramerate, + SPA_POD_CHOICE_RANGE_Fraction(&SPA_FRACTION(framerate, 1), + &SPA_FRACTION(1, 1), + &SPA_FRACTION(framerate, 1))); + + ret = pw_stream_connect(output->stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, + PW_STREAM_FLAG_DRIVER | + PW_STREAM_FLAG_MAP_BUFFERS, + params, 1); + if (ret != 0) { + weston_log("Failed to connect PipeWire stream: %s", + spa_strerror(ret)); + return -1; + } + + return 0; +} + +static int +finish_frame_handler(void *data) +{ + struct pipewire_output *output = data; + int refresh_nsec = millihz_to_nsec(output->base.current_mode->refresh); + struct timespec ts; + struct timespec now; + int64_t delta; + + /* + * Skip weston_output_finish_frame() if the repaint state machine was + * reset, e.g. by calling weston_compositor_sleep(). + */ + if (output->base.repaint_status != REPAINT_AWAITING_COMPLETION) + return 1; + + /* + * The timer only has msec precision, but if the approximately hit our + * target, report an exact time stamp by adding to the previous frame + * time. + */ + timespec_add_nsec(&ts,&output->base.frame_time, refresh_nsec); + + /* If we are more than 1.5 ms late, report the current time instead. */ + weston_compositor_read_presentation_clock(output->base.compositor, &now); + delta = timespec_sub_to_nsec(&now, &ts); + if (delta > 1500000) + ts = now; + + weston_output_finish_frame(&output->base, &ts, 0); + + return 1; +} + +static int +pipewire_output_enable(struct weston_output *base) +{ + struct weston_renderer *renderer = base->compositor->renderer; + struct pipewire_output *output = to_pipewire_output(base); + struct pipewire_backend *backend; + struct wl_event_loop *loop; + int ret; + const struct pixman_renderer_output_options options = { + .use_shadow = true, + .fb_size = { + .width = output->base.width, + .height = output->base.height, + }, + .format = output->pixel_format, + }; + + backend = output->backend; + + ret = renderer->pixman->output_create(&output->base, &options); + if (ret < 0) + return ret; + + loop = wl_display_get_event_loop(backend->compositor->wl_display); + output->finish_frame_timer = wl_event_loop_add_timer(loop, + finish_frame_handler, + output); + + ret = pipewire_output_connect(output); + if (ret < 0) + goto err; + + return 0; +err: + renderer->pixman->output_destroy(&output->base); + + wl_event_source_remove(output->finish_frame_timer); + + return ret; +} + +static int +pipewire_output_disable(struct weston_output *base) +{ + struct weston_renderer *renderer = base->compositor->renderer; + struct pipewire_output *output = to_pipewire_output(base); + + if (!output->base.enabled) + return 0; + + pw_stream_disconnect(output->stream); + + renderer->pixman->output_destroy(&output->base); + + wl_event_source_remove(output->finish_frame_timer); + + return 0; +} + +static void +pipewire_output_destroy(struct weston_output *base) +{ + struct pipewire_output *output = to_pipewire_output(base); + + assert(output); + + pipewire_output_disable(&output->base); + weston_output_release(&output->base); + + pw_stream_destroy(output->stream); + + free(output); +} + +static void +pipewire_output_stream_state_changed(void *data, enum pw_stream_state old, + enum pw_stream_state state, + const char *error_message) +{ + struct pipewire_output *output = data; + + pipewire_output_debug(output, "state changed: %s -> %s", + pw_stream_state_as_string(old), + pw_stream_state_as_string(state)); + + switch (state) { + case PW_STREAM_STATE_STREAMING: + /* Repaint required to push the frame to the new consumer. */ + weston_output_damage(&output->base); + weston_output_schedule_repaint(&output->base); + break; + default: + break; + } +} + +static void +pipewire_output_stream_param_changed(void *data, uint32_t id, + const struct spa_pod *format) +{ + struct pipewire_output *output = data; + uint8_t buffer[1024]; + struct spa_pod_builder builder = + SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + const struct spa_pod *params[2]; + struct spa_video_info video_info; + int32_t width; + int32_t height; + int32_t stride; + int32_t size; + + if (!format || id != SPA_PARAM_Format) + return; + + if (spa_format_parse(format, &video_info.media_type, + &video_info.media_subtype) < 0) + return; + if (video_info.media_type != SPA_MEDIA_TYPE_video || + video_info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return; + + spa_format_video_raw_parse(format, &video_info.info.raw); + + pipewire_output_debug(output, "param changed: %dx%d@(%d/%d) (%s)", + video_info.info.raw.size.width, + video_info.info.raw.size.height, + video_info.info.raw.max_framerate.num, + video_info.info.raw.max_framerate.denom, + spa_debug_type_find_short_name(spa_type_video_format, + video_info.info.raw.format)); + + width = video_info.info.raw.size.width; + height = video_info.info.raw.size.height; + stride = width * output->pixel_format->bpp / 8; + size = height * stride; + + + params[0] = spa_pod_builder_add_object(&builder, + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride), + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(4, 2, 8)); + + params[1] = spa_pod_builder_add_object(&builder, + SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); + + pw_stream_update_params(output->stream, params, 2); +} + +static void +pipewire_output_stream_add_buffer(void *data, struct pw_buffer *buffer) +{ + struct pipewire_output *output = data; + struct weston_compositor *ec = output->base.compositor; + const struct pixman_renderer_interface *pixman = ec->renderer->pixman; + struct pipewire_frame_data *frame_data; + const struct pixel_format_info *format; + unsigned int width; + unsigned int height; + unsigned int stride; + void *ptr; + + pipewire_output_debug(output, "add buffer: %p", buffer); + + frame_data = xzalloc(sizeof *frame_data); + buffer->user_data = frame_data; + + format = output->pixel_format; + width = output->base.width; + height = output->base.height; + stride = width * format->bpp / 8; + ptr = buffer->buffer->datas[0].data; + + frame_data->renderbuffer = pixman->create_image_from_ptr(&output->base, + format, width, + height, ptr, + stride); +} + +static void +pipewire_output_stream_remove_buffer(void *data, struct pw_buffer *buffer) +{ + struct pipewire_output *output = data; + struct pipewire_frame_data *frame_data = buffer->user_data; + + pipewire_output_debug(output, "remove buffer: %p", buffer); + + weston_renderbuffer_unref(frame_data->renderbuffer); + free(frame_data); +} + +static const struct pw_stream_events stream_events = { + PW_VERSION_STREAM_EVENTS, + .state_changed = pipewire_output_stream_state_changed, + .param_changed = pipewire_output_stream_param_changed, + .add_buffer = pipewire_output_stream_add_buffer, + .remove_buffer = pipewire_output_stream_remove_buffer, +}; + +static struct weston_output * +pipewire_create_output(struct weston_backend *backend, const char *name) +{ + struct pipewire_output *output; + struct pipewire_backend *b = container_of(backend, struct pipewire_backend, base); + struct pw_properties *props; + + output = zalloc(sizeof *output); + if (output == NULL) + return NULL; + + weston_output_init(&output->base, b->compositor, name); + + output->base.destroy = pipewire_output_destroy; + output->base.disable = pipewire_output_disable; + output->base.enable = pipewire_output_enable; + output->base.attach_head = NULL; + + weston_compositor_add_pending_output(&output->base, b->compositor); + + output->backend = b; + output->pixel_format = b->pixel_format; + + props = pw_properties_new(NULL, NULL); + pw_properties_setf(props, PW_KEY_NODE_NAME, "weston.%s", name); + + output->stream = pw_stream_new(b->core, name, props); + if (!output->stream) { + weston_log("Cannot initialize PipeWire stream\n"); + free(output); + return NULL; + } + + pw_stream_add_listener(output->stream, &output->stream_listener, + &stream_events, output); + + return &output->base; +} + +static void +pipewire_destroy(struct weston_backend *base) +{ + struct pipewire_backend *b = container_of(base, struct pipewire_backend, base); + struct weston_compositor *ec = b->compositor; + struct weston_head *head, *next; + + weston_log_scope_destroy(b->debug); + b->debug = NULL; + + weston_compositor_shutdown(ec); + + pw_loop_leave(b->loop); + pw_loop_destroy(b->loop); + wl_event_source_remove(b->loop_source); + + wl_list_for_each_safe(head, next, &ec->head_list, compositor_link) + pipewire_head_destroy(head); + + free(b); +} + +static void +pipewire_head_create(struct weston_backend *backend, const char *name, + const struct pipewire_config *config) +{ + struct pipewire_backend *b = to_pipewire_backend(backend); + struct pipewire_head *head; + struct weston_head *base; + + head = xzalloc(sizeof *head); + + head->config = *config; + + base = &head->base; + weston_head_init(base, name); + weston_head_set_monitor_strings(base, "PipeWire", name, NULL); + weston_head_set_physical_size(base, config->width, config->height); + + base->backend = &b->base; + + weston_head_set_connection_status(base, true); + weston_compositor_add_head(b->compositor, base); +} + +static void +pipewire_head_destroy(struct weston_head *base) +{ + struct pipewire_head *head = to_pipewire_head(base); + + if (!head) + return; + + weston_head_release(&head->base); + free(head); +} + +static int +pipewire_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 void +pipewire_submit_buffer(struct pipewire_output *output, + struct pw_buffer *buffer) +{ + struct spa_buffer *spa_buffer; + struct spa_meta_header *h; + const struct pixel_format_info *pixel_format; + unsigned int stride; + size_t size; + + pixel_format = output->pixel_format; + stride = output->base.width * pixel_format->bpp / 8; + size = output->base.height * stride; + + spa_buffer = buffer->buffer; + + if ((h = spa_buffer_find_meta_data(spa_buffer, SPA_META_Header, + sizeof(struct spa_meta_header)))) { + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + h->pts = SPA_TIMESPEC_TO_NSEC(&ts); + h->flags = 0; + h->seq = output->seq; + h->dts_offset = 0; + } + + spa_buffer->datas[0].chunk->offset = 0; + spa_buffer->datas[0].chunk->stride = stride; + spa_buffer->datas[0].chunk->size = size; + + pipewire_output_debug(output, "queue buffer: %p (seq %d)", + buffer, output->seq); + pw_stream_queue_buffer(output->stream, buffer); + + output->seq++; +} + +static void +pipewire_output_arm_timer(struct pipewire_output *output) +{ + struct weston_compositor *ec = output->base.compositor; + struct timespec now; + struct timespec target; + int refresh_nsec = millihz_to_nsec(output->base.current_mode->refresh); + int refresh_msec = refresh_nsec / 1000000; + int next_frame_delta; + + 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 = 1; + if (next_frame_delta > refresh_msec) + next_frame_delta = refresh_msec; + + wl_event_source_timer_update(output->finish_frame_timer, next_frame_delta); +} + +static int +pipewire_output_repaint(struct weston_output *base, pixman_region32_t *damage) +{ + struct pipewire_output *output = to_pipewire_output(base); + struct weston_compositor *ec = output->base.compositor; + struct pw_buffer *buffer; + struct pipewire_frame_data *frame_data; + + assert(output); + + if (pw_stream_get_state(output->stream, NULL) != PW_STREAM_STATE_STREAMING) + goto out; + + if (!pixman_region32_not_empty(damage)) + goto out; + + buffer = pw_stream_dequeue_buffer(output->stream); + if (!buffer) { + weston_log("Failed to dequeue PipeWire buffer\n"); + goto out; + } + pipewire_output_debug(output, "dequeued buffer: %p", buffer); + + frame_data = buffer->user_data; + ec->renderer->repaint_output(&output->base, damage, frame_data->renderbuffer); + + pipewire_submit_buffer(output, buffer); + + pixman_region32_subtract(&ec->primary_plane.damage, + &ec->primary_plane.damage, damage); +out: + + pipewire_output_arm_timer(output); + + return 0; +} + +static struct weston_mode * +pipewire_insert_new_mode(struct weston_output *output, + int width, int height, int rate) +{ + struct weston_mode *mode; + + mode = zalloc(sizeof *mode); + if (!mode) + return NULL; + mode->width = width; + mode->height = height; + mode->refresh = rate; + wl_list_insert(&output->mode_list, &mode->link); + + return mode; +} + +static struct weston_mode * +pipewire_ensure_matching_mode(struct weston_output *output, struct weston_mode *target) +{ + struct weston_mode *local; + + wl_list_for_each(local, &output->mode_list, link) { + if ((local->width == target->width) && (local->height == target->height)) + return local; + } + + return pipewire_insert_new_mode(output, + target->width, target->height, + target->refresh); +} + +static int +pipewire_switch_mode(struct weston_output *base, struct weston_mode *target_mode) +{ + struct pipewire_output *output = to_pipewire_output(base); + struct weston_mode *local_mode; + struct weston_size fb_size; + + assert(output); + + local_mode = pipewire_ensure_matching_mode(base, target_mode); + + base->current_mode->flags &= ~WL_OUTPUT_MODE_CURRENT; + + base->current_mode = base->native_mode = local_mode; + base->current_mode->flags |= WL_OUTPUT_MODE_CURRENT; + + fb_size.width = target_mode->width; + fb_size.height = target_mode->height; + + weston_renderer_resize_output(base, &fb_size, NULL); + + return 0; +} + +static int +pipewire_output_set_size(struct weston_output *base, int width, int height) +{ + struct pipewire_output *output = to_pipewire_output(base); + struct weston_head *head; + struct pipewire_head *pw_head; + struct weston_mode *current_mode; + struct weston_mode init_mode; + int framerate = -1; + + /* We can only be called once. */ + assert(!output->base.current_mode); + + wl_list_for_each(head, &output->base.head_list, output_link) { + pw_head = to_pipewire_head(head); + + if (width == -1) + width = pw_head->config.width; + if (height == -1) + height = pw_head->config.height; + framerate = pw_head->config.framerate; + } + if (framerate == -1 || width == -1 || height == -1) + return -1; + + init_mode.width = width; + init_mode.height = height; + init_mode.refresh = framerate * 1000; + + current_mode = pipewire_ensure_matching_mode(&output->base, &init_mode); + current_mode->flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; + + output->base.current_mode = output->base.native_mode = current_mode; + + output->base.start_repaint_loop = pipewire_output_start_repaint_loop; + output->base.repaint = pipewire_output_repaint; + output->base.assign_planes = NULL; + output->base.set_backlight = NULL; + output->base.set_dpms = NULL; + output->base.switch_mode = pipewire_switch_mode; + + return 0; +} + +static int +parse_gbm_format(const char *gbm_format, + const struct pixel_format_info *default_format, + const struct pixel_format_info **format) +{ + if (gbm_format == NULL) { + *format = default_format; + return 0; + } + + *format = pixel_format_get_info_by_drm_name(gbm_format); + if (!*format) { + weston_log("Invalid output format %s: using default format (%s)\n", + gbm_format, default_format->drm_format_name); + *format = default_format; + } + + return 0; +} + +static void +pipewire_output_set_gbm_format(struct weston_output *base, const char *gbm_format) +{ + struct pipewire_output *output = to_pipewire_output(base); + struct pipewire_backend *backend = output->backend; + + parse_gbm_format(gbm_format, backend->pixel_format, + &output->pixel_format); +} + +static const struct weston_pipewire_output_api api = { + pipewire_head_create, + pipewire_output_set_size, + pipewire_output_set_gbm_format, +}; + +static int +weston_pipewire_loop_handler(int fd, uint32_t mask, void *data) +{ + struct pipewire_backend *pipewire = data; + int ret; + + ret = pw_loop_iterate(pipewire->loop, 0); + if (ret < 0) + weston_log("pipewire_loop_iterate failed: %s\n", + spa_strerror(ret)); + + return 0; +} + +static void +weston_pipewire_error(void *data, uint32_t id, int seq, int res, + const char *error) +{ + weston_log("PipeWire remote error: %s\n", error); +} + +static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .error = weston_pipewire_error, +}; + +static int +weston_pipewire_init(struct pipewire_backend *backend) +{ + struct wl_event_loop *loop; + + pw_init(NULL, NULL); + + backend->loop = pw_loop_new(NULL); + if (!backend->loop) + return -1; + + pw_loop_enter(backend->loop); + + backend->context = pw_context_new(backend->loop, NULL, 0); + if (!backend->context) { + weston_log("Failed to create PipeWire context\n"); + goto err_loop; + } + + backend->core = pw_context_connect(backend->context, NULL, 0); + if (!backend->core) { + weston_log("Failed to connect to PipeWire context\n"); + goto err_context; + } + + pw_core_add_listener(backend->core, + &backend->core_listener, &core_events, backend); + + loop = wl_display_get_event_loop(backend->compositor->wl_display); + backend->loop_source = + wl_event_loop_add_fd(loop, pw_loop_get_fd(backend->loop), + WL_EVENT_READABLE, + weston_pipewire_loop_handler, + backend); + + return 0; + +err_context: + pw_context_destroy(backend->context); + backend->context = NULL; +err_loop: + pw_loop_leave(backend->loop); + pw_loop_destroy(backend->loop); + backend->loop = NULL; + + return -1; +} + +static void +pipewire_backend_create_outputs(struct pipewire_backend *backend, + int num_outputs) +{ + char name[32] = "pipewire"; + int i; + + for (i = 0; i < num_outputs; i++) { + if (num_outputs > 1) + snprintf(name, sizeof name, "pipewire-%u", i); + pipewire_head_create(&backend->base, name, &default_config); + } +} + +static struct pipewire_backend * +pipewire_backend_create(struct weston_compositor *compositor, + struct weston_pipewire_backend_config *config) +{ + struct pipewire_backend *backend; + int ret; + + backend = zalloc(sizeof *backend); + if (backend == NULL) + return NULL; + + backend->compositor = compositor; + backend->base.destroy = pipewire_destroy; + backend->base.create_output = pipewire_create_output; + + compositor->backend = &backend->base; + + if (weston_compositor_set_presentation_clock_software(compositor) < 0) + goto err_compositor; + + switch (config->renderer) { + case WESTON_RENDERER_AUTO: + case WESTON_RENDERER_PIXMAN: + break; + default: + weston_log("Unsupported renderer requested\n"); + goto err_compositor; + } + + if (weston_compositor_init_renderer(compositor, WESTON_RENDERER_PIXMAN, + NULL) < 0) + goto err_compositor; + + compositor->capabilities |= WESTON_CAP_ARBITRARY_MODES; + + ret = weston_pipewire_init(backend); + if (ret < 0) { + weston_log("Failed to initialize PipeWire\n"); + goto err_compositor; + } + + ret = weston_plugin_api_register(compositor, WESTON_PIPEWIRE_OUTPUT_API_NAME, + &api, sizeof(api)); + if (ret < 0) { + weston_log("Failed to register PipeWire output API\n"); + goto err_compositor; + } + + parse_gbm_format(config->gbm_format, + pixel_format_get_info(DRM_FORMAT_XRGB8888), + &backend->pixel_format); + + pipewire_backend_create_outputs(backend, config->num_outputs); + + return backend; + +err_compositor: + weston_compositor_shutdown(compositor); + + free(backend); + return NULL; +} + +static void +config_init_to_defaults(struct weston_pipewire_backend_config *config) +{ + config->gbm_format = "xrgb8888"; + config->num_outputs = 1; +} + +WL_EXPORT int +weston_backend_init(struct weston_compositor *compositor, + struct weston_backend_config *config_base) +{ + struct pipewire_backend *backend; + struct weston_pipewire_backend_config config = {{ 0, }}; + + weston_log("Initializing PipeWire backend\n"); + + if (config_base == NULL || + config_base->struct_version != WESTON_PIPEWIRE_BACKEND_CONFIG_VERSION || + config_base->struct_size > sizeof(struct weston_pipewire_backend_config)) { + weston_log("PipeWire backend config structure is invalid\n"); + return -1; + } + + config_init_to_defaults(&config); + memcpy(&config, config_base, config_base->struct_size); + + backend = pipewire_backend_create(compositor, &config); + if (backend == NULL) + return -1; + + backend->debug = + weston_compositor_add_log_scope(compositor, "pipewire", + "Debug messages from pipewire backend\n", + NULL, NULL, NULL); + + return 0; +} diff --git a/libweston/backend-rdp/meson.build b/libweston/backend-rdp/meson.build index 4f34fdbb8..9b2ed0301 100644 --- a/libweston/backend-rdp/meson.build +++ b/libweston/backend-rdp/meson.build @@ -4,24 +4,37 @@ 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', + 'rdpdisp.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 8e333f4c4..849267459 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -31,103 +31,46 @@ #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 "shared/xalloc.h" #include #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; +/* 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 - 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; +extern PWtsApiFunctionTable FreeRDP_InitWtsApi(void); - struct rdp_peers_item item; -}; -typedef struct rdp_peer_context RdpPeerContext; +static BOOL +xf_peer_adjust_monitor_layout(freerdp_peer *client); -static inline struct rdp_head * -to_rdp_head(struct weston_head *base) +static struct rdp_output * +rdp_get_first_output(struct rdp_backend *b) { - return container_of(base, struct rdp_head, base); -} + struct weston_output *output; + struct rdp_output *rdp_output; -static inline struct rdp_output * -to_rdp_output(struct weston_output *base) -{ - return container_of(base, struct rdp_output, base); -} + wl_list_for_each(output, &b->compositor->output_list, link) { + rdp_output = to_rdp_output(output); + if (rdp_output) + return rdp_output; + } -static inline struct rdp_backend * -to_rdp_backend(struct weston_compositor *base) -{ - return container_of(base->backend, struct rdp_backend, base); + return NULL; } static void @@ -137,7 +80,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 +97,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 +134,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 +151,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 +185,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 +210,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; @@ -303,15 +246,20 @@ static void 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; + struct rdp_output *output = rdp_get_first_output(context->rdpBackend); + rdpSettings *settings = peer->context->settings; + const struct weston_renderer *renderer; + pixman_image_t *image; + + renderer = output->base.compositor->renderer; + image = renderer->pixman->renderbuffer_get_image(output->renderbuffer); if (settings->RemoteFxCodec) - rdp_peer_refresh_rfx(region, output->shadow_surface, peer); + rdp_peer_refresh_rfx(region, image, peer); else if (settings->NSCodec) - rdp_peer_refresh_nsc(region, output->shadow_surface, peer); + rdp_peer_refresh_nsc(region, image, peer); else - rdp_peer_refresh_raw(region, output->shadow_surface, peer); + rdp_peer_refresh_raw(region, image, peer); } static int @@ -326,30 +274,55 @@ 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 = output->backend; + 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); + ec->renderer->repaint_output(&output->base, damage, + output->renderbuffer); 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)) - { - rdp_peer_refresh_region(damage, outputPeer->peer); + pixman_region32_t transformed_damage; + pixman_region32_init(&transformed_damage); + weston_region_global_to_output(&transformed_damage, + output_base, + damage); + wl_list_for_each(peer, &b->peers, link) { + if ((peer->flags & RDP_PEER_ACTIVATED) && + (peer->flags & RDP_PEER_OUTPUT_ENABLED)) { + rdp_peer_refresh_region(&transformed_damage, peer->peer); } } + pixman_region32_fini(&transformed_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; } @@ -365,184 +338,157 @@ finish_frame_handler(void *data) return 1; } -static struct weston_mode * -rdp_insert_new_mode(struct weston_output *output, int width, int height, int rate) -{ - struct weston_mode *ret; - ret = zalloc(sizeof *ret); - if (!ret) - return NULL; - ret->width = width; - ret->height = height; - ret->refresh = rate; - wl_list_insert(&output->mode_list, &ret->link); - return ret; -} - -static struct weston_mode * -ensure_matching_mode(struct weston_output *output, struct weston_mode *target) -{ - struct weston_mode *local; - - wl_list_for_each(local, &output->mode_list, link) { - if ((local->width == target->width) && (local->height == target->height)) - return local; - } - - return rdp_insert_new_mode(output, target->width, target->height, RDP_MODE_FREQ); -} - -static int -rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) +static void +rdp_output_set_mode(struct weston_output *base, struct weston_mode *mode) { - struct rdp_output *rdpOutput = container_of(output, struct rdp_output, base); + struct rdp_output *rdpOutput = container_of(base, struct rdp_output, base); + struct rdp_backend *b = rdpOutput->backend; + struct weston_output *output = base; 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, }; - - 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); - return -ENOENT; + struct weston_renderbuffer *new_renderbuffer; + + mode->refresh = b->rdp_monitor_refresh_rate; + weston_output_set_single_mode(base, mode); + + if (base->enabled) { + const struct pixman_renderer_interface *pixman; + const struct pixel_format_info *pfmt; + pixman_image_t *old_image, *new_image; + + weston_renderer_resize_output(output, &(struct weston_size){ + .width = output->current_mode->width, + .height = output->current_mode->height }, NULL); + + pixman = b->compositor->renderer->pixman; + + old_image = + pixman->renderbuffer_get_image(rdpOutput->renderbuffer); + pfmt = pixel_format_get_info_by_pixman(PIXMAN_x8r8g8b8); + new_renderbuffer = + pixman->create_image_from_ptr(output, pfmt, + mode->width, mode->height, + 0, mode->width * 4); + new_image = pixman->renderbuffer_get_image(new_renderbuffer); + pixman_image_composite32(PIXMAN_OP_SRC, old_image, 0, new_image, + 0, 0, 0, 0, 0, 0, mode->width, + mode->height); + weston_renderbuffer_unref(rdpOutput->renderbuffer); + rdpOutput->renderbuffer = new_renderbuffer; } - if (local_mode == output->current_mode) - return 0; - - output->current_mode->flags &= ~WL_OUTPUT_MODE_CURRENT; - - output->current_mode = local_mode; - output->current_mode->flags |= WL_OUTPUT_MODE_CURRENT; - - pixman_renderer_output_destroy(output); - pixman_renderer_output_create(output, &options); - - new_shadow_buffer = pixman_image_create_bits(PIXMAN_x8r8g8b8, target_mode->width, - target_mode->height, 0, target_mode->width * 4); - pixman_image_composite32(PIXMAN_OP_SRC, rdpOutput->shadow_surface, 0, new_shadow_buffer, - 0, 0, 0, 0, 0, 0, target_mode->width, target_mode->height); - pixman_image_unref(rdpOutput->shadow_surface); - rdpOutput->shadow_surface = new_shadow_buffer; - - wl_list_for_each(rdpPeer, &rdpOutput->peers, link) { - settings = rdpPeer->peer->settings; - if (settings->DesktopWidth == (UINT32)target_mode->width && - settings->DesktopHeight == (UINT32)target_mode->height) + /* Apparently settings->DesktopWidth is supposed to be primary only. + * For now we only work with a single monitor, so we don't need to + * check that we're primary here. + */ + wl_list_for_each(rdpPeer, &b->peers, link) { + settings = rdpPeer->peer->context->settings; + if (settings->DesktopWidth == (uint32_t)mode->width && + settings->DesktopHeight == (uint32_t)mode->height) continue; if (!settings->DesktopResize) { /* too bad this peer does not support desktop resize */ + weston_log("desktop resize is not allowed\n"); rdpPeer->peer->Close(rdpPeer->peer); } else { - settings->DesktopWidth = target_mode->width; - settings->DesktopHeight = target_mode->height; - rdpPeer->peer->update->DesktopResize(rdpPeer->peer->context); + settings->DesktopWidth = mode->width; + settings->DesktopHeight = mode->height; + rdpPeer->peer->context->update->DesktopResize(rdpPeer->peer->context); } } - return 0; } static int -rdp_output_set_size(struct weston_output *base, - int width, int height) +rdp_output_switch_mode(struct weston_output *base, struct weston_mode *mode) { - struct rdp_output *output = to_rdp_output(base); - struct weston_head *head; - struct weston_mode *currentMode; - struct weston_mode initMode; - - /* We can only be called once. */ - assert(!output->base.current_mode); + rdp_output_set_mode(base, mode); - wl_list_for_each(head, &output->base.head_list, output_link) { - weston_head_set_monitor_strings(head, "weston", "rdp", NULL); - - /* This is a virtual output, so report a zero physical size. - * It's better to let frontends/clients use their defaults. */ - 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; - - currentMode = ensure_matching_mode(&output->base, &initMode); - if (!currentMode) - return -1; - - output->base.current_mode = output->base.native_mode = currentMode; + return 0; +} - output->base.start_repaint_loop = rdp_output_start_repaint_loop; - output->base.repaint = rdp_output_repaint; - output->base.assign_planes = NULL; - output->base.set_backlight = NULL; - output->base.set_dpms = NULL; - output->base.switch_mode = rdp_switch_mode; +static void +rdp_head_get_monitor(struct weston_head *base, + struct weston_rdp_monitor *monitor) +{ + struct rdp_head *h = to_rdp_head(base); - return 0; + monitor->x = h->config.x; + monitor->y = h->config.y; + monitor->width = h->config.width; + monitor->height = h->config.height; + monitor->desktop_scale = h->config.attributes.desktopScaleFactor; } static int rdp_output_enable(struct weston_output *base) { + const struct weston_renderer *renderer = base->compositor->renderer; + const struct pixman_renderer_interface *pixman = renderer->pixman; 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, + .fb_size = { + .width = output->base.current_mode->width, + .height = output->base.current_mode->height + }, + .format = pixel_format_get_info_by_pixman(PIXMAN_x8r8g8b8) }; - output->shadow_surface = pixman_image_create_bits(PIXMAN_x8r8g8b8, - output->base.current_mode->width, - output->base.current_mode->height, - NULL, - output->base.current_mode->width * 4); - if (output->shadow_surface == NULL) { - weston_log("Failed to create surface for frame buffer.\n"); + assert(output); + + b = output->backend; + + if (renderer->pixman->output_create(&output->base, &options) < 0) { return -1; } - if (pixman_renderer_output_create(&output->base, &options) < 0) { - pixman_image_unref(output->shadow_surface); + output->renderbuffer = + pixman->create_image_from_ptr(&output->base, options.format, + output->base.current_mode->width, + output->base.current_mode->height, + NULL, + output->base.current_mode->width * 4); + if (output->renderbuffer == NULL) { + weston_log("Failed to create surface for frame buffer.\n"); + renderer->pixman->output_destroy(&output->base); return -1; } loop = wl_display_get_event_loop(b->compositor->wl_display); output->finish_frame_timer = wl_event_loop_add_timer(loop, finish_frame_handler, output); - b->output = output; - return 0; } static int rdp_output_disable(struct weston_output *base) { + struct weston_renderer *renderer = base->compositor->renderer; struct rdp_output *output = to_rdp_output(base); - struct rdp_backend *b = to_rdp_backend(base->compositor); + + assert(output); if (!output->base.enabled) return 0; - pixman_image_unref(output->shadow_surface); - pixman_renderer_output_destroy(&output->base); + weston_renderbuffer_unref(output->renderbuffer); + output->renderbuffer = NULL; + renderer->pixman->output_destroy(&output->base); wl_event_source_remove(output->finish_frame_timer); - b->output = NULL; 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); @@ -550,58 +496,89 @@ rdp_output_destroy(struct weston_output *base) } static struct weston_output * -rdp_output_create(struct weston_compositor *compositor, const char *name) +rdp_output_create(struct weston_backend *backend, const char *name) { + struct rdp_backend *b = container_of(backend, struct rdp_backend, base); + struct weston_compositor *compositor = b->compositor; struct rdp_output *output; - output = zalloc(sizeof *output); - if (output == NULL) - return NULL; + output = xzalloc(sizeof *output); weston_output_init(&output->base, compositor, name); output->base.destroy = rdp_output_destroy; output->base.disable = rdp_output_disable; output->base.enable = rdp_output_enable; - output->base.attach_head = NULL; + + output->base.start_repaint_loop = rdp_output_start_repaint_loop; + output->base.repaint = rdp_output_repaint; + output->base.switch_mode = rdp_output_switch_mode; + + output->backend = b; weston_compositor_add_pending_output(&output->base, compositor); return &output->base; } -static int -rdp_head_create(struct weston_compositor *compositor, const char *name) +void +rdp_head_create(struct rdp_backend *backend, rdpMonitor *config) { struct rdp_head *head; + char name[13] = {}; /* "rdp-" + 8 chars for hex uint32_t + NULL. */ + + head = xzalloc(sizeof *head); + head->index = backend->head_index++; + if (config) + head->config = *config; + else { + /* Before any client conenctions we create a default head + * with no configuration. Make it the primary, and make + * it avoid the high dpi scaling paths. + */ + head->config.is_primary = true; + head->config.attributes.desktopScaleFactor = 0; + } - head = zalloc(sizeof *head); - if (!head) - return -1; + sprintf(name, "rdp-%x", head->index); weston_head_init(&head->base, name); - weston_head_set_connection_status(&head->base, true); - weston_compositor_add_head(compositor, &head->base); + weston_head_set_monitor_strings(&head->base, "weston", "rdp", NULL); - return 0; + if (config) + weston_head_set_physical_size(&head->base, + config->attributes.physicalWidth, + config->attributes.physicalHeight); + else + weston_head_set_physical_size(&head->base, 0, 0); + + head->base.backend = &backend->base; + + weston_head_set_connection_status(&head->base, true); + weston_compositor_add_head(backend->compositor, &head->base); } -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); } -static void -rdp_destroy(struct weston_compositor *ec) +void +rdp_destroy(struct weston_backend *backend) { - struct rdp_backend *b = to_rdp_backend(ec); + struct rdp_backend *b = container_of(backend, struct rdp_backend, base); + struct weston_compositor *ec = b->compositor; struct weston_head *base, *next; 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 +590,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 +643,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 +672,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 +706,34 @@ rdp_peer_context_new(freerdp_peer* client, RdpPeerContext* context) 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 +752,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 +806,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 +846,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 +899,74 @@ 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 void +rdp_full_refresh(freerdp_peer *peer, struct rdp_output *output) +{ + pixman_box32_t box; + pixman_region32_t damage; + + box.x1 = 0; + box.y1 = 0; + box.x2 = output->base.current_mode->width; + box.y2 = output->base.current_mode->height; + pixman_region32_init_with_extents(&damage, &box); + + rdp_peer_refresh_region(&damage, peer); + + pixman_region32_fini(&damage); +} static BOOL xf_peer_activate(freerdp_peer* client) @@ -895,17 +980,15 @@ 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]; POINTER_SYSTEM_UPDATE pointer_system; + int width, height; peerCtx = (RdpPeerContext *)client->context; b = peerCtx->rdpBackend; peersItem = &peerCtx->item; - output = b->output; - settings = client->settings; + output = rdp_get_first_output(b); + settings = client->context->settings; if (!settings->SurfaceCommandsEnabled) { weston_log("client doesn't support required SurfaceCommands\n"); @@ -913,65 +996,69 @@ 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; } - if (output->base.width != (int)settings->DesktopWidth || - output->base.height != (int)settings->DesktopHeight) - { - if (b->no_clients_resize) { - /* RDP peers don't dictate their resolution to weston */ + 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 we don't allow resize, we need to tell the client to resize itself. + * We still need the xf_peer_adjust_monitor_layout() call to make sure + * we've set up scaling appropriately. + */ + if (b->no_clients_resize) { + struct weston_mode *mode = output->base.current_mode; + + if (mode->width != (int)settings->DesktopWidth || + mode->height != (int)settings->DesktopHeight) { if (!settings->DesktopResize) { /* peer does not support desktop resize */ - weston_log("%s: client doesn't support resizing, closing connection\n", __FUNCTION__); + weston_log("client doesn't support resizing, closing connection\n"); return FALSE; } else { - settings->DesktopWidth = output->base.width; - settings->DesktopHeight = output->base.height; - client->update->DesktopResize(client->context); - } - } else { - /* ask weston to adjust size */ - struct weston_mode new_mode; - struct weston_mode *target_mode; - new_mode.width = (int)settings->DesktopWidth; - new_mode.height = (int)settings->DesktopHeight; - target_mode = ensure_matching_mode(&output->base, &new_mode); - if (!target_mode) { - weston_log("client mode not found\n"); - return FALSE; + settings->DesktopWidth = mode->width; + settings->DesktopHeight = mode->height; + client->context->update->DesktopResize(client->context); } - weston_output_mode_set_native(&output->base, target_mode, 1); - output->base.width = new_mode.width; - output->base.height = new_mode.height; } + } else { + xf_peer_adjust_monitor_layout(client); } weston_output = &output->base; - rfx_context_reset(peerCtx->rfx_context, weston_output->width, weston_output->height); - nsc_context_reset(peerCtx->nsc_context, weston_output->width, weston_output->height); + width = weston_output->width * weston_output->scale; + height = weston_output->height * weston_output->scale; + rfx_context_reset(peerCtx->rfx_context, width, height); + nsc_context_reset(peerCtx->nsc_context, width, height); if (peersItem->flags & RDP_PEER_ACTIVATED) 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,25 +1083,33 @@ 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); - /* sends a full refresh */ - box.x1 = 0; - box.y1 = 0; - box.x2 = output->base.width; - box.y2 = output->base.height; - pixman_region32_init_with_extents(&damage, &box); + rdp_full_refresh(client, output); - rdp_peer_refresh_region(&damage, client); + return TRUE; - pixman_region32_fini(&damage); +error_exit: - return TRUE; + 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,23 +1118,191 @@ xf_peer_post_connect(freerdp_peer *client) return TRUE; } +static bool +rdp_translate_and_notify_mouse_position(RdpPeerContext *peerContext, UINT16 x, UINT16 y) +{ + struct weston_coord_global pos; + struct timespec time; + int sx, sy; + + if (!peerContext->item.seat) + return FALSE; + + /* (TS_POINTERX_EVENT):The xy-coordinate of the pointer relative to the top-left + * corner of the server's desktop combined all monitors */ + + /* first, convert the coordinate based on primary monitor's upper-left as (0,0) */ + sx = x + peerContext->desktop_left; + sy = y + peerContext->desktop_top; + + /* translate client's x/y to the coordinate in weston space. */ + /* TODO: to_weston_coordinate() is translate based on where pointer is, + not based-on where/which window underneath. Thus, this doesn't + work when window lays across more than 2 monitors and each monitor has + different scaling. In such case, hit test to that window area on + non primary-resident monitor (surface->output) dosn't work. */ + if (to_weston_coordinate(peerContext, &sx, &sy)) { + pos.c = weston_coord(sx, sy); + weston_compositor_get_time(&time); + notify_motion_absolute(peerContext->item.seat, &time, pos); + return TRUE; + } + return FALSE; +} + +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) { RdpPeerContext *peerContext = (RdpPeerContext *)input->context; - struct rdp_output *output; uint32_t button = 0; bool need_frame = false; struct timespec time; - if (flags & PTR_FLAGS_MOVE) { - 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); + 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))) { + if (rdp_translate_and_notify_mouse_position(peerContext, x, y)) need_frame = true; - } } if (flags & PTR_FLAGS_BUTTON1) @@ -1049,6 +1312,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 +1326,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 +1347,43 @@ 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; + struct weston_coord_global pos; + + 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); + } - output = peerContext->rdpBackend->output; + 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 = rdp_get_first_output(peerContext->rdpBackend); if (x < output->base.width && y < output->base.height) { weston_compositor_get_time(&time); - notify_motion_absolute(peerContext->item.seat, &time, x, y); + pos.c = weston_coord(x, y); + notify_motion_absolute(peerContext->item.seat, &time, pos); + need_frame = true; } + if (need_frame) + notify_pointer_frame(peerContext->item.seat); + return TRUE; } @@ -1110,20 +1393,32 @@ xf_input_synchronize_event(rdpInput *input, UINT32 flags) { freerdp_peer *client = input->context->peer; RdpPeerContext *peerCtx = (RdpPeerContext *)input->context; - struct rdp_output *output = peerCtx->rdpBackend->output; - pixman_box32_t box; - pixman_region32_t damage; - - /* sends a full refresh */ - box.x1 = 0; - box.y1 = 0; - box.x2 = output->base.width; - box.y2 = output->base.height; - pixman_region32_init_with_extents(&damage, &box); + struct rdp_backend *b = peerCtx->rdpBackend; + struct rdp_output *output = rdp_get_first_output(b); + struct weston_keyboard *keyboard; + + 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); + } - rdp_peer_refresh_region(&damage, client); + rdp_full_refresh(client, output); - pixman_region32_fini(&damage); return TRUE; } @@ -1133,7 +1428,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 +1450,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 +1489,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 +1504,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; } @@ -1190,11 +1526,90 @@ xf_suppress_output(rdpContext *context, BYTE allow, const RECTANGLE_16 *area) return TRUE; } +static BOOL +xf_peer_adjust_monitor_layout(freerdp_peer *client) +{ + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + rdpSettings *settings = client->context->settings; + rdpMonitor *monitors; + unsigned int monitor_count; + BOOL success; + bool fallback = false; + unsigned int i; + + rdp_debug(b, "%s:\n", __func__); + rdp_debug(b, " DesktopWidth:%d, DesktopHeight:%d\n", settings->DesktopWidth, settings->DesktopHeight); + rdp_debug(b, " UseMultimon:%d\n", settings->UseMultimon); + rdp_debug(b, " ForceMultimon:%d\n", settings->ForceMultimon); + rdp_debug(b, " MonitorCount:%d\n", settings->MonitorCount); + rdp_debug(b, " HasMonitorAttributes:%d\n", settings->HasMonitorAttributes); + rdp_debug(b, " HiDefRemoteApp:%d\n", settings->HiDefRemoteApp); + + if (settings->MonitorCount > 1) { + weston_log("multiple monitor is not supported"); + fallback = true; + } + + if (b->no_clients_resize) + fallback = true; + + if (settings->MonitorCount > RDP_MAX_MONITOR) { + weston_log("Client reports more monitors then expected:(%d)\n", + settings->MonitorCount); + return FALSE; + } + if ((settings->MonitorCount > 0 && settings->MonitorDefArray) && !fallback) { + rdpMonitor *rdp_monitor = settings->MonitorDefArray; + monitor_count = settings->MonitorCount; + monitors = xmalloc(sizeof(*monitors) * monitor_count); + for (i = 0; i < monitor_count; i++) { + monitors[i] = rdp_monitor[i]; + if (!settings->HasMonitorAttributes) { + monitors[i].attributes.physicalWidth = 0; + monitors[i].attributes.physicalHeight = 0; + monitors[i].attributes.orientation = ORIENTATION_LANDSCAPE; + monitors[i].attributes.desktopScaleFactor = 100; + monitors[i].attributes.deviceScaleFactor = 100; + } + } + } else { + monitor_count = 1; + monitors = xmalloc(sizeof(*monitors) * monitor_count); + /* when no monitor array provided, generate from desktop settings */ + monitors[0].x = 0; + monitors[0].y = 0; + monitors[0].width = settings->DesktopWidth; + monitors[0].height = settings->DesktopHeight; + monitors[0].is_primary = 1; + monitors[0].attributes.physicalWidth = settings->DesktopPhysicalWidth; + monitors[0].attributes.physicalHeight = settings->DesktopPhysicalHeight; + monitors[0].attributes.orientation = settings->DesktopOrientation; + monitors[0].attributes.desktopScaleFactor = settings->DesktopScaleFactor; + monitors[0].attributes.deviceScaleFactor = settings->DeviceScaleFactor; + monitors[0].orig_screen = 0; + + if (b->no_clients_resize) { + /* If we're not allowing clients to resize us, set these + * to 0 so the front end knows it needs to make something + * up. + */ + monitors[0].width = 0; + monitors[0].height = 0; + monitors[0].attributes.desktopScaleFactor = 0; + } + } + success = handle_adjust_monitor_layout(client, monitor_count, monitors); + + free(monitors); + return success; +} + 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 +1624,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 +1645,76 @@ 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; + if (!b->no_clients_resize) { + settings->SupportMonitorLayoutPdu = TRUE; + client->AdjustMonitorsLayout = xf_peer_adjust_monitor_layout; + } + + 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; @@ -1285,7 +1734,8 @@ rdp_incoming_peer(freerdp_listener *instance, freerdp_peer *client) } static const struct weston_rdp_output_api api = { - rdp_output_set_size, + rdp_head_get_monitor, + rdp_output_set_mode, }; static struct rdp_backend * @@ -1299,37 +1749,87 @@ rdp_backend_create(struct weston_compositor *compositor, struct weston_head *base, *next; - b = zalloc(sizeof *b); - if (b == NULL) - return NULL; - + b = xzalloc(sizeof *b); + 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; } + switch (config->renderer) { + case WESTON_RENDERER_PIXMAN: + case WESTON_RENDERER_AUTO: + break; + default: + weston_log("Unsupported renderer requested\n"); + goto err_free_strings; + } + + /* 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; - if (pixman_renderer_init(compositor) < 0) + if (weston_compositor_init_renderer(compositor, WESTON_RENDERER_PIXMAN, + NULL) < 0) goto err_compositor; - if (rdp_head_create(compositor, "rdp") < 0) - goto err_compositor; + rdp_head_create(b, NULL); compositor->capabilities |= WESTON_CAP_ARBITRARY_MODES; @@ -1337,25 +1837,34 @@ 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"); if (!fd_str) { weston_log("RDP_FD env variable not set\n"); - goto err_output; + goto err_compositor; } fd = strtoul(fd_str, &fd_tail, 10); if (errno != 0 || fd_tail == fd_str || *fd_tail != '\0' || rdp_peer_init(freerdp_peer_new(fd), b)) - goto err_output; + goto err_compositor; } ret = weston_plugin_api_register(compositor, WESTON_RDP_OUTPUT_API_NAME, @@ -1363,22 +1872,29 @@ rdp_backend_create(struct weston_compositor *compositor, if (ret < 0) { weston_log("Failed to register output API.\n"); - goto err_output; + goto err_listener; } return b; err_listener: freerdp_listener_free(b->listener); -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); @@ -1389,6 +1905,7 @@ rdp_backend_create(struct weston_compositor *compositor, static void config_init_to_defaults(struct weston_rdp_backend_config *config) { + config->renderer = WESTON_RENDERER_AUTO; config->bind_address = NULL; config->port = 3389; config->rdp_key = NULL; @@ -1397,6 +1914,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 +1947,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 000000000..11b127346 --- /dev/null +++ b/libweston/backend-rdp/rdp.h @@ -0,0 +1,281 @@ +/* + * 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 RDP_MAX_MONITOR 16 +#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_backend { + struct weston_backend base; + struct weston_compositor *compositor; + + freerdp_listener *listener; + struct wl_event_source *listener_events[MAX_FREERDP_FDS]; + 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; + + uint32_t head_index; +}; + +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; + uint32_t index; + bool matched; + rdpMonitor config; +}; + +struct rdp_output { + struct weston_output base; + struct rdp_backend *backend; + struct wl_event_source *finish_frame_timer; + struct weston_renderbuffer *renderbuffer; +}; + +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; + + /* Multiple monitor support (monitor topology) */ + int32_t desktop_top, desktop_left; + int32_t desktop_width, desktop_height; +}; + +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__) + +/* rdpdisp.c */ +bool +handle_adjust_monitor_layout(freerdp_peer *client, + int monitor_count, rdpMonitor *monitors); + +struct weston_output * +to_weston_coordinate(RdpPeerContext *peerContext, + int32_t *x, int32_t *y); + +/* 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); + +/* rdp.c */ +void +rdp_head_create(struct rdp_backend *backend, rdpMonitor *config); + +void +rdp_destroy(struct weston_backend *backend); + +void +rdp_head_destroy(struct weston_head *base); + +static inline struct rdp_head * +to_rdp_head(struct weston_head *base) +{ + if (base->backend->destroy != rdp_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); +} + +#endif diff --git a/libweston/backend-rdp/rdpclip.c b/libweston/backend-rdp/rdpclip.c new file mode 100644 index 000000000..e08f4ca21 --- /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/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c new file mode 100644 index 000000000..3e2ee210c --- /dev/null +++ b/libweston/backend-rdp/rdpdisp.c @@ -0,0 +1,363 @@ +/* + * 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 "rdp.h" + +#include "shared/xalloc.h" + +static bool +match_primary(struct rdp_backend *rdp, rdpMonitor *a, rdpMonitor *b) +{ + if (a->is_primary && b->is_primary) + return true; + + return false; +} + +static bool +match_dimensions(struct rdp_backend *rdp, rdpMonitor *a, rdpMonitor *b) +{ + int scale_a = a->attributes.desktopScaleFactor; + int scale_b = b->attributes.desktopScaleFactor; + + + if (a->width != b->width || + a->height != b->height || + scale_a != scale_b) + return false; + + return true; +} + +static bool +match_position(struct rdp_backend *rdp, rdpMonitor *a, rdpMonitor *b) +{ + if (a->x != b->x || + a->y != b->y) + return false; + + return true; +} + +static bool +match_exact(struct rdp_backend *rdp, rdpMonitor *a, rdpMonitor *b) +{ + if (match_dimensions(rdp, a, b) && + match_position(rdp, a, b)) + return true; + + return false; +} + +static bool +match_any(struct rdp_backend *rdp, rdpMonitor *a, rdpMonitor *b) +{ + return true; +} + +static void +update_head(struct rdp_backend *rdp, struct rdp_head *head, rdpMonitor *config) +{ + struct weston_mode mode = {}; + int scale; + bool changed = false; + + head->matched = true; + scale = config->attributes.desktopScaleFactor / 100; + scale = scale ? scale : 1; + + if (!match_position(rdp, &head->config, config)) + changed = true; + + if (!match_dimensions(rdp, &head->config, config)) { + mode.flags = WL_OUTPUT_MODE_PREFERRED; + mode.width = config->width; + mode.height = config->height; + mode.refresh = rdp->rdp_monitor_refresh_rate; + weston_output_mode_set_native(head->base.output, + &mode, scale); + changed = true; + } + + if (changed) { + weston_head_set_device_changed(&head->base); + } + head->config = *config; +} + +static void +match_heads(struct rdp_backend *rdp, rdpMonitor *config, uint32_t count, + int *done, + bool (*cmp)(struct rdp_backend *rdp, rdpMonitor *a, rdpMonitor *b)) +{ + struct weston_head *iter; + struct rdp_head *current; + uint32_t i; + + wl_list_for_each(iter, &rdp->compositor->head_list, compositor_link) { + current = to_rdp_head(iter); + if (!current || current->matched) + continue; + + for (i = 0; i < count; i++) { + if (*done & (1 << i)) + continue; + + if (cmp(rdp, ¤t->config, &config[i])) { + *done |= 1 << i; + update_head(rdp, current, &config[i]); + break; + } + } + } +} + +static void +disp_layout_change(freerdp_peer *client, rdpMonitor *config, UINT32 monitorCount) +{ + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + struct rdp_head *current; + struct weston_head *iter, *tmp; + pixman_region32_t desktop; + int done = 0; + + assert_compositor_thread(b); + + pixman_region32_init(&desktop); + + /* Prune heads that were never enabled, and flag heads as unmatched */ + wl_list_for_each_safe(iter, tmp, &b->compositor->head_list, compositor_link) { + current = to_rdp_head(iter); + if (!current) + continue; + + if (!iter->output) { + rdp_head_destroy(iter); + continue; + } + current->matched = false; + } + + /* We want the primary head to remain primary - it + * should always be rdp-0. + */ + match_heads(b, config, monitorCount, &done, match_primary); + + /* Look for any exact match */ + match_heads(b, config, monitorCount, &done, match_exact); + + /* Match first head with the same dimensions */ + match_heads(b, config, monitorCount, &done, match_dimensions); + + /* Match head with the same position */ + match_heads(b, config, monitorCount, &done, match_position); + + /* Pick any available head */ + match_heads(b, config, monitorCount, &done, match_any); + + /* Destroy any heads we won't be using */ + wl_list_for_each_safe(iter, tmp, &b->compositor->head_list, compositor_link) { + current = to_rdp_head(iter); + if (!current) + continue; + + if (!current->matched) + rdp_head_destroy(iter); + } + + + for (uint32_t i = 0; i < monitorCount; i++) { + /* accumulate monitor layout */ + pixman_region32_union_rect(&desktop, &desktop, + config[i].x, + config[i].y, + config[i].width, + config[i].height); + + /* Create new heads for any without matches */ + if (!(done & (1 << i))) + rdp_head_create(b, &config[i]); + } + peerCtx->desktop_left = desktop.extents.x1; + peerCtx->desktop_top = desktop.extents.y1; + peerCtx->desktop_width = desktop.extents.x2 - desktop.extents.x1; + peerCtx->desktop_height = desktop.extents.y2 - desktop.extents.y1; + pixman_region32_fini(&desktop); +} + +static bool +disp_sanity_check_layout(RdpPeerContext *peerCtx, rdpMonitor *config, uint32_t count) +{ + struct rdp_backend *b = peerCtx->rdpBackend; + uint32_t primaryCount = 0; + uint32_t i; + + /* dump client monitor topology */ + rdp_debug(b, "%s:---INPUT---\n", __func__); + for (i = 0; i < count; i++) { + int scale = config[i].attributes.desktopScaleFactor / 100; + + rdp_debug(b, " rdpMonitor[%d]: x:%d, y:%d, width:%d, height:%d, is_primary:%d\n", + i, config[i].x, config[i].y, + config[i].width, config[i].height, + config[i].is_primary); + rdp_debug(b, " rdpMonitor[%d]: physicalWidth:%d, physicalHeight:%d, orientation:%d\n", + i, config[i].attributes.physicalWidth, + config[i].attributes.physicalHeight, + config[i].attributes.orientation); + rdp_debug(b, " rdpMonitor[%d]: desktopScaleFactor:%d, deviceScaleFactor:%d\n", + i, config[i].attributes.desktopScaleFactor, + config[i].attributes.deviceScaleFactor); + + rdp_debug(b, " rdpMonitor[%d]: scale:%d\n", + i, scale); + } + + for (i = 0; i < count; i++) { + /* make sure there is only one primary and its position at client */ + if (config[i].is_primary) { + /* count number of primary */ + if (++primaryCount > 1) { + weston_log("%s: RDP client reported unexpected primary count (%d)\n", + __func__, primaryCount); + return false; + } + /* primary must be at (0,0) in client space */ + if (config[i].x != 0 || config[i].y != 0) { + weston_log("%s: RDP client reported primary is not at (0,0) but (%d,%d).\n", + __func__, config[i].x, config[i].y); + return false; + } + } + } + return true; +} + +bool +handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor *monitors) +{ + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + + if (!disp_sanity_check_layout(peerCtx, monitors, monitor_count)) + return true; + + disp_layout_change(client, monitors, monitor_count); + + return true; +} + +static bool +rect_contains(int32_t px, int32_t py, int32_t rx, int32_t ry, + int32_t width, int32_t height) +{ + if (px < rx) + return false; + if (py < ry) + return false; + if (px >= rx + width) + return false; + if (py >= ry + height) + return false; + + return true; +} + +static bool +rdp_head_contains(struct rdp_head *rdp_head, int32_t x, int32_t y) +{ + rdpMonitor *config = &rdp_head->config; + + /* If we're forcing RDP desktop size then we don't have + * useful information in the monitor structs, but we + * can rely on the output settings in that case. + */ + if (config->width == 0) { + struct weston_head *head = &rdp_head->base; + struct weston_output *output = head->output; + + if (!output) + return false; + + return rect_contains(x, y, output->x, output->y, + output->width * output->scale, + output->height * output->scale); + } + + return rect_contains(x, y, config->x, config->y, + config->width, config->height); +} + +/* Input x/y in client space, output x/y in weston space */ +struct weston_output * +to_weston_coordinate(RdpPeerContext *peerContext, int32_t *x, int32_t *y) +{ + struct rdp_backend *b = peerContext->rdpBackend; + int sx = *x, sy = *y; + struct weston_head *head_iter; + + /* First, find which monitor contains this x/y. */ + wl_list_for_each(head_iter, &b->compositor->head_list, compositor_link) { + struct rdp_head *head = to_rdp_head(head_iter); + + if (!head) + continue; + + if (rdp_head_contains(head, sx, sy)) { + struct weston_output *output = head->base.output; + float scale = 1.0f / head->base.output->scale; + + sx -= head->config.x; + sy -= head->config.y; + /* scale x/y to client output space. */ + sx *= scale; + sy *= scale; + /* translate x/y to offset of this output in weston space. */ + sx += output->x; + sy += output->y; + rdp_debug_verbose(b, "%s: (x:%d, y:%d) -> (sx:%d, sy:%d) at head:%s\n", + __func__, *x, *y, sx, sy, head->base.name); + *x = sx; + *y = sy; + return output; + } + } + /* x/y is outside of any monitors. */ + return NULL; +} diff --git a/libweston/backend-rdp/rdputil.c b/libweston/backend-rdp/rdputil.c new file mode 100644 index 000000000..9aca3139b --- /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-vnc/meson.build b/libweston/backend-vnc/meson.build new file mode 100644 index 000000000..a57063568 --- /dev/null +++ b/libweston/backend-vnc/meson.build @@ -0,0 +1,32 @@ +if not get_option('backend-vnc') + subdir_done() +endif + +config_h.set('BUILD_VNC_COMPOSITOR', '1') +dep_neatvnc = dependency('neatvnc', version: ['>= 0.6.0', '< 0.7.0'], required: false, fallback: ['neatvnc', 'neatvnc_dep']) +if not dep_neatvnc.found() + error('VNC backend requires neatvnc which was not found. Or, you can use \'-Dbackend-vnc=false\'.') +endif + +dep_aml = dependency('aml', version: ['>= 0.3.0', '< 0.4.0'], required: false, fallback: ['aml', 'aml_dep']) +if not dep_aml.found() + error('VNC backend requires libaml which was not found. Or, you can use \'-Dbackend-vnc=false\'.') +endif + +deps_vnc = [ + dep_libweston_private, + dep_neatvnc, + dep_aml, + dep_libdrm_headers, +] +plugin_vnc = shared_library( + 'vnc-backend', + [ 'vnc.c' ], + include_directories: common_inc, + dependencies: deps_vnc, + name_prefix: '', + install: true, + install_dir: dir_module_libweston +) +env_modmap += 'vnc-backend.so=@0@;'.format(plugin_vnc.full_path()) +install_headers(backend_vnc_h, subdir: dir_include_libweston_install) diff --git a/libweston/backend-vnc/vnc.c b/libweston/backend-vnc/vnc.c new file mode 100644 index 000000000..25a5c97f2 --- /dev/null +++ b/libweston/backend-vnc/vnc.c @@ -0,0 +1,1170 @@ +/* + * Copyright © 2019-2020 Stefan Agner + * Copyright © 2021-2022 Pengutronix, Philipp Zabel + * Copyright © 2022 Pengutronix, Rouven Czerwinski + * based on backend-rdp: + * Copyright © 2013 Hardening + * + * 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 +#define AML_UNSTABLE_API 1 +#include +#include +#include + +#include "shared/helpers.h" +#include "shared/xalloc.h" +#include "shared/timespec-util.h" +#include +#include +#include +#include "pixel-formats.h" +#include "pixman-renderer.h" + +#define DEFAULT_AXIS_STEP_DISTANCE 10 + +struct vnc_output; + +struct vnc_backend { + struct weston_backend base; + struct weston_compositor *compositor; + struct weston_log_scope *debug; + struct vnc_output *output; + + struct xkb_rule_names xkb_rule_name; + struct xkb_keymap *xkb_keymap; + + struct aml *aml; + struct wl_event_source *aml_event; + struct nvnc *server; + int vnc_monitor_refresh_rate; +}; + +struct vnc_output { + struct weston_output base; + struct weston_plane cursor_plane; + struct weston_surface *cursor_surface; + struct vnc_backend *backend; + struct wl_event_source *finish_frame_timer; + struct nvnc_display *display; + + struct nvnc_fb_pool *fb_pool; + + struct wl_list peers; +}; + +struct vnc_peer { + struct vnc_backend *backend; + struct weston_seat *seat; + struct nvnc_client *client; + + enum nvnc_button_mask last_button_mask; + struct wl_list link; +}; + +struct vnc_head { + struct weston_head base; +}; + +static void +vnc_output_destroy(struct weston_output *base); + +static inline struct vnc_output * +to_vnc_output(struct weston_output *base) +{ + if (base->destroy != vnc_output_destroy) + return NULL; + return container_of(base, struct vnc_output, base); +} + +static void +vnc_head_destroy(struct weston_head *base); + +static void +vnc_destroy(struct weston_backend *backend); + +static inline struct vnc_head * +to_vnc_head(struct weston_head *base) +{ + if (base->backend->destroy != vnc_destroy) + return NULL; + return container_of(base, struct vnc_head, base); +} + +struct vnc_keysym_to_keycode { + const uint32_t keysym; + const uint32_t code; + const bool shift; +}; + +static const +struct vnc_keysym_to_keycode key_translation[] = { + {XKB_KEY_KP_Enter, 0x60, false }, + {XKB_KEY_Return, 0x1c, false }, + {XKB_KEY_space, 0x39, false }, + {XKB_KEY_BackSpace, 0xe, false }, + {XKB_KEY_Tab, 0xf, false }, + {XKB_KEY_Escape, 0x1, false }, + {XKB_KEY_Shift_L, 0x2a, false }, + {XKB_KEY_Shift_R, 0x36, false }, + {XKB_KEY_Control_L, 0x1d, false }, + {XKB_KEY_Control_R, 0x9d, false }, + {XKB_KEY_Alt_L, 0x38, false }, + {XKB_KEY_Alt_R, 0x64, false }, + {XKB_KEY_Meta_L, 0x38, false }, + {XKB_KEY_Meta_R, 0x64, false }, + {XKB_KEY_Super_L, 0x7d, false }, + {XKB_KEY_Print, 0x63, false }, + {XKB_KEY_Pause, 0x77, false }, + {XKB_KEY_Caps_Lock, 0x3a, false }, + {XKB_KEY_Scroll_Lock, 0x46, false }, + {XKB_KEY_A, 0x1e, true }, + {XKB_KEY_a, 0x1e, false }, + {XKB_KEY_B, 0x30, true }, + {XKB_KEY_b, 0x30, false }, + {XKB_KEY_C, 0x2e, true }, + {XKB_KEY_c, 0x2e, false }, + {XKB_KEY_D, 0x20, true }, + {XKB_KEY_d, 0x20, false }, + {XKB_KEY_E, 0x12, true }, + {XKB_KEY_e, 0x12, false }, + {XKB_KEY_F, 0x21, true }, + {XKB_KEY_f, 0x21, false }, + {XKB_KEY_G, 0x22, true }, + {XKB_KEY_g, 0x22, false }, + {XKB_KEY_H, 0x23, true }, + {XKB_KEY_h, 0x23, false }, + {XKB_KEY_I, 0x17, true }, + {XKB_KEY_i, 0x17, false }, + {XKB_KEY_J, 0x24, true }, + {XKB_KEY_j, 0x24, false }, + {XKB_KEY_K, 0x25, true }, + {XKB_KEY_k, 0x25, false }, + {XKB_KEY_L, 0x26, true }, + {XKB_KEY_l, 0x26, false }, + {XKB_KEY_M, 0x32, true }, + {XKB_KEY_m, 0x32, false }, + {XKB_KEY_N, 0x31, true }, + {XKB_KEY_n, 0x31, false }, + {XKB_KEY_O, 0x18, true }, + {XKB_KEY_o, 0x18, false }, + {XKB_KEY_P, 0x19, true }, + {XKB_KEY_p, 0x19, false }, + {XKB_KEY_Q, 0x10, true }, + {XKB_KEY_q, 0x10, false }, + {XKB_KEY_R, 0x13, true }, + {XKB_KEY_r, 0x13, false }, + {XKB_KEY_S, 0x1f, true }, + {XKB_KEY_s, 0x1f, false }, + {XKB_KEY_T, 0x14, true }, + {XKB_KEY_t, 0x14, false }, + {XKB_KEY_U, 0x16, true }, + {XKB_KEY_u, 0x16, false }, + {XKB_KEY_V, 0x2f, true }, + {XKB_KEY_v, 0x2f, false }, + {XKB_KEY_W, 0x11, true }, + {XKB_KEY_w, 0x11, false }, + {XKB_KEY_X, 0x2d, true }, + {XKB_KEY_x, 0x2d, false }, + {XKB_KEY_Y, 0x15, true }, + {XKB_KEY_y, 0x15, false }, + {XKB_KEY_Z, 0x2c, true }, + {XKB_KEY_z, 0x2c, false }, + {XKB_KEY_grave, 0x29, false }, + {XKB_KEY_asciitilde, 0x29, true }, + {XKB_KEY_1, 0x02, false }, + {XKB_KEY_exclam, 0x02, true }, + {XKB_KEY_2, 0x03, false }, + {XKB_KEY_at, 0x03, true }, + {XKB_KEY_3, 0x04, false }, + {XKB_KEY_numbersign, 0x04, true }, + {XKB_KEY_4, 0x05, false }, + {XKB_KEY_dollar, 0x05, true }, + {XKB_KEY_5, 0x06, false }, + {XKB_KEY_percent, 0x06, true }, + {XKB_KEY_6, 0x07, false }, + {XKB_KEY_asciicircum, 0x07, true }, + {XKB_KEY_7, 0x08, false }, + {XKB_KEY_ampersand, 0x08, true }, + {XKB_KEY_8, 0x09, false }, + {XKB_KEY_asterisk, 0x09, true }, + {XKB_KEY_9, 0x0a, false }, + {XKB_KEY_parenleft, 0x0a, true }, + {XKB_KEY_0, 0x0b, false }, + {XKB_KEY_parenright, 0x0b, true }, + {XKB_KEY_minus, 0x0c, false, }, + {XKB_KEY_underscore, 0x0c, true }, + {XKB_KEY_equal, 0x0d, false }, + {XKB_KEY_plus, 0x0d, true }, + {XKB_KEY_bracketleft, 0x1a, false }, + {XKB_KEY_braceleft, 0x1a, true }, + {XKB_KEY_bracketright, 0x1b, false }, + {XKB_KEY_braceright, 0x1b, true }, + {XKB_KEY_semicolon, 0x27, false }, + {XKB_KEY_colon, 0x27, true }, + {XKB_KEY_apostrophe, 0x28, false }, + {XKB_KEY_quotedbl, 0x28, true }, + {XKB_KEY_backslash, 0x2b, false }, + {XKB_KEY_bar, 0x2b, true }, + {XKB_KEY_comma, 0x33, false }, + {XKB_KEY_less, 0x33, true }, + {XKB_KEY_period, 0x34, false }, + {XKB_KEY_greater, 0x34, true }, + {XKB_KEY_slash, 0x35, false }, + {XKB_KEY_question, 0x35, true }, + {XKB_KEY_F1, 0x3b, false }, + {XKB_KEY_F2, 0x3c, false }, + {XKB_KEY_F3, 0x3d, false }, + {XKB_KEY_F4, 0x3e, false }, + {XKB_KEY_F5, 0x3f, false }, + {XKB_KEY_F6, 0x40, false }, + {XKB_KEY_F7, 0x41, false }, + {XKB_KEY_F8, 0x42, false }, + {XKB_KEY_F9, 0x43, false }, + {XKB_KEY_F10, 0x44, false }, + {XKB_KEY_F11, 0x57, false }, + {XKB_KEY_F12, 0x58, false }, + {XKB_KEY_Home, 0x66, false }, + {XKB_KEY_Up, 0x67, false }, + {XKB_KEY_Prior, 0x68, false }, + {XKB_KEY_Left, 0x69, false }, + {XKB_KEY_Right, 0x6a, false }, + {XKB_KEY_End, 0x6b, false }, + {XKB_KEY_Down, 0x6c, false }, + {XKB_KEY_Next, 0x6d, false }, + { }, +}; + +static void +vnc_handle_key_event(struct nvnc_client *client, uint32_t keysym, + bool is_pressed) +{ + struct vnc_peer *peer = nvnc_get_userdata(client); + uint32_t key = 0; + bool needs_shift = false; + enum weston_key_state_update state_update; + enum wl_keyboard_key_state state; + struct timespec time; + int i; + + weston_compositor_get_time(&time); + + if (is_pressed) + state = WL_KEYBOARD_KEY_STATE_PRESSED; + else + state = WL_KEYBOARD_KEY_STATE_RELEASED; + + /* Generally ignore shift state as per RFC6143 Section 7.5.4 */ + if (keysym == XKB_KEY_Shift_L || keysym == XKB_KEY_Shift_R) + return; + + /* Allow selected modifiers */ + if (keysym == XKB_KEY_Control_L || keysym == XKB_KEY_Control_R || + keysym == XKB_KEY_Alt_L || keysym == XKB_KEY_Alt_R) + state_update = STATE_UPDATE_AUTOMATIC; + else + state_update = STATE_UPDATE_NONE; + + for (i = 0; key_translation[i].keysym; i++) { + if (key_translation[i].keysym == keysym) { + key = key_translation[i].code; + needs_shift = key_translation[i].shift; + break; + } + } + + if (!key) { + weston_log("Key not found: keysym %08x, translated %08x\n", + keysym, key); + return; + } + + /* emulate lshift press */ + if (needs_shift) + notify_key(peer->seat, &time, KEY_LEFTSHIFT, + WL_KEYBOARD_KEY_STATE_PRESSED, + STATE_UPDATE_AUTOMATIC); + + /* send detected key code */ + notify_key(peer->seat, &time, key, state, state_update); + + /* emulate lshift release */ + if (needs_shift) + notify_key(peer->seat, &time, KEY_LEFTSHIFT, + WL_KEYBOARD_KEY_STATE_RELEASED, + STATE_UPDATE_AUTOMATIC); +} + +static void +vnc_handle_key_code_event(struct nvnc_client *client, uint32_t key, + bool is_pressed) +{ + struct vnc_peer *peer = nvnc_get_userdata(client); + enum wl_keyboard_key_state state; + struct timespec time; + + weston_compositor_get_time(&time); + + if (is_pressed) + state = WL_KEYBOARD_KEY_STATE_PRESSED; + else + state = WL_KEYBOARD_KEY_STATE_RELEASED; + + notify_key(peer->seat, &time, key, state, STATE_UPDATE_AUTOMATIC); +} + +static void +vnc_pointer_event(struct nvnc_client *client, uint16_t x, uint16_t y, + enum nvnc_button_mask button_mask) +{ + struct vnc_peer *peer = nvnc_get_userdata(client); + struct vnc_output *output = peer->backend->output; + struct timespec time; + enum nvnc_button_mask changed_button_mask; + + weston_compositor_get_time(&time); + + if (x < output->base.width && y < output->base.height) { + struct weston_coord_global pos; + + pos = weston_coord_global_from_output_point(x, y, &output->base); + notify_motion_absolute(peer->seat, &time, pos); + } + + changed_button_mask = peer->last_button_mask ^ button_mask; + + if (changed_button_mask & NVNC_BUTTON_LEFT) + notify_button(peer->seat, &time, BTN_LEFT, + (button_mask & NVNC_BUTTON_LEFT) ? + WL_POINTER_BUTTON_STATE_PRESSED : + WL_POINTER_BUTTON_STATE_RELEASED); + + if (changed_button_mask & NVNC_BUTTON_MIDDLE) + notify_button(peer->seat, &time, BTN_MIDDLE, + (button_mask & NVNC_BUTTON_MIDDLE) ? + WL_POINTER_BUTTON_STATE_PRESSED : + WL_POINTER_BUTTON_STATE_RELEASED); + + if (changed_button_mask & NVNC_BUTTON_RIGHT) + notify_button(peer->seat, &time, BTN_RIGHT, + (button_mask & NVNC_BUTTON_RIGHT) ? + WL_POINTER_BUTTON_STATE_PRESSED : + WL_POINTER_BUTTON_STATE_RELEASED); + + if ((button_mask & NVNC_SCROLL_UP) || + (button_mask & NVNC_SCROLL_DOWN)) { + struct weston_pointer_axis_event weston_event; + + weston_event.axis = WL_POINTER_AXIS_VERTICAL_SCROLL; + + /* DEFAULT_AXIS_STEP_DISTANCE is stolen from compositor-x11.c */ + if (button_mask & NVNC_SCROLL_UP) + weston_event.value = -DEFAULT_AXIS_STEP_DISTANCE; + if (button_mask & NVNC_SCROLL_DOWN) + weston_event.value = DEFAULT_AXIS_STEP_DISTANCE; + weston_event.has_discrete = false; + + notify_axis(peer->seat, &time, &weston_event); + } + + peer->last_button_mask = button_mask; + + notify_pointer_frame(peer->seat); +} + +static bool +vnc_handle_auth(const char *username, const char *password, void *userdata) +{ + struct passwd *pw = getpwnam(username); + + if (!pw || pw->pw_uid != getuid()) { + weston_log("VNC: wrong user '%s'\n", username); + return false; + } + + return weston_authenticate_user(username, password); +} + +static void +vnc_client_cleanup(struct nvnc_client *client) +{ + struct vnc_peer *peer = nvnc_get_userdata(client); + struct vnc_output *output = peer->backend->output; + + wl_list_remove(&peer->link); + weston_seat_release_keyboard(peer->seat); + weston_seat_release_pointer(peer->seat); + weston_seat_release(peer->seat); + free(peer); + weston_log("VNC Client disconnected\n"); + + if (wl_list_empty(&output->peers)) + weston_output_power_off(&output->base); +} + +static struct weston_pointer * +vnc_output_get_pointer(struct vnc_output *output) +{ + struct weston_pointer *pointer = NULL; + struct weston_paint_node *pnode; + struct vnc_peer *peer; + + wl_list_for_each(peer, &output->peers, link) { + pointer = weston_seat_get_pointer(peer->seat); + break; + } + + if (!pointer) + return NULL; + + wl_list_for_each(pnode, &output->base.paint_node_z_order_list, z_order_link) { + if (pnode->view == pointer->sprite) + return pointer; + } + + return NULL; +} + +static void +vnc_output_update_cursor(struct vnc_output *output) +{ + struct vnc_backend *backend = output->backend; + struct weston_pointer *pointer; + struct weston_view *view; + struct weston_buffer *buffer; + struct nvnc_fb *fb; + int32_t stride; + uint32_t format; + uint8_t *src, *dst; + int i; + + pointer = vnc_output_get_pointer(output); + if (!pointer) + return; + + view = pointer->sprite; + if (!weston_view_has_valid_buffer(view)) + return; + + buffer = view->surface->buffer_ref.buffer; + if (buffer->type != WESTON_BUFFER_SHM) + return; + + format = wl_shm_buffer_get_format(buffer->shm_buffer); + if (format != WL_SHM_FORMAT_ARGB8888) + return; + + weston_view_move_to_plane(view, &output->cursor_plane); + + if (view->surface == output->cursor_surface && + !pixman_region32_not_empty(&view->surface->damage)) + return; + + output->cursor_surface = view->surface; + + stride = wl_shm_buffer_get_stride(buffer->shm_buffer); + + fb = nvnc_fb_new(buffer->width, buffer->height, DRM_FORMAT_ARGB8888, + buffer->width); + assert(fb); + + src = wl_shm_buffer_get_data(buffer->shm_buffer); + dst = nvnc_fb_get_addr(fb); + + wl_shm_buffer_begin_access(buffer->shm_buffer); + for (i = 0; i < buffer->height; i++) + memcpy(dst + i * 4 * buffer->width, src + i * stride, + 4 * buffer->width); + wl_shm_buffer_end_access(buffer->shm_buffer); + + nvnc_set_cursor(backend->server, fb, buffer->width, buffer->height, + pointer->hotspot.c.x, pointer->hotspot.c.y, true); + nvnc_fb_unref(fb); +} + +/* + * Convert damage rectangles from 32-bit global coordinates to 16-bit local + * coordinates. The output transformation has to be a pure translation. + */ +static void +vnc_region_global_to_output(pixman_region16_t *dst, + struct weston_output *output, + pixman_region32_t *src) +{ + struct pixman_box32 *src_rects; + struct pixman_box16 *dest_rects; + int n_rects = 0; + int i; + + src_rects = pixman_region32_rectangles(src, &n_rects); + if (!n_rects) + return; + + dest_rects = xcalloc(n_rects, sizeof(*dest_rects)); + + for (i = 0; i < n_rects; i++) { + dest_rects[i].x1 = src_rects[i].x1 - output->x; + dest_rects[i].y1 = src_rects[i].y1 - output->y; + dest_rects[i].x2 = src_rects[i].x2 - output->x; + dest_rects[i].y2 = src_rects[i].y2 - output->y; + } + + pixman_region_init_rects(dst, dest_rects, n_rects); + free(dest_rects); +} + +static void +vnc_log_scope_print_region(struct weston_log_scope *log, pixman_region32_t *region) +{ + struct pixman_box32 *rects; + int n_rects = 0; + int i; + + rects = pixman_region32_rectangles(region, &n_rects); + if (!n_rects) { + weston_log_scope_printf(log, " empty"); + return; + } + + for (i = 0; i < n_rects; i++) { + int width = rects[i].x2 - rects[i].x1; + int height = rects[i].y2 - rects[i].y1; + + weston_log_scope_printf(log, " %dx%d(%d,%d)", width, height, + rects[i].x1, rects[i].y1); + } +} + +static void +vnc_log_damage(struct vnc_backend *backend, pixman_region32_t *buffer_damage, + pixman_region32_t *update_damage) +{ + char timestr[128]; + + if (!weston_log_scope_is_enabled(backend->debug)) + return; + + weston_log_scope_timestamp(backend->debug, timestr, sizeof timestr); + + weston_log_scope_printf(backend->debug, "%s buffer damage:", timestr); + vnc_log_scope_print_region(backend->debug, buffer_damage); + weston_log_scope_printf(backend->debug, "\n"); + + weston_log_scope_printf(backend->debug, "%s update damage:", timestr); + vnc_log_scope_print_region(backend->debug, update_damage); + weston_log_scope_printf(backend->debug, "\n\n"); +} + +static void +vnc_update_buffer(struct nvnc_display *display, struct pixman_region32 *damage) +{ + struct nvnc *server = nvnc_display_get_server(display); + struct vnc_backend *backend = nvnc_get_userdata(server); + struct vnc_output *output = backend->output; + struct weston_compositor *ec = output->base.compositor; + struct weston_renderbuffer *renderbuffer; + pixman_region16_t local_damage; + struct nvnc_fb *fb; + + fb = nvnc_fb_pool_acquire(output->fb_pool); + assert(fb); + + renderbuffer = nvnc_get_userdata(fb); + if (!renderbuffer) { + const struct pixman_renderer_interface *pixman; + const struct pixel_format_info *pfmt; + + pixman = ec->renderer->pixman; + pfmt = pixel_format_get_info(DRM_FORMAT_XRGB8888); + + renderbuffer = + pixman->create_image_from_ptr(&output->base, pfmt, + output->base.width, + output->base.height, + nvnc_fb_get_addr(fb), + output->base.width * 4); + + /* This is a new buffer, so the whole surface is damaged. */ + pixman_region32_copy(&renderbuffer->damage, + &output->base.region); + + nvnc_set_userdata(fb, renderbuffer, + (nvnc_cleanup_fn)weston_renderbuffer_unref); + } + + vnc_log_damage(backend, &renderbuffer->damage, damage); + + ec->renderer->repaint_output(&output->base, damage, renderbuffer); + + /* Convert to local coordinates */ + pixman_region_init(&local_damage); + vnc_region_global_to_output(&local_damage, &output->base, damage); + + nvnc_display_feed_buffer(output->display, fb, &local_damage); + nvnc_fb_unref(fb); + pixman_region_fini(&local_damage); +} + +static void +vnc_new_client(struct nvnc_client *client) +{ + struct nvnc *server = nvnc_client_get_server(client); + struct vnc_backend *backend = nvnc_get_userdata(server); + struct vnc_output *output = backend->output; + struct vnc_peer *peer; + const char *seat_name = "VNC Client"; + + weston_log("New VNC client connected\n"); + + peer = xzalloc(sizeof(*peer)); + peer->client = client; + peer->backend = backend; + peer->seat = xzalloc(sizeof(*peer->seat)); + + weston_seat_init(peer->seat, backend->compositor, seat_name); + weston_seat_init_pointer(peer->seat); + weston_seat_init_keyboard(peer->seat, backend->xkb_keymap); + + if (wl_list_empty(&output->peers)) + weston_output_power_on(&output->base); + + wl_list_insert(&output->peers, &peer->link); + + nvnc_set_userdata(client, peer, NULL); + nvnc_set_client_cleanup_fn(client, vnc_client_cleanup); + + /* + * Make up for repaints that were skipped when no clients were + * connected. + */ + weston_output_schedule_repaint(&output->base); +} + + +static int +finish_frame_handler(void *data) +{ + struct vnc_output *output = data; + int refresh_nsec = millihz_to_nsec(output->base.current_mode->refresh); + struct timespec now, ts; + int delta; + + /* The timer only has msec precision, but if we approximately hit our + * target, report an exact time stamp by adding to the previous frame + * time. + */ + timespec_add_nsec(&ts, &output->base.frame_time, refresh_nsec); + + /* If we are more than 1.5 ms late, report the current time instead. */ + weston_compositor_read_presentation_clock(output->base.compositor, &now); + delta = (int)timespec_sub_to_nsec(&now, &ts); + if (delta > 1500000) + ts = now; + + weston_output_finish_frame(&output->base, &ts, 0); + + return 1; +} + +static int +vnc_output_enable(struct weston_output *base) +{ + struct weston_renderer *renderer = base->compositor->renderer; + struct vnc_output *output = to_vnc_output(base); + struct vnc_backend *backend; + struct wl_event_loop *loop; + const struct pixman_renderer_output_options options = { + .fb_size = { + .width = output->base.width, + .height = output->base.height, + }, + .format = pixel_format_get_info(DRM_FORMAT_XRGB8888), + }; + + assert(output); + + backend = output->backend; + backend->output = output; + + weston_plane_init(&output->cursor_plane, backend->compositor); + + if (renderer->pixman->output_create(&output->base, &options) < 0) + return -1; + + loop = wl_display_get_event_loop(backend->compositor->wl_display); + output->finish_frame_timer = wl_event_loop_add_timer(loop, + finish_frame_handler, + output); + + output->fb_pool = nvnc_fb_pool_new(output->base.width, + output->base.height, + options.format->format, + output->base.width); + + output->display = nvnc_display_new(0, 0); + + nvnc_add_display(backend->server, output->display); + + return 0; +} + +static int +vnc_output_disable(struct weston_output *base) +{ + struct weston_renderer *renderer = base->compositor->renderer; + struct vnc_output *output = to_vnc_output(base); + struct vnc_backend *backend; + + assert(output); + + backend = output->backend; + + if (!output->base.enabled) + return 0; + + nvnc_display_unref(output->display); + nvnc_fb_pool_unref(output->fb_pool); + + renderer->pixman->output_destroy(&output->base); + + wl_event_source_remove(output->finish_frame_timer); + backend->output = NULL; + + weston_plane_release(&output->cursor_plane); + + return 0; +} + +static void +vnc_output_destroy(struct weston_output *base) +{ + struct vnc_output *output = to_vnc_output(base); + + /* Can only be called on outputs created by vnc_create_output() */ + assert(output); + + vnc_output_disable(&output->base); + weston_output_release(&output->base); + + free(output); +} + +static struct weston_output * +vnc_create_output(struct weston_backend *backend, const char *name) +{ + struct vnc_backend *b = container_of(backend, struct vnc_backend, base); + struct vnc_output *output; + + output = zalloc(sizeof *output); + if (output == NULL) + return NULL; + + weston_output_init(&output->base, b->compositor, name); + + output->base.destroy = vnc_output_destroy; + output->base.disable = vnc_output_disable; + output->base.enable = vnc_output_enable; + output->base.attach_head = NULL; + + output->backend = b; + + weston_compositor_add_pending_output(&output->base, b->compositor); + + return &output->base; +} + +static void +vnc_destroy(struct weston_backend *base) +{ + struct vnc_backend *backend = container_of(base, struct vnc_backend, base); + struct weston_compositor *ec = backend->compositor; + struct weston_head *head, *next; + + nvnc_close(backend->server); + + weston_compositor_shutdown(ec); + + wl_event_source_remove(backend->aml_event); + + aml_unref(backend->aml); + + wl_list_for_each_safe(head, next, &ec->head_list, compositor_link) + vnc_head_destroy(head); + + xkb_keymap_unref(backend->xkb_keymap); + + if (backend->debug) + weston_log_scope_destroy(backend->debug); + + free(backend); +} + +static void +vnc_head_create(struct vnc_backend *backend, const char *name) +{ + struct vnc_head *head; + + head = xzalloc(sizeof *head); + + weston_head_init(&head->base, name); + weston_head_set_monitor_strings(&head->base, "weston", "vnc", NULL); + weston_head_set_physical_size(&head->base, 0, 0); + + head->base.backend = &backend->base; + + weston_head_set_connection_status(&head->base, true); + weston_compositor_add_head(backend->compositor, &head->base); +} + +static void +vnc_head_destroy(struct weston_head *base) +{ + struct vnc_head *head = to_vnc_head(base); + + if (!head) + return; + + weston_head_release(&head->base); + free(head); +} + +static int +vnc_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 +vnc_output_repaint(struct weston_output *base, pixman_region32_t *damage) +{ + struct vnc_output *output = to_vnc_output(base); + struct weston_compositor *ec = output->base.compositor; + struct vnc_backend *backend = output->backend; + 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; + + assert(output); + + if (wl_list_empty(&output->peers)) + weston_output_power_off(base); + + if (pixman_region32_not_empty(damage)) { + vnc_update_buffer(output->display, damage); + pixman_region32_subtract(&ec->primary_plane.damage, + &ec->primary_plane.damage, damage); + } + + /* + * Make sure damage of this (or previous) damage is handled + * + * This will usually invoke the render callback where the (pixman) + * renderer gets invoked + */ + aml_dispatch(backend->aml); + + 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 = 1; + if (next_frame_delta > refresh_msec) + next_frame_delta = refresh_msec; + + wl_event_source_timer_update(output->finish_frame_timer, + next_frame_delta); + + return 0; +} + +static bool +vnc_clients_support_cursor(struct vnc_output *output) +{ + struct vnc_peer *peer; + + wl_list_for_each(peer, &output->peers, link) { + if (!nvnc_client_supports_cursor(peer->client)) + return false; + } + + return true; +} + +static void +vnc_output_assign_planes(struct weston_output *base) +{ + struct vnc_output *output = to_vnc_output(base); + + assert(output); + + if (wl_list_empty(&output->peers)) + return; + + /* Update VNC cursor and move cursor view to plane */ + if (vnc_clients_support_cursor(output)) + vnc_output_update_cursor(output); +} + +static int +vnc_switch_mode(struct weston_output *base, struct weston_mode *target_mode) +{ + struct vnc_output *output = to_vnc_output(base); + struct weston_size fb_size; + + assert(output); + + weston_output_set_single_mode(base, target_mode); + + fb_size.width = target_mode->width; + fb_size.height = target_mode->height; + + weston_renderer_resize_output(base, &fb_size, NULL); + + nvnc_fb_pool_resize(output->fb_pool, target_mode->width, + target_mode->height, DRM_FORMAT_XRGB8888, + target_mode->width); + + return 0; +} + +static int +vnc_output_set_size(struct weston_output *base, int width, int height) +{ + struct vnc_output *output = to_vnc_output(base); + struct vnc_backend *backend = output->backend; + struct weston_mode init_mode; + + /* We can only be called once. */ + assert(!output->base.current_mode); + + wl_list_init(&output->peers); + + init_mode.width = width; + init_mode.height = height; + init_mode.refresh = backend->vnc_monitor_refresh_rate; + + weston_output_set_single_mode(base, &init_mode); + + output->base.start_repaint_loop = vnc_output_start_repaint_loop; + output->base.repaint = vnc_output_repaint; + output->base.assign_planes = vnc_output_assign_planes; + output->base.set_backlight = NULL; + output->base.set_dpms = NULL; + output->base.switch_mode = vnc_switch_mode; + + return 0; +} + +static const struct weston_vnc_output_api api = { + vnc_output_set_size, +}; + +static int +vnc_aml_dispatch(int fd, uint32_t mask, void *data) +{ + struct aml *aml = data; + + aml_poll(aml, 0); + aml_dispatch(aml); + + return 0; +} + +static struct vnc_backend * +vnc_backend_create(struct weston_compositor *compositor, + struct weston_vnc_backend_config *config) +{ + struct vnc_backend *backend; + struct wl_event_loop *loop; + struct weston_head *base, *next; + int ret; + int fd; + + backend = zalloc(sizeof *backend); + if (backend == NULL) + return NULL; + + backend->compositor = compositor; + backend->base.destroy = vnc_destroy; + backend->base.create_output = vnc_create_output; + backend->vnc_monitor_refresh_rate = config->refresh_rate * 1000; + + backend->debug = weston_compositor_add_log_scope(compositor, + "vnc-backend", + "Debug messages from VNC backend\n", + NULL, NULL, NULL); + + compositor->backend = &backend->base; + + if (weston_compositor_set_presentation_clock_software(compositor) < 0) + goto err_compositor; + + switch (config->renderer) { + case WESTON_RENDERER_AUTO: + case WESTON_RENDERER_PIXMAN: + break; + default: + weston_log("Unsupported renderer requested\n"); + goto err_compositor; + } + + if (weston_compositor_init_renderer(compositor, WESTON_RENDERER_PIXMAN, + NULL) < 0) + goto err_compositor; + + vnc_head_create(backend, "vnc"); + + compositor->capabilities |= WESTON_CAP_ARBITRARY_MODES; + + backend->xkb_rule_name.rules = strdup(compositor->xkb_names.rules); + backend->xkb_rule_name.model = strdup(compositor->xkb_names.model); + backend->xkb_rule_name.layout = strdup(compositor->xkb_names.layout); + + backend->xkb_keymap = xkb_keymap_new_from_names( + backend->compositor->xkb_context, + &backend->xkb_rule_name, 0); + + loop = wl_display_get_event_loop(backend->compositor->wl_display); + + backend->aml = aml_new(); + if (!backend->aml) + goto err_output; + aml_set_default(backend->aml); + + fd = aml_get_fd(backend->aml); + + backend->aml_event = wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, + vnc_aml_dispatch, + backend->aml); + + backend->server = nvnc_open(config->bind_address, config->port); + if (!backend->server) + goto err_output; + + nvnc_set_new_client_fn(backend->server, vnc_new_client); + nvnc_set_pointer_fn(backend->server, vnc_pointer_event); + nvnc_set_key_fn(backend->server, vnc_handle_key_event); + nvnc_set_key_code_fn(backend->server, vnc_handle_key_code_event); + nvnc_set_userdata(backend->server, backend, NULL); + nvnc_set_name(backend->server, "Weston VNC backend"); + + if (!nvnc_has_auth()) { + weston_log("Neat VNC built without TLS support\n"); + goto err_output; + } + if (!config->server_cert && !config->server_key) { + weston_log("The VNC backend requires a key and a certificate for TLS security" + " (--vnc-tls-cert/--vnc-tls-key)\n"); + goto err_output; + } + if (!config->server_cert) { + weston_log("Missing TLS certificate (--vnc-tls-cert)\n"); + goto err_output; + } + if (!config->server_key) { + weston_log("Missing TLS key (--vnc-tls-key)\n"); + goto err_output; + } + + ret = nvnc_enable_auth(backend->server, config->server_key, + config->server_cert, vnc_handle_auth, + NULL); + if (ret) { + weston_log("Failed to enable TLS support\n"); + goto err_output; + } + + weston_log("TLS support activated\n"); + + ret = weston_plugin_api_register(compositor, WESTON_VNC_OUTPUT_API_NAME, + &api, sizeof(api)); + if (ret < 0) { + weston_log("Failed to register output API.\n"); + goto err_output; + } + + return backend; + +err_output: + wl_list_for_each_safe(base, next, &compositor->head_list, compositor_link) + vnc_head_destroy(base); +err_compositor: + weston_compositor_shutdown(compositor); + free(backend); + return NULL; +} + +static void +config_init_to_defaults(struct weston_vnc_backend_config *config) +{ + config->bind_address = NULL; + config->port = 5900; + config->refresh_rate = VNC_DEFAULT_FREQ; +} + +WL_EXPORT int +weston_backend_init(struct weston_compositor *compositor, + struct weston_backend_config *config_base) +{ + struct vnc_backend *backend; + struct weston_vnc_backend_config config = {{ 0, }}; + + weston_log("Initializing VNC backend\n"); + + if (config_base == NULL || + config_base->struct_version != WESTON_VNC_BACKEND_CONFIG_VERSION || + config_base->struct_size > sizeof(struct weston_vnc_backend_config)) { + weston_log("VNC backend config structure is invalid\n"); + return -1; + } + + config_init_to_defaults(&config); + memcpy(&config, config_base, config_base->struct_size); + + backend = vnc_backend_create(compositor, &config); + if (backend == NULL) + return -1; + return 0; +} diff --git a/libweston/backend-wayland/meson.build b/libweston/backend-wayland/meson.build index 29270b58d..e36ab619e 100644 --- a/libweston/backend-wayland/meson.build +++ b/libweston/backend-wayland/meson.build @@ -21,6 +21,7 @@ deps_wlwl = [ dep_libweston_private, dep_libdrm_headers, dep_lib_cairo_shared, + dep_lib_gl_borders, ] if get_option('renderer-gl') diff --git a/libweston/backend-wayland/wayland.c b/libweston/backend-wayland/wayland.c index 5c3d8e9af..48c21b99a 100644 --- a/libweston/backend-wayland/wayland.c +++ b/libweston/backend-wayland/wayland.c @@ -48,6 +48,7 @@ #include #include #include "renderer-gl/gl-renderer.h" +#include "gl-borders.h" #include "shared/weston-drm-fourcc.h" #include "shared/weston-egl-ext.h" #include "pixman-renderer.h" @@ -56,14 +57,22 @@ #include "shared/os-compatibility.h" #include "shared/cairo-util.h" #include "shared/timespec-util.h" +#include "shared/xalloc.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" #include "xdg-shell-client-protocol.h" #include "presentation-time-server-protocol.h" #include "linux-dmabuf.h" +#include #include #define WINDOW_TITLE "Weston Compositor" +#define WINDOW_MIN_WIDTH 128 +#define WINDOW_MIN_HEIGHT 128 + +#define WINDOW_MAX_WIDTH 8192 +#define WINDOW_MAX_HEIGHT 8192 + static const uint32_t wayland_formats[] = { DRM_FORMAT_ARGB8888, }; @@ -76,7 +85,6 @@ struct wayland_backend { struct wl_display *wl_display; struct wl_registry *registry; struct wl_compositor *compositor; - struct wl_shell *shell; struct xdg_wm_base *xdg_wm_base; struct zwp_fullscreen_shell_v1 *fshell; struct wl_shm *shm; @@ -87,7 +95,6 @@ struct wayland_backend { uint32_t event_mask; } parent; - bool use_pixman; bool sprawl_across_outputs; bool fullscreen; @@ -100,10 +107,14 @@ struct wayland_backend { /* These struct wayland_input objects are waiting for the outer * compositor to provide a name and initial capabilities. */ struct wl_list pending_input_list; + + const struct pixel_format_info **formats; + unsigned int formats_count; }; struct wayland_output { struct weston_output base; + struct wayland_backend *backend; struct { bool draw_initial_frame; @@ -112,7 +123,6 @@ struct wayland_output { struct wl_output *output; uint32_t global_id; - struct wl_shell_surface *shell_surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; int configure_width, configure_height; @@ -126,12 +136,7 @@ struct wayland_output { struct { struct wl_egl_window *egl_window; - struct { - cairo_surface_t *top; - cairo_surface_t *left; - cairo_surface_t *right; - cairo_surface_t *bottom; - } border; + struct weston_gl_borders borders; } gl; struct { @@ -140,6 +145,7 @@ struct wayland_output { } shm; struct weston_mode mode; + struct weston_mode native_mode; struct wl_callback *frame_cb; }; @@ -188,7 +194,7 @@ struct wayland_shm_buffer { pixman_region32_t damage; /**< in global coords */ int frame_damaged; - pixman_image_t *pm_image; + struct weston_renderbuffer *renderbuffer; cairo_surface_t *c_surface; }; @@ -231,31 +237,40 @@ struct wayland_input { enum wl_seat_capability caps; }; -struct gl_renderer_interface *gl_renderer; +static void +wayland_destroy(struct weston_backend *backend); static inline struct wayland_head * to_wayland_head(struct weston_head *base) { + if (base->backend->destroy != wayland_destroy) + return NULL; return container_of(base, struct wayland_head, base); } +static void +wayland_output_destroy(struct weston_output *base); + static inline struct wayland_output * to_wayland_output(struct weston_output *base) { + if (base->destroy != wayland_output_destroy) + return NULL; return container_of(base, struct wayland_output, base); } static inline struct wayland_backend * -to_wayland_backend(struct weston_compositor *base) +to_wayland_backend(struct weston_backend *base) { - return container_of(base->backend, struct wayland_backend, base); + return container_of(base, struct wayland_backend, base); } static void wayland_shm_buffer_destroy(struct wayland_shm_buffer *buffer) { cairo_surface_destroy(buffer->c_surface); - pixman_image_unref(buffer->pm_image); + if (buffer->output) + weston_renderbuffer_unref(buffer->renderbuffer); wl_buffer_destroy(buffer->buffer); munmap(buffer->data, buffer->size); @@ -286,14 +301,17 @@ static const struct wl_buffer_listener buffer_listener = { static struct wayland_shm_buffer * wayland_output_get_shm_buffer(struct wayland_output *output) { - struct wayland_backend *b = - to_wayland_backend(output->base.compositor); + const struct weston_renderer *renderer; + const struct pixman_renderer_interface *pixman; + struct wayland_backend *b = output->backend; + const struct pixel_format_info *pfmt = b->formats[0]; + uint32_t shm_format = pixel_format_get_shm_format(pfmt); struct wl_shm *shm = b->parent.shm; struct wayland_shm_buffer *sb; struct wl_shm_pool *pool; int width, height, stride; - int32_t fx, fy; + struct weston_geometry area; int fd; unsigned char *data; @@ -358,7 +376,7 @@ wayland_output_get_shm_buffer(struct wayland_output *output) sb->buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, - WL_SHM_FORMAT_ARGB8888); + shm_format); wl_buffer_add_listener(sb->buffer, &buffer_listener, sb); wl_shm_pool_destroy(pool); close(fd); @@ -369,14 +387,27 @@ wayland_output_get_shm_buffer(struct wayland_output *output) cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, width, height, stride); - fx = 0; - fy = 0; - if (output->frame) - frame_interior(output->frame, &fx, &fy, 0, 0); - sb->pm_image = - pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, - (uint32_t *)(data + fy * stride) + fx, - stride); + if (output->frame) { + frame_interior(output->frame, &area.x, &area.y, + &area.width, &area.height); + } else { + area.x = 0; + area.y = 0; + area.width = output->base.current_mode->width; + area.height = output->base.current_mode->height; + } + + renderer = b->compositor->renderer; + pixman = renderer->pixman; + + /* Address only the interior, excluding output decorations */ + if (renderer->type == WESTON_RENDERER_PIXMAN) { + sb->renderbuffer = + pixman->create_image_from_ptr(&output->base, pfmt, + area.width, area.height, + (uint32_t *)(data + area.y * stride) + area.x, + stride); + } return sb; } @@ -429,71 +460,13 @@ draw_initial_frame(struct wayland_output *output) static void wayland_output_update_gl_border(struct wayland_output *output) { - int32_t ix, iy, iwidth, iheight, fwidth, fheight; - cairo_t *cr; - if (!output->frame) return; if (!(frame_status(output->frame) & FRAME_STATUS_REPAINT)) return; - fwidth = frame_width(output->frame); - fheight = frame_height(output->frame); - frame_interior(output->frame, &ix, &iy, &iwidth, &iheight); - - if (!output->gl.border.top) - output->gl.border.top = - cairo_image_surface_create(CAIRO_FORMAT_ARGB32, - fwidth, iy); - cr = cairo_create(output->gl.border.top); - frame_repaint(output->frame, cr); - cairo_destroy(cr); - gl_renderer->output_set_border(&output->base, GL_RENDERER_BORDER_TOP, - fwidth, iy, - cairo_image_surface_get_stride(output->gl.border.top) / 4, - cairo_image_surface_get_data(output->gl.border.top)); - - - if (!output->gl.border.left) - output->gl.border.left = - cairo_image_surface_create(CAIRO_FORMAT_ARGB32, - ix, 1); - cr = cairo_create(output->gl.border.left); - cairo_translate(cr, 0, -iy); - frame_repaint(output->frame, cr); - cairo_destroy(cr); - gl_renderer->output_set_border(&output->base, GL_RENDERER_BORDER_LEFT, - ix, 1, - cairo_image_surface_get_stride(output->gl.border.left) / 4, - cairo_image_surface_get_data(output->gl.border.left)); - - - if (!output->gl.border.right) - output->gl.border.right = - cairo_image_surface_create(CAIRO_FORMAT_ARGB32, - fwidth - (ix + iwidth), 1); - cr = cairo_create(output->gl.border.right); - cairo_translate(cr, -(iwidth + ix), -iy); - frame_repaint(output->frame, cr); - cairo_destroy(cr); - gl_renderer->output_set_border(&output->base, GL_RENDERER_BORDER_RIGHT, - fwidth - (ix + iwidth), 1, - cairo_image_surface_get_stride(output->gl.border.right) / 4, - cairo_image_surface_get_data(output->gl.border.right)); - - - if (!output->gl.border.bottom) - output->gl.border.bottom = - cairo_image_surface_create(CAIRO_FORMAT_ARGB32, - fwidth, fheight - (iy + iheight)); - cr = cairo_create(output->gl.border.bottom); - cairo_translate(cr, 0, -(iy + iheight)); - frame_repaint(output->frame, cr); - cairo_destroy(cr); - gl_renderer->output_set_border(&output->base, GL_RENDERER_BORDER_BOTTOM, - fwidth, fheight - (iy + iheight), - cairo_image_surface_get_stride(output->gl.border.bottom) / 4, - cairo_image_surface_get_data(output->gl.border.bottom)); + weston_gl_borders_update(&output->gl.borders, output->frame, + &output->base); } #endif @@ -501,8 +474,11 @@ static int wayland_output_start_repaint_loop(struct weston_output *output_base) { struct wayland_output *output = to_wayland_output(output_base); - struct wayland_backend *wb = - to_wayland_backend(output->base.compositor); + struct wayland_backend *wb; + + assert(output); + + wb = output->backend; /* If this is the initial frame, we need to attach a buffer so that * the compositor can map the surface and include it in its render @@ -526,18 +502,21 @@ wayland_output_start_repaint_loop(struct weston_output *output_base) #ifdef ENABLE_EGL static int wayland_output_repaint_gl(struct weston_output *output_base, - pixman_region32_t *damage, - void *repaint_data) + pixman_region32_t *damage) { struct wayland_output *output = to_wayland_output(output_base); - struct weston_compositor *ec = output->base.compositor; + struct weston_compositor *ec; + + assert(output); + + ec = output->base.compositor; output->frame_cb = wl_surface_frame(output->parent.surface); wl_callback_add_listener(output->frame_cb, &frame_listener, output); wayland_output_update_gl_border(output); - ec->renderer->repaint_output(&output->base, damage); + ec->renderer->repaint_output(&output->base, damage, NULL); pixman_region32_subtract(&ec->primary_plane.damage, &ec->primary_plane.damage, damage); @@ -594,15 +573,7 @@ wayland_shm_buffer_attach(struct wayland_shm_buffer *sb) int i, n; pixman_region32_init(&damage); - pixman_region32_copy(&damage, &sb->damage); - pixman_region32_translate(&damage, -sb->output->base.x, - -sb->output->base.y); - - weston_transformed_region(sb->output->base.width, - sb->output->base.height, - sb->output->base.transform, - sb->output->base.current_scale, - &damage, &damage); + weston_region_global_to_output(&damage, &sb->output->base, &sb->damage); if (sb->output->frame) { frame_interior(sb->output->frame, &ix, &iy, &iwidth, &iheight); @@ -638,14 +609,16 @@ wayland_shm_buffer_attach(struct wayland_shm_buffer *sb) static int wayland_output_repaint_pixman(struct weston_output *output_base, - pixman_region32_t *damage, - void *repaint_data) + pixman_region32_t *damage) { struct wayland_output *output = to_wayland_output(output_base); - struct wayland_backend *b = - to_wayland_backend(output->base.compositor); + struct wayland_backend *b; struct wayland_shm_buffer *sb; + assert(output); + + b = output->backend; + if (output->frame) { if (frame_status(output->frame) & FRAME_STATUS_REPAINT) wl_list_for_each(sb, &output->shm.buffers, link) @@ -658,8 +631,8 @@ wayland_output_repaint_pixman(struct weston_output *output_base, sb = wayland_output_get_shm_buffer(output); wayland_output_update_shm_border(sb); - pixman_renderer_output_set_buffer(output_base, sb->pm_image); - b->compositor->renderer->repaint_output(output_base, &sb->damage); + b->compositor->renderer->repaint_output(output_base, &sb->damage, + sb->renderbuffer); wayland_shm_buffer_attach(sb); @@ -692,11 +665,6 @@ wayland_backend_destroy_output_surface(struct wayland_output *output) output->parent.xdg_surface = NULL; } - if (output->parent.shell_surface) { - wl_shell_surface_destroy(output->parent.shell_surface); - output->parent.shell_surface = NULL; - } - wl_surface_destroy(output->parent.surface); output->parent.surface = NULL; } @@ -710,40 +678,44 @@ wayland_output_destroy_shm_buffers(struct wayland_output *output) wl_list_for_each_safe(buffer, next, &output->shm.free_buffers, free_link) wayland_shm_buffer_destroy(buffer); /* These will get thrown away when they get released */ - wl_list_for_each(buffer, &output->shm.buffers, link) + wl_list_for_each(buffer, &output->shm.buffers, link) { + if (buffer->renderbuffer) { + weston_renderbuffer_unref(buffer->renderbuffer); + buffer->renderbuffer = NULL; + } buffer->output = NULL; + } } static int wayland_output_disable(struct weston_output *base) { + const struct weston_renderer *renderer = base->compositor->renderer; struct wayland_output *output = to_wayland_output(base); - struct wayland_backend *b = to_wayland_backend(base->compositor); + + assert(output); if (!output->base.enabled) return 0; - if (b->use_pixman) { - pixman_renderer_output_destroy(&output->base); + wayland_output_destroy_shm_buffers(output); + + if (renderer->type == WESTON_RENDERER_PIXMAN) { + renderer->pixman->output_destroy(&output->base); #ifdef ENABLE_EGL } else { - gl_renderer->output_destroy(&output->base); + weston_gl_borders_fini(&output->gl.borders, &output->base); + + renderer->gl->output_destroy(&output->base); wl_egl_window_destroy(output->gl.egl_window); #endif } - wayland_output_destroy_shm_buffers(output); - wayland_backend_destroy_output_surface(output); if (output->frame) frame_destroy(output->frame); - cairo_surface_destroy(output->gl.border.top); - cairo_surface_destroy(output->gl.border.left); - cairo_surface_destroy(output->gl.border.right); - cairo_surface_destroy(output->gl.border.bottom); - return 0; } @@ -752,6 +724,8 @@ wayland_output_destroy(struct weston_output *base) { struct wayland_output *output = to_wayland_output(base); + assert(output); + wayland_output_disable(&output->base); weston_output_release(&output->base); @@ -763,29 +737,36 @@ wayland_output_destroy(struct weston_output *base) free(output); } -static const struct wl_shell_surface_listener shell_surface_listener; - #ifdef ENABLE_EGL static int wayland_output_init_gl_renderer(struct wayland_output *output) { - int32_t fwidth = 0, fheight = 0; + const struct weston_mode *mode = output->base.current_mode; + struct wayland_backend *b = output->backend; + const struct weston_renderer *renderer; struct gl_renderer_output_options options = { - .drm_formats = wayland_formats, - .drm_formats_count = ARRAY_LENGTH(wayland_formats), + .formats = b->formats, + .formats_count = b->formats_count, }; if (output->frame) { - fwidth = frame_width(output->frame); - fheight = frame_height(output->frame); + frame_interior(output->frame, &options.area.x, &options.area.y, + &options.area.width, &options.area.height); + options.fb_size.width = frame_width(output->frame); + options.fb_size.height = frame_height(output->frame); } else { - fwidth = output->base.current_mode->width; - fheight = output->base.current_mode->height; + options.area.x = 0; + options.area.y = 0; + options.area.width = mode->width; + options.area.height = mode->height; + options.fb_size.width = mode->width; + options.fb_size.height = mode->height; } output->gl.egl_window = wl_egl_window_create(output->parent.surface, - fwidth, fheight); + options.fb_size.width, + options.fb_size.height); if (!output->gl.egl_window) { weston_log("failure to create wl_egl_window\n"); return -1; @@ -793,7 +774,9 @@ wayland_output_init_gl_renderer(struct wayland_output *output) options.window_for_legacy = output->gl.egl_window; options.window_for_platform = output->gl.egl_window; - if (gl_renderer->output_window_create(&output->base, &options) < 0) + renderer = output->base.compositor->renderer; + + if (renderer->gl->output_window_create(&output->base, &options) < 0) goto cleanup_window; return 0; @@ -807,97 +790,86 @@ wayland_output_init_gl_renderer(struct wayland_output *output) static int wayland_output_init_pixman_renderer(struct wayland_output *output) { + struct weston_renderer *renderer = output->base.compositor->renderer; const struct pixman_renderer_output_options options = { .use_shadow = true, + .fb_size = { + .width = output->base.current_mode->width, + .height = output->base.current_mode->height + }, + .format = output->backend->formats[0] }; - return pixman_renderer_output_create(&output->base, &options); + return renderer->pixman->output_create(&output->base, &options); } static void wayland_output_resize_surface(struct wayland_output *output) { - struct wayland_backend *b = - to_wayland_backend(output->base.compositor); - int32_t ix, iy, iwidth, iheight; - int32_t width, height; + struct wayland_backend *b = output->backend; + /* Defaults for without frame: */ + struct weston_size fb_size = { + .width = output->base.current_mode->width, + .height = output->base.current_mode->height + }; + struct weston_geometry area = { + .x = 0, + .y = 0, + .width = fb_size.width, + .height = fb_size.height + }; + struct weston_geometry inp = area; + struct weston_geometry opa = area; struct wl_region *region; - width = output->base.current_mode->width; - height = output->base.current_mode->height; - if (output->frame) { - frame_resize_inside(output->frame, width, height); - - frame_input_rect(output->frame, &ix, &iy, &iwidth, &iheight); - region = wl_compositor_create_region(b->parent.compositor); - wl_region_add(region, ix, iy, iwidth, iheight); - wl_surface_set_input_region(output->parent.surface, region); - wl_region_destroy(region); - - if (output->parent.xdg_surface) { - xdg_surface_set_window_geometry(output->parent.xdg_surface, - ix, - iy, - iwidth, - iheight); - } + frame_resize_inside(output->frame, area.width, area.height); + frame_interior(output->frame, &area.x, &area.y, NULL, NULL); + fb_size.width = frame_width(output->frame); + fb_size.height = frame_height(output->frame); - frame_opaque_rect(output->frame, &ix, &iy, &iwidth, &iheight); - region = wl_compositor_create_region(b->parent.compositor); - wl_region_add(region, ix, iy, iwidth, iheight); - wl_surface_set_opaque_region(output->parent.surface, region); - wl_region_destroy(region); + frame_input_rect(output->frame, &inp.x, &inp.y, + &inp.width, &inp.height); + frame_opaque_rect(output->frame, &opa.x, &opa.y, + &opa.width, &opa.height); + } - width = frame_width(output->frame); - height = frame_height(output->frame); - } else { - region = wl_compositor_create_region(b->parent.compositor); - wl_region_add(region, 0, 0, width, height); - wl_surface_set_input_region(output->parent.surface, region); - wl_region_destroy(region); - - region = wl_compositor_create_region(b->parent.compositor); - wl_region_add(region, 0, 0, width, height); - wl_surface_set_opaque_region(output->parent.surface, region); - wl_region_destroy(region); - - if (output->parent.xdg_surface) { - xdg_surface_set_window_geometry(output->parent.xdg_surface, - 0, - 0, - width, - height); - } + region = wl_compositor_create_region(b->parent.compositor); + wl_region_add(region, inp.x, inp.y, inp.width, inp.height); + wl_surface_set_input_region(output->parent.surface, region); + wl_region_destroy(region); + + if (output->parent.xdg_surface) { + xdg_surface_set_window_geometry(output->parent.xdg_surface, + inp.x, inp.y, + inp.width, inp.height); } + region = wl_compositor_create_region(b->parent.compositor); + wl_region_add(region, opa.x, opa.y, opa.width, opa.height); + wl_surface_set_opaque_region(output->parent.surface, region); + wl_region_destroy(region); + #ifdef ENABLE_EGL if (output->gl.egl_window) { wl_egl_window_resize(output->gl.egl_window, - width, height, 0, 0); + fb_size.width, fb_size.height, 0, 0); + weston_renderer_resize_output(&output->base, &fb_size, &area); /* These will need to be re-created due to the resize */ - gl_renderer->output_set_border(&output->base, - GL_RENDERER_BORDER_TOP, - 0, 0, 0, NULL); - cairo_surface_destroy(output->gl.border.top); - output->gl.border.top = NULL; - gl_renderer->output_set_border(&output->base, - GL_RENDERER_BORDER_LEFT, - 0, 0, 0, NULL); - cairo_surface_destroy(output->gl.border.left); - output->gl.border.left = NULL; - gl_renderer->output_set_border(&output->base, - GL_RENDERER_BORDER_RIGHT, - 0, 0, 0, NULL); - cairo_surface_destroy(output->gl.border.right); - output->gl.border.right = NULL; - gl_renderer->output_set_border(&output->base, - GL_RENDERER_BORDER_BOTTOM, - 0, 0, 0, NULL); - cairo_surface_destroy(output->gl.border.bottom); - output->gl.border.bottom = NULL; - } + weston_gl_borders_fini(&output->gl.borders, &output->base); + } else #endif + { + /* + * Pixman-renderer never knows about decorations, we blit them + * ourselves. + */ + struct weston_size pm_size = { + .width = area.width, + .height = area.height + }; + weston_renderer_resize_output(&output->base, &pm_size, NULL); + } wayland_output_destroy_shm_buffers(output); } @@ -905,8 +877,7 @@ wayland_output_resize_surface(struct wayland_output *output) static int wayland_output_set_windowed(struct wayland_output *output) { - struct wayland_backend *b = - to_wayland_backend(output->base.compositor); + struct wayland_backend *b = output->backend; if (output->frame) return 0; @@ -928,8 +899,6 @@ wayland_output_set_windowed(struct wayland_output *output) if (output->parent.xdg_toplevel) { xdg_toplevel_unset_fullscreen(output->parent.xdg_toplevel); - } else if (output->parent.shell_surface) { - wl_shell_surface_set_toplevel(output->parent.shell_surface); } else { abort(); } @@ -939,7 +908,6 @@ wayland_output_set_windowed(struct wayland_output *output) static void wayland_output_set_fullscreen(struct wayland_output *output, - enum wl_shell_surface_fullscreen_method method, uint32_t framerate, struct wl_output *target) { if (output->frame) { @@ -951,9 +919,6 @@ wayland_output_set_fullscreen(struct wayland_output *output, if (output->parent.xdg_toplevel) { xdg_toplevel_set_fullscreen(output->parent.xdg_toplevel, target); - } else if (output->parent.shell_surface) { - wl_shell_surface_set_fullscreen(output->parent.shell_surface, - method, framerate, target); } else { abort(); } @@ -1030,7 +995,7 @@ static enum mode_status wayland_output_fullscreen_shell_mode_feedback(struct wayland_output *output, struct weston_mode *mode) { - struct wayland_backend *b = to_wayland_backend(output->base.compositor); + struct wayland_backend *b = output->backend; struct zwp_fullscreen_shell_mode_feedback_v1 *mode_feedback; enum mode_status mode_status; int ret = 0; @@ -1059,29 +1024,38 @@ wayland_output_fullscreen_shell_mode_feedback(struct wayland_output *output, } static int -wayland_output_switch_mode(struct weston_output *output_base, - struct weston_mode *mode) +wayland_output_switch_mode_finish(struct wayland_output *output) { - struct wayland_output *output = to_wayland_output(output_base); - struct wayland_backend *b; - struct wl_surface *old_surface; - struct weston_mode *old_mode; - enum mode_status mode_status; + struct weston_renderer *renderer = output->base.compositor->renderer; - if (output_base == NULL) { - weston_log("output is NULL.\n"); - return -1; + if (renderer->type == WESTON_RENDERER_PIXMAN) { + renderer->pixman->output_destroy(&output->base); + if (wayland_output_init_pixman_renderer(output) < 0) + return -1; +#ifdef ENABLE_EGL + } else { + renderer->gl->output_destroy(&output->base); + wl_egl_window_destroy(output->gl.egl_window); + if (wayland_output_init_gl_renderer(output) < 0) + return -1; +#endif } - if (mode == NULL) { - weston_log("mode is NULL.\n"); - return -1; - } + weston_output_schedule_repaint(&output->base); - b = to_wayland_backend(output_base->compositor); + return 0; +} - if (output->parent.xdg_surface || output->parent.shell_surface || !b->parent.fshell) - return -1; +static int +wayland_output_switch_mode_fshell(struct wayland_output *output, + struct weston_mode *mode) +{ + struct wayland_backend *b; + struct wl_surface *old_surface; + struct weston_mode *old_mode; + enum mode_status mode_status; + + b = output->backend; mode = wayland_output_choose_mode(output, mode); if (mode == NULL) @@ -1117,26 +1091,57 @@ wayland_output_switch_mode(struct weston_output *output_base, old_mode->flags &= ~WL_OUTPUT_MODE_CURRENT; output->base.current_mode->flags |= WL_OUTPUT_MODE_CURRENT; - if (b->use_pixman) { - pixman_renderer_output_destroy(output_base); - if (wayland_output_init_pixman_renderer(output) < 0) - goto err_output; -#ifdef ENABLE_EGL - } else { - gl_renderer->output_destroy(output_base); - wl_egl_window_destroy(output->gl.egl_window); - if (wayland_output_init_gl_renderer(output) < 0) - goto err_output; -#endif - } wl_surface_destroy(old_surface); - weston_output_schedule_repaint(&output->base); + return wayland_output_switch_mode_finish(output); +} - return 0; +static int +wayland_output_switch_mode_xdg(struct wayland_output *output, + struct weston_mode *mode) +{ + if (output->backend->sprawl_across_outputs) + return -1; + + assert (&output->mode == output->base.current_mode); + + output->mode.width = mode->width; + output->mode.height = mode->height; + + if (mode->width < WINDOW_MIN_WIDTH) + output->mode.width = WINDOW_MIN_WIDTH; + if (mode->width > WINDOW_MAX_WIDTH) + output->mode.width = WINDOW_MAX_WIDTH; + + if (mode->height < WINDOW_MIN_HEIGHT) + output->mode.height = WINDOW_MIN_HEIGHT; + if (mode->height > WINDOW_MAX_HEIGHT) + output->mode.height = WINDOW_MAX_HEIGHT; + + /* Blow the old buffers because we changed size/surfaces */ + wayland_output_resize_surface(output); + + return wayland_output_switch_mode_finish(output); +} + +static int +wayland_output_switch_mode(struct weston_output *output_base, + struct weston_mode *mode) +{ + struct wayland_output *output = to_wayland_output(output_base); + + assert(output); + + if (mode == NULL) { + weston_log("mode is NULL.\n"); + return -1; + } + + if (output->parent.xdg_surface) + return wayland_output_switch_mode_xdg(output, mode); + if (output->backend->parent.fshell) + return wayland_output_switch_mode_fshell(output, mode); -err_output: - /* XXX */ return -1; } @@ -1162,7 +1167,26 @@ handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *toplevel, output->parent.configure_height = height; output->parent.wait_for_configure = false; - /* FIXME: implement resizing */ + + if (width > 0 && height > 0) { + if (output->frame) { + int32_t top, bottom, left, right; + frame_border_sizes(output->frame, &top, &bottom, + &left, &right); + width -= left + right; + height -= top + bottom; + } + output->native_mode.width = width; + output->native_mode.height = height; + + if (weston_output_mode_set_native(&output->base, + &output->native_mode, + output->base.current_scale) < 0) { + output->native_mode.width = output->mode.width; + output->native_mode.height = output->mode.height; + weston_log("Mode switch failed\n"); + } + } } static void @@ -1185,7 +1209,7 @@ static const struct xdg_toplevel_listener xdg_toplevel_listener = { static int wayland_backend_create_output_surface(struct wayland_output *output) { - struct wayland_backend *b = to_wayland_backend(output->base.compositor); + struct wayland_backend *b = output->backend; assert(!output->parent.surface); @@ -1221,20 +1245,6 @@ wayland_backend_create_output_surface(struct wayland_output *output) weston_log("wayland-backend: Using xdg_wm_base\n"); } - else if (b->parent.shell) { - output->parent.shell_surface = - wl_shell_get_shell_surface(b->parent.shell, - output->parent.surface); - if (!output->parent.shell_surface) { - wl_surface_destroy(output->parent.surface); - return -1; - } - - wl_shell_surface_add_listener(output->parent.shell_surface, - &shell_surface_listener, output); - - weston_log("wayland-backend: Using wl_shell\n"); - } return 0; } @@ -1243,10 +1253,14 @@ static int wayland_output_enable(struct weston_output *base) { struct wayland_output *output = to_wayland_output(base); - struct wayland_backend *b = to_wayland_backend(base->compositor); + struct wayland_backend *b; enum mode_status mode_status; int ret = 0; + assert(output); + + b = output->backend; + wl_list_init(&output->shm.buffers); wl_list_init(&output->shm.free_buffers); @@ -1261,7 +1275,7 @@ wayland_output_enable(struct weston_output *base) if (ret < 0) return -1; - if (b->use_pixman) { + if (base->compositor->renderer->type == WESTON_RENDERER_PIXMAN) { if (wayland_output_init_pixman_renderer(output) < 0) goto err_output; @@ -1295,13 +1309,9 @@ wayland_output_enable(struct weston_output *base) output->parent.draw_initial_frame = true; } - } else { - wayland_output_set_fullscreen(output, - WL_SHELL_SURFACE_FULLSCREEN_METHOD_DRIVER, - output->mode.refresh, output->parent.output); } } else if (b->fullscreen) { - wayland_output_set_fullscreen(output, 0, 0, NULL); + wayland_output_set_fullscreen(output, 0, NULL); } else { wayland_output_set_windowed(output); } @@ -1326,9 +1336,16 @@ static int wayland_output_attach_head(struct weston_output *output_base, struct weston_head *head_base) { - struct wayland_backend *b = to_wayland_backend(output_base->compositor); struct wayland_output *output = to_wayland_output(output_base); struct wayland_head *head = to_wayland_head(head_base); + struct wayland_backend *b; + + assert(output); + + if (!head) + return -1; + + b = output->backend; if (!wl_list_empty(&output->base.head_list)) return -1; @@ -1353,6 +1370,8 @@ wayland_output_detach_head(struct weston_output *output_base, { struct wayland_output *output = to_wayland_output(output_base); + assert(output); + /* Rely on the disable hook if the output was enabled. We do not * support cloned heads, so detaching is guaranteed to disable the * output. @@ -1366,8 +1385,10 @@ wayland_output_detach_head(struct weston_output *output_base, } static struct weston_output * -wayland_output_create(struct weston_compositor *compositor, const char *name) +wayland_output_create(struct weston_backend *backend, const char *name) { + struct wayland_backend *b = container_of(backend, struct wayland_backend, base); + struct weston_compositor *compositor = b->compositor; struct wayland_output *output; char *title; @@ -1394,14 +1415,17 @@ wayland_output_create(struct weston_compositor *compositor, const char *name) output->base.attach_head = wayland_output_attach_head; output->base.detach_head = wayland_output_detach_head; + output->backend = b; + weston_compositor_add_pending_output(&output->base, compositor); return &output->base; } static struct wayland_head * -wayland_head_create(struct weston_compositor *compositor, const char *name) +wayland_head_create(struct wayland_backend *backend, const char *name) { + struct weston_compositor *compositor = backend->compositor; struct wayland_head *head; assert(name); @@ -1411,6 +1435,9 @@ wayland_head_create(struct weston_compositor *compositor, const char *name) return NULL; weston_head_init(&head->base, name); + + head->base.backend = &backend->base; + weston_head_set_connection_status(&head->base, true); weston_compositor_add_head(compositor, &head->base); @@ -1418,17 +1445,19 @@ wayland_head_create(struct weston_compositor *compositor, const char *name) } static int -wayland_head_create_windowed(struct weston_compositor *compositor, +wayland_head_create_windowed(struct weston_backend *base, const char *name) { - if (!wayland_head_create(compositor, name)) + struct wayland_backend *backend = to_wayland_backend(base); + + if (!wayland_head_create(backend, name)) return -1; return 0; } static int -wayland_head_create_for_parent_output(struct weston_compositor *compositor, +wayland_head_create_for_parent_output(struct wayland_backend *backend, struct wayland_parent_output *poutput) { struct wayland_head *head; @@ -1439,7 +1468,7 @@ wayland_head_create_for_parent_output(struct weston_compositor *compositor, if (ret < 1 || (unsigned)ret >= sizeof(name)) return -1; - head = wayland_head_create(compositor, name); + head = wayland_head_create(backend, name); if (!head) return -1; @@ -1458,8 +1487,12 @@ wayland_head_create_for_parent_output(struct weston_compositor *compositor, } static void -wayland_head_destroy(struct wayland_head *head) +wayland_head_destroy(struct weston_head *base) { + struct wayland_head *head = to_wayland_head(base); + + assert(head); + if (head->parent_output) head->parent_output->head = NULL; @@ -1474,6 +1507,9 @@ wayland_output_set_size(struct weston_output *base, int width, int height) struct weston_head *head; int output_width, output_height; + if (!output) + return -1; + /* We can only be called once. */ assert(!output->base.current_mode); @@ -1554,7 +1590,7 @@ static int wayland_output_setup_fullscreen(struct wayland_output *output, struct wayland_head *head) { - struct wayland_backend *b = to_wayland_backend(output->base.compositor); + struct wayland_backend *b = output->backend; int width = 0, height = 0; output->base.scale = 1; @@ -1564,13 +1600,10 @@ wayland_output_setup_fullscreen(struct wayland_output *output, return -1; /* What should size be set if conditional is false? */ - if (b->parent.xdg_wm_base || b->parent.shell) { + if (b->parent.xdg_wm_base) { if (output->parent.xdg_toplevel) xdg_toplevel_set_fullscreen(output->parent.xdg_toplevel, output->parent.output); - else if (output->parent.shell_surface) - wl_shell_surface_set_fullscreen(output->parent.shell_surface, - 0, 0, NULL); wl_display_roundtrip(b->parent.wl_display); @@ -1594,36 +1627,6 @@ wayland_output_setup_fullscreen(struct wayland_output *output, return -1; } -static void -shell_surface_ping(void *data, struct wl_shell_surface *shell_surface, - uint32_t serial) -{ - wl_shell_surface_pong(shell_surface, serial); -} - -static void -shell_surface_configure(void *data, struct wl_shell_surface *shell_surface, - uint32_t edges, int32_t width, int32_t height) -{ - struct wayland_output *output = data; - - output->parent.configure_width = width; - output->parent.configure_height = height; - - /* FIXME: implement resizing */ -} - -static void -shell_surface_popup_done(void *data, struct wl_shell_surface *shell_surface) -{ -} - -static const struct wl_shell_surface_listener shell_surface_listener = { - shell_surface_ping, - shell_surface_configure, - shell_surface_popup_done -}; - /* Events received from the wayland-server this compositor is client of: */ /* parent input interface */ @@ -1661,11 +1664,12 @@ input_handle_pointer_enter(void *data, struct wl_pointer *pointer, int32_t fx, fy; enum theme_location location; double x, y; + struct weston_coord_global pos; if (!surface) { input->output = NULL; input->has_focus = false; - notify_pointer_focus(&input->base, NULL, 0, 0); + clear_pointer_focus(&input->base); return; } @@ -1690,16 +1694,17 @@ input_handle_pointer_enter(void *data, struct wl_pointer *pointer, location = THEME_LOCATION_CLIENT_AREA; } - weston_output_transform_coordinate(&input->output->base, x, y, &x, &y); + pos = weston_coord_global_from_output_point(x, y, + &input->output->base); if (location == THEME_LOCATION_CLIENT_AREA) { input->has_focus = true; - notify_pointer_focus(&input->base, &input->output->base, x, y); + notify_pointer_focus(&input->base, &input->output->base, pos); wl_pointer_set_cursor(input->parent.pointer, input->enter_serial, NULL, 0, 0); } else { input->has_focus = false; - notify_pointer_focus(&input->base, NULL, 0, 0); + clear_pointer_focus(&input->base); input_set_cursor(input); } } @@ -1720,7 +1725,7 @@ input_handle_pointer_leave(void *data, struct wl_pointer *pointer, weston_output_schedule_repaint(&input->output->base); } - notify_pointer_focus(&input->base, NULL, 0, 0); + clear_pointer_focus(&input->base); input->output = NULL; input->has_focus = false; } @@ -1734,6 +1739,7 @@ input_handle_motion(void *data, struct wl_pointer *pointer, enum theme_location location; bool want_frame = false; double x, y; + struct weston_coord_global pos; struct timespec ts; if (!input->output) @@ -1755,25 +1761,26 @@ input_handle_motion(void *data, struct wl_pointer *pointer, location = THEME_LOCATION_CLIENT_AREA; } - weston_output_transform_coordinate(&input->output->base, x, y, &x, &y); + pos = weston_coord_global_from_output_point(x, y, + &input->output->base); if (input->has_focus && location != THEME_LOCATION_CLIENT_AREA) { input_set_cursor(input); - notify_pointer_focus(&input->base, NULL, 0, 0); + clear_pointer_focus(&input->base); input->has_focus = false; want_frame = true; } else if (!input->has_focus && location == THEME_LOCATION_CLIENT_AREA) { wl_pointer_set_cursor(input->parent.pointer, input->enter_serial, NULL, 0, 0); - notify_pointer_focus(&input->base, &input->output->base, x, y); + notify_pointer_focus(&input->base, &input->output->base, pos); input->has_focus = true; want_frame = true; } if (location == THEME_LOCATION_CLIENT_AREA) { timespec_from_msec(&ts, time); - notify_motion_absolute(&input->base, &ts, x, y); + notify_motion_absolute(&input->base, &ts, pos); want_frame = true; } @@ -1801,9 +1808,6 @@ input_handle_button(void *data, struct wl_pointer *pointer, if (input->output->parent.xdg_toplevel) xdg_toplevel_move(input->output->parent.xdg_toplevel, input->parent.seat, serial); - else if (input->output->parent.shell_surface) - wl_shell_surface_move(input->output->parent.shell_surface, - input->parent.seat, serial); frame_status_clear(input->output->frame, FRAME_STATUS_MOVE); return; @@ -1819,6 +1823,13 @@ input_handle_button(void *data, struct wl_pointer *pointer, return; } + if (frame_status(input->output->frame) & FRAME_STATUS_RESIZE) { + xdg_toplevel_resize(input->output->parent.xdg_toplevel, + input->parent.seat, serial, + location); + frame_status_clear(input->output->frame, + FRAME_STATUS_RESIZE); + } if (frame_status(input->output->frame) & FRAME_STATUS_REPAINT) weston_output_schedule_repaint(&input->output->base); @@ -2126,6 +2137,7 @@ input_handle_touch_down(void *data, struct wl_touch *wl_touch, enum theme_location location; bool first_touch; int32_t fx, fy; + struct weston_coord_global pos; double x, y; struct timespec ts; @@ -2157,9 +2169,6 @@ input_handle_touch_down(void *data, struct wl_touch *wl_touch, if (output->parent.xdg_toplevel) xdg_toplevel_move(output->parent.xdg_toplevel, input->parent.seat, serial); - else if (output->parent.shell_surface) - wl_shell_surface_move(output->parent.shell_surface, - input->parent.seat, serial); frame_status_clear(output->frame, FRAME_STATUS_MOVE); return; @@ -2169,9 +2178,9 @@ input_handle_touch_down(void *data, struct wl_touch *wl_touch, return; } - weston_output_transform_coordinate(&output->base, x, y, &x, &y); + pos = weston_coord_global_from_output_point(x,y, &output->base); - notify_touch(input->touch_device, &ts, id, x, y, WL_TOUCH_DOWN); + notify_touch(input->touch_device, &ts, id, &pos, WL_TOUCH_DOWN); input->touch_active = true; } @@ -2187,10 +2196,6 @@ input_handle_touch_up(void *data, struct wl_touch *wl_touch, timespec_from_msec(&ts, time); input->touch_points--; - if (input->touch_points == 0) { - input->touch_focus = NULL; - input->touch_active = false; - } if (!output) return; @@ -2212,7 +2217,7 @@ input_handle_touch_up(void *data, struct wl_touch *wl_touch, } if (active) - notify_touch(input->touch_device, &ts, id, 0, 0, WL_TOUCH_UP); + notify_touch(input->touch_device, &ts, id, NULL, WL_TOUCH_UP); } static void @@ -2224,6 +2229,7 @@ input_handle_touch_motion(void *data, struct wl_touch *wl_touch, struct wayland_output *output = input->touch_focus; int32_t fx, fy; double x, y; + struct weston_coord_global pos; struct timespec ts; x = wl_fixed_to_double(fixed_x); @@ -2239,9 +2245,9 @@ input_handle_touch_motion(void *data, struct wl_touch *wl_touch, y -= fy; } - weston_output_transform_coordinate(&output->base, x, y, &x, &y); + pos = weston_coord_global_from_output_point(x, y, &output->base); - notify_touch(input->touch_device, &ts, id, x, y, WL_TOUCH_MOTION); + notify_touch(input->touch_device, &ts, id, &pos, WL_TOUCH_MOTION); } static void @@ -2253,6 +2259,11 @@ input_handle_touch_frame(void *data, struct wl_touch *wl_touch) return; notify_touch_frame(input->touch_device); + + if (input->touch_points == 0) { + input->touch_focus = NULL; + input->touch_active = false; + } } static void @@ -2439,7 +2450,8 @@ wayland_input_destroy(struct wayland_input *input) if (input->touch_device) weston_touch_device_destroy(input->touch_device); - weston_seat_release(&input->base); + if (input->seat_initialized) + weston_seat_release(&input->base); if (input->parent.keyboard) { if (input->seat_version >= WL_KEYBOARD_RELEASE_SINCE_VERSION) @@ -2580,7 +2592,7 @@ output_sync_callback(void *data, struct wl_callback *callback, uint32_t unused) assert(output->backend->sprawl_across_outputs); - wayland_head_create_for_parent_output(output->backend->compositor, output); + wayland_head_create_for_parent_output(output->backend, output); } static const struct wl_callback_listener output_sync_listener = { @@ -2629,7 +2641,7 @@ wayland_parent_output_destroy(struct wayland_parent_output *output) wl_callback_destroy(output->sync_cb); if (output->head) - wayland_head_destroy(output->head); + wayland_head_destroy(&output->head->base); wl_output_destroy(output->global); free(output->physical.make); @@ -2671,10 +2683,6 @@ registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, &xdg_wm_base_interface, 1); xdg_wm_base_add_listener(b->parent.xdg_wm_base, &wm_base_listener, b); - } else if (strcmp(interface, "wl_shell") == 0) { - b->parent.shell = - wl_registry_bind(registry, name, - &wl_shell_interface, 1); } else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0) { b->parent.fshell = wl_registry_bind(registry, name, @@ -2738,18 +2746,25 @@ wayland_backend_handle_event(int fd, uint32_t mask, void *data) } static void -wayland_destroy(struct weston_compositor *ec) +wayland_destroy(struct weston_backend *backend) { - struct wayland_backend *b = to_wayland_backend(ec); + struct wayland_backend *b = container_of(backend, struct wayland_backend, base); + struct weston_compositor *ec = b->compositor; struct weston_head *base, *next; + struct wayland_parent_output *output, *next_output; struct wayland_input *input, *next_input; wl_event_source_remove(b->parent.wl_source); weston_compositor_shutdown(ec); - wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) - wayland_head_destroy(to_wayland_head(base)); + wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) { + if (to_wayland_head(base)) + wayland_head_destroy(base); + } + + wl_list_for_each_safe(output, next_output, &b->parent.output_list, link) + wayland_parent_output_destroy(output); wl_list_for_each_safe(input, next_input, &b->input_list, link) wayland_input_destroy(input); @@ -2763,9 +2778,6 @@ wayland_destroy(struct weston_compositor *ec) if (b->parent.xdg_wm_base) xdg_wm_base_destroy(b->parent.xdg_wm_base); - if (b->parent.shell) - wl_shell_destroy(b->parent.shell); - if (b->parent.fshell) zwp_fullscreen_shell_v1_release(b->parent.fshell); @@ -2784,6 +2796,8 @@ wayland_destroy(struct weston_compositor *ec) wl_display_flush(b->parent.wl_display); wl_display_disconnect(b->parent.wl_display); + cleanup_after_cairo(); + free(b); } @@ -2833,7 +2847,7 @@ fullscreen_binding(struct weston_keyboard *keyboard, return; if (input->output->frame) - wayland_output_set_fullscreen(input->output, 0, 0, NULL); + wayland_output_set_fullscreen(input->output, 0, NULL); else wayland_output_set_windowed(input->output); @@ -2846,6 +2860,7 @@ wayland_backend_create(struct weston_compositor *compositor, { struct wayland_backend *b; struct wl_event_loop *loop; + enum weston_renderer_type renderer = new_config->renderer; int fd; b = zalloc(sizeof *b); @@ -2882,37 +2897,38 @@ wayland_backend_create(struct weston_compositor *compositor, create_cursor(b, new_config); -#ifdef ENABLE_EGL - b->use_pixman = new_config->use_pixman; -#else - b->use_pixman = true; -#endif b->fullscreen = new_config->fullscreen; - if (!b->use_pixman) { - gl_renderer = weston_load_module("gl-renderer.so", - "gl_renderer_interface"); - if (!gl_renderer) - b->use_pixman = true; - } + b->formats_count = ARRAY_LENGTH(wayland_formats); + b->formats = pixel_format_get_array(wayland_formats, b->formats_count); - if (!b->use_pixman) { + if (renderer == WESTON_RENDERER_AUTO || + renderer == WESTON_RENDERER_GL) { const struct gl_renderer_display_options options = { .egl_platform = EGL_PLATFORM_WAYLAND_KHR, .egl_native_display = b->parent.wl_display, .egl_surface_type = EGL_WINDOW_BIT, - .drm_formats = wayland_formats, - .drm_formats_count = ARRAY_LENGTH(wayland_formats), + .formats = b->formats, + .formats_count = b->formats_count, }; - if (gl_renderer->display_create(compositor, &options) < 0) { - weston_log("Failed to initialize the GL renderer; " - "falling back to pixman.\n"); - b->use_pixman = true; + + if (weston_compositor_init_renderer(compositor, + WESTON_RENDERER_GL, + &options.base) < 0) { + weston_log("Failed to initialize the GL renderer"); + if (renderer == WESTON_RENDERER_AUTO) { + weston_log_continue("; falling back to Pixman.\n"); + renderer = WESTON_RENDERER_PIXMAN; + } else { + goto err_display; + } } } - if (b->use_pixman) { - if (pixman_renderer_init(compositor) < 0) { + if (renderer == WESTON_RENDERER_PIXMAN) { + if (weston_compositor_init_renderer(compositor, + WESTON_RENDERER_PIXMAN, + NULL) < 0) { weston_log("Failed to initialize pixman renderer\n"); goto err_display; } @@ -2943,6 +2959,7 @@ wayland_backend_create(struct weston_compositor *compositor, wl_display_disconnect(b->parent.wl_display); err_compositor: weston_compositor_shutdown(compositor); + free(b->formats); free(b); return NULL; } @@ -2959,6 +2976,8 @@ wayland_backend_destroy(struct wayland_backend *b) wl_cursor_theme_destroy(b->cursor_theme); weston_compositor_shutdown(b->compositor); + cleanup_after_cairo(); + free(b->formats); free(b); } @@ -3001,13 +3020,13 @@ weston_backend_init(struct weston_compositor *compositor, wl_display_roundtrip(b->parent.wl_display); wl_list_for_each(poutput, &b->parent.output_list, link) - wayland_head_create_for_parent_output(compositor, poutput); + wayland_head_create_for_parent_output(b, poutput); return 0; } if (new_config.fullscreen) { - if (!wayland_head_create(compositor, "wayland-fullscreen")) { + if (!wayland_head_create(b, "wayland-fullscreen")) { weston_log("Unable to create a fullscreen head.\n"); goto err_outputs; } diff --git a/libweston/backend-x11/meson.build b/libweston/backend-x11/meson.build index 3e6ca54fe..5d30bcce8 100644 --- a/libweston/backend-x11/meson.build +++ b/libweston/backend-x11/meson.build @@ -16,6 +16,7 @@ if not dep_x11_xcb.found() endif deps_x11 = [ + dep_egl, # optional dep_libweston_private, dep_libdrm_headers, dep_x11_xcb, @@ -37,13 +38,6 @@ if dep_xcb_xkb.found() config_h.set('HAVE_XCB_XKB', '1') endif -if get_option('renderer-gl') - if not dep_egl.found() - error('x11-backend + gl-renderer requires egl which was not found. Or, you can use \'-Dbackend-x11=false\' or \'-Drenderer-gl=false\'.') - endif - deps_x11 += dep_egl -endif - plugin_x11 = shared_library( 'x11-backend', srcs_x11, diff --git a/libweston/backend-x11/x11.c b/libweston/backend-x11/x11.c index 5c82584ba..43fff36e1 100644 --- a/libweston/backend-x11/x11.c +++ b/libweston/backend-x11/x11.c @@ -59,10 +59,12 @@ #include "renderer-gl/gl-renderer.h" #include "shared/weston-drm-fourcc.h" #include "shared/weston-egl-ext.h" +#include "shared/xalloc.h" #include "pixman-renderer.h" #include "presentation-time-server-protocol.h" #include "linux-dmabuf.h" #include "linux-explicit-synchronization.h" +#include #include #define DEFAULT_AXIS_STEP_DISTANCE 10 @@ -92,15 +94,13 @@ struct x11_backend { uint8_t xkb_event_base; int fullscreen; int no_input; - int use_pixman; int has_net_wm_state_fullscreen; /* We could map multi-pointer X to multiple wayland seats, but * for now we only support core X input. */ struct weston_seat core_seat; - double prev_x; - double prev_y; + struct weston_coord_global prev_pos; struct { xcb_atom_t wm_protocols; @@ -119,6 +119,10 @@ struct x11_backend { xcb_atom_t cardinal; xcb_atom_t xkb_names; } atom; + xcb_generic_event_t *prev_event; + + const struct pixel_format_info **formats; + unsigned int formats_count; }; struct x11_head { @@ -127,6 +131,7 @@ struct x11_head { struct x11_output { struct weston_output base; + struct x11_backend *backend; xcb_window_t window; struct weston_mode mode; @@ -135,7 +140,7 @@ struct x11_output { xcb_gc_t gc; xcb_shm_seg_t segment; - pixman_image_t *hw_surface; + struct weston_renderbuffer *renderbuffer; int shm_id; void *buf; uint8_t depth; @@ -149,24 +154,32 @@ struct window_delete_data { xcb_window_t window; }; -struct gl_renderer_interface *gl_renderer; +static void +x11_destroy(struct weston_backend *backend); static inline struct x11_head * to_x11_head(struct weston_head *base) { + if (base->backend->destroy != x11_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); } static inline struct x11_backend * -to_x11_backend(struct weston_compositor *base) +to_x11_backend(struct weston_backend *backend) { - return container_of(base->backend, struct x11_backend, base); + return container_of(backend, struct x11_backend, base); } static xcb_screen_t * @@ -417,13 +430,16 @@ 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); + ec->renderer->repaint_output(output_base, damage, NULL); pixman_region32_subtract(&ec->primary_plane.damage, &ec->primary_plane.damage, damage); @@ -436,8 +452,7 @@ 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 x11_backend *b; pixman_region32_t transformed_region; pixman_box32_t *rects; xcb_rectangle_t *output_rects; @@ -445,14 +460,15 @@ set_clip_for_output(struct weston_output *output_base, pixman_region32_t *region int nrects, i; xcb_generic_error_t *err; + if (!output) + return; + + b = output->backend; + pixman_region32_init(&transformed_region); - pixman_region32_copy(&transformed_region, region); - pixman_region32_translate(&transformed_region, - -output_base->x, -output_base->y); - weston_transformed_region(output_base->width, output_base->height, - output_base->transform, - output_base->current_scale, - &transformed_region, &transformed_region); + weston_region_global_to_output(&transformed_region, + output_base, + region); rects = pixman_region32_rectangles(&transformed_region, &nrects); output_rects = calloc(nrects, sizeof(xcb_rectangle_t)); @@ -486,27 +502,35 @@ 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); + const struct weston_renderer *renderer; + pixman_image_t *image; + struct weston_compositor *ec; + struct x11_backend *b; xcb_void_cookie_t cookie; xcb_generic_error_t *err; - pixman_renderer_output_set_buffer(output_base, output->hw_surface); - ec->renderer->repaint_output(output_base, damage); + assert(output); + + ec = output->base.compositor; + renderer = ec->renderer; + b = output->backend; + + image = renderer->pixman->renderbuffer_get_image(output->renderbuffer); + + ec->renderer->repaint_output(output_base, damage, output->renderbuffer); pixman_region32_subtract(&ec->primary_plane.damage, &ec->primary_plane.damage, damage); set_clip_for_output(output_base, damage); cookie = xcb_shm_put_image_checked(b->conn, output->window, output->gc, - pixman_image_get_width(output->hw_surface), - pixman_image_get_height(output->hw_surface), + pixman_image_get_width(image), + pixman_image_get_height(image), 0, 0, - pixman_image_get_width(output->hw_surface), - pixman_image_get_height(output->hw_surface), + pixman_image_get_width(image), + pixman_image_get_height(image), 0, 0, output->depth, XCB_IMAGE_FORMAT_Z_PIXMAP, 0, output->segment, 0); err = xcb_request_check(b->conn, cookie); @@ -538,8 +562,8 @@ x11_output_deinit_shm(struct x11_backend *b, struct x11_output *output) xcb_generic_error_t *err; xcb_free_gc(b->conn, output->gc); - pixman_image_unref(output->hw_surface); - output->hw_surface = NULL; + weston_renderbuffer_unref(output->renderbuffer); + output->renderbuffer = NULL; cookie = xcb_shm_detach_checked(b->conn, output->segment); err = xcb_request_check(b->conn, cookie); if (err) { @@ -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; }; @@ -699,18 +723,15 @@ get_depth_of_visual(xcb_screen_t *screen, return 0; } -static int -x11_output_init_shm(struct x11_backend *b, struct x11_output *output, - int width, int height) +static const struct pixel_format_info * +x11_output_get_shm_pixel_format(struct x11_output *output) { + struct x11_backend *b = output->backend; xcb_visualtype_t *visual_type; xcb_screen_t *screen; xcb_format_iterator_t fmt; - xcb_void_cookie_t cookie; - xcb_generic_error_t *err; const xcb_query_extension_reply_t *ext; int bitsperpixel = 0; - pixman_format_code_t pixman_format; /* Check if SHM is available */ ext = xcb_get_extension_data(b->conn, &xcb_shm_id); @@ -718,7 +739,7 @@ x11_output_init_shm(struct x11_backend *b, struct x11_output *output, /* SHM is missing */ weston_log("SHM extension is not available\n"); errno = ENOENT; - return -1; + return NULL; } screen = x11_compositor_get_default_screen(b); @@ -726,7 +747,7 @@ x11_output_init_shm(struct x11_backend *b, struct x11_output *output, if (!visual_type) { weston_log("Failed to lookup visual for root window\n"); errno = ENOENT; - return -1; + return NULL; } weston_log("Found visual, bits per value: %d, red_mask: %.8x, green_mask: %.8x, blue_mask: %.8x\n", visual_type->bits_per_rgb_value, @@ -752,19 +773,28 @@ x11_output_init_shm(struct x11_backend *b, struct x11_output *output, visual_type->green_mask == 0x00ff00 && visual_type->blue_mask == 0x0000ff) { weston_log("Will use x8r8g8b8 format for SHM surfaces\n"); - pixman_format = PIXMAN_x8r8g8b8; + return pixel_format_get_info_by_pixman(PIXMAN_x8r8g8b8); } else if (bitsperpixel == 16 && visual_type->red_mask == 0x00f800 && visual_type->green_mask == 0x0007e0 && visual_type->blue_mask == 0x00001f) { weston_log("Will use r5g6b5 format for SHM surfaces\n"); - pixman_format = PIXMAN_r5g6b5; + return pixel_format_get_info_by_pixman(PIXMAN_r5g6b5); } else { weston_log("Can't find appropriate format for SHM pixmap\n"); errno = ENOTSUP; - return -1; + return NULL; } +} +static int +x11_output_init_shm(struct x11_backend *b, struct x11_output *output, + const struct pixel_format_info *pfmt, int width, int height) +{ + struct weston_renderer *renderer = output->base.compositor->renderer; + int bitsperpixel = pfmt->bpp; + xcb_void_cookie_t cookie; + xcb_generic_error_t *err; /* Create SHM segment and attach it */ output->shm_id = shmget(IPC_PRIVATE, width * height * (bitsperpixel / 8), IPC_CREAT | S_IRWXU); @@ -790,8 +820,11 @@ x11_output_init_shm(struct x11_backend *b, struct x11_output *output, shmctl(output->shm_id, IPC_RMID, NULL); /* Now create pixman image */ - output->hw_surface = pixman_image_create_bits(pixman_format, width, height, output->buf, - width * (bitsperpixel / 8)); + output->renderbuffer = + renderer->pixman->create_image_from_ptr(&output->base, + pfmt, width, height, + output->buf, + width * (bitsperpixel / 8)); output->gc = xcb_generate_id(b->conn); xcb_create_gc(b->conn, output->gc, output->window, 0, NULL); @@ -803,12 +836,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); + struct weston_size fb_size; static uint32_t values[2]; - int ret; - b = to_x11_backend(base->compositor); - output = to_x11_output(base); + assert(output); + + b = output->backend; if (mode->width == output->mode.width && mode->height == output->mode.height) @@ -832,42 +866,22 @@ x11_output_switch_mode(struct weston_output *base, struct weston_mode *mode) XCB_CONFIG_WINDOW_HEIGHT, values); } - output->mode.width = mode->width; - output->mode.height = mode->height; + fb_size.width = output->mode.width = mode->width; + fb_size.height = output->mode.height = mode->height; - if (b->use_pixman) { - const struct pixman_renderer_output_options options = { - .use_shadow = true, - }; - pixman_renderer_output_destroy(&output->base); - x11_output_deinit_shm(b, output); + weston_renderer_resize_output(&output->base, &fb_size, NULL); - if (x11_output_init_shm(b, output, - output->base.current_mode->width, - output->base.current_mode->height) < 0) { - weston_log("Failed to initialize SHM for the X11 output\n"); + if (base->compositor->renderer->type == WESTON_RENDERER_PIXMAN) { + const struct pixel_format_info *pfmt; + x11_output_deinit_shm(b, output); + pfmt = x11_output_get_shm_pixel_format(output); + if (!pfmt) return -1; - } - - if (pixman_renderer_output_create(&output->base, &options) < 0) { - weston_log("Failed to create pixman renderer for output\n"); - x11_output_deinit_shm(b, output); + if (x11_output_init_shm(b, output, pfmt, + fb_size.width, fb_size.height) < 0) { + weston_log("Failed to initialize SHM for the X11 output\n"); return -1; } - } else { - Window xid = (Window) output->window; - const struct gl_renderer_output_options options = { - .window_for_legacy = (EGLNativeWindowType) (uintptr_t) output->window, - .window_for_platform = &xid, - .drm_formats = x11_formats, - .drm_formats_count = ARRAY_LENGTH(x11_formats), - }; - - gl_renderer->output_destroy(&output->base); - - ret = gl_renderer->output_window_create(&output->base, &options); - if (ret < 0) - return -1; } output->resize_pending = false; @@ -879,19 +893,24 @@ x11_output_switch_mode(struct weston_output *base, struct weston_mode *mode) static int x11_output_disable(struct weston_output *base) { + const struct weston_renderer *renderer = base->compositor->renderer; struct x11_output *output = to_x11_output(base); - struct x11_backend *backend = to_x11_backend(base->compositor); + struct x11_backend *backend; + + assert(output); + + backend = output->backend; if (!output->base.enabled) return 0; wl_event_source_remove(output->finish_frame_timer); - if (backend->use_pixman) { - pixman_renderer_output_destroy(&output->base); + if (renderer->type == WESTON_RENDERER_PIXMAN) { x11_output_deinit_shm(backend, output); + renderer->pixman->output_destroy(&output->base); } else { - gl_renderer->output_destroy(&output->base); + renderer->gl->output_destroy(&output->base); } xcb_destroy_window(backend->conn, output->window); @@ -905,6 +924,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); @@ -914,8 +935,14 @@ x11_output_destroy(struct weston_output *base) static int x11_output_enable(struct weston_output *base) { + const struct weston_renderer *renderer = base->compositor->renderer; 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 = output->backend; static const char name[] = "Weston Compositor"; static const char class[] = "weston-1\0Weston Compositor"; @@ -954,8 +981,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, @@ -1015,19 +1041,25 @@ x11_output_enable(struct weston_output *base) if (b->fullscreen) x11_output_wait_for_map(b, output); - if (b->use_pixman) { + if (renderer->type == WESTON_RENDERER_PIXMAN) { const struct pixman_renderer_output_options options = { .use_shadow = true, + .fb_size = { + .width = mode->width, + .height = mode->height + }, + .format = x11_output_get_shm_pixel_format(output) }; - if (x11_output_init_shm(b, output, - output->base.current_mode->width, - output->base.current_mode->height) < 0) { - weston_log("Failed to initialize SHM for the X11 output\n"); + if (!options.format) goto err; - } - if (pixman_renderer_output_create(&output->base, &options) < 0) { + if (renderer->pixman->output_create(&output->base, &options) < 0) { weston_log("Failed to create pixman renderer for output\n"); - x11_output_deinit_shm(b, output); + goto err; + } + if (x11_output_init_shm(b, output, options.format, + mode->width, mode->height) < 0) { + weston_log("Failed to initialize SHM for the X11 output\n"); + renderer->pixman->output_destroy(&output->base); goto err; } @@ -1039,12 +1071,17 @@ x11_output_enable(struct weston_output *base) const struct gl_renderer_output_options options = { .window_for_legacy = (EGLNativeWindowType) (uintptr_t) output->window, .window_for_platform = &xid, - .drm_formats = x11_formats, - .drm_formats_count = ARRAY_LENGTH(x11_formats), + .formats = b->formats, + .formats_count = b->formats_count, + .area.x = 0, + .area.y = 0, + .area.width = mode->width, + .area.height = mode->height, + .fb_size.width = mode->width, + .fb_size.height = mode->height, }; - ret = gl_renderer->output_window_create(&output->base, - &options); + ret = renderer->gl->output_window_create(base, &options); if (ret < 0) goto err; @@ -1062,9 +1099,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 +1114,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 = output->backend; + scrn = b->screen; + /* We can only be called once. */ assert(!output->base.current_mode); @@ -1130,8 +1171,10 @@ x11_output_set_size(struct weston_output *base, int width, int height) } static struct weston_output * -x11_output_create(struct weston_compositor *compositor, const char *name) +x11_output_create(struct weston_backend *backend, const char *name) { + struct x11_backend *b = container_of(backend, struct x11_backend, base); + struct weston_compositor *compositor = b->compositor; struct x11_output *output; /* name can't be NULL. */ @@ -1148,14 +1191,17 @@ x11_output_create(struct weston_compositor *compositor, const char *name) output->base.enable = x11_output_enable; output->base.attach_head = NULL; + output->backend = b; + weston_compositor_add_pending_output(&output->base, compositor); return &output->base; } static int -x11_head_create(struct weston_compositor *compositor, const char *name) +x11_head_create(struct weston_backend *base, const char *name) { + struct x11_backend *backend = to_x11_backend(base); struct x11_head *head; assert(name); @@ -1165,15 +1211,22 @@ x11_head_create(struct weston_compositor *compositor, const char *name) return -1; weston_head_init(&head->base, name); + + head->base.backend = &backend->base; + weston_head_set_connection_status(&head->base, true); - weston_compositor_add_head(compositor, &head->base); + weston_compositor_add_head(backend->compositor, &head->base); return 0; } 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); } @@ -1375,7 +1428,7 @@ x11_backend_deliver_motion_event(struct x11_backend *b, xcb_generic_event_t *event) { struct x11_output *output; - double x, y; + struct weston_coord_global pos; struct weston_pointer_motion_event motion_event = { 0 }; xcb_motion_notify_event_t *motion_notify = (xcb_motion_notify_event_t *) event; @@ -1387,23 +1440,19 @@ x11_backend_deliver_motion_event(struct x11_backend *b, if (!output) return; - weston_output_transform_coordinate(&output->base, - motion_notify->event_x, - motion_notify->event_y, - &x, &y); + pos = weston_coord_global_from_output_point(motion_notify->event_x, + motion_notify->event_y, + &output->base); motion_event = (struct weston_pointer_motion_event) { .mask = WESTON_POINTER_MOTION_REL, - .dx = x - b->prev_x, - .dy = y - b->prev_y + .rel = weston_coord_sub(pos.c, b->prev_pos.c), }; - weston_compositor_get_time(&time); notify_motion(&b->core_seat, &time, &motion_event); notify_pointer_frame(&b->core_seat); - b->prev_x = x; - b->prev_y = y; + b->prev_pos = pos; } static void @@ -1411,7 +1460,7 @@ x11_backend_deliver_enter_event(struct x11_backend *b, xcb_generic_event_t *event) { struct x11_output *output; - double x, y; + struct weston_coord_global pos; xcb_enter_notify_event_t *enter_notify = (xcb_enter_notify_event_t *) event; @@ -1423,14 +1472,13 @@ x11_backend_deliver_enter_event(struct x11_backend *b, if (!output) return; - weston_output_transform_coordinate(&output->base, - enter_notify->event_x, - enter_notify->event_y, &x, &y); + pos = weston_coord_global_from_output_point(enter_notify->event_x, + enter_notify->event_y, + &output->base); - notify_pointer_focus(&b->core_seat, &output->base, x, y); + notify_pointer_focus(&b->core_seat, &output->base, pos); - b->prev_x = x; - b->prev_y = y; + b->prev_pos = pos; } static int @@ -1450,7 +1498,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 +1514,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 +1530,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 +1544,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 +1569,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 +1595,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; @@ -1587,7 +1634,7 @@ x11_backend_handle_event(int fd, uint32_t mask, void *data) break; if (!b->has_xkb) update_xkb_state_from_core(b, enter_notify->state); - notify_pointer_focus(&b->core_seat, NULL, 0, 0); + clear_pointer_focus(&b->core_seat); break; case XCB_CLIENT_MESSAGE: @@ -1643,7 +1690,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 +1724,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 +1738,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; @@ -1782,42 +1829,27 @@ x11_backend_get_wm_info(struct x11_backend *c) } static void -x11_destroy(struct weston_compositor *ec) +x11_destroy(struct weston_backend *base) { - struct x11_backend *backend = to_x11_backend(ec); - struct weston_head *base, *next; + struct x11_backend *backend = container_of(base, struct x11_backend, base); + struct weston_compositor *ec = backend->compositor; + struct weston_head *head, *next; wl_event_source_remove(backend->xcb_source); x11_input_destroy(backend); 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(head, next, &ec->head_list, compositor_link) { + if (to_x11_head(head)) + x11_head_destroy(head); + } XCloseDisplay(backend->dpy); + free(backend->formats); free(backend); } -static int -init_gl_renderer(struct x11_backend *b) -{ - const struct gl_renderer_display_options options = { - .egl_platform = EGL_PLATFORM_X11_KHR, - .egl_native_display = b->dpy, - .egl_surface_type = EGL_WINDOW_BIT, - .drm_formats = x11_formats, - .drm_formats_count = ARRAY_LENGTH(x11_formats), - }; - - gl_renderer = weston_load_module("gl-renderer.so", - "gl_renderer_interface"); - if (!gl_renderer) - return -1; - - return gl_renderer->display_create(b->compositor, &options); -} - static const struct weston_windowed_output_api api = { x11_output_set_size, x11_head_create, @@ -1866,17 +1898,30 @@ x11_backend_create(struct weston_compositor *compositor, config->fullscreen = 0; } - b->use_pixman = config->use_pixman; - if (b->use_pixman) { - if (pixman_renderer_init(compositor) < 0) { + b->formats_count = ARRAY_LENGTH(x11_formats); + b->formats = pixel_format_get_array(x11_formats, b->formats_count); + + if (config->renderer == WESTON_RENDERER_PIXMAN) { + if (weston_compositor_init_renderer(compositor, + WESTON_RENDERER_PIXMAN, + NULL) < 0) { weston_log("Failed to initialize pixman renderer for X11 backend\n"); goto err_xdisplay; } } - else if (init_gl_renderer(b) < 0) { - goto err_xdisplay; + else { + const struct gl_renderer_display_options options = { + .egl_platform = EGL_PLATFORM_X11_KHR, + .egl_native_display = b->dpy, + .egl_surface_type = EGL_WINDOW_BIT, + .formats = b->formats, + .formats_count = b->formats_count, + }; + if (weston_compositor_init_renderer(compositor, + WESTON_RENDERER_GL, + &options.base) < 0) + goto err_xdisplay; } - weston_log("Using %s renderer\n", config->use_pixman ? "pixman" : "gl"); b->base.destroy = x11_destroy; b->base.create_output = x11_output_create; @@ -1923,6 +1968,7 @@ x11_backend_create(struct weston_compositor *compositor, err_xdisplay: XCloseDisplay(b->dpy); err_free: + free(b->formats); free(b); return NULL; } diff --git a/libweston/backend.h b/libweston/backend.h index 3ae59a6c7..a8ad819dd 100644 --- a/libweston/backend.h +++ b/libweston/backend.h @@ -32,8 +32,10 @@ #ifndef LIBWESTON_BACKEND_INTERNAL_H #define LIBWESTON_BACKEND_INTERNAL_H +struct weston_hdr_metadata_type1; + struct weston_backend { - void (*destroy)(struct weston_compositor *compositor); + void (*destroy)(struct weston_backend *backend); /** Begin a repaint sequence * @@ -46,31 +48,25 @@ 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_backend *backend); /** 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_backend *backend); /** 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_backend *backend); /** Allocate a new output * - * @param compositor The compositor. + * @param backend The backend. * @param name Name for the new output. * * Allocates a new output structure that embeds a weston_output, @@ -80,12 +76,11 @@ struct weston_backend { * Must set weston_output members @c destroy, @c enable and @c disable. */ struct weston_output * - (*create_output)(struct weston_compositor *compositor, - const char *name); + (*create_output)(struct weston_backend *backend, const char *name); /** Notify of device addition/removal * - * @param compositor The compositor. + * @param backend The backend. * @param device The device that has changed. * @param added Where it was added (or removed) * @@ -93,20 +88,26 @@ struct weston_backend { * The backend can decide what to do based on whether it is a * device that it is controlling or not. */ - void (*device_changed)(struct weston_compositor *compositor, + void (*device_changed)(struct weston_backend *backend, dev_t device, bool added); /** Verifies if the dmabuf can be used directly/scanned-out by the HW. * - * @param compositor The compositor. + * @param backend The backend. * @param buffer The dmabuf to verify. * * Determines if the buffer can be imported directly by the display * controller/HW. Back-ends can use this to check if the supplied * buffer can be scanned-out, as to void importing it into the GPU. */ - bool (*can_scanout_dmabuf)(struct weston_compositor *compositor, + bool (*can_scanout_dmabuf)(struct weston_backend *backend, struct linux_dmabuf_buffer *buffer); + + const struct weston_drm_format_array * + (*get_supported_formats)(struct weston_compositor *ec); + + bool (*import_dmabuf)(struct weston_compositor* compositor, + struct linux_dmabuf_buffer *dmabuf); }; /* weston_head */ @@ -142,6 +143,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 +159,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, @@ -169,14 +171,18 @@ int weston_output_mode_set_native(struct weston_output *output, struct weston_mode *mode, int32_t scale); -void -weston_output_transform_coordinate(struct weston_output *output, - double device_x, double device_y, - double *x, double *y); + +struct weston_coord_global +weston_coord_global_from_output_point(double x, double y, + const struct weston_output *output); void -weston_output_region_from_global(struct weston_output *output, - pixman_region32_t *region); +weston_region_global_to_output(pixman_region32_t *dst, + struct weston_output *output, + pixman_region32_t *src); + +const struct weston_hdr_metadata_type1 * +weston_output_get_hdr_metadata_type1(const struct weston_output *output); /* weston_seat */ @@ -205,7 +211,7 @@ notify_motion(struct weston_seat *seat, const struct timespec *time, struct weston_pointer_motion_event *event); void notify_motion_absolute(struct weston_seat *seat, const struct timespec *time, - double x, double y); + struct weston_coord_global pos); void notify_modifiers(struct weston_seat *seat, uint32_t serial); @@ -214,7 +220,10 @@ notify_pointer_frame(struct weston_seat *seat); void notify_pointer_focus(struct weston_seat *seat, struct weston_output *output, - double x, double y); + struct weston_coord_global pos); + +void +clear_pointer_focus(struct weston_seat *seat); /* weston_touch_device */ @@ -222,7 +231,7 @@ void notify_touch_normalized(struct weston_touch_device *device, const struct timespec *time, int touch_id, - double x, double y, + const struct weston_coord_global *pos, const struct weston_point2d_device_normalized *norm, int touch_type); @@ -232,9 +241,9 @@ notify_touch_normalized(struct weston_touch_device *device, */ static inline void notify_touch(struct weston_touch_device *device, const struct timespec *time, - int touch_id, double x, double y, int touch_type) + int touch_id, const struct weston_coord_global *pos, int touch_type) { - notify_touch_normalized(device, time, touch_id, x, y, NULL, touch_type); + notify_touch_normalized(device, time, touch_id, pos, NULL, touch_type); } void @@ -253,4 +262,46 @@ notify_touch_calibrator_cancel(struct weston_touch_device *device); void notify_touch_calibrator_frame(struct weston_touch_device *device); +void +notify_tablet_added(struct weston_tablet *tablet); + +void +notify_tablet_tool_added(struct weston_tablet_tool *tool); + +void +notify_tablet_tool_proximity_in(struct weston_tablet_tool *tool, + const struct timespec *time, + struct weston_tablet *tablet); +void +notify_tablet_tool_proximity_out(struct weston_tablet_tool *tool, + const struct timespec *time); +void +notify_tablet_tool_motion(struct weston_tablet_tool *tool, + const struct timespec *time, + struct weston_coord_global pos); +void +notify_tablet_tool_pressure(struct weston_tablet_tool *tool, + const struct timespec *time, uint32_t pressure); +void +notify_tablet_tool_distance(struct weston_tablet_tool *tool, + const struct timespec *time, uint32_t distance); +void +notify_tablet_tool_tilt(struct weston_tablet_tool *tool, + const struct timespec *time, + int32_t tilt_x, int32_t tilt_y); +void +notify_tablet_tool_button(struct weston_tablet_tool *tool, + const struct timespec *time, + uint32_t button, + uint32_t state); +void +notify_tablet_tool_up(struct weston_tablet_tool *tool, + const struct timespec *time); +void +notify_tablet_tool_down(struct weston_tablet_tool *tool, + const struct timespec *time); +void +notify_tablet_tool_frame(struct weston_tablet_tool *tool, + const struct timespec *time); + #endif diff --git a/libweston/bindings.c b/libweston/bindings.c index 2ca999a3f..4d2ad94f6 100644 --- a/libweston/bindings.c +++ b/libweston/bindings.c @@ -33,6 +33,7 @@ #include "libweston-internal.h" #include "shared/helpers.h" #include "shared/timespec-util.h" +#include "tablet-unstable-v2-server-protocol.h" struct weston_binding { uint32_t key; @@ -67,7 +68,8 @@ weston_compositor_add_binding(struct weston_compositor *compositor, WL_EXPORT struct weston_binding * weston_compositor_add_key_binding(struct weston_compositor *compositor, - uint32_t key, uint32_t modifier, + uint32_t key, + enum weston_keyboard_modifier modifier, weston_key_binding_handler_t handler, void *data) { @@ -85,7 +87,7 @@ weston_compositor_add_key_binding(struct weston_compositor *compositor, WL_EXPORT struct weston_binding * weston_compositor_add_modifier_binding(struct weston_compositor *compositor, - uint32_t modifier, + enum weston_keyboard_modifier modifier, weston_modifier_binding_handler_t handler, void *data) { @@ -103,7 +105,8 @@ weston_compositor_add_modifier_binding(struct weston_compositor *compositor, WL_EXPORT struct weston_binding * weston_compositor_add_button_binding(struct weston_compositor *compositor, - uint32_t button, uint32_t modifier, + uint32_t button, + enum weston_keyboard_modifier modifier, weston_button_binding_handler_t handler, void *data) { @@ -121,7 +124,7 @@ weston_compositor_add_button_binding(struct weston_compositor *compositor, WL_EXPORT struct weston_binding * weston_compositor_add_touch_binding(struct weston_compositor *compositor, - uint32_t modifier, + enum weston_keyboard_modifier modifier, weston_touch_binding_handler_t handler, void *data) { @@ -137,9 +140,29 @@ weston_compositor_add_touch_binding(struct weston_compositor *compositor, return binding; } +WL_EXPORT struct weston_binding * +weston_compositor_add_tablet_tool_binding(struct weston_compositor *compositor, + uint32_t button, + enum weston_keyboard_modifier modifier, + weston_tablet_tool_binding_handler_t handler, + void *data) +{ + struct weston_binding *binding; + + binding = weston_compositor_add_binding(compositor, 0, button, 0, + modifier, handler, data); + if (binding == NULL) + return NULL; + + wl_list_insert(compositor->tablet_tool_binding_list.prev, &binding->link); + + return binding; +} + WL_EXPORT struct weston_binding * weston_compositor_add_axis_binding(struct weston_compositor *compositor, - uint32_t axis, uint32_t modifier, + uint32_t axis, + enum weston_keyboard_modifier modifier, weston_axis_binding_handler_t handler, void *data) { @@ -310,8 +333,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, @@ -395,6 +418,25 @@ weston_compositor_run_touch_binding(struct weston_compositor *compositor, } } +void +weston_compositor_run_tablet_tool_binding(struct weston_compositor *compositor, + struct weston_tablet_tool *tool, + uint32_t button, uint32_t state_w) +{ + enum zwp_tablet_tool_v2_button_state state = state_w; + struct weston_binding *b; + + if (state != ZWP_TABLET_TOOL_V2_BUTTON_STATE_PRESSED) + return; + + wl_list_for_each(b, &compositor->tablet_tool_binding_list, link) { + if (b->modifier == tool->seat->modifier_state) { + weston_tablet_tool_binding_handler_t handler = b->handler; + handler(tool, button, b->data); + } + } +} + int weston_compositor_run_axis_binding(struct weston_compositor *compositor, struct weston_pointer *pointer, diff --git a/libweston/clipboard.c b/libweston/clipboard.c index 7d60351a7..a82d8fe85 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 18340a393..656acd4ab 100644 --- a/libweston/color-lcms/color-lcms.c +++ b/libweston/color-lcms/color-lcms.c @@ -27,11 +27,52 @@ #include "config.h" #include +#include #include #include "color.h" #include "color-lcms.h" #include "shared/helpers.h" +#include "shared/xalloc.h" + +const char * +cmlcms_category_name(enum cmlcms_category cat) +{ + static const char *const category_names[] = { + [CMLCMS_CATEGORY_INPUT_TO_BLEND] = "input-to-blend", + [CMLCMS_CATEGORY_BLEND_TO_OUTPUT] = "blend-to-output", + [CMLCMS_CATEGORY_INPUT_TO_OUTPUT] = "input-to-output", + }; + + if (cat < 0 || cat >= ARRAY_LENGTH(category_names)) + return "[illegal category value]"; + + return category_names[cat] ?: "[undocumented category value]"; +} + +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 +89,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 +145,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 +202,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 +367,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,13 +381,64 @@ 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)); cmsDeleteContext(cm->lcms_ctx); + + weston_log_scope_destroy(cm->transforms_scope); + weston_log_scope_destroy(cm->optimizer_scope); + weston_log_scope_destroy(cm->profiles_scope); + free(cm); } +static void +transforms_scope_new_sub(struct weston_log_subscription *subs, void *data) +{ + struct weston_color_manager_lcms *cm = data; + struct cmlcms_color_transform *xform; + char *str; + + if (wl_list_empty(&cm->color_transform_list)) + return; + + weston_log_subscription_printf(subs, "Existent:\n"); + wl_list_for_each(xform, &cm->color_transform_list, link) { + weston_log_subscription_printf(subs, "Color transformation %p:\n", xform); + + str = cmlcms_color_transform_search_param_string(&xform->search_key); + weston_log_subscription_printf(subs, "%s", str); + free(str); + + str = weston_color_transform_string(&xform->base); + weston_log_subscription_printf(subs, " %s", str); + free(str); + } +} + +static void +profiles_scope_new_sub(struct weston_log_subscription *subs, void *data) +{ + struct weston_color_manager_lcms *cm = data; + struct cmlcms_color_profile *cprof; + char *str; + + if (wl_list_empty(&cm->color_profile_list)) + return; + + weston_log_subscription_printf(subs, "Existent:\n"); + wl_list_for_each(cprof, &cm->color_profile_list, link) { + weston_log_subscription_printf(subs, "Color profile %p:\n", cprof); + + str = cmlcms_color_profile_print(cprof); + weston_log_subscription_printf(subs, "%s", str); + free(str); + } +} + WL_EXPORT struct weston_color_manager * weston_color_manager_create(struct weston_compositor *compositor) { @@ -199,16 +456,35 @@ 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); + cm->transforms_scope = + weston_compositor_add_log_scope(compositor, "color-lcms-transformations", + "Color transformation creation and destruction.\n", + transforms_scope_new_sub, NULL, cm); + cm->optimizer_scope = + weston_compositor_add_log_scope(compositor, "color-lcms-optimizer", + "Color transformation pipeline optimizer. It's best " \ + "used together with the color-lcms-transformations " \ + "log scope.\n", NULL, NULL, NULL); + cm->profiles_scope = + weston_compositor_add_log_scope(compositor, "color-lcms-profiles", + "Color profile creation and destruction.\n", + profiles_scope_new_sub, NULL, cm); + + if (!cm->profiles_scope || !cm->transforms_scope || !cm->optimizer_scope) + goto err; + return &cm->base; + +err: + weston_log_scope_destroy(cm->transforms_scope); + weston_log_scope_destroy(cm->optimizer_scope); + weston_log_scope_destroy(cm->profiles_scope); + free(cm); + return NULL; } diff --git a/libweston/color-lcms/color-lcms.h b/libweston/color-lcms/color-lcms.h index dd5c38fcf..1f0e90e3b 100644 --- a/libweston/color-lcms/color-lcms.h +++ b/libweston/color-lcms/color-lcms.h @@ -29,16 +29,21 @@ #include #include +#include #include "color.h" #include "shared/helpers.h" struct weston_color_manager_lcms { struct weston_color_manager base; + struct weston_log_scope *profiles_scope; + struct weston_log_scope *transforms_scope; + struct weston_log_scope *optimizer_scope; cmsContext lcms_ctx; 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,8 +64,63 @@ struct cmlcms_color_profile { cmsHPROFILE profile; struct cmlcms_md5_sum md5sum; + + /** The curves to decode an electrical signal + * + * For ICC profiles, if the profile type is matrix-shaper, then eotf + * contains the TRC, otherwise eotf contains an approximated EOTF if the + * profile is used for output. + * The field may be populated on demand. + */ + cmsToneCurve *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, }; +const char * +cmlcms_category_name(enum cmlcms_category cat); + static inline struct cmlcms_color_profile * get_cprof(struct weston_color_profile *cprof_base) { @@ -78,18 +138,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 +154,46 @@ struct cmlcms_color_transform { struct cmlcms_color_transform_search_param search_key; - /* for EOTF types */ - cmsToneCurve *curve; + /* + * Cached data in case weston_color_transform needs them. + * Pre-curve and post-curve refer to the weston_color_transform + * pipeline elements and have no semantic meaning. They both are a + * result of optimizing an arbitrary LittleCMS pipeline, not + * e.g. EOTF or VCGT per se. + */ + cmsToneCurve *pre_curve[3]; + cmsToneCurve *post_curve[3]; + + /** + * 3D LUT color mapping part of the transformation, if needed by the + * weston_color_transform. This is used as a fallback when an + * arbitrary LittleCMS pipeline cannot be translated into a more + * specific form. + */ + cmsHTRANSFORM cmap_3dlut; + + /** + * Certain categories of transformations need their own LittleCMS + * contexts in order to use our LittleCMS plugin. + */ + cmsContext lcms_ctx; + + /** + * The result of pipeline construction, optimization, and analysis. + */ + enum { + /** Error producing a pipeline */ + CMLCMS_TRANSFORM_FAILED = 0, + + /** + * Pipeline was optimized into weston_color_transform, + * 3D LUT not used. + */ + CMLCMS_TRANSFORM_OPTIMIZED, + + /** The transformation uses 3D LUT. */ + CMLCMS_TRANSFORM_3DLUT, + } status; }; static inline struct cmlcms_color_transform * @@ -117,4 +209,37 @@ cmlcms_color_transform_get(struct weston_color_manager_lcms *cm, void cmlcms_color_transform_destroy(struct cmlcms_color_transform *xform); +char * +cmlcms_color_transform_search_param_string(const struct cmlcms_color_transform_search_param *search_key); + +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); + +char * +cmlcms_color_profile_print(const 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); + +cmsToneCurve * +lcmsJoinToneCurve(cmsContext context_id, const cmsToneCurve *X, + const cmsToneCurve *Y, unsigned int resulting_points); + #endif /* WESTON_COLOR_LCMS_H */ diff --git a/libweston/color-lcms/color-profile.c b/libweston/color-lcms/color-profile.c index 8884a0711..6fa9cd04a 100644 --- a/libweston/color-lcms/color-profile.c +++ b/libweston/color-lcms/color-profile.c @@ -35,6 +35,224 @@ #include "color-lcms.h" #include "shared/helpers.h" #include "shared/string-helpers.h" +#include "shared/xalloc.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)) + */ +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 @@ -81,6 +299,17 @@ cmlcms_find_color_profile_by_md5(const struct weston_color_manager_lcms *cm, return NULL; } +char * +cmlcms_color_profile_print(const struct cmlcms_color_profile *cprof) +{ + char *str; + + str_printf(&str, " description: %s\n", cprof->base.description); + abort_oom_if_null(str); + + return str; +} + static struct cmlcms_color_profile * cmlcms_color_profile_create(struct weston_color_manager_lcms *cm, cmsHPROFILE profile, @@ -88,6 +317,7 @@ cmlcms_color_profile_create(struct weston_color_manager_lcms *cm, char **errmsg) { struct cmlcms_color_profile *cprof; + char *str; cprof = zalloc(sizeof *cprof); if (!cprof) @@ -99,18 +329,53 @@ cmlcms_color_profile_create(struct weston_color_manager_lcms *cm, cmsGetHeaderProfileID(profile, cprof->md5sum.bytes); wl_list_insert(&cm->color_profile_list, &cprof->link); + weston_log_scope_printf(cm->profiles_scope, + "New color profile: %p\n", cprof); + + str = cmlcms_color_profile_print(cprof); + weston_log_scope_printf(cm->profiles_scope, "%s", str); + free(str); + return cprof; } -static void +void cmlcms_color_profile_destroy(struct cmlcms_color_profile *cprof) { + struct weston_color_manager_lcms *cm = get_cmlcms(cprof->base.cm); + wl_list_remove(&cprof->link); + cmsFreeToneCurveTriple(cprof->vcgt); + cmsFreeToneCurveTriple(cprof->eotf); + cmsFreeToneCurveTriple(cprof->output_inv_eotf_vcgt); cmsCloseProfile(cprof->profile); + + weston_log_scope_printf(cm->profiles_scope, "Destroyed color profile %p. " \ + "Description: %s\n", cprof, cprof->base.description); + 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, @@ -125,12 +390,58 @@ make_icc_file_description(cmsHPROFILE profile, "%02x", md5sum->bytes[i]); } - str_printf(&desc, "ICCv%f %s %s", cmsGetProfileVersion(profile), + str_printf(&desc, "ICCv%.1f %s %s", cmsGetProfileVersion(profile), name_part, md5sum_str); 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->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 713be1899..4d6ab7879 100644 --- a/libweston/color-lcms/color-transform.c +++ b/libweston/color-lcms/color-transform.c @@ -1,6 +1,6 @@ /* - * Copyright 2021 Collabora, Ltd. - * Copyright 2021 Advanced Micro Devices, Inc. + * Copyright 2021-2022 Collabora, Ltd. + * Copyright 2021-2022 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 @@ -28,109 +28,1013 @@ #include #include +#include #include "color.h" #include "color-lcms.h" #include "shared/helpers.h" +#include "shared/string-helpers.h" +#include "shared/xalloc.h" -/* Arguments to cmsBuildParametricToneCurve() */ -struct tone_curve_def { - cmsInt32Number cmstype; - cmsFloat64Number params[5]; -}; +/** + * LCMS compares this parameter with the actual version of the LCMS and enforces + * the minimum version is plug-in. If the actual LCMS version is lower than the + * plug-in requirement the function cmsCreateContext is failed with plug-in as + * parameter. + */ +#define REQUIRED_LCMS_VERSION 2120 -/* - * 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. +/** Precision for detecting identity matrix */ +#define MATRIX_PRECISION_BITS 12 + +/** + * 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; + cmsFloat32Number x; - assert(xform->curve != NULL); assert(len > 1); + for (i = 0; i < 3; i++) + assert(curves[i]); 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_output_inv_eotf_vcgt(struct weston_color_transform *xform_base, + float *values, unsigned len) +{ + struct cmlcms_color_transform *xform = get_xform(xform_base); + struct cmlcms_color_profile *p = xform->search_key.output_profile; + + assert(p && "output_profile"); + fill_in_curves(p->output_inv_eotf_vcgt, values, len); +} + +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); + + fill_in_curves(xform->pre_curve, values, len); +} + +static void +cmlcms_fill_in_post_curve(struct weston_color_transform *xform_base, + float *values, unsigned len) +{ + struct cmlcms_color_transform *xform = get_xform(xform_base); + + fill_in_curves(xform->post_curve, 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]); + } + } } } void cmlcms_color_transform_destroy(struct cmlcms_color_transform *xform) { + struct weston_color_manager_lcms *cm = get_cmlcms(xform->base.cm); + wl_list_remove(&xform->link); - if (xform->curve) - cmsFreeToneCurve(xform->curve); + + cmsFreeToneCurveTriple(xform->pre_curve); + + if (xform->cmap_3dlut) + cmsDeleteTransform(xform->cmap_3dlut); + + cmsFreeToneCurveTriple(xform->post_curve); + + if (xform->lcms_ctx) + cmsDeleteContext(xform->lcms_ctx); + + unref_cprof(xform->search_key.input_profile); + unref_cprof(xform->search_key.output_profile); + + weston_log_scope_printf(cm->transforms_scope, + "Destroyed color transformation %p.\n", xform); + free(xform); } -static struct cmlcms_color_transform * -cmlcms_color_transform_create(struct weston_color_manager_lcms *cm, - const struct cmlcms_color_transform_search_param *param) +/** + * Matrix infinity norm + * + * http://www.netlib.org/lapack/lug/node75.html + */ +static double +matrix_inf_norm(const cmsMAT3 *mat) { + unsigned row; + double infnorm = -1.0; + + for (row = 0; row < 3; row++) { + unsigned col; + double sum = 0.0; + + for (col = 0; col < 3; col++) + sum += fabs(mat->v[col].n[row]); + + if (infnorm < sum) + infnorm = sum; + } + + return infnorm; +} + +/* + * The method of testing for identity matrix is from + * https://gitlab.freedesktop.org/pq/fourbyfour/-/blob/master/README.d/precision_testing.md#inversion-error + */ +static bool +matrix_is_identity(const cmsMAT3 *mat, int bits_precision) +{ + cmsMAT3 tmp = *mat; + double err; + int i; + + /* subtract identity matrix */ + for (i = 0; i < 3; i++) + tmp.v[i].n[i] -= 1.0; + + err = matrix_inf_norm(&tmp); + + return -log2(err) >= bits_precision; +} + +static const cmsMAT3 * +stage_matrix_transpose(const _cmsStageMatrixData *smd) +{ + /* smd is row-major, cmsMAT3 is column-major */ + return (const cmsMAT3 *)smd->Double; +} + +static bool +is_matrix_stage_with_zero_offset(const cmsStage *stage) +{ + const _cmsStageMatrixData *data; + int rows; + int r; + + if (!stage || cmsStageType(stage) != cmsSigMatrixElemType) + return false; + + data = cmsStageData(stage); + if (!data->Offset) + return true; + + rows = cmsStageOutputChannels(stage); + for (r = 0; r < rows; r++) + if (data->Offset[r] != 0.0f) + return false; + + return true; +} + +static bool +is_identity_matrix_stage(const cmsStage *stage) +{ + _cmsStageMatrixData *data; + + if (!is_matrix_stage_with_zero_offset(stage)) + return false; + + data = cmsStageData(stage); + return matrix_is_identity(stage_matrix_transpose(data), + MATRIX_PRECISION_BITS); +} + +/* Returns the matrix (next * prev). */ +static cmsStage * +multiply_matrix_stages(cmsContext context_id, cmsStage *next, cmsStage *prev) +{ + _cmsStageMatrixData *prev_, *next_; + cmsMAT3 res; + cmsStage *ret; + + prev_ = cmsStageData(prev); + next_ = cmsStageData(next); + + /* res = prev^T * next^T */ + _cmsMAT3per(&res, stage_matrix_transpose(next_), + stage_matrix_transpose(prev_)); + + /* + * res is column-major while Alloc function takes row-major; + * the cast effectively transposes the matrix. + * We return (prev^T * next^T)^T = next * prev. + */ + ret = cmsStageAllocMatrix(context_id, 3, 3, + (const cmsFloat64Number*)&res, NULL); + abort_oom_if_null(ret); + return ret; +} + +/** Merge consecutive matrices into a single matrix, and drop identity matrices + * + * If we have a pipeline { M1, M2, M3 } of matrices only, then the total + * operation is the matrix M = M3 * M2 * M1 because the pipeline first applies + * M1, then M2, and finally M3. + */ +static bool +merge_matrices(cmsPipeline **lut, cmsContext context_id) +{ + cmsPipeline *pipe; + cmsStage *elem; + cmsStage *prev = NULL; + cmsStage *freeme = NULL; + bool modified = false; + + pipe = cmsPipelineAlloc(context_id, 3, 3); + abort_oom_if_null(pipe); + + elem = cmsPipelineGetPtrToFirstStage(*lut); + do { + if (is_matrix_stage_with_zero_offset(prev) && + is_matrix_stage_with_zero_offset(elem)) { + /* replace the two matrices with a merged one */ + prev = multiply_matrix_stages(context_id, elem, prev); + if (freeme) + cmsStageFree(freeme); + freeme = prev; + modified = true; + } else { + if (prev) { + if (is_identity_matrix_stage(prev)) { + /* skip inserting it */ + modified = true; + } else { + cmsPipelineInsertStage(pipe, cmsAT_END, + cmsStageDup(prev)); + } + } + prev = elem; + } + + if (elem) + elem = cmsStageNext(elem); + } while (prev); + + if (freeme) + cmsStageFree(freeme); + + cmsPipelineFree(*lut); + *lut = pipe; + + return modified; +} + +/* + * XXX: Joining curve sets pair by pair might cause precision problems, + * especially as we convert even analytical curve types into tabulated. + * It might be preferable to convert a whole chain of curve sets at once + * instead. + */ +static cmsStage * +join_curvesets(cmsContext context_id, const cmsStage *prev, + const cmsStage *next, unsigned int num_samples) +{ + _cmsStageToneCurvesData *prev_, *next_; + cmsToneCurve *arr[3]; + cmsUInt32Number i; + cmsStage *ret = NULL; + + prev_ = cmsStageData(prev); + next_ = cmsStageData(next); + + assert(prev_->nCurves == ARRAY_LENGTH(arr)); + assert(next_->nCurves == ARRAY_LENGTH(arr)); + + for (i = 0; i < ARRAY_LENGTH(arr); i++) { + arr[i] = lcmsJoinToneCurve(context_id, prev_->TheCurves[i], + next_->TheCurves[i], num_samples); + abort_oom_if_null(arr[i]); + } + + ret = cmsStageAllocToneCurves(context_id, ARRAY_LENGTH(arr), arr); + abort_oom_if_null(ret); + cmsFreeToneCurveTriple(arr); + return ret; +} + +static bool +is_identity_curve_stage(const cmsStage *stage) +{ + const _cmsStageToneCurvesData *data; + unsigned int i; + bool is_identity = true; + + assert(stage); + + if (cmsStageType(stage) != cmsSigCurveSetElemType) + return false; + + data = cmsStageData(stage); + for (i = 0; i < data->nCurves; i++) + is_identity &= cmsIsToneCurveLinear(data->TheCurves[i]); + + return is_identity; +} + +static bool +merge_curvesets(cmsPipeline **lut, cmsContext context_id) +{ + cmsPipeline *pipe; + cmsStage *elem; + cmsStage *prev = NULL; + cmsStage *freeme = NULL; + bool modified = false; + + pipe = cmsPipelineAlloc(context_id, 3, 3); + abort_oom_if_null(pipe); + + elem = cmsPipelineGetPtrToFirstStage(*lut); + do { + if (prev && cmsStageType(prev) == cmsSigCurveSetElemType && + elem && cmsStageType(elem) == cmsSigCurveSetElemType) { + /* Replace two curve set elements with a merged one. */ + prev = join_curvesets(context_id, prev, elem, + cmlcms_reasonable_1D_points()); + if (freeme) + cmsStageFree(freeme); + freeme = prev; + modified = true; + } else { + if (prev) { + if (is_identity_curve_stage(prev)) { + /* skip inserting it */ + modified = true; + } else { + cmsPipelineInsertStage(pipe, cmsAT_END, + cmsStageDup(prev)); + } + } + prev = elem; + } + + if (elem) + elem = cmsStageNext(elem); + } while (prev); + + if (freeme) + cmsStageFree(freeme); + + cmsPipelineFree(*lut); + *lut = pipe; + + return modified; +} + +static bool +translate_curve_element(struct weston_color_curve *curve, + cmsToneCurve *stash[3], + void (*func)(struct weston_color_transform *xform, + float *values, unsigned len), + cmsStage *elem) +{ + _cmsStageToneCurvesData *trc_data; + unsigned i; + + assert(cmsStageType(elem) == cmsSigCurveSetElemType); + + trc_data = cmsStageData(elem); + if (trc_data->nCurves != 3) + return false; + + curve->type = WESTON_COLOR_CURVE_TYPE_LUT_3x1D; + curve->u.lut_3x1d.fill_in = func; + curve->u.lut_3x1d.optimal_len = cmlcms_reasonable_1D_points(); + + for (i = 0; i < 3; i++) { + stash[i] = cmsDupToneCurve(trc_data->TheCurves[i]); + abort_oom_if_null(stash[i]); + } + + return true; +} + +static bool +translate_matrix_element(struct weston_color_mapping *map, cmsStage *elem) +{ + _cmsStageMatrixData *data = cmsStageData(elem); + int c, r; + + if (!is_matrix_stage_with_zero_offset(elem)) + return false; + + if (cmsStageInputChannels(elem) != 3 || + cmsStageOutputChannels(elem) != 3) + return false; + + map->type = WESTON_COLOR_MAPPING_TYPE_MATRIX; + + /* + * map->u.mat.matrix is column-major, while + * data->Double is row-major. + */ + for (c = 0; c < 3; c++) + for (r = 0; r < 3; r++) + map->u.mat.matrix[c * 3 + r] = data->Double[r * 3 + c]; + + return true; +} + +static bool +translate_pipeline(struct cmlcms_color_transform *xform, const cmsPipeline *lut) +{ + cmsStage *elem; + + xform->base.pre_curve.type = WESTON_COLOR_CURVE_TYPE_IDENTITY; + xform->base.mapping.type = WESTON_COLOR_MAPPING_TYPE_IDENTITY; + xform->base.post_curve.type = WESTON_COLOR_CURVE_TYPE_IDENTITY; + + elem = cmsPipelineGetPtrToFirstStage(lut); + + if (!elem) + return true; + + if (cmsStageType(elem) == cmsSigCurveSetElemType) { + if (!translate_curve_element(&xform->base.pre_curve, + xform->pre_curve, + cmlcms_fill_in_pre_curve, elem)) + return false; + + elem = cmsStageNext(elem); + } + + if (!elem) + return true; + + if (cmsStageType(elem) == cmsSigMatrixElemType) { + if (!translate_matrix_element(&xform->base.mapping, elem)) + return false; + + elem = cmsStageNext(elem); + } + + if (!elem) + return true; + + if (cmsStageType(elem) == cmsSigCurveSetElemType) { + if (!translate_curve_element(&xform->base.post_curve, + xform->post_curve, + cmlcms_fill_in_post_curve, elem)) + return false; + + elem = cmsStageNext(elem); + } + + if (!elem) + return true; + + return false; +} + +static cmsBool +optimize_float_pipeline(cmsPipeline **lut, cmsContext context_id, + struct cmlcms_color_transform *xform) +{ + bool cont_opt; + + /** + * This optimization loop will delete identity stages. Deleting + * identity matrix stages is harmless, but deleting identity + * curve set stages also removes the implicit clamping they do + * on their input values. + */ + do { + cont_opt = merge_matrices(lut, context_id); + cont_opt |= merge_curvesets(lut, context_id); + } while (cont_opt); + + if (translate_pipeline(xform, *lut)) { + xform->status = CMLCMS_TRANSFORM_OPTIMIZED; + return TRUE; + } + + xform->base.pre_curve.type = WESTON_COLOR_CURVE_TYPE_IDENTITY; + 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(); + xform->base.post_curve.type = WESTON_COLOR_CURVE_TYPE_IDENTITY; + + xform->status = CMLCMS_TRANSFORM_3DLUT; + + /* + * We use cmsDoTransform() to realize the 3D LUT. Return false so + * that LittleCMS installs its usual float transform machinery, + * running on the pipeline we optimized here. + */ + return FALSE; +} + +static const char * +cmlcms_stage_type_to_str(cmsStage *stage) +{ + /* This table is based on cmsStageSignature enum type from the + * LittleCMS API. */ + switch (cmsStageType(stage)) + { + case cmsSigCurveSetElemType: + return "CurveSet"; + case cmsSigMatrixElemType: + return "Matrix"; + case cmsSigCLutElemType: + return "CLut"; + case cmsSigBAcsElemType: + return "BAcs"; + case cmsSigEAcsElemType: + return "EAcs"; + case cmsSigXYZ2LabElemType: + return "XYZ2Lab"; + case cmsSigLab2XYZElemType: + return "Lab2XYz"; + case cmsSigNamedColorElemType: + return "NamedColor"; + case cmsSigLabV2toV4: + return "LabV2toV4"; + case cmsSigLabV4toV2: + return "LabV4toV2"; + case cmsSigIdentityElemType: + return "Identity"; + case cmsSigLab2FloatPCS: + return "Lab2FloatPCS"; + case cmsSigFloatPCS2Lab: + return "FloatPCS2Lab"; + case cmsSigXYZ2FloatPCS: + return "XYZ2FloatPCS"; + case cmsSigFloatPCS2XYZ: + return "FloatPCS2XYZ"; + case cmsSigClipNegativesElemType: + return "ClipNegatives"; + } + + return NULL; +} + +static void +matrix_print(cmsStage *stage, struct weston_log_scope *scope) +{ + const _cmsStageMatrixData *data; + const unsigned int SIZE = 3; + unsigned int row, col; + double elem; + const char *sep; + + if (!weston_log_scope_is_enabled(scope)) + return; + + assert(cmsStageType(stage) == cmsSigMatrixElemType); + data = cmsStageData(stage); + + for (row = 0; row < SIZE; row++) { + weston_log_scope_printf(scope, " "); + + for (col = 0, sep = ""; col < SIZE; col++) { + elem = data->Double[row * SIZE + col]; + weston_log_scope_printf(scope, "%s% .4f", sep, elem); + sep = " "; + } + + /* We print offset after the last column of the matrix. */ + if (data->Offset) + weston_log_scope_printf(scope, "% .4f", data->Offset[row]); + + weston_log_scope_printf(scope, "\n"); + } +} + +static void +pipeline_print(cmsPipeline **lut, cmsContext context_id, + struct weston_log_scope *scope) +{ + cmsStage *stage = cmsPipelineGetPtrToFirstStage(*lut); + const char *type_str; + + if (!weston_log_scope_is_enabled(scope)) + return; + + if (!stage) { + weston_log_scope_printf(scope, "no elements\n"); + return; + } + + while (stage != NULL) { + type_str = cmlcms_stage_type_to_str(stage); + /* Unknown type, just print the hex */ + if (!type_str) + weston_log_scope_printf(scope, " unknown type 0x%x\n", + cmsStageType(stage)); + else + weston_log_scope_printf(scope, " %s\n", type_str); + + switch(cmsStageType(stage)) { + case cmsSigMatrixElemType: + matrix_print(stage, scope); + break; + default: + break; + } + + stage = cmsStageNext(stage); + } +} + +/** LittleCMS transform plugin entry point + * + * This function is called by LittleCMS when it is creating a new + * cmsHTRANSFORM. We have the opportunity to inspect and override everything. + * The initial cmsPipeline resulting from e.g. + * cmsCreateMultiprofileTransformTHR() is handed to us for inspection before + * the said function call returns. + * + * \param xform_fn If we handle the given transformation, we should assign + * our own transformation function here. We do not do that, because: + * a) Even when we optimize the pipeline, but do not handle the transformation, + * we rely on LittleCMS' own float transformation machinery. + * b) When we do handle the transformation, we will not be calling + * cmsDoTransform() anymore. + * + * \param user_data We could store a void pointer to custom user data + * through this pointer to be carried with the cmsHTRANSFORM. + * Here none is needed. + * + * \param free_private_data_fn We could store a function pointer for freeing + * our user data when the cmsHTRANSFORM is destroyed. None needed. + * + * \param lut The LittleCMS pipeline that describes this transformation. + * We can create our own and replace the original completely in + * optimize_float_pipeline(). + * + * \param input_format Pointer to the format used as input for this + * transformation. I suppose we could override it if we wanted to, but + * no need. + * + * \param output_format Similar to input format. + * + * \param flags Some flags we could also override? See cmsFLAGS_* defines. + * + * \return If this returns TRUE, it implies we handle the transformation. No + * other plugin will be tried anymore and the transformation object is + * complete. If this returns FALSE, the search for a plugin to handle this + * transformation continues and falls back to the usual handling inside + * LittleCMS. + */ +static cmsBool +transform_factory(_cmsTransform2Fn *xform_fn, + void **user_data, + _cmsFreeUserDataFn *free_private_data_fn, + cmsPipeline **lut, + cmsUInt32Number *input_format, + cmsUInt32Number *output_format, + cmsUInt32Number *flags) +{ + struct weston_color_manager_lcms *cm; struct cmlcms_color_transform *xform; - const struct tone_curve_def *tonedef; + cmsContext context_id; + bool ret; - if (param->type < 0 || param->type >= CMLCMS_TYPE__END) { - weston_log("color-lcms error: bad color transform type in %s.\n", - __func__); - return NULL; + if (T_CHANNELS(*input_format) != 3) { + weston_log("color-lcms debug: input format is not 3-channel."); + return FALSE; + } + if (T_CHANNELS(*output_format) != 3) { + weston_log("color-lcms debug: output format is not 3-channel."); + return FALSE; } - tonedef = &predefined_eotf_curves[param->type]; + if (!T_FLOAT(*input_format)) { + weston_log("color-lcms debug: input format is not float."); + return FALSE; + } + if (!T_FLOAT(*output_format)) { + weston_log("color-lcms debug: output format is not float."); + return FALSE; + } + context_id = cmsGetPipelineContextID(*lut); + assert(context_id); + xform = cmsGetContextUserData(context_id); + assert(xform); - xform = zalloc(sizeof *xform); - if (!xform) - return NULL; + cm = get_cmlcms(xform->base.cm); + + /* Print pipeline before optimization */ + weston_log_scope_printf(cm->optimizer_scope, + " transform pipeline before optimization:\n"); + pipeline_print(lut, context_id, cm->optimizer_scope); + + /* Optimize pipeline */ + ret = optimize_float_pipeline(lut, context_id, xform); + + /* Print pipeline after optimization */ + weston_log_scope_printf(cm->optimizer_scope, + " transform pipeline after optimization:\n"); + pipeline_print(lut, context_id, cm->optimizer_scope); + + return ret; +} + +static cmsPluginTransform transform_plugin = { + .base = { + .Magic = cmsPluginMagicNumber, + .ExpectedVersion = REQUIRED_LCMS_VERSION, + .Type = cmsPluginTransformSig, + .Next = NULL + }, + .factories.xform = transform_factory, +}; + +static void +lcms_xform_error_logger(cmsContext context_id, + cmsUInt32Number error_code, + const char *text) +{ + struct cmlcms_color_transform *xform; + struct cmlcms_color_profile *in; + struct cmlcms_color_profile *out; + + xform = cmsGetContextUserData(context_id); + in = xform->search_key.input_profile; + out = xform->search_key.output_profile; + + weston_log("LittleCMS error with color transformation from " + "'%s' to '%s', %s: %s\n", + in ? in->base.description : "(none)", + out ? out->base.description : "(none)", + cmlcms_category_name(xform->search_key.category), + text); +} + +static cmsHPROFILE +profile_from_rgb_curves(cmsContext ctx, cmsToneCurve *const curveset[3]) +{ + cmsHPROFILE p; + int i; + + for (i = 0; i < 3; i++) + assert(curveset[i]); + + p = cmsCreateLinearizationDeviceLinkTHR(ctx, cmsSigRgbData, curveset); + abort_oom_if_null(p); + + return p; +} + +static bool +xform_realize_chain(struct cmlcms_color_transform *xform) +{ + struct weston_color_manager_lcms *cm = get_cmlcms(xform->base.cm); + struct cmlcms_color_profile *output_profile = xform->search_key.output_profile; + cmsHPROFILE chain[5]; + unsigned chain_len = 0; + cmsHPROFILE extra = NULL; + + chain[chain_len++] = xform->search_key.input_profile->profile; + chain[chain_len++] = output_profile->profile; - 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; + switch (xform->search_key.category) { + case CMLCMS_CATEGORY_INPUT_TO_BLEND: + /* Add linearization step to make blending well-defined. */ + extra = profile_from_rgb_curves(cm->lcms_ctx, output_profile->eotf); + chain[chain_len++] = extra; + break; + case CMLCMS_CATEGORY_INPUT_TO_OUTPUT: + /* Just add VCGT if it is provided. */ + if (output_profile->vcgt[0]) { + extra = profile_from_rgb_curves(cm->lcms_ctx, + output_profile->vcgt); + chain[chain_len++] = extra; + } + break; + case CMLCMS_CATEGORY_BLEND_TO_OUTPUT: + assert(0 && "category handled in the caller"); + return false; + } + + assert(chain_len <= ARRAY_LENGTH(chain)); + + /** + * Binding to our LittleCMS plug-in occurs here. + * If you want to disable the plug-in while debugging, + * replace &transform_plugin with NULL. + */ + xform->lcms_ctx = cmsCreateContext(&transform_plugin, xform); + abort_oom_if_null(xform->lcms_ctx); + cmsSetLogErrorHandlerTHR(xform->lcms_ctx, lcms_xform_error_logger); + + assert(xform->status == CMLCMS_TRANSFORM_FAILED); + /* transform_factory() is invoked by this call. */ + xform->cmap_3dlut = cmsCreateMultiprofileTransformTHR(xform->lcms_ctx, + chain, + chain_len, + TYPE_RGB_FLT, + TYPE_RGB_FLT, + xform->search_key.intent_output, + 0); + cmsCloseProfile(extra); + + if (!xform->cmap_3dlut) + goto failed; + + if (xform->status != CMLCMS_TRANSFORM_3DLUT) { + cmsDeleteTransform(xform->cmap_3dlut); + xform->cmap_3dlut = NULL; + } + + switch (xform->status) { + case CMLCMS_TRANSFORM_FAILED: + goto failed; + case CMLCMS_TRANSFORM_OPTIMIZED: + case CMLCMS_TRANSFORM_3DLUT: + break; } + return true; + +failed: + cmsDeleteContext(xform->lcms_ctx); + xform->lcms_ctx = NULL; + + return false; +} + +char * +cmlcms_color_transform_search_param_string(const struct cmlcms_color_transform_search_param *search_key) +{ + const char *input_prof_desc = "none", *output_prof_desc = "none"; + char *str; + + if (search_key->input_profile) + input_prof_desc = search_key->input_profile->base.description; + + if (search_key->output_profile) + output_prof_desc = search_key->output_profile->base.description; + + str_printf(&str, " catergory: %s\n" \ + " input profile: %s\n" \ + " output profile: %s\n" \ + " selected intent from output profile: %u\n", + cmlcms_category_name(search_key->category), + input_prof_desc, + output_prof_desc, + search_key->intent_output); + + abort_oom_if_null(str); + + return str; +} + +static struct cmlcms_color_transform * +cmlcms_color_transform_create(struct weston_color_manager_lcms *cm, + const struct cmlcms_color_transform_search_param *search_param) +{ + struct cmlcms_color_transform *xform; + const char *err_msg; + char *str; + + xform = xzalloc(sizeof *xform); weston_color_transform_init(&xform->base, &cm->base); - xform->search_key = *param; + wl_list_init(&xform->link); + xform->search_key = *search_param; + xform->search_key.input_profile = ref_cprof(search_param->input_profile); + xform->search_key.output_profile = ref_cprof(search_param->output_profile); - 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; + weston_log_scope_printf(cm->transforms_scope, + "New color transformation: %p\n", xform); + str = cmlcms_color_transform_search_param_string(&xform->search_key); + weston_log_scope_printf(cm->transforms_scope, "%s", str); + free(str); + + /* Ensure the linearization etc. have been extracted. */ + if (!search_param->output_profile->eotf[0]) { + if (!retrieve_eotf_and_output_inv_eotf(cm->lcms_ctx, + search_param->output_profile->profile, + search_param->output_profile->eotf, + search_param->output_profile->output_inv_eotf_vcgt, + search_param->output_profile->vcgt, + cmlcms_reasonable_1D_points())) { + err_msg = "retrieve_eotf_and_output_inv_eotf failed"; + goto error; + } + } + + /* + * The blending space is chosen to be the output device space but + * linearized. This means that BLEND_TO_OUTPUT only needs to + * undo the linearization and add VCGT. + */ + switch (search_param->category) { + case CMLCMS_CATEGORY_INPUT_TO_BLEND: + case CMLCMS_CATEGORY_INPUT_TO_OUTPUT: + if (!xform_realize_chain(xform)) { + err_msg = "xform_realize_chain failed"; + goto error; + } + break; + 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_output_inv_eotf_vcgt; + xform->base.pre_curve.u.lut_3x1d.optimal_len = + cmlcms_reasonable_1D_points(); + xform->status = CMLCMS_TRANSFORM_OPTIMIZED; + break; + } wl_list_insert(&cm->color_transform_list, &xform->link); + assert(xform->status != CMLCMS_TRANSFORM_FAILED); + + str = weston_color_transform_string(&xform->base); + weston_log_scope_printf(cm->transforms_scope, " %s", str); + free(str); return xform; + +error: + weston_log_scope_printf(cm->transforms_scope, + " %s\n", err_msg); + cmlcms_color_transform_destroy(xform); + 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 86e2871f7..4aefd4b3b 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 4b0a0c385..4bd1e7620 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 eb1d45ebf..2834d7974 100644 --- a/libweston/color.c +++ b/libweston/color.c @@ -38,6 +38,8 @@ #include "color.h" #include "libweston-internal.h" +#include +#include "shared/xalloc.h" /** * Increase reference count of the color profile object @@ -165,6 +167,87 @@ weston_color_transform_init(struct weston_color_transform *xform, wl_signal_init(&xform->destroy_signal); } +static const char * +curve_type_to_str(enum weston_color_curve_type curve_type) +{ + switch (curve_type) { + case WESTON_COLOR_CURVE_TYPE_IDENTITY: + return "identity"; + case WESTON_COLOR_CURVE_TYPE_LUT_3x1D: + return "3x1D LUT"; + } + return "???"; +} + +static const char * +mapping_type_to_str(enum weston_color_mapping_type mapping_type) +{ + switch (mapping_type) { + case WESTON_COLOR_MAPPING_TYPE_IDENTITY: + return "identity"; + case WESTON_COLOR_MAPPING_TYPE_3D_LUT: + return "3D LUT"; + case WESTON_COLOR_MAPPING_TYPE_MATRIX: + return "matrix"; + } + return "???"; +} + +/** + * Print the color transform pipeline to a string + * + * \param xform The color transform. + * \return The string in which the pipeline is printed. + */ +WL_EXPORT char * +weston_color_transform_string(const struct weston_color_transform *xform) +{ + enum weston_color_mapping_type mapping_type = xform->mapping.type; + enum weston_color_curve_type pre_type = xform->pre_curve.type; + enum weston_color_curve_type post_type = xform->post_curve.type; + const char *empty = ""; + const char *sep = empty; + FILE *fp; + char *str = NULL; + size_t size = 0; + + fp = open_memstream(&str, &size); + abort_oom_if_null(fp); + + fprintf(fp, "pipeline: "); + + if (pre_type != WESTON_COLOR_CURVE_TYPE_IDENTITY) { + fprintf(fp, "%spre %s", sep, curve_type_to_str(pre_type)); + if (pre_type == WESTON_COLOR_CURVE_TYPE_LUT_3x1D) + fprintf(fp, " [%u]", xform->pre_curve.u.lut_3x1d.optimal_len); + sep = ", "; + } + + if (mapping_type != WESTON_COLOR_MAPPING_TYPE_IDENTITY) { + fprintf(fp, "%smapping %s", sep, mapping_type_to_str(mapping_type)); + if (mapping_type == WESTON_COLOR_MAPPING_TYPE_3D_LUT) + fprintf(fp, " [%u]", xform->mapping.u.lut3d.optimal_len); + sep = ", "; + } + + if (post_type != WESTON_COLOR_CURVE_TYPE_IDENTITY) { + fprintf(fp, "%spost %s", sep, curve_type_to_str(post_type)); + if (post_type == WESTON_COLOR_CURVE_TYPE_LUT_3x1D) + fprintf(fp, " [%u]", xform->post_curve.u.lut_3x1d.optimal_len); + sep = ", "; + } + + if (sep == empty) + fprintf(fp, "identity\n"); + else + fprintf(fp, "\n"); + + fclose(fp); + abort_oom_if_null(str); + + return str; +} + /** Deep copy */ void weston_surface_color_transform_copy(struct weston_surface_color_transform *dst, @@ -297,3 +380,55 @@ weston_compositor_load_icc_file(struct weston_compositor *compositor, 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 d2137af82..96d0d28f6 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,90 @@ 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, + + /** matrix */ + WESTON_COLOR_MAPPING_TYPE_MATRIX, +}; + +/** + * 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; +}; + +/** + * A 3x3 matrix and data is arranged as column major + */ +struct weston_color_mapping_matrix { + float matrix[9]; +}; + +/** + * 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; + struct weston_color_mapping_matrix mat; + } u; +}; + /** * Describes a color transformation formula * @@ -124,10 +209,10 @@ 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; */ + struct weston_color_curve post_curve; }; /** @@ -223,53 +308,19 @@ struct weston_color_manager { struct weston_output *output, struct weston_surface_color_transform *surf_xform); - /** Get output's blending space 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. - */ - 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. - */ - 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 + /** Compute derived color properties for an output * * \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. + * \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. + * 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_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 @@ -286,6 +337,9 @@ void weston_color_transform_init(struct weston_color_transform *xform, struct weston_color_manager *cm); +char * +weston_color_transform_string(const struct weston_color_transform *xform); + void weston_surface_color_transform_copy(struct weston_surface_color_transform *dst, const struct weston_surface_color_transform *src); @@ -305,4 +359,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 f87eb371f..e3b9afd9e 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -52,23 +52,28 @@ #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" #include "shared/string-helpers.h" #include "shared/timespec-util.h" #include "shared/signal.h" +#include "shared/xalloc.h" +#include "tearing-control-v1-server-protocol.h" #include "git-version.h" #include #include @@ -76,6 +81,9 @@ #include "backend.h" #include "libweston-internal.h" #include "color.h" +#include "output-capture.h" +#include "pixman-renderer.h" +#include "renderer-gl/gl-renderer.h" #include "weston-log-internal.h" @@ -87,9 +95,6 @@ #define DEFAULT_REPAINT_WINDOW 7 /* milliseconds */ -static void -weston_output_update_matrix(struct weston_output *output); - static void weston_output_transform_scale_init(struct weston_output *output, uint32_t transform, uint32_t scale); @@ -101,6 +106,67 @@ weston_compositor_build_view_list(struct weston_compositor *compositor, static char * weston_output_create_heads_string(struct weston_output *output); +static void +subsurface_committed(struct weston_surface *surface, + struct weston_coord_surface new_origin); + +static void +weston_view_dirty_paint_nodes(struct weston_view *view) +{ + struct weston_paint_node *node; + + wl_list_for_each(node, &view->paint_node_list, view_link) { + assert(node->surface == view->surface); + + node->status |= PAINT_NODE_VIEW_DIRTY; + } +} + +static void +weston_surface_dirty_paint_nodes(struct weston_surface *surface) +{ + struct weston_paint_node *node; + + wl_list_for_each(node, &surface->paint_node_list, surface_link) { + assert(node->surface == surface); + + node->status |= PAINT_NODE_VIEW_DIRTY; + } +} + +static void +weston_output_dirty_paint_nodes(struct weston_output *output) +{ + struct weston_paint_node *node; + + wl_list_for_each(node, &output->paint_node_list, output_link) { + assert(node->output == output); + + node->status |= PAINT_NODE_OUTPUT_DIRTY; + } +} + +static void +paint_node_update(struct weston_paint_node *pnode) +{ + struct weston_matrix *mat = &pnode->buffer_to_output_matrix; + bool view_dirty = pnode->status & PAINT_NODE_VIEW_DIRTY; + bool output_dirty = pnode->status & PAINT_NODE_OUTPUT_DIRTY; + + if (view_dirty || output_dirty) { + weston_view_buffer_to_output_matrix(pnode->view, + pnode->output, mat); + weston_matrix_invert(&pnode->output_to_buffer_matrix, mat); + pnode->needs_filtering = weston_matrix_needs_filtering(mat); + + pnode->valid_transform = weston_matrix_to_transform(mat, + &pnode->transform); + } + + pnode->status = PAINT_NODE_CLEAN; + +} + static struct weston_paint_node * weston_paint_node_create(struct weston_surface *surface, struct weston_view *view, @@ -130,6 +196,8 @@ weston_paint_node_create(struct weston_surface *surface, break; } + pnode->need_through_hole = false; + pnode->surface = surface; wl_list_insert(&surface->paint_node_list, &pnode->surface_link); @@ -141,6 +209,9 @@ weston_paint_node_create(struct weston_surface *surface, wl_list_init(&pnode->z_order_link); + pnode->status = PAINT_NODE_ALL_DIRTY; + paint_node_update(pnode); + return pnode; } @@ -184,6 +255,12 @@ weston_mode_switch_send_events(struct weston_head *head, if (version >= WL_OUTPUT_SCALE_SINCE_VERSION && scale_changed) wl_output_send_scale(resource, output->current_scale); + if (version >= WL_OUTPUT_NAME_SINCE_VERSION) + wl_output_send_name(resource, head->name); + + if (version >= WL_OUTPUT_DESCRIPTION_SINCE_VERSION) + wl_output_send_description(resource, head->model); + if (version >= WL_OUTPUT_DONE_SINCE_VERSION) wl_output_send_done(resource); } @@ -198,6 +275,13 @@ weston_mode_switch_send_events(struct weston_head *head, } } +WL_EXPORT bool +weston_output_contains_point(struct weston_output *output, + int32_t x, int32_t y) +{ + return pixman_region32_contains_point(&output->region, x, y, NULL); +} + static void weston_mode_switch_finish(struct weston_output *output, int mode_changed, int scale_changed) @@ -226,13 +310,11 @@ weston_mode_switch_finish(struct weston_output *output, if (!pointer) continue; - x = wl_fixed_to_int(pointer->x); - y = wl_fixed_to_int(pointer->y); - + x = pointer->pos.c.x; + y = pointer->pos.c.y; if (!pixman_region32_contains_point(&old_output_region, x, y, NULL) || - pixman_region32_contains_point(&output->region, - x, y, NULL)) + weston_output_contains_point(output, x, y)) continue; if (x >= output->x + output->width) @@ -240,8 +322,7 @@ weston_mode_switch_finish(struct weston_output *output, if (y >= output->y + output->height) y = output->y + output->height - 1; - pointer->x = wl_fixed_from_int(x); - pointer->y = wl_fixed_from_int(y); + pointer->pos.c = weston_coord(x, y); } pixman_region32_fini(&old_output_region); @@ -249,6 +330,8 @@ weston_mode_switch_finish(struct weston_output *output, if (!mode_changed && !scale_changed) return; + weston_output_damage(output); + /* notify clients of the changes */ wl_list_for_each(head, &output->head_list, output_link) weston_mode_switch_send_events(head, @@ -393,6 +476,7 @@ weston_view_create(struct weston_surface *surface) wl_list_insert(&surface->views, &view->surface_link); wl_signal_init(&view->destroy_signal); + wl_signal_init(&view->unmap_signal); wl_list_init(&view->link); wl_list_init(&view->layer_link.link); wl_list_init(&view->paint_node_list); @@ -410,6 +494,7 @@ weston_view_create(struct weston_surface *surface) pixman_region32_init(&view->geometry.scissor); pixman_region32_init(&view->transform.boundingbox); view->transform.dirty = 1; + weston_view_update_transform(view); return view; } @@ -629,160 +714,94 @@ 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) +WL_EXPORT struct weston_coord_global +weston_coord_surface_to_global(const struct weston_view *view, + struct weston_coord_surface coord) { - surface->compositor->renderer->surface_set_color(surface, red, green, blue, alpha); - surface->is_opaque = !(alpha < 1.0); -} + struct weston_coord_global out; -WL_EXPORT void -weston_view_to_global_float(struct weston_view *view, - float sx, float sy, float *x, float *y) -{ - if (view->transform.enabled) { - struct weston_vector v = { { sx, sy, 0.0f, 1.0f } }; + assert(!view->transform.dirty); + assert(view->surface == coord.coordinate_space_id); - weston_matrix_transform(&view->transform.matrix, &v); + out.c = weston_matrix_transform_coord(&view->transform.matrix, + coord.c); + return out; +} - if (fabsf(v.f[3]) < 1e-6) { - weston_log("warning: numerical instability in " - "%s(), divisor = %g\n", __func__, - v.f[3]); - *x = 0; - *y = 0; - return; - } +WL_EXPORT struct weston_coord_surface +weston_coord_global_to_surface(const struct weston_view *view, + struct weston_coord_global coord) +{ + struct weston_coord_surface out; - *x = v.f[0] / v.f[3]; - *y = v.f[1] / v.f[3]; - } else { - *x = sx + view->geometry.x; - *y = sy + view->geometry.y; - } + assert(!view->transform.dirty); + out.c = weston_matrix_transform_coord(&view->transform.inverse, + coord.c); + out.coordinate_space_id = view->surface; + return out; } -/** Transform a point to buffer coordinates - * - * \param width Surface width. - * \param height Surface height. - * \param transform Buffer transform. - * \param scale Buffer scale. - * \param sx Surface x coordinate of a point. - * \param sy Surface y coordinate of a point. - * \param[out] bx Buffer x coordinate of the point. - * \param[out] by Buffer Y coordinate of the point. - * - * Converts the given surface-local coordinates to buffer coordinates - * according to the given buffer transform and scale. - * This ignores wp_viewport. - * - * The given width and height must be the result of inverse scaled and - * inverse transformed buffer size. - */ -WL_EXPORT void -weston_transformed_coord(int width, int height, - enum wl_output_transform transform, - int32_t scale, - float sx, float sy, float *bx, float *by) +WL_EXPORT struct weston_coord_buffer +weston_coord_surface_to_buffer(const struct weston_surface *surface, + struct weston_coord_surface coord) { - switch (transform) { - case WL_OUTPUT_TRANSFORM_NORMAL: - default: - *bx = sx; - *by = sy; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED: - *bx = width - sx; - *by = sy; - break; - case WL_OUTPUT_TRANSFORM_90: - *bx = sy; - *by = width - sx; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_90: - *bx = sy; - *by = sx; - break; - case WL_OUTPUT_TRANSFORM_180: - *bx = width - sx; - *by = height - sy; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - *bx = sx; - *by = height - sy; - break; - case WL_OUTPUT_TRANSFORM_270: - *bx = height - sy; - *by = sx; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - *bx = height - sy; - *by = width - sx; - break; - } + struct weston_coord_buffer tmp; + + assert(surface == coord.coordinate_space_id); - *bx *= scale; - *by *= scale; + tmp.c = weston_matrix_transform_coord(&surface->surface_to_buffer_matrix, + coord.c); + return tmp; } -/** Transform a rectangle to buffer coordinates - * - * \param width Surface width. - * \param height Surface height. - * \param transform Buffer transform. - * \param scale Buffer scale. - * \param rect Rectangle in surface coordinates. - * \return Rectangle in buffer coordinates. - * - * Converts the given surface-local rectangle to buffer coordinates - * according to the given buffer transform and scale. The resulting - * rectangle is guaranteed to be well-formed. - * This ignores wp_viewport. - * - * The given width and height must be the result of inverse scaled and - * inverse transformed buffer size. - */ WL_EXPORT pixman_box32_t -weston_transformed_rect(int width, int height, - enum wl_output_transform transform, - int32_t scale, - pixman_box32_t rect) +weston_matrix_transform_rect(struct weston_matrix *matrix, + pixman_box32_t rect) { - float x1, x2, y1, y2; + int i; + pixman_box32_t out; - pixman_box32_t ret; + /* since pixman regions are defined by two corners we have + * to be careful with rotations that aren't multiples of 90. + * We need to take all four corners of the region and rotate + * them, then construct the largest possible two corner + * rectangle from the result. + */ + struct weston_coord corners[4] = { + weston_coord(rect.x1, rect.y1), + weston_coord(rect.x2, rect.y1), + weston_coord(rect.x1, rect.y2), + weston_coord(rect.x2, rect.y2), + }; - weston_transformed_coord(width, height, transform, scale, - rect.x1, rect.y1, &x1, &y1); - weston_transformed_coord(width, height, transform, scale, - rect.x2, rect.y2, &x2, &y2); + for (i = 0; i < 4; i++) + corners[i] = weston_matrix_transform_coord(matrix, corners[i]); - if (x1 <= x2) { - ret.x1 = x1; - ret.x2 = x2; - } else { - ret.x1 = x2; - ret.x2 = x1; - } + out.x1 = floor(corners[0].x); + out.y1 = floor(corners[0].y); + out.x2 = ceil(corners[0].x); + out.y2 = ceil(corners[0].y); - if (y1 <= y2) { - ret.y1 = y1; - ret.y2 = y2; - } else { - ret.y1 = y2; - ret.y2 = y1; + for (i = 1; i < 4; i++) { + if (floor(corners[i].x) < out.x1) + out.x1 = floor(corners[i].x); + if (floor(corners[i].y) < out.y1) + out.y1 = floor(corners[i].y); + if (ceil(corners[i].x) > out.x2) + out.x2 = ceil(corners[i].x); + if (ceil(corners[i].y) > out.y2) + out.y2 = ceil(corners[i].y); } - - return ret; + return out; } -/** Transform a region by a matrix, restricted to axis-aligned transformations +/** Transform a region by a matrix * - * Warning: This function does not work for projective, affine, or matrices - * that encode arbitrary rotations. Only 90-degree step rotations are - * supported. + * Warning: This function does not work perfectly for projective, + * affine, or matrices that encode arbitrary rotations. Only 90-degree + * step rotations are exact. + * + * More complicated matrices result in some expansion. */ WL_EXPORT void weston_matrix_transform_region(pixman_region32_t *dest, @@ -797,198 +816,14 @@ weston_matrix_transform_region(pixman_region32_t *dest, if (!dest_rects) return; - for (i = 0; i < nrects; i++) { - struct weston_vector vec1 = {{ - src_rects[i].x1, src_rects[i].y1, 0, 1 - }}; - weston_matrix_transform(matrix, &vec1); - vec1.f[0] /= vec1.f[3]; - vec1.f[1] /= vec1.f[3]; - - struct weston_vector vec2 = {{ - src_rects[i].x2, src_rects[i].y2, 0, 1 - }}; - weston_matrix_transform(matrix, &vec2); - vec2.f[0] /= vec2.f[3]; - vec2.f[1] /= vec2.f[3]; - - if (vec1.f[0] < vec2.f[0]) { - dest_rects[i].x1 = floor(vec1.f[0]); - dest_rects[i].x2 = ceil(vec2.f[0]); - } else { - dest_rects[i].x1 = floor(vec2.f[0]); - dest_rects[i].x2 = ceil(vec1.f[0]); - } - - if (vec1.f[1] < vec2.f[1]) { - dest_rects[i].y1 = floor(vec1.f[1]); - dest_rects[i].y2 = ceil(vec2.f[1]); - } else { - dest_rects[i].y1 = floor(vec2.f[1]); - dest_rects[i].y2 = ceil(vec1.f[1]); - } - } - - pixman_region32_clear(dest); - pixman_region32_init_rects(dest, dest_rects, nrects); - free(dest_rects); -} - -/** Transform a region to buffer coordinates - * - * \param width Surface width. - * \param height Surface height. - * \param transform Buffer transform. - * \param scale Buffer scale. - * \param[in] src Region in surface coordinates. - * \param[out] dest Resulting region in buffer coordinates. - * - * Converts the given surface-local region to buffer coordinates - * according to the given buffer transform and scale. - * This ignores wp_viewport. - * - * The given width and height must be the result of inverse scaled and - * inverse transformed buffer size. - * - * src and dest are allowed to point to the same memory for in-place conversion. - */ -WL_EXPORT void -weston_transformed_region(int width, int height, - enum wl_output_transform transform, - int32_t scale, - pixman_region32_t *src, pixman_region32_t *dest) -{ - pixman_box32_t *src_rects, *dest_rects; - int nrects, i; - - if (transform == WL_OUTPUT_TRANSFORM_NORMAL && scale == 1) { - if (src != dest) - pixman_region32_copy(dest, src); - return; - } - - src_rects = pixman_region32_rectangles(src, &nrects); - dest_rects = malloc(nrects * sizeof(*dest_rects)); - if (!dest_rects) - return; - - if (transform == WL_OUTPUT_TRANSFORM_NORMAL) { - memcpy(dest_rects, src_rects, nrects * sizeof(*dest_rects)); - } else { - for (i = 0; i < nrects; i++) { - switch (transform) { - default: - case WL_OUTPUT_TRANSFORM_NORMAL: - dest_rects[i].x1 = src_rects[i].x1; - dest_rects[i].y1 = src_rects[i].y1; - dest_rects[i].x2 = src_rects[i].x2; - dest_rects[i].y2 = src_rects[i].y2; - break; - case WL_OUTPUT_TRANSFORM_90: - dest_rects[i].x1 = src_rects[i].y1; - dest_rects[i].y1 = width - src_rects[i].x2; - dest_rects[i].x2 = src_rects[i].y2; - dest_rects[i].y2 = width - src_rects[i].x1; - break; - case WL_OUTPUT_TRANSFORM_180: - dest_rects[i].x1 = width - src_rects[i].x2; - dest_rects[i].y1 = height - src_rects[i].y2; - dest_rects[i].x2 = width - src_rects[i].x1; - dest_rects[i].y2 = height - src_rects[i].y1; - break; - case WL_OUTPUT_TRANSFORM_270: - dest_rects[i].x1 = height - src_rects[i].y2; - dest_rects[i].y1 = src_rects[i].x1; - dest_rects[i].x2 = height - src_rects[i].y1; - dest_rects[i].y2 = src_rects[i].x2; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED: - dest_rects[i].x1 = width - src_rects[i].x2; - dest_rects[i].y1 = src_rects[i].y1; - dest_rects[i].x2 = width - src_rects[i].x1; - dest_rects[i].y2 = src_rects[i].y2; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_90: - dest_rects[i].x1 = src_rects[i].y1; - dest_rects[i].y1 = src_rects[i].x1; - dest_rects[i].x2 = src_rects[i].y2; - dest_rects[i].y2 = src_rects[i].x2; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - dest_rects[i].x1 = src_rects[i].x1; - dest_rects[i].y1 = height - src_rects[i].y2; - dest_rects[i].x2 = src_rects[i].x2; - dest_rects[i].y2 = height - src_rects[i].y1; - break; - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - dest_rects[i].x1 = height - src_rects[i].y2; - dest_rects[i].y1 = width - src_rects[i].x2; - dest_rects[i].x2 = height - src_rects[i].y1; - dest_rects[i].y2 = width - src_rects[i].x1; - break; - } - } - } - - if (scale != 1) { - for (i = 0; i < nrects; i++) { - dest_rects[i].x1 *= scale; - dest_rects[i].x2 *= scale; - dest_rects[i].y1 *= scale; - dest_rects[i].y2 *= scale; - } - } + for (i = 0; i < nrects; i++) + dest_rects[i] = weston_matrix_transform_rect(matrix, src_rects[i]); pixman_region32_clear(dest); pixman_region32_init_rects(dest, dest_rects, nrects); free(dest_rects); } -static void -viewport_surface_to_buffer(struct weston_surface *surface, - float sx, float sy, float *bx, float *by) -{ - struct weston_buffer_viewport *vp = &surface->buffer_viewport; - double src_width, src_height; - double src_x, src_y; - - if (vp->buffer.src_width == wl_fixed_from_int(-1)) { - if (vp->surface.width == -1) { - *bx = sx; - *by = sy; - return; - } - - src_x = 0.0; - src_y = 0.0; - src_width = surface->width_from_buffer; - src_height = surface->height_from_buffer; - } else { - src_x = wl_fixed_to_double(vp->buffer.src_x); - src_y = wl_fixed_to_double(vp->buffer.src_y); - src_width = wl_fixed_to_double(vp->buffer.src_width); - src_height = wl_fixed_to_double(vp->buffer.src_height); - } - - *bx = sx * src_width / surface->width + src_x; - *by = sy * src_height / surface->height + src_y; -} - -WL_EXPORT void -weston_surface_to_buffer_float(struct weston_surface *surface, - float sx, float sy, float *bx, float *by) -{ - struct weston_buffer_viewport *vp = &surface->buffer_viewport; - - /* first transform coordinates if the viewport is set */ - viewport_surface_to_buffer(surface, sx, sy, bx, by); - - weston_transformed_coord(surface->width_from_buffer, - surface->height_from_buffer, - vp->buffer.transform, vp->buffer.scale, - *bx, *by, bx, by); -} - /** Transform a rectangle from surface coordinates to buffer coordinates * * \param surface The surface to fetch wp_viewport and buffer transformation @@ -1011,22 +846,8 @@ WL_EXPORT pixman_box32_t weston_surface_to_buffer_rect(struct weston_surface *surface, pixman_box32_t rect) { - struct weston_buffer_viewport *vp = &surface->buffer_viewport; - float xf, yf; - - /* first transform box coordinates if the viewport is set */ - viewport_surface_to_buffer(surface, rect.x1, rect.y1, &xf, &yf); - rect.x1 = floorf(xf); - rect.y1 = floorf(yf); - - viewport_surface_to_buffer(surface, rect.x2, rect.y2, &xf, &yf); - rect.x2 = ceilf(xf); - rect.y2 = ceilf(yf); - - return weston_transformed_rect(surface->width_from_buffer, - surface->height_from_buffer, - vp->buffer.transform, vp->buffer.scale, - rect); + return weston_matrix_transform_rect(&surface->surface_to_buffer_matrix, + rect); } /** Transform a region from surface coordinates to buffer coordinates @@ -1067,6 +888,16 @@ weston_surface_to_buffer_region(struct weston_surface *surface, free(dest_rects); } +WL_EXPORT void +weston_view_buffer_to_output_matrix(const struct weston_view *view, + const struct weston_output *output, + struct weston_matrix *matrix) +{ + *matrix = view->surface->buffer_to_surface_matrix; + weston_matrix_multiply(matrix, &view->transform.matrix); + weston_matrix_multiply(matrix, &output->matrix); +} + WL_EXPORT void weston_view_move_to_plane(struct weston_view *view, struct weston_plane *plane) @@ -1305,6 +1136,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 @@ -1330,7 +1169,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, @@ -1406,13 +1247,15 @@ static void weston_view_to_view_map(struct weston_view *from, struct weston_view *to, int from_x, int from_y, int *to_x, int *to_y) { - float x, y; + struct weston_coord_surface cs; + struct weston_coord_global cg; - weston_view_to_global_float(from, from_x, from_y, &x, &y); - weston_view_from_global_float(to, x, y, &x, &y); + cs = weston_coord_surface(from_x, from_y, from->surface); + cg = weston_coord_surface_to_global(from, cs); + cs = weston_coord_global_to_surface(to, cg); - *to_x = round(x); - *to_y = round(y); + *to_x = round(cs.c.x); + *to_y = round(cs.c.y); } static void @@ -1452,16 +1295,20 @@ view_compute_bbox(struct weston_view *view, const pixman_box32_t *inbox, } for (i = 0; i < 4; ++i) { - float x, y; - weston_view_to_global_float(view, s[i][0], s[i][1], &x, &y); - if (x < min_x) - min_x = x; - if (x > max_x) - max_x = x; - if (y < min_y) - min_y = y; - if (y > max_y) - max_y = y; + struct weston_coord_surface cs; + struct weston_coord_global cg; + + cs = weston_coord_surface(s[i][0], s[i][1], + view->surface); + cg = weston_coord_surface_to_global(view, cs); + if (cg.c.x < min_x) + min_x = cg.c.x; + if (cg.c.x > max_x) + max_x = cg.c.x; + if (cg.c.y < min_y) + min_y = cg.c.y; + if (cg.c.y > max_y) + max_y = cg.c.y; } int_x = floorf(min_x); @@ -1495,19 +1342,19 @@ weston_view_update_transform_disable(struct weston_view *view) view->transform.enabled = 0; /* round off fractions when not transformed */ - view->geometry.x = roundf(view->geometry.x); - view->geometry.y = roundf(view->geometry.y); + view->geometry.pos_offset.x = round(view->geometry.pos_offset.x); + view->geometry.pos_offset.y = round(view->geometry.pos_offset.y); /* Otherwise identity matrix, but with x and y translation. */ view->transform.position.matrix.type = WESTON_MATRIX_TRANSFORM_TRANSLATE; - view->transform.position.matrix.d[12] = view->geometry.x; - view->transform.position.matrix.d[13] = view->geometry.y; + view->transform.position.matrix.d[12] = view->geometry.pos_offset.x; + view->transform.position.matrix.d[13] = view->geometry.pos_offset.y; view->transform.matrix = view->transform.position.matrix; view->transform.inverse = view->transform.position.matrix; - view->transform.inverse.d[12] = -view->geometry.x; - view->transform.inverse.d[13] = -view->geometry.y; + view->transform.inverse.d[12] = -view->geometry.pos_offset.x; + view->transform.inverse.d[13] = -view->geometry.pos_offset.y; pixman_region32_init_rect(&view->transform.boundingbox, 0, 0, @@ -1517,7 +1364,8 @@ weston_view_update_transform_disable(struct weston_view *view) weston_view_update_transform_scissor(view, &view->transform.boundingbox); pixman_region32_translate(&view->transform.boundingbox, - view->geometry.x, view->geometry.y); + view->geometry.pos_offset.x, + view->geometry.pos_offset.y); if (view->alpha == 1.0) { pixman_region32_copy(&view->transform.opaque, @@ -1527,8 +1375,8 @@ weston_view_update_transform_disable(struct weston_view *view) &view->transform.opaque, &view->geometry.scissor); pixman_region32_translate(&view->transform.opaque, - view->geometry.x, - view->geometry.y); + view->geometry.pos_offset.x, + view->geometry.pos_offset.y); } } @@ -1546,8 +1394,8 @@ weston_view_update_transform_enable(struct weston_view *view) /* Otherwise identity matrix, but with x and y translation. */ view->transform.position.matrix.type = WESTON_MATRIX_TRANSFORM_TRANSLATE; - view->transform.position.matrix.d[12] = view->geometry.x; - view->transform.position.matrix.d[13] = view->geometry.y; + view->transform.position.matrix.d[12] = view->geometry.pos_offset.x; + view->transform.position.matrix.d[13] = view->geometry.pos_offset.y; weston_matrix_init(matrix); wl_list_for_each(tform, &view->geometry.transformation_list, link) @@ -1589,14 +1437,6 @@ weston_view_update_transform_enable(struct weston_view *view) 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) { @@ -1668,73 +1508,8 @@ weston_view_geometry_dirty(struct weston_view *view) wl_list_for_each(child, &view->geometry.child_list, geometry.parent_link) weston_view_geometry_dirty(child); -} - -WL_EXPORT void -weston_view_to_global_fixed(struct weston_view *view, - wl_fixed_t vx, wl_fixed_t vy, - wl_fixed_t *x, wl_fixed_t *y) -{ - float xf, yf; - - weston_view_to_global_float(view, - wl_fixed_to_double(vx), - wl_fixed_to_double(vy), - &xf, &yf); - *x = wl_fixed_from_double(xf); - *y = wl_fixed_from_double(yf); -} - -WL_EXPORT void -weston_view_from_global_float(struct weston_view *view, - float x, float y, float *vx, float *vy) -{ - if (view->transform.enabled) { - struct weston_vector v = { { x, y, 0.0f, 1.0f } }; - - weston_matrix_transform(&view->transform.inverse, &v); - - if (fabsf(v.f[3]) < 1e-6) { - weston_log("warning: numerical instability in " - "weston_view_from_global(), divisor = %g\n", - v.f[3]); - *vx = 0; - *vy = 0; - return; - } - - *vx = v.f[0] / v.f[3]; - *vy = v.f[1] / v.f[3]; - } else { - *vx = x - view->geometry.x; - *vy = y - view->geometry.y; - } -} -WL_EXPORT void -weston_view_from_global_fixed(struct weston_view *view, - wl_fixed_t x, wl_fixed_t y, - wl_fixed_t *vx, wl_fixed_t *vy) -{ - float vxf, vyf; - - weston_view_from_global_float(view, - wl_fixed_to_double(x), - wl_fixed_to_double(y), - &vxf, &vyf); - *vx = wl_fixed_from_double(vxf); - *vy = wl_fixed_from_double(vyf); -} - -WL_EXPORT void -weston_view_from_global(struct weston_view *view, - int32_t x, int32_t y, int32_t *vx, int32_t *vy) -{ - float vxf, vyf; - - weston_view_from_global_float(view, x, y, &vxf, &vyf); - *vx = floorf(vxf); - *vy = floorf(vyf); + weston_view_dirty_paint_nodes(view); } /** @@ -1789,14 +1564,30 @@ weston_surface_damage(struct weston_surface *surface) weston_surface_schedule_repaint(surface); } +WL_EXPORT void +weston_view_set_rel_position(struct weston_view *view, float x, float y) +{ + assert(view->geometry.parent); + + if (view->geometry.pos_offset.x == x && + view->geometry.pos_offset.y == y) + return; + + view->geometry.pos_offset = weston_coord(x, y); + weston_view_geometry_dirty(view); +} + WL_EXPORT void weston_view_set_position(struct weston_view *view, float x, float y) { - if (view->geometry.x == x && view->geometry.y == y) + assert(view->surface->committed != subsurface_committed); + assert(!view->geometry.parent); + + if (view->geometry.pos_offset.x == x && + view->geometry.pos_offset.y == y) return; - view->geometry.x = x; - view->geometry.y = y; + view->geometry.pos_offset = weston_coord(x, y); weston_view_geometry_dirty(view); } @@ -1809,6 +1600,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 @@ -1982,7 +1774,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 @@ -1999,6 +1795,8 @@ weston_view_matches_output_entirely(struct weston_view *ev, pixman_box32_t *extents = pixman_region32_extents(&ev->transform.boundingbox); + assert(!ev->transform.dirty); + if (extents->x1 != output->x || extents->y1 != output->y || extents->x2 != output->x + output->width || @@ -2041,6 +1839,18 @@ weston_surface_is_mapped(struct weston_surface *surface) return surface->is_mapped; } +/** Check if the weston_surface is emitting an unmapping commit + * + * @param surface The weston_surface. + * + * Returns true if the surface is emitting an unmapping commit. + */ +WL_EXPORT bool +weston_surface_is_unmapping(struct weston_surface *surface) +{ + return surface->is_unmapping; +} + static void surface_set_size(struct weston_surface *surface, int32_t width, int32_t height) { @@ -2070,7 +1880,7 @@ fixed_round_up_to_int(wl_fixed_t f) return wl_fixed_to_int(wl_fixed_from_int(1) - 1 + f); } -static void +WESTON_EXPORT_FOR_TESTS void convert_size_by_transform_scale(int32_t *width_out, int32_t *height_out, int32_t width, int32_t height, uint32_t transform, @@ -2103,7 +1913,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; @@ -2152,46 +1962,49 @@ weston_compositor_get_time(struct timespec *time) clock_gettime(CLOCK_REALTIME, time); } +bool +weston_view_takes_input_at_point(struct weston_view *view, + struct weston_coord_surface pos) +{ + assert(pos.coordinate_space_id == view->surface); + + if (!pixman_region32_contains_point(&view->surface->input, + pos.c.x, pos.c.y, NULL)) + return false; + + if (view->geometry.scissor_enabled && + !pixman_region32_contains_point(&view->geometry.scissor, + pos.c.x, pos.c.y, NULL)) + return false; + + return true; +} + /** weston_compositor_pick_view * \ingroup compositor */ WL_EXPORT struct weston_view * weston_compositor_pick_view(struct weston_compositor *compositor, - wl_fixed_t x, wl_fixed_t y, - wl_fixed_t *vx, wl_fixed_t *vy) + struct weston_coord_global pos) { struct weston_view *view; - wl_fixed_t view_x, view_y; - int view_ix, view_iy; - int ix = wl_fixed_to_int(x); - int iy = wl_fixed_to_int(y); /* Can't use paint node list: occlusion by input regions, not opaque. */ wl_list_for_each(view, &compositor->view_list, link) { - if (!pixman_region32_contains_point( - &view->transform.boundingbox, ix, iy, NULL)) - continue; + struct weston_coord_surface surf_pos; - weston_view_from_global_fixed(view, x, y, &view_x, &view_y); - view_ix = wl_fixed_to_int(view_x); - view_iy = wl_fixed_to_int(view_y); + weston_view_update_transform(view); - if (!pixman_region32_contains_point(&view->surface->input, - view_ix, view_iy, NULL)) + if (!pixman_region32_contains_point( + &view->transform.boundingbox, pos.c.x, pos.c.y, NULL)) continue; - if (view->geometry.scissor_enabled && - !pixman_region32_contains_point(&view->geometry.scissor, - view_ix, view_iy, NULL)) + surf_pos = weston_coord_global_to_surface(view, pos); + if (!weston_view_takes_input_at_point(view, surf_pos)) continue; - *vx = view_x; - *vy = view_y; return view; } - - *vx = wl_fixed_from_int(-1000000); - *vy = wl_fixed_from_int(-1000000); return NULL; } @@ -2225,22 +2038,34 @@ weston_view_unmap(struct weston_view *view) view->output_mask = 0; weston_surface_assign_output(view->surface); - if (weston_surface_is_mapped(view->surface)) - return; - - wl_list_for_each(seat, &view->surface->compositor->seat_list, link) { - struct weston_touch *touch = weston_seat_get_touch(seat); - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_keyboard *keyboard = - weston_seat_get_keyboard(seat); - - if (keyboard && keyboard->focus == view->surface) - weston_keyboard_set_focus(keyboard, NULL); - if (pointer && pointer->focus == view) - weston_pointer_clear_focus(pointer); - if (touch && touch->focus == view) - weston_touch_set_focus(touch, NULL); + if (!weston_surface_is_mapped(view->surface)) { + wl_list_for_each(seat, &view->surface->compositor->seat_list, link) { + struct weston_touch *touch = weston_seat_get_touch(seat); + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(seat); + struct weston_tablet_tool *tool; + + if (keyboard && keyboard->focus == view->surface) + weston_keyboard_set_focus(keyboard, NULL); + if (pointer && pointer->focus == view) + weston_pointer_clear_focus(pointer); + if (touch && touch->focus == view) + weston_touch_set_focus(touch, NULL); + + wl_list_for_each(tool, &seat->tablet_tool_list, link) { + if (tool->focus == view) + weston_tablet_tool_set_focus(tool, NULL, 0); + } + } } + 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 @@ -2258,8 +2083,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; } @@ -2298,8 +2121,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; @@ -2333,7 +2166,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); @@ -2352,6 +2186,9 @@ weston_surface_destroy(struct weston_surface *surface) fd_clear(&surface->acquire_fence_fd); + if (surface->tear_control) + surface->tear_control->surface = NULL; + free(surface); } @@ -2364,7 +2201,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) @@ -2375,24 +2212,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); @@ -2408,45 +2258,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 == ref->buffer && type == ref->type) + return; - if (buffer && buffer != ref->buffer) { - buffer->busy_count++; - wl_signal_add(&buffer->destroy_signal, - &ref->destroy_listener); + /* 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 @@ -2511,21 +2443,196 @@ 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)) + if (weston_surface_is_mapped(surface)) { weston_surface_unmap(surface); + /* This is the unmapping commit */ + surface->is_unmapping = true; + } } surface->compositor->renderer->attach(surface, buffer); 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 @@ -2554,16 +2661,31 @@ 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) +surface_flush_damage(struct weston_surface *surface, struct weston_output *output) { - 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), - TLP_OUTPUT(surface->output), TLP_END); + TL_POINT(surface->compositor, "core_flush_damage", + TLP_SURFACE(surface), TLP_OUTPUT(output), TLP_END); pixman_region32_clear(&surface->damage); } @@ -2574,6 +2696,8 @@ view_accumulate_damage(struct weston_view *view, { pixman_region32_t damage; + assert(!view->transform.dirty); + pixman_region32_init(&damage); if (view->transform.enabled) { pixman_box32_t *extents; @@ -2583,7 +2707,8 @@ view_accumulate_damage(struct weston_view *view, } else { pixman_region32_copy(&damage, &view->surface->damage); pixman_region32_translate(&damage, - view->geometry.x, view->geometry.y); + view->geometry.pos_offset.x, + view->geometry.pos_offset.y); } pixman_region32_intersect(&damage, &damage, @@ -2640,7 +2765,7 @@ output_accumulate_damage(struct weston_output *output) continue; pnode->surface->touched = true; - surface_flush_damage(pnode->surface); + surface_flush_damage(pnode->surface, output); /* Both the renderer and the backend have seen the buffer * by now. If renderer needs the buffer, it has its own @@ -2651,7 +2776,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); } @@ -2702,8 +2829,10 @@ view_ensure_paint_node(struct weston_view *view, struct weston_output *output) return NULL; pnode = weston_view_find_paint_node(view, output); - if (pnode) + if (pnode) { + paint_node_update(pnode); return pnode; + } return weston_paint_node_create(view->surface, view, output); } @@ -2752,10 +2881,10 @@ view_list_add_subsurface_view(struct weston_compositor *compositor, wl_list_insert(&sub->surface->views, &view->surface_link); } else { view = weston_view_create(sub->surface); - weston_view_set_position(view, - sub->position.x, - sub->position.y); weston_view_set_transform_parent(view, parent); + weston_view_set_rel_position(view, + sub->position.offset.c.x, + sub->position.offset.c.y); } view->parent_view = parent; @@ -2794,6 +2923,25 @@ 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)) { + weston_log_paced(&compositor->unmapped_surface_or_view_pacer, + 1, 0, + "Detected an unmapped surface or view in " + "the layer list, which should not occur.\n"); + + 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)) { @@ -2869,7 +3017,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; @@ -2909,10 +3057,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; } @@ -2941,10 +3093,7 @@ weston_output_repaint(struct weston_output *output, void *repaint_data) pixman_region32_subtract(&output_damage, &output_damage, &ec->primary_plane.clip); - 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); @@ -2966,6 +3115,8 @@ weston_output_repaint(struct weston_output *output, void *repaint_data) animation->frame(animation, output, &output->frame_time); } + weston_output_capture_info_repaint_done(output->capture_info); + TL_POINT(ec, "core_repaint_posted", TLP_OUTPUT(output), TLP_END); return r; @@ -2980,8 +3131,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; @@ -3006,12 +3156,15 @@ weston_output_maybe_repaint(struct weston_output *output, struct timespec *now, if (!output->repaint_needed) goto err; + if (output->power_state == WESTON_OUTPUT_POWER_FORCED_OFF) + goto err; + /* If repaint fails, we aren't going to get weston_output_finish_frame * to trigger a new repaint, so drop it from repaint and hope * 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; @@ -3063,41 +3216,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->backend); 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->backend); } else { if (compositor->backend->repaint_cancel) - compositor->backend->repaint_cancel(compositor, - repaint_data); + compositor->backend->repaint_cancel(compositor->backend); } 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); + } } } @@ -3191,18 +3359,23 @@ weston_output_finish_frame(struct weston_output *output, output->frame_time = *stamp; + /* If we're tearing just repaint right away */ + if (presented_flags & WESTON_FINISH_FRAME_TEARING) { + output->next_repaint = now; + goto out; + } + timespec_add_nsec(&output->next_repaint, stamp, refresh_nsec); timespec_add_msec(&output->next_repaint, &output->next_repaint, -compositor->repaint_msec); msec_rel = timespec_sub_to_msec(&output->next_repaint, &now); if (msec_rel < -1000 || msec_rel > 1000) { - static bool warned; - - if (!warned) - weston_log("Warning: computed repaint delay is " - "insane: %lld msec\n", (long long) msec_rel); - warned = true; + weston_log_paced(&output->repaint_delay_pacer, + 5, 60 * 60 * 1000, + "Warning: computed repaint delay for output " + "[%s] is abnormal: %lld msec\n", + output->name, (long long) msec_rel); output->next_repaint = now; } @@ -3244,7 +3417,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); } @@ -3390,6 +3565,9 @@ weston_output_schedule_repaint(struct weston_output *output) compositor->state == WESTON_COMPOSITOR_OFFSCREEN) return; + if (output->power_state == WESTON_OUTPUT_POWER_FORCED_OFF) + return; + if (!output->repaint_needed) TL_POINT(compositor, "core_repaint_req", TLP_OUTPUT(output), TLP_END); @@ -3422,6 +3600,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) { @@ -3434,22 +3622,33 @@ 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) { + if (sx != 0 || sy != 0) { + wl_resource_post_error(resource, + WL_SURFACE_ERROR_INVALID_OFFSET, + "Can't attach with an offset"); + return; + } + } else { + surface->pending.sx = sx; + surface->pending.sy = sy; + } + /* Attach, attach, without commit in between does not send * wl_buffer.release. */ weston_surface_state_set_buffer(&surface->pending, buffer); - surface->pending.sx = sx; - surface->pending.sy = sy; surface->pending.newly_attached = 1; } @@ -3577,12 +3776,13 @@ weston_surface_commit_subsurface_order(struct weston_surface *surface) } } -static void +WESTON_EXPORT_FOR_TESTS void weston_surface_build_buffer_matrix(const struct weston_surface *surface, struct weston_matrix *matrix) { const struct weston_buffer_viewport *vp = &surface->buffer_viewport; double src_width, src_height, dest_width, dest_height; + struct weston_matrix transform_matrix; weston_matrix_init(matrix); @@ -3613,44 +3813,13 @@ weston_surface_build_buffer_matrix(const struct weston_surface *surface, wl_fixed_to_double(vp->buffer.src_y), 0); - switch (vp->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(matrix, -1, 1, 1); - weston_matrix_translate(matrix, - surface->width_from_buffer, 0, 0); - break; - } - - switch (vp->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(matrix, 0, -1); - weston_matrix_translate(matrix, - 0, surface->width_from_buffer, 0); - break; - case WL_OUTPUT_TRANSFORM_180: - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - weston_matrix_rotate_xy(matrix, -1, 0); - weston_matrix_translate(matrix, - surface->width_from_buffer, - surface->height_from_buffer, 0); - break; - case WL_OUTPUT_TRANSFORM_270: - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - weston_matrix_rotate_xy(matrix, 0, 1); - weston_matrix_translate(matrix, - surface->height_from_buffer, 0, 0); - break; - } - - weston_matrix_scale(matrix, vp->buffer.scale, vp->buffer.scale, 1); + weston_matrix_init_transform(&transform_matrix, + vp->buffer.transform, + 0, 0, + surface->width_from_buffer, + surface->height_from_buffer, + vp->buffer.scale); + weston_matrix_multiply(matrix, &transform_matrix); } /** @@ -3677,7 +3846,7 @@ weston_surface_is_pending_viewport_source_valid( if (vp->buffer.src_width == wl_fixed_from_int(-1)) return true; - if (pend->newly_attached) { + if (pend->newly_attached || pend->buffer_viewport.changed) { if (pend->buffer) { convert_size_by_transform_scale(&width_from_buffer, &height_from_buffer, @@ -3832,10 +4001,25 @@ 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) { + /* It's possible that this surface's buffer and transform changed + * at the same time in such a way that its size remains the same. + * + * That means we can't depend on view_geometry_dirty() from a + * size update to invalidate the paint node data in all relevant + * cases, so just smash it here. + */ + weston_surface_dirty_paint_nodes(surface); + 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); + if (surface->committed) { + struct weston_coord_surface new_origin; + + new_origin = weston_coord_surface(state->sx, + state->sy, + surface); + surface->committed(surface, new_origin); + } } state->sx = 0; @@ -3898,6 +4082,9 @@ weston_surface_commit_state(struct weston_surface *surface, weston_surface_set_desired_protection(surface, state->desired_protection); wl_signal_emit(&surface->commit_signal, surface); + + /* Surface is fully unmapped now */ + surface->is_unmapping = false; } static void @@ -3955,14 +4142,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, @@ -3988,12 +4168,12 @@ surface_commit(struct wl_client *client, struct wl_resource *resource) return; } - weston_surface_commit(surface); - wl_list_for_each(sub, &surface->subsurface_list, parent_link) { if (sub->surface != surface) weston_subsurface_parent_commit(sub, 0); } + + weston_surface_commit(surface); } static void @@ -4035,6 +4215,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, @@ -4045,7 +4237,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 @@ -4072,7 +4265,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); } @@ -4156,7 +4349,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); @@ -4193,7 +4387,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 */ @@ -4219,6 +4416,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); @@ -4301,13 +4501,14 @@ weston_subsurface_parent_commit(struct weston_subsurface *sub, int parent_is_synchronized) { struct weston_view *view; - if (sub->position.set) { + + if (sub->position.changed) { wl_list_for_each(view, &sub->surface->views, surface_link) - weston_view_set_position(view, - sub->position.x, - sub->position.y); + weston_view_set_rel_position(view, + sub->position.offset.c.x, + sub->position.offset.c.y); - sub->position.set = 0; + sub->position.changed = false; } if (parent_is_synchronized || sub->synchronized) @@ -4321,35 +4522,44 @@ subsurface_get_label(struct weston_surface *surface, char *buf, size_t len) } static void -subsurface_committed(struct weston_surface *surface, int32_t dx, int32_t dy) +subsurface_committed(struct weston_surface *surface, + struct weston_coord_surface new_origin) { struct weston_view *view; - wl_list_for_each(view, &surface->views, surface_link) - weston_view_set_position(view, - view->geometry.x + dx, - view->geometry.y + dy); + wl_list_for_each(view, &surface->views, surface_link) { + struct weston_coord_surface tmp = new_origin; + + if (!view->geometry.parent) { + weston_log_paced(&view->subsurface_parent_log_pacer, + 1, 0, "Client attempted to commit on a " + "subsurface without a parent surface\n"); + continue; + } + tmp.c = weston_coord_add(tmp.c, + view->geometry.pos_offset); + weston_view_set_rel_position(view, tmp.c.x, tmp.c.y); + } /* No need to check parent mappedness, because if parent is not * 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 * @@ -4421,8 +4631,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. @@ -4431,15 +4640,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 @@ -4461,8 +4670,8 @@ weston_surface_get_bounding_box(struct weston_surface *surface) wl_list_for_each(subsurface, &surface->subsurface_list, parent_link) pixman_region32_union_rect(®ion, ®ion, - subsurface->position.x, - subsurface->position.y, + subsurface->position.offset.c.x, + subsurface->position.offset.c.y, subsurface->surface->width, subsurface->surface->height); @@ -4560,9 +4769,8 @@ subsurface_set_position(struct wl_client *client, if (!sub) return; - sub->position.x = x; - sub->position.y = y; - sub->position.set = 1; + sub->position.offset = weston_coord_surface(x, y, sub->surface); + sub->position.changed = true; } static struct weston_subsurface * @@ -4775,7 +4983,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; @@ -4820,6 +5029,8 @@ weston_subsurface_create(uint32_t id, struct weston_surface *surface, return NULL; } + sub->position.offset = weston_coord_surface(0, 0, surface); + wl_resource_set_implementation(sub->resource, &subsurface_implementation, sub, subsurface_resource_destroy); @@ -4952,10 +5163,13 @@ weston_compositor_dpms(struct weston_compositor *compositor, enum dpms_enum state) { struct weston_output *output; + enum dpms_enum dpms; - wl_list_for_each(output, &compositor->output_list, link) + wl_list_for_each(output, &compositor->output_list, link) { + dpms = output->power_state == WESTON_OUTPUT_POWER_FORCED_OFF ? WESTON_DPMS_OFF : state; if (output->set_dpms) - output->set_dpms(output, state); + output->set_dpms(output, dpms); + } } /** Restores the compositor to active status @@ -5067,14 +5281,12 @@ idle_handler(void *data) } WL_EXPORT void -weston_plane_init(struct weston_plane *plane, - struct weston_compositor *ec, - int32_t x, int32_t y) +weston_plane_init(struct weston_plane *plane, struct weston_compositor *ec) { pixman_region32_init(&plane->damage); pixman_region32_init(&plane->clip); - plane->x = x; - plane->y = y; + plane->x = 0; + plane->y = 0; plane->compositor = ec; /* Init the link so that the call to wl_list_remove() when releasing @@ -5148,11 +5360,16 @@ bind_output(struct wl_client *client, return; } - wl_list_insert(&head->resource_list, wl_resource_get_link(resource)); + if (!output) { + wl_resource_set_implementation(resource, &output_interface, + NULL, NULL); + return; + } + + wl_list_insert(&head->resource_list, wl_resource_get_link(resource)); wl_resource_set_implementation(resource, &output_interface, head, unbind_resource); - assert(output); wl_output_send_geometry(resource, output->x, output->y, @@ -5173,6 +5390,12 @@ bind_output(struct wl_client *client, mode->refresh); } + if (version >= WL_OUTPUT_NAME_SINCE_VERSION) + wl_output_send_name(resource, head->name); + + if (version >= WL_OUTPUT_DESCRIPTION_SINCE_VERSION) + wl_output_send_description(resource, head->model); + if (version >= WL_OUTPUT_DONE_SINCE_VERSION) wl_output_send_done(resource); } @@ -5181,10 +5404,68 @@ static void weston_head_add_global(struct weston_head *head) { head->global = wl_global_create(head->compositor->wl_display, - &wl_output_interface, 3, + &wl_output_interface, 4, head, bind_output); } +struct weston_destroy_global_data { + struct wl_global *global; + struct wl_event_source *event_source; + struct wl_listener destroy_listener; +}; + +static void +weston_destroy_global(struct weston_destroy_global_data *data) +{ + wl_list_remove(&data->destroy_listener.link); + wl_global_destroy(data->global); + wl_event_source_remove(data->event_source); + free(data); +} + +static void +global_compositor_destroy_handler(struct wl_listener *listener, void *_data) +{ + struct weston_destroy_global_data *data = + wl_container_of(listener, data, destroy_listener); + + weston_destroy_global(data); +} + +static int +weston_global_handle_timer_event(void *data) +{ + weston_destroy_global(data); + return 0; +} + +static void +weston_global_destroy_save(struct weston_compositor *compositor, + struct wl_global *global) +{ + struct weston_destroy_global_data *data; + struct wl_event_loop *loop; + + if (compositor->state == WESTON_COMPOSITOR_OFFSCREEN) { + wl_global_destroy(global); + return; + } + + wl_global_remove(global); + + data = xzalloc(sizeof *data); + data->global = global; + + loop = wl_display_get_event_loop(compositor->wl_display); + data->event_source = + wl_event_loop_add_timer(loop, weston_global_handle_timer_event, + data); + wl_event_source_timer_update(data->event_source, 5000); + + data->destroy_listener.notify = global_compositor_destroy_handler; + wl_signal_add(&compositor->destroy_signal, &data->destroy_listener); +} + /** Remove the global wl_output protocol object * * \param head The head whose global to remove. @@ -5197,7 +5478,7 @@ weston_head_remove_global(struct weston_head *head) struct wl_resource *resource, *tmp; if (head->global) - wl_global_destroy(head->global); + weston_global_destroy_save(head->compositor, head->global); head->global = NULL; wl_resource_for_each_safe(resource, tmp, &head->resource_list) { @@ -5259,6 +5540,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; } @@ -5458,6 +5740,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. @@ -5497,6 +5806,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); @@ -5582,7 +5893,23 @@ weston_head_release(struct weston_head *head) wl_list_remove(&head->compositor_link); } -static void +/** Propagate device information changes + * + * \param head The head that changed. + * + * The information about the connected display device, e.g. a monitor, may + * change without being disconnected in between. Changing information + * causes a call to the heads_changed hook. + * + * Normally this is handled automatically by the generic setters, but if + * a backend has + * specific head properties it may have to call this directly. + * + * \sa weston_head_reset_device_changed, weston_compositor_set_heads_changed_cb, + * weston_head_is_device_changed + * \ingroup head + */ +WL_EXPORT void weston_head_set_device_changed(struct weston_head *head) { head->device_changed = true; @@ -5784,31 +6111,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 @@ -6006,6 +6331,10 @@ 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, + struct weston_coord_global pos); + /* Move other outputs when one is resized so the space remains contiguous. */ static void weston_compositor_reflow_outputs(struct weston_compositor *compositor, @@ -6014,6 +6343,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; @@ -6024,98 +6356,45 @@ weston_compositor_reflow_outputs(struct weston_compositor *compositor, } if (start_resizing) { - weston_output_move(output, output->x + delta_width, output->y); - output->dirty = 1; + struct weston_coord_global pos; + + pos.c = weston_coord(output->x + delta_width, + output->y); + weston_output_set_position(output, pos); } } } -/** Transform a region in-place from global to output coordinates +/** Transform a region from global to output coordinates * + * \param dst The region transformed into output coordinates * \param output The output that defines the transformation. - * \param region The region to be transformed in-place. + * \param src The region to be transformed, in global coordinates. * * 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 */ WL_EXPORT void -weston_output_region_from_global(struct weston_output *output, - pixman_region32_t *region) +weston_region_global_to_output(pixman_region32_t *dst, + struct weston_output *output, + pixman_region32_t *src) { - 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); - } + weston_matrix_transform_region(dst, &output->matrix, src); } -static void +WESTON_EXPORT_FOR_TESTS 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: - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - weston_matrix_translate(&output->matrix, -output->width, 0, 0); - weston_matrix_scale(&output->matrix, -1, 1, 1); - break; - } - - switch (output->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_translate(&output->matrix, -output->width, 0, 0); - weston_matrix_rotate_xy(&output->matrix, 0, -1); - break; - case WL_OUTPUT_TRANSFORM_180: - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - weston_matrix_translate(&output->matrix, - -output->width, -output->height, 0); - weston_matrix_rotate_xy(&output->matrix, -1, 0); - break; - case WL_OUTPUT_TRANSFORM_270: - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - weston_matrix_translate(&output->matrix, 0, -output->height, 0); - weston_matrix_rotate_xy(&output->matrix, 0, 1); - break; - } - - if (output->current_scale != 1) - weston_matrix_scale(&output->matrix, - output->current_scale, - output->current_scale, 1); + weston_output_dirty_paint_nodes(output); - output->dirty = 0; + weston_matrix_init_transform(&output->matrix, output->transform, + output->x, output->y, + output->width, output->height, + output->current_scale); weston_matrix_invert(&output->inverse_matrix, &output->matrix); } @@ -6148,22 +6427,29 @@ 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, + struct weston_coord_global pos) { struct weston_head *head; struct wl_resource *resource; int ver; - output->move_x = x - output->x; - output->move_y = y - output->y; + if (!output->enabled) { + output->x = pos.c.x; + output->y = pos.c.y; + return; + } + + output->move_x = pos.c.x - output->x; + output->move_y = pos.c.y - output->y; if (output->move_x == 0 && output->move_y == 0) return; - weston_output_init_geometry(output, x, y); + weston_output_init_geometry(output, pos.c.x, pos.c.y); - output->dirty = 1; + weston_output_update_matrix(output); /* Move views on this output. */ wl_signal_emit(&output->compositor->output_moved_signal, output); @@ -6195,6 +6481,29 @@ weston_output_move(struct weston_output *output, int x, int y) } } +/** + * \ingroup output + */ +WL_EXPORT void +weston_output_move(struct weston_output *output, + struct weston_coord_global pos) +{ + /* 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, pos); +} + /** Signal that a pending output is taken into use. * * Removes the output from the pending list and adds it to the compositor's @@ -6245,13 +6554,12 @@ weston_compositor_add_output(struct weston_compositor *compositor, weston_view_geometry_dirty(view); } -/** Transform device coordinates into global coordinates +/** Create a weston_coord_global from a point and a weston_output * + * \param x x coordinate on the output + * \param y y coordinate on the output * \param output the weston_output object - * \param[in] device_x X coordinate in device units. - * \param[in] device_y Y coordinate in device units. - * \param[out] x X coordinate in the global space. - * \param[out] y Y coordinate in the global space. + * \return coordinate in global space corresponding to x, y on the output * * Transforms coordinates from the device coordinate space (physical pixel * units) to the global coordinate space (logical pixel units). This takes @@ -6260,68 +6568,119 @@ weston_compositor_add_output(struct weston_compositor *compositor, * \ingroup output * \internal */ -WL_EXPORT void -weston_output_transform_coordinate(struct weston_output *output, - double device_x, double device_y, - double *x, double *y) +WL_EXPORT struct weston_coord_global +weston_coord_global_from_output_point(double x, double y, + const struct weston_output *output) { - struct weston_vector p = { { - device_x, - device_y, - 0.0, - 1.0 } }; + struct weston_coord c; + struct weston_coord_global tmp; - weston_matrix_transform(&output->inverse_matrix, &p); - - *x = p.f[0] / p.f[3]; - *y = p.f[1] / p.f[3]; + c = weston_coord(x, y); + tmp.c = weston_matrix_transform_coord(&output->inverse_matrix, c); + return tmp; } -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 @@ -6366,6 +6725,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); @@ -6381,7 +6745,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); @@ -6397,6 +6761,8 @@ weston_compositor_remove_output(struct weston_output *output) wl_list_for_each(head, &output->head_list, output_link) weston_head_remove_global(head); + weston_output_capture_info_destroy(&output->capture_info); + compositor->output_id_pool &= ~(1u << output->id); output->id = 0xffffffff; /* invalid */ } @@ -6415,13 +6781,16 @@ WL_EXPORT void weston_output_set_scale(struct weston_output *output, int32_t scale) { - /* We can only set scale on a disabled output */ - assert(!output->enabled); + output->scale = scale; + if (!output->enabled) + return; - /* We only want to set scale once */ - assert(!output->scale); + if (output->current_scale == scale) + return; - output->scale = scale; + output->current_scale = scale; + weston_mode_switch_finish(output, false, true); + wl_signal_emit(&output->compositor->output_resized_signal, output); } /** Sets the output transform for a given output. @@ -6459,7 +6828,7 @@ weston_output_set_transform(struct weston_output *output, weston_output_init_geometry(output, output->x, output->y); - output->dirty = 1; + weston_output_update_matrix(output); /* Notify clients of the change for output transform. */ wl_list_for_each(head, &output->head_list, output_link) { @@ -6494,15 +6863,13 @@ weston_output_set_transform(struct weston_output *output, mid_y = output->y + output->height / 2; ev.mask = WESTON_POINTER_MOTION_ABS; - ev.x = wl_fixed_to_double(wl_fixed_from_int(mid_x)); - ev.y = wl_fixed_to_double(wl_fixed_from_int(mid_y)); - + ev.abs.c = weston_coord(mid_x, mid_y); wl_list_for_each(seat, &output->compositor->seat_list, link) { struct weston_pointer *pointer = weston_seat_get_pointer(seat); if (pointer && pixman_region32_contains_point(&old_region, - wl_fixed_to_int(pointer->x), - wl_fixed_to_int(pointer->y), + pointer->pos.c.x, + pointer->pos.c.y, NULL)) weston_pointer_move(pointer, &ev); } @@ -6535,23 +6902,167 @@ 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; return false; } - /* Remove outdated cached color transformations */ - wl_list_for_each(pnode, &output->paint_node_list, output_link) { - weston_surface_color_transform_fini(&pnode->surf_xform); - pnode->surf_xform_valid = false; + /* Remove outdated cached color transformations */ + wl_list_for_each(pnode, &output->paint_node_list, output_link) { + weston_surface_color_transform_fini(&pnode->surf_xform); + pnode->surf_xform_valid = false; + } + } + + weston_color_profile_unref(old); + + 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; +} + +WL_EXPORT void +weston_output_set_single_mode(struct weston_output *output, + struct weston_mode *target) +{ + struct weston_mode *iter, *local = NULL, *mode; + + wl_list_for_each(iter, &output->mode_list, link) { + assert(!local); + + if ((iter->width == target->width) && + (iter->height == target->height) && + (iter->refresh == target->refresh)) { + mode = iter; + goto out; + } else { + local = iter; } } + /* Make sure we create the new one before freeing the old one + * because some mode switch code uses pointer comparisons! If + * we freed the old mode first, malloc could theoretically give + * us back the same pointer. + */ + mode = xzalloc(sizeof *mode); + mode->width = target->width; + mode->height = target->height; + mode->refresh = target->refresh; + mode->flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; + wl_list_insert(&output->mode_list, &mode->link); +out: + output->current_mode = mode; + output->native_mode = mode; - weston_color_profile_unref(old); - - return true; + if (local) { + wl_list_remove(&local->link); + free(local); + } } /** Initializes a weston_output object with enough data so @@ -6581,8 +7092,10 @@ 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; + output->power_state = WESTON_OUTPUT_POWER_NORMAL; wl_list_init(&output->head_list); @@ -6646,6 +7159,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 @@ -6654,8 +7206,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(). @@ -6684,11 +7236,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", @@ -6713,51 +7262,54 @@ 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; wl_signal_init(&output->frame_signal); 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_damage(output); + 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; wl_list_init(&output->animation_list); wl_list_init(&output->feedback_list); wl_list_init(&output->paint_node_list); wl_list_init(&output->paint_node_z_order_list); - if (!weston_output_set_color_transforms(output)) + weston_output_update_matrix(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; + output->capture_info = weston_output_capture_info_create(); + assert(output->capture_info); + /* Enable the output (set up the crtc or create a * window representing the output, set up the * renderer, etc) */ 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); + weston_output_capture_info_destroy(&output->capture_info); return -1; } weston_compositor_add_output(output->compositor, output); + weston_output_damage(output); head_names = weston_output_create_heads_string(output); weston_log("Output '%s' enabled with head(s) %s\n", @@ -6906,13 +7458,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); @@ -6948,13 +7498,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. @@ -6963,9 +7515,12 @@ 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) { - assert(compositor->backend->create_output); + struct weston_output *output; + + assert(head->backend->create_output); if (weston_compositor_find_output_by_name(compositor, name)) { weston_log("Warning: attempted to create an output with a " @@ -6973,34 +7528,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 = head->backend->create_output(head->backend, 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; } @@ -7063,6 +7595,95 @@ 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; +} + +/* Set the forced-power state of output + * + * \param output The output to set power state. + * \param state The power state to set for output. + * + * Set the forced-power state of output, then update DPMS mode for output + * when compositor is active. + * + * \ingroup output + */ +static void +weston_output_force_power(struct weston_output *output, + enum weston_output_power_state power) +{ + enum dpms_enum dpms; + + output->power_state = power; + + if (output->compositor->state == WESTON_COMPOSITOR_SLEEPING || + output->compositor->state == WESTON_COMPOSITOR_OFFSCREEN) + return; + + if (!output->set_dpms || !output->enabled) + return; + + dpms = (power == WESTON_OUTPUT_POWER_NORMAL) ? WESTON_DPMS_ON : WESTON_DPMS_OFF; + output->set_dpms(output, dpms); +} + +/* Set the power state of output to normal mode + * + * \param output The output to set on. + * + * This function will make the forced-off power of the output to normal state. + * In case when compositor is sleeping or offscreen, the power state will be + * applied once the compositor wakes up. + * + * \ingroup output + */ +WL_EXPORT void +weston_output_power_on(struct weston_output *output) +{ + weston_output_force_power(output, WESTON_OUTPUT_POWER_NORMAL); +} + +/* Force the power state of output to off mode + * + * \param output The output to set off. + * + * This function ceases rendering on a given output and will power it off + * via DPMS when compositor is active. Otherwise the output is forced off + * when the compositor wakes up. + * + * \ingroup output + */ +WL_EXPORT void +weston_output_power_off(struct weston_output *output) +{ + weston_output_force_power(output, WESTON_OUTPUT_POWER_FORCED_OFF); +} + static void xdg_output_unlist(struct wl_resource *resource) { @@ -7415,6 +8036,115 @@ compositor_bind(struct wl_client *client, compositor, NULL); } +static void +set_presentation_hint(struct wl_client *client, struct wl_resource *resource, uint32_t hint) +{ + struct weston_tearing_control *tc = wl_resource_get_user_data(resource); + struct weston_surface *surf = tc->surface; + + if (hint == WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC) + surf->tear_control->may_tear = true; + else + surf->tear_control->may_tear = false; +} + +static void +destroy_tearing_control(struct wl_client *client, struct wl_resource *res) +{ + struct weston_tearing_control *tc = wl_resource_get_user_data(res); + struct weston_surface *surf = tc->surface; + + if (surf) + surf->tear_control = NULL; + + wl_resource_destroy(res); +} + +static const struct wp_tearing_control_v1_interface tearing_interface = { + set_presentation_hint, + destroy_tearing_control, +}; + +static void +destroy_tearing_controller(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +free_tearing_control(struct wl_resource *res) +{ + struct weston_tearing_control *tc = wl_resource_get_user_data(res); + struct weston_surface *surf = tc->surface; + + if (surf) + surf->tear_control = NULL; + + free(tc); +} + +static void +get_tearing_control(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface_resource) +{ + struct wl_resource *ctl_res; + struct weston_tearing_control *control; + struct weston_surface *surface; + uint32_t version; + + surface = wl_resource_get_user_data(surface_resource); + if (surface->tear_control) { + wl_resource_post_error(resource, + WP_TEARING_CONTROL_MANAGER_V1_ERROR_TEARING_CONTROL_EXISTS, + "Surface already has a tearing controller"); + return; + } + + version = wl_resource_get_version(resource); + ctl_res = wl_resource_create(client, + &wp_tearing_control_v1_interface, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + control = xzalloc(sizeof *control); + control->may_tear = false; + control->surface = surface; + surface->tear_control = control; + wl_resource_set_implementation(ctl_res, &tearing_interface, + control, free_tearing_control); +} + +static const struct wp_tearing_control_manager_v1_interface +tearing_control_manager_implementation = { + destroy_tearing_controller, + get_tearing_control, +}; + +static void +bind_tearing_controller(struct wl_client *client, void *data, + uint32_t version, uint32_t id) +{ + struct weston_compositor *compositor = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, + &wp_tearing_control_manager_v1_interface, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &tearing_control_manager_implementation, + compositor, NULL); +} + static const char * output_repaint_status_text(struct weston_output *output) { @@ -7437,43 +8167,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 @@ -7489,7 +8236,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); } @@ -7501,6 +8248,17 @@ 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"); + if (wl_list_empty(&view->layer_link.link)) { + if (!get_view_layer(view)) + fprintf(fp, "\t[view is not part of any layer]\n"); + else + fprintf(fp, "\t[view is under parent view layer]\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); @@ -7743,6 +8501,7 @@ weston_compositor_create(struct wl_display *display, wl_signal_init(&ec->heads_changed_signal); wl_signal_init(&ec->output_heads_changed_signal); wl_signal_init(&ec->session_signal); + wl_signal_init(&ec->output_capture.ask_auth); ec->session_active = true; ec->output_id_pool = 0; @@ -7754,7 +8513,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; @@ -7774,9 +8533,21 @@ 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 (!wl_global_create(ec->wl_display, + &wp_tearing_control_manager_v1_interface, 1, + ec, bind_tearing_controller)) + goto fail; + if (weston_input_init(ec) != 0) goto fail; + weston_compositor_install_capture_protocol(ec); + wl_list_init(&ec->view_list); wl_list_init(&ec->plane_list); wl_list_init(&ec->layer_list); @@ -7788,12 +8559,14 @@ weston_compositor_create(struct wl_display *display, wl_list_init(&ec->modifier_binding_list); wl_list_init(&ec->button_binding_list); wl_list_init(&ec->touch_binding_list); + wl_list_init(&ec->tablet_tool_binding_list); wl_list_init(&ec->axis_binding_list); wl_list_init(&ec->debug_binding_list); + wl_list_init(&ec->tablet_manager_resource_list); wl_list_init(&ec->plugin_api_list); - weston_plane_init(&ec->primary_plane, ec, 0, 0); + weston_plane_init(&ec->primary_plane, ec); weston_compositor_stack_plane(ec, &ec->primary_plane, NULL); wl_data_device_manager_init(ec->wl_display); @@ -7825,6 +8598,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: @@ -7843,6 +8620,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); @@ -7866,6 +8646,7 @@ weston_compositor_shutdown(struct weston_compositor *ec) weston_binding_list_destroy_all(&ec->touch_binding_list); weston_binding_list_destroy_all(&ec->axis_binding_list); weston_binding_list_destroy_all(&ec->debug_binding_list); + weston_binding_list_destroy_all(&ec->tablet_tool_binding_list); weston_plane_release(&ec->primary_plane); @@ -7967,10 +8748,9 @@ weston_compositor_set_presentation_clock_software( */ WL_EXPORT void weston_compositor_read_presentation_clock( - const struct weston_compositor *compositor, + struct weston_compositor *compositor, struct timespec *ts) { - static bool warned; int ret; ret = clock_gettime(compositor->presentation_clock, ts); @@ -7978,12 +8758,12 @@ weston_compositor_read_presentation_clock( ts->tv_sec = 0; ts->tv_nsec = 0; - if (!warned) - weston_log("Error: failure to read " - "the presentation clock %#x: '%s' (%d)\n", - compositor->presentation_clock, - strerror(errno), errno); - warned = true; + weston_log_paced(&compositor->presentation_clock_failure_pacer, + 1, 0, + "Error: failure to read " + "the presentation clock %#x: '%s' (%d)\n", + compositor->presentation_clock, + strerror(errno), errno); } } @@ -8008,8 +8788,15 @@ weston_compositor_import_dmabuf(struct weston_compositor *compositor, struct linux_dmabuf_buffer *buffer) { struct weston_renderer *renderer; + struct weston_backend *backend; renderer = compositor->renderer; + backend = compositor->backend; + + /* first try backend import, if fail, fallback to render import */ + if (backend->import_dmabuf) + if(backend->import_dmabuf(compositor, buffer)) + return true; if (renderer->import_dmabuf == NULL) return false; @@ -8026,7 +8813,7 @@ weston_compositor_dmabuf_can_scanout(struct weston_compositor *compositor, if (backend->can_scanout_dmabuf == NULL) return false; - return backend->can_scanout_dmabuf(compositor, buffer); + return backend->can_scanout_dmabuf(backend, buffer); } WL_EXPORT void @@ -8088,8 +8875,23 @@ weston_module_path_from_env(const char *name, char *path, size_t path_len) return 0; } +/** A wrapper function to open and return the entry point of a shared library + * module + * + * This function loads the module and provides the caller with the entry point + * address which can be later used to execute shared library code. It can be + * used to load-up libweston modules but also other modules, specific to the + * compositor (i.e., weston). + * + * \param name the name of the shared library + * \param entrypoint the entry point of the shared library + * \param module_dir the path where to look for the shared library module + * \return the address of the module specified the entry point, or NULL otherwise + * + */ WL_EXPORT void * -weston_load_module(const char *name, const char *entrypoint) +weston_load_module(const char *name, const char *entrypoint, + const char *module_dir) { char path[PATH_MAX]; void *module, *init; @@ -8102,7 +8904,7 @@ weston_load_module(const char *name, const char *entrypoint) len = weston_module_path_from_env(name, path, sizeof path); if (len == 0) len = snprintf(path, sizeof path, "%s/%s", - LIBWESTON_MODULEDIR, name); + module_dir, name); } else { len = snprintf(path, sizeof path, "%s", name); } @@ -8190,7 +8992,7 @@ weston_compositor_destroy(struct weston_compositor *compositor) weston_compositor_xkb_destroy(compositor); if (compositor->backend) - compositor->backend->destroy(compositor); + compositor->backend->destroy(compositor->backend); /* The backend is responsible for destroying the heads. */ assert(wl_list_empty(&compositor->head_list)); @@ -8206,6 +9008,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); @@ -8245,9 +9050,10 @@ 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_PIPEWIRE] = "pipewire-backend.so", [WESTON_BACKEND_RDP] = "rdp-backend.so", + [WESTON_BACKEND_VNC] = "vnc-backend.so", [WESTON_BACKEND_WAYLAND] = "wayland-backend.so", [WESTON_BACKEND_X11] = "x11-backend.so", }; @@ -8282,7 +9088,9 @@ weston_compositor_load_backend(struct weston_compositor *compositor, if (backend >= ARRAY_LENGTH(backend_map)) return -1; - backend_init = weston_load_module(backend_map[backend], "weston_backend_init"); + backend_init = weston_load_module(backend_map[backend], + "weston_backend_init", + LIBWESTON_MODULEDIR); if (!backend_init) return -1; @@ -8307,6 +9115,46 @@ weston_compositor_load_backend(struct weston_compositor *compositor, return 0; } +WL_EXPORT int +weston_compositor_init_renderer(struct weston_compositor *compositor, + enum weston_renderer_type renderer_type, + const struct weston_renderer_options *options) +{ + const struct gl_renderer_interface *gl_renderer; + const struct gl_renderer_display_options *gl_options; + int ret; + + switch (renderer_type) { + case WESTON_RENDERER_GL: + gl_renderer = weston_load_module("gl-renderer.so", + "gl_renderer_interface", + LIBWESTON_MODULEDIR); + if (!gl_renderer) + return -1; + + gl_options = container_of(options, + struct gl_renderer_display_options, + base); + ret = gl_renderer->display_create(compositor, gl_options); + if (ret < 0) + return ret; + + compositor->renderer->gl = gl_renderer; + weston_log("Using GL renderer\n"); + break; + case WESTON_RENDERER_PIXMAN: + ret = pixman_renderer_init(compositor); + if (ret < 0) + return ret; + weston_log("Using Pixman renderer\n"); + break; + default: + ret = -1; + } + + return ret; +} + /** weston_compositor_load_xwayland * \ingroup compositor */ @@ -8315,7 +9163,9 @@ weston_compositor_load_xwayland(struct weston_compositor *compositor) { int (*module_init)(struct weston_compositor *ec); - module_init = weston_load_module("xwayland.so", "weston_module_init"); + module_init = weston_load_module("xwayland.so", + "weston_module_init", + LIBWESTON_MODULEDIR); if (!module_init) return -1; if (module_init(compositor) < 0) @@ -8343,7 +9193,9 @@ weston_compositor_load_color_manager(struct weston_compositor *compositor) return -1; } - cm_create = weston_load_module("color-lcms.so", "weston_color_manager_create"); + cm_create = weston_load_module("color-lcms.so", + "weston_color_manager_create", + LIBWESTON_MODULEDIR); if (!cm_create) { weston_log("Error: Could not load color-lcms.so.\n"); return -1; @@ -8420,3 +9272,49 @@ weston_output_disable_planes_decr(struct weston_output *output) weston_schedule_surface_protection_update(output->compositor); } + +WL_EXPORT struct weston_renderbuffer * +weston_renderbuffer_ref(struct weston_renderbuffer *renderbuffer) +{ + renderbuffer->refcount++; + + return renderbuffer; +} + +WL_EXPORT void +weston_renderbuffer_unref(struct weston_renderbuffer *renderbuffer) +{ + assert(renderbuffer->refcount > 0); + + if (--renderbuffer->refcount > 0) + return; + + renderbuffer->destroy(renderbuffer); +} + +/** Tell the renderer that the target framebuffer size has changed + * + * \param output The output that was resized. + * \param fb_size The framebuffer size, including output decorations. + * \param area The composited area inside the framebuffer, excluding + * decorations. This can also be NULL, which means the whole fb_size is + * the composited area. + */ +WL_EXPORT void +weston_renderer_resize_output(struct weston_output *output, + const struct weston_size *fb_size, + const struct weston_geometry *area) +{ + struct weston_renderer *r = output->compositor->renderer; + struct weston_geometry def = { + .x = 0, + .y = 0, + .width = fb_size->width, + .height = fb_size->height + }; + + if (!r->resize_output(output, fb_size, area ?: &def)) { + weston_log("Error: Resizing output '%s' failed.\n", + output->name); + } +} diff --git a/libweston/data-device.c b/libweston/data-device.c index 6ac6f65fc..9e5030105 100644 --- a/libweston/data-device.c +++ b/libweston/data-device.c @@ -46,7 +46,7 @@ struct weston_drag { struct wl_listener focus_listener; struct weston_view *icon; struct wl_listener icon_destroy_listener; - int32_t dx, dy; + struct weston_coord_surface offset; struct weston_keyboard_grab keyboard_grab; }; @@ -412,7 +412,7 @@ drag_surface_configure(struct weston_drag *drag, struct weston_pointer *pointer, struct weston_touch *touch, struct weston_surface *es, - int32_t sx, int32_t sy) + struct weston_coord_surface new_origin) { struct weston_layer_entry *list; float fx, fy; @@ -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,21 +431,22 @@ 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; } - drag->dx += sx; - drag->dy += sy; + assert(drag->offset.coordinate_space_id && + drag->offset.coordinate_space_id == new_origin.coordinate_space_id); + drag->offset.c = weston_coord_add(drag->offset.c, new_origin.c); /* init to 0 for avoiding a compile warning */ fx = fy = 0; if (pointer) { - fx = wl_fixed_to_double(pointer->x) + drag->dx; - fy = wl_fixed_to_double(pointer->y) + drag->dy; + fx = pointer->pos.c.x + drag->offset.c.x; + fy = pointer->pos.c.y + drag->offset.c.y; } else if (touch) { - fx = wl_fixed_to_double(touch->grab_x) + drag->dx; - fy = wl_fixed_to_double(touch->grab_y) + drag->dy; + fx = wl_fixed_to_double(touch->grab_x) + drag->offset.c.x; + fy = wl_fixed_to_double(touch->grab_y) + drag->offset.c.y; } weston_view_set_position(drag->icon, fx, fy); } @@ -459,14 +460,14 @@ pointer_drag_surface_get_label(struct weston_surface *surface, static void pointer_drag_surface_committed(struct weston_surface *es, - int32_t sx, int32_t sy) + struct weston_coord_surface new_origin) { struct weston_pointer_drag *drag = es->committed_private; struct weston_pointer *pointer = drag->grab.pointer; assert(es->committed == pointer_drag_surface_committed); - drag_surface_configure(&drag->base, pointer, NULL, es, sx, sy); + drag_surface_configure(&drag->base, pointer, NULL, es, new_origin); } static int @@ -477,14 +478,15 @@ touch_drag_surface_get_label(struct weston_surface *surface, } static void -touch_drag_surface_committed(struct weston_surface *es, int32_t sx, int32_t sy) +touch_drag_surface_committed(struct weston_surface *es, + struct weston_coord_surface new_origin) { struct weston_touch_drag *drag = es->committed_private; struct weston_touch *touch = drag->grab.touch; assert(es->committed == touch_drag_surface_committed); - drag_surface_configure(&drag->base, NULL, touch, es, sx, sy); + drag_surface_configure(&drag->base, NULL, touch, es, new_origin); } static void @@ -496,30 +498,39 @@ destroy_drag_focus(struct wl_listener *listener, void *data) drag->focus_resource = NULL; } +static void +weston_drag_clear_focus(struct weston_drag *drag) +{ + if (drag->focus_resource) { + wl_data_device_send_leave(drag->focus_resource); + wl_list_remove(&drag->focus_listener.link); + drag->focus_resource = NULL; + drag->focus = NULL; + } +} + static void weston_drag_set_focus(struct weston_drag *drag, struct weston_seat *seat, struct weston_view *view, - wl_fixed_t sx, wl_fixed_t sy) + struct weston_coord_surface surf_pos) { struct wl_resource *resource, *offer_resource = NULL; struct wl_display *display = seat->compositor->wl_display; struct weston_data_offer *offer; uint32_t serial; - if (drag->focus && view && drag->focus->surface == view->surface) { + assert(view); + assert(surf_pos.coordinate_space_id == view->surface); + + if (drag->focus && drag->focus->surface == view->surface) { drag->focus = view; return; } - if (drag->focus_resource) { - wl_data_device_send_leave(drag->focus_resource); - wl_list_remove(&drag->focus_listener.link); - drag->focus_resource = NULL; - drag->focus = NULL; - } + weston_drag_clear_focus(drag); - if (!view || !view->surface->resource) + if (!view->surface->resource) return; if (!drag->data_source && @@ -559,7 +570,9 @@ weston_drag_set_focus(struct weston_drag *drag, } wl_data_device_send_enter(resource, serial, view->surface->resource, - sx, sy, offer_resource); + wl_fixed_from_double(surf_pos.c.x), + wl_fixed_from_double(surf_pos.c.y), + offer_resource); drag->focus = view; drag->focus_listener.notify = destroy_drag_focus; @@ -567,20 +580,33 @@ weston_drag_set_focus(struct weston_drag *drag, drag->focus_resource = resource; } +static void +drag_grab_focus_internal(struct weston_drag *drag, struct weston_seat *seat, + struct weston_coord_global pos) +{ + struct weston_view *view; + + view = weston_compositor_pick_view(seat->compositor, pos); + if (drag->focus == view) + return; + + if (view) { + struct weston_coord_surface surf_pos; + + surf_pos = weston_coord_global_to_surface(view, pos); + weston_drag_set_focus(drag, seat, view, surf_pos); + } else + weston_drag_clear_focus(drag); +} + static void drag_grab_focus(struct weston_pointer_grab *grab) { struct weston_pointer_drag *drag = container_of(grab, struct weston_pointer_drag, grab); struct weston_pointer *pointer = grab->pointer; - struct weston_view *view; - wl_fixed_t sx, sy; - view = weston_compositor_pick_view(pointer->seat->compositor, - pointer->x, pointer->y, - &sx, &sy); - if (drag->base.focus != view) - weston_drag_set_focus(&drag->base, pointer->seat, view, sx, sy); + drag_grab_focus_internal(&drag->base, pointer->seat, pointer->pos); } static void @@ -592,25 +618,27 @@ drag_grab_motion(struct weston_pointer_grab *grab, container_of(grab, struct weston_pointer_drag, grab); struct weston_pointer *pointer = drag->grab.pointer; float fx, fy; - wl_fixed_t sx, sy; uint32_t msecs; weston_pointer_move(pointer, event); if (drag->base.icon) { - fx = wl_fixed_to_double(pointer->x) + drag->base.dx; - fy = wl_fixed_to_double(pointer->y) + drag->base.dy; + fx = pointer->pos.c.x + drag->base.offset.c.x; + fy = pointer->pos.c.y + drag->base.offset.c.y; weston_view_set_position(drag->base.icon, fx, fy); weston_view_schedule_repaint(drag->base.icon); } if (drag->base.focus_resource) { + struct weston_coord_surface surf_pos; + msecs = timespec_to_msec(time); - weston_view_from_global_fixed(drag->base.focus, - pointer->x, pointer->y, - &sx, &sy); + surf_pos = weston_coord_global_to_surface(drag->base.focus, + pointer->pos); - wl_data_device_send_motion(drag->base.focus_resource, msecs, sx, sy); + wl_data_device_send_motion(drag->base.focus_resource, msecs, + wl_fixed_from_double(surf_pos.c.x), + wl_fixed_from_double(surf_pos.c.y)); } } @@ -629,7 +657,7 @@ data_device_end_drag_grab(struct weston_drag *drag, weston_view_destroy(drag->icon); } - weston_drag_set_focus(drag, seat, NULL, 0, 0); + weston_drag_clear_focus(drag); } static void @@ -770,15 +798,10 @@ static void drag_grab_touch_focus(struct weston_touch_drag *drag) { struct weston_touch *touch = drag->grab.touch; - struct weston_view *view; - wl_fixed_t view_x, view_y; - - view = weston_compositor_pick_view(touch->seat->compositor, - touch->grab_x, touch->grab_y, - &view_x, &view_y); - if (drag->base.focus != view) - weston_drag_set_focus(&drag->base, touch->seat, - view, view_x, view_y); + struct weston_coord_global pos; + + pos.c = weston_coord_from_fixed(touch->grab_x, touch->grab_y); + drag_grab_focus_internal(&drag->base, touch->seat, pos); } static void @@ -789,7 +812,6 @@ drag_grab_touch_motion(struct weston_touch_grab *grab, struct weston_touch_drag *touch_drag = container_of(grab, struct weston_touch_drag, grab); struct weston_touch *touch = grab->touch; - wl_fixed_t view_x, view_y; float fx, fy; uint32_t msecs; @@ -798,19 +820,24 @@ drag_grab_touch_motion(struct weston_touch_grab *grab, drag_grab_touch_focus(touch_drag); if (touch_drag->base.icon) { - fx = wl_fixed_to_double(touch->grab_x) + touch_drag->base.dx; - fy = wl_fixed_to_double(touch->grab_y) + touch_drag->base.dy; + fx = wl_fixed_to_double(touch->grab_x) + + touch_drag->base.offset.c.x; + fy = wl_fixed_to_double(touch->grab_y) + + touch_drag->base.offset.c.y; weston_view_set_position(touch_drag->base.icon, fx, fy); weston_view_schedule_repaint(touch_drag->base.icon); } if (touch_drag->base.focus_resource) { + struct weston_coord_global tmp_g; + struct weston_coord_surface c; + + tmp_g.c = weston_coord_from_fixed(touch->grab_x, touch->grab_y); msecs = timespec_to_msec(time); - weston_view_from_global_fixed(touch_drag->base.focus, - touch->grab_x, touch->grab_y, - &view_x, &view_y); + c = weston_coord_global_to_surface(touch_drag->base.focus, tmp_g); wl_data_device_send_motion(touch_drag->base.focus_resource, - msecs, view_x, view_y); + msecs, wl_fixed_from_double(c.c.x), + wl_fixed_from_double(c.c.y)); } } @@ -945,6 +972,7 @@ weston_pointer_start_drag(struct weston_pointer *pointer, icon->committed_private = drag; weston_surface_set_label_func(icon, pointer_drag_surface_get_label); + drag->base.offset = weston_coord_surface(0, 0, icon); } else { drag->base.icon = NULL; } @@ -1008,6 +1036,7 @@ weston_touch_start_drag(struct weston_touch *touch, icon->committed_private = drag; weston_surface_set_label_func(icon, touch_drag_surface_get_label); + drag->base.offset = weston_coord_surface(0, 0, icon); } else { drag->base.icon = NULL; } @@ -1057,13 +1086,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 +1268,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 new file mode 100644 index 000000000..f79bde6f8 --- /dev/null +++ b/libweston/desktop/client.c @@ -0,0 +1,224 @@ +/* + * 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" + +struct weston_desktop_client { + struct weston_desktop *desktop; + struct wl_client *client; + struct wl_resource *resource; + struct wl_list surface_list; + uint32_t ping_serial; + struct wl_event_source *ping_timer; + struct wl_signal destroy_signal; +}; + +void +weston_desktop_client_add_destroy_listener(struct weston_desktop_client *client, + struct wl_listener *listener) +{ + wl_signal_add(&client->destroy_signal, listener); +} + +void +weston_desktop_client_destroy(struct weston_desktop_client *client) +{ + struct wl_list *list = &client->surface_list; + struct wl_list *link, *tmp; + + assert(client->resource == NULL); + + wl_signal_emit(&client->destroy_signal, client); + + for (link = list->next, tmp = link->next; + link != list; + link = tmp, tmp = link->next) { + wl_list_remove(link); + wl_list_init(link); + } + + if (client->ping_timer != NULL) + wl_event_source_remove(client->ping_timer); + + free(client); +} + +static void +weston_desktop_client_handle_destroy(struct wl_resource *resource) +{ + struct weston_desktop_client *client = + wl_resource_get_user_data(resource); + + assert(client->resource == resource); + client->resource = NULL; + + weston_desktop_client_destroy(client); +} + +static int +weston_desktop_client_ping_timeout(void *user_data) +{ + struct weston_desktop_client *client = user_data; + + weston_desktop_api_ping_timeout(client->desktop, client); + return 1; +} + +struct weston_desktop_client * +weston_desktop_client_create(struct weston_desktop *desktop, + struct wl_client *wl_client, + wl_dispatcher_func_t dispatcher, + const struct wl_interface *interface, + const void *implementation, uint32_t version, + uint32_t id) +{ + struct weston_desktop_client *client; + struct wl_display *display; + struct wl_event_loop *loop; + + client = zalloc(sizeof(struct weston_desktop_client)); + if (client == NULL) { + if (wl_client != NULL) + wl_client_post_no_memory(wl_client); + return NULL; + } + + client->desktop = desktop; + client->client = wl_client; + + wl_list_init(&client->surface_list); + wl_signal_init(&client->destroy_signal); + + if (wl_client == NULL) + return client; + + client->resource = wl_resource_create(wl_client, interface, version, id); + if (client->resource == NULL) { + wl_client_post_no_memory(wl_client); + free(client); + return NULL; + } + + if (dispatcher != NULL) + wl_resource_set_dispatcher(client->resource, dispatcher, + weston_desktop_client_handle_destroy, client, + weston_desktop_client_handle_destroy); + else + wl_resource_set_implementation(client->resource, implementation, + client, + weston_desktop_client_handle_destroy); + + display = wl_client_get_display(client->client); + loop = wl_display_get_event_loop(display); + client->ping_timer = + wl_event_loop_add_timer(loop, + weston_desktop_client_ping_timeout, + client); + if (client->ping_timer == NULL) + wl_client_post_no_memory(wl_client); + + return client; +} + +struct weston_desktop * +weston_desktop_client_get_desktop(struct weston_desktop_client *client) +{ + return client->desktop; +} + +struct wl_resource * +weston_desktop_client_get_resource(struct weston_desktop_client *client) +{ + return client->resource; +} + +struct wl_list * +weston_desktop_client_get_surface_list(struct weston_desktop_client *client) +{ + return &client->surface_list; +} + +WL_EXPORT struct wl_client * +weston_desktop_client_get_client(struct weston_desktop_client *client) +{ + return client->client; +} + +WL_EXPORT void +weston_desktop_client_for_each_surface(struct weston_desktop_client *client, + void (*callback)(struct weston_desktop_surface *surface, void *user_data), + void *user_data) +{ + struct wl_list *list = &client->surface_list; + struct wl_list *link; + + for (link = list->next; link != list; link = link->next) + callback(weston_desktop_surface_from_client_link(link), + user_data); +} + +WL_EXPORT int +weston_desktop_client_ping(struct weston_desktop_client *client) +{ + struct weston_desktop_surface *surface = + weston_desktop_surface_from_client_link(client->surface_list.next); + const struct weston_desktop_surface_implementation *implementation = + weston_desktop_surface_get_implementation(surface); + void *implementation_data = + weston_desktop_surface_get_implementation_data(surface); + + if (implementation->ping == NULL) + return -1; + + if (client->ping_serial != 0) + return 1; + + client->ping_serial = + wl_display_next_serial(wl_client_get_display(client->client)); + wl_event_source_timer_update(client->ping_timer, 10000); + + implementation->ping(surface, client->ping_serial, implementation_data); + + return 0; +} + +void +weston_desktop_client_pong(struct weston_desktop_client *client, uint32_t serial) +{ + if (client->ping_serial != serial) + return; + + weston_desktop_api_pong(client->desktop, client); + + wl_event_source_timer_update(client->ping_timer, 0); + client->ping_serial = 0; +} diff --git a/libweston/desktop/internal.h b/libweston/desktop/internal.h new file mode 100644 index 000000000..c8d09272c --- /dev/null +++ b/libweston/desktop/internal.h @@ -0,0 +1,257 @@ +/* + * 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. + */ + +#ifndef WESTON_DESKTOP_INTERNAL_H +#define WESTON_DESKTOP_INTERNAL_H + +#include + +struct weston_desktop_seat; +struct weston_desktop_client; + +struct weston_compositor * +weston_desktop_get_compositor(struct weston_desktop *desktop); +struct wl_display * +weston_desktop_get_display(struct weston_desktop *desktop); + +void +weston_desktop_api_ping_timeout(struct weston_desktop *desktop, + struct weston_desktop_client *client); +void +weston_desktop_api_pong(struct weston_desktop *desktop, + struct weston_desktop_client *client); +void +weston_desktop_api_surface_added(struct weston_desktop *desktop, + struct weston_desktop_surface *surface); +void +weston_desktop_api_surface_removed(struct weston_desktop *desktop, + struct weston_desktop_surface *surface); +void +weston_desktop_api_committed(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + int32_t sx, int32_t sy); +void +weston_desktop_api_show_window_menu(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + struct weston_seat *seat, + int32_t x, int32_t y); +void +weston_desktop_api_set_parent(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + struct weston_desktop_surface *parent); +void +weston_desktop_api_move(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + struct weston_seat *seat, uint32_t serial); +void +weston_desktop_api_resize(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + struct weston_seat *seat, uint32_t serial, + enum weston_desktop_surface_edge edges); +void +weston_desktop_api_fullscreen_requested(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + bool fullscreen, + struct weston_output *output); +void +weston_desktop_api_maximized_requested(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + bool maximized); +void +weston_desktop_api_minimized_requested(struct weston_desktop *desktop, + struct weston_desktop_surface *surface); + +void +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); + +struct weston_desktop_surface_implementation { + void (*set_activated)(struct weston_desktop_surface *surface, + void *user_data, bool activated); + void (*set_fullscreen)(struct weston_desktop_surface *surface, + void *user_data, bool fullscreen); + void (*set_maximized)(struct weston_desktop_surface *surface, + void *user_data, bool maximized); + void (*set_resizing)(struct weston_desktop_surface *surface, + 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, + void *user_data); + void (*ping)(struct weston_desktop_surface *surface, uint32_t serial, + void *user_data); + void (*close)(struct weston_desktop_surface *surface, void *user_data); + + bool (*get_activated)(struct weston_desktop_surface *surface, + void *user_data); + bool (*get_fullscreen)(struct weston_desktop_surface *surface, + void *user_data); + bool (*get_maximized)(struct weston_desktop_surface *surface, + void *user_data); + bool (*get_resizing)(struct weston_desktop_surface *surface, + void *user_data); + bool (*get_pending_activated)(struct weston_desktop_surface *surface, + void *user_data); + bool (*get_pending_fullscreen)(struct weston_desktop_surface *surface, + void *user_data); + bool (*get_pending_maximized)(struct weston_desktop_surface *surface, + void *user_data); + bool (*get_pending_resizing)(struct weston_desktop_surface *surface, + void *user_data); + struct weston_size + (*get_max_size)(struct weston_desktop_surface *surface, + void *user_data); + struct weston_size + (*get_min_size)(struct weston_desktop_surface *surface, + void *user_data); + + void (*destroy)(struct weston_desktop_surface *surface, + void *user_data); +}; + +struct weston_desktop_client * +weston_desktop_client_create(struct weston_desktop *desktop, + struct wl_client *client, + wl_dispatcher_func_t dispatcher, + const struct wl_interface *interface, + const void *implementation, uint32_t version, + uint32_t id); +void +weston_desktop_client_destroy(struct weston_desktop_client *client); + +void +weston_desktop_client_add_destroy_listener(struct weston_desktop_client *client, + struct wl_listener *listener); +struct weston_desktop * +weston_desktop_client_get_desktop(struct weston_desktop_client *client); +struct wl_resource * +weston_desktop_client_get_resource(struct weston_desktop_client *client); +struct wl_list * +weston_desktop_client_get_surface_list(struct weston_desktop_client *client); + +void +weston_desktop_client_pong(struct weston_desktop_client *client, + uint32_t serial); + +struct weston_desktop_surface * +weston_desktop_surface_create(struct weston_desktop *desktop, + struct weston_desktop_client *client, + struct weston_surface *surface, + const struct weston_desktop_surface_implementation *implementation, + void *implementation_data); +void +weston_desktop_surface_destroy(struct weston_desktop_surface *surface); +void +weston_desktop_surface_resource_destroy(struct wl_resource *resource); +struct wl_resource * +weston_desktop_surface_add_resource(struct weston_desktop_surface *surface, + const struct wl_interface *interface, + const void *implementation, uint32_t id, + wl_resource_destroy_func_t destroy); +struct weston_desktop_surface * +weston_desktop_surface_from_grab_link(struct wl_list *grab_link); + +struct wl_list * +weston_desktop_surface_get_client_link(struct weston_desktop_surface *surface); +struct weston_desktop_surface * +weston_desktop_surface_from_client_link(struct wl_list *link); +bool +weston_desktop_surface_has_implementation(struct weston_desktop_surface *surface, + const struct weston_desktop_surface_implementation *implementation); +const struct weston_desktop_surface_implementation * +weston_desktop_surface_get_implementation(struct weston_desktop_surface *surface); +void * +weston_desktop_surface_get_implementation_data(struct weston_desktop_surface *surface); +bool +weston_desktop_surface_get_grab(struct weston_desktop_surface *surface); + +void +weston_desktop_surface_set_title(struct weston_desktop_surface *surface, + const char *title); +void +weston_desktop_surface_set_app_id(struct weston_desktop_surface *surface, + const char *app_id); +void +weston_desktop_surface_set_pid(struct weston_desktop_surface *surface, + pid_t pid); +void +weston_desktop_surface_set_geometry(struct weston_desktop_surface *surface, + struct weston_geometry geometry); +void +weston_desktop_surface_set_relative_to(struct weston_desktop_surface *surface, + struct weston_desktop_surface *parent, + int32_t x, int32_t y, bool use_geometry); +void +weston_desktop_surface_unset_relative_to(struct weston_desktop_surface *surface); +void +weston_desktop_surface_popup_grab(struct weston_desktop_surface *popup, + struct weston_desktop_surface *parent, + struct weston_desktop_seat *seat, + uint32_t serial); +void +weston_desktop_surface_popup_ungrab(struct weston_desktop_surface *popup, + struct weston_desktop_seat *seat); +void +weston_desktop_surface_popup_dismiss(struct weston_desktop_surface *surface); + +struct weston_desktop_surface * +weston_desktop_seat_popup_grab_get_topmost_surface(struct weston_desktop_seat *seat); +bool +weston_desktop_seat_popup_grab_start(struct weston_desktop_seat *seat, + struct weston_desktop_surface *parent, + struct wl_client *client, uint32_t serial); +void +weston_desktop_seat_popup_grab_add_surface(struct weston_desktop_seat *seat, + struct wl_list *link); +void +weston_desktop_seat_popup_grab_remove_surface(struct weston_desktop_seat *seat, + struct wl_list *link); + +void +weston_desktop_destroy_request(struct wl_client *client, + struct wl_resource *resource); +struct wl_global * +weston_desktop_xdg_wm_base_create(struct weston_desktop *desktop, + struct wl_display *display); +struct wl_global * +weston_desktop_xdg_shell_v6_create(struct weston_desktop *desktop, + struct wl_display *display); +void +weston_desktop_xwayland_init(struct weston_desktop *desktop); +void +weston_desktop_xwayland_fini(struct weston_desktop *desktop); + +#endif /* WESTON_DESKTOP_INTERNAL_H */ diff --git a/libweston/desktop/libweston-desktop.c b/libweston/desktop/libweston-desktop.c new file mode 100644 index 000000000..05240cfbb --- /dev/null +++ b/libweston/desktop/libweston-desktop.c @@ -0,0 +1,286 @@ +/* + * 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 "shared/helpers.h" + +#include +#include "internal.h" + + +struct weston_desktop { + struct weston_compositor *compositor; + struct weston_desktop_api api; + 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. */ +}; + +void +weston_desktop_destroy_request(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +WL_EXPORT struct weston_desktop * +weston_desktop_create(struct weston_compositor *compositor, + const struct weston_desktop_api *api, void *user_data) +{ + struct weston_desktop *desktop; + struct wl_display *display = compositor->wl_display; + + assert(api->surface_added); + assert(api->surface_removed); + + desktop = zalloc(sizeof(struct weston_desktop)); + desktop->compositor = compositor; + desktop->user_data = user_data; + + desktop->api.struct_size = + MIN(sizeof(struct weston_desktop_api), api->struct_size); + memcpy(&desktop->api, api, desktop->api.struct_size); + + desktop->xdg_wm_base = + weston_desktop_xdg_wm_base_create(desktop, display); + if (desktop->xdg_wm_base == NULL) { + weston_desktop_destroy(desktop); + return NULL; + } + + weston_desktop_xwayland_init(desktop); + + return desktop; +} + +WL_EXPORT void +weston_desktop_destroy(struct weston_desktop *desktop) +{ + if (desktop == NULL) + return; + + weston_desktop_xwayland_fini(desktop); + + if (desktop->xdg_shell_v6 != NULL) + wl_global_destroy(desktop->xdg_shell_v6); + if (desktop->xdg_wm_base != NULL) + wl_global_destroy(desktop->xdg_wm_base); + + free(desktop); +} + + +struct weston_compositor * +weston_desktop_get_compositor(struct weston_desktop *desktop) +{ + return desktop->compositor; +} + +struct wl_display * +weston_desktop_get_display(struct weston_desktop *desktop) +{ + return desktop->compositor->wl_display; +} + +void +weston_desktop_api_ping_timeout(struct weston_desktop *desktop, + struct weston_desktop_client *client) +{ + if (desktop->api.ping_timeout != NULL) + desktop->api.ping_timeout(client, desktop->user_data); +} + +void +weston_desktop_api_pong(struct weston_desktop *desktop, + struct weston_desktop_client *client) +{ + if (desktop->api.pong != NULL) + desktop->api.pong(client, desktop->user_data); +} + +void +weston_desktop_api_surface_added(struct weston_desktop *desktop, + struct weston_desktop_surface *surface) +{ + struct weston_desktop_client *client = + weston_desktop_surface_get_client(surface); + struct wl_list *list = weston_desktop_client_get_surface_list(client); + struct wl_list *link = weston_desktop_surface_get_client_link(surface); + + desktop->api.surface_added(surface, desktop->user_data); + wl_list_insert(list, link); +} + +void +weston_desktop_api_surface_removed(struct weston_desktop *desktop, + struct weston_desktop_surface *surface) +{ + struct wl_list *link = weston_desktop_surface_get_client_link(surface); + + wl_list_remove(link); + wl_list_init(link); + desktop->api.surface_removed(surface, desktop->user_data); +} + +void +weston_desktop_api_committed(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + int32_t sx, int32_t sy) +{ + if (desktop->api.committed != NULL) + desktop->api.committed(surface, sx, sy, desktop->user_data); +} + +void +weston_desktop_api_show_window_menu(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + struct weston_seat *seat, + int32_t x, int32_t y) +{ + if (desktop->api.show_window_menu != NULL) + desktop->api.show_window_menu(surface, seat, x, y, + 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, + struct weston_desktop_surface *parent) +{ + if (desktop->api.set_parent != NULL) + desktop->api.set_parent(surface, parent, desktop->user_data); +} + +void +weston_desktop_api_move(struct weston_desktop *desktop, + struct weston_desktop_surface *surface, + struct weston_seat *seat, uint32_t serial) +{ + if (desktop->api.move != NULL) + 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, + struct weston_seat *seat, uint32_t serial, + enum weston_desktop_surface_edge edges) +{ + if (desktop->api.resize != NULL) + desktop->api.resize(surface, seat, serial, edges, + 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, + bool fullscreen, + struct weston_output *output) +{ + if (desktop->api.fullscreen_requested != NULL) + desktop->api.fullscreen_requested(surface, fullscreen, output, + 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, + bool maximized) +{ + if (desktop->api.maximized_requested != NULL) + desktop->api.maximized_requested(surface, maximized, + 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) +{ + if (desktop->api.minimized_requested != NULL) + 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, + int32_t x, int32_t y) +{ + if (desktop->api.set_xwayland_position != NULL) + 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 000000000..4588ad106 --- /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 new file mode 100644 index 000000000..bc6b310af --- /dev/null +++ b/libweston/desktop/seat.c @@ -0,0 +1,575 @@ +/* + * 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" +#include "shared/timespec-util.h" + +struct weston_desktop_seat { + struct wl_listener seat_destroy_listener; + struct weston_seat *seat; + struct { + struct weston_keyboard_grab keyboard; + struct weston_pointer_grab pointer; + struct weston_touch_grab touch; + bool initial_up; + struct wl_client *client; + struct wl_list surfaces; + struct weston_desktop_surface *grab_surface; + struct wl_listener grab_surface_destroy_listener; + } popup_grab; +}; + +static void weston_desktop_seat_popup_grab_end(struct weston_desktop_seat *seat); + +static void +weston_desktop_seat_popup_grab_keyboard_key(struct weston_keyboard_grab *grab, + const struct timespec *time, + uint32_t key, + enum wl_keyboard_key_state state) +{ + weston_keyboard_send_key(grab->keyboard, time, key, state); +} + +static void +weston_desktop_seat_popup_grab_keyboard_modifiers(struct weston_keyboard_grab *grab, + uint32_t serial, + uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, + uint32_t group) +{ + weston_keyboard_send_modifiers(grab->keyboard, serial, mods_depressed, + mods_latched, mods_locked, group); +} + +static void +weston_desktop_seat_popup_grab_keyboard_cancel(struct weston_keyboard_grab *grab) +{ + struct weston_desktop_seat *seat = + wl_container_of(grab, seat, popup_grab.keyboard); + + weston_desktop_seat_popup_grab_end(seat); +} + +static const struct weston_keyboard_grab_interface weston_desktop_seat_keyboard_popup_grab_interface = { + .key = weston_desktop_seat_popup_grab_keyboard_key, + .modifiers = weston_desktop_seat_popup_grab_keyboard_modifiers, + .cancel = weston_desktop_seat_popup_grab_keyboard_cancel, +}; + +static void +weston_desktop_seat_popup_grab_pointer_focus(struct weston_pointer_grab *grab) +{ + struct weston_desktop_seat *seat = + wl_container_of(grab, seat, popup_grab.pointer); + struct weston_pointer *pointer = grab->pointer; + struct weston_view *view; + + view = weston_compositor_pick_view(pointer->seat->compositor, + pointer->pos); + + /* Ignore views that don't belong to the grabbing client */ + if (view != NULL && + view->surface->resource != NULL && + wl_resource_get_client(view->surface->resource) != seat->popup_grab.client) + view = NULL; + + if (pointer->focus == view) + return; + + if (view) + weston_pointer_set_focus(pointer, view); + else + weston_pointer_clear_focus(pointer); +} + +static void +weston_desktop_seat_popup_grab_pointer_motion(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + weston_pointer_send_motion(grab->pointer, time, event); +} + +static void +weston_desktop_seat_popup_grab_pointer_button(struct weston_pointer_grab *grab, + const struct timespec *time, + uint32_t button, + enum wl_pointer_button_state state) +{ + struct weston_desktop_seat *seat = + wl_container_of(grab, seat, popup_grab.pointer); + struct weston_pointer *pointer = grab->pointer; + bool initial_up = seat->popup_grab.initial_up; + + if (state == WL_POINTER_BUTTON_STATE_RELEASED) + seat->popup_grab.initial_up = true; + + if (weston_pointer_has_focus_resource(pointer)) + weston_pointer_send_button(pointer, time, button, state); + else if (state == WL_POINTER_BUTTON_STATE_RELEASED && + (initial_up || + (timespec_sub_to_msec(time, &grab->pointer->grab_time) > 500))) + weston_desktop_seat_popup_grab_end(seat); +} + +static void +weston_desktop_seat_popup_grab_pointer_axis(struct weston_pointer_grab *grab, + const struct timespec *time, + struct weston_pointer_axis_event *event) +{ + weston_pointer_send_axis(grab->pointer, time, event); +} + +static void +weston_desktop_seat_popup_grab_pointer_axis_source(struct weston_pointer_grab *grab, + uint32_t source) +{ + weston_pointer_send_axis_source(grab->pointer, source); +} + +static void +weston_desktop_seat_popup_grab_pointer_frame(struct weston_pointer_grab *grab) +{ + weston_pointer_send_frame(grab->pointer); +} + +static void +weston_desktop_seat_popup_grab_pointer_cancel(struct weston_pointer_grab *grab) +{ + struct weston_desktop_seat *seat = + wl_container_of(grab, seat, popup_grab.pointer); + + weston_desktop_seat_popup_grab_end(seat); +} + +static const struct weston_pointer_grab_interface weston_desktop_seat_pointer_popup_grab_interface = { + .focus = weston_desktop_seat_popup_grab_pointer_focus, + .motion = weston_desktop_seat_popup_grab_pointer_motion, + .button = weston_desktop_seat_popup_grab_pointer_button, + .axis = weston_desktop_seat_popup_grab_pointer_axis, + .axis_source = weston_desktop_seat_popup_grab_pointer_axis_source, + .frame = weston_desktop_seat_popup_grab_pointer_frame, + .cancel = weston_desktop_seat_popup_grab_pointer_cancel, +}; + +static void +weston_desktop_seat_popup_grab_touch_down(struct weston_touch_grab *grab, + const struct timespec *time, + int touch_id, + wl_fixed_t sx, wl_fixed_t sy) +{ + struct weston_coord_global pos; + + pos.c = weston_coord_from_fixed(sx, sy); + weston_touch_send_down(grab->touch, time, touch_id, pos); +} + +static void +weston_desktop_seat_popup_grab_touch_up(struct weston_touch_grab *grab, + const struct timespec *time, + int touch_id) +{ + weston_touch_send_up(grab->touch, time, touch_id); +} + +static void +weston_desktop_seat_popup_grab_touch_motion(struct weston_touch_grab *grab, + const struct timespec *time, + int touch_id, + wl_fixed_t sx, wl_fixed_t sy) +{ + struct weston_coord_global pos; + + pos.c = weston_coord_from_fixed(sx, sy); + weston_touch_send_motion(grab->touch, time, touch_id, pos); +} + +static void +weston_desktop_seat_popup_grab_touch_frame(struct weston_touch_grab *grab) +{ + weston_touch_send_frame(grab->touch); +} + +static void +weston_desktop_seat_popup_grab_touch_cancel(struct weston_touch_grab *grab) +{ + struct weston_desktop_seat *seat = + wl_container_of(grab, seat, popup_grab.touch); + + weston_desktop_seat_popup_grab_end(seat); +} + +static const struct weston_touch_grab_interface weston_desktop_seat_touch_popup_grab_interface = { + .down = weston_desktop_seat_popup_grab_touch_down, + .up = weston_desktop_seat_popup_grab_touch_up, + .motion = weston_desktop_seat_popup_grab_touch_motion, + .frame = weston_desktop_seat_popup_grab_touch_frame, + .cancel = weston_desktop_seat_popup_grab_touch_cancel, +}; + +static void +weston_desktop_seat_popup_grab_tablet_tool_proximity_in(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + struct weston_tablet *tablet) +{ +} + +static void +weston_desktop_seat_popup_grab_tablet_tool_proximity_out(struct weston_tablet_tool_grab *grab, + const struct timespec *time) +{ + weston_tablet_tool_send_proximity_out(grab->tool, time); +} + +static void +weston_desktop_seat_popup_grab_tablet_tool_motion(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + struct weston_coord_global pos) +{ + weston_tablet_tool_send_motion(grab->tool, time, pos); +} + +static void +weston_desktop_seat_popup_grab_tablet_tool_down(struct weston_tablet_tool_grab *grab, + const struct timespec *time) +{ + weston_tablet_tool_send_down(grab->tool, time); +} + +static void +weston_desktop_seat_popup_grab_tablet_tool_up(struct weston_tablet_tool_grab *grab, + const struct timespec *time) +{ + weston_tablet_tool_send_up(grab->tool, time); +} + +static void +weston_desktop_seat_popup_grab_tablet_tool_pressure(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + uint32_t pressure) +{ + weston_tablet_tool_send_pressure(grab->tool, time, pressure); +} + +static void +weston_desktop_seat_popup_grab_tablet_tool_distance(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + uint32_t distance) +{ + weston_tablet_tool_send_distance(grab->tool, time, distance); +} + +static void +weston_desktop_seat_popup_grab_tablet_tool_tilt(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + wl_fixed_t tilt_x, wl_fixed_t tilt_y) +{ + weston_tablet_tool_send_tilt(grab->tool, time, tilt_x, tilt_y); +} + +static void +weston_desktop_seat_popup_grab_tablet_tool_button(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + uint32_t button, uint32_t state) +{ + weston_tablet_tool_send_button(grab->tool, time, button, state); +} + +static void +weston_desktop_seat_popup_grab_tablet_tool_frame(struct weston_tablet_tool_grab *grab, + const struct timespec *time) +{ + weston_tablet_tool_send_frame(grab->tool, time); +} + +static void +weston_desktop_seat_popup_grab_tablet_tool_cancel(struct weston_tablet_tool_grab *grab) +{ + struct weston_desktop_seat *seat = + wl_container_of(grab, seat, popup_grab.pointer); + + weston_desktop_seat_popup_grab_end(seat); +} + +static const struct weston_tablet_tool_grab_interface weston_desktop_seat_tablet_tool_popup_grab_interface = { + weston_desktop_seat_popup_grab_tablet_tool_proximity_in, + weston_desktop_seat_popup_grab_tablet_tool_proximity_out, + weston_desktop_seat_popup_grab_tablet_tool_motion, + weston_desktop_seat_popup_grab_tablet_tool_down, + weston_desktop_seat_popup_grab_tablet_tool_up, + weston_desktop_seat_popup_grab_tablet_tool_pressure, + weston_desktop_seat_popup_grab_tablet_tool_distance, + weston_desktop_seat_popup_grab_tablet_tool_tilt, + weston_desktop_seat_popup_grab_tablet_tool_button, + weston_desktop_seat_popup_grab_tablet_tool_frame, + weston_desktop_seat_popup_grab_tablet_tool_cancel, +}; + +static void +weston_desktop_seat_destroy(struct wl_listener *listener, void *data) +{ + struct weston_desktop_seat *seat = + wl_container_of(listener, seat, seat_destroy_listener); + + free(seat); +} + +struct weston_desktop_seat * +weston_desktop_seat_from_seat(struct weston_seat *wseat) +{ + struct wl_listener *listener; + struct weston_desktop_seat *seat; + + if (wseat == NULL) + return NULL; + + listener = wl_signal_get(&wseat->destroy_signal, + weston_desktop_seat_destroy); + if (listener != NULL) + return wl_container_of(listener, seat, seat_destroy_listener); + + seat = zalloc(sizeof(struct weston_desktop_seat)); + if (seat == NULL) + return NULL; + + seat->seat = wseat; + + seat->seat_destroy_listener.notify = weston_desktop_seat_destroy; + wl_signal_add(&wseat->destroy_signal, &seat->seat_destroy_listener); + + seat->popup_grab.keyboard.interface = + &weston_desktop_seat_keyboard_popup_grab_interface; + seat->popup_grab.pointer.interface = + &weston_desktop_seat_pointer_popup_grab_interface; + seat->popup_grab.touch.interface = + &weston_desktop_seat_touch_popup_grab_interface; + wl_list_init(&seat->popup_grab.surfaces); + + return seat; +} + +struct weston_desktop_surface * +weston_desktop_seat_popup_grab_get_topmost_surface(struct weston_desktop_seat *seat) +{ + if (seat == NULL || wl_list_empty(&seat->popup_grab.surfaces)) + return NULL; + + struct wl_list *grab_link = seat->popup_grab.surfaces.next; + + return weston_desktop_surface_from_grab_link(grab_link); +} + +static void +popup_grab_grab_surface_destroy(struct wl_listener *listener, void *data) +{ + struct weston_desktop_seat *seat = + wl_container_of(listener, seat, + popup_grab.grab_surface_destroy_listener); + + seat->popup_grab.grab_surface = NULL; +} + +bool +weston_desktop_seat_popup_grab_start(struct weston_desktop_seat *seat, + struct weston_desktop_surface *parent, + struct wl_client *client, uint32_t serial) +{ + assert(seat == NULL || seat->popup_grab.client == NULL || + seat->popup_grab.client == client); + + struct weston_seat *wseat = seat != NULL ? seat->seat : NULL; + /* weston_seat_get_* functions can properly handle a NULL wseat */ + struct weston_keyboard *keyboard = weston_seat_get_keyboard(wseat); + struct weston_pointer *pointer = weston_seat_get_pointer(wseat); + struct weston_touch *touch = weston_seat_get_touch(wseat); + struct weston_tablet_tool *tool; + bool tool_found = false; + + if (wseat) { + wl_list_for_each(tool, &wseat->tablet_tool_list, link) { + if (tool->grab_serial == serial) { + tool_found = true; + break; + } + } + } + + if ((keyboard == NULL || keyboard->grab_serial != serial) && + (pointer == NULL || pointer->grab_serial != serial) && + (touch == NULL || touch->grab_serial != serial) && + !tool_found) { + return false; + } + + wl_list_for_each(tool, &wseat->tablet_tool_list, link) { + if (tool->grab->interface != &weston_desktop_seat_tablet_tool_popup_grab_interface) { + struct weston_tablet_tool_grab *grab = zalloc(sizeof(*grab)); + + grab->interface = &weston_desktop_seat_tablet_tool_popup_grab_interface; + weston_tablet_tool_start_grab(tool, grab); + } + } + + seat->popup_grab.initial_up = + (pointer == NULL || pointer->button_count == 0); + seat->popup_grab.client = client; + + if (keyboard != NULL && + keyboard->grab->interface != &weston_desktop_seat_keyboard_popup_grab_interface) { + struct weston_surface *parent_surface; + + weston_keyboard_start_grab(keyboard, &seat->popup_grab.keyboard); + seat->popup_grab.grab_surface = parent; + + parent_surface = weston_desktop_surface_get_surface(parent); + seat->popup_grab.grab_surface_destroy_listener.notify = + popup_grab_grab_surface_destroy; + wl_signal_add(&parent_surface->destroy_signal, + &seat->popup_grab.grab_surface_destroy_listener); + } + + if (pointer != NULL && + pointer->grab->interface != &weston_desktop_seat_pointer_popup_grab_interface) + weston_pointer_start_grab(pointer, &seat->popup_grab.pointer); + + if (touch != NULL && + touch->grab->interface != &weston_desktop_seat_touch_popup_grab_interface) + weston_touch_start_grab(touch, &seat->popup_grab.touch); + + return true; +} + +static void +weston_desktop_seat_popup_grab_end(struct weston_desktop_seat *seat) +{ + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat->seat); + struct weston_pointer *pointer = weston_seat_get_pointer(seat->seat); + struct weston_touch *touch = weston_seat_get_touch(seat->seat); + struct weston_tablet_tool *tool; + + while (!wl_list_empty(&seat->popup_grab.surfaces)) { + struct wl_list *link = seat->popup_grab.surfaces.prev; + struct weston_desktop_surface *surface = + weston_desktop_surface_from_grab_link(link); + + wl_list_remove(link); + wl_list_init(link); + weston_desktop_surface_popup_dismiss(surface); + } + + if (keyboard != NULL && + keyboard->grab->interface == &weston_desktop_seat_keyboard_popup_grab_interface) { + struct weston_desktop_surface *grab_desktop_surface; + struct weston_surface *grab_surface; + + weston_keyboard_end_grab(keyboard); + + grab_desktop_surface = seat->popup_grab.grab_surface; + grab_surface = + weston_desktop_surface_get_surface(grab_desktop_surface); + weston_keyboard_set_focus(keyboard, grab_surface); + } + + if (pointer != NULL && + pointer->grab->interface == &weston_desktop_seat_pointer_popup_grab_interface) + weston_pointer_end_grab(pointer); + + if (touch != NULL && + touch->grab->interface == &weston_desktop_seat_touch_popup_grab_interface) + weston_touch_end_grab(touch); + + wl_list_for_each(tool, &seat->seat->tablet_tool_list, link) { + if (tool->grab->interface == &weston_desktop_seat_tablet_tool_popup_grab_interface) { + struct weston_tablet_tool_grab *grab = tool->grab; + weston_tablet_tool_end_grab(tool); + free(grab); + } + } + + seat->popup_grab.client = NULL; + if (seat->popup_grab.grab_surface) { + seat->popup_grab.grab_surface = NULL; + wl_list_remove(&seat->popup_grab.grab_surface_destroy_listener.link); + } +} + +void +weston_desktop_seat_popup_grab_add_surface(struct weston_desktop_seat *seat, + struct wl_list *link) +{ + struct weston_desktop_surface *desktop_surface; + struct weston_surface *surface; + + assert(seat->popup_grab.client != NULL); + + wl_list_insert(&seat->popup_grab.surfaces, link); + + desktop_surface = + weston_desktop_seat_popup_grab_get_topmost_surface(seat); + surface = weston_desktop_surface_get_surface(desktop_surface); + weston_keyboard_set_focus(seat->popup_grab.keyboard.keyboard, surface); +} + +void +weston_desktop_seat_popup_grab_remove_surface(struct weston_desktop_seat *seat, + struct wl_list *link) +{ + assert(seat->popup_grab.client != NULL); + + wl_list_remove(link); + wl_list_init(link); + if (wl_list_empty(&seat->popup_grab.surfaces)) { + weston_desktop_seat_popup_grab_end(seat); + } else { + struct weston_desktop_surface *desktop_surface; + struct weston_surface *surface; + + desktop_surface = + weston_desktop_seat_popup_grab_get_topmost_surface(seat); + surface = weston_desktop_surface_get_surface(desktop_surface); + weston_keyboard_set_focus(seat->popup_grab.keyboard.keyboard, + surface); + } +} + +WL_EXPORT void +weston_seat_break_desktop_grabs(struct weston_seat *wseat) +{ + struct weston_desktop_seat *seat = weston_desktop_seat_from_seat(wseat); + + weston_desktop_seat_popup_grab_end(seat); +} diff --git a/libweston/desktop/surface.c b/libweston/desktop/surface.c new file mode 100644 index 000000000..74707f6a6 --- /dev/null +++ b/libweston/desktop/surface.c @@ -0,0 +1,893 @@ +/* + * 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 +#include "internal.h" + +struct weston_desktop_view { + struct wl_list link; + struct weston_view *view; + struct weston_desktop_view *parent; + struct wl_list children_list; + struct wl_list children_link; +}; + +struct weston_desktop_surface { + struct weston_desktop *desktop; + struct weston_desktop_client *client; + struct wl_list client_link; + const struct weston_desktop_surface_implementation *implementation; + void *implementation_data; + void *user_data; + struct weston_surface *surface; + struct wl_list view_list; + struct weston_position buffer_move; + struct wl_listener surface_commit_listener; + struct wl_listener surface_destroy_listener; + struct wl_listener client_destroy_listener; + struct wl_list children_list; + + struct wl_list resource_list; + bool has_geometry; + struct weston_geometry geometry; + struct { + char *title; + char *app_id; + pid_t pid; + struct wl_signal metadata_signal; + }; + struct { + struct weston_desktop_surface *parent; + struct wl_list children_link; + struct weston_position position; + bool use_geometry; + }; + struct { + struct wl_list grab_link; + }; +}; + +static void +weston_desktop_surface_update_view_position(struct weston_desktop_surface *surface) +{ + struct weston_desktop_view *view; + int32_t x, y; + + x = surface->position.x; + y = surface->position.y; + + if (surface->use_geometry) { + struct weston_desktop_surface *parent = + weston_desktop_surface_get_parent(surface); + struct weston_geometry geometry, parent_geometry; + + geometry = weston_desktop_surface_get_geometry(surface); + parent_geometry = weston_desktop_surface_get_geometry(parent); + + x += parent_geometry.x - geometry.x; + y += parent_geometry.y - geometry.y; + + wl_list_for_each(view, &surface->view_list, link) + weston_view_set_rel_position(view->view, x, y); + } else { + wl_list_for_each(view, &surface->view_list, link) + weston_view_set_position(view->view, x, y); + } +} + + +static void +weston_desktop_view_propagate_layer(struct weston_desktop_view *view); + +static void +weston_desktop_view_destroy(struct weston_desktop_view *view) +{ + struct weston_desktop_view *child_view, *tmp; + + wl_list_for_each_safe(child_view, tmp, &view->children_list, children_link) + weston_desktop_view_destroy(child_view); + + wl_list_remove(&view->children_link); + wl_list_remove(&view->link); + + weston_view_damage_below(view->view); + if (view->parent != NULL) + weston_view_destroy(view->view); + + free(view); +} + +void +weston_desktop_surface_destroy(struct weston_desktop_surface *surface) +{ + struct weston_desktop_view *view, *next_view; + struct weston_desktop_surface *child, *next_child; + + wl_list_remove(&surface->surface_commit_listener.link); + wl_list_remove(&surface->surface_destroy_listener.link); + wl_list_remove(&surface->client_destroy_listener.link); + + if (!wl_list_empty(&surface->resource_list)) { + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &surface->resource_list) { + wl_resource_set_user_data(resource, NULL); + wl_list_remove(wl_resource_get_link(resource)); + } + } + + surface->implementation->destroy(surface, surface->implementation_data); + + surface->surface->committed = NULL; + surface->surface->committed_private = NULL; + + weston_desktop_surface_unset_relative_to(surface); + wl_list_remove(&surface->client_link); + + wl_list_for_each_safe(child, next_child, + &surface->children_list, + children_link) + weston_desktop_surface_unset_relative_to(child); + + wl_list_for_each_safe(view, next_view, &surface->view_list, link) + weston_desktop_view_destroy(view); + + free(surface->title); + free(surface->app_id); + + free(surface); +} + +static void +weston_desktop_surface_surface_committed(struct wl_listener *listener, + void *data) +{ + struct weston_desktop_surface *surface = + wl_container_of(listener, surface, surface_commit_listener); + + if (surface->implementation->committed != NULL) + surface->implementation->committed(surface, + surface->implementation_data, + surface->buffer_move.x, + surface->buffer_move.y); + + if (surface->parent != NULL) { + struct weston_desktop_view *view; + + wl_list_for_each(view, &surface->view_list, link) { + weston_view_set_transform_parent(view->view, + view->parent->view); + weston_desktop_view_propagate_layer(view->parent); + } + weston_desktop_surface_update_view_position(surface); + } + + if (!wl_list_empty(&surface->children_list)) { + struct weston_desktop_surface *child; + + wl_list_for_each(child, &surface->children_list, children_link) + weston_desktop_surface_update_view_position(child); + } + + surface->buffer_move.x = 0; + surface->buffer_move.y = 0; +} + +static void +weston_desktop_surface_surface_destroyed(struct wl_listener *listener, + void *data) +{ + struct weston_desktop_surface *surface = + wl_container_of(listener, surface, surface_destroy_listener); + + weston_desktop_surface_destroy(surface); +} + +void +weston_desktop_surface_resource_destroy(struct wl_resource *resource) +{ + struct weston_desktop_surface *surface = + wl_resource_get_user_data(resource); + + if (surface != NULL) + weston_desktop_surface_destroy(surface); +} + +static void +weston_desktop_surface_committed(struct weston_surface *wsurface, + struct weston_coord_surface new_origin) +{ + struct weston_desktop_surface *surface = wsurface->committed_private; + + surface->buffer_move.x = new_origin.c.x; + surface->buffer_move.y = new_origin.c.y; +} + +static void +weston_desktop_surface_client_destroyed(struct wl_listener *listener, + void *data) +{ + struct weston_desktop_surface *surface = + wl_container_of(listener, surface, client_destroy_listener); + + weston_desktop_surface_destroy(surface); +} + +struct weston_desktop_surface * +weston_desktop_surface_create(struct weston_desktop *desktop, + struct weston_desktop_client *client, + struct weston_surface *wsurface, + const struct weston_desktop_surface_implementation *implementation, + void *implementation_data) +{ + assert(implementation->destroy != NULL); + + struct weston_desktop_surface *surface; + + surface = zalloc(sizeof(struct weston_desktop_surface)); + if (surface == NULL) { + if (client != NULL) + wl_client_post_no_memory(weston_desktop_client_get_client(client)); + return NULL; + } + + surface->desktop = desktop; + surface->implementation = implementation; + surface->implementation_data = implementation_data; + surface->surface = wsurface; + + surface->client = client; + surface->client_destroy_listener.notify = + weston_desktop_surface_client_destroyed; + weston_desktop_client_add_destroy_listener( + client, &surface->client_destroy_listener); + + wsurface->committed = weston_desktop_surface_committed; + wsurface->committed_private = surface; + + surface->pid = -1; + + surface->surface_commit_listener.notify = + weston_desktop_surface_surface_committed; + wl_signal_add(&surface->surface->commit_signal, + &surface->surface_commit_listener); + surface->surface_destroy_listener.notify = + weston_desktop_surface_surface_destroyed; + wl_signal_add(&surface->surface->destroy_signal, + &surface->surface_destroy_listener); + + wl_list_init(&surface->client_link); + wl_list_init(&surface->resource_list); + wl_list_init(&surface->children_list); + wl_list_init(&surface->children_link); + wl_list_init(&surface->view_list); + wl_list_init(&surface->grab_link); + + wl_signal_init(&surface->metadata_signal); + + return surface; +} + +struct wl_resource * +weston_desktop_surface_add_resource(struct weston_desktop_surface *surface, + const struct wl_interface *interface, + const void *implementation, uint32_t id, + wl_resource_destroy_func_t destroy) +{ + struct wl_resource *client_resource = + weston_desktop_client_get_resource(surface->client); + struct wl_client *wl_client = + weston_desktop_client_get_client(surface->client); + struct wl_resource *resource; + + resource = wl_resource_create(wl_client, + interface, + wl_resource_get_version(client_resource), + id); + if (resource == NULL) { + wl_client_post_no_memory(wl_client); + weston_desktop_surface_destroy(surface); + return NULL; + } + if (destroy == NULL) + destroy = weston_desktop_surface_resource_destroy; + wl_resource_set_implementation(resource, implementation, surface, destroy); + wl_list_insert(&surface->resource_list, wl_resource_get_link(resource)); + + return resource; +} + +struct weston_desktop_surface * +weston_desktop_surface_from_grab_link(struct wl_list *grab_link) +{ + struct weston_desktop_surface *surface = + wl_container_of(grab_link, surface, grab_link); + + return surface; +} + +WL_EXPORT bool +weston_surface_is_desktop_surface(struct weston_surface *wsurface) +{ + return wsurface->committed == weston_desktop_surface_committed; +} + +WL_EXPORT struct weston_desktop_surface * +weston_surface_get_desktop_surface(struct weston_surface *wsurface) +{ + if (!weston_surface_is_desktop_surface(wsurface)) + return NULL; + return wsurface->committed_private; +} + +WL_EXPORT void +weston_desktop_surface_set_user_data(struct weston_desktop_surface *surface, + void *user_data) +{ + surface->user_data = user_data; +} + +static struct weston_desktop_view * +weston_desktop_surface_create_desktop_view(struct weston_desktop_surface *surface) +{ + struct wl_client *wl_client= + weston_desktop_client_get_client(surface->client); + struct weston_desktop_view *view, *child_view; + struct weston_view *wview; + struct weston_desktop_surface *child; + + wview = weston_view_create(surface->surface); + if (wview == NULL) { + if (wl_client != NULL) + wl_client_post_no_memory(wl_client); + return NULL; + } + + view = zalloc(sizeof(struct weston_desktop_view)); + if (view == NULL) { + if (wl_client != NULL) + wl_client_post_no_memory(wl_client); + return NULL; + } + + view->view = wview; + wl_list_init(&view->children_list); + wl_list_init(&view->children_link); + wl_list_insert(surface->view_list.prev, &view->link); + + wl_list_for_each(child, &surface->children_list, children_link) { + child_view = + weston_desktop_surface_create_desktop_view(child); + if (child_view == NULL) { + weston_desktop_view_destroy(view); + return NULL; + } + + child_view->parent = view; + wl_list_insert(view->children_list.prev, + &child_view->children_link); + } + + return view; +} + +WL_EXPORT struct weston_view * +weston_desktop_surface_create_view(struct weston_desktop_surface *surface) +{ + struct weston_desktop_view *view; + + view = weston_desktop_surface_create_desktop_view(surface); + if (view == NULL) + return NULL; + + return view->view; +} + +WL_EXPORT void +weston_desktop_surface_unlink_view(struct weston_view *wview) +{ + struct weston_desktop_surface *surface; + struct weston_desktop_view *view; + + if (!weston_surface_is_desktop_surface(wview->surface)) + return; + + surface = weston_surface_get_desktop_surface(wview->surface); + wl_list_for_each(view, &surface->view_list, link) { + if (view->view == wview) { + weston_desktop_view_destroy(view); + return; + } + } +} + +static void +weston_desktop_view_propagate_layer(struct weston_desktop_view *view) +{ + struct weston_desktop_view *child; + struct wl_list *link = &view->view->layer_link.link; + + wl_list_for_each_reverse(child, &view->children_list, children_link) { + struct weston_layer_entry *prev = + wl_container_of(link->prev, prev, link); + + if (prev == &child->view->layer_link) + continue; + + child->view->is_mapped = true; + weston_view_damage_below(child->view); + weston_view_geometry_dirty(child->view); + weston_layer_entry_remove(&child->view->layer_link); + weston_layer_entry_insert(prev, &child->view->layer_link); + weston_view_geometry_dirty(child->view); + weston_surface_damage(child->view->surface); + weston_view_update_transform(child->view); + + weston_desktop_view_propagate_layer(child); + } +} + +WL_EXPORT void +weston_desktop_surface_propagate_layer(struct weston_desktop_surface *surface) +{ + struct weston_desktop_view *view; + + wl_list_for_each(view, &surface->view_list, link) + weston_desktop_view_propagate_layer(view); +} + +WL_EXPORT void +weston_desktop_surface_set_activated(struct weston_desktop_surface *surface, bool activated) +{ + if (surface->implementation->set_activated != NULL) + surface->implementation->set_activated(surface, + surface->implementation_data, + activated); +} + +WL_EXPORT void +weston_desktop_surface_set_fullscreen(struct weston_desktop_surface *surface, bool fullscreen) +{ + if (surface->implementation->set_fullscreen != NULL) + surface->implementation->set_fullscreen(surface, + surface->implementation_data, + fullscreen); +} + +WL_EXPORT void +weston_desktop_surface_set_maximized(struct weston_desktop_surface *surface, bool maximized) +{ + if (surface->implementation->set_maximized != NULL) + surface->implementation->set_maximized(surface, + surface->implementation_data, + maximized); +} + +WL_EXPORT void +weston_desktop_surface_set_resizing(struct weston_desktop_surface *surface, bool resizing) +{ + if (surface->implementation->set_resizing != NULL) + surface->implementation->set_resizing(surface, + surface->implementation_data, + resizing); +} + +WL_EXPORT void +weston_desktop_surface_set_size(struct weston_desktop_surface *surface, int32_t width, int32_t height) +{ + if (surface->implementation->set_size != NULL) + surface->implementation->set_size(surface, + surface->implementation_data, + 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) +{ + if (surface->implementation->close != NULL) + surface->implementation->close(surface, + surface->implementation_data); +} + +WL_EXPORT void +weston_desktop_surface_add_metadata_listener(struct weston_desktop_surface *surface, + struct wl_listener *listener) +{ + wl_signal_add(&surface->metadata_signal, listener); +} + +struct weston_desktop_surface * +weston_desktop_surface_from_client_link(struct wl_list *link) +{ + struct weston_desktop_surface *surface; + + surface = wl_container_of(link, surface, client_link); + return surface; +} + +struct wl_list * +weston_desktop_surface_get_client_link(struct weston_desktop_surface *surface) +{ + return &surface->client_link; +} + +bool +weston_desktop_surface_has_implementation(struct weston_desktop_surface *surface, + const struct weston_desktop_surface_implementation *implementation) +{ + return surface->implementation == implementation; +} + +const struct weston_desktop_surface_implementation * +weston_desktop_surface_get_implementation(struct weston_desktop_surface *surface) +{ + return surface->implementation; +} + +void * +weston_desktop_surface_get_implementation_data(struct weston_desktop_surface *surface) +{ + return surface->implementation_data; +} + +WL_EXPORT struct weston_desktop_surface * +weston_desktop_surface_get_parent(struct weston_desktop_surface *surface) +{ + return surface->parent; +} + +bool +weston_desktop_surface_get_grab(struct weston_desktop_surface *surface) +{ + return !wl_list_empty(&surface->grab_link); +} + +WL_EXPORT struct weston_desktop_client * +weston_desktop_surface_get_client(struct weston_desktop_surface *surface) +{ + return surface->client; +} + +WL_EXPORT void * +weston_desktop_surface_get_user_data(struct weston_desktop_surface *surface) +{ + return surface->user_data; +} + +WL_EXPORT struct weston_surface * +weston_desktop_surface_get_surface(struct weston_desktop_surface *surface) +{ + return surface->surface; +} + +WL_EXPORT const char * +weston_desktop_surface_get_title(struct weston_desktop_surface *surface) +{ + return surface->title; +} + +WL_EXPORT const char * +weston_desktop_surface_get_app_id(struct weston_desktop_surface *surface) +{ + return surface->app_id; +} + +WL_EXPORT pid_t +weston_desktop_surface_get_pid(struct weston_desktop_surface *surface) +{ + pid_t pid; + + if (surface->pid != -1) { + pid = surface->pid; + } else { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(surface); + struct wl_client *wl_client = + weston_desktop_client_get_client(client); + + /* wl_client should always be valid, because only in the + * xwayland case it wouldn't be, but in that case we won't + * reach here, as the pid is initialized to 0. */ + assert(wl_client); + wl_client_get_credentials(wl_client, &pid, NULL, NULL); + } + return pid; +} + +WL_EXPORT bool +weston_desktop_surface_get_activated(struct weston_desktop_surface *surface) +{ + if (surface->implementation->get_activated == NULL) + return false; + return surface->implementation->get_activated(surface, + surface->implementation_data); +} + +WL_EXPORT bool +weston_desktop_surface_get_resizing(struct weston_desktop_surface *surface) +{ + if (surface->implementation->get_resizing == NULL) + return false; + return surface->implementation->get_resizing(surface, + surface->implementation_data); +} + +WL_EXPORT bool +weston_desktop_surface_get_maximized(struct weston_desktop_surface *surface) +{ + if (surface->implementation->get_maximized == NULL) + return false; + return surface->implementation->get_maximized(surface, + surface->implementation_data); +} + +WL_EXPORT bool +weston_desktop_surface_get_fullscreen(struct weston_desktop_surface *surface) +{ + if (surface->implementation->get_fullscreen == NULL) + return false; + return surface->implementation->get_fullscreen(surface, + surface->implementation_data); +} + +WL_EXPORT bool +weston_desktop_surface_get_pending_activated(struct weston_desktop_surface *surface) +{ + if (surface->implementation->get_pending_activated == NULL) + return false; + return surface->implementation->get_pending_activated(surface, + surface->implementation_data); +} + +WL_EXPORT bool +weston_desktop_surface_get_pending_resizing(struct weston_desktop_surface *surface) +{ + if (surface->implementation->get_pending_resizing == NULL) + return false; + return surface->implementation->get_pending_resizing(surface, + surface->implementation_data); +} + +WL_EXPORT bool +weston_desktop_surface_get_pending_maximized(struct weston_desktop_surface *surface) +{ + if (surface->implementation->get_pending_maximized == NULL) + return false; + return surface->implementation->get_pending_maximized(surface, + surface->implementation_data); +} + +WL_EXPORT bool +weston_desktop_surface_get_pending_fullscreen(struct weston_desktop_surface *surface) +{ + if (surface->implementation->get_pending_fullscreen == NULL) + return false; + return surface->implementation->get_pending_fullscreen(surface, + surface->implementation_data); +} + +WL_EXPORT struct weston_geometry +weston_desktop_surface_get_geometry(struct weston_desktop_surface *surface) +{ + if (surface->has_geometry) + return surface->geometry; + return weston_surface_get_bounding_box(surface->surface); +} + +WL_EXPORT struct weston_size +weston_desktop_surface_get_max_size(struct weston_desktop_surface *surface) +{ + struct weston_size size = { 0, 0 }; + + if (surface->implementation->get_max_size == NULL) + return size; + return surface->implementation->get_max_size(surface, + surface->implementation_data); +} + +WL_EXPORT struct weston_size +weston_desktop_surface_get_min_size(struct weston_desktop_surface *surface) +{ + struct weston_size size = { 0, 0 }; + + if (surface->implementation->get_min_size == NULL) + return size; + return surface->implementation->get_min_size(surface, + surface->implementation_data); +} + +void +weston_desktop_surface_set_title(struct weston_desktop_surface *surface, + const char *title) +{ + char *tmp, *old; + + tmp = strdup(title); + if (tmp == NULL) + return; + + old = surface->title; + surface->title = tmp; + wl_signal_emit(&surface->metadata_signal, surface); + free(old); +} + +void +weston_desktop_surface_set_app_id(struct weston_desktop_surface *surface, + const char *app_id) +{ + char *tmp, *old; + + tmp = strdup(app_id); + if (tmp == NULL) + return; + + old = surface->app_id; + surface->app_id = tmp; + wl_signal_emit(&surface->metadata_signal, surface); + free(old); +} + +void +weston_desktop_surface_set_pid(struct weston_desktop_surface *surface, + pid_t pid) +{ + surface->pid = pid; +} + +void +weston_desktop_surface_set_geometry(struct weston_desktop_surface *surface, + struct weston_geometry geometry) +{ + surface->has_geometry = true; + surface->geometry = geometry; +} + +void +weston_desktop_surface_set_relative_to(struct weston_desktop_surface *surface, + struct weston_desktop_surface *parent, + int32_t x, int32_t y, bool use_geometry) +{ + struct weston_desktop_view *view, *parent_view; + struct wl_list *link, *tmp; + + assert(parent); + + surface->position.x = x; + surface->position.y = y; + surface->use_geometry = use_geometry; + + if (surface->parent == parent) + return; + + surface->parent = parent; + wl_list_remove(&surface->children_link); + wl_list_insert(surface->parent->children_list.prev, + &surface->children_link); + + link = surface->view_list.next; + tmp = link->next; + wl_list_for_each(parent_view, &parent->view_list, link) { + if (link == &surface->view_list) { + view = weston_desktop_surface_create_desktop_view(surface); + if (view == NULL) + return; + tmp = &surface->view_list; + } else { + view = wl_container_of(link, view, link); + wl_list_remove(&view->children_link); + } + + view->parent = parent_view; + wl_list_insert(parent_view->children_list.prev, + &view->children_link); + weston_desktop_view_propagate_layer(view); + + link = tmp; + tmp = link->next; + } + for (; link != &surface->view_list; link = tmp, tmp = link->next) { + view = wl_container_of(link, view, link); + weston_desktop_view_destroy(view); + } +} + +void +weston_desktop_surface_unset_relative_to(struct weston_desktop_surface *surface) +{ + struct weston_desktop_view *view, *tmp; + + if (surface->parent == NULL) + return; + + surface->parent = NULL; + wl_list_remove(&surface->children_link); + wl_list_init(&surface->children_link); + + wl_list_for_each_safe(view, tmp, &surface->view_list, link) + weston_desktop_view_destroy(view); +} + +void +weston_desktop_surface_popup_grab(struct weston_desktop_surface *surface, + struct weston_desktop_surface *parent, + struct weston_desktop_seat *seat, + uint32_t serial) +{ + struct wl_client *wl_client = + weston_desktop_client_get_client(surface->client); + if (weston_desktop_seat_popup_grab_start(seat, parent, wl_client, serial)) + weston_desktop_seat_popup_grab_add_surface(seat, &surface->grab_link); + else + weston_desktop_surface_popup_dismiss(surface); +} + +void +weston_desktop_surface_popup_ungrab(struct weston_desktop_surface *surface, + struct weston_desktop_seat *seat) +{ + weston_desktop_seat_popup_grab_remove_surface(seat, &surface->grab_link); +} + +void +weston_desktop_surface_popup_dismiss(struct weston_desktop_surface *surface) +{ + struct weston_desktop_view *view, *tmp; + + wl_list_for_each_safe(view, tmp, &surface->view_list, link) + weston_desktop_view_destroy(view); + wl_list_remove(&surface->grab_link); + wl_list_init(&surface->grab_link); + weston_desktop_surface_close(surface); +} + +WL_EXPORT void +weston_desktop_surface_foreach_child(struct weston_desktop_surface *surface, + void (* callback)(struct weston_desktop_surface *child, + void *user_data), + void *user_data) +{ + struct weston_desktop_surface *child; + + wl_list_for_each(child, &surface->children_list, children_link) + callback(child, user_data); +} diff --git a/libweston/desktop/xdg-shell-v6.c b/libweston/desktop/xdg-shell-v6.c new file mode 100644 index 000000000..92579ac60 --- /dev/null +++ b/libweston/desktop/xdg-shell-v6.c @@ -0,0 +1,1499 @@ +/* + * 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 "xdg-shell-unstable-v6-server-protocol.h" + +#include +#include "internal.h" + +#define WD_XDG_SHELL_PROTOCOL_VERSION 1 + +static const char *weston_desktop_xdg_toplevel_role = "xdg_toplevel"; +static const char *weston_desktop_xdg_popup_role = "xdg_popup"; + +struct weston_desktop_xdg_positioner { + struct weston_desktop *desktop; + struct weston_desktop_client *client; + struct wl_resource *resource; + + struct weston_size size; + struct weston_geometry anchor_rect; + enum zxdg_positioner_v6_anchor anchor; + enum zxdg_positioner_v6_gravity gravity; + enum zxdg_positioner_v6_constraint_adjustment constraint_adjustment; + struct weston_position offset; +}; + +enum weston_desktop_xdg_surface_role { + WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE, + WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL, + WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP, +}; + +struct weston_desktop_xdg_surface { + struct wl_resource *resource; + struct weston_desktop *desktop; + struct weston_surface *surface; + struct weston_desktop_surface *desktop_surface; + bool configured; + struct wl_event_source *configure_idle; + struct wl_list configure_list; /* weston_desktop_xdg_surface_configure::link */ + + bool has_next_geometry; + struct weston_geometry next_geometry; + + enum weston_desktop_xdg_surface_role role; +}; + +struct weston_desktop_xdg_surface_configure { + struct wl_list link; /* weston_desktop_xdg_surface::configure_list */ + uint32_t serial; +}; + +struct weston_desktop_xdg_toplevel_state { + bool maximized; + bool fullscreen; + bool resizing; + bool activated; +}; + +struct weston_desktop_xdg_toplevel_configure { + struct weston_desktop_xdg_surface_configure base; + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; +}; + +struct weston_desktop_xdg_toplevel { + struct weston_desktop_xdg_surface base; + + struct wl_resource *resource; + bool added; + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; + } pending; + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; + struct weston_size min_size, max_size; + } next; + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size min_size, max_size; + } current; +}; + +struct weston_desktop_xdg_popup { + struct weston_desktop_xdg_surface base; + + struct wl_resource *resource; + bool committed; + struct weston_desktop_xdg_surface *parent; + struct weston_desktop_seat *seat; + struct weston_geometry geometry; +}; + +#define weston_desktop_surface_role_biggest_size (sizeof(struct weston_desktop_xdg_toplevel)) +#define weston_desktop_surface_configure_biggest_size (sizeof(struct weston_desktop_xdg_toplevel)) + + +static struct weston_geometry +weston_desktop_xdg_positioner_get_geometry(struct weston_desktop_xdg_positioner *positioner, + struct weston_desktop_surface *dsurface, + struct weston_desktop_surface *parent) +{ + struct weston_geometry geometry = { + .x = positioner->offset.x, + .y = positioner->offset.y, + .width = positioner->size.width, + .height = positioner->size.height, + }; + + if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_TOP) + geometry.y += positioner->anchor_rect.y; + else if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM) + geometry.y += positioner->anchor_rect.y + positioner->anchor_rect.height; + else + geometry.y += positioner->anchor_rect.y + positioner->anchor_rect.height / 2; + + if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_LEFT) + geometry.x += positioner->anchor_rect.x; + else if (positioner->anchor & ZXDG_POSITIONER_V6_ANCHOR_RIGHT) + geometry.x += positioner->anchor_rect.x + positioner->anchor_rect.width; + else + geometry.x += positioner->anchor_rect.x + positioner->anchor_rect.width / 2; + + if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_TOP) + geometry.y -= geometry.height; + else if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_BOTTOM) + geometry.y = geometry.y; + else + geometry.y -= geometry.height / 2; + + if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_LEFT) + geometry.x -= geometry.width; + else if (positioner->gravity & ZXDG_POSITIONER_V6_GRAVITY_RIGHT) + geometry.x = geometry.x; + else + geometry.x -= geometry.width / 2; + + if (positioner->constraint_adjustment == ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_NONE) + return geometry; + + /* TODO: add compositor policy configuration and the code here */ + + return geometry; +} + +static void +weston_desktop_xdg_positioner_protocol_set_size(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t width, int32_t height) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + if (width < 1 || height < 1) { + wl_resource_post_error(resource, + ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, + "width and height must be positives and non-zero"); + return; + } + + positioner->size.width = width; + positioner->size.height = height; +} + +static void +weston_desktop_xdg_positioner_protocol_set_anchor_rect(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t x, int32_t y, + int32_t width, int32_t height) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + if (width < 1 || height < 1) { + wl_resource_post_error(resource, + ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, + "width and height must be positives and non-zero"); + return; + } + + positioner->anchor_rect.x = x; + positioner->anchor_rect.y = y; + positioner->anchor_rect.width = width; + positioner->anchor_rect.height = height; +} + +static void +weston_desktop_xdg_positioner_protocol_set_anchor(struct wl_client *wl_client, + struct wl_resource *resource, + enum zxdg_positioner_v6_anchor anchor) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + if (((anchor & ZXDG_POSITIONER_V6_ANCHOR_TOP ) && + (anchor & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM)) || + ((anchor & ZXDG_POSITIONER_V6_ANCHOR_LEFT) && + (anchor & ZXDG_POSITIONER_V6_ANCHOR_RIGHT))) { + wl_resource_post_error(resource, + ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, + "same-axis values are not allowed"); + return; + } + + positioner->anchor = anchor; +} + +static void +weston_desktop_xdg_positioner_protocol_set_gravity(struct wl_client *wl_client, + struct wl_resource *resource, + enum zxdg_positioner_v6_gravity gravity) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + if (((gravity & ZXDG_POSITIONER_V6_GRAVITY_TOP) && + (gravity & ZXDG_POSITIONER_V6_GRAVITY_BOTTOM)) || + ((gravity & ZXDG_POSITIONER_V6_GRAVITY_LEFT) && + (gravity & ZXDG_POSITIONER_V6_GRAVITY_RIGHT))) { + wl_resource_post_error(resource, + ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, + "same-axis values are not allowed"); + return; + } + + positioner->gravity = gravity; +} + +static void +weston_desktop_xdg_positioner_protocol_set_constraint_adjustment(struct wl_client *wl_client, + struct wl_resource *resource, + enum zxdg_positioner_v6_constraint_adjustment constraint_adjustment) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + positioner->constraint_adjustment = constraint_adjustment; +} + +static void +weston_desktop_xdg_positioner_protocol_set_offset(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t x, int32_t y) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + positioner->offset.x = x; + positioner->offset.y = y; +} + +static void +weston_desktop_xdg_positioner_destroy(struct wl_resource *resource) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + free(positioner); +} + +static const struct zxdg_positioner_v6_interface weston_desktop_xdg_positioner_implementation = { + .destroy = weston_desktop_destroy_request, + .set_size = weston_desktop_xdg_positioner_protocol_set_size, + .set_anchor_rect = weston_desktop_xdg_positioner_protocol_set_anchor_rect, + .set_anchor = weston_desktop_xdg_positioner_protocol_set_anchor, + .set_gravity = weston_desktop_xdg_positioner_protocol_set_gravity, + .set_constraint_adjustment = weston_desktop_xdg_positioner_protocol_set_constraint_adjustment, + .set_offset = weston_desktop_xdg_positioner_protocol_set_offset, +}; + +static void +weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface); + +static void +weston_desktop_xdg_toplevel_ensure_added(struct weston_desktop_xdg_toplevel *toplevel) +{ + if (toplevel->added) + return; + + weston_desktop_api_surface_added(toplevel->base.desktop, + toplevel->base.desktop_surface); + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); + toplevel->added = true; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_parent(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *parent_resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop_surface *parent = NULL; + + if (parent_resource != NULL) + parent = wl_resource_get_user_data(parent_resource); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_set_parent(toplevel->base.desktop, dsurface, parent); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_title(struct wl_client *wl_client, + struct wl_resource *resource, + const char *title) +{ + struct weston_desktop_surface *toplevel = + wl_resource_get_user_data(resource); + + weston_desktop_surface_set_title(toplevel, title); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_app_id(struct wl_client *wl_client, + struct wl_resource *resource, + const char *app_id) +{ + struct weston_desktop_surface *toplevel = + wl_resource_get_user_data(resource); + + weston_desktop_surface_set_app_id(toplevel, app_id); +} + +static void +weston_desktop_xdg_toplevel_protocol_show_window_menu(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial, + int32_t x, int32_t y) +{ + 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_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + if (!toplevel->base.configured) { + wl_resource_post_error(toplevel->resource, + ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, + "Surface has not been configured yet"); + return; + } + + if (seat == NULL) + return; + + weston_desktop_api_show_window_menu(toplevel->base.desktop, + dsurface, seat, x, y); +} + +static void +weston_desktop_xdg_toplevel_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_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + if (!toplevel->base.configured) { + wl_resource_post_error(toplevel->resource, + ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, + "Surface has not been configured yet"); + return; + } + + if (seat == NULL) + return; + + weston_desktop_api_move(toplevel->base.desktop, dsurface, seat, serial); +} + +static void +weston_desktop_xdg_toplevel_protocol_resize(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial, + enum zxdg_toplevel_v6_resize_edge 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_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + enum weston_desktop_surface_edge surf_edges = + (enum weston_desktop_surface_edge) edges; + + if (!toplevel->base.configured) { + wl_resource_post_error(toplevel->resource, + ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, + "Surface has not been configured yet"); + return; + } + + if (seat == NULL) + return; + + weston_desktop_api_resize(toplevel->base.desktop, + dsurface, seat, serial, surf_edges); +} + +static void +weston_desktop_xdg_toplevel_ack_configure(struct weston_desktop_xdg_toplevel *toplevel, + struct weston_desktop_xdg_toplevel_configure *configure) +{ + toplevel->next.state = configure->state; + toplevel->next.size = configure->size; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_min_size(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t width, int32_t height) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + toplevel->next.min_size.width = width; + toplevel->next.min_size.height = height; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_max_size(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t width, int32_t height) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + toplevel->next.max_size.width = width; + toplevel->next.max_size.height = height; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_maximized(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_maximized_requested(toplevel->base.desktop, dsurface, true); +} + +static void +weston_desktop_xdg_toplevel_protocol_unset_maximized(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_maximized_requested(toplevel->base.desktop, dsurface, false); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_fullscreen(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_xdg_toplevel *toplevel = + 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_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_fullscreen_requested(toplevel->base.desktop, dsurface, + true, output); +} + +static void +weston_desktop_xdg_toplevel_protocol_unset_fullscreen(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_fullscreen_requested(toplevel->base.desktop, dsurface, + false, NULL); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_minimized(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_minimized_requested(toplevel->base.desktop, dsurface); +} + +static void +weston_desktop_xdg_toplevel_send_configure(struct weston_desktop_xdg_toplevel *toplevel, + struct weston_desktop_xdg_toplevel_configure *configure) +{ + uint32_t *s; + struct wl_array states; + + configure->state = toplevel->pending.state; + configure->size = toplevel->pending.size; + + wl_array_init(&states); + if (toplevel->pending.state.maximized) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED; + } + if (toplevel->pending.state.fullscreen) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN; + } + if (toplevel->pending.state.resizing) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = ZXDG_TOPLEVEL_V6_STATE_RESIZING; + } + if (toplevel->pending.state.activated) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = ZXDG_TOPLEVEL_V6_STATE_ACTIVATED; + } + + zxdg_toplevel_v6_send_configure(toplevel->resource, + toplevel->pending.size.width, + toplevel->pending.size.height, + &states); + + wl_array_release(&states); +}; + +static void +weston_desktop_xdg_toplevel_set_maximized(struct weston_desktop_surface *dsurface, + void *user_data, bool maximized) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.maximized = maximized; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_fullscreen(struct weston_desktop_surface *dsurface, + void *user_data, bool fullscreen) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.fullscreen = fullscreen; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_resizing(struct weston_desktop_surface *dsurface, + void *user_data, bool resizing) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.resizing = resizing; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_activated(struct weston_desktop_surface *dsurface, + void *user_data, bool activated) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.activated = activated; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_size(struct weston_desktop_surface *dsurface, + void *user_data, + int32_t width, int32_t height) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.size.width = width; + toplevel->pending.size.height = height; + + 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) +{ + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(toplevel->base.desktop_surface); + + if (!weston_surface_has_content(wsurface) && !toplevel->added) { + weston_desktop_xdg_toplevel_ensure_added(toplevel); + return; + } + + if (!weston_surface_has_content(wsurface)) { + if (weston_surface_is_unmapping(wsurface)) + weston_desktop_api_committed(toplevel->base.desktop, + toplevel->base.desktop_surface, sx, sy); + return; + } + + struct weston_geometry geometry = + weston_desktop_surface_get_geometry(toplevel->base.desktop_surface); + + if ((toplevel->next.state.maximized || toplevel->next.state.fullscreen) && + (toplevel->next.size.width != geometry.width || + toplevel->next.size.height != geometry.height)) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(toplevel->base.desktop_surface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + ZXDG_SHELL_V6_ERROR_INVALID_SURFACE_STATE, + "xdg_surface buffer does not match the configured state"); + return; + } + + toplevel->current.state = toplevel->next.state; + toplevel->current.min_size = toplevel->next.min_size; + toplevel->current.max_size = toplevel->next.max_size; + + weston_desktop_api_committed(toplevel->base.desktop, + toplevel->base.desktop_surface, + sx, sy); +} + +static void +weston_desktop_xdg_toplevel_close(struct weston_desktop_xdg_toplevel *toplevel) +{ + zxdg_toplevel_v6_send_close(toplevel->resource); +} + +static bool +weston_desktop_xdg_toplevel_get_maximized(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.maximized; +} + +static bool +weston_desktop_xdg_toplevel_get_fullscreen(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.fullscreen; +} + +static bool +weston_desktop_xdg_toplevel_get_resizing(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.resizing; +} + +static bool +weston_desktop_xdg_toplevel_get_activated(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.activated; +} + +static bool +weston_desktop_xdg_toplevel_get_pending_maximized(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->pending.state.maximized; +} + +static bool +weston_desktop_xdg_toplevel_get_pending_fullscreen(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->pending.state.fullscreen; +} + +static bool +weston_desktop_xdg_toplevel_get_pending_resizing(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->pending.state.resizing; +} + +static bool +weston_desktop_xdg_toplevel_get_pending_activated(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->pending.state.activated; +} + +static void +weston_desktop_xdg_toplevel_destroy(struct weston_desktop_xdg_toplevel *toplevel) +{ + if (toplevel->added) + weston_desktop_api_surface_removed(toplevel->base.desktop, + toplevel->base.desktop_surface); +} + +static void +weston_desktop_xdg_toplevel_resource_destroy(struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + + if (dsurface != NULL) + weston_desktop_surface_resource_destroy(resource); +} + +static const struct zxdg_toplevel_v6_interface weston_desktop_xdg_toplevel_implementation = { + .destroy = weston_desktop_destroy_request, + .set_parent = weston_desktop_xdg_toplevel_protocol_set_parent, + .set_title = weston_desktop_xdg_toplevel_protocol_set_title, + .set_app_id = weston_desktop_xdg_toplevel_protocol_set_app_id, + .show_window_menu = weston_desktop_xdg_toplevel_protocol_show_window_menu, + .move = weston_desktop_xdg_toplevel_protocol_move, + .resize = weston_desktop_xdg_toplevel_protocol_resize, + .set_min_size = weston_desktop_xdg_toplevel_protocol_set_min_size, + .set_max_size = weston_desktop_xdg_toplevel_protocol_set_max_size, + .set_maximized = weston_desktop_xdg_toplevel_protocol_set_maximized, + .unset_maximized = weston_desktop_xdg_toplevel_protocol_unset_maximized, + .set_fullscreen = weston_desktop_xdg_toplevel_protocol_set_fullscreen, + .unset_fullscreen = weston_desktop_xdg_toplevel_protocol_unset_fullscreen, + .set_minimized = weston_desktop_xdg_toplevel_protocol_set_minimized, +}; + +static void +weston_desktop_xdg_popup_protocol_grab(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_desktop_xdg_popup *popup = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_seat *wseat = wl_resource_get_user_data(seat_resource); + struct weston_desktop_seat *seat = weston_desktop_seat_from_seat(wseat); + struct weston_desktop_surface *topmost; + bool parent_is_toplevel = + popup->parent->role == WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL; + + /* 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 (popup->committed) { + wl_resource_post_error(popup->resource, + ZXDG_POPUP_V6_ERROR_INVALID_GRAB, + "xdg_popup already is mapped"); + return; + } + + /* If seat is NULL then get_topmost_surface will return NULL. In + * combination with setting parent_is_toplevel to TRUE here we will + * avoid posting an error, and we will instead gracefully fail the + * grab and dismiss the surface. + * FIXME: this is a hack because currently we cannot check the topmost + * parent with a destroyed weston_seat */ + if (seat == NULL) + parent_is_toplevel = true; + + topmost = weston_desktop_seat_popup_grab_get_topmost_surface(seat); + if ((topmost == NULL && !parent_is_toplevel) || + (topmost != NULL && topmost != popup->parent->desktop_surface)) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(dsurface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + ZXDG_SHELL_V6_ERROR_NOT_THE_TOPMOST_POPUP, + "xdg_popup was not created on the topmost popup"); + return; + } + + popup->seat = seat; + weston_desktop_surface_popup_grab(popup->base.desktop_surface, + popup->parent->desktop_surface, + popup->seat, serial); +} + +static void +weston_desktop_xdg_popup_send_configure(struct weston_desktop_xdg_popup *popup) +{ + zxdg_popup_v6_send_configure(popup->resource, + popup->geometry.x, + popup->geometry.y, + popup->geometry.width, + popup->geometry.height); +} + +static void +weston_desktop_xdg_popup_update_position(struct weston_desktop_surface *dsurface, + void *user_data); + +static void +weston_desktop_xdg_popup_committed(struct weston_desktop_xdg_popup *popup) +{ + struct weston_surface *wsurface = + weston_desktop_surface_get_surface (popup->base.desktop_surface); + struct weston_view *view; + + wl_list_for_each(view, &wsurface->views, surface_link) + weston_view_update_transform(view); + + if (!popup->committed) + weston_desktop_xdg_surface_schedule_configure(&popup->base); + 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 +weston_desktop_xdg_popup_update_position(struct weston_desktop_surface *dsurface, + void *user_data) +{ +} + +static void +weston_desktop_xdg_popup_close(struct weston_desktop_xdg_popup *popup) +{ + zxdg_popup_v6_send_popup_done(popup->resource); +} + +static void +weston_desktop_xdg_popup_destroy(struct weston_desktop_xdg_popup *popup) +{ + struct weston_desktop_surface *topmost; + struct weston_desktop_client *client = + weston_desktop_surface_get_client(popup->base.desktop_surface); + + if (!weston_desktop_surface_get_grab(popup->base.desktop_surface)) + return; + + topmost = weston_desktop_seat_popup_grab_get_topmost_surface(popup->seat); + if (topmost != popup->base.desktop_surface) { + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + ZXDG_SHELL_V6_ERROR_NOT_THE_TOPMOST_POPUP, + "xdg_popup was destroyed while it was not the topmost popup."); + } + + weston_desktop_surface_popup_ungrab(popup->base.desktop_surface, + popup->seat); +} + +static void +weston_desktop_xdg_popup_resource_destroy(struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + + if (dsurface != NULL) + weston_desktop_surface_resource_destroy(resource); +} + +static const struct zxdg_popup_v6_interface weston_desktop_xdg_popup_implementation = { + .destroy = weston_desktop_destroy_request, + .grab = weston_desktop_xdg_popup_protocol_grab, +}; + +static void +weston_desktop_xdg_surface_send_configure(void *user_data) +{ + struct weston_desktop_xdg_surface *surface = user_data; + struct weston_desktop_xdg_surface_configure *configure; + + surface->configure_idle = NULL; + + configure = zalloc(weston_desktop_surface_configure_biggest_size); + if (configure == NULL) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(surface->desktop_surface); + struct wl_client *wl_client = + weston_desktop_client_get_client(client); + wl_client_post_no_memory(wl_client); + return; + } + wl_list_insert(surface->configure_list.prev, &configure->link); + configure->serial = + wl_display_next_serial(weston_desktop_get_display(surface->desktop)); + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_send_configure((struct weston_desktop_xdg_toplevel *) surface, + (struct weston_desktop_xdg_toplevel_configure *) configure); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_send_configure((struct weston_desktop_xdg_popup *) surface); + break; + } + + zxdg_surface_v6_send_configure(surface->resource, configure->serial); +} + +static bool +weston_desktop_xdg_toplevel_state_compare(struct weston_desktop_xdg_toplevel *toplevel) +{ + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; + } configured; + + if (!toplevel->base.configured) + return false; + + if (wl_list_empty(&toplevel->base.configure_list)) { + /* Last configure is actually the current state, just use it */ + configured.state = toplevel->current.state; + configured.size.width = toplevel->base.surface->width; + configured.size.height = toplevel->base.surface->height; + } else { + struct weston_desktop_xdg_toplevel_configure *configure = + wl_container_of(toplevel->base.configure_list.prev, + configure, base.link); + + configured.state = configure->state; + configured.size = configure->size; + } + + if (toplevel->pending.state.activated != configured.state.activated) + return false; + if (toplevel->pending.state.fullscreen != configured.state.fullscreen) + return false; + if (toplevel->pending.state.maximized != configured.state.maximized) + return false; + if (toplevel->pending.state.resizing != configured.state.resizing) + return false; + + if (toplevel->pending.size.width == configured.size.width && + toplevel->pending.size.height == configured.size.height) + return true; + + if (toplevel->pending.size.width == 0 && + toplevel->pending.size.height == 0) + return true; + + return false; +} + +static void +weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface) +{ + struct wl_display *display = weston_desktop_get_display(surface->desktop); + struct wl_event_loop *loop = wl_display_get_event_loop(display); + bool pending_same = false; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + pending_same = weston_desktop_xdg_toplevel_state_compare((struct weston_desktop_xdg_toplevel *) surface); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + break; + } + + if (surface->configure_idle != NULL) { + if (!pending_same) + return; + + wl_event_source_remove(surface->configure_idle); + surface->configure_idle = NULL; + } else { + if (pending_same) + return; + + surface->configure_idle = + wl_event_loop_add_idle(loop, + weston_desktop_xdg_surface_send_configure, + surface); + } +} + +static void +weston_desktop_xdg_surface_protocol_get_toplevel(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(dsurface); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + if (weston_surface_set_role(wsurface, weston_desktop_xdg_toplevel_role, + resource, ZXDG_SHELL_V6_ERROR_ROLE) < 0) + return; + + toplevel->resource = + weston_desktop_surface_add_resource(toplevel->base.desktop_surface, + &zxdg_toplevel_v6_interface, + &weston_desktop_xdg_toplevel_implementation, + id, weston_desktop_xdg_toplevel_resource_destroy); + if (toplevel->resource == NULL) + return; + + toplevel->base.role = WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL; +} + +static void +weston_desktop_xdg_surface_protocol_get_popup(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *parent_resource, + struct wl_resource *positioner_resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(dsurface); + struct weston_desktop_xdg_popup *popup = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop_surface *parent_surface = + wl_resource_get_user_data(parent_resource); + struct weston_desktop_xdg_surface *parent = + weston_desktop_surface_get_implementation_data(parent_surface); + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(positioner_resource); + + /* Checking whether the size and anchor rect both have a positive size + * is enough to verify both have been correctly set */ + if (positioner->size.width == 0 || positioner->anchor_rect.width == 0) { + wl_resource_post_error(resource, + ZXDG_SHELL_V6_ERROR_INVALID_POSITIONER, + "positioner object is not complete"); + return; + } + + if (weston_surface_set_role(wsurface, weston_desktop_xdg_popup_role, + resource, ZXDG_SHELL_V6_ERROR_ROLE) < 0) + return; + + popup->resource = + weston_desktop_surface_add_resource(popup->base.desktop_surface, + &zxdg_popup_v6_interface, + &weston_desktop_xdg_popup_implementation, + id, weston_desktop_xdg_popup_resource_destroy); + if (popup->resource == NULL) + return; + + popup->base.role = WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP; + popup->parent = parent; + + popup->geometry = + weston_desktop_xdg_positioner_get_geometry(positioner, + dsurface, + parent_surface); + + weston_desktop_surface_set_relative_to(popup->base.desktop_surface, + parent_surface, + popup->geometry.x, + popup->geometry.y, + true); +} + +static bool +weston_desktop_xdg_surface_check_role(struct weston_desktop_xdg_surface *surface) +{ + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(surface->desktop_surface); + const char *role; + + role = weston_surface_get_role(wsurface); + if (role != NULL && + (role == weston_desktop_xdg_toplevel_role || + role == weston_desktop_xdg_popup_role)) + return true; + + wl_resource_post_error(surface->resource, + ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, + "xdg_surface must have a role"); + return false; +} + +static void +weston_desktop_xdg_surface_protocol_set_window_geometry(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t x, int32_t y, + int32_t width, int32_t height) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_surface *surface = + weston_desktop_surface_get_implementation_data(dsurface); + + if (!weston_desktop_xdg_surface_check_role(surface)) + return; + + surface->has_next_geometry = true; + surface->next_geometry.x = x; + surface->next_geometry.y = y; + surface->next_geometry.width = width; + surface->next_geometry.height = height; +} + +static void +weston_desktop_xdg_surface_protocol_ack_configure(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t serial) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_surface *surface = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop_xdg_surface_configure *configure, *temp; + bool found = false; + + if (!weston_desktop_xdg_surface_check_role(surface)) + return; + + wl_list_for_each_safe(configure, temp, &surface->configure_list, link) { + if (configure->serial < serial) { + wl_list_remove(&configure->link); + free(configure); + } else if (configure->serial == serial) { + wl_list_remove(&configure->link); + found = true; + break; + } else { + break; + } + } + if (!found) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(dsurface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + wl_resource_post_error(client_resource, + ZXDG_SHELL_V6_ERROR_INVALID_SURFACE_STATE, + "Wrong configure serial: %u", serial); + return; + } + + surface->configured = true; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_ack_configure((struct weston_desktop_xdg_toplevel *) surface, + (struct weston_desktop_xdg_toplevel_configure *) configure); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + break; + } + + free(configure); +} + +static void +weston_desktop_xdg_surface_ping(struct weston_desktop_surface *dsurface, + uint32_t serial, void *user_data) +{ + struct weston_desktop_client *client = + weston_desktop_surface_get_client(dsurface); + + zxdg_shell_v6_send_ping(weston_desktop_client_get_resource(client), + serial); +} + +static void +weston_desktop_xdg_surface_committed(struct weston_desktop_surface *dsurface, + void *user_data, + int32_t sx, int32_t sy) +{ + struct weston_desktop_xdg_surface *surface = user_data; + struct weston_surface *wsurface = + weston_desktop_surface_get_surface (dsurface); + + 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"); + return; + } + + if (surface->has_next_geometry) { + surface->has_next_geometry = false; + weston_desktop_surface_set_geometry(surface->desktop_surface, + surface->next_geometry); + } + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + wl_resource_post_error(surface->resource, + ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED, + "xdg_surface must have a role"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_committed((struct weston_desktop_xdg_toplevel *) surface, sx, sy); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_committed((struct weston_desktop_xdg_popup *) surface); + break; + } +} + +static void +weston_desktop_xdg_surface_close(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_surface *surface = user_data; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_close((struct weston_desktop_xdg_toplevel *) surface); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_close((struct weston_desktop_xdg_popup *) surface); + break; + } +} + +static void +weston_desktop_xdg_surface_destroy(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_surface *surface = user_data; + struct weston_desktop_xdg_surface_configure *configure, *temp; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_destroy((struct weston_desktop_xdg_toplevel *) surface); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_destroy((struct weston_desktop_xdg_popup *) surface); + break; + } + + if (surface->configure_idle != NULL) + wl_event_source_remove(surface->configure_idle); + + wl_list_for_each_safe(configure, temp, &surface->configure_list, link) + free(configure); + + free(surface); +} + +static const struct zxdg_surface_v6_interface weston_desktop_xdg_surface_implementation = { + .destroy = weston_desktop_destroy_request, + .get_toplevel = weston_desktop_xdg_surface_protocol_get_toplevel, + .get_popup = weston_desktop_xdg_surface_protocol_get_popup, + .set_window_geometry = weston_desktop_xdg_surface_protocol_set_window_geometry, + .ack_configure = weston_desktop_xdg_surface_protocol_ack_configure, +}; + +static const struct weston_desktop_surface_implementation weston_desktop_xdg_surface_internal_implementation = { + /* These are used for toplevel only */ + .set_maximized = weston_desktop_xdg_toplevel_set_maximized, + .set_fullscreen = weston_desktop_xdg_toplevel_set_fullscreen, + .set_resizing = weston_desktop_xdg_toplevel_set_resizing, + .set_activated = weston_desktop_xdg_toplevel_set_activated, + .set_size = weston_desktop_xdg_toplevel_set_size, + + .get_maximized = weston_desktop_xdg_toplevel_get_maximized, + .get_fullscreen = weston_desktop_xdg_toplevel_get_fullscreen, + .get_resizing = weston_desktop_xdg_toplevel_get_resizing, + .get_activated = weston_desktop_xdg_toplevel_get_activated, + + .get_pending_maximized = weston_desktop_xdg_toplevel_get_pending_maximized, + .get_pending_fullscreen = weston_desktop_xdg_toplevel_get_pending_fullscreen, + .get_pending_resizing = weston_desktop_xdg_toplevel_get_pending_resizing, + .get_pending_activated = weston_desktop_xdg_toplevel_get_pending_activated, + + /* These are used for popup only */ + .update_position = weston_desktop_xdg_popup_update_position, + + /* Common API */ + .committed = weston_desktop_xdg_surface_committed, + .ping = weston_desktop_xdg_surface_ping, + .close = weston_desktop_xdg_surface_close, + + .destroy = weston_desktop_xdg_surface_destroy, +}; + +static void +weston_desktop_xdg_shell_protocol_create_positioner(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id) +{ + struct weston_desktop_client *client = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_positioner *positioner; + + positioner = zalloc(sizeof(struct weston_desktop_xdg_positioner)); + if (positioner == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + positioner->client = client; + positioner->desktop = weston_desktop_client_get_desktop(positioner->client); + + positioner->resource = + wl_resource_create(wl_client, + &zxdg_positioner_v6_interface, + wl_resource_get_version(resource), id); + if (positioner->resource == NULL) { + wl_client_post_no_memory(wl_client); + free(positioner); + return; + } + wl_resource_set_implementation(positioner->resource, + &weston_desktop_xdg_positioner_implementation, + positioner, weston_desktop_xdg_positioner_destroy); +} + +static void +weston_desktop_xdg_surface_resource_destroy(struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + + if (dsurface != NULL) + weston_desktop_surface_resource_destroy(resource); +} + +static void +weston_desktop_xdg_shell_protocol_get_xdg_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_xdg_surface *surface; + + surface = zalloc(weston_desktop_surface_role_biggest_size); + if (surface == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + surface->desktop = weston_desktop_client_get_desktop(client); + surface->surface = wsurface; + wl_list_init(&surface->configure_list); + + surface->desktop_surface = + weston_desktop_surface_create(surface->desktop, client, + surface->surface, + &weston_desktop_xdg_surface_internal_implementation, + surface); + if (surface->desktop_surface == NULL) { + free(surface); + return; + } + + surface->resource = + weston_desktop_surface_add_resource(surface->desktop_surface, + &zxdg_surface_v6_interface, + &weston_desktop_xdg_surface_implementation, + id, weston_desktop_xdg_surface_resource_destroy); + if (surface->resource == NULL) + return; + + 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"); + return; + } +} + +static void +weston_desktop_xdg_shell_protocol_pong(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t serial) +{ + struct weston_desktop_client *client = + wl_resource_get_user_data(resource); + + weston_desktop_client_pong(client, serial); +} + +static const struct zxdg_shell_v6_interface weston_desktop_xdg_shell_implementation = { + .destroy = weston_desktop_destroy_request, + .create_positioner = weston_desktop_xdg_shell_protocol_create_positioner, + .get_xdg_surface = weston_desktop_xdg_shell_protocol_get_xdg_surface, + .pong = weston_desktop_xdg_shell_protocol_pong, +}; + +static void +weston_desktop_xdg_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, + &zxdg_shell_v6_interface, + &weston_desktop_xdg_shell_implementation, + version, id); +} + +struct wl_global * +weston_desktop_xdg_shell_v6_create(struct weston_desktop *desktop, struct wl_display *display) +{ + return wl_global_create(display, &zxdg_shell_v6_interface, + WD_XDG_SHELL_PROTOCOL_VERSION, desktop, + weston_desktop_xdg_shell_bind); +} diff --git a/libweston/desktop/xdg-shell.c b/libweston/desktop/xdg-shell.c new file mode 100644 index 000000000..fb8536ac4 --- /dev/null +++ b/libweston/desktop/xdg-shell.c @@ -0,0 +1,1718 @@ +/* + * 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 "xdg-shell-server-protocol.h" + +#include +#include "internal.h" +#include "shared/helpers.h" + +/************************************************************************************ + * WARNING: This file implements the stable xdg shell protocol. + * Any changes to this file may also need to be added to the xdg-shell-v6.c file which + * implements the older unstable xdg shell v6 protocol. + ************************************************************************************/ + +#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"; + +struct weston_desktop_xdg_positioner { + struct weston_desktop *desktop; + struct weston_desktop_client *client; + struct wl_resource *resource; + + struct weston_size size; + struct weston_geometry anchor_rect; + enum xdg_positioner_anchor anchor; + enum xdg_positioner_gravity gravity; + enum xdg_positioner_constraint_adjustment constraint_adjustment; + struct weston_position offset; +}; + +enum weston_desktop_xdg_surface_role { + WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE, + WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL, + WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP, +}; + +struct weston_desktop_xdg_surface { + struct wl_resource *resource; + struct weston_desktop *desktop; + struct weston_surface *surface; + struct weston_desktop_surface *desktop_surface; + bool configured; + struct wl_event_source *configure_idle; + struct wl_list configure_list; /* weston_desktop_xdg_surface_configure::link */ + + bool has_next_geometry; + struct weston_geometry next_geometry; + + enum weston_desktop_xdg_surface_role role; +}; + +struct weston_desktop_xdg_surface_configure { + struct wl_list link; /* weston_desktop_xdg_surface::configure_list */ + uint32_t serial; +}; + +struct weston_desktop_xdg_toplevel_state { + bool maximized; + bool fullscreen; + bool resizing; + bool activated; + uint32_t tiled_orientation; +}; + +struct weston_desktop_xdg_toplevel_configure { + struct weston_desktop_xdg_surface_configure base; + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; +}; + +struct weston_desktop_xdg_toplevel { + struct weston_desktop_xdg_surface base; + + struct wl_resource *resource; + bool added; + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; + } pending; + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; + struct weston_size min_size, max_size; + } next; + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size min_size, max_size; + } current; +}; + +struct weston_desktop_xdg_popup { + struct weston_desktop_xdg_surface base; + + struct wl_resource *resource; + bool committed; + struct weston_desktop_xdg_surface *parent; + struct weston_desktop_seat *seat; + struct weston_geometry geometry; + + bool pending_reposition; + uint32_t pending_reposition_token; +}; + +#define weston_desktop_surface_role_biggest_size \ + MAX(sizeof(struct weston_desktop_xdg_toplevel), \ + sizeof(struct weston_desktop_xdg_popup)) +#define weston_desktop_surface_configure_biggest_size weston_desktop_surface_role_biggest_size + + +static struct weston_geometry +weston_desktop_xdg_positioner_get_geometry(struct weston_desktop_xdg_positioner *positioner, + struct weston_desktop_surface *dsurface, + struct weston_desktop_surface *parent) +{ + struct weston_geometry geometry = { + .x = positioner->offset.x, + .y = positioner->offset.y, + .width = positioner->size.width, + .height = positioner->size.height, + }; + + switch (positioner->anchor) { + case XDG_POSITIONER_ANCHOR_TOP: + case XDG_POSITIONER_ANCHOR_TOP_LEFT: + case XDG_POSITIONER_ANCHOR_TOP_RIGHT: + geometry.y += positioner->anchor_rect.y; + break; + case XDG_POSITIONER_ANCHOR_BOTTOM: + case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT: + case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT: + geometry.y += positioner->anchor_rect.y + positioner->anchor_rect.height; + break; + default: + geometry.y += positioner->anchor_rect.y + positioner->anchor_rect.height / 2; + } + + switch (positioner->anchor) { + case XDG_POSITIONER_ANCHOR_LEFT: + case XDG_POSITIONER_ANCHOR_TOP_LEFT: + case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT: + geometry.x += positioner->anchor_rect.x; + break; + case XDG_POSITIONER_ANCHOR_RIGHT: + case XDG_POSITIONER_ANCHOR_TOP_RIGHT: + case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT: + geometry.x += positioner->anchor_rect.x + positioner->anchor_rect.width; + break; + default: + geometry.x += positioner->anchor_rect.x + positioner->anchor_rect.width / 2; + } + + switch (positioner->gravity) { + case XDG_POSITIONER_GRAVITY_TOP: + case XDG_POSITIONER_GRAVITY_TOP_LEFT: + case XDG_POSITIONER_GRAVITY_TOP_RIGHT: + geometry.y -= geometry.height; + break; + case XDG_POSITIONER_GRAVITY_BOTTOM: + case XDG_POSITIONER_GRAVITY_BOTTOM_LEFT: + case XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT: + geometry.y = geometry.y; + break; + default: + geometry.y -= geometry.height / 2; + } + + switch (positioner->gravity) { + case XDG_POSITIONER_GRAVITY_LEFT: + case XDG_POSITIONER_GRAVITY_TOP_LEFT: + case XDG_POSITIONER_GRAVITY_BOTTOM_LEFT: + geometry.x -= geometry.width; + break; + case XDG_POSITIONER_GRAVITY_RIGHT: + case XDG_POSITIONER_GRAVITY_TOP_RIGHT: + case XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT: + geometry.x = geometry.x; + break; + default: + geometry.x -= geometry.width / 2; + } + + if (positioner->constraint_adjustment == XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE) + return geometry; + + /* TODO: add compositor policy configuration and the code here */ + + return geometry; +} + +static void +weston_desktop_xdg_positioner_protocol_set_size(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t width, int32_t height) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + if (width < 1 || height < 1) { + wl_resource_post_error(resource, + XDG_POSITIONER_ERROR_INVALID_INPUT, + "width and height must be positives and non-zero"); + return; + } + + positioner->size.width = width; + positioner->size.height = height; +} + +static void +weston_desktop_xdg_positioner_protocol_set_anchor_rect(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t x, int32_t y, + int32_t width, int32_t height) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + if (width < 0 || height < 0) { + wl_resource_post_error(resource, + XDG_POSITIONER_ERROR_INVALID_INPUT, + "width and height must be non-negative"); + return; + } + + positioner->anchor_rect.x = x; + positioner->anchor_rect.y = y; + positioner->anchor_rect.width = width; + positioner->anchor_rect.height = height; +} + +static void +weston_desktop_xdg_positioner_protocol_set_anchor(struct wl_client *wl_client, + struct wl_resource *resource, + enum xdg_positioner_anchor anchor) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + positioner->anchor = anchor; +} + +static void +weston_desktop_xdg_positioner_protocol_set_gravity(struct wl_client *wl_client, + struct wl_resource *resource, + enum xdg_positioner_gravity gravity) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + positioner->gravity = gravity; +} + +static void +weston_desktop_xdg_positioner_protocol_set_constraint_adjustment(struct wl_client *wl_client, + struct wl_resource *resource, + enum xdg_positioner_constraint_adjustment constraint_adjustment) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + positioner->constraint_adjustment = constraint_adjustment; +} + +static void +weston_desktop_xdg_positioner_protocol_set_offset(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t x, int32_t y) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + positioner->offset.x = x; + positioner->offset.y = y; +} + +static void +weston_desktop_xdg_positioner_protocol_set_reactive(struct wl_client *wl_client, + struct wl_resource *resource) +{ +} + +static void +weston_desktop_xdg_positioner_protocol_set_parent_size(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t width, + int32_t height) +{ +} + +static void +weston_desktop_xdg_positioner_protocol_set_parent_configure(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t serial) +{ +} + +static void +weston_desktop_xdg_positioner_destroy(struct wl_resource *resource) +{ + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(resource); + + free(positioner); +} + +static const struct xdg_positioner_interface weston_desktop_xdg_positioner_implementation = { + .destroy = weston_desktop_destroy_request, + .set_size = weston_desktop_xdg_positioner_protocol_set_size, + .set_anchor_rect = weston_desktop_xdg_positioner_protocol_set_anchor_rect, + .set_anchor = weston_desktop_xdg_positioner_protocol_set_anchor, + .set_gravity = weston_desktop_xdg_positioner_protocol_set_gravity, + .set_constraint_adjustment = weston_desktop_xdg_positioner_protocol_set_constraint_adjustment, + .set_offset = weston_desktop_xdg_positioner_protocol_set_offset, + .set_reactive = weston_desktop_xdg_positioner_protocol_set_reactive, + .set_parent_size = weston_desktop_xdg_positioner_protocol_set_parent_size, + .set_parent_configure = weston_desktop_xdg_positioner_protocol_set_parent_configure, +}; + +static void +weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface); + +static void +weston_desktop_xdg_toplevel_ensure_added(struct weston_desktop_xdg_toplevel *toplevel) +{ + if (toplevel->added) + return; + + weston_desktop_api_surface_added(toplevel->base.desktop, + toplevel->base.desktop_surface); + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); + toplevel->added = true; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_parent(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *parent_resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop_surface *parent = NULL; + + if (parent_resource != NULL) + parent = wl_resource_get_user_data(parent_resource); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_set_parent(toplevel->base.desktop, dsurface, parent); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_title(struct wl_client *wl_client, + struct wl_resource *resource, + const char *title) +{ + struct weston_desktop_surface *toplevel = + wl_resource_get_user_data(resource); + + weston_desktop_surface_set_title(toplevel, title); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_app_id(struct wl_client *wl_client, + struct wl_resource *resource, + const char *app_id) +{ + struct weston_desktop_surface *toplevel = + wl_resource_get_user_data(resource); + + weston_desktop_surface_set_app_id(toplevel, app_id); +} + +static void +weston_desktop_xdg_toplevel_protocol_show_window_menu(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial, + int32_t x, int32_t y) +{ + 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_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + if (!toplevel->base.configured) { + wl_resource_post_error(toplevel->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "Surface has not been configured yet"); + return; + } + + if (seat == NULL) + return; + + weston_desktop_api_show_window_menu(toplevel->base.desktop, + dsurface, seat, x, y); +} + +static void +weston_desktop_xdg_toplevel_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_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + if (!toplevel->base.configured) { + wl_resource_post_error(toplevel->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "Surface has not been configured yet"); + return; + } + + if (seat == NULL) + return; + + weston_desktop_api_move(toplevel->base.desktop, dsurface, seat, serial); +} + +static void +weston_desktop_xdg_toplevel_protocol_resize(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *seat_resource, + uint32_t serial, + enum xdg_toplevel_resize_edge 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_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + enum weston_desktop_surface_edge surf_edges = + (enum weston_desktop_surface_edge) edges; + + if (!toplevel->base.configured) { + wl_resource_post_error(toplevel->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "Surface has not been configured yet"); + return; + } + + if (seat == NULL) + return; + + weston_desktop_api_resize(toplevel->base.desktop, + dsurface, seat, serial, surf_edges); +} + +static void +weston_desktop_xdg_toplevel_ack_configure(struct weston_desktop_xdg_toplevel *toplevel, + struct weston_desktop_xdg_toplevel_configure *configure) +{ + toplevel->next.state = configure->state; + toplevel->next.size = configure->size; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_min_size(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t width, int32_t height) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + toplevel->next.min_size.width = width; + toplevel->next.min_size.height = height; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_max_size(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t width, int32_t height) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + toplevel->next.max_size.width = width; + toplevel->next.max_size.height = height; +} + +static void +weston_desktop_xdg_toplevel_protocol_set_maximized(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_maximized_requested(toplevel->base.desktop, dsurface, true); +} + +static void +weston_desktop_xdg_toplevel_protocol_unset_maximized(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_maximized_requested(toplevel->base.desktop, dsurface, false); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_fullscreen(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_xdg_toplevel *toplevel = + 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_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_fullscreen_requested(toplevel->base.desktop, dsurface, + true, output); +} + +static void +weston_desktop_xdg_toplevel_protocol_unset_fullscreen(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_fullscreen_requested(toplevel->base.desktop, dsurface, + false, NULL); +} + +static void +weston_desktop_xdg_toplevel_protocol_set_minimized(struct wl_client *wl_client, + struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_toplevel *toplevel = + weston_desktop_surface_get_implementation_data(dsurface); + + weston_desktop_xdg_toplevel_ensure_added(toplevel); + weston_desktop_api_minimized_requested(toplevel->base.desktop, dsurface); +} + +static void +weston_desktop_xdg_toplevel_send_configure(struct weston_desktop_xdg_toplevel *toplevel, + struct weston_desktop_xdg_toplevel_configure *configure) +{ + uint32_t *s; + struct wl_array states; + + configure->state = toplevel->pending.state; + configure->size = toplevel->pending.size; + + wl_array_init(&states); + if (toplevel->pending.state.maximized) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = XDG_TOPLEVEL_STATE_MAXIMIZED; + } + if (toplevel->pending.state.fullscreen) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = XDG_TOPLEVEL_STATE_FULLSCREEN; + } + if (toplevel->pending.state.resizing) { + s = wl_array_add(&states, sizeof(uint32_t)); + *s = XDG_TOPLEVEL_STATE_RESIZING; + } + if (toplevel->pending.state.activated) { + s = wl_array_add(&states, sizeof(uint32_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, + &states); + + wl_array_release(&states); +}; + +static void +weston_desktop_xdg_toplevel_set_maximized(struct weston_desktop_surface *dsurface, + void *user_data, bool maximized) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.maximized = maximized; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_fullscreen(struct weston_desktop_surface *dsurface, + void *user_data, bool fullscreen) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.fullscreen = fullscreen; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_resizing(struct weston_desktop_surface *dsurface, + void *user_data, bool resizing) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.resizing = resizing; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_activated(struct weston_desktop_surface *dsurface, + void *user_data, bool activated) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.state.activated = activated; + weston_desktop_xdg_surface_schedule_configure(&toplevel->base); +} + +static void +weston_desktop_xdg_toplevel_set_size(struct weston_desktop_surface *dsurface, + void *user_data, + int32_t width, int32_t height) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + toplevel->pending.size.width = width; + toplevel->pending.size.height = height; + + 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) +{ + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(toplevel->base.desktop_surface); + + if (!weston_surface_has_content(wsurface) && !toplevel->added) { + weston_desktop_xdg_toplevel_ensure_added(toplevel); + return; + } + + if (!weston_surface_has_content(wsurface)) { + if (weston_surface_is_unmapping(wsurface)) + weston_desktop_api_committed(toplevel->base.desktop, + toplevel->base.desktop_surface, sx, sy); + return; + } + + struct weston_geometry geometry = + weston_desktop_surface_get_geometry(toplevel->base.desktop_surface); + + if (toplevel->next.state.maximized && + (toplevel->next.size.width != geometry.width || + toplevel->next.size.height != geometry.height)) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(toplevel->base.desktop_surface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE, + "xdg_surface geometry (%" PRIi32 " x %" PRIi32 ") " + "does not match the configured maximized state (%" PRIi32 " x %" PRIi32 ")", + geometry.width, geometry.height, + toplevel->next.size.width, + toplevel->next.size.height); + return; + } + + if (toplevel->next.state.fullscreen && + (toplevel->next.size.width < geometry.width || + toplevel->next.size.height < geometry.height)) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(toplevel->base.desktop_surface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE, + "xdg_surface geometry (%" PRIi32 " x %" PRIi32 ") " + "is larger than the configured fullscreen state (%" PRIi32 " x %" PRIi32 ")", + geometry.width, geometry.height, + toplevel->next.size.width, + toplevel->next.size.height); + return; + } + + toplevel->current.state = toplevel->next.state; + toplevel->current.min_size = toplevel->next.min_size; + toplevel->current.max_size = toplevel->next.max_size; + + weston_desktop_api_committed(toplevel->base.desktop, + toplevel->base.desktop_surface, + sx, sy); +} + +static void +weston_desktop_xdg_toplevel_close(struct weston_desktop_xdg_toplevel *toplevel) +{ + xdg_toplevel_send_close(toplevel->resource); +} + +static bool +weston_desktop_xdg_toplevel_get_maximized(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.maximized; +} + +static bool +weston_desktop_xdg_toplevel_get_fullscreen(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.fullscreen; +} + +static bool +weston_desktop_xdg_toplevel_get_resizing(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.resizing; +} + +static bool +weston_desktop_xdg_toplevel_get_activated(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->current.state.activated; +} + +static bool +weston_desktop_xdg_toplevel_get_pending_maximized(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->pending.state.maximized; +} + +static bool +weston_desktop_xdg_toplevel_get_pending_fullscreen(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->pending.state.fullscreen; +} + +static bool +weston_desktop_xdg_toplevel_get_pending_resizing(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->pending.state.resizing; +} + +static bool +weston_desktop_xdg_toplevel_get_pending_activated(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_toplevel *toplevel = user_data; + + return toplevel->pending.state.activated; +} + +static void +weston_desktop_xdg_toplevel_destroy(struct weston_desktop_xdg_toplevel *toplevel) +{ + if (toplevel->added) + weston_desktop_api_surface_removed(toplevel->base.desktop, + toplevel->base.desktop_surface); +} + +static void +weston_desktop_xdg_toplevel_resource_destroy(struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + + if (dsurface != NULL) + weston_desktop_surface_resource_destroy(resource); +} + +static const struct xdg_toplevel_interface weston_desktop_xdg_toplevel_implementation = { + .destroy = weston_desktop_destroy_request, + .set_parent = weston_desktop_xdg_toplevel_protocol_set_parent, + .set_title = weston_desktop_xdg_toplevel_protocol_set_title, + .set_app_id = weston_desktop_xdg_toplevel_protocol_set_app_id, + .show_window_menu = weston_desktop_xdg_toplevel_protocol_show_window_menu, + .move = weston_desktop_xdg_toplevel_protocol_move, + .resize = weston_desktop_xdg_toplevel_protocol_resize, + .set_min_size = weston_desktop_xdg_toplevel_protocol_set_min_size, + .set_max_size = weston_desktop_xdg_toplevel_protocol_set_max_size, + .set_maximized = weston_desktop_xdg_toplevel_protocol_set_maximized, + .unset_maximized = weston_desktop_xdg_toplevel_protocol_unset_maximized, + .set_fullscreen = weston_desktop_xdg_toplevel_protocol_set_fullscreen, + .unset_fullscreen = weston_desktop_xdg_toplevel_protocol_unset_fullscreen, + .set_minimized = weston_desktop_xdg_toplevel_protocol_set_minimized, +}; + +static void +weston_desktop_xdg_popup_protocol_grab(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_desktop_xdg_popup *popup = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_seat *wseat = wl_resource_get_user_data(seat_resource); + struct weston_desktop_seat *seat = weston_desktop_seat_from_seat(wseat); + struct weston_desktop_surface *topmost; + bool parent_is_toplevel = + popup->parent->role == WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL; + + /* 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 (popup->committed) { + wl_resource_post_error(popup->resource, + XDG_POPUP_ERROR_INVALID_GRAB, + "xdg_popup already is mapped"); + return; + } + + /* If seat is NULL then get_topmost_surface will return NULL. In + * combination with setting parent_is_toplevel to TRUE here we will + * avoid posting an error, and we will instead gracefully fail the + * grab and dismiss the surface. + * FIXME: this is a hack because currently we cannot check the topmost + * parent with a destroyed weston_seat */ + if (seat == NULL) + parent_is_toplevel = true; + + topmost = weston_desktop_seat_popup_grab_get_topmost_surface(seat); + if ((topmost == NULL && !parent_is_toplevel) || + (topmost != NULL && topmost != popup->parent->desktop_surface)) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(dsurface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP, + "xdg_popup was not created on the topmost popup"); + return; + } + + popup->seat = seat; + weston_desktop_surface_popup_grab(popup->base.desktop_surface, + popup->parent->desktop_surface, + popup->seat, serial); +} + +static bool +is_positioner_valid(struct weston_desktop_xdg_positioner *positioner) +{ + /* Checking whether the size and anchor rect both have a positive size + * is enough to verify both have been correctly set */ + if (positioner->size.width == 0 || positioner->anchor_rect.width == 0) + return false; + + if (positioner->anchor_rect.height == 0) + return false; + + return true; +} + +static void +weston_desktop_xdg_popup_protocol_reposition(struct wl_client *wl_client, + struct wl_resource *resource, + struct wl_resource *positioner_resource, + uint32_t token) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_popup *popup = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(positioner_resource); + struct weston_desktop_surface *parent_dsurface; + + if (!is_positioner_valid(positioner)) { + wl_resource_post_error(resource, + XDG_WM_BASE_ERROR_INVALID_POSITIONER, + "positioner object is not complete"); + return; + } + + parent_dsurface = popup->parent->desktop_surface; + popup->geometry = + weston_desktop_xdg_positioner_get_geometry(positioner, + dsurface, + parent_dsurface); + popup->pending_reposition = true; + popup->pending_reposition_token = token; + if (popup->committed) + weston_desktop_xdg_surface_schedule_configure(&popup->base); +} + +static void +weston_desktop_xdg_popup_send_configure(struct weston_desktop_xdg_popup *popup) +{ + if (popup->pending_reposition) { + popup->pending_reposition = false; + xdg_popup_send_repositioned(popup->resource, + popup->pending_reposition_token); + } + xdg_popup_send_configure(popup->resource, + popup->geometry.x, + popup->geometry.y, + popup->geometry.width, + popup->geometry.height); +} + +static void +weston_desktop_xdg_popup_update_position(struct weston_desktop_surface *dsurface, + void *user_data); + +static void +weston_desktop_xdg_popup_committed(struct weston_desktop_xdg_popup *popup) +{ + struct weston_surface *wsurface = + weston_desktop_surface_get_surface (popup->base.desktop_surface); + struct weston_view *view; + + wl_list_for_each(view, &wsurface->views, surface_link) + weston_view_update_transform(view); + + if (!popup->committed) + weston_desktop_xdg_surface_schedule_configure(&popup->base); + 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 +weston_desktop_xdg_popup_update_position(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_popup *popup = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop_surface *parent_dsurface; + + parent_dsurface = popup->parent->desktop_surface; + weston_desktop_surface_set_relative_to(popup->base.desktop_surface, + parent_dsurface, + popup->geometry.x, + popup->geometry.y, + true); +} + +static void +weston_desktop_xdg_popup_close(struct weston_desktop_xdg_popup *popup) +{ + xdg_popup_send_popup_done(popup->resource); +} + +static void +weston_desktop_xdg_popup_destroy(struct weston_desktop_xdg_popup *popup) +{ + struct weston_desktop_surface *topmost; + struct weston_desktop_client *client = + weston_desktop_surface_get_client(popup->base.desktop_surface); + + if (!weston_desktop_surface_get_grab(popup->base.desktop_surface)) + return; + + topmost = weston_desktop_seat_popup_grab_get_topmost_surface(popup->seat); + if (topmost != popup->base.desktop_surface) { + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + + wl_resource_post_error(client_resource, + XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP, + "xdg_popup was destroyed while it was not the topmost popup."); + } + + weston_desktop_surface_popup_ungrab(popup->base.desktop_surface, + popup->seat); +} + +static void +weston_desktop_xdg_popup_resource_destroy(struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + + if (dsurface != NULL) + weston_desktop_surface_resource_destroy(resource); +} + +static const struct xdg_popup_interface weston_desktop_xdg_popup_implementation = { + .destroy = weston_desktop_destroy_request, + .grab = weston_desktop_xdg_popup_protocol_grab, + .reposition = weston_desktop_xdg_popup_protocol_reposition, +}; + +static void +weston_desktop_xdg_surface_send_configure(void *user_data) +{ + struct weston_desktop_xdg_surface *surface = user_data; + struct weston_desktop_xdg_surface_configure *configure; + + surface->configure_idle = NULL; + + configure = zalloc(weston_desktop_surface_configure_biggest_size); + if (configure == NULL) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(surface->desktop_surface); + struct wl_client *wl_client = + weston_desktop_client_get_client(client); + wl_client_post_no_memory(wl_client); + return; + } + wl_list_insert(surface->configure_list.prev, &configure->link); + configure->serial = + wl_display_next_serial(weston_desktop_get_display(surface->desktop)); + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_send_configure((struct weston_desktop_xdg_toplevel *) surface, + (struct weston_desktop_xdg_toplevel_configure *) configure); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_send_configure((struct weston_desktop_xdg_popup *) surface); + break; + } + + xdg_surface_send_configure(surface->resource, configure->serial); +} + +static bool +weston_desktop_xdg_toplevel_state_compare(struct weston_desktop_xdg_toplevel *toplevel) +{ + struct { + struct weston_desktop_xdg_toplevel_state state; + struct weston_size size; + } configured; + + if (!toplevel->base.configured) + return false; + + if (wl_list_empty(&toplevel->base.configure_list)) { + /* Last configure is actually the current state, just use it */ + configured.state = toplevel->current.state; + configured.size.width = toplevel->base.surface->width; + configured.size.height = toplevel->base.surface->height; + } else { + struct weston_desktop_xdg_toplevel_configure *configure = + wl_container_of(toplevel->base.configure_list.prev, + configure, base.link); + + configured.state = configure->state; + configured.size = configure->size; + } + + if (toplevel->pending.state.activated != configured.state.activated) + return false; + if (toplevel->pending.state.fullscreen != configured.state.fullscreen) + return false; + if (toplevel->pending.state.maximized != configured.state.maximized) + 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) + return true; + + if (toplevel->pending.size.width == 0 && + toplevel->pending.size.height == 0) + return true; + + return false; +} + +static void +weston_desktop_xdg_surface_schedule_configure(struct weston_desktop_xdg_surface *surface) +{ + struct wl_display *display = weston_desktop_get_display(surface->desktop); + struct wl_event_loop *loop = wl_display_get_event_loop(display); + bool pending_same = false; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + pending_same = weston_desktop_xdg_toplevel_state_compare((struct weston_desktop_xdg_toplevel *) surface); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + break; + } + + if (surface->configure_idle != NULL) { + if (!pending_same) + return; + + wl_event_source_remove(surface->configure_idle); + surface->configure_idle = NULL; + } else { + if (pending_same) + return; + + surface->configure_idle = + wl_event_loop_add_idle(loop, + weston_desktop_xdg_surface_send_configure, + surface); + } +} + +static void +weston_desktop_xdg_surface_protocol_get_toplevel(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_surface *wsurface = + 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) + return; + + toplevel->resource = + weston_desktop_surface_add_resource(toplevel->base.desktop_surface, + &xdg_toplevel_interface, + &weston_desktop_xdg_toplevel_implementation, + id, weston_desktop_xdg_toplevel_resource_destroy); + if (toplevel->resource == NULL) + 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 +weston_desktop_xdg_surface_protocol_get_popup(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *parent_resource, + struct wl_resource *positioner_resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(dsurface); + struct weston_desktop_xdg_popup *popup = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop_surface *parent_surface; + struct weston_desktop_xdg_surface *parent; + struct weston_desktop_xdg_positioner *positioner = + wl_resource_get_user_data(positioner_resource); + + /* Popup parents are allowed to be non-null, but only if a parent is + * specified 'using some other protocol' before committing. Since we + * don't support such a protocol yet, clients cannot legitimately + * create a popup with a non-null parent. */ + if (!parent_resource) { + wl_resource_post_error(resource, + XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT, + "popup parent must be non-null"); + return; + } + + parent_surface = wl_resource_get_user_data(parent_resource); + parent = weston_desktop_surface_get_implementation_data(parent_surface); + + if (!is_positioner_valid(positioner)) { + wl_resource_post_error(resource, + XDG_WM_BASE_ERROR_INVALID_POSITIONER, + "positioner object is not complete"); + return; + } + + if (weston_surface_set_role(wsurface, weston_desktop_xdg_popup_role, + resource, XDG_WM_BASE_ERROR_ROLE) < 0) + return; + + popup->resource = + weston_desktop_surface_add_resource(popup->base.desktop_surface, + &xdg_popup_interface, + &weston_desktop_xdg_popup_implementation, + id, weston_desktop_xdg_popup_resource_destroy); + if (popup->resource == NULL) + return; + + popup->base.role = WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP; + popup->parent = parent; + + popup->geometry = + weston_desktop_xdg_positioner_get_geometry(positioner, + dsurface, + parent_surface); + + weston_desktop_surface_set_relative_to(popup->base.desktop_surface, + parent_surface, + popup->geometry.x, + popup->geometry.y, + true); +} + +static bool +weston_desktop_xdg_surface_check_role(struct weston_desktop_xdg_surface *surface) +{ + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(surface->desktop_surface); + const char *role; + + role = weston_surface_get_role(wsurface); + if (role != NULL && + (role == weston_desktop_xdg_toplevel_role || + role == weston_desktop_xdg_popup_role)) + return true; + + wl_resource_post_error(surface->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "xdg_surface must have a role"); + return false; +} + +static void +weston_desktop_xdg_surface_protocol_set_window_geometry(struct wl_client *wl_client, + struct wl_resource *resource, + int32_t x, int32_t y, + int32_t width, int32_t height) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_surface *surface = + weston_desktop_surface_get_implementation_data(dsurface); + + if (!weston_desktop_xdg_surface_check_role(surface)) + return; + + surface->has_next_geometry = true; + surface->next_geometry.x = x; + surface->next_geometry.y = y; + surface->next_geometry.width = width; + surface->next_geometry.height = height; +} + +static void +weston_desktop_xdg_surface_protocol_ack_configure(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t serial) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_surface *surface = + weston_desktop_surface_get_implementation_data(dsurface); + struct weston_desktop_xdg_surface_configure *configure, *temp; + bool found = false; + + if (!weston_desktop_xdg_surface_check_role(surface)) + return; + + wl_list_for_each_safe(configure, temp, &surface->configure_list, link) { + if (configure->serial < serial) { + wl_list_remove(&configure->link); + free(configure); + } else if (configure->serial == serial) { + wl_list_remove(&configure->link); + found = true; + break; + } else { + break; + } + } + if (!found) { + struct weston_desktop_client *client = + weston_desktop_surface_get_client(dsurface); + struct wl_resource *client_resource = + weston_desktop_client_get_resource(client); + wl_resource_post_error(client_resource, + XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE, + "Wrong configure serial: %u", serial); + return; + } + + surface->configured = true; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_ack_configure((struct weston_desktop_xdg_toplevel *) surface, + (struct weston_desktop_xdg_toplevel_configure *) configure); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + break; + } + + free(configure); +} + +static void +weston_desktop_xdg_surface_ping(struct weston_desktop_surface *dsurface, + uint32_t serial, void *user_data) +{ + struct weston_desktop_client *client = + weston_desktop_surface_get_client(dsurface); + + xdg_wm_base_send_ping(weston_desktop_client_get_resource(client), + serial); +} + +static void +weston_desktop_xdg_surface_committed(struct weston_desktop_surface *dsurface, + void *user_data, + int32_t sx, int32_t sy) +{ + struct weston_desktop_xdg_surface *surface = user_data; + struct weston_surface *wsurface = + weston_desktop_surface_get_surface (dsurface); + + 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"); + return; + } + + if (surface->has_next_geometry) { + surface->has_next_geometry = false; + weston_desktop_surface_set_geometry(surface->desktop_surface, + surface->next_geometry); + } + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + wl_resource_post_error(surface->resource, + XDG_SURFACE_ERROR_NOT_CONSTRUCTED, + "xdg_surface must have a role"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_committed((struct weston_desktop_xdg_toplevel *) surface, sx, sy); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_committed((struct weston_desktop_xdg_popup *) surface); + break; + } +} + +static void +weston_desktop_xdg_surface_close(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_surface *surface = user_data; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + assert(0 && "not reached"); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_close((struct weston_desktop_xdg_toplevel *) surface); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_close((struct weston_desktop_xdg_popup *) surface); + break; + } +} + +static void +weston_desktop_xdg_surface_destroy(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xdg_surface *surface = user_data; + struct weston_desktop_xdg_surface_configure *configure, *temp; + + switch (surface->role) { + case WESTON_DESKTOP_XDG_SURFACE_ROLE_NONE: + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_TOPLEVEL: + weston_desktop_xdg_toplevel_destroy((struct weston_desktop_xdg_toplevel *) surface); + break; + case WESTON_DESKTOP_XDG_SURFACE_ROLE_POPUP: + weston_desktop_xdg_popup_destroy((struct weston_desktop_xdg_popup *) surface); + break; + } + + if (surface->configure_idle != NULL) + wl_event_source_remove(surface->configure_idle); + + wl_list_for_each_safe(configure, temp, &surface->configure_list, link) + free(configure); + + free(surface); +} + +static const struct xdg_surface_interface weston_desktop_xdg_surface_implementation = { + .destroy = weston_desktop_destroy_request, + .get_toplevel = weston_desktop_xdg_surface_protocol_get_toplevel, + .get_popup = weston_desktop_xdg_surface_protocol_get_popup, + .set_window_geometry = weston_desktop_xdg_surface_protocol_set_window_geometry, + .ack_configure = weston_desktop_xdg_surface_protocol_ack_configure, +}; + +static const struct weston_desktop_surface_implementation weston_desktop_xdg_surface_internal_implementation = { + /* These are used for toplevel only */ + .set_maximized = weston_desktop_xdg_toplevel_set_maximized, + .set_fullscreen = weston_desktop_xdg_toplevel_set_fullscreen, + .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, + .get_resizing = weston_desktop_xdg_toplevel_get_resizing, + .get_activated = weston_desktop_xdg_toplevel_get_activated, + + .get_pending_maximized = weston_desktop_xdg_toplevel_get_pending_maximized, + .get_pending_fullscreen = weston_desktop_xdg_toplevel_get_pending_fullscreen, + .get_pending_resizing = weston_desktop_xdg_toplevel_get_pending_resizing, + .get_pending_activated = weston_desktop_xdg_toplevel_get_pending_activated, + + /* These are used for popup only */ + .update_position = weston_desktop_xdg_popup_update_position, + + /* Common API */ + .committed = weston_desktop_xdg_surface_committed, + .ping = weston_desktop_xdg_surface_ping, + .close = weston_desktop_xdg_surface_close, + + .destroy = weston_desktop_xdg_surface_destroy, +}; + +static void +weston_desktop_xdg_shell_protocol_create_positioner(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t id) +{ + struct weston_desktop_client *client = + wl_resource_get_user_data(resource); + struct weston_desktop_xdg_positioner *positioner; + + positioner = zalloc(sizeof(struct weston_desktop_xdg_positioner)); + if (positioner == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + positioner->client = client; + positioner->desktop = weston_desktop_client_get_desktop(positioner->client); + + positioner->resource = + wl_resource_create(wl_client, + &xdg_positioner_interface, + wl_resource_get_version(resource), id); + if (positioner->resource == NULL) { + wl_client_post_no_memory(wl_client); + free(positioner); + return; + } + wl_resource_set_implementation(positioner->resource, + &weston_desktop_xdg_positioner_implementation, + positioner, weston_desktop_xdg_positioner_destroy); +} + +static void +weston_desktop_xdg_surface_resource_destroy(struct wl_resource *resource) +{ + struct weston_desktop_surface *dsurface = + wl_resource_get_user_data(resource); + + if (dsurface != NULL) + weston_desktop_surface_resource_destroy(resource); +} + +static void +weston_desktop_xdg_shell_protocol_get_xdg_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_xdg_surface *surface; + + if (wsurface->committed) { + wl_resource_post_error(resource, + XDG_WM_BASE_ERROR_ROLE, + "xdg_surface must not have any other role"); + return; + } + + 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"); + return; + } + + surface = zalloc(weston_desktop_surface_role_biggest_size); + if (surface == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + + surface->desktop = weston_desktop_client_get_desktop(client); + surface->surface = wsurface; + wl_list_init(&surface->configure_list); + + surface->desktop_surface = + weston_desktop_surface_create(surface->desktop, client, + surface->surface, + &weston_desktop_xdg_surface_internal_implementation, + surface); + if (surface->desktop_surface == NULL) { + free(surface); + return; + } + + surface->resource = + weston_desktop_surface_add_resource(surface->desktop_surface, + &xdg_surface_interface, + &weston_desktop_xdg_surface_implementation, + id, weston_desktop_xdg_surface_resource_destroy); + if (surface->resource == NULL) + return; +} + +static void +weston_desktop_xdg_shell_protocol_pong(struct wl_client *wl_client, + struct wl_resource *resource, + uint32_t serial) +{ + struct weston_desktop_client *client = + wl_resource_get_user_data(resource); + + weston_desktop_client_pong(client, serial); +} + +static const struct xdg_wm_base_interface weston_desktop_xdg_shell_implementation = { + .destroy = weston_desktop_destroy_request, + .create_positioner = weston_desktop_xdg_shell_protocol_create_positioner, + .get_xdg_surface = weston_desktop_xdg_shell_protocol_get_xdg_surface, + .pong = weston_desktop_xdg_shell_protocol_pong, +}; + +static void +weston_desktop_xdg_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, + &xdg_wm_base_interface, + &weston_desktop_xdg_shell_implementation, + version, id); +} + +struct wl_global * +weston_desktop_xdg_wm_base_create(struct weston_desktop *desktop, + struct wl_display *display) +{ + return wl_global_create(display, &xdg_wm_base_interface, + WD_XDG_SHELL_PROTOCOL_VERSION, desktop, + weston_desktop_xdg_shell_bind); +} diff --git a/libweston/desktop/xwayland.c b/libweston/desktop/xwayland.c new file mode 100644 index 000000000..b4bc9ee6d --- /dev/null +++ b/libweston/desktop/xwayland.c @@ -0,0 +1,504 @@ +/* + * 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" +#include "xwayland/xwayland-internal-interface.h" + +enum weston_desktop_xwayland_surface_state { + NONE, + TOPLEVEL, + MAXIMIZED, + FULLSCREEN, + TRANSIENT, + XWAYLAND, +}; + +struct weston_desktop_xwayland { + struct weston_desktop *desktop; + struct weston_desktop_client *client; + struct weston_layer layer; +}; + +struct weston_desktop_xwayland_surface { + struct weston_desktop_xwayland *xwayland; + struct weston_desktop *desktop; + struct weston_desktop_surface *surface; + struct wl_listener resource_destroy_listener; + struct weston_view *view; + const struct weston_xwayland_client_interface *client_interface; + struct weston_geometry next_geometry; + bool has_next_geometry; + bool committed; + bool added; + enum weston_desktop_xwayland_surface_state state; + enum weston_desktop_xwayland_surface_state prev_state; +}; + +static void +weston_desktop_xwayland_surface_change_state(struct weston_desktop_xwayland_surface *surface, + enum weston_desktop_xwayland_surface_state state, + struct weston_desktop_surface *parent, + int32_t x, int32_t y) +{ + struct weston_surface *wsurface; + bool to_add = (parent == NULL && state != XWAYLAND); + + assert(state != NONE); + assert(!parent || state == TRANSIENT); + + if (to_add && surface->added) { + surface->state = state; + return; + } + + wsurface = weston_desktop_surface_get_surface(surface->surface); + + if (surface->state != state) { + if (surface->state == XWAYLAND) { + assert(!surface->added); + + weston_desktop_surface_unlink_view(surface->view); + weston_view_destroy(surface->view); + surface->view = NULL; + weston_surface_unmap(wsurface); + } + + if (to_add) { + weston_desktop_surface_unset_relative_to(surface->surface); + weston_desktop_api_surface_added(surface->desktop, + surface->surface); + surface->added = true; + if (surface->state == NONE && surface->committed) + /* We had a race, and wl_surface.commit() was + * faster, just fake a commit to map the + * surface */ + weston_desktop_api_committed(surface->desktop, + surface->surface, + 0, 0); + + } else if (surface->added) { + weston_desktop_api_surface_removed(surface->desktop, + surface->surface); + surface->added = false; + } + + if (state == XWAYLAND) { + assert(!surface->added); + + surface->view = + weston_desktop_surface_create_view(surface->surface); + weston_layer_entry_insert(&surface->xwayland->layer.view_list, + &surface->view->layer_link); + surface->view->is_mapped = true; + weston_surface_map(wsurface); + } + + surface->state = state; + } + + if (parent != NULL) + weston_desktop_surface_set_relative_to(surface->surface, parent, + x, y, false); +} + +static void +weston_desktop_xwayland_surface_committed(struct weston_desktop_surface *dsurface, + void *user_data, + int32_t sx, int32_t sy) +{ + struct weston_desktop_xwayland_surface *surface = user_data; + struct weston_geometry oldgeom; + + assert(dsurface == surface->surface); + surface->committed = true; + +#ifdef WM_DEBUG + weston_log("%s: xwayland surface %p\n", __func__, surface); +#endif + + if (surface->has_next_geometry) { + oldgeom = weston_desktop_surface_get_geometry(surface->surface); + /* 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, + surface->next_geometry); + } + + if (surface->added) + weston_desktop_api_committed(surface->desktop, surface->surface, + sx, sy); + + /* If we're an override redirect window, the shell has no knowledge of + * our existence, so it won't assign us an output. + * + * We should already have dirty geometry if we're a new view, so just + * update the transform to get us an output assigned, or we won't + * cause a repaint. + */ + if (surface->state == XWAYLAND) + weston_view_update_transform(surface->view); +} + +static void +weston_desktop_xwayland_surface_set_size(struct weston_desktop_surface *dsurface, + void *user_data, + int32_t width, int32_t height) +{ + struct weston_desktop_xwayland_surface *surface = user_data; + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(surface->surface); + + surface->client_interface->send_configure(wsurface, width, height); +} + +static void +weston_desktop_xwayland_surface_destroy(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xwayland_surface *surface = user_data; + + wl_list_remove(&surface->resource_destroy_listener.link); + + weston_desktop_surface_unset_relative_to(surface->surface); + if (surface->added) + weston_desktop_api_surface_removed(surface->desktop, + surface->surface); + else if (surface->state == XWAYLAND) + weston_desktop_surface_unlink_view(surface->view); + + free(surface); +} + +static void +weston_desktop_xwayland_surface_close(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xwayland_surface *surface = user_data; + struct weston_surface *wsurface = + weston_desktop_surface_get_surface(surface->surface); + + surface->client_interface->send_close(wsurface); +} + +static bool +weston_desktop_xwayland_surface_get_maximized(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xwayland_surface *surface = user_data; + + return surface->state == MAXIMIZED; +} + +static bool +weston_desktop_xwayland_surface_get_fullscreen(struct weston_desktop_surface *dsurface, + void *user_data) +{ + struct weston_desktop_xwayland_surface *surface = user_data; + + return surface->state == FULLSCREEN; +} + +static const struct weston_desktop_surface_implementation weston_desktop_xwayland_surface_internal_implementation = { + .committed = weston_desktop_xwayland_surface_committed, + .set_size = weston_desktop_xwayland_surface_set_size, + + .get_maximized = weston_desktop_xwayland_surface_get_maximized, + .get_fullscreen = weston_desktop_xwayland_surface_get_fullscreen, + + .destroy = weston_desktop_xwayland_surface_destroy, + .close = weston_desktop_xwayland_surface_close, +}; + +static void +weston_destop_xwayland_resource_destroyed(struct wl_listener *listener, + void *data) +{ + struct weston_desktop_xwayland_surface *surface = + wl_container_of(listener, surface, resource_destroy_listener); + + weston_desktop_surface_destroy(surface->surface); +} + +static struct weston_desktop_xwayland_surface * +create_surface(struct weston_desktop_xwayland *xwayland, + struct weston_surface *wsurface, + const struct weston_xwayland_client_interface *client_interface) +{ + struct weston_desktop_xwayland_surface *surface; + + surface = zalloc(sizeof(struct weston_desktop_xwayland_surface)); + if (surface == NULL) + return NULL; + + surface->xwayland = xwayland; + surface->desktop = xwayland->desktop; + surface->client_interface = client_interface; + + surface->surface = + weston_desktop_surface_create(surface->desktop, + xwayland->client, wsurface, + &weston_desktop_xwayland_surface_internal_implementation, + surface); + if (surface->surface == NULL) { + free(surface); + return NULL; + } + + surface->resource_destroy_listener.notify = + weston_destop_xwayland_resource_destroyed; + wl_resource_add_destroy_listener(wsurface->resource, + &surface->resource_destroy_listener); + + weston_desktop_surface_set_pid(surface->surface, 0); + + return surface; +} + +static void +set_toplevel(struct weston_desktop_xwayland_surface *surface) +{ + weston_desktop_xwayland_surface_change_state(surface, TOPLEVEL, NULL, + 0, 0); +} + +static void +set_toplevel_with_position(struct weston_desktop_xwayland_surface *surface, + int32_t x, int32_t y) +{ + weston_desktop_xwayland_surface_change_state(surface, TOPLEVEL, NULL, + 0, 0); + weston_desktop_api_set_xwayland_position(surface->desktop, + surface->surface, x, y); +} + +static void +set_parent(struct weston_desktop_xwayland_surface *surface, + struct weston_surface *wparent) +{ + struct weston_desktop_surface *parent; + + if (!weston_surface_is_desktop_surface(wparent)) + return; + + parent = weston_surface_get_desktop_surface(wparent); + weston_desktop_api_set_parent(surface->desktop, surface->surface, parent); +} + +static void +set_transient(struct weston_desktop_xwayland_surface *surface, + struct weston_surface *wparent, int x, int y) +{ + struct weston_desktop_surface *parent; + + if (!weston_surface_is_desktop_surface(wparent)) + return; + + parent = weston_surface_get_desktop_surface(wparent); + weston_desktop_xwayland_surface_change_state(surface, TRANSIENT, parent, + x, y); +} + +static void +set_fullscreen(struct weston_desktop_xwayland_surface *surface, + struct weston_output *output) +{ + weston_desktop_xwayland_surface_change_state(surface, FULLSCREEN, NULL, + 0, 0); + weston_desktop_api_fullscreen_requested(surface->desktop, + surface->surface, true, output); +} + +static void +set_xwayland(struct weston_desktop_xwayland_surface *surface, int x, int y) +{ + weston_desktop_xwayland_surface_change_state(surface, XWAYLAND, NULL, + x, y); + weston_view_set_position(surface->view, x, y); +} + +static int +move(struct weston_desktop_xwayland_surface *surface, + struct weston_pointer *pointer) +{ + if (surface->state == TOPLEVEL || + surface->state == MAXIMIZED || + surface->state == FULLSCREEN) + weston_desktop_api_move(surface->desktop, surface->surface, + pointer->seat, pointer->grab_serial); + return 0; +} + +static int +resize(struct weston_desktop_xwayland_surface *surface, + struct weston_pointer *pointer, uint32_t edges) +{ + if (surface->state == TOPLEVEL || + surface->state == MAXIMIZED || + surface->state == FULLSCREEN) + weston_desktop_api_resize(surface->desktop, surface->surface, + pointer->seat, pointer->grab_serial, + edges); + return 0; +} + +static void +set_title(struct weston_desktop_xwayland_surface *surface, const char *title) +{ + weston_desktop_surface_set_title(surface->surface, title); +} + +static void +set_window_geometry(struct weston_desktop_xwayland_surface *surface, + int32_t x, int32_t y, int32_t width, int32_t height) +{ + surface->has_next_geometry = true; + surface->next_geometry.x = x; + surface->next_geometry.y = y; + surface->next_geometry.width = width; + surface->next_geometry.height = height; +} + +static void +set_maximized(struct weston_desktop_xwayland_surface *surface) +{ + weston_desktop_xwayland_surface_change_state(surface, MAXIMIZED, NULL, + 0, 0); + weston_desktop_api_maximized_requested(surface->desktop, + 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, + .set_toplevel_with_position = set_toplevel_with_position, + .set_parent = set_parent, + .set_transient = set_transient, + .set_fullscreen = set_fullscreen, + .set_xwayland = set_xwayland, + .move = move, + .resize = resize, + .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 +weston_desktop_xwayland_init(struct weston_desktop *desktop) +{ + struct weston_compositor *compositor = weston_desktop_get_compositor(desktop); + struct weston_desktop_xwayland *xwayland; + + xwayland = zalloc(sizeof(struct weston_desktop_xwayland)); + if (xwayland == NULL) + return; + + xwayland->desktop = desktop; + xwayland->client = weston_desktop_client_create(desktop, NULL, NULL, NULL, NULL, 0, 0); + + weston_layer_init(&xwayland->layer, compositor); + /* 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_TOP_UI - 1); + + compositor->xwayland = xwayland; + compositor->xwayland_interface = &weston_desktop_xwayland_interface; +} + +void +weston_desktop_xwayland_fini(struct weston_desktop *desktop) +{ + struct weston_compositor *compositor = weston_desktop_get_compositor(desktop); + struct weston_desktop_xwayland *xwayland; + + xwayland = compositor->xwayland; + + weston_desktop_client_destroy(xwayland->client); + weston_layer_fini(&xwayland->layer); + free(xwayland); + + compositor->xwayland = NULL; + compositor->xwayland_interface = NULL; +} diff --git a/libweston/gl-borders.c b/libweston/gl-borders.c new file mode 100644 index 000000000..edf9df443 --- /dev/null +++ b/libweston/gl-borders.c @@ -0,0 +1,100 @@ +/* + * Copyright © 2010-2011 Benjamin Franzke + * Copyright © 2012 Intel Corporation + * Copyright © 2013 Jason Ekstrand + * 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 "gl-borders.h" +#include "shared/helpers.h" + +void +weston_gl_borders_update(struct weston_gl_borders *borders, + struct frame *frame, + struct weston_output *output) +{ + const struct gl_renderer_interface *glri = + output->compositor->renderer->gl; + int32_t ix, iy, iwidth, iheight, fwidth, fheight; + + fwidth = frame_width(frame); + fheight = frame_height(frame); + frame_interior(frame, &ix, &iy, &iwidth, &iheight); + + struct weston_geometry border_area[4] = { + [GL_RENDERER_BORDER_TOP] = { + .x = 0, .y = 0, + .width = fwidth, .height = iy + }, + [GL_RENDERER_BORDER_LEFT] = { + .x = 0, .y = iy, + .width = ix, .height = 1 + }, + [GL_RENDERER_BORDER_RIGHT] = { + .x = iwidth + ix, .y = iy, + .width = fwidth - (ix + iwidth), .height = 1 + }, + [GL_RENDERER_BORDER_BOTTOM] = { + .x = 0, .y = iy + iheight, + .width = fwidth, .height = fheight - (iy + iheight) + }, + }; + + for (unsigned i = 0; i < ARRAY_LENGTH(border_area); i++) { + const struct weston_geometry *g = &border_area[i]; + int tex_width; + cairo_t *cr; + + if (!borders->tile[i]) { + borders->tile[i] = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + g->width, g->height); + } + + tex_width = cairo_image_surface_get_stride(borders->tile[i]) / 4; + + cr = cairo_create(borders->tile[i]); + cairo_translate(cr, -g->x, -g->y); + frame_repaint(frame, cr); + cairo_destroy(cr); + glri->output_set_border(output, i, g->width, g->height, tex_width, + cairo_image_surface_get_data(borders->tile[i])); + } +} + +void +weston_gl_borders_fini(struct weston_gl_borders *borders, + struct weston_output *output) +{ + const struct gl_renderer_interface *glri = + output->compositor->renderer->gl; + + for (unsigned i = 0; i < ARRAY_LENGTH(borders->tile); i++) { + glri->output_set_border(output, i, 0, 0, 0, NULL); + cairo_surface_destroy(borders->tile[i]); + borders->tile[i] = NULL; + } +} diff --git a/libweston/gl-borders.h b/libweston/gl-borders.h new file mode 100644 index 000000000..fd2dff3ed --- /dev/null +++ b/libweston/gl-borders.h @@ -0,0 +1,42 @@ +/* + * 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 "renderer-gl/gl-renderer.h" +#include "shared/cairo-util.h" + +struct weston_gl_borders { + cairo_surface_t *tile[4]; /* enum gl_renderer_border_side */ +}; + +void +weston_gl_borders_update(struct weston_gl_borders *borders, + struct frame *frame, + struct weston_output *output); + +void +weston_gl_borders_fini(struct weston_gl_borders *borders, + struct weston_output *output); diff --git a/libweston/input.c b/libweston/input.c index 6fb4bed39..4e442005c 100644 --- a/libweston/input.c +++ b/libweston/input.c @@ -38,6 +38,7 @@ #include #include #include +#include #include "shared/helpers.h" #include "shared/os-compatibility.h" @@ -48,6 +49,7 @@ #include "relative-pointer-unstable-v1-server-protocol.h" #include "pointer-constraints-unstable-v1-server-protocol.h" #include "input-timestamps-unstable-v1-server-protocol.h" +#include "tablet-unstable-v2-server-protocol.h" enum pointer_constraint_type { POINTER_CONSTRAINT_TYPE_LOCK, @@ -61,13 +63,9 @@ enum motion_direction { MOTION_DIRECTION_NEGATIVE_Y = 1 << 3, }; -struct vec2d { - double x, y; -}; - struct line { - struct vec2d a; - struct vec2d b; + struct weston_coord a; + struct weston_coord b; }; struct border { @@ -310,43 +308,42 @@ static void unbind_resource(struct wl_resource *resource) wl_list_remove(wl_resource_get_link(resource)); } -WL_EXPORT void +WL_EXPORT struct weston_coord_global weston_pointer_motion_to_abs(struct weston_pointer *pointer, - struct weston_pointer_motion_event *event, - wl_fixed_t *x, wl_fixed_t *y) + struct weston_pointer_motion_event *event) { + struct weston_coord_global pos; + if (event->mask & WESTON_POINTER_MOTION_ABS) { - *x = wl_fixed_from_double(event->x); - *y = wl_fixed_from_double(event->y); + return event->abs; } else if (event->mask & WESTON_POINTER_MOTION_REL) { - *x = pointer->x + wl_fixed_from_double(event->dx); - *y = pointer->y + wl_fixed_from_double(event->dy); - } else { - assert(!"invalid motion event"); - *x = *y = 0; + pos.c = weston_coord_add(pointer->pos.c, event->rel); + return pos; } + + assert(!"invalid motion event"); + pos.c = weston_coord(0, 0); + return pos; } static bool weston_pointer_motion_to_rel(struct weston_pointer *pointer, struct weston_pointer_motion_event *event, - double *dx, double *dy, - double *dx_unaccel, double *dy_unaccel) + struct weston_coord *rel, + struct weston_coord *rel_unaccel) { if (event->mask & WESTON_POINTER_MOTION_REL && event->mask & WESTON_POINTER_MOTION_REL_UNACCEL) { - *dx = event->dx; - *dy = event->dy; - *dx_unaccel = event->dx_unaccel; - *dy_unaccel = event->dy_unaccel; + *rel = event->rel; + *rel_unaccel = event->rel_unaccel; return true; } else if (event->mask & WESTON_POINTER_MOTION_REL) { - *dx_unaccel = *dx = event->dx; - *dy_unaccel = *dy = event->dy; + *rel = event->rel; + *rel_unaccel = event->rel; return true; } else if (event->mask & WESTON_POINTER_MOTION_REL_UNACCEL) { - *dx_unaccel = *dx = event->dx_unaccel; - *dy_unaccel = *dy = event->dy_unaccel; + *rel = event->rel_unaccel; + *rel_unaccel = event->rel_unaccel; return true; } else { return false; @@ -428,6 +425,26 @@ touch_focus_resource_destroyed(struct wl_listener *listener, void *data) weston_touch_set_focus(touch, NULL); } +static void +tablet_tool_focus_view_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_tablet_tool *tool = + container_of(listener, struct weston_tablet_tool, + focus_view_listener); + + weston_tablet_tool_set_focus(tool, NULL, 0); +} + +static void +tablet_tool_focus_resource_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_tablet_tool *tool = + container_of(listener, struct weston_tablet_tool, + focus_resource_listener); + + weston_tablet_tool_set_focus(tool, NULL, 0); +} + static void move_resources(struct wl_list *destination, struct wl_list *source) { @@ -455,17 +472,25 @@ default_grab_pointer_focus(struct weston_pointer_grab *grab) { struct weston_pointer *pointer = grab->pointer; struct weston_view *view; - wl_fixed_t sx, sy; + bool surface_jump = false; if (pointer->button_count > 0) return; view = weston_compositor_pick_view(pointer->seat->compositor, - pointer->x, pointer->y, - &sx, &sy); + pointer->pos); + if (view && view == pointer->focus) { + struct weston_coord_surface surf_pos; + + weston_view_update_transform(view); - if (pointer->focus != view || pointer->sx != sx || pointer->sy != sy) - weston_pointer_set_focus(pointer, view, sx, sy); + surf_pos = weston_coord_global_to_surface(view, pointer->pos); + if (pointer->sx != wl_fixed_from_double(surf_pos.c.x) || + pointer->sy != wl_fixed_from_double(surf_pos.c.y)) + surface_jump = true; + } + if (pointer->focus != view || surface_jump) + weston_pointer_set_focus(pointer, view); } static void @@ -474,8 +499,7 @@ pointer_send_relative_motion(struct weston_pointer *pointer, struct weston_pointer_motion_event *event) { uint64_t time_usec; - double dx, dy, dx_unaccel, dy_unaccel; - wl_fixed_t dxf, dyf, dxf_unaccel, dyf_unaccel; + struct weston_coord rel, rel_unaccel; struct wl_list *resource_list; struct wl_resource *resource; @@ -483,8 +507,8 @@ pointer_send_relative_motion(struct weston_pointer *pointer, return; if (!weston_pointer_motion_to_rel(pointer, event, - &dx, &dy, - &dx_unaccel, &dy_unaccel)) + &rel, + &rel_unaccel)) return; resource_list = &pointer->focus_client->relative_pointer_resources; @@ -492,18 +516,15 @@ pointer_send_relative_motion(struct weston_pointer *pointer, if (time_usec == 0) time_usec = timespec_to_usec(time); - dxf = wl_fixed_from_double(dx); - dyf = wl_fixed_from_double(dy); - dxf_unaccel = wl_fixed_from_double(dx_unaccel); - dyf_unaccel = wl_fixed_from_double(dy_unaccel); - wl_resource_for_each(resource, resource_list) { zwp_relative_pointer_v1_send_relative_motion( resource, (uint32_t) (time_usec >> 32), (uint32_t) time_usec, - dxf, dyf, - dxf_unaccel, dyf_unaccel); + wl_fixed_from_double(rel.x), + wl_fixed_from_double(rel.y), + wl_fixed_from_double(rel_unaccel.x), + wl_fixed_from_double(rel_unaccel.y)); } } @@ -534,19 +555,28 @@ weston_pointer_send_motion(struct weston_pointer *pointer, const struct timespec *time, struct weston_pointer_motion_event *event) { - wl_fixed_t x, y; - wl_fixed_t old_sx = pointer->sx; - wl_fixed_t old_sy = pointer->sy; + wl_fixed_t old_sx; + wl_fixed_t old_sy; + struct weston_view *old_focus = pointer->focus; if (pointer->focus) { - weston_pointer_motion_to_abs(pointer, event, &x, &y); - weston_view_from_global_fixed(pointer->focus, x, y, - &pointer->sx, &pointer->sy); + struct weston_coord_global pos; + struct weston_coord_surface surf_pos; + + pos = weston_pointer_motion_to_abs(pointer, event); + old_sx = pointer->sx; + old_sy = pointer->sy; + weston_view_update_transform(pointer->focus); + + surf_pos = weston_coord_global_to_surface(pointer->focus, pos); + pointer->sx = wl_fixed_from_double(surf_pos.c.x); + pointer->sy = wl_fixed_from_double(surf_pos.c.y); } weston_pointer_move(pointer, event); - if (old_sx != pointer->sx || old_sy != pointer->sy) { + if (pointer->focus && old_focus == pointer->focus && + (old_sx != pointer->sx || old_sy != pointer->sy)) { pointer_send_motion(pointer, time, pointer->sx, pointer->sy); } @@ -623,17 +653,15 @@ default_grab_pointer_button(struct weston_pointer_grab *grab, struct weston_pointer *pointer = grab->pointer; struct weston_compositor *compositor = pointer->seat->compositor; struct weston_view *view; - wl_fixed_t sx, sy; weston_pointer_send_button(pointer, time, button, state); if (pointer->button_count == 0 && state == WL_POINTER_BUTTON_STATE_RELEASED) { view = weston_compositor_pick_view(compositor, - pointer->x, pointer->y, - &sx, &sy); + pointer->pos); - weston_pointer_set_focus(pointer, view, sx, sy); + weston_pointer_set_focus(pointer, view); } } @@ -804,8 +832,7 @@ weston_touch_has_focus_resource(struct weston_touch *touch) * \param touch The touch where the down events originates from. * \param time The timestamp of the event * \param touch_id The touch_id value of the event - * \param x The x value of the event - * \param y The y value of the event + * \param pos The global coordinate of the event * * For every resource that is currently in focus, send a wl_touch.down event * with the passed parameters. The focused resources are the wl_touch @@ -813,19 +840,21 @@ weston_touch_has_focus_resource(struct weston_touch *touch) */ WL_EXPORT void weston_touch_send_down(struct weston_touch *touch, const struct timespec *time, - int touch_id, wl_fixed_t x, wl_fixed_t y) + int touch_id, struct weston_coord_global pos) { struct wl_display *display = touch->seat->compositor->wl_display; uint32_t serial; struct wl_resource *resource; struct wl_list *resource_list; - wl_fixed_t sx, sy; + struct weston_coord_surface surf_pos; uint32_t msecs; if (!weston_touch_has_focus_resource(touch)) return; - weston_view_from_global_fixed(touch->focus, x, y, &sx, &sy); + weston_view_update_transform(touch->focus); + + surf_pos = weston_coord_global_to_surface(touch->focus, pos); resource_list = &touch->focus_resource_list; serial = wl_display_next_serial(display); @@ -836,7 +865,9 @@ weston_touch_send_down(struct weston_touch *touch, const struct timespec *time, time); wl_touch_send_down(resource, serial, msecs, touch->focus->surface->resource, - touch_id, sx, sy); + touch_id, + wl_fixed_from_double(surf_pos.c.x), + wl_fixed_from_double(surf_pos.c.y)); } } @@ -845,7 +876,10 @@ default_grab_touch_down(struct weston_touch_grab *grab, const struct timespec *time, int touch_id, wl_fixed_t x, wl_fixed_t y) { - weston_touch_send_down(grab->touch, time, touch_id, x, y); + struct weston_coord_global pos; + + pos.c = weston_coord_from_fixed(x, y); + weston_touch_send_down(grab->touch, time, touch_id, pos); } /** Send wl_touch.up events to focused resources. @@ -894,8 +928,7 @@ default_grab_touch_up(struct weston_touch_grab *grab, * \param touch The touch where the motion events originates from. * \param time The timestamp of the event * \param touch_id The touch_id value of the event - * \param x The x value of the event - * \param y The y value of the event + * \param pos The global coordinate of the event * * For every resource that is currently in focus, send a wl_touch.motion event * with the passed parameters. The focused resources are the wl_touch @@ -904,17 +937,19 @@ default_grab_touch_up(struct weston_touch_grab *grab, WL_EXPORT void weston_touch_send_motion(struct weston_touch *touch, const struct timespec *time, int touch_id, - wl_fixed_t x, wl_fixed_t y) + struct weston_coord_global pos) { struct wl_resource *resource; struct wl_list *resource_list; - wl_fixed_t sx, sy; uint32_t msecs; + struct weston_coord_surface surf_pos; if (!weston_touch_has_focus_resource(touch)) return; - weston_view_from_global_fixed(touch->focus, x, y, &sx, &sy); + weston_view_update_transform(touch->focus); + + surf_pos = weston_coord_global_to_surface(touch->focus, pos); resource_list = &touch->focus_resource_list; msecs = timespec_to_msec(time); @@ -922,8 +957,9 @@ weston_touch_send_motion(struct weston_touch *touch, send_timestamps_for_input_resource(resource, &touch->timestamps_list, time); - wl_touch_send_motion(resource, msecs, - touch_id, sx, sy); + wl_touch_send_motion(resource, msecs, touch_id, + wl_fixed_from_double(surf_pos.c.x), + wl_fixed_from_double(surf_pos.c.y)); } } @@ -932,7 +968,10 @@ default_grab_touch_motion(struct weston_touch_grab *grab, const struct timespec *time, int touch_id, wl_fixed_t x, wl_fixed_t y) { - weston_touch_send_motion(grab->touch, time, touch_id, x, y); + struct weston_coord_global pos; + + pos.c = weston_coord_from_fixed(x, y); + weston_touch_send_motion(grab->touch, time, touch_id, pos); } @@ -1102,6 +1141,16 @@ find_resource_for_surface(struct wl_list *list, struct weston_surface *surface) return wl_resource_find_for_client(list, wl_resource_get_client(surface->resource)); } +static struct wl_resource * +find_resource_for_view(struct wl_list *list, struct weston_view *view) +{ + if (!view) + return NULL; + + return find_resource_for_surface(list, + view->surface); +} + /** Send wl_keyboard.modifiers events to focused resources and pointer * focused resources. * @@ -1233,17 +1282,13 @@ weston_pointer_create(struct weston_seat *seat) pointer->sprite_destroy_listener.notify = pointer_handle_sprite_destroy; /* FIXME: Pick better co-ords. */ - pointer->x = wl_fixed_from_int(100); - pointer->y = wl_fixed_from_int(100); + pointer->pos.c = weston_coord(100, 100); pointer->output_destroy_listener.notify = weston_pointer_handle_output_destroy; wl_signal_add(&seat->compositor->output_destroyed_signal, &pointer->output_destroy_listener); - pointer->sx = wl_fixed_from_int(-1000000); - pointer->sy = wl_fixed_from_int(-1000000); - return pointer; } @@ -1388,1307 +1433,2101 @@ weston_touch_destroy(struct weston_touch *touch) free(touch); } -static void -seat_send_updated_caps(struct weston_seat *seat) +WL_EXPORT struct weston_tablet * +weston_tablet_create(void) { - enum wl_seat_capability caps = 0; - struct wl_resource *resource; + struct weston_tablet *tablet; - if (seat->pointer_device_count > 0) - caps |= WL_SEAT_CAPABILITY_POINTER; - if (seat->keyboard_device_count > 0) - caps |= WL_SEAT_CAPABILITY_KEYBOARD; - if (seat->touch_device_count > 0) - caps |= WL_SEAT_CAPABILITY_TOUCH; + tablet = zalloc(sizeof *tablet); + if (tablet == NULL) + return NULL; - wl_resource_for_each(resource, &seat->base_resource_list) { - wl_seat_send_capabilities(resource, caps); - } - wl_signal_emit(&seat->updated_caps_signal, seat); -} + wl_list_init(&tablet->resource_list); + wl_list_init(&tablet->tool_list); + return tablet; +} -/** Clear the pointer focus - * - * \param pointer the pointer to clear focus for. - * - * This can be used to unset pointer focus and set the co-ordinates to the - * arbitrary values we use for the no focus case. - * - * There's no requirement to use this function. For example, passing the - * results of a weston_compositor_pick_view() directly to - * weston_pointer_set_focus() will do the right thing when no view is found. - */ WL_EXPORT void -weston_pointer_clear_focus(struct weston_pointer *pointer) +weston_tablet_destroy(struct weston_tablet *tablet) { - weston_pointer_set_focus(pointer, NULL, - wl_fixed_from_int(-1000000), - wl_fixed_from_int(-1000000)); + struct wl_resource *resource; + struct weston_tablet_tool *tool, *tmptool; + + wl_resource_for_each(resource, &tablet->resource_list) { + zwp_tablet_v2_send_removed(resource); + wl_resource_set_user_data(resource, NULL); + } + + /* Remove the tablet from the list */ + wl_list_remove(&tablet->link); + + /* Remove any local tools */ + wl_list_for_each_safe(tool, tmptool, &tablet->tool_list, link) + weston_seat_release_tablet_tool(tool); + + if (wl_list_empty(&tablet->resource_list)) { + free(tablet->name); + free(tablet); + } } WL_EXPORT void -weston_pointer_set_focus(struct weston_pointer *pointer, - struct weston_view *view, - wl_fixed_t sx, wl_fixed_t sy) +weston_tablet_tool_set_focus(struct weston_tablet_tool *tool, + struct weston_view *view, + const struct timespec *time) { - struct weston_pointer_client *pointer_client; - struct weston_keyboard *kbd = weston_seat_get_keyboard(pointer->seat); - struct wl_resource *resource; - struct wl_resource *surface_resource; - struct wl_display *display = pointer->seat->compositor->wl_display; - uint32_t serial; struct wl_list *focus_resource_list; - int refocus = 0; + struct wl_resource *resource; + struct weston_seat *seat = tool->seat; + uint32_t msecs; - if ((!pointer->focus && view) || - (pointer->focus && !view) || - (pointer->focus && pointer->focus->surface != view->surface) || - pointer->sx != sx || pointer->sy != sy) - refocus = 1; + focus_resource_list = &tool->focus_resource_list; + /* FIXME: correct timestamp? */ + msecs = time ? timespec_to_msec(time) : 0; + if (tool->focus && !wl_list_empty(focus_resource_list)) { + wl_resource_for_each(resource, focus_resource_list) { + if (tool->tip_is_down) + zwp_tablet_tool_v2_send_up(resource); - if (pointer->focus_client && refocus) { - focus_resource_list = &pointer->focus_client->pointer_resources; - if (!wl_list_empty(focus_resource_list)) { - serial = wl_display_next_serial(display); - surface_resource = pointer->focus->surface->resource; - wl_resource_for_each(resource, focus_resource_list) { - wl_pointer_send_leave(resource, serial, - surface_resource); - pointer_send_frame(resource); - } + zwp_tablet_tool_v2_send_proximity_out(resource); + zwp_tablet_tool_v2_send_frame(resource, msecs); } - pointer->focus_client = NULL; + move_resources(&tool->resource_list, focus_resource_list); } - pointer_client = find_pointer_client_for_view(pointer, view); - if (pointer_client && refocus) { - struct wl_client *surface_client = pointer_client->client; + if (find_resource_for_view(&tool->resource_list, view)) { + struct wl_client *surface_client = + wl_resource_get_client(view->surface->resource); - serial = wl_display_next_serial(display); + move_resources_for_client(focus_resource_list, + &tool->resource_list, + surface_client); - if (kbd && kbd->focus != view->surface) - send_modifiers_to_client_in_list(surface_client, - &kbd->resource_list, - serial, - kbd); + tool->focus_serial = wl_display_next_serial(seat->compositor->wl_display); + wl_resource_for_each(resource, focus_resource_list) { + struct wl_resource *tr; - pointer->focus_client = pointer_client; + tr = wl_resource_find_for_client(&tool->current_tablet->resource_list, + surface_client); - focus_resource_list = &pointer->focus_client->pointer_resources; - wl_resource_for_each(resource, focus_resource_list) { - wl_pointer_send_enter(resource, - serial, - view->surface->resource, - sx, sy); - pointer_send_frame(resource); - } + zwp_tablet_tool_v2_send_proximity_in(resource, tool->focus_serial, + tr, view->surface->resource); - pointer->focus_serial = serial; + if (tool->tip_is_down) + zwp_tablet_tool_v2_send_down(resource, + tool->focus_serial); + + zwp_tablet_tool_v2_send_frame(resource, msecs); + } } - wl_list_remove(&pointer->focus_view_listener.link); - wl_list_init(&pointer->focus_view_listener.link); - wl_list_remove(&pointer->focus_resource_listener.link); - wl_list_init(&pointer->focus_resource_listener.link); + wl_list_remove(&tool->focus_view_listener.link); + wl_list_init(&tool->focus_view_listener.link); + wl_list_remove(&tool->focus_resource_listener.link); + wl_list_init(&tool->focus_resource_listener.link); + if (view) - wl_signal_add(&view->destroy_signal, &pointer->focus_view_listener); + wl_signal_add(&view->destroy_signal, + &tool->focus_view_listener); if (view && view->surface->resource) wl_resource_add_destroy_listener(view->surface->resource, - &pointer->focus_resource_listener); + &tool->focus_resource_listener); + tool->focus = view; + tool->focus_view_listener.notify = tablet_tool_focus_view_destroyed; - pointer->focus = view; - pointer->focus_view_listener.notify = pointer_focus_view_destroyed; - pointer->sx = sx; - pointer->sy = sy; + wl_signal_emit(&tool->focus_signal, tool); +} - assert(view || sx == wl_fixed_from_int(-1000000)); - assert(view || sy == wl_fixed_from_int(-1000000)); +WL_EXPORT void +weston_tablet_tool_start_grab(struct weston_tablet_tool *tool, + struct weston_tablet_tool_grab *grab) +{ + tool->grab = grab; + grab->tool = tool; +} - wl_signal_emit(&pointer->focus_signal, pointer); +WL_EXPORT void +weston_tablet_tool_end_grab(struct weston_tablet_tool *tool) +{ + tool->grab = &tool->default_grab; } static void -send_enter_to_resource_list(struct wl_list *list, - struct weston_keyboard *keyboard, - struct weston_surface *surface, - uint32_t serial) +default_grab_tablet_tool_proximity_in(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + struct weston_tablet *tablet) { - struct wl_resource *resource; - - wl_resource_for_each(resource, list) { - wl_keyboard_send_enter(resource, serial, - surface->resource, - &keyboard->keys); - send_modifiers_to_resource(keyboard, resource, serial); - } } WL_EXPORT void -weston_keyboard_set_focus(struct weston_keyboard *keyboard, - struct weston_surface *surface) +weston_tablet_tool_send_proximity_out(struct weston_tablet_tool *tool, + const struct timespec *time) { - struct weston_seat *seat = keyboard->seat; - struct wl_resource *resource; - struct wl_display *display = keyboard->seat->compositor->wl_display; - uint32_t serial; - struct wl_list *focus_resource_list; - - /* Keyboard focus on a surface without a client is equivalent to NULL - * focus as nothing would react to the keyboard events anyway. - * Just set focus to NULL instead - the destroy listener hangs on the - * wl_resource anyway. - */ - if (surface && !surface->resource) - surface = NULL; + weston_tablet_tool_set_focus(tool, NULL, time); - focus_resource_list = &keyboard->focus_resource_list; + /* Hide the cursor */ + if (tool->sprite && weston_surface_is_mapped(tool->sprite->surface)) + weston_surface_unmap(tool->sprite->surface); +} - if (!wl_list_empty(focus_resource_list) && keyboard->focus != surface) { - serial = wl_display_next_serial(display); - wl_resource_for_each(resource, focus_resource_list) { - wl_keyboard_send_leave(resource, serial, - keyboard->focus->resource); - } - move_resources(&keyboard->resource_list, focus_resource_list); - } +static void +default_grab_tablet_tool_proximity_out(struct weston_tablet_tool_grab *grab, + const struct timespec *time) +{ + weston_tablet_tool_send_proximity_out(grab->tool, time); +} - if (find_resource_for_surface(&keyboard->resource_list, surface) && - keyboard->focus != surface) { - struct wl_client *surface_client = - wl_resource_get_client(surface->resource); +WL_EXPORT void +weston_tablet_tool_send_motion(struct weston_tablet_tool *tool, + const struct timespec *time, + struct weston_coord_global pos) +{ + struct weston_view *current_view; + struct wl_resource *resource; + struct weston_coord_surface surf_pos; - serial = wl_display_next_serial(display); + current_view = weston_compositor_pick_view(tool->seat->compositor, pos); + if (current_view != tool->focus) + weston_tablet_tool_set_focus(tool, current_view, time); + weston_tablet_tool_cursor_move(tool, pos); + surf_pos = weston_coord_global_to_surface(tool->focus, pos); - move_resources_for_client(focus_resource_list, - &keyboard->resource_list, - surface_client); - send_enter_to_resource_list(focus_resource_list, - keyboard, - surface, - serial); - keyboard->focus_serial = serial; + wl_resource_for_each(resource, &tool->focus_resource_list) { + zwp_tablet_tool_v2_send_motion(resource, + wl_fixed_from_double(surf_pos.c.x), + wl_fixed_from_double(surf_pos.c.y)); } - - /* Since this function gets called from the surface destroy handler - * we can't just remove the kbd focus listener, or we might corrupt - * the list it's in. - * Instead, we'll just set a flag to ignore the focus when the - * compositor regains kbd focus. - */ - seat->use_saved_kbd_focus = false; - - wl_list_remove(&keyboard->focus_resource_listener.link); - wl_list_init(&keyboard->focus_resource_listener.link); - if (surface) - wl_resource_add_destroy_listener(surface->resource, - &keyboard->focus_resource_listener); - - keyboard->focus = surface; - wl_signal_emit(&keyboard->focus_signal, keyboard); } -/* Users of this function must manually manage the keyboard focus */ -WL_EXPORT void -weston_keyboard_start_grab(struct weston_keyboard *keyboard, - struct weston_keyboard_grab *grab) +static void +default_grab_tablet_tool_motion(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + struct weston_coord_global pos) { - keyboard->grab = grab; - grab->keyboard = keyboard; + weston_tablet_tool_send_motion(grab->tool, time, pos); } WL_EXPORT void -weston_keyboard_end_grab(struct weston_keyboard *keyboard) +weston_tablet_tool_send_down(struct weston_tablet_tool *tool, + const struct timespec *time) { - keyboard->grab = &keyboard->default_grab; + struct wl_resource *resource; + struct wl_list *resource_list = &tool->focus_resource_list; + + if (!wl_list_empty(resource_list)) { + wl_resource_for_each(resource, resource_list) + zwp_tablet_tool_v2_send_down(resource, tool->grab_serial); + } } static void -weston_keyboard_cancel_grab(struct weston_keyboard *keyboard) +default_grab_tablet_tool_down(struct weston_tablet_tool_grab *grab, + const struct timespec *time) { - keyboard->grab->interface->cancel(keyboard->grab); + weston_tablet_tool_send_down(grab->tool, time); } WL_EXPORT void -weston_pointer_start_grab(struct weston_pointer *pointer, - struct weston_pointer_grab *grab) +weston_tablet_tool_send_up(struct weston_tablet_tool *tool, + const struct timespec *time) { - pointer->grab = grab; - grab->pointer = pointer; - pointer->grab->interface->focus(pointer->grab); + struct wl_resource *resource; + struct wl_list *resource_list = &tool->focus_resource_list; + + if (!wl_list_empty(resource_list)) { + wl_resource_for_each(resource, resource_list) + zwp_tablet_tool_v2_send_up(resource); + } +} + +static void +default_grab_tablet_tool_up(struct weston_tablet_tool_grab *grab, + const struct timespec *time) +{ + weston_tablet_tool_send_up(grab->tool, time); } WL_EXPORT void -weston_pointer_end_grab(struct weston_pointer *pointer) +weston_tablet_tool_send_pressure(struct weston_tablet_tool *tool, + const struct timespec *time, + uint32_t pressure) { - pointer->grab = &pointer->default_grab; - pointer->grab->interface->focus(pointer->grab); + struct wl_resource *resource; + struct wl_list *resource_list = &tool->focus_resource_list; + + if (!wl_list_empty(resource_list)) { + wl_resource_for_each(resource, resource_list) + zwp_tablet_tool_v2_send_pressure(resource, pressure); + } } static void -weston_pointer_cancel_grab(struct weston_pointer *pointer) +default_grab_tablet_tool_pressure(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + uint32_t pressure) { - pointer->grab->interface->cancel(pointer->grab); + weston_tablet_tool_send_pressure(grab->tool, time, pressure); } WL_EXPORT void -weston_touch_start_grab(struct weston_touch *touch, struct weston_touch_grab *grab) +weston_tablet_tool_send_distance(struct weston_tablet_tool *tool, + const struct timespec *time, + uint32_t distance) { - touch->grab = grab; - grab->touch = touch; -} + struct wl_resource *resource; + struct wl_list *resource_list = &tool->focus_resource_list; -WL_EXPORT void -weston_touch_end_grab(struct weston_touch *touch) -{ - touch->grab = &touch->default_grab; + if (!wl_list_empty(resource_list)) { + wl_resource_for_each(resource, resource_list) + zwp_tablet_tool_v2_send_distance(resource, distance); + } } static void -weston_touch_cancel_grab(struct weston_touch *touch) +default_grab_tablet_tool_distance(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + uint32_t distance) { - touch->grab->interface->cancel(touch->grab); + weston_tablet_tool_send_distance(grab->tool, time, distance); } -static void -weston_pointer_clamp_for_output(struct weston_pointer *pointer, - struct weston_output *output, - wl_fixed_t *fx, wl_fixed_t *fy) +WL_EXPORT void +weston_tablet_tool_send_tilt(struct weston_tablet_tool *tool, + const struct timespec *time, + wl_fixed_t tilt_x, wl_fixed_t tilt_y) { - int x, y; + struct wl_resource *resource; + struct wl_list *resource_list = &tool->focus_resource_list; - x = wl_fixed_to_int(*fx); - y = wl_fixed_to_int(*fy); + if (!wl_list_empty(resource_list)) { + wl_resource_for_each(resource, resource_list) + zwp_tablet_tool_v2_send_tilt(resource, tilt_x, tilt_y); + } +} - if (x < output->x) - *fx = wl_fixed_from_int(output->x); - else if (x >= output->x + output->width) - *fx = wl_fixed_from_int(output->x + - output->width - 1); - if (y < output->y) - *fy = wl_fixed_from_int(output->y); - else if (y >= output->y + output->height) - *fy = wl_fixed_from_int(output->y + - output->height - 1); +static void +default_grab_tablet_tool_tilt(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + wl_fixed_t tilt_x, wl_fixed_t tilt_y) +{ + weston_tablet_tool_send_tilt(grab->tool, time, tilt_x, tilt_y); } WL_EXPORT void -weston_pointer_clamp(struct weston_pointer *pointer, wl_fixed_t *fx, wl_fixed_t *fy) +weston_tablet_tool_send_button(struct weston_tablet_tool *tool, + const struct timespec *time, + uint32_t button, uint32_t state) { - struct weston_compositor *ec = pointer->seat->compositor; - struct weston_output *output, *prev = NULL; - int x, y, old_x, old_y, valid = 0; - - x = wl_fixed_to_int(*fx); - y = wl_fixed_to_int(*fy); - old_x = wl_fixed_to_int(pointer->x); - old_y = wl_fixed_to_int(pointer->y); + struct wl_resource *resource; - wl_list_for_each(output, &ec->output_list, link) { - if (pointer->seat->output && pointer->seat->output != output) - continue; - if (pixman_region32_contains_point(&output->region, - x, y, NULL)) - valid = 1; - if (pixman_region32_contains_point(&output->region, - old_x, old_y, NULL)) - prev = output; + wl_resource_for_each(resource, &tool->focus_resource_list) { + zwp_tablet_tool_v2_send_button(resource, tool->grab_serial, + button, state); } - - if (!prev) - prev = pointer->seat->output; - - if (prev && !valid) - weston_pointer_clamp_for_output(pointer, prev, fx, fy); } static void -weston_pointer_move_to(struct weston_pointer *pointer, - wl_fixed_t x, wl_fixed_t y) +default_grab_tablet_tool_button(struct weston_tablet_tool_grab *grab, + const struct timespec *time, + uint32_t button, + enum zwp_tablet_tool_v2_button_state state) { - int32_t ix, iy; - - weston_pointer_clamp (pointer, &x, &y); - - pointer->x = x; - pointer->y = y; - - ix = wl_fixed_to_int(x); - iy = wl_fixed_to_int(y); - - if (pointer->sprite) { - weston_view_set_position(pointer->sprite, - ix - pointer->hotspot_x, - iy - pointer->hotspot_y); - weston_view_schedule_repaint(pointer->sprite); - } - - pointer->grab->interface->focus(pointer->grab); - wl_signal_emit(&pointer->motion_signal, pointer); + weston_tablet_tool_send_button(grab->tool, time, button, state); } WL_EXPORT void -weston_pointer_move(struct weston_pointer *pointer, - struct weston_pointer_motion_event *event) +weston_tablet_tool_send_frame(struct weston_tablet_tool *tool, + const struct timespec *time) { - wl_fixed_t x, y; + struct wl_resource *resource; + struct wl_list *resource_list = &tool->focus_resource_list; + uint32_t msecs; - weston_pointer_motion_to_abs(pointer, event, &x, &y); - weston_pointer_move_to(pointer, x, y); + msecs = timespec_to_msec(time); + if (!wl_list_empty(resource_list)) { + wl_resource_for_each(resource, resource_list) + zwp_tablet_tool_v2_send_frame(resource, msecs); + } } -/** Verify if the pointer is in a valid position and move it if it isn't. - */ static void -weston_pointer_handle_output_destroy(struct wl_listener *listener, void *data) +default_grab_tablet_tool_frame(struct weston_tablet_tool_grab *grab, + const struct timespec *time) { - struct weston_pointer *pointer; - struct weston_compositor *ec; - struct weston_output *output, *closest = NULL; - int x, y, distance, min = INT_MAX; - wl_fixed_t fx, fy; - - pointer = container_of(listener, struct weston_pointer, - output_destroy_listener); - ec = pointer->seat->compositor; - - x = wl_fixed_to_int(pointer->x); - y = wl_fixed_to_int(pointer->y); - - wl_list_for_each(output, &ec->output_list, link) { - if (pixman_region32_contains_point(&output->region, - x, y, NULL)) - return; - - /* Aproximante the distance from the pointer to the center of - * the output. */ - distance = abs(output->x + output->width / 2 - x) + - abs(output->y + output->height / 2 - y); - if (distance < min) { - min = distance; - closest = output; - } - } - - /* Nothing to do if there's no output left. */ - if (!closest) - return; - - fx = pointer->x; - fy = pointer->y; + weston_tablet_tool_send_frame(grab->tool, time); +} - weston_pointer_clamp_for_output(pointer, closest, &fx, &fy); - weston_pointer_move_to(pointer, fx, fy); +static void +default_grab_tablet_tool_cancel(struct weston_tablet_tool_grab *grab) +{ } -WL_EXPORT void -notify_motion(struct weston_seat *seat, - const struct timespec *time, - struct weston_pointer_motion_event *event) +static struct weston_tablet_tool_grab_interface default_tablet_tool_grab_interface = { + default_grab_tablet_tool_proximity_in, + default_grab_tablet_tool_proximity_out, + default_grab_tablet_tool_motion, + default_grab_tablet_tool_down, + default_grab_tablet_tool_up, + default_grab_tablet_tool_pressure, + default_grab_tablet_tool_distance, + default_grab_tablet_tool_tilt, + default_grab_tablet_tool_button, + default_grab_tablet_tool_frame, + default_grab_tablet_tool_cancel, +}; + +static void +tablet_tool_unmap_sprite(struct weston_tablet_tool *tool) { - struct weston_compositor *ec = seat->compositor; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); + if (weston_surface_is_mapped(tool->sprite->surface)) + weston_surface_unmap(tool->sprite->surface); - weston_compositor_wake(ec); - pointer->grab->interface->motion(pointer->grab, time, event); + wl_list_remove(&tool->sprite_destroy_listener.link); + tool->sprite->surface->committed = NULL; + tool->sprite->surface->committed_private = NULL; + weston_view_destroy(tool->sprite); + tool->sprite = NULL; } static void -run_modifier_bindings(struct weston_seat *seat, uint32_t old, uint32_t new) +tablet_tool_handle_sprite_destroy(struct wl_listener *listener, void *data) { - struct weston_compositor *compositor = seat->compositor; - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - uint32_t diff; - unsigned int i; - struct { - uint32_t xkb; - enum weston_keyboard_modifier weston; - } mods[] = { - { keyboard->xkb_info->ctrl_mod, MODIFIER_CTRL }, - { keyboard->xkb_info->alt_mod, MODIFIER_ALT }, - { keyboard->xkb_info->super_mod, MODIFIER_SUPER }, - { keyboard->xkb_info->shift_mod, MODIFIER_SHIFT }, - }; - - diff = new & ~old; - for (i = 0; i < ARRAY_LENGTH(mods); i++) { - if (diff & (1 << mods[i].xkb)) - weston_compositor_run_modifier_binding(compositor, - keyboard, - mods[i].weston, - WL_KEYBOARD_KEY_STATE_PRESSED); - } + struct weston_tablet_tool *tool = + container_of(listener, struct weston_tablet_tool, + sprite_destroy_listener); - diff = old & ~new; - for (i = 0; i < ARRAY_LENGTH(mods); i++) { - if (diff & (1 << mods[i].xkb)) - weston_compositor_run_modifier_binding(compositor, - keyboard, - mods[i].weston, - WL_KEYBOARD_KEY_STATE_RELEASED); - } + tool->sprite = NULL; } -WL_EXPORT void -notify_motion_absolute(struct weston_seat *seat, const struct timespec *time, - double x, double y) +WL_EXPORT struct weston_tablet_tool * +weston_tablet_tool_create(void) { - struct weston_compositor *ec = seat->compositor; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_pointer_motion_event event = { 0 }; + struct weston_tablet_tool *tool; - weston_compositor_wake(ec); + tool = zalloc(sizeof *tool); + if (tool == NULL) + return NULL; - event = (struct weston_pointer_motion_event) { - .mask = WESTON_POINTER_MOTION_ABS, - .x = x, - .y = y, - }; + wl_list_init(&tool->resource_list); + wl_list_init(&tool->focus_resource_list); - pointer->grab->interface->motion(pointer->grab, time, &event); -} + wl_list_init(&tool->sprite_destroy_listener.link); + tool->sprite_destroy_listener.notify = tablet_tool_handle_sprite_destroy; -static unsigned int -peek_next_activate_serial(struct weston_compositor *c) -{ - unsigned serial = c->activate_serial + 1; + wl_list_init(&tool->focus_view_listener.link); + tool->focus_view_listener.notify = tablet_tool_focus_view_destroyed; - return serial == 0 ? 1 : serial; -} + wl_list_init(&tool->focus_resource_listener.link); + tool->focus_resource_listener.notify = tablet_tool_focus_resource_destroyed; -static void -inc_activate_serial(struct weston_compositor *c) -{ - c->activate_serial = peek_next_activate_serial (c); + tool->default_grab.interface = &default_tablet_tool_grab_interface; + tool->default_grab.tool = tool; + tool->grab = &tool->default_grab; + + wl_signal_init(&tool->focus_signal); + wl_signal_init(&tool->removed_signal); + + return tool; } WL_EXPORT void -weston_view_activate_input(struct weston_view *view, - struct weston_seat *seat, - uint32_t flags) +weston_tablet_tool_destroy(struct weston_tablet_tool *tool) { - struct weston_compositor *compositor = seat->compositor; + struct wl_resource *resource, *tmp; - if (flags & WESTON_ACTIVATE_FLAG_CLICKED) { - view->click_to_activate_serial = - peek_next_activate_serial(compositor); + if (tool->sprite) + tablet_tool_unmap_sprite(tool); + + wl_resource_for_each_safe(resource, tmp, &tool->resource_list) { + zwp_tablet_tool_v2_send_removed(resource); + wl_resource_set_user_data(resource, NULL); + } + wl_resource_for_each(resource, &tool->focus_resource_list) { + wl_resource_set_user_data(resource, NULL); } - weston_seat_set_keyboard_focus(seat, view->surface); + wl_list_remove(&tool->link); + wl_list_remove(&tool->resource_list); + wl_list_remove(&tool->focus_resource_list); + wl_list_remove(&tool->focus_view_listener.link); + wl_list_remove(&tool->focus_resource_listener.link); + free(tool); } WL_EXPORT void -notify_button(struct weston_seat *seat, const struct timespec *time, - int32_t button, enum wl_pointer_button_state state) +weston_tablet_tool_cursor_move(struct weston_tablet_tool *tool, + struct weston_coord_global pos) { - struct weston_compositor *compositor = seat->compositor; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); + tool->pos = pos; - if (state == WL_POINTER_BUTTON_STATE_PRESSED) { - weston_compositor_idle_inhibit(compositor); - if (pointer->button_count == 0) { - pointer->grab_button = button; - pointer->grab_time = *time; - pointer->grab_x = pointer->x; - pointer->grab_y = pointer->y; - } - pointer->button_count++; - } else { - weston_compositor_idle_release(compositor); - pointer->button_count--; + if (tool->sprite) { + weston_view_set_position(tool->sprite, + pos.c.x - tool->hotspot.c.x, + pos.c.y - tool->hotspot.c.y); + weston_view_schedule_repaint(tool->sprite); } - - weston_compositor_run_button_binding(compositor, pointer, time, button, - state); - - pointer->grab->interface->button(pointer->grab, time, button, state); - - if (pointer->button_count == 1) - pointer->grab_serial = - wl_display_get_serial(compositor->wl_display); } -WL_EXPORT void -notify_axis(struct weston_seat *seat, const struct timespec *time, - struct weston_pointer_axis_event *event) +static void +seat_send_updated_caps(struct weston_seat *seat) { - struct weston_compositor *compositor = seat->compositor; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - weston_compositor_wake(compositor); + enum wl_seat_capability caps = 0; + struct wl_resource *resource; - if (weston_compositor_run_axis_binding(compositor, pointer, - time, event)) - return; + if (seat->pointer_device_count > 0) + caps |= WL_SEAT_CAPABILITY_POINTER; + if (seat->keyboard_device_count > 0) + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + if (seat->touch_device_count > 0) + caps |= WL_SEAT_CAPABILITY_TOUCH; - pointer->grab->interface->axis(pointer->grab, time, event); + wl_resource_for_each(resource, &seat->base_resource_list) { + wl_seat_send_capabilities(resource, caps); + } + wl_signal_emit(&seat->updated_caps_signal, seat); } + +/** Clear the pointer focus + * + * \param pointer the pointer to clear focus for. + * + * This can be used to unset pointer focus and set the co-ordinates to the + * arbitrary values we use for the no focus case. + * + * There's no requirement to use this function. Passing NULL directly to + * weston_pointer_set_focus() will do the right thing. + */ WL_EXPORT void -notify_axis_source(struct weston_seat *seat, uint32_t source) +weston_pointer_clear_focus(struct weston_pointer *pointer) { - struct weston_compositor *compositor = seat->compositor; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - - weston_compositor_wake(compositor); - - pointer->grab->interface->axis_source(pointer->grab, source); + weston_pointer_set_focus(pointer, NULL); } WL_EXPORT void -notify_pointer_frame(struct weston_seat *seat) +weston_pointer_set_focus(struct weston_pointer *pointer, + struct weston_view *view) { - struct weston_compositor *compositor = seat->compositor; - struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_pointer_client *pointer_client; + struct weston_keyboard *kbd = weston_seat_get_keyboard(pointer->seat); + struct wl_resource *resource; + struct wl_resource *surface_resource; + struct wl_display *display = pointer->seat->compositor->wl_display; + uint32_t serial; + struct wl_list *focus_resource_list; + int refocus = 0; + wl_fixed_t sx, sy; - weston_compositor_wake(compositor); + if (view) { + struct weston_coord_surface surf_pos; - pointer->grab->interface->frame(pointer->grab); -} + surf_pos = weston_coord_global_to_surface(view, pointer->pos); + sx = wl_fixed_from_double(surf_pos.c.x); + sy = wl_fixed_from_double(surf_pos.c.y); -WL_EXPORT int -weston_keyboard_set_locks(struct weston_keyboard *keyboard, - uint32_t mask, uint32_t value) -{ - uint32_t serial; - xkb_mod_mask_t mods_depressed, mods_latched, mods_locked, group; + if (!weston_view_takes_input_at_point(view, surf_pos)) + weston_log("View focused with external coordinate %d, %d\n", + (int)surf_pos.c.x, (int)surf_pos.c.y); + } + + if ((!pointer->focus && view) || + (pointer->focus && !view) || + (pointer->focus && pointer->focus->surface != view->surface) || + (view && (pointer->sx != sx || pointer->sy != sy))) + refocus = 1; + + if (pointer->focus_client && refocus) { + focus_resource_list = &pointer->focus_client->pointer_resources; + if (!wl_list_empty(focus_resource_list)) { + serial = wl_display_next_serial(display); + surface_resource = pointer->focus->surface->resource; + wl_resource_for_each(resource, focus_resource_list) { + wl_pointer_send_leave(resource, serial, + surface_resource); + pointer_send_frame(resource); + } + } + + pointer->focus_client = NULL; + } + + pointer_client = find_pointer_client_for_view(pointer, view); + if (pointer_client && refocus) { + struct wl_client *surface_client = pointer_client->client; + + serial = wl_display_next_serial(display); + + if (kbd && kbd->focus != view->surface) + send_modifiers_to_client_in_list(surface_client, + &kbd->resource_list, + serial, + kbd); + + pointer->focus_client = pointer_client; + + focus_resource_list = &pointer->focus_client->pointer_resources; + wl_resource_for_each(resource, focus_resource_list) { + wl_pointer_send_enter(resource, + serial, + view->surface->resource, + sx, sy); + pointer_send_frame(resource); + } + + pointer->focus_serial = serial; + } + + wl_list_remove(&pointer->focus_view_listener.link); + wl_list_init(&pointer->focus_view_listener.link); + wl_list_remove(&pointer->focus_resource_listener.link); + wl_list_init(&pointer->focus_resource_listener.link); + if (view) + wl_signal_add(&view->destroy_signal, &pointer->focus_view_listener); + if (view && view->surface->resource) + wl_resource_add_destroy_listener(view->surface->resource, + &pointer->focus_resource_listener); + + pointer->focus = view; + pointer->focus_view_listener.notify = pointer_focus_view_destroyed; + if (view) { + pointer->sx = sx; + pointer->sy = sy; + } + + wl_signal_emit(&pointer->focus_signal, pointer); +} + +static void +send_enter_to_resource_list(struct wl_list *list, + struct weston_keyboard *keyboard, + struct weston_surface *surface, + uint32_t serial) +{ + struct wl_resource *resource; + + wl_resource_for_each(resource, list) { + wl_keyboard_send_enter(resource, serial, + surface->resource, + &keyboard->keys); + send_modifiers_to_resource(keyboard, resource, serial); + } +} + +WL_EXPORT void +weston_keyboard_set_focus(struct weston_keyboard *keyboard, + struct weston_surface *surface) +{ + struct weston_seat *seat = keyboard->seat; + struct wl_resource *resource; + struct wl_display *display = keyboard->seat->compositor->wl_display; + uint32_t serial; + struct wl_list *focus_resource_list; + + /* Keyboard focus on a surface without a client is equivalent to NULL + * focus as nothing would react to the keyboard events anyway. + * Just set focus to NULL instead - the destroy listener hangs on the + * wl_resource anyway. + */ + if (surface && !surface->resource) + surface = NULL; + + focus_resource_list = &keyboard->focus_resource_list; + + if (!wl_list_empty(focus_resource_list) && keyboard->focus != surface) { + serial = wl_display_next_serial(display); + wl_resource_for_each(resource, focus_resource_list) { + wl_keyboard_send_leave(resource, serial, + keyboard->focus->resource); + } + move_resources(&keyboard->resource_list, focus_resource_list); + } + + if (find_resource_for_surface(&keyboard->resource_list, surface) && + keyboard->focus != surface) { + struct wl_client *surface_client = + wl_resource_get_client(surface->resource); + + serial = wl_display_next_serial(display); + + move_resources_for_client(focus_resource_list, + &keyboard->resource_list, + surface_client); + send_enter_to_resource_list(focus_resource_list, + keyboard, + surface, + serial); + keyboard->focus_serial = serial; + } + + /* Since this function gets called from the surface destroy handler + * we can't just remove the kbd focus listener, or we might corrupt + * the list it's in. + * Instead, we'll just set a flag to ignore the focus when the + * compositor regains kbd focus. + */ + seat->use_saved_kbd_focus = false; + + wl_list_remove(&keyboard->focus_resource_listener.link); + wl_list_init(&keyboard->focus_resource_listener.link); + if (surface) + wl_resource_add_destroy_listener(surface->resource, + &keyboard->focus_resource_listener); + + keyboard->focus = surface; + wl_signal_emit(&keyboard->focus_signal, keyboard); +} + +/* Users of this function must manually manage the keyboard focus */ +WL_EXPORT void +weston_keyboard_start_grab(struct weston_keyboard *keyboard, + struct weston_keyboard_grab *grab) +{ + keyboard->grab = grab; + grab->keyboard = keyboard; +} + +WL_EXPORT void +weston_keyboard_end_grab(struct weston_keyboard *keyboard) +{ + keyboard->grab = &keyboard->default_grab; +} + +static void +weston_keyboard_cancel_grab(struct weston_keyboard *keyboard) +{ + keyboard->grab->interface->cancel(keyboard->grab); +} + +WL_EXPORT void +weston_pointer_start_grab(struct weston_pointer *pointer, + struct weston_pointer_grab *grab) +{ + pointer->grab = grab; + grab->pointer = pointer; + pointer->grab->interface->focus(pointer->grab); +} + +WL_EXPORT void +weston_pointer_end_grab(struct weston_pointer *pointer) +{ + pointer->grab = &pointer->default_grab; + pointer->grab->interface->focus(pointer->grab); +} + +static void +weston_pointer_cancel_grab(struct weston_pointer *pointer) +{ + pointer->grab->interface->cancel(pointer->grab); +} + +WL_EXPORT void +weston_touch_start_grab(struct weston_touch *touch, struct weston_touch_grab *grab) +{ + touch->grab = grab; + grab->touch = touch; +} + +WL_EXPORT void +weston_touch_end_grab(struct weston_touch *touch) +{ + touch->grab = &touch->default_grab; +} + +static void +weston_touch_cancel_grab(struct weston_touch *touch) +{ + touch->grab->interface->cancel(touch->grab); +} + +static struct weston_coord_global +weston_pointer_clamp_for_output(struct weston_pointer *pointer, + struct weston_output *output, + struct weston_coord_global pos) +{ + struct weston_coord_global out = pos; + int x = pos.c.x; + int y = pos.c.y; + + if (x < output->x) + out.c.x = output->x; + else if (x >= output->x + output->width) + out.c.x = output->x + output->width - 1; + if (y < output->y) + out.c.y = output->y; + else if (y >= output->y + output->height) + out.c.y = output->y + output->height - 1; + + return out; +} + +WL_EXPORT struct weston_coord_global +weston_pointer_clamp(struct weston_pointer *pointer, struct weston_coord_global pos) +{ + struct weston_compositor *ec = pointer->seat->compositor; + struct weston_output *output, *prev = NULL; + int valid = 0; + + wl_list_for_each(output, &ec->output_list, link) { + if (pointer->seat->output && pointer->seat->output != output) + continue; + if (weston_output_contains_point(output, pos.c.x, pos.c.y)) + valid = 1; + if (weston_output_contains_point(output, pointer->pos.c.x, + pointer->pos.c.y)) + prev = output; + } + + if (!prev) + prev = pointer->seat->output; + + if (prev && !valid) + pos = weston_pointer_clamp_for_output(pointer, prev, pos); + + return pos; +} + +static void +weston_pointer_move_to(struct weston_pointer *pointer, + struct weston_coord_global pos) +{ + pos = weston_pointer_clamp(pointer, pos); + + pointer->pos = pos; + + if (pointer->sprite) { + weston_view_set_position(pointer->sprite, + pos.c.x - pointer->hotspot.c.x, + pos.c.y - pointer->hotspot.c.y); + weston_view_schedule_repaint(pointer->sprite); + } + + pointer->grab->interface->focus(pointer->grab); + wl_signal_emit(&pointer->motion_signal, pointer); +} + +WL_EXPORT void +weston_pointer_move(struct weston_pointer *pointer, + struct weston_pointer_motion_event *event) +{ + struct weston_coord_global pos; + + pos = weston_pointer_motion_to_abs(pointer, event); + weston_pointer_move_to(pointer, pos); +} + +/** Verify if the pointer is in a valid position and move it if it isn't. + */ +static void +weston_pointer_handle_output_destroy(struct wl_listener *listener, void *data) +{ + struct weston_pointer *pointer; + struct weston_compositor *ec; + struct weston_output *output, *closest = NULL; + int x, y, distance, min = INT_MAX; + struct weston_coord_global pos; + + pointer = container_of(listener, struct weston_pointer, + output_destroy_listener); + ec = pointer->seat->compositor; + + x = pointer->pos.c.x; + y = pointer->pos.c.y; + + wl_list_for_each(output, &ec->output_list, link) { + if (weston_output_contains_point(output, x, y)) + return; + + /* Aproximante the distance from the pointer to the center of + * the output. */ + distance = abs(output->x + output->width / 2 - x) + + abs(output->y + output->height / 2 - y); + if (distance < min) { + min = distance; + closest = output; + } + } + + /* Nothing to do if there's no output left. */ + if (!closest) + return; + + pos = weston_pointer_clamp_for_output(pointer, closest, pointer->pos); + weston_pointer_move_to(pointer, pos); +} + +WL_EXPORT void +notify_motion(struct weston_seat *seat, + const struct timespec *time, + struct weston_pointer_motion_event *event) +{ + struct weston_compositor *ec = seat->compositor; + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + weston_compositor_wake(ec); + pointer->grab->interface->motion(pointer->grab, time, event); +} + +static void +run_modifier_bindings(struct weston_seat *seat, uint32_t old, uint32_t new) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + uint32_t diff; + unsigned int i; + struct { + uint32_t xkb; + enum weston_keyboard_modifier weston; + } mods[] = { + { keyboard->xkb_info->ctrl_mod, MODIFIER_CTRL }, + { keyboard->xkb_info->alt_mod, MODIFIER_ALT }, + { keyboard->xkb_info->super_mod, MODIFIER_SUPER }, + { keyboard->xkb_info->shift_mod, MODIFIER_SHIFT }, + }; + + diff = new & ~old; + for (i = 0; i < ARRAY_LENGTH(mods); i++) { + if (diff & (1 << mods[i].xkb)) + weston_compositor_run_modifier_binding(compositor, + keyboard, + mods[i].weston, + WL_KEYBOARD_KEY_STATE_PRESSED); + } + + diff = old & ~new; + for (i = 0; i < ARRAY_LENGTH(mods); i++) { + if (diff & (1 << mods[i].xkb)) + weston_compositor_run_modifier_binding(compositor, + keyboard, + mods[i].weston, + WL_KEYBOARD_KEY_STATE_RELEASED); + } +} + +WL_EXPORT void +notify_motion_absolute(struct weston_seat *seat, const struct timespec *time, + struct weston_coord_global pos) +{ + struct weston_compositor *ec = seat->compositor; + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_pointer_motion_event event = { 0 }; + + weston_compositor_wake(ec); + + event = (struct weston_pointer_motion_event) { + .mask = WESTON_POINTER_MOTION_ABS, + .abs = pos, + }; + pointer->grab->interface->motion(pointer->grab, time, &event); +} + +static unsigned int +peek_next_activate_serial(struct weston_compositor *c) +{ + unsigned serial = c->activate_serial + 1; + + return serial == 0 ? 1 : serial; +} + +static void +inc_activate_serial(struct weston_compositor *c) +{ + c->activate_serial = peek_next_activate_serial (c); +} + +WL_EXPORT void +weston_view_activate_input(struct weston_view *view, + struct weston_seat *seat, + uint32_t flags) +{ + struct weston_compositor *compositor = seat->compositor; + + if (flags & WESTON_ACTIVATE_FLAG_CLICKED) { + view->click_to_activate_serial = + peek_next_activate_serial(compositor); + } + + weston_seat_set_keyboard_focus(seat, view->surface); +} + +WL_EXPORT void +notify_button(struct weston_seat *seat, const struct timespec *time, + int32_t button, enum wl_pointer_button_state state) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + weston_compositor_idle_inhibit(compositor); + if (pointer->button_count == 0) { + pointer->grab_button = button; + pointer->grab_time = *time; + pointer->grab_pos = pointer->pos; + } + pointer->button_count++; + } else { + weston_compositor_idle_release(compositor); + pointer->button_count--; + } + + weston_compositor_run_button_binding(compositor, pointer, time, button, + state); + + pointer->grab->interface->button(pointer->grab, time, button, state); + + if (pointer->button_count == 1) + pointer->grab_serial = + wl_display_get_serial(compositor->wl_display); +} + +WL_EXPORT void +notify_axis(struct weston_seat *seat, const struct timespec *time, + struct weston_pointer_axis_event *event) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + weston_compositor_wake(compositor); + + if (weston_compositor_run_axis_binding(compositor, pointer, + time, event)) + return; + + pointer->grab->interface->axis(pointer->grab, time, event); +} + +WL_EXPORT void +notify_axis_source(struct weston_seat *seat, uint32_t source) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + weston_compositor_wake(compositor); + + pointer->grab->interface->axis_source(pointer->grab, source); +} + +WL_EXPORT void +notify_pointer_frame(struct weston_seat *seat) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + weston_compositor_wake(compositor); + + pointer->grab->interface->frame(pointer->grab); +} + +WL_EXPORT int +weston_keyboard_set_locks(struct weston_keyboard *keyboard, + uint32_t mask, uint32_t value) +{ + uint32_t serial; + xkb_mod_mask_t mods_depressed, mods_latched, mods_locked, group; xkb_mod_mask_t num, caps; - /* We don't want the leds to go out of sync with the actual state - * so if the backend has no way to change the leds don't try to - * change the state */ - if (!keyboard->seat->led_update) - return -1; + /* We don't want the leds to go out of sync with the actual state + * so if the backend has no way to change the leds don't try to + * change the state */ + if (!keyboard->seat->led_update) + return -1; + + mods_depressed = xkb_state_serialize_mods(keyboard->xkb_state.state, + XKB_STATE_DEPRESSED); + mods_latched = xkb_state_serialize_mods(keyboard->xkb_state.state, + XKB_STATE_LATCHED); + mods_locked = xkb_state_serialize_mods(keyboard->xkb_state.state, + XKB_STATE_LOCKED); + group = xkb_state_serialize_group(keyboard->xkb_state.state, + XKB_STATE_EFFECTIVE); + + num = (1 << keyboard->xkb_info->mod2_mod); + caps = (1 << keyboard->xkb_info->caps_mod); + if (mask & WESTON_NUM_LOCK) { + if (value & WESTON_NUM_LOCK) + mods_locked |= num; + else + mods_locked &= ~num; + } + if (mask & WESTON_CAPS_LOCK) { + if (value & WESTON_CAPS_LOCK) + mods_locked |= caps; + else + mods_locked &= ~caps; + } + + xkb_state_update_mask(keyboard->xkb_state.state, mods_depressed, + mods_latched, mods_locked, 0, 0, group); + + serial = wl_display_next_serial( + keyboard->seat->compositor->wl_display); + notify_modifiers(keyboard->seat, serial); + + return 0; +} + +WL_EXPORT void +notify_modifiers(struct weston_seat *seat, uint32_t serial) +{ + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + struct weston_keyboard_grab *grab = keyboard->grab; + uint32_t mods_depressed, mods_latched, mods_locked, group; + uint32_t mods_lookup; + enum weston_led leds = 0; + int changed = 0; + + /* Serialize and update our internal state, checking to see if it's + * different to the previous state. */ + mods_depressed = xkb_state_serialize_mods(keyboard->xkb_state.state, + XKB_STATE_MODS_DEPRESSED); + mods_latched = xkb_state_serialize_mods(keyboard->xkb_state.state, + XKB_STATE_MODS_LATCHED); + mods_locked = xkb_state_serialize_mods(keyboard->xkb_state.state, + XKB_STATE_MODS_LOCKED); + group = xkb_state_serialize_layout(keyboard->xkb_state.state, + XKB_STATE_LAYOUT_EFFECTIVE); + + if (mods_depressed != keyboard->modifiers.mods_depressed || + mods_latched != keyboard->modifiers.mods_latched || + mods_locked != keyboard->modifiers.mods_locked || + group != keyboard->modifiers.group) + changed = 1; + + run_modifier_bindings(seat, keyboard->modifiers.mods_depressed, + mods_depressed); + + keyboard->modifiers.mods_depressed = mods_depressed; + keyboard->modifiers.mods_latched = mods_latched; + keyboard->modifiers.mods_locked = mods_locked; + keyboard->modifiers.group = group; + + /* And update the modifier_state for bindings. */ + mods_lookup = mods_depressed | mods_latched; + seat->modifier_state = 0; + if (mods_lookup & (1 << keyboard->xkb_info->ctrl_mod)) + seat->modifier_state |= MODIFIER_CTRL; + if (mods_lookup & (1 << keyboard->xkb_info->alt_mod)) + seat->modifier_state |= MODIFIER_ALT; + if (mods_lookup & (1 << keyboard->xkb_info->super_mod)) + seat->modifier_state |= MODIFIER_SUPER; + if (mods_lookup & (1 << keyboard->xkb_info->shift_mod)) + seat->modifier_state |= MODIFIER_SHIFT; + + /* Finally, notify the compositor that LEDs have changed. */ + if (xkb_state_led_index_is_active(keyboard->xkb_state.state, + keyboard->xkb_info->num_led)) + leds |= LED_NUM_LOCK; + if (xkb_state_led_index_is_active(keyboard->xkb_state.state, + keyboard->xkb_info->caps_led)) + leds |= LED_CAPS_LOCK; + if (xkb_state_led_index_is_active(keyboard->xkb_state.state, + keyboard->xkb_info->scroll_led)) + leds |= LED_SCROLL_LOCK; + if (leds != keyboard->xkb_state.leds && seat->led_update) + seat->led_update(seat, leds); + keyboard->xkb_state.leds = leds; + + if (changed) { + grab->interface->modifiers(grab, + serial, + keyboard->modifiers.mods_depressed, + keyboard->modifiers.mods_latched, + keyboard->modifiers.mods_locked, + keyboard->modifiers.group); + } +} + +static void +update_modifier_state(struct weston_seat *seat, uint32_t serial, uint32_t key, + enum wl_keyboard_key_state state) +{ + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + enum xkb_key_direction direction; + + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) + direction = XKB_KEY_DOWN; + else + direction = XKB_KEY_UP; + + /* Offset the keycode by 8, as the evdev XKB rules reflect X's + * broken keycode system, which starts at 8. */ + xkb_state_update_key(keyboard->xkb_state.state, key + 8, direction); + + notify_modifiers(seat, serial); +} + +WL_EXPORT void +weston_keyboard_send_keymap(struct weston_keyboard *kbd, struct wl_resource *resource) +{ + struct weston_xkb_info *xkb_info = kbd->xkb_info; + int fd; + size_t size; + enum ro_anonymous_file_mapmode mapmode; + + if (wl_resource_get_version(resource) < 7) + mapmode = RO_ANONYMOUS_FILE_MAPMODE_SHARED; + else + mapmode = RO_ANONYMOUS_FILE_MAPMODE_PRIVATE; + + fd = os_ro_anonymous_file_get_fd(xkb_info->keymap_rofile, mapmode); + size = os_ro_anonymous_file_size(xkb_info->keymap_rofile); + + if (fd == -1) { + weston_log("creating a keymap file failed: %s\n", + strerror(errno)); + return; + } + + wl_keyboard_send_keymap(resource, + WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, + fd, + size); + + os_ro_anonymous_file_put_fd(fd); +} + +static void +send_modifiers(struct wl_resource *resource, uint32_t serial, struct weston_keyboard *keyboard) +{ + wl_keyboard_send_modifiers(resource, serial, + keyboard->modifiers.mods_depressed, + keyboard->modifiers.mods_latched, + keyboard->modifiers.mods_locked, + keyboard->modifiers.group); +} + +static struct weston_xkb_info * +weston_xkb_info_create(struct xkb_keymap *keymap); + +static void +update_keymap(struct weston_seat *seat) +{ + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + struct wl_resource *resource; + struct weston_xkb_info *xkb_info; + struct xkb_state *state; + xkb_mod_mask_t latched_mods; + xkb_mod_mask_t locked_mods; + + xkb_info = weston_xkb_info_create(keyboard->pending_keymap); + + xkb_keymap_unref(keyboard->pending_keymap); + keyboard->pending_keymap = NULL; + + if (!xkb_info) { + weston_log("failed to create XKB info\n"); + return; + } + + state = xkb_state_new(xkb_info->keymap); + if (!state) { + weston_log("failed to initialise XKB state\n"); + weston_xkb_info_destroy(xkb_info); + return; + } + + latched_mods = xkb_state_serialize_mods(keyboard->xkb_state.state, + XKB_STATE_MODS_LATCHED); + locked_mods = xkb_state_serialize_mods(keyboard->xkb_state.state, + XKB_STATE_MODS_LOCKED); + xkb_state_update_mask(state, + 0, /* depressed */ + latched_mods, + locked_mods, + 0, 0, 0); + + weston_xkb_info_destroy(keyboard->xkb_info); + keyboard->xkb_info = xkb_info; + + xkb_state_unref(keyboard->xkb_state.state); + keyboard->xkb_state.state = state; + + wl_resource_for_each(resource, &keyboard->resource_list) + weston_keyboard_send_keymap(keyboard, resource); + wl_resource_for_each(resource, &keyboard->focus_resource_list) + weston_keyboard_send_keymap(keyboard, resource); + + notify_modifiers(seat, wl_display_next_serial(seat->compositor->wl_display)); + + if (!latched_mods && !locked_mods) + return; + + wl_resource_for_each(resource, &keyboard->resource_list) + send_modifiers(resource, wl_display_get_serial(seat->compositor->wl_display), keyboard); + wl_resource_for_each(resource, &keyboard->focus_resource_list) + send_modifiers(resource, wl_display_get_serial(seat->compositor->wl_display), keyboard); +} + +WL_EXPORT void +notify_key(struct weston_seat *seat, const struct timespec *time, uint32_t key, + enum wl_keyboard_key_state state, + enum weston_key_state_update update_state) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + struct weston_keyboard_grab *grab = keyboard->grab; + uint32_t *k, *end; + + end = keyboard->keys.data + keyboard->keys.size; + for (k = keyboard->keys.data; k < end; k++) { + if (*k == key) { + /* Ignore server-generated repeats. */ + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) + return; + *k = *--end; + } + } + keyboard->keys.size = (void *) end - keyboard->keys.data; + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + k = wl_array_add(&keyboard->keys, sizeof *k); + *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, + key, state); + grab = keyboard->grab; + } + + grab->interface->key(grab, time, key, state); + + if (keyboard->pending_keymap && + keyboard->keys.size == 0) + update_keymap(seat); + + if (update_state == STATE_UPDATE_AUTOMATIC) { + update_modifier_state(seat, + wl_display_get_serial(compositor->wl_display), + key, + state); + } + + keyboard->grab_serial = wl_display_get_serial(compositor->wl_display); + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + keyboard->grab_time = *time; + keyboard->grab_key = key; + } +} + +WL_EXPORT void +clear_pointer_focus(struct weston_seat *seat) +{ + /* FIXME: We should call weston_pointer_set_focus(seat, NULL) here, + * but somehow that breaks re-entry... */ +} + +WL_EXPORT void +notify_pointer_focus(struct weston_seat *seat, struct weston_output *output, + struct weston_coord_global pos) +{ + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + + assert(output); + + weston_pointer_move_to(pointer, pos); +} + +static void +destroy_device_saved_kbd_focus(struct wl_listener *listener, void *data) +{ + struct weston_seat *ws; + + ws = container_of(listener, struct weston_seat, + saved_kbd_focus_listener); + + ws->saved_kbd_focus = NULL; + + wl_list_remove(&ws->saved_kbd_focus_listener.link); + ws->saved_kbd_focus_listener.notify = NULL; +} + +WL_EXPORT void +notify_keyboard_focus_in(struct weston_seat *seat, struct wl_array *keys, + enum weston_key_state_update update_state) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + struct weston_surface *surface; + uint32_t *k, serial; - mods_depressed = xkb_state_serialize_mods(keyboard->xkb_state.state, - XKB_STATE_DEPRESSED); - mods_latched = xkb_state_serialize_mods(keyboard->xkb_state.state, - XKB_STATE_LATCHED); - mods_locked = xkb_state_serialize_mods(keyboard->xkb_state.state, - XKB_STATE_LOCKED); - group = xkb_state_serialize_group(keyboard->xkb_state.state, - XKB_STATE_EFFECTIVE); + serial = wl_display_next_serial(compositor->wl_display); + wl_array_copy(&keyboard->keys, keys); + wl_array_for_each(k, &keyboard->keys) { + weston_compositor_idle_inhibit(compositor); + if (update_state == STATE_UPDATE_AUTOMATIC) + update_modifier_state(seat, serial, *k, + WL_KEYBOARD_KEY_STATE_PRESSED); + } - num = (1 << keyboard->xkb_info->mod2_mod); - caps = (1 << keyboard->xkb_info->caps_mod); - if (mask & WESTON_NUM_LOCK) { - if (value & WESTON_NUM_LOCK) - mods_locked |= num; - else - mods_locked &= ~num; + surface = seat->saved_kbd_focus; + if (surface) { + wl_list_remove(&seat->saved_kbd_focus_listener.link); + seat->saved_kbd_focus_listener.notify = NULL; + seat->saved_kbd_focus = NULL; + if (seat->use_saved_kbd_focus) + weston_keyboard_set_focus(keyboard, surface); } - if (mask & WESTON_CAPS_LOCK) { - if (value & WESTON_CAPS_LOCK) - mods_locked |= caps; - else - mods_locked &= ~caps; +} + +WL_EXPORT void +notify_keyboard_focus_out(struct weston_seat *seat) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_surface *focus = keyboard->focus; + uint32_t *k, serial; + + serial = wl_display_next_serial(compositor->wl_display); + wl_array_for_each(k, &keyboard->keys) { + weston_compositor_idle_release(compositor); + update_modifier_state(seat, serial, *k, + WL_KEYBOARD_KEY_STATE_RELEASED); } - xkb_state_update_mask(keyboard->xkb_state.state, mods_depressed, - mods_latched, mods_locked, 0, 0, group); + seat->modifier_state = 0; - serial = wl_display_next_serial( - keyboard->seat->compositor->wl_display); - notify_modifiers(keyboard->seat, serial); + weston_keyboard_set_focus(keyboard, NULL); + weston_keyboard_cancel_grab(keyboard); + if (pointer) + weston_pointer_cancel_grab(pointer); - return 0; + 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, + &seat->saved_kbd_focus_listener); + } } WL_EXPORT void -notify_modifiers(struct weston_seat *seat, uint32_t serial) +weston_touch_set_focus(struct weston_touch *touch, struct weston_view *view) { - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct weston_keyboard_grab *grab = keyboard->grab; - uint32_t mods_depressed, mods_latched, mods_locked, group; - uint32_t mods_lookup; - enum weston_led leds = 0; - int changed = 0; + struct wl_list *focus_resource_list; - /* Serialize and update our internal state, checking to see if it's - * different to the previous state. */ - mods_depressed = xkb_state_serialize_mods(keyboard->xkb_state.state, - XKB_STATE_MODS_DEPRESSED); - mods_latched = xkb_state_serialize_mods(keyboard->xkb_state.state, - XKB_STATE_MODS_LATCHED); - mods_locked = xkb_state_serialize_mods(keyboard->xkb_state.state, - XKB_STATE_MODS_LOCKED); - group = xkb_state_serialize_layout(keyboard->xkb_state.state, - XKB_STATE_LAYOUT_EFFECTIVE); + focus_resource_list = &touch->focus_resource_list; - if (mods_depressed != keyboard->modifiers.mods_depressed || - mods_latched != keyboard->modifiers.mods_latched || - mods_locked != keyboard->modifiers.mods_locked || - group != keyboard->modifiers.group) - changed = 1; + if (view && touch->focus && + touch->focus->surface == view->surface) { + touch->focus = view; + return; + } - run_modifier_bindings(seat, keyboard->modifiers.mods_depressed, - mods_depressed); + wl_list_remove(&touch->focus_resource_listener.link); + wl_list_init(&touch->focus_resource_listener.link); + wl_list_remove(&touch->focus_view_listener.link); + wl_list_init(&touch->focus_view_listener.link); - keyboard->modifiers.mods_depressed = mods_depressed; - keyboard->modifiers.mods_latched = mods_latched; - keyboard->modifiers.mods_locked = mods_locked; - keyboard->modifiers.group = group; + if (!wl_list_empty(focus_resource_list)) { + move_resources(&touch->resource_list, + focus_resource_list); + } - /* And update the modifier_state for bindings. */ - mods_lookup = mods_depressed | mods_latched; - seat->modifier_state = 0; - if (mods_lookup & (1 << keyboard->xkb_info->ctrl_mod)) - seat->modifier_state |= MODIFIER_CTRL; - if (mods_lookup & (1 << keyboard->xkb_info->alt_mod)) - seat->modifier_state |= MODIFIER_ALT; - if (mods_lookup & (1 << keyboard->xkb_info->super_mod)) - seat->modifier_state |= MODIFIER_SUPER; - if (mods_lookup & (1 << keyboard->xkb_info->shift_mod)) - seat->modifier_state |= MODIFIER_SHIFT; + if (view) { + struct wl_client *surface_client; - /* Finally, notify the compositor that LEDs have changed. */ - if (xkb_state_led_index_is_active(keyboard->xkb_state.state, - keyboard->xkb_info->num_led)) - leds |= LED_NUM_LOCK; - if (xkb_state_led_index_is_active(keyboard->xkb_state.state, - keyboard->xkb_info->caps_led)) - leds |= LED_CAPS_LOCK; - if (xkb_state_led_index_is_active(keyboard->xkb_state.state, - keyboard->xkb_info->scroll_led)) - leds |= LED_SCROLL_LOCK; - if (leds != keyboard->xkb_state.leds && seat->led_update) - seat->led_update(seat, leds); - keyboard->xkb_state.leds = leds; + if (!view->surface->resource) { + touch->focus = NULL; + return; + } - if (changed) { - grab->interface->modifiers(grab, - serial, - keyboard->modifiers.mods_depressed, - keyboard->modifiers.mods_latched, - keyboard->modifiers.mods_locked, - keyboard->modifiers.group); + surface_client = wl_resource_get_client(view->surface->resource); + move_resources_for_client(focus_resource_list, + &touch->resource_list, + surface_client); + wl_resource_add_destroy_listener(view->surface->resource, + &touch->focus_resource_listener); + wl_signal_add(&view->destroy_signal, &touch->focus_view_listener); } + touch->focus = view; } static void -update_modifier_state(struct weston_seat *seat, uint32_t serial, uint32_t key, - enum wl_keyboard_key_state state) +process_touch_normal(struct weston_touch_device *device, + const struct timespec *time, int touch_id, + const struct weston_coord_global *pos, int touch_type) { - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - enum xkb_key_direction direction; + struct weston_touch *touch = device->aggregate; + struct weston_touch_grab *grab = device->aggregate->grab; + struct weston_compositor *ec = device->aggregate->seat->compositor; + struct weston_view *ev; + wl_fixed_t x, y; - if (state == WL_KEYBOARD_KEY_STATE_PRESSED) - direction = XKB_KEY_DOWN; - else - direction = XKB_KEY_UP; + if (pos) { + assert(touch_type != WL_TOUCH_UP); + x = wl_fixed_from_double(pos->c.x); + y = wl_fixed_from_double(pos->c.y); + } else { + assert(touch_type == WL_TOUCH_UP); + } - /* Offset the keycode by 8, as the evdev XKB rules reflect X's - * broken keycode system, which starts at 8. */ - xkb_state_update_key(keyboard->xkb_state.state, key + 8, direction); + /* Update grab's global coordinates. */ + if (touch_id == touch->grab_touch_id && touch_type != WL_TOUCH_UP) { + touch->grab_x = x; + touch->grab_y = y; + } - notify_modifiers(seat, serial); -} + switch (touch_type) { + case WL_TOUCH_DOWN: + /* the first finger down picks the view, and all further go + * to that view for the remainder of the touch session i.e. + * until all touch points are up again. */ + if (touch->num_tp == 1) { + ev = weston_compositor_pick_view(ec, *pos); + weston_touch_set_focus(touch, ev); + } else if (!touch->focus) { + /* Unexpected condition: We have non-initial touch but + * there is no focused surface. + */ + weston_log("touch event received with %d points down " + "but no surface focused\n", touch->num_tp); + return; + } -WL_EXPORT void -weston_keyboard_send_keymap(struct weston_keyboard *kbd, struct wl_resource *resource) -{ - struct weston_xkb_info *xkb_info = kbd->xkb_info; - int fd; - size_t size; - enum ro_anonymous_file_mapmode mapmode; + weston_compositor_run_touch_binding(ec, touch, + time, touch_type); - if (wl_resource_get_version(resource) < 7) - mapmode = RO_ANONYMOUS_FILE_MAPMODE_SHARED; - else - mapmode = RO_ANONYMOUS_FILE_MAPMODE_PRIVATE; + grab->interface->down(grab, time, touch_id, x, y); + if (touch->num_tp == 1) { + touch->grab_serial = + wl_display_get_serial(ec->wl_display); + touch->grab_touch_id = touch_id; + touch->grab_time = *time; + touch->grab_x = x; + touch->grab_y = y; + } - fd = os_ro_anonymous_file_get_fd(xkb_info->keymap_rofile, mapmode); - size = os_ro_anonymous_file_size(xkb_info->keymap_rofile); + break; + case WL_TOUCH_MOTION: + ev = touch->focus; + if (!ev) + break; - if (fd == -1) { - weston_log("creating a keymap file failed: %s\n", - strerror(errno)); - return; + grab->interface->motion(grab, time, touch_id, x, y); + break; + case WL_TOUCH_UP: + grab->interface->up(grab, time, touch_id); + touch->pending_focus_reset = true; + break; } +} - wl_keyboard_send_keymap(resource, - WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, - fd, - size); +static enum weston_touch_mode +get_next_touch_mode(enum weston_touch_mode from) +{ + switch (from) { + case WESTON_TOUCH_MODE_PREP_NORMAL: + return WESTON_TOUCH_MODE_NORMAL; - os_ro_anonymous_file_put_fd(fd); -} + case WESTON_TOUCH_MODE_PREP_CALIB: + return WESTON_TOUCH_MODE_CALIB; -static void -send_modifiers(struct wl_resource *resource, uint32_t serial, struct weston_keyboard *keyboard) -{ - wl_keyboard_send_modifiers(resource, serial, - keyboard->modifiers.mods_depressed, - keyboard->modifiers.mods_latched, - keyboard->modifiers.mods_locked, - keyboard->modifiers.group); -} + case WESTON_TOUCH_MODE_NORMAL: + case WESTON_TOUCH_MODE_CALIB: + return from; + } -static struct weston_xkb_info * -weston_xkb_info_create(struct xkb_keymap *keymap); + return WESTON_TOUCH_MODE_NORMAL; +} +/** Global touch mode update + * + * If no seat has a touch down and the compositor is in a PREP touch mode, + * set the compositor to the goal touch mode. + * + * Calls calibrator if touch mode changed. + */ static void -update_keymap(struct weston_seat *seat) +weston_compositor_update_touch_mode(struct weston_compositor *compositor) { - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct wl_resource *resource; - struct weston_xkb_info *xkb_info; - struct xkb_state *state; - xkb_mod_mask_t latched_mods; - xkb_mod_mask_t locked_mods; + struct weston_seat *seat; + struct weston_touch *touch; + enum weston_touch_mode goal; - xkb_info = weston_xkb_info_create(keyboard->pending_keymap); + wl_list_for_each(seat, &compositor->seat_list, link) { + touch = weston_seat_get_touch(seat); + if (!touch) + continue; - xkb_keymap_unref(keyboard->pending_keymap); - keyboard->pending_keymap = NULL; + if (touch->num_tp > 0) + return; + } - if (!xkb_info) { - weston_log("failed to create XKB info\n"); - return; + goal = get_next_touch_mode(compositor->touch_mode); + if (compositor->touch_mode != goal) { + compositor->touch_mode = goal; + touch_calibrator_mode_changed(compositor); } +} - state = xkb_state_new(xkb_info->keymap); - if (!state) { - weston_log("failed to initialise XKB state\n"); - weston_xkb_info_destroy(xkb_info); +/** Start transition to normal touch event handling + * + * The touch event mode changes when all touches on all touch devices have + * been lifted. If no touches are currently down, the transition is immediate. + * + * \sa weston_touch_mode + */ +void +weston_compositor_set_touch_mode_normal(struct weston_compositor *compositor) +{ + switch (compositor->touch_mode) { + case WESTON_TOUCH_MODE_PREP_NORMAL: + case WESTON_TOUCH_MODE_NORMAL: + return; + case WESTON_TOUCH_MODE_PREP_CALIB: + compositor->touch_mode = WESTON_TOUCH_MODE_NORMAL; + touch_calibrator_mode_changed(compositor); return; + case WESTON_TOUCH_MODE_CALIB: + compositor->touch_mode = WESTON_TOUCH_MODE_PREP_NORMAL; } - latched_mods = xkb_state_serialize_mods(keyboard->xkb_state.state, - XKB_STATE_MODS_LATCHED); - locked_mods = xkb_state_serialize_mods(keyboard->xkb_state.state, - XKB_STATE_MODS_LOCKED); - xkb_state_update_mask(state, - 0, /* depressed */ - latched_mods, - locked_mods, - 0, 0, 0); - - weston_xkb_info_destroy(keyboard->xkb_info); - keyboard->xkb_info = xkb_info; - - xkb_state_unref(keyboard->xkb_state.state); - keyboard->xkb_state.state = state; - - wl_resource_for_each(resource, &keyboard->resource_list) - weston_keyboard_send_keymap(keyboard, resource); - wl_resource_for_each(resource, &keyboard->focus_resource_list) - weston_keyboard_send_keymap(keyboard, resource); - - notify_modifiers(seat, wl_display_next_serial(seat->compositor->wl_display)); + weston_compositor_update_touch_mode(compositor); +} - if (!latched_mods && !locked_mods) +/** Start transition to calibrator touch event handling + * + * The touch event mode changes when all touches on all touch devices have + * been lifted. If no touches are currently down, the transition is immediate. + * + * \sa weston_touch_mode + */ +void +weston_compositor_set_touch_mode_calib(struct weston_compositor *compositor) +{ + switch (compositor->touch_mode) { + case WESTON_TOUCH_MODE_PREP_CALIB: + case WESTON_TOUCH_MODE_CALIB: + assert(0); + return; + case WESTON_TOUCH_MODE_PREP_NORMAL: + compositor->touch_mode = WESTON_TOUCH_MODE_CALIB; + touch_calibrator_mode_changed(compositor); return; + case WESTON_TOUCH_MODE_NORMAL: + compositor->touch_mode = WESTON_TOUCH_MODE_PREP_CALIB; + } - wl_resource_for_each(resource, &keyboard->resource_list) - send_modifiers(resource, wl_display_get_serial(seat->compositor->wl_display), keyboard); - wl_resource_for_each(resource, &keyboard->focus_resource_list) - send_modifiers(resource, wl_display_get_serial(seat->compositor->wl_display), keyboard); + weston_compositor_update_touch_mode(compositor); } +/** Feed in touch down, motion, and up events, calibratable device. + * + * It assumes always the correct cycle sequence until it gets here: touch_down + * → touch_update → ... → touch_update → touch_end. The driver is responsible + * for sending along such order. + * + * \param device The physical device that generated the event. + * \param time The event timestamp. + * \param touch_id ID for the touch point of this event (multi-touch). + * \param pos X,Y coordinate in compositor global space, or NULL for WL_TOUCH_UP. + * \param norm Normalized device X, Y coordinates in calibration space, or NULL. + * \param touch_type Either WL_TOUCH_DOWN, WL_TOUCH_UP, or WL_TOUCH_MOTION. + * + * Coordinates double_x and double_y are used for normal operation. + * + * Coordinates norm are only used for touch device calibration. If and only if + * the weston_touch_device does not support calibrating, norm must be NULL. + * + * The calibration space is the normalized coordinate space + * [0.0, 1.0]×[0.0, 1.0] of the weston_touch_device. This is assumed to + * map to the similar normalized coordinate space of the associated + * weston_output. + */ WL_EXPORT void -notify_key(struct weston_seat *seat, const struct timespec *time, uint32_t key, - enum wl_keyboard_key_state state, - enum weston_key_state_update update_state) +notify_touch_normalized(struct weston_touch_device *device, + const struct timespec *time, + int touch_id, + const struct weston_coord_global *pos, + const struct weston_point2d_device_normalized *norm, + int touch_type) { - struct weston_compositor *compositor = seat->compositor; - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct weston_keyboard_grab *grab = keyboard->grab; - uint32_t *k, *end; + struct weston_seat *seat = device->aggregate->seat; + struct weston_touch *touch = device->aggregate; - if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { - weston_compositor_idle_inhibit(compositor); + if (touch_type != WL_TOUCH_UP) { + assert(pos); + + if (weston_touch_device_can_calibrate(device)) + assert(norm != NULL); + else + assert(norm == NULL); } else { - weston_compositor_idle_release(compositor); + assert(!pos); } - end = keyboard->keys.data + keyboard->keys.size; - for (k = keyboard->keys.data; k < end; k++) { - if (*k == key) { - /* Ignore server-generated repeats. */ - if (state == WL_KEYBOARD_KEY_STATE_PRESSED) - return; - *k = *--end; + /* Update touchpoints count regardless of the current mode. */ + switch (touch_type) { + case WL_TOUCH_DOWN: + weston_compositor_idle_inhibit(seat->compositor); + + touch->num_tp++; + break; + case WL_TOUCH_UP: + if (touch->num_tp == 0) { + /* This can happen if we start out with one or + * more fingers on the touch screen, in which + * case we didn't get the corresponding down + * event. */ + weston_log("Unmatched touch up event on seat %s, device %s\n", + seat->seat_name, device->syspath); + return; } - } - keyboard->keys.size = (void *) end - keyboard->keys.data; - if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { - k = wl_array_add(&keyboard->keys, sizeof *k); - *k = key; - } + weston_compositor_idle_release(seat->compositor); - if (grab == &keyboard->default_grab || - grab == &keyboard->input_method_grab) { - weston_compositor_run_key_binding(compositor, keyboard, time, - key, state); - grab = keyboard->grab; + touch->num_tp--; + break; + default: + break; } - grab->interface->key(grab, time, key, state); - - if (keyboard->pending_keymap && - keyboard->keys.size == 0) - update_keymap(seat); - - if (update_state == STATE_UPDATE_AUTOMATIC) { - update_modifier_state(seat, - wl_display_get_serial(compositor->wl_display), - key, - state); + /* Properly forward the touch event */ + switch (weston_touch_device_get_mode(device)) { + case WESTON_TOUCH_MODE_NORMAL: + case WESTON_TOUCH_MODE_PREP_CALIB: + process_touch_normal(device, time, touch_id, pos, touch_type); + break; + case WESTON_TOUCH_MODE_CALIB: + case WESTON_TOUCH_MODE_PREP_NORMAL: + notify_touch_calibrator(device, time, touch_id, + norm, touch_type); + break; } +} - keyboard->grab_serial = wl_display_get_serial(compositor->wl_display); - if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { - keyboard->grab_time = *time; - keyboard->grab_key = key; +WL_EXPORT void +notify_touch_frame(struct weston_touch_device *device) +{ + struct weston_touch_grab *grab; + + 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); + if (grab->touch->pending_focus_reset) { + if (grab->touch->num_tp == 0) + weston_touch_set_focus(grab->touch, NULL); + grab->touch->pending_focus_reset = false; + } + break; + case WESTON_TOUCH_MODE_CALIB: + case WESTON_TOUCH_MODE_PREP_NORMAL: + notify_touch_calibrator_frame(device); + break; } + + weston_compositor_update_touch_mode(device->aggregate->seat->compositor); } WL_EXPORT void -notify_pointer_focus(struct weston_seat *seat, struct weston_output *output, - double x, double y) +notify_touch_cancel(struct weston_touch_device *device) { - struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_touch_grab *grab; - if (output) { - weston_pointer_move_to(pointer, - wl_fixed_from_double(x), - wl_fixed_from_double(y)); - } else { - /* FIXME: We should call weston_pointer_set_focus(seat, - * NULL) here, but somehow that breaks re-entry... */ + switch (weston_touch_device_get_mode(device)) { + case WESTON_TOUCH_MODE_NORMAL: + case WESTON_TOUCH_MODE_PREP_CALIB: + grab = device->aggregate->grab; + grab->interface->cancel(grab); + break; + case WESTON_TOUCH_MODE_CALIB: + case WESTON_TOUCH_MODE_PREP_NORMAL: + notify_touch_calibrator_cancel(device); + break; } + + weston_compositor_update_touch_mode(device->aggregate->seat->compositor); } -static void -destroy_device_saved_kbd_focus(struct wl_listener *listener, void *data) +static int +pointer_cursor_surface_get_label(struct weston_surface *surface, + char *buf, size_t len) { - struct weston_seat *ws; + return snprintf(buf, len, "cursor"); +} - ws = container_of(listener, struct weston_seat, - saved_kbd_focus_listener); +static void +tablet_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} - ws->saved_kbd_focus = NULL; +static const struct zwp_tablet_v2_interface tablet_interface = { + tablet_destroy, +}; - wl_list_remove(&ws->saved_kbd_focus_listener.link); - ws->saved_kbd_focus_listener.notify = NULL; +static void +send_tablet_added(struct weston_tablet *tablet, + struct wl_resource *tablet_seat_resource, + struct wl_resource *tablet_resource) +{ + zwp_tablet_seat_v2_send_tablet_added(tablet_seat_resource, tablet_resource); + zwp_tablet_v2_send_name(tablet_resource, tablet->name); + zwp_tablet_v2_send_id(tablet_resource, tablet->vid, tablet->pid); + zwp_tablet_v2_send_path(tablet_resource, tablet->path); + zwp_tablet_v2_send_done(tablet_resource); } -WL_EXPORT void -notify_keyboard_focus_in(struct weston_seat *seat, struct wl_array *keys, - enum weston_key_state_update update_state) +static void +tablet_add_resource(struct weston_tablet *tablet, + struct wl_client *client, + struct wl_resource *tablet_seat_resource) { - struct weston_compositor *compositor = seat->compositor; - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct weston_surface *surface; - uint32_t *k, serial; + struct wl_resource *tablet_resource; - serial = wl_display_next_serial(compositor->wl_display); - wl_array_copy(&keyboard->keys, keys); - wl_array_for_each(k, &keyboard->keys) { - weston_compositor_idle_inhibit(compositor); - if (update_state == STATE_UPDATE_AUTOMATIC) - update_modifier_state(seat, serial, *k, - WL_KEYBOARD_KEY_STATE_PRESSED); - } + tablet_resource = wl_resource_create(client, + &zwp_tablet_v2_interface, + 1, 0); - surface = seat->saved_kbd_focus; - if (surface) { - wl_list_remove(&seat->saved_kbd_focus_listener.link); - seat->saved_kbd_focus_listener.notify = NULL; - seat->saved_kbd_focus = NULL; - if (seat->use_saved_kbd_focus) - weston_keyboard_set_focus(keyboard, surface); - } + wl_list_insert(&tablet->resource_list, + wl_resource_get_link(tablet_resource)); + wl_resource_set_implementation(tablet_resource, + &tablet_interface, + tablet, + unbind_resource); + + wl_resource_set_user_data(tablet_resource, tablet); + send_tablet_added(tablet, tablet_seat_resource, tablet_resource); } WL_EXPORT void -notify_keyboard_focus_out(struct weston_seat *seat) +notify_tablet_added(struct weston_tablet *tablet) { - struct weston_compositor *compositor = seat->compositor; - struct weston_keyboard *keyboard = weston_seat_get_keyboard(seat); - struct weston_pointer *pointer = weston_seat_get_pointer(seat); - struct weston_surface *focus = keyboard->focus; - uint32_t *k, serial; + struct wl_resource *tablet_seat_resource; + struct weston_seat *seat = tablet->seat; - serial = wl_display_next_serial(compositor->wl_display); - wl_array_for_each(k, &keyboard->keys) { - weston_compositor_idle_release(compositor); - update_modifier_state(seat, serial, *k, - WL_KEYBOARD_KEY_STATE_RELEASED); + wl_resource_for_each(tablet_seat_resource, + &seat->tablet_seat_resource_list) { + tablet_add_resource(tablet, + wl_resource_get_client(tablet_seat_resource), + tablet_seat_resource); } +} - seat->modifier_state = 0; +static void +tablet_tool_cursor_surface_committed(struct weston_surface *es, + struct weston_coord_surface new_origin) +{ + struct weston_tablet_tool *tool = es->committed_private; + struct weston_coord_global pos; - weston_keyboard_set_focus(keyboard, NULL); - weston_keyboard_cancel_grab(keyboard); - if (pointer) - weston_pointer_cancel_grab(pointer); + if (es->width == 0) + return; - if (focus) { - seat->use_saved_kbd_focus = true; - seat->saved_kbd_focus = focus; - seat->saved_kbd_focus_listener.notify = - destroy_device_saved_kbd_focus; - wl_signal_add(&focus->destroy_signal, - &seat->saved_kbd_focus_listener); + assert(es == tool->sprite->surface); + + tool->hotspot.c = weston_coord_sub(tool->hotspot.c, new_origin.c); + pos.c = weston_coord_sub(tool->pos.c, tool->hotspot.c); + + weston_view_set_position(tool->sprite, pos.c.x, pos.c.y); + + empty_region(&es->pending.input); + empty_region(&es->input); + + if (!weston_surface_is_mapped(es)) { + weston_layer_entry_insert( + &es->compositor->cursor_layer.view_list, + &tool->sprite->layer_link); + weston_view_update_transform(tool->sprite); + es->is_mapped = true; + tool->sprite->is_mapped = true; } } -WL_EXPORT void -weston_touch_set_focus(struct weston_touch *touch, struct weston_view *view) +static void +tablet_tool_set_cursor(struct wl_client *client, struct wl_resource *resource, + uint32_t serial, struct wl_resource *surface_resource, + int32_t hotspot_x, int32_t hotspot_y) { - struct wl_list *focus_resource_list; + struct weston_tablet_tool *tool = wl_resource_get_user_data(resource); + struct weston_surface *surface = NULL; - focus_resource_list = &touch->focus_resource_list; + if (!tool) + return; - if (view && touch->focus && - touch->focus->surface == view->surface) { - touch->focus = view; + if (surface_resource) + surface = wl_resource_get_user_data(surface_resource); + + if (tool->focus == NULL) return; - } - wl_list_remove(&touch->focus_resource_listener.link); - wl_list_init(&touch->focus_resource_listener.link); - wl_list_remove(&touch->focus_view_listener.link); - wl_list_init(&touch->focus_view_listener.link); + /* tablet->focus->surface->resource can be NULL. Surfaces like the + * black_surface used in shell.c for fullscreen don't have + * a resource, but can still have focus */ + if (tool->focus->surface->resource == NULL) + return; - if (!wl_list_empty(focus_resource_list)) { - move_resources(&touch->resource_list, - focus_resource_list); + if (wl_resource_get_client(tool->focus->surface->resource) != client) + return; + + if (tool->focus_serial - serial > UINT32_MAX / 2) + return; + + if (surface && tool->sprite && surface != tool->sprite->surface && + surface->committed) { + wl_resource_post_error(surface->resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "surface->configure already set"); + return; } - if (view) { - struct wl_client *surface_client; + if (tool->sprite) + tablet_tool_unmap_sprite(tool); - if (!view->surface->resource) { - touch->focus = NULL; - return; - } + if (!surface) + return; - surface_client = wl_resource_get_client(view->surface->resource); - move_resources_for_client(focus_resource_list, - &touch->resource_list, - surface_client); - wl_resource_add_destroy_listener(view->surface->resource, - &touch->focus_resource_listener); - wl_signal_add(&view->destroy_signal, &touch->focus_view_listener); + wl_signal_add(&surface->destroy_signal, + &tool->sprite_destroy_listener); + surface->committed = tablet_tool_cursor_surface_committed; + surface->committed_private = tool; + tool->sprite = weston_view_create(surface); + tool->hotspot = weston_coord_surface(hotspot_x, hotspot_y, surface); + + if (surface->buffer_ref.buffer) { + struct weston_coord_surface delta = + weston_coord_surface(0, 0, surface); + + tablet_tool_cursor_surface_committed(surface, delta); } - touch->focus = view; } static void -process_touch_normal(struct weston_touch_device *device, - const struct timespec *time, int touch_id, - double double_x, double double_y, int touch_type) +tablet_tool_destroy(struct wl_client *client, struct wl_resource *resource) { - struct weston_touch *touch = device->aggregate; - struct weston_touch_grab *grab = device->aggregate->grab; - struct weston_compositor *ec = device->aggregate->seat->compositor; - struct weston_view *ev; - 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); + wl_resource_destroy(resource); +} - /* Update grab's global coordinates. */ - if (touch_id == touch->grab_touch_id && touch_type != WL_TOUCH_UP) { - touch->grab_x = x; - touch->grab_y = y; +static const struct zwp_tablet_tool_v2_interface tablet_tool_interface = { + tablet_tool_set_cursor, + tablet_tool_destroy, +}; + +static void +send_tool_added(struct weston_tablet_tool *tool, + struct wl_resource *tool_seat_resource, + struct wl_resource *tool_resource) +{ + uint32_t caps, cap; + zwp_tablet_seat_v2_send_tool_added(tool_seat_resource, tool_resource); + zwp_tablet_tool_v2_send_type(tool_resource, tool->type); + zwp_tablet_tool_v2_send_hardware_serial(tool_resource, + tool->serial >> 32, + tool->serial & 0xFFFFFFFF); + zwp_tablet_tool_v2_send_hardware_id_wacom(tool_resource, + tool->hwid >> 32, + tool->hwid & 0xFFFFFFFF); + caps = tool->capabilities; + while (caps != 0) { + cap = ffs(caps) - 1; + zwp_tablet_tool_v2_send_capability(tool_resource, cap); + caps &= ~(1 << cap); } - switch (touch_type) { - case WL_TOUCH_DOWN: - /* the first finger down picks the view, and all further go - * to that view for the remainder of the touch session i.e. - * until all touch points are up again. */ - if (touch->num_tp == 1) { - ev = weston_compositor_pick_view(ec, x, y, &sx, &sy); - weston_touch_set_focus(touch, ev); - } else if (!touch->focus) { - /* Unexpected condition: We have non-initial touch but - * there is no focused surface. - */ - weston_log("touch event received with %d points down " - "but no surface focused\n", touch->num_tp); - return; - } + zwp_tablet_tool_v2_send_done(tool_resource); +} - weston_compositor_run_touch_binding(ec, touch, - time, touch_type); +static void +tablet_tool_add_resource(struct weston_tablet_tool *tool, + struct wl_client *client, + struct wl_resource *tablet_seat_resource) +{ + struct wl_resource *tool_resource; - grab->interface->down(grab, time, touch_id, x, y); - if (touch->num_tp == 1) { - touch->grab_serial = - wl_display_get_serial(ec->wl_display); - touch->grab_touch_id = touch_id; - touch->grab_time = *time; - touch->grab_x = x; - touch->grab_y = y; - } + tool_resource = wl_resource_create(client, + &zwp_tablet_tool_v2_interface, + 1, 0); - break; - case WL_TOUCH_MOTION: - ev = touch->focus; - if (!ev) - break; + wl_list_insert(&tool->resource_list, + wl_resource_get_link(tool_resource)); + wl_resource_set_implementation(tool_resource, + &tablet_tool_interface, + tool, unbind_resource); - grab->interface->motion(grab, time, touch_id, x, y); - break; - case WL_TOUCH_UP: - grab->interface->up(grab, time, touch_id); - if (touch->num_tp == 0) - weston_touch_set_focus(touch, NULL); - break; - } + wl_resource_set_user_data(tool_resource, tool); + send_tool_added(tool, tablet_seat_resource, tool_resource); } -static enum weston_touch_mode -get_next_touch_mode(enum weston_touch_mode from) +WL_EXPORT void +notify_tablet_tool_added(struct weston_tablet_tool *tool) { - switch (from) { - case WESTON_TOUCH_MODE_PREP_NORMAL: - return WESTON_TOUCH_MODE_NORMAL; + struct wl_resource *tablet_seat_resource; + struct weston_seat *seat = tool->seat; + struct wl_client *client; - case WESTON_TOUCH_MODE_PREP_CALIB: - return WESTON_TOUCH_MODE_CALIB; + wl_signal_emit(&seat->tablet_tool_added_signal, tool); - case WESTON_TOUCH_MODE_NORMAL: - case WESTON_TOUCH_MODE_CALIB: - return from; + wl_resource_for_each(tablet_seat_resource, + &seat->tablet_seat_resource_list) { + client = wl_resource_get_client(tablet_seat_resource); + tablet_tool_add_resource(tool, client, + tablet_seat_resource); } - - return WESTON_TOUCH_MODE_NORMAL; } -/** Global touch mode update - * - * If no seat has a touch down and the compositor is in a PREP touch mode, - * set the compositor to the goal touch mode. - * - * Calls calibrator if touch mode changed. - */ -static void -weston_compositor_update_touch_mode(struct weston_compositor *compositor) +WL_EXPORT void +notify_tablet_tool_proximity_in(struct weston_tablet_tool *tool, + const struct timespec *time, + struct weston_tablet *tablet) { - struct weston_seat *seat; - struct weston_touch *touch; - enum weston_touch_mode goal; + struct weston_tablet_tool_grab *grab = tool->grab; - wl_list_for_each(seat, &compositor->seat_list, link) { - touch = weston_seat_get_touch(seat); - if (!touch) - continue; + grab->tool->current_tablet = tablet; - if (touch->num_tp > 0) - return; - } + grab->interface->proximity_in(grab, time, tablet); +} - goal = get_next_touch_mode(compositor->touch_mode); - if (compositor->touch_mode != goal) { - compositor->touch_mode = goal; - touch_calibrator_mode_changed(compositor); - } +WL_EXPORT void +notify_tablet_tool_proximity_out(struct weston_tablet_tool *tool, + const struct timespec *time) +{ + struct weston_tablet_tool_grab *grab = tool->grab; + + grab->interface->proximity_out(grab, time); +} + +WL_EXPORT void +notify_tablet_tool_motion(struct weston_tablet_tool *tool, + const struct timespec *time, + struct weston_coord_global pos) +{ + struct weston_tablet_tool_grab *grab = tool->grab; + + weston_compositor_wake(tool->seat->compositor); + + grab->interface->motion(grab, time, pos); } -/** Start transition to normal touch event handling - * - * The touch event mode changes when all touches on all touch devices have - * been lifted. If no touches are currently down, the transition is immediate. - * - * \sa weston_touch_mode - */ -void -weston_compositor_set_touch_mode_normal(struct weston_compositor *compositor) +WL_EXPORT void +notify_tablet_tool_pressure(struct weston_tablet_tool *tool, + const struct timespec *time, uint32_t pressure) { - switch (compositor->touch_mode) { - case WESTON_TOUCH_MODE_PREP_NORMAL: - case WESTON_TOUCH_MODE_NORMAL: - return; - case WESTON_TOUCH_MODE_PREP_CALIB: - compositor->touch_mode = WESTON_TOUCH_MODE_NORMAL; - touch_calibrator_mode_changed(compositor); - return; - case WESTON_TOUCH_MODE_CALIB: - compositor->touch_mode = WESTON_TOUCH_MODE_PREP_NORMAL; - } + struct weston_tablet_tool_grab *grab = tool->grab; - weston_compositor_update_touch_mode(compositor); + weston_compositor_wake(tool->seat->compositor); + + grab->interface->pressure(grab, time, pressure); } -/** Start transition to calibrator touch event handling - * - * The touch event mode changes when all touches on all touch devices have - * been lifted. If no touches are currently down, the transition is immediate. - * - * \sa weston_touch_mode - */ -void -weston_compositor_set_touch_mode_calib(struct weston_compositor *compositor) +WL_EXPORT void +notify_tablet_tool_distance(struct weston_tablet_tool *tool, + const struct timespec *time, uint32_t distance) { - switch (compositor->touch_mode) { - case WESTON_TOUCH_MODE_PREP_CALIB: - case WESTON_TOUCH_MODE_CALIB: - assert(0); - return; - case WESTON_TOUCH_MODE_PREP_NORMAL: - compositor->touch_mode = WESTON_TOUCH_MODE_CALIB; - touch_calibrator_mode_changed(compositor); - return; - case WESTON_TOUCH_MODE_NORMAL: - compositor->touch_mode = WESTON_TOUCH_MODE_PREP_CALIB; - } + struct weston_tablet_tool_grab *grab = tool->grab; - weston_compositor_update_touch_mode(compositor); + weston_compositor_wake(tool->seat->compositor); + + grab->interface->distance(grab, time, distance); } -/** Feed in touch down, motion, and up events, calibratable device. - * - * It assumes always the correct cycle sequence until it gets here: touch_down - * → touch_update → ... → touch_update → touch_end. The driver is responsible - * for sending along such order. - * - * \param device The physical device that generated the event. - * \param time The event timestamp. - * \param touch_id ID for the touch point of this event (multi-touch). - * \param x X coordinate in compositor global space. - * \param y Y coordinate in compositor global space. - * \param norm Normalized device X, Y coordinates in calibration space, or NULL. - * \param touch_type Either WL_TOUCH_DOWN, WL_TOUCH_UP, or WL_TOUCH_MOTION. - * - * Coordinates double_x and double_y are used for normal operation. - * - * Coordinates norm are only used for touch device calibration. If and only if - * the weston_touch_device does not support calibrating, norm must be NULL. - * - * The calibration space is the normalized coordinate space - * [0.0, 1.0]×[0.0, 1.0] of the weston_touch_device. This is assumed to - * map to the similar normalized coordinate space of the associated - * weston_output. - */ WL_EXPORT void -notify_touch_normalized(struct weston_touch_device *device, +notify_tablet_tool_tilt(struct weston_tablet_tool *tool, const struct timespec *time, - int touch_id, - double x, double y, - const struct weston_point2d_device_normalized *norm, - int touch_type) + wl_fixed_t tilt_x, wl_fixed_t tilt_y) { - struct weston_seat *seat = device->aggregate->seat; - struct weston_touch *touch = device->aggregate; + struct weston_tablet_tool_grab *grab = tool->grab; - if (touch_type != WL_TOUCH_UP) { - if (weston_touch_device_can_calibrate(device)) - assert(norm != NULL); - else - assert(norm == NULL); - } + weston_compositor_wake(tool->seat->compositor); - /* Update touchpoints count regardless of the current mode. */ - switch (touch_type) { - case WL_TOUCH_DOWN: - weston_compositor_idle_inhibit(seat->compositor); + grab->interface->tilt(grab, time, tilt_x, tilt_y); +} - touch->num_tp++; - break; - case WL_TOUCH_UP: - if (touch->num_tp == 0) { - /* This can happen if we start out with one or - * more fingers on the touch screen, in which - * case we didn't get the corresponding down - * event. */ - weston_log("Unmatched touch up event on seat %s, device %s\n", - seat->seat_name, device->syspath); - return; - } - weston_compositor_idle_release(seat->compositor); +WL_EXPORT void +notify_tablet_tool_button(struct weston_tablet_tool *tool, + const struct timespec *time, + uint32_t button, uint32_t state) +{ + struct weston_tablet_tool_grab *grab = tool->grab; + struct weston_compositor *compositor = tool->seat->compositor; - touch->num_tp--; - break; - default: - break; + if (state == ZWP_TABLET_TOOL_V2_BUTTON_STATE_PRESSED) { + tool->button_count++; + if (tool->button_count == 1) + weston_compositor_idle_inhibit(compositor); + } else { + tool->button_count--; + if (tool->button_count == 1) + weston_compositor_idle_release(compositor); } - /* Properly forward the touch event */ - switch (weston_touch_device_get_mode(device)) { - case WESTON_TOUCH_MODE_NORMAL: - case WESTON_TOUCH_MODE_PREP_CALIB: - process_touch_normal(device, time, touch_id, x, y, touch_type); - break; - case WESTON_TOUCH_MODE_CALIB: - case WESTON_TOUCH_MODE_PREP_NORMAL: - notify_touch_calibrator(device, time, touch_id, - norm, touch_type); - break; - } + tool->grab_serial = wl_display_next_serial(compositor->wl_display); + + weston_compositor_run_tablet_tool_binding(compositor, tool, button, state); + + grab->interface->button(grab, time, button, state); } WL_EXPORT void -notify_touch_frame(struct weston_touch_device *device) +notify_tablet_tool_down(struct weston_tablet_tool *tool, + const struct timespec *time) { - struct weston_touch_grab *grab; + struct weston_tablet_tool_grab *grab = tool->grab; + struct weston_compositor *compositor = tool->seat->compositor; - 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); - break; - case WESTON_TOUCH_MODE_CALIB: - case WESTON_TOUCH_MODE_PREP_NORMAL: - notify_touch_calibrator_frame(device); - break; - } + weston_compositor_idle_inhibit(compositor); - weston_compositor_update_touch_mode(device->aggregate->seat->compositor); + tool->tip_is_down = true; + tool->grab_serial = wl_display_get_serial(compositor->wl_display); + tool->grab_pos = tool->pos; + + weston_compositor_run_tablet_tool_binding(compositor, tool, BTN_TOUCH, + ZWP_TABLET_TOOL_V2_BUTTON_STATE_PRESSED); + grab->interface->down(grab, time); } WL_EXPORT void -notify_touch_cancel(struct weston_touch_device *device) +notify_tablet_tool_up(struct weston_tablet_tool *tool, + const struct timespec *time) { - struct weston_touch_grab *grab; + struct weston_tablet_tool_grab *grab = tool->grab; + struct weston_compositor *compositor = tool->seat->compositor; - switch (weston_touch_device_get_mode(device)) { - case WESTON_TOUCH_MODE_NORMAL: - case WESTON_TOUCH_MODE_PREP_CALIB: - grab = device->aggregate->grab; - grab->interface->cancel(grab); - break; - case WESTON_TOUCH_MODE_CALIB: - case WESTON_TOUCH_MODE_PREP_NORMAL: - notify_touch_calibrator_cancel(device); - break; - } + weston_compositor_idle_release(compositor); - weston_compositor_update_touch_mode(device->aggregate->seat->compositor); + tool->tip_is_down = false; + + grab->interface->up(grab, time); } -static int -pointer_cursor_surface_get_label(struct weston_surface *surface, - char *buf, size_t len) +WL_EXPORT void +notify_tablet_tool_frame(struct weston_tablet_tool *tool, + const struct timespec *time) { - return snprintf(buf, len, "cursor"); + struct weston_tablet_tool_grab *grab = tool->grab; + + grab->interface->frame(grab, time); } + static void pointer_cursor_surface_committed(struct weston_surface *es, - int32_t dx, int32_t dy) + struct weston_coord_surface new_origin) { struct weston_pointer *pointer = es->committed_private; - int x, y; + struct weston_coord_global pos; if (es->width == 0) return; assert(es == pointer->sprite->surface); - pointer->hotspot_x -= dx; - pointer->hotspot_y -= dy; - - x = wl_fixed_to_int(pointer->x) - pointer->hotspot_x; - y = wl_fixed_to_int(pointer->y) - pointer->hotspot_y; + pointer->hotspot.c = weston_coord_sub(pointer->hotspot.c, + new_origin.c); + pos.c = weston_coord_sub(pointer->pos.c, pointer->hotspot.c); - weston_view_set_position(pointer->sprite, x, y); + weston_view_set_position(pointer->sprite, pos.c.x, pos.c.y); empty_region(&es->pending.input); empty_region(&es->input); @@ -2697,7 +3536,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; } } @@ -2735,7 +3574,7 @@ pointer_set_cursor(struct wl_client *client, struct wl_resource *resource, } if (pointer->sprite && pointer->sprite->surface == surface && - pointer->hotspot_x == x && pointer->hotspot_y == y) + pointer->hotspot.c.x == x && pointer->hotspot.c.y == y) return; if (!pointer->sprite || pointer->sprite->surface != surface) { @@ -2757,11 +3596,13 @@ pointer_set_cursor(struct wl_client *client, struct wl_resource *resource, pointer->sprite = weston_view_create(surface); } - pointer->hotspot_x = x; - pointer->hotspot_y = y; + pointer->hotspot.c = weston_coord(x, y); if (surface->width != 0) { - pointer_cursor_surface_committed(surface, 0, 0); + struct weston_coord_surface zero; + + zero = weston_coord_surface(0, 0, surface); + pointer_cursor_surface_committed(surface, zero); weston_view_schedule_repaint(pointer->sprite); } } @@ -2821,17 +3662,18 @@ seat_get_pointer(struct wl_client *client, struct wl_resource *resource, if (pointer->focus && pointer->focus->surface->resource && wl_resource_get_client(pointer->focus->surface->resource) == client) { - wl_fixed_t sx, sy; + struct weston_coord_surface surf_pos; - weston_view_from_global_fixed(pointer->focus, - pointer->x, - pointer->y, - &sx, &sy); + weston_view_update_transform(pointer->focus); + + surf_pos = weston_coord_global_to_surface(pointer->focus, + pointer->pos); wl_pointer_send_enter(cr, pointer->focus_serial, pointer->focus->surface->resource, - sx, sy); + wl_fixed_from_double(surf_pos.c.x), + wl_fixed_from_double(surf_pos.c.y)); pointer_send_frame(cr); } } @@ -3390,6 +4232,20 @@ weston_seat_release_pointer(struct weston_seat *seat) } } +WL_EXPORT void +weston_seat_release_tablet_tool(struct weston_tablet_tool *tool) +{ + wl_signal_emit(&tool->removed_signal, tool); + + weston_tablet_tool_destroy(tool); +} + +WL_EXPORT void +weston_seat_release_tablet(struct weston_tablet *tablet) +{ + weston_tablet_destroy(tablet); +} + WL_EXPORT int weston_seat_init_touch(struct weston_seat *seat) { @@ -3415,6 +4271,39 @@ weston_seat_init_touch(struct weston_seat *seat) return 0; } +WL_EXPORT struct weston_tablet * +weston_seat_add_tablet(struct weston_seat *seat) +{ + struct weston_tablet *tablet; + + weston_tablet_manager_init(seat->compositor); + + tablet = weston_tablet_create(); + if (tablet == NULL) + return NULL; + + tablet->seat = seat; + + return tablet; +} + +WL_EXPORT struct weston_tablet_tool * +weston_seat_add_tablet_tool(struct weston_seat *seat) +{ + struct weston_tablet_tool *tool; + + weston_tablet_manager_init(seat->compositor); + + tool = weston_tablet_tool_create(); + if (tool == NULL) + return NULL; + + wl_list_init(&tool->resource_list); + tool->seat = seat; + + return tool; +} + WL_EXPORT void weston_seat_release_touch(struct weston_seat *seat) { @@ -3439,6 +4328,10 @@ weston_seat_init(struct weston_seat *seat, struct weston_compositor *ec, wl_list_init(&seat->drag_resource_list); wl_signal_init(&seat->destroy_signal); wl_signal_init(&seat->updated_caps_signal); + wl_list_init(&seat->tablet_seat_resource_list); + wl_list_init(&seat->tablet_list); + wl_list_init(&seat->tablet_tool_list); + wl_signal_init(&seat->tablet_tool_added_signal); seat->global = wl_global_create(ec->wl_display, &wl_seat_interface, MIN(wl_seat_interface.version, 7), @@ -3459,6 +4352,8 @@ WL_EXPORT void weston_seat_release(struct weston_seat *seat) { struct wl_resource *resource; + struct weston_tablet *tablet, *tmp; + struct weston_tablet_tool *tool, *tmp_tool; wl_resource_for_each(resource, &seat->base_resource_list) { wl_resource_set_user_data(resource, NULL); @@ -3482,6 +4377,10 @@ weston_seat_release(struct weston_seat *seat) weston_keyboard_destroy(seat->keyboard_state); if (seat->touch_state) weston_touch_destroy(seat->touch_state); + wl_list_for_each_safe(tablet, tmp, &seat->tablet_list, link) + weston_tablet_destroy(tablet); + wl_list_for_each_safe(tool, tmp_tool, &seat->tablet_tool_list, link) + weston_tablet_tool_destroy(tool); free (seat->seat_name); @@ -3620,6 +4519,87 @@ weston_seat_get_touch(struct weston_seat *seat) return NULL; } +static void +tablet_seat_destroy(struct wl_client *client, struct wl_resource *resource) +{ +} + +static const struct zwp_tablet_seat_v2_interface tablet_seat_interface = { + tablet_seat_destroy, +}; + +static void +tablet_manager_get_tablet_seat(struct wl_client *client, struct wl_resource *resource, + uint32_t id, struct wl_resource *seat_resource) +{ + struct weston_seat *seat = wl_resource_get_user_data(seat_resource); + struct wl_resource *cr; + struct weston_tablet *tablet; + struct weston_tablet_tool *tool; + + cr = wl_resource_create(client, &zwp_tablet_seat_v2_interface, + wl_resource_get_version(resource), id); + if (cr == NULL) { + wl_client_post_no_memory(client); + return; + } + + /* store the resource in the weston_seat */ + wl_list_insert(&seat->tablet_seat_resource_list, wl_resource_get_link(cr)); + wl_resource_set_implementation(cr, &tablet_seat_interface, seat, + unbind_resource); + + /* Notify client of any tablets already connected to the system */ + wl_list_for_each(tablet, &seat->tablet_list, link) { + tablet_add_resource(tablet, client, cr); + /* Notify client of any local tools already known */ + wl_list_for_each(tool, &tablet->tool_list, link) + tablet_tool_add_resource(tool, client, cr); + } + + /* Notify client of any global tools already known */ + wl_list_for_each(tool, &seat->tablet_tool_list, link) + tablet_tool_add_resource(tool, client, cr); +} + +static void +tablet_manager_destroy(struct wl_client *client, struct wl_resource *resource) +{ + +} + +static const struct zwp_tablet_manager_v2_interface tablet_manager_interface = { + tablet_manager_get_tablet_seat, + tablet_manager_destroy, +}; + +static void +bind_tablet_manager(struct wl_client *client, void *data, uint32_t version, + uint32_t id) +{ + struct weston_compositor *compositor = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, &zwp_tablet_manager_v2_interface, + MIN(version, 1), id); + wl_resource_set_implementation(resource, &tablet_manager_interface, + data, unbind_resource); + wl_list_insert(&compositor->tablet_manager_resource_list, + wl_resource_get_link(resource)); +} + +WL_EXPORT void +weston_tablet_manager_init(struct weston_compositor *compositor) +{ + if (compositor->tablet_manager) + return; + + compositor->tablet_manager = wl_global_create(compositor->wl_display, + &zwp_tablet_manager_v2_interface, + 1, compositor, + bind_tablet_manager); +} + /** Sets the keyboard focus to the given surface * * \param surface the surface to focus on @@ -3655,8 +4635,8 @@ enable_pointer_constraint(struct weston_pointer_constraint *constraint, constraint->view = view; pointer_constraint_notify_activated(constraint); weston_pointer_start_grab(constraint->pointer, &constraint->grab); - wl_list_remove(&constraint->surface_destroy_listener.link); - wl_list_init(&constraint->surface_destroy_listener.link); + wl_signal_add(&constraint->view->unmap_signal, + &constraint->view_unmap_listener); } static bool @@ -3671,6 +4651,8 @@ weston_pointer_constraint_disable(struct weston_pointer_constraint *constraint) constraint->view = NULL; pointer_constraint_notify_deactivated(constraint); weston_pointer_end_grab(constraint->grab.pointer); + wl_list_remove(&constraint->view_unmap_listener.link); + wl_list_init(&constraint->view_unmap_listener.link); } void @@ -3680,7 +4662,6 @@ weston_pointer_constraint_destroy(struct weston_pointer_constraint *constraint) weston_pointer_constraint_disable(constraint); wl_list_remove(&constraint->pointer_destroy_listener.link); - wl_list_remove(&constraint->surface_destroy_listener.link); wl_list_remove(&constraint->surface_commit_listener.link); wl_list_remove(&constraint->surface_activate_listener.link); @@ -3733,7 +4714,7 @@ maybe_enable_pointer_constraint(struct weston_pointer_constraint *constraint) struct weston_pointer *pointer = constraint->pointer; struct weston_keyboard *keyboard; struct weston_seat *seat = pointer->seat; - int32_t x, y; + struct weston_coord_surface c; /* Postpone if no view of the surface was most recently clicked. */ wl_list_for_each(vit, &surface->views, surface_link) { @@ -3750,16 +4731,14 @@ maybe_enable_pointer_constraint(struct weston_pointer_constraint *constraint) if (!keyboard || keyboard->focus != surface) return; + weston_view_update_transform(view); /* Postpone constraint if the pointer is not within the * constraint region. */ - weston_view_from_global(view, - wl_fixed_to_int(pointer->x), - wl_fixed_to_int(pointer->y), - &x, &y); + c = weston_coord_global_to_surface(view, pointer->pos); if (!is_within_constraint_region(constraint, - wl_fixed_from_int(x), - wl_fixed_from_int(y))) + wl_fixed_from_double(c.c.x), + wl_fixed_from_double(c.c.y))) return; enable_pointer_constraint(constraint, view); @@ -3877,13 +4856,13 @@ pointer_constraint_pointer_destroyed(struct wl_listener *listener, void *data) } static void -pointer_constraint_surface_destroyed(struct wl_listener *listener, void *data) +pointer_constraint_view_unmapped(struct wl_listener *listener, void *data) { - struct weston_pointer_constraint *constraint = - container_of(listener, struct weston_pointer_constraint, - surface_destroy_listener); + struct weston_pointer_constraint *constraint = + container_of(listener, struct weston_pointer_constraint, + view_unmap_listener); - weston_pointer_constraint_destroy(constraint); + disable_pointer_constraint(constraint); } static void @@ -3893,6 +4872,9 @@ pointer_constraint_surface_committed(struct wl_listener *listener, void *data) container_of(listener, struct weston_pointer_constraint, surface_commit_listener); + if (is_pointer_constraint_enabled(constraint)) + weston_view_update_transform(constraint->view); + if (constraint->region_is_pending) { constraint->region_is_pending = false; pixman_region32_copy(&constraint->region, @@ -3947,8 +4929,8 @@ weston_pointer_constraint_create(struct weston_surface *surface, constraint->surface_activate_listener.notify = pointer_constraint_surface_activate; - constraint->surface_destroy_listener.notify = - pointer_constraint_surface_destroyed; + constraint->view_unmap_listener.notify = + pointer_constraint_view_unmapped; constraint->surface_commit_listener.notify = pointer_constraint_surface_committed; constraint->pointer_destroy_listener.notify = @@ -3958,8 +4940,6 @@ weston_pointer_constraint_create(struct weston_surface *surface, &constraint->surface_activate_listener); wl_signal_add(&pointer->destroy_signal, &constraint->pointer_destroy_listener); - wl_signal_add(&surface->destroy_signal, - &constraint->surface_destroy_listener); wl_signal_add(&surface->commit_signal, &constraint->surface_commit_listener); @@ -4029,17 +5009,20 @@ locked_pointer_destroy(struct wl_client *client, { struct weston_pointer_constraint *constraint = wl_resource_get_user_data(resource); - wl_fixed_t x, y; if (constraint && constraint->view && constraint->hint_is_pending && is_within_constraint_region(constraint, constraint->hint_x, constraint->hint_y)) { - weston_view_to_global_fixed(constraint->view, - constraint->hint_x, - constraint->hint_y, - &x, &y); - weston_pointer_move_to(constraint->pointer, x, y); + struct weston_coord_global pos; + struct weston_coord_surface surf_pos; + + surf_pos = weston_coord_surface_from_fixed(constraint->hint_x, + constraint->hint_y, + constraint->view->surface); + pos = weston_coord_surface_to_global(constraint->view, + surf_pos); + weston_pointer_move_to(constraint->pointer, pos); } wl_resource_destroy(resource); } @@ -4122,33 +5105,15 @@ confined_pointer_grab_pointer_focus(struct weston_pointer_grab *grab) } static double -vec2d_cross_product(struct vec2d a, struct vec2d b) +weston_coord_cross_product(struct weston_coord a, struct weston_coord b) { return a.x * b.y - a.y * b.x; } -static struct vec2d -vec2d_add(struct vec2d a, struct vec2d b) -{ - return (struct vec2d) { - .x = a.x + b.x, - .y = a.y + b.y, - }; -} - -static struct vec2d -vec2d_subtract(struct vec2d a, struct vec2d b) -{ - return (struct vec2d) { - .x = a.x - b.x, - .y = a.y - b.y, - }; -} - -static struct vec2d -vec2d_multiply_constant(double c, struct vec2d a) +static struct weston_coord +weston_coord_multiply_constant(double c, struct weston_coord a) { - return (struct vec2d) { + return (struct weston_coord) { .x = c * a.x, .y = c * a.y, }; @@ -4156,12 +5121,13 @@ vec2d_multiply_constant(double c, struct vec2d a) static bool lines_intersect(struct line *line1, struct line *line2, - struct vec2d *intersection) + struct weston_coord *intersection) { - struct vec2d p = line1->a; - struct vec2d r = vec2d_subtract(line1->b, line1->a); - struct vec2d q = line2->a; - struct vec2d s = vec2d_subtract(line2->b, line2->a); + struct weston_coord p = line1->a; + struct weston_coord r = weston_coord_sub(line1->b, line1->a); + struct weston_coord q = line2->a; + struct weston_coord s = weston_coord_sub(line2->b, line2->a); + struct weston_coord tmp; double rxs; double sxr; double t; @@ -4186,42 +5152,38 @@ lines_intersect(struct line *line1, struct line *line2, * u = ((p - q) × r) / (s × r) */ - rxs = vec2d_cross_product(r, s); - sxr = vec2d_cross_product(s, r); + rxs = weston_coord_cross_product(r, s); + sxr = weston_coord_cross_product(s, r); /* If r × s = 0 then the lines are either parallel or collinear. */ if (fabs(rxs) < DBL_MIN) return false; - t = vec2d_cross_product(vec2d_subtract(q, p), s) / rxs; - u = vec2d_cross_product(vec2d_subtract(p, q), r) / sxr; + tmp = weston_coord_sub(q, p); + t = weston_coord_cross_product(tmp, s) / rxs; + tmp = weston_coord_sub(p, q); + u = weston_coord_cross_product(tmp, r) / sxr; /* The lines only intersect if 0 ≤ t ≤ 1 and 0 ≤ u ≤ 1. */ if (t < 0.0 || t > 1.0 || u < 0.0 || u > 1.0) return false; - *intersection = vec2d_add(p, vec2d_multiply_constant(t, r)); + *intersection = weston_coord_add(p, weston_coord_multiply_constant(t, r)); return true; } static struct border * add_border(struct wl_array *array, - double x1, double y1, - double x2, double y2, + struct weston_coord pos1, + struct weston_coord pos2, enum motion_direction blocking_dir) { struct border *border = wl_array_add(array, sizeof *border); *border = (struct border) { .line = (struct line) { - .a = (struct vec2d) { - .x = x1, - .y = y1, - }, - .b = (struct vec2d) { - .x = x2, - .y = y2, - }, + .a = pos1, + .b = pos2 }, .blocking_dir = blocking_dir, }; @@ -4262,14 +5224,20 @@ add_non_overlapping_edges(pixman_box32_t *boxes, * borders with the same left x coordinate, the wider one comes first. */ for (i = band_above_start; i < band_below_start; i++) { + struct weston_coord pos1, pos2; pixman_box32_t *box = &boxes[i]; - add_border(&band_merge, box->x1, box->y2, box->x2, box->y2, - MOTION_DIRECTION_POSITIVE_Y); + + pos1 = weston_coord(box->x1, box->y2); + pos2 = weston_coord(box->x2, box->y2); + add_border(&band_merge, pos1, pos2, MOTION_DIRECTION_POSITIVE_Y); } for (i = band_below_start; i < band_below_end; i++) { + struct weston_coord pos1, pos2; pixman_box32_t *box= &boxes[i]; - add_border(&band_merge, box->x1, box->y1, box->x2, box->y1, - MOTION_DIRECTION_NEGATIVE_Y); + + pos1 = weston_coord(box->x1, box->y1); + pos2 = weston_coord(box->x2, box->y1); + add_border(&band_merge, pos1, pos2, MOTION_DIRECTION_NEGATIVE_Y); } qsort(band_merge.data, band_merge.size / sizeof *border, @@ -4321,10 +5289,8 @@ add_non_overlapping_edges(pixman_box32_t *boxes, * -----[ ]---- */ new_border = add_border(borders, - border->line.b.x, - border->line.b.y, - prev_border->line.b.x, - prev_border->line.b.y, + border->line.b, + prev_border->line.b, prev_border->blocking_dir); prev_border->line.b.x = border->line.a.x; prev_border = new_border; @@ -4354,18 +5320,19 @@ add_band_bottom_edges(pixman_box32_t *boxes, struct wl_array *borders) { int i; + struct weston_coord pos1, pos2; for (i = band_start; i < band_end; i++) { - add_border(borders, - boxes[i].x1, boxes[i].y2, - boxes[i].x2, boxes[i].y2, - MOTION_DIRECTION_POSITIVE_Y); + pos1 = weston_coord(boxes[i].x1, boxes[i].y2); + pos2 = weston_coord(boxes[i].x2, boxes[i].y2); + add_border(borders, pos1, pos2, MOTION_DIRECTION_POSITIVE_Y); } } static void region_to_outline(pixman_region32_t *region, struct wl_array *borders) { + struct weston_coord pos1, pos2; pixman_box32_t *boxes; int num_boxes; int i; @@ -4449,31 +5416,27 @@ region_to_outline(pixman_region32_t *region, struct wl_array *borders) /* Add the top border if the box is part of the current roof. */ if (boxes[i].y1 == current_roof) { - add_border(borders, - boxes[i].x1, boxes[i].y1, - boxes[i].x2, boxes[i].y1, - MOTION_DIRECTION_NEGATIVE_Y); + pos1 = weston_coord(boxes[i].x1, boxes[i].y1); + pos2 = weston_coord(boxes[i].x2, boxes[i].y1); + add_border(borders, pos1, pos2, MOTION_DIRECTION_NEGATIVE_Y); } /* Add the bottom border of the last band. */ if (boxes[i].y2 == bottom_most) { - add_border(borders, - boxes[i].x1, boxes[i].y2, - boxes[i].x2, boxes[i].y2, - MOTION_DIRECTION_POSITIVE_Y); + pos1 = weston_coord(boxes[i].x1, boxes[i].y2); + pos2 = weston_coord(boxes[i].x2, boxes[i].y2); + add_border(borders, pos1, pos2, MOTION_DIRECTION_POSITIVE_Y); } /* Always add the left border. */ - add_border(borders, - boxes[i].x1, boxes[i].y1, - boxes[i].x1, boxes[i].y2, - MOTION_DIRECTION_NEGATIVE_X); + pos1 = weston_coord(boxes[i].x1, boxes[i].y1); + pos2 = weston_coord(boxes[i].x1, boxes[i].y2); + add_border(borders, pos1, pos2, MOTION_DIRECTION_NEGATIVE_X); /* Always add the right border. */ - add_border(borders, - boxes[i].x2, boxes[i].y1, - boxes[i].x2, boxes[i].y2, - MOTION_DIRECTION_POSITIVE_X); + pos1 = weston_coord(boxes[i].x2, boxes[i].y1); + pos2 = weston_coord(boxes[i].x2, boxes[i].y2); + add_border(borders, pos1, pos2, MOTION_DIRECTION_POSITIVE_X); prev_top = boxes[i].y1; } @@ -4509,8 +5472,8 @@ get_closest_border(struct wl_array *borders, uint32_t directions) { struct border *border; - struct vec2d intersection; - struct vec2d delta; + struct weston_coord intersection; + struct weston_coord delta; double distance_2; struct border *closest_border = NULL; double closest_distance_2 = DBL_MAX; @@ -4522,7 +5485,7 @@ get_closest_border(struct wl_array *borders, if (!lines_intersect(&border->line, motion, &intersection)) continue; - delta = vec2d_subtract(intersection, motion->a); + delta = weston_coord_sub(intersection, motion->a); distance_2 = delta.x*delta.x + delta.y*delta.y; if (distance_2 < closest_distance_2) { closest_border = border; @@ -4579,25 +5542,26 @@ get_motion_directions(struct line *motion) return directions; } -static void +static struct weston_coord_global weston_pointer_clamp_event_to_region(struct weston_pointer *pointer, struct weston_pointer_motion_event *event, - pixman_region32_t *region, - wl_fixed_t *clamped_x, - wl_fixed_t *clamped_y) + pixman_region32_t *region) { - wl_fixed_t x, y; - wl_fixed_t sx, sy; wl_fixed_t old_sx = pointer->sx; wl_fixed_t old_sy = pointer->sy; struct wl_array borders; struct line motion; struct border *closest_border; - float new_x_f, new_y_f; uint32_t directions; + struct weston_coord_global pos; + struct weston_coord_global clamped_pos; + struct weston_coord_surface clamped_surf_pos; + struct weston_coord_surface surf_pos; + + assert(pointer->focus); - weston_pointer_motion_to_abs(pointer, event, &x, &y); - weston_view_from_global_fixed(pointer->focus, x, y, &sx, &sy); + pos = weston_pointer_motion_to_abs(pointer, event); + surf_pos = weston_coord_global_to_surface(pointer->focus, pos); wl_array_init(&borders); @@ -4611,14 +5575,11 @@ weston_pointer_clamp_event_to_region(struct weston_pointer *pointer, region_to_outline(region, &borders); motion = (struct line) { - .a = (struct vec2d) { + .a = (struct weston_coord) { .x = wl_fixed_to_double(old_sx), .y = wl_fixed_to_double(old_sy), }, - .b = (struct vec2d) { - .x = wl_fixed_to_double(sx), - .y = wl_fixed_to_double(sy), - }, + .b = surf_pos.c, }; directions = get_motion_directions(&motion); @@ -4632,13 +5593,13 @@ weston_pointer_clamp_event_to_region(struct weston_pointer *pointer, break; } - weston_view_to_global_float(pointer->focus, - (float) motion.b.x, (float) motion.b.y, - &new_x_f, &new_y_f); - *clamped_x = wl_fixed_from_double(new_x_f); - *clamped_y = wl_fixed_from_double(new_y_f); + clamped_surf_pos = weston_coord_surface(motion.b.x, motion.b.y, + pointer->focus->surface); + clamped_pos = weston_coord_surface_to_global(pointer->focus, + clamped_surf_pos); wl_array_release(&borders); + return clamped_pos; } static double @@ -4703,16 +5664,14 @@ warp_to_behind_border(struct border *border, wl_fixed_t *sx, wl_fixed_t *sy) static void maybe_warp_confined_pointer(struct weston_pointer_constraint *constraint) { - wl_fixed_t x; - wl_fixed_t y; wl_fixed_t sx; wl_fixed_t sy; + struct weston_coord_surface c; - weston_view_from_global_fixed(constraint->view, - constraint->pointer->x, - constraint->pointer->y, - &sx, - &sy); + c = weston_coord_global_to_surface(constraint->view, + constraint->pointer->pos); + sx = wl_fixed_from_double(c.c.x); + sy = wl_fixed_from_double(c.c.y); if (!is_within_constraint_region(constraint, sx, sy)) { double xf = wl_fixed_to_double(sx); @@ -4722,6 +5681,8 @@ maybe_warp_confined_pointer(struct weston_pointer_constraint *constraint) struct border *border; double closest_distance_2 = DBL_MAX; struct border *closest_border = NULL; + struct weston_coord_global cg; + struct weston_coord_surface cs; wl_array_init(&borders); @@ -4729,6 +5690,7 @@ maybe_warp_confined_pointer(struct weston_pointer_constraint *constraint) pixman_region32_intersect(&confine_region, &constraint->view->surface->input, &constraint->region); + assert(pixman_region32_not_empty(&confine_region)); region_to_outline(&confine_region, &borders); pixman_region32_fini(&confine_region); @@ -4747,8 +5709,10 @@ maybe_warp_confined_pointer(struct weston_pointer_constraint *constraint) wl_array_release(&borders); - weston_view_to_global_fixed(constraint->view, sx, sy, &x, &y); - weston_pointer_move_to(constraint->pointer, x, y); + cs = weston_coord_surface_from_fixed(sx, sy, + constraint->view->surface); + cg = weston_coord_surface_to_global(constraint->view, cs); + weston_pointer_move_to(constraint->pointer, cg); } } @@ -4761,27 +5725,31 @@ confined_pointer_grab_pointer_motion(struct weston_pointer_grab *grab, container_of(grab, struct weston_pointer_constraint, grab); struct weston_pointer *pointer = grab->pointer; struct weston_surface *surface; - wl_fixed_t x, y; wl_fixed_t old_sx = pointer->sx; wl_fixed_t old_sy = pointer->sy; pixman_region32_t confine_region; + struct weston_coord_global pos; + struct weston_coord_surface surf_pos; assert(pointer->focus); assert(pointer->focus->surface == constraint->surface); surface = pointer->focus->surface; + weston_view_update_transform(pointer->focus); + pixman_region32_init(&confine_region); pixman_region32_intersect(&confine_region, &surface->input, &constraint->region); - weston_pointer_clamp_event_to_region(pointer, event, - &confine_region, &x, &y); - weston_pointer_move_to(pointer, x, y); + pos = weston_pointer_clamp_event_to_region(pointer, event, + &confine_region); + weston_pointer_move_to(pointer, pos); pixman_region32_fini(&confine_region); - weston_view_from_global_fixed(pointer->focus, x, y, - &pointer->sx, &pointer->sy); + surf_pos = weston_coord_global_to_surface(pointer->focus, pos); + pointer->sx = wl_fixed_from_double(surf_pos.c.x); + pointer->sy = wl_fixed_from_double(surf_pos.c.y); if (old_sx != pointer->sx || old_sy != pointer->sy) { pointer_send_motion(pointer, time, diff --git a/libweston/launcher-impl.h b/libweston/launcher-impl.h index 6bcbbc54c..954dc6dea 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 6aa8ad0f2..73fc85a7b 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) { @@ -110,14 +118,19 @@ seat_open_device(struct weston_launcher *launcher, const char *path, int flags) struct launcher_libseat *wl = wl_container_of(launcher, wl, base); struct launcher_libseat_device *dev; struct stat st; + int loop = 0; dev = zalloc(sizeof(struct launcher_libseat_device)); if (dev == NULL) { goto err_alloc; } +retry: dev->device_id = libseat_open_device(wl->seat, path, &dev->fd); if (dev->device_id == -1) { + sleep(1); + if(loop++ < 10) + goto retry; goto err_open; } @@ -179,9 +192,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 +246,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 +289,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 3fca1dff6..5b73bdae3 100644 --- a/libweston/launcher-logind.c +++ b/libweston/launcher-logind.c @@ -77,6 +77,7 @@ launcher_logind_take_device(struct launcher_logind *wl, uint32_t major, bool b; int r, fd; dbus_bool_t paused; + int loop = 0; m = dbus_message_new_method_call("org.freedesktop.login1", wl->spath, @@ -94,10 +95,14 @@ launcher_logind_take_device(struct launcher_logind *wl, uint32_t major, goto err_unref; } +retry: reply = dbus_connection_send_with_reply_and_block(wl->dbus, m, -1, NULL); if (!reply) { + sleep(1); weston_log("logind: TakeDevice on %d:%d failed.\n", major, minor); + if(loop++ < 10) + goto retry; r = -ENODEV; goto err_unref; } @@ -496,13 +501,14 @@ device_paused(struct launcher_logind *wl, DBusMessage *m) * "gone" means the device is gone. We handle it the same as "force" as * a following udev event will be caught, too. * - * If it's our main DRM device, tell the compositor to go asleep. */ - + * If it's our main DRM device and not "gone", tell the compositor to go asleep. */ if (!strcmp(type, "pause")) launcher_logind_pause_device_complete(wl, major, minor); + else if (strcmp(type, "gone") == 0) + return; if (wl->sync_drm && wl->compositor->backend->device_changed) - wl->compositor->backend->device_changed(wl->compositor, + wl->compositor->backend->device_changed(wl->compositor->backend, makedev(major,minor), false); } @@ -529,7 +535,7 @@ device_resumed(struct launcher_logind *wl, DBusMessage *m) * notify the compositor to wake up. */ if (wl->sync_drm && wl->compositor->backend->device_changed) - wl->compositor->backend->device_changed(wl->compositor, + wl->compositor->backend->device_changed(wl->compositor->backend, makedev(major,minor), true); } @@ -695,29 +701,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 +742,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 +786,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 +859,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 b2219b682..b277606dc 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 dd7b77022..2ee7ceb57 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/libinput-device.c b/libweston/libinput-device.c index 4ea89de04..31b88ac43 100644 --- a/libweston/libinput-device.c +++ b/libweston/libinput-device.c @@ -45,6 +45,13 @@ #include "shared/helpers.h" #include "shared/timespec-util.h" +#include "tablet-unstable-v2-server-protocol.h" + +struct tablet_output_listener { + struct wl_listener base; + struct wl_list tablet_list; +}; + void evdev_led_update(struct evdev_device *device, enum weston_led weston_leds) { @@ -123,12 +130,10 @@ handle_pointer_motion(struct libinput_device *libinput_device, .mask = WESTON_POINTER_MOTION_REL | WESTON_POINTER_MOTION_REL_UNACCEL, .time = time, - .dx = libinput_event_pointer_get_dx(pointer_event), - .dy = libinput_event_pointer_get_dy(pointer_event), - .dx_unaccel = dx_unaccel, - .dy_unaccel = dy_unaccel, }; - + event.rel = weston_coord(libinput_event_pointer_get_dx(pointer_event), + libinput_event_pointer_get_dy(pointer_event)); + event.rel_unaccel = weston_coord(dx_unaccel, dy_unaccel); notify_motion(device->seat, &time, &event); return true; @@ -142,6 +147,7 @@ handle_pointer_motion_absolute( struct evdev_device *device = libinput_device_get_user_data(libinput_device); struct weston_output *output = device->output; + struct weston_coord_global pos; struct timespec time; double x, y; uint32_t width, height; @@ -160,9 +166,8 @@ handle_pointer_motion_absolute( width); y = libinput_event_pointer_get_absolute_y_transformed(pointer_event, height); - - weston_output_transform_coordinate(device->output, x, y, &x, &y); - notify_motion_absolute(device->seat, &time, x, y); + pos = weston_coord_global_from_output_point(x, y, output); + notify_motion_absolute(device->seat, &time, pos); return true; } @@ -248,7 +253,6 @@ static bool handle_pointer_axis(struct libinput_device *libinput_device, struct libinput_event_pointer *pointer_event) { - static int warned; struct evdev_device *device = libinput_device_get_user_data(libinput_device); double vert, horiz; @@ -282,10 +286,8 @@ handle_pointer_axis(struct libinput_device *libinput_device, wl_axis_source = WL_POINTER_AXIS_SOURCE_CONTINUOUS; break; default: - if (warned < 5) { - weston_log("Unknown scroll source %d.\n", source); - warned++; - } + weston_log_paced(&device->unknown_scroll_pacer, 5, 0, + "Unknown scroll source %d.\n", source); return false; } @@ -448,6 +450,7 @@ handle_touch_with_coords(struct libinput_device *libinput_device, uint32_t width, height; struct timespec time; int32_t slot; + struct weston_coord_global pos; if (!device->output) return; @@ -461,16 +464,16 @@ handle_touch_with_coords(struct libinput_device *libinput_device, x = libinput_event_touch_get_x_transformed(touch_event, width); y = libinput_event_touch_get_y_transformed(touch_event, height); - weston_output_transform_coordinate(device->output, - x, y, &x, &y); + pos = weston_coord_global_from_output_point(x, y, device->output); if (weston_touch_device_can_calibrate(device->touch_device)) { norm.x = libinput_event_touch_get_x_transformed(touch_event, 1); norm.y = libinput_event_touch_get_y_transformed(touch_event, 1); notify_touch_normalized(device->touch_device, &time, slot, - x, y, &norm, touch_type); + &pos, &norm, touch_type); } else { - notify_touch(device->touch_device, &time, slot, x, y, touch_type); + notify_touch(device->touch_device, &time, slot, + &pos, touch_type); } } @@ -500,7 +503,7 @@ handle_touch_up(struct libinput_device *libinput_device, timespec_from_usec(&time, libinput_event_touch_get_time_usec(touch_event)); - notify_touch(device->touch_device, &time, slot, 0, 0, WL_TOUCH_UP); + notify_touch(device->touch_device, &time, slot, NULL, WL_TOUCH_UP); } static void @@ -513,6 +516,247 @@ handle_touch_frame(struct libinput_device *libinput_device, notify_touch_frame(device->touch_device); } +static void +process_tablet_axis(struct weston_output *output, struct weston_tablet *tablet, + struct weston_tablet_tool *tool, + struct libinput_event_tablet_tool *axis_event) +{ + struct timespec time; + const int NORMALIZED_AXIS_MAX = 65535; + + timespec_from_usec(&time, + libinput_event_tablet_tool_get_time(axis_event)); + + if (libinput_event_tablet_tool_x_has_changed(axis_event) || + libinput_event_tablet_tool_y_has_changed(axis_event)) { + double x, y; + uint32_t width, height; + struct weston_coord_global pos; + + width = output->current_mode->width; + height = output->current_mode->height; + x = libinput_event_tablet_tool_get_x_transformed(axis_event, + width); + y = libinput_event_tablet_tool_get_y_transformed(axis_event, + height); + + pos = weston_coord_global_from_output_point(x, y, output); + notify_tablet_tool_motion(tool, &time, pos); + } + + if (libinput_event_tablet_tool_pressure_has_changed(axis_event)) { + double pressure; + + pressure = libinput_event_tablet_tool_get_pressure(axis_event); + /* convert axis range [0.0, 1.0] to [0, 65535] */ + pressure *= NORMALIZED_AXIS_MAX; + notify_tablet_tool_pressure(tool, &time, pressure); + } + + if (libinput_event_tablet_tool_distance_has_changed(axis_event)) { + double distance; + + distance = libinput_event_tablet_tool_get_distance(axis_event); + /* convert axis range [0.0, 1.0] to [0, 65535] */ + distance *= NORMALIZED_AXIS_MAX; + notify_tablet_tool_distance(tool, &time, distance); + } + + if (libinput_event_tablet_tool_tilt_x_has_changed(axis_event) || + libinput_event_tablet_tool_tilt_y_has_changed(axis_event)) { + double tx, ty; + + tx = libinput_event_tablet_tool_get_tilt_x(axis_event); + ty = libinput_event_tablet_tool_get_tilt_y(axis_event); + notify_tablet_tool_tilt(tool, &time, + wl_fixed_from_double(tx), + wl_fixed_from_double(ty)); + } +} + +static void +idle_notify_tablet_tool_frame(void *data) +{ + struct weston_tablet_tool *tool = data; + + notify_tablet_tool_frame(tool, &tool->frame_time); + timespec_from_nsec(&tool->frame_time, 0); +} + +/* + * libinput does not provide frame information. So assume that all events that + * belong to the same hardware event have the same timestamp and are created + * together. Use an idle callback to delay the frame event until all events that + * blong together have been handled. + */ +static void +async_notify_tablet_tool_frame(struct weston_tablet_tool *tool, + struct timespec *time) +{ + if (timespec_eq(&tool->frame_time, time)) + return; + + /* If this is a second timestamp, then push out the first frame and use + * the already queued callback for the new timestamp. Otherwise queue a + * new callback. */ + if (!timespec_is_zero(&tool->frame_time)) + notify_tablet_tool_frame(tool, &tool->frame_time); + else { + struct wl_event_loop *loop; + + loop = wl_display_get_event_loop(tool->seat->compositor->wl_display); + wl_event_loop_add_idle(loop, idle_notify_tablet_tool_frame, tool); + } + tool->frame_time = *time; +} + +static void +handle_tablet_proximity(struct libinput_device *libinput_device, + struct libinput_event_tablet_tool *proximity_event) +{ + struct evdev_device *device; + struct weston_tablet *tablet; + struct weston_tablet_tool *tool; + struct libinput_tablet_tool *libinput_tool; + struct timespec time; + + device = libinput_device_get_user_data(libinput_device); + timespec_from_usec(&time, + libinput_event_tablet_tool_get_time(proximity_event)); + libinput_tool = libinput_event_tablet_tool_get_tool(proximity_event); + + tool = libinput_tablet_tool_get_user_data(libinput_tool); + tablet = device->tablet; + + if (libinput_event_tablet_tool_get_proximity_state(proximity_event) == + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT) { + notify_tablet_tool_proximity_out(tool, &time); + async_notify_tablet_tool_frame(tool, &time); + return; + } + + if (!tool) { + uint64_t serial; + enum libinput_tablet_tool_type libinput_tool_type; + uint32_t type; + + serial = libinput_tablet_tool_get_serial(libinput_tool); + libinput_tool_type = libinput_tablet_tool_get_type(libinput_tool); + + switch (libinput_tool_type) { + case LIBINPUT_TABLET_TOOL_TYPE_PEN: + type = ZWP_TABLET_TOOL_V2_TYPE_PEN; + break; + case LIBINPUT_TABLET_TOOL_TYPE_ERASER: + type = ZWP_TABLET_TOOL_V2_TYPE_ERASER; + break; + default: + weston_log("Unknown libinput tool type %d\n", + libinput_tool_type); + return; + } + + tool = weston_seat_add_tablet_tool(device->seat); + tool->serial = serial; + tool->hwid = libinput_tablet_tool_get_tool_id(libinput_tool); + tool->type = type; + tool->capabilities = 0; + + if (libinput_tablet_tool_has_distance(libinput_tool)) + tool->capabilities |= 1 << ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE; + if (libinput_tablet_tool_has_pressure(libinput_tool)) + tool->capabilities |= 1 << ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE; + if (libinput_tablet_tool_has_tilt(libinput_tool)) + tool->capabilities |= 1 << ZWP_TABLET_TOOL_V2_CAPABILITY_TILT; + + /* unique tools are tracked globally, others per tablet */ + if (libinput_tablet_tool_is_unique(libinput_tool)) + wl_list_insert(&device->seat->tablet_tool_list, &tool->link); + else + wl_list_insert(&tablet->tool_list, &tool->link); + + libinput_tablet_tool_set_user_data(libinput_tool, tool); + + notify_tablet_tool_added(tool); + } + + notify_tablet_tool_proximity_in(tool, &time, tablet); + process_tablet_axis(device->output, tablet, tool, proximity_event); + async_notify_tablet_tool_frame(tool, &time); +} + +static void +handle_tablet_axis(struct libinput_device *libinput_device, + struct libinput_event_tablet_tool *axis_event) +{ + struct evdev_device *device = + libinput_device_get_user_data(libinput_device); + struct weston_tablet_tool *tool; + struct weston_tablet *tablet = device->tablet; + struct libinput_tablet_tool *libinput_tool; + struct timespec time; + + libinput_tool = libinput_event_tablet_tool_get_tool(axis_event); + tool = libinput_tablet_tool_get_user_data(libinput_tool); + timespec_from_usec(&time, + libinput_event_tablet_tool_get_time(axis_event)); + + process_tablet_axis(device->output, tablet, tool, axis_event); + + async_notify_tablet_tool_frame(tool, &time); +} + +static void +handle_tablet_tip(struct libinput_device *libinput_device, + struct libinput_event_tablet_tool *tip_event) +{ + struct evdev_device *device = + libinput_device_get_user_data(libinput_device); + struct weston_tablet_tool *tool; + struct libinput_tablet_tool *libinput_tool; + struct timespec time; + + libinput_tool = libinput_event_tablet_tool_get_tool(tip_event); + tool = libinput_tablet_tool_get_user_data(libinput_tool); + timespec_from_usec(&time, + libinput_event_tablet_tool_get_time(tip_event)); + + process_tablet_axis(device->output, device->tablet, tool, tip_event); + + if (libinput_event_tablet_tool_get_tip_state(tip_event) == + LIBINPUT_TABLET_TOOL_TIP_DOWN) + notify_tablet_tool_down(tool, &time); + else + notify_tablet_tool_up(tool, &time); + async_notify_tablet_tool_frame(tool, &time); +} + + +static void +handle_tablet_button(struct libinput_device *libinput_device, + struct libinput_event_tablet_tool *button_event) +{ + struct weston_tablet_tool *tool; + struct libinput_tablet_tool *libinput_tool; + struct timespec time; + uint32_t button; + enum zwp_tablet_tool_v2_button_state state; + + libinput_tool = libinput_event_tablet_tool_get_tool(button_event); + tool = libinput_tablet_tool_get_user_data(libinput_tool); + timespec_from_usec(&time, + libinput_event_tablet_tool_get_time(button_event)); + button = libinput_event_tablet_tool_get_button(button_event); + if (libinput_event_tablet_tool_get_button_state(button_event) == + LIBINPUT_BUTTON_STATE_PRESSED) + state = ZWP_TABLET_TOOL_V2_BUTTON_STATE_PRESSED; + else + state = ZWP_TABLET_TOOL_V2_BUTTON_STATE_RELEASED; + + notify_tablet_tool_button(tool, &time, button, state); + async_notify_tablet_tool_frame(tool, &time); +} + int evdev_device_process_event(struct libinput_event *event) { @@ -565,6 +809,22 @@ evdev_device_process_event(struct libinput_event *event) handle_touch_frame(libinput_device, libinput_event_get_touch_event(event)); break; + case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: + handle_tablet_proximity(libinput_device, + libinput_event_get_tablet_tool_event(event)); + break; + case LIBINPUT_EVENT_TABLET_TOOL_TIP: + handle_tablet_tip(libinput_device, + libinput_event_get_tablet_tool_event(event)); + break; + case LIBINPUT_EVENT_TABLET_TOOL_AXIS: + handle_tablet_axis(libinput_device, + libinput_event_get_tablet_tool_event(event)); + break; + case LIBINPUT_EVENT_TABLET_TOOL_BUTTON: + handle_tablet_button(libinput_device, + libinput_event_get_tablet_tool_event(event)); + break; default: handled = 0; weston_log("unknown libinput event %d\n", @@ -715,6 +975,33 @@ evdev_device_set_output(struct evdev_device *device, evdev_device_set_calibration(device); } +static void +evdev_device_init_tablet(struct evdev_device *device, + struct libinput_device *libinput_device, + struct weston_seat *seat) +{ + struct weston_tablet *tablet; + struct udev_device *udev_device; + + tablet = weston_seat_add_tablet(seat); + tablet->name = strdup(libinput_device_get_name(libinput_device)); + tablet->vid = libinput_device_get_id_vendor(libinput_device); + tablet->pid = libinput_device_get_id_product(libinput_device); + + udev_device = libinput_device_get_udev_device(libinput_device); + if (udev_device) { + tablet->path = udev_device_get_devnode(udev_device); + udev_device_unref(udev_device); + } + + wl_list_insert(&seat->tablet_list, &tablet->link); + device->seat_caps |= EVDEV_SEAT_TABLET; + + device->tablet = tablet; + + notify_tablet_added(tablet); +} + struct evdev_device * evdev_device_create(struct libinput_device *libinput_device, struct weston_seat *seat) @@ -755,6 +1042,10 @@ evdev_device_create(struct libinput_device *libinput_device, device->seat_caps |= EVDEV_SEAT_TOUCH; device->touch_device = create_touch_device(device); } + if (libinput_device_has_capability(libinput_device, + LIBINPUT_DEVICE_CAP_TABLET_TOOL)) { + evdev_device_init_tablet(device, libinput_device, seat); + } libinput_device_set_user_data(libinput_device, device); libinput_device_ref(libinput_device); @@ -773,6 +1064,8 @@ evdev_device_destroy(struct evdev_device *device) weston_touch_device_destroy(device->touch_device); weston_seat_release_touch(device->seat); } + if (device->seat_caps & EVDEV_SEAT_TABLET) + weston_seat_release_tablet(device->tablet); if (device->output) wl_list_remove(&device->output_destroy_listener.link); diff --git a/libweston/libinput-device.h b/libweston/libinput-device.h index d3fc645dc..8a06a4966 100644 --- a/libweston/libinput-device.h +++ b/libweston/libinput-device.h @@ -38,7 +38,8 @@ enum evdev_device_seat_capability { EVDEV_SEAT_POINTER = (1 << 0), EVDEV_SEAT_KEYBOARD = (1 << 1), - EVDEV_SEAT_TOUCH = (1 << 2) + EVDEV_SEAT_TOUCH = (1 << 2), + EVDEV_SEAT_TABLET = (1 << 3) }; struct evdev_device { @@ -49,9 +50,11 @@ struct evdev_device { struct wl_list link; struct weston_output *output; struct wl_listener output_destroy_listener; + struct weston_tablet *tablet; char *output_name; int fd; bool override_wl_calibration; + struct weston_log_pacer unknown_scroll_pacer; }; void diff --git a/libweston/libinput-seat.c b/libweston/libinput-seat.c index a9c7d6f32..5ecb97413 100644 --- a/libweston/libinput-seat.c +++ b/libweston/libinput-seat.c @@ -123,9 +123,7 @@ device_added(struct udev_input *input, struct libinput_device *libinput_device) pointer = weston_seat_get_pointer(seat); if (seat->output && pointer) - weston_pointer_clamp(pointer, - &pointer->x, - &pointer->y); + pointer->pos = weston_pointer_clamp(pointer, pointer->pos); output_name = libinput_device_get_output_name(libinput_device); if (output_name) { diff --git a/libweston/libweston-internal.h b/libweston/libweston-internal.h index 7c30706f4..64fa93f6f 100644 --- a/libweston/libweston-internal.h +++ b/libweston/libweston-internal.h @@ -41,8 +41,97 @@ */ #include +#include #include "color.h" +/* compositor <-> renderer interface */ + +struct weston_renderbuffer { + pixman_region32_t damage; + int refcount; + + void (*destroy)(struct weston_renderbuffer *renderbuffer); +}; + +struct weston_renderbuffer * +weston_renderbuffer_ref(struct weston_renderbuffer *renderbuffer); + +void +weston_renderbuffer_unref(struct weston_renderbuffer *renderbuffer); + +struct weston_renderer_options { +}; + +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, + struct weston_renderbuffer *renderbuffer); + + /** See weston_renderer_resize_output() + * + * \return True for success, false for leaving the output in a mess. + */ + bool (*resize_output)(struct weston_output *output, + const struct weston_size *fb_size, + const struct weston_geometry *area); + + 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); + + enum weston_renderer_type type; + const struct gl_renderer_interface *gl; + const struct pixman_renderer_interface *pixman; +}; + +struct weston_tearing_control { + struct weston_surface *surface; + bool may_tear; +}; + +void +weston_renderer_resize_output(struct weston_output *output, + const struct weston_size *fb_size, + const struct weston_geometry *area); + +static inline void +check_compositing_area(const struct weston_size *fb_size, + const struct weston_geometry *area) +{ + assert(fb_size); + assert(fb_size->width > 0); + assert(fb_size->height > 0); + + assert(area); + assert(area->x >= 0); + assert(area->width > 0); + assert(area->x <= fb_size->width - area->width); + assert(area->y >= 0); + assert(area->height > 0); + assert(area->y <= fb_size->height - area->height); +} + /* weston_buffer */ void @@ -50,7 +139,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, @@ -92,9 +182,14 @@ weston_compositor_print_scene_graph(struct weston_compositor *ec); void weston_compositor_read_presentation_clock( - const struct weston_compositor *compositor, + struct weston_compositor *compositor, struct timespec *ts); +int +weston_compositor_init_renderer(struct weston_compositor *compositor, + enum weston_renderer_type renderer_type, + const struct weston_renderer_options *options); + int weston_compositor_run_axis_binding(struct weston_compositor *compositor, struct weston_pointer *pointer, @@ -129,6 +224,10 @@ weston_compositor_run_touch_binding(struct weston_compositor *compositor, const struct timespec *time, int touch_type); void +weston_compositor_run_tablet_tool_binding(struct weston_compositor *compositor, + struct weston_tablet_tool *tool, + uint32_t button, uint32_t state_w); +void weston_compositor_stack_plane(struct weston_compositor *ec, struct weston_plane *plane, struct weston_plane *above); @@ -161,12 +260,15 @@ weston_output_disable_planes_incr(struct weston_output *output); void weston_output_disable_planes_decr(struct weston_output *output); +void +weston_output_set_single_mode(struct weston_output *output, + struct weston_mode *target); + /* weston_plane */ void -weston_plane_init(struct weston_plane *plane, - struct weston_compositor *ec, - int32_t x, int32_t y); +weston_plane_init(struct weston_plane *plane, struct weston_compositor *ec); + void weston_plane_release(struct weston_plane *plane); @@ -206,6 +308,15 @@ weston_seat_release_pointer(struct weston_seat *seat); void weston_seat_release_touch(struct weston_seat *seat); +struct weston_tablet * +weston_seat_add_tablet(struct weston_seat *seat); +struct weston_tablet_tool * +weston_seat_add_tablet_tool(struct weston_seat *seat); +void +weston_seat_release_tablet_tool(struct weston_tablet_tool *tablet_tool); +void +weston_seat_release_tablet(struct weston_tablet *tablet); + void weston_seat_update_keymap(struct weston_seat *seat, struct xkb_keymap *keymap); @@ -214,9 +325,10 @@ wl_data_device_set_keyboard_focus(struct weston_seat *seat); /* weston_pointer */ -void +struct weston_coord_global weston_pointer_clamp(struct weston_pointer *pointer, - wl_fixed_t *fx, wl_fixed_t *fy); + struct weston_coord_global pos); + void weston_pointer_set_default_grab(struct weston_pointer *pointer, const struct weston_pointer_grab_interface *interface); @@ -254,10 +366,26 @@ weston_touch_start_drag(struct weston_touch *touch, bool weston_touch_device_can_calibrate(struct weston_touch_device *device); -/* weston_surface */ +/* weston_tablet */ + +void +weston_tablet_manager_init(struct weston_compositor *ec); + +struct weston_tablet * +weston_tablet_create(void); + void -weston_surface_to_buffer_float(struct weston_surface *surface, - float x, float y, float *bx, float *by); +weston_tablet_destroy(struct weston_tablet *tablet); + +/* weston_tablet_tool */ + +struct weston_tablet_tool * +weston_tablet_tool_create(void); + +void +weston_tablet_tool_destroy(struct weston_tablet_tool *tool); + +/* weston_surface */ pixman_box32_t weston_surface_to_buffer_rect(struct weston_surface *surface, pixman_box32_t rect); @@ -281,13 +409,6 @@ weston_spring_update(struct weston_spring *spring, const struct timespec *time); /* weston_view */ -void -weston_view_to_global_fixed(struct weston_view *view, - wl_fixed_t sx, wl_fixed_t sy, - wl_fixed_t *x, wl_fixed_t *y); -void -weston_view_from_global_float(struct weston_view *view, - float x, float y, float *vx, float *vy); bool weston_view_is_opaque(struct weston_view *ev, pixman_region32_t *region); @@ -297,25 +418,22 @@ weston_view_has_valid_buffer(struct weston_view *ev); bool weston_view_matches_output_entirely(struct weston_view *ev, struct weston_output *output); + +bool +weston_view_takes_input_at_point(struct weston_view *view, + struct weston_coord_surface surf_pos); + void weston_view_move_to_plane(struct weston_view *view, struct weston_plane *plane); - void -weston_transformed_coord(int width, int height, - enum wl_output_transform transform, - int32_t scale, - float sx, float sy, float *bx, float *by); +weston_view_buffer_to_output_matrix(const struct weston_view *view, + const struct weston_output *output, + struct weston_matrix *matrix); + pixman_box32_t -weston_transformed_rect(int width, int height, - enum wl_output_transform transform, - int32_t scale, - pixman_box32_t rect); -void -weston_transformed_region(int width, int height, - enum wl_output_transform transform, - int32_t scale, - pixman_region32_t *src, pixman_region32_t *dest); +weston_matrix_transform_rect(struct weston_matrix *matrix, + pixman_box32_t rect); void weston_matrix_transform_region(pixman_region32_t *dest, struct weston_matrix *matrix, @@ -389,6 +507,16 @@ 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); + +enum paint_node_status { + PAINT_NODE_CLEAN = 0, + PAINT_NODE_OUTPUT_DIRTY = 1 << 1, + PAINT_NODE_VIEW_DIRTY = 1 << 2, + PAINT_NODE_ALL_DIRTY = 0xf, +}; + /** * paint node * @@ -411,6 +539,14 @@ struct weston_paint_node { /* Mutable members: */ + enum paint_node_status status; + struct weston_matrix buffer_to_output_matrix; + struct weston_matrix output_to_buffer_matrix; + bool needs_filtering; + + bool valid_transform; + enum wl_output_transform transform; + /* struct weston_output::paint_node_z_order_list */ struct wl_list z_order_link; @@ -418,6 +554,8 @@ struct weston_paint_node { bool surf_xform_valid; uint32_t try_view_on_plane_failure_reasons; + + bool need_through_hole; }; struct weston_paint_node * @@ -428,4 +566,27 @@ 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); + +void +weston_surface_build_buffer_matrix(const struct weston_surface *surface, + struct weston_matrix *matrix); + +void +weston_output_update_matrix(struct weston_output *output); + +void +convert_size_by_transform_scale(int32_t *width_out, int32_t *height_out, + int32_t width, int32_t height, + uint32_t transform, + int32_t scale); + +/* User authentication for remote backends */ + +bool +weston_authenticate_user(const char *username, const char *password); + #endif diff --git a/libweston/linux-dmabuf.c b/libweston/linux-dmabuf.c index 21de498d6..2e8fab591 100644 --- a/libweston/linux-dmabuf.c +++ b/libweston/linux-dmabuf.c @@ -35,7 +35,10 @@ #include #include "linux-dmabuf.h" +#include "linux-dmabuf-unstable-v1-server-protocol.h" +#include "backend.h" #include "shared/os-compatibility.h" +#include "shared/helpers.h" #include "libweston-internal.h" #include "shared/weston-drm-fourcc.h" @@ -120,8 +123,7 @@ params_add(struct wl_client *client, if (wl_resource_get_version(params_resource) < ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) buffer->attributes.modifier[plane_idx] = DRM_FORMAT_MOD_INVALID; else - buffer->attributes.modifier[plane_idx] = ((uint64_t)modifier_hi << 32) | - modifier_lo; + buffer->attributes.modifier[plane_idx] = u64_from_u32s(modifier_hi, modifier_lo); buffer->attributes.n_planes++; } @@ -146,6 +148,9 @@ destroy_linux_dmabuf_wl_buffer(struct wl_resource *resource) assert(buffer->buffer_resource == resource); assert(!buffer->params_resource); + if (buffer->gem_handle_close_func) + buffer->gem_handle_close_func(buffer); + if (buffer->user_data_destroy_func) buffer->user_data_destroy_func(buffer); @@ -342,12 +347,35 @@ params_create_immed(struct wl_client *client, format, flags); } +static void +params_add_dtrc_meta(struct wl_client *client, + struct wl_resource *params_resource, + uint32_t rfc_chroma_offset, + uint32_t rfc_luma_offset) +{ + struct linux_dmabuf_buffer *buffer; + + buffer = wl_resource_get_user_data(params_resource); + if (!buffer) { + wl_resource_post_error(params_resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, + "params was already used to create a wl_buffer"); + return; + } + + assert(buffer->params_resource == params_resource); + assert(!buffer->buffer_resource); + + buffer->attributes.dtrc_meta = rfc_luma_offset | ((uint64_t)rfc_chroma_offset << 32); +} + static const struct zwp_linux_buffer_params_v1_interface zwp_linux_buffer_params_implementation = { params_destroy, params_add, params_create, - params_create_immed + params_create_immed, + params_add_dtrc_meta }; static void @@ -963,6 +991,21 @@ linux_dmabuf_buffer_get(struct wl_resource *resource) return buffer; } +/** Set drmbackend-private data + * + * set the drm gem handle close callback in the linux_dmabuf_buffer + * + * \param buffer The linux_dmabuf_buffer object to set for. + * \param func Destructor function to be called to close gem handle + * when the linux_dmabuf_buffer gets destroyed. + */ +WL_EXPORT void +linux_dmabuf_buffer_gem_handle_close_cb(struct linux_dmabuf_buffer *buffer, + dmabuf_gem_handle_close_func func) +{ + buffer->gem_handle_close_func = func; +} + /** Set renderer-private data * * Set the user data for the linux_dmabuf_buffer. It is invalid to overwrite @@ -1063,6 +1106,27 @@ bind_linux_dmabuf(struct wl_client *client, } } } + + if (compositor->backend->get_supported_formats) { + supported_formats = compositor->backend->get_supported_formats(compositor); + wl_array_for_each(fmt, &supported_formats->arr) { + modifiers = weston_drm_format_get_modifiers(fmt, &num_modifiers); + for (i = 0; i < num_modifiers; i++) { + if (version >= ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) { + uint32_t modifier_lo = modifiers[i] & 0xFFFFFFFF; + uint32_t modifier_hi = modifiers[i] >> 32; + zwp_linux_dmabuf_v1_send_modifier(resource, + fmt->format, + modifier_hi, + modifier_lo); + } else if (modifiers[i] == DRM_FORMAT_MOD_LINEAR || + modifiers[i] == DRM_FORMAT_MOD_INVALID) { + zwp_linux_dmabuf_v1_send_format(resource, + fmt->format); + } + } + } + } } /** Advertise linux_dmabuf support diff --git a/libweston/linux-dmabuf.h b/libweston/linux-dmabuf.h index 7cae93c58..4f6c48022 100644 --- a/libweston/linux-dmabuf.h +++ b/libweston/linux-dmabuf.h @@ -26,14 +26,15 @@ #ifndef WESTON_LINUX_DMABUF_H #define WESTON_LINUX_DMABUF_H -#include -#include "linux-dmabuf-unstable-v1-server-protocol.h" +#include #define MAX_DMABUF_PLANES 4 struct linux_dmabuf_buffer; typedef void (*dmabuf_user_data_destroy_func)( struct linux_dmabuf_buffer *buffer); +typedef void (*dmabuf_gem_handle_close_func)( + struct linux_dmabuf_buffer *buffer); struct dmabuf_attributes { int32_t width; @@ -45,6 +46,7 @@ struct dmabuf_attributes { uint32_t offset[MAX_DMABUF_PLANES]; uint32_t stride[MAX_DMABUF_PLANES]; uint64_t modifier[MAX_DMABUF_PLANES]; + uint64_t dtrc_meta; }; struct linux_dmabuf_buffer { @@ -73,6 +75,9 @@ struct linux_dmabuf_buffer { /**< marked as scan-out capable, avoids any composition */ bool direct_display; + + uint32_t gem_handles[MAX_DMABUF_PLANES]; + dmabuf_gem_handle_close_func gem_handle_close_func; }; enum weston_dmabuf_feedback_tranche_preference { @@ -154,6 +159,10 @@ weston_direct_display_setup(struct weston_compositor *compositor); struct linux_dmabuf_buffer * linux_dmabuf_buffer_get(struct wl_resource *resource); +void +linux_dmabuf_buffer_gem_handle_close_cb(struct linux_dmabuf_buffer *buffer, + dmabuf_gem_handle_close_func func); + void linux_dmabuf_buffer_set_user_data(struct linux_dmabuf_buffer *buffer, void *data, diff --git a/libweston/log.c b/libweston/log.c index 911feae4e..a949da4ef 100644 --- a/libweston/log.c +++ b/libweston/log.c @@ -34,6 +34,7 @@ #include +#include "shared/timespec-util.h" #include #include "weston-log-internal.h" @@ -129,6 +130,103 @@ weston_log(const char *fmt, ...) return l; } +/** weston logger with throttling + * + * Throttled logger that will suppress a message after a fixed number of + * prints, and optionally reset the counter reset_ms miliseconds after + * the first message in a burst. + * + * On the first new message printed with this pacer after the timeout + * expires, a count of suppressed messages will also be printed. + * + * Note that the "initialized" member of struct weston_log_pacer must be + * set to 0 before first call. + * + * \param pacer The pacer instance + * \param max_burst Number of messages to allow before throttling - must + * not be zero. + * \param reset_ms Duration from burst start before the count is reset, or + * zero to never reset. + * \param fmt The format string + * + * \ingroup wlog + */ +WL_EXPORT void +weston_log_paced(struct weston_log_pacer *pacer, + unsigned int max_burst, + unsigned int reset_ms, + const char *fmt, ...) +{ + va_list argp; + struct timespec now; + int64_t since_burst_start; + int64_t suppressed = 0; + + assert(max_burst != 0); + + /* If CLOCK_MONOTONIC fails we silently give up on ever + * reseting the timer. */ + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) { + now.tv_sec = 0; + now.tv_nsec = 0; + pacer->burst_start = now; + } + + if (!pacer->initialized) { + pacer->initialized = true; + pacer->burst_start = now; + pacer->max_burst = max_burst; + pacer->reset_ms = reset_ms; + } else { + assert(pacer->max_burst == max_burst); + assert(pacer->reset_ms == reset_ms); + } + since_burst_start = timespec_sub_to_msec(&now, &pacer->burst_start); + + if (pacer->reset_ms && since_burst_start > pacer->reset_ms) { + if (pacer->event_count > pacer->max_burst) { + suppressed = pacer->event_count - + pacer->max_burst; + } + pacer->event_count = 0; + } + + if (pacer->event_count == 0) { + pacer->burst_start = now; + since_burst_start = 0; + } + + pacer->event_count++; + if (pacer->event_count > pacer->max_burst) + return; + + va_start(argp, fmt); + weston_vlog(fmt, argp); + va_end(argp); + + if (suppressed) { + weston_log_continue(STAMP_SPACE "Warning: %" PRId64 " similar " + "messages previously suppressed\n", + suppressed); + } + + /* If we're not going to throttle next time, return immediately, + * otherwise print a little more information */ + if (pacer->event_count != pacer->max_burst) + return; + + if (pacer->reset_ms) { + int64_t next_reset = pacer->reset_ms - since_burst_start; + + weston_log_continue(STAMP_SPACE "Warning: the above message " + "will be suppresssed for the next %" + PRId64 " ms.\n", next_reset); + } else { + weston_log_continue(STAMP_SPACE "Warning: the above message " + "will not be printed again.\n"); + } +} + /** weston_vlog_continue calls log_continue_handler * * \ingroup wlog diff --git a/libweston/meson.build b/libweston/meson.build index 257d69501..a6d53a0bb 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -10,6 +10,7 @@ deps_libweston = [ srcs_libweston = [ git_version_h, 'animation.c', + 'auth.c', 'bindings.c', 'clipboard.c', 'color.c', @@ -24,6 +25,7 @@ srcs_libweston = [ 'linux-sync-file.c', 'log.c', 'noop-renderer.c', + 'output-capture.c', 'pixel-formats.c', 'pixman-renderer.c', 'plugin-registry.c', @@ -35,7 +37,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, @@ -50,8 +51,10 @@ srcs_libweston = [ pointer_constraints_unstable_v1_server_protocol_h, relative_pointer_unstable_v1_protocol_c, 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, + tearing_control_v1_protocol_c, + tearing_control_v1_server_protocol_h, text_cursor_position_protocol_c, text_cursor_position_server_protocol_h, text_input_unstable_v1_protocol_c, @@ -68,14 +71,43 @@ srcs_libweston = [ weston_debug_server_protocol_h, weston_direct_display_protocol_c, weston_direct_display_server_protocol_h, + weston_output_capture_protocol_c, + weston_output_capture_server_protocol_h, + tablet_unstable_v2_protocol_c, + tablet_unstable_v2_server_protocol_h, ] +subdir('desktop') +subdir('shell-utils') + if get_option('renderer-gl') dep_egl = dependency('egl', required: false) if not dep_egl.found() error('libweston + gl-renderer requires egl which was not found. Or, you can use \'-Drenderer-gl=false\'.') endif deps_libweston += dep_egl +else + dep_egl = dependency('', required: false) +endif + +if get_option('backend-vnc') + dep_pam = dependency('pam', required: false) + if not dep_pam.found() + dep_pam = cc.find_library('pam') + endif + if not dep_pam.found() + error('VNC backend requires libpam which was not found. Or, you can use \'-Dbackend-vnc=false\'.') + endif + config_h.set('HAVE_PAM', '1') + deps_libweston += dep_pam +endif + +if get_option('renderer-g2d') + dep_egl = dependency('egl', required: false) + if not dep_egl.found() + error('libweston + g2d-renderer requires egl which was not found. Or, you can use \'-Drenderer-g2d=false\'.') + endif + deps_libweston += dep_egl endif lib_weston = shared_library( @@ -88,11 +120,6 @@ lib_weston = shared_library( dependencies: deps_libweston ) -deps_for_libweston_users = [ - dep_wayland_server, - dep_pixman, - dep_xkbcommon, -] # For external users, like Weston. dep_libweston_public = declare_dependency( @@ -144,9 +171,7 @@ pkgconfig.generate( ) srcs_session_helper = [ - 'launcher-direct.c', 'launcher-util.c', - 'launcher-weston-launch.c', ] deps_session_helper = [ dep_libweston_private_h ] @@ -155,20 +180,15 @@ if get_option('backend-drm') endif systemd_dep = dependency('', required: false) -if get_option('launcher-logind') +if get_option('deprecated-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 \'-Ddeprecated-launcher-logind=false\'') endif dbus_dep = dependency('dbus-1', version: '>= 1.6', required: false) if not dbus_dep.found() - error('logind support requires dbus-1 >= 1.6 which was not found. Or, you can use \'-Dlauncher-logind=false\'') + error('logind support requires dbus-1 >= 1.6 which was not found. Or, you can use \'-Ddeprecated-launcher-logind=false\'') endif config_h.set('HAVE_DBUS', '1') @@ -182,6 +202,8 @@ if get_option('launcher-logind') dbus_dep, systemd_dep, ] + + warning('deprecated-launcher-logind is enabled. This will go away, see https://gitlab.freedesktop.org/wayland/weston/-/issues/488') endif if get_option('launcher-libseat') libseat_dep = dependency('libseat', version: '>= 0.4') @@ -208,7 +230,8 @@ lib_libinput_backend = static_library( 'libinput-backend', [ 'libinput-device.c', - 'libinput-seat.c' + 'libinput-seat.c', + tablet_unstable_v2_server_protocol_h ], dependencies: [ dep_libweston_private, @@ -228,30 +251,30 @@ 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 +lib_gl_borders = static_library( + 'gl-borders', + 'gl-borders.c', + include_directories: common_inc, + dependencies: [ + dep_lib_cairo_shared, + dep_egl, # for gl-renderer.h + deps_for_libweston_users, + ], + build_by_default: false, + install: false +) +dep_lib_gl_borders = declare_dependency( + link_with: lib_gl_borders, + dependencies: dep_lib_cairo_shared +) subdir('color-lcms') subdir('renderer-gl') +subdir('renderer-g2d') subdir('backend-drm') -subdir('backend-fbdev') subdir('backend-headless') +subdir('backend-pipewire') subdir('backend-rdp') +subdir('backend-vnc') subdir('backend-wayland') subdir('backend-x11') diff --git a/libweston/noop-renderer.c b/libweston/noop-renderer.c index d86e7f0b1..7a07fb107 100644 --- a/libweston/noop-renderer.c +++ b/libweston/noop-renderer.c @@ -31,48 +31,71 @@ #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; } static void noop_renderer_repaint_output(struct weston_output *output, - pixman_region32_t *output_damage) + pixman_region32_t *output_damage, + struct weston_renderbuffer *renderbuffer) +{ +} + +static bool +noop_renderer_resize_output(struct weston_output *output, + const struct weston_size *fb_size, + const struct weston_geometry *area) { + check_compositing_area(fb_size, area); + return true; } 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 +107,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 +126,20 @@ 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.resize_output = noop_renderer_resize_output; + renderer->base.flush_damage = noop_renderer_flush_damage; + renderer->base.attach = noop_renderer_attach; + renderer->base.destroy = noop_renderer_destroy; + renderer->base.type = WESTON_RENDERER_NOOP; + ec->renderer = &renderer->base; return 0; } diff --git a/libweston/output-capture.c b/libweston/output-capture.c new file mode 100644 index 000000000..3265f063e --- /dev/null +++ b/libweston/output-capture.c @@ -0,0 +1,700 @@ +/* + * 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 "libweston-internal.h" +#include "output-capture.h" +#include "pixel-formats.h" +#include "shared/helpers.h" +#include "shared/weston-drm-fourcc.h" +#include "shared/xalloc.h" +#include "weston-output-capture-server-protocol.h" + +/* Lifetimes + * + * weston_output_capture_info is created at weston_output enable, and + * destroyed at weston_output disable. It maintains lists of + * weston_capture_source and weston_capture_task. + * + * Protocol request weston_capture_v1.create creates weston_capture_source + * whose lifetime is equal to the weston_capture_source_v1 protocol object + * (wl_resource) lifetime. + * + * weston_capture_source is associated with a weston_output. When the + * weston_output is disabled, weston_capture_source is removed from the list + * in weston_output_capture_info and any pending task is retired as failed. + * Furhter capture attempts on the source will be immediately failed. + * + * Protocol request weston_capture_source_v1.capture creates + * weston_capture_task, if the weston_capture_source still has its output and + * no pending task. weston_capture_task becomes the pending task for the + * weston_capture_source, and is added to the list in + * weston_output_capture_info. Retiring weston_capture_task destroys it. + * + * Each weston_capture_task is associated with a wl_buffer (weston_buffer). + * If the buffer is destroyed, the task is retired as failed. + * + * Operation + * + * Each weston_capture_source has a "pixel source" property. Pixel source + * describes what the capture shall actually contain. See + * weston_capture_v1.create request in the protocol specification. + * One pixel source can be provided by at most one component at a time. + * + * Whenever a renderer or DRM-backend is repainting an output, they will + * use weston_output_pull_capture_task() at the appropriate stages to see + * if there are any capture tasks to be serviced for a specific pixel source. + * The renderer or DRM-backend must then retire the returned tasks by either + * failing or completing them. + * + * When an output repaint completes, no weston_capture_task shall remain in + * the list. Renderers or backends could stash them in their own lists though. + * + * In order to allow clients to allocate correctly sized and formatted buffers + * to hold captured images, weston_output_capture_info maintains the current + * size and format for each type of pixel source. Renderers and DRM-backend + * who provide pixel sources are also responsible for keeping the buffer + * requirements information up-to-date with + * weston_output_update_capture_info(). + */ + +/** Implementation of weston_capture_source_v1 protocol object */ +struct weston_capture_source { + struct wl_resource *resource; + + /* struct weston_output_capture_info::capture_source_list */ + struct wl_list link; + + enum weston_output_capture_source pixel_source; + + /* weston_output_capture_info_destroy() will reset this. */ + struct weston_output *output; + + struct weston_capture_task *pending; +}; + +/** A pending task to capture an output */ +struct weston_capture_task { + /* We get cleaned up through owner->pending pointing to us. */ + struct weston_capture_source *owner; + + /* struct weston_output_capture_info::pending_capture_list */ + struct wl_list link; + + struct weston_buffer *buffer; + struct wl_listener buffer_resource_destroy_listener; +}; + +/** Buffer requirements broadcasting for a pixel source */ +struct weston_output_capture_source_info { + enum weston_output_capture_source pixel_source; + + int width; + int height; + uint32_t drm_format; +}; + +/** Capture records for an output */ +struct weston_output_capture_info { + /* struct weston_capture_task::link */ + struct wl_list pending_capture_list; + + /* struct weston_capture_source::link */ + struct wl_list capture_source_list; + + struct weston_output_capture_source_info source_info[WESTON_OUTPUT_CAPTURE_SOURCE__COUNT]; +}; + +/** Create capture tracking information on weston_output enable */ +struct weston_output_capture_info * +weston_output_capture_info_create(void) +{ + struct weston_output_capture_info *ci; + unsigned i; + + ci = xzalloc(sizeof *ci); + + wl_list_init(&ci->pending_capture_list); + wl_list_init(&ci->capture_source_list); + + /* + * Initialize to no sources available by leaving + * width, height and drm_format as zero. + */ + for (i = 0; i < ARRAY_LENGTH(ci->source_info); i++) + ci->source_info[i].pixel_source = i; + + return ci; +} + +/** Clean up capture tracking information on weston_output disable */ +void +weston_output_capture_info_destroy(struct weston_output_capture_info **cip) +{ + struct weston_output_capture_info *ci = *cip; + struct weston_capture_source *csrc, *tmp; + + assert(ci); + + /* Unlink sources. They get destroyed by their wl_resource later. */ + wl_list_for_each_safe(csrc, tmp, &ci->capture_source_list, link) { + csrc->output = NULL; + + wl_list_remove(&csrc->link); + wl_list_init(&csrc->link); + + if (csrc->pending) + weston_capture_task_retire_failed(csrc->pending, "output removed"); + } + + assert(wl_list_empty(&ci->pending_capture_list)); + + free(ci); + *cip = NULL; +} + +/** Assert that all capture tasks were taken + * + * This is called at the end of a weston_output repaint cycle when the renderer + * and the backend have had their chance to service all pending capture tasks. + * The remaining tasks would not be serviced by anything, so make sure none + * linger. + */ +void +weston_output_capture_info_repaint_done(struct weston_output_capture_info *ci) +{ + assert(wl_list_empty(&ci->pending_capture_list)); +} + +static bool +source_info_is_available(const struct weston_output_capture_source_info *csi) +{ + return csi->width > 0 && csi->height > 0 && + csi->drm_format != DRM_FORMAT_INVALID; +} + +static void +capture_info_send_source_info(struct weston_output_capture_info *ci, + struct weston_output_capture_source_info *csi) +{ + struct weston_capture_source *csrc; + + wl_list_for_each(csrc, &ci->capture_source_list, link) { + if (csrc->pixel_source != csi->pixel_source) + continue; + + weston_capture_source_v1_send_format(csrc->resource, + csi->drm_format); + weston_capture_source_v1_send_size(csrc->resource, + csi->width, csi->height); + } +} + +static struct weston_output_capture_source_info * +capture_info_get_csi(struct weston_output_capture_info *ci, + enum weston_output_capture_source src) +{ + int srcidx = src; + + assert(ci); + assert(srcidx >= 0 && srcidx < (int)ARRAY_LENGTH(ci->source_info)); + + return &ci->source_info[srcidx]; +} + +/** Update capture requirements broadcast to clients + * + * This is called by renderers and DRM-backend to update the buffer + * requirements information that is delivered to clients wanting to capture + * the output. This is how clients know what size and format buffer they + * need to allocate for the given output and pixel source. + * + * \param output The output whose capture info to update. + * \param src The source type on the output. + * \param width The new buffer width. + * \param height The new buffer height. + * \param format The new pixel format. + * + * If any one of width, height or format is zero/NULL, the source becomes + * unavailable to clients. Otherwise the source becomes available. + * + * Initially all sources are unavailable. + */ +WL_EXPORT void +weston_output_update_capture_info(struct weston_output *output, + enum weston_output_capture_source src, + int width, int height, + const struct pixel_format_info *format) +{ + struct weston_output_capture_info *ci = output->capture_info; + struct weston_output_capture_source_info *csi; + + csi = capture_info_get_csi(ci, src); + + if (csi->width == width && + csi->height == height && + csi->drm_format == format->format) + return; + + csi->width = width; + csi->height = height; + csi->drm_format = format->format; + + if (source_info_is_available(csi)) { + capture_info_send_source_info(ci, csi); + } else { + struct weston_capture_task *ct, *tmp; + + /* + * This source just became unavailable, so fail all pending + * tasks using it. + */ + wl_list_for_each_safe(ct, tmp, + &ci->pending_capture_list, link) { + if (ct->owner->pixel_source != csi->pixel_source) + continue; + + weston_capture_task_retire_failed(ct, "source removed"); + } + } +} + +static bool +buffer_is_compatible(struct weston_buffer *buffer, + struct weston_output_capture_source_info *csi) +{ + return buffer->width == csi->width && + buffer->height == csi->height && + buffer->pixel_format->format == csi->drm_format && + buffer->format_modifier == DRM_FORMAT_MOD_LINEAR; +} + +static void +weston_capture_task_destroy(struct weston_capture_task *ct) +{ + if (ct->owner->pixel_source != WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK && + ct->owner->output) + weston_output_disable_planes_decr(ct->owner->output); + + assert(ct->owner->pending == ct); + ct->owner->pending = NULL; + wl_list_remove(&ct->link); + wl_list_remove(&ct->buffer_resource_destroy_listener.link); + free(ct); +} + +static void +weston_capture_task_buffer_destroy_handler(struct wl_listener *l, void *data) +{ + struct weston_capture_task *ct = + wl_container_of(l, ct, buffer_resource_destroy_listener); + + /* + * Client destroyed the wl_buffer object. By protocol spec, this is + * undefined behaviour. Do the most sensible thing. + */ + weston_capture_task_retire_failed(ct, "wl_buffer destroyed"); +} + +static struct weston_capture_task * +weston_capture_task_create(struct weston_capture_source *csrc, + struct weston_buffer *buffer) +{ + struct weston_capture_task *ct; + + ct = xzalloc(sizeof *ct); + + ct->owner = csrc; + /* Owner will explicitly destroy us if the owner gets destroyed. */ + + ct->buffer = buffer; + ct->buffer_resource_destroy_listener.notify = weston_capture_task_buffer_destroy_handler; + wl_resource_add_destroy_listener(buffer->resource, + &ct->buffer_resource_destroy_listener); + + wl_list_insert(&csrc->output->capture_info->pending_capture_list, + &ct->link); + + if (ct->owner->pixel_source != WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK) + weston_output_disable_planes_incr(ct->owner->output); + + return ct; +} + +static bool +capture_is_authorized(struct weston_capture_source *csrc) +{ + struct weston_compositor *compositor = csrc->output->compositor; + const struct weston_output_capture_client who = { + .client = wl_resource_get_client(csrc->resource), + .output = csrc->output, + }; + struct weston_output_capture_attempt att = { + .who = &who, + .authorized = false, + .denied = false, + }; + + wl_signal_emit(&compositor->output_capture.ask_auth, &att); + + return att.authorized && !att.denied; +} + +/** Fetch the next capture task + * + * This is used by renderers and DRM-backend to get the next capture task + * they want to service. Only tasks for the given pixel source will be + * returned. + * + * Width, height and drm_format are for ensuring that + * weston_output_update_capture_info() was up-to-date before this. + * + * \return A capture task, or NULL if no more tasks. + */ +WL_EXPORT struct weston_capture_task * +weston_output_pull_capture_task(struct weston_output *output, + enum weston_output_capture_source src, + int width, int height, + const struct pixel_format_info *format) +{ + struct weston_output_capture_info *ci = output->capture_info; + struct weston_output_capture_source_info *csi; + struct weston_capture_task *ct, *tmp; + + /* + * Make sure the capture provider (renderers, DRM-backend) called + * weston_output_update_capture_info() if something changed, so that + * the 'retry' event keeps its promise of size/format events been + * already sent. + */ + csi = capture_info_get_csi(ci, src); + assert(csi->width == width); + assert(csi->height == height); + assert(csi->drm_format == format->format); + + wl_list_for_each_safe(ct, tmp, &ci->pending_capture_list, link) { + assert(ct->owner->output == output); + + if (ct->owner->pixel_source != src) + continue; + + if (!capture_is_authorized(ct->owner)) { + weston_capture_task_retire_failed(ct, "unauthorized"); + continue; + } + + /* + * Tell the client to retry, if requirements changed after + * the task was filed. + */ + if (!buffer_is_compatible(ct->buffer, csi)) { + weston_capture_source_v1_send_retry(ct->owner->resource); + weston_capture_task_destroy(ct); + continue; + } + + /* pass ct ownership to the caller */ + wl_list_remove(&ct->link); + wl_list_init(&ct->link); + + return ct; + } + + return NULL; +} + +/** Check if any renderer-based capture tasks are waiting on the output */ +WL_EXPORT bool +weston_output_has_renderer_capture_tasks(struct weston_output *output) +{ + struct weston_output_capture_info *ci = output->capture_info; + struct weston_capture_task *ct; + + wl_list_for_each(ct, &ci->pending_capture_list, link) + if (ct->owner->pixel_source != WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK) + return true; + return false; +} + +/** Get the destination buffer */ +WL_EXPORT struct weston_buffer * +weston_capture_task_get_buffer(struct weston_capture_task *ct) +{ + return ct->buffer; +} + +/** Signal completion of the capture task + * + * Sends 'complete' protocol event to the client, and destroys the task. + */ +WL_EXPORT void +weston_capture_task_retire_complete(struct weston_capture_task *ct) +{ + weston_capture_source_v1_send_complete(ct->owner->resource); + weston_capture_task_destroy(ct); +} + +/** Signal failure of the capture task + * + * Sends 'failed' protocol event to the client, and destroys the task. + */ +WL_EXPORT void +weston_capture_task_retire_failed(struct weston_capture_task *ct, + const char *err_msg) +{ + weston_capture_source_v1_send_failed(ct->owner->resource, err_msg); + weston_capture_task_destroy(ct); +} + +static void +destroy_capture_source(struct wl_resource *csrc_resource) +{ + struct weston_capture_source *csrc; + + csrc = wl_resource_get_user_data(csrc_resource); + assert(csrc_resource == csrc->resource); + + if (csrc->pending) + weston_capture_task_destroy(csrc->pending); + + wl_list_remove(&csrc->link); + free(csrc); +} + +static void +weston_capture_source_v1_destroy(struct wl_client *client, + struct wl_resource *csrc_resource) +{ + wl_resource_destroy(csrc_resource); +} + +static void +weston_capture_source_v1_capture(struct wl_client *client, + struct wl_resource *csrc_resource, + struct wl_resource *buffer_resource) +{ + struct weston_output_capture_source_info *csi; + struct weston_capture_source *csrc; + struct weston_buffer *buffer; + + csrc = wl_resource_get_user_data(csrc_resource); + assert(csrc_resource == csrc->resource); + + /* A capture task already exists? */ + if (csrc->pending) { + wl_resource_post_error(csrc->resource, + WESTON_CAPTURE_SOURCE_V1_ERROR_SEQUENCE, + "capture attempted before previous capture retired"); + return; + } + + /* weston_output disabled after creating the source? */ + if (!csrc->output) { + weston_capture_source_v1_send_failed(csrc->resource, "output removed"); + return; + } + + /* Is the pixel source not available? */ + csi = capture_info_get_csi(csrc->output->capture_info, + csrc->pixel_source); + if (!source_info_is_available(csi)) { + weston_capture_source_v1_send_failed(csrc->resource, "source unavailable"); + return; + } + + buffer = weston_buffer_from_resource(csrc->output->compositor, + buffer_resource); + if (!buffer) { + wl_client_post_no_memory(client); + return; + } + + /* If the buffer not up-to-date with the size and format? */ + if (!buffer_is_compatible(buffer, csi)) { + weston_capture_source_v1_send_retry(csrc->resource); + return; + } + + csrc->pending = weston_capture_task_create(csrc, buffer); + weston_output_schedule_repaint(csrc->output); +} + +static const struct weston_capture_source_v1_interface weston_capture_source_v1_impl = { + .destroy = weston_capture_source_v1_destroy, + .capture = weston_capture_source_v1_capture, +}; + +static int32_t +pixel_source_from_proto(uint32_t v) +{ + switch (v) { + case WESTON_CAPTURE_V1_SOURCE_WRITEBACK: + return WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK; + case WESTON_CAPTURE_V1_SOURCE_FRAMEBUFFER: + return WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER; + case WESTON_CAPTURE_V1_SOURCE_FULL_FRAMEBUFFER: + return WESTON_OUTPUT_CAPTURE_SOURCE_FULL_FRAMEBUFFER; + case WESTON_CAPTURE_V1_SOURCE_BLENDING: + return WESTON_OUTPUT_CAPTURE_SOURCE_BLENDING; + default: + return -1; + } +} + +static void +weston_capture_v1_create(struct wl_client *client, + struct wl_resource *capture_resource, + struct wl_resource *output_resource, + uint32_t source, + uint32_t capture_source_new_id) +{ + int32_t isrc = pixel_source_from_proto(source); + struct weston_capture_source *csrc; + struct weston_head *head; + + if (isrc < 0) { + wl_resource_post_error(capture_resource, + WESTON_CAPTURE_V1_ERROR_INVALID_SOURCE, + "%u is not a valid source", source); + return; + } + + csrc = zalloc(sizeof *csrc); + if (!csrc) { + wl_client_post_no_memory(client); + return; + } + + csrc->pixel_source = isrc; + wl_list_init(&csrc->link); + + csrc->resource = wl_resource_create(client, + &weston_capture_source_v1_interface, + wl_resource_get_version(capture_resource), + capture_source_new_id); + if (!csrc->resource) { + free(csrc); + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(csrc->resource, + &weston_capture_source_v1_impl, + csrc, destroy_capture_source); + + head = weston_head_from_resource(output_resource); + if (head) { + struct weston_output *output = head->output; + struct weston_output_capture_info *ci = output->capture_info; + struct weston_output_capture_source_info *csi; + + csi = capture_info_get_csi(ci, csrc->pixel_source); + wl_list_insert(&ci->capture_source_list, &csrc->link); + + csrc->output = output; + + if (source_info_is_available(csi)) + capture_info_send_source_info(ci, csi); + } + /* + * if (!head) then weston_capture_source_v1_capture() will respond with + * the failed event. + */ +} + +static void +weston_capture_v1_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct weston_capture_v1_interface weston_capture_v1_impl = { + .destroy = weston_capture_v1_destroy, + .create = weston_capture_v1_create, +}; + +static void +bind_weston_capture(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct wl_resource *resource; + + /* Access control is done at capture request. */ + resource = wl_resource_create(client, &weston_capture_v1_interface, + version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &weston_capture_v1_impl, + NULL, NULL); +} + +void +weston_compositor_install_capture_protocol(struct weston_compositor *compositor) +{ + compositor->output_capture.weston_capture_v1 = + wl_global_create(compositor->wl_display, + &weston_capture_v1_interface, + 1, NULL, bind_weston_capture); + abort_oom_if_null(compositor->output_capture.weston_capture_v1); +} + +/** Add a new authority that may authorize or deny screenshots + * + * \param compositor The compositor instance. + * \param listener The listener to populate, and which will be passed as the + * listener to the auth callback. + * \param auth The callback function which shall be called every time any + * client sends a request to capture an output. + * + * The callback function \c auth is called with argument \c att. If you + * want to authorize the screenshot after inspecting the fields in + * \c att->who , you must set \c att->authorized to true. If you want to + * deny the screenshot instead, set \c att->denied to true. Otherwise, + * do not change anything. + * + * Any screenshot is carried out only if after iterating through all + * authorities \c att->authorized is true and \c att->denied is false. + * Both default to false, which forbids screenshots without any authorities. + * + * You can remove an added authority by \c wl_list_remove(&listener->link) . + */ +WL_EXPORT void +weston_compositor_add_screenshot_authority(struct weston_compositor *compositor, + struct wl_listener *listener, + void (*auth)(struct wl_listener *l, + struct weston_output_capture_attempt *att)) +{ + listener->notify = (wl_notify_func_t)auth; + wl_signal_add(&compositor->output_capture.ask_auth, listener); +} diff --git a/libweston/output-capture.h b/libweston/output-capture.h new file mode 100644 index 000000000..cb2f4fd6a --- /dev/null +++ b/libweston/output-capture.h @@ -0,0 +1,98 @@ +/* + * 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 + +/** Copy of weston_capture_v1.source enum from protocol */ +enum weston_output_capture_source { + /** DRM KMS hardware writeback */ + WESTON_OUTPUT_CAPTURE_SOURCE_WRITEBACK = 0, + + /** framebuffer desktop area */ + WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER, + + /** complete framebuffer, including borders if any */ + WESTON_OUTPUT_CAPTURE_SOURCE_FULL_FRAMEBUFFER, + + /** blending buffer, potentially light-linear */ + WESTON_OUTPUT_CAPTURE_SOURCE_BLENDING, +}; +#define WESTON_OUTPUT_CAPTURE_SOURCE__COUNT (WESTON_OUTPUT_CAPTURE_SOURCE_BLENDING + 1) + +struct weston_output_capture_info; + +/* + * For weston_output core implementation: + */ + +struct weston_output_capture_info * +weston_output_capture_info_create(void); + +void +weston_output_capture_info_destroy(struct weston_output_capture_info **cip); + +void +weston_output_capture_info_repaint_done(struct weston_output_capture_info *ci); + +/* + * For actual capturing implementations (renderers, DRM-backend): + */ + +void +weston_output_update_capture_info(struct weston_output *output, + enum weston_output_capture_source src, + int width, int height, + const struct pixel_format_info *format); + +bool +weston_output_has_renderer_capture_tasks(struct weston_output *output); + +struct weston_capture_task; + +struct weston_capture_task * +weston_output_pull_capture_task(struct weston_output *output, + enum weston_output_capture_source src, + int width, int height, + const struct pixel_format_info *format); + +struct weston_buffer * +weston_capture_task_get_buffer(struct weston_capture_task *ct); + +void +weston_capture_task_retire_failed(struct weston_capture_task *ct, + const char *err_msg); + +void +weston_capture_task_retire_complete(struct weston_capture_task *ct); + + +/* + * entry point for weston_compositor + */ + +void +weston_compositor_install_capture_protocol(struct weston_compositor *compositor); diff --git a/libweston/pixel-formats.c b/libweston/pixel-formats.c index 9064cb666..ae3427d38 100644 --- a/libweston/pixel-formats.c +++ b/libweston/pixel-formats.c @@ -40,6 +40,7 @@ #include "shared/helpers.h" #include "shared/string-helpers.h" #include "shared/weston-drm-fourcc.h" +#include "shared/xalloc.h" #include "wayland-util.h" #include "pixel-formats.h" @@ -48,6 +49,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 +66,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 +83,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 +133,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 +143,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 +186,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 +196,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 +218,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 +249,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 +262,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 +274,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 +287,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 +297,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 +308,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 +318,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 +329,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 +338,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 +347,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 +357,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 +368,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), @@ -367,6 +471,16 @@ static const struct pixel_format_info pixel_format_table[] = { .hsub = 2, .vsub = 2, }, + { +#if USE_DRM_FORMAT_NV15 + DRM_FORMAT(NV15), +#else + DRM_FORMAT(NV12_10LE40), +#endif + .num_planes = 2, + .hsub = 2, + .vsub = 2, + }, { DRM_FORMAT(NV21), SAMPLER_TYPE(EGL_TEXTURE_Y_UV_WL), @@ -472,6 +586,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 +647,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 +694,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 +729,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 +771,37 @@ pixel_format_get_modifier(uint64_t modifier) return mod_str; } -#else -WL_EXPORT char * -pixel_format_get_modifier(uint64_t modifier) + +WL_EXPORT uint32_t +pixel_format_get_shm_format(const struct pixel_format_info *info) { - char *mod_str; - str_printf(&mod_str, "0x%llx", (unsigned long long) modifier); - return mod_str; + /* Only these two format codes differ between wl_shm and DRM fourcc */ + switch (info->format) { + case DRM_FORMAT_ARGB8888: + return WL_SHM_FORMAT_ARGB8888; + case DRM_FORMAT_XRGB8888: + return WL_SHM_FORMAT_XRGB8888; + default: + break; + } + + return info->format; +} + +WL_EXPORT const struct pixel_format_info ** +pixel_format_get_array(const uint32_t *drm_formats, unsigned int formats_count) +{ + const struct pixel_format_info **formats; + unsigned int i; + + formats = xcalloc(formats_count, sizeof(*formats)); + for (i = 0; i < formats_count; i++) { + formats[i] = pixel_format_get_info(drm_formats[i]); + if (!formats[i]) { + free(formats); + return NULL; + } + } + + return formats; } -#endif diff --git a/libweston/pixel-formats.h b/libweston/pixel-formats.h index 0b1a5f58b..3fc79972e 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 * @@ -297,3 +345,26 @@ pixel_format_height_for_plane(const struct pixel_format_info *format, */ char * pixel_format_get_modifier(uint64_t modifier); + +/** + * Return the wl_shm format code + * + * @param info Pixel format info structure + * @returns The wl_shm format code for this pixel format. + */ +uint32_t +pixel_format_get_shm_format(const struct pixel_format_info *info); + +/** + * Get pixel format array for an array of DRM format codes + * + * Given an array of DRM format codes, return an array of corresponding pixel + * format info pointers. + * + * @param formats Array of DRM format codes to get info for + * @param formats_count Number of entries in formats. + * @returns An array of pixel format info pointers, or NULL if any format could + * not be found. Must be freed by the caller. + */ +const struct pixel_format_info ** +pixel_format_get_array(const uint32_t *formats, unsigned int formats_count); diff --git a/libweston/pixman-renderer.c b/libweston/pixman-renderer.c index 754adce2f..b4806f2f4 100644 --- a/libweston/pixman-renderer.c +++ b/libweston/pixman-renderer.c @@ -35,15 +35,21 @@ #include "pixman-renderer.h" #include "color.h" #include "pixel-formats.h" +#include "output-capture.h" #include "shared/helpers.h" +#include "shared/signal.h" +#include "shared/weston-drm-fourcc.h" +#include "shared/xalloc.h" #include struct pixman_output_state { - void *shadow_buffer; pixman_image_t *shadow_image; + const struct pixel_format_info *shadow_format; pixman_image_t *hw_buffer; - pixman_region32_t *hw_extra_damage; + const struct pixel_format_info *hw_format; + struct weston_size fb_size; + struct wl_list renderbuffer_list; }; struct pixman_surface_state { @@ -58,6 +64,13 @@ struct pixman_surface_state { struct wl_listener renderer_destroy_listener; }; +struct pixman_renderbuffer { + struct weston_renderbuffer base; + + pixman_image_t *image; + struct wl_list link; +}; + struct pixman_renderer { struct weston_renderer base; @@ -68,6 +81,15 @@ struct pixman_renderer { struct wl_signal destroy_signal; }; +static pixman_image_t * +pixman_renderer_renderbuffer_get_image(struct weston_renderbuffer *renderbuffer) +{ + struct pixman_renderbuffer *rb; + + rb = container_of(renderbuffer, struct pixman_renderbuffer, base); + return rb->image; +} + static inline struct pixman_output_state * get_output_state(struct weston_output *output) { @@ -94,9 +116,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 +128,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 */ @@ -119,8 +141,8 @@ pixman_renderer_read_pixels(struct weston_output *output, x, y, /* src_x, src_y */ 0, 0, /* mask_x, mask_y */ 0, 0, /* dest_x, dest_y */ - pixman_image_get_width (po->hw_buffer), /* width */ - pixman_image_get_height (po->hw_buffer) /* height */); + po->fb_size.width, /* width */ + po->fb_size.height /* height */); pixman_image_unref(out_buf); @@ -146,30 +168,6 @@ weston_matrix_to_pixman_transform(pixman_transform_t *pt, pt->matrix[2][2] = pixman_double_to_fixed(wm->d[15]); } -static void -pixman_renderer_compute_transform(pixman_transform_t *transform_out, - struct weston_view *ev, - struct weston_output *output) -{ - struct weston_matrix matrix; - - /* Set up the source transformation based on the surface - position, the output position/transform/scale and the client - specified buffer transform/scale */ - matrix = output->inverse_matrix; - - if (ev->transform.enabled) { - weston_matrix_multiply(&matrix, &ev->transform.inverse); - } else { - weston_matrix_translate(&matrix, - -ev->geometry.x, -ev->geometry.y, 0); - } - - weston_matrix_multiply(&matrix, &ev->surface->surface_to_buffer_matrix); - - weston_matrix_to_pixman_transform(transform_out, &matrix); -} - static bool view_transformation_is_translation(struct weston_view *view) { @@ -188,14 +186,16 @@ region_intersect_only_translation(pixman_region32_t *result_global, pixman_region32_t *surf, struct weston_view *view) { - float view_x, view_y; + struct weston_coord_surface cs; + struct weston_coord_global cg; + cs = weston_coord_surface(0, 0, view->surface); assert(view_transformation_is_translation(view)); /* Convert from surface to global coordinates */ pixman_region32_copy(result_global, surf); - weston_view_to_global_float(view, 0, 0, &view_x, &view_y); - pixman_region32_translate(result_global, (int)view_x, (int)view_y); + cg = weston_coord_surface_to_global(view, cs); + pixman_region32_translate(result_global, cg.c.x, cg.c.y); pixman_region32_intersect(result_global, result_global, global); } @@ -231,7 +231,8 @@ composite_whole(pixman_op_t op, } static void -composite_clipped(pixman_image_t *src, +composite_clipped(struct weston_output *output, + pixman_image_t *src, pixman_image_t *mask, pixman_image_t *dest, const pixman_transform_t *transform, @@ -300,35 +301,32 @@ composite_clipped(pixman_image_t *src, } if (n_box > 1) { - static bool warned = false; - - if (!warned) - weston_log("Pixman-renderer warning: %dx overdraw\n", - n_box); - warned = true; + weston_log_paced(&output->pixman_overdraw_pacer, 1, 0, + "Pixman-renderer warning: %dx overdraw\n", + n_box); } } /** Paint an intersected region * - * \param ev The view to be painted. - * \param output The output being painted. + * \param pnode The paint node to be painted. * \param repaint_output The region to be painted in output coordinates. * \param source_clip The region of the source image to use, in source image * coordinates. If NULL, use the whole source image. * \param pixman_op Compositing operator, either SRC or OVER. */ static void -repaint_region(struct weston_view *ev, struct weston_output *output, +repaint_region(struct weston_paint_node *pnode, pixman_region32_t *repaint_output, pixman_region32_t *source_clip, pixman_op_t pixman_op) { + struct weston_output *output = pnode->output; + struct weston_view *ev = pnode->view; struct pixman_renderer *pr = (struct pixman_renderer *) output->compositor->renderer; struct pixman_surface_state *ps = get_surface_state(ev->surface); struct pixman_output_state *po = get_output_state(output); - struct weston_buffer_viewport *vp = &ev->surface->buffer_viewport; pixman_image_t *target_image; pixman_transform_t transform; pixman_filter_t filter; @@ -343,9 +341,10 @@ repaint_region(struct weston_view *ev, struct weston_output *output, /* Clip rendering to the damaged output region */ pixman_image_set_clip_region32(target_image, repaint_output); - pixman_renderer_compute_transform(&transform, ev, output); + weston_matrix_to_pixman_transform(&transform, + &pnode->output_to_buffer_matrix); - if (ev->transform.enabled || output->current_scale != vp->buffer.scale) + if (pnode->needs_filtering) filter = PIXMAN_FILTER_BILINEAR; else filter = PIXMAN_FILTER_NEAREST; @@ -361,7 +360,7 @@ repaint_region(struct weston_view *ev, struct weston_output *output, } if (source_clip) - composite_clipped(ps->image, mask_image, target_image, + composite_clipped(output, ps->image, mask_image, target_image, &transform, filter, source_clip); else composite_whole(pixman_op, ps->image, mask_image, @@ -381,17 +380,20 @@ repaint_region(struct weston_view *ev, struct weston_output *output, 0, 0, /* src_x, src_y */ 0, 0, /* mask_x, mask_y */ 0, 0, /* dest_x, dest_y */ - pixman_image_get_width (target_image), /* width */ - pixman_image_get_height (target_image) /* height */); + po->fb_size.width, /* width */ + po->fb_size.height /* height */); pixman_image_set_clip_region32(target_image, NULL); } static void -draw_view_translated(struct weston_view *view, struct weston_output *output, +draw_node_translated(struct weston_paint_node *pnode, pixman_region32_t *repaint_global) { - struct weston_surface *surface = view->surface; + struct weston_output *output = pnode->output; + struct weston_surface *surface = pnode->surface; + struct weston_view *view = pnode->view; + /* non-opaque region in surface coordinates: */ pixman_region32_t surface_blend; /* region to be painted in output coordinates: */ @@ -414,10 +416,11 @@ draw_view_translated(struct weston_view *view, struct weston_output *output, repaint_global, &surface->opaque, view); - weston_output_region_from_global(output, - &repaint_output); + weston_region_global_to_output(&repaint_output, + output, + &repaint_output); - repaint_region(view, output, &repaint_output, NULL, + repaint_region(pnode, &repaint_output, NULL, PIXMAN_OP_SRC); } } @@ -426,10 +429,11 @@ draw_view_translated(struct weston_view *view, struct weston_output *output, region_intersect_only_translation(&repaint_output, repaint_global, &surface_blend, view); - weston_output_region_from_global(output, &repaint_output); + weston_region_global_to_output(&repaint_output, + output, + &repaint_output); - repaint_region(view, output, &repaint_output, NULL, - PIXMAN_OP_OVER); + repaint_region(pnode, &repaint_output, NULL, PIXMAN_OP_OVER); } pixman_region32_fini(&surface_blend); @@ -437,11 +441,12 @@ draw_view_translated(struct weston_view *view, struct weston_output *output, } static void -draw_view_source_clipped(struct weston_view *view, - struct weston_output *output, +draw_node_source_clipped(struct weston_paint_node *pnode, pixman_region32_t *repaint_global) { - struct weston_surface *surface = view->surface; + struct weston_surface *surface = pnode->surface; + struct weston_output *output = pnode->output; + struct weston_view *view = pnode->view; pixman_region32_t surf_region; pixman_region32_t buffer_region; pixman_region32_t repaint_output; @@ -462,10 +467,10 @@ draw_view_source_clipped(struct weston_view *view, pixman_region32_init(&repaint_output); pixman_region32_copy(&repaint_output, repaint_global); - weston_output_region_from_global(output, &repaint_output); + weston_region_global_to_output(&repaint_output, output, + &repaint_output); - repaint_region(view, output, &repaint_output, &buffer_region, - PIXMAN_OP_OVER); + repaint_region(pnode, &repaint_output, &buffer_region, PIXMAN_OP_OVER); pixman_region32_fini(&repaint_output); pixman_region32_fini(&buffer_region); @@ -489,6 +494,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); @@ -505,7 +523,7 @@ draw_paint_node(struct weston_paint_node *pnode, * Also the boundingbox is accurate rather than an * approximation. */ - draw_view_translated(pnode->view, pnode->output, &repaint); + draw_node_translated(pnode, &repaint); } else { /* The complex case: the view transformation does not allow * converting opaque etc. regions into global coordinate space. @@ -514,7 +532,7 @@ draw_paint_node(struct weston_paint_node *pnode, * to be used whole. Source clipping does not work with * PIXMAN_OP_SRC. */ - draw_view_source_clipped(pnode->view, pnode->output, &repaint); + draw_node_source_clipped(pnode, &repaint); } out: @@ -542,7 +560,7 @@ copy_to_hw_buffer(struct weston_output *output, pixman_region32_t *region) pixman_region32_init(&output_region); pixman_region32_copy(&output_region, region); - weston_output_region_from_global(output, &output_region); + weston_region_global_to_output(&output_region, output, &output_region); pixman_image_set_clip_region32 (po->hw_buffer, &output_region); pixman_region32_fini(&output_region); @@ -554,43 +572,113 @@ copy_to_hw_buffer(struct weston_output *output, pixman_region32_t *region) 0, 0, /* src_x, src_y */ 0, 0, /* mask_x, mask_y */ 0, 0, /* dest_x, dest_y */ - pixman_image_get_width (po->hw_buffer), /* width */ - pixman_image_get_height (po->hw_buffer) /* height */); + po->fb_size.width, /* width */ + po->fb_size.height /* height */); pixman_image_set_clip_region32 (po->hw_buffer, NULL); } +static void +pixman_renderer_do_capture(struct weston_buffer *into, pixman_image_t *from) +{ + struct wl_shm_buffer *shm = into->shm_buffer; + pixman_image_t *dest; + + assert(into->type == WESTON_BUFFER_SHM); + assert(shm); + + wl_shm_buffer_begin_access(shm); + + dest = pixman_image_create_bits(into->pixel_format->pixman_format, + into->width, into->height, + wl_shm_buffer_get_data(shm), + wl_shm_buffer_get_stride(shm)); + abort_oom_if_null(dest); + + pixman_image_composite32(PIXMAN_OP_SRC, from, NULL /* mask */, dest, + 0, 0, /* src_x, src_y */ + 0, 0, /* mask_x, mask_y */ + 0, 0, /* dest_x, dest_y */ + into->width, into->height); + + pixman_image_unref(dest); + + wl_shm_buffer_end_access(shm); +} + +static void +pixman_renderer_do_capture_tasks(struct weston_output *output, + enum weston_output_capture_source source, + pixman_image_t *from, + const struct pixel_format_info *pfmt) +{ + int width = pixman_image_get_width(from); + int height = pixman_image_get_height(from); + struct weston_capture_task *ct; + + while ((ct = weston_output_pull_capture_task(output, source, + width, height, + pfmt))) { + struct weston_buffer *buffer = weston_capture_task_get_buffer(ct); + + assert(buffer->width == width); + assert(buffer->height == height); + assert(buffer->pixel_format->format == pfmt->format); + + if (buffer->type != WESTON_BUFFER_SHM) { + weston_capture_task_retire_failed(ct, "pixman: unsupported buffer"); + continue; + } + + pixman_renderer_do_capture(buffer, from); + weston_capture_task_retire_complete(ct); + } +} + +static void +pixman_renderer_output_set_buffer(struct weston_output *output, + pixman_image_t *buffer); + static void pixman_renderer_repaint_output(struct weston_output *output, - pixman_region32_t *output_damage) + pixman_region32_t *output_damage, + struct weston_renderbuffer *renderbuffer) { struct pixman_output_state *po = get_output_state(output); - pixman_region32_t hw_damage; + struct pixman_renderbuffer *rb; + + assert(renderbuffer); + + rb = container_of(renderbuffer, struct pixman_renderbuffer, base); + + pixman_renderer_output_set_buffer(output, rb->image); 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; + if (!po->hw_buffer) return; - } - pixman_region32_init(&hw_damage); - if (po->hw_extra_damage) { - pixman_region32_union(&hw_damage, - po->hw_extra_damage, output_damage); - po->hw_extra_damage = NULL; - } else { - pixman_region32_copy(&hw_damage, output_damage); + /* Accumulate damage in all renderbuffers */ + wl_list_for_each(rb, &po->renderbuffer_list, link) { + pixman_region32_union(&rb->base.damage, + &rb->base.damage, + output_damage); } if (po->shadow_image) { repaint_surfaces(output, output_damage); - copy_to_hw_buffer(output, &hw_damage); + pixman_renderer_do_capture_tasks(output, + WESTON_OUTPUT_CAPTURE_SOURCE_BLENDING, + po->shadow_image, po->shadow_format); + copy_to_hw_buffer(output, &renderbuffer->damage); } else { - repaint_surfaces(output, &hw_damage); + repaint_surfaces(output, &renderbuffer->damage); } - pixman_region32_fini(&hw_damage); + pixman_renderer_do_capture_tasks(output, + WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER, + po->hw_buffer, po->hw_format); + pixman_region32_clear(&renderbuffer->damage); wl_signal_emit(&output->frame_signal, output_damage); @@ -598,7 +686,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 +708,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 +735,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 +754,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 +815,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 +870,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 +882,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, @@ -829,6 +914,66 @@ pixman_renderer_surface_copy_content(struct weston_surface *surface, return 0; } +static bool +pixman_renderer_resize_output(struct weston_output *output, + const struct weston_size *fb_size, + const struct weston_geometry *area) +{ + struct pixman_output_state *po = get_output_state(output); + struct pixman_renderbuffer *renderbuffer, *tmp; + + check_compositing_area(fb_size, area); + + /* + * Pixman-renderer does not implement output decorations blitting, + * wayland-backend does it on its own. + */ + assert(area->x == 0); + assert(area->y == 0); + assert(fb_size->width == area->width); + assert(fb_size->height == area->height); + + pixman_renderer_output_set_buffer(output, NULL); + + wl_list_for_each_safe(renderbuffer, tmp, &po->renderbuffer_list, link) { + wl_list_remove(&renderbuffer->link); + weston_renderbuffer_unref(&renderbuffer->base); + } + + po->fb_size = *fb_size; + + /* + * Have a hw_format only after the first call to + * pixman_renderer_output_set_buffer(). + */ + if (po->hw_format) { + weston_output_update_capture_info(output, + WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER, + po->fb_size.width, + po->fb_size.height, + po->hw_format); + } + + if (!po->shadow_format) + return true; + + if (po->shadow_image) + pixman_image_unref(po->shadow_image); + + po->shadow_image = + pixman_image_create_bits_no_clear(po->shadow_format->pixman_format, + fb_size->width, fb_size->height, + NULL, 0); + + weston_output_update_capture_info(output, + WESTON_OUTPUT_CAPTURE_SOURCE_BLENDING, + po->fb_size.width, + po->fb_size.height, + po->shadow_format); + + return !!po->shadow_image; +} + static void debug_binding(struct weston_keyboard *keyboard, const struct timespec *time, uint32_t key, void *data) @@ -850,6 +995,8 @@ debug_binding(struct weston_keyboard *keyboard, const struct timespec *time, } } +static struct pixman_renderer_interface pixman_renderer_interface; + WL_EXPORT int pixman_renderer_init(struct weston_compositor *ec) { @@ -865,14 +1012,14 @@ pixman_renderer_init(struct weston_compositor *ec) renderer->debug_color = NULL; renderer->base.read_pixels = pixman_renderer_read_pixels; renderer->base.repaint_output = pixman_renderer_repaint_output; + renderer->base.resize_output = pixman_renderer_resize_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; + renderer->base.type = WESTON_RENDERER_PIXMAN; + renderer->base.pixman = &pixman_renderer_interface; ec->renderer = &renderer->base; ec->capabilities |= WESTON_CAP_ROTATION_ANY; ec->capabilities |= WESTON_CAP_VIEW_CLIP_MASK; @@ -902,74 +1049,84 @@ pixman_renderer_init(struct weston_compositor *ec) return 0; } -WL_EXPORT void +static void pixman_renderer_output_set_buffer(struct weston_output *output, pixman_image_t *buffer) { + struct weston_compositor *compositor = output->compositor; 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_image_ref(po->hw_buffer); - } -} + if (!po->hw_buffer) + return; -WL_EXPORT void -pixman_renderer_output_set_hw_extra_damage(struct weston_output *output, - pixman_region32_t *extra_damage) -{ - struct pixman_output_state *po = get_output_state(output); + pixman_format = pixman_image_get_format(po->hw_buffer); + po->hw_format = pixel_format_get_info_by_pixman(pixman_format); + compositor->read_format = po->hw_format; + assert(po->hw_format); + + pixman_image_ref(po->hw_buffer); - po->hw_extra_damage = extra_damage; + assert(po->fb_size.width == pixman_image_get_width(po->hw_buffer)); + assert(po->fb_size.height == pixman_image_get_height(po->hw_buffer)); + + /* + * The size cannot change, but the format might, or we did not have + * hw_format in pixman_renderer_resize_output() yet. + */ + weston_output_update_capture_info(output, + WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER, + po->fb_size.width, + po->fb_size.height, + po->hw_format); } -WL_EXPORT int +static int pixman_renderer_output_create(struct weston_output *output, const struct pixman_renderer_output_options *options) { struct pixman_output_state *po; - int w, h; + struct weston_geometry area = { + .x = 0, + .y = 0, + .width = options->fb_size.width, + .height = options->fb_size.height + }; po = zalloc(sizeof *po); if (po == NULL) return -1; - if (options->use_shadow) { - /* set shadow image transformation */ - w = output->current_mode->width; - h = output->current_mode->height; - - po->shadow_buffer = malloc(w * h * 4); + output->renderer_state = po; - if (!po->shadow_buffer) { - free(po); - return -1; - } + if (options->use_shadow) + po->shadow_format = pixel_format_get_info(DRM_FORMAT_XRGB8888); - po->shadow_image = - pixman_image_create_bits(PIXMAN_x8r8g8b8, w, h, - po->shadow_buffer, w * 4); + wl_list_init(&po->renderbuffer_list); - if (!po->shadow_image) { - free(po->shadow_buffer); - free(po); - return -1; - } + if (!pixman_renderer_resize_output(output, &options->fb_size, &area)) { + output->renderer_state = NULL; + free(po); + return -1; } - output->renderer_state = po; + weston_output_update_capture_info(output, + WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER, + area.width, area.height, + options->format); return 0; } -WL_EXPORT void +static void pixman_renderer_output_destroy(struct weston_output *output) { struct pixman_output_state *po = get_output_state(output); + struct pixman_renderbuffer *renderbuffer, *tmp; if (po->shadow_image) pixman_image_unref(po->shadow_image); @@ -977,11 +1134,92 @@ 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; + wl_list_for_each_safe(renderbuffer, tmp, &po->renderbuffer_list, link) { + wl_list_remove(&renderbuffer->link); + weston_renderbuffer_unref(&renderbuffer->base); + } + free(po); } + +static void +pixman_renderer_renderbuffer_destroy(struct weston_renderbuffer *renderbuffer); + +static struct weston_renderbuffer * +pixman_renderer_create_image_from_ptr(struct weston_output *output, + const struct pixel_format_info *format, + int width, int height, uint32_t *ptr, + int rowstride) +{ + struct pixman_output_state *po = get_output_state(output); + struct pixman_renderbuffer *renderbuffer; + + assert(po); + + renderbuffer = xzalloc(sizeof(*renderbuffer)); + + renderbuffer->image = pixman_image_create_bits(format->pixman_format, + width, height, ptr, + rowstride); + if (!renderbuffer->image) { + free(renderbuffer); + return NULL; + } + + pixman_region32_init(&renderbuffer->base.damage); + renderbuffer->base.refcount = 2; + renderbuffer->base.destroy = pixman_renderer_renderbuffer_destroy; + wl_list_insert(&po->renderbuffer_list, &renderbuffer->link); + + return &renderbuffer->base; +} + +static struct weston_renderbuffer * +pixman_renderer_create_image(struct weston_output *output, + const struct pixel_format_info *format, int width, + int height) +{ + struct pixman_output_state *po = get_output_state(output); + struct pixman_renderbuffer *renderbuffer; + + assert(po); + + renderbuffer = xzalloc(sizeof(*renderbuffer)); + + renderbuffer->image = + pixman_image_create_bits_no_clear(format->pixman_format, width, + height, NULL, 0); + if (!renderbuffer->image) { + free(renderbuffer); + return NULL; + } + + pixman_region32_init(&renderbuffer->base.damage); + renderbuffer->base.refcount = 2; + renderbuffer->base.destroy = pixman_renderer_renderbuffer_destroy; + wl_list_insert(&po->renderbuffer_list, &renderbuffer->link); + + return &renderbuffer->base; +} + +static void +pixman_renderer_renderbuffer_destroy(struct weston_renderbuffer *renderbuffer) +{ + struct pixman_renderbuffer *rb; + + rb = container_of(renderbuffer, struct pixman_renderbuffer, base); + pixman_image_unref(rb->image); + pixman_region32_fini(&rb->base.damage); + free(rb); +} + +static struct pixman_renderer_interface pixman_renderer_interface = { + .output_create = pixman_renderer_output_create, + .output_destroy = pixman_renderer_output_destroy, + .create_image_from_ptr = pixman_renderer_create_image_from_ptr, + .create_image = pixman_renderer_create_image, + .renderbuffer_get_image = pixman_renderer_renderbuffer_get_image, +}; diff --git a/libweston/pixman-renderer.h b/libweston/pixman-renderer.h index 2b81dde51..e508ef87e 100644 --- a/libweston/pixman-renderer.h +++ b/libweston/pixman-renderer.h @@ -28,6 +28,7 @@ #include #include "backend.h" #include "libweston-internal.h" +#include "pixman.h" int pixman_renderer_init(struct weston_compositor *ec); @@ -35,19 +36,25 @@ pixman_renderer_init(struct weston_compositor *ec); struct pixman_renderer_output_options { /** Composite into a shadow buffer, copying to the hardware buffer */ bool use_shadow; + /** Initial framebuffer size */ + struct weston_size fb_size; + /** Initial pixel format */ + const struct pixel_format_info *format; }; -int -pixman_renderer_output_create(struct weston_output *output, - const struct pixman_renderer_output_options *options); - -void -pixman_renderer_output_set_buffer(struct weston_output *output, - pixman_image_t *buffer); - -void -pixman_renderer_output_set_hw_extra_damage(struct weston_output *output, - pixman_region32_t *extra_damage); - -void -pixman_renderer_output_destroy(struct weston_output *output); +struct pixman_renderer_interface { + int (*output_create)(struct weston_output *output, + const struct pixman_renderer_output_options *options); + void (*output_destroy)(struct weston_output *output); + + struct weston_renderbuffer *(*create_image_from_ptr)(struct weston_output *output, + const struct pixel_format_info *format, + int width, + int height, + uint32_t *ptr, + int stride); + struct weston_renderbuffer *(*create_image)(struct weston_output *output, + const struct pixel_format_info *format, + int width, int height); + pixman_image_t *(*renderbuffer_get_image)(struct weston_renderbuffer *renderbuffer); +}; diff --git a/libweston/renderer-g2d/g2d-renderer.c b/libweston/renderer-g2d/g2d-renderer.c new file mode 100644 index 000000000..b56f11a6b --- /dev/null +++ b/libweston/renderer-g2d/g2d-renderer.c @@ -0,0 +1,2109 @@ +/* + * Copyright (c) 2016 Freescale Semiconductor, Inc. + * Copyright © 2012 Intel Corporation + * Copyright © 2015 Collabora, Ltd. + * Copyright 2018 NXP + * + * 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 "g2d-renderer.h" +#include "vertex-clipping.h" +#include "linux-dmabuf.h" +#include "linux-dmabuf-unstable-v1-server-protocol.h" +#include "linux-explicit-synchronization.h" +#include "shared/fd-util.h" +#include "shared/helpers.h" +#include "shared/platform.h" +#include "pixel-formats.h" + +#define BUFFER_DAMAGE_COUNT 3 +#define ALIGN_TO_16(a) (((a) + 15) & ~15) +#define ALIGN_TO_64(a) (((a) + 63) & ~63) + +#ifdef ENABLE_EGL +static PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display = NULL; +#ifndef PFNEGLUPDATEWAYLANDBUFFERWL +typedef EGLBoolean (EGLAPIENTRYP PFNEGLUPDATEWAYLANDBUFFERWL)(EGLDisplay dpy, struct wl_resource *buffer, EGLint attribute); +#endif +#endif + +enum g2d_rotation_angle +{ + /* rotation angle 0 */ + G2D_ROTATION_ANGLE_0 = 0x10, + + /* clockwise rotation */ + G2D_ROTATION_ANGLE_POSITIVE_90 = 0x20, + G2D_ROTATION_ANGLE_POSITIVE_180 = 0x40, + G2D_ROTATION_ANGLE_POSITIVE_270 = 0x80, + + /* Anticlockwise rotation */ + G2D_ROTATION_ANGLE_NEGATIVE_90 = 0x08, + G2D_ROTATION_ANGLE_NEGATIVE_180 = 0x04, + G2D_ROTATION_ANGLE_NEGATIVE_270 = 0x02, +}; + +struct wl_viv_buffer +{ + struct wl_resource *resource; + void *surface; + signed int width; + signed int height; + enum g2d_format format; + unsigned int alignedWidth; + unsigned int alignedHeight; + unsigned int physical[3]; + unsigned int gpuBaseAddr; + enum g2d_tiling tiling; + signed int fd; + + unsigned int ts_addr; + unsigned int fc_enabled; + unsigned int fcValue; + unsigned int fcValueUpper; + unsigned int compressed; + unsigned int tileStatus_enabled; +}; + +typedef struct _g2dRECT +{ + int left; + int top; + int right; + int bottom; +} g2dRECT; + +struct g2d_output_state { + int current_buffer; + pixman_region32_t buffer_damage[BUFFER_DAMAGE_COUNT]; + struct g2d_surfaceEx *drm_hw_buffer; + int width; + int height; +}; + +struct g2d_surface_state { + float color[4]; + struct weston_buffer_reference buffer_ref; + struct weston_buffer_release_reference buffer_release_ref; + int pitch; /* in pixels */ + int attached; + pixman_region32_t texture_damage; + struct g2d_surfaceEx g2d_surface; + struct g2d_buf *shm_buf; + struct g2d_buf *dma_buf; + int shm_buf_length; + int bpp; + + struct weston_surface *surface; + struct wl_listener surface_destroy_listener; + struct wl_listener renderer_destroy_listener; +}; + +struct g2d_renderer { + struct weston_renderer base; + struct wl_signal destroy_signal; +#ifdef ENABLE_EGL + NativeDisplayType display; + EGLDisplay egl_display; + struct wl_display *wl_display; + PFNEGLBINDWAYLANDDISPLAYWL bind_display; + PFNEGLUNBINDWAYLANDDISPLAYWL unbind_display; + PFNEGLQUERYWAYLANDBUFFERWL query_buffer; + PFNEGLUPDATEWAYLANDBUFFERWL update_buffer; + + EGLDeviceEXT egl_device; + const char *drm_device; + + PFNEGLQUERYDISPLAYATTRIBEXTPROC query_display_attrib; + PFNEGLQUERYDEVICESTRINGEXTPROC query_device_string; + bool has_device_query; + + bool has_dmabuf_import_modifiers; + PFNEGLQUERYDMABUFFORMATSEXTPROC query_dmabuf_formats; + PFNEGLQUERYDMABUFMODIFIERSEXTPROC query_dmabuf_modifiers; +#endif + void *handle; + int use_drm; + struct weston_drm_format_array supported_formats; +}; + +static int +g2d_renderer_create_surface(struct weston_surface *surface); + +static inline struct g2d_surface_state * +get_surface_state(struct weston_surface *surface) +{ + if (!surface->renderer_state) + g2d_renderer_create_surface(surface); + + return (struct g2d_surface_state *)surface->renderer_state; +} + +static inline struct g2d_renderer * +get_renderer(struct weston_compositor *ec) +{ + return (struct g2d_renderer *)ec->renderer; +} + +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) > (b)) ? (b) : (a)) +/* + * Compute the boundary vertices of the intersection of the global coordinate + * aligned rectangle 'rect', and an arbitrary quadrilateral produced from + * 'surf_rect' when transformed from surface coordinates into global coordinates. + * The vertices are written to 'ex' and 'ey', and the return value is the + * number of vertices. Vertices are produced in clockwise winding order. + * Guarantees to produce either zero vertices, or 3-8 vertices with non-zero + * polygon area. + */ +static int +calculate_edges(struct weston_view *ev, pixman_box32_t *rect, + pixman_box32_t *surf_rect, struct weston_coord *e) +{ + + struct clip_context ctx; + int i, n; + float min_x, max_x, min_y, max_y; + struct weston_surface *es = ev->surface; + struct weston_coord_surface tmp[4] = { + weston_coord_surface(surf_rect->x1, surf_rect->y1, es), + weston_coord_surface(surf_rect->x2, surf_rect->y1, es), + weston_coord_surface(surf_rect->x2, surf_rect->y2, es), + weston_coord_surface(surf_rect->x1, surf_rect->y2, es), + }; + struct polygon8 surf; + + surf.n = 4; + + ctx.clip.x1 = rect->x1; + ctx.clip.y1 = rect->y1; + ctx.clip.x2 = rect->x2; + ctx.clip.y2 = rect->y2; + + /* transform surface to screen space: */ + for (i = 0; i < surf.n; i++) + surf.pos[i] = weston_coord_surface_to_global(ev, tmp[i]).c; + + /* find bounding box: */ + min_x = max_x = surf.pos[0].x; + min_y = max_y = surf.pos[0].y; + + for (i = 1; i < surf.n; i++) { + min_x = MIN(min_x, surf.pos[i].x); + max_x = MAX(max_x, surf.pos[i].x); + min_y = MIN(min_y, surf.pos[i].y); + max_y = MAX(max_y, surf.pos[i].y); + } + + /* First, simple bounding box check to discard early transformed + * surface rects that do not intersect with the clip region: + */ + if ((min_x >= ctx.clip.x2) || (max_x <= ctx.clip.x1) || + (min_y >= ctx.clip.y2) || (max_y <= ctx.clip.y1)) + return 0; + + /* Simple case, bounding box edges are parallel to surface edges, + * there will be only four edges. We just need to clip the surface + * vertices to the clip rect bounds: + */ + if (!ev->transform.enabled) + return clip_simple(&ctx, &surf, e); + + /* Transformed case: use a general polygon clipping algorithm to + * clip the surface rectangle with each side of 'rect'. + * The algorithm is Sutherland-Hodgman, as explained in + * http://www.codeguru.com/cpp/misc/misc/graphics/article.php/c8965/Polygon-Clipping.htm + * but without looking at any of that code. + */ + n = clip_transformed(&ctx, &surf, e); + + if (n < 3) + return 0; + + return n; + +} + +static void +calculate_rect_with_transform(int surfaceWidth, int surfaceHeight, + uint32_t transform, g2dRECT *rect) +{ + g2dRECT tmp = *rect; + + switch (transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + default: + break; + case WL_OUTPUT_TRANSFORM_270: + rect->right = surfaceWidth - tmp.top; + rect->left = rect->right - (tmp.bottom - tmp.top); + rect->top = tmp.left; + rect->bottom = rect->top + (tmp.right - tmp.left); + break; + case WL_OUTPUT_TRANSFORM_90: + rect->left = tmp.top; + rect->right = rect->left + (tmp.bottom - tmp.top); + rect->bottom = surfaceHeight - tmp.left; + rect->top = rect->bottom - (tmp.right - tmp.left); + break; + case WL_OUTPUT_TRANSFORM_180: + rect->left = surfaceWidth - tmp.right; + rect->right = rect->left + (tmp.right - tmp.left); + rect->bottom = surfaceHeight - tmp.top; + rect->top = rect->bottom - (tmp.bottom - tmp.top); + break; + } +} + +static void +convert_size_by_view_transform(int *width_out, int *height_out, int width, int height, uint32_t transform) +{ + switch (transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + default: + *width_out = width; + *height_out = height; + break; + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + *width_out = height; + *height_out = width; + break; + } +} + +static enum g2d_rotation +convert_transform_to_rot(uint32_t view_transform, uint32_t output_transform) +{ + uint8_t angle = G2D_ROTATION_ANGLE_0; + enum g2d_rotation rot; + + /* First, rotate according to the angle set by the client. */ + angle = angle << view_transform; + /* Then, rotate according to the angle of the output. */ + angle = angle >> output_transform; + + switch(angle) { + case G2D_ROTATION_ANGLE_0: + default: + rot = G2D_ROTATION_0; + break; + case G2D_ROTATION_ANGLE_POSITIVE_270: + case G2D_ROTATION_ANGLE_NEGATIVE_90: + rot = G2D_ROTATION_90; + break; + case G2D_ROTATION_ANGLE_POSITIVE_90: + case G2D_ROTATION_ANGLE_NEGATIVE_270: + rot = G2D_ROTATION_270; + break; + case G2D_ROTATION_ANGLE_POSITIVE_180: + case G2D_ROTATION_ANGLE_NEGATIVE_180: + rot = G2D_ROTATION_180; + break; + } + return rot; +} + +static inline struct g2d_output_state * +get_output_state(struct weston_output *output) +{ + return (struct g2d_output_state *)output->renderer_state; +} + +static int +g2d_getG2dFormat_from_pixman(pixman_format_code_t Format, enum g2d_format* g2dFormat) +{ + switch(Format) + { + case PIXMAN_r5g6b5: + *g2dFormat = G2D_RGB565; + break; + case PIXMAN_a8b8g8r8: + *g2dFormat = G2D_RGBA8888; + break; + case PIXMAN_x8b8g8r8: + *g2dFormat = G2D_RGBX8888; + break; + case PIXMAN_a8r8g8b8: + *g2dFormat = G2D_BGRA8888; + break; + case PIXMAN_x8r8g8b8 : + *g2dFormat = G2D_BGRX8888; + break; + case PIXMAN_b5g6r5: + *g2dFormat = G2D_BGR565; + break; + case PIXMAN_b8g8r8a8: + *g2dFormat = G2D_ARGB8888; + break; + case PIXMAN_r8g8b8a8: + *g2dFormat = G2D_ABGR8888; + break; + case PIXMAN_b8g8r8x8: + *g2dFormat = G2D_XRGB8888; + break; + case PIXMAN_r8g8b8x8: + *g2dFormat = G2D_XBGR8888; + break; + case PIXMAN_yv12: + *g2dFormat = G2D_YV12; + break; + case PIXMAN_yuy2: + *g2dFormat = G2D_YUYV; + break; + default: + weston_log("Error in function %s, Format(%d) not supported\n", __func__, Format); + return -1; + } + return 0; +} + +static void printG2dSurfaceInfo(struct g2d_surfaceEx* g2dSurface, const char* msg) +{ + weston_log("%s physicAddr = %x left = %d right = %d top=%d bottom=%d stride= %d tiling = %d, format=%d \n", + msg, + g2dSurface->base.planes[0], + g2dSurface->base.left, + g2dSurface->base.right, + g2dSurface->base.top, + g2dSurface->base.bottom, + g2dSurface->base.stride, + g2dSurface->tiling, + g2dSurface->base.format); +} + +static int +get_g2dSurface(struct wl_viv_buffer *buffer, struct g2d_surfaceEx *g2dSurface) +{ + if(buffer->width <= 0 || buffer->height <= 0) + { + weston_log("invalid EGL buffer in function %s\n", __func__); + return -EINVAL; + } + g2dSurface->base.format = buffer->format; + g2dSurface->tiling = buffer->tiling; + g2dSurface->base.planes[0] = buffer->physical[0] + buffer->gpuBaseAddr; + g2dSurface->base.planes[1] = buffer->physical[1] + buffer->gpuBaseAddr; + g2dSurface->base.planes[2] = buffer->physical[2] + buffer->gpuBaseAddr; + g2dSurface->base.left = 0; + g2dSurface->base.top = 0; + g2dSurface->base.right = buffer->width; + g2dSurface->base.bottom = buffer->height; + g2dSurface->base.stride = buffer->alignedWidth; + g2dSurface->base.width = buffer->width; + g2dSurface->base.height = buffer->height; + g2dSurface->base.rot = G2D_ROTATION_0; + + if(buffer->ts_addr && buffer->tileStatus_enabled) + { + g2dSurface->tiling |= G2D_TILED_STATUS; + g2dSurface->ts.ts_addr = buffer->ts_addr; + g2dSurface->ts.fc_enabled = buffer->fc_enabled; + g2dSurface->ts.fc_value = buffer->fcValue; + g2dSurface->ts.fc_value_upper = buffer->fcValueUpper; + } + + return 0; +} + +static void +g2d_SetSurfaceRect(struct g2d_surfaceEx* g2dSurface, g2dRECT* rect) +{ + if(g2dSurface && rect) + { + g2dSurface->base.left = rect->left; + g2dSurface->base.top = rect->top; + g2dSurface->base.right = rect->right; + g2dSurface->base.bottom = rect->bottom; + } +} + +#define _hasAlpha(format) (format==G2D_RGBA8888 || format==G2D_BGRA8888 \ + || format==G2D_ARGB8888 || format==G2D_ABGR8888) + +static int +g2d_blit_surface(void *handle, struct g2d_surfaceEx * srcG2dSurface, struct g2d_surfaceEx *dstG2dSurface, + g2dRECT *srcRect, g2dRECT *dstRect) +{ + g2d_SetSurfaceRect(srcG2dSurface, srcRect); + g2d_SetSurfaceRect(dstG2dSurface, dstRect); + srcG2dSurface->base.blendfunc = G2D_ONE; + dstG2dSurface->base.blendfunc = G2D_ONE_MINUS_SRC_ALPHA; + if(!(_hasAlpha(srcG2dSurface->base.format))){ + g2d_disable(handle, G2D_BLEND); + } + + if(g2d_blitEx(handle, srcG2dSurface, dstG2dSurface)){ + printG2dSurfaceInfo(srcG2dSurface, "SRC:"); + printG2dSurfaceInfo(dstG2dSurface, "DST:"); + return -1; + } + return 0; +} + +static void +g2d_clip_rects(enum g2d_rotation transform, + g2dRECT *srcRect, + g2dRECT *dstrect, + int dstWidth, + int dstHeight) +{ + int srcWidth = srcRect->right - srcRect->left; + int srcHeight = srcRect->bottom - srcRect->top; + float scale_v = 1.0f; + float scale_h = 1.0f; + + if(transform == G2D_ROTATION_90 + || transform == G2D_ROTATION_270) + { + scale_h = (float)srcHeight / (dstrect->right - dstrect->left); + scale_v = (float)srcWidth / (dstrect->bottom - dstrect->top); + } + else + { + scale_h = (float)srcWidth / (dstrect->right - dstrect->left); + scale_v = (float)srcHeight / (dstrect->bottom - dstrect->top); + } + switch (transform) { + case G2D_ROTATION_0: + if(dstrect->left < 0) + { + srcRect->left += floorf((float)(-dstrect->left) * scale_h); + dstrect->left = 0; + if(srcRect->left >= srcRect->right) + return; + } + if(dstrect->right > dstWidth) + { + srcRect->right -= floorf((float)(dstrect->right - dstWidth) * scale_h); + dstrect->right = dstWidth; + if(srcRect->right <= srcRect->left) + return; + } + if(dstrect->top < 0) + { + srcRect->top += floorf((float)(-dstrect->top) * scale_v); + dstrect->top = 0; + if(srcRect->top >= srcRect->bottom) + return; + } + if(dstrect->bottom > dstHeight) + { + srcRect->bottom -= floorf((float)(dstrect->bottom - dstHeight) * scale_v); + dstrect->bottom = dstHeight; + if(srcRect->bottom < 0) + return; + } + break; + case G2D_ROTATION_270: + if(dstrect->left < 0) + { + srcRect->bottom -= floorf((float)(-dstrect->left) * scale_h); + dstrect->left = 0; + if(srcRect->top >= srcRect->bottom) + return; + } + if(dstrect->bottom > dstHeight) + { + srcRect->right -= floorf((float)(dstrect->bottom - dstHeight) * scale_v); + dstrect->bottom = dstHeight; + if(srcRect->right < 0) + return; + } + if(dstrect->top < 0) + { + srcRect->left += floorf((float)(-dstrect->top) * scale_v); + dstrect->top = 0; + if(srcRect->left > srcRect->right) + return; + } + if(dstrect->right > dstWidth) { + srcRect->top += floorf((float)(dstrect->right - dstWidth) * scale_h); + dstrect->right = dstWidth; + if(srcRect->top >= srcRect->bottom) + return; + } + break; + case G2D_ROTATION_90: + if(dstrect->left < 0) + { + srcRect->top += floorf((float)(-dstrect->left) * scale_h); + dstrect->left = 0; + if(srcRect->top >= srcRect->bottom) + return; + } + if(dstrect->top < 0) + { + srcRect->right -= floorf((float)(-dstrect->top) * scale_v); + dstrect->top = 0; + if(srcRect->left >= srcRect->right) + return; + } + if(dstrect->bottom > dstHeight) + { + srcRect->left += floorf((float)(dstrect->bottom - dstHeight) * scale_v); + dstrect->bottom = dstHeight; + if(srcRect->right <= srcRect->left) + return; + } + if(dstrect->right > dstWidth) + { + srcRect->bottom -= floorf((float)(dstrect->right - dstWidth) * scale_h); + dstrect->right = dstWidth; + if(srcRect->bottom <= srcRect->top) + return; + } + break; + case G2D_ROTATION_180: + if(dstrect->left < 0) + { + srcRect->right -= floorf((float)(-dstrect->left) * scale_h); + dstrect->left = 0; + if(srcRect->left >= srcRect->right) + return; + } + if(dstrect->right > dstWidth) + { + srcRect->left += floorf((float)(dstrect->right - dstWidth) * scale_h); + dstrect->right = dstWidth; + if(srcRect->right <= srcRect->left) + return; + } + if(dstrect->top < 0) + { + srcRect->bottom -= floorf((float)(-dstrect->top) * scale_v); + dstrect->top = 0; + if(srcRect->top >= srcRect->bottom) + return; + } + if(dstrect->bottom > dstHeight) { + srcRect->top += floorf((float)(dstrect->bottom - dstHeight) * scale_v); + dstrect->bottom = dstHeight; + if(srcRect->top >= srcRect->bottom) + return; + } + break; + default: + break; + } +} + +static int +g2d_renderer_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) +{ + struct g2d_surfaceEx dstSurface; + struct g2d_surfaceEx *srcSurface; + struct g2d_output_state *go = get_output_state(output); + struct g2d_renderer *gr = get_renderer(output->compositor); + struct g2d_buf *read_buf = NULL; + enum g2d_format dst_format; + g2dRECT srcRect = {x, y, x + width, y + height}; + g2dRECT dstRect = {0, 0, width, height}; + + if( g2d_getG2dFormat_from_pixman(format->pixman_format, &dst_format)) + return -1; + + read_buf = g2d_alloc(width * height * 4, 0); + if( !read_buf) + return -1; + + srcSurface = go->drm_hw_buffer; + + dstSurface.base.planes[0] = read_buf->buf_paddr; + dstSurface.base.format = dst_format; + dstSurface.base.width = width; + dstSurface.base.height = height; + dstSurface.base.stride = width; + dstSurface.base.rot = G2D_FLIP_V; + if(g2d_blit_surface(gr->handle, srcSurface, &dstSurface, &srcRect, &dstRect)) { + g2d_free(read_buf); + return -1; + } + g2d_finish(gr->handle); + + memcpy(pixels, read_buf->buf_vaddr, width * height * PIXMAN_FORMAT_BPP(format->pixman_format)/8); + g2d_free(read_buf); + + return 0; +} + +static int g2d_int_from_double(double d) +{ + return wl_fixed_to_int(wl_fixed_from_double(d)); +} + +static void +repaint_region(struct weston_view *ev, struct weston_output *output, struct g2d_output_state *go, pixman_region32_t *region, + pixman_region32_t *surf_region){ + + struct g2d_renderer *gr = get_renderer(ev->surface->compositor); + struct g2d_surface_state *gs = get_surface_state(ev->surface); + + pixman_box32_t *rects, *surf_rects, *bb_rects; + int i, j, nrects, nsurf, nbb=0; + g2dRECT srcRect = {0}; + g2dRECT dstrect = {0}; + g2dRECT clipRect = {0}; + int dstWidth = 0; + int dstHeight = 0; + struct g2d_surfaceEx *dstsurface = go->drm_hw_buffer; + struct g2d_surfaceEx srcsurface = gs->g2d_surface; + uint32_t view_transform = ev->surface->buffer_viewport.buffer.transform; + int src_x = wl_fixed_to_int (ev->surface->buffer_viewport.buffer.src_x); + int src_y = wl_fixed_to_int (ev->surface->buffer_viewport.buffer.src_y); + int width = wl_fixed_to_int (ev->surface->buffer_viewport.buffer.src_width); + int height = wl_fixed_to_int (ev->surface->buffer_viewport.buffer.src_height); + int src_width = -1; + int src_height = -1; + int scale = ev->surface->buffer_viewport.buffer.scale; + if (ev->alpha < 1.0) { + /* Skip the render for global alpha, a workaround to disable the + fade effect, it created garbage info in the sequence test.*/ + return; + } + + if (srcsurface.base.width <= 0 || srcsurface.base.height <= 0) { + return; + } + + bb_rects = pixman_region32_rectangles(&ev->transform.boundingbox, &nbb); + + if(!gs->attached || nbb <= 0) + { + return; + } + + convert_size_by_view_transform(&src_width, &src_height, width, height, view_transform); + + rects = pixman_region32_rectangles(region, &nrects); + surf_rects = pixman_region32_rectangles(surf_region, &nsurf); + if(src_width != -1 && src_width > 0 && src_x >=0 && src_y >= 0 + && src_x < gs->g2d_surface.base.width + && src_y < gs->g2d_surface.base.height) + { + srcRect.left = src_x * scale; + srcRect.top = src_y * scale; + srcRect.right = min (gs->g2d_surface.base.width, (src_x + src_width) * scale); + srcRect.bottom = min (gs->g2d_surface.base.height, (src_y + src_height) * scale); + } + else + { + srcRect.left = srcsurface.base.left; + srcRect.top = srcsurface.base.top; + srcRect.right = srcsurface.base.right; + srcRect.bottom = srcsurface.base.bottom; + } + + dstWidth = dstsurface->base.width; + dstHeight = dstsurface->base.height; + /*Calculate the destrect once for all*/ + dstrect.left = bb_rects[0].x1; + dstrect.top = bb_rects[0].y1; + dstrect.right = bb_rects[0].x2; + dstrect.bottom = bb_rects[0].y2; + /*Multi display support*/ + if(output->x > 0) + { + dstrect.left = dstrect.left - output->x; + dstrect.right = dstrect.right - output->x; + } + + calculate_rect_with_transform(dstsurface->base.width, + dstsurface->base.height, + output->transform, &dstrect); + + /* Calculate the angle at which the frame buffer really needs to be rotated based + * on the rotation angle of the output and the angle set by the client. + */ + srcsurface.base.rot = convert_transform_to_rot(view_transform, output->transform); + g2d_clip_rects(srcsurface.base.rot, &srcRect, &dstrect, dstWidth, dstHeight); + + for (i = 0; i < nrects; i++) + { + pixman_box32_t *rect = &rects[i]; + float min_x, max_x, min_y, max_y; + + for (j = 0; j < nsurf; j++) + { + pixman_box32_t *surf_rect = &surf_rects[j]; + struct weston_coord e[8]; /* edge points in screen space */ + int n; + int m=0; + n = calculate_edges(ev, rect, surf_rect, e); + if (n < 3) + continue; + + min_x = max_x = e[0].x; + min_y = max_y = e[0].y; + for (m = 1; m < n; m++) + { + min_x = min(min_x, e[m].x); + max_x = max(max_x, e[m].x); + min_y = min(min_y, e[m].y); + max_y = max(max_y, e[m].y); + } + + clipRect.left = g2d_int_from_double(min_x); + clipRect.top = g2d_int_from_double(min_y); + clipRect.right = g2d_int_from_double(max_x); + clipRect.bottom = g2d_int_from_double(max_y); + + if(output->x > 0) + { + clipRect.left = clipRect.left - output->x; + clipRect.right = clipRect.right - output->x; + } + /* Need compute the clip rect with transform */ + calculate_rect_with_transform(dstsurface->base.width, + dstsurface->base.height, + output->transform, &clipRect); + if(clipRect.left >= clipRect.right || clipRect.top >= clipRect.bottom) + { + return; + } + g2d_set_clipping(gr->handle, clipRect.left, clipRect.top, clipRect.right, clipRect.bottom); + g2d_blit_surface(gr->handle, &srcsurface, dstsurface, &srcRect, &dstrect); + } + } +} + +static int sync_wait(int fd, int timeout) +{ + struct pollfd fds; + int ret; + + if (fd < 0) { + errno = EINVAL; + return -1; + } + + fds.fd = fd; + fds.events = POLLIN; + + do { + ret = poll(&fds, 1, timeout); + if (ret > 0) { + if (fds.revents & (POLLERR | POLLNVAL)) { + errno = EINVAL; + return -1; + } + return 0; + } else if (ret == 0) { + errno = ETIME; + return -1; + } + } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); + + return ret; +} + +static int +ensure_surface_buffer_is_ready(struct g2d_renderer *gr, + struct g2d_surface_state *gs) +{ + int ret = 0; + struct weston_surface *surface = gs->surface; + struct weston_buffer *buffer = gs->buffer_ref.buffer; + + if (!buffer) + return 0; + + if(buffer->type == WESTON_BUFFER_RENDERER_OPAQUE) { + if (!buffer->resource) + return 0; + + /*Update vivBuffer and set g2d surface */ + struct wl_viv_buffer *vivBuffer = wl_resource_get_user_data(buffer->resource); + + if (gr->update_buffer) + gr->update_buffer(gr->egl_display, (void *)buffer->resource, EGL_WAYLAND_BUFFER_WL); + + ret = get_g2dSurface(vivBuffer, &gs->g2d_surface); + + if (ret < 0) + return ret; + } + + if (surface->acquire_fence_fd < 0) + return 0; + + ret = sync_wait(surface->acquire_fence_fd, 2000); + + if (ret < 0 && errno == ETIME){ + /* Print a warning. */ + weston_log("%s: Warning: wait for fence fd=%d", __func__, surface->acquire_fence_fd); + + /* Wait for ever. */ + ret = sync_wait(surface->acquire_fence_fd, -1); + } + + return ret; +} + +static void +draw_view(struct weston_view *ev, struct weston_output *output, + pixman_region32_t *damage) /* in global coordinates */ +{ + struct weston_compositor *ec = ev->surface->compositor; + struct g2d_output_state *go = get_output_state(output); + struct g2d_surface_state *gs = get_surface_state(ev->surface); + struct g2d_renderer *gr = get_renderer(ec); + /* repaint bounding region in global coordinates: */ + pixman_region32_t repaint; + /* opaque region in surface coordinates: */ + pixman_region32_t surface_opaque; + /* non-opaque region in surface coordinates: */ + pixman_region32_t surface_blend; + + pixman_region32_init(&repaint); + pixman_region32_intersect(&repaint, + &ev->transform.boundingbox, damage); + pixman_region32_subtract(&repaint, &repaint, &ev->clip); + + if (!pixman_region32_not_empty(&repaint)) + goto out; + + if (ensure_surface_buffer_is_ready(gr, gs) < 0) + goto out; + + /* blended region is whole surface minus opaque region: */ + pixman_region32_init_rect(&surface_blend, 0, 0, + ev->surface->width, ev->surface->height); + if (ev->geometry.scissor_enabled) + pixman_region32_intersect(&surface_blend, &surface_blend, + &ev->geometry.scissor); + pixman_region32_subtract(&surface_blend, &surface_blend, + &ev->surface->opaque); + + /* XXX: Should we be using ev->transform.opaque here? */ + pixman_region32_init(&surface_opaque); + if (ev->geometry.scissor_enabled) + pixman_region32_intersect(&surface_opaque, + &ev->surface->opaque, + &ev->geometry.scissor); + else + pixman_region32_copy(&surface_opaque, &ev->surface->opaque); + + if (pixman_region32_not_empty(&surface_opaque)) { + if (ev->alpha < 1.0) { + g2d_enable(gr->handle, G2D_BLEND); + g2d_enable(gr->handle, G2D_GLOBAL_ALPHA); + gs->g2d_surface.base.global_alpha = ev->alpha * 0xFF; + } + repaint_region(ev, output, go, &repaint, &surface_opaque); + g2d_disable(gr->handle, G2D_GLOBAL_ALPHA); + g2d_disable(gr->handle, G2D_BLEND); + } + + if (pixman_region32_not_empty(&surface_blend)) { + g2d_enable(gr->handle, G2D_BLEND); + if (ev->alpha < 1.0) { + g2d_enable(gr->handle, G2D_GLOBAL_ALPHA); + gs->g2d_surface.base.global_alpha = ev->alpha * 0xFF; + } + repaint_region(ev, output, go, &repaint, &surface_blend); + g2d_disable(gr->handle, G2D_GLOBAL_ALPHA); + g2d_disable(gr->handle, G2D_BLEND); + } + pixman_region32_fini(&surface_blend); + pixman_region32_fini(&surface_opaque); + +out: + pixman_region32_fini(&repaint); +} + +static void +repaint_views(struct weston_output *output, pixman_region32_t *damage) +{ + struct weston_compositor *compositor = output->compositor; + struct weston_view *view; + + wl_list_for_each_reverse(view, &compositor->view_list, link) + if (view->plane == &compositor->primary_plane) + draw_view(view, output, damage); +} + +static void +output_get_damage(struct weston_output *output, + pixman_region32_t *buffer_damage) +{ + struct g2d_output_state *go = get_output_state(output); + int i; + + for (i = 0; i < BUFFER_DAMAGE_COUNT; i++) + pixman_region32_union(buffer_damage, + buffer_damage, + &go->buffer_damage[i]); +} + +static void +output_rotate_damage(struct weston_output *output, + pixman_region32_t *output_damage) +{ + struct g2d_output_state *go = get_output_state(output); + + go->current_buffer = (go->current_buffer + 1) % BUFFER_DAMAGE_COUNT; + + pixman_region32_copy(&go->buffer_damage[go->current_buffer], output_damage); +} + +#if G2D_VERSION_MAJOR >= 2 && defined(BUILD_DRM_COMPOSITOR) +static void +g2d_update_buffer_release_fences(struct weston_compositor *compositor, + int fence_fd) +{ + struct weston_view *view; + + wl_list_for_each_reverse(view, &compositor->view_list, link) { + struct g2d_surface_state *gs; + struct weston_buffer_release *buffer_release; + + if (view->plane != &compositor->primary_plane) + continue; + + gs = get_surface_state(view->surface); + buffer_release = gs->buffer_release_ref.buffer_release; + + if(!buffer_release) { + continue; + } + + /* If we have a buffer_release then it means we support fences, + * and we should be able to create the release fence. If we + * can't, something has gone horribly wrong, so disconnect the + * client. + */ + if (fence_fd == -1) { + fd_clear(&buffer_release->fence_fd); + continue; + } + + fd_update(&buffer_release->fence_fd, dup(fence_fd)); + } +} +#endif + +static void +g2d_renderer_repaint_output(struct weston_output *output, + pixman_region32_t *output_damage, + struct weston_renderbuffer *renderbuffer) +{ + struct weston_compositor *compositor = output->compositor; + struct g2d_renderer *gr = get_renderer(compositor); + pixman_region32_t buffer_damage, total_damage; +#if G2D_VERSION_MAJOR >= 2 && defined(BUILD_DRM_COMPOSITOR) + struct g2d_output_state *go = get_output_state(output); +#endif + int fence_fd = -1; + + pixman_region32_init(&total_damage); + pixman_region32_init(&buffer_damage); + + output_get_damage(output, &buffer_damage); + output_rotate_damage(output, output_damage); + pixman_region32_union(&total_damage, &buffer_damage, output_damage); + + repaint_views(output, &total_damage); + + pixman_region32_fini(&total_damage); + pixman_region32_fini(&buffer_damage); + +#if G2D_VERSION_MAJOR >= 2 && defined(BUILD_DRM_COMPOSITOR) + fence_fd = g2d_create_fence_fd(gr->handle); + g2d_update_buffer_release_fences(compositor, fence_fd); + + fd_clear(&go->drm_hw_buffer->reserved[0]); + go->drm_hw_buffer->reserved[0] = fence_fd; +#endif + + if(fence_fd == -1) + g2d_finish(gr->handle); + + wl_signal_emit(&output->frame_signal, output_damage); +} + +static bool +g2d_renderer_fill_buffer_info(struct weston_compositor *ec, + struct weston_buffer *buffer) +{ + struct g2d_renderer *gr = get_renderer(ec); + EGLint format; + uint32_t fourcc; + bool ret = true; + + buffer->legacy_buffer = (struct wl_buffer *)buffer->resource; + 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"); + 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: + fourcc = DRM_FORMAT_XRGB8888; + break; + case EGL_TEXTURE_RGBA: + fourcc = DRM_FORMAT_ARGB8888; + break; + case EGL_TEXTURE_EXTERNAL_WL: + fourcc = DRM_FORMAT_ARGB8888; + break; + case EGL_TEXTURE_Y_XUXV_WL: + fourcc = DRM_FORMAT_YUYV; + break; + case EGL_TEXTURE_Y_UV_WL: + fourcc = DRM_FORMAT_NV12; + break; + case EGL_TEXTURE_Y_U_V_WL: + fourcc = DRM_FORMAT_YUV420; + break; + default: + assert(0 && "not reached"); + } + + buffer->pixel_format = pixel_format_get_info(fourcc); + assert(buffer->pixel_format); + buffer->format_modifier = DRM_FORMAT_MOD_LINEAR; + + return true; + +err_free: + return false; + +} + +static void +g2d_renderer_attach_egl(struct weston_surface *es, struct weston_buffer *buffer) +{ + struct wl_viv_buffer *vivBuffer = wl_resource_get_user_data(buffer->resource); + buffer->width = vivBuffer->width; + buffer->height = vivBuffer->height; +} + +static void +g2d_renderer_copy_shm_buffer(struct g2d_surface_state *gs, struct weston_buffer *buffer) +{ + int alignedWidth = ALIGN_TO_16(buffer->width); + int height = 0; + uint8_t *src = wl_shm_buffer_get_data(buffer->shm_buffer); + uint8_t *dst = gs->shm_buf->buf_vaddr; + int bpp = gs->bpp; + int plane_size[3] = {0,}; + int src_plane_offset[3] = {0,}; + int dst_plane_offset[3] = {0,}; + int uv_src_stride = 0; + int uv_dst_stride = 0; + int n_planes = 0; + int i, j; + + switch (wl_shm_buffer_get_format(buffer->shm_buffer)) { + case WL_SHM_FORMAT_XRGB8888: + case WL_SHM_FORMAT_ARGB8888: + case WL_SHM_FORMAT_RGB565: + n_planes = 1; + height = buffer->height; + plane_size[0] = wl_shm_buffer_get_stride(buffer->shm_buffer)*buffer->height; + break; + case WL_SHM_FORMAT_YUYV: + n_planes = 1; + height = ALIGN_TO_16(buffer->height); + plane_size[0] = wl_shm_buffer_get_stride(buffer->shm_buffer)*buffer->height; + break; + case WL_SHM_FORMAT_NV12: + n_planes = 2; + height = ALIGN_TO_16(buffer->height); + plane_size[0] = wl_shm_buffer_get_stride(buffer->shm_buffer)*buffer->height; + plane_size[1] = wl_shm_buffer_get_stride(buffer->shm_buffer)*buffer->height / 2; + src_plane_offset[1] = plane_size[0]; + dst_plane_offset[1] = alignedWidth * height; + uv_src_stride = wl_shm_buffer_get_stride(buffer->shm_buffer); + uv_dst_stride = alignedWidth; + break; + case WL_SHM_FORMAT_YUV420: + n_planes = 3; + height = ALIGN_TO_16(buffer->height); + plane_size[0] = wl_shm_buffer_get_stride(buffer->shm_buffer)*buffer->height; + plane_size[1] = wl_shm_buffer_get_stride(buffer->shm_buffer)*buffer->height / 4; + plane_size[2] = plane_size[1]; + src_plane_offset[1] = plane_size[0]; + src_plane_offset[2] = plane_size[0] + plane_size[1]; + dst_plane_offset[1] = alignedWidth * height; + dst_plane_offset[2] = dst_plane_offset[1] + alignedWidth * height / 4; + uv_src_stride = wl_shm_buffer_get_stride(buffer->shm_buffer) / 2; + uv_dst_stride = alignedWidth / 2; + break; + default: + weston_log("warning: copy shm buffer meet unknown format: %08x\n", + wl_shm_buffer_get_format(buffer->shm_buffer)); + return; + } + + wl_shm_buffer_begin_access(buffer->shm_buffer); + if(alignedWidth == buffer->width && height == buffer->height) + { + for (i = 0; i < n_planes; i++) + memcpy (dst + dst_plane_offset[i], src + src_plane_offset[i], plane_size[i]); + } + else + { + int src_stride = wl_shm_buffer_get_stride(buffer->shm_buffer); + int dst_stride = alignedWidth * bpp; + /* copy the 1st plane */ + for (i = 0; i < buffer->height; i++) + { + memcpy(dst + dst_plane_offset[0] + dst_stride * i, src + src_plane_offset[0] + src_stride * i, src_stride); + } + /* copy the rest plane */ + for (i = 1; i < n_planes; i++) + { + for (j = 0; j < buffer->height / 2; j++) + { + memcpy(dst + dst_plane_offset[i] + uv_dst_stride * j, src + src_plane_offset[i] + uv_src_stride * j, uv_src_stride); + } + } + } + wl_shm_buffer_end_access(buffer->shm_buffer); +} + +static void +g2d_renderer_flush_damage(struct weston_surface *surface, + struct weston_buffer *buffer) +{ + struct g2d_surface_state *gs = get_surface_state(surface); + struct weston_view *view; + int texture_used; + pixman_region32_union(&gs->texture_damage, + &gs->texture_damage, &surface->damage); + + if (!buffer) + return; + + texture_used = 0; + wl_list_for_each(view, &surface->views, surface_link) { + if (view->plane == &surface->compositor->primary_plane) { + texture_used = 1; + break; + } + } + if (!texture_used) + return; + + if (!pixman_region32_not_empty(&gs->texture_damage)) + goto done; + + if(wl_shm_buffer_get(buffer->resource)) + { + g2d_renderer_copy_shm_buffer(gs, buffer); + } + +done: + pixman_region32_fini(&gs->texture_damage); + pixman_region32_init(&gs->texture_damage); + + weston_buffer_reference(&gs->buffer_ref, NULL, BUFFER_WILL_NOT_BE_ACCESSED); + weston_buffer_release_reference(&gs->buffer_release_ref, NULL); +} + +static void +g2d_renderer_attach_solid(struct weston_surface *surface, + struct weston_buffer *buffer) +{ + struct g2d_surface_state *gs = get_surface_state(surface); + + gs->color[0] = buffer->solid.r; + gs->color[1] = buffer->solid.g; + gs->color[2] = buffer->solid.b; + gs->color[3] = buffer->solid.a; +} + +static void +g2d_renderer_attach_shm(struct weston_surface *es, struct weston_buffer *buffer) +{ + struct g2d_surface_state *gs = get_surface_state(es); + struct wl_shm_buffer *shm_buffer = buffer->shm_buffer; + int buffer_length = 0; + int alloc_new_buff = 1; + int alignedWidth = 0; + int height = 0; + enum g2d_format g2dFormat = 0; + + buffer->width = wl_shm_buffer_get_width(shm_buffer); + buffer->height = wl_shm_buffer_get_height(shm_buffer); + alignedWidth = ALIGN_TO_16(buffer->width); + + switch (wl_shm_buffer_get_format(shm_buffer)) { + case WL_SHM_FORMAT_XRGB8888: + g2dFormat = G2D_BGRX8888; + gs->bpp = 4; + break; + case WL_SHM_FORMAT_ARGB8888: + g2dFormat = G2D_BGRA8888; + gs->bpp = 4; + break; + case WL_SHM_FORMAT_RGB565: + g2dFormat = G2D_RGB565; + gs->bpp = 2; + break; + case WL_SHM_FORMAT_YUYV: + g2dFormat = G2D_YUYV; + height = ALIGN_TO_16(buffer->height); + buffer_length = alignedWidth * height * 2; + gs->bpp = 2; + break; + case WL_SHM_FORMAT_YUV420: + g2dFormat = G2D_I420; + height = ALIGN_TO_16(buffer->height); + buffer_length = alignedWidth * height * 3/2; + gs->bpp = 1; + break; + case WL_SHM_FORMAT_NV12: + g2dFormat = G2D_NV12; + height = ALIGN_TO_16(buffer->height); + buffer_length = alignedWidth * height * 3/2; + gs->bpp = 1; + break; + default: + weston_log("warning: unknown shm buffer format: %08x\n", + wl_shm_buffer_get_format(shm_buffer)); + return; + } + + if (height == 0) + height = buffer->height; + + if (buffer_length == 0) + buffer_length = alignedWidth * buffer->height * gs->bpp; + + /* Only allocate a new g2d buff if it is larger than existing one.*/ + gs->shm_buf_length = buffer_length; + if(gs->shm_buf && gs->shm_buf->buf_size > buffer_length) + { + alloc_new_buff = 0; + } + + if(alloc_new_buff) + { + if(gs->shm_buf) + g2d_free(gs->shm_buf); + gs->shm_buf = g2d_alloc(buffer_length, 0); + gs->g2d_surface.base.planes[0] = gs->shm_buf->buf_paddr; + gs->g2d_surface.base.planes[1] = gs->g2d_surface.base.planes[0] + alignedWidth * height; + gs->g2d_surface.base.planes[2] = gs->g2d_surface.base.planes[1] + alignedWidth * height / 4; + } + + gs->g2d_surface.base.left = 0; + gs->g2d_surface.base.top = 0; + gs->g2d_surface.base.right = buffer->width; + gs->g2d_surface.base.bottom = buffer->height; + gs->g2d_surface.base.stride = alignedWidth; + gs->g2d_surface.base.width = buffer->width; + gs->g2d_surface.base.height = height; + gs->g2d_surface.base.rot = G2D_ROTATION_0; + gs->g2d_surface.base.clrcolor = 0xFF400000; + gs->g2d_surface.tiling = G2D_LINEAR; + gs->g2d_surface.base.format = g2dFormat; +} + + +static void +g2d_renderer_get_g2dformat_from_dmabuf(uint32_t dmaformat, + enum g2d_format *g2dFormat, int *bpp) +{ + switch (dmaformat) { + case DRM_FORMAT_ARGB8888: + *g2dFormat = G2D_BGRA8888; + *bpp = 4; + break; + case DRM_FORMAT_ABGR8888: + *g2dFormat = G2D_RGBA8888; + *bpp = 4; + break; + case DRM_FORMAT_XRGB8888: + *g2dFormat = G2D_BGRX8888; + *bpp = 4; + break; + case DRM_FORMAT_RGB565: + *g2dFormat = G2D_RGB565; + *bpp = 2; + break; + case DRM_FORMAT_YUYV: + *g2dFormat = G2D_YUYV; + *bpp = 2; + break; + case DRM_FORMAT_NV12: + *g2dFormat = G2D_NV12; + *bpp = 1; + break; + case DRM_FORMAT_YUV420: + *g2dFormat = G2D_I420; + *bpp = 1; + break; + default: + *g2dFormat = -1; + weston_log("warning: unknown dmabuf buffer format: %08x\n", dmaformat); + break; + } +} + +static void +g2d_renderer_attach_dmabuf(struct weston_surface *es, struct weston_buffer *buffer) +{ + struct g2d_surface_state *gs = get_surface_state(es); + struct linux_dmabuf_buffer *dmabuf = buffer->dmabuf; + int alignedWidth = 0, alignedHeight = 0; + enum g2d_format g2dFormat; + unsigned int *paddr; + int i = 0; + int bpp = 1; + + buffer->width = dmabuf->attributes.width; + buffer->height = dmabuf->attributes.height; + if(dmabuf->attributes.modifier[0] == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED || + dmabuf->attributes.modifier[0] == DRM_FORMAT_MOD_VIVANTE_SPLIT_SUPER_TILED) { + alignedWidth = ALIGN_TO_64(buffer->width); + alignedHeight = ALIGN_TO_64(buffer->height); + } else { + alignedWidth = ALIGN_TO_16(buffer->width); + alignedHeight = ALIGN_TO_16(buffer->height); + } + g2d_renderer_get_g2dformat_from_dmabuf(dmabuf->attributes.format, &g2dFormat, &bpp); + + if (g2dFormat < 0) + return; + + paddr = (unsigned int *)linux_dmabuf_buffer_get_user_data(dmabuf); + for (i = 0; i < dmabuf->attributes.n_planes; i++) { + gs->g2d_surface.base.planes[i] = paddr[i] + dmabuf->attributes.offset[i]; + } + + gs->g2d_surface.base.left = 0; + gs->g2d_surface.base.top = 0; + gs->g2d_surface.base.right = buffer->width; + gs->g2d_surface.base.bottom = buffer->height; + gs->g2d_surface.base.width = alignedWidth; + gs->g2d_surface.base.height = alignedHeight; + gs->g2d_surface.base.rot = G2D_ROTATION_0; + if (dmabuf->attributes.modifier[0] == DRM_FORMAT_MOD_AMPHION_TILED) { + gs->g2d_surface.base.stride = dmabuf->attributes.stride[0]; + gs->g2d_surface.tiling = G2D_AMPHION_TILED; + } else if(dmabuf->attributes.modifier[0] == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED || + dmabuf->attributes.modifier[0] == DRM_FORMAT_MOD_VIVANTE_SPLIT_SUPER_TILED){ + gs->g2d_surface.base.stride = alignedWidth; + gs->g2d_surface.tiling = G2D_SUPERTILED; + } else { + gs->g2d_surface.base.stride = dmabuf->attributes.stride[0] / bpp; + gs->g2d_surface.tiling = G2D_LINEAR; + } + gs->g2d_surface.base.format = g2dFormat; +} + +static void +g2d_renderer_query_dmabuf_formats(struct weston_compositor *wc, + int **formats, int *num_formats) +{ + int num; + static const int dma_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB565, + DRM_FORMAT_YUYV, + DRM_FORMAT_NV12, + DRM_FORMAT_YUV420, + }; + + num = ARRAY_LENGTH(dma_formats); + *formats = calloc(num, sizeof(int)); + memcpy(*formats, dma_formats, num * sizeof(int)); + + *num_formats = num; +} + +static void +g2d_renderer_query_dmabuf_modifiers(struct weston_compositor *wc, int format, + uint64_t **modifiers, + int *num_modifiers) +{ + struct g2d_renderer *gr = get_renderer(wc); + int num; + + /* + * Set modifiers with DRM_FORMAT_MOD_LINEAR as default, + * if not support eglQueryDmaBufModifiersEXT. + */ + if (!gr->has_dmabuf_import_modifiers) { + *num_modifiers = 1; + *modifiers = calloc(*num_modifiers, sizeof(uint64_t)); + (*modifiers)[0] = DRM_FORMAT_MOD_LINEAR; + return; + } + + if (!gr->query_dmabuf_modifiers(gr->egl_display, format, 0, NULL, + NULL, &num) || + num == 0) { + *num_modifiers = 0; + return; + } + + *modifiers = calloc(num, sizeof(uint64_t)); + if (*modifiers == NULL) { + *num_modifiers = 0; + return; + } + + if (!gr->query_dmabuf_modifiers(gr->egl_display, format, + num, *modifiers, NULL, &num)) { + *num_modifiers = 0; + free(*modifiers); + return; + } + + *num_modifiers = num; +} + +static void +free_paddr_buf (struct linux_dmabuf_buffer *buffer) +{ + unsigned int * paddr = (unsigned int *)buffer->user_data; + if (paddr) + free (paddr); +} + +static bool +g2d_renderer_import_dmabuf(struct weston_compositor *wc, + struct linux_dmabuf_buffer *dmabuf) +{ + struct g2d_buf *g2dBuf = NULL; + enum g2d_format g2dFormat; + unsigned int *paddr = NULL; + int i = 0; + int bpp = 1; + + if (!dmabuf) + return false; + + g2d_renderer_get_g2dformat_from_dmabuf(dmabuf->attributes.format, &g2dFormat, &bpp); + if (g2dFormat < 0) + return false; + + paddr = malloc (sizeof (unsigned int) * dmabuf->attributes.n_planes); + if (!paddr) + return false; + + for (i = 0; i < dmabuf->attributes.n_planes; i++) { + if (g2dBuf) + g2d_free(g2dBuf); + g2dBuf = g2d_buf_from_fd(dmabuf->attributes.fd[i]); + if(!g2dBuf) + return false; + paddr[i] = g2dBuf->buf_paddr; + } + + if(!g2dBuf) + return false; + else + g2d_free(g2dBuf); + + linux_dmabuf_buffer_set_user_data(dmabuf, (void *)paddr, free_paddr_buf); + + return true; +} + +static const struct weston_drm_format_array * +g2d_renderer_get_supported_formats(struct weston_compositor *ec) +{ + struct g2d_renderer *gr = get_renderer(ec); + + return &gr->supported_formats; +} + +static int +populate_supported_formats(struct weston_compositor *ec, + struct weston_drm_format_array *supported_formats) +{ + struct weston_drm_format *fmt; + int *formats = NULL; + uint64_t *modifiers = NULL; + int num_formats, num_modifiers; + int i, j; + int ret = 0; + + /* Use EGL_EXT_image_dma_buf_import_modifiers to query the + * list of formats/modifiers of the renderer. */ + g2d_renderer_query_dmabuf_formats(ec, &formats, &num_formats); + if (num_formats == 0) + return 0; + + for (i = 0; i < num_formats; i++) { + fmt = weston_drm_format_array_add_format(supported_formats, + formats[i]); + if (!fmt) { + ret = -1; + goto out; + } + + /* Always add DRM_FORMAT_MOD_INVALID, as EGL implementations + * support implicit modifiers. */ + ret = weston_drm_format_add_modifier(fmt, DRM_FORMAT_MOD_INVALID); + if (ret < 0) + goto out; + + g2d_renderer_query_dmabuf_modifiers(ec, formats[i], + &modifiers, &num_modifiers); + if (num_modifiers == 0) + continue; + + for (j = 0; j < num_modifiers; j++) { + /* Skip MOD_INVALID, as it has already been added. */ + if (modifiers[j] == DRM_FORMAT_MOD_INVALID) + continue; + ret = weston_drm_format_add_modifier(fmt, modifiers[j]); + if (ret < 0) { + free(modifiers); + goto out; + } + } + free(modifiers); + } + +out: + free(formats); + return ret; +} + +static void +g2d_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) +{ + struct g2d_surface_state *gs = get_surface_state(es); + + if (!buffer) { + gs->attached = 0; + return; + } + + switch (buffer->type) { + case WESTON_BUFFER_SHM: + g2d_renderer_attach_shm(es, buffer); + break; + case WESTON_BUFFER_DMABUF: + g2d_renderer_attach_dmabuf(es, buffer); + break; + case WESTON_BUFFER_RENDERER_OPAQUE: + g2d_renderer_attach_egl(es, buffer); + break; + case WESTON_BUFFER_SOLID: + g2d_renderer_attach_solid(es, buffer); + break; + default: + break; + } + gs->attached = 1; + 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); +} + +static void +surface_state_destroy(struct g2d_surface_state *gs, struct g2d_renderer *gr) +{ + wl_list_remove(&gs->surface_destroy_listener.link); + wl_list_remove(&gs->renderer_destroy_listener.link); + if(gs->surface) + gs->surface->renderer_state = NULL; + + if(gs->shm_buf) + { + g2d_free(gs->shm_buf); + gs->shm_buf = NULL; + } + if(gs->dma_buf) + { + g2d_free(gs->dma_buf); + gs->dma_buf = NULL; + } + + weston_buffer_reference(&gs->buffer_ref, NULL, BUFFER_WILL_NOT_BE_ACCESSED); + weston_buffer_release_reference(&gs->buffer_release_ref, NULL); + free(gs); +} + +static void +surface_state_handle_surface_destroy(struct wl_listener *listener, void *data) +{ + struct g2d_surface_state *gs; + struct g2d_renderer *gr; + + gs = container_of(listener, struct g2d_surface_state, + surface_destroy_listener); + + gr = get_renderer(gs->surface->compositor); + surface_state_destroy(gs, gr); +} + +static void +surface_state_handle_renderer_destroy(struct wl_listener *listener, void *data) +{ + struct g2d_surface_state *gs; + struct g2d_renderer *gr; + + gr = data; + + gs = container_of(listener, struct g2d_surface_state, + renderer_destroy_listener); + + surface_state_destroy(gs, gr); +} + + +static int +g2d_renderer_create_surface(struct weston_surface *surface) +{ + struct g2d_surface_state *gs; + struct g2d_renderer *gr = get_renderer(surface->compositor); + + gs = zalloc(sizeof *gs); + if (gs == NULL) + return -1; + + /* A buffer is never attached to solid color surfaces, yet + * they still go through texcoord computations. Do not divide + * by zero there. + */ + gs->pitch = 1; + + gs->surface = surface; + + pixman_region32_init(&gs->texture_damage); + surface->renderer_state = gs; + + gs->surface_destroy_listener.notify = + surface_state_handle_surface_destroy; + wl_signal_add(&surface->destroy_signal, + &gs->surface_destroy_listener); + + gs->renderer_destroy_listener.notify = + surface_state_handle_renderer_destroy; + wl_signal_add(&gr->destroy_signal, + &gs->renderer_destroy_listener); + + if (surface->buffer_ref.buffer) { + g2d_renderer_attach(surface, surface->buffer_ref.buffer); + if (surface->buffer_ref.buffer->type == WESTON_BUFFER_SHM) { + g2d_renderer_flush_damage(surface, + surface->buffer_ref.buffer); + } + } + + return 0; +} + +/* create use-g2d-renderer */ +static void +create_g2d_file() +{ + char *dir, *path; + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + + dir = getenv("XDG_RUNTIME_DIR"); + path = malloc(strlen(dir) + 40); + strcpy(path, dir); + strcat(path, "/use-g2d-renderer"); + close(open(path, O_CREAT | O_RDWR, mode)); + free(path); +} + +/* remove use-g2d-renderer */ +static void +remove_g2d_file() +{ + char *dir, *path; + + dir = getenv("XDG_RUNTIME_DIR"); + path = malloc(strlen(dir) + 40); + strcpy(path, dir); + strcat(path, "/use-g2d-renderer"); + remove(path); + free(path); +} + +static void +g2d_renderer_output_destroy(struct weston_output *output) +{ + struct g2d_output_state *go = get_output_state(output); + int i; + + for (i = 0; i < BUFFER_DAMAGE_COUNT; i++) + { + pixman_region32_fini(&go->buffer_damage[i]); + } + +#if G2D_VERSION_MAJOR >= 2 && defined(BUILD_DRM_COMPOSITOR) + fd_clear(&go->drm_hw_buffer->reserved[0]); +#endif + + free(go); +} + +static void +g2d_renderer_destroy(struct weston_compositor *ec) +{ + struct g2d_renderer *gr = get_renderer(ec); + + wl_signal_emit(&gr->destroy_signal, gr); + g2d_close(gr->handle); +#ifdef ENABLE_EGL + if(gr->bind_display) + gr->bind_display(gr->egl_display, gr->wl_display); + eglTerminate(gr->egl_display); +#endif + free(ec->renderer); + ec->renderer = NULL; + + weston_drm_format_array_fini(&gr->supported_formats); + + remove_g2d_file(); +} + +static void +g2d_renderer_set_egl_device(struct g2d_renderer *gr) +{ + EGLAttrib attrib; + const char *extensions; + + if (!gr->query_display_attrib(gr->egl_display, EGL_DEVICE_EXT, &attrib)) { + weston_log("failed to get EGL device\n"); + return; + } + + gr->egl_device = (EGLDeviceEXT) attrib; + + extensions = gr->query_device_string(gr->egl_device, EGL_EXTENSIONS); + if (!extensions) { + weston_log("failed to get EGL extensions\n"); + return; + } + + /* 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")) + gr->drm_device = gr->query_device_string(gr->egl_device, + EGL_DRM_RENDER_NODE_FILE_EXT); + + /* The extension is not supported by the Mesa version of the system or + * the query failed. Fallback to EGL_DRM_DEVICE_FILE_EXT */ + if (!gr->drm_device && weston_check_egl_extension(extensions, "EGL_EXT_device_drm")) + 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"); +} + + +static int +create_default_dmabuf_feedback(struct weston_compositor *ec, + struct g2d_renderer *gr) +{ + struct stat dev_stat; + struct weston_dmabuf_feedback_tranche *tranche; + uint32_t flags = 0; + + if (stat(gr->drm_device, &dev_stat) != 0) { + weston_log("%s: device disappeared, so we can't recover\n", __func__); + abort(); + } + + ec->default_dmabuf_feedback = + weston_dmabuf_feedback_create(dev_stat.st_rdev); + if (!ec->default_dmabuf_feedback) + return -1; + + tranche = + weston_dmabuf_feedback_tranche_create(ec->default_dmabuf_feedback, + ec->dmabuf_feedback_format_table, + dev_stat.st_rdev, flags, + RENDERER_PREF); + if (!tranche) { + weston_dmabuf_feedback_destroy(ec->default_dmabuf_feedback); + ec->default_dmabuf_feedback = NULL; + return -1; + } + + return 0; +} + +static int +g2d_renderer_create(struct weston_compositor *ec) +{ + struct g2d_renderer *gr; + + gr = calloc(1, sizeof *gr); + if (gr == NULL) + return -1; + + weston_drm_format_array_init(&gr->supported_formats); + + gr->base.read_pixels = g2d_renderer_read_pixels; + gr->base.repaint_output = g2d_renderer_repaint_output; + gr->base.flush_damage = g2d_renderer_flush_damage; + gr->base.attach = g2d_renderer_attach; + gr->base.destroy = g2d_renderer_destroy; + gr->base.import_dmabuf = g2d_renderer_import_dmabuf; + gr->base.get_supported_formats = g2d_renderer_get_supported_formats; + gr->base.fill_buffer_info = g2d_renderer_fill_buffer_info; + gr->base.type = WESTON_RENDERER_G2D; + ec->renderer = &gr->base; +#ifdef ENABLE_EGL + gr->bind_display = + (void *) eglGetProcAddress("eglBindWaylandDisplayWL"); + gr->unbind_display = + (void *) eglGetProcAddress("eglUnbindWaylandDisplayWL"); + gr->query_buffer = + (void *) eglGetProcAddress("eglQueryWaylandBufferWL"); + gr->update_buffer = + (void *) eglGetProcAddress("eglUpdateWaylandBufferWL"); + gr->query_display_attrib = + (void *) eglGetProcAddress("eglQueryDisplayAttribEXT"); + gr->query_device_string = + (void *) eglGetProcAddress("eglQueryDeviceStringEXT"); + if (!get_platform_display) + { + get_platform_display = (void *) eglGetProcAddress( + "eglGetPlatformDisplayEXT"); + } + + ec->capabilities |= WESTON_CAP_EXPLICIT_SYNC; +#endif + if(g2d_open(&gr->handle)) + { + weston_log("g2d_open fail.\n"); + return -1; + } + + ec->capabilities |= WESTON_CAP_ROTATION_ANY; + ec->capabilities |= WESTON_CAP_CAPTURE_YFLIP; + ec->capabilities |= WESTON_CAP_VIEW_CLIP_MASK; + ec->read_format = pixel_format_get_info_by_pixman(PIXMAN_a8r8g8b8); + + 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_NV12); + wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_YUYV); + + wl_signal_init(&gr->destroy_signal); + + create_g2d_file(); + + return 0; +} + +static int +g2d_drm_display_create(struct weston_compositor *ec, void *native_window) +{ + struct g2d_renderer *gr; +#ifdef ENABLE_EGL + const char *extensions; + int ret; +#endif + + if(g2d_renderer_create(ec) < 0) + { + weston_log("g2d_renderer_create faile.\n"); + return -1; + } +#ifdef ENABLE_EGL + gr = get_renderer(ec); + gr->wl_display = ec->wl_display; + if(get_platform_display) + gr->egl_display = get_platform_display(EGL_PLATFORM_GBM_KHR, + native_window, NULL); + if(gr->bind_display) + gr->bind_display(gr->egl_display, gr->wl_display); + + eglInitialize(gr->egl_display, NULL, NULL); + + extensions = + (const char *) eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (!extensions) { + weston_log("Retrieving EGL extension string failed.\n"); + return -1; + } + + if (weston_check_egl_extension(extensions, "EGL_EXT_device_query")) { + gr->query_display_attrib = + (void *) eglGetProcAddress("eglQueryDisplayAttribEXT"); + gr->query_device_string = + (void *) eglGetProcAddress("eglQueryDeviceStringEXT"); + gr->has_device_query = true; + } + + if (gr->has_device_query) + g2d_renderer_set_egl_device(gr); + + if (weston_check_egl_extension(extensions, + "EGL_EXT_image_dma_buf_import_modifiers")) { + gr->query_dmabuf_formats = + (void *) eglGetProcAddress("eglQueryDmaBufFormatsEXT"); + gr->query_dmabuf_modifiers = + (void *) eglGetProcAddress("eglQueryDmaBufModifiersEXT"); + assert(gr->query_dmabuf_formats); + assert(gr->query_dmabuf_modifiers); + gr->has_dmabuf_import_modifiers = true; + } + + ret = populate_supported_formats(ec, &gr->supported_formats); + if (ret < 0) + goto fail_terminate; + + if (gr->drm_device) { + /* We support dma-buf feedback only when the renderer + * exposes a DRM-device */ + ec->dmabuf_feedback_format_table = + weston_dmabuf_feedback_format_table_create(&gr->supported_formats); + if (!ec->dmabuf_feedback_format_table) + goto fail_terminate; + ret = create_default_dmabuf_feedback(ec, gr); + if (ret < 0) + goto fail_feedback; + } +#endif + gr->use_drm = 1; + + return 0; + +fail_terminate: + weston_drm_format_array_fini(&gr->supported_formats); + eglTerminate(gr->egl_display); +fail_feedback: + weston_dmabuf_feedback_format_table_destroy(ec->dmabuf_feedback_format_table); + ec->dmabuf_feedback_format_table = NULL; + + return -1; +} + +static void +g2d_renderer_output_set_buffer(struct weston_output *output, struct g2d_surfaceEx *buffer) +{ + struct g2d_output_state *go = get_output_state(output); + go->drm_hw_buffer = buffer; +} + +static int +g2d_renderer_get_surface_fence_fd(struct g2d_surfaceEx *buffer) +{ + return buffer->reserved[0]; +} + +static int +g2d_drm_renderer_output_create(struct weston_output *output) +{ + struct g2d_output_state *go; + int i = 0; + + go = zalloc(sizeof *go); + if (go == NULL) + return -1; + output->renderer_state = go; + + for (i = 0; i < BUFFER_DAMAGE_COUNT; i++) + pixman_region32_init(&go->buffer_damage[i]); + + return 0; + } + +static int +drm_create_g2d_image(struct g2d_surfaceEx* g2dSurface, + enum g2d_format g2dFormat, + void *vaddr, + int w, int h, int stride, + int size, + int dmafd) +{ + struct g2d_buf * buffer = NULL; + + buffer = g2d_buf_from_fd(dmafd); + if (!buffer) + return -1; + + buffer->buf_vaddr = vaddr; + buffer->buf_size = size; + g2dSurface->base.planes[0] = buffer->buf_paddr; + g2dSurface->base.left = 0; + g2dSurface->base.top = 0; + g2dSurface->base.right = w; + g2dSurface->base.bottom = h; + g2dSurface->base.stride = w; + g2dSurface->base.width = w; + g2dSurface->base.height = h; + g2dSurface->base.format = g2dFormat; + g2dSurface->base.rot = G2D_ROTATION_0; + g2dSurface->base.clrcolor = 0xFF400000; + g2dSurface->tiling = G2D_LINEAR; + g2dSurface->reserved[0] = -1; + + return 0; +} + + WL_EXPORT struct g2d_renderer_interface g2d_renderer_interface = { + .create = g2d_renderer_create, + .drm_display_create = g2d_drm_display_create, + .drm_output_create = g2d_drm_renderer_output_create, + .create_g2d_image = drm_create_g2d_image, + .output_set_buffer = g2d_renderer_output_set_buffer, + .output_destroy = g2d_renderer_output_destroy, + .get_surface_fence_fd = g2d_renderer_get_surface_fence_fd, +}; diff --git a/libweston/renderer-g2d/g2d-renderer.h b/libweston/renderer-g2d/g2d-renderer.h new file mode 100644 index 000000000..d9967f650 --- /dev/null +++ b/libweston/renderer-g2d/g2d-renderer.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2015 Freescale Semiconductor, Inc. + * Copyright © 2013 Vasily Khoruzhick + * + * 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 __g2d_renderer_h_ +#define __g2d_renderer_h_ + +#include +#include "backend.h" +#include "libweston-internal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_EGL +#include +#include +#endif + +struct g2d_renderer_interface { + int (*create)(struct weston_compositor *ec); + + int (*drm_display_create)(struct weston_compositor *ec, void *native_window); + + int (*drm_output_create)(struct weston_output *output); + + int (*create_g2d_image)(struct g2d_surfaceEx* g2dSurface, + enum g2d_format g2dFormat, + void *vaddr, + int w, int h, int stride, + int size, + int dmafd); + + void (*output_set_buffer)(struct weston_output *output, + struct g2d_surfaceEx *buffer); + + void (*output_destroy)(struct weston_output *output); + + int (*get_surface_fence_fd)(struct g2d_surfaceEx *buffer); +}; + +#endif diff --git a/libweston/renderer-g2d/meson.build b/libweston/renderer-g2d/meson.build new file mode 100644 index 000000000..52f80bf72 --- /dev/null +++ b/libweston/renderer-g2d/meson.build @@ -0,0 +1,42 @@ +if not get_option('renderer-g2d') + subdir_done() +endif + +config_h.set('ENABLE_EGL', '1') +config_h.set('ENABLE_IMXG2D', '1') + +srcs_renderer_g2d = [ + 'g2d-renderer.c', + linux_dmabuf_unstable_v1_protocol_c, + linux_dmabuf_unstable_v1_server_protocol_h, +] + +dep_g2d = cc.find_library('g2d') + +deps_renderer_g2d = [ + dep_libm, + dep_pixman, + dep_libweston_private, + dep_libdrm_headers, + dep_vertex_clipping, + dep_g2d, +] + +foreach name : [ 'egl' ] + d = dependency(name, required: false) + if not d.found() + error('g2d-renderer requires @0@ which was not found. Or, you can use \'-Drenderer-g2d=false\'.'.format(name)) + endif + deps_renderer_g2d += d +endforeach + +plugin_g2d = shared_library( + 'g2d-renderer', + srcs_renderer_g2d, + include_directories: common_inc, + dependencies: deps_renderer_g2d, + name_prefix: '', + install: true, + install_dir: dir_module_libweston +) +env_modmap += 'g2d-renderer.so=@0@;'.format(plugin_g2d.full_path()) diff --git a/libweston/renderer-gl/egl-glue.c b/libweston/renderer-gl/egl-glue.c index 013172afd..f6d7c4870 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" @@ -391,12 +392,10 @@ explain_egl_config_criteria(EGLint egl_surface_type, EGLConfig gl_renderer_get_egl_config(struct gl_renderer *gr, EGLint egl_surface_type, - const uint32_t *drm_formats, - unsigned drm_formats_count) + const struct pixel_format_info *const *formats, + unsigned formats_count) { EGLConfig egl_config; - const struct pixel_format_info *pinfo[16]; - unsigned pinfo_count; unsigned i; char *what; EGLint config_attribs[] = { @@ -408,27 +407,17 @@ gl_renderer_get_egl_config(struct gl_renderer *gr, EGL_NONE }; - assert(drm_formats_count < ARRAY_LENGTH(pinfo)); - drm_formats_count = MIN(drm_formats_count, ARRAY_LENGTH(pinfo)); - - for (pinfo_count = 0, i = 0; i < drm_formats_count; i++) { - pinfo[pinfo_count] = pixel_format_get_info(drm_formats[i]); - if (!pinfo[pinfo_count]) { - weston_log("Bad/unknown DRM format code 0x%08x.\n", - drm_formats[i]); - continue; - } - pinfo_count++; - } + for (i = 0; i < formats_count; i++) + assert(formats[i]); if (egl_config_is_compatible(gr, gr->egl_config, egl_surface_type, - pinfo, pinfo_count)) + formats, formats_count)) return gr->egl_config; - if (egl_choose_config(gr, config_attribs, pinfo, pinfo_count, + if (egl_choose_config(gr, config_attribs, formats, formats_count, &egl_config) < 0) { what = explain_egl_config_criteria(egl_surface_type, - pinfo, pinfo_count); + formats, formats_count); weston_log("No EGLConfig matches %s.\n", what); free(what); log_all_egl_configs(gr->egl_display); @@ -443,7 +432,7 @@ gl_renderer_get_egl_config(struct gl_renderer *gr, if (gr->egl_config != EGL_NO_CONFIG_KHR && egl_config != gr->egl_config) { what = explain_egl_config_criteria(egl_surface_type, - pinfo, pinfo_count); + formats, formats_count); weston_log("Found an EGLConfig matching %s but it is not usable" " because neither EGL_KHR_no_config_context nor " "EGL_MESA_configless_context are supported by EGL.\n", @@ -477,7 +466,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 +479,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 +564,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 +584,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 +607,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 +732,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 cfadb8859..1ea95b2c1 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,19 @@ #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 +#define SHADER_COLOR_MAPPING_MATRIX 2 + #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 +76,13 @@ 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 int c_color_post_curve = DEF_COLOR_POST_CURVE; 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 || + c_color_post_curve != SHADER_COLOR_CURVE_IDENTITY; vec4 yuva2rgba(vec4 yuva) @@ -105,13 +119,21 @@ uniform samplerExternalOES tex; uniform sampler2D tex; #endif -varying vec2 v_texcoord; +varying HIGHPRECISION 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; +uniform HIGHPRECISION sampler2D color_post_curve_lut_2d; +uniform HIGHPRECISION vec2 color_post_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 +uniform HIGHPRECISION mat3 color_mapping_matrix; vec4 sample_input_texture() @@ -171,6 +193,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 +230,71 @@ 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 if (c_color_mapping == SHADER_COLOR_MAPPING_MATRIX) + return color_mapping_matrix * color.rgb; + else /* Never reached, bad c_color_mapping. */ + return vec3(1.0, 0.3, 1.0); +} + +float +sample_color_post_curve_lut_2d(float x, compile_const int row) +{ + float tx = lut_texcoord(x, color_post_curve_lut_scale_offset); + + return texture2D(color_post_curve_lut_2d, + vec2(tx, (float(row) + 0.5) / 4.0)).x; +} + +vec3 +color_post_curve(vec3 color) +{ + vec3 ret; + + if (c_color_post_curve == SHADER_COLOR_CURVE_IDENTITY) { + return color; + } else if (c_color_post_curve == SHADER_COLOR_CURVE_LUT_3x1D) { + ret.r = sample_color_post_curve_lut_2d(color.r, 0); + ret.g = sample_color_post_curve_lut_2d(color.g, 1); + ret.b = sample_color_post_curve_lut_2d(color.b, 2); + return ret; + } else { + /* Never reached, bad c_color_post_curve. */ + 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); + color.rgb = color_post_curve(color.rgb); return color; } @@ -218,35 +307,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; - } + if (c_need_color_pipeline) + color = color_pipeline(color); /* Produces straight alpha */ - color = color_pipeline(color); - - /* View alpha (opacity) */ - color.a *= 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 72101b472..888df8470 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,13 @@ 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, + SHADER_COLOR_MAPPING_MATRIX, +}; + /** GL shader requirements key * * This structure is used as a binary blob key for building and searching @@ -70,13 +78,15 @@ 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:2; /* enum gl_shader_color_mapping */ + unsigned color_post_curve:1; /* enum gl_shader_color_curve */ /* * 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_:22; }; static_assert(sizeof(struct gl_shader_requirements) == 4 /* total bitfield size in bytes */, @@ -96,11 +106,21 @@ 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; + GLfloat matrix[9]; + } color_mapping; + GLuint color_post_curve_lut_tex; + GLfloat color_post_curve_lut_scale_offset[2]; }; 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 +176,8 @@ struct gl_renderer { bool has_texture_type_2_10_10_10_rev; bool has_gl_texture_rg; + bool has_texture_norm16; + bool has_pack_reverse; struct gl_shader *current_shader; struct gl_shader *fallback_shader; @@ -178,6 +200,16 @@ struct gl_renderer { bool has_wait_sync; PFNEGLWAITSYNCKHRPROC wait_sync; + bool has_disjoint_timer_query; + PFNGLGENQUERIESEXTPROC gen_queries; + PFNGLDELETEQUERIESEXTPROC delete_queries; + PFNGLBEGINQUERYEXTPROC begin_query; + PFNGLENDQUERYEXTPROC end_query; +#if !defined(NDEBUG) + PFNGLGETQUERYOBJECTIVEXTPROC get_query_object_iv; +#endif + PFNGLGETQUERYOBJECTUI64VEXTPROC get_query_object_ui64v; + bool gl_supports_color_transforms; /** Shader program cache in most recently used order @@ -198,7 +230,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); @@ -206,8 +239,8 @@ log_egl_config_info(EGLDisplay egldpy, EGLConfig eglconfig); EGLConfig gl_renderer_get_egl_config(struct gl_renderer *gr, EGLint egl_surface_type, - const uint32_t *drm_formats, - unsigned drm_formats_count); + const struct pixel_format_info *const *formats, + unsigned formats_count); int gl_renderer_setup_egl_display(struct gl_renderer *gr, void *native_display); diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index a5f5eae44..2ad8fa7c3 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -52,14 +52,17 @@ #include "linux-dmabuf.h" #include "linux-dmabuf-unstable-v1-server-protocol.h" #include "linux-explicit-synchronization.h" +#include "output-capture.h" #include "pixel-formats.h" #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" +#include "shared/xalloc.h" #define BUFFER_DAMAGE_COUNT 2 @@ -88,6 +91,9 @@ struct gl_fbo_texture { }; struct gl_output_state { + struct weston_size fb_size; /**< in pixels, including borders */ + struct weston_geometry area; /**< composited area in pixels inside fb */ + EGLSurface egl_surface; pixman_region32_t buffer_damage[BUFFER_DAMAGE_COUNT]; int buffer_damage_index; @@ -98,45 +104,18 @@ struct gl_output_state { struct weston_matrix output_matrix; - EGLSyncKHR begin_render_sync, end_render_sync; + EGLSyncKHR render_sync; + GLuint render_query; /* struct timeline_render_point::link */ struct wl_list timeline_render_point_list; + const struct pixel_format_info *shadow_format; 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 +125,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; @@ -209,16 +189,11 @@ struct gl_surface_state { struct wl_listener renderer_destroy_listener; }; -enum timeline_render_point_type { - TIMELINE_RENDER_POINT_TYPE_BEGIN, - TIMELINE_RENDER_POINT_TYPE_END -}; - struct timeline_render_point { struct wl_list link; /* gl_output_state::timeline_render_point_list */ - enum timeline_render_point_type type; int fd; + GLuint query; struct weston_output *output; struct wl_event_source *event_source; }; @@ -275,6 +250,86 @@ 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_begin_render_query(struct gl_renderer *gr, GLuint query) +{ + if (weston_log_scope_is_enabled(gr->compositor->timeline) && + gr->has_native_fence_sync && + gr->has_disjoint_timer_query) + gr->begin_query(GL_TIME_ELAPSED_EXT, query); +} + +static void +timeline_end_render_query(struct gl_renderer *gr) +{ + if (weston_log_scope_is_enabled(gr->compositor->timeline) && + gr->has_native_fence_sync && + gr->has_disjoint_timer_query) + gr->end_query(GL_TIME_ELAPSED_EXT); +} + static void timeline_render_point_destroy(struct timeline_render_point *trp) { @@ -288,17 +343,33 @@ static int timeline_render_point_handler(int fd, uint32_t mask, void *data) { struct timeline_render_point *trp = data; - const char *tp_name = trp->type == TIMELINE_RENDER_POINT_TYPE_BEGIN ? - "renderer_gpu_begin" : "renderer_gpu_end"; + struct timespec end; + + if ((mask & WL_EVENT_READABLE) && + (weston_linux_sync_file_read_timestamp(trp->fd, &end) == 0)) { + struct gl_renderer *gr = get_renderer(trp->output->compositor); + struct timespec begin; + GLuint64 elapsed; +#if !defined(NDEBUG) + GLint result_available; + + /* The elapsed time result must now be available since the + * begin/end queries are meant to be queued prior to fence sync + * creation. */ + gr->get_query_object_iv(trp->query, + GL_QUERY_RESULT_AVAILABLE_EXT, + &result_available); + assert(result_available == GL_TRUE); +#endif - if (mask & WL_EVENT_READABLE) { - struct timespec tspec = { 0 }; + gr->get_query_object_ui64v(trp->query, GL_QUERY_RESULT_EXT, + &elapsed); + timespec_add_nsec(&begin, &end, -elapsed); - if (weston_linux_sync_file_read_timestamp(trp->fd, - &tspec) == 0) { - TL_POINT(trp->output->compositor, tp_name, TLP_GPU(&tspec), - TLP_OUTPUT(trp->output), TLP_END); - } + TL_POINT(trp->output->compositor, "renderer_gpu_begin", + TLP_GPU(&begin), TLP_OUTPUT(trp->output), TLP_END); + TL_POINT(trp->output->compositor, "renderer_gpu_end", + TLP_GPU(&end), TLP_OUTPUT(trp->output), TLP_END); } timeline_render_point_destroy(trp); @@ -322,7 +393,7 @@ static void timeline_submit_render_sync(struct gl_renderer *gr, struct weston_output *output, EGLSyncKHR sync, - enum timeline_render_point_type type) + GLuint query) { struct gl_output_state *go; struct wl_event_loop *loop; @@ -331,6 +402,7 @@ timeline_submit_render_sync(struct gl_renderer *gr, if (!weston_log_scope_is_enabled(gr->compositor->timeline) || !gr->has_native_fence_sync || + !gr->has_disjoint_timer_query || sync == EGL_NO_SYNC_KHR) return; @@ -347,8 +419,8 @@ timeline_submit_render_sync(struct gl_renderer *gr, return; } - trp->type = type; trp->fd = fd; + trp->query = query; trp->output = output; trp->event_source = wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, @@ -358,90 +430,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)) - /* * Compute the boundary vertices of the intersection of the global coordinate * aligned rectangle 'rect', and an arbitrary quadrilateral produced from @@ -453,17 +441,22 @@ dmabuf_image_destroy(struct dmabuf_image *image) */ static int calculate_edges(struct weston_view *ev, pixman_box32_t *rect, - pixman_box32_t *surf_rect, GLfloat *ex, GLfloat *ey) + pixman_box32_t *surf_rect, struct weston_coord *e) { struct clip_context ctx; int i, n; GLfloat min_x, max_x, min_y, max_y; - struct polygon8 surf = { - { surf_rect->x1, surf_rect->x2, surf_rect->x2, surf_rect->x1 }, - { surf_rect->y1, surf_rect->y1, surf_rect->y2, surf_rect->y2 }, - 4 + struct weston_surface *es = ev->surface; + struct weston_coord_surface tmp[4] = { + weston_coord_surface(surf_rect->x1, surf_rect->y1, es), + weston_coord_surface(surf_rect->x2, surf_rect->y1, es), + weston_coord_surface(surf_rect->x2, surf_rect->y2, es), + weston_coord_surface(surf_rect->x1, surf_rect->y2, es), }; + struct polygon8 surf; + + surf.n = 4; ctx.clip.x1 = rect->x1; ctx.clip.y1 = rect->y1; @@ -472,18 +465,17 @@ calculate_edges(struct weston_view *ev, pixman_box32_t *rect, /* transform surface to screen space: */ for (i = 0; i < surf.n; i++) - weston_view_to_global_float(ev, surf.x[i], surf.y[i], - &surf.x[i], &surf.y[i]); + surf.pos[i] = weston_coord_surface_to_global(ev, tmp[i]).c; /* find bounding box: */ - min_x = max_x = surf.x[0]; - min_y = max_y = surf.y[0]; + min_x = max_x = surf.pos[0].x; + min_y = max_y = surf.pos[0].y; for (i = 1; i < surf.n; i++) { - min_x = min(min_x, surf.x[i]); - max_x = max(max_x, surf.x[i]); - min_y = min(min_y, surf.y[i]); - max_y = max(max_y, surf.y[i]); + min_x = MIN(min_x, surf.pos[i].x); + max_x = MAX(max_x, surf.pos[i].x); + min_y = MIN(min_y, surf.pos[i].y); + max_y = MAX(max_y, surf.pos[i].y); } /* First, simple bounding box check to discard early transformed @@ -498,7 +490,7 @@ calculate_edges(struct weston_view *ev, pixman_box32_t *rect, * vertices to the clip rect bounds: */ if (!ev->transform.enabled) - return clip_simple(&ctx, &surf, ex, ey); + return clip_simple(&ctx, &surf, e); /* Transformed case: use a general polygon clipping algorithm to * clip the surface rectangle with each side of 'rect'. @@ -506,7 +498,7 @@ calculate_edges(struct weston_view *ev, pixman_box32_t *rect, * http://www.codeguru.com/cpp/misc/misc/graphics/article.php/c8965/Polygon-Clipping.htm * but without looking at any of that code. */ - n = clip_transformed(&ctx, &surf, ex, ey); + n = clip_transformed(&ctx, &surf, e); if (n < 3) return 0; @@ -563,12 +555,14 @@ compress_bands(pixman_box32_t *inrects, int nrects, pixman_box32_t **outrects) } static int -texture_region(struct weston_view *ev, +texture_region(struct weston_paint_node *pnode, pixman_region32_t *region, pixman_region32_t *surf_region) { - struct gl_surface_state *gs = get_surface_state(ev->surface); - struct weston_compositor *ec = ev->surface->compositor; + struct gl_surface_state *gs = get_surface_state(pnode->surface); + struct weston_buffer *buffer = gs->buffer_ref.buffer; + struct weston_compositor *ec = pnode->surface->compositor; + struct weston_view *ev = pnode->view; struct gl_renderer *gr = get_renderer(ec); GLfloat *v, inv_width, inv_height; unsigned int *vtxcnt, nvtx = 0; @@ -593,15 +587,14 @@ 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]; for (j = 0; j < nsurf; j++) { pixman_box32_t *surf_rect = &surf_rects[j]; - GLfloat sx, sy, bx, by; - GLfloat ex[8], ey[8]; /* edge points in screen space */ + struct weston_coord e[8]; /* edge points in screen space */ int n; /* The transformed surface, after clipping to the clip region, @@ -618,26 +611,31 @@ texture_region(struct weston_view *ev, * form the intersection of the clip rect and the transformed * surface. */ - n = calculate_edges(ev, rect, surf_rect, ex, ey); + n = calculate_edges(ev, rect, surf_rect, e); if (n < 3) continue; /* emit edge points: */ for (k = 0; k < n; k++) { - weston_view_from_global_float(ev, ex[k], ey[k], - &sx, &sy); + struct weston_coord_global pos_g; + struct weston_coord_surface pos_s; + struct weston_coord_buffer pos_b; + + pos_g.c = e[k]; + /* position: */ - *(v++) = ex[k]; - *(v++) = ey[k]; + *(v++) = pos_g.c.x; + *(v++) = pos_g.c.y; + /* texcoord: */ - weston_surface_to_buffer_float(ev->surface, - sx, sy, - &bx, &by); - *(v++) = bx * inv_width; - if (gs->y_inverted) { - *(v++) = by * inv_height; + pos_s = weston_coord_global_to_surface(ev, pos_g); + pos_b = weston_coord_surface_to_buffer(ev->surface, pos_s); + + *(v++) = pos_b.c.x * inv_width; + if (buffer->buffer_origin == ORIGIN_TOP_LEFT) { + *(v++) = pos_b.c.y * inv_height; } else { - *(v++) = (gs->height - by) * inv_height; + *(v++) = (buffer->height - pos_b.c.y) * inv_height; } } @@ -713,10 +711,142 @@ gl_fbo_texture_fini(struct gl_fbo_texture *fbotex) fbotex->tex = 0; } +static bool +gl_renderer_do_capture(struct gl_renderer *gr, struct weston_buffer *into, + const struct weston_geometry *rect) +{ + struct wl_shm_buffer *shm = into->shm_buffer; + const struct pixel_format_info *fmt = into->pixel_format; + void *shm_pixels; + void *read_target; + int32_t stride; + pixman_image_t *tmp = NULL; + + assert(fmt->gl_type != 0); + assert(fmt->gl_format != 0); + assert(into->type == WESTON_BUFFER_SHM); + assert(shm); + + stride = wl_shm_buffer_get_stride(shm); + if (stride % 4 != 0) + return false; + + glPixelStorei(GL_PACK_ALIGNMENT, 4); + + shm_pixels = wl_shm_buffer_get_data(shm); + + if (gr->has_pack_reverse) { + /* Make glReadPixels() return top row first. */ + glPixelStorei(GL_PACK_REVERSE_ROW_ORDER_ANGLE, GL_TRUE); + read_target = shm_pixels; + } else { + /* + * glReadPixels() returns bottom row first. We need to + * read into a temporary buffer and y-flip it. + */ + tmp = pixman_image_create_bits(fmt->pixman_format, + rect->width, rect->height, + NULL, 0); + if (!tmp) + return false; + + read_target = pixman_image_get_data(tmp); + } + + wl_shm_buffer_begin_access(shm); + + glReadPixels(rect->x, rect->y, rect->width, rect->height, + fmt->gl_format, fmt->gl_type, read_target); + + if (tmp) { + pixman_image_t *shm_image; + pixman_transform_t flip; + + shm_image = pixman_image_create_bits_no_clear(fmt->pixman_format, + rect->width, + rect->height, + shm_pixels, + stride); + abort_oom_if_null(shm_image); + + pixman_transform_init_scale(&flip, pixman_fixed_1, + pixman_fixed_minus_1); + pixman_transform_translate(&flip, NULL, 0, + pixman_int_to_fixed(rect->height)); + pixman_image_set_transform(tmp, &flip); + + pixman_image_composite32(PIXMAN_OP_SRC, + tmp, /* src */ + NULL, /* mask */ + shm_image, /* dest */ + 0, 0, /* src x,y */ + 0, 0, /* mask x,y */ + 0, 0, /* dest x,y */ + rect->width, rect->height); + + pixman_image_unref(shm_image); + pixman_image_unref(tmp); + } + + wl_shm_buffer_end_access(shm); + + return true; +} + +static void +gl_renderer_do_capture_tasks(struct gl_renderer *gr, + struct weston_output *output, + enum weston_output_capture_source source) +{ + struct gl_output_state *go = get_output_state(output); + const struct pixel_format_info *format; + struct weston_capture_task *ct; + struct weston_geometry rect; + + switch (source) { + case WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER: + format = output->compositor->read_format; + rect = go->area; + /* Because glReadPixels has bottom-left origin */ + rect.y = go->fb_size.height - go->area.y - go->area.height; + break; + case WESTON_OUTPUT_CAPTURE_SOURCE_FULL_FRAMEBUFFER: + format = output->compositor->read_format; + rect.x = 0; + rect.y = 0; + rect.width = go->fb_size.width; + rect.height = go->fb_size.height; + break; + default: + assert(0); + return; + } + + while ((ct = weston_output_pull_capture_task(output, source, rect.width, + rect.height, format))) { + struct weston_buffer *buffer = weston_capture_task_get_buffer(ct); + + assert(buffer->width == rect.width); + assert(buffer->height == rect.height); + assert(buffer->pixel_format->format == format->format); + + if (buffer->type != WESTON_BUFFER_SHM || + buffer->buffer_origin != ORIGIN_TOP_LEFT) { + weston_capture_task_retire_failed(ct, "GL: unsupported buffer"); + continue; + } + + if (gl_renderer_do_capture(gr, buffer, &rect)) + weston_capture_task_retire_complete(ct); + else + weston_capture_task_retire_failed(ct, "GL: capture failed"); + } +} + static void -gl_renderer_send_shader_error(struct weston_view *view) +gl_renderer_send_shader_error(struct weston_paint_node *pnode) { - struct wl_resource *resource = view->surface->resource; + struct wl_resource *resource = pnode->surface->resource; if (!resource) return; @@ -740,6 +870,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 +889,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; @@ -789,12 +920,12 @@ triangle_fan_debug(struct gl_renderer *gr, static void repaint_region(struct gl_renderer *gr, - struct weston_view *ev, - struct weston_output *output, + struct weston_paint_node *pnode, pixman_region32_t *region, pixman_region32_t *surf_region, const struct gl_shader_config *sconf) { + struct weston_output *output = pnode->output; GLfloat *v; unsigned int *vtxcnt; int i, first, nfans; @@ -807,21 +938,18 @@ repaint_region(struct gl_renderer *gr, * polygon for each pair, and store it as a triangle fan if * it has a non-zero area (at least 3 vertices, actually). */ - nfans = texture_region(ev, region, surf_region); + nfans = texture_region(pnode, region, surf_region); v = gr->vertices.data; vtxcnt = gr->vtxcnt.data; /* position: */ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof *v, &v[0]); - glEnableVertexAttribArray(0); - /* texcoord: */ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof *v, &v[2]); - glEnableVertexAttribArray(1); if (!gl_renderer_use_program(gr, sconf)) { - gl_renderer_send_shader_error(ev); + gl_renderer_send_shader_error(pnode); /* continue drawing with the fallback shader */ } @@ -832,9 +960,6 @@ repaint_region(struct gl_renderer *gr, first += vtxcnt[i]; } - glDisableVertexAttribArray(1); - glDisableVertexAttribArray(0); - gr->vertices.size = 0; gr->vtxcnt.size = 0; } @@ -888,7 +1013,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 +1057,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 +1068,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__); } @@ -961,25 +1087,27 @@ censor_override(struct gl_shader_config *sconf, */ static void maybe_censor_override(struct gl_shader_config *sconf, - struct weston_output *output, - struct weston_view *ev) + struct weston_paint_node *pnode) { - struct gl_surface_state *gs = get_surface_state(ev->surface); + struct weston_output *output = pnode->output; + struct weston_surface *surface = pnode->surface; + struct gl_surface_state *gs = get_surface_state(surface); + struct weston_buffer *buffer = gs->buffer_ref.buffer; bool recording_censor = (output->disable_planes > 0) && - (ev->surface->desired_protection > WESTON_HDCP_DISABLE); + (surface->desired_protection > WESTON_HDCP_DISABLE); bool unprotected_censor = - (ev->surface->desired_protection > output->current_protection); + (surface->desired_protection > output->current_protection); - if (gs->direct_display) { + if (buffer->direct_display) { censor_override(sconf, output); return; } /* When not in enforced mode, the client is notified of the protection */ /* change, so content censoring is not required */ - if (ev->surface->protection_mode != + if (surface->protection_mode != WESTON_SURFACE_PROTECTION_MODE_ENFORCED) return; @@ -991,18 +1119,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; } @@ -1034,12 +1163,66 @@ gl_shader_config_init_for_paint_node(struct gl_shader_config *sconf, return true; } +static void +draw_through_hole(struct weston_paint_node *pnode, + pixman_region32_t *output_region /* in global coordinates */) +{ + struct gl_renderer *gr = get_renderer(pnode->surface->compositor); + struct gl_output_state *go = get_output_state(pnode->output); + + /* repaint bounding region in global coordinates: */ + pixman_region32_t repaint; + /* through hole region in surface coordinates: */ + pixman_region32_t surface_hole; + + /* set shader conf for through hole */ + struct gl_shader_config sconf = { + .req = { + /* set to SOLID, shader will draw a solid color region */ + .variant = SHADER_VARIANT_SOLID, + .input_is_premult = true, + }, + .projection = go->output_matrix, + /* through hole need to be a colorless opaque region */ + .view_alpha = 1.0, + .unicolor = { 0.0, 0.0, 0.0, 0.0 }, + /* Not necessary, beacuse for solid surface, + * shader will use unicolor not textture */ + .input_tex = {0, 0, 0} + }; + + /* Disable color blend. This will allow through + * hole cover the color below, so that the video on underlay + * plane will be displayed */ + glDisable(GL_BLEND); + + pixman_region32_init(&repaint); + pixman_region32_intersect(&repaint, + &pnode->view->transform.boundingbox, output_region); + pixman_region32_subtract(&repaint, &repaint, &pnode->view->clip); + + if (!pixman_region32_not_empty(&repaint)) { + pixman_region32_fini(&repaint); + return; + } + + pixman_region32_init_rect(&surface_hole, 0, 0, + pnode->surface->width, pnode->surface->height); + + repaint_region(gr, pnode, &repaint, &surface_hole, &sconf); + + pixman_region32_fini(&surface_hole); + pixman_region32_fini(&repaint); +} + static void draw_paint_node(struct weston_paint_node *pnode, pixman_region32_t *damage /* in global coordinates */) { 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 +1232,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); @@ -1066,10 +1247,7 @@ draw_paint_node(struct weston_paint_node *pnode, if (ensure_surface_buffer_is_ready(gr, gs) < 0) goto out; - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - - if (pnode->view->transform.enabled || pnode->output->zoom.active || - pnode->output->current_scale != pnode->surface->buffer_viewport.buffer.scale) + if (pnode->needs_filtering) filter = GL_LINEAR; else filter = GL_NEAREST; @@ -1095,7 +1273,7 @@ draw_paint_node(struct weston_paint_node *pnode, else pixman_region32_copy(&surface_opaque, &pnode->surface->opaque); - maybe_censor_override(&sconf, pnode->output, pnode->view); + maybe_censor_override(&sconf, pnode); if (pixman_region32_not_empty(&surface_opaque)) { struct gl_shader_config alt = sconf; @@ -1114,15 +1292,13 @@ draw_paint_node(struct weston_paint_node *pnode, else glDisable(GL_BLEND); - repaint_region(gr, pnode->view, pnode->output, - &repaint, &surface_opaque, &alt); + repaint_region(gr, pnode, &repaint, &surface_opaque, &alt); gs->used_in_output_repaint = true; } if (pixman_region32_not_empty(&surface_blend)) { glEnable(GL_BLEND); - repaint_region(gr, pnode->view, pnode->output, - &repaint, &surface_blend, &sconf); + repaint_region(gr, pnode, &repaint, &surface_blend, &sconf); gs->used_in_output_repaint = true; } @@ -1139,11 +1315,21 @@ repaint_views(struct weston_output *output, pixman_region32_t *damage) struct weston_compositor *compositor = output->compositor; struct weston_paint_node *pnode; + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + wl_list_for_each_reverse(pnode, &output->paint_node_z_order_list, z_order_link) { if (pnode->view->plane == &compositor->primary_plane) draw_paint_node(pnode, damage); + else if (pnode->need_through_hole) + draw_through_hole(pnode, &output->region); } + + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(0); } static int @@ -1276,13 +1462,7 @@ draw_output_border_texture(struct gl_renderer *gr, glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, verts); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, texcoord); - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices); - - glDisableVertexAttribArray(1); - glDisableVertexAttribArray(0); } static int @@ -1296,6 +1476,48 @@ output_has_borders(struct weston_output *output) go->borders[GL_RENDERER_BORDER_LEFT].data; } +static struct weston_geometry +output_get_border_area(const struct gl_output_state *go, + enum gl_renderer_border_side side) +{ + const struct weston_size *fb = &go->fb_size; + const struct weston_geometry *area = &go->area; + + switch (side) { + case GL_RENDERER_BORDER_TOP: + return (struct weston_geometry){ + .x = 0, + .y = 0, + .width = fb->width, + .height = area->y + }; + case GL_RENDERER_BORDER_LEFT: + return (struct weston_geometry){ + .x = 0, + .y = area->y, + .width = area->x, + .height = area->height + }; + case GL_RENDERER_BORDER_RIGHT: + return (struct weston_geometry){ + .x = area->x + area->width, + .y = area->y, + .width = fb->width - area->x - area->width, + .height = area->height + }; + case GL_RENDERER_BORDER_BOTTOM: + return (struct weston_geometry){ + .x = 0, + .y = area->y + area->height, + .width = fb->width, + .height = fb->height - area->y - area->height + }; + } + + assert(0); + return (struct weston_geometry){}; +} + static void draw_output_borders(struct weston_output *output, enum gl_border_status border_status) @@ -1307,54 +1529,47 @@ 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; - int full_width, full_height; + const struct weston_size *fb = &go->fb_size; + unsigned side; 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; } - top = &go->borders[GL_RENDERER_BORDER_TOP]; - bottom = &go->borders[GL_RENDERER_BORDER_BOTTOM]; - left = &go->borders[GL_RENDERER_BORDER_LEFT]; - right = &go->borders[GL_RENDERER_BORDER_RIGHT]; - - full_width = output->current_mode->width + left->width + right->width; - full_height = output->current_mode->height + top->height + bottom->height; - glDisable(GL_BLEND); - glViewport(0, 0, full_width, full_height); + glViewport(0, 0, fb->width, fb->height); weston_matrix_init(&sconf.projection); weston_matrix_translate(&sconf.projection, - -full_width / 2.0, -full_height / 2.0, 0); + -fb->width / 2.0, -fb->height / 2.0, 0); weston_matrix_scale(&sconf.projection, - 2.0 / full_width, -2.0 / full_height, 1); + 2.0 / fb->width, -2.0 / fb->height, 1); glActiveTexture(GL_TEXTURE0); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + + for (side = 0; side < 4; side++) { + struct weston_geometry g; + + if (!(border_status & (1 << side))) + continue; + + g = output_get_border_area(go, side); + draw_output_border_texture(gr, go, &sconf, side, + g.x, g.y, g.width, g.height); + } - if (border_status & BORDER_TOP_DIRTY) - draw_output_border_texture(gr, go, &sconf, GL_RENDERER_BORDER_TOP, - 0, 0, - full_width, top->height); - if (border_status & BORDER_LEFT_DIRTY) - draw_output_border_texture(gr, go, &sconf, GL_RENDERER_BORDER_LEFT, - 0, top->height, - left->width, output->current_mode->height); - if (border_status & BORDER_RIGHT_DIRTY) - draw_output_border_texture(gr, go, &sconf, GL_RENDERER_BORDER_RIGHT, - full_width - right->width, top->height, - right->width, output->current_mode->height); - if (border_status & BORDER_BOTTOM_DIRTY) - draw_output_border_texture(gr, go, &sconf, GL_RENDERER_BORDER_BOTTOM, - 0, full_height - bottom->height, - full_width, bottom->height); + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(0); } static void @@ -1363,35 +1578,18 @@ output_get_border_damage(struct weston_output *output, pixman_region32_t *damage) { struct gl_output_state *go = get_output_state(output); - struct gl_border_image *top, *bottom, *left, *right; - int full_width, full_height; + unsigned side; - if (border_status == BORDER_STATUS_CLEAN) - return; /* Clean. Nothing to do. */ + for (side = 0; side < 4; side++) { + struct weston_geometry g; - top = &go->borders[GL_RENDERER_BORDER_TOP]; - bottom = &go->borders[GL_RENDERER_BORDER_BOTTOM]; - left = &go->borders[GL_RENDERER_BORDER_LEFT]; - right = &go->borders[GL_RENDERER_BORDER_RIGHT]; + if (!(border_status & (1 << side))) + continue; - full_width = output->current_mode->width + left->width + right->width; - full_height = output->current_mode->height + top->height + bottom->height; - if (border_status & BORDER_TOP_DIRTY) - pixman_region32_union_rect(damage, damage, - 0, 0, - full_width, top->height); - if (border_status & BORDER_LEFT_DIRTY) + g = output_get_border_area(go, side); pixman_region32_union_rect(damage, damage, - 0, top->height, - left->width, output->current_mode->height); - if (border_status & BORDER_RIGHT_DIRTY) - pixman_region32_union_rect(damage, damage, - full_width - right->width, top->height, - right->width, output->current_mode->height); - if (border_status & BORDER_BOTTOM_DIRTY) - pixman_region32_union_rect(damage, damage, - 0, full_height - bottom->height, - full_width, bottom->height); + g.x, g.y, g.width, g.height); + } } static void @@ -1479,26 +1677,21 @@ pixman_region_to_egl_y_invert(struct weston_output *output, struct gl_output_state *go = get_output_state(output); pixman_region32_t transformed; struct pixman_box32 *box; - int buffer_height; EGLint *d; int i; /* Translate from global to output co-ordinate space. */ pixman_region32_init(&transformed); - pixman_region32_copy(&transformed, global_region); - pixman_region32_translate(&transformed, -output->x, -output->y); - weston_transformed_region(output->width, output->height, - output->transform, - output->current_scale, - &transformed, &transformed); + weston_region_global_to_output(&transformed, + output, + global_region); /* If we have borders drawn around the output, shift our output damage * to account for borders being drawn around the outside, adding any * damage resulting from borders being redrawn. */ if (output_has_borders(output)) { pixman_region32_translate(&transformed, - go->borders[GL_RENDERER_BORDER_LEFT].width, - go->borders[GL_RENDERER_BORDER_TOP].height); + go->area.x, go->area.y); output_get_border_damage(output, go->border_status, &transformed); } @@ -1508,14 +1701,10 @@ pixman_region_to_egl_y_invert(struct weston_output *output, box = pixman_region32_rectangles(&transformed, nrects); *rects = malloc(*nrects * 4 * sizeof(EGLint)); - buffer_height = go->borders[GL_RENDERER_BORDER_TOP].height + - output->current_mode->height + - go->borders[GL_RENDERER_BORDER_BOTTOM].height; - d = *rects; for (i = 0; i < *nrects; ++i) { *d++ = box[i].x1; - *d++ = buffer_height - box[i].y2; + *d++ = go->fb_size.height - box[i].y2; *d++ = box[i].x2 - box[i].x1; *d++ = box[i].y2 - box[i].y1; } @@ -1548,15 +1737,17 @@ blit_shadow_to_output(struct weston_output *output, .input_tex[0] = go->shadow.tex, }; struct gl_renderer *gr = get_renderer(output->compositor); - double width = output->current_mode->width; - double height = output->current_mode->height; + double width = go->area.width; + double height = go->area.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; } @@ -1570,7 +1761,11 @@ blit_shadow_to_output(struct weston_output *output, pixman_region32_intersect(&translated_damage, output_damage, &output->region); /* Convert to output pixel coordinates in-place */ - weston_output_region_from_global(output, &translated_damage); + weston_region_global_to_output(&translated_damage, output, + &translated_damage); + + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); rects = pixman_region32_rectangles(&translated_damage, &n_rects); for (i = 0; i < n_rects; i++) { @@ -1585,16 +1780,17 @@ blit_shadow_to_output(struct weston_output *output, verts[6] = rects[i].x1 / width; verts[7] = (height - rects[i].y2) / height; + /* position: */ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, verts); - glEnableVertexAttribArray(0); - /* texcoord: */ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, verts); - glEnableVertexAttribArray(1); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(0); + glBindTexture(GL_TEXTURE_2D, 0); pixman_region32_fini(&translated_damage); } @@ -1609,7 +1805,8 @@ blit_shadow_to_output(struct weston_output *output, */ static void gl_renderer_repaint_output(struct weston_output *output, - pixman_region32_t *output_damage) + pixman_region32_t *output_damage, + struct weston_renderbuffer *renderbuffer) { struct gl_output_state *go = get_output_state(output); struct weston_compositor *compositor = output->compositor; @@ -1622,9 +1819,12 @@ gl_renderer_repaint_output(struct weston_output *output, pixman_region32_t total_damage; enum gl_border_status border_status = BORDER_STATUS_CLEAN; struct weston_paint_node *pnode; + const int32_t area_inv_y = + go->fb_size.height - go->area.y - go->area.height; 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; @@ -1640,36 +1840,25 @@ gl_renderer_repaint_output(struct weston_output *output, } } - if (go->begin_render_sync != EGL_NO_SYNC_KHR) - gr->destroy_sync(gr->egl_display, go->begin_render_sync); - if (go->end_render_sync != EGL_NO_SYNC_KHR) - gr->destroy_sync(gr->egl_display, go->end_render_sync); - - go->begin_render_sync = create_render_sync(gr); + timeline_begin_render_query(gr, go->render_query); /* Calculate the global GL matrix */ go->output_matrix = output->matrix; weston_matrix_translate(&go->output_matrix, - -(output->current_mode->width / 2.0), - -(output->current_mode->height / 2.0), 0); + -(go->area.width / 2.0), + -(go->area.height / 2.0), 0); weston_matrix_scale(&go->output_matrix, - 2.0 / output->current_mode->width, - -2.0 / output->current_mode->height, 1); + 2.0 / go->area.width, + -2.0 / go->area.height, 1); /* If using shadow, redirect all drawing to it first. */ if (shadow_exists(go)) { - /* XXX: Shadow code does not support resizing. */ - assert(output->current_mode->width == go->shadow.width); - assert(output->current_mode->height == go->shadow.height); - glBindFramebuffer(GL_FRAMEBUFFER, go->shadow.fbo); - glViewport(0, 0, go->shadow.width, go->shadow.height); + glViewport(0, 0, go->area.width, go->area.height); } else { glBindFramebuffer(GL_FRAMEBUFFER, 0); - glViewport(go->borders[GL_RENDERER_BORDER_LEFT].width, - go->borders[GL_RENDERER_BORDER_BOTTOM].height, - output->current_mode->width, - output->current_mode->height); + glViewport(go->area.x, area_inv_y, + go->area.width, go->area.height); } /* In fan debug mode, redraw everything to make sure that we clear any @@ -1725,10 +1914,8 @@ gl_renderer_repaint_output(struct weston_output *output, repaint_views(output, output_damage); glBindFramebuffer(GL_FRAMEBUFFER, 0); - glViewport(go->borders[GL_RENDERER_BORDER_LEFT].width, - go->borders[GL_RENDERER_BORDER_BOTTOM].height, - output->current_mode->width, - output->current_mode->height); + glViewport(go->area.x, area_inv_y, + go->area.width, go->area.height); blit_shadow_to_output(output, &total_damage); } else { repaint_views(output, &total_damage); @@ -1739,9 +1926,17 @@ gl_renderer_repaint_output(struct weston_output *output, draw_output_borders(output, border_status); + gl_renderer_do_capture_tasks(gr, output, + WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER); + gl_renderer_do_capture_tasks(gr, output, + WESTON_OUTPUT_CAPTURE_SOURCE_FULL_FRAMEBUFFER); wl_signal_emit(&output->frame_signal, output_damage); - go->end_render_sync = create_render_sync(gr); + timeline_end_render_query(gr); + + if (go->render_sync != EGL_NO_SYNC_KHR) + gr->destroy_sync(gr->egl_display, go->render_sync); + go->render_sync = create_render_sync(gr); if (gr->swap_buffers_with_damage && !gr->fan_debug) { int n_egl_rects; @@ -1771,10 +1966,8 @@ gl_renderer_repaint_output(struct weston_output *output, /* We have to submit the render sync objects after swap buffers, since * the objects get assigned a valid sync file fd only after a gl flush. */ - timeline_submit_render_sync(gr, output, go->begin_render_sync, - TIMELINE_RENDER_POINT_TYPE_BEGIN); - timeline_submit_render_sync(gr, output, go->end_render_sync, - TIMELINE_RENDER_POINT_TYPE_END); + timeline_submit_render_sync(gr, output, go->render_sync, + go->render_query); update_buffer_release_fences(compositor, output); @@ -1783,33 +1976,27 @@ 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); + struct gl_renderer *gr = get_renderer(output->compositor); - x += go->borders[GL_RENDERER_BORDER_LEFT].width; - y += go->borders[GL_RENDERER_BORDER_BOTTOM].height; + x += go->area.x; + y += go->fb_size.height - go->area.y - go->area.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; + if (gr->has_pack_reverse) + glPixelStorei(GL_PACK_REVERSE_ROW_ORDER_ANGLE, GL_FALSE); 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 +2009,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 +2019,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 +2058,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; - 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; - } - 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); + /* well, they all are so far ... */ 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; + 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)); } - 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 */ - case EGL_TEXTURE_RGBA: - default: - num_planes = 1; - gs->shader_variant = SHADER_VARIANT_RGBA; + fourcc = DRM_FORMAT_XRGB8888; + gb->num_images = 1; + gb->shader_variant = SHADER_VARIANT_RGBA; + break; + case EGL_TEXTURE_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) + const struct dmabuf_attributes *attributes) { - struct egl_image *image; EGLint attribs[52]; int atti = 0; bool has_modifier; @@ -2327,112 +2582,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 +2609,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 +2620,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 +2645,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 +2741,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 +2749,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 +2762,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; - image = dmabuf_image_create(); - image->dmabuf = dmabuf; + gb = zalloc(sizeof(*gb)); + if (!gb) + return NULL; + + 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 +2914,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 +2935,117 @@ 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; + /** + * if backend can handle dmabuf directly, then we only need set + * size to buffer. + * */ + if (surface->compositor->backend->import_dmabuf) { + struct weston_compositor *compositor = surface->compositor; + struct weston_backend *backend = surface->compositor->backend; + if (backend->import_dmabuf(compositor, dmabuf)){ + attach_direct_display_censor_placeholder(surface, buffer); + buffer->width = dmabuf->attributes.width; + buffer->height = dmabuf->attributes.height; + return true; + } + } - 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 +3074,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 +3116,84 @@ populate_supported_formats(struct weston_compositor *ec, 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; + + 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 (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 { + 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 +3246,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 +3293,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); @@ -3127,6 +3316,8 @@ gl_renderer_surface_copy_content(struct weston_surface *surface, glDisableVertexAttribArray(1); glDisableVertexAttribArray(0); + if (gr->has_pack_reverse) + glPixelStorei(GL_PACK_REVERSE_ROW_ORDER_ANGLE, GL_FALSE); glPixelStorei(GL_PACK_ALIGNMENT, bytespp); glReadPixels(src_x, src_y, width, height, gl_format, GL_UNSIGNED_BYTE, target); @@ -3142,21 +3333,19 @@ gl_renderer_surface_copy_content(struct weston_surface *surface, 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 +3391,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 +3407,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 +3460,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 +3481,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 @@ -3317,6 +3510,42 @@ gl_renderer_output_set_border(struct weston_output *output, go->border_status |= 1 << side; } +static bool +gl_renderer_resize_output(struct weston_output *output, + const struct weston_size *fb_size, + const struct weston_geometry *area) +{ + struct gl_output_state *go = get_output_state(output); + const struct pixel_format_info *shfmt = go->shadow_format; + bool ret; + + check_compositing_area(fb_size, area); + + go->fb_size = *fb_size; + go->area = *area; + + weston_output_update_capture_info(output, + WESTON_OUTPUT_CAPTURE_SOURCE_FRAMEBUFFER, + area->width, area->height, + output->compositor->read_format); + + weston_output_update_capture_info(output, + WESTON_OUTPUT_CAPTURE_SOURCE_FULL_FRAMEBUFFER, + fb_size->width, fb_size->height, + output->compositor->read_format); + + if (!shfmt) + return true; + + if (shadow_exists(go)) + gl_fbo_texture_fini(&go->shadow); + + ret = gl_fbo_texture_init(&go->shadow, area->width, area->height, + shfmt->gl_format, GL_RGBA, shfmt->gl_type); + + return ret; +} + static int gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface); @@ -3324,14 +3553,14 @@ static EGLSurface gl_renderer_create_window_surface(struct gl_renderer *gr, EGLNativeWindowType window_for_legacy, void *window_for_platform, - const uint32_t *drm_formats, - unsigned drm_formats_count) + const struct pixel_format_info *const *formats, + unsigned formats_count) { EGLSurface egl_surface = EGL_NO_SURFACE; EGLConfig egl_config; egl_config = gl_renderer_get_egl_config(gr, EGL_WINDOW_BIT, - drm_formats, drm_formats_count); + formats, formats_count); if (egl_config == EGL_NO_CONFIG_KHR) return EGL_NO_SURFACE; @@ -3352,12 +3581,13 @@ gl_renderer_create_window_surface(struct gl_renderer *gr, static int gl_renderer_output_create(struct weston_output *output, - EGLSurface surface) + EGLSurface surface, + const struct weston_size *fb_size, + const struct weston_geometry *area) { struct gl_output_state *go; struct gl_renderer *gr = get_renderer(output->compositor); const struct weston_testsuite_quirks *quirks; - bool ret; int i; quirks = &output->compositor->test_data.test_quirks; @@ -3371,33 +3601,37 @@ gl_renderer_output_create(struct weston_output *output, for (i = 0; i < BUFFER_DAMAGE_COUNT; i++) pixman_region32_init(&go->buffer_damage[i]); + if (gr->has_disjoint_timer_query) + gr->gen_queries(1, &go->render_query); + wl_list_init(&go->timeline_render_point_list); - go->begin_render_sync = EGL_NO_SYNC_KHR; - go->end_render_sync = EGL_NO_SYNC_KHR; + go->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); - ret = gl_fbo_texture_init(&go->shadow, - output->current_mode->width, - output->current_mode->height, - GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT); - if (ret) { - weston_log("Output %s uses 16F shadow.\n", - output->name); - } else { - weston_log("Output %s failed to create 16F shadow.\n", - output->name); - free(go); - return -1; - } + go->shadow_format = + pixel_format_get_info(DRM_FORMAT_ABGR16161616F); } output->renderer_state = go; + if (!gl_renderer_resize_output(output, fb_size, area)) { + weston_log("Output %s failed to create 16F shadow.\n", + output->name); + output->renderer_state = NULL; + free(go); + return -1; + } + + if (shadow_exists(go)) { + weston_log("Output %s uses 16F shadow.\n", + output->name); + } + return 0; } @@ -3413,14 +3647,15 @@ gl_renderer_output_window_create(struct weston_output *output, egl_surface = gl_renderer_create_window_surface(gr, options->window_for_legacy, options->window_for_platform, - options->drm_formats, - options->drm_formats_count); + options->formats, + options->formats_count); if (egl_surface == EGL_NO_SURFACE) { weston_log("failed to create egl surface\n"); return -1; } - ret = gl_renderer_output_create(output, egl_surface); + ret = gl_renderer_output_create(output, egl_surface, + &options->fb_size, &options->area); if (ret < 0) weston_platform_destroy_egl_surface(gr->egl_display, egl_surface); @@ -3438,14 +3673,14 @@ gl_renderer_output_pbuffer_create(struct weston_output *output, EGLint value = 0; int ret; EGLint pbuffer_attribs[] = { - EGL_WIDTH, options->width, - EGL_HEIGHT, options->height, + EGL_WIDTH, options->fb_size.width, + EGL_HEIGHT, options->fb_size.height, EGL_NONE }; pbuffer_config = gl_renderer_get_egl_config(gr, EGL_PBUFFER_BIT, - options->drm_formats, - options->drm_formats_count); + options->formats, + options->formats_count); if (pbuffer_config == EGL_NO_CONFIG_KHR) { weston_log("failed to choose EGL config for PbufferSurface\n"); return -1; @@ -3470,7 +3705,8 @@ gl_renderer_output_pbuffer_create(struct weston_output *output, " Continuing anyway.\n", value); } - ret = gl_renderer_output_create(output, egl_surface); + ret = gl_renderer_output_create(output, egl_surface, + &options->fb_size, &options->area); if (ret < 0) { eglDestroySurface(gr->egl_display, egl_surface); } else { @@ -3504,13 +3740,14 @@ gl_renderer_output_destroy(struct weston_output *output) weston_log("warning: discarding pending timeline render" "objects at output destruction"); + if (gr->has_disjoint_timer_query) + gr->delete_queries(1, &go->render_query); + wl_list_for_each_safe(trp, tmp, &go->timeline_render_point_list, link) timeline_render_point_destroy(trp); - if (go->begin_render_sync != EGL_NO_SYNC_KHR) - gr->destroy_sync(gr->egl_display, go->begin_render_sync); - if (go->end_render_sync != EGL_NO_SYNC_KHR) - gr->destroy_sync(gr->egl_display, go->end_render_sync); + if (go->render_sync != EGL_NO_SYNC_KHR) + gr->destroy_sync(gr->egl_display, go->render_sync); free(go); } @@ -3522,10 +3759,10 @@ gl_renderer_create_fence_fd(struct weston_output *output) struct gl_renderer *gr = get_renderer(output->compositor); int fd; - if (go->end_render_sync == EGL_NO_SYNC_KHR) + if (go->render_sync == EGL_NO_SYNC_KHR) return -1; - fd = gr->dup_native_fence_fd(gr->egl_display, go->end_render_sync); + fd = gr->dup_native_fence_fd(gr->egl_display, go->render_sync); if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) return -1; @@ -3536,7 +3773,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 +3789,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 +3810,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 +3893,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; @@ -3668,20 +3907,20 @@ gl_renderer_display_create(struct weston_compositor *ec, gr->base.read_pixels = gl_renderer_read_pixels; gr->base.repaint_output = gl_renderer_repaint_output; + gr->base.resize_output = gl_renderer_resize_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; + gr->base.type = WESTON_RENDERER_GL; 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; @@ -3697,8 +3936,8 @@ gl_renderer_display_create(struct weston_compositor *ec, gr->egl_config = gl_renderer_get_egl_config(gr, egl_surface_type, - options->drm_formats, - options->drm_formats_count); + options->formats, + options->formats_count); if (gr->egl_config == EGL_NO_CONFIG_KHR) { weston_log("failed to choose EGL config\n"); goto fail_terminate; @@ -3711,7 +3950,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 +3971,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; } @@ -3766,6 +4000,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 +4027,7 @@ gl_renderer_display_create(struct weston_compositor *ec, 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 +4150,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 +4167,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 +4181,12 @@ 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 (weston_check_egl_extension(extensions, "GL_ANGLE_pack_reverse_row_order")) + gr->has_pack_reverse = 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,10 +4196,50 @@ 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; } + if (weston_check_egl_extension(extensions, "GL_EXT_disjoint_timer_query")) { + PFNGLGETQUERYIVEXTPROC get_query_iv = + (void *) eglGetProcAddress("glGetQueryivEXT"); + int elapsed_bits; + + assert(get_query_iv); + get_query_iv(GL_TIME_ELAPSED_EXT, GL_QUERY_COUNTER_BITS_EXT, + &elapsed_bits); + if (elapsed_bits != 0) { + gr->gen_queries = + (void *) eglGetProcAddress("glGenQueriesEXT"); + gr->delete_queries = + (void *) eglGetProcAddress("glDeleteQueriesEXT"); + gr->begin_query = (void *) eglGetProcAddress("glBeginQueryEXT"); + gr->end_query = (void *) eglGetProcAddress("glEndQueryEXT"); +#if !defined(NDEBUG) + gr->get_query_object_iv = + (void *) eglGetProcAddress("glGetQueryObjectivEXT"); +#endif + gr->get_query_object_ui64v = + (void *) eglGetProcAddress("glGetQueryObjectui64vEXT"); + assert(gr->gen_queries); + assert(gr->delete_queries); + assert(gr->begin_query); + assert(gr->end_query); + assert(gr->get_query_object_iv); + assert(gr->get_query_object_ui64v); + gr->has_disjoint_timer_query = true; + } else { + weston_log("warning: Disabling render GPU timeline due " + "to lack of support for elapsed counters by " + "the GL_EXT_disjoint_timer_query " + "extension\n"); + } + } else if (gr->has_native_fence_sync) { + weston_log("warning: Disabling render GPU timeline due to " + "missing GL_EXT_disjoint_timer_query extension\n"); + } + glActiveTexture(GL_TEXTURE0); gr->fallback_shader = gl_renderer_create_fallback_shader(gr); @@ -3976,9 +4261,19 @@ 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 "glReadPixels supports y-flip: %s\n", + yesno(gr->has_pack_reverse)); + 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-renderer.h b/libweston/renderer-gl/gl-renderer.h index 1430bb14e..7407b79c6 100644 --- a/libweston/renderer-gl/gl-renderer.h +++ b/libweston/renderer-gl/gl-renderer.h @@ -23,6 +23,8 @@ * SOFTWARE. */ +#pragma once + #include "config.h" #include @@ -64,16 +66,17 @@ enum gl_renderer_border_side { * \see struct gl_renderer_interface */ struct gl_renderer_display_options { + struct weston_renderer_options base; /** The EGL platform identifier */ EGLenum egl_platform; /** The native display corresponding to the given EGL platform */ void *egl_native_display; /** EGL_SURFACE_TYPE bits for the base EGLConfig */ EGLint egl_surface_type; - /** Array of DRM pixel formats acceptable for the base EGLConfig */ - const uint32_t *drm_formats; - /** The \c drm_formats array length */ - unsigned drm_formats_count; + /** Array of pixel formats acceptable for the base EGLConfig */ + const struct pixel_format_info **formats; + /** The \c formats array length */ + unsigned formats_count; }; struct gl_renderer_output_options { @@ -81,21 +84,25 @@ struct gl_renderer_output_options { EGLNativeWindowType window_for_legacy; /** Native window handle for \c eglCreatePlatformWindowSurface */ void *window_for_platform; - /** Array of DRM pixel formats acceptable for the window */ - const uint32_t *drm_formats; - /** The \c drm_formats array length */ - unsigned drm_formats_count; + /** Size of the framebuffer in pixels, including borders */ + struct weston_size fb_size; + /** Area inside the framebuffer in pixels for composited content */ + struct weston_geometry area; + /** Array of pixel formats acceptable for the window */ + const struct pixel_format_info **formats; + /** The \c formats array length */ + unsigned formats_count; }; struct gl_renderer_pbuffer_options { - /** Width of the rendering surface in pixels */ - int width; - /** Height of the rendering surface in pixels */ - int height; - /** Array of DRM pixel formats acceptable for the pbuffer */ - const uint32_t *drm_formats; - /** The \c drm_formats array length */ - unsigned drm_formats_count; + /** Size of the framebuffer in pixels, including borders */ + struct weston_size fb_size; + /** Area inside the framebuffer in pixels for composited content */ + struct weston_geometry area; + /** Array of pixel formats acceptable for the pbuffer */ + const struct pixel_format_info **formats; + /** The \c formats array length */ + unsigned formats_count; }; struct gl_renderer_interface { @@ -116,14 +123,14 @@ struct gl_renderer_interface { * advertises it. Without the advertisement this function fails. * * If neither EGL_KHR_no_config_context or EGL_MESA_configless_context - * are supported, the arguments egl_surface_type, drm_formats, and - * drm_formats_count are used to find a so called base EGLConfig. The + * are supported, the arguments egl_surface_type, formats, and + * formats_count are used to find a so called base EGLConfig. The * GL context is created with the base EGLConfig, and outputs will be * required to use the same config as well. If one or both of the * extensions are supported, these arguments are unused, and each * output can use a different EGLConfig (pixel format). * - * The first format in drm_formats that matches any EGLConfig + * The first format in formats that matches any EGLConfig * determines which EGLConfig is chosen. On EGL GBM platform, the * pixel format must match exactly. On other platforms, it is enough * that each R, G, B, A channel has the same number of bits as in the @@ -147,7 +154,7 @@ struct gl_renderer_interface { * used, otherwise \c window_for_legacy is used. This is because the * handle on X11 platform is different between the two. * - * The first format in drm_formats that matches any EGLConfig + * The first format in formats that matches any EGLConfig * determines which EGLConfig is chosen. See \c display_create about * how the matching works and the possible limitations. * @@ -168,7 +175,7 @@ struct gl_renderer_interface { * the output. The repaint results will be kept internal and can only * be accessed through e.g. screen capture. * - * The first format in drm_formats that matches any EGLConfig + * The first format in formats that matches any EGLConfig * determines which EGLConfig is chosen. See \c display_create about * how the matching works and the possible limitations. * diff --git a/libweston/renderer-gl/gl-shader-config-color-transformation.c b/libweston/renderer-gl/gl-shader-config-color-transformation.c index 21a456539..22d9fff53 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 @@ -29,6 +30,7 @@ #include #include +#include #include #include "color.h" @@ -44,11 +46,24 @@ 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 weston_color_mapping_matrix mat; + }; +} ; + 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; + struct gl_renderer_color_curve post_curve; }; static void @@ -58,10 +73,20 @@ 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_curve_fini(&gl_xform->post_curve); + gl_renderer_color_mapping_fini(&gl_xform->mapping); wl_list_remove(&gl_xform->destroy_listener.link); free(gl_xform); } @@ -120,6 +145,8 @@ gl_color_curve_lut_3x1d(struct gl_renderer_color_curve *gl_curve, /* * Four rows, see fragment.glsl sample_color_pre_curve_lut_2d(). * The fourth row is unused in fragment.glsl color_pre_curve(). + * Four rows, see fragment.glsl sample_color_post_curve_lut_2d(). + * The fourth row is unused in fragment.glsl color_post_curve(). */ lut = calloc(lut_len * nr_rows, sizeof *lut); if (!lut) @@ -152,6 +179,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 +228,11 @@ 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, + .post_curve.type = SHADER_COLOR_CURVE_IDENTITY, + .post_curve.tex = 0, + .post_curve.scale = 0.0f, + .post_curve.offset = 0.0f, }; struct gl_renderer_color_transform *gl_xform; bool ok = false; @@ -190,6 +263,38 @@ 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; + case WESTON_COLOR_MAPPING_TYPE_MATRIX: + gl_xform->mapping.type = SHADER_COLOR_MAPPING_MATRIX; + gl_xform->mapping.mat = xform->mapping.u.mat; + ok = true; + break; + } + if (!ok) { + gl_renderer_color_transform_destroy(gl_xform); + return NULL; + } + switch (xform->post_curve.type) { + case WESTON_COLOR_CURVE_TYPE_IDENTITY: + gl_xform->post_curve = no_op_gl_xform.post_curve; + ok = true; + break; + case WESTON_COLOR_CURVE_TYPE_LUT_3x1D: + ok = gl_color_curve_lut_3x1d(&gl_xform->post_curve, + &xform->post_curve, xform); + break; + } if (!ok) { gl_renderer_color_transform_destroy(gl_xform); return NULL; @@ -203,6 +308,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 +319,32 @@ 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_post_curve = gl_xform->post_curve.type; + sconf->color_post_curve_lut_tex = gl_xform->post_curve.tex; + sconf->color_post_curve_lut_scale_offset[0] = gl_xform->post_curve.scale; + sconf->color_post_curve_lut_scale_offset[1] = gl_xform->post_curve.offset; + + 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_MATRIX: + assert(sconf->req.color_mapping == SHADER_COLOR_MAPPING_MATRIX); + ARRAY_COPY(sconf->color_mapping.matrix, gl_xform->mapping.mat.matrix); + 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 97f288c07..8effa18e9 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,19 @@ 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; + GLint matrix_uniform; + } color_mapping; + GLint color_post_curve_lut_2d_uniform; + GLint color_post_curve_lut_scale_offset_uniform; struct wl_list link; /* gl_renderer::shader_list */ struct timespec last_used; }; @@ -97,6 +107,20 @@ 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) + CASERET(SHADER_COLOR_MAPPING_MATRIX) +#undef CASERET + } + + return "!?!?"; /* never reached */ +} + static void dump_program_with_line_numbers(int count, const char **sources) { @@ -162,9 +186,11 @@ 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 %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), + gl_shader_color_curve_to_string(req->color_post_curve), req->input_is_premult ? '+' : '-', req->green_tint ? '+' : '-'); if (size < 0) @@ -182,10 +208,14 @@ 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_COLOR_POST_CURVE %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_color_curve_to_string(req->color_post_curve), gl_shader_texture_variant_to_string(req->variant)); if (size < 0) return NULL; @@ -260,14 +290,41 @@ 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->color_uniform = glGetUniformLocation(shader->program, - "unicolor"); + shader->view_alpha_uniform = glGetUniformLocation(shader->program, "view_alpha"); + if (requirements->variant == SHADER_VARIANT_SOLID) { + shader->color_uniform = glGetUniformLocation(shader->program, + "unicolor"); + assert(shader->color_uniform != -1); + } else { + shader->color_uniform = -1; + } shader->color_pre_curve_lut_2d_uniform = glGetUniformLocation(shader->program, "color_pre_curve_lut_2d"); shader->color_pre_curve_lut_scale_offset_uniform = glGetUniformLocation(shader->program, "color_pre_curve_lut_scale_offset"); + shader->color_post_curve_lut_2d_uniform = + glGetUniformLocation(shader->program, "color_post_curve_lut_2d"); + shader->color_post_curve_lut_scale_offset_uniform = + glGetUniformLocation(shader->program, "color_post_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_MATRIX: + shader->color_mapping.matrix_uniform = + glGetUniformLocation(shader->program, + "color_mapping_matrix"); + break; + case SHADER_COLOR_MAPPING_IDENTITY: + break; + } free(conf); wl_list_insert(&gr->shader_list, &shader->link); @@ -376,6 +433,8 @@ 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, + .color_post_curve = SHADER_COLOR_CURVE_IDENTITY, }; struct gl_shader *shader; @@ -480,8 +539,11 @@ 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); + + if (shader->color_uniform != -1) + glUniform4fv(shader->color_uniform, 1, sconf->unicolor); + + 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 +559,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 +569,53 @@ 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); + i++; + glUniform2fv(shader->color_mapping.lut3d.scale_offset_uniform, + 1, sconf->color_mapping.lut3d.scale_offset); + break; + case SHADER_COLOR_MAPPING_MATRIX: + assert(shader->color_mapping.matrix_uniform != -1); + glUniformMatrix3fv(shader->color_mapping.matrix_uniform, + 1, GL_FALSE, + sconf->color_mapping.matrix); + break; + } + + switch (sconf->req.color_post_curve) { + case SHADER_COLOR_CURVE_IDENTITY: + assert(sconf->color_post_curve_lut_tex == 0); + break; + case SHADER_COLOR_CURVE_LUT_3x1D: + assert(sconf->color_post_curve_lut_tex != 0); + assert(shader->color_post_curve_lut_2d_uniform != -1); + assert(shader->color_post_curve_lut_scale_offset_uniform != -1); + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, sconf->color_post_curve_lut_tex); + glUniform1i(shader->color_post_curve_lut_2d_uniform, i); + i++; + glUniform2fv(shader->color_post_curve_lut_scale_offset_uniform, + 1, sconf->color_post_curve_lut_scale_offset); + break; + } } bool @@ -540,7 +641,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/renderer-gl/vertex.glsl b/libweston/renderer-gl/vertex.glsl index 03282e331..8b8019c3c 100644 --- a/libweston/renderer-gl/vertex.glsl +++ b/libweston/renderer-gl/vertex.glsl @@ -25,10 +25,21 @@ * SOFTWARE. */ +/* Always use high-precision for vertex calculations */ +precision highp float; + +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define FRAG_PRECISION highp +#else +#define FRAG_PRECISION mediump +#endif + uniform mat4 proj; attribute vec2 position; attribute vec2 texcoord; -varying vec2 v_texcoord; + +/* Match the varying precision to the fragment shader */ +varying FRAG_PRECISION vec2 v_texcoord; void main() { diff --git a/libweston/screenshooter.c b/libweston/screenshooter.c index fedc4805c..9730baf17 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); @@ -288,10 +312,9 @@ weston_recorder_frame_notify(struct wl_listener *listener, void *data) pixman_region32_init(&damage); pixman_region32_init(&transformed_damage); pixman_region32_intersect(&damage, &output->region, data); - pixman_region32_translate(&damage, -output->x, -output->y); - weston_transformed_region(output->width, output->height, - output->transform, output->current_scale, - &damage, &transformed_damage); + weston_region_global_to_output(&transformed_damage, + output, + &damage); pixman_region32_fini(&damage); r = pixman_region32_rectangles(&transformed_damage, &n); @@ -418,7 +441,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/shell-utils/meson.build b/libweston/shell-utils/meson.build new file mode 100644 index 000000000..36dae2bae --- /dev/null +++ b/libweston/shell-utils/meson.build @@ -0,0 +1,3 @@ +srcs_libweston += files([ + 'shell-utils.c', +]) diff --git a/libweston/shell-utils/shell-utils.c b/libweston/shell-utils/shell-utils.c new file mode 100644 index 000000000..6665062f5 --- /dev/null +++ b/libweston/shell-utils/shell-utils.c @@ -0,0 +1,248 @@ +/* + * Copyright 2010-2012 Intel Corporation + * Copyright 2013 Raspberry Pi Foundation + * Copyright 2011-2012,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 + +/** + * \defgroup shell-utils Shell utils + * + * These are some commonly used functions in our shells, useful for other shells + * as well. + */ + + +/** + * \ingroup shell-utils + */ +WL_EXPORT struct weston_output * +weston_shell_utils_get_default_output(struct weston_compositor *compositor) +{ + if (wl_list_empty(&compositor->output_list)) + return NULL; + + return container_of(compositor->output_list.next, + struct weston_output, link); +} + +/** + * \ingroup shell-utils + */ +WL_EXPORT struct weston_output * +weston_shell_utils_get_focused_output(struct weston_compositor *compositor) +{ + struct weston_seat *seat; + struct weston_output *output = NULL; + + wl_list_for_each(seat, &compositor->seat_list, link) { + struct weston_touch *touch = weston_seat_get_touch(seat); + struct weston_pointer *pointer = weston_seat_get_pointer(seat); + struct weston_keyboard *keyboard = + weston_seat_get_keyboard(seat); + + /* Priority has touch focus, then pointer and + * then keyboard focus. We should probably have + * three for loops and check first for touch, + * then for pointer, etc. but unless somebody has some + * objections, I think this is sufficient. */ + if (touch && touch->focus) + output = touch->focus->output; + else if (pointer && pointer->focus) + output = pointer->focus->output; + else if (keyboard && keyboard->focus) + output = keyboard->focus->output; + + if (output) + break; + } + + return output; +} + +/** + * \ingroup shell-utils + * + * TODO: Fix this function to take into account nested subsurfaces. + */ +WL_EXPORT void +weston_shell_utils_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.offset.c.x, + subsurface->position.offset.c.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); +} + +/** + * \ingroup shell-utils + */ +WL_EXPORT void +weston_shell_utils_center_on_output(struct weston_view *view, + struct weston_output *output) +{ + int32_t surf_x, surf_y, width, height; + float x, y; + + if (!output) { + weston_view_set_position(view, 0, 0); + return; + } + + weston_shell_utils_subsurfaces_boundingbox(view->surface, &surf_x, + &surf_y, &width, &height); + + x = output->x + (output->width - width) / 2 - surf_x / 2; + y = output->y + (output->height - height) / 2 - surf_y / 2; + + weston_view_set_position(view, x, y); +} + +/** + * \ingroup shell-utils + */ +WL_EXPORT int +weston_shell_utils_surface_get_label(struct weston_surface *surface, + char *buf, size_t len) +{ + const char *t, *c; + struct weston_desktop_surface *desktop_surface = + weston_surface_get_desktop_surface(surface); + + t = weston_desktop_surface_get_title(desktop_surface); + c = weston_desktop_surface_get_app_id(desktop_surface); + + return snprintf(buf, len, "%s window%s%s%s%s%s", + "top-level", + t ? " '" : "", t ?: "", t ? "'" : "", + c ? " of " : "", c ?: ""); +} + +/** + * \ingroup shell-utils + */ +WL_EXPORT struct weston_curtain * +weston_shell_utils_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) + goto err_curtain; + + view = weston_view_create(surface); + if (view == NULL) + goto err_surface; + + 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); + + pixman_region32_fini(&surface->input); + if (params->capture_input) { + pixman_region32_init_rect(&surface->input, 0, 0, + params->width, params->height); + } else { + pixman_region32_init(&surface->input); + } + + weston_surface_map(surface); + + 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; +} + +/** + * \ingroup shell-utils + */ +WL_EXPORT void +weston_shell_utils_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); +} diff --git a/libweston/touch-calibration.c b/libweston/touch-calibration.c index 9dd99bba4..bbef89fc8 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); @@ -286,7 +286,8 @@ touch_calibrator_convert(struct wl_client *client, struct weston_output *output; struct weston_surface *surface; uint32_t version; - struct weston_vector p = { { 0.0, 0.0, 0.0, 1.0 } }; + struct weston_coord_surface ps; + struct weston_coord_global pg; struct weston_point2d_device_normalized norm; version = wl_resource_get_version(resource); @@ -328,10 +329,11 @@ touch_calibrator_convert(struct wl_client *client, /* Convert from surface-local coordinates into global, from global * into output-raw, do perspective division and normalize. */ - weston_view_to_global_float(calibrator->view, x, y, &p.f[0], &p.f[1]); - weston_matrix_transform(&output->matrix, &p); - norm.x = p.f[0] / (p.f[3] * output->current_mode->width); - norm.y = p.f[1] / (p.f[3] * output->current_mode->height); + ps = weston_coord_surface(x, y, calibrator->view->surface); + pg = weston_coord_surface_to_global(calibrator->view, ps); + pg.c = weston_matrix_transform_coord(&output->matrix, pg.c); + norm.x = pg.c.x / output->current_mode->width; + norm.y = pg.c.y / output->current_mode->height; if (!normalized_is_valid(&norm)) { wl_resource_post_error(resource, @@ -674,6 +676,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 a71e7336a..c0c2f51ee 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/ */ @@ -102,8 +103,8 @@ enum path_transition { static void clip_append_vertex(struct clip_context *ctx, float x, float y) { - *ctx->vertices.x++ = x; - *ctx->vertices.y++ = y; + *ctx->vertices = weston_coord(x, y); + ctx->vertices++; } static enum path_transition @@ -194,17 +195,16 @@ clip_polygon_topbottom(struct clip_context *ctx, static void clip_context_prepare(struct clip_context *ctx, const struct polygon8 *src, - float *dst_x, float *dst_y) + struct weston_coord *dst) { - ctx->prev.x = src->x[src->n - 1]; - ctx->prev.y = src->y[src->n - 1]; - ctx->vertices.x = dst_x; - ctx->vertices.y = dst_y; + ctx->prev.x = src->pos[src->n - 1].x; + ctx->prev.y = src->pos[src->n - 1].y; + ctx->vertices = dst; } static int clip_polygon_left(struct clip_context *ctx, const struct polygon8 *src, - float *dst_x, float *dst_y) + struct weston_coord *dst) { enum path_transition trans; int i; @@ -212,18 +212,18 @@ clip_polygon_left(struct clip_context *ctx, const struct polygon8 *src, if (src->n < 2) return 0; - clip_context_prepare(ctx, src, dst_x, dst_y); + clip_context_prepare(ctx, src, dst); for (i = 0; i < src->n; i++) { - trans = path_transition_left_edge(ctx, src->x[i], src->y[i]); - clip_polygon_leftright(ctx, trans, src->x[i], src->y[i], + trans = path_transition_left_edge(ctx, src->pos[i].x, src->pos[i].y); + clip_polygon_leftright(ctx, trans, src->pos[i].x, src->pos[i].y, ctx->clip.x1); } - return ctx->vertices.x - dst_x; + return ctx->vertices - dst; } static int clip_polygon_right(struct clip_context *ctx, const struct polygon8 *src, - float *dst_x, float *dst_y) + struct weston_coord *dst) { enum path_transition trans; int i; @@ -231,18 +231,18 @@ clip_polygon_right(struct clip_context *ctx, const struct polygon8 *src, if (src->n < 2) return 0; - clip_context_prepare(ctx, src, dst_x, dst_y); + clip_context_prepare(ctx, src, dst); for (i = 0; i < src->n; i++) { - trans = path_transition_right_edge(ctx, src->x[i], src->y[i]); - clip_polygon_leftright(ctx, trans, src->x[i], src->y[i], + trans = path_transition_right_edge(ctx, src->pos[i].x, src->pos[i].y); + clip_polygon_leftright(ctx, trans, src->pos[i].x, src->pos[i].y, ctx->clip.x2); } - return ctx->vertices.x - dst_x; + return ctx->vertices - dst; } static int clip_polygon_top(struct clip_context *ctx, const struct polygon8 *src, - float *dst_x, float *dst_y) + struct weston_coord *dst) { enum path_transition trans; int i; @@ -250,18 +250,18 @@ clip_polygon_top(struct clip_context *ctx, const struct polygon8 *src, if (src->n < 2) return 0; - clip_context_prepare(ctx, src, dst_x, dst_y); + clip_context_prepare(ctx, src, dst); for (i = 0; i < src->n; i++) { - trans = path_transition_top_edge(ctx, src->x[i], src->y[i]); - clip_polygon_topbottom(ctx, trans, src->x[i], src->y[i], + trans = path_transition_top_edge(ctx, src->pos[i].x, src->pos[i].y); + clip_polygon_topbottom(ctx, trans, src->pos[i].x, src->pos[i].y, ctx->clip.y1); } - return ctx->vertices.x - dst_x; + return ctx->vertices - dst; } static int clip_polygon_bottom(struct clip_context *ctx, const struct polygon8 *src, - float *dst_x, float *dst_y) + struct weston_coord *dst) { enum path_transition trans; int i; @@ -269,61 +269,53 @@ clip_polygon_bottom(struct clip_context *ctx, const struct polygon8 *src, if (src->n < 2) return 0; - clip_context_prepare(ctx, src, dst_x, dst_y); + clip_context_prepare(ctx, src, dst); for (i = 0; i < src->n; i++) { - trans = path_transition_bottom_edge(ctx, src->x[i], src->y[i]); - clip_polygon_topbottom(ctx, trans, src->x[i], src->y[i], + trans = path_transition_bottom_edge(ctx, src->pos[i].x, src->pos[i].y); + clip_polygon_topbottom(ctx, trans, src->pos[i].x, src->pos[i].y, ctx->clip.y2); } - return ctx->vertices.x - dst_x; + return ctx->vertices - dst; } -#define max(a, b) (((a) > (b)) ? (a) : (b)) -#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, - float *ey) + struct weston_coord *e) { int i; for (i = 0; i < surf->n; i++) { - ex[i] = clip(surf->x[i], ctx->clip.x1, ctx->clip.x2); - ey[i] = clip(surf->y[i], ctx->clip.y1, ctx->clip.y2); + e[i].x = CLIP(surf->pos[i].x, ctx->clip.x1, ctx->clip.x2); + e[i].y = CLIP(surf->pos[i].y, ctx->clip.y1, ctx->clip.y2); } return surf->n; } -int +WESTON_EXPORT_FOR_TESTS int clip_transformed(struct clip_context *ctx, struct polygon8 *surf, - float *ex, - float *ey) + struct weston_coord *e) { struct polygon8 polygon; int i, n; - polygon.n = clip_polygon_left(ctx, surf, polygon.x, polygon.y); - surf->n = clip_polygon_right(ctx, &polygon, surf->x, surf->y); - polygon.n = clip_polygon_top(ctx, surf, polygon.x, polygon.y); - surf->n = clip_polygon_bottom(ctx, &polygon, surf->x, surf->y); + polygon.n = clip_polygon_left(ctx, surf, polygon.pos); + surf->n = clip_polygon_right(ctx, &polygon, surf->pos); + polygon.n = clip_polygon_top(ctx, surf, polygon.pos); + surf->n = clip_polygon_bottom(ctx, &polygon, surf->pos); /* Get rid of duplicate vertices */ - ex[0] = surf->x[0]; - ey[0] = surf->y[0]; + e[0] = surf->pos[0]; n = 1; for (i = 1; i < surf->n; i++) { - if (float_difference(ex[n - 1], surf->x[i]) == 0.0f && - float_difference(ey[n - 1], surf->y[i]) == 0.0f) + if (float_difference(e[n - 1].x, surf->pos[i].x) == 0.0f && + float_difference(e[n - 1].y, surf->pos[i].y) == 0.0f) continue; - ex[n] = surf->x[i]; - ey[n] = surf->y[i]; + e[n] = surf->pos[i]; n++; } - if (float_difference(ex[n - 1], surf->x[0]) == 0.0f && - float_difference(ey[n - 1], surf->y[0]) == 0.0f) + if (float_difference(e[n - 1].x, surf->pos[0].x) == 0.0f && + float_difference(e[n - 1].y, surf->pos[0].y) == 0.0f) n--; return n; diff --git a/libweston/vertex-clipping.h b/libweston/vertex-clipping.h index 0c699021a..0bf764be3 100644 --- a/libweston/vertex-clipping.h +++ b/libweston/vertex-clipping.h @@ -25,9 +25,10 @@ #ifndef _WESTON_VERTEX_CLIPPING_H #define _WESTON_VERTEX_CLIPPING_H +#include + struct polygon8 { - float x[8]; - float y[8]; + struct weston_coord pos[8]; int n; }; @@ -42,10 +43,7 @@ struct clip_context { float x2, y2; } clip; - struct { - float *x; - float *y; - } vertices; + struct weston_coord *vertices; }; float @@ -54,13 +52,11 @@ float_difference(float a, float b); int clip_simple(struct clip_context *ctx, struct polygon8 *surf, - float *ex, - float *ey); + struct weston_coord *e); int clip_transformed(struct clip_context *ctx, struct polygon8 *surf, - float *ex, - float *ey);\ + struct weston_coord *e); #endif diff --git a/libweston/weston-log-flight-rec.c b/libweston/weston-log-flight-rec.c index 7364c81ac..a577a38a8 100644 --- a/libweston/weston-log-flight-rec.c +++ b/libweston/weston-log-flight-rec.c @@ -177,15 +177,6 @@ weston_log_flight_recorder_write(struct weston_log_subscriber *sub, } -static void -weston_log_flight_recorder_map_memory(struct weston_debug_log_flight_recorder *flight_rec) -{ - size_t i = 0; - - for (i = 0; i < flight_rec->rb.size; i++) - flight_rec->rb.buf[i] = 0xff; -} - static void weston_log_subscriber_display_flight_rec_data(struct weston_ring_buffer *rb, FILE *file) @@ -271,7 +262,7 @@ weston_log_subscriber_create_flight_rec(size_t size) weston_primary_flight_recorder_ring_buffer = &flight_rec->rb; /* write some data to the rb such that the memory gets mapped */ - weston_log_flight_recorder_map_memory(flight_rec); + memset(flight_rec->rb.buf, 0xff, flight_rec->rb.size); return &flight_rec->base; } diff --git a/libweston/weston-log.c b/libweston/weston-log.c index 276fde267..93f95c9a2 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/man/meson.build b/man/meson.build index a2b8edc25..aad90dceb 100644 --- a/man/meson.build +++ b/man/meson.build @@ -1,5 +1,5 @@ man_conf = configuration_data() -man_conf.set('weston_native_backend', opt_backend_native) +man_conf.set('weston_native_backend', backend_default) man_conf.set('weston_modules_dir', dir_module_weston) man_conf.set('libweston_modules_dir', dir_module_libweston) man_conf.set('weston_shell_client', get_option('desktop-shell-client-default')) @@ -53,3 +53,12 @@ if get_option('backend-rdp') configuration: man_conf ) endif + +if get_option('backend-vnc') + configure_file( + input: 'weston-vnc.man', + output: 'weston-vnc.7', + install_dir: join_paths(dir_man, 'man7'), + configuration: man_conf + ) +endif diff --git a/man/weston-bindings.man b/man/weston-bindings.man index e88a9e85b..d528a8073 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 01a336e1d..3cfd371de 100644 --- a/man/weston-drm.man +++ b/man/weston-drm.man @@ -2,9 +2,7 @@ .SH NAME weston-drm \- the DRM backend for Weston .SH SYNOPSIS -.B weston-launch -.LP -.B weston --backend=drm-backend.so +.B weston --backend=drm . .\" *************************************************************** .SH DESCRIPTION @@ -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,32 @@ 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 +.BR xrgb8888 ", " xrgb2101010 ", " rgb565 +and some of their permutations. +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 +134,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 +165,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 +229,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 +241,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 f6cdd1de4..b2f84afcf 100644 --- a/man/weston-rdp.man +++ b/man/weston-rdp.man @@ -2,7 +2,7 @@ .SH NAME weston-rdp \- the RDP backend for Weston .SH SYNOPSIS -.B weston --backend=rdp-backend.so +.B weston --backend=rdp . .\" *************************************************************** .SH DESCRIPTION @@ -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-vnc.man b/man/weston-vnc.man new file mode 100644 index 000000000..a4225fda6 --- /dev/null +++ b/man/weston-vnc.man @@ -0,0 +1,84 @@ +.TH WESTON-RDP 7 "2017-12-14" "Weston @version@" +.SH NAME +weston-vnc \- the VNC backend for Weston +.SH SYNOPSIS +.B weston --backend=vnc +. +.\" *************************************************************** +.SH DESCRIPTION +The VNC backend allows to run a +.B weston +environment without the need of specific graphic hardware, or input devices. Users can interact with +.B weston +only by connecting using the remote framebuffer protocol (RFB). + +The VNC backend uses Neat VNC to implement the VNC part, it acts as a VNC server +listening for incoming connections. It supports different encodings for encoding +the graphical content, depending on what is supported by the VNC client. + +The VNC backend is not multi-seat aware, so if a second client connects to the +backend, the first client will be disconnected. + +The VNC client has to authenticate as the user running weston. This requires a PAM configuration file +.BR /etc/pam.d/weston-remote-access . + +.\" *************************************************************** +.SH CONFIGURATION +. +The VNC backend uses the following entries from +.BR weston.ini . +.SS Section vnc +.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 +. +When the VNC backend is loaded, +.B weston +will understand the following additional command line options. +.TP +.B \-\-width\fR=\fIwidth\fR +The width of the framebuffer. It defaults to 640. +.TP +.B \-\-height\fR=\fIheight\fR +The height of the framebuffer. It defaults to 480. +.TP +\fB\-\-port\fR=\fIport\fR +The TCP port to listen on for connections. It defaults to 5900. +.TP +\fB\-\-vnc\-tls\-key\fR=\fIfile\fR +The file containing the key for doing TLS security. To have TLS security you also need +to ship a file containing a certificate. +.TP +\fB\-\-vnc\-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. + + +.\" *************************************************************** +.SH Generating cryptographic material for the VNC backend +. +You can generate a key and certificate file to use with TLS security using typical +.B openssl +invocations: + +.nf +$ openssl genrsa -out tls.key 2048 +Generating RSA private key, 2048 bit long modulus +[...] +$ openssl req -new -key tls.key -out tls.csr +[...] +$ openssl x509 -req -days 365 -signkey tls.key -in tls.csr -out tls.crt +[...] +.fi + +You will get the tls.key and tls.crt files to use with the VNC backend. +. +.\" *************************************************************** +.SH "SEE ALSO" +.BR weston (1) +.\".BR weston.ini (5) diff --git a/man/weston.ini.man b/man/weston.ini.man index 5877425c1..e0ab5edb4 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,13 +90,13 @@ 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 section is used to select the startup compositor modules and general options. .TP 7 -.BI "shell=" desktop-shell.so +.BI "shell=" desktop specifies a shell to load (string). This can be used to load your own implemented shell or one with Weston as default. Available shells in the @@ -102,13 +105,15 @@ directory are: .PP .RS 10 .nf -.BR desktop-shell.so +.BR desktop +.BR fullscreen +.BR ivi +.BR kiosk .fi .RE .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 @@ -122,19 +127,18 @@ directory are: .fi .RE .TP 7 -.BI "backend=" headless-backend.so -overrides defaults backend. Available backend modules in the -.IR "@libweston_modules_dir@" -directory are: +.BI "backend=" headless +overrides defaults backend. Available backends are: .PP .RS 10 .nf -.BR drm-backend.so -.BR fbdev-backend.so -.BR headless-backend.so -.BR rdp-backend.so -.BR wayland-backend.so -.BR x11-backend.so +.BR drm +.BR headless +.BR rdp +.BR pipewire +.BR vnc +.BR wayland +.BR x11 .fi .RE .TP 7 @@ -148,16 +152,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 +164,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 @@ -203,12 +189,29 @@ directory are: .fi .RE .TP 7 +.BI "renderer=" auto +Selects a renderer to use for internal composition when required, or +.BI auto +to select the most appropriate renderer. Available renderers are: +.PP +.RS 10 +.nf +.BR auto +.BR gl +.BR noop +.BR pixman +.fi +.RE +Not all backends support all renderers. +.TP 7 .BI "use-pixman=" true +Deprecated in favour of the +.BI "renderer=" +option. 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 +225,15 @@ 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. - +.TP 7 +.BI "output-decorations=" true +For headless-backend with GL-renderer only: draws output window decorations, +similar to what wayland-backend does for floating output windows. +Boolean, defaults to +.BR false . +These decorations cannot normally be screenshot. This option is useful for +the Weston test suite only. +.\"--------------------------------------------------------------------- .SH "LIBINPUT SECTION" The .B libinput @@ -331,18 +342,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 @@ -351,8 +362,12 @@ different shell plugins. .PP The entries that can appear in this section are: .TP 7 -.BI "client=" file -sets the path for the shell client to run. If not specified +.BI "client=" "@weston_libexecdir@/@weston_shell_client@" +specifies the path for the shell client to run. +It is possible to pass arguments and environment variables to the program, +for example, 'ENVFOO=bar ENVBAR=baz /path/to/program --arg anotherarg', +with entries that are space-separated but with no support for quoting. +If no client was specified then .I @weston_shell_client@ is launched (string). .TP 7 @@ -422,7 +437,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 +459,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 +484,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 +492,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 +503,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 +517,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 +545,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 +555,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 +563,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 +571,95 @@ 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 "content-type=" content_type +The type of the content being primarily displayed to this output. Can be "no +data" (default), "graphics", "photo", "cinema" or "game". .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 +It is possible to pass arguments and environment variables to the program, +for example, 'ENVFOO=bar ENVBAR=baz /path/to/program --arg anotherarg', +with entries that are space-separated but with no support for quoting. + .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 +667,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" +.BI "command=" "@weston_bindir@/weston --backend=rdp \ +--shell=fullscreen --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 +701,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 c453a7d35..20c511a6d 100644 --- a/man/weston.man +++ b/man/weston.man @@ -14,91 +14,90 @@ 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." +.BR Xwayland ", see below." . .\" *************************************************************** .SH BACKENDS .TP -.I drm-backend.so +.I drm The DRM backend uses Linux KMS for output and evdev devices for input. It supports multiple monitors in a unified desktop with DPMS. See .BR weston-drm (7), if installed. .TP -.I wayland-backend.so +.I wayland The Wayland backend runs on another Wayland server, a different Weston instance, for example. Weston shows up as a single desktop window on the parent server. .TP -.I x11-backend.so +.I x11 The X11 backend runs on an X server. Each Weston output becomes an X window. This is a cheap way to test multi-monitor support of a Wayland shell, desktop, or applications. .TP -.I rdp-backend.so +.I rdp The RDP backend runs in memory without the need of graphical hardware. Access to the desktop is done by using the RDP protocol. Each connecting client has its own seat making it a cheap way to test multi-seat support. See .BR weston-rdp (7), if installed. +.TP +.I vnc +The VNC backend runs in memory without the need of graphical hardware. Access +to the desktop is done by using the RFB protocol. Currently only one +connecting client is supported. See +.BR weston-vnc (7), +if installed. +.TP +.I pipewire +The PipeWire backend runs in memory without the need of graphical hardware and +creates a PipeWire node for each output. It can be used to capture Weston +outputs for processing with another application. . .\" *************************************************************** .SH SHELLS -Each of these shells have its own public protocol interface for clients. -This means that a client must be specifically written for a shell protocol, -otherwise it will not work. -.TP -Desktop shell -Desktop shell is like a modern X desktop environment, concentrating -on traditional keyboard and mouse user interfaces and the familiar -desktop-like window management. Desktop shell consists of the -shell plugin -.I desktop-shell.so -and the special client -.B weston-desktop-shell -which provides the wallpaper, panel, and screen locking dialog. -.TP -Fullscreen shell -Fullscreen shell is intended for a client that needs to take over -whole outputs, often all outputs. This is primarily intended for -running another compositor on Weston. The other compositor does not -need to handle any platform-specifics like DRM/KMS or evdev/libinput. -The shell consists only of the shell plugin -.IR fullscreen-shell.so . -.TP -IVI-shell -In-vehicle infotainment shell is a special purpose shell that exposes -a GENIVI Layer Manager compatible API to controller modules, and a very -simple shell protocol towards clients. IVI-shell starts with loading -.IR ivi-shell.so , -and then a controller module which may launch helper clients. +Weston's user interface is implemented by individual 'shell' plugins. +A number of shells are provided for different usecases. +.TP +.I desktop +The desktop shell is Weston's default mode. It provides an example of a +desktop-like environment, featuring a panel with launchers and a clock, +a background, and an interactive task switcher. Whilst not intended to be +a full-fledged desktop environment in and of itself, it is an example of +how such an environment can be built. +.TP +.I kiosk +The kiosk shell is intended for environments which want to run a single +application at a time. Applications will be made full screen and +activated as they are started. +.TP +.I fullscreen +The fullscreen shell is a deprecated implementation of the ideas behind +the kiosk shell. It requires specific client support for the +.I zwp_fullscreen_shell_v1 +interface. +.TP +.I ivi +The IVI shell is a special-purpose shell which exposes an API compatible +with the GENIVI Layer Manager to user-provided HMI controller modules. +It is intended for use in automotive environments. . .\" *************************************************************** .SH XWAYLAND -XWayland requires a special X.org server to be installed. This X server will -connect to a Wayland server as a Wayland client, and X clients will connect to -the X server. XWayland provides backwards compatibility to X applications in a -Wayland stack. - -XWayland is activated by instructing -.BR weston " to load the XWayland module, see " EXAMPLES . -Weston starts listening on a new X display socket, and exports it in the -environment variable -.BR DISPLAY . -When the first X client connects, Weston launches a special X server as a -Wayland client to handle the X client and all future X clients. +Weston can support X11 clients running within a Weston session via an +X server called +.BR Xwayland "." +Xwayland is built as a separate executable, provided by X.Org. Once built +and installed, it can be activated with the +.BR \-\-xwayland +option. Weston will listen on a new X11 display socket and export it +through the +.BR DISPLAY +environment variable. It has also its own X window manager where cursor themes and sizes can be chosen using @@ -111,12 +110,14 @@ and . .SS Weston core options: .TP -\fB\-\^B\fR\fIbackend.so\fR, \fB\-\-backend\fR=\fIbackend.so\fR +\fB\-\^B\fR\fIbackend\fR, \fB\-\-backend\fR=\fIbackend\fR Load -.I backend.so -instead of the default backend. The file is searched for in -.IR "@weston_modules_dir@" , -or you can pass an absolute path. The default backend is +.I backend +instead of the default backend, see +.IR BACKENDS . +The backend module is searched for in +.IR "@weston_modules_dir@" . +The default backend is .I @weston_native_backend@ unless the environment suggests otherwise, see .IR DISPLAY " and " WAYLAND_DISPLAY . @@ -129,9 +130,9 @@ The argument can also be an absolute path starting with a If the path is not absolute, it will be searched in the normal config paths, see .BR weston.ini (5). -If also +This option is ignored if the .B --no-config -is given, no configuration file will be read. +option is passed. .TP .BR \-\-debug Enable debug protocol extension @@ -147,12 +148,6 @@ to take screenshots of the outputs using weston-screenshooter application, which can lead to silently leaking the output contents. This option should not be used in production. .TP -\fB\-\^l\fIscope1,scope2\fR, \fB\-\-logger-scopes\fR=\fIscope1,scope2\fR -Specify to which log scopes should subscribe to. When no scopes are supplied, -the log "log" scope will be subscribed by default. Useful to control which -streams to write data into the logger and can be helpful in diagnosing early -start-up code. -.TP \fB\-\^f\fIscope1,scope2\fR, \fB\-\-flight-rec-scopes\fR=\fIscope1,scope2\fR Specify to which scopes should subscribe to. Useful to control which streams to write data into the flight recorder. Flight recorder has limited space, once @@ -160,9 +155,6 @@ the flight recorder is full new data will overwrite the old data. Without any scopes specified, it subscribes to 'log' and 'drm-backend' scopes. Passing an empty value would disable the flight recorder entirely. .TP -.BR \-\-version -Print the program version. -.TP .BR \-\^h ", " \-\-help Print a summary of command line options, and quit. .TP @@ -180,8 +172,11 @@ Append log messages to the file .I file.log instead of writing them to stderr. .TP -\fB\-\-xwayland\fR -Ask Weston to load the XWayland module. +\fB\-\^l\fIscope1,scope2\fR, \fB\-\-logger-scopes\fR=\fIscope1,scope2\fR +Specify to which log scopes should subscribe to. When no scopes are supplied, +the log "log" scope will be subscribed by default. Useful to control which +streams to write data into the logger and can be helpful in diagnosing early +start-up code. .TP \fB\-\-modules\fR=\fImodule1.so,module2.so\fR Load the comma-separated list of modules. Only used by the test @@ -195,6 +190,14 @@ Do not read for the compositor. Avoids e.g. loading compositor modules via the configuration file, which is useful for unit tests. .TP +\fB\-\-renderer\fR=\fIrenderer\fR +Select which renderer to use for Weston's internal composition. Defaults to +automatic selection. +.TP +\fB\-\-shell\fR=\fIshell\fR +Select which shell to load to provide Weston's user interface. See +.BR ENVIRONMENT "." +.TP \fB\-\^S\fR\fIname\fR, \fB\-\-socket\fR=\fIname\fR Weston will listen in the Wayland socket called .IR name . @@ -202,6 +205,8 @@ Weston will export .B WAYLAND_DISPLAY with this value in the environment for all child processes to allow them to connect to the right server automatically. +.BR \-\-version +Print the program version. .TP \fB\-\-wait-for-debugger\fR Raises SIGSTOP before initializing the compositor. This allows the user to @@ -209,6 +214,9 @@ attach with a debugger and continue execution by sending SIGCONT. This is useful for debugging a crash on start-up when it would be inconvenient to launch weston directly from a debugger. There is also a .IR weston.ini " option to do the same." +.TP +\fB\-\-xwayland\fR +Support X11 clients through the Xwayland server. . .SS DRM backend options: See @@ -238,7 +246,9 @@ Give all outputs a scale factor of .I N. .TP .B \-\-use\-pixman -Use the pixman renderer. By default, weston will try to use EGL and +Deprecated in favour of the +.BI \-\-renderer +option. Use the pixman renderer. By default weston will try to use EGL and GLES2 for rendering and will fall back to the pixman-based renderer for software compositing if EGL cannot be used. Passing this option will force weston to use the pixman renderer. @@ -264,7 +274,9 @@ Give all outputs a scale factor of .I N. .TP .B \-\-use\-pixman -Use the pixman renderer. By default weston will try to use EGL and +Deprecated in favour of the +.BI \-\-renderer +option. Use the pixman renderer. By default weston will try to use EGL and GLES2 for rendering. Passing this option will make weston use the pixman library for software compsiting. . @@ -272,6 +284,10 @@ pixman library for software compsiting. See .BR weston-rdp (7). . +.SS VNC backend options: +See +.BR weston-vnc (7). +. . .\" *************************************************************** .SH FILES @@ -294,7 +310,7 @@ The X display. If is set, and .B WAYLAND_DISPLAY is not set, the default backend becomes -.IR x11-backend.so . +.IR x11 . .TP .B WAYLAND_DEBUG If set to any value, causes libwayland to print the live protocol @@ -311,7 +327,7 @@ is not set, the socket name is "wayland-0". If .B WAYLAND_DISPLAY is already set, the default backend becomes -.IR wayland-backend.so . +.IR wayland . This allows launching Weston as a nested server. .TP .B WAYLAND_SOCKET @@ -350,20 +366,19 @@ Wayland clients will automatically use this. . .\" *************************************************************** .SH BUGS -Bugs should be reported to the freedesktop.org bugzilla at -https://bugs.freedesktop.org with product "Wayland" and -component "weston". +Bugs should be reported to +.BR https://gitlab.freedesktop.org/wayland/weston/ "." . .\" *************************************************************** .SH WWW -http://wayland.freedesktop.org/ +https://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" @@ -375,4 +390,5 @@ weston .BR weston-debug (1), .BR weston-drm (7), .BR weston-rdp (7), +.BR weston-vnc (7), .BR weston.ini (5) diff --git a/meson.build b/meson.build index 0d53c1b0b..53dd670b5 100644 --- a/meson.build +++ b/meson.build @@ -1,16 +1,16 @@ project('weston', 'c', - version: '10.0.1', + version: '12.0.3', default_options: [ 'warning_level=3', 'c_std=gnu99', 'b_lundef=true', ], - meson_version: '>= 0.52.1', + meson_version: '>= 0.63.0', license: 'MIT/Expat', ) -libweston_major = 10 +libweston_major = 12 # 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('deprecated-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')) @@ -40,6 +44,7 @@ dir_data_pc = join_paths(dir_data, 'pkgconfig') dir_lib_pc = join_paths(dir_lib, 'pkgconfig') dir_man = join_paths(dir_prefix, get_option('mandir')) dir_protocol_libweston = join_paths('libweston-@0@'.format(libweston_major), 'protocols') +dir_sysconf = join_paths(dir_prefix, get_option('sysconfdir')) public_inc = include_directories('include') common_inc = [ include_directories('.'), public_inc ] @@ -56,8 +61,7 @@ config_h = configuration_data() cc = meson.get_compiler('c') -global_args = [] -global_args_maybe = [ +global_args = cc.get_supported_arguments( '-Wmissing-prototypes', '-Wno-unused-parameter', '-Wno-shift-negative-value', # required due to Pixman @@ -65,13 +69,8 @@ global_args_maybe = [ '-Wno-pedantic', '-Wundef', '-fvisibility=hidden', -] -foreach a : global_args_maybe - if cc.has_argument(a) - global_args += a - endif -endforeach -add_global_arguments(global_args, language: 'c') +) +add_project_arguments(global_args, language: 'c') if cc.has_header_symbol('sys/sysmacros.h', 'major') config_h.set('MAJOR_IN_SYSMACROS', 1) @@ -80,7 +79,8 @@ elif cc.has_header_symbol('sys/mkdev.h', 'major') endif optional_libc_funcs = [ - 'mkostemp', 'strchrnul', 'initgroups', 'posix_fallocate', 'memfd_create' + 'mkostemp', 'strchrnul', 'initgroups', 'posix_fallocate', + 'memfd_create', 'unreachable', ] foreach func : optional_libc_funcs if cc.has_function(func) @@ -113,12 +113,11 @@ config_h.set_quoted('PACKAGE_BUGREPORT', 'https://gitlab.freedesktop.org/wayland config_h.set_quoted('BINDIR', dir_bin) config_h.set_quoted('DATADIR', dir_data) +config_h.set_quoted('LIBDIR', dir_lib) 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' ] @@ -127,8 +126,7 @@ if backend_default == 'auto' endif endforeach endif -opt_backend_native = backend_default + '-backend.so' -config_h.set_quoted('WESTON_NATIVE_BACKEND', opt_backend_native) +config_h.set_quoted('WESTON_NATIVE_BACKEND', backend_default) message('The default backend is ' + backend_default) if not get_option('backend-' + backend_default) error('Backend @0@ was chosen as native but is not being built.'.format(backend_default)) @@ -139,38 +137,39 @@ 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_libinput = dependency('libinput', version: '>= 1.2.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_lcms2 = dependency('lcms2', version: '>= 2.9', required: false) + 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') + config_h.set('USE_DRM_FORMAT_NV15', '1') endif prog_python = import('python').find_installation('python3') files_xxd_py = files('tools/xxd.py') cmd_xxd = [ prog_python, files_xxd_py, '@INPUT@', '@OUTPUT@' ] +deps_for_libweston_users = [ + dep_wayland_server, + dep_pixman, + dep_xkbcommon, +] + + subdir('include') subdir('protocol') subdir('shared') subdir('libweston') -subdir('libweston-desktop') subdir('xwayland') subdir('compositor') subdir('desktop-shell') @@ -184,6 +183,14 @@ subdir('wcap') subdir('tests') subdir('data') subdir('man') +subdir('pam') + +if meson.version().version_compare('>= 0.58.0') + devenv = environment() + devenv.set('WESTON_MODULE_MAP', env_modmap) + devenv.set('WESTON_DATA_DIR', join_paths(meson.current_source_dir(), 'data')) + meson.add_devenv(devenv) +endif configure_file(output: 'config.h', configuration: config_h) diff --git a/meson_options.txt b/meson_options.txt index 8a527d74f..643c6d619 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -20,6 +20,12 @@ option( value: true, description: 'Weston backend: headless (testing)' ) +option( + 'backend-pipewire', + type: 'boolean', + value: true, + description: 'PipeWire backend: screencasting via PipeWire' +) option( 'backend-rdp', type: 'boolean', @@ -32,6 +38,12 @@ option( value: true, description: 'Compositor: RDP screen-sharing support' ) +option( + 'backend-vnc', + type: 'boolean', + value: true, + description: 'Weston backend: VNC remote screensharing' +) option( 'backend-wayland', type: 'boolean', @@ -44,16 +56,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' ) @@ -66,10 +72,10 @@ option( ) option( - 'deprecated-weston-launch', + 'renderer-g2d', type: 'boolean', - value: false, - description: 'Deprecated weston launcher for systems without logind' + value: true, + description: 'Weston renderer: G2D, i.MX G2D support' ) option( @@ -138,13 +144,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,23 +151,29 @@ 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( - 'launcher-logind', + 'deprecated-launcher-logind', type: 'boolean', - value: true, - description: 'Compositor: support systemd-logind D-Bus protocol' + value: false, + description: 'DEPRECATED: Compositor: support systemd-logind D-Bus protocol (superseded by launcher-libseat)' ) option( 'launcher-libseat', type: 'boolean', - value: false, + value: true, description: 'Compositor: support libseat' ) @@ -230,12 +235,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/pam/meson.build b/pam/meson.build new file mode 100644 index 000000000..7b7eff8dc --- /dev/null +++ b/pam/meson.build @@ -0,0 +1,8 @@ +if not get_option('backend-vnc') + subdir_done() +endif + +install_data( + 'weston-remote-access', + install_dir: join_paths(dir_sysconf, 'pam.d') +) diff --git a/pam/weston-remote-access b/pam/weston-remote-access new file mode 100644 index 000000000..d3014dd8b --- /dev/null +++ b/pam/weston-remote-access @@ -0,0 +1,3 @@ +#%PAM-1.0 +auth include login +account include login diff --git a/pipewire/pipewire-plugin.c b/pipewire/pipewire-plugin.c index b194f4c64..cb07521a7 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; @@ -182,7 +192,10 @@ pipewire_output_handle_frame(struct pipewire_output *output, int fd, if ((h = spa_buffer_find_meta_data(spa_buffer, SPA_META_Header, sizeof(struct spa_meta_header)))) { - h->pts = -1; + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + h->pts = SPA_TIMESPEC_TO_NSEC(&ts); h->flags = 0; h->seq = output->seq++; h->dts_offset = 0; @@ -310,17 +323,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 +550,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 +563,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 +647,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 7d869dadf..84eb8b9b8 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -1,55 +1,66 @@ -dep_scanner = dependency('wayland-scanner', native: true) -prog_scanner = find_program(dep_scanner.get_pkgconfig_variable('wayland_scanner')) +dep_scanner = dependency('wayland-scanner', native: false) +prog_scanner = find_program(dep_scanner.get_variable(pkgconfig: '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.31', + 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', + 'weston-output-capture.xml', ], install_dir: join_paths(dir_data, dir_protocol_libweston) ) generated_protocols = [ - [ 'input-method', 'v1' ], - [ 'input-timestamps', 'v1' ], + [ 'fullscreen-shell', 'unstable', 'v1' ], + [ 'fractional-scale', 'staging', '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' ], + [ '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' ], + [ 'tearing-control', 'staging', 'v1' ], [ 'text-cursor-position', 'internal' ], - [ 'text-input', 'v1' ], + [ 'text-input', 'unstable', 'v1' ], [ 'viewporter', 'stable' ], [ 'weston-debug', 'internal' ], [ 'weston-desktop-shell', 'internal' ], - [ 'weston-screenshooter', 'internal' ], + [ 'weston-output-capture', 'internal' ], [ 'weston-content-protection', 'internal' ], [ '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' ], + [ 'xwayland-shell', 'staging', 'v1' ], + [ 'hdr10-metadata', 'unstable', 'v1' ], ] foreach proto: generated_protocols proto_name = proto[0] if proto[1] == 'internal' base_file = proto_name - xml_path = '@0@.xml'.format(proto_name) + xml_path = proto_name + '.xml' 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]) - xml_path = '@0@/unstable/@1@/@2@.xml'.format(dir_wp_base, proto_name, base_file) + xml_path = dir_wp_base / 'stable' / base_file / (base_file + '.xml') + elif proto[1] == 'unstable' + base_file = '@0@-unstable-@1@'.format(proto_name, proto[2]) + xml_path = dir_wp_base / 'unstable' / proto_name / (base_file + '.xml') + elif proto[1] == 'staging' + base_file = '@0@-@1@'.format(proto_name, proto[2]) + xml_path = dir_wp_base / 'staging' / proto_name / (base_file + '.xml') endif foreach output_type: [ 'client-header', 'server-header', 'private-code' ] diff --git a/protocol/weston-output-capture.xml b/protocol/weston-output-capture.xml new file mode 100644 index 000000000..ed56e0dc1 --- /dev/null +++ b/protocol/weston-output-capture.xml @@ -0,0 +1,217 @@ + + + + + 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. + + + + + The global interface exposing Weston screenshooting functionality + intended for single shots. + + This is a privileged inteface. + + + + + Affects no other protocol objects in any way. + + + + + + + + + + + + + + + + + This creates a weston_capture_source_v1 object corresponding to the + given wl_output. The object delivers information for allocating + suitable buffers, and exposes the capture function. + + The object will be using the given pixel source for capturing images. + If the source is not available, all attempts to capture will fail + gracefully. + + 'writeback' source will use hardware writeback feature of DRM KMS for + capturing. This may allow hardware planes to remain used + during the capture. This source is often not available. + + 'framebuffer' source copies the contents of the final framebuffer. + Using this source temporarily disables all use of hardware planes and + DRM KMS color pipeline features. This source is always available. + + 'full_framebuffer' is otherwise the same as 'framebuffer' except it + will include also any borders (decorations) that the framebuffer may + contain. + + 'blending' source copies the contents of the intermediate blending + buffer, which should be in linear-light format. Using this source + temporarily disables all use of hardware planes. This source is only + available when a blending buffer exists, e.g. when color management + is active on the output. + + If the pixel source is not one of the defined enumeration values, + 'invalid_source' protocol error is raised. + + + + + + + + + + An object representing image capturing functionality for a single + source. When created, it sends the initial events if and only if the + output still exists and the specified pixel source is available on + the output. + + + + + + + + + + If a capture is on-going on this object, this will cancel it and + make the image buffer contents undefined. + + This object is destroyed. + + + + + + If the given wl_buffer is compatible, the associated output will go + through a repaint some time after this request has been processed, + and that repaint will execute the capture. + Once the capture is complete, 'complete' event is emitted. + + If the given wl_buffer is incompatible, the event 'retry' is + emitted. + + If the capture fails or the buffer type is unsupported, the event + 'failed' is emitted. + + The client must wait for one of these events before attempting + 'capture' on this object again. If 'capture' is requested again before + any of those events, 'sequence' protocol error is raised. + + The wl_buffer object will not emit wl_buffer.release event due to + this request. + + The wl_buffer must refer to compositor-writable storage. If buffer + storage is not writable, either the protocol error bad_buffer or + wl_shm.error.invalid_fd is raised. + + If the wl_buffer is destroyed before any event is emitted, the buffer + contents become undefined. + + A compositor is required to implement capture into wl_shm buffers. + Other buffer types may or may not be supported. + + + + + + + This event delivers the pixel format that should be used for the + image buffer. Any buffer is incompatible if it does not have + this pixel format. + + The format modifier is linear (DRM_FORMAT_MOD_LINEAR). + + This is an initial event, and sent whenever the required format + changes. + + + + + + + This event delivers the size that should be used for the + image buffer. Any buffer is incompatible if it does not have + this size. + + Row alignment of the buffer must be 4 bytes, and it must not contain + further row padding. Otherwise the buffer is unsupported. + + This is an initial event, and sent whenever the required size + changes. + + + + + + + + This event is emitted as a response to 'capture' request when it + has successfully completed. + + If the buffer used in the shot is a dmabuf, the client also needs to + wait for any implicit fences on it before accessing the contents. + + + + + + This event is emitted as a response to 'capture' request when it + cannot succeed due to an incompatible buffer. The client has already + received the events delivering the new buffer parameters. The client + should retry the capture with the new buffer parameters. + + + + + + This event is emitted as a response to 'capture' request when it + has failed for reasons other than an incompatible buffer. The reasons + may include: unsupported buffer type, unsupported buffer stride, + unsupported image source, the image source (output) was removed, or + compositor policy denied the capture. + + The string 'msg' may contain a human-readable explanation of the + failure to aid debugging. + + + + + + diff --git a/protocol/weston-test.xml b/protocol/weston-test.xml index f880414d6..845b1d412 100644 --- a/protocol/weston-test.xml +++ b/protocol/weston-test.xml @@ -34,6 +34,12 @@ These requests may allow clients to do very bad things. + + + + + diff --git a/releasing.md b/releasing.md index 8afb33b8f..deae784f4 100644 --- a/releasing.md +++ b/releasing.md @@ -25,28 +25,21 @@ To make a release of Weston, follow these steps. git push 3. Run the `release.sh` script to generate the tarballs, sign and upload them, - and generate a release announcement template. This script can be obtained - from X.org's modular package: + and generate a release announcement template. This script can be obtained + from the Wayland repository: - https://gitlab.freedesktop.org/xorg/util/modular/blob/master/release.sh + https://gitlab.freedesktop.org/wayland/wayland/-/blob/main/release.sh - The script supports a `--dry-run` option to test it without actually doing a - release. If the script fails on the distcheck step due to a test suite error - that can't be fixed for some reason, you can skip testsuite by specifying - the `--dist` argument. Pass `--help` to see other supported options. +4. Compose the release announcements. The script will generate a + weston-x.y.z.announce file with a list of changes and tags. Prepend these + with a human-readable listing of the most notable changes. For x.y.0 + releases, indicate the schedule for the x.y+1.0 release. - release.sh . - -5. Compose the release announcements. The script will generate *.x.y.z.announce - files with a list of changes and tags. Prepend these with a human-readable - listing of the most notable changes. For x.y.0 releases, indicate the - schedule for the x.y+1.0 release. - -6. PGP sign the release announcement and send it to +5. PGP sign the release announcement and send it to . -7. Update `releases.html` in wayland.freedesktop.org with links to tarballs and - the release email URL. Copy tarballs produced by `release.sh` to `releases/`. +6. Update `releases.html` in wayland.freedesktop.org with links to tarballs and + the release email URL. Once satisfied: diff --git a/remoting/remoting-plugin.c b/remoting/remoting-plugin.c index e5f5ca4aa..4d4dea7e3 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 3bdf96549..6bce0d150 100644 --- a/shared/cairo-util.c +++ b/shared/cairo-util.c @@ -37,9 +37,11 @@ #include "shared/helpers.h" #include "image-loader.h" +#include "shared/xalloc.h" #include #ifdef HAVE_PANGO +#include #include #endif @@ -275,7 +277,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 +329,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 @@ -416,9 +420,7 @@ theme_create(void) struct theme *t; cairo_t *cr; - t = malloc(sizeof *t); - if (t == NULL) - return NULL; + t = xzalloc(sizeof *t); t->margin = 32; t->width = 6; @@ -478,6 +480,10 @@ theme_create(void) void theme_destroy(struct theme *t) { +#ifdef HAVE_PANGO + if (t->pango_context) + g_object_unref(t->pango_context); +#endif cairo_surface_destroy(t->active_frame); cairo_surface_destroy(t->inactive_frame); cairo_surface_destroy(t->shadow); @@ -486,18 +492,28 @@ theme_destroy(struct theme *t) #ifdef HAVE_PANGO static PangoLayout * -create_layout(cairo_t *cr, const char *title) +create_layout(struct theme *t, cairo_t *cr, const char *title) { PangoLayout *layout; PangoFontDescription *desc; - layout = pango_cairo_create_layout(cr); + if (!t->pango_context) { + PangoFontMap *fontmap; + + fontmap = pango_cairo_font_map_new(); + t->pango_context = pango_font_map_create_context(fontmap); + g_object_unref(fontmap); + } + + pango_cairo_update_context(cr, t->pango_context); + layout = pango_layout_new(t->pango_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,7 +581,7 @@ theme_render_frame(struct theme *t, PangoLayout *title_layout; PangoRectangle logical; - title_layout = create_layout(cr, title); + title_layout = create_layout(t, cr, title); pango_layout_get_pixel_extents (title_layout, NULL, &logical); text_width = MIN(title_rect->width, logical.width); @@ -606,6 +622,10 @@ theme_render_frame(struct theme *t, cairo_set_source_rgb(cr, 0.4, 0.4, 0.4); SHOW_TEXT(cr); } + +#ifdef HAVE_PANGO + g_object_unref(title_layout); +#endif } } @@ -662,3 +682,23 @@ 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) +{ + /* some clients, particular weston-editor, still creates indirectly a + * new font map; this makes sure we untie that up and avoid an assert + * from cairo */ +#ifdef HAVE_PANGO + pango_cairo_font_map_set_default(NULL); +#endif + cairo_debug_reset_static_data(); +#ifdef HAVE_PANGO + FcFini(); +#endif +} diff --git a/shared/cairo-util.h b/shared/cairo-util.h index 6fd11f6bf..0926fc615 100644 --- a/shared/cairo-util.h +++ b/shared/cairo-util.h @@ -28,6 +28,9 @@ #include #include +#ifdef HAVE_PANGO +#include +#endif #include #include @@ -57,6 +60,9 @@ struct theme { int margin; int width; int titlebar_height; +#ifdef HAVE_PANGO + PangoContext *pango_context; +#endif }; struct theme * @@ -161,6 +167,14 @@ frame_width(struct frame *frame); int32_t frame_height(struct frame *frame); +void +frame_border_sizes(struct frame *frame, int32_t *top, int32_t *bottom, + int32_t *left, int32_t *right); + +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); @@ -227,7 +241,14 @@ frame_double_touch_down(struct frame *frame, void *data, int32_t id, void frame_double_touch_up(struct frame *frame, void *data, int32_t id); +/* May set FRAME_STATUS_REPAINT */ +enum theme_location +frame_tablet_tool_motion(struct frame *frame, void *pointer, int x, int y); + 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 ed120d502..755348c08 100644 --- a/shared/config-parser.c +++ b/shared/config-parser.c @@ -41,9 +41,16 @@ #include #include #include +#include #include "helpers.h" #include "string-helpers.h" +/** + * \defgroup weston-config Weston configuration + * + * Helper functions to read out ini configuration file. + */ + struct weston_config_entry { char *key; char *value; @@ -129,8 +136,10 @@ config_section_get_entry(struct weston_config_section *section, return NULL; } -WL_EXPORT -struct weston_config_section * +/** + * \ingroup weston-config + */ +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 +161,10 @@ weston_config_get_section(struct weston_config *config, const char *section, return NULL; } -WL_EXPORT -int +/** + * \ingroup weston-config + */ +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 +186,10 @@ weston_config_section_get_int(struct weston_config_section *section, return 0; } -WL_EXPORT -int +/** + * \ingroup weston-config + */ +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 +225,10 @@ weston_config_section_get_uint(struct weston_config_section *section, return 0; } -WL_EXPORT -int +/** + * \ingroup weston-config + */ +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 +265,10 @@ weston_config_section_get_color(struct weston_config_section *section, return 0; } -WL_EXPORT -int +/** + * \ingroup weston-config + */ +WL_EXPORT int weston_config_section_get_double(struct weston_config_section *section, const char *key, double *value, double default_value) @@ -276,8 +293,10 @@ weston_config_section_get_double(struct weston_config_section *section, return 0; } -WL_EXPORT -int +/** + * \ingroup weston-config + */ +WL_EXPORT int weston_config_section_get_string(struct weston_config_section *section, const char *key, char **value, const char *default_value) @@ -299,8 +318,10 @@ weston_config_section_get_string(struct weston_config_section *section, return 0; } -WL_EXPORT -int +/** + * \ingroup weston-config + */ +WL_EXPORT int weston_config_section_get_bool(struct weston_config_section *section, const char *key, bool *value, bool default_value) @@ -327,8 +348,10 @@ weston_config_section_get_bool(struct weston_config_section *section, return 0; } -WL_EXPORT -const char * +/** + * \ingroup weston-config + */ +WL_EXPORT const char * weston_config_get_name_from_env(void) { const char *name; @@ -340,6 +363,19 @@ weston_config_get_name_from_env(void) return "weston.ini"; } +WL_EXPORT +void +weston_config_set_env(struct weston_config_section *section) +{ + struct weston_config_entry *e; + + if (section == NULL) + return; + wl_list_for_each(e, §ion->entry_list, link) { + setenv(e->key, e->value, 1); + } +} + static struct weston_config_section * config_add_section(struct weston_config *config, const char *name) { @@ -389,42 +425,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 +444,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 +454,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 +471,83 @@ 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; +} + +/** + * \ingroup weston-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 +/** + * \ingroup weston-config + */ +WL_EXPORT int weston_config_next_section(struct weston_config *config, struct weston_config_section **section, const char **name) @@ -502,8 +570,10 @@ weston_config_next_section(struct weston_config *config, return 1; } -WL_EXPORT -void +/** + * \ingroup weston-config + */ +WL_EXPORT void weston_config_destroy(struct weston_config *config) { struct weston_config_section *s, *next_s; @@ -524,3 +594,37 @@ weston_config_destroy(struct weston_config *config) free(config); } + +/** + * \ingroup weston-config + */ +WL_EXPORT uint32_t +weston_config_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/frame.c b/shared/frame.c index e8a5cad62..8094b37c1 100644 --- a/shared/frame.c +++ b/shared/frame.c @@ -493,27 +493,48 @@ 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_border_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; +} +void +frame_decoration_sizes(struct frame *frame, int32_t *top, int32_t *bottom, + int32_t *left, int32_t *right) +{ + struct theme *t = frame->theme; + + frame_border_sizes(frame, top, bottom, left, right); + + if (frame->flags & FRAME_FLAG_MAXIMIZED) + return; - frame_resize(frame, width + decoration_width, - height + decoration_height); + /* 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_decoration_sizes(frame, &top, &bottom, &left, &right); + frame_resize(frame, width + left + right, + height + top + bottom); } int32_t @@ -994,6 +1015,44 @@ frame_double_touch_up(struct frame *frame, void *data, int32_t id) } } +enum theme_location +frame_tablet_tool_motion(struct frame *frame, void *data, int x, int y) +{ + struct frame_pointer *tool_pointer = frame_pointer_get(frame, data); + struct frame_button *button, + *prev_button = tool_pointer->hover_button; + enum theme_location location; + + location = theme_get_location(frame->theme, tool_pointer->x, + tool_pointer->y, frame->width, + frame->height, + frame->flags & FRAME_FLAG_MAXIMIZED ? + THEME_FRAME_MAXIMIZED : 0); + + if (!tool_pointer) + return location; + + tool_pointer->x = x; + tool_pointer->y = y; + + button = frame_find_button(frame, x, y); + + if (prev_button) { + if (prev_button == button) + /* The button hasn't changed so we're done here */ + return location; + else + frame_button_leave(prev_button, tool_pointer); + } + + if (button) + frame_button_enter(button); + + tool_pointer->hover_button = button; + + return location; +} + void frame_repaint(struct frame *frame, cairo_t *cr) { diff --git a/shared/hash.c b/shared/hash.c new file mode 100644 index 000000000..820e51fad --- /dev/null +++ b/shared/hash.c @@ -0,0 +1,309 @@ +/* + * Copyright © 2009 Intel Corporation + * Copyright © 1988-2004 Keith Packard and Bart Massey. + * + * 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. + * + * Except as contained in this notice, the names of the authors + * or their institutions shall not be used in advertising or + * otherwise to promote the sale, use or other dealings in this + * Software without prior written authorization from the + * authors. + * + * Authors: + * Eric Anholt + * Keith Packard + */ + +#include "config.h" + +#include +#include + +#include "hash.h" + +struct hash_entry { + uint32_t hash; + void *data; +}; + +struct hash_table { + struct hash_entry *table; + uint32_t size; + uint32_t rehash; + uint32_t max_entries; + uint32_t size_index; + uint32_t entries; + uint32_t deleted_entries; +}; + +#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) + +/* + * From Knuth -- a good choice for hash/rehash values is p, p-2 where + * p and p-2 are both prime. These tables are sized to have an extra 10% + * free to avoid exponential performance degradation as the hash table fills + */ + +static const uint32_t deleted_data; + +static const struct { + uint32_t max_entries, size, rehash; +} hash_sizes[] = { + { 2, 5, 3 }, + { 4, 7, 5 }, + { 8, 13, 11 }, + { 16, 19, 17 }, + { 32, 43, 41 }, + { 64, 73, 71 }, + { 128, 151, 149 }, + { 256, 283, 281 }, + { 512, 571, 569 }, + { 1024, 1153, 1151 }, + { 2048, 2269, 2267 }, + { 4096, 4519, 4517 }, + { 8192, 9013, 9011 }, + { 16384, 18043, 18041 }, + { 32768, 36109, 36107 }, + { 65536, 72091, 72089 }, + { 131072, 144409, 144407 }, + { 262144, 288361, 288359 }, + { 524288, 576883, 576881 }, + { 1048576, 1153459, 1153457 }, + { 2097152, 2307163, 2307161 }, + { 4194304, 4613893, 4613891 }, + { 8388608, 9227641, 9227639 }, + { 16777216, 18455029, 18455027 }, + { 33554432, 36911011, 36911009 }, + { 67108864, 73819861, 73819859 }, + { 134217728, 147639589, 147639587 }, + { 268435456, 295279081, 295279079 }, + { 536870912, 590559793, 590559791 }, + { 1073741824, 1181116273, 1181116271}, + { 2147483648ul, 2362232233ul, 2362232231ul} +}; + +static int +entry_is_free(struct hash_entry *entry) +{ + return entry->data == NULL; +} + +static int +entry_is_deleted(struct hash_entry *entry) +{ + return entry->data == &deleted_data; +} + +static int +entry_is_present(struct hash_entry *entry) +{ + return entry->data != NULL && entry->data != &deleted_data; +} + +struct hash_table * +hash_table_create(void) +{ + struct hash_table *ht; + + ht = malloc(sizeof(*ht)); + if (ht == NULL) + return NULL; + + ht->size_index = 0; + ht->size = hash_sizes[ht->size_index].size; + ht->rehash = hash_sizes[ht->size_index].rehash; + ht->max_entries = hash_sizes[ht->size_index].max_entries; + ht->table = calloc(ht->size, sizeof(*ht->table)); + ht->entries = 0; + ht->deleted_entries = 0; + + if (ht->table == NULL) { + free(ht); + return NULL; + } + + return ht; +} + +/** + * Frees the given hash table. + */ +void +hash_table_destroy(struct hash_table *ht) +{ + if (!ht) + return; + + free(ht->table); + free(ht); +} + +/** + * Finds a hash table entry with the given key and hash of that key. + * + * Returns NULL if no entry is found. Note that the data pointer may be + * modified by the user. + */ +static void * +hash_table_search(struct hash_table *ht, uint32_t hash) +{ + uint32_t hash_address; + + hash_address = hash % ht->size; + do { + uint32_t double_hash; + + struct hash_entry *entry = ht->table + hash_address; + + if (entry_is_free(entry)) { + return NULL; + } else if (entry_is_present(entry) && entry->hash == hash) { + return entry; + } + + double_hash = 1 + hash % ht->rehash; + + hash_address = (hash_address + double_hash) % ht->size; + } while (hash_address != hash % ht->size); + + return NULL; +} + +void +hash_table_for_each(struct hash_table *ht, + hash_table_iterator_func_t func, void *data) +{ + struct hash_entry *entry; + uint32_t i; + + for (i = 0; i < ht->size; i++) { + entry = ht->table + i; + if (entry_is_present(entry)) + func(entry->data, data); + } +} + +void * +hash_table_lookup(struct hash_table *ht, uint32_t hash) +{ + struct hash_entry *entry; + + entry = hash_table_search(ht, hash); + if (entry != NULL) + return entry->data; + + return NULL; +} + +static void +hash_table_rehash(struct hash_table *ht, unsigned int new_size_index) +{ + struct hash_table old_ht; + struct hash_entry *table, *entry; + + if (new_size_index >= ARRAY_SIZE(hash_sizes)) + return; + + table = calloc(hash_sizes[new_size_index].size, sizeof(*ht->table)); + if (table == NULL) + return; + + old_ht = *ht; + + ht->table = table; + ht->size_index = new_size_index; + ht->size = hash_sizes[ht->size_index].size; + ht->rehash = hash_sizes[ht->size_index].rehash; + ht->max_entries = hash_sizes[ht->size_index].max_entries; + ht->entries = 0; + ht->deleted_entries = 0; + + for (entry = old_ht.table; + entry != old_ht.table + old_ht.size; + entry++) { + if (entry_is_present(entry)) { + hash_table_insert(ht, entry->hash, entry->data); + } + } + + free(old_ht.table); +} + +/** + * Inserts the data with the given hash into the table. + * + * Note that insertion may rearrange the table on a resize or rehash, + * so previously found hash_entries are no longer valid after this function. + */ +int +hash_table_insert(struct hash_table *ht, uint32_t hash, void *data) +{ + uint32_t hash_address; + + if (ht->entries >= ht->max_entries) { + hash_table_rehash(ht, ht->size_index + 1); + } else if (ht->deleted_entries + ht->entries >= ht->max_entries) { + hash_table_rehash(ht, ht->size_index); + } + + hash_address = hash % ht->size; + do { + struct hash_entry *entry = ht->table + hash_address; + uint32_t double_hash; + + if (!entry_is_present(entry)) { + if (entry_is_deleted(entry)) + ht->deleted_entries--; + entry->hash = hash; + entry->data = data; + ht->entries++; + return 0; + } + + double_hash = 1 + hash % ht->rehash; + + hash_address = (hash_address + double_hash) % ht->size; + } while (hash_address != hash % ht->size); + + /* We could hit here if a required resize failed. An unchecked-malloc + * application could ignore this result. + */ + return -1; +} + +/** + * This function deletes the given hash table entry. + * + * Note that deletion doesn't otherwise modify the table, so an iteration over + * the table deleting entries is safe. + */ +void +hash_table_remove(struct hash_table *ht, uint32_t hash) +{ + struct hash_entry *entry; + + entry = hash_table_search(ht, hash); + if (entry != NULL) { + entry->data = (void *) &deleted_data; + ht->entries--; + ht->deleted_entries++; + } +} diff --git a/shared/hash.h b/shared/hash.h new file mode 100644 index 000000000..334d8f47a --- /dev/null +++ b/shared/hash.h @@ -0,0 +1,51 @@ +/* + * Copyright © 2009 Intel Corporation + * Copyright © 1988-2004 Keith Packard and Bart Massey. + * + * 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. + * + * Except as contained in this notice, the names of the authors + * or their institutions shall not be used in advertising or + * otherwise to promote the sale, use or other dealings in this + * Software without prior written authorization from the + * authors. + * + * Authors: + * Eric Anholt + * Keith Packard + */ + +#ifndef HASH_H +#define HASH_H + +#include + +struct hash_table; +struct hash_table *hash_table_create(void); +typedef void (*hash_table_iterator_func_t)(void *element, void *data); + +void hash_table_destroy(struct hash_table *ht); +void *hash_table_lookup(struct hash_table *ht, uint32_t hash); +int hash_table_insert(struct hash_table *ht, uint32_t hash, void *data); +void hash_table_remove(struct hash_table *ht, uint32_t hash); +void hash_table_for_each(struct hash_table *ht, + hash_table_iterator_func_t func, void *data); + +#endif diff --git a/shared/helpers.h b/shared/helpers.h index 1688b8ea7..872a78c0b 100644 --- a/shared/helpers.h +++ b/shared/helpers.h @@ -22,6 +22,8 @@ #ifndef WESTON_HELPERS_H #define WESTON_HELPERS_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -80,6 +82,19 @@ do { \ #define MAX(x,y) (((x) > (y)) ? (x) : (y)) #endif +/** + * Clips the value to the maximum to the first item provided. + * + * @param c the first item to compare. + * @param x the second item to compare. + * @param y the third item to compare. + * @return the value that evaluates to lesser than the maximum of + * the two other parameters. + */ +#ifndef CLIP +#define CLIP(c, x, y) MIN(MAX(c, x), y) +#endif + /** * Returns a pointer to the containing struct of a given member item. * @@ -159,6 +174,44 @@ 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"))) + +static inline uint64_t +u64_from_u32s(uint32_t hi, uint32_t lo) +{ + return ((uint64_t)hi << 32) + lo; +} + +#ifndef __has_builtin +# define __has_builtin(x) 0 +#endif + +#if defined(HAVE_UNREACHABLE) || __has_builtin(__builtin_unreachable) +#define unreachable(str) \ +do { \ + assert(!str); \ + __builtin_unreachable(); \ +} while (0) +#elif defined (_MSC_VER) +#define unreachable(str) \ +do { \ + assert(!str); \ + __assume(0); \ +} while (0) +#else +#define unreachable(str) assert(!str) +#endif + #ifdef __cplusplus } #endif diff --git a/shared/matrix.c b/shared/matrix.c index 4e8d6b40b..472f299a0 100644 --- a/shared/matrix.c +++ b/shared/matrix.c @@ -26,17 +26,13 @@ #include "config.h" +#include #include #include #include #include -#ifdef UNIT_TEST -#define WL_EXPORT -#else #include -#endif - #include @@ -65,16 +61,16 @@ weston_matrix_multiply(struct weston_matrix *m, const struct weston_matrix *n) { struct weston_matrix tmp; const float *row, *column; - div_t d; - int i, j; + int i, j, k; - for (i = 0; i < 16; i++) { - tmp.d[i] = 0; - d = div(i, 4); - row = m->d + d.quot * 4; - column = n->d + d.rem; - for (j = 0; j < 4; j++) - tmp.d[i] += row[j] * column[j * 4]; + for (i = 0; i < 4; i++) { + row = m->d + i * 4; + for (j = 0; j < 4; j++) { + tmp.d[4 * i + j] = 0; + column = n->d + j; + for (k = 0; k < 4; k++) + tmp.d[4 * i + j] += row[k] * column[k * 4]; + } } tmp.type = m->type | n->type; memcpy(m, &tmp, sizeof tmp); @@ -115,7 +111,8 @@ weston_matrix_rotate_xy(struct weston_matrix *matrix, float cos, float sin) /* v <- m * v */ WL_EXPORT void -weston_matrix_transform(struct weston_matrix *matrix, struct weston_vector *v) +weston_matrix_transform(const struct weston_matrix *matrix, + struct weston_vector *v) { int i, j; struct weston_vector t; @@ -129,6 +126,22 @@ weston_matrix_transform(struct weston_matrix *matrix, struct weston_vector *v) *v = t; } +WL_EXPORT struct weston_coord +weston_matrix_transform_coord(const struct weston_matrix *matrix, + struct weston_coord c) +{ + struct weston_coord out; + struct weston_vector t = { { c.x, c.y, 0.0, 1.0 } }; + + weston_matrix_transform(matrix, &t); + + assert(fabsf(t.f[3]) > 1e-6); + + out.x = t.f[0] / t.f[3]; + out.y = t.f[1] / t.f[3]; + return out; +} + static inline void swap_rows(double *a, double *b) { @@ -169,7 +182,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 +217,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. @@ -274,3 +287,296 @@ weston_matrix_invert(struct weston_matrix *inverse, return 0; } + +static bool +near_zero(float a) +{ + if (fabs(a) > 0.00001) + return false; + + return true; +} + +static float +get_el(const struct weston_matrix *matrix, int row, int col) +{ + assert(row >= 0 && row <= 3); + assert(col >= 0 && col <= 3); + + return matrix->d[col * 4 + row]; +} + +static bool +near_zero_at(const struct weston_matrix *matrix, int row, int col) +{ + return near_zero(get_el(matrix, row, col)); +} + +static bool +near_one_at(const struct weston_matrix *matrix, int row, int col) +{ + return near_zero(get_el(matrix, row, col) - 1.0); +} + +static bool +near_pm_one_at(const struct weston_matrix *matrix, int row, int col) +{ + return near_zero(fabs(get_el(matrix, row, col)) - 1.0); +} + +static bool +near_int_at(const struct weston_matrix *matrix, int row, int col) +{ + float el = get_el(matrix, row, col); + + return near_zero(roundf(el) - el); +} + +/* Lazy decompose the matrix to figure out whether its operations will + * cause an image to look ugly without some kind of filtering. + * + * while this is a 3D transformation matrix, we only concern ourselves + * with 2D for this test. We do use some small rounding to try to catch + * sequences of operations that lead back to a matrix that doesn't + * require filters. + * + * We assume the matrix won't be used to transform a vector with w != 1.0 + * + * Filtering will be necessary when: + * a non-integral translation is applied + * non-affine (perspective) translation is in use + * any scaling (other than -1) is in use + * a rotation that isn't a multiple of 90 degrees about Z is present + */ +WL_EXPORT bool +weston_matrix_needs_filtering(const struct weston_matrix *matrix) +{ + /* check for non-integral X/Y translation - ignore Z */ + if (!near_int_at(matrix, 0, 3) || + !near_int_at(matrix, 1, 3)) + return true; + + /* Any transform matrix that matches this will be non-affine. */ + if (!near_zero_at(matrix, 3, 0) || + !near_zero_at(matrix, 3, 1) || + !near_zero_at(matrix, 3, 2) || + !near_pm_one_at(matrix, 3, 3)) + return true; + + /* Check for anything that could come from a rotation that isn't + * around the Z axis: + * [ ? ? 0 ? ] + * [ ? ? 0 ? ] + * [ 0 0 ±1 ? ] + * [ ? ? ? 1 ] + * It's not clear that we'd realistically see a -1 in [2][2], but + * it wouldn't require filtering if we did, so allow it. + */ + if (!near_zero_at(matrix, 0, 2) || + !near_zero_at(matrix, 1, 2) || + !near_zero_at(matrix, 2, 0) || + !near_zero_at(matrix, 2, 1) || + !near_pm_one_at(matrix, 2, 2)) + return true; + + /* We've culled the low hanging fruit, now let's match the only + * matrices left we don't have to filter, before defaulting to + * filtering. + * + * These are a combination of testing rotation and scaling at once: */ + if (near_pm_one_at(matrix, 0, 0)) { + /* This could be a multiple of 90 degree rotation about Z, + * possibly with a flip, if the matrix is of the form: + * [ ±1 0 0 ? ] + * [ 0 ±1 0 ? ] + * [ 0 0 1 ? ] + * [ 0 0 0 1 ] + * Forcing ±1 excludes non-unity scale. + */ + if (near_zero_at(matrix, 1, 0) && + near_zero_at(matrix, 0, 1) && + near_pm_one_at(matrix, 1, 1)) + return false; + } + if (near_zero_at(matrix, 0, 0)) { + /* This could be a multiple of 90 degree rotation about Z, + * possibly with a flip, if the matrix is of the form: + * [ 0 ±1 0 ? ] + * [ ±1 0 0 ? ] + * [ 0 0 1 ? ] + * [ 0 0 0 1 ] + * Forcing ±1 excludes non-unity scale. + */ + if (near_zero_at(matrix, 1, 1) && + near_pm_one_at(matrix, 1, 0) && + near_pm_one_at(matrix, 0, 1)) + return false; + } + + /* The matrix wasn't "simple" enough to classify with dumb + * heuristics, so recommend filtering */ + return true; +} + +/** Examine a matrix to see if it applies a standard output transform. + * + * \param mat matrix to examine + * \param[out] transform the transform, if applicable + * \return true if a standard transform is present + + * Note that the check only considers rotations and flips. + * If any other scale or translation is present, those may have to + * be dealt with by the caller in some way. + */ +WL_EXPORT bool +weston_matrix_to_transform(const struct weston_matrix *mat, + enum wl_output_transform *transform) +{ + /* As a first pass we can eliminate any matrix that doesn't have + * zeroes in these positions: + * [ ? ? 0 ? ] + * [ ? ? 0 ? ] + * [ 0 0 ? ? ] + * [ 0 0 0 ? ] + * As they will be non-affine, or rotations about axes + * other than Z. + */ + if (!near_zero_at(mat, 2, 0) || + !near_zero_at(mat, 3, 0) || + !near_zero_at(mat, 2, 1) || + !near_zero_at(mat, 3, 1) || + !near_zero_at(mat, 0, 2) || + !near_zero_at(mat, 1, 2) || + !near_zero_at(mat, 3, 2)) + return false; + + /* Enforce the form: + * [ ? ? 0 ? ] + * [ ? ? 0 ? ] + * [ 0 0 ? ? ] + * [ 0 0 0 1 ] + * While we could scale all the elements by a constant to make + * 3,3 == 1, we choose to be lazy and not bother. A matrix + * that doesn't fit this form seems likely to be too complicated + * to pass the other checks. + */ + if (!near_one_at(mat, 3, 3)) + return false; + + if (near_zero_at(mat, 0, 0)) { + if (!near_zero_at(mat, 1, 1)) + return false; + + /* We now have a matrix like: + * [ 0 A 0 ? ] + * [ B 0 0 ? ] + * [ 0 0 ? ? ] + * [ 0 0 0 1 ] + * When transforming a vector with a matrix of this form, the X + * and Y coordinates are effectively exchanged, so we have a + * 90 or 270 degree rotation (not 0 or 180), and could have + * a flip depending on the signs of A and B. + * + * We don't require A and B to have the same absolute value, + * so there may be independent scales in the X or Y dimensions. + */ + if (get_el(mat, 0, 1) > 0) { + /* A is positive */ + + if (get_el(mat, 1, 0) > 0) + *transform = WL_OUTPUT_TRANSFORM_FLIPPED_90; + else + *transform = WL_OUTPUT_TRANSFORM_90; + } else { + /* A is negative */ + + if (get_el(mat, 1, 0) > 0) + *transform = WL_OUTPUT_TRANSFORM_270; + else + *transform = WL_OUTPUT_TRANSFORM_FLIPPED_270; + } + } else if (near_zero_at(mat, 1, 0)) { + if (!near_zero_at(mat, 0, 1)) + return false; + + /* We now have a matrix like: + * [ A 0 0 ? ] + * [ 0 B 0 ? ] + * [ 0 0 ? ? ] + * [ 0 0 0 1 ] + * This case won't exchange the X and Y inputs, so the + * transform is 0 or 180 degrees. We could have a flip + * depending on the signs of A and B. + * + * We don't require A and B to have the same absolute value, + * so there may be independent scales in the X or Y dimensions. + */ + if (get_el(mat, 0, 0) > 0) { + /* A is positive */ + + if (get_el(mat, 1, 1) > 0) + *transform = WL_OUTPUT_TRANSFORM_NORMAL; + else + *transform = WL_OUTPUT_TRANSFORM_FLIPPED_180; + } else { + /* A is negative */ + + if (get_el(mat, 1, 1) > 0) + *transform = WL_OUTPUT_TRANSFORM_FLIPPED; + else + *transform = WL_OUTPUT_TRANSFORM_180; + } + } else { + return false; + } + + return true; +} + +WL_EXPORT void +weston_matrix_init_transform(struct weston_matrix *matrix, + enum wl_output_transform transform, + int x, int y, int width, int height, + int scale) +{ + weston_matrix_init(matrix); + + weston_matrix_translate(matrix, -x, -y, 0); + + switch (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(matrix, -1, 1, 1); + weston_matrix_translate(matrix, width, 0, 0); + break; + default: + break; + } + + switch (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(matrix, 0, -1); + weston_matrix_translate(matrix, 0, width, 0); + break; + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + weston_matrix_rotate_xy(matrix, -1, 0); + weston_matrix_translate(matrix, + width, height, 0); + break; + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + weston_matrix_rotate_xy(matrix, 0, 1); + weston_matrix_translate(matrix, height, 0, 0); + break; + } + + weston_matrix_scale(matrix, scale, scale, 1); +} diff --git a/shared/meson.build b/shared/meson.build index 9a4af5306..86d46a030 100644 --- a/shared/meson.build +++ b/shared/meson.build @@ -4,9 +4,11 @@ srcs_libshared = [ 'signal.c', 'file-util.c', 'os-compatibility.c', - 'xalloc.c', + 'process-util.c', + 'hash.c', ] -deps_libshared = [dep_wayland_client, dep_wayland_server] +deps_libshared = [dep_wayland_client, dep_wayland_server, + dep_pixman, deps_for_libweston_users] lib_libshared = static_library( 'shared', @@ -22,6 +24,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 +60,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 f687f92f9..a9d91c525 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 6aaa26884..85f65854a 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 000000000..fe895d234 --- /dev/null +++ b/shared/process-util.c @@ -0,0 +1,271 @@ +/* + * 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++) { + if (s->fds[i] >= 0) + 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 000000000..aa35c7768 --- /dev/null +++ b/shared/process-util.h @@ -0,0 +1,95 @@ +/* + * 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); + +#define FDSTR_INIT ((struct fdstr){ { 0 }, { -1, -1 }}) + +/** + * 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 e7174b21a..302cfa812 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/timespec-util.h b/shared/timespec-util.h index f79969bb7..f76b9537c 100644 --- a/shared/timespec-util.h +++ b/shared/timespec-util.h @@ -30,6 +30,7 @@ #include #include #include +#include #define NSEC_PER_SEC 1000000000 @@ -216,7 +217,7 @@ static inline void timespec_from_proto(struct timespec *a, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { - a->tv_sec = ((uint64_t)tv_sec_hi << 32) + tv_sec_lo; + a->tv_sec = u64_from_u32s(tv_sec_hi, tv_sec_lo); a->tv_nsec = tv_nsec; } diff --git a/shared/weston-drm-fourcc.h b/shared/weston-drm-fourcc.h index 41b86ed87..0a013f791 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 cd39dd8b3..a2747f9cd 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 000000000..260dd5215 --- /dev/null +++ b/shared/xcb-xwayland.c @@ -0,0 +1,162 @@ +/* + * 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) }, + { "WL_SURFACE_SERIAL", F(wl_surface_serial) }, + { "_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 000000000..f1044ebcc --- /dev/null +++ b/shared/xcb-xwayland.h @@ -0,0 +1,108 @@ +/* + * 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 wl_surface_serial; + 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/subprojects/.gitignore b/subprojects/.gitignore new file mode 100644 index 000000000..532b5ce63 --- /dev/null +++ b/subprojects/.gitignore @@ -0,0 +1,2 @@ +/display-info + diff --git a/subprojects/aml.wrap b/subprojects/aml.wrap new file mode 100644 index 000000000..cb77c5059 --- /dev/null +++ b/subprojects/aml.wrap @@ -0,0 +1,4 @@ +[wrap-git] +directory = aml +url = https://github.com/any1/aml.git +revision = v0.3.0 diff --git a/subprojects/display-info.wrap b/subprojects/display-info.wrap new file mode 100644 index 000000000..8787b700f --- /dev/null +++ b/subprojects/display-info.wrap @@ -0,0 +1,5 @@ +[wrap-git] +directory = display-info +url = https://gitlab.freedesktop.org/emersion/libdisplay-info.git +revision = 0.1.1 + diff --git a/subprojects/neatvnc.wrap b/subprojects/neatvnc.wrap new file mode 100644 index 000000000..50f914a40 --- /dev/null +++ b/subprojects/neatvnc.wrap @@ -0,0 +1,4 @@ +[wrap-git] +directory = neatvnc +url = https://github.com/any1/neatvnc.git +revision = v0.6.0 diff --git a/tests/alpha-blending-test.c b/tests/alpha-blending-test.c index e2916be97..99ea8e57d 100644 --- a/tests/alpha-blending-test.c +++ b/tests/alpha-blending-test.c @@ -27,14 +27,16 @@ #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 { struct fixture_metadata meta; - enum renderer_type renderer; + enum weston_renderer_type renderer; bool color_management; }; @@ -43,17 +45,17 @@ static const int BLOCK_WIDTH = 3; static const struct setup_args my_setup_args[] = { { - .renderer = RENDERER_PIXMAN, + .renderer = WESTON_RENDERER_PIXMAN, .color_management = false, .meta.name = "pixman" }, { - .renderer = RENDERER_GL, + .renderer = WESTON_RENDERER_GL, .color_management = false, .meta.name = "GL" }, { - .renderer = RENDERER_GL, + .renderer = WESTON_RENDERER_GL, .color_management = true, .meta.name = "GL sRGB EOTF" }, @@ -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); @@ -416,9 +346,9 @@ TEST(alpha_blend) /* attach, damage, commit background window */ move_client(client, 0, 0); - shot = capture_screenshot_of_output(client); + shot = capture_screenshot_of_output(client, NULL); assert(shot); - match = verify_image(shot, "alpha_blend", seq_no, NULL, seq_no); + match = verify_image(shot->image, "alpha_blend", seq_no, NULL, seq_no); assert(check_blend_pattern(bg, fg, shot, space)); assert(match); diff --git a/tests/bad-buffer-test.c b/tests/bad-buffer-test.c index 31f72cb84..6d966d9b3 100644 --- a/tests/bad-buffer-test.c +++ b/tests/bad-buffer-test.c @@ -42,6 +42,7 @@ fixture_setup(struct weston_test_harness *harness) struct compositor_setup setup; compositor_setup_defaults(&setup); + setup.shell = SHELL_TEST_DESKTOP; return weston_test_harness_execute_as_client(harness, &setup); } diff --git a/tests/buffer-transforms-test.c b/tests/buffer-transforms-test.c index ca52e2456..f7ec51e11 100644 --- a/tests/buffer-transforms-test.c +++ b/tests/buffer-transforms-test.c @@ -35,14 +35,14 @@ #define TRANSFORM(x) WL_OUTPUT_TRANSFORM_ ## x, #x #define RENDERERS(s, t) \ { \ - .renderer = RENDERER_PIXMAN, \ + .renderer = WESTON_RENDERER_PIXMAN, \ .scale = s, \ .transform = WL_OUTPUT_TRANSFORM_ ## t, \ .transform_name = #t, \ .meta.name = "pixman " #s " " #t, \ }, \ { \ - .renderer = RENDERER_GL, \ + .renderer = WESTON_RENDERER_GL, \ .scale = s, \ .transform = WL_OUTPUT_TRANSFORM_ ## t, \ .transform_name = #t, \ @@ -51,7 +51,7 @@ struct setup_args { struct fixture_metadata meta; - enum renderer_type renderer; + enum weston_renderer_type renderer; int scale; enum wl_output_transform transform; const char *transform_name; @@ -143,7 +143,7 @@ TEST_P(buffer_transform, my_buffer_args) bargs->transform); move_client(client, 19, 19); - match = verify_screen_content(client, refname, 0, NULL, 0); + match = verify_screen_content(client, refname, 0, NULL, 0, NULL); assert(match); client_destroy(client); diff --git a/tests/color-icc-output-test.c b/tests/color-icc-output-test.c new file mode 100644 index 000000000..076a360de --- /dev/null +++ b/tests/color-icc-output-test.c @@ -0,0 +1,927 @@ +/* + * 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; + + /** + * VCGT tag exponents for each channel. If any is zeroed, we ignore + * the VCGT tag. + */ + double vcgt_exponents[COLOR_CHAN_NUM]; +}; + +static const struct setup_args my_setup_args[] = { + /* name, ref img, pipeline, tolerance, dim, profile type, clut tolerance, vcgt_exponents */ + { { "sRGB->sRGB MAT" }, 0, &pipeline_sRGB, 0.0, 0, PTYPE_MATRIX_SHAPER }, + { { "sRGB->sRGB MAT VCGT" }, 3, &pipeline_sRGB, 0.8, 0, PTYPE_MATRIX_SHAPER, 0.0000, {1.1, 1.2, 1.3} }, + { { "sRGB->adobeRGB MAT" }, 1, &pipeline_adobeRGB, 1.4, 0, PTYPE_MATRIX_SHAPER }, + { { "sRGB->adobeRGB MAT VCGT" }, 4, &pipeline_adobeRGB, 1.0, 0, PTYPE_MATRIX_SHAPER, 0.0000, {1.1, 1.2, 1.3} }, + { { "sRGB->BT2020 MAT" }, 2, &pipeline_BT2020, 4.5, 0, PTYPE_MATRIX_SHAPER }, + { { "sRGB->sRGB CLUT" }, 0, &pipeline_sRGB, 0.0, 17, PTYPE_CLUT, 0.0005 }, + { { "sRGB->sRGB CLUT VCGT" }, 3, &pipeline_sRGB, 0.9, 17, PTYPE_CLUT, 0.0005, {1.1, 1.2, 1.3} }, + { { "sRGB->adobeRGB CLUT" }, 1, &pipeline_adobeRGB, 1.8, 17, PTYPE_CLUT, 0.0065 }, + { { "sRGB->adobeRGB CLUT VCGT" }, 4, &pipeline_adobeRGB, 1.1, 17, PTYPE_CLUT, 0.0065, {1.1, 1.2, 1.3} }, +}; + +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; + + assert(dim_size); + + cLUT_stage = cmsStageAllocCLutFloat(context_id, dim_size, 3, 3, NULL); + cmsStageSampleCLutFloat(cLUT_stage, sampler_matrix, (void *)mat, 0); + + return cLUT_stage; +} + +static void +vcgt_tag_add_to_profile(cmsContext context_id, cmsHPROFILE profile, + const double vcgt_exponents[COLOR_CHAN_NUM]) +{ + cmsToneCurve *vcgt_tag_curves[COLOR_CHAN_NUM]; + unsigned int i; + + if (!should_include_vcgt(vcgt_exponents)) + return; + + for (i = 0; i < COLOR_CHAN_NUM; i++) + vcgt_tag_curves[i] = cmsBuildGamma(context_id, vcgt_exponents[i]); + + assert(cmsWriteTag(profile, cmsSigVcgtTag, vcgt_tag_curves)); + + cmsFreeToneCurveTriple(vcgt_tag_curves); +} + +/* + * 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); + + vcgt_tag_add_to_profile(context_id, hRGB, arg->vcgt_exponents); + + 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 setup_args *arg) +{ + cmsToneCurve *arr_curves[3]; + cmsHPROFILE hRGB; + int type_inverse_tone_curve; + double inverse_tone_curve_param[5]; + + assert(find_tone_curve_type(arg->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, + &arg->pipeline->prim_output, arr_curves); + assert(hRGB); + + vcgt_tag_add_to_profile(context_id, hRGB, arg->vcgt_exponents); + + 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); + 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 = WESTON_RENDERER_GL; + setup.backend = WESTON_BACKEND_HEADLESS; + setup.width = WINDOW_WIDTH; + setup.height = WINDOW_HEIGHT; + setup.shell = SHELL_TEST_DESKTOP; + setup.logging_scopes = "log,color-lcms-profiles,color-lcms-transformations,color-lcms-optimizer"; + + file_name = build_output_icc_profile(arg); + if (!file_name) + return RESULT_HARD_ERROR; + + weston_ini_setup(&setup, + cfgln("[core]"), + cfgln("output-decorations=true"), + 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(arg->meta.name); +#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, + arg->vcgt_exponents, + &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. + * + * This tests particularly the chain of input-to-blend followed by + * blend-to-output categories of color transformations. + */ +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, NULL); + assert(shot); + + match = verify_image(shot->image, "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, + const double vcgt_exponents[COLOR_CHAN_NUM], + 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); + + if (should_include_vcgt(vcgt_exponents)) + for (i = 0; i < COLOR_CHAN_NUM; i++) + ref.rgb[i] = pow(ref.rgb[i], vcgt_exponents[i]); + + 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(arg->meta.name); +#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, arg->vcgt_exponents, 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, NULL); + assert(shot); + match = verify_image(shot->image, "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 */ +} + +/* + * Test that output decorations have the expected colors. + * + * This is the only way to test input-to-output category of color + * transformations. They are used only for output decorations and some other + * debug-like features. The input color space is hardcoded to sRGB in the + * compositor. + * + * Because the output decorations are drawn with Cairo, we do not have an + * easy access to the ground-truth image and so do not check the results + * against a reference formula. + */ +TEST(output_icc_decorations) +{ + int seq_no = get_test_fixture_index(); + const struct setup_args *arg = &my_setup_args[seq_no]; + struct client *client; + struct buffer *shot; + pixman_image_t *img; + bool match; + + client = create_client(); + + shot = client_capture_output(client, client->output, + WESTON_CAPTURE_V1_SOURCE_FULL_FRAMEBUFFER); + img = image_convert_to_a8r8g8b8(shot->image); + + match = verify_image(img, "output-icc-decorations", + arg->ref_image_index, NULL, seq_no); + assert(match); + + pixman_image_unref(img); + buffer_destroy(shot); + client_destroy(client); +} diff --git a/tests/color-manager-test.c b/tests/color-manager-test.c index 2045573e2..c095541ff 100644 --- a/tests/color-manager-test.c +++ b/tests/color-manager-test.c @@ -34,7 +34,7 @@ fixture_setup(struct weston_test_harness *harness) struct compositor_setup setup; compositor_setup_defaults(&setup); - setup.renderer = RENDERER_GL; + setup.renderer = WESTON_RENDERER_GL; setup.shell = SHELL_TEST_DESKTOP; weston_ini_setup(&setup, diff --git a/tests/color-metadata-errors-test.c b/tests/color-metadata-errors-test.c new file mode 100644 index 000000000..61ce33def --- /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 000000000..d24928c1f --- /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 = WESTON_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 52250781d..5db692e5a 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) +{ + o = ensure_unit_range(o); + return pow(o, 1./2.4); +} + +float +apply_tone_curve(enum transfer_fn fn, float r) { - cf->r = sRGB_EOTF_inv(cf->r); - cf->g = sRGB_EOTF_inv(cf->g); - cf->b = sRGB_EOTF_inv(cf->b); + 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,303 @@ 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; +} + +bool +should_include_vcgt(const double vcgt_exponents[COLOR_CHAN_NUM]) +{ + unsigned int i; + + for (i = 0; i < COLOR_CHAN_NUM; i++) + if (vcgt_exponents[i] == 0.0) + return false; + + return true; +} + +void +process_pixel_using_pipeline(enum transfer_fn pre_curve, + const struct lcmsMAT3 *mat, + enum transfer_fn post_curve, + const double vcgt_exponents[COLOR_CHAN_NUM], + const struct color_float *in, + struct color_float *out) +{ + struct color_float cf; + unsigned i; + + cf = color_float_apply_curve(pre_curve, *in); + cf = color_float_apply_matrix(mat, cf); + cf = color_float_apply_curve(post_curve, cf); + + if (should_include_vcgt(vcgt_exponents)) + for (i = 0; i < COLOR_CHAN_NUM; i++) + cf.rgb[i] = pow(cf.rgb[i], vcgt_exponents[i]); + + *out = 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 a9cfd02d3..46817dfc1 100644 --- a/tests/color_util.h +++ b/tests/color_util.h @@ -24,19 +24,176 @@ * 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); + +bool +should_include_vcgt(const double vcgt_exponents[COLOR_CHAN_NUM]); + +void +process_pixel_using_pipeline(enum transfer_fn pre_curve, + const struct lcmsMAT3 *mat, + enum transfer_fn post_curve, + const double vcgt_exponents[COLOR_CHAN_NUM], + 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 583c83f2a..33ad5d0b3 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 000000000..3a5161907 --- /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/devices-test.c b/tests/devices-test.c index 719f45950..e84273ccf 100644 --- a/tests/devices-test.c +++ b/tests/devices-test.c @@ -36,6 +36,8 @@ fixture_setup(struct weston_test_harness *harness) compositor_setup_defaults(&setup); + setup.shell = SHELL_TEST_DESKTOP; + return weston_test_harness_execute_as_client(harness, &setup); } DECLARE_FIXTURE_SETUP(fixture_setup); diff --git a/tests/drm-smoke-test.c b/tests/drm-smoke-test.c index d42ddf266..10e4bd2ea 100644 --- a/tests/drm-smoke-test.c +++ b/tests/drm-smoke-test.c @@ -36,7 +36,7 @@ fixture_setup(struct weston_test_harness *harness) compositor_setup_defaults(&setup); setup.shell = SHELL_TEST_DESKTOP; setup.backend = WESTON_BACKEND_DRM; - setup.renderer = RENDERER_PIXMAN; + setup.renderer = WESTON_RENDERER_PIXMAN; return weston_test_harness_execute_as_client(harness, &setup); } @@ -90,7 +90,7 @@ TEST(drm_screenshot_no_damage) { */ for (i = 0; i < 5; i++) { ret = verify_screen_content(client, "drm_screenshot_no_damage", - 0, NULL, i); + 0, NULL, i, "Virtual-1"); assert(ret); } diff --git a/tests/drm-writeback-screenshot-test.c b/tests/drm-writeback-screenshot-test.c new file mode 100644 index 000000000..d7ea5d4b5 --- /dev/null +++ b/tests/drm-writeback-screenshot-test.c @@ -0,0 +1,150 @@ +/* + * Copyright © 2023 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 "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" +#include "weston-output-capture-client-protocol.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.backend = WESTON_BACKEND_DRM; + setup.renderer = WESTON_RENDERER_PIXMAN; + setup.shell = SHELL_TEST_DESKTOP; + + weston_ini_setup(&setup, + cfgln("[shell]"), + cfgln("startup-animation=%s", "none")); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +static void +draw_stuff(pixman_image_t *image) +{ + int w, h; + int stride; /* bytes */ + 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); + + assert(PIXMAN_FORMAT_BPP(fmt) == 32); + + for (x = 0; x < w; x++) + for (y = 0; y < h; y++) { + b = x; + g = x + y; + r = y; + pixel = pixels + (y * stride / 4) + x; + *pixel = (255U << 24) | (r << 16) | (g << 8) | b; + } +} + +TEST(drm_writeback_screenshot) { + + struct client *client; + struct buffer *buffer; + struct buffer *screenshot = NULL; + pixman_image_t *reference = NULL; + pixman_image_t *diffimg = NULL; + struct wl_surface *surface; + struct rectangle clip; + const char *fname; + bool match; + int frame; + + /* create client */ + testlog("Creating client for test\n"); + client = create_client_and_test_surface(100, 100, 100, 100); + assert(client); + surface = client->surface->wl_surface; + + /* move pointer away from image so it does not interfere with the + * comparison of the writeback screenshot with the reference image */ + weston_test_move_pointer(client->test->weston_test, 0, 1, 0, 0, 0); + + buffer = create_shm_buffer_a8r8g8b8(client, 100, 100); + draw_stuff(buffer->image); + + wl_surface_attach(surface, buffer->proxy, 0, 0); + wl_surface_damage(surface, 0, 0, 100, 100); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait(client, &frame); + + /* take screenshot */ + testlog("Taking a screenshot\n"); + screenshot = client_capture_output(client, client->output, + WESTON_CAPTURE_V1_SOURCE_WRITEBACK); + assert(screenshot); + + /* take another screenshot; this is important to ensure the + * writeback state machine is working correctly */ + testlog("Taking another screenshot\n"); + screenshot = client_capture_output(client, client->output, + WESTON_CAPTURE_V1_SOURCE_WRITEBACK); + assert(screenshot); + + /* load reference image */ + fname = screenshot_reference_filename("drm-writeback-screenshot", 0); + testlog("Loading good reference image %s\n", fname); + reference = load_image_from_png(fname); + assert(reference); + + /* check if they match - only the colored square matters, so the + * clip is used to ignore the background */ + clip.x = 100; + clip.y = 100; + clip.width = 100; + clip.height = 100; + match = check_images_match(screenshot->image, reference, &clip, NULL); + testlog("Screenshot %s reference image\n", match? "equal to" : "different from"); + if (!match) { + diffimg = visualize_image_difference(screenshot->image, reference, &clip, NULL); + fname = screenshot_output_filename("drm-writeback-screenshot-error", 0); + write_image_as_png(diffimg, fname); + pixman_image_unref(diffimg); + } + + pixman_image_unref(reference); + buffer_destroy(screenshot); + client_destroy(client); + + assert(match); +} \ No newline at end of file diff --git a/tests/event-test.c b/tests/event-test.c index c2290b10a..e19404110 100644 --- a/tests/event-test.c +++ b/tests/event-test.c @@ -35,6 +35,7 @@ fixture_setup(struct weston_test_harness *harness) struct compositor_setup setup; compositor_setup_defaults(&setup); + setup.shell = SHELL_TEST_DESKTOP; return weston_test_harness_execute_as_client(harness, &setup); } diff --git a/tests/image-iter.h b/tests/image-iter.h new file mode 100644 index 000000000..ab9b08303 --- /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 e6e2962fa..e3b6a54b4 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 @@ -38,7 +39,7 @@ fixture_setup(struct weston_test_harness *harness) struct compositor_setup setup; compositor_setup_defaults(&setup); - setup.renderer = RENDERER_PIXMAN; + setup.renderer = WESTON_RENDERER_PIXMAN; setup.width = 320; setup.height = 240; setup.shell = SHELL_DESKTOP; @@ -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) @@ -127,7 +118,7 @@ TEST(internal_screenshot) /* Take a snapshot. Result will be in screenshot->wl_buffer. */ testlog("Taking a screenshot\n"); - screenshot = capture_screenshot_of_output(client); + screenshot = capture_screenshot_of_output(client, NULL); assert(screenshot); /* Load good reference image */ diff --git a/tests/ivi-layout-internal-test.c b/tests/ivi-layout-internal-test.c index 8f2d6be87..85a873ffa 100644 --- a/tests/ivi-layout-internal-test.c +++ b/tests/ivi-layout-internal-test.c @@ -85,40 +85,6 @@ iassert_fail(const char *cond, const char *file, int line, * These are all internal ivi_layout API tests that do not require * any client objects. */ -static void -test_surface_bad_visibility(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->surface_set_visibility(NULL, true) == IVI_FAILED); - - lyt->commit_changes(); -} - -static void -test_surface_bad_destination_rectangle(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->surface_set_destination_rectangle(NULL, 20, 30, 200, 300) == IVI_FAILED); -} - -static void -test_surface_bad_source_rectangle(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->surface_set_source_rectangle(NULL, 20, 30, 200, 300) == IVI_FAILED); -} - -static void -test_surface_bad_properties(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->get_properties_of_surface(NULL) == NULL); -} - static void test_layer_create(struct test_context *ctx) { @@ -158,7 +124,7 @@ test_layer_visibility(struct test_context *ctx) iassert(prop->visibility == false); - iassert(lyt->layer_set_visibility(ivilayer, true) == IVI_SUCCEEDED); + lyt->layer_set_visibility(ivilayer, true); iassert(prop->visibility == false); @@ -208,8 +174,8 @@ test_layer_dimension(struct test_context *ctx) iassert(prop->dest_width == 200); iassert(prop->dest_height == 300); - iassert(lyt->layer_set_destination_rectangle(ivilayer, prop->dest_x, prop->dest_y, - 400, 600) == IVI_SUCCEEDED); + lyt->layer_set_destination_rectangle(ivilayer, prop->dest_x, prop->dest_y, + 400, 600); iassert(prop->dest_width == 200); iassert(prop->dest_height == 300); @@ -236,8 +202,8 @@ test_layer_position(struct test_context *ctx) iassert(prop->dest_x == 0); iassert(prop->dest_y == 0); - iassert(lyt->layer_set_destination_rectangle(ivilayer, 20, 30, - prop->dest_width, prop->dest_height) == IVI_SUCCEEDED); + lyt->layer_set_destination_rectangle(ivilayer, 20, 30, + prop->dest_width, prop->dest_height); iassert(prop->dest_x == 0); iassert(prop->dest_y == 0); @@ -267,8 +233,7 @@ test_layer_destination_rectangle(struct test_context *ctx) iassert(prop->dest_x == 0); iassert(prop->dest_y == 0); - iassert(lyt->layer_set_destination_rectangle( - ivilayer, 20, 30, 400, 600) == IVI_SUCCEEDED); + lyt->layer_set_destination_rectangle(ivilayer, 20, 30, 400, 600); prop = lyt->get_properties_of_layer(ivilayer); iassert(prop->dest_width == 200); @@ -302,8 +267,7 @@ test_layer_source_rectangle(struct test_context *ctx) iassert(prop->source_x == 0); iassert(prop->source_y == 0); - iassert(lyt->layer_set_source_rectangle( - ivilayer, 20, 30, 400, 600) == IVI_SUCCEEDED); + lyt->layer_set_source_rectangle(ivilayer, 20, 30, 400, 600); prop = lyt->get_properties_of_layer(ivilayer); iassert(prop->source_width == 200); @@ -322,23 +286,6 @@ test_layer_source_rectangle(struct test_context *ctx) lyt->layer_destroy(ivilayer); } -static void -test_layer_bad_remove(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - lyt->layer_destroy(NULL); -} - -static void -test_layer_bad_visibility(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->layer_set_visibility(NULL, true) == IVI_FAILED); - - lyt->commit_changes(); -} - static void test_layer_bad_opacity(struct test_context *ctx) { @@ -349,9 +296,6 @@ test_layer_bad_opacity(struct test_context *ctx) ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); iassert(ivilayer != NULL); - iassert(lyt->layer_set_opacity( - NULL, wl_fixed_from_double(0.3)) == IVI_FAILED); - iassert(lyt->layer_set_opacity( ivilayer, wl_fixed_from_double(0.3)) == IVI_SUCCEEDED); @@ -370,40 +314,11 @@ test_layer_bad_opacity(struct test_context *ctx) iassert(prop->opacity == wl_fixed_from_double(0.3)); - iassert(lyt->layer_set_opacity( - NULL, wl_fixed_from_double(0.5)) == IVI_FAILED); - lyt->commit_changes(); lyt->layer_destroy(ivilayer); } -static void -test_layer_bad_destination_rectangle(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->layer_set_destination_rectangle( - NULL, 20, 30, 200, 300) == IVI_FAILED); -} - -static void -test_layer_bad_source_rectangle(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->layer_set_source_rectangle( - NULL, 20, 30, 200, 300) == IVI_FAILED); -} - -static void -test_layer_bad_properties(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->get_properties_of_layer(NULL) == NULL); -} - static void test_commit_changes_after_visibility_set_layer_destroy(struct test_context *ctx) { @@ -413,7 +328,7 @@ test_commit_changes_after_visibility_set_layer_destroy(struct test_context *ctx) ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); iassert(ivilayer != NULL); - iassert(lyt->layer_set_visibility(ivilayer, true) == IVI_SUCCEEDED); + lyt->layer_set_visibility(ivilayer, true); lyt->layer_destroy(ivilayer); lyt->commit_changes(); } @@ -442,8 +357,7 @@ test_commit_changes_after_source_rectangle_set_layer_destroy(struct test_context ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); iassert(ivilayer != NULL); - iassert(lyt->layer_set_source_rectangle( - ivilayer, 20, 30, 200, 300) == IVI_SUCCEEDED); + lyt->layer_set_source_rectangle( ivilayer, 20, 30, 200, 300); lyt->layer_destroy(ivilayer); lyt->commit_changes(); } @@ -457,8 +371,7 @@ test_commit_changes_after_destination_rectangle_set_layer_destroy(struct test_co ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); iassert(ivilayer != NULL); - iassert(lyt->layer_set_destination_rectangle( - ivilayer, 20, 30, 200, 300) == IVI_SUCCEEDED); + lyt->layer_set_destination_rectangle(ivilayer, 20, 30, 200, 300); lyt->layer_destroy(ivilayer); lyt->commit_changes(); } @@ -524,11 +437,11 @@ test_screen_render_order(struct test_context *ctx) for (i = 0; i < LAYER_NUM; i++) ivilayers[i] = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(i), 200, 300); - iassert(lyt->screen_set_render_order(output, ivilayers, LAYER_NUM) == IVI_SUCCEEDED); + lyt->screen_set_render_order(output, ivilayers, LAYER_NUM); lyt->commit_changes(); - iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); + lyt->get_layers_on_screen(output, &length, &array); iassert(length == LAYER_NUM); for (i = 0; i < LAYER_NUM; i++) iassert(array[i] == ivilayers[i]); @@ -538,11 +451,11 @@ test_screen_render_order(struct test_context *ctx) array = NULL; - iassert(lyt->screen_set_render_order(output, NULL, 0) == IVI_SUCCEEDED); + lyt->screen_set_render_order(output, NULL, 0); lyt->commit_changes(); - iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); + lyt->get_layers_on_screen(output, &length, &array); iassert(length == 0 && array == NULL); for (i = 0; i < LAYER_NUM; i++) @@ -551,39 +464,6 @@ test_screen_render_order(struct test_context *ctx) #undef LAYER_NUM } -static void -test_screen_bad_render_order(struct test_context *ctx) -{ -#define LAYER_NUM (3) - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct weston_output *output; - struct ivi_layout_layer *ivilayers[LAYER_NUM] = {}; - struct ivi_layout_layer **array; - int32_t length = 0; - uint32_t i; - - if (!iassert(!wl_list_empty(&ctx->compositor->output_list))) - return; - - output = wl_container_of(ctx->compositor->output_list.next, output, link); - - for (i = 0; i < LAYER_NUM; i++) - ivilayers[i] = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(i), 200, 300); - - iassert(lyt->screen_set_render_order(NULL, ivilayers, LAYER_NUM) == IVI_FAILED); - - lyt->commit_changes(); - - iassert(lyt->get_layers_on_screen(NULL, &length, &array) == IVI_FAILED); - iassert(lyt->get_layers_on_screen(output, NULL, &array) == IVI_FAILED); - iassert(lyt->get_layers_on_screen(output, &length, NULL) == IVI_FAILED); - - for (i = 0; i < LAYER_NUM; i++) - lyt->layer_destroy(ivilayers[i]); - -#undef LAYER_NUM -} - static void test_screen_add_layers(struct test_context *ctx) { @@ -602,12 +482,12 @@ test_screen_add_layers(struct test_context *ctx) for (i = 0; i < LAYER_NUM; i++) { ivilayers[i] = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(i), 200, 300); - iassert(lyt->screen_add_layer(output, ivilayers[i]) == IVI_SUCCEEDED); + lyt->screen_add_layer(output, ivilayers[i]); } lyt->commit_changes(); - iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); + lyt->get_layers_on_screen(output, &length, &array); iassert(length == LAYER_NUM); for (i = 0; i < (uint32_t)length; i++) iassert(array[i] == ivilayers[i]); @@ -617,13 +497,13 @@ test_screen_add_layers(struct test_context *ctx) array = NULL; - iassert(lyt->screen_set_render_order(output, NULL, 0) == IVI_SUCCEEDED); + lyt->screen_set_render_order(output, NULL, 0); for (i = LAYER_NUM; i-- > 0;) - iassert(lyt->screen_add_layer(output, ivilayers[i]) == IVI_SUCCEEDED); + lyt->screen_add_layer(output, ivilayers[i]); lyt->commit_changes(); - iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); + lyt->get_layers_on_screen(output, &length, &array); iassert(length == LAYER_NUM); for (i = 0; i < (uint32_t)length; i++) iassert(array[i] == ivilayers[LAYER_NUM - (i + 1)]); @@ -654,14 +534,14 @@ test_screen_remove_layer(struct test_context *ctx) output = wl_container_of(ctx->compositor->output_list.next, output, link); - iassert(lyt->screen_add_layer(output, ivilayer) == IVI_SUCCEEDED); + lyt->screen_add_layer(output, ivilayer); lyt->commit_changes(); - iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); + lyt->get_layers_on_screen(output, &length, &array); iassert(length == 1); iassert(array[0] == ivilayer); - iassert(lyt->screen_remove_layer(output, ivilayer) == IVI_SUCCEEDED); + lyt->screen_remove_layer(output, ivilayer); lyt->commit_changes(); if (length > 0) @@ -669,40 +549,13 @@ test_screen_remove_layer(struct test_context *ctx) array = NULL; - iassert(lyt->get_layers_on_screen(output, &length, &array) == IVI_SUCCEEDED); + lyt->get_layers_on_screen(output, &length, &array); iassert(length == 0); iassert(array == NULL); lyt->layer_destroy(ivilayer); } -static void -test_screen_bad_remove_layer(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - struct weston_output *output; - - if (wl_list_empty(&ctx->compositor->output_list)) - return; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - iassert(ivilayer != NULL); - - output = wl_container_of(ctx->compositor->output_list.next, output, link); - - iassert(lyt->screen_remove_layer(NULL, ivilayer) == IVI_FAILED); - lyt->commit_changes(); - - iassert(lyt->screen_remove_layer(output, NULL) == IVI_FAILED); - lyt->commit_changes(); - - iassert(lyt->screen_remove_layer(NULL, NULL) == IVI_FAILED); - lyt->commit_changes(); - - lyt->layer_destroy(ivilayer); -} - static void test_commit_changes_after_render_order_set_layer_destroy( @@ -722,7 +575,7 @@ test_commit_changes_after_render_order_set_layer_destroy( for (i = 0; i < LAYER_NUM; i++) ivilayers[i] = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(i), 200, 300); - iassert(lyt->screen_set_render_order(output, ivilayers, LAYER_NUM) == IVI_SUCCEEDED); + lyt->screen_set_render_order(output, ivilayers, LAYER_NUM); lyt->layer_destroy(ivilayers[1]); @@ -764,22 +617,20 @@ test_layer_properties_changed_notification(struct test_context *ctx) ctx->layer_property_changed.notify = test_layer_properties_changed_notification_callback; - iassert(lyt->layer_add_listener(ivilayer, &ctx->layer_property_changed) == IVI_SUCCEEDED); + lyt->layer_add_listener(ivilayer, &ctx->layer_property_changed); lyt->commit_changes(); iassert(ctx->user_flags == 0); - iassert(lyt->layer_set_destination_rectangle( - ivilayer, 20, 30, 200, 300) == IVI_SUCCEEDED); + lyt->layer_set_destination_rectangle(ivilayer, 20, 30, 200, 300); lyt->commit_changes(); iassert(ctx->user_flags == 1); ctx->user_flags = 0; - iassert(lyt->layer_set_destination_rectangle( - ivilayer, 20, 30, 200, 300) == IVI_SUCCEEDED); + lyt->layer_set_destination_rectangle(ivilayer, 20, 30, 200, 300); lyt->commit_changes(); @@ -826,7 +677,7 @@ test_layer_create_notification(struct test_context *ctx) ctx->user_flags = 0; ctx->layer_created.notify = test_layer_create_notification_callback; - iassert(lyt->add_listener_create_layer(&ctx->layer_created) == IVI_SUCCEEDED); + lyt->add_listener_create_layer(&ctx->layer_created); ivilayers[0] = lyt->layer_create_with_dimension(layers[0], 200, 300); iassert(ctx->user_flags == 1); @@ -876,7 +727,7 @@ test_layer_remove_notification(struct test_context *ctx) ctx->layer_removed.notify = test_layer_remove_notification_callback; ivilayers[0] = lyt->layer_create_with_dimension(layers[0], 200, 300); - iassert(lyt->add_listener_remove_layer(&ctx->layer_removed) == IVI_SUCCEEDED); + lyt->add_listener_remove_layer(&ctx->layer_removed); lyt->layer_destroy(ivilayers[0]); iassert(ctx->user_flags == 1); @@ -892,77 +743,11 @@ test_layer_remove_notification(struct test_context *ctx) #undef LAYER_NUM } -static void -test_layer_bad_properties_changed_notification_callback(struct wl_listener *listener, void *data) -{ -} - -static void -test_layer_bad_properties_changed_notification(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - - ctx->layer_property_changed.notify = test_layer_bad_properties_changed_notification_callback; - - iassert(lyt->layer_add_listener(NULL, &ctx->layer_property_changed) == IVI_FAILED); - iassert(lyt->layer_add_listener(ivilayer, NULL) == IVI_FAILED); - - lyt->layer_destroy(ivilayer); -} - -static void -test_surface_bad_configure_notification(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->add_listener_configure_surface(NULL) == IVI_FAILED); -} - -static void -test_layer_bad_create_notification(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->add_listener_create_layer(NULL) == IVI_FAILED); -} - -static void -test_surface_bad_create_notification(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->add_listener_create_surface(NULL) == IVI_FAILED); -} - -static void -test_layer_bad_remove_notification(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->add_listener_remove_layer(NULL) == IVI_FAILED); -} - -static void -test_surface_bad_remove_notification(struct test_context *ctx) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - - iassert(lyt->add_listener_remove_surface(NULL) == IVI_FAILED); -} - /************************ tests end ********************************/ static void run_internal_tests(struct test_context *ctx) { - test_surface_bad_visibility(ctx); - test_surface_bad_destination_rectangle(ctx); - test_surface_bad_source_rectangle(ctx); - test_surface_bad_properties(ctx); - test_layer_create(ctx); test_layer_visibility(ctx); test_layer_opacity(ctx); @@ -970,12 +755,7 @@ run_internal_tests(struct test_context *ctx) test_layer_position(ctx); test_layer_destination_rectangle(ctx); test_layer_source_rectangle(ctx); - test_layer_bad_remove(ctx); - test_layer_bad_visibility(ctx); test_layer_bad_opacity(ctx); - test_layer_bad_destination_rectangle(ctx); - test_layer_bad_source_rectangle(ctx); - test_layer_bad_properties(ctx); test_commit_changes_after_visibility_set_layer_destroy(ctx); test_commit_changes_after_opacity_set_layer_destroy(ctx); test_commit_changes_after_source_rectangle_set_layer_destroy(ctx); @@ -984,21 +764,13 @@ run_internal_tests(struct test_context *ctx) test_get_layer_after_destory_layer(ctx); test_screen_render_order(ctx); - test_screen_bad_render_order(ctx); test_screen_add_layers(ctx); test_screen_remove_layer(ctx); - test_screen_bad_remove_layer(ctx); test_commit_changes_after_render_order_set_layer_destroy(ctx); test_layer_properties_changed_notification(ctx); test_layer_create_notification(ctx); test_layer_remove_notification(ctx); - test_layer_bad_properties_changed_notification(ctx); - test_surface_bad_configure_notification(ctx); - test_layer_bad_create_notification(ctx); - test_surface_bad_create_notification(ctx); - test_layer_bad_remove_notification(ctx); - test_surface_bad_remove_notification(ctx); } PLUGIN_TEST(ivi_layout_internal) diff --git a/tests/ivi-layout-test-client.c b/tests/ivi-layout-test-client.c index 9ac9a4dd2..b49701cc8 100644 --- a/tests/ivi-layout-test-client.c +++ b/tests/ivi-layout-test-client.c @@ -205,7 +205,6 @@ const char * const basic_test_names[] = { "surface_source_rectangle", "surface_bad_opacity", "surface_properties_changed_notification", - "surface_bad_properties_changed_notification", "surface_on_many_layer", }; @@ -218,7 +217,6 @@ const char * const surface_property_commit_changes_test_names[] = { const char * const render_order_test_names[] = { "layer_render_order", - "layer_bad_render_order", "layer_add_surfaces", }; diff --git a/tests/ivi-layout-test-plugin.c b/tests/ivi-layout-test-plugin.c index 75ce1436d..9f0ecafb9 100644 --- a/tests/ivi-layout-test-plugin.c +++ b/tests/ivi-layout-test-plugin.c @@ -53,7 +53,7 @@ struct runner_test { static void runner_func_##name(struct test_context *); \ \ const struct runner_test runner_test_##name \ - __attribute__ ((section ("plugin_test_section"))) = \ + __attribute__ ((used, section ("plugin_test_section"))) = \ { \ #name, runner_func_##name \ }; \ @@ -323,14 +323,12 @@ RUNNER_TEST(surface_visibility) { const struct ivi_layout_interface *lyt = ctx->layout_interface; struct ivi_layout_surface *ivisurf; - int32_t ret; const struct ivi_layout_surface_properties *prop; ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); runner_assert(ivisurf); - ret = lyt->surface_set_visibility(ivisurf, true); - runner_assert(ret == IVI_SUCCEEDED); + lyt->surface_set_visibility(ivisurf, true); lyt->commit_changes(); @@ -375,9 +373,8 @@ RUNNER_TEST(surface_dimension) runner_assert(prop->dest_width == 1); runner_assert(prop->dest_height == 1); - runner_assert(IVI_SUCCEEDED == - lyt->surface_set_destination_rectangle(ivisurf, prop->dest_x, - prop->dest_y, 200, 300)); + lyt->surface_set_destination_rectangle(ivisurf, prop->dest_x, + prop->dest_y, 200, 300); runner_assert(prop->dest_width == 1); runner_assert(prop->dest_height == 1); @@ -404,9 +401,9 @@ RUNNER_TEST(surface_position) runner_assert(prop->dest_x == 0); runner_assert(prop->dest_y == 0); - runner_assert(lyt->surface_set_destination_rectangle( - ivisurf, 20, 30, - prop->dest_width, prop->dest_height) == IVI_SUCCEEDED); + lyt->surface_set_destination_rectangle(ivisurf, 20, 30, + prop->dest_width, + prop->dest_height); runner_assert(prop->dest_x == 0); runner_assert(prop->dest_y == 0); @@ -435,8 +432,7 @@ RUNNER_TEST(surface_destination_rectangle) runner_assert(prop->dest_x == 0); runner_assert(prop->dest_y == 0); - runner_assert(lyt->surface_set_destination_rectangle( - ivisurf, 20, 30, 200, 300) == IVI_SUCCEEDED); + lyt->surface_set_destination_rectangle(ivisurf, 20, 30, 200, 300); prop = lyt->get_properties_of_surface(ivisurf); runner_assert_or_return(prop); @@ -471,8 +467,7 @@ RUNNER_TEST(surface_source_rectangle) runner_assert(prop->source_x == 0); runner_assert(prop->source_y == 0); - runner_assert(lyt->surface_set_source_rectangle( - ivisurf, 20, 30, 200, 300) == IVI_SUCCEEDED); + lyt->surface_set_source_rectangle(ivisurf, 20, 30, 200, 300); prop = lyt->get_properties_of_surface(ivisurf); runner_assert_or_return(prop); @@ -500,9 +495,6 @@ RUNNER_TEST(surface_bad_opacity) ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); runner_assert(ivisurf != NULL); - runner_assert(lyt->surface_set_opacity( - NULL, wl_fixed_from_double(0.3)) == IVI_FAILED); - runner_assert(lyt->surface_set_opacity( ivisurf, wl_fixed_from_double(0.3)) == IVI_SUCCEEDED); @@ -521,9 +513,6 @@ RUNNER_TEST(surface_bad_opacity) runner_assert(prop->opacity == wl_fixed_from_double(0.3)); - runner_assert(lyt->surface_set_opacity( - NULL, wl_fixed_from_double(0.5)) == IVI_FAILED); - lyt->commit_changes(); } @@ -542,14 +531,12 @@ RUNNER_TEST(surface_on_many_layer) for (i = 0; i < IVI_TEST_LAYER_COUNT; i++) { ivilayers[i] = lyt->layer_create_with_dimension( IVI_TEST_LAYER_ID(i), 200, 300); - runner_assert(lyt->layer_add_surface( - ivilayers[i], ivisurf) == IVI_SUCCEEDED); + lyt->layer_add_surface(ivilayers[i], ivisurf); } lyt->commit_changes(); - runner_assert(lyt->get_layers_under_surface( - ivisurf, &length, &array) == IVI_SUCCEEDED); + lyt->get_layers_under_surface(ivisurf, &length, &array); runner_assert(IVI_TEST_LAYER_COUNT == length); for (i = 0; i < IVI_TEST_LAYER_COUNT; i++) runner_assert(array[i] == ivilayers[i]); @@ -564,8 +551,7 @@ RUNNER_TEST(surface_on_many_layer) lyt->commit_changes(); - runner_assert(lyt->get_layers_under_surface( - ivisurf, &length, &array) == IVI_SUCCEEDED); + lyt->get_layers_under_surface( ivisurf, &length, &array); runner_assert(length == 0 && array == NULL); for (i = 0; i < IVI_TEST_LAYER_COUNT; i++) @@ -586,8 +572,7 @@ RUNNER_TEST(commit_changes_after_visibility_set_surface_destroy) ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); runner_assert(ivisurf != NULL); - runner_assert(lyt->surface_set_visibility( - ivisurf, true) == IVI_SUCCEEDED); + lyt->surface_set_visibility(ivisurf, true); } RUNNER_TEST(commit_changes_after_opacity_set_surface_destroy) @@ -608,8 +593,7 @@ RUNNER_TEST(commit_changes_after_source_rectangle_set_surface_destroy) ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); runner_assert(ivisurf != NULL); - runner_assert(lyt->surface_set_source_rectangle( - ivisurf, 20, 30, 200, 300) == IVI_SUCCEEDED); + lyt->surface_set_source_rectangle( ivisurf, 20, 30, 200, 300); } RUNNER_TEST(commit_changes_after_destination_rectangle_set_surface_destroy) @@ -619,8 +603,7 @@ RUNNER_TEST(commit_changes_after_destination_rectangle_set_surface_destroy) ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); runner_assert(ivisurf != NULL); - runner_assert(lyt->surface_set_destination_rectangle( - ivisurf, 20, 30, 200, 300) == IVI_SUCCEEDED); + lyt->surface_set_destination_rectangle( ivisurf, 20, 30, 200, 300); } RUNNER_TEST(get_surface_after_destroy_surface) @@ -646,13 +629,11 @@ RUNNER_TEST(layer_render_order) for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) ivisurfs[i] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(i)); - runner_assert(lyt->layer_set_render_order( - ivilayer, ivisurfs, IVI_TEST_SURFACE_COUNT) == IVI_SUCCEEDED); + lyt->layer_set_render_order( ivilayer, ivisurfs, IVI_TEST_SURFACE_COUNT); lyt->commit_changes(); - runner_assert(lyt->get_surfaces_on_layer( - ivilayer, &length, &array) == IVI_SUCCEEDED); + lyt->get_surfaces_on_layer(ivilayer, &length, &array); runner_assert(IVI_TEST_SURFACE_COUNT == length); for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) runner_assert(array[i] == ivisurfs[i]); @@ -660,15 +641,13 @@ RUNNER_TEST(layer_render_order) if (length > 0) free(array); - runner_assert(lyt->layer_set_render_order( - ivilayer, NULL, 0) == IVI_SUCCEEDED); + lyt->layer_set_render_order(ivilayer, NULL, 0); array = NULL; lyt->commit_changes(); - runner_assert(lyt->get_surfaces_on_layer( - ivilayer, &length, &array) == IVI_SUCCEEDED); + lyt->get_surfaces_on_layer(ivilayer, &length, &array); runner_assert(length == 0 && array == NULL); lyt->layer_destroy(ivilayer); @@ -688,13 +667,11 @@ RUNNER_TEST(test_layer_render_order_destroy_one_surface_p1) for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) ivisurfs[i] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(i)); - runner_assert(lyt->layer_set_render_order( - ivilayer, ivisurfs, IVI_TEST_SURFACE_COUNT) == IVI_SUCCEEDED); + lyt->layer_set_render_order(ivilayer, ivisurfs, IVI_TEST_SURFACE_COUNT); lyt->commit_changes(); - runner_assert(lyt->get_surfaces_on_layer( - ivilayer, &length, &array) == IVI_SUCCEEDED); + lyt->get_surfaces_on_layer(ivilayer, &length, &array); runner_assert(IVI_TEST_SURFACE_COUNT == length); for (i = 0; i < length; i++) runner_assert(array[i] == ivisurfs[i]); @@ -716,8 +693,7 @@ RUNNER_TEST(test_layer_render_order_destroy_one_surface_p2) ivisurfs[0] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); ivisurfs[1] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(2)); - runner_assert(lyt->get_surfaces_on_layer( - ivilayer, &length, &array) == IVI_SUCCEEDED); + lyt->get_surfaces_on_layer(ivilayer, &length, &array); runner_assert(2 == length); for (i = 0; i < length; i++) runner_assert(array[i] == ivisurfs[i]); @@ -728,40 +704,6 @@ RUNNER_TEST(test_layer_render_order_destroy_one_surface_p2) lyt->layer_destroy(ivilayer); } -RUNNER_TEST(layer_bad_render_order) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_layer *ivilayer; - struct ivi_layout_surface *ivisurfs[IVI_TEST_SURFACE_COUNT] = {}; - struct ivi_layout_surface **array = NULL; - int32_t length = 0; - uint32_t i; - - ivilayer = lyt->layer_create_with_dimension(IVI_TEST_LAYER_ID(0), 200, 300); - - for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) - ivisurfs[i] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(i)); - - runner_assert(lyt->layer_set_render_order( - NULL, ivisurfs, IVI_TEST_SURFACE_COUNT) == IVI_FAILED); - - lyt->commit_changes(); - - runner_assert(lyt->get_surfaces_on_layer( - NULL, &length, &array) == IVI_FAILED); - runner_assert(length == 0 && array == NULL); - - runner_assert(lyt->get_surfaces_on_layer( - ivilayer, NULL, &array) == IVI_FAILED); - runner_assert(array == NULL); - - runner_assert(lyt->get_surfaces_on_layer( - ivilayer, &length, NULL) == IVI_FAILED); - runner_assert(length == 0); - - lyt->layer_destroy(ivilayer); -} - RUNNER_TEST(layer_add_surfaces) { const struct ivi_layout_interface *lyt = ctx->layout_interface; @@ -775,14 +717,12 @@ RUNNER_TEST(layer_add_surfaces) for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) { ivisurfs[i] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(i)); - runner_assert(lyt->layer_add_surface( - ivilayer, ivisurfs[i]) == IVI_SUCCEEDED); + lyt->layer_add_surface(ivilayer, ivisurfs[i]); } lyt->commit_changes(); - runner_assert(lyt->get_surfaces_on_layer( - ivilayer, &length, &array) == IVI_SUCCEEDED); + lyt->get_surfaces_on_layer(ivilayer, &length, &array); runner_assert(IVI_TEST_SURFACE_COUNT == length); for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) runner_assert(array[i] == ivisurfs[i]); @@ -790,17 +730,14 @@ RUNNER_TEST(layer_add_surfaces) if (length > 0) free(array); - runner_assert(lyt->layer_set_render_order( - ivilayer, NULL, 0) == IVI_SUCCEEDED); + lyt->layer_set_render_order(ivilayer, NULL, 0); for (i = IVI_TEST_SURFACE_COUNT; i-- > 0;) - runner_assert(lyt->layer_add_surface( - ivilayer, ivisurfs[i]) == IVI_SUCCEEDED); + lyt->layer_add_surface(ivilayer, ivisurfs[i]); lyt->commit_changes(); - runner_assert(lyt->get_surfaces_on_layer( - ivilayer, &length, &array) == IVI_SUCCEEDED); + lyt->get_surfaces_on_layer(ivilayer, &length, &array); runner_assert(IVI_TEST_SURFACE_COUNT == length); for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) runner_assert(array[i] == ivisurfs[IVI_TEST_SURFACE_COUNT - (i + 1)]); @@ -823,8 +760,7 @@ RUNNER_TEST(commit_changes_after_render_order_set_surface_destroy) for (i = 0; i < IVI_TEST_SURFACE_COUNT; i++) ivisurfs[i] = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(i)); - runner_assert(lyt->layer_set_render_order( - ivilayer, ivisurfs, IVI_TEST_SURFACE_COUNT) == IVI_SUCCEEDED); + lyt->layer_set_render_order(ivilayer, ivisurfs, IVI_TEST_SURFACE_COUNT); } RUNNER_TEST(cleanup_layer) @@ -864,23 +800,20 @@ RUNNER_TEST(surface_properties_changed_notification) ctx->surface_property_changed.notify = test_surface_properties_changed_notification_callback; - runner_assert(lyt->surface_add_listener( - ivisurf, &ctx->surface_property_changed) == IVI_SUCCEEDED); + lyt->surface_add_listener(ivisurf, &ctx->surface_property_changed); lyt->commit_changes(); runner_assert(ctx->user_flags == 0); - runner_assert(lyt->surface_set_destination_rectangle( - ivisurf, 20, 30, 200, 300) == IVI_SUCCEEDED); + lyt->surface_set_destination_rectangle(ivisurf, 20, 30, 200, 300); lyt->commit_changes(); runner_assert(ctx->user_flags == 1); ctx->user_flags = 0; - runner_assert(lyt->surface_set_destination_rectangle( - ivisurf, 20, 30, 200, 300) == IVI_SUCCEEDED); + lyt->surface_set_destination_rectangle(ivisurf, 20, 30, 200, 300); lyt->commit_changes(); @@ -889,8 +822,7 @@ RUNNER_TEST(surface_properties_changed_notification) // remove surface property changed listener. wl_list_remove(&ctx->surface_property_changed.link); ctx->user_flags = 0; - runner_assert(lyt->surface_set_destination_rectangle( - ivisurf, 40, 50, 400, 500) == IVI_SUCCEEDED); + lyt->surface_set_destination_rectangle(ivisurf, 40, 50, 400, 500); lyt->commit_changes(); @@ -916,7 +848,7 @@ RUNNER_TEST(surface_configure_notification_p1) const struct ivi_layout_interface *lyt = ctx->layout_interface; ctx->surface_configured.notify = test_surface_configure_notification_callback; - runner_assert(IVI_SUCCEEDED == lyt->add_listener_configure_surface(&ctx->surface_configured)); + lyt->add_listener_configure_surface(&ctx->surface_configured); lyt->commit_changes(); ctx->user_flags = 0; @@ -958,8 +890,7 @@ RUNNER_TEST(surface_create_notification_p1) const struct ivi_layout_interface *lyt = ctx->layout_interface; ctx->surface_created.notify = test_surface_create_notification_callback; - runner_assert(lyt->add_listener_create_surface( - &ctx->surface_created) == IVI_SUCCEEDED); + lyt->add_listener_create_surface(&ctx->surface_created); ctx->user_flags = 0; } @@ -997,8 +928,7 @@ RUNNER_TEST(surface_remove_notification_p1) const struct ivi_layout_interface *lyt = ctx->layout_interface; ctx->surface_removed.notify = test_surface_remove_notification_callback; - runner_assert(lyt->add_listener_remove_surface(&ctx->surface_removed) - == IVI_SUCCEEDED); + lyt->add_listener_remove_surface(&ctx->surface_removed); ctx->user_flags = 0; } @@ -1016,24 +946,3 @@ RUNNER_TEST(surface_remove_notification_p3) { runner_assert(ctx->user_flags == 0); } - -static void -test_surface_bad_properties_changed_notification_callback(struct wl_listener *listener, void *data) -{ -} - -RUNNER_TEST(surface_bad_properties_changed_notification) -{ - const struct ivi_layout_interface *lyt = ctx->layout_interface; - struct ivi_layout_surface *ivisurf; - - ivisurf = lyt->get_surface_from_id(IVI_TEST_SURFACE_ID(0)); - runner_assert(ivisurf != NULL); - - ctx->surface_property_changed.notify = test_surface_bad_properties_changed_notification_callback; - - runner_assert(lyt->surface_add_listener( - NULL, &ctx->surface_property_changed) == IVI_FAILED); - runner_assert(lyt->surface_add_listener( - ivisurf, NULL) == IVI_FAILED); -} diff --git a/tests/keyboard-test.c b/tests/keyboard-test.c index 01ffa0f8a..9ccbfd06f 100644 --- a/tests/keyboard-test.c +++ b/tests/keyboard-test.c @@ -38,6 +38,7 @@ fixture_setup(struct weston_test_harness *harness) struct compositor_setup setup; compositor_setup_defaults(&setup); + setup.shell = SHELL_TEST_DESKTOP; return weston_test_harness_execute_as_client(harness, &setup); } diff --git a/tests/lcms-util-test.c b/tests/lcms-util-test.c new file mode 100644 index 000000000..e7d8b69ee --- /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 000000000..4e3046342 --- /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/tests/lcms_util.h b/tests/lcms_util.h new file mode 100644 index 000000000..8808b6416 --- /dev/null +++ b/tests/lcms_util.h @@ -0,0 +1,39 @@ +/* + * 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 "color_util.h" + +cmsToneCurve * +build_MPE_curve(cmsContext ctx, enum transfer_fn fn); + +cmsStage * +build_MPE_curve_stage(cmsContext context_id, enum transfer_fn fn); + +cmsBool +SetTextTags(cmsHPROFILE hProfile, const wchar_t* Description); diff --git a/tests/linux-explicit-synchronization-test.c b/tests/linux-explicit-synchronization-test.c index 4d3bffabd..ae3e897d8 100644 --- a/tests/linux-explicit-synchronization-test.c +++ b/tests/linux-explicit-synchronization-test.c @@ -40,10 +40,12 @@ fixture_setup(struct weston_test_harness *harness) compositor_setup_defaults(&setup); + setup.shell = SHELL_TEST_DESKTOP; + /* We need to use the pixman renderer, since a few of the tests depend * on the renderer holding onto a surface buffer until the next one * is committed, which the noop renderer doesn't do. */ - setup.renderer = RENDERER_PIXMAN; + setup.renderer = WESTON_RENDERER_PIXMAN; return weston_test_harness_execute_as_client(harness, &setup); } diff --git a/tests/matrix-test.c b/tests/matrix-test.c index 03b92162b..11c840daa 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/matrix-transform-test.c b/tests/matrix-transform-test.c new file mode 100644 index 000000000..19dfdff37 --- /dev/null +++ b/tests/matrix-transform-test.c @@ -0,0 +1,464 @@ +/* + * 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 "libweston-internal.h" +#include "libweston/matrix.h" + +#include "weston-test-client-helper.h" + +static void +transform_expect(struct weston_matrix *a, bool valid, enum wl_output_transform ewt) +{ + enum wl_output_transform wt; + + assert(weston_matrix_to_transform(a, &wt) == valid); + if (valid) + assert(wt == ewt); +} + +TEST(transformation_matrix) +{ + struct weston_matrix a, b; + int i; + + weston_matrix_init(&a); + weston_matrix_init(&b); + + weston_matrix_multiply(&a, &b); + assert(a.type == 0); + + /* Make b a matrix that rotates a surface on the x,y plane by 90 + * degrees counter-clockwise */ + weston_matrix_rotate_xy(&b, 0, -1); + assert(b.type == WESTON_MATRIX_TRANSFORM_ROTATE); + for (i = 0; i < 10; i++) { + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_90); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_180); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_270); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + } + + weston_matrix_init(&b); + /* Make b a matrix that rotates a surface on the x,y plane by 45 + * degrees counter-clockwise. This should alternate between a + * standard transform and a rotation that fails to match any + * known rotations. */ + weston_matrix_rotate_xy(&b, cos(-M_PI / 4.0), sin(-M_PI / 4.0)); + assert(b.type == WESTON_MATRIX_TRANSFORM_ROTATE); + for (i = 0; i < 10; i++) { + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, false, 0); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_90); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, false, 0); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_180); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, false, 0); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_270); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, false, 0); + + weston_matrix_multiply(&a, &b); + assert(a.type == WESTON_MATRIX_TRANSFORM_ROTATE); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + } + + weston_matrix_init(&b); + /* Make b a matrix that rotates a surface on the x,y plane by 45 + * degrees counter-clockwise. This should alternate between a + * standard transform and a rotation that fails to match any known + * rotations. */ + weston_matrix_rotate_xy(&b, cos(-M_PI / 4.0), sin(-M_PI / 4.0)); + /* Flip a */ + weston_matrix_scale(&a, -1.0, 1.0, 1.0); + for (i = 0; i < 10; i++) { + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + /* Since we're not translated or scaled, any matrix that + * matches a standard wl_output_transform should not need + * filtering when used to transform images - but any + * matrix that fails to match will. */ + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED_90); + assert(!weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED_180); + assert(!weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED_270); + assert(!weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED); + assert(!weston_matrix_needs_filtering(&a)); + } + + weston_matrix_init(&a); + /* Flip a around Y*/ + weston_matrix_scale(&a, 1.0, -1.0, 1.0); + for (i = 0; i < 100; i++) { + /* Throw some arbitrary translation in here to make sure it + * doesn't have any impact. */ + weston_matrix_translate(&a, 31.0, -25.0, 0.0); + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED_270); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED_90); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED_180); + } + + /* Scale shouldn't matter, as long as it's positive */ + weston_matrix_scale(&a, 4.0, 3.0, 1.0); + /* Invert b so it rotates the opposite direction, go back the other way. */ + weston_matrix_invert(&b, &b); + for (i = 0; i < 100; i++) { + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED_90); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED_270); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, false, 0); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_FLIPPED_180); + assert(weston_matrix_needs_filtering(&a)); + } + + /* Flipping Y should return us from here to normal */ + weston_matrix_scale(&a, 1.0, -1.0, 1.0); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + + weston_matrix_init(&a); + weston_matrix_init(&b); + weston_matrix_translate(&b, 0.5, -0.75, 0); + /* Crawl along with translations, 0.5 and .75 will both hit an integer multiple + * at the same time every 4th step, so assert that only the 4th steps don't need + * filtering */ + for (i = 0; i < 100; i++) { + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + assert(weston_matrix_needs_filtering(&a)); + + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + assert(!weston_matrix_needs_filtering(&a)); + } + + weston_matrix_init(&b); + weston_matrix_scale(&b, 1.5, 2.0, 1.0); + for (i = 0; i < 10; i++) { + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + assert(weston_matrix_needs_filtering(&a)); + } + weston_matrix_invert(&b, &b); + for (i = 0; i < 9; i++) { + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + assert(weston_matrix_needs_filtering(&a)); + } + /* Last step should bring us back to a matrix that doesn't need + * a filter */ + weston_matrix_multiply(&a, &b); + transform_expect(&a, true, WL_OUTPUT_TRANSFORM_NORMAL); + assert(!weston_matrix_needs_filtering(&a)); +} + +static void +simple_weston_surface_prepare(struct weston_surface *surf, + int buffer_width, int buffer_height, + int surface_width, int surface_height, + int scale, uint32_t transform, + int src_x, int src_y, + int src_width, int src_height) +{ + struct weston_buffer_viewport vp = { + .buffer = { + .transform = transform, + .scale = scale, + .src_x = wl_fixed_from_int(src_x), + .src_y = wl_fixed_from_int(src_y), + .src_width = wl_fixed_from_int(src_width), + .src_height = wl_fixed_from_int(src_height), + }, + .surface = { + .width = surface_width, + .height = surface_height, + }, + }; + surf->buffer_viewport = vp; + convert_size_by_transform_scale(&surf->width_from_buffer, + &surf->height_from_buffer, + buffer_width, + buffer_height, + transform, + scale); + weston_surface_build_buffer_matrix(surf, + &surf->surface_to_buffer_matrix); + weston_matrix_invert(&surf->buffer_to_surface_matrix, + &surf->surface_to_buffer_matrix); +} + +static void +surface_test_all_transforms(struct weston_surface *surf, + int buffer_width, int buffer_height, + int surface_width, int surface_height, + int scale, int src_x, int src_y, + int src_width, int src_height) +{ + int transform; + + for (transform = WL_OUTPUT_TRANSFORM_NORMAL; + transform <= WL_OUTPUT_TRANSFORM_FLIPPED_270; transform++) { + simple_weston_surface_prepare(surf, + buffer_width, buffer_height, + surface_width, surface_height, + scale, transform, + src_x, src_y, + src_width, src_height); + transform_expect(&surf->surface_to_buffer_matrix, + true, transform); + } +} + +TEST(surface_matrix_to_standard_transform) +{ + struct weston_surface surf; + int scale; + + for (scale = 1; scale < 8; scale++) { + /* A simple case */ + surface_test_all_transforms(&surf, 500, 700, -1, -1, scale, + 0, 0, 500, 700); + /* Translate the source corner */ + surface_test_all_transforms(&surf, 500, 700, -1, -1, scale, + 70, 20, 500, 700); + /* Get some scaling (and fractional translation) in there */ + surface_test_all_transforms(&surf, 723, 300, 512, 77, scale, + 120, 10, 200, 200); + } +} + +static void +simple_weston_output_prepare(struct weston_output *output, + int x, int y, int width, int height, + int scale, uint32_t transform) +{ + output->x = x; + output->y = y; + output->width = width; + output->height = height; + output->current_scale = scale; + output->transform = transform; + wl_list_init(&output->paint_node_list); + weston_output_update_matrix(output); +} + +static struct weston_vector +simple_transform_vector(struct weston_output *output, struct weston_vector in) +{ + struct weston_vector out = in; + int scale = output->current_scale; + + switch (output->transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + out.f[0] = (-output->x + in.f[0]) * scale; + out.f[1] = (-output->y + in.f[1]) * scale; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + out.f[0] = (output->x + output->width - in.f[0]) * scale; + out.f[1] = (-output->y + in.f[1]) * scale; + break; + case WL_OUTPUT_TRANSFORM_90: + out.f[0] = (-output->y + in.f[1]) * scale; + out.f[1] = (output->x + output->width - in.f[0]) * scale; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + out.f[0] = (-output->y + in.f[1]) * scale; + out.f[1] = (-output->x + in.f[0]) * scale; + break; + case WL_OUTPUT_TRANSFORM_180: + out.f[0] = (output->x + output->width - in.f[0]) * scale; + out.f[1] = (output->y + output->height - in.f[1]) * scale; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + out.f[0] = (-output->x + in.f[0]) * scale; + out.f[1] = (output->y + output->height - in.f[1]) * scale; + break; + case WL_OUTPUT_TRANSFORM_270: + out.f[0] = (output->y + output->height - in.f[1]) * scale; + out.f[1] = (-output->x + in.f[0]) * scale; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + out.f[0] = (output->y + output->height - in.f[1]) * scale; + out.f[1] = (output->x + output->width - in.f[0]) * scale; + break; + } + out.f[2] = 0; + out.f[3] = 1; + + return out; +} + +static void +output_test_all_transforms(struct weston_output *output, + int x, int y, int width, int height, int scale) +{ + int i; + int transform; + struct weston_vector t = { { 7.0, 13.0, 0.0, 1.0 } }; + struct weston_vector v, sv; + + for (transform = WL_OUTPUT_TRANSFORM_NORMAL; + transform <= WL_OUTPUT_TRANSFORM_FLIPPED_270; transform++) { + simple_weston_output_prepare(output, x, y, width, height, + scale, transform); + /* The inverse matrix takes us from output to global space, + * which makes it the one that will have the expected + * standard transform. + */ + transform_expect(&output->matrix, true, transform); + + v = t; + weston_matrix_transform(&output->matrix, &v); + sv = simple_transform_vector(output, t); + for (i = 0; i < 4; i++) + assert (sv.f[i] == v.f[i]); + } +} + +TEST(output_matrix_to_standard_transform) +{ + struct weston_output output; + int scale; + + /* Just a few arbitrary sizes and positions to make sure we have + * scales and translations. + */ + for (scale = 1; scale < 8; scale++) { + output_test_all_transforms(&output, 0, 0, 1024, 768, scale); + output_test_all_transforms(&output, 1000, 1000, 1024, 768, scale); + output_test_all_transforms(&output, 1024, 768, 1920, 1080, scale); + } +} diff --git a/tests/meson.build b/tests/meson.build index d8e96e77d..1d59a93e1 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 ], 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_output_capture_client_protocol_h, + weston_output_capture_protocol_c, viewporter_client_protocol_h, viewporter_protocol_c, 'color_util.h', @@ -41,6 +43,8 @@ lib_test_client = static_library( dep_libshared, dep_wayland_client, dep_libexec_weston, + dep_libweston_private, + dep_libdrm_headers, dep_pixman, dependency('cairo'), ], @@ -55,10 +59,26 @@ dep_test_client = declare_dependency( dep_wayland_client, dep_test_runner, dep_pixman, + dep_libdrm_headers, dependency('libudev', version: '>= 136'), ] ) +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 +110,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,13 +144,19 @@ 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', 'dep_objs': dep_libdrm_headers, }, { 'name': 'drm-smoke', 'run_exclusive': true }, + { 'name': 'drm-writeback-screenshot', 'run_exclusive': true }, { 'name': 'event', }, { 'name': 'internal-screenshot', }, { @@ -152,7 +176,25 @@ tests = [ linux_explicit_synchronization_unstable_v1_protocol_c, ], }, + { + 'name': 'matrix', + 'dep_objs': [ dep_libm ] + }, + { + 'name': 'matrix-transform', + 'dep_objs': dep_libm, + }, + { + 'name': 'output-capture-protocol', + 'sources': [ + 'output-capture-protocol-test.c', + weston_output_capture_protocol_c, + weston_output_capture_client_protocol_h, + ], + 'dep_objs': [ dep_libdrm_headers ], + }, { 'name': 'output-damage', }, + { 'name': 'output-decorations', }, { 'name': 'output-transforms', }, { 'name': 'plugin-registry', }, { @@ -181,6 +223,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 +253,6 @@ tests = [ input_timestamps_unstable_v1_protocol_c, ], }, - { - 'name': 'vertex-clip', - 'dep_objs': dep_vertex_clipping, - }, { 'name': 'viewporter', }, { 'name': 'viewporter-shot', }, { @@ -220,15 +266,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_libweston_public ] }, ] +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', [ @@ -240,14 +310,34 @@ tests_standalone = [ ] if get_option('xwayland') - d = dependency('x11', required: false) - if not d.found() - error('Xwayland tests require libX11 which was not found. Or, you can use \'-Dxwayland=false\'.') + xcb_dep = dependency('xcb', required: false) + xcb_cursor_dep = dependency('xcb-cursor', required: false) + + if not xcb_dep.found() or not xcb_cursor_dep.found() + error('xcb and xcb-cursor required for running xwayland tests') endif - tests += { + + libxwayland_test_client = static_library( + 'test-xwayland-client', + [ 'xcb-client-helper.c', weston_test_client_protocol_h ], + include_directories: common_inc, + dependencies: [ + dep_pixman, dep_xcb_xwayland, + xcb_dep, xcb_cursor_dep, + dep_wayland_client, + ], + install: false, + ) + + dep_libxwayland_test = declare_dependency( + dependencies: [ xcb_dep, xcb_cursor_dep ], + link_with: libxwayland_test_client, + ) + + tests += [ { 'name': 'xwayland', - 'dep_objs': d, - } + 'dep_objs': dep_libxwayland_test, + } ] endif # Manual test plugin, not used in the automatic suite @@ -319,12 +409,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 +452,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/output-capture-protocol-test.c b/tests/output-capture-protocol-test.c new file mode 100644 index 000000000..8cd8a4ced --- /dev/null +++ b/tests/output-capture-protocol-test.c @@ -0,0 +1,340 @@ +/* + * 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 "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" +#include "shared/xalloc.h" +#include "weston-output-capture-client-protocol.h" +#include "shared/weston-drm-fourcc.h" + +struct setup_args { + struct fixture_metadata meta; + enum weston_renderer_type renderer; + uint32_t expected_drm_format; +}; + +static const struct setup_args my_setup_args[] = { + { + .meta.name = "pixman", + .renderer = WESTON_RENDERER_PIXMAN, + .expected_drm_format = DRM_FORMAT_XRGB8888, + }, + { + .meta.name = "GL", + .renderer = WESTON_RENDERER_GL, + .expected_drm_format = DRM_FORMAT_ARGB8888, + }, +}; + +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 = 100; + setup.height = 60; + setup.shell = SHELL_TEST_DESKTOP; + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, my_setup_args, meta); + +struct capturer { + int width; + int height; + uint32_t drm_format; + + struct weston_capture_v1 *factory; + struct weston_capture_source_v1 *source; + + enum { + CAPTURE_TASK_PENDING = 0, + CAPTURE_TASK_COMPLETE, + CAPTURE_TASK_RETRY, + CAPTURE_TASK_FAILED, + } state; + + struct { + bool size; + bool format; + bool reply; + } events; + + char *last_failure; +}; + +static void +capture_source_handle_format(void *data, + struct weston_capture_source_v1 *proxy, + uint32_t drm_format) +{ + struct capturer *capt = data; + + assert(capt->source == proxy); + + capt->events.format = true; + capt->drm_format = drm_format; +} + +static void +capture_source_handle_size(void *data, + struct weston_capture_source_v1 *proxy, + int32_t width, int32_t height) +{ + struct capturer *capt = data; + + assert(capt->source == proxy); + + capt->events.size = true; + capt->width = width; + capt->height = height; +} + +static void +capture_source_handle_complete(void *data, + struct weston_capture_source_v1 *proxy) +{ + struct capturer *capt = data; + + assert(capt->source == proxy); + assert(capt->state == CAPTURE_TASK_PENDING); + capt->state = CAPTURE_TASK_COMPLETE; + capt->events.reply = true; +} + +static void +capture_source_handle_retry(void *data, + struct weston_capture_source_v1 *proxy) +{ + struct capturer *capt = data; + + assert(capt->source == proxy); + assert(capt->state == CAPTURE_TASK_PENDING); + capt->state = CAPTURE_TASK_RETRY; + capt->events.reply = true; +} + +static void +capture_source_handle_failed(void *data, + struct weston_capture_source_v1 *proxy, + const char *msg) +{ + struct capturer *capt = data; + + assert(capt->source == proxy); + assert(capt->state == CAPTURE_TASK_PENDING); + capt->state = CAPTURE_TASK_FAILED; + capt->events.reply = true; + + free(capt->last_failure); + capt->last_failure = msg ? xstrdup(msg) : NULL; +} + +static const struct weston_capture_source_v1_listener capture_source_handlers = { + .format = capture_source_handle_format, + .size = capture_source_handle_size, + .complete = capture_source_handle_complete, + .retry = capture_source_handle_retry, + .failed = capture_source_handle_failed, +}; + +static struct capturer * +capturer_create(struct client *client, + struct output *output, + enum weston_capture_v1_source src) +{ + struct capturer *capt; + + capt = xzalloc(sizeof *capt); + + capt->factory = bind_to_singleton_global(client, + &weston_capture_v1_interface, + 1); + + capt->source = weston_capture_v1_create(capt->factory, + output->wl_output, src); + weston_capture_source_v1_add_listener(capt->source, + &capture_source_handlers, capt); + + return capt; +} + +static void +capturer_destroy(struct capturer *capt) +{ + weston_capture_source_v1_destroy(capt->source); + weston_capture_v1_destroy(capt->factory); + free(capt->last_failure); + free(capt); +} + +/* + * Use the guaranteed source and all the right parameters to check that + * shooting succeeds on the first try. + */ +TEST(simple_shot) +{ + const struct setup_args *fix = &my_setup_args[get_test_fixture_index()]; + struct client *client; + struct capturer *capt; + struct buffer *buf; + + client = create_client(); + capt = capturer_create(client, client->output, + WESTON_CAPTURE_V1_SOURCE_FRAMEBUFFER); + client_roundtrip(client); + + assert(capt->events.format); + assert(capt->events.size); + assert(capt->state == CAPTURE_TASK_PENDING); + assert(capt->drm_format == fix->expected_drm_format); + assert(capt->width > 0); + assert(capt->height > 0); + assert(!capt->events.reply); + + buf = create_shm_buffer(client, capt->width, capt->height, + fix->expected_drm_format); + + weston_capture_source_v1_capture(capt->source, buf->proxy); + while (!capt->events.reply) + assert(wl_display_dispatch(client->wl_display) >= 0); + + assert(capt->state == CAPTURE_TASK_COMPLETE); + + capturer_destroy(capt); + buffer_destroy(buf); + client_destroy(client); +} + +/* + * Use a guaranteed source, but use an unsupported pixel format. + * This should always cause a retry. + */ +TEST(retry_on_wrong_format) +{ + const uint32_t drm_format = DRM_FORMAT_ABGR2101010; + struct client *client; + struct capturer *capt; + struct buffer *buf; + + client = create_client(); + capt = capturer_create(client, client->output, + WESTON_CAPTURE_V1_SOURCE_FRAMEBUFFER); + client_roundtrip(client); + + assert(capt->events.format); + assert(capt->events.size); + assert(capt->state == CAPTURE_TASK_PENDING); + assert(capt->drm_format != drm_format && "fix this test"); + assert(capt->width > 0); + assert(capt->height > 0); + assert(!capt->events.reply); + + buf = create_shm_buffer(client, capt->width, capt->height, drm_format); + + weston_capture_source_v1_capture(capt->source, buf->proxy); + while (!capt->events.reply) + assert(wl_display_dispatch(client->wl_display) >= 0); + + assert(capt->state == CAPTURE_TASK_RETRY); + + capturer_destroy(capt); + buffer_destroy(buf); + client_destroy(client); +} + +/* + * Use a guaranteed source, but use a smaller buffer size. + * This should always cause a retry. + */ +TEST(retry_on_wrong_size) +{ + struct client *client; + struct capturer *capt; + struct buffer *buf; + + client = create_client(); + capt = capturer_create(client, client->output, + WESTON_CAPTURE_V1_SOURCE_FRAMEBUFFER); + client_roundtrip(client); + + assert(capt->events.format); + assert(capt->events.size); + assert(capt->state == CAPTURE_TASK_PENDING); + assert(capt->width > 5); + assert(capt->height > 5); + assert(!capt->events.reply); + + buf = create_shm_buffer(client, capt->width - 3, capt->height - 3, + capt->drm_format); + + weston_capture_source_v1_capture(capt->source, buf->proxy); + while (!capt->events.reply) + assert(wl_display_dispatch(client->wl_display) >= 0); + + assert(capt->state == CAPTURE_TASK_RETRY); + + capturer_destroy(capt); + buffer_destroy(buf); + client_destroy(client); +} + +/* + * Try a source that is guaranteed to not exist, and check that + * capturing fails. + */ +TEST(writeback_on_headless_fails) +{ + struct client *client; + struct capturer *capt; + struct buffer *buf; + + client = create_client(); + buf = create_shm_buffer_a8r8g8b8(client, 5, 5); + capt = capturer_create(client, client->output, + WESTON_CAPTURE_V1_SOURCE_WRITEBACK); + client_roundtrip(client); + + assert(!capt->events.format); + assert(!capt->events.size); + assert(capt->state == CAPTURE_TASK_PENDING); + + /* Trying pixel source that is not available should fail immediately */ + weston_capture_source_v1_capture(capt->source, buf->proxy); + client_roundtrip(client); + + assert(!capt->events.format); + assert(!capt->events.size); + assert(capt->state == CAPTURE_TASK_FAILED); + assert(strcmp(capt->last_failure, "source unavailable") == 0); + + capturer_destroy(capt); + buffer_destroy(buf); + client_destroy(client); +} diff --git a/tests/output-damage-test.c b/tests/output-damage-test.c index f96c6c84b..b021ea519 100644 --- a/tests/output-damage-test.c +++ b/tests/output-damage-test.c @@ -34,7 +34,7 @@ #define RENDERERS(s, t) \ { \ - .renderer = RENDERER_PIXMAN, \ + .renderer = WESTON_RENDERER_PIXMAN, \ .scale = s, \ .transform = WL_OUTPUT_TRANSFORM_ ## t, \ .transform_name = #t, \ @@ -42,7 +42,7 @@ .meta.name = "pixman " #s " " #t, \ }, \ { \ - .renderer = RENDERER_GL, \ + .renderer = WESTON_RENDERER_GL, \ .scale = s, \ .transform = WL_OUTPUT_TRANSFORM_ ## t, \ .transform_name = #t, \ @@ -50,7 +50,7 @@ .meta.name = "GL no-shadow " #s " " #t, \ }, \ { \ - .renderer = RENDERER_GL, \ + .renderer = WESTON_RENDERER_GL, \ .scale = s, \ .transform = WL_OUTPUT_TRANSFORM_ ## t, \ .transform_name = #t, \ @@ -60,7 +60,7 @@ struct setup_args { struct fixture_metadata meta; - enum renderer_type renderer; + enum weston_renderer_type renderer; int scale; enum wl_output_transform transform; const char *transform_name; @@ -221,7 +221,7 @@ TEST(output_damage) */ for (i = 1; i < COUNT_BUFS; i++) { commit_buffer_with_damage(client->surface, buf[i], damages[i]); - if (!verify_screen_content(client, refname, i, NULL, i)) + if (!verify_screen_content(client, refname, i, NULL, i, NULL)) match = false; } diff --git a/tests/output-decorations-test.c b/tests/output-decorations-test.c new file mode 100644 index 000000000..e24f68528 --- /dev/null +++ b/tests/output-decorations-test.c @@ -0,0 +1,84 @@ +/* + * 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 "weston-test-client-helper.h" +#include "weston-test-fixture-compositor.h" + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness) +{ + struct compositor_setup setup; + + compositor_setup_defaults(&setup); + setup.renderer = WESTON_RENDERER_GL; + setup.width = 300; + setup.height = 150; + setup.shell = SHELL_TEST_DESKTOP; + + weston_ini_setup(&setup, + cfgln("[core]"), + cfgln("output-decorations=true")); + + return weston_test_harness_execute_as_client(harness, &setup); +} +DECLARE_FIXTURE_SETUP(fixture_setup); + +/* + * Basic screenshot test for output decorations + * + * Tests that the cairo-util code for drawing window decorations works at all + * through headless-backend. The window decorations are normally used as output + * decorations by wayland-backend when the outputs are windows in a parent + * compositor. + * + * This works only with GL-renderer. Pixman-renderer has no code for blitting + * output decorations and does not even know they exist. + * + * Headless-backend sets window title string to NULL because it might be + * difficult to ensure text rendering is pixel-precise between different + * systems. + */ +TEST(output_decorations) +{ + struct client *client; + struct buffer *shot; + pixman_image_t *img; + bool match; + + client = create_client(); + + shot = client_capture_output(client, client->output, + WESTON_CAPTURE_V1_SOURCE_FULL_FRAMEBUFFER); + img = image_convert_to_a8r8g8b8(shot->image); + + match = verify_image(img, "output-decorations", 0, NULL, 0); + assert(match); + + pixman_image_unref(img); + buffer_destroy(shot); + client_destroy(client); +} diff --git a/tests/output-transforms-test.c b/tests/output-transforms-test.c index 392484d72..006ab35ae 100644 --- a/tests/output-transforms-test.c +++ b/tests/output-transforms-test.c @@ -35,14 +35,14 @@ #define TRANSFORM(x) WL_OUTPUT_TRANSFORM_ ## x, #x #define RENDERERS(s, t) \ { \ - .renderer = RENDERER_PIXMAN, \ + .renderer = WESTON_RENDERER_PIXMAN, \ .scale = s, \ .transform = WL_OUTPUT_TRANSFORM_ ## t, \ .transform_name = #t, \ .meta.name = "pixman " #s " " #t, \ }, \ { \ - .renderer = RENDERER_GL, \ + .renderer = WESTON_RENDERER_GL, \ .scale = s, \ .transform = WL_OUTPUT_TRANSFORM_ ## t, \ .transform_name = #t, \ @@ -51,7 +51,7 @@ struct setup_args { struct fixture_metadata meta; - enum renderer_type renderer; + enum weston_renderer_type renderer; int scale; enum wl_output_transform transform; const char *transform_name; @@ -143,7 +143,7 @@ TEST_P(output_transform, my_buffer_args) bargs->transform); move_client(client, 19, 19); - match = verify_screen_content(client, refname, 0, NULL, 0); + match = verify_screen_content(client, refname, 0, NULL, 0, NULL); assert(match); client_destroy(client); diff --git a/tests/plugin-registry-test.c b/tests/plugin-registry-test.c index 85de5f8a2..66981e6bc 100644 --- a/tests/plugin-registry-test.c +++ b/tests/plugin-registry-test.c @@ -40,6 +40,7 @@ fixture_setup(struct weston_test_harness *harness) struct compositor_setup setup; compositor_setup_defaults(&setup); + setup.shell = SHELL_TEST_DESKTOP; return weston_test_harness_execute_as_plugin(harness, &setup); } diff --git a/tests/pointer-shot-test.c b/tests/pointer-shot-test.c index 606eda3be..a78d7ed9f 100644 --- a/tests/pointer-shot-test.c +++ b/tests/pointer-shot-test.c @@ -35,16 +35,16 @@ struct setup_args { struct fixture_metadata meta; - enum renderer_type renderer; + enum weston_renderer_type renderer; }; static const struct setup_args my_setup_args[] = { { - .renderer = RENDERER_PIXMAN, + .renderer = WESTON_RENDERER_PIXMAN, .meta.name = "pixman" }, { - .renderer = RENDERER_GL, + .renderer = WESTON_RENDERER_GL, .meta.name = "GL" }, }; @@ -149,7 +149,7 @@ TEST(pointer_cursor_retains_committed_buffer_after_reenter) client->input->pointer->serial, main_cursor_surface->wl_surface, 0, 0); match = verify_screen_content(client, "pointer_cursor_reenter", 0, - NULL, 0); + NULL, 0, NULL); assert(match); /* Move the cursor just outside the main surface. */ @@ -158,7 +158,7 @@ TEST(pointer_cursor_retains_committed_buffer_after_reenter) client->input->pointer->serial, back_cursor_surface->wl_surface, 0, 0); match = verify_screen_content(client, "pointer_cursor_reenter", 1, - NULL, 1); + NULL, 1, NULL); assert(match); /* And back in the main surface again. */ @@ -167,7 +167,7 @@ TEST(pointer_cursor_retains_committed_buffer_after_reenter) client->input->pointer->serial, main_cursor_surface->wl_surface, 0, 0); match = verify_screen_content(client, "pointer_cursor_reenter", 2, - NULL, 2); + NULL, 2, NULL); assert(match); surface_destroy(back_cursor_surface); diff --git a/tests/pointer-test.c b/tests/pointer-test.c index 2e999ab5f..cd727ac68 100644 --- a/tests/pointer-test.c +++ b/tests/pointer-test.c @@ -39,6 +39,7 @@ fixture_setup(struct weston_test_harness *harness) struct compositor_setup setup; compositor_setup_defaults(&setup); + setup.shell = SHELL_TEST_DESKTOP; return weston_test_harness_execute_as_client(harness, &setup); } diff --git a/tests/presentation-test.c b/tests/presentation-test.c index f6e3a7bc3..a0862d60a 100644 --- a/tests/presentation-test.c +++ b/tests/presentation-test.c @@ -45,6 +45,7 @@ fixture_setup(struct weston_test_harness *harness) struct compositor_setup setup; compositor_setup_defaults(&setup); + setup.shell = SHELL_TEST_DESKTOP; return weston_test_harness_execute_as_client(harness, &setup); } @@ -123,7 +124,7 @@ feedback_presented(void *data, assert(fb->result == FB_PENDING); fb->result = FB_PRESENTED; - fb->seq = ((uint64_t)seq_hi << 32) + seq_lo; + fb->seq = u64_from_u32s(seq_hi, seq_lo); timespec_from_proto(&fb->time, tv_sec_hi, tv_sec_lo, tv_nsec); fb->refresh_nsec = refresh_nsec; fb->flags = flags; diff --git a/tests/reference/drm-writeback-screenshot-00.png b/tests/reference/drm-writeback-screenshot-00.png new file mode 100644 index 000000000..728b738e7 Binary files /dev/null and b/tests/reference/drm-writeback-screenshot-00.png differ diff --git a/tests/reference/output-decorations-00.png b/tests/reference/output-decorations-00.png new file mode 100644 index 000000000..13f9aa82e Binary files /dev/null and b/tests/reference/output-decorations-00.png differ diff --git a/tests/reference/output-icc-decorations-00.png b/tests/reference/output-icc-decorations-00.png new file mode 100644 index 000000000..2bac388fc Binary files /dev/null and b/tests/reference/output-icc-decorations-00.png differ diff --git a/tests/reference/output-icc-decorations-01.png b/tests/reference/output-icc-decorations-01.png new file mode 100644 index 000000000..90aa82735 Binary files /dev/null and b/tests/reference/output-icc-decorations-01.png differ diff --git a/tests/reference/output-icc-decorations-02.png b/tests/reference/output-icc-decorations-02.png new file mode 100644 index 000000000..dbb892897 Binary files /dev/null and b/tests/reference/output-icc-decorations-02.png differ diff --git a/tests/reference/output-icc-decorations-03.png b/tests/reference/output-icc-decorations-03.png new file mode 100644 index 000000000..f10f000fa Binary files /dev/null and b/tests/reference/output-icc-decorations-03.png differ diff --git a/tests/reference/output-icc-decorations-04.png b/tests/reference/output-icc-decorations-04.png new file mode 100644 index 000000000..6b0c62224 Binary files /dev/null and b/tests/reference/output-icc-decorations-04.png differ 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 000000000..beec77b82 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 000000000..45250ecc2 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 000000000..7714b1032 Binary files /dev/null and b/tests/reference/output_icc_alpha_blend-02.png differ diff --git a/tests/reference/output_icc_alpha_blend-03.png b/tests/reference/output_icc_alpha_blend-03.png new file mode 100644 index 000000000..9072beda2 Binary files /dev/null and b/tests/reference/output_icc_alpha_blend-03.png differ diff --git a/tests/reference/output_icc_alpha_blend-04.png b/tests/reference/output_icc_alpha_blend-04.png new file mode 100644 index 000000000..0919087a5 Binary files /dev/null and b/tests/reference/output_icc_alpha_blend-04.png differ diff --git a/tests/reference/shaper_matrix-00.png b/tests/reference/shaper_matrix-00.png new file mode 100644 index 000000000..3671b0eed 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 000000000..6f600083b 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 000000000..0abca0be8 Binary files /dev/null and b/tests/reference/shaper_matrix-02.png differ diff --git a/tests/reference/shaper_matrix-03.png b/tests/reference/shaper_matrix-03.png new file mode 100644 index 000000000..693385c86 Binary files /dev/null and b/tests/reference/shaper_matrix-03.png differ diff --git a/tests/reference/shaper_matrix-04.png b/tests/reference/shaper_matrix-04.png new file mode 100644 index 000000000..68fc25bfa Binary files /dev/null and b/tests/reference/shaper_matrix-04.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 000000000..2fa7f5945 Binary files /dev/null and b/tests/reference/single-pixel-buffer-00.png differ diff --git a/tests/roles-test.c b/tests/roles-test.c index cbad65e5c..d88428847 100644 --- a/tests/roles-test.c +++ b/tests/roles-test.c @@ -40,6 +40,7 @@ fixture_setup(struct weston_test_harness *harness) struct compositor_setup setup; compositor_setup_defaults(&setup); + setup.shell = SHELL_TEST_DESKTOP; setup.logging_scopes = "log,proto,test-harness-plugin"; return weston_test_harness_execute_as_client(harness, &setup); diff --git a/tests/safe-signal-output-removal-test.c b/tests/safe-signal-output-removal-test.c index 0a747307a..3ff225992 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 #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_shell_utils_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_shell_utils_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 3248f0f70..4b5ac643e 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 000000000..e7cf13314 --- /dev/null +++ b/tests/single-pixel-buffer-test.c @@ -0,0 +1,112 @@ +/* + * 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 weston_renderer_type renderer; +}; + +static const struct setup_args my_setup_args[] = { + { + .renderer = WESTON_RENDERER_PIXMAN, + .meta.name = "pixman" + }, + { + .renderer = WESTON_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, NULL); + assert(match); + + wl_buffer_destroy(buffer); + wp_viewport_destroy(viewport); + wp_single_pixel_buffer_manager_v1_destroy(mgr); + client_destroy(client); +} diff --git a/tests/subsurface-shot-test.c b/tests/subsurface-shot-test.c index 165344971..4bbc1af68 100644 --- a/tests/subsurface-shot-test.c +++ b/tests/subsurface-shot-test.c @@ -35,16 +35,16 @@ struct setup_args { struct fixture_metadata meta; - enum renderer_type renderer; + enum weston_renderer_type renderer; }; static const struct setup_args my_setup_args[] = { { - .renderer = RENDERER_PIXMAN, + .renderer = WESTON_RENDERER_PIXMAN, .meta.name = "pixman" }, { - .renderer = RENDERER_GL, + .renderer = WESTON_RENDERER_GL, .meta.name = "GL" }, }; @@ -103,7 +103,7 @@ check_screen(struct client *client, bool match; match = verify_screen_content(client, ref_image, ref_seq_no, clip, - seq_no); + seq_no, NULL); return match ? 0 : -1; } @@ -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/subsurface-test.c b/tests/subsurface-test.c index 53f8f227f..edaea9fa7 100644 --- a/tests/subsurface-test.c +++ b/tests/subsurface-test.c @@ -39,6 +39,7 @@ fixture_setup(struct weston_test_harness *harness) struct compositor_setup setup; compositor_setup_defaults(&setup); + setup.shell = SHELL_TEST_DESKTOP; return weston_test_harness_execute_as_client(harness, &setup); } diff --git a/tests/surface-global-test.c b/tests/surface-global-test.c index a9e4ff9de..a58acad8c 100644 --- a/tests/surface-global-test.c +++ b/tests/surface-global-test.c @@ -40,6 +40,7 @@ fixture_setup(struct weston_test_harness *harness) struct compositor_setup setup; compositor_setup_defaults(&setup); + setup.shell = SHELL_TEST_DESKTOP; return weston_test_harness_execute_as_plugin(harness, &setup); } @@ -50,9 +51,8 @@ PLUGIN_TEST(surface_to_from_global) /* struct weston_compositor *compositor; */ struct weston_surface *surface; struct weston_view *view; - float x, y; - wl_fixed_t fx, fy; - int32_t ix, iy; + struct weston_coord_global cg; + struct weston_coord_surface cs; surface = weston_surface_create(compositor); assert(surface); @@ -63,32 +63,42 @@ PLUGIN_TEST(surface_to_from_global) weston_view_set_position(view, 5, 10); weston_view_update_transform(view); - weston_view_to_global_float(view, 33, 22, &x, &y); - assert(x == 38 && y == 32); + cs = weston_coord_surface(33, 22, surface); + cg = weston_coord_surface_to_global(view, cs); + assert(cg.c.x == 38 && cg.c.y == 32); - weston_view_to_global_float(view, -8, -2, &x, &y); - assert(x == -3 && y == 8); + cs = weston_coord_surface(-8, -2, surface); + cg = weston_coord_surface_to_global(view, cs); + assert(cg.c.x == -3 && cg.c.y == 8); - weston_view_to_global_fixed(view, wl_fixed_from_int(12), - wl_fixed_from_int(5), &fx, &fy); - assert(fx == wl_fixed_from_int(17) && fy == wl_fixed_from_int(15)); + cs = weston_coord_surface_from_fixed(wl_fixed_from_int(12), + wl_fixed_from_int(5), surface); + cg = weston_coord_surface_to_global(view, cs); + assert(wl_fixed_from_double(cg.c.x) == wl_fixed_from_int(17) && + wl_fixed_from_double(cg.c.y) == wl_fixed_from_int(15)); - weston_view_from_global_float(view, 38, 32, &x, &y); - assert(x == 33 && y == 22); + cg.c = weston_coord(38, 32); + cs = weston_coord_global_to_surface(view, cg); + assert(cs.c.x == 33 && cs.c.y == 22); - weston_view_from_global_float(view, 42, 5, &x, &y); - assert(x == 37 && y == -5); + cg.c = weston_coord(42, 5); + cs = weston_coord_global_to_surface(view, cg); + assert(cs.c.x == 37 && cs.c.y == -5); - weston_view_from_global_fixed(view, wl_fixed_from_int(21), - wl_fixed_from_int(100), &fx, &fy); - assert(fx == wl_fixed_from_int(16) && fy == wl_fixed_from_int(90)); + cg.c = weston_coord_from_fixed(wl_fixed_from_int(21), + wl_fixed_from_int(100)); + cs = weston_coord_global_to_surface(view, cg); + assert(wl_fixed_from_double(cs.c.x) == wl_fixed_from_int(16) && + wl_fixed_from_double(cs.c.y) == wl_fixed_from_int(90)); - weston_view_from_global(view, 0, 0, &ix, &iy); - assert(ix == -5 && iy == -10); + cg.c = weston_coord(0, 0); + cs = weston_coord_global_to_surface(view, cg); + assert(cs.c.x == -5 && cs.c.y == -10); - weston_view_from_global(view, 5, 10, &ix, &iy); - assert(ix == 0 && iy == 0); + cg.c = weston_coord(5, 10); + cs = weston_coord_global_to_surface(view, cg); + assert(cs.c.x == 0 && cs.c.y == 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 68288a869..c1a4499ad 100644 --- a/tests/surface-test.c +++ b/tests/surface-test.c @@ -39,6 +39,7 @@ fixture_setup(struct weston_test_harness *harness) struct compositor_setup setup; compositor_setup_defaults(&setup); + setup.shell = SHELL_TEST_DESKTOP; return weston_test_harness_execute_as_plugin(harness, &setup); } @@ -49,7 +50,8 @@ PLUGIN_TEST(surface_transform) /* struct weston_compositor *compositor; */ struct weston_surface *surface; struct weston_view *view; - float x, y; + struct weston_coord_surface coord_s; + struct weston_coord_global coord_g; surface = weston_surface_create(compositor); assert(surface); @@ -59,16 +61,18 @@ PLUGIN_TEST(surface_transform) surface->height = 200; weston_view_set_position(view, 100, 100); weston_view_update_transform(view); - weston_view_to_global_float(view, 20, 20, &x, &y); + coord_s = weston_coord_surface(20, 20, surface); + coord_g = weston_coord_surface_to_global(view, coord_s); - fprintf(stderr, "20,20 maps to %f, %f\n", x, y); - assert(x == 120 && y == 120); + fprintf(stderr, "20,20 maps to %f, %f\n", coord_g.c.x, coord_g.c.y); + assert(coord_g.c.x == 120 && coord_g.c.y == 120); weston_view_set_position(view, 150, 300); weston_view_update_transform(view); - weston_view_to_global_float(view, 50, 40, &x, &y); - assert(x == 200 && y == 340); + coord_s = weston_coord_surface(50, 40, surface); + coord_g = weston_coord_surface_to_global(view, coord_s); + assert(coord_g.c.x == 200 && coord_g.c.y == 340); /* Destroys all views too. */ - weston_surface_destroy(surface); + weston_surface_unref(surface); } diff --git a/tests/text-test.c b/tests/text-test.c index 65e00d524..2f5103b60 100644 --- a/tests/text-test.c +++ b/tests/text-test.c @@ -39,6 +39,7 @@ fixture_setup(struct weston_test_harness *harness) struct compositor_setup setup; compositor_setup_defaults(&setup); + setup.shell = SHELL_DESKTOP; return weston_test_harness_execute_as_client(harness, &setup); } diff --git a/tests/touch-test.c b/tests/touch-test.c index c2b563c9b..d6a4ac409 100644 --- a/tests/touch-test.c +++ b/tests/touch-test.c @@ -39,6 +39,7 @@ fixture_setup(struct weston_test_harness *harness) struct compositor_setup setup; compositor_setup_defaults(&setup); + setup.shell = SHELL_TEST_DESKTOP; return weston_test_harness_execute_as_client(harness, &setup); } @@ -57,18 +58,51 @@ create_touch_test_client(void) return cl; } +static void +send_broken_touch(struct client *client, const struct timespec *time) +{ + uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; + + timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + + weston_test_send_touch(client->test->weston_test, tv_sec_hi, tv_sec_lo, + tv_nsec, 1, 1, 1, WL_TOUCH_UP); + + expect_protocol_error(client, &weston_test_interface, + WESTON_TEST_ERROR_TOUCH_UP_WITH_COORDINATE); +} + static void send_touch(struct client *client, const struct timespec *time, uint32_t touch_type) { uint32_t tv_sec_hi, tv_sec_lo, tv_nsec; + wl_fixed_t x = 0, y = 0; timespec_to_proto(time, &tv_sec_hi, &tv_sec_lo, &tv_nsec); + if (touch_type != WL_TOUCH_UP) { + x = 1; + y = 1; + } + weston_test_send_touch(client->test->weston_test, tv_sec_hi, tv_sec_lo, - tv_nsec, 1, 1, 1, touch_type); + tv_nsec, 1, x, y, touch_type); client_roundtrip(client); } +TEST(broken_touch_event) +{ + struct client *client = create_touch_test_client(); + struct input_timestamps *input_ts = + input_timestamps_create_for_touch(client); + + send_broken_touch(client, &t1); + + input_timestamps_destroy(input_ts); + + client_destroy(client); +} + TEST(touch_events) { struct client *client = create_touch_test_client(); diff --git a/tests/vertex-clip-test.c b/tests/vertex-clip-test.c index 3e62412c2..8cde2702a 100644 --- a/tests/vertex-clip-test.c +++ b/tests/vertex-clip-test.c @@ -60,11 +60,10 @@ populate_clip_context (struct clip_context *ctx) static int clip_polygon (struct clip_context *ctx, struct polygon8 *polygon, - float *vertices_x, - float *vertices_y) + struct weston_coord *pos) { populate_clip_context(ctx); - return clip_transformed(ctx, polygon, vertices_x, vertices_y); + return clip_transformed(ctx, polygon, pos); } struct vertex_clip_test_data @@ -78,94 +77,152 @@ const struct vertex_clip_test_data test_data[] = /* All inside */ { { - { INSIDE_X1, INSIDE_X2, INSIDE_X2, INSIDE_X1 }, - { INSIDE_Y1, INSIDE_Y1, INSIDE_Y2, INSIDE_Y2 }, - 4 + .pos = { + { .x = INSIDE_X1, .y = INSIDE_Y1 }, + { .x = INSIDE_X2, .y = INSIDE_Y1 }, + { .x = INSIDE_X2, .y = INSIDE_Y2 }, + { .x = INSIDE_X1, .y = INSIDE_Y2 }, + }, + .n = 4 }, { - { INSIDE_X1, INSIDE_X2, INSIDE_X2, INSIDE_X1 }, - { INSIDE_Y1, INSIDE_Y1, INSIDE_Y2, INSIDE_Y2 }, - 4 + .pos = { + { .x = INSIDE_X1, .y = INSIDE_Y1 }, + { .x = INSIDE_X2, .y = INSIDE_Y1 }, + { .x = INSIDE_X2, .y = INSIDE_Y2 }, + { .x = INSIDE_X1, .y = INSIDE_Y2 }, + }, + .n = 4 } }, /* Top outside */ { { - { INSIDE_X1, INSIDE_X2, INSIDE_X2, INSIDE_X1 }, - { INSIDE_Y1, INSIDE_Y1, OUTSIDE_Y2, OUTSIDE_Y2 }, - 4 + .pos = { + { .x = INSIDE_X1, .y = INSIDE_Y1 }, + { .x = INSIDE_X2, .y = INSIDE_Y1 }, + { .x = INSIDE_X2, .y = OUTSIDE_Y2 }, + { .x = INSIDE_X1, .y = OUTSIDE_Y2 }, + }, + .n = 4 }, { - { INSIDE_X1, INSIDE_X1, INSIDE_X2, INSIDE_X2 }, - { BOUNDING_BOX_TOP_Y, INSIDE_Y1, INSIDE_Y1, BOUNDING_BOX_TOP_Y }, - 4 + .pos = { + { .x = INSIDE_X1, .y = BOUNDING_BOX_TOP_Y }, + { .x = INSIDE_X1, .y = INSIDE_Y1 }, + { .x = INSIDE_X2, .y = INSIDE_Y1 }, + { .x = INSIDE_X2, .y = BOUNDING_BOX_TOP_Y }, + }, + .n = 4 } }, /* Bottom outside */ { { - { INSIDE_X1, INSIDE_X2, INSIDE_X2, INSIDE_X1 }, - { OUTSIDE_Y1, OUTSIDE_Y1, INSIDE_Y2, INSIDE_Y2 }, - 4 + .pos = { + { .x = INSIDE_X1, .y = OUTSIDE_Y1 }, + { .x = INSIDE_X2, .y = OUTSIDE_Y1 }, + { .x = INSIDE_X2, .y = INSIDE_Y2 }, + { .x = INSIDE_X1, .y = INSIDE_Y2 }, + }, + .n = 4 }, { - { INSIDE_X1, INSIDE_X2, INSIDE_X2, INSIDE_X1 }, - { BOUNDING_BOX_BOTTOM_Y, BOUNDING_BOX_BOTTOM_Y, INSIDE_Y2, INSIDE_Y2 }, - 4 + .pos = { + { .x = INSIDE_X1, .y = BOUNDING_BOX_BOTTOM_Y }, + { .x = INSIDE_X2, .y = BOUNDING_BOX_BOTTOM_Y }, + { .x = INSIDE_X2, .y = INSIDE_Y2 }, + { .x = INSIDE_X1, .y = INSIDE_Y2 }, + }, + .n = 4 } }, /* Left outside */ { { - { OUTSIDE_X1, INSIDE_X2, INSIDE_X2, OUTSIDE_X1 }, - { INSIDE_Y1, INSIDE_Y1, INSIDE_Y2, INSIDE_Y2 }, - 4 + .pos = { + { .x = OUTSIDE_X1, .y = INSIDE_Y1 }, + { .x = INSIDE_X2, .y = INSIDE_Y1 }, + { .x = INSIDE_X2, .y = INSIDE_Y2 }, + { .x = OUTSIDE_X1, .y = INSIDE_Y2 }, + }, + .n = 4 }, { - { BOUNDING_BOX_LEFT_X, INSIDE_X2, INSIDE_X2, BOUNDING_BOX_LEFT_X }, - { INSIDE_Y1, INSIDE_Y1, INSIDE_Y2, INSIDE_Y2 }, - 4 + .pos = { + { .x = BOUNDING_BOX_LEFT_X, .y = INSIDE_Y1 }, + { .x = INSIDE_X2, .y = INSIDE_Y1 }, + { .x = INSIDE_X2, .y = INSIDE_Y2 }, + { .x = BOUNDING_BOX_LEFT_X, .y = INSIDE_Y2 }, + }, + .n = 4 } }, /* Right outside */ { { - { INSIDE_X1, OUTSIDE_X2, OUTSIDE_X2, INSIDE_X1 }, - { INSIDE_Y1, INSIDE_Y1, INSIDE_Y2, INSIDE_Y2 }, - 4 + .pos = { + { .x = INSIDE_X1, .y = INSIDE_Y1 }, + { .x = OUTSIDE_X2, .y = INSIDE_Y1 }, + { .x = OUTSIDE_X2, .y = INSIDE_Y2 }, + { .x = INSIDE_X1, .y = INSIDE_Y2 }, + }, + .n = 4 }, { - { INSIDE_X1, BOUNDING_BOX_RIGHT_X, BOUNDING_BOX_RIGHT_X, INSIDE_X1 }, - { INSIDE_Y1, INSIDE_Y1, INSIDE_Y2, INSIDE_Y2 }, - 4 + .pos = { + { .x = INSIDE_X1, .y = INSIDE_Y1 }, + { .x = BOUNDING_BOX_RIGHT_X, .y = INSIDE_Y1 }, + { .x = BOUNDING_BOX_RIGHT_X, .y = INSIDE_Y2 }, + { .x = INSIDE_X1, .y = INSIDE_Y2 }, + }, + .n = 4 } }, /* Diamond extending from bounding box edges, clip to bounding box */ { { - { BOUNDING_BOX_LEFT_X - 25, BOUNDING_BOX_LEFT_X + 25, BOUNDING_BOX_RIGHT_X + 25, BOUNDING_BOX_RIGHT_X - 25 }, - { BOUNDING_BOX_BOTTOM_Y + 25, BOUNDING_BOX_TOP_Y + 25, BOUNDING_BOX_TOP_Y - 25, BOUNDING_BOX_BOTTOM_Y - 25 }, - 4 + .pos = { + { .x = BOUNDING_BOX_LEFT_X - 25, .y = BOUNDING_BOX_BOTTOM_Y + 25 }, + { .x = BOUNDING_BOX_LEFT_X + 25, .y = BOUNDING_BOX_TOP_Y + 25 }, + { .x = BOUNDING_BOX_RIGHT_X + 25, .y = BOUNDING_BOX_TOP_Y - 25 }, + { .x = BOUNDING_BOX_RIGHT_X - 25, .y = BOUNDING_BOX_BOTTOM_Y - 25 }, + }, + .n = 4 }, { - { BOUNDING_BOX_LEFT_X, BOUNDING_BOX_LEFT_X, BOUNDING_BOX_RIGHT_X, BOUNDING_BOX_RIGHT_X }, - { BOUNDING_BOX_BOTTOM_Y, BOUNDING_BOX_TOP_Y, BOUNDING_BOX_TOP_Y, BOUNDING_BOX_BOTTOM_Y }, - 4 + .pos = { + { .x = BOUNDING_BOX_LEFT_X, .y = BOUNDING_BOX_BOTTOM_Y }, + { .x = BOUNDING_BOX_LEFT_X, .y = BOUNDING_BOX_TOP_Y }, + { .x = BOUNDING_BOX_RIGHT_X, .y = BOUNDING_BOX_TOP_Y }, + { .x = BOUNDING_BOX_RIGHT_X, .y = BOUNDING_BOX_BOTTOM_Y }, + }, + .n = 4 } }, /* Diamond inside of bounding box edges, clip t bounding box, 8 resulting vertices */ { { - { BOUNDING_BOX_LEFT_X - 12.5, BOUNDING_BOX_LEFT_X + 25, BOUNDING_BOX_RIGHT_X + 12.5, BOUNDING_BOX_RIGHT_X - 25 }, - { BOUNDING_BOX_BOTTOM_Y + 25, BOUNDING_BOX_TOP_Y + 12.5, BOUNDING_BOX_TOP_Y - 25, BOUNDING_BOX_BOTTOM_Y - 12.5 }, - 4 + .pos = { + { .x = BOUNDING_BOX_LEFT_X - 12.5, .y = BOUNDING_BOX_BOTTOM_Y + 25 }, + { .x = BOUNDING_BOX_LEFT_X + 25, .y = BOUNDING_BOX_TOP_Y + 12.5 }, + { .x = BOUNDING_BOX_RIGHT_X + 12.5, .y = BOUNDING_BOX_TOP_Y - 25 }, + { .x = BOUNDING_BOX_RIGHT_X - 25, .y = BOUNDING_BOX_BOTTOM_Y - 12.5 }, + }, + .n = 4 }, { - { BOUNDING_BOX_LEFT_X + 12.5, BOUNDING_BOX_LEFT_X, BOUNDING_BOX_LEFT_X, BOUNDING_BOX_LEFT_X + 12.5, - BOUNDING_BOX_RIGHT_X - 12.5, BOUNDING_BOX_RIGHT_X, BOUNDING_BOX_RIGHT_X, BOUNDING_BOX_RIGHT_X - 12.5 }, - { BOUNDING_BOX_BOTTOM_Y, BOUNDING_BOX_BOTTOM_Y + 12.5, BOUNDING_BOX_TOP_Y - 12.5, BOUNDING_BOX_TOP_Y, - BOUNDING_BOX_TOP_Y, BOUNDING_BOX_TOP_Y - 12.5, BOUNDING_BOX_BOTTOM_Y + 12.5, BOUNDING_BOX_BOTTOM_Y }, - 8 + .pos = { + { .x = BOUNDING_BOX_LEFT_X + 12.5, .y = BOUNDING_BOX_BOTTOM_Y }, + { .x = BOUNDING_BOX_LEFT_X, .y = BOUNDING_BOX_BOTTOM_Y + 12.5 }, + { .x = BOUNDING_BOX_LEFT_X, .y = BOUNDING_BOX_TOP_Y - 12.5 }, + { .x = BOUNDING_BOX_LEFT_X + 12.5, .y = BOUNDING_BOX_TOP_Y }, + { .x = BOUNDING_BOX_RIGHT_X - 12.5, .y = BOUNDING_BOX_TOP_Y }, + { .x = BOUNDING_BOX_RIGHT_X, .y = BOUNDING_BOX_TOP_Y - 12.5 }, + { .x = BOUNDING_BOX_RIGHT_X, .y = BOUNDING_BOX_BOTTOM_Y + 12.5}, + { .x = BOUNDING_BOX_RIGHT_X - 12.5, .y = BOUNDING_BOX_BOTTOM_Y }, + }, + .n = 8 } } }; @@ -176,8 +233,7 @@ static void deep_copy_polygon8(const struct polygon8 *src, struct polygon8 *dst) { dst->n = src->n; - ARRAY_COPY(dst->x, src->x); - ARRAY_COPY(dst->y, src->y); + ARRAY_COPY(dst->pos, src->pos); } TEST_P(clip_polygon_n_vertices_emitted, test_data) @@ -185,10 +241,9 @@ TEST_P(clip_polygon_n_vertices_emitted, test_data) struct vertex_clip_test_data *tdata = data; struct clip_context ctx; struct polygon8 polygon; - float vertices_x[8]; - float vertices_y[8]; + struct weston_coord vertices[8]; deep_copy_polygon8(&tdata->surface, &polygon); - int emitted = clip_polygon(&ctx, &polygon, vertices_x, vertices_y); + int emitted = clip_polygon(&ctx, &polygon, vertices); assert(emitted == tdata->expected.n); } @@ -198,16 +253,15 @@ TEST_P(clip_polygon_expected_vertices, test_data) struct vertex_clip_test_data *tdata = data; struct clip_context ctx; struct polygon8 polygon; - float vertices_x[8]; - float vertices_y[8]; + struct weston_coord vertices[8]; deep_copy_polygon8(&tdata->surface, &polygon); - int emitted = clip_polygon(&ctx, &polygon, vertices_x, vertices_y); + int emitted = clip_polygon(&ctx, &polygon, vertices); int i = 0; for (; i < emitted; ++i) { - assert(vertices_x[i] == tdata->expected.x[i]); - assert(vertices_y[i] == tdata->expected.y[i]); + assert(vertices[i].x == tdata->expected.pos[i].x); + assert(vertices[i].y == tdata->expected.pos[i].y); } } diff --git a/tests/viewporter-shot-test.c b/tests/viewporter-shot-test.c index 7a8226beb..1f9bca85b 100644 --- a/tests/viewporter-shot-test.c +++ b/tests/viewporter-shot-test.c @@ -35,16 +35,16 @@ struct setup_args { struct fixture_metadata meta; - enum renderer_type renderer; + enum weston_renderer_type renderer; }; static const struct setup_args my_setup_args[] = { { - .renderer = RENDERER_PIXMAN, + .renderer = WESTON_RENDERER_PIXMAN, .meta.name = "pixman" }, { - .renderer = RENDERER_GL, + .renderer = WESTON_RENDERER_GL, .meta.name = "GL" }, }; @@ -92,7 +92,7 @@ TEST(viewport_upscale_solid) move_client(client, 19, 19); match = verify_screen_content(client, "viewport_upscale_solid", 0, - NULL, 0); + NULL, 0, NULL); assert(match); wp_viewport_destroy(viewport); diff --git a/tests/viewporter-test.c b/tests/viewporter-test.c index 4f0109506..cbb973ef4 100644 --- a/tests/viewporter-test.c +++ b/tests/viewporter-test.c @@ -42,6 +42,7 @@ fixture_setup(struct weston_test_harness *harness) struct compositor_setup setup; compositor_setup_defaults(&setup); + setup.shell = SHELL_TEST_DESKTOP; return weston_test_harness_execute_as_client(harness, &setup); } 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 000000000..b2546a31c --- /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 01c4b804d..2a7f938d3 100644 --- a/tests/weston-test-client-helper.c +++ b/tests/weston-test-client-helper.c @@ -37,10 +37,15 @@ #include #include "test-config.h" +#include "pixel-formats.h" +#include "shared/weston-drm-fourcc.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" +#include "weston-output-capture-client-protocol.h" #define max(a, b) (((a) > (b)) ? (a) : (b)) #define min(a, b) (((a) > (b)) ? (b) : (a)) @@ -441,10 +446,11 @@ static const struct wl_surface_listener surface_listener = { surface_leave }; -static struct buffer * +struct buffer * create_shm_buffer(struct client *client, int width, int height, - pixman_format_code_t format, uint32_t wlfmt) + uint32_t drm_format) { + const struct pixel_format_info *pfmt; struct wl_shm *shm = client->wl_shm; struct buffer *buf; size_t stride_bytes; @@ -456,9 +462,13 @@ create_shm_buffer(struct client *client, int width, int height, assert(width > 0); assert(height > 0); + pfmt = pixel_format_get_info(drm_format); + assert(pfmt); + assert(pixel_format_get_plane_count(pfmt) == 1); + buf = xzalloc(sizeof *buf); - bytes_pp = PIXMAN_FORMAT_BPP(format) / 8; + bytes_pp = pfmt->bpp / 8; stride_bytes = width * bytes_pp; /* round up to multiple of 4 bytes for Pixman */ stride_bytes = (stride_bytes + 3) & ~3u; @@ -478,11 +488,13 @@ create_shm_buffer(struct client *client, int width, int height, pool = wl_shm_create_pool(shm, fd, buf->len); buf->proxy = wl_shm_pool_create_buffer(pool, 0, width, height, - stride_bytes, wlfmt); + stride_bytes, + pixel_format_get_shm_format(pfmt)); wl_shm_pool_destroy(pool); close(fd); - buf->image = pixman_image_create_bits(format, width, height, + buf->image = pixman_image_create_bits(pfmt->pixman_format, + width, height, data, stride_bytes); assert(buf->proxy); @@ -496,8 +508,23 @@ create_shm_buffer_a8r8g8b8(struct client *client, int width, int height) { assert(client->has_argb); - return create_shm_buffer(client, width, height, - PIXMAN_a8r8g8b8, WL_SHM_FORMAT_ARGB8888); + return create_shm_buffer(client, width, height, DRM_FORMAT_ARGB8888); +} + +static struct buffer * +create_pixman_buffer(int width, int height, pixman_format_code_t pixman_format) +{ + struct buffer *buf; + + assert(width > 0); + assert(height > 0); + + buf = xzalloc(sizeof *buf); + buf->image = pixman_image_create_bits(pixman_format, + width, height, NULL, 0); + assert(buf->image); + + return buf; } void @@ -542,19 +569,6 @@ test_handle_pointer_position(void *data, struct weston_test *weston_test, test->pointer_x, test->pointer_y); } -static void -test_handle_capture_screenshot_done(void *data, struct weston_screenshooter *screenshooter) -{ - struct client *client = data; - - testlog("Screenshot has been captured\n"); - client->buffer_copy_done = true; -} - -static const struct weston_screenshooter_listener screenshooter_listener = { - test_handle_capture_screenshot_done -}; - static const struct weston_test_listener test_listener = { test_handle_pointer_position, }; @@ -715,6 +729,20 @@ output_handle_scale(void *data, output->scale = scale; } +static void +output_handle_name(void *data, struct wl_output *wl_output, const char *name) +{ + struct output *output = data; + output->name = strdup(name); +} + +static void +output_handle_description(void *data, struct wl_output *wl_output, const char *desc) +{ + struct output *output = data; + output->desc = strdup(desc); +} + static void output_handle_done(void *data, struct wl_output *wl_output) @@ -729,6 +757,8 @@ static const struct wl_output_listener output_listener = { output_handle_mode, output_handle_done, output_handle_scale, + output_handle_name, + output_handle_description, }; static void @@ -737,6 +767,8 @@ output_destroy(struct output *output) assert(wl_proxy_get_version((struct wl_proxy *)output->wl_output) >= 3); wl_output_release(output->wl_output); wl_list_remove(&output->link); + free(output->name); + free(output->desc); free(output); } @@ -798,12 +830,6 @@ handle_global(void *data, struct wl_registry *registry, &weston_test_interface, version); weston_test_add_listener(test->weston_test, &test_listener, test); client->test = test; - } else if (strcmp(interface, "weston_screenshooter") == 0) { - client->screenshooter = - wl_registry_bind(registry, id, - &weston_screenshooter_interface, 1); - weston_screenshooter_add_listener(client->screenshooter, - &screenshooter_listener, client); } } @@ -870,22 +896,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 +1002,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 +1025,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) { @@ -1076,8 +1098,6 @@ client_destroy(struct client *client) free(client->test); } - if (client->screenshooter) - weston_screenshooter_destroy(client->screenshooter); if (client->wl_shm) wl_shm_destroy(client->wl_shm); if (client->wl_compositor) @@ -1147,6 +1167,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 +1254,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 +1269,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 +1283,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 +1369,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 +1450,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 +1460,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 +1517,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; + struct image_header ih = image_header_from(image); + cairo_format_t fmt = format_pixman2cairo(ih.pixman_format); - fmt = format_pixman2cairo(pixman_image_get_format(image)); - - 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) { @@ -1532,25 +1537,21 @@ write_image_as_png(pixman_image_t *image, const char *fname) return true; } -static pixman_image_t * +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; } @@ -1619,46 +1620,166 @@ load_image_from_png(const char *fname) return converted; } +struct output_capturer { + int width; + int height; + uint32_t drm_format; + + struct weston_capture_v1 *factory; + struct weston_capture_source_v1 *source; + + bool complete; +}; + +static void +output_capturer_handle_format(void *data, + struct weston_capture_source_v1 *proxy, + uint32_t drm_format) +{ + struct output_capturer *capt = data; + + capt->drm_format = drm_format; +} + +static void +output_capturer_handle_size(void *data, + struct weston_capture_source_v1 *proxy, + int32_t width, int32_t height) +{ + struct output_capturer *capt = data; + + capt->width = width; + capt->height = height; +} + +static void +output_capturer_handle_complete(void *data, + struct weston_capture_source_v1 *proxy) +{ + struct output_capturer *capt = data; + + capt->complete = true; +} + +static void +output_capturer_handle_retry(void *data, + struct weston_capture_source_v1 *proxy) +{ + assert(0 && "output capture retry in tests indicates a race"); +} + +static void +output_capturer_handle_failed(void *data, + struct weston_capture_source_v1 *proxy, + const char *msg) +{ + testlog("output capture failed: %s", msg ? msg : "?"); + assert(0 && "output capture failed"); +} + +static const struct weston_capture_source_v1_listener output_capturer_source_handlers = { + .format = output_capturer_handle_format, + .size = output_capturer_handle_size, + .complete = output_capturer_handle_complete, + .retry = output_capturer_handle_retry, + .failed = output_capturer_handle_failed, +}; + +struct buffer * +client_capture_output(struct client *client, + struct output *output, + enum weston_capture_v1_source src) +{ + struct output_capturer capt = {}; + struct buffer *buf; + + capt.factory = bind_to_singleton_global(client, + &weston_capture_v1_interface, + 1); + + capt.source = weston_capture_v1_create(capt.factory, + output->wl_output, src); + weston_capture_source_v1_add_listener(capt.source, + &output_capturer_source_handlers, + &capt); + + client_roundtrip(client); + + assert(capt.width != 0 && capt.height != 0 && capt.drm_format != 0 && + "capture source not available"); + + buf = create_shm_buffer(client, + capt.width, capt.height, capt.drm_format); + + weston_capture_source_v1_capture(capt.source, buf->proxy); + while (!capt.complete) + assert(wl_display_dispatch(client->wl_display) >= 0); + + weston_capture_source_v1_destroy(capt.source); + weston_capture_v1_destroy(capt.factory); + + return buf; +} + /** * Take screenshot of a single output * - * Requests a screenshot from the server of the output that the - * client appears on. This implies that the compositor goes through an output + * Requests a screenshot from the server of the output specified + * in output_name. This implies that the compositor goes through an output * repaint to provide the screenshot before this function returns. This * function is therefore both a server roundtrip and a wait for a repaint. * + * The resulting buffer shall contain a copy of the framebuffer contents, + * the output area only, that is, without borders (output decorations). + * The shot is in output physical pixels, with the output scale and + * orientation rather than scale=1 or orientation=normal. The pixel format + * is ensured to be PIXMAN_a8r8g8b8. + * + * @param client a client instance, as created by create_client() + * @param output_name the name of the output, as specified by wl_output.name * @returns A new buffer object, that should be freed with buffer_destroy(). */ struct buffer * -capture_screenshot_of_output(struct client *client) +capture_screenshot_of_output(struct client *client, const char *output_name) { - struct buffer *buffer; + struct image_header ih; + struct buffer *shm; + struct buffer *buf; + struct output *output = NULL; - assert(client->screenshooter); + if (output_name) { + struct output *output_iter; - buffer = create_shm_buffer_a8r8g8b8(client, - client->output->width, - client->output->height); + wl_list_for_each(output_iter, &client->output_list, link) { + if (!strcmp(output_name, output_iter->name)) { + output = output_iter; + break; + } + } - client->buffer_copy_done = false; - weston_screenshooter_take_shot(client->screenshooter, - client->output->wl_output, - buffer->proxy); - while (client->buffer_copy_done == false) - assert(wl_display_dispatch(client->wl_display) >= 0); + assert(output); + } else { + output = client->output; + } - /* FIXME: Document somewhere the orientation the screenshot is taken - * and how the clip coords are interpreted, in case of scaling/transform. - * If we're using read_pixels() just make sure it is documented somewhere. - * Protocol docs in the XML, comparison function docs in Doxygen style. - */ + shm = client_capture_output(client, output, + WESTON_CAPTURE_V1_SOURCE_FRAMEBUFFER); + ih = image_header_from(shm->image); + + if (ih.pixman_format == PIXMAN_a8r8g8b8) + return shm; - return buffer; + buf = create_pixman_buffer(ih.width, ih.height, PIXMAN_a8r8g8b8); + pixman_image_composite32(PIXMAN_OP_SRC, shm->image, NULL, buf->image, + 0, 0, 0, 0, 0, 0, ih.width, ih.height); + + buffer_destroy(shm); + return buf; } static void write_visual_diff(pixman_image_t *ref_image, - struct buffer *shot, + pixman_image_t *shot, const struct rectangle *clip, const char *test_name, int seq_no, @@ -1673,7 +1794,7 @@ write_visual_diff(pixman_image_t *ref_image, assert(ret >= 0); fname = screenshot_output_filename(ext_test_name, seq_no); - diff = visualize_image_difference(ref_image, shot->image, clip, fuzz); + diff = visualize_image_difference(ref_image, shot, clip, fuzz); write_image_as_png(diff, fname); pixman_image_unref(diff); @@ -1712,7 +1833,7 @@ write_visual_diff(pixman_image_t *ref_image, * \sa verify_screen_content */ bool -verify_image(struct buffer *shot, +verify_image(pixman_image_t *shot, const char *ref_image, int ref_seq_no, const struct rectangle *clip, @@ -1733,7 +1854,7 @@ verify_image(struct buffer *shot, } if (ref) { - match = check_images_match(ref, shot->image, clip, &gl_fuzz); + match = check_images_match(ref, shot, clip, &gl_fuzz); testlog("Verify reference image %s vs. shot %s: %s\n", ref_fname, shot_fname, match ? "PASS" : "FAIL"); @@ -1748,7 +1869,7 @@ verify_image(struct buffer *shot, } if (!match) - write_image_as_png(shot->image, shot_fname); + write_image_as_png(shot, shot_fname); free(ref_fname); free(shot_fname); @@ -1766,6 +1887,8 @@ verify_image(struct buffer *shot, * \param ref_seq_no See verify_image(). * \param clip See verify_image(). * \param seq_no See verify_image(). + * \param output_name the output name as specified by wl_output.name. If NULL, + * this is the last wl_output advertised by wl_registry. * \return True if the screen contents matches the reference image, * false otherwise. */ @@ -1774,14 +1897,14 @@ verify_screen_content(struct client *client, const char *ref_image, int ref_seq_no, const struct rectangle *clip, - int seq_no) + int seq_no, const char *output_name) { struct buffer *shot; bool match; - shot = capture_screenshot_of_output(client); + shot = capture_screenshot_of_output(client, output_name); assert(shot); - match = verify_image(shot, ref_image, ref_seq_no, clip, seq_no); + match = verify_image(shot->image, ref_image, ref_seq_no, clip, seq_no); buffer_destroy(shot); return match; diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h index 8e1505d4d..5d0c5363c 100644 --- a/tests/weston-test-client-helper.h +++ b/tests/weston-test-client-helper.h @@ -37,8 +37,8 @@ #include #include "weston-test-runner.h" #include "weston-test-client-protocol.h" -#include "weston-screenshooter-client-protocol.h" #include "viewporter-client-protocol.h" +#include "weston-output-capture-client-protocol.h" struct client { struct wl_display *wl_display; @@ -66,8 +66,6 @@ struct client { int has_argb; struct wl_list global_list; struct wl_list output_list; /* struct output::link */ - struct weston_screenshooter *screenshooter; - bool buffer_copy_done; }; struct global { @@ -163,6 +161,8 @@ struct output { int height; int scale; int initialized; + char *name; + char *desc; }; struct buffer { @@ -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,9 +207,16 @@ 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); +struct buffer * +create_shm_buffer(struct client *client, int width, int height, + uint32_t drm_format); + struct buffer * create_shm_buffer_a8r8g8b8(struct client *client, int width, int height); @@ -232,9 +241,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 +254,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, @@ -265,10 +274,18 @@ pixman_image_t * load_image_from_png(const char *fname); struct buffer * -capture_screenshot_of_output(struct client *client); +capture_screenshot_of_output(struct client *client, const char *output_name); + +struct buffer * +client_capture_output(struct client *client, + struct output *output, + enum weston_capture_v1_source src); + +pixman_image_t * +image_convert_to_a8r8g8b8(pixman_image_t *image); bool -verify_image(struct buffer *shot, +verify_image(pixman_image_t *shot, const char *ref_image, int ref_seq_no, const struct rectangle *clip, @@ -279,7 +296,7 @@ verify_screen_content(struct client *client, const char *ref_image, int ref_seq_no, const struct rectangle *clip, - int seq_no); + int seq_no, const char *output_name); struct buffer * client_buffer_from_image_file(struct client *client, diff --git a/tests/weston-test-desktop-shell.c b/tests/weston-test-desktop-shell.c index 91654baa9..84855c9ea 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 +#include +#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_shell_utils_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_shell_utils_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_shell_utils_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 d4ad369d6..c62a8532f 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; @@ -181,8 +188,8 @@ compositor_setup_defaults_(struct compositor_setup *setup, *setup = (struct compositor_setup) { .test_quirks = (struct weston_testsuite_quirks){ }, .backend = WESTON_BACKEND_HEADLESS, - .renderer = RENDERER_NOOP, - .shell = SHELL_DESKTOP, + .renderer = WESTON_RENDERER_NOOP, + .shell = SHELL_TEST_DESKTOP, .xwayland = false, .width = 320, .height = 240, @@ -199,52 +206,37 @@ static const char * 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", - [WESTON_BACKEND_X11] = "X11-backend.so", + [WESTON_BACKEND_DRM] = "drm", + [WESTON_BACKEND_HEADLESS] = "headless", + [WESTON_BACKEND_RDP] = "rdp", + [WESTON_BACKEND_VNC] = "vnc", + [WESTON_BACKEND_WAYLAND] = "wayland", + [WESTON_BACKEND_X11] = "x11", }; assert(b >= 0 && b < ARRAY_LENGTH(names)); return names[b]; } static const char * -renderer_to_arg(enum weston_compositor_backend b, enum renderer_type r) +renderer_to_str(enum weston_renderer_type t) { - static const char * const headless_names[] = { - [RENDERER_NOOP] = NULL, - [RENDERER_PIXMAN] = "--use-pixman", - [RENDERER_GL] = "--use-gl", - }; - static const char * const drm_names[] = { - [RENDERER_PIXMAN] = "--use-pixman", - [RENDERER_GL] = NULL, + static const char * const names[] = { + [WESTON_RENDERER_NOOP] = "noop", + [WESTON_RENDERER_PIXMAN] = "pixman", + [WESTON_RENDERER_GL] = "gl", }; - - switch (b) { - case WESTON_BACKEND_HEADLESS: - assert(r >= RENDERER_NOOP && r <= RENDERER_GL); - return headless_names[r]; - case WESTON_BACKEND_DRM: - assert(r >= RENDERER_PIXMAN && r <= RENDERER_GL); - return drm_names[r]; - default: - assert(0 && "renderer_to_str() does not know the backend"); - } - - return NULL; + assert(t >= 0 && t <= ARRAY_LENGTH(names)); + return names[t]; } static const char * shell_to_str(enum shell_type t) { static const char * const names[] = { - [SHELL_TEST_DESKTOP] = "weston-test-desktop-shell.so", - [SHELL_DESKTOP] = "desktop-shell.so", - [SHELL_FULLSCREEN] = "fullscreen-shell.so", - [SHELL_IVI] = "ivi-shell.so", + [SHELL_TEST_DESKTOP] = "weston-test-desktop", + [SHELL_DESKTOP] = "desktop", + [SHELL_FULLSCREEN] = "fullscreen", + [SHELL_IVI] = "ivi", }; assert(t >= 0 && t < ARRAY_LENGTH(names)); return names[t]; @@ -284,158 +276,139 @@ execute_compositor(const struct compositor_setup *setup, struct weston_testsuite_data test_data; 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 + const char *drm_device; + 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) { - - drm_device = getenv("WESTON_TEST_SUITE_DRM_DEVICE"); - if (!drm_device) { - fprintf(stderr, "Skipping DRM-backend tests because " \ - "WESTON_TEST_SUITE_DRM_DEVICE is not set. " \ - "See test suite documentation to learn how " \ - "to run them.\n"); - ret = RESULT_SKIP; - goto out; - } - asprintf(&tmp, "--drm-device=%s", drm_device); - prog_args_take(&args, tmp); - - prog_args_take(&args, strdup("--seat=weston-test-seat")); - - prog_args_take(&args, strdup("--continue-without-input")); - - lock_fd = wait_for_lock(); - if (lock_fd == -1) { - ret = RESULT_FAIL; - goto out; - } - } - /* 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 { prog_args_take(&args, strdup("--no-config")); } - ctmp = renderer_to_arg(setup->backend, setup->renderer); - if (ctmp) - prog_args_take(&args, strdup(ctmp)); + str_printf(&tmp, "--renderer=%s", renderer_to_str(setup->renderer)); + prog_args_take(&args, tmp); - 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); } if (setup->xwayland) prog_args_take(&args, strdup("--xwayland")); + 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; + } + + if (setup->backend == WESTON_BACKEND_DRM) { +#ifndef BUILD_DRM_COMPOSITOR + fprintf(stderr, "DRM-backend required but not built, skipping.\n"); + ret = RESULT_SKIP; +#else + drm_device = getenv("WESTON_TEST_SUITE_DRM_DEVICE"); + if (!drm_device) { + fprintf(stderr, "Skipping DRM-backend tests because " \ + "WESTON_TEST_SUITE_DRM_DEVICE is not set. " \ + "See test suite documentation to learn how " \ + "to run them.\n"); + ret = RESULT_SKIP; + } + + str_printf(&tmp, "--drm-device=%s", drm_device); + prog_args_take(&args, tmp); + + prog_args_take(&args, strdup("--seat=weston-test-seat")); + + prog_args_take(&args, strdup("--continue-without-input")); + + lock_fd = wait_for_lock(); + if (lock_fd == -1) + ret = RESULT_FAIL; +#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 == WESTON_RENDERER_GL) { + fprintf(stderr, "GL-renderer required but not built, skipping.\n"); + ret = RESULT_SKIP; + } +#endif + 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); -out: + if (ret == RESULT_OK) + ret = wet_main(args.argc, args.argv, &test_data); + prog_args_fini(&args); /* We acquired a lock (if this is a DRM-backend test) and now we can @@ -472,7 +445,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-fixture-compositor.h b/tests/weston-test-fixture-compositor.h index 89869bba9..b94f62be4 100644 --- a/tests/weston-test-fixture-compositor.h +++ b/tests/weston-test-fixture-compositor.h @@ -31,20 +31,6 @@ #include "weston-testsuite-data.h" -/** Weston renderer type - * - * \sa compositor_setup - * \ingroup testharness - */ -enum renderer_type { - /** Dummy renderer that does nothing. */ - RENDERER_NOOP = 0, - /** Pixman-renderer */ - RENDERER_PIXMAN, - /** GL-renderer */ - RENDERER_GL -}; - /** Weston shell plugin * * \sa compositor_setup @@ -76,7 +62,7 @@ struct compositor_setup { /** The backend to use. */ enum weston_compositor_backend backend; /** The renderer to use. */ - enum renderer_type renderer; + enum weston_renderer_type renderer; /** The shell plugin to use. */ enum shell_type shell; /** Whether to enable xwayland support. */ @@ -113,7 +99,7 @@ compositor_setup_defaults_(struct compositor_setup *setup, * The defaults are: * - backend: headless * - renderer: noop - * - shell: desktop shell + * - shell: test desktop shell * - xwayland: no * - width: 320 * - height: 240 diff --git a/tests/weston-test-runner.c b/tests/weston-test-runner.c index 0ab7bc1b4..1651fe1b3 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_(); @@ -642,6 +662,7 @@ main(int argc, char *argv[]) fi_end = fi + 1; } + printf("TAP version 13\n"); tap_plan(&harness->data, fi_end - fi); testlog("Iterating through %d fixtures.\n", fi_end - fi); diff --git a/tests/weston-test.c b/tests/weston-test.c index f8db286b8..cc6ba4332 100644 --- a/tests/weston-test.c +++ b/tests/weston-test.c @@ -146,11 +146,14 @@ notify_pointer_position(struct weston_test *test, struct wl_resource *resource) struct weston_seat *seat = get_seat(test); struct weston_pointer *pointer = weston_seat_get_pointer(seat); - weston_test_send_pointer_position(resource, pointer->x, pointer->y); + weston_test_send_pointer_position(resource, + wl_fixed_from_double(pointer->pos.c.x), + wl_fixed_from_double(pointer->pos.c.y)); } static void -test_surface_committed(struct weston_surface *surface, int32_t sx, int32_t sy) +test_surface_committed(struct weston_surface *surface, + struct weston_coord_surface new_origin) { struct weston_test_surface *test_surface = surface->committed_private; struct weston_test *test = test_surface->test; @@ -164,7 +167,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; } @@ -285,12 +288,13 @@ move_pointer(struct wl_client *client, struct wl_resource *resource, struct weston_seat *seat = get_seat(test); struct weston_pointer *pointer = weston_seat_get_pointer(seat); struct weston_pointer_motion_event event = { 0 }; + struct weston_coord_global pos; struct timespec time; + pos.c = weston_coord(x, y); event = (struct weston_pointer_motion_event) { .mask = WESTON_POINTER_MOTION_REL, - .dx = wl_fixed_to_double(wl_fixed_from_int(x) - pointer->x), - .dy = wl_fixed_to_double(wl_fixed_from_int(y) - pointer->y), + .rel = weston_coord_sub(pos.c, pointer->pos.c), }; timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); @@ -421,13 +425,27 @@ send_touch(struct wl_client *client, struct wl_resource *resource, struct weston_test *test = wl_resource_get_user_data(resource); struct weston_touch_device *device = test->touch_device[0]; struct timespec time; + struct weston_coord_global pos; assert(device); timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); - notify_touch(device, &time, touch_id, wl_fixed_to_double(x), - wl_fixed_to_double(y), touch_type); + if (touch_type == WL_TOUCH_UP) { + if (x != 0 || y != 0) { + wl_resource_post_error(resource, + WESTON_TEST_ERROR_TOUCH_UP_WITH_COORDINATE, + "Test protocol sent valid " + "coordinates with WL_TOUCH_UP"); + + return; + } + + notify_touch(device, &time, touch_id, NULL, touch_type); + } else { + pos.c = weston_coord_from_fixed(x, y); + notify_touch(device, &time, touch_id, &pos, touch_type); + } } static const struct weston_test_interface test_implementation = { diff --git a/tests/xcb-client-helper.c b/tests/xcb-client-helper.c new file mode 100644 index 000000000..f8f649e34 --- /dev/null +++ b/tests/xcb-client-helper.c @@ -0,0 +1,776 @@ +/* + * 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 +#include "test-config.h" +#include "shared/os-compatibility.h" +#include "shared/helpers.h" +#include "shared/xalloc.h" +#include "shared/xcb-xwayland.h" +#include +#include "xcb-client-helper.h" + +#define DEBUG + +#ifdef DEBUG +#define printfd(fmt, args...) do { \ + fprintf(stderr, fmt, ##args); \ +} while (0) +#else +#define printfd(fmt, args...) {} +#endif + +struct event_response { + uint8_t response_type; + bool (*eventcb)(xcb_generic_event_t *e, struct window_x11 *win); + const char *name; +}; + +const char *to_event_name(uint8_t event); + +static xcb_drawable_t +handle_event_to_wid(xcb_generic_event_t *ev) +{ + xcb_drawable_t wid; + + switch (EVENT_TYPE(ev)) { + case XCB_CREATE_NOTIFY: { + xcb_create_notify_event_t *ce = (xcb_create_notify_event_t *) ev; + wid = ce->window; + break; + } + case XCB_DESTROY_NOTIFY: { + xcb_destroy_notify_event_t *de = (xcb_destroy_notify_event_t *) ev; + wid = de->window; + break; + } + case XCB_MAP_NOTIFY: { + xcb_map_notify_event_t *mn = (xcb_map_notify_event_t *) ev; + wid = mn->window; + break; + } + case XCB_UNMAP_NOTIFY: { + xcb_unmap_notify_event_t *un = (xcb_unmap_notify_event_t *) ev; + wid = un->window; + break; + } + case XCB_PROPERTY_NOTIFY: { + xcb_property_notify_event_t *pn = (xcb_property_notify_event_t *) ev; + wid = pn->window; + break; + } + case XCB_CONFIGURE_NOTIFY: { + xcb_configure_notify_event_t *cn = (xcb_configure_notify_event_t *) ev; + wid = cn->window; + break; + } + case XCB_EXPOSE: { + xcb_expose_event_t *ep = (xcb_expose_event_t *) ev; + wid = ep->window; + break; + } + case XCB_REPARENT_NOTIFY: { + xcb_reparent_notify_event_t *re = (xcb_reparent_notify_event_t *) ev; + wid = re->window; + break; + } + default: + wid = 0; + } + + return wid; +} + +void +handle_event_remove_pending(struct window_state *wstate) +{ + wl_list_remove(&wstate->link); + free(wstate); +} + +/* returns true if all events in the pending_list has been accounted for */ +static bool +handle_event_check_pending(struct window_x11 *window, xcb_generic_event_t *ev) +{ + + struct window_state *wstate, *wstate_next; + struct wl_list *pending_events = + &window->tentative_state.pending_events_list; + xcb_drawable_t wid; + uint8_t event; + bool found = false; + + event = EVENT_TYPE(ev); + wid = handle_event_to_wid(ev); + + wl_list_for_each_safe(wstate, wstate_next, pending_events, link) { + if (wstate->event != event) + continue; + + if (wstate->wid == wid) { + handle_event_remove_pending(wstate); + found = true; + printfd("%s: removed event %d - %s\n", __func__, + event, to_event_name(event)); + break; + } + } + + if (!found) { + printfd("%s(): event id %d, name %s not found\n", __func__, + event, to_event_name(event)); + return false; + } + + /* still need to get events? -> wait one more round */ + if (!wl_list_empty(pending_events)) { + printfd("%s(): still have %d events to handle!\n", __func__, + wl_list_length(pending_events)); + return false; + } + + return true; +} + +/** In case you need to wait for a notify event call use this function to do so + * and then call handle_events_x11() at the end, to wait for the events to be + * delivered/handled. Note that handle_events_x11() will wait forever if + * handle_event_set_pending() was called for an event which never arrives. + * + * You can call this function multiple times for the same wid, in case you + * expect multiple events to be delivered (i.e., a map notify event and a + * expose one). All functions in this XCB wrapper library calls this function, + * with the user only needing to call handle_events_x11() at the end. This + * function is only needed if the test itself requires to wait for additional + * events, or when expanding this library with other states changes + * (max/fullscreen). + * + * \param window the window_x11 in question + * \param event the event to wait for, like XCB_MAP_NOTIFY, XCB_EXPOSE, etc. + * \param pending_state the pending event to wait for, similar to the XCB ones + * but defined in enum w_state + * \param wid the window id, could be different than that of the window itself + */ +void +handle_event_set_pending(struct window_x11 *window, uint8_t event, + enum w_state pending_state, xcb_drawable_t wid) +{ + struct window_state *wstate = xzalloc(sizeof(*wstate)); + + wstate->event = event; + wstate->wid = wid; + wstate->pending_state = pending_state; + wl_list_insert(&window->tentative_state.pending_events_list, + &wstate->link); + + printfd("%s: Added pending event id %d - name %s, wid %d\n", + __func__, event, to_event_name(event), wid); +} + +static bool +handle_map_notify(xcb_generic_event_t *e, struct window_x11 *window) +{ + xcb_map_notify_event_t *ce = (xcb_map_notify_event_t *) e; + + if (ce->window != window->win_id) + return false; + + window_state_set_flag(window, MAPPED); + + return true; +} + +static bool +handle_unmap_notify(xcb_generic_event_t *e, struct window_x11 *window) +{ + xcb_unmap_notify_event_t *ce = (xcb_unmap_notify_event_t*) e; + + if (ce->window != window->win_id && ce->window != window->frame_id) + return false; + + assert(window_state_has_flag(window, MAPPED)); + window_state_set_flag(window, UNMAPPED); + + return true; +} + +static bool +handle_create_notify(xcb_generic_event_t *e, struct window_x11 *window) +{ + xcb_create_notify_event_t *ce = (xcb_create_notify_event_t*) e; + + if (ce->window != window->win_id) + return false; + + window_state_set_flag(window, CREATED); + + return true; +} + + +static bool +handle_destroy_notify(xcb_generic_event_t *e, struct window_x11 *window) +{ + xcb_destroy_notify_event_t *dn = (xcb_destroy_notify_event_t*) e; + + if (window->win_id != dn->window) + return false; + + assert(window_state_has_flag(window, CREATED)); + window_state_set_flag(window, DESTROYED); + + return true; +} + +static bool +handle_property_notify(xcb_generic_event_t *e, struct window_x11 *window) +{ + xcb_property_notify_event_t *pn = (xcb_property_notify_event_t *) e; + struct atom_x11 *atoms = window->conn->atoms; + + if (pn->window != window->win_id) + return false; + + if (pn->atom == atoms->net_wm_name) { + window_state_set_flag(window, PROPERTY_NAME); + return true; + } + + return false; +} + +static bool +handle_expose(xcb_generic_event_t *e, struct window_x11 *window) +{ + xcb_expose_event_t *ep = (xcb_expose_event_t *) e; + + if (ep->window != window->win_id) + return false; + + window_state_set_flag(window, EXPOSE); + return true; +} + +static bool +handle_configure_notify(xcb_generic_event_t *e, struct window_x11 *window) +{ + xcb_configure_notify_event_t *cn = (xcb_configure_notify_event_t*) e; + + /* we're not interested into other's windows */ + if (cn->window != window->win_id) + return false; + + return true; +} + +static bool +handle_reparent_notify(xcb_generic_event_t *e, struct window_x11 *window) +{ + xcb_reparent_notify_event_t *re = (xcb_reparent_notify_event_t *) e; + + if (re->window == window->win_id && window->frame_id == 0) { + window->frame_id = re->parent; + window_state_set_flag(window, REPARENT); + printfd("Window reparent frame id %d\n", window->frame_id); + return true; + } + + return false; +} + +/* the event handlers should return a boolean that denotes that fact + * they've been handled. One can customize that behaviour such that + * it forces handle_events_x11() to wait for additional events, in case + * that's needed. */ +static const struct event_response events[] = { + { XCB_CREATE_NOTIFY, handle_create_notify, "CREATE_NOTIFY" }, + { XCB_MAP_NOTIFY, handle_map_notify, "MAP_NOTIFY" }, + { XCB_UNMAP_NOTIFY, handle_unmap_notify, "UNMAP_NOTIFY" }, + { XCB_EXPOSE, handle_expose, "EXPOSE_NOTIFY" }, + { XCB_PROPERTY_NOTIFY, handle_property_notify, "PROPERTY_NOTIFY" }, + { XCB_CONFIGURE_NOTIFY, handle_configure_notify, "CONFIGURE_NOTIFY" }, + { XCB_DESTROY_NOTIFY, handle_destroy_notify, "DESTROY_NOTIFY" }, + { XCB_REPARENT_NOTIFY, handle_reparent_notify, "REPARENT_NOTIFY" }, +}; + +const char * +to_event_name(uint8_t event) +{ + size_t i; + for (i = 0; i < ARRAY_LENGTH(events); i++) + if (events[i].response_type == event) + return events[i].name; + + return "(unknown event)"; +} + +/** + * Tells the X server to display the window. Call handle_events_x11() after + * calling this function to wait for events to be delivered. Note there's no + * need to include additional events, they're already added. + * + * \sa handle_events_x11(). + * + * \param window the window in question + */ +void +window_x11_map(struct window_x11 *window) +{ + handle_event_set_pending(window, XCB_MAP_NOTIFY, MAPPED, window->win_id); + handle_event_set_pending(window, XCB_EXPOSE, EXPOSE, window->win_id); + + /* doing a synchronization for the frame wid helps with other potential + * states changes like max/fullscreen or if we try to map the window + * from the beginning with a max/fullscreen state rather than normal + * state (!max && !fullscreen) + */ + handle_event_set_pending(window, XCB_REPARENT_NOTIFY, REPARENT, window->win_id); + + xcb_map_window(window->conn->connection, window->win_id); + xcb_flush(window->conn->connection); +} + +/** + * \sa window_x11_map, handle_events_x11. Tells the X server to unmap the + * window. Call handle_events_x11() to wait for the events to be delivered. + * + * \param window the window in question + */ +void +window_x11_unmap(struct window_x11 *window) +{ + handle_event_set_pending(window, XCB_UNMAP_NOTIFY, UNMAPPED, window->win_id); + + xcb_unmap_window(window->conn->connection, window->win_id); + xcb_flush(window->conn->connection); +} + +static xcb_generic_event_t * +poll_for_event(xcb_connection_t *conn) +{ + int fd = xcb_get_file_descriptor(conn); + struct pollfd pollfds = {}; + int rpol; + + pollfds.fd = fd; + pollfds.events = POLLIN; + + rpol = ppoll(&pollfds, 1, NULL, NULL); + if (rpol > 0 && (pollfds.revents & POLLIN)) + return xcb_wait_for_event(conn); + + return NULL; +} + +static void +window_x11_set_cursor(struct window_x11 *window, const char *cursor_name) +{ + assert(window); + assert(window->ctx == NULL); + + if (xcb_cursor_context_new(window->conn->connection, + window->screen, &window->ctx) < 0) { + fprintf(stderr, "Error creating context!\n"); + return; + } + + window->cursor = xcb_cursor_load_cursor(window->ctx, cursor_name); + + xcb_change_window_attributes(window->conn->connection, + window->root_win_id, XCB_CW_CURSOR, + &window->cursor); + xcb_flush(window->conn->connection); +} + +static bool +handle_event(xcb_generic_event_t *ev, struct window_x11 *window) +{ + uint8_t event = EVENT_TYPE(ev); + bool events_handled = false; + size_t i; + + for (i = 0; i < ARRAY_LENGTH(events); i++) { + if (event == events[i].response_type) { + bool ev_cb_handled = events[i].eventcb(ev, window); + if (!ev_cb_handled) + return false; + + events_handled = + handle_event_check_pending(window, ev); + } + } + + return events_handled; +} + +/** + * Each operation on 'window_x11' requires calling handle_events_x11() to a) flush + * out the connection, b) poll for a xcb_generic_event_t and c) to call the + * appropriate event handler; + * + * This function should never block to allow a programmatic way of applying + * different operations/states to the window. If that happens, running it + * under meson test will cause a test fail (with a timeout). + * + * Before calling this function, one *shall* use handle_event_set_pending() to + * explicitly set which events to wait for. Not doing so will effectively + * deadlock the test, as it will wait for pending events never being set. + * + * Note that all state change functions, including map and unmap, would + * implicitly, if not otherwise stated, set the pending events to wait for. + * + * \sa handle_event_set_pending() + * \param window the X11 window in question + */ +int +handle_events_x11(struct window_x11 *window) +{ + bool running = true; + xcb_generic_event_t *ev; + int ret = 0; + + assert(window->handle_in_progress == false); + window->handle_in_progress = true; + + do { + bool events_handled = false; + + xcb_flush(window->conn->connection); + + if (xcb_connection_has_error(window->conn->connection)) { + fprintf(stderr, "X11 connection got interrupted\n"); + ret = -1; + break; + } + + ev = poll_for_event(window->conn->connection); + if (!ev) { + fprintf(stderr, "Error, no event received, " + "although we requested for one!\n"); + break; + } + + events_handled = handle_event(ev, window); + + /* signals that we've done processing all the pending events */ + if (events_handled) { + running = false; + } + + free(ev); + } while (running); + + window->handle_in_progress = false; + return ret; +} + +/* Might be useful in case you'd want to receive create notify for our own window. */ +void +window_x11_notify_for_root_events(struct window_x11 *window) +{ + int mask_values = + XCB_EVENT_MASK_STRUCTURE_NOTIFY | + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY; + + xcb_change_window_attributes(window->conn->connection, + window->root_win_id, + XCB_CW_EVENT_MASK, &mask_values); + xcb_flush(window->conn->connection); +} + +/** + * Sets the x11 window a property name. Call handle_events_x11() to + * wait for the events to be delivered. + * + * \param window the window in question + * \param name the name, as string + * + */ +void +window_x11_set_win_name(struct window_x11 *window, const char *name) +{ + struct atom_x11 *atoms = window->conn->atoms; + + handle_event_set_pending(window, XCB_PROPERTY_NOTIFY, PROPERTY_NAME, window->win_id); + + xcb_change_property(window->conn->connection, XCB_PROP_MODE_REPLACE, + window->win_id, atoms->net_wm_name, + atoms->string, 8, + strlen(name), name); + xcb_flush(window->conn->connection); +} + +/** Create a x11 connection. + * + * \sa window_get_connection() to retrieve it in the same tests to avoid + * creating a new connection + * \sa create_x11_window(), where you need to pass this connection_x11 object. + * + * \return a struct connection_x11 which defines a x11 connection. + */ +struct connection_x11 * +create_x11_connection(void) +{ + struct connection_x11 *conn; + + if (access(XSERVER_PATH, X_OK) != 0) + return NULL; + + conn = xzalloc(sizeof(*conn)); + conn->connection = xcb_connect(NULL, NULL); + if (!conn->connection) + return NULL; + + conn->atoms = xzalloc(sizeof(struct atom_x11)); + + /* retrieve atoms */ + x11_get_atoms(conn->connection, conn->atoms); + + return conn; +} + +/** Destroys a x11 connection. Use this at the end (of the test) to destroy the + * x11 connection. + * + * \param conn the x11 connection in question. + */ +void +destroy_x11_connection(struct connection_x11 *conn) +{ + xcb_disconnect(conn->connection); + + free(conn->atoms); + free(conn); +} + + +/** + * creates a X window, based on the initial supplied values. All operations + * performed will work on this window_x11 object. + * + * The creation and destruction of the window_x11 object is handled implictly + * so there's no need wait for (additional) events, like it is required for + * all other change state operations. + * + * The window is not mapped/displayed so that needs to happen explictly, by + * calling window_x11_map() and then waiting for events using + * handle_events_x11(). + * + * \param width initial size, width value + * \param height initial size, height value + * \param pos_x initial position, x value + * \param pos_y initial position, y value + * \param conn X11 connection + * \param bg_color a background color + * \param parent the window_x11 parent + * \return a pointer to window_x11, which gets destroyed with destroy_x11_window() + */ +struct window_x11 * +create_x11_window(int width, int height, int pos_x, int pos_y, + struct connection_x11 *conn, pixman_color_t bg_color, + struct window_x11 *parent) +{ + uint32_t colorpixel = 0x0; + uint32_t values[2]; + uint32_t mask = 0; + + xcb_colormap_t colormap; + struct window_x11 *window; + xcb_window_t parent_win_id; + xcb_alloc_color_cookie_t cookie; + xcb_alloc_color_reply_t *reply; + xcb_void_cookie_t cookie_create; + xcb_generic_error_t *error_create; + const struct xcb_setup_t *xcb_setup; + + assert(conn); + window = xzalloc(sizeof(*window)); + + window->conn = conn; + xcb_setup = xcb_get_setup(window->conn->connection); + window->screen = xcb_setup_roots_iterator(xcb_setup).data; + + wl_list_init(&window->window_list); + wl_list_init(&window->tentative_state.pending_events_list); + + window->root_win_id = window->screen->root; + window->parent = parent; + if (window->parent) { + parent_win_id = window->parent->win_id; + wl_list_insert(&parent->window_list, &window->window_link); + } else { + parent_win_id = window->root_win_id; + } + + colormap = window->screen->default_colormap; + cookie = xcb_alloc_color(window->conn->connection, colormap, + bg_color.red, bg_color.blue, bg_color.green); + reply = xcb_alloc_color_reply(window->conn->connection, cookie, NULL); + assert(reply); + + colorpixel = reply->pixel; + free(reply); + + window->background = xcb_generate_id(window->conn->connection); + mask = XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES; + values[0] = colorpixel; + values[1] = 0; + window->bg_color = bg_color; + + xcb_create_gc(window->conn->connection, window->background, + window->root_win_id, mask, values); + + /* create the window */ + window->win_id = xcb_generate_id(window->conn->connection); + mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; + values[0] = colorpixel; + values[1] = XCB_EVENT_MASK_EXPOSURE | + XCB_EVENT_MASK_KEY_PRESS | + XCB_EVENT_MASK_VISIBILITY_CHANGE | + XCB_EVENT_MASK_STRUCTURE_NOTIFY | + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | + XCB_EVENT_MASK_PROPERTY_CHANGE; + + window->pos_x = pos_x; + window->pos_y = pos_y; + + window->width = width; + window->height = height; + + cookie_create = xcb_create_window_checked(window->conn->connection, + XCB_COPY_FROM_PARENT, + window->win_id, parent_win_id, + window->pos_x, window->pos_y, + window->width, window->height, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + window->screen->root_visual, + mask, values); + error_create = xcb_request_check(window->conn->connection, cookie_create); + assert(error_create == NULL); + + window_state_set_flag(window, CREATED); + window_x11_set_cursor(window, "left_ptr"); + + return window; +} + +static void +kill_window(struct window_x11 *window) +{ + handle_event_set_pending(window, XCB_DESTROY_NOTIFY, DESTROYED, window->win_id); + + xcb_destroy_window(window->conn->connection, window->win_id); + xcb_flush(window->conn->connection); +} + +/** + * \sa create_x11_window(). The creation and destruction of the window_x11 is + * handled implicitly so there's no wait for (additional) events. + * + * This function would wait for destroy notify event and will disconnect from + * the server. No further operation can happen on the window_x11, except + * for destroying the x11 connection using destroy_x11_connection(). + * + * \param window the window in question + */ +void +destroy_x11_window(struct window_x11 *window) +{ + struct window_state *wstate, *wstate_next; + + xcb_free_cursor(window->conn->connection, window->cursor); + xcb_cursor_context_free(window->ctx); + xcb_flush(window->conn->connection); + + kill_window(window); + handle_events_x11(window); + + /* in case we're called before any events have been handled */ + wl_list_for_each_safe(wstate, wstate_next, + &window->tentative_state.pending_events_list, link) + handle_event_remove_pending(wstate); + + free(window); +} + +/** + * Return the reply_t for an atom + * + * \param window the window in question + * \param win the handle for the window; could be different from the window itself! + * \param atom the atom in question + * + */ +xcb_get_property_reply_t * +window_x11_dump_prop(struct window_x11 *window, xcb_drawable_t win, xcb_atom_t atom) +{ + xcb_get_property_cookie_t prop_cookie; + xcb_get_property_reply_t *prop_reply; + + prop_cookie = xcb_get_property(window->conn->connection, 0, win, atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, 2048); + + prop_reply = xcb_get_property_reply(window->conn->connection, prop_cookie, NULL); + + /* callers needs to free it */ + return prop_reply; +} + +/** Retrieve the atoms + * + * \param win the window in question from which to retrieve the atoms + * + */ +struct atom_x11 * +window_get_atoms(struct window_x11 *win) +{ + return win->conn->atoms; +} + +/** Retrive the connection_x11 from the window_x11 + * + * \param win the window in question from which to retrieve the connection + * + */ +struct xcb_connection_t * +window_get_connection(struct window_x11 *win) +{ + return win->conn->connection; +} diff --git a/tests/xcb-client-helper.h b/tests/xcb-client-helper.h new file mode 100644 index 000000000..e8684bec7 --- /dev/null +++ b/tests/xcb-client-helper.h @@ -0,0 +1,195 @@ +/* + * 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 "config.h" + +#include +#include +#include +#include + +#include "shared/xcb-xwayland.h" +#include +#include + +#include +#include + +enum w_state { + CREATED = 1 << 0, + MAPPED = 1 << 1, + UNMAPPED = 1 << 2, + PROPERTY_NAME = 1 << 3, + DESTROYED = 1 << 4, + EXPOSE = 1 << 5, + REPARENT = 1 << 6, +}; + +struct window_state { + uint8_t event; + enum w_state pending_state; + xcb_drawable_t wid; + struct wl_list link; /** window_x11.tentative_state::pending_events_list */ +}; + +struct connection_x11 { + struct atom_x11 *atoms; + struct xcb_connection_t *connection; +}; + +struct window_x11 { + struct window_x11 *parent; + struct xcb_screen_t *screen; + struct connection_x11 *conn; + bool handle_in_progress; + + xcb_drawable_t root_win_id; /* screen root */ + xcb_drawable_t win_id; /* this window */ + xcb_drawable_t parent_win_id; /* the parent, if set */ + + xcb_gcontext_t background; + + xcb_cursor_context_t *ctx; + xcb_cursor_t cursor; + + int width; + int height; + + int pos_x; + int pos_y; + + pixman_color_t bg_color; + + /* these track what the X11 client does */ + struct { + /* pending queue events */ + struct wl_list pending_events_list; /** window_state::link */ + } tentative_state; + + /* these track what we got back from the server */ + struct { + /* applied, received event */ + uint32_t win_state; + } state; + + struct wl_list window_list; + struct wl_list window_link; + + xcb_window_t frame_id; +}; + +void +window_x11_map(struct window_x11 *window); + +void +window_x11_unmap(struct window_x11 *window); + + +struct connection_x11 * +create_x11_connection(void); + +void +destroy_x11_connection(struct connection_x11 *conn); + +struct window_x11 * +create_x11_window(int width, int height, int pos_x, int pos_y, struct connection_x11 *conn, + pixman_color_t bg_color, struct window_x11 *parent); +void +destroy_x11_window(struct window_x11 *window); + +void +window_x11_set_win_name(struct window_x11 *window, const char *name); + +xcb_get_property_reply_t * +window_x11_dump_prop(struct window_x11 *window, xcb_drawable_t win, xcb_atom_t atom); + +void +handle_event_set_pending(struct window_x11 *window, uint8_t event, + enum w_state pending_state, xcb_drawable_t wid); + +void +handle_event_remove_pending(struct window_state *wstate); + +int +handle_events_x11(struct window_x11 *window); + +struct atom_x11 * +window_get_atoms(struct window_x11 *win); + +struct xcb_connection_t * +window_get_connection(struct window_x11 *win); + +void +window_x11_notify_for_root_events(struct window_x11 *window); + +/* note that the flag is already bitshiftted */ +static inline bool +window_state_has_flag(struct window_x11 *win, enum w_state flag) +{ + return (win->state.win_state & flag) == flag; +} + +static inline void +window_state_set_flag(struct window_x11 *win, enum w_state flag) +{ + win->state.win_state |= flag; +} + +static inline void +window_state_clear_flag(struct window_x11 *win, enum w_state flag) +{ + win->state.win_state &= ~flag; +} + +static inline void +window_state_clear_all_flags(struct window_x11 *win) +{ + win->state.win_state = 0; +} + +/** + * A wrapper over handle_events_x11() to check if the pending flag has been + * set, that waits for events calling handle_events_x11() and that verifies + * afterwards if the flag has indeed been applied. + */ +static inline void +handle_events_and_check_flags(struct window_x11 *win, enum w_state flag) +{ + struct wl_list *pending_events = + &win->tentative_state.pending_events_list; + struct window_state *wstate; + bool found_pending_flag = false; + + wl_list_for_each(wstate, pending_events, link) { + if ((wstate->pending_state & flag) == flag) + found_pending_flag = true; + } + assert(found_pending_flag); + + handle_events_x11(win); + assert(window_state_has_flag(win, flag)); +} diff --git a/tests/xwayland-test.c b/tests/xwayland-test.c index 7017eb008..693be2d02 100644 --- a/tests/xwayland-test.c +++ b/tests/xwayland-test.c @@ -1,5 +1,6 @@ /* * Copyright © 2015 Samsung Electronics Co., 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 @@ -27,8 +28,10 @@ * * This is done in steps: * 1) Confirm that the WL_SURFACE_ID atom exists - * 2) Confirm that the window manager's name is "Weston WM" - * 3) Make sure we can map a window + * 2) Confirm that our window name is "Xwayland Test Window" + * 3) Confirm that there's conforming Window Manager + * 4) Confirm that the window manager's name is "Weston WM" + * 5) Make sure we can map a window */ #include "config.h" @@ -37,12 +40,13 @@ #include #include #include -#include -#include #include #include "weston-test-runner.h" #include "weston-test-fixture-compositor.h" +#include "shared/string-helpers.h" +#include "weston-test-client-helper.h" +#include "xcb-client-helper.h" static enum test_result_code fixture_setup(struct weston_test_harness *harness) @@ -50,69 +54,123 @@ fixture_setup(struct weston_test_harness *harness) struct compositor_setup setup; compositor_setup_defaults(&setup); + setup.shell = SHELL_TEST_DESKTOP; setup.xwayland = true; + setup.logging_scopes = "log,xwm-wm-x11"; return weston_test_harness_execute_as_client(harness, &setup); } DECLARE_FIXTURE_SETUP(fixture_setup); +static char * +get_x11_window_name(struct window_x11 *window, xcb_drawable_t win) +{ + xcb_get_property_reply_t *reply; + int reply_len; + char *name; + struct atom_x11 *atoms = window_get_atoms(window); + + reply = window_x11_dump_prop(window, win, atoms->net_wm_name); + assert(reply); + + assert(reply->type == atoms->string || + reply->type == atoms->utf8_string); + reply_len = xcb_get_property_value_length(reply); + assert(reply_len > 0); + + str_printf(&name, "%.*s", reply_len, + (char *) xcb_get_property_value(reply)); + free(reply); + return name; +} + +static char * +get_wm_name(struct window_x11 *window) +{ + xcb_get_property_reply_t *reply; + xcb_generic_error_t *error; + xcb_get_property_cookie_t prop_cookie; + char *wm_name = NULL; + struct atom_x11 *atoms = window_get_atoms(window); + struct xcb_connection_t *conn = window_get_connection(window); + + prop_cookie = xcb_get_property(conn, 0, window->root_win_id, + atoms->net_supporting_wm_check, + XCB_ATOM_WINDOW, 0, 1024); + reply = xcb_get_property_reply(conn, prop_cookie, &error); + assert(reply); + assert(reply->type == XCB_ATOM_WINDOW); + assert(reply->format == 32); + + xcb_window_t wm_id = *(xcb_window_t *) xcb_get_property_value(reply); + wm_name = get_x11_window_name(window, wm_id); + free(reply); + + free(error); + return wm_name; +} + TEST(xwayland_client_test) { - Display *display; - Window window, root, *support; - XEvent event; - int screen, status, actual_format; - unsigned long nitems, bytes; - Atom atom, type_atom, actual_type; + struct window_x11 *window; + struct connection_x11 *conn; + xcb_get_property_reply_t *reply; + char *win_name; char *wm_name; - - if (access(XSERVER_PATH, X_OK) != 0) - exit(77); - - display = XOpenDisplay(NULL); - if (!display) - exit(EXIT_FAILURE); - - atom = XInternAtom(display, "WL_SURFACE_ID", True); - assert(atom != None); - - atom = XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", True); - assert(atom != None); - - screen = DefaultScreen(display); - root = RootWindow(display, screen); - - status = XGetWindowProperty(display, root, atom, 0L, ~0L, - False, XA_WINDOW, &actual_type, - &actual_format, &nitems, &bytes, - (void *)&support); - assert(status == Success); - - atom = XInternAtom(display, "_NET_WM_NAME", True); - assert(atom != None); - type_atom = XInternAtom(display, "UTF8_STRING", True); - assert(atom != None); - status = XGetWindowProperty(display, *support, atom, 0L, BUFSIZ, - False, type_atom, &actual_type, - &actual_format, &nitems, &bytes, - (void *)&wm_name); - assert(status == Success); - assert(nitems); - assert(strcmp("Weston WM", wm_name) == 0); - free(support); + pixman_color_t bg_color; + struct atom_x11 *atoms; + + color_rgb888(&bg_color, 255, 0, 0); + + conn = create_x11_connection(); + assert(conn); + window = create_x11_window(100, 100, 100, 100, conn, bg_color, NULL); + assert(window); + + window_x11_set_win_name(window, "Xwayland Test Window"); + handle_events_and_check_flags(window, PROPERTY_NAME); + + + /* The Window Manager MUST set _NET_SUPPORTING_WM_CHECK on the root + * window to be the ID of a child window created by himself, to + * indicate that a compliant window manager is active. + * + * The child window MUST also have the _NET_SUPPORTING_WM_CHECK + * property set to the ID of the child window. The child window MUST + * also have the _NET_WM_NAME property set to the name of the Window + * Manager. + * + * See Extended Window Manager Hints, + * https://specifications.freedesktop.org/wm-spec/latest/ar01s03.html, + * _NET_SUPPORTING_WM_CHECK + * */ + atoms = window_get_atoms(window); + assert(atoms->net_supporting_wm_check != XCB_ATOM_NONE); + assert(atoms->wl_surface_id != XCB_ATOM_NONE); + assert(atoms->net_wm_name != XCB_ATOM_NONE); + assert(atoms->utf8_string != XCB_ATOM_NONE); + + reply = window_x11_dump_prop(window, window->root_win_id, + atoms->net_supporting_wm_check); + assert(reply); + assert(reply->type == XCB_ATOM_WINDOW); + free(reply); + + window_x11_map(window); + handle_events_and_check_flags(window, MAPPED); + + win_name = get_x11_window_name(window, window->win_id); + assert(strcmp(win_name, "Xwayland Test Window") == 0); + free(win_name); + + wm_name = get_wm_name(window); + assert(wm_name); + assert(strcmp(wm_name, "Weston WM") == 0); free(wm_name); - window = XCreateSimpleWindow(display, root, 100, 100, 300, 300, 1, - BlackPixel(display, screen), - WhitePixel(display, screen)); - XSelectInput(display, window, ExposureMask); - XMapWindow(display, window); - - while (1) { - XNextEvent(display, &event); - if (event.type == Expose) - break; - } + window_x11_unmap(window); + handle_events_and_check_flags(window, UNMAPPED); - XCloseDisplay(display); + destroy_x11_window(window); + destroy_x11_connection(conn); } diff --git a/tests/yuv-buffer-test.c b/tests/yuv-buffer-test.c index ad0b298bb..2a3c679c8 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" @@ -43,7 +44,7 @@ fixture_setup(struct weston_test_harness *harness) struct compositor_setup setup; compositor_setup_defaults(&setup); - setup.renderer = RENDERER_GL; + setup.renderer = WESTON_RENDERER_GL; setup.width = 324; setup.height = 264; setup.shell = SHELL_TEST_DESKTOP; @@ -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 }, @@ -485,7 +464,7 @@ TEST_P(yuv_buffer_shm, yuv_cases) buf = my_case->create_buffer(client, my_case->drm_format, img); show_window_with_yuv(client, buf); - match = verify_screen_content(client, "yuv-buffer", 0, NULL, 0); + match = verify_screen_content(client, "yuv-buffer", 0, NULL, 0, NULL); assert(match); yuv_buffer_destroy(buf); diff --git a/tools/zunitc/inc/zunitc/zunitc.h b/tools/zunitc/inc/zunitc/zunitc.h index 16b211ba1..d285c59da 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_context.h b/tools/zunitc/src/zuc_context.h index 609f34bd7..c476f5e1a 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 395bdd748..18f030158 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 011b1942d..54cd5072c 100644 --- a/weston.ini.in +++ b/weston.ini.in @@ -1,84 +1,37 @@ [core] -#modules=cms-colord.so -#xwayland=true -#shell=desktop-shell.so -#gbm-format=xrgb2101010 -#require-input=true +#gbm-format=argb8888 +idle-time=0 +#use-g2d=true +xwayland=true +#repaint-window=16 +#enable-overlay-view=1 +modules=screen-share.so -[shell] -background-image=/usr/share/backgrounds/gnome/Aqua.jpg -background-color=0xff002244 -background-type=tile -clock-format=minutes -panel-color=0x90ff0000 -locking=true -animation=zoom -startup-animation=fade -#binding-modifier=ctrl -#num-workspaces=6 -#cursor-theme=whiteglass -#cursor-size=24 +#[shell] +#size=1920x1080 -#animation=fade +[libinput] +touchscreen_calibrator=true -[launcher] -icon=/usr/share/icons/gnome/24x24/apps/utilities-terminal.png -path=/usr/bin/gnome-terminal - -[launcher] -icon=/usr/share/icons/gnome/24x24/apps/utilities-terminal.png -path=@bindir@/weston-terminal - -[launcher] -icon=/usr/share/icons/hicolor/24x24/apps/google-chrome.png -path=/usr/bin/google-chrome - -[launcher] -icon=/usr/share/icons/gnome/24x24/apps/arts.png -path=@bindir@/weston-flower - -[input-method] -path=@libexecdir@/weston-keyboard +#[environment-variables] +#GBM_MULTI_BUFFER=3 #[output] -#name=LVDS1 -#mode=1680x1050 -#transform=90 -#icc_profile=/usr/share/color/icc/colord/Bluish.icc +#name=HDMI-A-1 +#mode=1920x1080@60 +#transform=rotate-90 #[output] -#name=VGA1 -#mode=173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync -#transform=flipped - -#[output] -#name=X1 -#mode=1024x768@60 -#transform=flipped-90 - -#[libinput] -#enable-tap=true -#tap-and-drag=true -#tap-and-drag-lock=true -#disable-while-typing=false -#middle-button-emulation=true -#left-handed=true -#rotation=90 -#accel-profile=flat -#accel-speed=.9 -#natural-scroll=true -#scroll-method=edge -# For button-triggered scrolling: -#scroll-method=button -#scroll-button=BTN_RIGHT - -#[touchpad] -#constant_accel_factor = 50 -#min_accel_factor = 0.16 -#max_accel_factor = 1.0 +#name=HDMI-A-2 +#mode=off +# WIDTHxHEIGHT Resolution size width and height in pixels +# off Disables the output +# preferred Uses the preferred mode +# current Uses the current crt controller mode +#transform=rotate-90 [screen-share] -command=@bindir@/weston --backend=rdp-backend.so --shell=fullscreen-shell.so --no-clients-resize +command=@bindir@/weston --backend=rdp --shell=fullscreen --no-clients-resize --rdp-tls-cert=/etc/freerdp/keys/server.crt --rdp-tls-key=/etc/freerdp/keys/server.key #start-on-startup=false #[xwayland] diff --git a/xwayland/dnd.c b/xwayland/dnd.c index aea0845f2..f8fab329c 100644 --- a/xwayland/dnd.c +++ b/xwayland/dnd.c @@ -40,7 +40,7 @@ #include #include "xwayland.h" -#include "hash.h" +#include "shared/hash.h" struct dnd_data_source { struct weston_data_source base; @@ -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 d8482a46c..48df79efa 100644 --- a/xwayland/meson.build +++ b/xwayland/meson.build @@ -4,7 +4,7 @@ endif xwayland_dep = dependency('xwayland', required: false) if xwayland_dep.found() - if xwayland_dep.get_pkgconfig_variable('have_listenfd') == 'true' + if xwayland_dep.get_variable(pkgconfig: 'have_listenfd') == 'true' config_h.set('HAVE_XWAYLAND_LISTENFD', '1') endif endif @@ -14,7 +14,8 @@ srcs_xwayland = [ 'window-manager.c', 'selection.c', 'dnd.c', - 'hash.c', + xwayland_shell_v1_server_protocol_h, + xwayland_shell_v1_protocol_c, ] dep_names_xwayland = [ @@ -26,7 +27,7 @@ dep_names_xwayland = [ 'cairo-xcb', ] -deps_xwayland = [ dep_libweston_public ] +deps_xwayland = [ dep_libweston_public, dep_xcb_xwayland, dep_lib_cairo_shared ] foreach name : dep_names_xwayland d = dependency(name, required: false) @@ -39,7 +40,6 @@ endforeach plugin_xwayland = shared_library( 'xwayland', srcs_xwayland, - link_with: lib_cairo_shared, include_directories: common_inc, dependencies: deps_xwayland, name_prefix: '', diff --git a/xwayland/selection.c b/xwayland/selection.c index c4845f20a..c243aa2da 100644 --- a/xwayland/selection.c +++ b/xwayland/selection.c @@ -25,6 +25,7 @@ #include "config.h" +#include #include #include #include @@ -152,7 +153,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 +200,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 +517,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 @@ -588,6 +591,7 @@ weston_wm_handle_selection_request(struct weston_wm *wm, weston_log_continue("property %s\n", get_atom_name(wm->conn, selection_request->property)); + assert(selection_request->requestor != wm->selection_window); wm->selection_request = *selection_request; wm->incr = 0; wm->flush_property_on_delete = 0; @@ -632,6 +636,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,15 +722,65 @@ weston_wm_set_selection(struct wl_listener *listener, void *data) wm->selection_window, wm->atom.clipboard, XCB_TIME_CURRENT_TIME); + + xcb_flush(wm->conn); +} + +static void +maybe_reassign_selection_seat(struct weston_wm *wm) +{ + struct weston_seat *seat; + + /* If we already have a seat, keep it */ + if (!wl_list_empty(&wm->selection_listener.link)) + return; + + seat = weston_wm_pick_seat(wm); + if (!seat) + return; + + wl_list_remove(&wm->selection_listener.link); + wl_list_remove(&wm->seat_destroy_listener.link); + + wl_signal_add(&seat->selection_signal, &wm->selection_listener); + wl_signal_add(&seat->destroy_signal, &wm->seat_destroy_listener); + + weston_wm_set_selection(&wm->selection_listener, seat); +} + +static void +weston_wm_seat_created(struct wl_listener *listener, void *data) +{ + struct weston_wm *wm = + container_of(listener, struct weston_wm, seat_create_listener); + + maybe_reassign_selection_seat(wm); +} + +static void +weston_wm_seat_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_wm *wm = + container_of(listener, struct weston_wm, seat_destroy_listener); + + wl_list_remove(&wm->selection_listener.link); + wl_list_init(&wm->selection_listener.link); + + wl_list_remove(&wm->seat_destroy_listener.link); + wl_list_init(&wm->seat_destroy_listener.link); + + /* Try to pick another available seat to fall back to */ + maybe_reassign_selection_seat(wm); } void weston_wm_selection_init(struct weston_wm *wm) { - struct weston_seat *seat; uint32_t values[1], mask; wl_list_init(&wm->selection_listener.link); + wl_list_init(&wm->seat_create_listener.link); + wl_list_init(&wm->seat_destroy_listener.link); wm->selection_request.requestor = XCB_NONE; @@ -752,11 +809,20 @@ weston_wm_selection_init(struct weston_wm *wm) xcb_xfixes_select_selection_input(wm->conn, wm->selection_window, wm->atom.clipboard, mask); - seat = weston_wm_pick_seat(wm); - if (seat == NULL) - return; + /* Try to set up a selection listener for any existing seat - we + * have a clipboard manager that can copy a subset of available + * selections so they don't disappear when the client owning + * them quits, but to make this work we need to have a seat + * to hang the selection off. + * + * If we have no seat or lose our seat we need to make sure we + * eventually assign a new one, so we listen for seat creation + * and destruction. + */ wm->selection_listener.notify = weston_wm_set_selection; - wl_signal_add(&seat->selection_signal, &wm->selection_listener); - - weston_wm_set_selection(&wm->selection_listener, seat); + wm->seat_destroy_listener.notify = weston_wm_seat_destroyed; + wm->seat_create_listener.notify = weston_wm_seat_created; + wl_signal_add(&wm->server->compositor->seat_created_signal, + &wm->seat_create_listener); + maybe_reassign_selection_seat(wm); } diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index ef8d92b07..e03ee701e 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -46,8 +46,10 @@ #include "xwayland-internal-interface.h" #include "shared/cairo-util.h" -#include "hash.h" +#include "shared/hash.h" #include "shared/helpers.h" +#include "shared/xcb-xwayland.h" +#include "xwayland-shell-v1-server-protocol.h" struct wm_size_hints { uint32_t flags; @@ -128,6 +130,8 @@ struct motif_wm_hints { #define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */ #define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */ +static const char *xwayland_surface_role = "xwayland"; + struct weston_output_weak_ref { struct weston_output *output; struct wl_listener destroy_listener; @@ -140,6 +144,7 @@ struct weston_wm_window { struct frame *frame; cairo_surface_t *cairo_surface; uint32_t surface_id; + uint64_t surface_serial; struct weston_surface *surface; struct weston_desktop_xwayland_surface *shsurf; struct wl_listener surface_destroy_listener; @@ -170,9 +175,23 @@ 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; +}; + +struct xwl_surface { + struct wl_resource *resource; + struct weston_wm *wm; + struct weston_surface *weston_surface; + uint64_t serial; + struct wl_listener surface_commit_listener; + struct wl_list link; }; static void @@ -196,6 +215,12 @@ static void xserver_map_shell_surface(struct weston_wm_window *window, struct weston_surface *surface); +static inline bool +weston_wm_window_is_maximized(struct weston_wm_window *window) +{ + return window->maximized_horz && window->maximized_vert; +} + static bool wm_debug_is_enabled(struct weston_wm *wm) { @@ -268,32 +293,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 +345,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 +358,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 +550,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 +593,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 +701,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,15 +816,28 @@ 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; } - if (configure_request->value_mask & XCB_CONFIG_WINDOW_WIDTH) + if (configure_request->value_mask & XCB_CONFIG_WINDOW_WIDTH) { window->width = configure_request->width; - if (configure_request->value_mask & XCB_CONFIG_WINDOW_HEIGHT) + if (!weston_wm_window_is_maximized(window)) + window->saved_width = window->width; + } + if (configure_request->value_mask & XCB_CONFIG_WINDOW_HEIGHT) { window->height = configure_request->height; + if (!weston_wm_window_is_maximized(window)) + window->saved_height = window->height; + } if (window->frame) { weston_wm_window_set_allow_commits(window, false); @@ -825,6 +864,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); } @@ -897,6 +937,9 @@ weston_wm_create_surface(struct wl_listener *listener, void *data) struct weston_wm, create_surface_listener); struct weston_wm_window *window; + if (wm->shell_bound) + return; + if (wl_resource_get_client(surface->resource) != wm->server->client) return; @@ -908,6 +951,7 @@ weston_wm_create_surface(struct wl_listener *listener, void *data) xserver_map_shell_surface(window, surface); window->surface_id = 0; wl_list_remove(&window->link); + wl_list_init(&window->link); break; } } @@ -916,24 +960,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 +1006,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 +1090,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 +1157,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 +1213,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); } /* @@ -1265,6 +1347,7 @@ weston_wm_handle_unmap_notify(struct weston_wm *wm, xcb_generic_event_t *event) * was mapped before this unmap request. */ wl_list_remove(&window->link); + wl_list_init(&window->link); window->surface_id = 0; } if (wm->focus_window == window) @@ -1381,6 +1464,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 +1529,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 +1619,22 @@ 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; + wl_list_init(&window->link); weston_output_weak_ref_init(&window->legacy_fullscreen_output); geometry_reply = xcb_get_geometry_reply(wm->conn, geometry_cookie, NULL); @@ -1549,8 +1673,7 @@ weston_wm_window_destroy(struct weston_wm_window *window) if (window->frame) frame_destroy(window->frame); - if (window->surface_id) - wl_list_remove(&window->link); + wl_list_remove(&window->link); if (window->surface) wl_list_remove(&window->surface_destroy_listener.link); @@ -1751,19 +1874,15 @@ 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); } -static inline bool -weston_wm_window_is_maximized(struct weston_wm_window *window) -{ - return window->maximized_horz && window->maximized_vert; -} - static void weston_wm_window_handle_state(struct weston_wm_window *window, xcb_client_message_event_t *client_message) @@ -1817,6 +1936,33 @@ 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) { + /* If window is currently in maximized or fullscreen state, + * don't override saved size. + */ + if (!weston_wm_window_is_maximized(window) && + !window->fullscreen) { + 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) { @@ -1839,6 +1985,8 @@ weston_wm_window_handle_surface_id(struct weston_wm_window *window, struct weston_wm *wm = window->wm; struct wl_resource *resource; + assert(!wm->shell_bound); + if (window->surface_id != 0) { wm_printf(wm, "already have surface id for window %d\n", window->id); @@ -1865,6 +2013,30 @@ weston_wm_window_handle_surface_id(struct weston_wm_window *window, } } +static void +weston_wm_window_handle_surface_serial(struct weston_wm_window *window, + xcb_client_message_event_t *client_message) +{ + struct xwl_surface *xsurf, *next; + struct weston_wm *wm = window->wm; + uint64_t serial = u64_from_u32s(client_message->data.data32[1], + client_message->data.data32[0]); + + window->surface_serial = serial; + wl_list_remove(&window->link); + wl_list_init(&window->link); + + wl_list_for_each_safe(xsurf, next, &wm->unpaired_surface_list, link) { + if (window->surface_serial == xsurf->serial) { + xserver_map_shell_surface(window, xsurf->weston_surface); + wl_list_remove(&xsurf->link); + wl_list_init(&xsurf->link); + return; + } + } + wl_list_insert(&wm->unpaired_window_list, &window->link); +} + static void weston_wm_handle_client_message(struct weston_wm *wm, xcb_generic_event_t *event) @@ -1892,8 +2064,13 @@ weston_wm_handle_client_message(struct weston_wm *wm, weston_wm_window_handle_moveresize(window, client_message); else if (client_message->type == wm->atom.net_wm_state) weston_wm_window_handle_state(window, client_message); - else if (client_message->type == wm->atom.wl_surface_id) + else if (client_message->type == wm->atom.wl_surface_id && + !wm->shell_bound) 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); + else if (client_message->type == wm->atom.wl_surface_serial) + weston_wm_window_handle_surface_serial(window, client_message); } enum cursor_type { @@ -2167,6 +2344,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 +2354,19 @@ 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) { + /* If window is currently in maximized or fullscreen state, + * don't override saved size. + */ + if (!weston_wm_window_is_maximized(window) && + !window->fullscreen) { + 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 +2431,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 +2439,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 +2578,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 +2590,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"); @@ -2566,6 +2683,155 @@ weston_wm_create_wm_window(struct weston_wm *wm) XCB_TIME_CURRENT_TIME); } +static void +free_xwl_surface(struct wl_resource *resource) +{ + struct xwl_surface *xsurf = wl_resource_get_user_data(resource); + + wl_list_remove(&xsurf->surface_commit_listener.link); + wl_list_remove(&xsurf->link); + free(xsurf); +} + +static void +xwl_surface_set_serial(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial_lo, + uint32_t serial_hi) +{ + struct xwl_surface *xsurf = wl_resource_get_user_data(resource); + uint64_t serial = u64_from_u32s(serial_hi, serial_lo); + + if (serial == 0) { + wl_resource_post_error(resource, + XWAYLAND_SURFACE_V1_ERROR_INVALID_SERIAL, + "Invalid serial for xwayland surface"); + return; + } + + if (xsurf->serial != 0) { + wl_resource_post_error(resource, + XWAYLAND_SURFACE_V1_ERROR_ALREADY_ASSOCIATED, + "Surface already has a serial"); + return; + } + xsurf->serial = serial; +} + +static void +xwl_surface_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct xwayland_surface_v1_interface xwl_surface_interface = { + .set_serial = xwl_surface_set_serial, + .destroy = xwl_surface_destroy, +}; + +static void +xwl_surface_committed(struct wl_listener *listener, void *data) +{ + struct weston_wm_window *window, *next; + struct xwl_surface *xsurf = wl_container_of(listener, xsurf, + surface_commit_listener); + + /* We haven't set a serial yet */ + if (xsurf->serial == 0) + return; + + window = get_wm_window(xsurf->weston_surface); + wl_list_remove(&xsurf->surface_commit_listener.link); + wl_list_init(&xsurf->surface_commit_listener.link); + + wl_list_for_each_safe(window, next, &xsurf->wm->unpaired_window_list, link) { + if (window->surface_serial == xsurf->serial) { + xserver_map_shell_surface(window, xsurf->weston_surface); + wl_list_remove(&window->link); + wl_list_init(&window->link); + return; + } + } + + wl_list_insert(&xsurf->wm->unpaired_surface_list, &xsurf->link); +} + +static void +get_xwl_surface(struct wl_client *client, struct wl_resource *resource, + uint32_t id, struct wl_resource *surface_resource) +{ + struct weston_wm *wm = wl_resource_get_user_data(resource); + struct weston_surface *surf; + struct xwl_surface *xsurf; + uint32_t version; + + surf = wl_resource_get_user_data(surface_resource); + if (weston_surface_set_role(surf, xwayland_surface_role, resource, + XWAYLAND_SHELL_V1_ERROR_ROLE) < 0) + return; + + xsurf = zalloc(sizeof *xsurf); + if (!xsurf) + goto fail; + + version = wl_resource_get_version(resource); + xsurf->resource = wl_resource_create(client, + &xwayland_surface_v1_interface, + version, id); + if (!xsurf->resource) + goto fail; + + wl_list_init(&xsurf->link); + xsurf->wm = wm; + xsurf->weston_surface = surf; + + wl_resource_set_implementation(xsurf->resource, &xwl_surface_interface, + xsurf, free_xwl_surface); + xsurf->surface_commit_listener.notify = xwl_surface_committed; + wl_signal_add(&surf->commit_signal, &xsurf->surface_commit_listener); + + return; + +fail: + wl_client_post_no_memory(client); +} + +static void +xwl_shell_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct xwayland_shell_v1_interface xwayland_shell_implementation = { + .get_xwayland_surface = get_xwl_surface, + .destroy = xwl_shell_destroy, +}; + +static void +bind_xwayland_shell(struct wl_client *client, + void *data, + uint32_t version, + uint32_t id) +{ + struct weston_wm *wm = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, &xwayland_shell_v1_interface, + version, id); + if (client != wm->server->client) { + wl_resource_post_error(resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "permission to bind xwayland_shell " + "denied"); + return; + } + + wm->shell_bound = true; + + wl_resource_set_implementation(resource, &xwayland_shell_implementation, + wm, NULL); +} + struct weston_wm * weston_wm_create(struct weston_xserver *wxs, int fd) { @@ -2573,7 +2839,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 +2893,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, @@ -2653,10 +2920,15 @@ weston_wm_create(struct weston_xserver *wxs, int fd) wl_signal_add(&wxs->compositor->kill_signal, &wm->kill_listener); wl_list_init(&wm->unpaired_window_list); + wl_list_init(&wm->unpaired_surface_list); weston_wm_create_cursors(wm); weston_wm_window_set_cursor(wm, wm->screen->root, XWM_CURSOR_LEFT_PTR); + wm->xwayland_shell_global = wl_global_create(wxs->compositor->wl_display, + &xwayland_shell_v1_interface, + 1, wm, bind_xwayland_shell); + /* Create wm window and take WM_S0 selection last, which * signals to Xwayland that we're done with setup. */ weston_wm_create_wm_window(wm); @@ -2669,17 +2941,28 @@ weston_wm_create(struct weston_xserver *wxs, int fd) void weston_wm_destroy(struct weston_wm *wm) { + wl_global_destroy(wm->xwayland_shell_global); /* FIXME: Free windows in hash. */ hash_table_destroy(wm->window_hash); weston_wm_destroy_cursors(wm); theme_destroy(wm->theme); xcb_disconnect(wm->conn); wl_event_source_remove(wm->source); + wl_list_remove(&wm->seat_create_listener.link); + wl_list_remove(&wm->seat_destroy_listener.link); wl_list_remove(&wm->selection_listener.link); wl_list_remove(&wm->activate_listener.link); wl_list_remove(&wm->kill_listener.link); wl_list_remove(&wm->create_surface_listener.link); + /* + * No, you cannot call cleanup_after_cairo() here, because Weston + * on wayland-backend would crash in an assert inside Cairo. + * Just rely on headless and wayland backends calling it. + * + * XXX: fix this for other backends. + */ + free(wm); } @@ -2730,6 +3013,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 +3025,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 +3041,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 +3131,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 +3195,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 +3278,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, @@ -2972,6 +3293,8 @@ xserver_map_shell_surface(struct weston_wm_window *window, parent->surface); } } else if (weston_wm_window_is_maximized(window)) { + window->saved_width = window->width; + window->saved_height = window->height; xwayland_interface->set_maximized(window->shsurf); } else { if (weston_wm_window_type_inactive(window)) { diff --git a/xwayland/xwayland-internal-interface.h b/xwayland/xwayland-internal-interface.h index c7dfd19bc..0e732772c 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 3ef0404fe..270dc99b0 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; @@ -63,6 +61,7 @@ struct weston_wm { xcb_screen_t *screen; struct hash_table *window_hash; struct weston_xserver *server; + struct wl_global *xwayland_shell_global; xcb_window_t wm_window; struct weston_wm_window *focus_window; struct theme *theme; @@ -90,84 +89,22 @@ struct weston_wm { int selection_property_set; int flush_property_on_delete; struct wl_listener selection_listener; + struct wl_listener seat_create_listener; + struct wl_listener seat_destroy_listener; 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 wl_list unpaired_surface_list; + bool shell_bound; + + 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