diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index eb6fb0b4..eeed7899 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -43,7 +43,7 @@ include: # API changes. If you need new features from ci-templates you must bump # this to the current SHA you require from the ci-templates repo, however # be aware that you may need to account for API changes when doing so. - ref: f210ea84576f756816da37908771edcee14ef7e6 + ref: 48c2c583a865bd59be21e8938df247faf460099c file: - '/templates/debian.yml' - '/templates/freebsd.yml' @@ -306,11 +306,11 @@ armv7-release-debian-build: .os-freebsd: variables: BUILD_OS: freebsd - FDO_DISTRIBUTION_VERSION: "13.2" + FDO_DISTRIBUTION_VERSION: "14.3" FDO_DISTRIBUTION_PACKAGES: 'libxslt meson ninja pkgconf expat libffi libepoll-shim libxml2' # bump this tag every time you change something which requires rebuilding the # base image - FDO_DISTRIBUTION_TAG: "2023-08-02.0" + FDO_DISTRIBUTION_TAG: "2025-07-20.0" # Don't build documentation since installing the required tools massively # increases the VM image (and therefore container) size. MESON_ARGS: "--fatal-meson-warnings -Dwerror=true -Ddocumentation=false" diff --git a/cursor/wayland-cursor.c b/cursor/wayland-cursor.c index 89ecc9a1..2e21db73 100644 --- a/cursor/wayland-cursor.c +++ b/cursor/wayland-cursor.c @@ -398,7 +398,7 @@ wl_cursor_theme_load(const char *name, int size, struct wl_shm *shm) return NULL; if (size < 0 || (size > 0 && INT_MAX / size / 4 < size)) - return NULL; + goto err; if (!name) name = "default"; @@ -409,7 +409,7 @@ wl_cursor_theme_load(const char *name, int size, struct wl_shm *shm) theme->pool = shm_pool_create(shm, size * size * 4); if (!theme->pool) - goto out_error_pool; + goto err; xcursor_load_theme(name, size, load_callback, theme); @@ -421,7 +421,7 @@ wl_cursor_theme_load(const char *name, int size, struct wl_shm *shm) return theme; -out_error_pool: +err: free(theme); return NULL; } diff --git a/doc/publican/sources/Color.xml b/doc/publican/sources/Color.xml new file mode 100644 index 00000000..ceee779e --- /dev/null +++ b/doc/publican/sources/Color.xml @@ -0,0 +1,139 @@ + + +%BOOK_ENTITIES; +]> + + + Color management + +
+ Overview + + + Color management in Wayland considers only displays. All pictures in + Wayland are always display-referred, meaning that the pixel values are + intended as-is for some specific display where they would produce the + light emissions (stimuli) the picture's + author desired. Wayland does not support displaying "raw" camera or + scanner images as they are not display-referred, nor are they even + pictures without complex and subjective processing. + + + Stimuli — the picture itself — are only half of the picture reproduction. + The other half is the environment where a display is viewed. A striking + example is comparing a brightly lit office to a dark movie theater, the + stimuli required to produce a good reading of the picture is greatly + different. Therefore display-referred does not include only the display + but the viewing environment as well. + + + Window systems have been very well capable of operating without any + explicit consideration to color management. This is because there used to + be the implicit assumption of the standard display, the sRGB display, + which all computer monitors implemented, more or less. The viewing + environment was and still is accounted by adjusting the display and/or the + room to produce a workable experience. Pictures are authored on a computer + system by drawing, painting and adjusting the picture until it looks right + on the author's monitor. This implicitly builds the standard display and + environment assumption into the picture data. Deviations from the sRGB + specification were minor enough that they often did not matter if not in a + professional context like the printing industry. Displaying video material + required some more attention to the details, because video and television + standards differ enough from the sRGB display. What really made explicit + color management a hard requirement for entertainment is the coming of + wide color gamut (WCG) and high dynamic range (HDR) materials and + displays. + + + The color management design in Wayland follows the general Wayland design + principles: compositors tell clients what would be the optimal thing to + do, clients tell the compositors what kind of pictures they are actually + producing, and then compositors display those pictures the best they can. + +
+ +
+ Protocol Interfaces + + + Color management interfaces in Wayland and divided into two protocols: + color-management + and + color-representation. + They are designed to work together, but they can also be used + independently when the other one is not needed. + + +
+ Color-management + + + Color management protocol has two main purposes. First, it puts the + responsibility of color management on the compositor. This means that + clients do not necessarily need to care about color management at all, + and can display just fine by using the traditional standard display + assumption even when the actual display is wildly different. Clients + can also choose to target some other assumed display and let the + compositor handle it, or they can explicitly render for the actual + display at hand. Second, when the window system has multiple different + monitors, and a wl_surface happens to span more than one monitor, the + compositor can display the surface content correctly on all spanned + monitors simultaneously, as much as physically possible. + + + Color-management protocol concentrates on colorimetry: when you have a + pixel with RGB values, what stimulus do those values represent. The + stimulus definition follows the CIE 1931 two-degree observer model. Some + core concepts here are color primaries, white point, transfer function, + and dynamic range. The viewing environment is represented in an + extremely simplified way as the reference white luminance. The + connection between pixel RGB values and stimulus plus viewing + environment is recorded in an image description + object. Clients can create image description objects and tag + wl_surfaces with them, to indicate what kind of surface + content there will be. Clients can also ask what image description the + compositor would prefer to have on the wl_surface, and that + preference can change over time, e.g. when the wl_surface + is moved from one + wl_output to another. Following the compositor's preference + may provide advantages in image quality and power consumption. + + + Image description objects can come in two flavors: parametric and + ICC-based. The above was written with parametric image descriptions in + mind, and they have first-class support for HDR. ICC-based image + descriptions are wrapping an ICC profile and have no other data. ICC + profiles are the standard tool for standard dynamic range (SDR) display + color management. This means the capabilities between the two flavors + differ, and one cannot always be replaced by the other. Compositor + support for each flavor is optional. + +
+ +
+ Color-representation + + + Color-representation protocol deals with (potentially sub-sampled) + YCbCr-RGB conversion, quantization range, and the inclusion of alpha in + the RGB color channels, a.k.a. pre-multiplication. There are several + different specifications on how an YCbCr-like (including ICtCp) signal, + with chroma sub-sampling or not, is created from a full-resolution RGB + image. Again, a client can tag a wl_surface with + color-representation metadata to tell the compositor what kind of pixel + data will be displayed through the wl_surface. + + + The main purpose of color-representation is to correctly off-load the + YCbCr-RGB conversion to the compositor, which can then opportunistically + off-load it further to very power-efficient fixed-function circuitry in + a display controller. This can significantly reduce power consumption + when watching videos compared to using a GPU for the same, and on some + embedded hardware platforms it is a hard requirement for processing high + resolution video. + +
+
+
diff --git a/doc/publican/sources/Wayland.xml b/doc/publican/sources/Wayland.xml index 0457c15c..7593097e 100644 --- a/doc/publican/sources/Wayland.xml +++ b/doc/publican/sources/Wayland.xml @@ -12,6 +12,7 @@ + diff --git a/doc/publican/sources/meson.build b/doc/publican/sources/meson.build index 52f3a681..a53b3890 100644 --- a/doc/publican/sources/meson.build +++ b/doc/publican/sources/meson.build @@ -54,6 +54,7 @@ publican_sources = [ 'Protocol.xml', 'Xwayland.xml', 'Compositors.xml', + 'Color.xml', 'Client.xml', 'Server.xml' ] diff --git a/meson.build b/meson.build index a3126b63..ce386a4c 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'wayland', 'c', - version: '1.23.91', + version: '1.24.90', license: 'MIT', meson_version: '>= 0.57.0', default_options: [ @@ -46,6 +46,7 @@ have_funcs = [ 'memfd_create', 'mremap', 'strndup', + 'gettid', ] foreach f: have_funcs config_h.set('HAVE_' + f.underscorify().to_upper(), cc.has_function(f)) diff --git a/protocol/wayland.xml b/protocol/wayland.xml index bee74a10..b961f187 100644 --- a/protocol/wayland.xml +++ b/protocol/wayland.xml @@ -1886,7 +1886,7 @@ - + A seat is a group of keyboards, pointer and touch devices. This object is published as a global during start up, or when such a @@ -2020,7 +2020,7 @@ - + The wl_pointer interface represents one or more input devices, such as mice, which control the pointer location and pointer_focus @@ -2130,8 +2130,9 @@ Mouse button click and release notifications. - The location of the click is given by the last motion or + The location of the click is given by the last motion, warp or enter event. + The time argument is a timestamp with millisecond granularity, with an undefined base. @@ -2431,9 +2432,30 @@ + + + + + + Notification of pointer location change within a surface. + This location change is not due to events on the input device, + but because either the surface under the pointer was moved and + thus the relative position of the pointer changed, or because + the compositor changed the pointer position in response to + an event like pointer confinement being exited. The arguments + surface_x and surface_y are the location relative to the + focused surface. + + This event must not occur in the same wl_pointer.frame as a + wl_pointer.enter or wl_pointer.motion event. + + + + + - + The wl_keyboard interface represents one or more keyboards associated with a seat. @@ -2614,7 +2636,7 @@ - + The wl_touch interface represents a touchscreen associated with a seat. diff --git a/src/connection.c b/src/connection.c index 593f52f3..2d1e8d1d 100644 --- a/src/connection.c +++ b/src/connection.c @@ -26,6 +26,8 @@ #define _GNU_SOURCE +#include "../config.h" + #include #include #include @@ -1491,11 +1493,56 @@ wl_closure_queue(struct wl_closure *closure, struct wl_connection *connection) return result; } +bool +wl_check_env_token(const char *env, const char *token) +{ + const char *ptr = env; + size_t token_len; + + if (env == NULL) + return false; + + token_len = strlen(token); + + // Scan the string for comma-separated tokens and look for a match. + while (true) { + const char *end; + size_t len; + + // Skip over any leading separators. + while (*ptr == ',') + ptr++; + + if (*ptr == '\x00') + return false; + + end = strchr(ptr + 1, ','); + + // If there isn't another separarator, then the rest of the string + // is one token. + if (end == NULL) + return (strcmp(ptr, token) == 0); + + len = end - ptr; + if (len == token_len && memcmp(ptr, token, len) == 0) { + return true; + } + + // Skip to the next token. + ptr += len; + } + + return false; +} + void wl_closure_print(struct wl_closure *closure, struct wl_object *target, int send, int discarded, uint32_t (*n_parse)(union wl_argument *arg), const char *queue_name, int color) { +#if defined(HAVE_GETTID) + static int include_tid = -1; +#endif // defined(HAVE_GETTID) int i; struct argument_details arg; const char *signature = closure->message->signature; @@ -1516,6 +1563,18 @@ wl_closure_print(struct wl_closure *closure, struct wl_object *target, color ? WL_DEBUG_COLOR_GREEN : "", time / 1000, time % 1000); +#if defined(HAVE_GETTID) + if (include_tid < 0) { + include_tid = wl_check_env_token(getenv("WAYLAND_DEBUG"), "thread_id"); + } + + if (include_tid) { + fprintf(f, "%sTID#%d ", + color ? WL_DEBUG_COLOR_CYAN : "", + (int) gettid()); + } +#endif + if (queue_name) { fprintf(f, "%s{%s} ", color ? WL_DEBUG_COLOR_YELLOW : "", diff --git a/src/wayland-client-core.h b/src/wayland-client-core.h index 970e6254..e0523e49 100644 --- a/src/wayland-client-core.h +++ b/src/wayland-client-core.h @@ -268,9 +268,16 @@ int wl_display_dispatch_queue_pending(struct wl_display *display, struct wl_event_queue *queue); +int +wl_display_dispatch_queue_pending_single(struct wl_display *display, + struct wl_event_queue *queue); + int wl_display_dispatch_pending(struct wl_display *display); +int +wl_display_dispatch_pending_single(struct wl_display *display); + int wl_display_get_error(struct wl_display *display); diff --git a/src/wayland-client.c b/src/wayland-client.c index fe14a6b1..ed686b5c 100644 --- a/src/wayland-client.c +++ b/src/wayland-client.c @@ -1236,7 +1236,7 @@ wl_display_connect_to_fd(int fd) no_color = getenv("NO_COLOR"); force_color = getenv("FORCE_COLOR"); debug = getenv("WAYLAND_DEBUG"); - if (debug && (strstr(debug, "client") || strstr(debug, "1"))) { + if (debug && (wl_check_env_token(debug, "client") || wl_check_env_token(debug, "1"))) { debug_client = 1; if (isatty(fileno(stderr))) debug_color = 1; @@ -1578,6 +1578,28 @@ queue_event(struct wl_display *display, int len) id = p[0]; opcode = p[1] & 0xffff; size = p[1] >> 16; + + /* + * If the message is larger than the maximum size of the + * connection buffer, the connection buffer will fill to + * its max size and stay there, with no message ever + * successfully being processed. If the user of + * libwayland-client uses a level-triggered event loop, + * this will cause the client to enter a loop that + * consumes CPU. To avoid this, immediately drop the + * connection. Since the maximum size of a message should + * not depend on the max buffer size chosen by the client, + * always compare the message size against the + * limit enforced by libwayland 1.22 and below (4096), + * rather than the actual value the client chose. + */ + if (size > WL_MAX_MESSAGE_SIZE) { + wl_log("Message length %u exceeds limit %d\n", + size, WL_MAX_MESSAGE_SIZE); + errno = E2BIG; + return -1; + } + if (len < size) return 0; @@ -1860,6 +1882,34 @@ err: return -1; } + +static int +dispatch_queue_single(struct wl_display *display, struct wl_event_queue *queue) +{ + if (display->last_error) + goto err; + + while (!wl_list_empty(&display->display_queue.event_list)) { + dispatch_event(display, &display->display_queue); + if (display->last_error) + goto err; + } + + if (!wl_list_empty(&queue->event_list)) { + dispatch_event(display, queue); + if (display->last_error) + goto err; + return 1; + } else { + return 0; + } + +err: + errno = display->last_error; + + return -1; +} + /** Prepare to read events from the display's file descriptor to a queue * * \param display The display context object @@ -2190,6 +2240,34 @@ wl_display_dispatch_queue_pending(struct wl_display *display, return ret; } +/** Dispatch at most one pending event in an event queue + * + * \param display The display context object + * \param queue The event queue to dispatch + * \return The number of dispatched events (0 or 1) on success or -1 on failure + * + * Dispatch at most one pending event for objects assigned to the given + * event queue. On failure -1 is returned and errno set appropriately. + * If there are no events queued, this function returns immediately. + * + * \memberof wl_display + * \since 1.25.0 + */ +WL_EXPORT int +wl_display_dispatch_queue_pending_single(struct wl_display *display, + struct wl_event_queue *queue) +{ + int ret; + + pthread_mutex_lock(&display->mutex); + + ret = dispatch_queue_single(display, queue); + + pthread_mutex_unlock(&display->mutex); + + return ret; +} + /** Process incoming events * * \param display The display context object @@ -2250,6 +2328,25 @@ wl_display_dispatch_pending(struct wl_display *display) &display->default_queue); } +/** Dispatch at most one pending event in the default event queue. + * + * \param display The display context object + * \return The number of dispatched events (0 or 1) on success or -1 on failure + * + * Dispatch at most one pending event for objects assigned to the default + * event queue. On failure -1 is returned and errno set appropriately. + * If there are no events queued, this function returns immediately. + * + * \memberof wl_display + * \since 1.25.0 + */ +WL_EXPORT int +wl_display_dispatch_pending_single(struct wl_display *display) +{ + return wl_display_dispatch_queue_pending_single(display, + &display->default_queue); +} + /** Retrieve the last error that occurred on a display * * \param display The display context object diff --git a/src/wayland-private.h b/src/wayland-private.h index 9aace67d..d0e4cfc6 100644 --- a/src/wayland-private.h +++ b/src/wayland-private.h @@ -49,6 +49,9 @@ #define WL_CLOSURE_MAX_ARGS 20 #define WL_BUFFER_DEFAULT_SIZE_POT 12 #define WL_BUFFER_DEFAULT_MAX_SIZE (1 << WL_BUFFER_DEFAULT_SIZE_POT) +#if WL_BUFFER_DEFAULT_MAX_SIZE < WL_MAX_MESSAGE_SIZE +# error default buffer cannot hold maximum-sized message +#endif #define WL_DEBUG_COLOR_RESET "\e[0m" #define WL_DEBUG_COLOR_RED "\e[31m" @@ -234,6 +237,9 @@ wl_closure_send(struct wl_closure *closure, struct wl_connection *connection); int wl_closure_queue(struct wl_closure *closure, struct wl_connection *connection); +bool +wl_check_env_token(const char *env, const char *token); + void wl_closure_print(struct wl_closure *closure, struct wl_object *target, int send, int discarded, diff --git a/src/wayland-server.c b/src/wayland-server.c index 15667644..c81d98f1 100644 --- a/src/wayland-server.c +++ b/src/wayland-server.c @@ -398,6 +398,29 @@ wl_client_connection_data(int fd, uint32_t mask, void *data) wl_connection_copy(connection, p, sizeof p); opcode = p[1] & 0xffff; size = p[1] >> 16; + + /* + * If the message is larger than the maximum size of the + * connection buffer, the connection buffer will fill to + * its max size and stay there, with no message ever + * successfully being processed. Since libwayland-server + * uses level-triggered epoll, it will cause the server to + * enter a loop that consumes CPU. To avoid this, + * immediately disconnect the client with a protocol + * error. Since the maximum size of a message should not + * depend on the buffer size chosen by the compositor, + * always compare the message size against the + * limit enforced by libwayland 1.22 and below (4096), + * rather than the actual value the compositor chose. + */ + if (size > WL_MAX_MESSAGE_SIZE) { + wl_resource_post_error(client->display_resource, + WL_DISPLAY_ERROR_INVALID_METHOD, + "message length %u exceeds %d", + size, WL_MAX_MESSAGE_SIZE); + break; + } + if (len < size) break; @@ -1175,7 +1198,7 @@ wl_display_create(void) no_color = getenv("NO_COLOR"); force_color = getenv("FORCE_COLOR"); debug = getenv("WAYLAND_DEBUG"); - if (debug && (strstr(debug, "server") || strstr(debug, "1"))) { + if (debug && (wl_check_env_token(debug, "server") || wl_check_env_token(debug, "1"))) { debug_server = 1; if (isatty(fileno(stderr))) debug_color = 1; diff --git a/src/wayland-util.h b/src/wayland-util.h index 4540f040..98c72fde 100644 --- a/src/wayland-util.h +++ b/src/wayland-util.h @@ -90,6 +90,14 @@ extern "C" { */ struct wl_object; +/** + * The maximum size of a protocol message. + * + * If a message size exceeds this value, the connection will be dropped. + * Servers will send an invalid_method error before disconnecting. + */ +#define WL_MAX_MESSAGE_SIZE 4096 + /** * Protocol message signature * diff --git a/tests/display-test.c b/tests/display-test.c index 89606c73..fe78b521 100644 --- a/tests/display-test.c +++ b/tests/display-test.c @@ -1695,6 +1695,75 @@ TEST(global_remove) display_destroy(d); } +static void +dispatch_single_read_events(struct wl_display *d) +{ + if (wl_display_prepare_read(d) < 0) { + return; + } + + int ret = 0; + do { + ret = wl_display_flush(d); + } while (ret < 0 && (errno == EINTR || errno == EAGAIN)); + assert(ret >= 0); + + struct pollfd pfd[1]; + pfd[0].fd = wl_display_get_fd(d); + pfd[0].events = POLLIN; + + do { + ret = poll(pfd, 1, -1); + } while (ret < 0 && errno == EINTR); + assert(ret > 0); + + wl_display_read_events(d); +} + +static void +dispatch_single_client(void) +{ + struct client *c = client_connect(); + + assert(wl_display_dispatch_pending_single(c->wl_display) == 0); + + struct wl_registry *registry = wl_display_get_registry(c->wl_display); + + dispatch_single_read_events(c->wl_display); + + // [1815110.061] {Default Queue} wl_registry#3.global(1, "test", 1) + assert(wl_display_dispatch_pending_single(c->wl_display) == 1); + + dispatch_single_read_events(c->wl_display); + + // [1815110.067] {Default Queue} wl_registry#3.global(2, "wl_seat", 1) + assert(wl_display_dispatch_pending_single(c->wl_display) == 1); + + // No more events + assert(wl_display_dispatch_pending_single(c->wl_display) == 0); + + wl_registry_destroy(registry); + + client_disconnect(c); +} + +TEST(dispatch_single) +{ + struct display *d = display_create(); + + struct wl_global *global = wl_global_create(d->wl_display, + &wl_seat_interface, + 1, d, bind_seat); + + client_create_noarg(d, dispatch_single_client); + + display_run(d); + + wl_global_destroy(global); + + display_destroy(d); +} + static void terminate_display(void *arg) {