diff --git a/.builds/alpine.yml b/.builds/alpine.yml index 20cb8212e..affd85411 100644 --- a/.builds/alpine.yml +++ b/.builds/alpine.yml @@ -20,8 +20,13 @@ packages: - xwayland-dev - libseat-dev - hwdata-dev + # for docs + - go + - zip sources: - https://gitlab.freedesktop.org/wlroots/wlroots.git +artifacts: + - public.zip tasks: - setup: | cd wlroots @@ -37,3 +42,16 @@ tasks: - tinywl: | cd wlroots/tinywl make + - docs: | + go install 'codeberg.org/emersion/gyosu@latest' + include_dir="$(echo /usr/local/include/wlroots-*)" + ~/go/bin/gyosu \ + -DWLR_USE_UNSTABLE \ + $(pkg-config --cflags-only-I $(basename "$include_dir")) \ + -Iwlroots/build/protocol/ \ + -fexported-symbols='wlr_*' -fexported-symbols='WLR_*' \ + -ffile-prefix-map="$include_dir/"= \ + -fsite-name=wlroots \ + -o public \ + "$include_dir/wlr/" + zip -r ~/public.zip public/ diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml index 8457ed585..fae04ab31 100644 --- a/.builds/archlinux.yml +++ b/.builds/archlinux.yml @@ -41,9 +41,10 @@ tasks: cd wlroots/build-gcc/tinywl sudo modprobe vkms udevadm settle + card="/dev/dri/$(ls /sys/devices/faux/vkms/drm/ | grep ^card)" export WLR_BACKENDS=drm export WLR_RENDERER=pixman - export WLR_DRM_DEVICES=/dev/dri/by-path/platform-vkms-card + export WLR_DRM_DEVICES="$card" export UBSAN_OPTIONS=halt_on_error=1 - sudo chmod ugo+rw /dev/dri/by-path/platform-vkms-card + sudo chmod ugo+rw "$card" sudo -E seatd-launch -- ./tinywl -s 'kill $PPID' || [ $? = 143 ] diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index 193f59a33..a99e9c911 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -20,19 +20,18 @@ packages: - x11/xcb-util-errors - x11/xcb-util-renderutil - x11/xcb-util-wm - - x11-servers/xwayland-devel + - x11-servers/xwayland - sysutils/libdisplay-info - sysutils/seatd - - gmake - hwdata sources: - https://gitlab.freedesktop.org/wlroots/wlroots.git tasks: - wlroots: | cd wlroots - meson setup build --fatal-meson-warnings -Dauto_features=enabled + meson setup build --fatal-meson-warnings -Dauto_features=enabled -Dallocators=gbm ninja -C build sudo ninja -C build install - tinywl: | cd wlroots/tinywl - gmake + make diff --git a/.gitignore b/.gitignore index d73983bfb..02cf3c093 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/subprojects/ +/subprojects/* +!/subprojects/*.wrap diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b0942ab31..e02463a9c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,7 @@ include: https://git.sr.ht/~emersion/dalligi/blob/master/templates/multi.yml alpine: extends: .dalligi + pages: true archlinux: extends: .dalligi freebsd: diff --git a/.mailmap b/.mailmap index 0e050b333..42dca553e 100644 --- a/.mailmap +++ b/.mailmap @@ -1 +1,2 @@ Isaac Freund +Kirill Primak diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3d13f5fb1..aea7d6960 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -213,6 +213,27 @@ reinitialized to be used again. it, and free the memory. Such functions should always be able to accept a NULL pointer. +If the object has signals, the destructor function must assert that their +listener lists are empty. + +```c +void wlr_thing_init(struct wlr_thing *thing) { + *thing = (struct wlr_thing){ + // ... + }; + + wl_signal_init(&thing->events.destroy); + wl_signal_init(&thing->events.foo); +} + +void wlr_thing_finish(struct wlr_thing *thing) { + wl_signal_emit_mutable(&thing->events.destroy, NULL); + + assert(wl_list_empty(&thing->events.destroy.listener_list)); + assert(wl_list_empty(&thing->events.foo.listener_list)); +} +``` + ### Error Codes For functions not returning a value, they should return a (stdbool.h) bool to @@ -237,6 +258,13 @@ used and `#undef` them after. * Document the contents and container of a `struct wl_list` with a `// content.link` and `// container.list` comment. +### Private fields + +Wrap private fields of public structures with `struct { … } WLR_PRIVATE`. This +ensures that compositor authors don't use them by accident. Within wlroots +`WLR_PRIVATE` is expanded to nothing, so private fields are accessed in the same +way as public ones. + ### Safety * Avoid string manipulation functions which don't take the size of the @@ -325,12 +353,14 @@ struct wlr_compositor { struct wl_global *global; … - struct wl_listener display_destroy; - struct { struct wl_signal new_surface; struct wl_signal destroy; } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; ``` diff --git a/backend/backend.c b/backend/backend.c index e4e8c8d8e..3d84aa636 100644 --- a/backend/backend.c +++ b/backend/backend.c @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -12,9 +11,6 @@ #include #include #include -#include "backend/backend.h" -#include "backend/multi.h" -#include "render/allocator/allocator.h" #include "types/wlr_output.h" #include "util/env.h" #include "util/time.h" @@ -50,6 +46,10 @@ void wlr_backend_init(struct wlr_backend *backend, void wlr_backend_finish(struct wlr_backend *backend) { wl_signal_emit_mutable(&backend->events.destroy, backend); + + assert(wl_list_empty(&backend->events.destroy.listener_list)); + assert(wl_list_empty(&backend->events.new_input.listener_list)); + assert(wl_list_empty(&backend->events.new_output.listener_list)); } bool wlr_backend_start(struct wlr_backend *backend) { @@ -121,14 +121,6 @@ int wlr_backend_get_drm_fd(struct wlr_backend *backend) { return backend->impl->get_drm_fd(backend); } -uint32_t backend_get_buffer_caps(struct wlr_backend *backend) { - if (!backend->impl->get_buffer_caps) { - return 0; - } - - return backend->impl->get_buffer_caps(backend); -} - static size_t parse_outputs_env(const char *name) { const char *outputs_str = getenv(name); if (outputs_str == NULL) { @@ -493,5 +485,10 @@ bool wlr_backend_commit(struct wlr_backend *backend, output_apply_commit(state->output, &state->base); } + for (size_t i = 0; i < states_len; i++) { + const struct wlr_backend_output_state *state = &states[i]; + output_send_commit_event(state->output, &state->base); + } + return true; } diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index 2649a1a55..41773d4f5 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -1,13 +1,16 @@ #include #include #include +#include +#include +#include #include -#include #include #include "backend/drm/drm.h" #include "backend/drm/fb.h" #include "backend/drm/iface.h" #include "backend/drm/util.h" +#include "render/color.h" static char *atomic_commit_flags_str(uint32_t flags) { const char *const l[] = { @@ -152,18 +155,20 @@ static bool create_gamma_lut_blob(struct wlr_drm_backend *drm, bool create_fb_damage_clips_blob(struct wlr_drm_backend *drm, int width, int height, const pixman_region32_t *damage, uint32_t *blob_id) { - if (!pixman_region32_not_empty(damage)) { - *blob_id = 0; - return true; - } - pixman_region32_t clipped; pixman_region32_init(&clipped); pixman_region32_intersect_rect(&clipped, damage, 0, 0, width, height); int rects_len; const pixman_box32_t *rects = pixman_region32_rectangles(&clipped, &rects_len); - int ret = drmModeCreatePropertyBlob(drm->fd, rects, sizeof(*rects) * rects_len, blob_id); + + int ret; + if (rects_len > 0) { + ret = drmModeCreatePropertyBlob(drm->fd, rects, sizeof(*rects) * rects_len, blob_id); + } else { + ret = 0; + *blob_id = 0; + } pixman_region32_fini(&clipped); if (ret != 0) { wlr_log_errno(WLR_ERROR, "Failed to create FB_DAMAGE_CLIPS property blob"); @@ -173,6 +178,85 @@ bool create_fb_damage_clips_blob(struct wlr_drm_backend *drm, return true; } +static uint8_t convert_cta861_eotf(enum wlr_color_transfer_function tf) { + switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_SRGB: + abort(); // unsupported + case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: + return 2; + case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: + abort(); // unsupported + case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: + abort(); // unsupported + case WLR_COLOR_TRANSFER_FUNCTION_BT1886: + abort(); // unsupported + } + abort(); // unreachable +} + +static uint16_t convert_cta861_color_coord(double v) { + if (v < 0) { + v = 0; + } + if (v > 1) { + v = 1; + } + return (uint16_t)round(v * 50000); +} + +static bool create_hdr_output_metadata_blob(struct wlr_drm_backend *drm, + const struct wlr_output_image_description *img_desc, uint32_t *blob_id) { + if (img_desc == NULL) { + *blob_id = 0; + return true; + } + + struct hdr_output_metadata metadata = { + .metadata_type = 0, + .hdmi_metadata_type1 = { + .eotf = convert_cta861_eotf(img_desc->transfer_function), + .metadata_type = 0, + .display_primaries = { + { + .x = convert_cta861_color_coord(img_desc->mastering_display_primaries.red.x), + .y = convert_cta861_color_coord(img_desc->mastering_display_primaries.red.y), + }, + { + .x = convert_cta861_color_coord(img_desc->mastering_display_primaries.green.x), + .y = convert_cta861_color_coord(img_desc->mastering_display_primaries.green.y), + }, + { + .x = convert_cta861_color_coord(img_desc->mastering_display_primaries.blue.x), + .y = convert_cta861_color_coord(img_desc->mastering_display_primaries.blue.y), + }, + }, + .white_point = { + .x = convert_cta861_color_coord(img_desc->mastering_display_primaries.white.x), + .y = convert_cta861_color_coord(img_desc->mastering_display_primaries.white.y), + }, + .max_display_mastering_luminance = img_desc->mastering_luminance.max, + .min_display_mastering_luminance = img_desc->mastering_luminance.min * 0.0001, + .max_cll = img_desc->max_cll, + .max_fall = img_desc->max_fall, + }, + }; + if (drmModeCreatePropertyBlob(drm->fd, &metadata, sizeof(metadata), blob_id) != 0) { + wlr_log_errno(WLR_ERROR, "Failed to create HDR_OUTPUT_METADATA property"); + return false; + } + return true; +} + +static uint64_t convert_primaries_to_colorspace(uint32_t primaries) { + switch (primaries) { + case 0: + return 0; // Default + case WLR_COLOR_NAMED_PRIMARIES_BT2020: + return 9; // BT2020_RGB + } + abort(); // unreachable +} + static uint64_t max_bpc_for_format(uint32_t format) { switch (format) { case DRM_FORMAT_XRGB2101010: @@ -247,19 +331,25 @@ bool drm_atomic_connector_prepare(struct wlr_drm_connector_state *state, bool mo } uint32_t gamma_lut = crtc->gamma_lut; - if (state->base->committed & WLR_OUTPUT_STATE_GAMMA_LUT) { + if (state->base->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) { + size_t dim = 0; + uint16_t *lut = NULL; + if (state->base->color_transform != NULL) { + struct wlr_color_transform_lut_3x1d *tr = + color_transform_lut_3x1d_from_base(state->base->color_transform); + dim = tr->dim; + lut = tr->lut_3x1d; + } + // Fallback to legacy gamma interface when gamma properties are not // available (can happen on older Intel GPUs that support gamma but not // degamma). if (crtc->props.gamma_lut == 0) { - if (!drm_legacy_crtc_set_gamma(drm, crtc, - state->base->gamma_lut_size, - state->base->gamma_lut)) { + if (!drm_legacy_crtc_set_gamma(drm, crtc, dim, lut)) { return false; } } else { - if (!create_gamma_lut_blob(drm, state->base->gamma_lut_size, - state->base->gamma_lut, &gamma_lut)) { + if (!create_gamma_lut_blob(drm, dim, lut, &gamma_lut)) { return false; } } @@ -272,6 +362,15 @@ bool drm_atomic_connector_prepare(struct wlr_drm_connector_state *state, bool mo state->primary_fb->wlr_buf->height, &state->base->damage, &fb_damage_clips); } + int in_fence_fd = -1; + if (state->wait_timeline != NULL) { + in_fence_fd = wlr_drm_syncobj_timeline_export_sync_file(state->wait_timeline, + state->wait_point); + if (in_fence_fd < 0) { + return false; + } + } + bool prev_vrr_enabled = output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED; bool vrr_enabled = prev_vrr_enabled; @@ -282,10 +381,25 @@ bool drm_atomic_connector_prepare(struct wlr_drm_connector_state *state, bool mo vrr_enabled = state->base->adaptive_sync_enabled; } + uint32_t colorspace = conn->colorspace; + if (state->base->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) { + colorspace = convert_primaries_to_colorspace( + state->base->image_description ? state->base->image_description->primaries : 0); + } + + uint32_t hdr_output_metadata = conn->hdr_output_metadata; + if ((state->base->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) && + !create_hdr_output_metadata_blob(drm, state->base->image_description, &hdr_output_metadata)) { + return false; + } + state->mode_id = mode_id; state->gamma_lut = gamma_lut; state->fb_damage_clips = fb_damage_clips; + state->primary_in_fence_fd = in_fence_fd; state->vrr_enabled = vrr_enabled; + state->colorspace = colorspace; + state->hdr_output_metadata = hdr_output_metadata; return true; } @@ -300,11 +414,23 @@ void drm_atomic_connector_apply_commit(struct wlr_drm_connector_state *state) { crtc->own_mode_id = true; commit_blob(drm, &crtc->mode_id, state->mode_id); commit_blob(drm, &crtc->gamma_lut, state->gamma_lut); + commit_blob(drm, &conn->hdr_output_metadata, state->hdr_output_metadata); conn->output.adaptive_sync_status = state->vrr_enabled ? WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED : WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED; destroy_blob(drm, state->fb_damage_clips); + if (state->primary_in_fence_fd >= 0) { + close(state->primary_in_fence_fd); + } + if (state->out_fence_fd >= 0) { + // TODO: error handling + wlr_drm_syncobj_timeline_import_sync_file(state->base->signal_timeline, + state->base->signal_point, state->out_fence_fd); + close(state->out_fence_fd); + } + + conn->colorspace = state->colorspace; } void drm_atomic_connector_rollback_commit(struct wlr_drm_connector_state *state) { @@ -314,8 +440,15 @@ void drm_atomic_connector_rollback_commit(struct wlr_drm_connector_state *state) rollback_blob(drm, &crtc->mode_id, state->mode_id); rollback_blob(drm, &crtc->gamma_lut, state->gamma_lut); + rollback_blob(drm, &conn->hdr_output_metadata, state->hdr_output_metadata); destroy_blob(drm, state->fb_damage_clips); + if (state->primary_in_fence_fd >= 0) { + close(state->primary_in_fence_fd); + } + if (state->out_fence_fd >= 0) { + close(state->out_fence_fd); + } } static void plane_disable(struct atomic *atom, struct wlr_drm_plane *plane) { @@ -327,7 +460,8 @@ static void plane_disable(struct atomic *atom, struct wlr_drm_plane *plane) { static void set_plane_props(struct atomic *atom, struct wlr_drm_backend *drm, struct wlr_drm_plane *plane, struct wlr_drm_fb *fb, uint32_t crtc_id, - int32_t x, int32_t y) { + const struct wlr_box *dst_box, + const struct wlr_fbox *src_box) { uint32_t id = plane->id; const struct wlr_drm_plane_props *props = &plane->props; @@ -337,28 +471,50 @@ static void set_plane_props(struct atomic *atom, struct wlr_drm_backend *drm, return; } - uint32_t width = fb->wlr_buf->width; - uint32_t height = fb->wlr_buf->height; - // The src_* properties are in 16.16 fixed point - atomic_add(atom, id, props->src_x, 0); - atomic_add(atom, id, props->src_y, 0); - atomic_add(atom, id, props->src_w, (uint64_t)width << 16); - atomic_add(atom, id, props->src_h, (uint64_t)height << 16); - atomic_add(atom, id, props->crtc_w, width); - atomic_add(atom, id, props->crtc_h, height); + atomic_add(atom, id, props->src_x, src_box->x * (1 << 16)); + atomic_add(atom, id, props->src_y, src_box->y * (1 << 16)); + atomic_add(atom, id, props->src_w, src_box->width * (1 << 16)); + atomic_add(atom, id, props->src_h, src_box->height * (1 << 16)); atomic_add(atom, id, props->fb_id, fb->id); atomic_add(atom, id, props->crtc_id, crtc_id); - atomic_add(atom, id, props->crtc_x, (uint64_t)x); - atomic_add(atom, id, props->crtc_y, (uint64_t)y); + atomic_add(atom, id, props->crtc_x, dst_box->x); + atomic_add(atom, id, props->crtc_y, dst_box->y); + atomic_add(atom, id, props->crtc_w, dst_box->width); + atomic_add(atom, id, props->crtc_h, dst_box->height); } -static bool supports_cursor_hotspots(const struct wlr_drm_plane* plane) { +static bool supports_cursor_hotspots(const struct wlr_drm_plane *plane) { return plane->props.hotspot_x && plane->props.hotspot_y; } +static void set_plane_in_fence_fd(struct atomic *atom, + struct wlr_drm_plane *plane, int sync_file_fd) { + if (!plane->props.in_fence_fd) { + wlr_log(WLR_ERROR, "Plane %"PRIu32 " is missing the IN_FENCE_FD property", + plane->id); + atom->failed = true; + return; + } + + atomic_add(atom, plane->id, plane->props.in_fence_fd, sync_file_fd); +} + +static void set_crtc_out_fence_ptr(struct atomic *atom, struct wlr_drm_crtc *crtc, + int *fd_ptr) { + if (!crtc->props.out_fence_ptr) { + wlr_log(WLR_ERROR, + "CRTC %"PRIu32" is missing the OUT_FENCE_PTR property", + crtc->id); + atom->failed = true; + return; + } + + atomic_add(atom, crtc->id, crtc->props.out_fence_ptr, (uintptr_t)fd_ptr); +} + static void atomic_connector_add(struct atomic *atom, - const struct wlr_drm_connector_state *state, bool modeset) { + struct wlr_drm_connector_state *state, bool modeset) { struct wlr_drm_connector *conn = state->connector; struct wlr_drm_backend *drm = conn->backend; struct wlr_drm_crtc *crtc = conn->crtc; @@ -376,6 +532,12 @@ static void atomic_connector_add(struct atomic *atom, if (modeset && active && conn->props.max_bpc != 0 && conn->max_bpc_bounds[1] != 0) { atomic_add(atom, conn->id, conn->props.max_bpc, pick_max_bpc(conn, state->primary_fb)); } + if (conn->props.colorspace != 0) { + atomic_add(atom, conn->id, conn->props.colorspace, state->colorspace); + } + if (conn->props.hdr_output_metadata != 0) { + atomic_add(atom, conn->id, conn->props.hdr_output_metadata, state->hdr_output_metadata); + } atomic_add(atom, crtc->id, crtc->props.mode_id, state->mode_id); atomic_add(atom, crtc->id, crtc->props.active, active); if (active) { @@ -385,16 +547,33 @@ static void atomic_connector_add(struct atomic *atom, if (crtc->props.vrr_enabled != 0) { atomic_add(atom, crtc->id, crtc->props.vrr_enabled, state->vrr_enabled); } + set_plane_props(atom, drm, crtc->primary, state->primary_fb, crtc->id, - 0, 0); + &state->primary_viewport.dst_box, &state->primary_viewport.src_box); if (crtc->primary->props.fb_damage_clips != 0) { atomic_add(atom, crtc->primary->id, crtc->primary->props.fb_damage_clips, state->fb_damage_clips); } + if (state->primary_in_fence_fd >= 0) { + set_plane_in_fence_fd(atom, crtc->primary, state->primary_in_fence_fd); + } + if (state->base->committed & WLR_OUTPUT_STATE_SIGNAL_TIMELINE) { + set_crtc_out_fence_ptr(atom, crtc, &state->out_fence_fd); + } if (crtc->cursor) { if (drm_connector_is_cursor_visible(conn)) { + struct wlr_fbox cursor_src = { + .width = state->cursor_fb->wlr_buf->width, + .height = state->cursor_fb->wlr_buf->height, + }; + struct wlr_box cursor_dst = { + .x = conn->cursor_x, + .y = conn->cursor_y, + .width = state->cursor_fb->wlr_buf->width, + .height = state->cursor_fb->wlr_buf->height, + }; set_plane_props(atom, drm, crtc->cursor, state->cursor_fb, - crtc->id, conn->cursor_x, conn->cursor_y); + crtc->id, &cursor_dst, &cursor_src); if (supports_cursor_hotspots(crtc->cursor)) { atomic_add(atom, crtc->cursor->id, crtc->cursor->props.hotspot_x, conn->cursor_hotspot_x); @@ -437,7 +616,7 @@ static bool atomic_device_commit(struct wlr_drm_backend *drm, if (state->modeset) { flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; } - if (!test_only && state->nonblock) { + if (state->nonblock) { flags |= DRM_MODE_ATOMIC_NONBLOCK; } @@ -456,33 +635,6 @@ out: return ok; } -bool drm_atomic_reset(struct wlr_drm_backend *drm) { - struct atomic atom; - atomic_begin(&atom); - - for (size_t i = 0; i < drm->num_crtcs; i++) { - struct wlr_drm_crtc *crtc = &drm->crtcs[i]; - atomic_add(&atom, crtc->id, crtc->props.mode_id, 0); - atomic_add(&atom, crtc->id, crtc->props.active, 0); - } - - struct wlr_drm_connector *conn; - wl_list_for_each(conn, &drm->connectors, link) { - atomic_add(&atom, conn->id, conn->props.crtc_id, 0); - } - - for (size_t i = 0; i < drm->num_planes; i++) { - plane_disable(&atom, &drm->planes[i]); - } - - uint32_t flags = DRM_MODE_ATOMIC_ALLOW_MODESET; - bool ok = atomic_commit(&atom, drm, NULL, NULL, flags); - atomic_finish(&atom); - - return ok; -} - const struct wlr_drm_interface atomic_iface = { .commit = atomic_device_commit, - .reset = drm_atomic_reset, }; diff --git a/backend/drm/backend.c b/backend/drm/backend.c index d166f4672..5a545b192 100644 --- a/backend/drm/backend.c +++ b/backend/drm/backend.c @@ -1,10 +1,8 @@ #include -#include #include #include #include #include -#include #include #include #include @@ -13,6 +11,7 @@ #include #include "backend/drm/drm.h" #include "backend/drm/fb.h" +#include "render/drm_format_set.h" struct wlr_drm_backend *get_drm_backend_from_backend( struct wlr_backend *wlr_backend) { @@ -53,7 +52,8 @@ static void backend_destroy(struct wlr_backend *backend) { wl_list_remove(&drm->dev_change.link); wl_list_remove(&drm->dev_remove.link); - if (drm->parent) { + if (drm->mgpu_renderer.wlr_rend) { + wlr_drm_format_set_finish(&drm->mgpu_formats); finish_drm_renderer(&drm->mgpu_renderer); } @@ -75,10 +75,6 @@ static int backend_get_drm_fd(struct wlr_backend *backend) { return drm->fd; } -static uint32_t backend_get_buffer_caps(struct wlr_backend *backend) { - return WLR_BUFFER_CAP_DMABUF; -} - static bool backend_test(struct wlr_backend *backend, const struct wlr_backend_output_state *states, size_t states_len) { struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); @@ -95,7 +91,6 @@ static const struct wlr_backend_impl backend_impl = { .start = backend_start, .destroy = backend_destroy, .get_drm_fd = backend_get_drm_fd, - .get_buffer_caps = backend_get_buffer_caps, .test = backend_test, .commit = backend_commit, }; @@ -117,11 +112,18 @@ static void handle_session_active(struct wl_listener *listener, void *data) { wlr_log(WLR_INFO, "DRM FD %s", session->active ? "resumed" : "paused"); if (!session->active) { + // Disconnect any active connectors so that the client will modeset and + // rerender when the session is activated again. + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->connectors, link) { + if (conn->status == DRM_MODE_CONNECTED) { + wlr_output_destroy(&conn->output); + } + } return; } scan_drm_connectors(drm, NULL); - restore_drm_device(drm); } static void handle_dev_change(struct wl_listener *listener, void *data) { @@ -165,6 +167,44 @@ static void handle_parent_destroy(struct wl_listener *listener, void *data) { backend_destroy(&drm->backend); } +static void sanitize_mgpu_modifiers(struct wlr_drm_format_set *set) { + for (size_t idx = 0; idx < set->len; idx++) { + // Implicit modifiers are not well-defined across devices, so strip + // them from all formats in multi-gpu scenarios. + struct wlr_drm_format *fmt = &set->formats[idx]; + wlr_drm_format_set_remove(set, fmt->format, DRM_FORMAT_MOD_INVALID); + } +} + +static bool init_mgpu_renderer(struct wlr_drm_backend *drm) { + if (!init_drm_renderer(drm, &drm->mgpu_renderer)) { + wlr_log(WLR_INFO, "Failed to initialize mgpu blit renderer" + ", falling back to scanning out from primary GPU"); + + for (uint32_t plane_idx = 0; plane_idx < drm->num_planes; plane_idx++) { + struct wlr_drm_plane *plane = &drm->planes[plane_idx]; + sanitize_mgpu_modifiers(&plane->formats); + } + return true; + } + + // We'll perform a multi-GPU copy for all submitted buffers, we need + // to be able to texture from them + struct wlr_renderer *renderer = drm->mgpu_renderer.wlr_rend; + const struct wlr_drm_format_set *texture_formats = + wlr_renderer_get_texture_formats(renderer, WLR_BUFFER_CAP_DMABUF); + if (texture_formats == NULL) { + wlr_log(WLR_ERROR, "Failed to query renderer texture formats"); + return false; + } + + wlr_drm_format_set_copy(&drm->mgpu_formats, texture_formats); + sanitize_mgpu_modifiers(&drm->mgpu_formats); + drm->backend.features.timeline = drm->backend.features.timeline && + drm->mgpu_renderer.wlr_rend->features.timeline; + return true; +} + struct wlr_backend *wlr_drm_backend_create(struct wlr_session *session, struct wlr_device *dev, struct wlr_backend *parent) { assert(session && dev); @@ -192,6 +232,8 @@ struct wlr_backend *wlr_drm_backend_create(struct wlr_session *session, } wlr_backend_init(&drm->backend, &backend_impl); + drm->backend.buffer_caps = WLR_BUFFER_CAP_DMABUF; + drm->session = session; wl_list_init(&drm->fbs); wl_list_init(&drm->connectors); @@ -234,34 +276,8 @@ struct wlr_backend *wlr_drm_backend_create(struct wlr_session *session, goto error_event; } - if (drm->parent) { - if (!init_drm_renderer(drm, &drm->mgpu_renderer)) { - wlr_log(WLR_ERROR, "Failed to initialize renderer"); - goto error_resources; - } - - // We'll perform a multi-GPU copy for all submitted buffers, we need - // to be able to texture from them - struct wlr_renderer *renderer = drm->mgpu_renderer.wlr_rend; - const struct wlr_drm_format_set *texture_formats = - wlr_renderer_get_texture_formats(renderer, WLR_BUFFER_CAP_DMABUF); - if (texture_formats == NULL) { - wlr_log(WLR_ERROR, "Failed to query renderer texture formats"); - goto error_mgpu_renderer; - } - - // Forbid implicit modifiers, because their meaning changes from one - // GPU to another. - for (size_t i = 0; i < texture_formats->len; i++) { - const struct wlr_drm_format *fmt = &texture_formats->formats[i]; - for (size_t j = 0; j < fmt->len; j++) { - uint64_t mod = fmt->modifiers[j]; - if (mod == DRM_FORMAT_MOD_INVALID) { - continue; - } - wlr_drm_format_set_add(&drm->mgpu_formats, fmt->format, mod); - } - } + if (drm->parent && !init_mgpu_renderer(drm)) { + goto error_mgpu_renderer; } drm->session_destroy.notify = handle_session_destroy; @@ -271,7 +287,6 @@ struct wlr_backend *wlr_drm_backend_create(struct wlr_session *session, error_mgpu_renderer: finish_drm_renderer(&drm->mgpu_renderer); -error_resources: finish_drm_resources(drm); error_event: wl_list_remove(&drm->session_active.link); diff --git a/backend/drm/drm.c b/backend/drm/drm.c index 5756f2897..86b52c684 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include @@ -14,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -24,9 +24,7 @@ #include "backend/drm/fb.h" #include "backend/drm/iface.h" #include "backend/drm/util.h" -#include "render/pixel_format.h" -#include "render/drm_format_set.h" -#include "render/wlr_renderer.h" +#include "render/color.h" #include "types/wlr_output.h" #include "util/env.h" #include "config.h" @@ -40,9 +38,12 @@ static const uint32_t COMMIT_OUTPUT_STATE = WLR_OUTPUT_STATE_BUFFER | WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_ENABLED | - WLR_OUTPUT_STATE_GAMMA_LUT | WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED | - WLR_OUTPUT_STATE_LAYERS; + WLR_OUTPUT_STATE_LAYERS | + WLR_OUTPUT_STATE_WAIT_TIMELINE | + WLR_OUTPUT_STATE_SIGNAL_TIMELINE | + WLR_OUTPUT_STATE_COLOR_TRANSFORM | + WLR_OUTPUT_STATE_IMAGE_DESCRIPTION; static const uint32_t SUPPORTED_OUTPUT_STATE = WLR_OUTPUT_STATE_BACKEND_OPTIONAL | COMMIT_OUTPUT_STATE; @@ -121,6 +122,7 @@ bool check_drm_features(struct wlr_drm_backend *drm) { drm->supports_tearing_page_flips = drmGetCap(drm->fd, DRM_CAP_ASYNC_PAGE_FLIP, &cap) == 0 && cap == 1; } else { drm->supports_tearing_page_flips = drmGetCap(drm->fd, DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP, &cap) == 0 && cap == 1; + drm->backend.features.timeline = drmGetCap(drm->fd, DRM_CAP_SYNCOBJ_TIMELINE, &cap) == 0 && cap == 1; } if (env_parse_bool("WLR_DRM_NO_MODIFIERS")) { @@ -170,7 +172,7 @@ static bool init_plane(struct wlr_drm_backend *drm, } p->type = type; - p->id = drm_plane->plane_id; + p->id = id; p->props = props; p->initial_crtc_id = drm_plane->crtc_id; @@ -396,6 +398,7 @@ void finish_drm_resources(struct wlr_drm_backend *drm) { struct wlr_drm_plane *plane = &drm->planes[i]; drm_plane_finish_surface(plane); wlr_drm_format_set_finish(&plane->formats); + free(plane->cursor_sizes); } free(drm->planes); @@ -553,6 +556,7 @@ static void drm_connector_apply_commit(const struct wlr_drm_connector_state *sta struct wlr_drm_crtc *crtc = conn->crtc; drm_fb_copy(&crtc->primary->queued_fb, state->primary_fb); + crtc->primary->viewport = state->primary_viewport; if (crtc->cursor != NULL) { drm_fb_copy(&crtc->cursor->queued_fb, state->cursor_fb); } @@ -576,6 +580,16 @@ static void drm_connector_apply_commit(const struct wlr_drm_connector_state *sta conn->cursor_enabled = false; conn->crtc = NULL; + + // Legacy uAPI doesn't support requesting page-flip events when + // turning off a CRTC + if (page_flip != NULL && conn->backend->iface == &legacy_iface) { + drm_page_flip_pop(page_flip, crtc->id); + conn->pending_page_flip = NULL; + if (page_flip->connectors_len == 0) { + drm_page_flip_destroy(page_flip); + } + } } } @@ -607,6 +621,7 @@ static bool drm_commit(struct wlr_drm_backend *drm, if (page_flip == NULL) { return false; } + page_flip->async = (flags & DRM_MODE_PAGE_FLIP_ASYNC); } bool ok = drm->iface->commit(drm, state, page_flip, flags, test_only); @@ -630,6 +645,8 @@ static void drm_connector_state_init(struct wlr_drm_connector_state *state, .connector = conn, .base = base, .active = output_pending_enabled(&conn->output, base), + .primary_in_fence_fd = -1, + .out_fence_fd = -1, }; struct wlr_output_mode *mode = conn->output.current_mode; @@ -639,7 +656,7 @@ static void drm_connector_state_init(struct wlr_drm_connector_state *state, if (base->committed & WLR_OUTPUT_STATE_MODE) { switch (base->mode_type) { - case WLR_OUTPUT_STATE_MODE_FIXED:; + case WLR_OUTPUT_STATE_MODE_FIXED: mode = base->mode; break; case WLR_OUTPUT_STATE_MODE_CUSTOM: @@ -666,8 +683,10 @@ static void drm_connector_state_init(struct wlr_drm_connector_state *state, struct wlr_drm_plane *primary = conn->crtc->primary; if (primary->queued_fb != NULL) { state->primary_fb = drm_fb_lock(primary->queued_fb); + state->primary_viewport = primary->viewport; } else if (primary->current_fb != NULL) { state->primary_fb = drm_fb_lock(primary->current_fb); + state->primary_viewport = primary->viewport; } if (conn->cursor_enabled) { @@ -687,6 +706,7 @@ static void drm_connector_state_init(struct wlr_drm_connector_state *state, static void drm_connector_state_finish(struct wlr_drm_connector_state *state) { drm_fb_clear(&state->primary_fb); drm_fb_clear(&state->cursor_fb); + wlr_drm_syncobj_timeline_unref(state->wait_timeline); } static bool drm_connector_state_update_primary_fb(struct wlr_drm_connector *conn, @@ -701,8 +721,16 @@ static bool drm_connector_state_update_primary_fb(struct wlr_drm_connector *conn struct wlr_drm_plane *plane = crtc->primary; struct wlr_buffer *source_buf = state->base->buffer; + struct wlr_drm_syncobj_timeline *wait_timeline = NULL; + uint64_t wait_point = 0; + if (state->base->committed & WLR_OUTPUT_STATE_WAIT_TIMELINE) { + wait_timeline = state->base->wait_timeline; + wait_point = state->base->wait_point; + } + assert(state->wait_timeline == NULL); + struct wlr_buffer *local_buf; - if (drm->parent) { + if (drm->mgpu_renderer.wlr_rend) { struct wlr_drm_format format = {0}; if (!drm_plane_pick_render_format(plane, &format, &drm->mgpu_renderer)) { wlr_log(WLR_ERROR, "Failed to pick primary plane format"); @@ -717,12 +745,23 @@ static bool drm_connector_state_update_primary_fb(struct wlr_drm_connector *conn return false; } - local_buf = drm_surface_blit(&plane->mgpu_surf, source_buf); + local_buf = drm_surface_blit(&plane->mgpu_surf, source_buf, + wait_timeline, wait_point); if (local_buf == NULL) { return false; } + + if (plane->mgpu_surf.timeline != NULL) { + state->wait_timeline = wlr_drm_syncobj_timeline_ref(plane->mgpu_surf.timeline); + state->wait_point = plane->mgpu_surf.point; + } } else { local_buf = wlr_buffer_lock(source_buf); + + if (wait_timeline != NULL) { + state->wait_timeline = wlr_drm_syncobj_timeline_ref(wait_timeline); + state->wait_point = wait_point; + } } bool ok = drm_fb_import(&state->primary_fb, drm, local_buf, @@ -734,6 +773,9 @@ static bool drm_connector_state_update_primary_fb(struct wlr_drm_connector *conn return false; } + output_state_get_buffer_src_box(state->base, &state->primary_viewport.src_box); + output_state_get_buffer_dst_box(state->base, &state->primary_viewport.dst_box); + return true; } @@ -742,7 +784,7 @@ static bool drm_connector_set_pending_layer_fbs(struct wlr_drm_connector *conn, struct wlr_drm_backend *drm = conn->backend; struct wlr_drm_crtc *crtc = conn->crtc; - if (!crtc || drm->parent) { + if (!crtc || drm->mgpu_renderer.wlr_rend) { return false; } @@ -784,13 +826,12 @@ static bool drm_connector_prepare(struct wlr_drm_connector_state *conn_state, bo return false; } - if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && state->enabled) { - if (output->current_mode == NULL && - !(state->committed & WLR_OUTPUT_STATE_MODE)) { - wlr_drm_conn_log(conn, WLR_DEBUG, - "Can't enable an output without a mode"); - return false; - } + if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && state->enabled && + output->width == 0 && output->height == 0 && + !(state->committed & WLR_OUTPUT_STATE_MODE)) { + wlr_drm_conn_log(conn, WLR_DEBUG, + "Can't enable an output without a mode"); + return false; } if ((state->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) && @@ -801,7 +842,36 @@ static bool drm_connector_prepare(struct wlr_drm_connector_state *conn_state, bo return false; } - if (test_only && conn->backend->parent) { + if ((state->committed & WLR_OUTPUT_STATE_BUFFER) && conn->backend->mgpu_renderer.wlr_rend) { + struct wlr_dmabuf_attributes dmabuf; + if (!wlr_buffer_get_dmabuf(state->buffer, &dmabuf)) { + wlr_drm_conn_log(conn, WLR_DEBUG, "Buffer is not a DMA-BUF"); + return false; + } + + if (!wlr_drm_format_set_has(&conn->backend->mgpu_formats, dmabuf.format, dmabuf.modifier)) { + wlr_drm_conn_log(conn, WLR_DEBUG, + "Buffer format 0x%"PRIX32" with modifier 0x%"PRIX64" cannot be " + "imported into multi-GPU renderer", + dmabuf.format, dmabuf.modifier); + return false; + } + } + + if ((state->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) && state->color_transform != NULL && + state->color_transform->type != COLOR_TRANSFORM_LUT_3X1D) { + wlr_drm_conn_log(conn, WLR_DEBUG, + "Only 3x1D LUT color transforms are supported"); + return false; + } + + if ((state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) && + conn->backend->iface != &atomic_iface) { + wlr_log(WLR_DEBUG, "Image descriptions are only supported by the atomic interface"); + return false; + } + + if (test_only && conn->backend->mgpu_renderer.wlr_rend) { // If we're running as a secondary GPU, we can't perform an atomic // commit without blitting a buffer. return true; @@ -871,7 +941,7 @@ static bool drm_connector_commit_state(struct wlr_drm_connector *conn, goto out; } - if (test_only && conn->backend->parent) { + if (test_only && conn->backend->mgpu_renderer.wlr_rend) { // If we're running as a secondary GPU, we can't perform an atomic // commit without blitting a buffer. ok = true; @@ -1068,7 +1138,7 @@ static bool drm_connector_set_cursor(struct wlr_output *output, } struct wlr_buffer *local_buf; - if (drm->parent) { + if (drm->mgpu_renderer.wlr_rend) { struct wlr_drm_format format = {0}; if (!drm_plane_pick_render_format(plane, &format, &drm->mgpu_renderer)) { wlr_log(WLR_ERROR, "Failed to pick cursor plane format"); @@ -1082,7 +1152,7 @@ static bool drm_connector_set_cursor(struct wlr_output *output, return false; } - local_buf = drm_surface_blit(&plane->mgpu_surf, buffer); + local_buf = drm_surface_blit(&plane->mgpu_surf, buffer, NULL, 0); if (local_buf == NULL) { return false; } @@ -1102,7 +1172,6 @@ static bool drm_connector_set_cursor(struct wlr_output *output, conn->cursor_height = buffer->height; } - wlr_output_update_needs_frame(output); return true; } @@ -1132,7 +1201,6 @@ static bool drm_connector_move_cursor(struct wlr_output *output, conn->cursor_x = box.x; conn->cursor_y = box.y; - wlr_output_update_needs_frame(output); return true; } @@ -1155,6 +1223,8 @@ static void dealloc_crtc(struct wlr_drm_connector *conn); static void drm_connector_destroy_output(struct wlr_output *output) { struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + wlr_output_finish(output); + dealloc_crtc(conn); conn->status = DRM_MODE_DISCONNECTED; @@ -1182,7 +1252,7 @@ static const struct wlr_drm_format_set *drm_connector_get_cursor_formats( if (!plane) { return NULL; } - if (conn->backend->parent) { + if (conn->backend->mgpu_renderer.wlr_rend) { return &conn->backend->mgpu_formats; } return &plane->formats; @@ -1211,7 +1281,7 @@ static const struct wlr_drm_format_set *drm_connector_get_primary_formats( if (!drm_connector_alloc_crtc(conn)) { return NULL; } - if (conn->backend->parent) { + if (conn->backend->mgpu_renderer.wlr_rend) { return &conn->backend->mgpu_formats; } return &conn->crtc->primary->formats; @@ -1348,7 +1418,7 @@ static void realloc_crtcs(struct wlr_drm_backend *drm, ++i; } - match_obj(num_connectors, connector_constraints, + match_connectors_with_crtcs(num_connectors, connector_constraints, drm->num_crtcs, previous_match, new_match); // Converts our crtc=>connector result into a connector=>crtc one. @@ -1476,14 +1546,14 @@ static struct wlr_drm_connector *create_drm_connector(struct wlr_drm_backend *dr return NULL; } - const char *conn_name = + const char *conn_type_name = drmModeGetConnectorTypeName(drm_conn->connector_type); - if (conn_name == NULL) { - conn_name = "Unknown"; + if (conn_type_name == NULL) { + conn_type_name = "Unknown"; } snprintf(wlr_conn->name, sizeof(wlr_conn->name), - "%s-%"PRIu32, conn_name, drm_conn->connector_type_id); + "%s-%"PRIu32, conn_type_name, drm_conn->connector_type_id); wlr_conn->possible_crtcs = drmModeConnectorGetPossibleCrtcs(drm->fd, drm_conn); @@ -1554,6 +1624,7 @@ static bool connect_drm_connector(struct wlr_drm_connector *wlr_conn, wlr_log(WLR_INFO, "Detected modes:"); + bool found_current_mode = false; for (int i = 0; i < drm_conn->count_modes; ++i) { if (drm_conn->modes[i].flags & DRM_MODE_FLAG_INTERLACE) { continue; @@ -1572,14 +1643,7 @@ static bool connect_drm_connector(struct wlr_drm_connector *wlr_conn, if (current_modeinfo != NULL && memcmp(&mode->drm_mode, current_modeinfo, sizeof(*current_modeinfo)) == 0) { wlr_output_state_set_mode(&state, &mode->wlr_mode); - - uint64_t mode_id = 0; - get_drm_prop(drm->fd, wlr_conn->crtc->id, - wlr_conn->crtc->props.mode_id, &mode_id); - - wlr_conn->crtc->own_mode_id = false; - wlr_conn->crtc->mode_id = mode_id; - wlr_conn->refresh = calculate_refresh_rate(current_modeinfo); + found_current_mode = true; } wlr_log(WLR_INFO, " %"PRId32"x%"PRId32" @ %.3f Hz %s", @@ -1590,6 +1654,23 @@ static bool connect_drm_connector(struct wlr_drm_connector *wlr_conn, wl_list_insert(modes.prev, &mode->wlr_mode.link); } + if (current_modeinfo != NULL) { + int32_t refresh = calculate_refresh_rate(current_modeinfo); + + if (!found_current_mode) { + wlr_output_state_set_custom_mode(&state, + current_modeinfo->hdisplay, current_modeinfo->vdisplay, refresh); + } + + uint64_t mode_id = 0; + get_drm_prop(drm->fd, wlr_conn->crtc->id, + wlr_conn->crtc->props.mode_id, &mode_id); + + wlr_conn->crtc->own_mode_id = false; + wlr_conn->crtc->mode_id = mode_id; + wlr_conn->refresh = refresh; + } + free(current_modeinfo); wlr_output_init(output, &drm->backend, &output_impl, drm->session->event_loop, &state); @@ -1636,7 +1717,11 @@ static bool connect_drm_connector(struct wlr_drm_connector *wlr_conn, size_t edid_len = 0; uint8_t *edid = get_drm_prop_blob(drm->fd, wlr_conn->id, wlr_conn->props.edid, &edid_len); - parse_edid(wlr_conn, edid_len, edid); + if (edid_len > 0) { + parse_edid(wlr_conn, edid_len, edid); + } else { + wlr_log(WLR_DEBUG, "Connector has no EDID"); + } free(edid); char *subconnector = NULL; @@ -1702,6 +1787,10 @@ void scan_drm_connectors(struct wlr_drm_backend *drm, } } + if (wlr_conn && wlr_conn->lease) { + continue; + } + // If the hotplug event contains a connector ID, ignore any other // connector. if (event != NULL && event->connector_id != 0 && @@ -1779,8 +1868,6 @@ void scan_drm_connectors(struct wlr_drm_backend *drm, destroy_drm_connector(conn); } - realloc_crtcs(drm, NULL); - for (size_t i = 0; i < new_outputs_len; ++i) { struct wlr_drm_connector *conn = new_outputs[i]; @@ -1820,108 +1907,6 @@ void scan_drm_leases(struct wlr_drm_backend *drm) { drmFree(list); } -static void build_current_connector_state(struct wlr_output_state *state, - struct wlr_drm_connector *conn) { - bool enabled = conn->status != DRM_MODE_DISCONNECTED && conn->output.enabled; - - wlr_output_state_init(state); - wlr_output_state_set_enabled(state, enabled); - if (!enabled) { - return; - } - - if (conn->output.current_mode != NULL) { - wlr_output_state_set_mode(state, conn->output.current_mode); - } else { - wlr_output_state_set_custom_mode(state, - conn->output.width, conn->output.height, conn->output.refresh); - } -} - -/** - * Check whether we need to perform a full reset after a VT switch. - * - * If any connector or plane has a different CRTC, we need to perform a full - * reset to restore our mapping. We couldn't avoid a full reset even if we - * used a single KMS atomic commit to apply our state: the kernel rejects - * commits which migrate a plane from one CRTC to another without going through - * an intermediate state where the plane is disabled. - */ -static bool skip_reset_for_restore(struct wlr_drm_backend *drm) { - struct wlr_drm_connector *conn; - wl_list_for_each(conn, &drm->connectors, link) { - drmModeConnector *drm_conn = drmModeGetConnectorCurrent(drm->fd, conn->id); - if (drm_conn == NULL) { - return false; - } - struct wlr_drm_crtc *crtc = connector_get_current_crtc(conn, drm_conn); - drmModeFreeConnector(drm_conn); - - if (crtc != NULL && conn->crtc != crtc) { - return false; - } - } - - for (size_t i = 0; i < drm->num_planes; i++) { - struct wlr_drm_plane *plane = &drm->planes[i]; - - drmModePlane *drm_plane = drmModeGetPlane(drm->fd, plane->id); - if (drm_plane == NULL) { - return false; - } - uint32_t crtc_id = drm_plane->crtc_id; - drmModeFreePlane(drm_plane); - - struct wlr_drm_crtc *crtc = NULL; - for (size_t i = 0; i < drm->num_crtcs; i++) { - if (drm->crtcs[i].id == crtc_id) { - crtc = &drm->crtcs[i]; - break; - } - } - if (crtc == NULL) { - continue; - } - - bool ok = false; - switch (plane->type) { - case DRM_PLANE_TYPE_PRIMARY: - ok = crtc->primary == plane; - break; - case DRM_PLANE_TYPE_CURSOR: - ok = crtc->cursor == plane; - break; - } - if (!ok) { - return false; - } - } - - return true; -} - -void restore_drm_device(struct wlr_drm_backend *drm) { - // The previous DRM master leaves KMS in an undefined state. We need - // to restore our own state, but be careful to avoid invalid - // configurations. The connector/CRTC mapping may have changed, so - // first disable all CRTCs, then light up the ones we were using - // before the VT switch. - // TODO: better use the atomic API to improve restoration after a VT switch - if (!skip_reset_for_restore(drm) && !drm->iface->reset(drm)) { - wlr_log(WLR_ERROR, "Failed to reset state after VT switch"); - } - - struct wlr_drm_connector *conn; - wl_list_for_each(conn, &drm->connectors, link) { - struct wlr_output_state state; - build_current_connector_state(&state, conn); - if (!drm_connector_commit_state(conn, &state, false)) { - wlr_drm_conn_log(conn, WLR_ERROR, "Failed to restore state after VT switch"); - } - wlr_output_state_finish(&state); - } -} - bool commit_drm_device(struct wlr_drm_backend *drm, const struct wlr_backend_output_state *output_states, size_t output_states_len, bool test_only) { @@ -1970,7 +1955,7 @@ bool commit_drm_device(struct wlr_drm_backend *drm, modeset |= output_state->base.allow_reconfiguration; } - if (test_only && drm->parent) { + if (test_only && drm->mgpu_renderer.wlr_rend) { // If we're running as a secondary GPU, we can't perform an atomic // commit without blitting a buffer. ok = true; @@ -2008,6 +1993,12 @@ static void handle_page_flip(int fd, unsigned seq, if (conn != NULL) { conn->pending_page_flip = NULL; } + + uint32_t present_flags = WLR_OUTPUT_PRESENT_HW_CLOCK | WLR_OUTPUT_PRESENT_HW_COMPLETION; + if (!page_flip->async) { + present_flags |= WLR_OUTPUT_PRESENT_VSYNC; + } + if (page_flip->connectors_len == 0) { drm_page_flip_destroy(page_flip); } @@ -2038,26 +2029,23 @@ static void handle_page_flip(int fd, unsigned seq, drm_fb_move(&layer->current_fb, &layer->queued_fb); } - uint32_t present_flags = WLR_OUTPUT_PRESENT_VSYNC | - WLR_OUTPUT_PRESENT_HW_CLOCK | WLR_OUTPUT_PRESENT_HW_COMPLETION; /* Don't report ZERO_COPY in multi-gpu situations, because we had to copy * data between the GPUs, even if we were using the direct scanout * interface. */ - if (!drm->parent) { + if (!drm->mgpu_renderer.wlr_rend) { present_flags |= WLR_OUTPUT_PRESENT_ZERO_COPY; } - struct timespec present_time = { - .tv_sec = tv_sec, - .tv_nsec = tv_usec * 1000, - }; struct wlr_output_event_present present_event = { /* The DRM backend guarantees that the presentation event will be for * the last submitted frame. */ .commit_seq = conn->output.commit_seq, .presented = drm->session->active, - .when = &present_time, + .when = { + .tv_sec = tv_sec, + .tv_nsec = tv_usec * 1000, + }, .seq = seq, .refresh = mhz_to_nsec(conn->refresh), .flags = present_flags, @@ -2191,6 +2179,7 @@ struct wlr_drm_lease *wlr_drm_create_lease(struct wlr_output **outputs, get_drm_connector_from_output(outputs[i]); conn->lease = lease; conn->crtc->lease = lease; + disconnect_drm_connector(conn); } return lease; @@ -2213,6 +2202,8 @@ void drm_lease_destroy(struct wlr_drm_lease *lease) { wl_signal_emit_mutable(&lease->events.destroy, NULL); + assert(wl_list_empty(&lease->events.destroy.listener_list)); + struct wlr_drm_connector *conn; wl_list_for_each(conn, &drm->connectors, link) { if (conn->lease == lease) { @@ -2227,4 +2218,5 @@ void drm_lease_destroy(struct wlr_drm_lease *lease) { } free(lease); + scan_drm_connectors(drm, NULL); } diff --git a/backend/drm/legacy.c b/backend/drm/legacy.c index 0c591f4dd..223852ec1 100644 --- a/backend/drm/legacy.c +++ b/backend/drm/legacy.c @@ -7,6 +7,8 @@ #include "backend/drm/fb.h" #include "backend/drm/iface.h" #include "backend/drm/util.h" +#include "render/color.h" +#include "types/wlr_output.h" static bool legacy_fb_props_match(struct wlr_drm_fb *fb1, struct wlr_drm_fb *fb2) { @@ -39,20 +41,41 @@ static bool legacy_crtc_test(const struct wlr_drm_connector_state *state, struct wlr_drm_connector *conn = state->connector; struct wlr_drm_crtc *crtc = conn->crtc; - if ((state->base->committed & WLR_OUTPUT_STATE_BUFFER) && !modeset) { - struct wlr_drm_fb *pending_fb = state->primary_fb; - - struct wlr_drm_fb *prev_fb = crtc->primary->queued_fb; - if (!prev_fb) { - prev_fb = crtc->primary->current_fb; + if (state->base->committed & WLR_OUTPUT_STATE_BUFFER) { + // If the size doesn't match, reject buffer (scaling is not supported) + int pending_width, pending_height; + output_pending_resolution(&state->connector->output, state->base, + &pending_width, &pending_height); + if (state->base->buffer->width != pending_width || + state->base->buffer->height != pending_height) { + wlr_log(WLR_DEBUG, "Primary buffer size mismatch"); + return false; + } + // Source crop is also not supported + struct wlr_fbox src_box; + output_state_get_buffer_src_box(state->base, &src_box); + if (src_box.x != 0.0 || src_box.y != 0.0 || + src_box.width != (double)state->base->buffer->width || + src_box.height != (double)state->base->buffer->height) { + wlr_log(WLR_DEBUG, "Source crop not supported in DRM-legacy output"); + return false; } - /* Legacy is only guaranteed to be able to display a FB if it's been - * allocated the same way as the previous one. */ - if (prev_fb != NULL && !legacy_fb_props_match(prev_fb, pending_fb)) { - wlr_drm_conn_log(conn, WLR_DEBUG, - "Cannot change scan-out buffer parameters with legacy KMS API"); - return false; + if (!modeset) { + struct wlr_drm_fb *pending_fb = state->primary_fb; + + struct wlr_drm_fb *prev_fb = crtc->primary->queued_fb; + if (!prev_fb) { + prev_fb = crtc->primary->current_fb; + } + + /* Legacy is only guaranteed to be able to display a FB if it's been + * allocated the same way as the previous one. */ + if (prev_fb != NULL && !legacy_fb_props_match(prev_fb, pending_fb)) { + wlr_drm_conn_log(conn, WLR_DEBUG, + "Cannot change scan-out buffer parameters with legacy KMS API"); + return false; + } } } @@ -102,9 +125,17 @@ static bool legacy_crtc_commit(const struct wlr_drm_connector_state *state, } } - if (state->base->committed & WLR_OUTPUT_STATE_GAMMA_LUT) { - if (!drm_legacy_crtc_set_gamma(drm, crtc, - state->base->gamma_lut_size, state->base->gamma_lut)) { + if (state->base->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) { + size_t dim = 0; + uint16_t *lut = NULL; + if (state->base->color_transform != NULL) { + struct wlr_color_transform_lut_3x1d *tr = + color_transform_lut_3x1d_from_base(state->base->color_transform); + dim = tr->dim; + lut = tr->lut_3x1d; + } + + if (!drm_legacy_crtc_set_gamma(drm, crtc, dim, lut)) { return false; } } @@ -128,7 +159,7 @@ static bool legacy_crtc_commit(const struct wlr_drm_connector_state *state, state->base->adaptive_sync_enabled ? "enabled" : "disabled"); } - if (cursor != NULL && drm_connector_is_cursor_visible(conn)) { + if (cursor != NULL && state->active && drm_connector_is_cursor_visible(conn)) { struct wlr_drm_fb *cursor_fb = state->cursor_fb; if (cursor_fb == NULL) { wlr_drm_conn_log(conn, WLR_DEBUG, "Failed to acquire cursor FB"); @@ -170,7 +201,9 @@ static bool legacy_crtc_commit(const struct wlr_drm_connector_state *state, } } - if (flags & DRM_MODE_PAGE_FLIP_EVENT) { + // Legacy uAPI doesn't support requesting page-flip events when + // turning off a CRTC + if (state->active && (flags & DRM_MODE_PAGE_FLIP_EVENT)) { if (drmModePageFlip(drm->fd, crtc->id, fb_id, flags, page_flip)) { wlr_drm_conn_log_errno(conn, WLR_ERROR, "drmModePageFlip failed"); return false; @@ -248,20 +281,6 @@ bool drm_legacy_crtc_set_gamma(struct wlr_drm_backend *drm, return true; } -static bool legacy_reset(struct wlr_drm_backend *drm) { - bool ok = true; - for (size_t i = 0; i < drm->num_crtcs; i++) { - struct wlr_drm_crtc *crtc = &drm->crtcs[i]; - if (drmModeSetCrtc(drm->fd, crtc->id, 0, 0, 0, NULL, 0, NULL) != 0) { - wlr_log_errno(WLR_ERROR, "Failed to disable CRTC %"PRIu32, - crtc->id); - ok = false; - } - } - return ok; -} - const struct wlr_drm_interface legacy_iface = { .commit = legacy_commit, - .reset = legacy_reset, }; diff --git a/backend/drm/libliftoff.c b/backend/drm/libliftoff.c index 85054476c..12761afd4 100644 --- a/backend/drm/libliftoff.c +++ b/backend/drm/libliftoff.c @@ -4,12 +4,14 @@ #include #include #include +#include #include #include "backend/drm/drm.h" #include "backend/drm/fb.h" #include "backend/drm/iface.h" #include "config.h" +#include "types/wlr_output.h" static void log_handler(enum liftoff_log_priority priority, const char *fmt, va_list args) { enum wlr_log_importance importance = WLR_SILENT; @@ -149,25 +151,23 @@ static bool add_prop(drmModeAtomicReq *req, uint32_t obj, } static bool set_plane_props(struct wlr_drm_plane *plane, - struct liftoff_layer *layer, struct wlr_drm_fb *fb, int32_t x, int32_t y, uint64_t zpos) { + struct liftoff_layer *layer, struct wlr_drm_fb *fb, uint64_t zpos, + const struct wlr_box *dst_box, const struct wlr_fbox *src_box) { if (fb == NULL) { wlr_log(WLR_ERROR, "Failed to acquire FB for plane %"PRIu32, plane->id); return false; } - uint32_t width = fb->wlr_buf->width; - uint32_t height = fb->wlr_buf->height; - - // The SRC_* properties are in 16.16 fixed point + // The src_* properties are in 16.16 fixed point return liftoff_layer_set_property(layer, "zpos", zpos) == 0 && - liftoff_layer_set_property(layer, "SRC_X", 0) == 0 && - liftoff_layer_set_property(layer, "SRC_Y", 0) == 0 && - liftoff_layer_set_property(layer, "SRC_W", (uint64_t)width << 16) == 0 && - liftoff_layer_set_property(layer, "SRC_H", (uint64_t)height << 16) == 0 && - liftoff_layer_set_property(layer, "CRTC_X", (uint64_t)x) == 0 && - liftoff_layer_set_property(layer, "CRTC_Y", (uint64_t)y) == 0 && - liftoff_layer_set_property(layer, "CRTC_W", width) == 0 && - liftoff_layer_set_property(layer, "CRTC_H", height) == 0 && + liftoff_layer_set_property(layer, "SRC_X", src_box->x * (1 << 16)) == 0 && + liftoff_layer_set_property(layer, "SRC_Y", src_box->y * (1 << 16)) == 0 && + liftoff_layer_set_property(layer, "SRC_W", src_box->width * (1 << 16)) == 0 && + liftoff_layer_set_property(layer, "SRC_H", src_box->height * (1 << 16)) == 0 && + liftoff_layer_set_property(layer, "CRTC_X", dst_box->x) == 0 && + liftoff_layer_set_property(layer, "CRTC_Y", dst_box->y) == 0 && + liftoff_layer_set_property(layer, "CRTC_W", dst_box->width) == 0 && + liftoff_layer_set_property(layer, "CRTC_H", dst_box->height) == 0 && liftoff_layer_set_property(layer, "FB_ID", fb->id) == 0; } @@ -331,14 +331,32 @@ static bool add_connector(drmModeAtomicReq *req, if (crtc->props.vrr_enabled != 0) { ok = ok && add_prop(req, crtc->id, crtc->props.vrr_enabled, state->vrr_enabled); } - ok = ok && - set_plane_props(crtc->primary, crtc->primary->liftoff_layer, state->primary_fb, 0, 0, 0) && - set_plane_props(crtc->primary, crtc->liftoff_composition_layer, state->primary_fb, 0, 0, 0); + + ok = ok && set_plane_props(crtc->primary, + crtc->primary->liftoff_layer, state->primary_fb, 0, + &state->primary_viewport.dst_box, + &state->primary_viewport.src_box); + ok = ok && set_plane_props(crtc->primary, + crtc->liftoff_composition_layer, state->primary_fb, 0, + &state->primary_viewport.dst_box, + &state->primary_viewport.src_box); + liftoff_layer_set_property(crtc->primary->liftoff_layer, "FB_DAMAGE_CLIPS", state->fb_damage_clips); liftoff_layer_set_property(crtc->liftoff_composition_layer, "FB_DAMAGE_CLIPS", state->fb_damage_clips); + if (state->primary_in_fence_fd >= 0) { + liftoff_layer_set_property(crtc->primary->liftoff_layer, + "IN_FENCE_FD", state->primary_in_fence_fd); + liftoff_layer_set_property(crtc->liftoff_composition_layer, + "IN_FENCE_FD", state->primary_in_fence_fd); + } + if (state->base->committed & WLR_OUTPUT_STATE_SIGNAL_TIMELINE) { + ok = ok && add_prop(req, crtc->id, crtc->props.out_fence_ptr, + (uintptr_t)&state->out_fence_fd); + } + if (state->base->committed & WLR_OUTPUT_STATE_LAYERS) { for (size_t i = 0; i < state->base->layers_len; i++) { const struct wlr_output_layer_state *layer_state = &state->base->layers[i]; @@ -349,9 +367,19 @@ static bool add_connector(drmModeAtomicReq *req, if (crtc->cursor) { if (drm_connector_is_cursor_visible(conn)) { + struct wlr_fbox cursor_src = { + .width = state->cursor_fb->wlr_buf->width, + .height = state->cursor_fb->wlr_buf->height, + }; + struct wlr_box cursor_dst = { + .x = conn->cursor_x, + .y = conn->cursor_y, + .width = state->cursor_fb->wlr_buf->width, + .height = state->cursor_fb->wlr_buf->height, + }; ok = ok && set_plane_props(crtc->cursor, crtc->cursor->liftoff_layer, - state->cursor_fb, conn->cursor_x, conn->cursor_y, - wl_list_length(&crtc->layers) + 1); + state->cursor_fb, wl_list_length(&crtc->layers) + 1, + &cursor_dst, &cursor_src); } else { ok = ok && disable_plane(crtc->cursor); } @@ -397,7 +425,7 @@ static bool commit(struct wlr_drm_backend *drm, if (state->modeset) { flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; } - if (!test_only && state->nonblock) { + if (state->nonblock) { flags |= DRM_MODE_ATOMIC_NONBLOCK; } @@ -479,5 +507,4 @@ const struct wlr_drm_interface liftoff_iface = { .init = init, .finish = finish, .commit = commit, - .reset = drm_atomic_reset, }; diff --git a/backend/drm/meson.build b/backend/drm/meson.build index 666c7cc46..d9e5b77f5 100644 --- a/backend/drm/meson.build +++ b/backend/drm/meson.build @@ -7,6 +7,7 @@ hwdata = dependency( libdisplay_info = dependency( 'libdisplay-info', + version: '>=0.2.0', required: 'drm' in backends, fallback: 'libdisplay-info', not_found_message: 'Required for the DRM backend.', diff --git a/backend/drm/properties.c b/backend/drm/properties.c index d78bfaf64..314023954 100644 --- a/backend/drm/properties.c +++ b/backend/drm/properties.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -21,8 +22,10 @@ struct prop_info { static const struct prop_info connector_info[] = { #define INDEX(name) (offsetof(struct wlr_drm_connector_props, name) / sizeof(uint32_t)) { "CRTC_ID", INDEX(crtc_id) }, + { "Colorspace", INDEX(colorspace) }, { "DPMS", INDEX(dpms) }, { "EDID", INDEX(edid) }, + { "HDR_OUTPUT_METADATA", INDEX(hdr_output_metadata) }, { "PATH", INDEX(path) }, { "content type", INDEX(content_type) }, { "link-status", INDEX(link_status) }, @@ -40,6 +43,7 @@ static const struct prop_info crtc_info[] = { { "GAMMA_LUT", INDEX(gamma_lut) }, { "GAMMA_LUT_SIZE", INDEX(gamma_lut_size) }, { "MODE_ID", INDEX(mode_id) }, + { "OUT_FENCE_PTR", INDEX(out_fence_ptr) }, { "VRR_ENABLED", INDEX(vrr_enabled) }, #undef INDEX }; @@ -55,6 +59,7 @@ static const struct prop_info plane_info[] = { { "FB_ID", INDEX(fb_id) }, { "HOTSPOT_X", INDEX(hotspot_x) }, { "HOTSPOT_Y", INDEX(hotspot_y) }, + { "IN_FENCE_FD", INDEX(in_fence_fd) }, { "IN_FORMATS", INDEX(in_formats) }, { "SIZE_HINTS", INDEX(size_hints) }, { "SRC_H", INDEX(src_h) }, @@ -77,14 +82,14 @@ static bool scan_properties(int fd, uint32_t id, uint32_t type, uint32_t *result const struct prop_info *info, size_t info_len) { drmModeObjectProperties *props = drmModeObjectGetProperties(fd, id, type); if (!props) { - wlr_log_errno(WLR_ERROR, "Failed to get DRM object properties"); + wlr_log_errno(WLR_ERROR, "Failed to get DRM object %" PRIu32 " properties", id); return false; } for (uint32_t i = 0; i < props->count_props; ++i) { drmModePropertyRes *prop = drmModeGetProperty(fd, props->props[i]); if (!prop) { - wlr_log_errno(WLR_ERROR, "Failed to get DRM object property"); + wlr_log_errno(WLR_ERROR, "Failed to get property %" PRIu32 " of DRM object %" PRIu32, props->props[i], id); continue; } diff --git a/backend/drm/renderer.c b/backend/drm/renderer.c index e4aadc106..af9908e91 100644 --- a/backend/drm/renderer.c +++ b/backend/drm/renderer.c @@ -1,31 +1,35 @@ #include #include +#include +#include #include #include #include #include "backend/drm/drm.h" #include "backend/drm/fb.h" #include "backend/drm/renderer.h" -#include "backend/backend.h" #include "render/drm_format_set.h" -#include "render/allocator/allocator.h" #include "render/pixel_format.h" #include "render/wlr_renderer.h" bool init_drm_renderer(struct wlr_drm_backend *drm, struct wlr_drm_renderer *renderer) { + wlr_log(WLR_DEBUG, "Creating multi-GPU renderer"); renderer->wlr_rend = renderer_autocreate_with_drm_fd(drm->fd); if (!renderer->wlr_rend) { - wlr_log(WLR_ERROR, "Failed to create renderer"); + return false; + } + if (wlr_renderer_get_texture_formats(renderer->wlr_rend, WLR_BUFFER_CAP_DMABUF) == NULL) { + wlr_log(WLR_ERROR, "Renderer did not support importing DMA-BUFs"); + wlr_renderer_destroy(renderer->wlr_rend); + renderer->wlr_rend = NULL; return false; } - uint32_t backend_caps = backend_get_buffer_caps(&drm->backend); - renderer->allocator = allocator_autocreate_with_drm_fd(backend_caps, - renderer->wlr_rend, drm->fd); + renderer->allocator = wlr_allocator_autocreate(&drm->backend, renderer->wlr_rend); if (renderer->allocator == NULL) { - wlr_log(WLR_ERROR, "Failed to create allocator"); wlr_renderer_destroy(renderer->wlr_rend); + renderer->wlr_rend = NULL; return false; } @@ -46,6 +50,7 @@ void finish_drm_surface(struct wlr_drm_surface *surf) { return; } + wlr_drm_syncobj_timeline_unref(surf->timeline); wlr_swapchain_destroy(surf->swapchain); *surf = (struct wlr_drm_surface){0}; @@ -68,13 +73,24 @@ bool init_drm_surface(struct wlr_drm_surface *surf, return false; } + int drm_fd = wlr_renderer_get_drm_fd(renderer->wlr_rend); + if (renderer->wlr_rend->features.timeline && drm_fd >= 0) { + surf->timeline = wlr_drm_syncobj_timeline_create(drm_fd); + if (surf->timeline == NULL) { + finish_drm_surface(surf); + wlr_log(WLR_ERROR, "Failed to create DRM syncobj timeline"); + return false; + } + } + surf->renderer = renderer; return true; } struct wlr_buffer *drm_surface_blit(struct wlr_drm_surface *surf, - struct wlr_buffer *buffer) { + struct wlr_buffer *buffer, + struct wlr_drm_syncobj_timeline *wait_timeline, uint64_t wait_point) { struct wlr_renderer *renderer = surf->renderer->wlr_rend; if (surf->swapchain->width != buffer->width || @@ -89,13 +105,18 @@ struct wlr_buffer *drm_surface_blit(struct wlr_drm_surface *surf, return NULL; } - struct wlr_buffer *dst = wlr_swapchain_acquire(surf->swapchain, NULL); + struct wlr_buffer *dst = wlr_swapchain_acquire(surf->swapchain); if (!dst) { wlr_log(WLR_ERROR, "Failed to acquire multi-GPU swapchain buffer"); goto error_tex; } - struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, dst, NULL); + surf->point++; + const struct wlr_buffer_pass_options pass_options = { + .signal_timeline = surf->timeline, + .signal_point = surf->point, + }; + struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, dst, &pass_options); if (pass == NULL) { wlr_log(WLR_ERROR, "Failed to begin render pass with multi-GPU destination buffer"); goto error_dst; @@ -104,6 +125,8 @@ struct wlr_buffer *drm_surface_blit(struct wlr_drm_surface *surf, wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options){ .texture = tex, .blend_mode = WLR_RENDER_BLEND_MODE_NONE, + .wait_timeline = wait_timeline, + .wait_point = wait_point, }); if (!wlr_render_pass_submit(pass)) { wlr_log(WLR_ERROR, "Failed to submit multi-GPU render pass"); diff --git a/backend/drm/util.c b/backend/drm/util.c index a14e5e1ef..dd2b37351 100644 --- a/backend/drm/util.c +++ b/backend/drm/util.c @@ -83,6 +83,17 @@ void parse_edid(struct wlr_drm_connector *conn, size_t len, const uint8_t *data) output->model = di_info_get_model(info); output->serial = di_info_get_serial(info); + const struct di_supported_signal_colorimetry *colorimetry = di_info_get_supported_signal_colorimetry(info); + bool has_bt2020 = colorimetry->bt2020_cycc || colorimetry->bt2020_ycc || colorimetry->bt2020_rgb; + if (conn->props.colorspace != 0 && has_bt2020) { + output->supported_primaries |= WLR_COLOR_NAMED_PRIMARIES_BT2020; + } + + const struct di_hdr_static_metadata *hdr_static_metadata = di_info_get_hdr_static_metadata(info); + if (conn->props.hdr_output_metadata != 0 && hdr_static_metadata->type1 && hdr_static_metadata->pq) { + output->supported_transfer_functions |= WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ; + } + di_info_destroy(info); } @@ -112,9 +123,9 @@ static bool is_taken(size_t n, const uint32_t arr[static n], uint32_t key) { * passing 12 arguments to a function. */ struct match_state { - const size_t num_objs; - const uint32_t *restrict objs; - const size_t num_res; + const size_t num_conns; + const uint32_t *restrict conns; + const size_t num_crtcs; size_t score; size_t replaced; uint32_t *restrict res; @@ -123,27 +134,31 @@ struct match_state { bool exit_early; }; -/* - * skips: The number of SKIP elements encountered so far. - * score: The number of resources we've matched so far. +/** + * Step to process a CRTC. + * + * This is a naive implementation of maximum bipartite matching. + * + * score: The number of connectors we've matched so far. * replaced: The number of changes from the original solution. - * i: The index of the current element. + * crtc_index: The index of the current CRTC. * * This tries to match a solution as close to st->orig as it can. * * Returns whether we've set a new best element with this solution. */ -static bool match_obj_(struct match_state *st, size_t skips, size_t score, size_t replaced, size_t i) { +static bool match_connectors_with_crtcs_(struct match_state *st, + size_t score, size_t replaced, size_t crtc_index) { // Finished - if (i >= st->num_res) { + if (crtc_index >= st->num_crtcs) { if (score > st->score || (score == st->score && replaced < st->replaced)) { st->score = score; st->replaced = replaced; - memcpy(st->best, st->res, sizeof(st->best[0]) * st->num_res); + memcpy(st->best, st->res, sizeof(st->best[0]) * st->num_crtcs); - st->exit_early = (st->score == st->num_res - skips - || st->score == st->num_objs) + st->exit_early = (st->score == st->num_crtcs + || st->score == st->num_conns) && st->replaced == 0; return true; @@ -152,27 +167,16 @@ static bool match_obj_(struct match_state *st, size_t skips, size_t score, size_ } } - if (st->orig[i] == SKIP) { - st->res[i] = SKIP; - return match_obj_(st, skips + 1, score, replaced, i + 1); - } - bool has_best = false; /* * Attempt to use the current solution first, to try and avoid * recalculating everything */ - if (st->orig[i] != UNMATCHED && !is_taken(i, st->res, st->orig[i])) { - st->res[i] = st->orig[i]; - size_t obj_score = st->objs[st->res[i]] != 0 ? 1 : 0; - if (match_obj_(st, skips, score + obj_score, replaced, i + 1)) { - has_best = true; - } - } - if (st->orig[i] == UNMATCHED) { - st->res[i] = UNMATCHED; - if (match_obj_(st, skips, score, replaced, i + 1)) { + if (st->orig[crtc_index] != UNMATCHED && !is_taken(crtc_index, st->res, st->orig[crtc_index])) { + st->res[crtc_index] = st->orig[crtc_index]; + size_t crtc_score = st->conns[st->res[crtc_index]] != 0 ? 1 : 0; + if (match_connectors_with_crtcs_(st, score + crtc_score, replaced, crtc_index + 1)) { has_best = true; } } @@ -180,29 +184,29 @@ static bool match_obj_(struct match_state *st, size_t skips, size_t score, size_ return true; } - if (st->orig[i] != UNMATCHED) { + if (st->orig[crtc_index] != UNMATCHED) { ++replaced; } - for (size_t candidate = 0; candidate < st->num_objs; ++candidate) { + for (size_t candidate = 0; candidate < st->num_conns; ++candidate) { // We tried this earlier - if (candidate == st->orig[i]) { + if (candidate == st->orig[crtc_index]) { continue; } // Not compatible - if (!(st->objs[candidate] & (1 << i))) { + if (!(st->conns[candidate] & (1 << crtc_index))) { continue; } // Already taken - if (is_taken(i, st->res, candidate)) { + if (is_taken(crtc_index, st->res, candidate)) { continue; } - st->res[i] = candidate; - size_t obj_score = st->objs[candidate] != 0 ? 1 : 0; - if (match_obj_(st, skips, score + obj_score, replaced, i + 1)) { + st->res[crtc_index] = candidate; + size_t crtc_score = st->conns[candidate] != 0 ? 1 : 0; + if (match_connectors_with_crtcs_(st, score + crtc_score, replaced, crtc_index + 1)) { has_best = true; } @@ -211,37 +215,37 @@ static bool match_obj_(struct match_state *st, size_t skips, size_t score, size_ } } - if (has_best) { - return true; + // Maybe this CRTC can't be matched + st->res[crtc_index] = UNMATCHED; + if (match_connectors_with_crtcs_(st, score, replaced, crtc_index + 1)) { + has_best = true; } - // Maybe this resource can't be matched - st->res[i] = UNMATCHED; - return match_obj_(st, skips, score, replaced, i + 1); + return has_best; } -size_t match_obj(size_t num_objs, const uint32_t objs[static restrict num_objs], - size_t num_res, const uint32_t res[static restrict num_res], - uint32_t out[static restrict num_res]) { - uint32_t solution[num_res]; - for (size_t i = 0; i < num_res; ++i) { +void match_connectors_with_crtcs(size_t num_conns, + const uint32_t conns[static restrict num_conns], + size_t num_crtcs, const uint32_t prev_crtcs[static restrict num_crtcs], + uint32_t new_crtcs[static restrict num_crtcs]) { + uint32_t solution[num_crtcs]; + for (size_t i = 0; i < num_crtcs; ++i) { solution[i] = UNMATCHED; } struct match_state st = { - .num_objs = num_objs, - .num_res = num_res, + .num_conns = num_conns, + .num_crtcs = num_crtcs, .score = 0, .replaced = SIZE_MAX, - .objs = objs, + .conns = conns, .res = solution, - .best = out, - .orig = res, + .best = new_crtcs, + .orig = prev_crtcs, .exit_early = false, }; - match_obj_(&st, 0, 0, 0, 0); - return st.score; + match_connectors_with_crtcs_(&st, 0, 0, 0); } void generate_cvt_mode(drmModeModeInfo *mode, int hdisplay, int vdisplay, diff --git a/backend/headless/backend.c b/backend/headless/backend.c index e643a06e1..d03f520b8 100644 --- a/backend/headless/backend.c +++ b/backend/headless/backend.c @@ -45,16 +45,9 @@ static void backend_destroy(struct wlr_backend *wlr_backend) { free(backend); } -static uint32_t get_buffer_caps(struct wlr_backend *wlr_backend) { - return WLR_BUFFER_CAP_DATA_PTR - | WLR_BUFFER_CAP_DMABUF - | WLR_BUFFER_CAP_SHM; -} - static const struct wlr_backend_impl backend_impl = { .start = backend_start, .destroy = backend_destroy, - .get_buffer_caps = get_buffer_caps, }; static void handle_event_loop_destroy(struct wl_listener *listener, void *data) { @@ -74,12 +67,17 @@ struct wlr_backend *wlr_headless_backend_create(struct wl_event_loop *loop) { wlr_backend_init(&backend->backend, &backend_impl); + backend->backend.buffer_caps = + WLR_BUFFER_CAP_DATA_PTR | WLR_BUFFER_CAP_DMABUF | WLR_BUFFER_CAP_SHM; + backend->event_loop = loop; wl_list_init(&backend->outputs); backend->event_loop_destroy.notify = handle_event_loop_destroy; wl_event_loop_add_destroy_listener(loop, &backend->event_loop_destroy); + backend->backend.features.timeline = true; + return &backend->backend; } diff --git a/backend/headless/output.c b/backend/headless/output.c index 5aaf1bd8a..a4cdb17c3 100644 --- a/backend/headless/output.c +++ b/backend/headless/output.c @@ -79,9 +79,20 @@ static bool output_commit(struct wlr_output *wlr_output, return true; } +static bool output_set_cursor(struct wlr_output *wlr_output, + struct wlr_buffer *buffer, int hotspot_x, int hotspot_y) { + return true; +} + +static bool output_move_cursor(struct wlr_output *wlr_output, int x, int y) { + return true; +} + static void output_destroy(struct wlr_output *wlr_output) { - struct wlr_headless_output *output = - headless_output_from_output(wlr_output); + struct wlr_headless_output *output = headless_output_from_output(wlr_output); + + wlr_output_finish(wlr_output); + wl_list_remove(&output->link); wl_event_source_remove(output->frame_timer); free(output); @@ -89,7 +100,10 @@ static void output_destroy(struct wlr_output *wlr_output) { static const struct wlr_output_impl output_impl = { .destroy = output_destroy, + .test = output_test, .commit = output_commit, + .set_cursor = output_set_cursor, + .move_cursor = output_move_cursor, }; bool wlr_output_is_headless(struct wlr_output *wlr_output) { diff --git a/backend/libinput/pointer.c b/backend/libinput/pointer.c index 1fe3d4790..9b9996780 100644 --- a/backend/libinput/pointer.c +++ b/backend/libinput/pointer.c @@ -61,25 +61,15 @@ void handle_pointer_button(struct libinput_event *event, .time_msec = usec_to_msec(libinput_event_pointer_get_time_usec(pevent)), .button = libinput_event_pointer_get_button(pevent), }; - // Ignore events which aren't a seat-wide state change. For instance, if - // the same button is pressed twice on the same seat, ignore the second - // press. - uint32_t seat_count = libinput_event_pointer_get_seat_button_count(pevent); switch (libinput_event_pointer_get_button_state(pevent)) { case LIBINPUT_BUTTON_STATE_PRESSED: wlr_event.state = WL_POINTER_BUTTON_STATE_PRESSED; - if (seat_count != 1) { - return; - } break; case LIBINPUT_BUTTON_STATE_RELEASED: wlr_event.state = WL_POINTER_BUTTON_STATE_RELEASED; - if (seat_count != 0) { - return; - } break; } - wl_signal_emit_mutable(&pointer->events.button, &wlr_event); + wlr_pointer_notify_button(pointer, &wlr_event); wl_signal_emit_mutable(&pointer->events.frame, pointer); } diff --git a/backend/libinput/tablet_pad.c b/backend/libinput/tablet_pad.c index 98b3e4f4b..2fbfb6a6c 100644 --- a/backend/libinput/tablet_pad.c +++ b/backend/libinput/tablet_pad.c @@ -104,6 +104,7 @@ void init_device_tablet_pad(struct wlr_libinput_input_device *dev) { struct udev_device *udev = libinput_device_get_udev_device(handle); char **dst = wl_array_add(&wlr_tablet_pad->paths, sizeof(char *)); *dst = strdup(udev_device_get_syspath(udev)); + udev_device_unref(udev); int groups = libinput_device_tablet_pad_get_num_mode_groups(handle); for (int i = 0; i < groups; ++i) { diff --git a/backend/libinput/tablet_tool.c b/backend/libinput/tablet_tool.c index cc9e60968..d43c6cd0a 100644 --- a/backend/libinput/tablet_tool.c +++ b/backend/libinput/tablet_tool.c @@ -6,6 +6,7 @@ #include #include #include "backend/libinput.h" +#include "config.h" struct tablet_tool { struct wlr_tablet_tool wlr_tool; @@ -36,6 +37,7 @@ void init_device_tablet(struct wlr_libinput_input_device *dev) { struct udev_device *udev = libinput_device_get_udev_device(dev->handle); char **dst = wl_array_add(&wlr_tablet->paths, sizeof(char *)); *dst = strdup(udev_device_get_syspath(udev)); + udev_device_unref(udev); wl_list_init(&dev->tablet_tools); } diff --git a/backend/multi/backend.c b/backend/multi/backend.c index 7dc9f548e..3d8fb96f5 100644 --- a/backend/multi/backend.c +++ b/backend/multi/backend.c @@ -6,7 +6,6 @@ #include #include #include -#include "backend/backend.h" #include "backend/multi.h" struct subbackend_state { @@ -52,6 +51,9 @@ static void multi_backend_destroy(struct wlr_backend *wlr_backend) { wlr_backend_finish(wlr_backend); + assert(wl_list_empty(&backend->events.backend_add.listener_list)); + assert(wl_list_empty(&backend->events.backend_remove.listener_list)); + // Some backends may depend on other backends, ie. destroying a backend may // also destroy other backends while (!wl_list_empty(&backend->backends)) { @@ -76,28 +78,6 @@ static int multi_backend_get_drm_fd(struct wlr_backend *backend) { return -1; } -static uint32_t multi_backend_get_buffer_caps(struct wlr_backend *backend) { - struct wlr_multi_backend *multi = multi_backend_from_backend(backend); - - if (wl_list_empty(&multi->backends)) { - return 0; - } - - uint32_t caps = WLR_BUFFER_CAP_DATA_PTR | WLR_BUFFER_CAP_DMABUF - | WLR_BUFFER_CAP_SHM; - - struct subbackend_state *sub; - wl_list_for_each(sub, &multi->backends, link) { - uint32_t backend_caps = backend_get_buffer_caps(sub->backend); - if (backend_caps != 0) { - // only count backend capable of presenting a buffer - caps = caps & backend_caps; - } - } - - return caps; -} - static int compare_output_state_backend(const void *data_a, const void *data_b) { const struct wlr_backend_output_state *a = data_a; const struct wlr_backend_output_state *b = data_b; @@ -126,22 +106,24 @@ static bool commit(struct wlr_backend *backend, qsort(by_backend, states_len, sizeof(by_backend[0]), compare_output_state_backend); bool ok = true; - for (size_t i = 0; i < states_len; i++) { + for (size_t i = 0; i < states_len;) { struct wlr_backend *sub = by_backend[i].output->backend; - size_t j = i; - while (j < states_len && by_backend[j].output->backend == sub) { - j++; + size_t len = 1; + while (i + len < states_len && + by_backend[i + len].output->backend == sub) { + len++; } if (test_only) { - ok = wlr_backend_test(sub, &by_backend[i], j - i); + ok = wlr_backend_test(sub, &by_backend[i], len); } else { - ok = wlr_backend_commit(sub, &by_backend[i], j - i); + ok = wlr_backend_commit(sub, &by_backend[i], len); } if (!ok) { break; } + i += len; } free(by_backend); @@ -162,7 +144,6 @@ static const struct wlr_backend_impl backend_impl = { .start = multi_backend_start, .destroy = multi_backend_destroy, .get_drm_fd = multi_backend_get_drm_fd, - .get_buffer_caps = multi_backend_get_buffer_caps, .test = multi_backend_test, .commit = multi_backend_commit, }; @@ -225,6 +206,33 @@ static struct subbackend_state *multi_backend_get_subbackend(struct wlr_multi_ba return NULL; } +static void multi_backend_refresh_features(struct wlr_multi_backend *multi) { + multi->backend.buffer_caps = 0; + multi->backend.features.timeline = true; + + bool has_buffer_cap = false; + uint32_t buffer_caps_intersection = + WLR_BUFFER_CAP_DATA_PTR | WLR_BUFFER_CAP_DMABUF | WLR_BUFFER_CAP_SHM; + struct subbackend_state *sub = NULL; + wl_list_for_each(sub, &multi->backends, link) { + // Only take into account backends capable of presenting a buffer + if (sub->backend->buffer_caps != 0) { + has_buffer_cap = true; + buffer_caps_intersection &= sub->backend->buffer_caps; + } + + // timeline is only applicable to backends that support DMABUFs + if (sub->backend->buffer_caps & WLR_BUFFER_CAP_DMABUF) { + multi->backend.features.timeline = multi->backend.features.timeline && + sub->backend->features.timeline; + } + } + + if (has_buffer_cap) { + multi->backend.buffer_caps = buffer_caps_intersection; + } +} + bool wlr_multi_backend_add(struct wlr_backend *_multi, struct wlr_backend *backend) { assert(_multi && backend); @@ -256,6 +264,7 @@ bool wlr_multi_backend_add(struct wlr_backend *_multi, wl_signal_add(&backend->events.new_output, &sub->new_output); sub->new_output.notify = new_output_reemit; + multi_backend_refresh_features(multi); wl_signal_emit_mutable(&multi->events.backend_add, backend); return true; } @@ -270,6 +279,7 @@ void wlr_multi_backend_remove(struct wlr_backend *_multi, if (sub) { wl_signal_emit_mutable(&multi->events.backend_remove, backend); subbackend_state_destroy(sub); + multi_backend_refresh_features(multi); } } diff --git a/backend/session/session.c b/backend/session/session.c index 6254a734a..32522fb42 100644 --- a/backend/session/session.c +++ b/backend/session/session.c @@ -191,32 +191,40 @@ static int handle_udev_event(int fd, uint32_t mask, void *data) { goto out; } + dev_t devnum = udev_device_get_devnum(udev_dev); if (strcmp(action, "add") == 0) { + struct wlr_device *dev; + wl_list_for_each(dev, &session->devices, link) { + if (dev->dev == devnum) { + wlr_log(WLR_DEBUG, "Skipping duplicate device %s", sysname); + goto out; + } + } + wlr_log(WLR_DEBUG, "DRM device %s added", sysname); struct wlr_session_add_event event = { .path = devnode, }; wl_signal_emit_mutable(&session->events.add_drm_card, &event); - } else if (strcmp(action, "change") == 0 || strcmp(action, "remove") == 0) { - dev_t devnum = udev_device_get_devnum(udev_dev); + } else if (strcmp(action, "change") == 0) { struct wlr_device *dev; wl_list_for_each(dev, &session->devices, link) { - if (dev->dev != devnum) { - continue; - } - - if (strcmp(action, "change") == 0) { + if (dev->dev == devnum) { wlr_log(WLR_DEBUG, "DRM device %s changed", sysname); struct wlr_device_change_event event = {0}; read_udev_change_event(&event, udev_dev); wl_signal_emit_mutable(&dev->events.change, &event); - } else if (strcmp(action, "remove") == 0) { + break; + } + } + } else if (strcmp(action, "remove") == 0) { + struct wlr_device *dev; + wl_list_for_each(dev, &session->devices, link) { + if (dev->dev == devnum) { wlr_log(WLR_DEBUG, "DRM device %s removed", sysname); wl_signal_emit_mutable(&dev->events.remove, NULL); - } else { - assert(0); + break; } - break; } } @@ -295,6 +303,11 @@ void wlr_session_destroy(struct wlr_session *session) { } wl_signal_emit_mutable(&session->events.destroy, session); + + assert(wl_list_empty(&session->events.active.listener_list)); + assert(wl_list_empty(&session->events.add_drm_card.listener_list)); + assert(wl_list_empty(&session->events.destroy.listener_list)); + wl_list_remove(&session->event_loop_destroy.link); wl_event_source_remove(session->udev_event); @@ -352,6 +365,13 @@ void wlr_session_close_file(struct wlr_session *session, if (libseat_close_device(session->seat_handle, dev->device_id) == -1) { wlr_log_errno(WLR_ERROR, "Failed to close device %d", dev->device_id); } + + assert(wl_list_empty(&dev->events.change.listener_list)); + // TODO: assert that the "remove" listener list is empty as well. Listeners + // will typically call wlr_session_close_file() in response, and + // wl_signal_emit_mutable() installs two phantom listeners, so we'd count + // these two. + close(dev->fd); wl_list_remove(&dev->link); free(dev); @@ -499,8 +519,6 @@ ssize_t wlr_session_find_gpus(struct wlr_session *session, break; } - bool is_boot_vga = false; - const char *path = udev_list_entry_get_name(entry); struct udev_device *dev = udev_device_new_from_syspath(session->udev, path); if (!dev) { @@ -516,14 +534,20 @@ ssize_t wlr_session_find_gpus(struct wlr_session *session, continue; } - // This is owned by 'dev', so we don't need to free it - struct udev_device *pci = - udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL); + bool is_primary = false; + const char *boot_display = udev_device_get_sysattr_value(dev, "boot_display"); + if (boot_display && strcmp(boot_display, "1") == 0) { + is_primary = true; + } else { + // This is owned by 'dev', so we don't need to free it + struct udev_device *pci = + udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL); - if (pci) { - const char *id = udev_device_get_sysattr_value(pci, "boot_vga"); - if (id && strcmp(id, "1") == 0) { - is_boot_vga = true; + if (pci) { + const char *id = udev_device_get_sysattr_value(pci, "boot_vga"); + if (id && strcmp(id, "1") == 0) { + is_primary = true; + } } } @@ -537,7 +561,7 @@ ssize_t wlr_session_find_gpus(struct wlr_session *session, udev_device_unref(dev); ret[i] = wlr_dev; - if (is_boot_vga) { + if (is_primary) { struct wlr_device *tmp = ret[0]; ret[0] = ret[i]; ret[i] = tmp; diff --git a/backend/wayland/backend.c b/backend/wayland/backend.c index c2b2f2faa..14a783b67 100644 --- a/backend/wayland/backend.c +++ b/backend/wayland/backend.c @@ -21,6 +21,7 @@ #include "drm-client-protocol.h" #include "linux-dmabuf-v1-client-protocol.h" +#include "linux-drm-syncobj-v1-client-protocol.h" #include "pointer-gestures-unstable-v1-client-protocol.h" #include "presentation-time-client-protocol.h" #include "xdg-activation-v1-client-protocol.h" @@ -54,14 +55,6 @@ struct wlr_wl_backend *get_wl_backend_from_backend(struct wlr_backend *wlr_backe static int dispatch_events(int fd, uint32_t mask, void *data) { struct wlr_wl_backend *wl = data; - if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { - if (mask & WL_EVENT_ERROR) { - wlr_log(WLR_ERROR, "Failed to read from remote Wayland display"); - } - wlr_backend_destroy(&wl->backend); - return 0; - } - int count = 0; if (mask & WL_EVENT_READABLE) { count = wl_display_dispatch(wl->remote_display); @@ -74,6 +67,18 @@ static int dispatch_events(int fd, uint32_t mask, void *data) { wl_display_flush(wl->remote_display); } + // Make sure we've consumed all data before disconnecting due to hangup, + // so that we process any wl_display.error events + if (!(mask & WL_EVENT_READABLE) && (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR))) { + if (mask & WL_EVENT_ERROR) { + wlr_log(WLR_ERROR, "Failed to read from remote Wayland display"); + } else { + wlr_log(WLR_DEBUG, "Disconnected from remote Wayland display"); + } + wlr_backend_destroy(&wl->backend); + return 0; + } + if (count < 0) { wlr_log(WLR_ERROR, "Failed to dispatch remote Wayland display"); wlr_backend_destroy(&wl->backend); @@ -178,7 +183,9 @@ static void linux_dmabuf_feedback_v1_handle_main_device(void *data, "falling back to primary node", name); } - feedback_data->backend->drm_render_name = strdup(name); + struct wlr_wl_backend *wl = feedback_data->backend; + assert(wl->drm_render_name == NULL); + wl->drm_render_name = strdup(name); drmFreeDevice(&device); } @@ -305,6 +312,7 @@ static char *get_render_name(const char *name) { static void legacy_drm_handle_device(void *data, struct wl_drm *drm, const char *name) { struct wlr_wl_backend *wl = data; + assert(wl->drm_render_name == NULL); wl->drm_render_name = get_render_name(name); } @@ -408,6 +416,9 @@ static void registry_global(void *data, struct wl_registry *registry, } else if (strcmp(iface, wp_viewporter_interface.name) == 0) { wl->viewporter = wl_registry_bind(registry, name, &wp_viewporter_interface, 1); + } else if (strcmp(iface, wp_linux_drm_syncobj_manager_v1_interface.name) == 0) { + wl->drm_syncobj_manager_v1 = wl_registry_bind(registry, name, + &wp_linux_drm_syncobj_manager_v1_interface, 1); } } @@ -481,6 +492,11 @@ static void backend_destroy(struct wlr_backend *backend) { destroy_wl_buffer(buffer); } + struct wlr_wl_drm_syncobj_timeline *timeline, *tmp_timeline; + wl_list_for_each_safe(timeline, tmp_timeline, &wl->drm_syncobj_timelines, link) { + destroy_wl_drm_syncobj_timeline(timeline); + } + wlr_backend_finish(backend); wl_list_remove(&wl->event_loop_destroy.link); @@ -515,6 +531,9 @@ static void backend_destroy(struct wlr_backend *backend) { if (wl->zwp_linux_dmabuf_v1) { zwp_linux_dmabuf_v1_destroy(wl->zwp_linux_dmabuf_v1); } + if (wl->drm_syncobj_manager_v1) { + wp_linux_drm_syncobj_manager_v1_destroy(wl->drm_syncobj_manager_v1); + } if (wl->legacy_drm != NULL) { wl_drm_destroy(wl->legacy_drm); } @@ -540,6 +559,7 @@ static void backend_destroy(struct wlr_backend *backend) { wl_compositor_destroy(wl->compositor); wl_registry_destroy(wl->registry); wl_display_flush(wl->remote_display); + wl_event_queue_destroy(wl->busy_loop_queue); if (wl->own_remote_display) { wl_display_disconnect(wl->remote_display); } @@ -551,17 +571,10 @@ static int backend_get_drm_fd(struct wlr_backend *backend) { return wl->drm_fd; } -static uint32_t get_buffer_caps(struct wlr_backend *backend) { - struct wlr_wl_backend *wl = get_wl_backend_from_backend(backend); - return (wl->zwp_linux_dmabuf_v1 ? WLR_BUFFER_CAP_DMABUF : 0) - | (wl->shm ? WLR_BUFFER_CAP_SHM : 0); -} - static const struct wlr_backend_impl backend_impl = { .start = backend_start, .destroy = backend_destroy, .get_drm_fd = backend_get_drm_fd, - .get_buffer_caps = get_buffer_caps, }; bool wlr_backend_is_wl(struct wlr_backend *b) { @@ -589,6 +602,7 @@ struct wlr_backend *wlr_wl_backend_create(struct wl_event_loop *loop, wl_list_init(&wl->outputs); wl_list_init(&wl->seats); wl_list_init(&wl->buffers); + wl_list_init(&wl->drm_syncobj_timelines); if (remote_display != NULL) { wl->remote_display = remote_display; @@ -601,10 +615,16 @@ struct wlr_backend *wlr_wl_backend_create(struct wl_event_loop *loop, wl->own_remote_display = true; } + wl->busy_loop_queue = wl_display_create_queue(wl->remote_display); + if (wl->busy_loop_queue == NULL) { + wlr_log_errno(WLR_ERROR, "Could not create a Wayland event queue"); + goto error_display; + } + wl->registry = wl_display_get_registry(wl->remote_display); if (!wl->registry) { wlr_log_errno(WLR_ERROR, "Could not obtain reference to remote registry"); - goto error_display; + goto error_queue; } wl_registry_add_listener(wl->registry, ®istry_listener, wl); @@ -621,6 +641,10 @@ struct wlr_backend *wlr_wl_backend_create(struct wl_event_loop *loop, goto error_registry; } + wl->backend.features.timeline = wl->drm_syncobj_manager_v1 != NULL; + + wl_display_roundtrip(wl->remote_display); // process initial event bursts + struct zwp_linux_dmabuf_feedback_v1 *linux_dmabuf_feedback_v1 = NULL; struct wlr_wl_linux_dmabuf_feedback_v1 feedback_data = { .backend = wl }; if (wl->zwp_linux_dmabuf_v1 != NULL && @@ -638,18 +662,27 @@ struct wlr_backend *wlr_wl_backend_create(struct wl_event_loop *loop, if (wl->legacy_drm != NULL) { wl_drm_destroy(wl->legacy_drm); wl->legacy_drm = NULL; + + free(wl->drm_render_name); + wl->drm_render_name = NULL; } - } - wl_display_roundtrip(wl->remote_display); // get linux-dmabuf formats + wl_display_roundtrip(wl->remote_display); // get linux-dmabuf feedback events + + if (feedback_data.format_table != NULL) { + munmap(feedback_data.format_table, feedback_data.format_table_size); + } - if (feedback_data.format_table != NULL) { - munmap(feedback_data.format_table, feedback_data.format_table_size); - } - if (linux_dmabuf_feedback_v1 != NULL) { zwp_linux_dmabuf_feedback_v1_destroy(linux_dmabuf_feedback_v1); } + if (wl->zwp_linux_dmabuf_v1) { + wl->backend.buffer_caps |= WLR_BUFFER_CAP_DMABUF; + } + if (wl->shm) { + wl->backend.buffer_caps |= WLR_BUFFER_CAP_SHM; + } + int fd = wl_display_get_fd(wl->remote_display); wl->remote_display_src = wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, dispatch_events, wl); @@ -693,6 +726,8 @@ error_registry: xdg_wm_base_destroy(wl->xdg_wm_base); } wl_registry_destroy(wl->registry); +error_queue: + wl_event_queue_destroy(wl->busy_loop_queue); error_display: if (wl->own_remote_display) { wl_display_disconnect(wl->remote_display); diff --git a/backend/wayland/meson.build b/backend/wayland/meson.build index fcf9fdd8f..b606a9bb9 100644 --- a/backend/wayland/meson.build +++ b/backend/wayland/meson.build @@ -1,6 +1,5 @@ wayland_client = dependency('wayland-client', - fallback: 'wayland', - default_options: wayland_project_options, + kwargs: wayland_kwargs, ) wlr_deps += wayland_client @@ -15,6 +14,7 @@ wlr_files += files( client_protos = [ 'drm', 'linux-dmabuf-v1', + 'linux-drm-syncobj-v1', 'pointer-gestures-unstable-v1', 'presentation-time', 'relative-pointer-unstable-v1', diff --git a/backend/wayland/output.c b/backend/wayland/output.c index 39803eae9..fb4d1f914 100644 --- a/backend/wayland/output.c +++ b/backend/wayland/output.c @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -20,6 +21,7 @@ #include "types/wlr_output.h" #include "linux-dmabuf-v1-client-protocol.h" +#include "linux-drm-syncobj-v1-client-protocol.h" #include "presentation-time-client-protocol.h" #include "viewporter-client-protocol.h" #include "xdg-activation-v1-client-protocol.h" @@ -31,7 +33,9 @@ static const uint32_t SUPPORTED_OUTPUT_STATE = WLR_OUTPUT_STATE_BUFFER | WLR_OUTPUT_STATE_ENABLED | WLR_OUTPUT_STATE_MODE | - WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED; + WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED | + WLR_OUTPUT_STATE_WAIT_TIMELINE | + WLR_OUTPUT_STATE_SIGNAL_TIMELINE; static size_t last_output_num = 0; @@ -94,14 +98,13 @@ static void presentation_feedback_handle_presented(void *data, uint32_t seq_hi, uint32_t seq_lo, uint32_t flags) { struct wlr_wl_presentation_feedback *feedback = data; - struct timespec t = { - .tv_sec = ((uint64_t)tv_sec_hi << 32) | tv_sec_lo, - .tv_nsec = tv_nsec, - }; struct wlr_output_event_present event = { .commit_seq = feedback->commit_seq, .presented = true, - .when = &t, + .when = { + .tv_sec = ((uint64_t)tv_sec_hi << 32) | tv_sec_lo, + .tv_nsec = tv_nsec, + }, .seq = ((uint64_t)seq_hi << 32) | seq_lo, .refresh = refresh_ns, .flags = flags, @@ -131,6 +134,11 @@ static const struct wp_presentation_feedback_listener .discarded = presentation_feedback_handle_discarded, }; +static void buffer_remove_drm_syncobj_waiter(struct wlr_wl_buffer *buffer) { + wlr_drm_syncobj_timeline_waiter_finish(&buffer->drm_syncobj_waiter); + buffer->has_drm_syncobj_waiter = false; +} + void destroy_wl_buffer(struct wlr_wl_buffer *buffer) { if (buffer == NULL) { return; @@ -138,22 +146,42 @@ void destroy_wl_buffer(struct wlr_wl_buffer *buffer) { wl_list_remove(&buffer->buffer_destroy.link); wl_list_remove(&buffer->link); wl_buffer_destroy(buffer->wl_buffer); + if (buffer->has_drm_syncobj_waiter) { + buffer_remove_drm_syncobj_waiter(buffer); + } if (!buffer->released) { wlr_buffer_unlock(buffer->buffer); } + wlr_drm_syncobj_timeline_unref(buffer->fallback_signal_timeline); free(buffer); } +static void buffer_release(struct wlr_wl_buffer *buffer) { + if (buffer->released) { + return; + } + buffer->released = true; + wlr_buffer_unlock(buffer->buffer); // might free buffer +} + static void buffer_handle_release(void *data, struct wl_buffer *wl_buffer) { struct wlr_wl_buffer *buffer = data; - buffer->released = true; - wlr_buffer_unlock(buffer->buffer); // might free buffer + if (buffer->has_drm_syncobj_waiter) { + return; + } + buffer_release(buffer); } static const struct wl_buffer_listener buffer_listener = { .release = buffer_handle_release, }; +static void buffer_handle_drm_syncobj_ready(struct wlr_drm_syncobj_timeline_waiter *waiter) { + struct wlr_wl_buffer *buffer = wl_container_of(waiter, buffer, drm_syncobj_waiter); + buffer_remove_drm_syncobj_waiter(buffer); + buffer_release(buffer); +} + static void buffer_handle_buffer_destroy(struct wl_listener *listener, void *data) { struct wlr_wl_buffer *buffer = @@ -176,6 +204,30 @@ static bool test_buffer(struct wlr_wl_backend *wl, } } +struct dmabuf_listener_data { + struct wl_buffer *wl_buffer; + bool done; +}; + +static void dmabuf_handle_created(void *data_, struct zwp_linux_buffer_params_v1 *params, + struct wl_buffer *buffer) { + struct dmabuf_listener_data *data = data_; + data->wl_buffer = buffer; + data->done = true; + wlr_log(WLR_DEBUG, "DMA-BUF imported into parent Wayland compositor"); +} + +static void dmabuf_handle_failed(void *data_, struct zwp_linux_buffer_params_v1 *params) { + struct dmabuf_listener_data *data = data_; + data->done = true; + wlr_log(WLR_ERROR, "Failed to import DMA-BUF into parent Wayland compositor"); +} + +static const struct zwp_linux_buffer_params_v1_listener dmabuf_listener = { + .created = dmabuf_handle_created, + .failed = dmabuf_handle_failed, +}; + static struct wl_buffer *import_dmabuf(struct wlr_wl_backend *wl, struct wlr_dmabuf_attributes *dmabuf) { uint32_t modifier_hi = dmabuf->modifier >> 32; @@ -187,18 +239,35 @@ static struct wl_buffer *import_dmabuf(struct wlr_wl_backend *wl, dmabuf->offset[i], dmabuf->stride[i], modifier_hi, modifier_lo); } - struct wl_buffer *wl_buffer = zwp_linux_buffer_params_v1_create_immed( - params, dmabuf->width, dmabuf->height, dmabuf->format, 0); + struct dmabuf_listener_data data = {0}; + zwp_linux_buffer_params_v1_add_listener(params, &dmabuf_listener, &data); + zwp_linux_buffer_params_v1_create(params, dmabuf->width, dmabuf->height, dmabuf->format, 0); + + struct wl_event_queue *display_queue = + wl_proxy_get_queue((struct wl_proxy *)wl->remote_display); + wl_proxy_set_queue((struct wl_proxy *)params, wl->busy_loop_queue); + + while (!data.done) { + if (wl_display_dispatch_queue(wl->remote_display, wl->busy_loop_queue) < 0) { + wlr_log(WLR_ERROR, "wl_display_dispatch_queue() failed"); + break; + } + } + + struct wl_buffer *buffer = data.wl_buffer; + if (buffer != NULL) { + wl_proxy_set_queue((struct wl_proxy *)buffer, display_queue); + } + zwp_linux_buffer_params_v1_destroy(params); - // TODO: handle create() errors - return wl_buffer; + return buffer; } static struct wl_buffer *import_shm(struct wlr_wl_backend *wl, struct wlr_shm_attributes *shm) { enum wl_shm_format wl_shm_format = convert_drm_format_to_wl_shm(shm->format); uint32_t size = shm->stride * shm->height; - struct wl_shm_pool *pool = wl_shm_create_pool(wl->shm, shm->fd, size); + struct wl_shm_pool *pool = wl_shm_create_pool(wl->shm, shm->fd, shm->offset + size); if (pool == NULL) { return NULL; } @@ -262,6 +331,58 @@ static struct wlr_wl_buffer *get_or_create_wl_buffer(struct wlr_wl_backend *wl, return create_wl_buffer(wl, wlr_buffer); } +void destroy_wl_drm_syncobj_timeline(struct wlr_wl_drm_syncobj_timeline *timeline) { + wp_linux_drm_syncobj_timeline_v1_destroy(timeline->wl); + wlr_addon_finish(&timeline->addon); + wl_list_remove(&timeline->link); + free(timeline); +} + +static void drm_syncobj_timeline_addon_destroy(struct wlr_addon *addon) { + struct wlr_wl_drm_syncobj_timeline *timeline = wl_container_of(addon, timeline, addon); + destroy_wl_drm_syncobj_timeline(timeline); +} + +static const struct wlr_addon_interface drm_syncobj_timeline_addon_impl = { + .name = "wlr_wl_drm_syncobj_timeline", + .destroy = drm_syncobj_timeline_addon_destroy, +}; + +static struct wlr_wl_drm_syncobj_timeline *get_or_create_drm_syncobj_timeline( + struct wlr_wl_backend *wl, struct wlr_drm_syncobj_timeline *wlr_timeline) { + struct wlr_addon *addon = + wlr_addon_find(&wlr_timeline->addons, wl, &drm_syncobj_timeline_addon_impl); + if (addon != NULL) { + struct wlr_wl_drm_syncobj_timeline *timeline = wl_container_of(addon, timeline, addon); + return timeline; + } + + struct wlr_wl_drm_syncobj_timeline *timeline = calloc(1, sizeof(*timeline)); + if (timeline == NULL) { + return NULL; + } + + timeline->base = wlr_timeline; + + int fd = wlr_drm_syncobj_timeline_export(wlr_timeline); + if (fd < 0) { + free(timeline); + return NULL; + } + + timeline->wl = wp_linux_drm_syncobj_manager_v1_import_timeline(wl->drm_syncobj_manager_v1, fd); + close(fd); + if (timeline->wl == NULL) { + free(timeline); + return NULL; + } + + wlr_addon_init(&timeline->addon, &wlr_timeline->addons, wl, &drm_syncobj_timeline_addon_impl); + wl_list_insert(&wl->drm_syncobj_timelines, &timeline->link); + + return timeline; +} + static bool update_title(struct wlr_wl_output *output, const char *title) { struct wlr_output *wlr_output = &output->wlr_output; @@ -301,6 +422,28 @@ static bool output_test(struct wlr_output *wlr_output, struct wlr_wl_output *output = get_wl_output_from_output(wlr_output); + if (state->committed & WLR_OUTPUT_STATE_BUFFER) { + // If the size doesn't match, reject buffer (scaling is not currently + // supported but could be implemented with viewporter) + int pending_width, pending_height; + output_pending_resolution(wlr_output, state, + &pending_width, &pending_height); + if (state->buffer->width != pending_width || + state->buffer->height != pending_height) { + wlr_log(WLR_DEBUG, "Primary buffer size mismatch"); + return false; + } + // Source crop is also not currently supported + struct wlr_fbox src_box; + output_state_get_buffer_src_box(state, &src_box); + if (src_box.x != 0.0 || src_box.y != 0.0 || + src_box.width != (double)state->buffer->width || + src_box.height != (double)state->buffer->height) { + wlr_log(WLR_DEBUG, "Source crop not supported in wayland output"); + return false; + } + } + uint32_t unsupported = state->committed & ~SUPPORTED_OUTPUT_STATE; if (unsupported != 0) { wlr_log(WLR_DEBUG, "Unsupported output state fields: 0x%"PRIx32, @@ -334,6 +477,21 @@ static bool output_test(struct wlr_output *wlr_output, return false; } + if ((state->committed & WLR_OUTPUT_STATE_SIGNAL_TIMELINE) && + !(state->committed & WLR_OUTPUT_STATE_WAIT_TIMELINE)) { + wlr_log(WLR_DEBUG, "Signal timeline requires a wait timeline"); + return false; + } + + if ((state->committed & WLR_OUTPUT_STATE_WAIT_TIMELINE) || + (state->committed & WLR_OUTPUT_STATE_SIGNAL_TIMELINE)) { + struct wlr_dmabuf_attributes dmabuf; + if (!wlr_buffer_get_dmabuf(state->buffer, &dmabuf)) { + wlr_log(WLR_DEBUG, "Wait/signal timelines require DMA-BUFs"); + return false; + } + } + if (state->committed & WLR_OUTPUT_STATE_LAYERS) { // If we can't use a sub-surface for a layer, then we can't use a // sub-surface for any layer underneath @@ -565,15 +723,15 @@ static const struct wl_callback_listener unmap_callback_listener = { .done = unmap_callback_handle_done, }; -static bool output_commit(struct wlr_output *wlr_output, - const struct wlr_output_state *state) { - struct wlr_wl_output *output = - get_wl_output_from_output(wlr_output); +static bool output_commit(struct wlr_output *wlr_output, const struct wlr_output_state *state) { + struct wlr_wl_output *output = get_wl_output_from_output(wlr_output); if (!output_test(wlr_output, state)) { return false; } + struct wlr_wl_backend *wl = output->backend; + bool pending_enabled = output_pending_enabled(wlr_output, state); if (wlr_output->enabled && !pending_enabled) { @@ -598,15 +756,28 @@ static bool output_commit(struct wlr_output *wlr_output, wl_surface_commit(output->surface); output->initialized = true; - wl_display_flush(output->backend->remote_display); + struct wl_event_queue *display_queue = + wl_proxy_get_queue((struct wl_proxy *)wl->remote_display); + wl_proxy_set_queue((struct wl_proxy *)output->xdg_surface, wl->busy_loop_queue); + wl_proxy_set_queue((struct wl_proxy *)output->xdg_toplevel, wl->busy_loop_queue); + + wl_display_flush(wl->remote_display); while (!output->configured) { - if (wl_display_dispatch(output->backend->remote_display) == -1) { - wlr_log(WLR_ERROR, "wl_display_dispatch() failed"); - return false; + if (wl_display_dispatch_queue(wl->remote_display, wl->busy_loop_queue) == -1) { + wlr_log(WLR_ERROR, "wl_display_dispatch_queue() failed"); + break; } } + + wl_proxy_set_queue((struct wl_proxy *)output->xdg_surface, display_queue); + wl_proxy_set_queue((struct wl_proxy *)output->xdg_toplevel, display_queue); + + if (!output->configured) { + return false; + } } + struct wlr_wl_buffer *buffer = NULL; if (state->committed & WLR_OUTPUT_STATE_BUFFER) { const pixman_region32_t *damage = NULL; if (state->committed & WLR_OUTPUT_STATE_DAMAGE) { @@ -614,8 +785,7 @@ static bool output_commit(struct wlr_output *wlr_output, } struct wlr_buffer *wlr_buffer = state->buffer; - struct wlr_wl_buffer *buffer = - get_or_create_wl_buffer(output->backend, wlr_buffer); + buffer = get_or_create_wl_buffer(wl, wlr_buffer); if (buffer == NULL) { return false; } @@ -624,6 +794,63 @@ static bool output_commit(struct wlr_output *wlr_output, damage_surface(output->surface, damage); } + if (state->committed & WLR_OUTPUT_STATE_WAIT_TIMELINE) { + struct wlr_wl_drm_syncobj_timeline *wait_timeline = + get_or_create_drm_syncobj_timeline(wl, state->wait_timeline); + + struct wlr_wl_drm_syncobj_timeline *signal_timeline; + uint64_t signal_point; + if (state->committed & WLR_OUTPUT_STATE_SIGNAL_TIMELINE) { + signal_timeline = get_or_create_drm_syncobj_timeline(wl, state->signal_timeline); + signal_point = state->signal_point; + } else { + if (buffer->fallback_signal_timeline == NULL) { + buffer->fallback_signal_timeline = + wlr_drm_syncobj_timeline_create(wl->drm_fd); + if (buffer->fallback_signal_timeline == NULL) { + return false; + } + } + signal_timeline = + get_or_create_drm_syncobj_timeline(wl, buffer->fallback_signal_timeline); + signal_point = ++buffer->fallback_signal_point; + } + + if (wait_timeline == NULL || signal_timeline == NULL) { + return false; + } + + if (output->drm_syncobj_surface_v1 == NULL) { + output->drm_syncobj_surface_v1 = wp_linux_drm_syncobj_manager_v1_get_surface( + wl->drm_syncobj_manager_v1, output->surface); + if (output->drm_syncobj_surface_v1 == NULL) { + return false; + } + } + + uint32_t wait_point_hi = state->wait_point >> 32; + uint32_t wait_point_lo = state->wait_point & UINT32_MAX; + uint32_t signal_point_hi = signal_point >> 32; + uint32_t signal_point_lo = signal_point & UINT32_MAX; + + wp_linux_drm_syncobj_surface_v1_set_acquire_point(output->drm_syncobj_surface_v1, + wait_timeline->wl, wait_point_hi, wait_point_lo); + wp_linux_drm_syncobj_surface_v1_set_release_point(output->drm_syncobj_surface_v1, + signal_timeline->wl, signal_point_hi, signal_point_lo); + + if (!wlr_drm_syncobj_timeline_waiter_init(&buffer->drm_syncobj_waiter, + signal_timeline->base, signal_point, 0, wl->event_loop, + buffer_handle_drm_syncobj_ready)) { + return false; + } + buffer->has_drm_syncobj_waiter = true; + } else { + if (output->drm_syncobj_surface_v1 != NULL) { + wp_linux_drm_syncobj_surface_v1_destroy(output->drm_syncobj_surface_v1); + output->drm_syncobj_surface_v1 = NULL; + } + } + if ((state->committed & WLR_OUTPUT_STATE_LAYERS) && !commit_layers(output, state->layers, state->layers_len)) { return false; @@ -637,9 +864,8 @@ static bool output_commit(struct wlr_output *wlr_output, wl_callback_add_listener(output->frame_callback, &frame_listener, output); struct wp_presentation_feedback *wp_feedback = NULL; - if (output->backend->presentation != NULL) { - wp_feedback = wp_presentation_feedback(output->backend->presentation, - output->surface); + if (wl->presentation != NULL) { + wp_feedback = wp_presentation_feedback(wl->presentation, output->surface); } if (output->has_configure_serial) { @@ -672,7 +898,7 @@ static bool output_commit(struct wlr_output *wlr_output, } } - wl_display_flush(output->backend->remote_display); + wl_display_flush(wl->remote_display); return true; } @@ -728,6 +954,8 @@ static void output_destroy(struct wlr_output *wlr_output) { return; } + wlr_output_finish(wlr_output); + wl_list_remove(&output->link); if (output->cursor.surface) { @@ -748,6 +976,9 @@ static void output_destroy(struct wlr_output *wlr_output) { wl_callback_destroy(output->unmap_callback); } + if (output->drm_syncobj_surface_v1) { + wp_linux_drm_syncobj_surface_v1_destroy(output->drm_syncobj_surface_v1); + } if (output->zxdg_toplevel_decoration_v1) { zxdg_toplevel_decoration_v1_destroy(output->zxdg_toplevel_decoration_v1); } @@ -824,6 +1055,12 @@ static void xdg_surface_handle_configure(void *data, output->has_configure_serial = true; output->configure_serial = serial; + if (!output->wlr_output.enabled) { + // We're waiting for a configure event after an initial commit to enable + // the output. Do not notify the compositor about the requested state. + return; + } + struct wlr_output_state state; wlr_output_state_init(&state); wlr_output_state_set_custom_mode(&state, req_width, req_height, 0); diff --git a/backend/wayland/pointer.c b/backend/wayland/pointer.c index 87383fa8d..b61e32cd0 100644 --- a/backend/wayland/pointer.c +++ b/backend/wayland/pointer.c @@ -112,7 +112,7 @@ static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, .state = state, .time_msec = time, }; - wl_signal_emit_mutable(&pointer->wlr_pointer.events.button, &event); + wlr_pointer_notify_button(&pointer->wlr_pointer, &event); } static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, diff --git a/backend/wayland/seat.c b/backend/wayland/seat.c index b812b53a8..a66357bf8 100644 --- a/backend/wayland/seat.c +++ b/backend/wayland/seat.c @@ -27,12 +27,14 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { struct wlr_keyboard *keyboard = data; + int64_t time_msec = get_current_time_msec(); + uint32_t *keycode_ptr; wl_array_for_each(keycode_ptr, keys) { struct wlr_keyboard_key_event event = { .keycode = *keycode_ptr, .state = WL_KEYBOARD_KEY_STATE_PRESSED, - .time_msec = get_current_time_msec(), + .time_msec = time_msec, .update_state = false, }; wlr_keyboard_notify_key(keyboard, &event); @@ -43,18 +45,12 @@ static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface) { struct wlr_keyboard *keyboard = data; - size_t num_keycodes = keyboard->num_keycodes; - uint32_t pressed[num_keycodes + 1]; - memcpy(pressed, keyboard->keycodes, - num_keycodes * sizeof(uint32_t)); - - for (size_t i = 0; i < num_keycodes; ++i) { - uint32_t keycode = pressed[i]; - + int64_t time_msec = get_current_time_msec(); + while (keyboard->num_keycodes > 0) { struct wlr_keyboard_key_event event = { - .keycode = keycode, + .keycode = keyboard->keycodes[keyboard->num_keycodes - 1], .state = WL_KEYBOARD_KEY_STATE_RELEASED, - .time_msec = get_current_time_msec(), + .time_msec = time_msec, .update_state = false, }; wlr_keyboard_notify_key(keyboard, &event); diff --git a/backend/x11/backend.c b/backend/x11/backend.c index 9c8ea326e..0983bad0a 100644 --- a/backend/x11/backend.c +++ b/backend/x11/backend.c @@ -212,17 +212,10 @@ static int backend_get_drm_fd(struct wlr_backend *backend) { return x11->drm_fd; } -static uint32_t get_buffer_caps(struct wlr_backend *backend) { - struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend); - return (x11->have_dri3 ? WLR_BUFFER_CAP_DMABUF : 0) - | (x11->have_shm ? WLR_BUFFER_CAP_SHM : 0); -} - static const struct wlr_backend_impl backend_impl = { .start = backend_start, .destroy = backend_destroy, .get_drm_fd = backend_get_drm_fd, - .get_buffer_caps = get_buffer_caps, }; bool wlr_backend_is_x11(struct wlr_backend *backend) { @@ -409,7 +402,7 @@ struct wlr_backend *wlr_x11_backend_create(struct wl_event_loop *loop, wl_list_init(&x11->outputs); x11->xcb = xcb_connect(x11_display, NULL); - if (!x11->xcb || xcb_connection_has_error(x11->xcb)) { + if (xcb_connection_has_error(x11->xcb)) { wlr_log(WLR_ERROR, "Failed to open xcb connection"); goto error_x11; } @@ -512,7 +505,10 @@ struct wlr_backend *wlr_x11_backend_create(struct wl_event_loop *loop, xcb_present_query_version(x11->xcb, 1, 2); xcb_present_query_version_reply_t *present_reply = xcb_present_query_version_reply(x11->xcb, present_cookie, NULL); - if (!present_reply || present_reply->major_version < 1) { + if (!present_reply) { + wlr_log(WLR_ERROR, "Failed to query Present version"); + goto error_display; + } else if (present_reply->major_version < 1) { wlr_log(WLR_ERROR, "X11 does not support required Present version " "(has %"PRIu32".%"PRIu32", want 1.0)", present_reply->major_version, present_reply->minor_version); @@ -533,7 +529,10 @@ struct wlr_backend *wlr_x11_backend_create(struct wl_event_loop *loop, xcb_xfixes_query_version(x11->xcb, 4, 0); xcb_xfixes_query_version_reply_t *fixes_reply = xcb_xfixes_query_version_reply(x11->xcb, fixes_cookie, NULL); - if (!fixes_reply || fixes_reply->major_version < 4) { + if (!fixes_reply) { + wlr_log(WLR_ERROR, "Failed to query Xfixes version"); + goto error_display; + } else if (fixes_reply->major_version < 4) { wlr_log(WLR_ERROR, "X11 does not support required Xfixes version " "(has %"PRIu32".%"PRIu32", want 4.0)", fixes_reply->major_version, fixes_reply->minor_version); @@ -555,7 +554,10 @@ struct wlr_backend *wlr_x11_backend_create(struct wl_event_loop *loop, xcb_input_xi_query_version(x11->xcb, 2, 0); xcb_input_xi_query_version_reply_t *xi_reply = xcb_input_xi_query_version_reply(x11->xcb, xi_cookie, NULL); - if (!xi_reply || xi_reply->major_version < 2) { + if (!xi_reply) { + wlr_log(WLR_ERROR, "Failed to query Xinput version"); + goto error_display; + } else if (xi_reply->major_version < 2) { wlr_log(WLR_ERROR, "X11 does not support required Xinput version " "(has %"PRIu32".%"PRIu32", want 2.0)", xi_reply->major_version, xi_reply->minor_version); @@ -564,6 +566,13 @@ struct wlr_backend *wlr_x11_backend_create(struct wl_event_loop *loop, } free(xi_reply); + if (x11->have_dri3) { + x11->backend.buffer_caps |= WLR_BUFFER_CAP_DMABUF; + } + if (x11->have_shm) { + x11->backend.buffer_caps |= WLR_BUFFER_CAP_SHM; + } + int fd = xcb_get_file_descriptor(x11->xcb); uint32_t events = WL_EVENT_READABLE | WL_EVENT_ERROR | WL_EVENT_HANGUP; x11->event_source = wl_event_loop_add_fd(loop, fd, events, x11_event, x11); diff --git a/backend/x11/input_device.c b/backend/x11/input_device.c index c2ed6fd33..8246653b7 100644 --- a/backend/x11/input_device.c +++ b/backend/x11/input_device.c @@ -36,7 +36,7 @@ static void send_button_event(struct wlr_x11_output *output, uint32_t key, .button = key, .state = st, }; - wl_signal_emit_mutable(&output->pointer.events.button, &ev); + wlr_pointer_notify_button(&output->pointer, &ev); wl_signal_emit_mutable(&output->pointer.events.frame, &output->pointer); } diff --git a/backend/x11/output.c b/backend/x11/output.c index 51a8459d1..0b63a7088 100644 --- a/backend/x11/output.c +++ b/backend/x11/output.c @@ -94,6 +94,8 @@ static void output_destroy(struct wlr_output *wlr_output) { struct wlr_x11_output *output = get_x11_output_from_output(wlr_output); struct wlr_x11_backend *x11 = output->x11; + wlr_output_finish(wlr_output); + pixman_region32_fini(&output->exposed); wlr_pointer_finish(&output->pointer); @@ -129,6 +131,27 @@ static bool output_test(struct wlr_output *wlr_output, return false; } + if (state->committed & WLR_OUTPUT_STATE_BUFFER) { + // If the size doesn't match, reject buffer (scaling is not supported) + int pending_width, pending_height; + output_pending_resolution(wlr_output, state, + &pending_width, &pending_height); + if (state->buffer->width != pending_width || + state->buffer->height != pending_height) { + wlr_log(WLR_DEBUG, "Primary buffer size mismatch"); + return false; + } + // Source crop is not supported + struct wlr_fbox src_box; + output_state_get_buffer_src_box(state, &src_box); + if (src_box.x != 0.0 || src_box.y != 0.0 || + src_box.width != (double)state->buffer->width || + src_box.height != (double)state->buffer->height) { + wlr_log(WLR_DEBUG, "Source crop not supported in X11 output"); + return false; + } + } + // All we can do to influence adaptive sync on the X11 backend is set the // _VARIABLE_REFRESH window property like mesa automatically does. We don't // have any control beyond that, so we set the state to enabled on creating @@ -747,9 +770,6 @@ void handle_x11_present_event(struct wlr_x11_backend *x11, output->last_msc = complete_notify->msc; - struct timespec t; - timespec_from_nsec(&t, complete_notify->ust * 1000); - uint32_t flags = 0; if (complete_notify->mode == XCB_PRESENT_COMPLETE_MODE_FLIP) { flags |= WLR_OUTPUT_PRESENT_ZERO_COPY; @@ -760,10 +780,10 @@ void handle_x11_present_event(struct wlr_x11_backend *x11, .output = &output->wlr_output, .commit_seq = complete_notify->serial, .presented = presented, - .when = &t, .seq = complete_notify->msc, .flags = flags, }; + timespec_from_nsec(&present_event.when, complete_notify->ust * 1000); wlr_output_send_present(&output->wlr_output, &present_event); wlr_output_send_frame(&output->wlr_output); diff --git a/docs/env_vars.md b/docs/env_vars.md index a2a86c84d..59c8f078f 100644 --- a/docs/env_vars.md +++ b/docs/env_vars.md @@ -12,6 +12,10 @@ wlroots reads these environment variables renderers: gles2, pixman, vulkan) * *WLR_RENDER_DRM_DEVICE*: specifies the DRM node to use for hardware-accelerated renderers. +* *WLR_RENDER_NO_EXPLICIT_SYNC*: set to 1 to disable explicit synchronization + support in renderers. +* *WLR_RENDERER_FORCE_SOFTWARE*: set to 1 to force software rendering for GLES2 + and Vulkan * *WLR_EGL_NO_MODIFIERS*: set to 1 to disable format modifiers in EGL, this can be used to understand and work around driver bugs. diff --git a/examples/cairo-buffer.c b/examples/cairo-buffer.c index 7de4a053b..6392b2ac5 100644 --- a/examples/cairo-buffer.c +++ b/examples/cairo-buffer.c @@ -24,6 +24,7 @@ struct cairo_buffer { static void cairo_buffer_destroy(struct wlr_buffer *wlr_buffer) { struct cairo_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + wlr_buffer_finish(wlr_buffer); cairo_surface_destroy(buffer->surface); free(buffer); } diff --git a/examples/fullscreen-shell.c b/examples/fullscreen-shell.c deleted file mode 100644 index c12f126e9..000000000 --- a/examples/fullscreen-shell.c +++ /dev/null @@ -1,258 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/** - * A minimal fullscreen-shell server. It only supports rendering. - */ - -struct fullscreen_server { - struct wl_display *wl_display; - struct wlr_backend *backend; - struct wlr_renderer *renderer; - struct wlr_allocator *allocator; - - struct wlr_fullscreen_shell_v1 *fullscreen_shell; - struct wl_listener present_surface; - - struct wlr_output_layout *output_layout; - struct wl_list outputs; - struct wl_listener new_output; -}; - -struct fullscreen_output { - struct wl_list link; - struct fullscreen_server *server; - struct wlr_output *wlr_output; - struct wlr_surface *surface; - struct wl_listener surface_destroy; - - struct wl_listener frame; -}; - -struct render_data { - struct wlr_output *output; - struct wlr_render_pass *render_pass; - struct timespec *when; -}; - -static void render_surface(struct wlr_surface *surface, - int sx, int sy, void *data) { - struct render_data *rdata = data; - struct wlr_output *output = rdata->output; - - struct wlr_texture *texture = wlr_surface_get_texture(surface); - if (texture == NULL) { - return; - } - - struct wlr_box box = { - .x = sx * output->scale, - .y = sy * output->scale, - .width = surface->current.width * output->scale, - .height = surface->current.height * output->scale, - }; - - enum wl_output_transform transform = wlr_output_transform_invert(surface->current.transform); - transform = wlr_output_transform_compose(transform, output->transform); - - wlr_render_pass_add_texture(rdata->render_pass, &(struct wlr_render_texture_options){ - .texture = texture, - .dst_box = box, - .transform = transform, - }); - - wlr_surface_send_frame_done(surface, rdata->when); -} - -static void output_handle_frame(struct wl_listener *listener, void *data) { - struct fullscreen_output *output = - wl_container_of(listener, output, frame); - - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - int width, height; - wlr_output_effective_resolution(output->wlr_output, &width, &height); - - struct wlr_output_state state; - wlr_output_state_init(&state); - struct wlr_render_pass *pass = wlr_output_begin_render_pass(output->wlr_output, &state, NULL, - NULL); - if (pass == NULL) { - return; - } - - wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ - .color = { 0.3, 0.3, 0.3, 1.0 }, - .box = { .width = width, .height = height }, - }); - - if (output->surface != NULL) { - struct render_data rdata = { - .output = output->wlr_output, - .render_pass = pass, - .when = &now, - }; - wlr_surface_for_each_surface(output->surface, render_surface, &rdata); - } - - wlr_render_pass_submit(pass); - wlr_output_commit_state(output->wlr_output, &state); - wlr_output_state_finish(&state); -} - -static void output_set_surface(struct fullscreen_output *output, - struct wlr_surface *surface); - -static void output_handle_surface_destroy(struct wl_listener *listener, - void *data) { - struct fullscreen_output *output = - wl_container_of(listener, output, surface_destroy); - output_set_surface(output, NULL); -} - -static void output_set_surface(struct fullscreen_output *output, - struct wlr_surface *surface) { - if (output->surface == surface) { - return; - } - - if (output->surface != NULL) { - wl_list_remove(&output->surface_destroy.link); - output->surface = NULL; - } - - if (surface != NULL) { - output->surface_destroy.notify = output_handle_surface_destroy; - wl_signal_add(&surface->events.destroy, &output->surface_destroy); - output->surface = surface; - } - - wlr_log(WLR_DEBUG, "Presenting surface %p on output %s", - surface, output->wlr_output->name); -} - -static void server_handle_new_output(struct wl_listener *listener, void *data) { - struct fullscreen_server *server = - wl_container_of(listener, server, new_output); - struct wlr_output *wlr_output = data; - - wlr_output_init_render(wlr_output, server->allocator, server->renderer); - - struct fullscreen_output *output = calloc(1, sizeof(*output)); - output->wlr_output = wlr_output; - output->server = server; - output->frame.notify = output_handle_frame; - wl_signal_add(&wlr_output->events.frame, &output->frame); - wl_list_insert(&server->outputs, &output->link); - - wlr_output_layout_add_auto(server->output_layout, wlr_output); - wlr_output_create_global(wlr_output, server->wl_display); - - struct wlr_output_state state; - wlr_output_state_init(&state); - wlr_output_state_set_enabled(&state, true); - struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); - if (mode != NULL) { - wlr_output_state_set_mode(&state, mode); - } - wlr_output_commit_state(wlr_output, &state); - wlr_output_state_finish(&state); -} - -static void server_handle_present_surface(struct wl_listener *listener, - void *data) { - struct fullscreen_server *server = - wl_container_of(listener, server, present_surface); - struct wlr_fullscreen_shell_v1_present_surface_event *event = data; - - struct fullscreen_output *output; - wl_list_for_each(output, &server->outputs, link) { - if (event->output == NULL || event->output == output->wlr_output) { - output_set_surface(output, event->surface); - } - } -} - -int main(int argc, char *argv[]) { - wlr_log_init(WLR_DEBUG, NULL); - - char *startup_cmd = NULL; - - int c; - while ((c = getopt(argc, argv, "s:")) != -1) { - switch (c) { - case 's': - startup_cmd = optarg; - break; - default: - printf("usage: %s [-s startup-command]\n", argv[0]); - return EXIT_FAILURE; - } - } - if (optind < argc) { - printf("usage: %s [-s startup-command]\n", argv[0]); - return EXIT_FAILURE; - } - - struct fullscreen_server server = {0}; - server.wl_display = wl_display_create(); - server.backend = wlr_backend_autocreate(wl_display_get_event_loop(server.wl_display), NULL); - server.renderer = wlr_renderer_autocreate(server.backend); - wlr_renderer_init_wl_display(server.renderer, server.wl_display); - server.allocator = wlr_allocator_autocreate(server.backend, - server.renderer); - - wlr_compositor_create(server.wl_display, 5, server.renderer); - - server.output_layout = wlr_output_layout_create(server.wl_display); - - wl_list_init(&server.outputs); - server.new_output.notify = server_handle_new_output; - wl_signal_add(&server.backend->events.new_output, &server.new_output); - - server.fullscreen_shell = wlr_fullscreen_shell_v1_create(server.wl_display); - server.present_surface.notify = server_handle_present_surface; - wl_signal_add(&server.fullscreen_shell->events.present_surface, - &server.present_surface); - - const char *socket = wl_display_add_socket_auto(server.wl_display); - if (!socket) { - wl_display_destroy(server.wl_display); - return EXIT_FAILURE; - } - - if (!wlr_backend_start(server.backend)) { - wl_display_destroy(server.wl_display); - return EXIT_FAILURE; - } - - setenv("WAYLAND_DISPLAY", socket, true); - if (startup_cmd != NULL) { - if (fork() == 0) { - execl("/bin/sh", "/bin/sh", "-c", startup_cmd, (void *)NULL); - } - } - - wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", - socket); - wl_display_run(server.wl_display); - - wl_display_destroy_clients(server.wl_display); - wl_display_destroy(server.wl_display); - return 0; -} diff --git a/examples/meson.build b/examples/meson.build index 3fe07df8e..28a83cccc 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -25,10 +25,6 @@ compositors = { 'output-layout': { 'src': ['output-layout.c', 'cat.c'], }, - 'fullscreen-shell': { - 'src': 'fullscreen-shell.c', - 'proto': ['fullscreen-shell-unstable-v1'], - }, 'scene-graph': { 'src': 'scene-graph.c', 'proto': ['xdg-shell'], diff --git a/examples/output-layers.c b/examples/output-layers.c index 5089c6aa5..0535ff0b1 100644 --- a/examples/output-layers.c +++ b/examples/output-layers.c @@ -87,16 +87,14 @@ static void output_handle_frame(struct wl_listener *listener, void *data) { layers_arr.size / sizeof(struct wlr_output_layer_state)); if (!wlr_output_test_state(output->wlr_output, &output_state)) { - wlr_log(WLR_ERROR, "wlr_output_test() failed"); + wlr_log(WLR_ERROR, "wlr_output_test_state() failed"); return; } int width, height; wlr_output_effective_resolution(output->wlr_output, &width, &height); - struct wlr_render_pass *pass = wlr_output_begin_render_pass(output->wlr_output, &output_state, - NULL, NULL); - + struct wlr_render_pass *pass = wlr_output_begin_render_pass(output->wlr_output, &output_state, NULL); wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ .box = { .width = width, .height = height }, .color = { 0.3, 0.3, 0.3, 1 }, diff --git a/examples/output-layout.c b/examples/output-layout.c index 3a3eeb24f..c481ddf80 100644 --- a/examples/output-layout.c +++ b/examples/output-layout.c @@ -114,7 +114,7 @@ static void output_frame_notify(struct wl_listener *listener, void *data) { struct wlr_output_state output_state; wlr_output_state_init(&output_state); - struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL, NULL); + struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL); wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ .box = { .width = wlr_output->width, .height = wlr_output->height }, diff --git a/examples/pointer.c b/examples/pointer.c index fed37a5b3..f3ef5435e 100644 --- a/examples/pointer.c +++ b/examples/pointer.c @@ -101,7 +101,7 @@ static void output_frame_notify(struct wl_listener *listener, void *data) { struct wlr_output_state output_state; wlr_output_state_init(&output_state); - struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL, NULL); + struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL); wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ .box = { .width = wlr_output->width, .height = wlr_output->height }, .color = { diff --git a/examples/rotation.c b/examples/rotation.c index 351973f61..18bfbd38d 100644 --- a/examples/rotation.c +++ b/examples/rotation.c @@ -59,7 +59,7 @@ static void output_frame_notify(struct wl_listener *listener, void *data) { struct wlr_output_state output_state; wlr_output_state_init(&output_state); - struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL, NULL); + struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL); wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ .box = { .width = wlr_output->width, .height = wlr_output->height }, diff --git a/examples/simple.c b/examples/simple.c index 81ed2e0e9..d16708654 100644 --- a/examples/simple.c +++ b/examples/simple.c @@ -63,7 +63,7 @@ static void output_frame_notify(struct wl_listener *listener, void *data) { struct wlr_output_state state; wlr_output_state_init(&state); - struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &state, NULL, NULL); + struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &state, NULL); wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ .box = { .width = wlr_output->width, .height = wlr_output->height }, .color = { diff --git a/examples/tablet.c b/examples/tablet.c index 8f0754df1..78666fff7 100644 --- a/examples/tablet.c +++ b/examples/tablet.c @@ -89,7 +89,7 @@ static void output_frame_notify(struct wl_listener *listener, void *data) { struct wlr_output_state output_state; wlr_output_state_init(&output_state); - struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL, NULL); + struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL); wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ .box = { .width = wlr_output->width, .height = wlr_output->height }, diff --git a/examples/touch.c b/examples/touch.c index e1bd73818..b891837a4 100644 --- a/examples/touch.c +++ b/examples/touch.c @@ -76,7 +76,7 @@ static void output_frame_notify(struct wl_listener *listener, void *data) { struct wlr_output_state output_state; wlr_output_state_init(&output_state); - struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL, NULL); + struct wlr_render_pass *pass = wlr_output_begin_render_pass(wlr_output, &output_state, NULL); wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){ .box = { .width = width, .height = height }, .color = { 0.25, 0.25, 0.25, 1 }, diff --git a/include/backend/backend.h b/include/backend/backend.h deleted file mode 100644 index 8c7440c3d..000000000 --- a/include/backend/backend.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef BACKEND_WLR_BACKEND_H -#define BACKEND_WLR_BACKEND_H - -#include - -/** - * Get the supported buffer capabilities. - * - * This functions returns a bitfield of supported wlr_buffer_cap. - */ -uint32_t backend_get_buffer_caps(struct wlr_backend *backend); - -#endif diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h index 1106e9f56..af4231f54 100644 --- a/include/backend/drm/drm.h +++ b/include/backend/drm/drm.h @@ -15,6 +15,11 @@ #include "backend/drm/properties.h" #include "backend/drm/renderer.h" +struct wlr_drm_viewport { + struct wlr_fbox src_box; + struct wlr_box dst_box; +}; + struct wlr_drm_plane { uint32_t type; uint32_t id; @@ -26,6 +31,8 @@ struct wlr_drm_plane { struct wlr_drm_fb *queued_fb; /* Buffer currently displayed on screen */ struct wlr_drm_fb *current_fb; + /* Viewport belonging to the last committed fb */ + struct wlr_drm_viewport viewport; struct wlr_drm_format_set formats; @@ -139,13 +146,20 @@ struct wlr_drm_connector_state { bool active; drmModeModeInfo mode; struct wlr_drm_fb *primary_fb; + struct wlr_drm_viewport primary_viewport; struct wlr_drm_fb *cursor_fb; + struct wlr_drm_syncobj_timeline *wait_timeline; + uint64_t wait_point; + // used by atomic uint32_t mode_id; uint32_t gamma_lut; uint32_t fb_damage_clips; + int primary_in_fence_fd, out_fence_fd; bool vrr_enabled; + uint32_t colorspace; + uint32_t hdr_output_metadata; }; /** @@ -164,6 +178,8 @@ struct wlr_drm_page_flip { struct wl_list link; // wlr_drm_connector.page_flips struct wlr_drm_page_flip_connector *connectors; size_t connectors_len; + // True if DRM_MODE_PAGE_FLIP_ASYNC was set + bool async; }; struct wlr_drm_page_flip_connector { @@ -198,6 +214,10 @@ struct wlr_drm_connector { // Last committed page-flip struct wlr_drm_page_flip *pending_page_flip; + // Atomic modesetting only + uint32_t colorspace; + uint32_t hdr_output_metadata; + int32_t refresh; }; @@ -211,7 +231,6 @@ void scan_drm_connectors(struct wlr_drm_backend *state, void scan_drm_leases(struct wlr_drm_backend *drm); bool commit_drm_device(struct wlr_drm_backend *drm, const struct wlr_backend_output_state *states, size_t states_len, bool test_only); -void restore_drm_device(struct wlr_drm_backend *drm); int handle_drm_event(int fd, uint32_t mask, void *data); void destroy_drm_connector(struct wlr_drm_connector *conn); bool drm_connector_is_cursor_visible(struct wlr_drm_connector *conn); diff --git a/include/backend/drm/fb.h b/include/backend/drm/fb.h index 91d0fffcc..e8d328e97 100644 --- a/include/backend/drm/fb.h +++ b/include/backend/drm/fb.h @@ -2,6 +2,10 @@ #define BACKEND_DRM_FB_H #include +#include +#include + +struct wlr_drm_format_set; struct wlr_drm_fb { struct wlr_buffer *wlr_buf; diff --git a/include/backend/drm/iface.h b/include/backend/drm/iface.h index 1ac1095a8..bc9610589 100644 --- a/include/backend/drm/iface.h +++ b/include/backend/drm/iface.h @@ -22,8 +22,6 @@ struct wlr_drm_interface { bool (*commit)(struct wlr_drm_backend *drm, const struct wlr_drm_device_state *state, struct wlr_drm_page_flip *page_flip, uint32_t flags, bool test_only); - // Turn off everything - bool (*reset)(struct wlr_drm_backend *drm); }; extern const struct wlr_drm_interface atomic_iface; diff --git a/include/backend/drm/properties.h b/include/backend/drm/properties.h index 570737021..c02d655ba 100644 --- a/include/backend/drm/properties.h +++ b/include/backend/drm/properties.h @@ -26,6 +26,8 @@ struct wlr_drm_connector_props { // atomic-modesetting only uint32_t crtc_id; + uint32_t colorspace; + uint32_t hdr_output_metadata; }; struct wlr_drm_crtc_props { @@ -38,6 +40,7 @@ struct wlr_drm_crtc_props { uint32_t active; uint32_t mode_id; + uint32_t out_fence_ptr; }; struct wlr_drm_plane_props { @@ -61,6 +64,7 @@ struct wlr_drm_plane_props { uint32_t fb_damage_clips; uint32_t hotspot_x; uint32_t hotspot_y; + uint32_t in_fence_fd; }; bool get_drm_connector_props(int fd, uint32_t id, diff --git a/include/backend/drm/renderer.h b/include/backend/drm/renderer.h index f53f720bc..2cf98fdb9 100644 --- a/include/backend/drm/renderer.h +++ b/include/backend/drm/renderer.h @@ -20,6 +20,9 @@ struct wlr_drm_renderer { struct wlr_drm_surface { struct wlr_drm_renderer *renderer; struct wlr_swapchain *swapchain; + + struct wlr_drm_syncobj_timeline *timeline; + uint64_t point; }; bool init_drm_renderer(struct wlr_drm_backend *drm, @@ -32,7 +35,8 @@ bool init_drm_surface(struct wlr_drm_surface *surf, void finish_drm_surface(struct wlr_drm_surface *surf); struct wlr_buffer *drm_surface_blit(struct wlr_drm_surface *surf, - struct wlr_buffer *buffer); + struct wlr_buffer *buffer, + struct wlr_drm_syncobj_timeline *wait_timeline, uint64_t wait_point); bool drm_plane_pick_render_format(struct wlr_drm_plane *plane, struct wlr_drm_format *fmt, struct wlr_drm_renderer *renderer); diff --git a/include/backend/drm/util.h b/include/backend/drm/util.h index 254f774da..9ba5f435e 100644 --- a/include/backend/drm/util.h +++ b/include/backend/drm/util.h @@ -18,26 +18,24 @@ const char *drm_connector_status_str(drmModeConnection status); void generate_cvt_mode(drmModeModeInfo *mode, int hdisplay, int vdisplay, float vrefresh); -// Part of match_obj -enum { - UNMATCHED = (uint32_t)-1, - SKIP = (uint32_t)-2, -}; +// Part of match_connectors_with_crtcs +#define UNMATCHED ((uint32_t)-1) -/* - * Tries to match some DRM objects with some other DRM resource. - * e.g. Match CRTCs with Encoders, CRTCs with Planes. +/** + * Tries to match DRM connectors with DRM CRTCs. * - * objs contains a bit array which resources it can be matched with. - * e.g. Bit 0 set means can be matched with res[0] + * conns contains an array of bitmasks describing compatible CRTCs. For + * instance bit 0 set in an connector element means that it's compatible with + * CRTC 0. * - * res contains an index of which objs it is matched with or UNMATCHED. + * prev_crtcs contains connector indices each CRTC was previously matched with, + * or UNMATCHED. * - * This solution is left in out. - * Returns the total number of matched solutions. + * new_crtcs is populated with the new connector indices. */ -size_t match_obj(size_t num_objs, const uint32_t objs[static restrict num_objs], - size_t num_res, const uint32_t res[static restrict num_res], - uint32_t out[static restrict num_res]); +void match_connectors_with_crtcs(size_t num_conns, + const uint32_t conns[static restrict num_conns], + size_t num_crtcs, const uint32_t prev_crtcs[static restrict num_crtcs], + uint32_t new_crtcs[static restrict num_crtcs]); #endif diff --git a/include/backend/libinput.h b/include/backend/libinput.h index cf685f0ef..874e9aa1f 100644 --- a/include/backend/libinput.h +++ b/include/backend/libinput.h @@ -12,8 +12,6 @@ #include #include -#include "config.h" - struct wlr_libinput_backend { struct wlr_backend backend; diff --git a/include/backend/wayland.h b/include/backend/wayland.h index aa266eef8..e24eb9fdf 100644 --- a/include/backend/wayland.h +++ b/include/backend/wayland.h @@ -14,6 +14,7 @@ #include #include #include +#include struct wlr_wl_backend { struct wlr_backend backend; @@ -21,6 +22,7 @@ struct wlr_wl_backend { /* local state */ bool started; struct wl_event_loop *event_loop; + struct wl_event_queue *busy_loop_queue; struct wl_list outputs; int drm_fd; struct wl_list buffers; // wlr_wl_buffer.link @@ -40,6 +42,8 @@ struct wlr_wl_backend { struct wp_presentation *presentation; struct wl_shm *shm; struct zwp_linux_dmabuf_v1 *zwp_linux_dmabuf_v1; + struct wp_linux_drm_syncobj_manager_v1 *drm_syncobj_manager_v1; + struct wl_list drm_syncobj_timelines; // wlr_wl_drm_syncobj_timeline.link struct zwp_relative_pointer_manager_v1 *zwp_relative_pointer_manager_v1; struct wl_list seats; // wlr_wl_seat.link struct zwp_tablet_manager_v2 *tablet_manager; @@ -58,6 +62,19 @@ struct wlr_wl_buffer { bool released; struct wl_list link; // wlr_wl_backend.buffers struct wl_listener buffer_destroy; + + bool has_drm_syncobj_waiter; + struct wlr_drm_syncobj_timeline_waiter drm_syncobj_waiter; + + struct wlr_drm_syncobj_timeline *fallback_signal_timeline; + uint64_t fallback_signal_point; +}; + +struct wlr_wl_drm_syncobj_timeline { + struct wlr_drm_syncobj_timeline *base; + struct wlr_addon addon; + struct wl_list link; // wlr_wl_backend.drm_syncobj_timelines + struct wp_linux_drm_syncobj_timeline_v1 *wl; }; struct wlr_wl_presentation_feedback { @@ -88,6 +105,7 @@ struct wlr_wl_output { struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1; + struct wp_linux_drm_syncobj_surface_v1 *drm_syncobj_surface_v1; struct wl_list presentation_feedbacks; char *title; @@ -190,6 +208,7 @@ bool create_wl_seat(struct wl_seat *wl_seat, struct wlr_wl_backend *wl, uint32_t global_name); void destroy_wl_seat(struct wlr_wl_seat *seat); void destroy_wl_buffer(struct wlr_wl_buffer *buffer); +void destroy_wl_drm_syncobj_timeline(struct wlr_wl_drm_syncobj_timeline *timeline); extern const struct wlr_pointer_impl wl_pointer_impl; extern const struct wlr_tablet_pad_impl wl_tablet_pad_impl; diff --git a/include/render/allocator/allocator.h b/include/render/allocator/allocator.h deleted file mode 100644 index 5f8bd2a1b..000000000 --- a/include/render/allocator/allocator.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef RENDER_ALLOCATOR_ALLOCATOR_H -#define RENDER_ALLOCATOR_ALLOCATOR_H - -#include - -struct wlr_allocator *allocator_autocreate_with_drm_fd( - uint32_t backend_caps, struct wlr_renderer *renderer, int drm_fd); - -#endif diff --git a/include/render/allocator/drm_dumb.h b/include/render/allocator/drm_dumb.h index 23c150bc3..f505d651a 100644 --- a/include/render/allocator/drm_dumb.h +++ b/include/render/allocator/drm_dumb.h @@ -1,9 +1,9 @@ #ifndef RENDER_ALLOCATOR_DRM_DUMB_H #define RENDER_ALLOCATOR_DRM_DUMB_H +#include #include #include -#include "render/allocator/allocator.h" struct wlr_drm_dumb_buffer { struct wlr_buffer base; diff --git a/include/render/allocator/gbm.h b/include/render/allocator/gbm.h index 7e043faf5..7d0016eb4 100644 --- a/include/render/allocator/gbm.h +++ b/include/render/allocator/gbm.h @@ -2,9 +2,9 @@ #define RENDER_ALLOCATOR_GBM_H #include +#include #include #include -#include "render/allocator/allocator.h" struct wlr_gbm_buffer { struct wlr_buffer base; diff --git a/include/render/allocator/shm.h b/include/render/allocator/shm.h index 4b80e4753..602efdc35 100644 --- a/include/render/allocator/shm.h +++ b/include/render/allocator/shm.h @@ -1,8 +1,8 @@ #ifndef RENDER_ALLOCATOR_SHM_H #define RENDER_ALLOCATOR_SHM_H +#include #include -#include "render/allocator/allocator.h" struct wlr_shm_buffer { struct wlr_buffer base; diff --git a/include/render/allocator/udmabuf.h b/include/render/allocator/udmabuf.h new file mode 100644 index 000000000..b38b5ab0d --- /dev/null +++ b/include/render/allocator/udmabuf.h @@ -0,0 +1,23 @@ +#ifndef RENDER_ALLOCATOR_UDMABUF_H +#define RENDER_ALLOCATOR_UDMABUF_H + +#include +#include + +struct wlr_udmabuf_buffer { + struct wlr_buffer base; + + size_t size; + struct wlr_shm_attributes shm; + struct wlr_dmabuf_attributes dmabuf; +}; + +struct wlr_udmabuf_allocator { + struct wlr_allocator base; + + int fd; +}; + +struct wlr_allocator *wlr_udmabuf_allocator_create(void); + +#endif diff --git a/include/render/color.h b/include/render/color.h index e18f12eeb..128b345e1 100644 --- a/include/render/color.h +++ b/include/render/color.h @@ -2,30 +2,14 @@ #define RENDER_COLOR_H #include +#include #include -/** - * The formula is approximated via a 3D look-up table. A 3D LUT is a - * three-dimensional array where each element is an RGB triplet. The flat lut_3d - * array has a length of dim_len³. - * - * 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. - * - * The offset of the RGB triplet given red, green and blue indices r_index, - * g_index and b_index is: - * - * offset = 3 * (r_index + dim_len * g_index + dim_len * dim_len * b_index) - */ -struct wlr_color_transform_lut3d { - float *lut_3d; - size_t dim_len; -}; - enum wlr_color_transform_type { - COLOR_TRANSFORM_SRGB, - COLOR_TRANSFORM_LUT_3D, + COLOR_TRANSFORM_INVERSE_EOTF, + COLOR_TRANSFORM_LCMS2, + COLOR_TRANSFORM_LUT_3X1D, + COLOR_TRANSFORM_PIPELINE, }; struct wlr_color_transform { @@ -33,7 +17,84 @@ struct wlr_color_transform { struct wlr_addon_set addons; // per-renderer helper state enum wlr_color_transform_type type; - struct wlr_color_transform_lut3d lut3d; }; +struct wlr_color_transform_inverse_eotf { + struct wlr_color_transform base; + + enum wlr_color_transfer_function tf; +}; + +/** + * The formula is approximated via three 1D look-up tables. The flat lut_3x1d + * array has a length of 3 * dim. + * + * The offset of a color value for a given channel and color index is: + * + * offset = channel_index * dim + color_index + */ +struct wlr_color_transform_lut_3x1d { + struct wlr_color_transform base; + + uint16_t *lut_3x1d; + size_t dim; +}; + +struct wlr_color_transform_pipeline { + struct wlr_color_transform base; + + struct wlr_color_transform **transforms; + size_t len; +}; + +void wlr_color_transform_init(struct wlr_color_transform *tr, + enum wlr_color_transform_type type); + +/** + * Get a struct wlr_color_transform_lcms2 from a generic struct wlr_color_transform. + * Asserts that the base type is COLOR_TRANSFORM_LCMS2. + */ +struct wlr_color_transform_lcms2 *color_transform_lcms2_from_base( + struct wlr_color_transform *tr); + +void color_transform_lcms2_finish(struct wlr_color_transform_lcms2 *tr); + +/** + * Evaluate a LCMS2 color transform for a given RGB triplet. + */ +void color_transform_lcms2_eval(struct wlr_color_transform_lcms2 *tr, + float out[static 3], const float in[static 3]); + +/** + * Gets a wlr_color_transform_inverse_eotf from a generic wlr_color_transform. + * Asserts that the base type is COLOR_TRANSFORM_INVERSE_EOTF + */ +struct wlr_color_transform_inverse_eotf *wlr_color_transform_inverse_eotf_from_base( + struct wlr_color_transform *tr); + +/** + * Get a struct wlr_color_transform_lut_3x1d from a generic + * struct wlr_color_transform. Asserts that the base type is + * COLOR_TRANSFORM_LUT_3X1D. + */ +struct wlr_color_transform_lut_3x1d *color_transform_lut_3x1d_from_base( + struct wlr_color_transform *tr); + +/** + * Obtain primaries values from a well-known primaries name. + */ +void wlr_color_primaries_from_named(struct wlr_color_primaries *out, + enum wlr_color_named_primaries named); + +/** + * Compute the matrix to convert RGB color values to CIE 1931 XYZ. + */ +void wlr_color_primaries_to_xyz(const struct wlr_color_primaries *primaries, float matrix[static 9]); + +/** + * Get default luminances for a transfer function. + */ +void wlr_color_transfer_function_get_default_luminance(enum wlr_color_transfer_function tf, + struct wlr_color_luminances *lum); + #endif diff --git a/include/render/egl.h b/include/render/egl.h index 0765cce74..103ab57df 100644 --- a/include/render/egl.h +++ b/include/render/egl.h @@ -38,6 +38,10 @@ struct wlr_egl { PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT; PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT; PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT; + PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR; + PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR; + PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID; + PFNEGLWAITSYNCKHRPROC eglWaitSyncKHR; } procs; bool has_modifiers; @@ -105,4 +109,12 @@ bool wlr_egl_make_current(struct wlr_egl *egl, struct wlr_egl_context *save_cont bool wlr_egl_unset_current(struct wlr_egl *egl); +EGLSyncKHR wlr_egl_create_sync(struct wlr_egl *egl, int fence_fd); + +void wlr_egl_destroy_sync(struct wlr_egl *egl, EGLSyncKHR sync); + +int wlr_egl_dup_fence_fd(struct wlr_egl *egl, EGLSyncKHR sync); + +bool wlr_egl_wait_sync(struct wlr_egl *egl, EGLSyncKHR sync); + #endif diff --git a/include/render/gles2.h b/include/render/gles2.h index a472ee9c6..6b852dcb7 100644 --- a/include/render/gles2.h +++ b/include/render/gles2.h @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -138,6 +137,8 @@ struct wlr_gles2_render_pass { float projection_matrix[9]; struct wlr_egl_context prev_ctx; struct wlr_gles2_render_timer *timer; + struct wlr_drm_syncobj_timeline *signal_timeline; + uint64_t signal_point; }; bool is_gles2_pixel_format_supported(const struct wlr_gles2_renderer *renderer, @@ -169,6 +170,7 @@ void push_gles2_debug_(struct wlr_gles2_renderer *renderer, void pop_gles2_debug(struct wlr_gles2_renderer *renderer); struct wlr_gles2_render_pass *begin_gles2_buffer_pass(struct wlr_gles2_buffer *buffer, - struct wlr_egl_context *prev_ctx, struct wlr_gles2_render_timer *timer); + struct wlr_egl_context *prev_ctx, struct wlr_gles2_render_timer *timer, + struct wlr_drm_syncobj_timeline *signal_timeline, uint64_t signal_point); #endif diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 29403f01f..5358b01c1 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,7 @@ struct wlr_vk_device { int drm_fd; + bool sync_file_import_export; bool implicit_sync_interop; bool sampler_ycbcr_conversion; @@ -150,6 +152,9 @@ struct wlr_vk_pipeline_layout { enum wlr_vk_texture_transform { WLR_VK_TEXTURE_TRANSFORM_IDENTITY = 0, WLR_VK_TEXTURE_TRANSFORM_SRGB = 1, + WLR_VK_TEXTURE_TRANSFORM_ST2084_PQ = 2, + WLR_VK_TEXTURE_TRANSFORM_GAMMA22 = 3, + WLR_VK_TEXTURE_TRANSFORM_BT1886 = 4, }; enum wlr_vk_shader_source { @@ -160,8 +165,12 @@ enum wlr_vk_shader_source { // Constants used to pick the color transform for the blend-to-output // fragment shader. Must match those in shaders/output.frag enum wlr_vk_output_transform { - WLR_VK_OUTPUT_TRANSFORM_INVERSE_SRGB = 0, - WLR_VK_OUTPUT_TRANSFORM_LUT3D = 1, + WLR_VK_OUTPUT_TRANSFORM_IDENTITY = 0, + WLR_VK_OUTPUT_TRANSFORM_INVERSE_SRGB = 1, + WLR_VK_OUTPUT_TRANSFORM_INVERSE_ST2084_PQ = 2, + WLR_VK_OUTPUT_TRANSFORM_LUT3D = 3, + WLR_VK_OUTPUT_TRANSFORM_INVERSE_GAMMA22 = 4, + WLR_VK_OUTPUT_TRANSFORM_INVERSE_BT1886 = 5, }; struct wlr_vk_pipeline_key { @@ -190,13 +199,24 @@ struct wlr_vk_render_format_setup { bool use_blending_buffer; VkRenderPass render_pass; + VkPipeline output_pipe_identity; VkPipeline output_pipe_srgb; + VkPipeline output_pipe_pq; VkPipeline output_pipe_lut3d; + VkPipeline output_pipe_gamma22; + VkPipeline output_pipe_bt1886; struct wlr_vk_renderer *renderer; struct wl_list pipelines; // struct wlr_vk_pipeline.link }; +// Final output framebuffer and image view +struct wlr_vk_render_buffer_out { + VkImageView image_view; + VkFramebuffer framebuffer; + bool transitioned; +}; + // Renderer-internal represenation of an wlr_buffer imported for rendering. struct wlr_vk_render_buffer { struct wlr_buffer *wlr_buffer; @@ -208,36 +228,40 @@ struct wlr_vk_render_buffer { uint32_t mem_count; VkImage image; + // Framebuffer and image view for rendering directly onto the buffer image, + // without any color transform. + struct { + struct wlr_vk_render_buffer_out out; + struct wlr_vk_render_format_setup *render_setup; + } linear; + // Framebuffer and image view for rendering directly onto the buffer image. // This requires that the image support an _SRGB VkFormat, and does // not work with color transforms. struct { + struct wlr_vk_render_buffer_out out; struct wlr_vk_render_format_setup *render_setup; - VkImageView image_view; - VkFramebuffer framebuffer; - bool transitioned; } srgb; // Framebuffer, image view, and blending image to render indirectly // onto the buffer image. This works for general image types and permits // color transforms. struct { + struct wlr_vk_render_buffer_out out; struct wlr_vk_render_format_setup *render_setup; - VkImageView image_view; - VkFramebuffer framebuffer; - bool transitioned; - VkImage blend_image; VkImageView blend_image_view; VkDeviceMemory blend_memory; VkDescriptorSet blend_descriptor_set; struct wlr_vk_descriptor_pool *blend_attachment_pool; bool blend_transitioned; - } plain; + } two_pass; }; -bool vulkan_setup_plain_framebuffer(struct wlr_vk_render_buffer *buffer, +bool vulkan_setup_one_pass_framebuffer(struct wlr_vk_render_buffer *buffer, + const struct wlr_dmabuf_attributes *dmabuf, bool srgb); +bool vulkan_setup_two_pass_framebuffer(struct wlr_vk_render_buffer *buffer, const struct wlr_dmabuf_attributes *dmabuf); struct wlr_vk_command_buffer { @@ -253,6 +277,8 @@ struct wlr_vk_command_buffer { // For DMA-BUF implicit sync interop, may be NULL VkSemaphore binary_semaphore; + + struct wl_array wait_semaphores; // VkSemaphore }; #define VULKAN_COMMAND_BUFFERS_CAP 64 @@ -331,7 +357,15 @@ struct wlr_vk_vert_pcr_data { float uv_size[2]; }; +struct wlr_vk_frag_texture_pcr_data { + float matrix[4][4]; // only a 3x3 subset is used + float alpha; + float luminance_multiplier; +}; + struct wlr_vk_frag_output_pcr_data { + float matrix[4][4]; // only a 3x3 subset is used + float luminance_multiplier; float lut_3d_offset; float lut_3d_scale; }; @@ -339,6 +373,7 @@ struct wlr_vk_frag_output_pcr_data { struct wlr_vk_texture_view { struct wl_list link; // struct wlr_vk_texture.views const struct wlr_vk_pipeline_layout *layout; + bool srgb; VkDescriptorSet ds; VkImageView image_view; @@ -353,7 +388,7 @@ struct wlr_vk_pipeline_layout *get_or_create_pipeline_layout( const struct wlr_vk_pipeline_layout_key *key); struct wlr_vk_texture_view *vulkan_texture_get_or_create_view( struct wlr_vk_texture *texture, - const struct wlr_vk_pipeline_layout *layout); + const struct wlr_vk_pipeline_layout *layout, bool srgb); // Creates a vulkan renderer for the given device. struct wlr_renderer *vulkan_renderer_create_for_device(struct wlr_vk_device *dev); @@ -367,17 +402,34 @@ VkCommandBuffer vulkan_record_stage_cb(struct wlr_vk_renderer *renderer); // finished execution. bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer); +struct wlr_vk_render_pass_texture { + struct wlr_vk_texture *texture; + + struct wlr_drm_syncobj_timeline *wait_timeline; + uint64_t wait_point; +}; + struct wlr_vk_render_pass { struct wlr_render_pass base; struct wlr_vk_renderer *renderer; struct wlr_vk_render_buffer *render_buffer; + struct wlr_vk_render_buffer_out *render_buffer_out; + struct wlr_vk_render_format_setup *render_setup; struct wlr_vk_command_buffer *command_buffer; struct rect_union updated_region; VkPipeline bound_pipeline; float projection[9]; bool failed; - bool srgb_pathway; // if false, rendering via intermediate blending buffer + bool two_pass; // rendering via intermediate blending buffer struct wlr_color_transform *color_transform; + + bool has_primaries; + struct wlr_color_primaries primaries; + + struct wlr_drm_syncobj_timeline *signal_timeline; + uint64_t signal_point; + + struct wl_array textures; // struct wlr_vk_render_pass_texture }; struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *renderer, @@ -419,8 +471,10 @@ bool vulkan_wait_command_buffer(struct wlr_vk_command_buffer *cb, struct wlr_vk_renderer *renderer); bool vulkan_sync_render_buffer(struct wlr_vk_renderer *renderer, - struct wlr_vk_render_buffer *render_buffer, struct wlr_vk_command_buffer *cb); -bool vulkan_sync_foreign_texture(struct wlr_vk_texture *texture); + struct wlr_vk_render_buffer *render_buffer, struct wlr_vk_command_buffer *cb, + struct wlr_drm_syncobj_timeline *signal_timeline, uint64_t signal_point); +bool vulkan_sync_foreign_texture(struct wlr_vk_texture *texture, + int sync_file_fds[static WLR_DMABUF_MAX_PLANES]); bool vulkan_read_pixels(struct wlr_vk_renderer *vk_renderer, VkFormat src_format, VkImage src_image, @@ -436,13 +490,12 @@ struct wlr_vk_texture { VkDeviceMemory memories[WLR_DMABUF_MAX_PLANES]; VkImage image; const struct wlr_vk_format *format; - enum wlr_vk_texture_transform transform; struct wlr_vk_command_buffer *last_used_cb; // to track when it can be destroyed bool dmabuf_imported; bool owned; // if dmabuf_imported: whether we have ownership of the image bool transitioned; // if dma_imported: whether we transitioned it away from preinit bool has_alpha; // whether the image is has alpha channel - bool using_mutable_srgb; // is this accessed through _SRGB format view + bool using_mutable_srgb; // can be accessed through _SRGB format view struct wl_list foreign_link; // wlr_vk_renderer.foreign_textures struct wl_list destroy_link; // wlr_vk_command_buffer.destroy_textures struct wl_list link; // wlr_vk_renderer.textures @@ -450,10 +503,8 @@ struct wlr_vk_texture { // If imported from a wlr_buffer struct wlr_buffer *buffer; struct wlr_addon buffer_addon; - // For DMA-BUF implicit sync interop - VkSemaphore foreign_semaphores[WLR_DMABUF_MAX_PLANES]; - struct wl_list views; // struct wlr_vk_texture_ds.link + struct wl_list views; // struct wlr_vk_texture_view.link }; struct wlr_vk_texture *vulkan_get_texture(struct wlr_texture *wlr_texture); @@ -485,6 +536,7 @@ struct wlr_vk_shared_buffer { VkDeviceSize buf_size; void *cpu_mapping; struct wl_array allocs; // struct wlr_vk_allocation + int64_t last_used_ms; }; // Suballocated range on a buffer. @@ -500,6 +552,7 @@ struct wlr_vk_color_transform { struct wl_list link; // wlr_vk_renderer, list of all color transforms struct { + size_t dim; VkImage image; VkImageView image_view; VkDeviceMemory memory; diff --git a/include/render/wlr_renderer.h b/include/render/wlr_renderer.h index d9823dc8b..f6661384b 100644 --- a/include/render/wlr_renderer.h +++ b/include/render/wlr_renderer.h @@ -9,7 +9,7 @@ struct wlr_renderer *renderer_autocreate_with_drm_fd(int drm_fd); /** * Get the supported render formats. Buffers allocated with a format from this - * list may be attached via wlr_renderer_begin_with_buffer. + * list may be used with wlr_renderer_begin_buffer_pass(). */ const struct wlr_drm_format_set *wlr_renderer_get_render_formats( struct wlr_renderer *renderer); diff --git a/include/types/wlr_buffer.h b/include/types/wlr_buffer.h index 947ccde7b..9d882d47d 100644 --- a/include/types/wlr_buffer.h +++ b/include/types/wlr_buffer.h @@ -50,15 +50,6 @@ struct wlr_dmabuf_buffer *dmabuf_buffer_create( */ bool dmabuf_buffer_drop(struct wlr_dmabuf_buffer *buffer); -/** - * Check whether a buffer is fully opaque. - * - * When true is returned, the buffer is guaranteed to be fully opaque, but the - * reverse is not true: false may be returned in cases where the buffer is fully - * opaque. - */ -bool buffer_is_opaque(struct wlr_buffer *buffer); - /** * Creates a struct wlr_client_buffer from a given struct wlr_buffer by creating * a texture from it, and copying its struct wl_resource. diff --git a/include/types/wlr_matrix.h b/include/types/wlr_matrix.h deleted file mode 100644 index ce599dc1b..000000000 --- a/include/types/wlr_matrix.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef TYPES_WLR_MATRIX_H -#define TYPES_WLR_MATRIX_H - -#include - -/** - * Writes a 2D orthographic projection matrix to mat of (width, height) with a - * specified wl_output_transform. - * - * Equivalent to glOrtho(0, width, 0, height, 1, -1) with the transform applied. - */ -void matrix_projection(float mat[static 9], int width, int height, - enum wl_output_transform transform); - -#endif diff --git a/include/types/wlr_output.h b/include/types/wlr_output.h index 09be35510..d59b05f0b 100644 --- a/include/types/wlr_output.h +++ b/include/types/wlr_output.h @@ -8,6 +8,8 @@ void output_pending_resolution(struct wlr_output *output, const struct wlr_output_state *state, int *width, int *height); bool output_pending_enabled(struct wlr_output *output, const struct wlr_output_state *state); +const struct wlr_output_image_description *output_pending_image_description( + struct wlr_output *output, const struct wlr_output_state *state); bool output_pick_format(struct wlr_output *output, const struct wlr_drm_format_set *display_formats, @@ -18,11 +20,18 @@ bool output_ensure_buffer(struct wlr_output *output, bool output_cursor_set_texture(struct wlr_output_cursor *cursor, struct wlr_texture *texture, bool own_texture, const struct wlr_fbox *src_box, int dst_width, int dst_height, enum wl_output_transform transform, - int32_t hotspot_x, int32_t hotspot_y); + int32_t hotspot_x, int32_t hotspot_y, struct wlr_drm_syncobj_timeline *wait_timeline, + uint64_t wait_point); void output_defer_present(struct wlr_output *output, struct wlr_output_event_present event); bool output_prepare_commit(struct wlr_output *output, const struct wlr_output_state *state); void output_apply_commit(struct wlr_output *output, const struct wlr_output_state *state); +void output_send_commit_event(struct wlr_output *output, const struct wlr_output_state *state); + +void output_state_get_buffer_src_box(const struct wlr_output_state *state, + struct wlr_fbox *out); +void output_state_get_buffer_dst_box(const struct wlr_output_state *state, + struct wlr_box *out); #endif diff --git a/include/types/wlr_region.h b/include/types/wlr_region.h index b00f8802d..a44a9f09a 100644 --- a/include/types/wlr_region.h +++ b/include/types/wlr_region.h @@ -1,7 +1,7 @@ #ifndef TYPES_WLR_REGION_H #define TYPES_WLR_REGION_H -#include +#include struct wl_client; diff --git a/include/types/wlr_scene.h b/include/types/wlr_scene.h index 80dcfd1bd..c4b40cdd0 100644 --- a/include/types/wlr_scene.h +++ b/include/types/wlr_scene.h @@ -5,6 +5,8 @@ struct wlr_scene *scene_node_get_root(struct wlr_scene_node *node); +void scene_node_get_size(struct wlr_scene_node *node, int *width, int *height); + void scene_surface_set_clip(struct wlr_scene_surface *surface, struct wlr_box *clip); #endif diff --git a/include/wlr/types/wlr_matrix.h b/include/util/matrix.h similarity index 65% rename from include/wlr/types/wlr_matrix.h rename to include/util/matrix.h index 674cb5382..c45c99a06 100644 --- a/include/wlr/types/wlr_matrix.h +++ b/include/util/matrix.h @@ -1,10 +1,5 @@ -/* - * This is a deprecated interface of wlroots. It will be removed in a future - * version. - */ - -#ifndef WLR_TYPES_WLR_MATRIX_H -#define WLR_TYPES_WLR_MATRIX_H +#ifndef UTIL_MATRIX_H +#define UTIL_MATRIX_H #include @@ -17,17 +12,12 @@ void wlr_matrix_identity(float mat[static 9]); void wlr_matrix_multiply(float mat[static 9], const float a[static 9], const float b[static 9]); -void wlr_matrix_transpose(float mat[static 9], const float a[static 9]); - /** Writes a 2D translation matrix to mat of magnitude (x, y) */ void wlr_matrix_translate(float mat[static 9], float x, float y); /** Writes a 2D scale matrix to mat of magnitude (x, y) */ void wlr_matrix_scale(float mat[static 9], float x, float y); -/** Writes a 2D rotation matrix to mat at an angle of rad radians */ -void wlr_matrix_rotate(float mat[static 9], float rad); - /** Writes a transformation matrix which applies the specified * wl_output_transform to mat */ void wlr_matrix_transform(float mat[static 9], @@ -38,7 +28,22 @@ void wlr_matrix_transform(float mat[static 9], * rotation. The result is written to mat, which can be applied to each * coordinate of the box to get a new coordinate from [-1,1]. */ void wlr_matrix_project_box(float mat[static 9], const struct wlr_box *box, - enum wl_output_transform transform, float rotation, - const float projection[static 9]); + enum wl_output_transform transform, const float projection[static 9]); + +/** + * Writes a 2D orthographic projection matrix to mat of (width, height) with a + * specified wl_output_transform. + * + * Equivalent to glOrtho(0, width, 0, height, 1, -1) with the transform applied. + */ +void matrix_projection(float mat[static 9], int width, int height, + enum wl_output_transform transform); + +/** + * Compute the inverse of a matrix. + * + * The matrix needs to be inversible. + */ +void matrix_invert(float out[static 9], float m[static 9]); #endif diff --git a/include/util/mem.h b/include/util/mem.h new file mode 100644 index 000000000..8971d2087 --- /dev/null +++ b/include/util/mem.h @@ -0,0 +1,14 @@ +#ifndef UTIL_MEM_H +#define UTIL_MEM_H + +#include +#include + +/** + * Allocate a new block of memory and copy *src to it, then store the address + * of the new allocation in *out. Returns true if it worked, or false if + * allocation failed. + */ +bool memdup(void *out, const void *src, size_t size); + +#endif // UTIL_MEM_H diff --git a/include/util/time.h b/include/util/time.h index 3f76aa47f..dd164bf2f 100644 --- a/include/util/time.h +++ b/include/util/time.h @@ -4,6 +4,8 @@ #include #include +static const long NSEC_PER_SEC = 1000000000; + /** * Get the current time, in milliseconds. */ diff --git a/include/wlr/backend.h b/include/wlr/backend.h index 6e2269586..31588ca9c 100644 --- a/include/wlr/backend.h +++ b/include/wlr/backend.h @@ -25,10 +25,21 @@ struct wlr_backend_output_state { /** * A backend provides a set of input and output devices. + * + * Buffer capabilities and features can change over the lifetime of a backend, + * for instance when a child backend is added to a multi-backend. */ struct wlr_backend { const struct wlr_backend_impl *impl; + // Bitfield of supported buffer capabilities (see enum wlr_buffer_cap) + uint32_t buffer_caps; + + struct { + // Whether wait/signal timelines are supported in output commits + bool timeline; + } features; + struct { /** Raised when destroyed */ struct wl_signal destroy; @@ -54,13 +65,12 @@ struct wlr_backend *wlr_backend_autocreate(struct wl_event_loop *loop, struct wlr_session **session_ptr); /** * Start the backend. This may signal new_input or new_output immediately, but - * may also wait until the display's event loop begins. Returns false on - * failure. + * may also wait until the event loop is started. Returns false on failure. */ bool wlr_backend_start(struct wlr_backend *backend); /** * Destroy the backend and clean up all of its resources. Normally called - * automatically when the struct wl_display is destroyed. + * automatically when the event loop is destroyed. */ void wlr_backend_destroy(struct wlr_backend *backend); /** diff --git a/include/wlr/backend/interface.h b/include/wlr/backend/interface.h index 938ca73c9..648fa66e8 100644 --- a/include/wlr/backend/interface.h +++ b/include/wlr/backend/interface.h @@ -18,7 +18,6 @@ struct wlr_backend_impl { bool (*start)(struct wlr_backend *backend); void (*destroy)(struct wlr_backend *backend); int (*get_drm_fd)(struct wlr_backend *backend); - uint32_t (*get_buffer_caps)(struct wlr_backend *backend); bool (*test)(struct wlr_backend *backend, const struct wlr_backend_output_state *states, size_t states_len); bool (*commit)(struct wlr_backend *backend, diff --git a/include/wlr/backend/session.h b/include/wlr/backend/session.h index 3179d1f54..a1ff0c317 100644 --- a/include/wlr/backend/session.h +++ b/include/wlr/backend/session.h @@ -43,11 +43,6 @@ struct wlr_session { */ bool active; - /* - * 0 if virtual terminals are not supported - * i.e. seat != "seat0" - */ - unsigned vtnr; char seat[256]; struct udev *udev; @@ -60,13 +55,16 @@ struct wlr_session { struct wl_list devices; // wlr_device.link struct wl_event_loop *event_loop; - struct wl_listener event_loop_destroy; struct { struct wl_signal active; struct wl_signal add_drm_card; // struct wlr_session_add_event struct wl_signal destroy; } events; + + struct { + struct wl_listener event_loop_destroy; + } WLR_PRIVATE; }; struct wlr_session_add_event { diff --git a/include/wlr/config.h.in b/include/wlr/config.h.in index e03049da6..ef186343e 100644 --- a/include/wlr/config.h.in +++ b/include/wlr/config.h.in @@ -1,19 +1,76 @@ #ifndef WLR_CONFIG_H #define WLR_CONFIG_H +/** + * Whether the DRM backend is compile-time enabled. Equivalent to the + * pkg-config "have_drm_backend" variable. + * + * Required for . + */ #mesondefine WLR_HAS_DRM_BACKEND +/** + * Whether the libinput backend is compile-time enabled. Equivalent to the + * pkg-config "have_libinput_backend" vartiable. + * + * Required for . + */ #mesondefine WLR_HAS_LIBINPUT_BACKEND +/** + * Whether the X11 backend is compile-time enabled. Equivalent to the + * pkg-config "have_x11_backend" variable. + * + * Required for . + */ #mesondefine WLR_HAS_X11_BACKEND +/** + * Whether the GLES2 renderer is compile-time enabled. Equivalent to the + * pkg-config "have_gles2_renderer" variable. + * + * Required for . + */ #mesondefine WLR_HAS_GLES2_RENDERER +/** + * Whether the Vulkan renderer is compile-time enabled. Equivalent to the + * pkg-config "have_vulkan_renderer" variable. + * + * Required for . + */ #mesondefine WLR_HAS_VULKAN_RENDERER +/** + * Whether the GBM allocator is compile-time enabled. Equivalent to the + * pkg-config "have_gbm_allocator" variable. + */ #mesondefine WLR_HAS_GBM_ALLOCATOR +/** + * Whether the udmabuf allocator is compile-time enabled. Equivalent to the + * pkg-config "have_udmabuf_allocator" variable. + */ +#mesondefine WLR_HAS_UDMABUF_ALLOCATOR +/** + * Whether Xwayland support is compile-time enabled. Equivalent to the + * pkg-config "have_xwayland" variable. + * + * Required for . + */ #mesondefine WLR_HAS_XWAYLAND +/** + * Whether session support is compile-time enabled. Equivalent to the + * pkg-config "have_session" variable. + * + * Required for . + */ #mesondefine WLR_HAS_SESSION +/** + * Whether traditional color management support is compile-time enabled. + * Equivalent to the pkg-config "have_color_management" variable. + * + * Required for ICC profile support in . + */ #mesondefine WLR_HAS_COLOR_MANAGEMENT #endif diff --git a/include/wlr/interfaces/wlr_buffer.h b/include/wlr/interfaces/wlr_buffer.h index 820300f65..4244a00d5 100644 --- a/include/wlr/interfaces/wlr_buffer.h +++ b/include/wlr/interfaces/wlr_buffer.h @@ -36,6 +36,11 @@ struct wlr_buffer_resource_interface { void wlr_buffer_init(struct wlr_buffer *buffer, const struct wlr_buffer_impl *impl, int width, int height); +/** + * Emit the destroy event and clean up common buffer state. + */ +void wlr_buffer_finish(struct wlr_buffer *buffer); + /** * Allows the registration of a struct wl_resource implementation. * diff --git a/include/wlr/interfaces/wlr_ext_image_capture_source_v1.h b/include/wlr/interfaces/wlr_ext_image_capture_source_v1.h new file mode 100644 index 000000000..8678d3c67 --- /dev/null +++ b/include/wlr/interfaces/wlr_ext_image_capture_source_v1.h @@ -0,0 +1,45 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_INTERFACES_WLR_EXT_IMAGE_CAPTURE_SOURCE_V1_H +#define WLR_INTERFACES_WLR_EXT_IMAGE_CAPTURE_SOURCE_V1_H + +#include +#include + +struct wlr_ext_image_copy_capture_frame_v1; +struct wlr_swapchain; +struct wlr_renderer; +struct wlr_seat; + +struct wlr_ext_image_capture_source_v1_interface { + // TODO: drop with_cursors flag + void (*start)(struct wlr_ext_image_capture_source_v1 *source, bool with_cursors); + void (*stop)(struct wlr_ext_image_capture_source_v1 *source); + void (*schedule_frame)(struct wlr_ext_image_capture_source_v1 *source); + void (*copy_frame)(struct wlr_ext_image_capture_source_v1 *source, + struct wlr_ext_image_copy_capture_frame_v1 *dst_frame, + struct wlr_ext_image_capture_source_v1_frame_event *frame_event); + struct wlr_ext_image_capture_source_v1_cursor *(*get_pointer_cursor)( + struct wlr_ext_image_capture_source_v1 *source, struct wlr_seat *seat); +}; + +void wlr_ext_image_capture_source_v1_init(struct wlr_ext_image_capture_source_v1 *source, + const struct wlr_ext_image_capture_source_v1_interface *impl); +void wlr_ext_image_capture_source_v1_finish(struct wlr_ext_image_capture_source_v1 *source); +bool wlr_ext_image_capture_source_v1_create_resource(struct wlr_ext_image_capture_source_v1 *source, + struct wl_client *client, uint32_t new_id); +bool wlr_ext_image_capture_source_v1_set_constraints_from_swapchain( + struct wlr_ext_image_capture_source_v1 *source, + struct wlr_swapchain *swapchain, struct wlr_renderer *renderer); + +void wlr_ext_image_capture_source_v1_cursor_init(struct wlr_ext_image_capture_source_v1_cursor *source_cursor, + const struct wlr_ext_image_capture_source_v1_interface *impl); +void wlr_ext_image_capture_source_v1_cursor_finish(struct wlr_ext_image_capture_source_v1_cursor *source_cursor); + +#endif diff --git a/include/wlr/interfaces/wlr_output.h b/include/wlr/interfaces/wlr_output.h index 2a40a0fe3..6da2c2c54 100644 --- a/include/wlr/interfaces/wlr_output.h +++ b/include/wlr/interfaces/wlr_output.h @@ -108,6 +108,10 @@ struct wlr_output_impl { void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend, const struct wlr_output_impl *impl, struct wl_event_loop *event_loop, const struct wlr_output_state *state); +/** + * Emit the destroy event and clean up common output state. + */ +void wlr_output_finish(struct wlr_output *output); /** * Notify compositors that they need to submit a new frame in order to apply * output changes. diff --git a/include/wlr/interfaces/wlr_pointer.h b/include/wlr/interfaces/wlr_pointer.h index 74e0feafe..93adb714e 100644 --- a/include/wlr/interfaces/wlr_pointer.h +++ b/include/wlr/interfaces/wlr_pointer.h @@ -19,4 +19,7 @@ void wlr_pointer_init(struct wlr_pointer *pointer, const struct wlr_pointer_impl *impl, const char *name); void wlr_pointer_finish(struct wlr_pointer *pointer); +void wlr_pointer_notify_button(struct wlr_pointer *pointer, + struct wlr_pointer_button_event *event); + #endif diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h index ddd4408b4..bc3baf181 100644 --- a/include/wlr/render/color.h +++ b/include/wlr/render/color.h @@ -10,8 +10,100 @@ #define WLR_RENDER_COLOR_H #include +#include #include +/** + * Well-known color primaries. + */ +enum wlr_color_named_primaries { + WLR_COLOR_NAMED_PRIMARIES_SRGB = 1 << 0, + WLR_COLOR_NAMED_PRIMARIES_BT2020 = 1 << 1, +}; + +/** + * Well-known color transfer functions. + */ +enum wlr_color_transfer_function { + WLR_COLOR_TRANSFER_FUNCTION_SRGB = 1 << 0, + WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ = 1 << 1, + WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR = 1 << 2, + WLR_COLOR_TRANSFER_FUNCTION_GAMMA22 = 1 << 3, + WLR_COLOR_TRANSFER_FUNCTION_BT1886 = 1 << 4, +}; + +/** + * Specifies alpha blending modes. Note that premultiplied_electrical + * is the default, so there is no "none" or "unset" value. + */ +enum wlr_alpha_mode { + WLR_COLOR_ALPHA_MODE_PREMULTIPLIED_ELECTRICAL, + WLR_COLOR_ALPHA_MODE_PREMULTIPLIED_OPTICAL, + WLR_COLOR_ALPHA_MODE_STRAIGHT, +}; + +/** + * Well-known color encodings, each representing a set of matrix coefficients + * used to convert that particular YCbCr encoding to RGB. NONE means the + * value is unset or unknown. + */ +enum wlr_color_encoding { + WLR_COLOR_ENCODING_NONE, + WLR_COLOR_ENCODING_IDENTITY, + WLR_COLOR_ENCODING_BT709, + WLR_COLOR_ENCODING_FCC, + WLR_COLOR_ENCODING_BT601, + WLR_COLOR_ENCODING_SMPTE240, + WLR_COLOR_ENCODING_BT2020, + WLR_COLOR_ENCODING_BT2020_CL, + WLR_COLOR_ENCODING_ICTCP, +}; + +/** + * Specifies whether a particular color-encoding uses full- or limited-range + * values. NONE means the value is unset or unknown. + */ +enum wlr_color_range { + WLR_COLOR_RANGE_NONE, + WLR_COLOR_RANGE_LIMITED, + WLR_COLOR_RANGE_FULL, +}; + +/** + * Chroma sample locations, corresponding to Chroma420SampleLocType code + * points in H.273. NONE means the value is unset or unknown. + */ +enum wlr_color_chroma_location { + WLR_COLOR_CHROMA_LOCATION_NONE, + WLR_COLOR_CHROMA_LOCATION_TYPE0, + WLR_COLOR_CHROMA_LOCATION_TYPE1, + WLR_COLOR_CHROMA_LOCATION_TYPE2, + WLR_COLOR_CHROMA_LOCATION_TYPE3, + WLR_COLOR_CHROMA_LOCATION_TYPE4, + WLR_COLOR_CHROMA_LOCATION_TYPE5, +}; + +/** + * CIE 1931 xy chromaticity coordinates. + */ +struct wlr_color_cie1931_xy { + float x, y; +}; + +/** + * Color primaries and white point describing a color volume. + */ +struct wlr_color_primaries { + struct wlr_color_cie1931_xy red, green, blue, white; +}; + +/** + * Luminance range and reference white luminance level, in cd/m². + */ +struct wlr_color_luminances { + float min, max, reference; +}; + /** * A color transformation formula, which maps a linear color space with * sRGB primaries to an output color space. @@ -36,15 +128,30 @@ struct wlr_color_transform *wlr_color_transform_init_linear_to_icc( const void *data, size_t size); /** - * Initialize a color transformation to apply sRGB encoding. - * Returns NULL on failure. + * Initialize a color transformation to apply EOTF⁻¹ encoding. Returns + * NULL on failure. */ -struct wlr_color_transform *wlr_color_transform_init_srgb(void); +struct wlr_color_transform *wlr_color_transform_init_linear_to_inverse_eotf( + enum wlr_color_transfer_function tf); + +/** + * Initialize a color transformation to apply three 1D look-up tables. dim + * is the number of elements in each individual LUT. Returns NULL on failure. + */ +struct wlr_color_transform *wlr_color_transform_init_lut_3x1d(size_t dim, + const uint16_t *r, const uint16_t *g, const uint16_t *b); + +/** + * Initialize a color transformation to apply a sequence of color transforms + * one after another. + */ +struct wlr_color_transform *wlr_color_transform_init_pipeline( + struct wlr_color_transform **transforms, size_t len); /** * Increase the reference count of the color transform by 1. */ -void wlr_color_transform_ref(struct wlr_color_transform *tr); +struct wlr_color_transform *wlr_color_transform_ref(struct wlr_color_transform *tr); /** * Reduce the reference count of the color transform by 1; freeing it and @@ -52,4 +159,10 @@ void wlr_color_transform_ref(struct wlr_color_transform *tr); */ void wlr_color_transform_unref(struct wlr_color_transform *tr); +/** + * Evaluate a color transform for a given RGB triplet. + */ +void wlr_color_transform_eval(struct wlr_color_transform *tr, + float out[static 3], const float in[static 3]); + #endif diff --git a/include/wlr/render/drm_format_set.h b/include/wlr/render/drm_format_set.h index cc38bbd23..16dcfe9fd 100644 --- a/include/wlr/render/drm_format_set.h +++ b/include/wlr/render/drm_format_set.h @@ -69,6 +69,9 @@ void wlr_drm_format_set_finish(struct wlr_drm_format_set *set); const struct wlr_drm_format *wlr_drm_format_set_get( const struct wlr_drm_format_set *set, uint32_t format); +bool wlr_drm_format_set_remove(struct wlr_drm_format_set *set, uint32_t format, + uint64_t modifier); + bool wlr_drm_format_set_has(const struct wlr_drm_format_set *set, uint32_t format, uint64_t modifier); diff --git a/include/wlr/render/drm_syncobj.h b/include/wlr/render/drm_syncobj.h index be3dce2d7..deef72dc9 100644 --- a/include/wlr/render/drm_syncobj.h +++ b/include/wlr/render/drm_syncobj.h @@ -4,6 +4,7 @@ #include #include #include +#include /** * A synchronization timeline. @@ -29,20 +30,24 @@ struct wlr_drm_syncobj_timeline { int drm_fd; uint32_t handle; - // private state + struct wlr_addon_set addons; - size_t n_refs; + struct { + size_t n_refs; + } WLR_PRIVATE; }; +struct wlr_drm_syncobj_timeline_waiter; + +typedef void (*wlr_drm_syncobj_timeline_ready_callback)( + struct wlr_drm_syncobj_timeline_waiter *waiter); + struct wlr_drm_syncobj_timeline_waiter { struct { - struct wl_signal ready; - } events; - - // private state - - int ev_fd; - struct wl_event_source *event_source; + int ev_fd; + struct wl_event_source *event_source; + wlr_drm_syncobj_timeline_ready_callback callback; + } WLR_PRIVATE; }; /** @@ -62,6 +67,17 @@ struct wlr_drm_syncobj_timeline *wlr_drm_syncobj_timeline_ref(struct wlr_drm_syn * Unreference a synchronization timeline. */ void wlr_drm_syncobj_timeline_unref(struct wlr_drm_syncobj_timeline *timeline); +/** + * Export a drm_syncobj FD from a timeline. + */ +int wlr_drm_syncobj_timeline_export(struct wlr_drm_syncobj_timeline *timeline); +/** + * Transfer a point from a timeline to another. + * + * Both timelines must have been created with the same DRM FD. + */ +bool wlr_drm_syncobj_timeline_transfer(struct wlr_drm_syncobj_timeline *dst, + uint64_t dst_point, struct wlr_drm_syncobj_timeline *src, uint64_t src_point); /** * Check if a timeline point has been signalled or has materialized. * @@ -78,10 +94,12 @@ bool wlr_drm_syncobj_timeline_check(struct wlr_drm_syncobj_timeline *timeline, * Asynchronously wait for a timeline point. * * See wlr_drm_syncobj_timeline_check() for a definition of flags. + * + * A callback must be provided that will be invoked when the waiter has finished. */ bool wlr_drm_syncobj_timeline_waiter_init(struct wlr_drm_syncobj_timeline_waiter *waiter, struct wlr_drm_syncobj_timeline *timeline, uint64_t point, uint32_t flags, - struct wl_event_loop *loop); + struct wl_event_loop *loop, wlr_drm_syncobj_timeline_ready_callback callback); /** * Cancel a timeline waiter. */ diff --git a/include/wlr/render/pass.h b/include/wlr/render/pass.h index 66480f7f7..1785ee562 100644 --- a/include/wlr/render/pass.h +++ b/include/wlr/render/pass.h @@ -12,6 +12,7 @@ #include #include #include +#include #include struct wlr_renderer; @@ -30,9 +31,23 @@ struct wlr_render_timer; struct wlr_buffer_pass_options { /* Timer to measure the duration of the render pass */ struct wlr_render_timer *timer; - /* Color transform to apply to the output of the render pass, - * leave NULL to indicate sRGB/no custom transform */ + /* Color transform to apply to the output of the render pass. + * Leave NULL to indicate the default transform (Gamma 2.2 encoding for + * sRGB monitors) */ struct wlr_color_transform *color_transform; + /** Primaries describing the color volume of the destination buffer */ + const struct wlr_color_primaries *primaries; + + /* Signal a timeline synchronization point when the render pass completes. + * + * When a compositor provides a signal timeline, the renderer may skip + * implicit signal synchronization. + * + * Support for this feature is advertised by features.timeline in + * struct wlr_renderer. + */ + struct wlr_drm_syncobj_timeline *signal_timeline; + uint64_t signal_point; }; /** @@ -88,6 +103,21 @@ struct wlr_render_texture_options { enum wlr_scale_filter_mode filter_mode; /* Blend mode */ enum wlr_render_blend_mode blend_mode; + /* Transfer function the source texture is encoded with */ + enum wlr_color_transfer_function transfer_function; + /* Primaries describing the color volume of the source texture */ + const struct wlr_color_primaries *primaries; + + /* Wait for a timeline synchronization point before texturing. + * + * When a compositor provides a wait timeline, the renderer may skip + * implicit wait synchronization. + * + * Support for this feature is advertised by features.timeline in + * struct wlr_renderer. + */ + struct wlr_drm_syncobj_timeline *wait_timeline; + uint64_t wait_point; }; /** diff --git a/include/wlr/render/swapchain.h b/include/wlr/render/swapchain.h index cb3aee9d5..4ca1ac590 100644 --- a/include/wlr/render/swapchain.h +++ b/include/wlr/render/swapchain.h @@ -10,9 +10,10 @@ struct wlr_swapchain_slot { struct wlr_buffer *buffer; bool acquired; // waiting for release - int age; - struct wl_listener release; + struct { + struct wl_listener release; + } WLR_PRIVATE; }; struct wlr_swapchain { @@ -23,7 +24,9 @@ struct wlr_swapchain { struct wlr_swapchain_slot slots[WLR_SWAPCHAIN_CAP]; - struct wl_listener allocator_destroy; + struct { + struct wl_listener allocator_destroy; + } WLR_PRIVATE; }; struct wlr_swapchain *wlr_swapchain_create( @@ -36,21 +39,12 @@ void wlr_swapchain_destroy(struct wlr_swapchain *swapchain); * The returned buffer is locked. When the caller is done with it, they must * unlock it by calling wlr_buffer_unlock. */ -struct wlr_buffer *wlr_swapchain_acquire(struct wlr_swapchain *swapchain, - int *age); +struct wlr_buffer *wlr_swapchain_acquire(struct wlr_swapchain *swapchain); /** * Returns true if this buffer has been created by this swapchain, and false * otherwise. */ bool wlr_swapchain_has_buffer(struct wlr_swapchain *swapchain, struct wlr_buffer *buffer); -/** - * Mark the buffer as submitted for presentation. This needs to be called by - * swap chain users on frame boundaries. - * - * If the buffer hasn't been created via the swap chain, the call is ignored. - */ -void wlr_swapchain_set_buffer_submitted(struct wlr_swapchain *swapchain, - struct wlr_buffer *buffer); #endif diff --git a/include/wlr/render/wlr_renderer.h b/include/wlr/render/wlr_renderer.h index bb9a55fcf..62a1cca2d 100644 --- a/include/wlr/render/wlr_renderer.h +++ b/include/wlr/render/wlr_renderer.h @@ -41,15 +41,25 @@ struct wlr_renderer { } events; struct { + /** + * Whether color transforms are supported for input textures + */ + bool input_color_transform; /** * Does the renderer support color transforms on its output? */ bool output_color_transform; + /** + * Whether wait/signal timelines are supported. + * + * See struct wlr_drm_syncobj_timeline. + */ + bool timeline; } features; - // private state - - const struct wlr_renderer_impl *impl; + struct { + const struct wlr_renderer_impl *impl; + } WLR_PRIVATE; }; /** diff --git a/include/wlr/types/wlr_alpha_modifier_v1.h b/include/wlr/types/wlr_alpha_modifier_v1.h index e400f4e56..f70191bb2 100644 --- a/include/wlr/types/wlr_alpha_modifier_v1.h +++ b/include/wlr/types/wlr_alpha_modifier_v1.h @@ -20,9 +20,9 @@ struct wlr_alpha_modifier_surface_v1_state { struct wlr_alpha_modifier_v1 { struct wl_global *global; - // private state - - struct wl_listener display_destroy; + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_alpha_modifier_v1 *wlr_alpha_modifier_v1_create(struct wl_display *display); diff --git a/include/wlr/types/wlr_buffer.h b/include/wlr/types/wlr_buffer.h index 9a655dd2b..8fd38905e 100644 --- a/include/wlr/types/wlr_buffer.h +++ b/include/wlr/types/wlr_buffer.h @@ -17,11 +17,15 @@ struct wlr_buffer; struct wlr_renderer; +/** + * Shared-memory attributes for a buffer. + */ struct wlr_shm_attributes { int fd; - uint32_t format; - int width, height, stride; - off_t offset; + uint32_t format; // FourCC code, see DRM_FORMAT_* in + int width, height; + int stride; // Number of bytes between consecutive pixel lines + off_t offset; // Offset in bytes of the first pixel in FD }; /** @@ -105,6 +109,15 @@ bool wlr_buffer_get_shm(struct wlr_buffer *buffer, */ struct wlr_buffer *wlr_buffer_try_from_resource(struct wl_resource *resource); +/** + * Check whether a buffer is fully opaque. + * + * When true is returned, the buffer is guaranteed to be fully opaque, but the + * reverse is not true: false may be returned in cases where the buffer is fully + * opaque. + */ +bool wlr_buffer_is_opaque(struct wlr_buffer *buffer); + /** * Buffer data pointer access flags. */ @@ -130,6 +143,12 @@ enum wlr_buffer_data_ptr_access_flag { */ bool wlr_buffer_begin_data_ptr_access(struct wlr_buffer *buffer, uint32_t flags, void **data, uint32_t *format, size_t *stride); +/** + * Indicate that a pointer to a buffer's underlying memory will no longer be + * used. + * + * This function must be called after wlr_buffer_begin_data_ptr_access(). + */ void wlr_buffer_end_data_ptr_access(struct wlr_buffer *buffer); /** @@ -148,12 +167,12 @@ struct wlr_client_buffer { */ struct wlr_buffer *source; - // private state + struct { + struct wl_listener source_destroy; + struct wl_listener renderer_destroy; - struct wl_listener source_destroy; - struct wl_listener renderer_destroy; - - size_t n_ignore_locks; + size_t n_ignore_locks; + } WLR_PRIVATE; }; /** @@ -162,4 +181,29 @@ struct wlr_client_buffer { */ struct wlr_client_buffer *wlr_client_buffer_get(struct wlr_buffer *buffer); +/** + * A single-pixel buffer. Used by clients to draw solid-color rectangles. + */ +struct wlr_single_pixel_buffer_v1 { + struct wlr_buffer base; + + // Full-scale for each component is UINT32_MAX + uint32_t r, g, b, a; + + struct { + struct wl_resource *resource; + struct wl_listener release; + + // Packed little-endian DRM_FORMAT_ARGB8888. Used for data_ptr_access + uint8_t argb8888[4]; + } WLR_PRIVATE; +}; + +/** + * If the wlr_buffer is a wlr_single_pixel_buffer_v1 then unwrap it. + * Otherwise, returns NULL. + */ +struct wlr_single_pixel_buffer_v1 *wlr_single_pixel_buffer_v1_try_from_buffer( + struct wlr_buffer *buffer); + #endif diff --git a/include/wlr/types/wlr_color_management_v1.h b/include/wlr/types/wlr_color_management_v1.h new file mode 100644 index 000000000..e6cb7dfbb --- /dev/null +++ b/include/wlr/types/wlr_color_management_v1.h @@ -0,0 +1,122 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_COLOR_MANAGEMENT_V1_H +#define WLR_TYPES_WLR_COLOR_MANAGEMENT_V1_H + +#include +#include + +#include + +struct wlr_surface; + +struct wlr_image_description_v1_data { + uint32_t tf_named; // enum wp_color_manager_v1_transfer_function, zero if unset + uint32_t primaries_named; // enum wp_color_manager_v1_primaries, zero if unset + + bool has_mastering_display_primaries; + struct wlr_color_primaries mastering_display_primaries; + + bool has_mastering_luminance; + struct { + float min, max; // cd/m² + } mastering_luminance; + + uint32_t max_cll, max_fall; // cd/m², zero if unset +}; + +struct wlr_color_manager_v1_features { + bool icc_v2_v4; + bool parametric; + bool set_primaries; + bool set_tf_power; + bool set_luminances; + bool set_mastering_display_primaries; + bool extended_target_volume; + bool windows_scrgb; +}; + +struct wlr_color_manager_v1_options { + struct wlr_color_manager_v1_features features; + + const enum wp_color_manager_v1_render_intent *render_intents; + size_t render_intents_len; + + const enum wp_color_manager_v1_transfer_function *transfer_functions; + size_t transfer_functions_len; + + const enum wp_color_manager_v1_primaries *primaries; + size_t primaries_len; +}; + +struct wlr_color_manager_v1 { + struct wl_global *global; + + struct { + struct wl_signal destroy; + } events; + + struct { + struct wlr_color_manager_v1_features features; + + enum wp_color_manager_v1_render_intent *render_intents; + size_t render_intents_len; + + enum wp_color_manager_v1_transfer_function *transfer_functions; + size_t transfer_functions_len; + + enum wp_color_manager_v1_primaries *primaries; + size_t primaries_len; + + struct wl_list outputs; // wlr_color_management_output_v1.link + struct wl_list surface_feedbacks; // wlr_color_management_surface_feedback_v1.link + + uint32_t last_image_desc_identity; + + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +struct wlr_color_manager_v1 *wlr_color_manager_v1_create(struct wl_display *display, + uint32_t version, const struct wlr_color_manager_v1_options *options); + +const struct wlr_image_description_v1_data * +wlr_surface_get_image_description_v1_data(struct wlr_surface *surface); + +void wlr_color_manager_v1_set_surface_preferred_image_description( + struct wlr_color_manager_v1 *manager, struct wlr_surface *surface, + const struct wlr_image_description_v1_data *data); + +/** + * Convert a protocol transfer function to enum wlr_color_transfer_function. + * Aborts if there is no matching wlroots entry. + */ +enum wlr_color_transfer_function +wlr_color_manager_v1_transfer_function_to_wlr(enum wp_color_manager_v1_transfer_function tf); + +/** + * Convert an enum wlr_color_transfer_function value into a protocol transfer function. + */ +enum wp_color_manager_v1_transfer_function +wlr_color_manager_v1_transfer_function_from_wlr(enum wlr_color_transfer_function tf); + +/** + * Convert a protocol named primaries to enum wlr_color_named_primaries. + * Aborts if there is no matching wlroots entry. + */ +enum wlr_color_named_primaries +wlr_color_manager_v1_primaries_to_wlr(enum wp_color_manager_v1_primaries primaries); + +/** + * Convert an enum wlr_color_named_primaries value into protocol primaries. + */ +enum wp_color_manager_v1_primaries +wlr_color_manager_v1_primaries_from_wlr(enum wlr_color_named_primaries primaries); + +#endif diff --git a/include/wlr/types/wlr_color_representation_v1.h b/include/wlr/types/wlr_color_representation_v1.h new file mode 100644 index 000000000..0a1958dec --- /dev/null +++ b/include/wlr/types/wlr_color_representation_v1.h @@ -0,0 +1,93 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_COLOR_REPRESENTATION_V1_H +#define WLR_TYPES_WLR_COLOR_REPRESENTATION_V1_H + +#include +#include +#include + +struct wlr_surface; + +// Supported coefficients and range are always paired together +struct wlr_color_representation_v1_coeffs_and_range { + enum wp_color_representation_surface_v1_coefficients coeffs; + enum wp_color_representation_surface_v1_range range; +}; + +struct wlr_color_representation_manager_v1 { + struct wl_global *global; + + struct { + // Manager is being destroyed + struct wl_signal destroy; + } events; + + struct { + enum wp_color_representation_surface_v1_alpha_mode + *supported_alpha_modes; + size_t supported_alpha_modes_len; + + struct wlr_color_representation_v1_coeffs_and_range + *supported_coeffs_and_ranges; + size_t supported_coeffs_and_ranges_len; + + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +// Options used when initialising a wlr_color_representation_manager_v1 +struct wlr_color_representation_v1_options { + enum wp_color_representation_surface_v1_alpha_mode + *supported_alpha_modes; + size_t supported_alpha_modes_len; + + const struct wlr_color_representation_v1_coeffs_and_range + *supported_coeffs_and_ranges; + size_t supported_coeffs_and_ranges_len; +}; + +struct wlr_color_representation_manager_v1 *wlr_color_representation_manager_v1_create( + struct wl_display *display, uint32_t version, + const struct wlr_color_representation_v1_options *options); + +// This is all the color-representation state which can be attached to a +// surface, double-buffered and made current on commit +struct wlr_color_representation_v1_surface_state { + // The enum premultiplied_electrical has value zero and is defined + // to be the default if unspecified. + enum wp_color_representation_surface_v1_alpha_mode alpha_mode; + + // If zero then indicates unset, otherwise values correspond to + // enum wp_color_representation_surface_v1_coefficients + uint32_t coefficients; + + // If zero then indicates unset, otherwise values correspond to + // enum wp_color_representation_surface_v1_range + uint32_t range; + + // If zero then indicates unset, otherwise values correspond to + // enum wp_color_representation_surface_v1_chroma_location + uint32_t chroma_location; +}; + +// Get the current color representation state committed to a surface +const struct wlr_color_representation_v1_surface_state *wlr_color_representation_v1_get_surface_state( + struct wlr_surface *surface); + +enum wlr_alpha_mode wlr_color_representation_v1_alpha_mode_to_wlr( + enum wp_color_representation_surface_v1_alpha_mode wp_val); +enum wlr_color_encoding wlr_color_representation_v1_color_encoding_to_wlr( + enum wp_color_representation_surface_v1_coefficients wp_val); +enum wlr_color_range wlr_color_representation_v1_color_range_to_wlr( + enum wp_color_representation_surface_v1_range wp_val); +enum wlr_color_chroma_location wlr_color_representation_v1_chroma_location_to_wlr( + enum wp_color_representation_surface_v1_chroma_location wp_val); + +#endif // WLR_TYPES_WLR_COLOR_REPRESENTATION_V1_H diff --git a/include/wlr/types/wlr_compositor.h b/include/wlr/types/wlr_compositor.h index fa9e354b9..9331e8af6 100644 --- a/include/wlr/types/wlr_compositor.h +++ b/include/wlr/types/wlr_compositor.h @@ -14,10 +14,11 @@ #include #include #include -#include #include #include +struct wlr_surface; + enum wlr_surface_state_field { WLR_SURFACE_STATE_BUFFER = 1 << 0, WLR_SURFACE_STATE_SURFACE_DAMAGE = 1 << 1, @@ -97,6 +98,13 @@ struct wlr_surface_role { * such object exists. */ void (*commit)(struct wlr_surface *surface); + /** + * Called when the surface is mapped. May be NULL. + * + * If the role is represented by an object, this is only called if + * such object exists. + */ + void (*map)(struct wlr_surface *surface); /** * Called when the surface is unmapped. May be NULL. * @@ -115,8 +123,11 @@ struct wlr_surface_output { struct wlr_output *output; struct wl_list link; // wlr_surface.current_outputs - struct wl_listener bind; - struct wl_listener destroy; + + struct { + struct wl_listener bind; + struct wl_listener destroy; + } WLR_PRIVATE; }; struct wlr_surface { @@ -180,28 +191,47 @@ struct wlr_surface { struct wl_resource *role_resource; struct { + /** + * Signals that the client has sent a wl_surface.commit request. + * + * The state to be committed can be accessed in wlr_surface.pending. + * + * The commit may not be applied immediately, in which case it's marked + * as "cached" and put into a queue. See wlr_surface_lock_pending(). + */ struct wl_signal client_commit; + /** + * Signals that a commit has been applied. + * + * The new state can be accessed in wlr_surface.current. + */ struct wl_signal commit; /** - * The `map` event signals that the surface has a non-null buffer - * committed and is ready to be displayed. + * Signals that the surface has a non-null buffer committed and is + * ready to be displayed. */ struct wl_signal map; /** - * The `unmap` event signals that the surface shouldn't be displayed - * anymore. This can happen when a null buffer is committed, - * the associated role object is destroyed, or when the role-specific - * conditions for the surface to be mapped no longer apply. + * Signals that the surface shouldn't be displayed anymore. This can + * happen when a null buffer is committed, the associated role object + * is destroyed, or when the role-specific conditions for the surface + * to be mapped no longer apply. */ struct wl_signal unmap; /** + * Signals that a new child sub-surface has been added. + * * Note: unlike other new_* signals, new_subsurface is emitted when * the subsurface is added to the parent surface's current state, * not when the object is created. */ struct wl_signal new_subsurface; // struct wlr_subsurface + + /** + * Signals that the surface is being destroyed. + */ struct wl_signal destroy; } events; @@ -210,33 +240,33 @@ struct wlr_surface { struct wlr_addon_set addons; void *data; - // private state - - struct wl_listener role_resource_destroy; - struct { - int32_t scale; - enum wl_output_transform transform; - int width, height; - int buffer_width, buffer_height; - } previous; + struct wl_listener role_resource_destroy; - bool unmap_commit; + struct { + int32_t scale; + enum wl_output_transform transform; + int width, height; + int buffer_width, buffer_height; + } previous; - bool opaque; + bool unmap_commit; - bool handling_commit; - bool pending_rejected; + bool opaque; - int32_t preferred_buffer_scale; - bool preferred_buffer_transform_sent; - enum wl_output_transform preferred_buffer_transform; + bool handling_commit; + bool pending_rejected; - struct wl_list synced; // wlr_surface_synced.link - size_t synced_len; + int32_t preferred_buffer_scale; + bool preferred_buffer_transform_sent; + enum wl_output_transform preferred_buffer_transform; - struct wl_resource *pending_buffer_resource; - struct wl_listener pending_buffer_resource_destroy; + struct wl_list synced; // wlr_surface_synced.link + size_t synced_len; + + struct wl_resource *pending_buffer_resource; + struct wl_listener pending_buffer_resource_destroy; + } WLR_PRIVATE; }; struct wlr_renderer; @@ -245,13 +275,15 @@ struct wlr_compositor { struct wl_global *global; struct wlr_renderer *renderer; // may be NULL - struct wl_listener display_destroy; - struct wl_listener renderer_destroy; - struct { struct wl_signal new_surface; struct wl_signal destroy; } events; + + struct { + struct wl_listener display_destroy; + struct wl_listener renderer_destroy; + } WLR_PRIVATE; }; typedef void (*wlr_surface_iterator_func_t)(struct wlr_surface *surface, @@ -381,7 +413,7 @@ void wlr_surface_send_frame_done(struct wlr_surface *surface, * surface coordinates. * X and y may be negative, if there are subsurfaces with negative position. */ -void wlr_surface_get_extends(struct wlr_surface *surface, struct wlr_box *box); +void wlr_surface_get_extents(struct wlr_surface *surface, struct wlr_box *box); /** * Get the struct wlr_surface corresponding to a wl_surface resource. @@ -453,6 +485,8 @@ void wlr_surface_set_preferred_buffer_scale(struct wlr_surface *surface, void wlr_surface_set_preferred_buffer_transform(struct wlr_surface *surface, enum wl_output_transform transform); +struct wlr_surface_synced; + /** * Implementation for struct wlr_surface_synced. * @@ -469,6 +503,11 @@ struct wlr_surface_synced_impl { void (*finish_state)(void *state); // Move a state. If NULL, memcpy() is used. void (*move_state)(void *dst, void *src); + + // Called when the state is committed. If NULL, this is a no-op. + // If an object is a surface role object which has state synchronized with + // the surface state, the role commit hook should be preferred over this. + void (*commit)(struct wlr_surface_synced *synced); }; /** diff --git a/include/wlr/types/wlr_content_type_v1.h b/include/wlr/types/wlr_content_type_v1.h index 6fa13d9be..1bea899eb 100644 --- a/include/wlr/types/wlr_content_type_v1.h +++ b/include/wlr/types/wlr_content_type_v1.h @@ -10,7 +10,7 @@ #define WLR_TYPES_WLR_CONTENT_TYPE_V1_H #include -#include "content-type-v1-protocol.h" +#include struct wlr_surface; @@ -23,9 +23,9 @@ struct wlr_content_type_manager_v1 { void *data; - // private state - - struct wl_listener display_destroy; + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_content_type_manager_v1 *wlr_content_type_manager_v1_create( diff --git a/include/wlr/types/wlr_cursor.h b/include/wlr/types/wlr_cursor.h index 57d6e508d..041f735cf 100644 --- a/include/wlr/types/wlr_cursor.h +++ b/include/wlr/types/wlr_cursor.h @@ -11,9 +11,9 @@ #include #include -#include struct wlr_input_device; +struct wlr_surface; struct wlr_xcursor_manager; /** diff --git a/include/wlr/types/wlr_cursor_shape_v1.h b/include/wlr/types/wlr_cursor_shape_v1.h index 55818c861..d0a21aeb9 100644 --- a/include/wlr/types/wlr_cursor_shape_v1.h +++ b/include/wlr/types/wlr_cursor_shape_v1.h @@ -10,7 +10,7 @@ #define WLR_TYPES_WLR_CURSOR_SHAPE_V1_H #include -#include "cursor-shape-v1-protocol.h" +#include /** * Manager for the cursor-shape-v1 protocol. @@ -28,9 +28,9 @@ struct wlr_cursor_shape_manager_v1 { void *data; - // private state - - struct wl_listener display_destroy; + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; enum wlr_cursor_shape_manager_v1_device_type { diff --git a/include/wlr/types/wlr_damage_ring.h b/include/wlr/types/wlr_damage_ring.h index 203d919f3..cc2eaa0f2 100644 --- a/include/wlr/types/wlr_damage_ring.h +++ b/include/wlr/types/wlr_damage_ring.h @@ -15,32 +15,27 @@ #include #include -/* For triple buffering, a history of two frames is required. */ -#define WLR_DAMAGE_RING_PREVIOUS_LEN 2 - struct wlr_box; struct wlr_damage_ring_buffer { struct wlr_buffer *buffer; - struct wl_listener destroy; pixman_region32_t damage; struct wlr_damage_ring *ring; struct wl_list link; // wlr_damage_ring.buffers + + struct { + struct wl_listener destroy; + } WLR_PRIVATE; }; struct wlr_damage_ring { - int32_t width, height; - // Difference between the current buffer and the previous one pixman_region32_t current; - // private state - - pixman_region32_t previous[WLR_DAMAGE_RING_PREVIOUS_LEN]; - size_t previous_idx; - - struct wl_list buffers; // wlr_damage_ring_buffer.link + struct { + struct wl_list buffers; // wlr_damage_ring_buffer.link + } WLR_PRIVATE; }; void wlr_damage_ring_init(struct wlr_damage_ring *ring); @@ -48,30 +43,17 @@ void wlr_damage_ring_init(struct wlr_damage_ring *ring); void wlr_damage_ring_finish(struct wlr_damage_ring *ring); /** - * Set ring bounds and damage the ring fully. - * - * Next time damage will be added, it will be cropped to the ring bounds. - * If at least one of the dimensions is 0, bounds are removed. - * - * By default, a damage ring doesn't have bounds. + * Add a region to the current damage. The region must be in the buffer-local + * coordinate space. */ -void wlr_damage_ring_set_bounds(struct wlr_damage_ring *ring, - int32_t width, int32_t height); - -/** - * Add a region to the current damage. - * - * Returns true if the region intersects the ring bounds, false otherwise. - */ -bool wlr_damage_ring_add(struct wlr_damage_ring *ring, +void wlr_damage_ring_add(struct wlr_damage_ring *ring, const pixman_region32_t *damage); /** - * Add a box to the current damage. - * - * Returns true if the box intersects the ring bounds, false otherwise. + * Add a box to the current damage. The box must be in the buffer-local + * coordinate space. */ -bool wlr_damage_ring_add_box(struct wlr_damage_ring *ring, +void wlr_damage_ring_add_box(struct wlr_damage_ring *ring, const struct wlr_box *box); /** @@ -79,20 +61,6 @@ bool wlr_damage_ring_add_box(struct wlr_damage_ring *ring, */ void wlr_damage_ring_add_whole(struct wlr_damage_ring *ring); -/** - * Rotate the damage ring. This needs to be called after using the accumulated - * damage, e.g. after rendering to an output's back buffer. - */ -void wlr_damage_ring_rotate(struct wlr_damage_ring *ring); - -/** - * Get accumulated damage, which is the difference between the current buffer - * and the buffer with age of buffer_age; in context of rendering, this is - * the region that needs to be redrawn. - */ -void wlr_damage_ring_get_buffer_damage(struct wlr_damage_ring *ring, - int buffer_age, pixman_region32_t *damage); - /** * Get accumulated buffer damage and rotate the damage ring. * @@ -102,6 +70,8 @@ void wlr_damage_ring_get_buffer_damage(struct wlr_damage_ring *ring, * * Users should damage the ring if an error occurs while rendering or * submitting the new buffer to the backend. + * + * The returned damage will be in the buffer-local coordinate space. */ void wlr_damage_ring_rotate_buffer(struct wlr_damage_ring *ring, struct wlr_buffer *buffer, pixman_region32_t *damage); diff --git a/include/wlr/types/wlr_data_control_v1.h b/include/wlr/types/wlr_data_control_v1.h index 4435f14a2..4dd642bfe 100644 --- a/include/wlr/types/wlr_data_control_v1.h +++ b/include/wlr/types/wlr_data_control_v1.h @@ -12,6 +12,12 @@ #include #include +/** + * Deprecated: this protocol is legacy and superseded by ext-data-control-v1. + * The implementation will be dropped in a future wlroots version. + * + * Consider using `wlr_ext_data_control_manager_v1` as a replacement. + */ struct wlr_data_control_manager_v1 { struct wl_global *global; struct wl_list devices; // wlr_data_control_device_v1.link @@ -21,7 +27,9 @@ struct wlr_data_control_manager_v1 { struct wl_signal new_device; // wlr_data_control_device_v1 } events; - struct wl_listener display_destroy; + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_data_control_device_v1 { @@ -33,9 +41,11 @@ struct wlr_data_control_device_v1 { struct wl_resource *selection_offer_resource; // current selection offer struct wl_resource *primary_selection_offer_resource; // current primary selection offer - struct wl_listener seat_destroy; - struct wl_listener seat_set_selection; - struct wl_listener seat_set_primary_selection; + struct { + struct wl_listener seat_destroy; + struct wl_listener seat_set_selection; + struct wl_listener seat_set_primary_selection; + } WLR_PRIVATE; }; struct wlr_data_control_manager_v1 *wlr_data_control_manager_v1_create( diff --git a/include/wlr/types/wlr_data_device.h b/include/wlr/types/wlr_data_device.h index 85d661a77..c36b1a5cb 100644 --- a/include/wlr/types/wlr_data_device.h +++ b/include/wlr/types/wlr_data_device.h @@ -16,13 +16,15 @@ struct wlr_data_device_manager { struct wl_global *global; struct wl_list data_sources; - struct wl_listener display_destroy; - struct { struct wl_signal destroy; } events; void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; enum wlr_data_offer_type { @@ -40,7 +42,9 @@ struct wlr_data_offer { enum wl_data_device_manager_dnd_action preferred_action; bool in_ask; - struct wl_listener source_destroy; + struct { + struct wl_listener source_destroy; + } WLR_PRIVATE; }; /** @@ -89,9 +93,11 @@ struct wlr_drag_icon { struct wl_signal destroy; } events; - struct wl_listener surface_destroy; - void *data; + + struct { + struct wl_listener surface_destroy; + } WLR_PRIVATE; }; enum wlr_drag_grab_type { @@ -124,11 +130,14 @@ struct wlr_drag { struct wl_signal destroy; } events; - struct wl_listener source_destroy; - struct wl_listener seat_client_destroy; - struct wl_listener icon_destroy; - void *data; + + struct { + struct wl_listener source_destroy; + struct wl_listener seat_client_destroy; + struct wl_listener focus_destroy; + struct wl_listener icon_destroy; + } WLR_PRIVATE; }; struct wlr_drag_motion_event { diff --git a/include/wlr/types/wlr_drm.h b/include/wlr/types/wlr_drm.h index b2a5ce5a1..62dfa04f5 100644 --- a/include/wlr/types/wlr_drm.h +++ b/include/wlr/types/wlr_drm.h @@ -21,7 +21,9 @@ struct wlr_drm_buffer { struct wl_resource *resource; // can be NULL if the client destroyed it struct wlr_dmabuf_attributes dmabuf; - struct wl_listener release; + struct { + struct wl_listener release; + } WLR_PRIVATE; }; /** @@ -40,12 +42,12 @@ struct wlr_drm { struct wl_signal destroy; } events; - // private state + struct { + char *node_name; + struct wlr_drm_format_set formats; - char *node_name; - struct wlr_drm_format_set formats; - - struct wl_listener display_destroy; + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_drm_buffer *wlr_drm_buffer_try_from_resource( diff --git a/include/wlr/types/wlr_drm_lease_v1.h b/include/wlr/types/wlr_drm_lease_v1.h index cda02a10f..b29a5f6dc 100644 --- a/include/wlr/types/wlr_drm_lease_v1.h +++ b/include/wlr/types/wlr_drm_lease_v1.h @@ -18,9 +18,10 @@ struct wlr_drm_lease_v1_manager { struct wl_list devices; // wlr_drm_lease_device_v1.link struct wl_display *display; - struct wl_listener display_destroy; struct { + struct wl_signal destroy; + /** * Upon receiving this signal, call * wlr_drm_lease_device_v1_grant_lease_request() to grant a lease of the @@ -29,6 +30,10 @@ struct wlr_drm_lease_v1_manager { */ struct wl_signal request; } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_drm_lease_device_v1 { @@ -43,9 +48,11 @@ struct wlr_drm_lease_device_v1 { struct wl_list requests; // wlr_drm_lease_request_v1.link struct wl_list link; // wlr_drm_lease_v1_manager.devices - struct wl_listener backend_destroy; - void *data; + + struct { + struct wl_listener backend_destroy; + } WLR_PRIVATE; }; struct wlr_drm_lease_v1; @@ -55,12 +62,12 @@ struct wlr_drm_lease_connector_v1 { struct wlr_output *output; struct wlr_drm_lease_device_v1 *device; - /** NULL if no client is currently leasing this connector */ - struct wlr_drm_lease_v1 *active_lease; - - struct wl_listener destroy; struct wl_list link; // wlr_drm_lease_device_v1.connectors + + struct { + struct wl_listener destroy; + } WLR_PRIVATE; }; struct wlr_drm_lease_request_v1 { @@ -84,14 +91,13 @@ struct wlr_drm_lease_v1 { struct wlr_drm_lease_device_v1 *device; - struct wlr_drm_lease_connector_v1 **connectors; - size_t n_connectors; - struct wl_list link; // wlr_drm_lease_device_v1.leases - struct wl_listener destroy; - void *data; + + struct { + struct wl_listener destroy; + } WLR_PRIVATE; }; /** diff --git a/include/wlr/types/wlr_export_dmabuf_v1.h b/include/wlr/types/wlr_export_dmabuf_v1.h index ba2d8bd85..2c5a24525 100644 --- a/include/wlr/types/wlr_export_dmabuf_v1.h +++ b/include/wlr/types/wlr_export_dmabuf_v1.h @@ -17,11 +17,13 @@ struct wlr_export_dmabuf_manager_v1 { struct wl_global *global; struct wl_list frames; // wlr_export_dmabuf_frame_v1.link - struct wl_listener display_destroy; - struct { struct wl_signal destroy; } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_export_dmabuf_frame_v1 { @@ -33,8 +35,10 @@ struct wlr_export_dmabuf_frame_v1 { bool cursor_locked; - struct wl_listener output_commit; - struct wl_listener output_destroy; + struct { + struct wl_listener output_commit; + struct wl_listener output_destroy; + } WLR_PRIVATE; }; struct wlr_export_dmabuf_manager_v1 *wlr_export_dmabuf_manager_v1_create( diff --git a/include/wlr/types/wlr_ext_data_control_v1.h b/include/wlr/types/wlr_ext_data_control_v1.h new file mode 100644 index 000000000..7cdc7e95b --- /dev/null +++ b/include/wlr/types/wlr_ext_data_control_v1.h @@ -0,0 +1,51 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_EXT_DATA_CONTROL_V1_H +#define WLR_TYPES_WLR_EXT_DATA_CONTROL_V1_H + +#include +#include + +struct wlr_ext_data_control_manager_v1 { + struct wl_global *global; + struct wl_list devices; // wlr_ext_data_control_device_v1.link + + struct { + struct wl_signal destroy; + struct wl_signal new_device; // wlr_ext_data_control_device_v1 + } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +struct wlr_ext_data_control_device_v1 { + struct wl_resource *resource; + struct wlr_ext_data_control_manager_v1 *manager; + struct wl_list link; // wlr_ext_data_control_manager_v1.devices + + struct wlr_seat *seat; + struct wl_resource *selection_offer_resource; // current selection offer + struct wl_resource *primary_selection_offer_resource; // current primary selection offer + + struct { + struct wl_listener seat_destroy; + struct wl_listener seat_set_selection; + struct wl_listener seat_set_primary_selection; + } WLR_PRIVATE; +}; + +struct wlr_ext_data_control_manager_v1 *wlr_ext_data_control_manager_v1_create( + struct wl_display *display, uint32_t version); + +void wlr_ext_data_control_device_v1_destroy( + struct wlr_ext_data_control_device_v1 *device); + +#endif diff --git a/include/wlr/types/wlr_ext_foreign_toplevel_list_v1.h b/include/wlr/types/wlr_ext_foreign_toplevel_list_v1.h index a5ba9d384..902418eaf 100644 --- a/include/wlr/types/wlr_ext_foreign_toplevel_list_v1.h +++ b/include/wlr/types/wlr_ext_foreign_toplevel_list_v1.h @@ -16,13 +16,15 @@ struct wlr_ext_foreign_toplevel_list_v1 { struct wl_list resources; // wl_resource_get_link() struct wl_list toplevels; // ext_foreign_toplevel_handle_v1.link - struct wl_listener display_destroy; - struct { struct wl_signal destroy; } events; void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_ext_foreign_toplevel_handle_v1 { @@ -64,4 +66,7 @@ void wlr_ext_foreign_toplevel_handle_v1_update_state( struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, const struct wlr_ext_foreign_toplevel_handle_v1_state *state); +struct wlr_ext_foreign_toplevel_handle_v1 *wlr_ext_foreign_toplevel_handle_v1_from_resource( + struct wl_resource *resource); + #endif diff --git a/include/wlr/types/wlr_ext_image_capture_source_v1.h b/include/wlr/types/wlr_ext_image_capture_source_v1.h new file mode 100644 index 000000000..e38f68795 --- /dev/null +++ b/include/wlr/types/wlr_ext_image_capture_source_v1.h @@ -0,0 +1,134 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_EXT_IMAGE_CAPTURE_SOURCE_V1_H +#define WLR_TYPES_WLR_EXT_IMAGE_CAPTURE_SOURCE_V1_H + +#include +#include +#include + +struct wlr_scene_node; +struct wlr_allocator; +struct wlr_renderer; + +/** + * A screen capture source. + * + * When the size, device or formats change, the constraints_update event is + * emitted. + * + * The device and formats advertised are suitable for copying into a + * struct wlr_buffer. + */ +struct wlr_ext_image_capture_source_v1 { + const struct wlr_ext_image_capture_source_v1_interface *impl; + struct wl_list resources; // wl_resource_get_link() + + uint32_t width, height; + + uint32_t *shm_formats; + size_t shm_formats_len; + + dev_t dmabuf_device; + struct wlr_drm_format_set dmabuf_formats; + + struct { + struct wl_signal constraints_update; + struct wl_signal frame; // struct wlr_ext_image_capture_source_v1_frame_event + struct wl_signal destroy; + } events; +}; + +/** + * Event indicating that the source has produced a new frame. + */ +struct wlr_ext_image_capture_source_v1_frame_event { + const pixman_region32_t *damage; +}; + +/** + * A cursor capture source. + * + * Provides additional cursor-specific functionality on top of + * struct wlr_ext_image_capture_source_v1. + */ +struct wlr_ext_image_capture_source_v1_cursor { + struct wlr_ext_image_capture_source_v1 base; + + bool entered; + int32_t x, y; + struct { + int32_t x, y; + } hotspot; + + struct { + struct wl_signal update; + } events; +}; + +/** + * Interface exposing one screen capture source per output. + */ +struct wlr_ext_output_image_capture_source_manager_v1 { + struct wl_global *global; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +/** + * Interface exposing one screen capture source per foreign toplevel. + */ +struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 { + struct wl_global *global; + + struct { + struct wl_signal destroy; + struct wl_signal new_request; // struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request + } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request { + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel_handle; + struct wl_client *client; + + struct { + uint32_t new_id; + } WLR_PRIVATE; +}; + +/** + * Obtain a struct wlr_ext_image_capture_source_v1 from an ext_image_capture_source_v1 + * resource. + * + * Asserts that the resource has the correct type. Returns NULL if the resource + * is inert. + */ +struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_from_resource(struct wl_resource *resource); + +struct wlr_ext_output_image_capture_source_manager_v1 *wlr_ext_output_image_capture_source_manager_v1_create( + struct wl_display *display, uint32_t version); + +struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 * +wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create(struct wl_display *display, uint32_t version); + +bool wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_accept( + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request *request, + struct wlr_ext_image_capture_source_v1 *source); + +struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_create_with_scene_node( + struct wlr_scene_node *node, struct wl_event_loop *event_loop, + struct wlr_allocator *allocator, struct wlr_renderer *renderer); + +#endif diff --git a/include/wlr/types/wlr_ext_image_copy_capture_v1.h b/include/wlr/types/wlr_ext_image_copy_capture_v1.h new file mode 100644 index 000000000..9b1ab3df1 --- /dev/null +++ b/include/wlr/types/wlr_ext_image_copy_capture_v1.h @@ -0,0 +1,65 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_EXT_IMAGE_COPY_CAPTURE_V1_H +#define WLR_TYPES_WLR_EXT_IMAGE_COPY_CAPTURE_V1_H + +#include +#include +#include +#include + +struct wlr_renderer; + +struct wlr_ext_image_copy_capture_manager_v1 { + struct wl_global *global; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +struct wlr_ext_image_copy_capture_frame_v1 { + struct wl_resource *resource; + bool capturing; + struct wlr_buffer *buffer; + pixman_region32_t buffer_damage; + + struct { + struct wl_signal destroy; + } events; + + struct { + struct wlr_ext_image_copy_capture_session_v1 *session; + } WLR_PRIVATE; +}; + +struct wlr_ext_image_copy_capture_manager_v1 *wlr_ext_image_copy_capture_manager_v1_create( + struct wl_display *display, uint32_t version); + +/** + * Notify the client that the frame is ready. + * + * This function destroys the frame. + */ +void wlr_ext_image_copy_capture_frame_v1_ready(struct wlr_ext_image_copy_capture_frame_v1 *frame, + enum wl_output_transform transform, const struct timespec *presentation_time); +/** + * Notify the client that the frame has failed. + * + * This function destroys the frame. + */ +void wlr_ext_image_copy_capture_frame_v1_fail(struct wlr_ext_image_copy_capture_frame_v1 *frame, + enum ext_image_copy_capture_frame_v1_failure_reason reason); +/** + * Copy a struct wlr_buffer into the client-provided buffer for the frame. + */ +bool wlr_ext_image_copy_capture_frame_v1_copy_buffer(struct wlr_ext_image_copy_capture_frame_v1 *frame, + struct wlr_buffer *src, struct wlr_renderer *renderer); + +#endif diff --git a/include/wlr/types/wlr_fixes.h b/include/wlr/types/wlr_fixes.h new file mode 100644 index 000000000..b227f7e28 --- /dev/null +++ b/include/wlr/types/wlr_fixes.h @@ -0,0 +1,28 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_FIXES_H +#define WLR_TYPES_WLR_FIXES_H + +#include + +struct wlr_fixes { + struct wl_global *global; + + struct { + struct wl_signal destroy; + } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +struct wlr_fixes *wlr_fixes_create(struct wl_display *display, uint32_t version); + +#endif diff --git a/include/wlr/types/wlr_foreign_toplevel_management_v1.h b/include/wlr/types/wlr_foreign_toplevel_management_v1.h index c4abcb3dd..d21c5fc70 100644 --- a/include/wlr/types/wlr_foreign_toplevel_management_v1.h +++ b/include/wlr/types/wlr_foreign_toplevel_management_v1.h @@ -18,13 +18,15 @@ struct wlr_foreign_toplevel_manager_v1 { struct wl_list resources; // wl_resource_get_link() struct wl_list toplevels; // wlr_foreign_toplevel_handle_v1.link - struct wl_listener display_destroy; - struct { struct wl_signal destroy; } events; void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; enum wlr_foreign_toplevel_handle_v1_state { @@ -39,10 +41,10 @@ struct wlr_foreign_toplevel_handle_v1_output { struct wlr_output *output; struct wlr_foreign_toplevel_handle_v1 *toplevel; - // private state - - struct wl_listener output_bind; - struct wl_listener output_destroy; + struct { + struct wl_listener output_bind; + struct wl_listener output_destroy; + } WLR_PRIVATE; }; struct wlr_foreign_toplevel_handle_v1 { diff --git a/include/wlr/types/wlr_fractional_scale_v1.h b/include/wlr/types/wlr_fractional_scale_v1.h index 9126360d9..09214eb83 100644 --- a/include/wlr/types/wlr_fractional_scale_v1.h +++ b/include/wlr/types/wlr_fractional_scale_v1.h @@ -20,9 +20,9 @@ struct wlr_fractional_scale_manager_v1 { struct wl_signal destroy; } events; - // private state - - struct wl_listener display_destroy; + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; void wlr_fractional_scale_v1_notify_scale( diff --git a/include/wlr/types/wlr_fullscreen_shell_v1.h b/include/wlr/types/wlr_fullscreen_shell_v1.h deleted file mode 100644 index 54e4c0550..000000000 --- a/include/wlr/types/wlr_fullscreen_shell_v1.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * This is a deprecated interface of wlroots. It will be removed in a future - * version. - */ - -#ifndef WLR_USE_UNSTABLE -#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" -#endif - -#ifndef WLR_TYPES_WLR_FULLSCREEN_SHELL_V1_H -#define WLR_TYPES_WLR_FULLSCREEN_SHELL_V1_H - -#include -#include "fullscreen-shell-unstable-v1-protocol.h" - -struct wlr_fullscreen_shell_v1 { - struct wl_global *global; - - struct { - struct wl_signal destroy; - // struct wlr_fullscreen_shell_v1_present_surface_event - struct wl_signal present_surface; - } events; - - struct wl_listener display_destroy; - - void *data; -}; - -struct wlr_fullscreen_shell_v1_present_surface_event { - struct wl_client *client; - struct wlr_surface *surface; // can be NULL - enum zwp_fullscreen_shell_v1_present_method method; - struct wlr_output *output; // can be NULL -}; - -struct wlr_fullscreen_shell_v1 *wlr_fullscreen_shell_v1_create( - struct wl_display *display); - -#endif diff --git a/include/wlr/types/wlr_gamma_control_v1.h b/include/wlr/types/wlr_gamma_control_v1.h index 48fce09f6..7a6df98a9 100644 --- a/include/wlr/types/wlr_gamma_control_v1.h +++ b/include/wlr/types/wlr_gamma_control_v1.h @@ -10,7 +10,10 @@ struct wlr_gamma_control_manager_v1 { struct wl_global *global; struct wl_list controls; // wlr_gamma_control_v1.link - struct wl_listener display_destroy; + // Fallback to use when an struct wlr_output doesn't support gamma LUTs. + // Can be used to apply gamma LUTs via a struct wlr_renderer. Leave zero to + // indicate that the fallback is unsupported. + size_t fallback_gamma_size; struct { struct wl_signal destroy; @@ -18,6 +21,10 @@ struct wlr_gamma_control_manager_v1 { } events; void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_gamma_control_manager_v1_set_gamma_event { @@ -34,9 +41,11 @@ struct wlr_gamma_control_v1 { uint16_t *table; size_t ramp_size; - struct wl_listener output_destroy_listener; - void *data; + + struct { + struct wl_listener output_destroy_listener; + } WLR_PRIVATE; }; struct wlr_gamma_control_manager_v1 *wlr_gamma_control_manager_v1_create( @@ -45,6 +54,8 @@ struct wlr_gamma_control_v1 *wlr_gamma_control_manager_v1_get_control( struct wlr_gamma_control_manager_v1 *manager, struct wlr_output *output); bool wlr_gamma_control_v1_apply(struct wlr_gamma_control_v1 *gamma_control, struct wlr_output_state *output_state); +struct wlr_color_transform *wlr_gamma_control_v1_get_color_transform( + struct wlr_gamma_control_v1 *gamma_control); void wlr_gamma_control_v1_send_failed_and_destroy(struct wlr_gamma_control_v1 *gamma_control); #endif diff --git a/include/wlr/types/wlr_idle_inhibit_v1.h b/include/wlr/types/wlr_idle_inhibit_v1.h index 596fbe889..9a42d4fdf 100644 --- a/include/wlr/types/wlr_idle_inhibit_v1.h +++ b/include/wlr/types/wlr_idle_inhibit_v1.h @@ -27,20 +27,21 @@ struct wlr_idle_inhibit_manager_v1 { struct wl_list inhibitors; // wlr_idle_inhibit_inhibitor_v1.link struct wl_global *global; - struct wl_listener display_destroy; - struct { struct wl_signal new_inhibitor; // struct wlr_idle_inhibitor_v1 struct wl_signal destroy; } events; void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_idle_inhibitor_v1 { struct wlr_surface *surface; struct wl_resource *resource; - struct wl_listener surface_destroy; struct wl_list link; // wlr_idle_inhibit_manager_v1.inhibitors @@ -49,6 +50,10 @@ struct wlr_idle_inhibitor_v1 { } events; void *data; + + struct { + struct wl_listener surface_destroy; + } WLR_PRIVATE; }; struct wlr_idle_inhibit_manager_v1 *wlr_idle_inhibit_v1_create(struct wl_display *display); diff --git a/include/wlr/types/wlr_idle_notify_v1.h b/include/wlr/types/wlr_idle_notify_v1.h index 8dea08045..508c0cb77 100644 --- a/include/wlr/types/wlr_idle_notify_v1.h +++ b/include/wlr/types/wlr_idle_notify_v1.h @@ -19,12 +19,12 @@ struct wlr_seat; struct wlr_idle_notifier_v1 { struct wl_global *global; - // private state + struct { + bool inhibited; + struct wl_list notifications; // wlr_idle_notification_v1.link - bool inhibited; - struct wl_list notifications; // wlr_idle_notification_v1.link - - struct wl_listener display_destroy; + struct wl_listener display_destroy; + } WLR_PRIVATE; }; diff --git a/include/wlr/types/wlr_input_method_v2.h b/include/wlr/types/wlr_input_method_v2.h index fd299cc85..aaafbf5f2 100644 --- a/include/wlr/types/wlr_input_method_v2.h +++ b/include/wlr/types/wlr_input_method_v2.h @@ -9,7 +9,6 @@ #ifndef WLR_TYPES_WLR_INPUT_METHOD_V2_H #define WLR_TYPES_WLR_INPUT_METHOD_V2_H #include -#include #include #include #include @@ -48,14 +47,16 @@ struct wlr_input_method_v2 { struct wl_list link; - struct wl_listener seat_client_destroy; - struct { - struct wl_signal commit; // struct wlr_input_method_v2 + struct wl_signal commit; struct wl_signal new_popup_surface; // struct wlr_input_popup_surface_v2 struct wl_signal grab_keyboard; // struct wlr_input_method_keyboard_grab_v2 - struct wl_signal destroy; // struct wlr_input_method_v2 + struct wl_signal destroy; } events; + + struct { + struct wl_listener seat_client_destroy; + } WLR_PRIVATE; }; struct wlr_input_popup_surface_v2 { @@ -77,25 +78,29 @@ struct wlr_input_method_keyboard_grab_v2 { struct wlr_input_method_v2 *input_method; struct wlr_keyboard *keyboard; - struct wl_listener keyboard_keymap; - struct wl_listener keyboard_repeat_info; - struct wl_listener keyboard_destroy; - struct { struct wl_signal destroy; // struct wlr_input_method_keyboard_grab_v2 } events; + + struct { + struct wl_listener keyboard_keymap; + struct wl_listener keyboard_repeat_info; + struct wl_listener keyboard_destroy; + } WLR_PRIVATE; }; struct wlr_input_method_manager_v2 { struct wl_global *global; struct wl_list input_methods; // struct wlr_input_method_v2.link - struct wl_listener display_destroy; + struct { + struct wl_signal new_input_method; // struct wlr_input_method_v2 + struct wl_signal destroy; + } events; struct { - struct wl_signal input_method; // struct wlr_input_method_v2 - struct wl_signal destroy; // struct wlr_input_method_manager_v2 - } events; + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_input_method_manager_v2 *wlr_input_method_manager_v2_create( diff --git a/include/wlr/types/wlr_keyboard.h b/include/wlr/types/wlr_keyboard.h index 904649367..20a310fde 100644 --- a/include/wlr/types/wlr_keyboard.h +++ b/include/wlr/types/wlr_keyboard.h @@ -16,12 +16,14 @@ #include #include -#define WLR_LED_COUNT 3 +#define WLR_LED_COUNT 5 enum wlr_keyboard_led { WLR_LED_NUM_LOCK = 1 << 0, WLR_LED_CAPS_LOCK = 1 << 1, WLR_LED_SCROLL_LOCK = 1 << 2, + WLR_LED_COMPOSE = 1 << 3, + WLR_LED_KANA = 1 << 4, }; #define WLR_MODIFIER_COUNT 8 @@ -115,6 +117,23 @@ bool wlr_keyboard_set_keymap(struct wlr_keyboard *kb, bool wlr_keyboard_keymaps_match(struct xkb_keymap *km1, struct xkb_keymap *km2); +/** + * Interpret pointer button key symbols. + * + * Returns a button code (BTN_*) if the key symbol is a pointer button + * (XKB_KEY_Pointer_Button*), 0 otherwise. + */ +uint32_t wlr_keyboard_keysym_to_pointer_button(xkb_keysym_t keysym); + +/** + * Interpret pointer motion key symbols. + * + * Sets dx and dy to horizontal and vertical motion deltas (0, 1 or -1) if the + * key symbol is a pointer motion (XKB_KEY_Pointer_*). Otherwise, sets both dx + * and dy to 0. + */ +void wlr_keyboard_keysym_to_pointer_motion(xkb_keysym_t keysym, int *dx, int *dy); + /** * Set the keyboard repeat info. * diff --git a/include/wlr/types/wlr_keyboard_shortcuts_inhibit_v1.h b/include/wlr/types/wlr_keyboard_shortcuts_inhibit_v1.h index 946e75f4e..6c9b6889f 100644 --- a/include/wlr/types/wlr_keyboard_shortcuts_inhibit_v1.h +++ b/include/wlr/types/wlr_keyboard_shortcuts_inhibit_v1.h @@ -27,14 +27,16 @@ struct wlr_keyboard_shortcuts_inhibit_manager_v1 { struct wl_list inhibitors; struct wl_global *global; - struct wl_listener display_destroy; - struct { struct wl_signal new_inhibitor; // struct wlr_keyboard_shortcuts_inhibitor_v1 struct wl_signal destroy; } events; void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_keyboard_shortcuts_inhibitor_v1 { @@ -43,9 +45,6 @@ struct wlr_keyboard_shortcuts_inhibitor_v1 { bool active; struct wl_resource *resource; - struct wl_listener surface_destroy; - struct wl_listener seat_destroy; - // wlr_keyboard_shortcuts_inhibit_manager_v1.inhibitors struct wl_list link; @@ -54,6 +53,11 @@ struct wlr_keyboard_shortcuts_inhibitor_v1 { } events; void *data; + + struct { + struct wl_listener surface_destroy; + struct wl_listener seat_destroy; + } WLR_PRIVATE; }; /* diff --git a/include/wlr/types/wlr_layer_shell_v1.h b/include/wlr/types/wlr_layer_shell_v1.h index eaf6af428..4234aa72e 100644 --- a/include/wlr/types/wlr_layer_shell_v1.h +++ b/include/wlr/types/wlr_layer_shell_v1.h @@ -13,6 +13,7 @@ #include #include #include +#include #include "wlr-layer-shell-unstable-v1-protocol.h" /** @@ -30,8 +31,6 @@ struct wlr_layer_shell_v1 { struct wl_global *global; - struct wl_listener display_destroy; - struct { // Note: the output may be NULL. In this case, it is your // responsibility to assign an output before returning. @@ -40,6 +39,10 @@ struct wlr_layer_shell_v1 { } events; void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; enum wlr_layer_surface_v1_state_field { @@ -49,6 +52,7 @@ enum wlr_layer_surface_v1_state_field { WLR_LAYER_SURFACE_V1_STATE_MARGIN = 1 << 3, WLR_LAYER_SURFACE_V1_STATE_KEYBOARD_INTERACTIVITY = 1 << 4, WLR_LAYER_SURFACE_V1_STATE_LAYER = 1 << 5, + WLR_LAYER_SURFACE_V1_STATE_EXCLUSIVE_EDGE = 1 << 6, }; struct wlr_layer_surface_v1_state { @@ -62,6 +66,7 @@ struct wlr_layer_surface_v1_state { enum zwlr_layer_surface_v1_keyboard_interactivity keyboard_interactive; uint32_t desired_width, desired_height; enum zwlr_layer_shell_v1_layer layer; + uint32_t exclusive_edge; uint32_t configure_serial; uint32_t actual_width, actual_height; @@ -111,9 +116,9 @@ struct wlr_layer_surface_v1 { void *data; - // private state - - struct wlr_surface_synced synced; + struct { + struct wlr_surface_synced synced; + } WLR_PRIVATE; }; struct wlr_layer_shell_v1 *wlr_layer_shell_v1_create(struct wl_display *display, @@ -185,4 +190,11 @@ struct wlr_surface *wlr_layer_surface_v1_popup_surface_at( struct wlr_layer_surface_v1 *wlr_layer_surface_v1_from_resource( struct wl_resource *resource); +/** + * Get the edge the exclusive zone must be applied to. + * + * Returns WLR_EDGE_NONE if the exclusive zone is nonpositive or must not be applied. + */ +enum wlr_edges wlr_layer_surface_v1_get_exclusive_edge(struct wlr_layer_surface_v1 *surface); + #endif diff --git a/include/wlr/types/wlr_linux_dmabuf_v1.h b/include/wlr/types/wlr_linux_dmabuf_v1.h index cf967f952..2193f9141 100644 --- a/include/wlr/types/wlr_linux_dmabuf_v1.h +++ b/include/wlr/types/wlr_linux_dmabuf_v1.h @@ -24,9 +24,9 @@ struct wlr_dmabuf_v1_buffer { struct wl_resource *resource; // can be NULL if the client destroyed it struct wlr_dmabuf_attributes attributes; - // private state - - struct wl_listener release; + struct { + struct wl_listener release; + } WLR_PRIVATE; }; /** @@ -55,18 +55,18 @@ struct wlr_linux_dmabuf_v1 { struct wl_signal destroy; } events; - // private state + struct { + struct wlr_linux_dmabuf_feedback_v1_compiled *default_feedback; + struct wlr_drm_format_set default_formats; // for legacy clients + struct wl_list surfaces; // wlr_linux_dmabuf_v1_surface.link - struct wlr_linux_dmabuf_feedback_v1_compiled *default_feedback; - struct wlr_drm_format_set default_formats; // for legacy clients - struct wl_list surfaces; // wlr_linux_dmabuf_v1_surface.link + int main_device_fd; // to sanity check FDs sent by clients, -1 if unavailable - int main_device_fd; // to sanity check FDs sent by clients, -1 if unavailable + struct wl_listener display_destroy; - struct wl_listener display_destroy; - - bool (*check_dmabuf_callback)(struct wlr_dmabuf_attributes *attribs, void *data); - void *check_dmabuf_callback_data; + bool (*check_dmabuf_callback)(struct wlr_dmabuf_attributes *attribs, void *data); + void *check_dmabuf_callback_data; + } WLR_PRIVATE; }; /** diff --git a/include/wlr/types/wlr_linux_drm_syncobj_v1.h b/include/wlr/types/wlr_linux_drm_syncobj_v1.h index c6e0617b1..733350412 100644 --- a/include/wlr/types/wlr_linux_drm_syncobj_v1.h +++ b/include/wlr/types/wlr_linux_drm_syncobj_v1.h @@ -12,6 +12,9 @@ #include #include +struct wlr_buffer; +struct wlr_surface; + struct wlr_linux_drm_syncobj_surface_v1_state { struct wlr_drm_syncobj_timeline *acquire_timeline; uint64_t acquire_point; @@ -23,11 +26,11 @@ struct wlr_linux_drm_syncobj_surface_v1_state { struct wlr_linux_drm_syncobj_manager_v1 { struct wl_global *global; - // private state + struct { + int drm_fd; - int drm_fd; - - struct wl_listener display_destroy; + struct wl_listener display_destroy; + } WLR_PRIVATE; }; /** @@ -43,4 +46,13 @@ struct wlr_linux_drm_syncobj_manager_v1 *wlr_linux_drm_syncobj_manager_v1_create struct wlr_linux_drm_syncobj_surface_v1_state *wlr_linux_drm_syncobj_v1_get_surface_state( struct wlr_surface *surface); +/** + * Signal the release point when wlr_buffer.events.release is emitted. + * + * Compositors unwilling to track fine-grained commit release can call this + * helper on surface commit. + */ +bool wlr_linux_drm_syncobj_v1_state_signal_release_with_buffer( + struct wlr_linux_drm_syncobj_surface_v1_state *state, struct wlr_buffer *buffer); + #endif diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index ff675bd34..2ae11a4d3 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -14,9 +14,11 @@ #include #include #include +#include #include #include #include +#include enum wlr_output_mode_aspect_ratio { WLR_OUTPUT_MODE_ASPECT_RATIO_NONE, @@ -45,8 +47,13 @@ struct wlr_output_cursor { int32_t hotspot_x, hotspot_y; struct wlr_texture *texture; bool own_texture; - struct wl_listener renderer_destroy; + struct wlr_drm_syncobj_timeline *wait_timeline; + uint64_t wait_point; struct wl_list link; + + struct { + struct wl_listener renderer_destroy; + } WLR_PRIVATE; }; enum wlr_output_adaptive_sync_status { @@ -62,10 +69,13 @@ enum wlr_output_state_field { WLR_OUTPUT_STATE_SCALE = 1 << 4, WLR_OUTPUT_STATE_TRANSFORM = 1 << 5, WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED = 1 << 6, - WLR_OUTPUT_STATE_GAMMA_LUT = 1 << 7, - WLR_OUTPUT_STATE_RENDER_FORMAT = 1 << 8, - WLR_OUTPUT_STATE_SUBPIXEL = 1 << 9, - WLR_OUTPUT_STATE_LAYERS = 1 << 10, + WLR_OUTPUT_STATE_RENDER_FORMAT = 1 << 7, + WLR_OUTPUT_STATE_SUBPIXEL = 1 << 8, + WLR_OUTPUT_STATE_LAYERS = 1 << 9, + WLR_OUTPUT_STATE_WAIT_TIMELINE = 1 << 10, + WLR_OUTPUT_STATE_SIGNAL_TIMELINE = 1 << 11, + WLR_OUTPUT_STATE_COLOR_TRANSFORM = 1 << 12, + WLR_OUTPUT_STATE_IMAGE_DESCRIPTION = 1 << 13, }; enum wlr_output_state_mode_type { @@ -73,6 +83,30 @@ enum wlr_output_state_mode_type { WLR_OUTPUT_STATE_MODE_CUSTOM, }; +/** + * Colorimetric image description. + * + * Carries information about the color encoding used for a struct wlr_buffer. + * + * Supported primaries are advertised in wlr_output.supported_primaries. + * Supported transfer functions are advertised in + * wlr_output.supported_transfer_functions. + * + * mastering_display_primaries, mastering_luminance, max_cll and max_fall are + * optional. Luminances are given in cd/m². + */ +struct wlr_output_image_description { + enum wlr_color_named_primaries primaries; + enum wlr_color_transfer_function transfer_function; + + struct wlr_color_primaries mastering_display_primaries; + struct { + double min, max; + } mastering_luminance; + double max_cll; // max content light level + double max_fall; // max frame-average light level +}; + /** * Holds the double-buffered output state. */ @@ -90,6 +124,16 @@ struct wlr_output_state { enum wl_output_subpixel subpixel; struct wlr_buffer *buffer; + // Source crop for the buffer. If all zeros then no crop is applied. + // As usual with source crop, this is in buffer coordinates. + // Double-buffered by WLR_OUTPUT_STATE_BUFFER along with `buffer`. + struct wlr_fbox buffer_src_box; + // Destination rect to scale the buffer to (after source crop). If width + // and height are zero then the buffer is displayed at native size. The + // offset is relative to the origin of this output. Double-buffered by + // WLR_OUTPUT_STATE_BUFFER along with `buffer`. + struct wlr_box buffer_dst_box; + /* Request a tearing page-flip. When enabled, this may cause the output to * display a part of the previous buffer and a part of the current buffer at * the same time. The backend may reject the commit if a tearing page-flip @@ -104,11 +148,17 @@ struct wlr_output_state { int32_t refresh; // mHz, may be zero } custom_mode; - uint16_t *gamma_lut; - size_t gamma_lut_size; - struct wlr_output_layer_state *layers; size_t layers_len; + + struct wlr_drm_syncobj_timeline *wait_timeline; + uint64_t wait_point; + struct wlr_drm_syncobj_timeline *signal_timeline; + uint64_t signal_point; + + struct wlr_color_transform *color_transform; + + struct wlr_output_image_description *image_description; }; struct wlr_output_impl; @@ -121,8 +171,9 @@ struct wlr_render_pass; * The `frame` event will be emitted when it is a good time for the compositor * to submit a new frame. * - * To render a new frame, compositors should call wlr_output_begin_render_pass(), - * perform rendering on that render pass and finally call wlr_output_commit(). + * To render a new frame compositors should call wlr_output_begin_render_pass(), + * perform rendering on that render pass, and finally call + * wlr_output_commit_state(). */ struct wlr_output { const struct wlr_output_impl *impl; @@ -143,12 +194,16 @@ struct wlr_output { int32_t width, height; int32_t refresh; // mHz, may be zero + uint32_t supported_primaries; // bitfield of enum wlr_color_named_primaries + uint32_t supported_transfer_functions; // bitfield of enum wlr_color_transfer_function + bool enabled; float scale; enum wl_output_subpixel subpixel; enum wl_output_transform transform; enum wlr_output_adaptive_sync_status adaptive_sync_status; uint32_t render_format; + const struct wlr_output_image_description *image_description; // Indicates whether making changes to adaptive sync status is supported. // If false, changes to adaptive sync status is guaranteed to fail. If @@ -205,11 +260,15 @@ struct wlr_output { struct wlr_renderer *renderer; struct wlr_swapchain *swapchain; - struct wl_listener display_destroy; - struct wlr_addon_set addons; void *data; + + struct { + struct wl_listener display_destroy; + struct wlr_output_image_description image_description_value; + struct wlr_color_transform *color_transform; + } WLR_PRIVATE; }; struct wlr_output_event_damage { @@ -219,13 +278,13 @@ struct wlr_output_event_damage { struct wlr_output_event_precommit { struct wlr_output *output; - struct timespec *when; + struct timespec when; const struct wlr_output_state *state; }; struct wlr_output_event_commit { struct wlr_output *output; - struct timespec *when; + struct timespec when; const struct wlr_output_state *state; }; @@ -251,7 +310,7 @@ struct wlr_output_event_present { // Whether the frame was presented at all. bool presented; // Time when the content update turned into light the first time. - struct timespec *when; + struct timespec when; // Vertical retrace counter. Zero if unavailable. unsigned seq; // Prediction of how many nanoseconds after `when` the very next output @@ -270,8 +329,6 @@ struct wlr_output_event_request_state { const struct wlr_output_state *state; }; -struct wlr_surface; - void wlr_output_create_global(struct wlr_output *output, struct wl_display *display); void wlr_output_destroy_global(struct wlr_output *output); /** @@ -280,7 +337,7 @@ void wlr_output_destroy_global(struct wlr_output *output); * the allocator and renderer to different values. * * Call this function prior to any call to wlr_output_begin_render_pass(), - * wlr_output_commit() or wlr_output_cursor_create(). + * wlr_output_commit_state() or wlr_output_cursor_create(). * * The buffer capabilities of the provided must match the capabilities of the * output's backend. Returns false otherwise. @@ -370,13 +427,7 @@ void wlr_output_lock_attach_render(struct wlr_output *output, bool lock); */ void wlr_output_lock_software_cursors(struct wlr_output *output, bool lock); /** - * Renders software cursors. This is a utility function that can be called when - * compositors render. - */ -void wlr_output_render_software_cursors(struct wlr_output *output, - const pixman_region32_t *damage); -/** - * Render software cursors. + * Render software cursors. The damage is in buffer-local coordinate space. * * This is a utility function that can be called when compositors render. */ @@ -509,17 +560,6 @@ void wlr_output_state_set_subpixel(struct wlr_output_state *state, */ void wlr_output_state_set_buffer(struct wlr_output_state *state, struct wlr_buffer *buffer); -/** - * Sets the gamma table for an output. `r`, `g` and `b` are gamma ramps for - * red, green and blue. `size` is the length of the ramps and must not exceed - * the value returned by wlr_output_get_gamma_size(). - * - * Providing zero-sized ramps resets the gamma table. - * - * This state will be applied once wlr_output_commit_state() is called. - */ -bool wlr_output_state_set_gamma_lut(struct wlr_output_state *state, - size_t ramp_size, const uint16_t *r, const uint16_t *g, const uint16_t *b); /** * Sets the damage region for an output. This is used as a hint to the backend * and can be used to reduce power consumption or increase performance on some @@ -541,6 +581,46 @@ void wlr_output_state_set_damage(struct wlr_output_state *state, */ void wlr_output_state_set_layers(struct wlr_output_state *state, struct wlr_output_layer_state *layers, size_t layers_len); +/** + * Set a timeline point to wait on before displaying the next frame. + * + * Committing a wait timeline point without a buffer is invalid. + * + * There is only a single wait timeline point, waiting for multiple timeline + * points is unsupported. + * + * Support for this feature is advertised by the timeline field in + * struct wlr_output. + */ +void wlr_output_state_set_wait_timeline(struct wlr_output_state *state, + struct wlr_drm_syncobj_timeline *timeline, uint64_t src_point); +/** + * Set a timeline point to be signalled when the frame is no longer being used + * by the backend. + * + * Committing a signal timeline point without a buffer is invalid. + * + * There is only a single signal timeline point, signalling multiple timeline + * points is unsupported. + * + * Support for this feature is advertised by the timeline field in + * struct wlr_output. + */ +void wlr_output_state_set_signal_timeline(struct wlr_output_state *state, + struct wlr_drm_syncobj_timeline *timeline, uint64_t dst_point); +/** + * Set the color transform for an output. + * + * The color transform is applied after blending output layers. + */ +void wlr_output_state_set_color_transform(struct wlr_output_state *state, + struct wlr_color_transform *tr); + +/** + * Set the colorimetry image description. + */ +bool wlr_output_state_set_image_description(struct wlr_output_state *state, + const struct wlr_output_image_description *image_desc); /** * Copies the output state from src to dst. It is safe to then @@ -583,7 +663,6 @@ bool wlr_output_configure_primary_swapchain(struct wlr_output *output, * frames or -1 if unknown. This is useful for damage tracking. */ struct wlr_render_pass *wlr_output_begin_render_pass(struct wlr_output *output, - struct wlr_output_state *state, int *buffer_age, - struct wlr_buffer_pass_options *render_options); + struct wlr_output_state *state, struct wlr_buffer_pass_options *render_options); #endif diff --git a/include/wlr/types/wlr_output_layer.h b/include/wlr/types/wlr_output_layer.h index af843d07a..af59ab8dc 100644 --- a/include/wlr/types/wlr_output_layer.h +++ b/include/wlr/types/wlr_output_layer.h @@ -23,16 +23,16 @@ * * To configure output layers, callers should call wlr_output_layer_create() to * create layers, attach struct wlr_output_layer_state onto - * struct wlr_output_state via wlr_output_set_layers() to describe their new - * state, and commit the output via wlr_output_commit(). + * struct wlr_output_state via wlr_output_state_set_layers() to describe their new + * state, and commit the output via wlr_output_commit_state(). * * Backends may have arbitrary limitations when it comes to displaying output * layers. Backends indicate whether or not a layer can be displayed via - * wlr_output_layer_state.accepted after wlr_output_test() or - * wlr_output_commit() is called. Compositors using the output layers API - * directly are expected to setup layers, call wlr_output_test(), paint the - * layers that the backend rejected with the renderer, then call - * wlr_output_commit(). + * wlr_output_layer_state.accepted after wlr_output_test_state() or + * wlr_output_commit_state() is called. Compositors using the output layers API + * directly are expected to setup layers, call wlr_output_test_state(), paint + * the layers that the backend rejected with the renderer, then call + * wlr_output_commit_state(). * * Callers are responsible for disabling output layers when they need the full * output contents to be composited onto a single buffer, e.g. during screen @@ -50,10 +50,10 @@ struct wlr_output_layer { void *data; - // private state - - struct wlr_fbox src_box; - struct wlr_box dst_box; + struct { + struct wlr_fbox src_box; + struct wlr_box dst_box; + } WLR_PRIVATE; }; /** @@ -72,9 +72,9 @@ struct wlr_output_layer_state { // to damage the whole buffer. const pixman_region32_t *damage; - // Populated by the backend after wlr_output_test() and wlr_output_commit(), - // indicates whether the backend has acknowledged and will take care of - // displaying the layer + // Populated by the backend after wlr_output_test_state() and + // wlr_output_commit_state(), indicates whether the backend has acknowledged + // and will take care of displaying the layer bool accepted; }; diff --git a/include/wlr/types/wlr_output_layout.h b/include/wlr/types/wlr_output_layout.h index 40c7fb93c..b2665b068 100644 --- a/include/wlr/types/wlr_output_layout.h +++ b/include/wlr/types/wlr_output_layout.h @@ -36,9 +36,9 @@ struct wlr_output_layout { void *data; - // private state - - struct wl_listener display_destroy; + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_output_layout_output { @@ -55,11 +55,11 @@ struct wlr_output_layout_output { struct wl_signal destroy; } events; - // private state + struct { + struct wlr_addon addon; - struct wlr_addon addon; - - struct wl_listener commit; + struct wl_listener commit; + } WLR_PRIVATE; }; struct wlr_output_layout *wlr_output_layout_create(struct wl_display *display); @@ -85,7 +85,7 @@ struct wlr_output *wlr_output_layout_output_at( * already a part of the output layout, it will become manually configured and * will be moved to the specified coordinates. * - * Returns true on success, false on a memory allocation error. + * Returns the output's output layout, or NULL on error. */ struct wlr_output_layout_output *wlr_output_layout_add(struct wlr_output_layout *layout, struct wlr_output *output, int lx, int ly); @@ -97,7 +97,7 @@ struct wlr_output_layout_output *wlr_output_layout_add(struct wlr_output_layout * changes. If the output is already a part of the layout, it will become * automatically configured. * - * Returns true on success, false on a memory allocation error. + * Returns the output's output layout, or NULL on error. */ struct wlr_output_layout_output *wlr_output_layout_add_auto(struct wlr_output_layout *layout, struct wlr_output *output); diff --git a/include/wlr/types/wlr_output_management_v1.h b/include/wlr/types/wlr_output_management_v1.h index 57b880dff..2835a5924 100644 --- a/include/wlr/types/wlr_output_management_v1.h +++ b/include/wlr/types/wlr_output_management_v1.h @@ -39,9 +39,11 @@ struct wlr_output_manager_v1 { struct wl_signal destroy; } events; - struct wl_listener display_destroy; - void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_output_head_v1_state { @@ -67,7 +69,9 @@ struct wlr_output_head_v1 { struct wl_list resources; // wl_resource_get_link() struct wl_list mode_resources; // wl_resource_get_link() - struct wl_listener output_destroy; + struct { + struct wl_listener output_destroy; + } WLR_PRIVATE; }; struct wlr_output_configuration_v1 { @@ -89,7 +93,9 @@ struct wlr_output_configuration_head_v1 { // client state struct wl_resource *resource; // can be NULL if finalized or disabled - struct wl_listener output_destroy; + struct { + struct wl_listener output_destroy; + } WLR_PRIVATE; }; /** diff --git a/include/wlr/types/wlr_output_power_management_v1.h b/include/wlr/types/wlr_output_power_management_v1.h index 688e37a4f..ef8da8e55 100644 --- a/include/wlr/types/wlr_output_power_management_v1.h +++ b/include/wlr/types/wlr_output_power_management_v1.h @@ -8,14 +8,16 @@ struct wlr_output_power_manager_v1 { struct wl_global *global; struct wl_list output_powers; // wlr_output_power_v1.link - struct wl_listener display_destroy; - struct { struct wl_signal set_mode; // struct wlr_output_power_v1_set_mode_event struct wl_signal destroy; } events; void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_output_power_v1 { @@ -24,10 +26,12 @@ struct wlr_output_power_v1 { struct wlr_output_power_manager_v1 *manager; struct wl_list link; // wlr_output_power_manager_v1.output_powers - struct wl_listener output_destroy_listener; - struct wl_listener output_commit_listener; - void *data; + + struct { + struct wl_listener output_destroy_listener; + struct wl_listener output_commit_listener; + } WLR_PRIVATE; }; struct wlr_output_power_v1_set_mode_event { diff --git a/include/wlr/types/wlr_output_swapchain_manager.h b/include/wlr/types/wlr_output_swapchain_manager.h index 24a5578e5..26b3e5636 100644 --- a/include/wlr/types/wlr_output_swapchain_manager.h +++ b/include/wlr/types/wlr_output_swapchain_manager.h @@ -13,6 +13,7 @@ struct wlr_backend; struct wlr_backend_output_state; +struct wlr_output; /** * Helper to allocate swapchains for mode-setting. @@ -30,9 +31,9 @@ struct wlr_backend_output_state; struct wlr_output_swapchain_manager { struct wlr_backend *backend; - // private state - - struct wl_array outputs; // struct wlr_output_swapchain_manager_output + struct { + struct wl_array outputs; // struct wlr_output_swapchain_manager_output + } WLR_PRIVATE; }; /** diff --git a/include/wlr/types/wlr_pointer.h b/include/wlr/types/wlr_pointer.h index 2fa851383..756ffa1f6 100644 --- a/include/wlr/types/wlr_pointer.h +++ b/include/wlr/types/wlr_pointer.h @@ -14,6 +14,8 @@ #include #include +#define WLR_POINTER_BUTTONS_CAP 16 + struct wlr_pointer_impl; struct wlr_pointer { @@ -23,6 +25,9 @@ struct wlr_pointer { char *output_name; + uint32_t buttons[WLR_POINTER_BUTTONS_CAP]; + size_t button_count; + struct { struct wl_signal motion; // struct wlr_pointer_motion_event struct wl_signal motion_absolute; // struct wlr_pointer_motion_absolute_event diff --git a/include/wlr/types/wlr_pointer_constraints_v1.h b/include/wlr/types/wlr_pointer_constraints_v1.h index 2b4722f7d..0d74ce56d 100644 --- a/include/wlr/types/wlr_pointer_constraints_v1.h +++ b/include/wlr/types/wlr_pointer_constraints_v1.h @@ -11,10 +11,10 @@ #include #include +#include #include #include #include -#include "pointer-constraints-unstable-v1-protocol.h" struct wlr_seat; @@ -55,8 +55,7 @@ struct wlr_pointer_constraint_v1 { struct { /** - * Called when a pointer constraint's region is updated, - * post-surface-commit. + * Emitted when a pointer constraint's region is updated. */ struct wl_signal set_region; struct wl_signal destroy; @@ -64,13 +63,14 @@ struct wlr_pointer_constraint_v1 { void *data; - // private state + struct { + struct wl_listener surface_destroy; + struct wl_listener seat_destroy; - struct wl_listener surface_commit; - struct wl_listener surface_destroy; - struct wl_listener seat_destroy; + struct wlr_surface_synced synced; - struct wlr_surface_synced synced; + bool destroying; + } WLR_PRIVATE; }; struct wlr_pointer_constraints_v1 { @@ -78,17 +78,15 @@ struct wlr_pointer_constraints_v1 { struct wl_list constraints; // wlr_pointer_constraint_v1.link struct { - /** - * Called when a new pointer constraint is created. - * - * The data pointer is a struct wlr_pointer_constraint_v1. - */ - struct wl_signal new_constraint; + struct wl_signal destroy; + struct wl_signal new_constraint; // struct wlr_pointer_constraint_v1 } events; - struct wl_listener display_destroy; - void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_pointer_constraints_v1 *wlr_pointer_constraints_v1_create( diff --git a/include/wlr/types/wlr_pointer_gestures_v1.h b/include/wlr/types/wlr_pointer_gestures_v1.h index 5510ce21c..bd2715178 100644 --- a/include/wlr/types/wlr_pointer_gestures_v1.h +++ b/include/wlr/types/wlr_pointer_gestures_v1.h @@ -20,13 +20,15 @@ struct wlr_pointer_gestures_v1 { struct wl_list pinches; // wl_resource_get_link() struct wl_list holds; // wl_resource_get_link() - struct wl_listener display_destroy; - struct { struct wl_signal destroy; } events; void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_pointer_gestures_v1 *wlr_pointer_gestures_v1_create( diff --git a/include/wlr/types/wlr_presentation_time.h b/include/wlr/types/wlr_presentation_time.h index b772295e7..c635208e4 100644 --- a/include/wlr/types/wlr_presentation_time.h +++ b/include/wlr/types/wlr_presentation_time.h @@ -11,7 +11,6 @@ #include #include -#include #include struct wlr_surface; @@ -26,7 +25,9 @@ struct wlr_presentation { struct wl_signal destroy; } events; - struct wl_listener display_destroy; + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_presentation_feedback { @@ -39,9 +40,11 @@ struct wlr_presentation_feedback { uint32_t output_commit_seq; bool zero_copy; - struct wl_listener output_commit; - struct wl_listener output_present; - struct wl_listener output_destroy; + struct { + struct wl_listener output_commit; + struct wl_listener output_present; + struct wl_listener output_destroy; + } WLR_PRIVATE; }; struct wlr_presentation_event { @@ -56,7 +59,7 @@ struct wlr_presentation_event { struct wlr_backend; struct wlr_presentation *wlr_presentation_create(struct wl_display *display, - struct wlr_backend *backend); + struct wlr_backend *backend, uint32_t version); /** * Mark the current surface's buffer as sampled. * @@ -89,7 +92,7 @@ void wlr_presentation_event_from_output(struct wlr_presentation_event *event, * * Instead of calling wlr_presentation_surface_sampled() and managing the * struct wlr_presentation_feedback itself, the compositor can call this function - * before a wlr_output_commit() call to indicate that the surface's current + * before a wlr_output_commit_state() call to indicate that the surface's current * contents have been copied to a buffer which will be displayed on the output. */ void wlr_presentation_surface_textured_on_output(struct wlr_surface *surface, diff --git a/include/wlr/types/wlr_primary_selection_v1.h b/include/wlr/types/wlr_primary_selection_v1.h index 78b542a04..3ab2e1bdd 100644 --- a/include/wlr/types/wlr_primary_selection_v1.h +++ b/include/wlr/types/wlr_primary_selection_v1.h @@ -16,13 +16,15 @@ struct wlr_primary_selection_v1_device_manager { struct wl_global *global; struct wl_list devices; // wlr_primary_selection_v1_device.link - struct wl_listener display_destroy; - struct { struct wl_signal destroy; } events; void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; /** @@ -36,11 +38,13 @@ struct wlr_primary_selection_v1_device { struct wl_list offers; // wl_resource_get_link() - struct wl_listener seat_destroy; - struct wl_listener seat_focus_change; - struct wl_listener seat_set_primary_selection; - void *data; + + struct { + struct wl_listener seat_destroy; + struct wl_listener seat_focus_change; + struct wl_listener seat_set_primary_selection; + } WLR_PRIVATE; }; struct wlr_primary_selection_v1_device_manager * diff --git a/include/wlr/types/wlr_region.h b/include/wlr/types/wlr_region.h deleted file mode 100644 index 483ac9687..000000000 --- a/include/wlr/types/wlr_region.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * This is a deprecated interface of wlroots. It will be removed in a future - * version. wlr/types/wlr_compositor.h should be used instead. - */ - -#ifndef WLR_TYPES_WLR_REGION_H -#define WLR_TYPES_WLR_REGION_H - -#include - -struct wl_resource; - -/** - * Obtain a Pixman region from a wl_region resource. - * - * To allow clients to create wl_region objects, call wlr_compositor_create(). - */ -const pixman_region32_t *wlr_region_from_resource(struct wl_resource *resource); - -#endif diff --git a/include/wlr/types/wlr_relative_pointer_v1.h b/include/wlr/types/wlr_relative_pointer_v1.h index fcd44d529..4db7b5220 100644 --- a/include/wlr/types/wlr_relative_pointer_v1.h +++ b/include/wlr/types/wlr_relative_pointer_v1.h @@ -30,9 +30,11 @@ struct wlr_relative_pointer_manager_v1 { struct wl_signal new_relative_pointer; // struct wlr_relative_pointer_v1 } events; - struct wl_listener display_destroy_listener; - void *data; + + struct { + struct wl_listener display_destroy_listener; + } WLR_PRIVATE; }; /** @@ -51,10 +53,12 @@ struct wlr_relative_pointer_v1 { struct wl_signal destroy; } events; - struct wl_listener seat_destroy; - struct wl_listener pointer_destroy; - void *data; + + struct { + struct wl_listener seat_destroy; + struct wl_listener pointer_destroy; + } WLR_PRIVATE; }; struct wlr_relative_pointer_manager_v1 *wlr_relative_pointer_manager_v1_create( diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index a8fc21595..9658a02b4 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -42,6 +42,8 @@ struct wlr_scene_output_layout; struct wlr_presentation; struct wlr_linux_dmabuf_v1; +struct wlr_gamma_control_manager_v1; +struct wlr_color_manager_v1; struct wlr_output_state; typedef bool (*wlr_scene_buffer_point_accepts_input_func_t)( @@ -74,9 +76,9 @@ struct wlr_scene_node { struct wlr_addon_set addons; - // private state - - pixman_region32_t visible; + struct { + pixman_region32_t visible; + } WLR_PRIVATE; }; enum wlr_scene_debug_damage_option { @@ -100,15 +102,20 @@ struct wlr_scene { // May be NULL struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1; + struct wlr_gamma_control_manager_v1 *gamma_control_manager_v1; + struct wlr_color_manager_v1 *color_manager_v1; - // private state + struct { + struct wl_listener linux_dmabuf_v1_destroy; + struct wl_listener gamma_control_manager_v1_destroy; + struct wl_listener gamma_control_manager_v1_set_gamma; + struct wl_listener color_manager_v1_destroy; - struct wl_listener linux_dmabuf_v1_destroy; - - enum wlr_scene_debug_damage_option debug_damage_option; - bool direct_scanout; - bool calculate_visibility; - bool highlight_transparent_region; + enum wlr_scene_debug_damage_option debug_damage_option; + bool direct_scanout; + bool calculate_visibility; + bool highlight_transparent_region; + } WLR_PRIVATE; }; /** A scene-graph node displaying a single surface. */ @@ -116,19 +123,23 @@ struct wlr_scene_surface { struct wlr_scene_buffer *buffer; struct wlr_surface *surface; - // private state + struct { + struct wlr_box clip; - struct wlr_box clip; + // Output used for frame pacing (surface frame callbacks, presentation + // time feedback, etc), may be NULL + struct wlr_output *frame_pacing_output; - struct wlr_addon addon; + struct wlr_addon addon; - struct wl_listener outputs_update; - struct wl_listener output_enter; - struct wl_listener output_leave; - struct wl_listener output_sample; - struct wl_listener frame_done; - struct wl_listener surface_destroy; - struct wl_listener surface_commit; + struct wl_listener outputs_update; + struct wl_listener output_enter; + struct wl_listener output_leave; + struct wl_listener output_sample; + struct wl_listener frame_done; + struct wl_listener surface_destroy; + struct wl_listener surface_commit; + } WLR_PRIVATE; }; /** A scene-graph node displaying a solid-colored rectangle */ @@ -148,6 +159,11 @@ struct wlr_scene_output_sample_event { bool direct_scanout; }; +struct wlr_scene_frame_done_event { + struct wlr_scene_output *output; + struct timespec when; +}; + /** A scene-graph node displaying a buffer */ struct wlr_scene_buffer { struct wlr_scene_node node; @@ -160,7 +176,7 @@ struct wlr_scene_buffer { struct wl_signal output_enter; // struct wlr_scene_output struct wl_signal output_leave; // struct wlr_scene_output struct wl_signal output_sample; // struct wlr_scene_output_sample_event - struct wl_signal frame_done; // struct timespec + struct wl_signal frame_done; // struct wlr_scene_frame_done_event } events; // May be NULL @@ -169,8 +185,7 @@ struct wlr_scene_buffer { /** * The output that the largest area of this buffer is displayed on. * This may be NULL if the buffer is not currently displayed on any - * outputs. This is the output that should be used for frame callbacks, - * presentation feedback, etc. + * outputs. */ struct wlr_scene_output *primary_output; @@ -180,19 +195,30 @@ struct wlr_scene_buffer { int dst_width, dst_height; enum wl_output_transform transform; pixman_region32_t opaque_region; + enum wlr_color_transfer_function transfer_function; + enum wlr_color_named_primaries primaries; - // private state + struct { + uint64_t active_outputs; + struct wlr_texture *texture; + struct wlr_linux_dmabuf_feedback_v1_init_options prev_feedback_options; - uint64_t active_outputs; - struct wlr_texture *texture; - struct wlr_linux_dmabuf_feedback_v1_init_options prev_feedback_options; + bool own_buffer; + int buffer_width, buffer_height; + bool buffer_is_opaque; - bool own_buffer; - int buffer_width, buffer_height; - bool buffer_is_opaque; + struct wlr_drm_syncobj_timeline *wait_timeline; + uint64_t wait_point; - struct wl_listener buffer_release; - struct wl_listener renderer_destroy; + struct wl_listener buffer_release; + struct wl_listener renderer_destroy; + + // True if the underlying buffer is a wlr_single_pixel_buffer_v1 + bool is_single_pixel_buffer; + // If is_single_pixel_buffer is set, contains the color of the buffer + // as {R, G, B, A} where the max value of each component is UINT32_MAX + uint32_t single_pixel_buffer_color[4]; + } WLR_PRIVATE; }; /** A viewport for an output in the scene-graph */ @@ -210,20 +236,39 @@ struct wlr_scene_output { struct wl_signal destroy; } events; - // private state + struct { + pixman_region32_t pending_commit_damage; - pixman_region32_t pending_commit_damage; + uint8_t index; - uint8_t index; - bool prev_scanout; + /** + * When scanout is applicable, we increment this every time a frame is rendered until + * DMABUF_FEEDBACK_DEBOUNCE_FRAMES is hit to debounce the scanout dmabuf feedback. Likewise, + * when scanout is no longer applicable, we decrement this until zero is hit to debounce + * composition dmabuf feedback. + */ + uint8_t dmabuf_feedback_debounce; + bool prev_scanout; - struct wl_listener output_commit; - struct wl_listener output_damage; - struct wl_listener output_needs_frame; + bool gamma_lut_changed; + struct wlr_gamma_control_v1 *gamma_lut; + struct wlr_color_transform *gamma_lut_color_transform; - struct wl_list damage_highlight_regions; + struct wlr_color_transform *prev_gamma_lut_color_transform; + struct wlr_color_transform *prev_supplied_color_transform; + struct wlr_color_transform *prev_combined_color_transform; - struct wl_array render_list; + struct wl_listener output_commit; + struct wl_listener output_damage; + struct wl_listener output_needs_frame; + + struct wl_list damage_highlight_regions; + + struct wl_array render_list; + + struct wlr_drm_syncobj_timeline *in_timeline; + uint64_t in_point; + } WLR_PRIVATE; }; struct wlr_scene_timer { @@ -236,12 +281,12 @@ struct wlr_scene_layer_surface_v1 { struct wlr_scene_tree *tree; struct wlr_layer_surface_v1 *layer_surface; - // private state - - struct wl_listener tree_destroy; - struct wl_listener layer_surface_destroy; - struct wl_listener layer_surface_map; - struct wl_listener layer_surface_unmap; + struct { + struct wl_listener tree_destroy; + struct wl_listener layer_surface_destroy; + struct wl_listener layer_surface_map; + struct wl_listener layer_surface_unmap; + } WLR_PRIVATE; }; /** @@ -306,6 +351,9 @@ struct wlr_scene_node *wlr_scene_node_at(struct wlr_scene_node *node, /** * Create a new scene-graph. + * + * The graph is also a struct wlr_scene_node. Associated resources can be + * destroyed through wlr_scene_node_destroy(). */ struct wlr_scene *wlr_scene_create(void); @@ -317,6 +365,21 @@ struct wlr_scene *wlr_scene_create(void); void wlr_scene_set_linux_dmabuf_v1(struct wlr_scene *scene, struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1); +/** + * Handles gamma_control_v1 for all outputs in the scene. + * + * Asserts that a struct wlr_gamma_control_manager_v1 hasn't already been set + * for the scene. + */ +void wlr_scene_set_gamma_control_manager_v1(struct wlr_scene *scene, + struct wlr_gamma_control_manager_v1 *gamma_control); + +/** + * Handles color_management_v1 feedback for all surfaces in the scene. + * + * Asserts that a struct wlr_color_manager_v1 hasn't already been set for the scene. + */ +void wlr_scene_set_color_manager_v1(struct wlr_scene *scene, struct wlr_color_manager_v1 *manager); /** * Add a node displaying nothing but its children. @@ -326,11 +389,29 @@ struct wlr_scene_tree *wlr_scene_tree_create(struct wlr_scene_tree *parent); /** * Add a node displaying a single surface to the scene-graph. * - * The child sub-surfaces are ignored. + * The child sub-surfaces are ignored. See wlr_scene_subsurface_tree_create() * - * wlr_surface_send_enter() and wlr_surface_send_leave() will be called - * automatically based on the position of the surface and outputs in - * the scene. + * Note that this helper does multiple things on behalf of the compositor. Some + * of these include protocol implementations where compositors just need to enable + * the protocols: + * - wp_viewporter + * - wp_presentation_time + * - wp_fractional_scale_v1 + * - wp_alpha_modifier_v1 + * - wp_linux_drm_syncobj_v1 + * - zwp_linux_dmabuf_v1 presentation feedback with wlr_scene_set_linux_dmabuf_v1() + * + * This helper will also transparently: + * - Send preferred buffer scale¹ + * - Send preferred buffer transform¹ + * - Restack xwayland surfaces. See wlr_xwayland_surface_restack()² + * - Send output enter/leave events. + * + * ¹ Note that scale and transform sent to the surface will be based on the output + * which has the largest visible surface area. Intelligent visibility calculations + * influence this. + * ² xwayland stacking order is undefined when the xwayland surfaces do not + * intersect. */ struct wlr_scene_surface *wlr_scene_surface_create(struct wlr_scene_tree *parent, struct wlr_surface *surface); @@ -360,8 +441,16 @@ struct wlr_scene_rect *wlr_scene_rect_from_node(struct wlr_scene_node *node); struct wlr_scene_surface *wlr_scene_surface_try_from_buffer( struct wlr_scene_buffer *scene_buffer); +/** + * Call wlr_surface_send_frame_done() if the surface is visible. + */ +void wlr_scene_surface_send_frame_done(struct wlr_scene_surface *scene_surface, + const struct timespec *when); + /** * Add a node displaying a solid-colored rectangle to the scene-graph. + * + * The color argument must be a premultiplied color value. */ struct wlr_scene_rect *wlr_scene_rect_create(struct wlr_scene_tree *parent, int width, int height, const float color[static 4]); @@ -373,6 +462,8 @@ void wlr_scene_rect_set_size(struct wlr_scene_rect *rect, int width, int height) /** * Change the color of an existing rectangle node. + * + * The color argument must be a premultiplied color value. */ void wlr_scene_rect_set_color(struct wlr_scene_rect *rect, const float color[static 4]); @@ -401,6 +492,28 @@ void wlr_scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer, void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buffer, struct wlr_buffer *buffer, const pixman_region32_t *region); +/** + * Options for wlr_scene_buffer_set_buffer_with_options(). + */ +struct wlr_scene_buffer_set_buffer_options { + // The damage region is in buffer-local coordinates. If the region is NULL, + // the whole buffer node will be damaged. + const pixman_region32_t *damage; + + // Wait for a timeline synchronization point before reading from the buffer. + struct wlr_drm_syncobj_timeline *wait_timeline; + uint64_t wait_point; +}; + +/** + * Sets the buffer's backing buffer. + * + * If the buffer is NULL, the buffer node will not be displayed. If options is + * NULL, empty options are used. + */ +void wlr_scene_buffer_set_buffer_with_options(struct wlr_scene_buffer *scene_buffer, + struct wlr_buffer *buffer, const struct wlr_scene_buffer_set_buffer_options *options); + /** * Sets the buffer's opaque region. This is an optimization hint used to * determine if buffers which reside under this one need to be rendered or not. @@ -445,11 +558,17 @@ void wlr_scene_buffer_set_opacity(struct wlr_scene_buffer *scene_buffer, void wlr_scene_buffer_set_filter_mode(struct wlr_scene_buffer *scene_buffer, enum wlr_scale_filter_mode filter_mode); +void wlr_scene_buffer_set_transfer_function(struct wlr_scene_buffer *scene_buffer, + enum wlr_color_transfer_function transfer_function); + +void wlr_scene_buffer_set_primaries(struct wlr_scene_buffer *scene_buffer, + enum wlr_color_named_primaries primaries); + /** * Calls the buffer's frame_done signal. */ void wlr_scene_buffer_send_frame_done(struct wlr_scene_buffer *scene_buffer, - struct timespec *now); + struct wlr_scene_frame_done_event *event); /** * Add a viewport for the specified output to the scene-graph. @@ -470,6 +589,11 @@ void wlr_scene_output_set_position(struct wlr_scene_output *scene_output, struct wlr_scene_output_state_options { struct wlr_scene_timer *timer; + + /** + * Color transform to apply before the output's color transform. Cannot be + * used when the output has a non-NULL image description set. + */ struct wlr_color_transform *color_transform; /** @@ -480,6 +604,12 @@ struct wlr_scene_output_state_options { struct wlr_swapchain *swapchain; }; +/** + * Returns true if scene wants to render a new frame. False, if no new frame + * is needed and an output commit can be skipped for the current frame. + */ +bool wlr_scene_output_needs_frame(struct wlr_scene_output *scene_output); + /** * Render and commit an output. */ diff --git a/include/wlr/types/wlr_screencopy_v1.h b/include/wlr/types/wlr_screencopy_v1.h index c1e990fa8..4f53a603c 100644 --- a/include/wlr/types/wlr_screencopy_v1.h +++ b/include/wlr/types/wlr_screencopy_v1.h @@ -14,17 +14,26 @@ #include #include +/** + * Deprecated: this protocol is deprecated and superseded by ext-image-copy-capture-v1. + * The implementation will be dropped in a future wlroots version. + * + * Consider using `wlr_ext_image_capture_source_v1` instead. + */ + struct wlr_screencopy_manager_v1 { struct wl_global *global; struct wl_list frames; // wlr_screencopy_frame_v1.link - struct wl_listener display_destroy; - struct { struct wl_signal destroy; } events; void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_screencopy_v1_client { @@ -50,11 +59,13 @@ struct wlr_screencopy_frame_v1 { struct wlr_buffer *buffer; struct wlr_output *output; - struct wl_listener output_commit; - struct wl_listener output_destroy; - struct wl_listener output_enable; void *data; + + struct { + struct wl_listener output_commit; + struct wl_listener output_destroy; + } WLR_PRIVATE; }; struct wlr_screencopy_manager_v1 *wlr_screencopy_manager_v1_create( diff --git a/include/wlr/types/wlr_seat.h b/include/wlr/types/wlr_seat.h index 06d8224ef..74b4341dd 100644 --- a/include/wlr/types/wlr_seat.h +++ b/include/wlr/types/wlr_seat.h @@ -78,15 +78,17 @@ struct wlr_touch_point { struct wlr_seat_client *focus_client; double sx, sy; - struct wl_listener surface_destroy; - struct wl_listener focus_surface_destroy; - struct wl_listener client_destroy; - struct { struct wl_signal destroy; } events; struct wl_list link; + + struct { + struct wl_listener surface_destroy; + struct wl_listener focus_surface_destroy; + struct wl_listener client_destroy; + } WLR_PRIVATE; }; struct wlr_seat_pointer_grab; @@ -170,7 +172,10 @@ struct wlr_seat_pointer_grab { void *data; }; -#define WLR_POINTER_BUTTONS_CAP 16 +struct wlr_seat_pointer_button { + uint32_t button; + size_t n_pressed; +}; struct wlr_seat_pointer_state { struct wlr_seat *seat; @@ -184,17 +189,21 @@ struct wlr_seat_pointer_state { bool sent_axis_source; enum wl_pointer_axis_source cached_axis_source; - uint32_t buttons[WLR_POINTER_BUTTONS_CAP]; + struct wlr_seat_pointer_button buttons[WLR_POINTER_BUTTONS_CAP]; size_t button_count; + uint32_t grab_button; uint32_t grab_serial; uint32_t grab_time; - struct wl_listener surface_destroy; struct { struct wl_signal focus_change; // struct wlr_seat_pointer_focus_change_event } events; + + struct { + struct wl_listener surface_destroy; + } WLR_PRIVATE; }; struct wlr_seat_keyboard_state { @@ -204,18 +213,20 @@ struct wlr_seat_keyboard_state { struct wlr_seat_client *focused_client; struct wlr_surface *focused_surface; - struct wl_listener keyboard_destroy; - struct wl_listener keyboard_keymap; - struct wl_listener keyboard_repeat_info; - - struct wl_listener surface_destroy; - struct wlr_seat_keyboard_grab *grab; struct wlr_seat_keyboard_grab *default_grab; struct { struct wl_signal focus_change; // struct wlr_seat_keyboard_focus_change_event } events; + + struct { + struct wl_listener keyboard_destroy; + struct wl_listener keyboard_keymap; + struct wl_listener keyboard_repeat_info; + + struct wl_listener surface_destroy; + } WLR_PRIVATE; }; struct wlr_seat_touch_state { @@ -239,7 +250,6 @@ struct wlr_seat { char *name; uint32_t capabilities; uint32_t accumulated_capabilities; - struct timespec last_event; struct wlr_data_source *selection_source; uint32_t selection_serial; @@ -258,11 +268,6 @@ struct wlr_seat { struct wlr_seat_keyboard_state keyboard_state; struct wlr_seat_touch_state touch_state; - struct wl_listener display_destroy; - struct wl_listener selection_source_destroy; - struct wl_listener primary_selection_source_destroy; - struct wl_listener drag_source_destroy; - struct { struct wl_signal pointer_grab_begin; struct wl_signal pointer_grab_end; @@ -298,6 +303,13 @@ struct wlr_seat { } events; void *data; + + struct { + struct wl_listener display_destroy; + struct wl_listener selection_source_destroy; + struct wl_listener primary_selection_source_destroy; + struct wl_listener drag_source_destroy; + } WLR_PRIVATE; }; struct wlr_seat_pointer_request_set_cursor_event { @@ -748,6 +760,6 @@ struct wlr_seat_client *wlr_seat_client_from_pointer_resource( /** * Check whether a surface has bound to touch events. */ -bool wlr_surface_accepts_touch(struct wlr_seat *wlr_seat, struct wlr_surface *surface); +bool wlr_surface_accepts_touch(struct wlr_surface *surface, struct wlr_seat *wlr_seat); #endif diff --git a/include/wlr/types/wlr_security_context_v1.h b/include/wlr/types/wlr_security_context_v1.h index f932a2b1e..40bb7a29c 100644 --- a/include/wlr/types/wlr_security_context_v1.h +++ b/include/wlr/types/wlr_security_context_v1.h @@ -28,11 +28,11 @@ struct wlr_security_context_manager_v1 { void *data; - // private state + struct { + struct wl_list contexts; // wlr_security_context_v1.link - struct wl_list contexts; // wlr_security_context_v1.link - - struct wl_listener display_destroy; + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_security_context_v1_state { diff --git a/include/wlr/types/wlr_server_decoration.h b/include/wlr/types/wlr_server_decoration.h index 7e78c5f21..2b0e859eb 100644 --- a/include/wlr/types/wlr_server_decoration.h +++ b/include/wlr/types/wlr_server_decoration.h @@ -49,14 +49,16 @@ struct wlr_server_decoration_manager { uint32_t default_mode; // enum wlr_server_decoration_manager_mode - struct wl_listener display_destroy; - struct { struct wl_signal new_decoration; struct wl_signal destroy; } events; void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_server_decoration { @@ -71,9 +73,11 @@ struct wlr_server_decoration { struct wl_signal mode; } events; - struct wl_listener surface_destroy_listener; - void *data; + + struct { + struct wl_listener surface_destroy_listener; + } WLR_PRIVATE; }; struct wlr_server_decoration_manager *wlr_server_decoration_manager_create( diff --git a/include/wlr/types/wlr_session_lock_v1.h b/include/wlr/types/wlr_session_lock_v1.h index 058bf0d74..2e9d80a9d 100644 --- a/include/wlr/types/wlr_session_lock_v1.h +++ b/include/wlr/types/wlr_session_lock_v1.h @@ -24,9 +24,9 @@ struct wlr_session_lock_manager_v1 { void *data; - // private state - - struct wl_listener display_destroy; + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_session_lock_v1 { @@ -42,9 +42,9 @@ struct wlr_session_lock_v1 { void *data; - // private state - - bool locked_sent; + struct { + bool locked_sent; + } WLR_PRIVATE; }; struct wlr_session_lock_surface_v1_state { @@ -79,11 +79,11 @@ struct wlr_session_lock_surface_v1 { void *data; - // private state + struct { + struct wlr_surface_synced synced; - struct wlr_surface_synced synced; - - struct wl_listener output_destroy; + struct wl_listener output_destroy; + } WLR_PRIVATE; }; struct wlr_session_lock_manager_v1 *wlr_session_lock_manager_v1_create( diff --git a/include/wlr/types/wlr_shm.h b/include/wlr/types/wlr_shm.h index 13a5ca5cd..650b7ffd0 100644 --- a/include/wlr/types/wlr_shm.h +++ b/include/wlr/types/wlr_shm.h @@ -25,12 +25,12 @@ struct wlr_renderer; struct wlr_shm { struct wl_global *global; - // private state + struct { + uint32_t *formats; + size_t formats_len; - uint32_t *formats; - size_t formats_len; - - struct wl_listener display_destroy; + struct wl_listener display_destroy; + } WLR_PRIVATE; }; /** diff --git a/include/wlr/types/wlr_single_pixel_buffer_v1.h b/include/wlr/types/wlr_single_pixel_buffer_v1.h index 3836203f8..64018a864 100644 --- a/include/wlr/types/wlr_single_pixel_buffer_v1.h +++ b/include/wlr/types/wlr_single_pixel_buffer_v1.h @@ -14,9 +14,9 @@ struct wlr_single_pixel_buffer_manager_v1 { struct wl_global *global; - // private state - - struct wl_listener display_destroy; + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; diff --git a/include/wlr/types/wlr_subcompositor.h b/include/wlr/types/wlr_subcompositor.h index bd3899b8a..251379d30 100644 --- a/include/wlr/types/wlr_subcompositor.h +++ b/include/wlr/types/wlr_subcompositor.h @@ -23,9 +23,9 @@ struct wlr_subsurface_parent_state { int32_t x, y; struct wl_list link; - // private state - - struct wlr_surface_synced *synced; + struct { + struct wlr_surface_synced *synced; + } WLR_PRIVATE; }; struct wlr_subsurface { @@ -41,32 +41,30 @@ struct wlr_subsurface { bool synchronized; bool added; - struct wl_listener surface_client_commit; - struct wl_listener parent_destroy; - struct { struct wl_signal destroy; } events; void *data; - // private state - - struct wlr_surface_synced parent_synced; - struct { - int32_t x, y; - } previous; + struct wlr_surface_synced parent_synced; + + struct wl_listener surface_client_commit; + struct wl_listener parent_destroy; + } WLR_PRIVATE; }; struct wlr_subcompositor { struct wl_global *global; - struct wl_listener display_destroy; - struct { struct wl_signal destroy; } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; /** diff --git a/include/wlr/types/wlr_tablet_v2.h b/include/wlr/types/wlr_tablet_v2.h index 6da893b9c..599e88019 100644 --- a/include/wlr/types/wlr_tablet_v2.h +++ b/include/wlr/types/wlr_tablet_v2.h @@ -10,10 +10,9 @@ #define WLR_TYPES_WLR_TABLET_V2_H #include +#include #include -#include "tablet-v2-protocol.h" - /* This can probably be even lower,the tools don't have a lot of buttons */ #define WLR_TABLET_V2_TOOL_BUTTONS_CAP 16 @@ -44,13 +43,15 @@ struct wlr_tablet_manager_v2 { struct wl_list clients; // wlr_tablet_manager_client_v2.link struct wl_list seats; // wlr_tablet_seat_v2.link - struct wl_listener display_destroy; - struct { struct wl_signal destroy; } events; void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_tablet_v2_tablet { @@ -59,9 +60,11 @@ struct wlr_tablet_v2_tablet { struct wlr_input_device *wlr_device; struct wl_list clients; // wlr_tablet_client_v2.tablet_link - struct wl_listener tablet_destroy; - struct wlr_tablet_client_v2 *current_client; + + struct { + struct wl_listener tablet_destroy; + } WLR_PRIVATE; }; struct wlr_tablet_v2_tablet_tool { @@ -69,11 +72,8 @@ struct wlr_tablet_v2_tablet_tool { struct wlr_tablet_tool *wlr_tool; struct wl_list clients; // wlr_tablet_tool_client_v2.tool_link - struct wl_listener tool_destroy; - struct wlr_tablet_tool_client_v2 *current_client; struct wlr_surface *focused_surface; - struct wl_listener surface_destroy; struct wlr_tablet_tool_v2_grab *grab; struct wlr_tablet_tool_v2_grab default_grab; @@ -88,6 +88,11 @@ struct wlr_tablet_v2_tablet_tool { struct { struct wl_signal set_cursor; // struct wlr_tablet_v2_event_cursor } events; + + struct { + struct wl_listener surface_destroy; + struct wl_listener tool_destroy; + } WLR_PRIVATE; }; struct wlr_tablet_v2_tablet_pad { @@ -99,8 +104,6 @@ struct wlr_tablet_v2_tablet_pad { size_t group_count; uint32_t *groups; - struct wl_listener pad_destroy; - struct wlr_tablet_pad_client_v2 *current_client; struct wlr_tablet_pad_v2_grab *grab; struct wlr_tablet_pad_v2_grab default_grab; @@ -110,6 +113,10 @@ struct wlr_tablet_v2_tablet_pad { struct wl_signal strip_feedback; // struct wlr_tablet_v2_event_feedback struct wl_signal ring_feedback; // struct wlr_tablet_v2_event_feedback } events; + + struct { + struct wl_listener pad_destroy; + } WLR_PRIVATE; }; struct wlr_tablet_v2_event_cursor { @@ -327,6 +334,6 @@ struct wlr_tablet_pad_v2_grab_interface { void wlr_tablet_v2_end_grab(struct wlr_tablet_v2_tablet_pad *pad); void wlr_tablet_v2_start_grab(struct wlr_tablet_v2_tablet_pad *pad, struct wlr_tablet_pad_v2_grab *grab); -bool wlr_surface_accepts_tablet_v2(struct wlr_tablet_v2_tablet *tablet, - struct wlr_surface *surface); +bool wlr_surface_accepts_tablet_v2(struct wlr_surface *surface, + struct wlr_tablet_v2_tablet *tablet); #endif /* WLR_TYPES_WLR_TABLET_V2_H */ diff --git a/include/wlr/types/wlr_tearing_control_v1.h b/include/wlr/types/wlr_tearing_control_v1.h index 76c5c6003..f2d4a1478 100644 --- a/include/wlr/types/wlr_tearing_control_v1.h +++ b/include/wlr/types/wlr_tearing_control_v1.h @@ -11,11 +11,9 @@ #include #include -#include +#include #include -#include "tearing-control-v1-protocol.h" - struct wlr_tearing_control_v1 { struct wl_client *client; struct wl_list link; @@ -30,13 +28,13 @@ struct wlr_tearing_control_v1 { struct wlr_surface *surface; - // private state + struct { + enum wp_tearing_control_v1_presentation_hint previous; + struct wlr_addon addon; + struct wlr_surface_synced synced; - enum wp_tearing_control_v1_presentation_hint previous; - struct wlr_addon addon; - struct wlr_surface_synced synced; - - struct wl_listener surface_commit; + struct wl_listener surface_commit; + } WLR_PRIVATE; }; struct wlr_tearing_control_manager_v1 { @@ -44,13 +42,16 @@ struct wlr_tearing_control_manager_v1 { struct wl_list surface_hints; // wlr_tearing_control_v1.link - struct wl_listener display_destroy; struct { struct wl_signal new_object; // struct wlr_tearing_control_v1* struct wl_signal destroy; } events; void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_tearing_control_manager_v1 *wlr_tearing_control_manager_v1_create( diff --git a/include/wlr/types/wlr_text_input_v3.h b/include/wlr/types/wlr_text_input_v3.h index 37397c1f3..29feb7e6c 100644 --- a/include/wlr/types/wlr_text_input_v3.h +++ b/include/wlr/types/wlr_text_input_v3.h @@ -56,27 +56,31 @@ struct wlr_text_input_v3 { struct wl_list link; - struct wl_listener surface_destroy; - struct wl_listener seat_destroy; + struct { + struct wl_signal enable; + struct wl_signal commit; + struct wl_signal disable; + struct wl_signal destroy; + } events; struct { - struct wl_signal enable; // struct wlr_text_input_v3 - struct wl_signal commit; // struct wlr_text_input_v3 - struct wl_signal disable; // struct wlr_text_input_v3 - struct wl_signal destroy; // struct wlr_text_input_v3 - } events; + struct wl_listener surface_destroy; + struct wl_listener seat_destroy; + } WLR_PRIVATE; }; struct wlr_text_input_manager_v3 { struct wl_global *global; - struct wl_list text_inputs; // struct wlr_text_input_v3.resource.link - - struct wl_listener display_destroy; + struct wl_list text_inputs; // wlr_text_input_v3.link struct { - struct wl_signal text_input; // struct wlr_text_input_v3 - struct wl_signal destroy; // struct wlr_input_method_manager_v3 + struct wl_signal new_text_input; // struct wlr_text_input_v3 + struct wl_signal destroy; } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_text_input_manager_v3 *wlr_text_input_manager_v3_create( diff --git a/include/wlr/types/wlr_transient_seat_v1.h b/include/wlr/types/wlr_transient_seat_v1.h index 84d419bc1..d996016bf 100644 --- a/include/wlr/types/wlr_transient_seat_v1.h +++ b/include/wlr/types/wlr_transient_seat_v1.h @@ -17,15 +17,17 @@ struct wlr_transient_seat_v1 { struct wl_resource *resource; struct wlr_seat *seat; - // private state - struct wl_listener seat_destroy; + struct { + struct wl_listener seat_destroy; + } WLR_PRIVATE; }; struct wlr_transient_seat_manager_v1 { struct wl_global *global; - struct wl_listener display_destroy; struct { + struct wl_signal destroy; + /** * Upon receiving this signal, call * wlr_transient_seat_v1_ready() to pass a newly created seat @@ -35,6 +37,10 @@ struct wlr_transient_seat_manager_v1 { */ struct wl_signal create_seat; // struct wlr_transient_seat_v1 } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_transient_seat_manager_v1 *wlr_transient_seat_manager_v1_create( diff --git a/include/wlr/types/wlr_viewporter.h b/include/wlr/types/wlr_viewporter.h index e1e2ec04a..55773724a 100644 --- a/include/wlr/types/wlr_viewporter.h +++ b/include/wlr/types/wlr_viewporter.h @@ -29,7 +29,9 @@ struct wlr_viewporter { struct wl_signal destroy; } events; - struct wl_listener display_destroy; + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_viewporter *wlr_viewporter_create(struct wl_display *display); diff --git a/include/wlr/types/wlr_virtual_keyboard_v1.h b/include/wlr/types/wlr_virtual_keyboard_v1.h index cb0bb9823..babc535a5 100644 --- a/include/wlr/types/wlr_virtual_keyboard_v1.h +++ b/include/wlr/types/wlr_virtual_keyboard_v1.h @@ -16,12 +16,14 @@ struct wlr_virtual_keyboard_manager_v1 { struct wl_global *global; struct wl_list virtual_keyboards; // wlr_virtual_keyboard_v1.link - struct wl_listener display_destroy; - struct { struct wl_signal new_virtual_keyboard; // struct wlr_virtual_keyboard_v1 struct wl_signal destroy; } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_virtual_keyboard_v1 { diff --git a/include/wlr/types/wlr_virtual_pointer_v1.h b/include/wlr/types/wlr_virtual_pointer_v1.h index a05268547..c8804e84e 100644 --- a/include/wlr/types/wlr_virtual_pointer_v1.h +++ b/include/wlr/types/wlr_virtual_pointer_v1.h @@ -18,12 +18,14 @@ struct wlr_virtual_pointer_manager_v1 { struct wl_global *global; struct wl_list virtual_pointers; // wlr_virtual_pointer_v1.link - struct wl_listener display_destroy; - struct { struct wl_signal new_virtual_pointer; // struct wlr_virtual_pointer_v1_new_pointer_event struct wl_signal destroy; } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_virtual_pointer_v1 { diff --git a/include/wlr/types/wlr_xdg_activation_v1.h b/include/wlr/types/wlr_xdg_activation_v1.h index f7b038b8c..c9eda5be2 100644 --- a/include/wlr/types/wlr_xdg_activation_v1.h +++ b/include/wlr/types/wlr_xdg_activation_v1.h @@ -28,17 +28,18 @@ struct wlr_xdg_activation_token_v1 { struct wl_signal destroy; } events; - // private state + struct { + char *token; + struct wl_resource *resource; // can be NULL + struct wl_event_source *timeout; // can be NULL - char *token; - struct wl_resource *resource; // can be NULL - struct wl_event_source *timeout; // can be NULL - - struct wl_listener seat_destroy; - struct wl_listener surface_destroy; + struct wl_listener seat_destroy; + struct wl_listener surface_destroy; + } WLR_PRIVATE; }; struct wlr_xdg_activation_v1 { + struct wl_global *global; uint32_t token_timeout_msec; // token timeout in milliseconds (0 to disable) struct wl_list tokens; // wlr_xdg_activation_token_v1.link @@ -49,13 +50,11 @@ struct wlr_xdg_activation_v1 { struct wl_signal new_token; // struct wlr_xdg_activation_token_v1 } events; - // private state + struct { + struct wl_display *display; - struct wl_display *display; - - struct wl_global *global; - - struct wl_listener display_destroy; + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_xdg_activation_v1_request_activate_event { diff --git a/include/wlr/types/wlr_xdg_decoration_v1.h b/include/wlr/types/wlr_xdg_decoration_v1.h index f1a8bc5b3..78f84eb39 100644 --- a/include/wlr/types/wlr_xdg_decoration_v1.h +++ b/include/wlr/types/wlr_xdg_decoration_v1.h @@ -14,14 +14,16 @@ struct wlr_xdg_decoration_manager_v1 { struct wl_global *global; struct wl_list decorations; // wlr_xdg_toplevel_decoration.link - struct wl_listener display_destroy; - struct { struct wl_signal new_toplevel_decoration; // struct wlr_xdg_toplevel_decoration struct wl_signal destroy; } events; void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_xdg_toplevel_decoration_v1_configure { @@ -54,13 +56,13 @@ struct wlr_xdg_toplevel_decoration_v1 { void *data; - // private state + struct { + struct wl_listener toplevel_destroy; + struct wl_listener surface_configure; + struct wl_listener surface_ack_configure; - struct wl_listener toplevel_destroy; - struct wl_listener surface_configure; - struct wl_listener surface_ack_configure; - - struct wlr_surface_synced synced; + struct wlr_surface_synced synced; + } WLR_PRIVATE; }; struct wlr_xdg_decoration_manager_v1 * diff --git a/include/wlr/types/wlr_xdg_dialog_v1.h b/include/wlr/types/wlr_xdg_dialog_v1.h new file mode 100644 index 000000000..0017015ad --- /dev/null +++ b/include/wlr/types/wlr_xdg_dialog_v1.h @@ -0,0 +1,58 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_XDG_DIALOG_V1_H +#define WLR_TYPES_WLR_XDG_DIALOG_V1_H + +#include +#include + +struct wlr_xdg_dialog_v1 { + struct wl_resource *resource; + struct wlr_xdg_toplevel *xdg_toplevel; + + bool modal; + + struct { + struct wl_signal destroy; + // Corresponds to both xdg_dialog_v1.set_modal and xdg_dialog_v1.unset_modal + struct wl_signal set_modal; + } events; + + struct { + struct wlr_addon surface_addon; + + struct wl_listener xdg_toplevel_destroy; + } WLR_PRIVATE; +}; + +struct wlr_xdg_wm_dialog_v1 { + struct wl_global *global; + + struct { + struct wl_signal destroy; + struct wl_signal new_dialog; // struct wlr_xdg_dialog_v1 + } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +struct wlr_xdg_wm_dialog_v1 *wlr_xdg_wm_dialog_v1_create(struct wl_display *display, + uint32_t version); + +/** + * Get a struct wlr_xdg_dialog_v1 from a struct wlr_xdg_toplevel. + * + * Returns NULL if there's no xdg_dialog_v1 associated with the xdg toplevel. + */ +struct wlr_xdg_dialog_v1 *wlr_xdg_dialog_v1_try_from_wlr_xdg_toplevel( + struct wlr_xdg_toplevel *xdg_toplevel); + +#endif diff --git a/include/wlr/types/wlr_xdg_foreign_registry.h b/include/wlr/types/wlr_xdg_foreign_registry.h index 54c91e4d7..aa33b33a6 100644 --- a/include/wlr/types/wlr_xdg_foreign_registry.h +++ b/include/wlr/types/wlr_xdg_foreign_registry.h @@ -23,17 +23,20 @@ struct wlr_xdg_foreign_registry { struct wl_list exported_surfaces; // struct wlr_xdg_foreign_exported_surface - struct wl_listener display_destroy; struct { struct wl_signal destroy; } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_xdg_foreign_exported { struct wl_list link; // wlr_xdg_foreign_registry.exported_surfaces struct wlr_xdg_foreign_registry *registry; - struct wlr_surface *surface; + struct wlr_xdg_toplevel *toplevel; char handle[WLR_XDG_FOREIGN_HANDLE_SIZE]; diff --git a/include/wlr/types/wlr_xdg_foreign_v1.h b/include/wlr/types/wlr_xdg_foreign_v1.h index bb26fcfca..975828c2c 100644 --- a/include/wlr/types/wlr_xdg_foreign_v1.h +++ b/include/wlr/types/wlr_xdg_foreign_v1.h @@ -18,9 +18,6 @@ struct wlr_xdg_foreign_v1 { struct wl_list objects; // wlr_xdg_exported_v1.link or wlr_xdg_imported_v1.link } exporter, importer; - struct wl_listener foreign_registry_destroy; - struct wl_listener display_destroy; - struct wlr_xdg_foreign_registry *registry; struct { @@ -28,34 +25,46 @@ struct wlr_xdg_foreign_v1 { } events; void *data; + + struct { + struct wl_listener foreign_registry_destroy; + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_xdg_exported_v1 { struct wlr_xdg_foreign_exported base; struct wl_resource *resource; - struct wl_listener xdg_toplevel_destroy; - struct wl_list link; // wlr_xdg_foreign_v1.exporter.objects + + struct { + struct wl_listener xdg_toplevel_destroy; + } WLR_PRIVATE; }; struct wlr_xdg_imported_v1 { struct wlr_xdg_foreign_exported *exported; - struct wl_listener exported_destroyed; struct wl_resource *resource; struct wl_list link; // wlr_xdg_foreign_v1.importer.objects struct wl_list children; + + struct { + struct wl_listener exported_destroyed; + } WLR_PRIVATE; }; struct wlr_xdg_imported_child_v1 { struct wlr_xdg_imported_v1 *imported; - struct wlr_surface *surface; + struct wlr_xdg_toplevel *toplevel; struct wl_list link; // wlr_xdg_imported_v1.children - struct wl_listener xdg_toplevel_destroy; - struct wl_listener xdg_toplevel_set_parent; + struct { + struct wl_listener xdg_toplevel_destroy; + struct wl_listener xdg_toplevel_set_parent; + } WLR_PRIVATE; }; struct wlr_xdg_foreign_v1 *wlr_xdg_foreign_v1_create( diff --git a/include/wlr/types/wlr_xdg_foreign_v2.h b/include/wlr/types/wlr_xdg_foreign_v2.h index d82854b46..0af0be52e 100644 --- a/include/wlr/types/wlr_xdg_foreign_v2.h +++ b/include/wlr/types/wlr_xdg_foreign_v2.h @@ -18,9 +18,6 @@ struct wlr_xdg_foreign_v2 { struct wl_list objects; // wlr_xdg_exported_v2.link or wlr_xdg_imported_v2.link } exporter, importer; - struct wl_listener foreign_registry_destroy; - struct wl_listener display_destroy; - struct wlr_xdg_foreign_registry *registry; struct { @@ -28,34 +25,46 @@ struct wlr_xdg_foreign_v2 { } events; void *data; + + struct { + struct wl_listener foreign_registry_destroy; + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_xdg_exported_v2 { struct wlr_xdg_foreign_exported base; struct wl_resource *resource; - struct wl_listener xdg_toplevel_destroy; - struct wl_list link; // wlr_xdg_foreign_v2.exporter.objects + + struct { + struct wl_listener xdg_toplevel_destroy; + } WLR_PRIVATE; }; struct wlr_xdg_imported_v2 { struct wlr_xdg_foreign_exported *exported; - struct wl_listener exported_destroyed; struct wl_resource *resource; struct wl_list link; // wlr_xdg_foreign_v2.importer.objects struct wl_list children; + + struct { + struct wl_listener exported_destroyed; + } WLR_PRIVATE; }; struct wlr_xdg_imported_child_v2 { struct wlr_xdg_imported_v2 *imported; - struct wlr_surface *surface; + struct wlr_xdg_toplevel *toplevel; struct wl_list link; // wlr_xdg_imported_v2.children - struct wl_listener xdg_toplevel_destroy; - struct wl_listener xdg_toplevel_set_parent; + struct { + struct wl_listener xdg_toplevel_destroy; + struct wl_listener xdg_toplevel_set_parent; + } WLR_PRIVATE; }; struct wlr_xdg_foreign_v2 *wlr_xdg_foreign_v2_create( diff --git a/include/wlr/types/wlr_xdg_output_v1.h b/include/wlr/types/wlr_xdg_output_v1.h index 340252ddf..4ee8ec3b8 100644 --- a/include/wlr/types/wlr_xdg_output_v1.h +++ b/include/wlr/types/wlr_xdg_output_v1.h @@ -21,8 +21,10 @@ struct wlr_xdg_output_v1 { int32_t x, y; int32_t width, height; - struct wl_listener destroy; - struct wl_listener description; + struct { + struct wl_listener destroy; + struct wl_listener description; + } WLR_PRIVATE; }; struct wlr_xdg_output_manager_v1 { @@ -35,10 +37,12 @@ struct wlr_xdg_output_manager_v1 { struct wl_signal destroy; } events; - struct wl_listener display_destroy; - struct wl_listener layout_add; - struct wl_listener layout_change; - struct wl_listener layout_destroy; + struct { + struct wl_listener display_destroy; + struct wl_listener layout_add; + struct wl_listener layout_change; + struct wl_listener layout_destroy; + } WLR_PRIVATE; }; struct wlr_xdg_output_manager_v1 *wlr_xdg_output_manager_v1_create( diff --git a/include/wlr/types/wlr_xdg_shell.h b/include/wlr/types/wlr_xdg_shell.h index d54c5366b..517d77442 100644 --- a/include/wlr/types/wlr_xdg_shell.h +++ b/include/wlr/types/wlr_xdg_shell.h @@ -10,10 +10,10 @@ #define WLR_TYPES_WLR_XDG_SHELL_H #include +#include #include #include #include -#include "xdg-shell-protocol.h" struct wlr_xdg_shell { struct wl_global *global; @@ -22,8 +22,6 @@ struct wlr_xdg_shell { struct wl_list popup_grabs; uint32_t ping_timeout; - struct wl_listener display_destroy; - struct { struct wl_signal new_surface; // struct wlr_xdg_surface struct wl_signal new_toplevel; // struct wlr_xdg_toplevel @@ -32,6 +30,10 @@ struct wlr_xdg_shell { } events; void *data; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_xdg_client { @@ -110,9 +112,9 @@ struct wlr_xdg_popup { struct wl_list grab_link; // wlr_xdg_popup_grab.popups - // private state - - struct wlr_surface_synced synced; + struct { + struct wlr_surface_synced synced; + } WLR_PRIVATE; }; // each seat gets a popup grab @@ -124,7 +126,10 @@ struct wlr_xdg_popup_grab { struct wlr_seat *seat; struct wl_list popups; struct wl_list link; // wlr_xdg_shell.popup_grabs - struct wl_listener seat_destroy; + + struct { + struct wl_listener seat_destroy; + } WLR_PRIVATE; }; enum wlr_xdg_surface_role { @@ -136,6 +141,7 @@ enum wlr_xdg_surface_role { struct wlr_xdg_toplevel_state { bool maximized, fullscreen, resizing, activated, suspended; uint32_t tiled; // enum wlr_edges + uint32_t constrained; // enum wlr_edges int32_t width, height; int32_t max_width, max_height; int32_t min_width, min_height; @@ -153,21 +159,34 @@ enum wlr_xdg_toplevel_configure_field { WLR_XDG_TOPLEVEL_CONFIGURE_WM_CAPABILITIES = 1 << 1, }; +/** + * State set in an toplevel configure sequence. + */ struct wlr_xdg_toplevel_configure { + // Bitmask of optional fields which are set uint32_t fields; // enum wlr_xdg_toplevel_configure_field + + // The following fields must always be set to reflect the current state bool maximized, fullscreen, resizing, activated, suspended; uint32_t tiled; // enum wlr_edges + uint32_t constrained; // enum wlr_edges int32_t width, height; + + // Only for WLR_XDG_TOPLEVEL_CONFIGURE_BOUNDS struct { int32_t width, height; } bounds; + // Only for WLR_XDG_TOPLEVEL_CONFIGURE_WM_CAPABILITIES uint32_t wm_capabilities; // enum wlr_xdg_toplevel_wm_capabilities }; struct wlr_xdg_toplevel_requested { bool maximized, minimized, fullscreen; struct wlr_output *fullscreen_output; - struct wl_listener fullscreen_output_destroy; + + struct { + struct wl_listener fullscreen_output_destroy; + } WLR_PRIVATE; }; struct wlr_xdg_toplevel { @@ -175,7 +194,6 @@ struct wlr_xdg_toplevel { struct wlr_xdg_surface *base; struct wlr_xdg_toplevel *parent; - struct wl_listener parent_unmap; struct wlr_xdg_toplevel_state current, pending; @@ -212,9 +230,11 @@ struct wlr_xdg_toplevel { struct wl_signal set_app_id; } events; - // private state + struct { + struct wlr_surface_synced synced; - struct wlr_surface_synced synced; + struct wl_listener parent_unmap; + } WLR_PRIVATE; }; struct wlr_xdg_surface_configure { @@ -228,9 +248,16 @@ struct wlr_xdg_surface_configure { }; }; +enum wlr_xdg_surface_state_field { + WLR_XDG_SURFACE_STATE_WINDOW_GEOMETRY = 1 << 0, +}; + struct wlr_xdg_surface_state { - uint32_t configure_serial; + uint32_t committed; // enum wlr_xdg_surface_state_field + struct wlr_box geometry; + + uint32_t configure_serial; }; /** @@ -274,6 +301,8 @@ struct wlr_xdg_surface { // Whether the latest commit is an initial commit bool initial_commit; + struct wlr_box geometry; + struct { struct wl_signal destroy; struct wl_signal ping_timeout; @@ -286,11 +315,11 @@ struct wlr_xdg_surface { void *data; - // private state + struct { + struct wlr_surface_synced synced; - struct wlr_surface_synced synced; - - struct wl_listener role_resource_destroy; + struct wl_listener role_resource_destroy; + } WLR_PRIVATE; }; struct wlr_xdg_toplevel_move_event { @@ -356,6 +385,12 @@ struct wlr_xdg_positioner *wlr_xdg_positioner_from_resource( */ void wlr_xdg_surface_ping(struct wlr_xdg_surface *surface); +/** + * Configure the toplevel. Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_configure(struct wlr_xdg_toplevel *toplevel, + const struct wlr_xdg_toplevel_configure *configure); + /** * Request that this toplevel surface be the given size. Returns the associated * configure serial. @@ -421,6 +456,14 @@ uint32_t wlr_xdg_toplevel_set_wm_capabilities(struct wlr_xdg_toplevel *toplevel, uint32_t wlr_xdg_toplevel_set_suspended(struct wlr_xdg_toplevel *toplevel, bool suspended); +/** + * Request that this toplevel consider itself constrained and doesn't attempt to + * resize from some edges. `constrained_edges` is a bitfield of enum wlr_edges. + * Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_set_constrained(struct wlr_xdg_toplevel *toplevel, + uint32_t constrained_edges); + /** * Request that this toplevel closes. */ @@ -523,17 +566,6 @@ struct wlr_xdg_toplevel *wlr_xdg_toplevel_try_from_wlr_surface(struct wlr_surfac */ struct wlr_xdg_popup *wlr_xdg_popup_try_from_wlr_surface(struct wlr_surface *surface); -/** - * Get the surface geometry. - * - * This is either the geometry as set by the client, or defaulted to the bounds - * of the surface + the subsurfaces (as specified by the protocol). - * - * The x and y value can be < 0. - */ -void wlr_xdg_surface_get_geometry(struct wlr_xdg_surface *surface, - struct wlr_box *box); - /** * Call `iterator` on each mapped surface and popup in the xdg-surface tree * (whether or not this xdg-surface is mapped), with the surface's position diff --git a/include/wlr/types/wlr_xdg_system_bell_v1.h b/include/wlr/types/wlr_xdg_system_bell_v1.h new file mode 100644 index 000000000..c58bcbbc5 --- /dev/null +++ b/include/wlr/types/wlr_xdg_system_bell_v1.h @@ -0,0 +1,35 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_XDG_SYSTEM_BELL_V1_H +#define WLR_TYPES_WLR_XDG_SYSTEM_BELL_V1_H + +#include + +struct wlr_xdg_system_bell_v1 { + struct wl_global *global; + + struct { + struct wl_signal destroy; + struct wl_signal ring; // struct wlr_xdg_system_bell_v1_ring_event + } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +struct wlr_xdg_system_bell_v1_ring_event { + struct wl_client *client; + struct wlr_surface *surface; // May be NULL +}; + +struct wlr_xdg_system_bell_v1 *wlr_xdg_system_bell_v1_create(struct wl_display *display, + uint32_t version); + +#endif diff --git a/include/wlr/types/wlr_xdg_toplevel_icon_v1.h b/include/wlr/types/wlr_xdg_toplevel_icon_v1.h new file mode 100644 index 000000000..a08a6a7f8 --- /dev/null +++ b/include/wlr/types/wlr_xdg_toplevel_icon_v1.h @@ -0,0 +1,80 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_XDG_TOPLEVEL_ICON_V1_H +#define WLR_TYPES_WLR_XDG_TOPLEVEL_ICON_V1_H + +#include + +#include + +struct wlr_xdg_toplevel_icon_manager_v1 { + struct wl_global *global; + + struct wl_list resources; + + int *sizes; + size_t n_sizes; + + struct { + struct wl_signal set_icon; // struct wlr_xdg_toplevel_icon_manager_v1_set_icon_event + struct wl_signal destroy; + } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +struct wlr_xdg_toplevel_icon_manager_v1_set_icon_event { + struct wlr_xdg_toplevel *toplevel; + + // Must be referenced to be used after the event is emitted + struct wlr_xdg_toplevel_icon_v1 *icon; // May be NULL +}; + +struct wlr_xdg_toplevel_icon_v1_buffer { + struct wlr_buffer *buffer; + int scale; + + struct wl_list link; // wlr_xdg_toplevel_icon_v1.buffers +}; + +struct wlr_xdg_toplevel_icon_v1 { + char *name; // May be NULL + struct wl_list buffers; // wlr_xdg_toplevel_icon_v1_buffer.link + + struct { + int n_refs; + bool immutable; + } WLR_PRIVATE; +}; + +struct wlr_xdg_toplevel_icon_manager_v1 *wlr_xdg_toplevel_icon_manager_v1_create( + struct wl_display *display, uint32_t version); + +/** + * Set icon size preferences. + * + * The list may be empty. + */ +void wlr_xdg_toplevel_icon_manager_v1_set_sizes(struct wlr_xdg_toplevel_icon_manager_v1 *manager, + int *sizes, size_t n_sizes); + +/** + * Reference an icon. + */ +struct wlr_xdg_toplevel_icon_v1 *wlr_xdg_toplevel_icon_v1_ref( + struct wlr_xdg_toplevel_icon_v1 *icon); + +/** + * Unreference an icon. When the icon reference count reaches 0, it is destroyed. + */ +void wlr_xdg_toplevel_icon_v1_unref(struct wlr_xdg_toplevel_icon_v1 *icon); + +#endif diff --git a/include/wlr/types/wlr_xdg_toplevel_tag_v1.h b/include/wlr/types/wlr_xdg_toplevel_tag_v1.h new file mode 100644 index 000000000..8318cab02 --- /dev/null +++ b/include/wlr/types/wlr_xdg_toplevel_tag_v1.h @@ -0,0 +1,41 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_XDG_TOPLEVEL_TAG_V1_H +#define WLR_TYPES_WLR_XDG_TOPLEVEL_TAG_V1_H + +#include + +struct wlr_xdg_toplevel_tag_manager_v1 { + struct wl_global *global; + + struct { + struct wl_signal set_tag; // struct wlr_xdg_toplevel_tag_manager_v1_set_tag_event + struct wl_signal set_description; // struct wlr_xdg_toplevel_tag_manager_v1_set_description_event + struct wl_signal destroy; + } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +struct wlr_xdg_toplevel_tag_manager_v1_set_tag_event { + struct wlr_xdg_toplevel *toplevel; + const char *tag; +}; + +struct wlr_xdg_toplevel_tag_manager_v1_set_description_event { + struct wlr_xdg_toplevel *toplevel; + const char *description; +}; + +struct wlr_xdg_toplevel_tag_manager_v1 *wlr_xdg_toplevel_tag_manager_v1_create( + struct wl_display *display, uint32_t version); + +#endif diff --git a/include/wlr/util/addon.h b/include/wlr/util/addon.h index c64200cf9..8819acce2 100644 --- a/include/wlr/util/addon.h +++ b/include/wlr/util/addon.h @@ -12,8 +12,9 @@ #include struct wlr_addon_set { - // private state - struct wl_list addons; + struct { + struct wl_list addons; + } WLR_PRIVATE; }; struct wlr_addon; @@ -26,9 +27,11 @@ struct wlr_addon_interface { struct wlr_addon { const struct wlr_addon_interface *impl; - // private state - const void *owner; - struct wl_list link; + + struct { + const void *owner; + struct wl_list link; + } WLR_PRIVATE; }; void wlr_addon_set_init(struct wlr_addon_set *set); diff --git a/include/wlr/util/box.h b/include/wlr/util/box.h index e866b1dfb..64dcbc5cf 100644 --- a/include/wlr/util/box.h +++ b/include/wlr/util/box.h @@ -71,6 +71,14 @@ bool wlr_box_intersection(struct wlr_box *dest, const struct wlr_box *box_a, */ bool wlr_box_contains_point(const struct wlr_box *box, double x, double y); +/** + * Verifies that a box is fully contained within another box. + * + * Returns true if the "smaller" box is fully contained within the "bigger" box. + * If either of the boxes are empty, false is returned. + */ +bool wlr_box_contains_box(const struct wlr_box *bigger, const struct wlr_box *smaller); + /** * Checks whether a box is empty or not. * diff --git a/include/wlr/xwayland/server.h b/include/wlr/xwayland/server.h index fa16a3b00..b699b8f4f 100644 --- a/include/wlr/xwayland/server.h +++ b/include/wlr/xwayland/server.h @@ -48,10 +48,12 @@ struct wlr_xwayland_server { struct wl_signal destroy; } events; - struct wl_listener client_destroy; - struct wl_listener display_destroy; - void *data; + + struct { + struct wl_listener client_destroy; + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_xwayland_server_ready_event { diff --git a/include/wlr/xwayland/shell.h b/include/wlr/xwayland/shell.h index da5cb9907..344ac2201 100644 --- a/include/wlr/xwayland/shell.h +++ b/include/wlr/xwayland/shell.h @@ -25,13 +25,13 @@ struct wlr_xwayland_shell_v1 { struct wl_signal new_surface; // struct wlr_xwayland_surface_v1 } events; - // private state + struct { + struct wl_client *client; + struct wl_list surfaces; // wlr_xwayland_surface_v1.link - struct wl_client *client; - struct wl_list surfaces; // wlr_xwayland_surface_v1.link - - struct wl_listener display_destroy; - struct wl_listener client_destroy; + struct wl_listener display_destroy; + struct wl_listener client_destroy; + } WLR_PRIVATE; }; /** @@ -41,12 +41,12 @@ struct wlr_xwayland_surface_v1 { struct wlr_surface *surface; uint64_t serial; - // private state - - struct wl_resource *resource; - struct wl_list link; - struct wlr_xwayland_shell_v1 *shell; - bool added; + struct { + struct wl_resource *resource; + struct wl_list link; + struct wlr_xwayland_shell_v1 *shell; + bool added; + } WLR_PRIVATE; }; /** diff --git a/include/wlr/xwayland/xwayland.h b/include/wlr/xwayland/xwayland.h index 4b476a036..1c52b3558 100644 --- a/include/wlr/xwayland/xwayland.h +++ b/include/wlr/xwayland/xwayland.h @@ -51,27 +51,28 @@ struct wlr_xwayland { struct wlr_seat *seat; struct { + struct wl_signal destroy; struct wl_signal ready; struct wl_signal new_surface; // struct wlr_xwayland_surface struct wl_signal remove_startup_info; // struct wlr_xwayland_remove_startup_info_event } events; /** - * Add a custom event handler to xwayland. Return 1 if the event was - * handled or 0 to use the default wlr-xwayland handler. wlr-xwayland will + * Add a custom event handler to xwayland. Return true if the event was + * handled or false to use the default wlr-xwayland handler. wlr-xwayland will * free the event. */ - int (*user_event_handler)(struct wlr_xwm *xwm, xcb_generic_event_t *event); + bool (*user_event_handler)(struct wlr_xwayland *wlr_xwayland, xcb_generic_event_t *event); void *data; - // private state - - struct wl_listener server_start; - struct wl_listener server_ready; - struct wl_listener server_destroy; - struct wl_listener seat_destroy; - struct wl_listener shell_destroy; + struct { + struct wl_listener server_start; + struct wl_listener server_ready; + struct wl_listener server_destroy; + struct wl_listener seat_destroy; + struct wl_listener shell_destroy; + } WLR_PRIVATE; }; enum wlr_xwayland_surface_decorations { @@ -92,6 +93,27 @@ enum wlr_xwayland_icccm_input_model { WLR_ICCCM_INPUT_MODEL_GLOBAL = 3, }; +/** + * The type of window (_NET_WM_WINDOW_TYPE). See: + * https://specifications.freedesktop.org/wm-spec/latest/ + */ +enum wlr_xwayland_net_wm_window_type { + WLR_XWAYLAND_NET_WM_WINDOW_TYPE_DESKTOP = 0, + WLR_XWAYLAND_NET_WM_WINDOW_TYPE_DOCK, + WLR_XWAYLAND_NET_WM_WINDOW_TYPE_TOOLBAR, + WLR_XWAYLAND_NET_WM_WINDOW_TYPE_MENU, + WLR_XWAYLAND_NET_WM_WINDOW_TYPE_UTILITY, + WLR_XWAYLAND_NET_WM_WINDOW_TYPE_SPLASH, + WLR_XWAYLAND_NET_WM_WINDOW_TYPE_DIALOG, + WLR_XWAYLAND_NET_WM_WINDOW_TYPE_DROPDOWN_MENU, + WLR_XWAYLAND_NET_WM_WINDOW_TYPE_POPUP_MENU, + WLR_XWAYLAND_NET_WM_WINDOW_TYPE_TOOLTIP, + WLR_XWAYLAND_NET_WM_WINDOW_TYPE_NOTIFICATION, + WLR_XWAYLAND_NET_WM_WINDOW_TYPE_COMBO, + WLR_XWAYLAND_NET_WM_WINDOW_TYPE_DND, + WLR_XWAYLAND_NET_WM_WINDOW_TYPE_NORMAL, +}; + /** * An Xwayland user interface component. It has an absolute position in * layout-local coordinates. @@ -112,13 +134,11 @@ struct wlr_xwayland_surface { struct wlr_surface *surface; struct wlr_addon surface_addon; - struct wl_listener surface_commit; - struct wl_listener surface_map; - struct wl_listener surface_unmap; int16_t x, y; uint16_t width, height; bool override_redirect; + float opacity; char *title; char *class; @@ -126,7 +146,6 @@ struct wlr_xwayland_surface { char *role; char *startup_id; pid_t pid; - bool has_utf8_title; struct wl_list children; // wlr_xwayland_surface.parent_link struct wlr_xwayland_surface *parent; @@ -159,6 +178,13 @@ struct wlr_xwayland_surface { bool maximized_vert, maximized_horz; bool minimized; bool withdrawn; + bool sticky; + bool shaded; + bool skip_taskbar; + bool skip_pager; + bool above; + bool below; + bool demands_attention; bool has_alpha; @@ -171,6 +197,14 @@ struct wlr_xwayland_surface { struct wl_signal request_maximize; struct wl_signal request_fullscreen; struct wl_signal request_activate; + struct wl_signal request_close; + struct wl_signal request_sticky; + struct wl_signal request_shaded; + struct wl_signal request_skip_taskbar; + struct wl_signal request_skip_pager; + struct wl_signal request_above; + struct wl_signal request_below; + struct wl_signal request_demands_attention; struct wl_signal associate; struct wl_signal dissociate; @@ -186,12 +220,24 @@ struct wlr_xwayland_surface { struct wl_signal set_strut_partial; struct wl_signal set_override_redirect; struct wl_signal set_geometry; + struct wl_signal set_opacity; + struct wl_signal set_icon; + struct wl_signal focus_in; + struct wl_signal grab_focus; /* can be used to set initial maximized/fullscreen geometry */ struct wl_signal map_request; struct wl_signal ping_timeout; } events; void *data; + + struct { + char *wm_name, *net_wm_name; + + struct wl_listener surface_commit; + struct wl_listener surface_map; + struct wl_listener surface_unmap; + } WLR_PRIVATE; }; struct wlr_xwayland_surface_configure_event { @@ -259,11 +305,32 @@ void wlr_xwayland_surface_set_minimized(struct wlr_xwayland_surface *surface, bool minimized); void wlr_xwayland_surface_set_maximized(struct wlr_xwayland_surface *surface, - bool maximized); + bool maximized_horz, bool maximized_vert); void wlr_xwayland_surface_set_fullscreen(struct wlr_xwayland_surface *surface, bool fullscreen); +void wlr_xwayland_surface_set_sticky( + struct wlr_xwayland_surface *surface, bool sticky); + +void wlr_xwayland_surface_set_shaded( + struct wlr_xwayland_surface *surface, bool shaded); + +void wlr_xwayland_surface_set_skip_taskbar( + struct wlr_xwayland_surface *surface, bool skip_taskbar); + +void wlr_xwayland_surface_set_skip_pager( + struct wlr_xwayland_surface *surface, bool skip_pager); + +void wlr_xwayland_surface_set_above( + struct wlr_xwayland_surface *surface, bool above); + +void wlr_xwayland_surface_set_below( + struct wlr_xwayland_surface *surface, bool below); + +void wlr_xwayland_surface_set_demands_attention( + struct wlr_xwayland_surface *surface, bool demands_attention); + void wlr_xwayland_set_seat(struct wlr_xwayland *xwayland, struct wlr_seat *seat); @@ -276,8 +343,28 @@ void wlr_xwayland_set_seat(struct wlr_xwayland *xwayland, struct wlr_xwayland_surface *wlr_xwayland_surface_try_from_wlr_surface( struct wlr_surface *surface); +/** + * Offer focus by sending WM_TAKE_FOCUS to a client window supporting it. + * The client may accept or ignore the offer. If it accepts, the surface will + * emit the focus_in signal notifying the compositor that it has received focus. + * + * This is a more compatible method of giving focus to windows using the + * Globally Active input model (see wlr_xwayland_icccm_input_model()) than + * calling wlr_xwayland_surface_activate() unconditionally, since there is no + * reliable way to know in advance whether these windows want to be focused. + */ +void wlr_xwayland_surface_offer_focus(struct wlr_xwayland_surface *xsurface); + void wlr_xwayland_surface_ping(struct wlr_xwayland_surface *surface); +/** + * Returns true if the surface has the given window type. + * Note: a surface may have multiple window types set. + */ +bool wlr_xwayland_surface_has_window_type( + const struct wlr_xwayland_surface *xsurface, + enum wlr_xwayland_net_wm_window_type window_type); + /** Metric to guess if an OR window should "receive" focus * * In the pure X setups, window managers usually straight up ignore override @@ -299,10 +386,10 @@ void wlr_xwayland_surface_ping(struct wlr_xwayland_surface *surface); * Returns: true if the window should receive focus * false if it should be ignored */ -bool wlr_xwayland_or_surface_wants_focus( +bool wlr_xwayland_surface_override_redirect_wants_focus( const struct wlr_xwayland_surface *xsurface); -enum wlr_xwayland_icccm_input_model wlr_xwayland_icccm_input_model( +enum wlr_xwayland_icccm_input_model wlr_xwayland_surface_icccm_input_model( const struct wlr_xwayland_surface *xsurface); /** @@ -315,6 +402,15 @@ enum wlr_xwayland_icccm_input_model wlr_xwayland_icccm_input_model( void wlr_xwayland_set_workareas(struct wlr_xwayland *wlr_xwayland, const struct wlr_box *workareas, size_t num_workareas); +/** + * Fetches the icon set via the _NET_WM_ICON property. + * + * Returns true on success. The caller is responsible for freeing the reply + * using xcb_ewmh_get_wm_icon_reply_wipe(). + */ +bool wlr_xwayland_surface_fetch_icon( + const struct wlr_xwayland_surface *xsurface, + xcb_ewmh_get_wm_icon_reply_t *icon_reply); /** * Get the XCB connection of the XWM. diff --git a/include/xwayland/xwm.h b/include/xwayland/xwm.h index 6259dd9bb..73f440d29 100644 --- a/include/xwayland/xwm.h +++ b/include/xwayland/xwm.h @@ -36,9 +36,11 @@ enum atom_name { NET_WM_STATE, NET_WM_STRUT_PARTIAL, NET_WM_WINDOW_TYPE, + NET_WM_ICON, WM_TAKE_FOCUS, WINDOW, NET_ACTIVE_WINDOW, + NET_CLOSE_WINDOW, NET_WM_MOVERESIZE, NET_SUPPORTING_WM_CHECK, NET_WM_STATE_FOCUSED, @@ -47,6 +49,13 @@ enum atom_name { NET_WM_STATE_MAXIMIZED_VERT, NET_WM_STATE_MAXIMIZED_HORZ, NET_WM_STATE_HIDDEN, + NET_WM_STATE_STICKY, + NET_WM_STATE_SHADED, + NET_WM_STATE_SKIP_TASKBAR, + NET_WM_STATE_SKIP_PAGER, + NET_WM_STATE_ABOVE, + NET_WM_STATE_BELOW, + NET_WM_STATE_DEMANDS_ATTENTION, NET_WM_PING, WM_CHANGE_STATE, WM_STATE, @@ -62,6 +71,7 @@ enum atom_name { NET_STARTUP_ID, NET_STARTUP_INFO, NET_STARTUP_INFO_BEGIN, + NET_WM_WINDOW_OPACITY, NET_WM_WINDOW_TYPE_NORMAL, NET_WM_WINDOW_TYPE_UTILITY, NET_WM_WINDOW_TYPE_TOOLTIP, @@ -73,6 +83,9 @@ enum atom_name { NET_WM_WINDOW_TYPE_NOTIFICATION, NET_WM_WINDOW_TYPE_SPLASH, NET_WM_WINDOW_TYPE_DESKTOP, + NET_WM_WINDOW_TYPE_DOCK, + NET_WM_WINDOW_TYPE_TOOLBAR, + NET_WM_WINDOW_TYPE_DIALOG, DND_SELECTION, DND_AWARE, DND_STATUS, @@ -103,6 +116,7 @@ struct wlr_xwm { xcb_connection_t *xcb_conn; xcb_screen_t *screen; xcb_window_t window; + xcb_window_t no_focus_window; xcb_visualid_t visual_id; xcb_colormap_t colormap; xcb_render_pictformat_t render_format_id; @@ -113,6 +127,7 @@ struct wlr_xwm { struct wlr_xwm_selection dnd_selection; struct wlr_xwayland_surface *focus_surface; + struct wlr_xwayland_surface *offered_focus; // Surfaces in creation order struct wl_list surfaces; // wlr_xwayland_surface.link @@ -123,6 +138,7 @@ struct wlr_xwm { struct wlr_drag *drag; struct wlr_xwayland_surface *drag_focus; + struct wlr_xwayland_surface *drop_focus; const xcb_query_extension_reply_t *xfixes; const xcb_query_extension_reply_t *xres; @@ -135,6 +151,7 @@ struct wlr_xwm { struct wl_listener compositor_new_surface; struct wl_listener compositor_destroy; struct wl_listener shell_v1_new_surface; + struct wl_listener shell_v1_destroy; struct wl_listener seat_set_selection; struct wl_listener seat_set_primary_selection; struct wl_listener seat_start_drag; @@ -143,8 +160,11 @@ struct wlr_xwm { struct wl_listener seat_drag_drop; struct wl_listener seat_drag_destroy; struct wl_listener seat_drag_source_destroy; + struct wl_listener drag_focus_destroy; + struct wl_listener drop_focus_destroy; }; +// xwm_create takes ownership of wm_fd and will close it under all circumstances. struct wlr_xwm *xwm_create(struct wlr_xwayland *wlr_xwayland, int wm_fd); void xwm_destroy(struct wlr_xwm *xwm); @@ -155,6 +175,7 @@ void xwm_set_cursor(struct wlr_xwm *xwm, const uint8_t *pixels, uint32_t stride, int xwm_handle_selection_event(struct wlr_xwm *xwm, xcb_generic_event_t *event); int xwm_handle_selection_client_message(struct wlr_xwm *xwm, xcb_client_message_event_t *ev); +void xwm_seat_unlink_drag_handlers(struct wlr_xwm *xwm); void xwm_set_seat(struct wlr_xwm *xwm, struct wlr_seat *seat); @@ -166,4 +187,6 @@ xcb_void_cookie_t xwm_send_event_with_size(xcb_connection_t *c, uint8_t propagate, xcb_window_t destination, uint32_t event_mask, const void *event, uint32_t length); +void xwm_schedule_flush(struct wlr_xwm *xwm); + #endif diff --git a/meson.build b/meson.build index c28bb772b..4b24e5046 100644 --- a/meson.build +++ b/meson.build @@ -1,13 +1,14 @@ project( 'wlroots', 'c', - version: '0.18.0', + version: '0.20.0-dev', license: 'MIT', - meson_version: '>=0.59.0', + meson_version: '>=1.3', default_options: [ - 'c_std=' + (meson.version().version_compare('>=1.3.0') ? 'c23,c11' : 'c11'), + 'c_std=' + (meson.version().version_compare('>=1.4.0') ? 'c23,c11' : 'c11'), 'warning_level=2', 'werror=true', + 'wrap_mode=nodownload', ], ) @@ -22,6 +23,7 @@ big_endian = target_machine.endian() == 'big' add_project_arguments([ '-D_POSIX_C_SOURCE=200809L', '-DWLR_USE_UNSTABLE', + '-DWLR_PRIVATE=', '-DWLR_LITTLE_ENDIAN=@0@'.format(little_endian.to_int()), '-DWLR_BIG_ENDIAN=@0@'.format(big_endian.to_int()), ], language: 'c') @@ -48,31 +50,10 @@ add_project_arguments(cc.get_supported_arguments([ '-Wno-unused-parameter', ]), language: 'c') -# Compute the relative path used by compiler invocations. -source_root = meson.current_source_dir().split('/') -build_root = meson.global_build_root().split('/') -relative_dir_parts = [] -i = 0 -in_prefix = true -foreach p : build_root - if i >= source_root.length() or not in_prefix or p != source_root[i] - in_prefix = false - relative_dir_parts += '..' - endif - i += 1 -endforeach -i = 0 -in_prefix = true -foreach p : source_root - if i >= build_root.length() or not in_prefix or build_root[i] != p - in_prefix = false - relative_dir_parts += p - endif - i += 1 -endforeach -relative_dir = join_paths(relative_dir_parts) + '/' +fs = import('fs') # Strip relative path prefixes from the code if possible, otherwise hide them. +relative_dir = fs.relative_to(meson.current_source_dir(), meson.global_build_root()) + '/' if cc.has_argument('-fmacro-prefix-map=/prefix/to/hide=') add_project_arguments( '-fmacro-prefix-map=@0@='.format(relative_dir), @@ -93,6 +74,7 @@ features = { 'gles2-renderer': false, 'vulkan-renderer': false, 'gbm-allocator': false, + 'udmabuf-allocator': false, 'session': false, 'color-management': false, } @@ -103,11 +85,16 @@ internal_features = { } internal_config = configuration_data() -wayland_project_options = ['tests=false', 'documentation=false'] +wayland_kwargs = { + 'version': '>=1.24.0', + 'fallback': 'wayland', + 'default_options': [ + 'tests=false', + 'documentation=false', + ], +} wayland_server = dependency('wayland-server', - version: '>=1.23', - fallback: 'wayland', - default_options: wayland_project_options, + kwargs: wayland_kwargs, ) drm = dependency('libdrm', @@ -118,8 +105,8 @@ drm = dependency('libdrm', 'tests=false', ], ) -xkbcommon = dependency( - 'xkbcommon', +xkbcommon = dependency('xkbcommon', + version: '>=1.8.0', fallback: 'libxkbcommon', default_options: [ 'enable-tools=false', @@ -129,7 +116,7 @@ xkbcommon = dependency( ], ) pixman = dependency('pixman-1', - version: '>=0.42.0', + version: '>=0.43.0', fallback: 'pixman', default_options: ['werror=false'], ) diff --git a/meson_options.txt b/meson.options similarity index 92% rename from meson_options.txt rename to meson.options index 35961b10e..5863764aa 100644 --- a/meson_options.txt +++ b/meson.options @@ -4,7 +4,7 @@ option('examples', type: 'boolean', value: true, description: 'Build example app option('icon_directory', description: 'Location used to look for cursors (default: ${datadir}/icons)', type: 'string', value: '') option('renderers', type: 'array', choices: ['auto', 'gles2', 'vulkan'], value: ['auto'], description: 'Select built-in renderers') option('backends', type: 'array', choices: ['auto', 'drm', 'libinput', 'x11'], value: ['auto'], description: 'Select built-in backends') -option('allocators', type: 'array', choices: ['auto', 'gbm'], value: ['auto'], +option('allocators', type: 'array', choices: ['auto', 'gbm', 'udmabuf'], value: ['auto'], description: 'Select built-in allocators') option('session', type: 'feature', value: 'auto', description: 'Enable session support') option('color-management', type: 'feature', value: 'auto', description: 'Enable support for color management') diff --git a/protocol/meson.build b/protocol/meson.build index a4476918b..613d18018 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -1,11 +1,15 @@ wayland_protos = dependency('wayland-protocols', - version: '>=1.35', + version: '>=1.44', fallback: 'wayland-protocols', default_options: ['tests=false'], ) wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') +wlr_deps += wayland_protos -wayland_scanner_dep = dependency('wayland-scanner', native: true) +wayland_scanner_dep = dependency('wayland-scanner', + kwargs: wayland_kwargs, + native: true, +) wayland_scanner = find_program( wayland_scanner_dep.get_variable('wayland_scanner'), native: true, @@ -21,22 +25,30 @@ protocols = { # Staging upstream protocols 'alpha-modifier-v1': wl_protocol_dir / 'staging/alpha-modifier/alpha-modifier-v1.xml', + 'color-management-v1': wl_protocol_dir / 'staging/color-management/color-management-v1.xml', + 'color-representation-v1': wl_protocol_dir / 'staging/color-representation/color-representation-v1.xml', 'content-type-v1': wl_protocol_dir / 'staging/content-type/content-type-v1.xml', 'cursor-shape-v1': wl_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml', 'drm-lease-v1': wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', 'ext-foreign-toplevel-list-v1': wl_protocol_dir / 'staging/ext-foreign-toplevel-list/ext-foreign-toplevel-list-v1.xml', 'ext-idle-notify-v1': wl_protocol_dir / 'staging/ext-idle-notify/ext-idle-notify-v1.xml', + 'ext-image-capture-source-v1': wl_protocol_dir / 'staging/ext-image-capture-source/ext-image-capture-source-v1.xml', + 'ext-image-copy-capture-v1': wl_protocol_dir / 'staging/ext-image-copy-capture/ext-image-copy-capture-v1.xml', 'ext-session-lock-v1': wl_protocol_dir / 'staging/ext-session-lock/ext-session-lock-v1.xml', + 'ext-data-control-v1': wl_protocol_dir / 'staging/ext-data-control/ext-data-control-v1.xml', 'fractional-scale-v1': wl_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml', 'linux-drm-syncobj-v1': wl_protocol_dir / 'staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml', 'security-context-v1': wl_protocol_dir / 'staging/security-context/security-context-v1.xml', 'single-pixel-buffer-v1': wl_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml', 'xdg-activation-v1': wl_protocol_dir / 'staging/xdg-activation/xdg-activation-v1.xml', + 'xdg-dialog-v1': wl_protocol_dir / 'staging/xdg-dialog/xdg-dialog-v1.xml', + 'xdg-system-bell-v1': wl_protocol_dir / 'staging/xdg-system-bell/xdg-system-bell-v1.xml', + 'xdg-toplevel-icon-v1': wl_protocol_dir / 'staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml', + 'xdg-toplevel-tag-v1': wl_protocol_dir / 'staging/xdg-toplevel-tag/xdg-toplevel-tag-v1.xml', 'xwayland-shell-v1': wl_protocol_dir / 'staging/xwayland-shell/xwayland-shell-v1.xml', 'tearing-control-v1': wl_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml', # Unstable upstream protocols - 'fullscreen-shell-unstable-v1': wl_protocol_dir / 'unstable/fullscreen-shell/fullscreen-shell-unstable-v1.xml', 'idle-inhibit-unstable-v1': wl_protocol_dir / 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml', 'keyboard-shortcuts-inhibit-unstable-v1': wl_protocol_dir / 'unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml', 'pointer-constraints-unstable-v1': wl_protocol_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml', diff --git a/protocol/wlr-export-dmabuf-unstable-v1.xml b/protocol/wlr-export-dmabuf-unstable-v1.xml index 751f7efbf..80ea012f5 100644 --- a/protocol/wlr-export-dmabuf-unstable-v1.xml +++ b/protocol/wlr-export-dmabuf-unstable-v1.xml @@ -43,7 +43,7 @@ - Capture the next frame of a an entire output. + Capture the next frame of an entire output. + summary="index of the plane the data in the object applies to"/> diff --git a/protocol/wlr-foreign-toplevel-management-unstable-v1.xml b/protocol/wlr-foreign-toplevel-management-unstable-v1.xml index 108133715..44505bbb6 100644 --- a/protocol/wlr-foreign-toplevel-management-unstable-v1.xml +++ b/protocol/wlr-foreign-toplevel-management-unstable-v1.xml @@ -58,7 +58,7 @@ - + This event indicates that the compositor is done sending events to the zwlr_foreign_toplevel_manager_v1. The server will destroy the object diff --git a/protocol/wlr-gamma-control-unstable-v1.xml b/protocol/wlr-gamma-control-unstable-v1.xml index a9db76240..16e0be8b1 100644 --- a/protocol/wlr-gamma-control-unstable-v1.xml +++ b/protocol/wlr-gamma-control-unstable-v1.xml @@ -72,7 +72,7 @@ tables. At any time the compositor can send a failed event indicating that this object is no longer valid. - There must always be at most one gamma control object per output, which + There can only be at most one gamma control object per output, which has exclusive access to this particular output. When the gamma control object is destroyed, the gamma table is restored to its original value. diff --git a/protocol/wlr-layer-shell-unstable-v1.xml b/protocol/wlr-layer-shell-unstable-v1.xml index d62fd51e9..e9f27e4fd 100644 --- a/protocol/wlr-layer-shell-unstable-v1.xml +++ b/protocol/wlr-layer-shell-unstable-v1.xml @@ -25,7 +25,7 @@ THIS SOFTWARE. - + Clients can use this interface to assign the surface_layer role to wl_surfaces. Such surfaces are assigned to a "layer" of the output and @@ -100,7 +100,7 @@ - + An interface that may be implemented by a wl_surface, for surfaces that are designed to be rendered as a layer of a stacked desktop-like @@ -367,6 +367,7 @@ + @@ -386,5 +387,21 @@ + + + + + + Requests an edge for the exclusive zone to apply. The exclusive + edge will be automatically deduced from anchor points when possible, + but when the surface is anchored to a corner, it will be necessary + to set it explicitly to disambiguate, as it is not possible to deduce + which one of the two corner edges should be used. + + The edge must be one the surface is anchored to, otherwise the + invalid_exclusive_edge protocol error will be raised. + + + diff --git a/protocol/wlr-output-management-unstable-v1.xml b/protocol/wlr-output-management-unstable-v1.xml index 411e2f049..541284a8c 100644 --- a/protocol/wlr-output-management-unstable-v1.xml +++ b/protocol/wlr-output-management-unstable-v1.xml @@ -156,8 +156,8 @@ not assume that the name is a reflection of an underlying DRM connector, X11 connection, etc. - If the compositor implements the xdg-output protocol and this head is - enabled, the xdg_output.name event must report the same name. + If this head matches a wl_output, the wl_output.name event must report + the same name. The name event is sent after a wlr_output_head object is created. This event is only sent once per object, and the name does not change over @@ -176,8 +176,8 @@ the make, model, serial of the underlying DRM connector or the display name of the underlying X11 connection, etc. - If the compositor implements xdg-output and this head is enabled, - the xdg_output.description must report the same description. + If this head matches a wl_output, the wl_output.description event must + report the same name. The description event is sent after a wlr_output_head object is created. This event is only sent once per object, and the description does not @@ -191,6 +191,10 @@ This event describes the physical size of the head. This event is only sent if the head has a physical size (e.g. is not a projector or a virtual device). + + The physical size event is sent after a wlr_output_head object is created. This + event is only sent once per object, and the physical size does not change over + the lifetime of the wlr_output_head object. @@ -264,9 +268,6 @@ This event describes the manufacturer of the head. - This must report the same make as the wl_output interface does in its - geometry event. - Together with the model and serial_number events the purpose is to allow clients to recognize heads from previous sessions and for example load head-specific configurations back. @@ -278,6 +279,10 @@ identify the head by available information from other events but should be aware that there is an increased risk of false positives. + If sent, the make event is sent after a wlr_output_head object is + created and only sent once per object. The make does not change over + the lifetime of the wlr_output_head object. + It is not recommended to display the make string in UI to users. For that the string provided by the description event should be preferred. @@ -288,9 +293,6 @@ This event describes the model of the head. - This must report the same model as the wl_output interface does in its - geometry event. - Together with the make and serial_number events the purpose is to allow clients to recognize heads from previous sessions and for example load head-specific configurations back. @@ -302,6 +304,10 @@ identify the head by available information from other events but should be aware that there is an increased risk of false positives. + If sent, the model event is sent after a wlr_output_head object is + created and only sent once per object. The model does not change over + the lifetime of the wlr_output_head object. + It is not recommended to display the model string in UI to users. For that the string provided by the description event should be preferred. @@ -323,6 +329,10 @@ available information from other events but should be aware that there is an increased risk of false positives. + If sent, the serial number event is sent after a wlr_output_head object + is created and only sent once per object. The serial number does not + change over the lifetime of the wlr_output_head object. + It is not recommended to display the serial_number string in UI to users. For that the string provided by the description event should be preferred. diff --git a/protocol/wlr-output-power-management-unstable-v1.xml b/protocol/wlr-output-power-management-unstable-v1.xml index a97783991..20dbb7760 100644 --- a/protocol/wlr-output-power-management-unstable-v1.xml +++ b/protocol/wlr-output-power-management-unstable-v1.xml @@ -50,7 +50,7 @@ - Create a output power management mode control that can be used to + Create an output power management mode control that can be used to adjust the power management mode for a given output. @@ -79,7 +79,7 @@ - + diff --git a/protocol/wlr-screencopy-unstable-v1.xml b/protocol/wlr-screencopy-unstable-v1.xml index 50b1b7d2a..85b57d7ea 100644 --- a/protocol/wlr-screencopy-unstable-v1.xml +++ b/protocol/wlr-screencopy-unstable-v1.xml @@ -88,7 +88,7 @@ supported buffer type. The "buffer_done" event is sent afterwards to indicate that all supported buffer types have been enumerated. The client will then be able to send a "copy" request. If the capture is successful, - the compositor will send a "flags" followed by a "ready" event. + the compositor will send a "flags" event followed by a "ready" event. For objects version 2 or lower, wl_shm buffers are always supported, ie. the "buffer" event is guaranteed to be sent. @@ -114,12 +114,12 @@ - Copy the frame to the supplied buffer. The buffer must have a the + Copy the frame to the supplied buffer. The buffer must have the correct size, see zwlr_screencopy_frame_v1.buffer and zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a supported format. - If the frame is successfully copied, a "flags" and a "ready" events are + If the frame is successfully copied, "flags" and "ready" events are sent. Otherwise, a "failed" event is sent. @@ -147,8 +147,7 @@ Called as soon as the frame is copied, indicating it is available - for reading. This event includes the time at which presentation happened - at. + for reading. This event includes the time at which the presentation took place. The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, each component being an unsigned 32-bit value. Whole seconds are in diff --git a/release.sh b/release.sh new file mode 100755 index 000000000..8531a23b4 --- /dev/null +++ b/release.sh @@ -0,0 +1,32 @@ +#!/bin/sh -eu + +prev=$(git describe --tags --abbrev=0) +next=$(meson rewrite kwargs info project / | jq -r '.kwargs["project#/"].version') + +case "$next" in +*-dev) + echo "This is a development version" + exit 1 + ;; +esac + +if [ "$prev" = "$next" ]; then + echo "Version not bumped in meson.build" + exit 1 +fi + +if ! git diff-index --quiet HEAD -- meson.build; then + echo "meson.build not committed" + exit 1 +fi + +shortlog="$(git shortlog --no-merges "$prev..")" +(echo "wlroots $next"; echo ""; echo "$shortlog") | git tag "$next" -ase -F - + +prefix=wlroots-$next +archive=$prefix.tar.gz +git archive --prefix="$prefix/" -o "$archive" "$next" +gpg --output "$archive".sig --detach-sig "$archive" + +git push --follow-tags +glab release create "$next" "$archive" "$archive.sig" --notes "" diff --git a/render/allocator/allocator.c b/render/allocator/allocator.c index 27b08fc82..b7dbf3d65 100644 --- a/render/allocator/allocator.c +++ b/render/allocator/allocator.c @@ -2,14 +2,13 @@ #include #include #include +#include #include #include #include #include #include #include -#include "backend/backend.h" -#include "render/allocator/allocator.h" #include "render/allocator/drm_dumb.h" #include "render/allocator/shm.h" #include "render/wlr_renderer.h" @@ -18,6 +17,10 @@ #include "render/allocator/gbm.h" #endif +#if WLR_HAS_UDMABUF_ALLOCATOR +#include "render/allocator/udmabuf.h" +#endif + void wlr_allocator_init(struct wlr_allocator *alloc, const struct wlr_allocator_interface *impl, uint32_t buffer_caps) { assert(impl && impl->destroy && impl->create_buffer); @@ -25,6 +28,7 @@ void wlr_allocator_init(struct wlr_allocator *alloc, .impl = impl, .buffer_caps = buffer_caps, }; + wl_signal_init(&alloc->events.destroy); } @@ -91,11 +95,17 @@ static int reopen_drm_node(int drm_fd, bool allow_render_node) { return new_fd; } -struct wlr_allocator *allocator_autocreate_with_drm_fd( - uint32_t backend_caps, struct wlr_renderer *renderer, - int drm_fd) { +struct wlr_allocator *wlr_allocator_autocreate(struct wlr_backend *backend, + struct wlr_renderer *renderer) { + uint32_t backend_caps = backend->buffer_caps; uint32_t renderer_caps = renderer->render_buffer_caps; + // Note, drm_fd may be negative if unavailable + int drm_fd = wlr_backend_get_drm_fd(backend); + if (drm_fd < 0) { + drm_fd = wlr_renderer_get_drm_fd(renderer); + } + struct wlr_allocator *alloc = NULL; uint32_t gbm_caps = WLR_BUFFER_CAP_DMABUF; @@ -141,20 +151,22 @@ struct wlr_allocator *allocator_autocreate_with_drm_fd( wlr_log(WLR_DEBUG, "Failed to create drm dumb allocator"); } - wlr_log(WLR_ERROR, "Failed to create allocator"); - return NULL; -} - -struct wlr_allocator *wlr_allocator_autocreate(struct wlr_backend *backend, - struct wlr_renderer *renderer) { - uint32_t backend_caps = backend_get_buffer_caps(backend); - // Note, drm_fd may be negative if unavailable - int drm_fd = wlr_backend_get_drm_fd(backend); - if (drm_fd < 0) { - drm_fd = wlr_renderer_get_drm_fd(renderer); + uint32_t udmabuf_caps = WLR_BUFFER_CAP_DMABUF | WLR_BUFFER_CAP_SHM; + if ((backend_caps & udmabuf_caps) && (renderer_caps & udmabuf_caps) && + drm_fd < 0) { +#if WLR_HAS_UDMABUF_ALLOCATOR + wlr_log(WLR_DEBUG, "Trying udmabuf allocator"); + if ((alloc = wlr_udmabuf_allocator_create()) != NULL) { + return alloc; + } + wlr_log(WLR_DEBUG, "Failed to create udmabuf allocator"); +#else + wlr_log(WLR_DEBUG, "Skipping udmabuf allocator: disabled at compile-time"); +#endif } - return allocator_autocreate_with_drm_fd(backend_caps, renderer, drm_fd); + wlr_log(WLR_ERROR, "Failed to create allocator"); + return NULL; } void wlr_allocator_destroy(struct wlr_allocator *alloc) { @@ -162,6 +174,9 @@ void wlr_allocator_destroy(struct wlr_allocator *alloc) { return; } wl_signal_emit_mutable(&alloc->events.destroy, NULL); + + assert(wl_list_empty(&alloc->events.destroy.listener_list)); + alloc->impl->destroy(alloc); } diff --git a/render/allocator/drm_dumb.c b/render/allocator/drm_dumb.c index eb4ce99fe..ea2757b0b 100644 --- a/render/allocator/drm_dumb.c +++ b/render/allocator/drm_dumb.c @@ -135,6 +135,8 @@ static bool buffer_get_dmabuf(struct wlr_buffer *wlr_buffer, static void buffer_destroy(struct wlr_buffer *wlr_buffer) { struct wlr_drm_dumb_buffer *buf = drm_dumb_buffer_from_buffer(wlr_buffer); + wlr_buffer_finish(wlr_buffer); + if (buf->data) { munmap(buf->data, buf->size); } diff --git a/render/allocator/gbm.c b/render/allocator/gbm.c index baa0fb6eb..fadfac9f8 100644 --- a/render/allocator/gbm.c +++ b/render/allocator/gbm.c @@ -9,7 +9,6 @@ #include #include -#include "config.h" #include "render/allocator/gbm.h" #include "render/drm_format_set.h" @@ -39,40 +38,12 @@ static bool export_gbm_bo(struct gbm_bo *bo, attribs.modifier = gbm_bo_get_modifier(bo); int i; - int32_t handle = -1; for (i = 0; i < attribs.n_planes; ++i) { -#if HAVE_GBM_BO_GET_FD_FOR_PLANE - (void)handle; - attribs.fd[i] = gbm_bo_get_fd_for_plane(bo, i); if (attribs.fd[i] < 0) { wlr_log(WLR_ERROR, "gbm_bo_get_fd_for_plane failed"); goto error_fd; } -#else - // GBM is lacking a function to get a FD for a given plane. Instead, - // check all planes have the same handle. We can't use - // drmPrimeHandleToFD because that messes up handle ref'counting in - // the user-space driver. - union gbm_bo_handle plane_handle = gbm_bo_get_handle_for_plane(bo, i); - if (plane_handle.s32 < 0) { - wlr_log(WLR_ERROR, "gbm_bo_get_handle_for_plane failed"); - goto error_fd; - } - if (i == 0) { - handle = plane_handle.s32; - } else if (plane_handle.s32 != handle) { - wlr_log(WLR_ERROR, "Failed to export GBM BO: " - "all planes don't have the same GEM handle"); - goto error_fd; - } - - attribs.fd[i] = gbm_bo_get_fd(bo); - if (attribs.fd[i] < 0) { - wlr_log(WLR_ERROR, "gbm_bo_get_fd failed"); - goto error_fd; - } -#endif attribs.offset[i] = gbm_bo_get_offset(bo, i); attribs.stride[i] = gbm_bo_get_stride_for_plane(bo, i); @@ -96,6 +67,7 @@ static struct wlr_gbm_buffer *create_buffer(struct wlr_gbm_allocator *alloc, bool has_modifier = true; uint64_t fallback_modifier = DRM_FORMAT_MOD_INVALID; + errno = 0; struct gbm_bo *bo = gbm_bo_create_with_modifiers(gbm_device, width, height, format->format, format->modifiers, format->len); if (bo == NULL) { @@ -106,14 +78,15 @@ static struct wlr_gbm_buffer *create_buffer(struct wlr_gbm_allocator *alloc, fallback_modifier = DRM_FORMAT_MOD_LINEAR; } else if (!wlr_drm_format_has(format, DRM_FORMAT_MOD_INVALID)) { // If the format doesn't accept an implicit modifier, bail out. - wlr_log(WLR_ERROR, "gbm_bo_create_with_modifiers failed"); + wlr_log_errno(WLR_ERROR, "gbm_bo_create_with_modifiers failed"); return NULL; } + errno = 0; bo = gbm_bo_create(gbm_device, width, height, format->format, usage); has_modifier = false; } if (bo == NULL) { - wlr_log(WLR_ERROR, "gbm_bo_create failed"); + wlr_log_errno(WLR_ERROR, "gbm_bo_create failed"); return NULL; } @@ -124,7 +97,6 @@ static struct wlr_gbm_buffer *create_buffer(struct wlr_gbm_allocator *alloc, } wlr_buffer_init(&buffer->base, &buffer_impl, width, height); buffer->gbm_bo = bo; - wl_list_insert(&alloc->buffers, &buffer->link); if (!export_gbm_bo(bo, &buffer->dmabuf)) { free(buffer); @@ -139,6 +111,8 @@ static struct wlr_gbm_buffer *create_buffer(struct wlr_gbm_allocator *alloc, buffer->dmabuf.modifier = fallback_modifier; } + wl_list_insert(&alloc->buffers, &buffer->link); + char *format_name = drmGetFormatName(buffer->dmabuf.format); char *modifier_name = drmGetFormatModifierName(buffer->dmabuf.modifier); wlr_log(WLR_DEBUG, "Allocated %dx%d GBM buffer " @@ -153,8 +127,10 @@ static struct wlr_gbm_buffer *create_buffer(struct wlr_gbm_allocator *alloc, } static void buffer_destroy(struct wlr_buffer *wlr_buffer) { - struct wlr_gbm_buffer *buffer = - get_gbm_buffer_from_buffer(wlr_buffer); + struct wlr_gbm_buffer *buffer = get_gbm_buffer_from_buffer(wlr_buffer); + + wlr_buffer_finish(wlr_buffer); + wlr_dmabuf_attributes_finish(&buffer->dmabuf); if (buffer->gbm_bo != NULL) { gbm_bo_destroy(buffer->gbm_bo); diff --git a/render/allocator/meson.build b/render/allocator/meson.build index 730a2a41b..b812f6941 100644 --- a/render/allocator/meson.build +++ b/render/allocator/meson.build @@ -1,6 +1,6 @@ allocators = get_option('allocators') if 'auto' in allocators and get_option('auto_features').enabled() - allocators = ['gbm'] + allocators = ['gbm', 'udmabuf'] elif 'auto' in allocators and get_option('auto_features').disabled() allocators = [] endif @@ -13,13 +13,29 @@ wlr_files += files( gbm = disabler() if 'gbm' in allocators or 'auto' in allocators - gbm = dependency('gbm', version: '>=17.1.0', required: 'gbm' in allocators) + gbm = dependency('gbm', version: '>=21.1', required: 'gbm' in allocators) endif if gbm.found() wlr_files += files('gbm.c') wlr_deps += gbm features += { 'gbm-allocator': true } - - has = cc.has_function('gbm_bo_get_fd_for_plane', dependencies: [gbm]) - internal_config.set10('HAVE_GBM_BO_GET_FD_FOR_PLANE', has) +endif + +udmabuf = false +if 'udmabuf' in allocators or 'auto' in allocators + args = ['-D_GNU_SOURCE'] + prefix = [ + '#include ', + '#include ', + ] + udmabuf = (cc.has_function('memfd_create', args: args, prefix: prefix) and + cc.has_define('F_ADD_SEALS', args: args, prefix: prefix) and + cc.has_header('linux/udmabuf.h')) +endif +if 'udmabuf' in allocators and not udmabuf + error('memfd_create(), F_ADD_SEALS and are required for the udmabuf allocator') +endif +if udmabuf + wlr_files += files('udmabuf.c') + features += { 'udmabuf-allocator': true } endif diff --git a/render/allocator/udmabuf.c b/render/allocator/udmabuf.c new file mode 100644 index 000000000..e0b01b70a --- /dev/null +++ b/render/allocator/udmabuf.c @@ -0,0 +1,162 @@ +#undef _POSIX_C_SOURCE +#define _GNU_SOURCE // for memfd_create() and F_ADD_SEALS + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "render/allocator/udmabuf.h" +#include "render/pixel_format.h" + +static bool buffer_get_shm(struct wlr_buffer *wlr_buffer, struct wlr_shm_attributes *shm) { + struct wlr_udmabuf_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + *shm = buffer->shm; + return true; +} + +static bool buffer_get_dmabuf(struct wlr_buffer *wlr_buffer, struct wlr_dmabuf_attributes *dmabuf) { + struct wlr_udmabuf_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + *dmabuf = buffer->dmabuf; + return true; +} + +static void buffer_destroy(struct wlr_buffer *wlr_buffer) { + struct wlr_udmabuf_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + wlr_dmabuf_attributes_finish(&buffer->dmabuf); + close(buffer->shm.fd); + free(buffer); +} + +static const struct wlr_buffer_impl buffer_impl = { + .destroy = buffer_destroy, + .get_shm = buffer_get_shm, + .get_dmabuf = buffer_get_dmabuf, +}; + +static struct wlr_buffer *allocator_create_buffer( + struct wlr_allocator *wlr_allocator, int width, int height, + const struct wlr_drm_format *format) { + struct wlr_udmabuf_allocator *allocator = wl_container_of(wlr_allocator, allocator, base); + + const struct wlr_pixel_format_info *info = + drm_get_pixel_format_info(format->format); + if (info == NULL) { + wlr_log(WLR_ERROR, "Unsupported pixel format 0x%"PRIX32, format->format); + return NULL; + } + + long page_size = sysconf(_SC_PAGE_SIZE); + if (page_size == -1) { + wlr_log_errno(WLR_ERROR, "Failed to query page size"); + return NULL; + } + + struct wlr_udmabuf_buffer *buffer = calloc(1, sizeof(*buffer)); + if (buffer == NULL) { + return NULL; + } + wlr_buffer_init(&buffer->base, &buffer_impl, width, height); + + // TODO: consider using a single file for multiple buffers + int stride = pixel_format_info_min_stride(info, width); // TODO: align? + size_t size = stride * height; + if (size % page_size != 0) { + size += page_size - (size % page_size); + } + + int memfd = memfd_create("wlroots", MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (memfd < 0) { + wlr_log_errno(WLR_ERROR, "memfd_create() failed"); + goto err_buffer; + } + + if (ftruncate(memfd, size) < 0) { + wlr_log_errno(WLR_ERROR, "ftruncate() failed"); + goto err_memfd; + } + + if (fcntl(memfd, F_ADD_SEALS, F_SEAL_SEAL | F_SEAL_SHRINK) < 0) { + wlr_log_errno(WLR_ERROR, "fcntl(F_ADD_SEALS) failed"); + goto err_memfd; + } + + struct udmabuf_create udmabuf_create = { + .memfd = memfd, + .flags = UDMABUF_FLAGS_CLOEXEC, + .offset = 0, + .size = size, + }; + int dmabuf_fd = ioctl(allocator->fd, UDMABUF_CREATE, &udmabuf_create); + if (dmabuf_fd < 0) { + wlr_log_errno(WLR_ERROR, "ioctl(UDMABUF_CREATE) failed"); + goto err_memfd; + } + + buffer->size = size; + buffer->shm = (struct wlr_shm_attributes){ + .width = width, + .height = height, + .format = format->format, + .offset = 0, + .stride = stride, + .fd = memfd, + }; + buffer->dmabuf = (struct wlr_dmabuf_attributes){ + .width = width, + .height = height, + .format = format->format, + .modifier = DRM_FORMAT_MOD_LINEAR, + .n_planes = 1, + .offset[0] = 0, + .stride[0] = stride, + .fd[0] = dmabuf_fd, + }; + + return &buffer->base; + +err_memfd: + close(memfd); +err_buffer: + free(buffer); + return NULL; +} + +static void allocator_destroy(struct wlr_allocator *wlr_allocator) { + struct wlr_udmabuf_allocator *allocator = wl_container_of(wlr_allocator, allocator, base); + close(allocator->fd); + free(allocator); +} + +static const struct wlr_allocator_interface allocator_impl = { + .destroy = allocator_destroy, + .create_buffer = allocator_create_buffer, +}; + +struct wlr_allocator *wlr_udmabuf_allocator_create(void) { + int fd = open("/dev/udmabuf", O_RDWR | O_CLOEXEC); + if (fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to open /dev/udmabuf"); + return NULL; + } + + struct wlr_udmabuf_allocator *allocator = calloc(1, sizeof(*allocator)); + if (allocator == NULL) { + close(fd); + return NULL; + } + wlr_allocator_init(&allocator->base, &allocator_impl, + WLR_BUFFER_CAP_SHM | WLR_BUFFER_CAP_DMABUF); + + allocator->fd = fd; + + return &allocator->base; +} diff --git a/render/color.c b/render/color.c index 639b004c8..0a1a67be3 100644 --- a/render/color.c +++ b/render/color.c @@ -1,27 +1,121 @@ #include #include +#include #include #include "render/color.h" +#include "util/matrix.h" -struct wlr_color_transform *wlr_color_transform_init_srgb(void) { - struct wlr_color_transform *tx = calloc(1, sizeof(struct wlr_color_transform)); +// See H.273 ColourPrimaries + +static const struct wlr_color_primaries COLOR_PRIMARIES_SRGB = { // code point 1 + .red = { 0.640, 0.330 }, + .green = { 0.300, 0.600 }, + .blue = { 0.150, 0.060 }, + .white = { 0.3127, 0.3290 }, +}; + +static const struct wlr_color_primaries COLOR_PRIMARIES_BT2020 = { // code point 9 + .red = { 0.708, 0.292 }, + .green = { 0.170, 0.797 }, + .blue = { 0.131, 0.046 }, + .white = { 0.3127, 0.3290 }, +}; + +void wlr_color_transform_init(struct wlr_color_transform *tr, enum wlr_color_transform_type type) { + *tr = (struct wlr_color_transform){ + .type = type, + .ref_count = 1, + }; + wlr_addon_set_init(&tr->addons); +} + +struct wlr_color_transform *wlr_color_transform_init_linear_to_inverse_eotf( + enum wlr_color_transfer_function tf) { + struct wlr_color_transform_inverse_eotf *tx = calloc(1, sizeof(*tx)); if (!tx) { return NULL; } - tx->type = COLOR_TRANSFORM_SRGB; - tx->ref_count = 1; - wlr_addon_set_init(&tx->addons); - return tx; + wlr_color_transform_init(&tx->base, COLOR_TRANSFORM_INVERSE_EOTF); + tx->tf = tf; + return &tx->base; +} + +struct wlr_color_transform *wlr_color_transform_init_lut_3x1d(size_t dim, + const uint16_t *r, const uint16_t *g, const uint16_t *b) { + uint16_t *lut_3x1d = malloc(3 * dim * sizeof(lut_3x1d[0])); + if (lut_3x1d == NULL) { + return NULL; + } + + memcpy(&lut_3x1d[0 * dim], r, dim * sizeof(lut_3x1d[0])); + memcpy(&lut_3x1d[1 * dim], g, dim * sizeof(lut_3x1d[0])); + memcpy(&lut_3x1d[2 * dim], b, dim * sizeof(lut_3x1d[0])); + + struct wlr_color_transform_lut_3x1d *tx = calloc(1, sizeof(*tx)); + if (!tx) { + free(lut_3x1d); + return NULL; + } + wlr_color_transform_init(&tx->base, COLOR_TRANSFORM_LUT_3X1D); + tx->lut_3x1d = lut_3x1d; + tx->dim = dim; + return &tx->base; +} + +struct wlr_color_transform *wlr_color_transform_init_pipeline( + struct wlr_color_transform **transforms, size_t len) { + assert(len > 0); + + struct wlr_color_transform **copy = calloc(len, sizeof(copy[0])); + if (copy == NULL) { + return NULL; + } + + struct wlr_color_transform_pipeline *tx = calloc(1, sizeof(*tx)); + if (!tx) { + free(copy); + return NULL; + } + wlr_color_transform_init(&tx->base, COLOR_TRANSFORM_PIPELINE); + + // TODO: flatten nested pipeline transforms + for (size_t i = 0; i < len; i++) { + copy[i] = wlr_color_transform_ref(transforms[i]); + } + + tx->transforms = copy; + tx->len = len; + + return &tx->base; } static void color_transform_destroy(struct wlr_color_transform *tr) { - free(tr->lut3d.lut_3d); + switch (tr->type) { + case COLOR_TRANSFORM_INVERSE_EOTF: + break; + case COLOR_TRANSFORM_LCMS2: + color_transform_lcms2_finish(color_transform_lcms2_from_base(tr)); + break; + case COLOR_TRANSFORM_LUT_3X1D:; + struct wlr_color_transform_lut_3x1d *lut_3x1d = color_transform_lut_3x1d_from_base(tr); + free(lut_3x1d->lut_3x1d); + break; + case COLOR_TRANSFORM_PIPELINE:; + struct wlr_color_transform_pipeline *pipeline = + wl_container_of(tr, pipeline, base); + for (size_t i = 0; i < pipeline->len; i++) { + wlr_color_transform_unref(pipeline->transforms[i]); + } + free(pipeline->transforms); + break; + } wlr_addon_set_finish(&tr->addons); free(tr); } -void wlr_color_transform_ref(struct wlr_color_transform *tr) { +struct wlr_color_transform *wlr_color_transform_ref(struct wlr_color_transform *tr) { tr->ref_count += 1; + return tr; } void wlr_color_transform_unref(struct wlr_color_transform *tr) { @@ -34,3 +128,212 @@ void wlr_color_transform_unref(struct wlr_color_transform *tr) { color_transform_destroy(tr); } } + +struct wlr_color_transform_inverse_eotf *wlr_color_transform_inverse_eotf_from_base( + struct wlr_color_transform *tr) { + assert(tr->type == COLOR_TRANSFORM_INVERSE_EOTF); + struct wlr_color_transform_inverse_eotf *inverse_eotf = wl_container_of(tr, inverse_eotf, base); + return inverse_eotf; +} + +struct wlr_color_transform_lut_3x1d *color_transform_lut_3x1d_from_base( + struct wlr_color_transform *tr) { + assert(tr->type == COLOR_TRANSFORM_LUT_3X1D); + struct wlr_color_transform_lut_3x1d *lut_3x1d = wl_container_of(tr, lut_3x1d, base); + return lut_3x1d; +} + +static float srgb_eval_inverse_eotf(float x) { + // See https://www.w3.org/Graphics/Color/srgb + if (x <= 0.0031308) { + return 12.92 * x; + } else { + return 1.055 * powf(x, 1.0 / 2.4) - 0.055; + } +} + +static float st2084_pq_eval_inverse_eotf(float x) { + // H.273 TransferCharacteristics code point 16 + float c1 = 0.8359375; + float c2 = 18.8515625; + float c3 = 18.6875; + float m = 78.84375; + float n = 0.1593017578125; + if (x < 0) { + x = 0; + } + if (x > 1) { + x = 1; + } + float pow_n = powf(x, n); + return powf((c1 + c2 * pow_n) / (1 + c3 * pow_n), m); +} + +static float bt1886_eval_inverse_eotf(float x) { + float lb = powf(0.0001, 1.0 / 2.4); + float lw = powf(1.0, 1.0 / 2.4); + float a = powf(lw - lb, 2.4); + float b = lb / (lw - lb); + return powf(x / a, 1.0 / 2.4) - b; +} + +static float transfer_function_eval_inverse_eotf( + enum wlr_color_transfer_function tf, float x) { + switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_SRGB: + return srgb_eval_inverse_eotf(x); + case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: + return st2084_pq_eval_inverse_eotf(x); + case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: + return x; + case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: + return powf(x, 1.0 / 2.2); + case WLR_COLOR_TRANSFER_FUNCTION_BT1886: + return bt1886_eval_inverse_eotf(x); + } + abort(); // unreachable +} + +static void color_transform_inverse_eotf_eval( + struct wlr_color_transform_inverse_eotf *tr, + float out[static 3], const float in[static 3]) { + for (size_t i = 0; i < 3; i++) { + out[i] = transfer_function_eval_inverse_eotf(tr->tf, in[i]); + } +} + +static float lut_1d_get(const uint16_t *lut, size_t len, size_t i) { + if (i >= len) { + i = len - 1; + } + return (float) lut[i] / UINT16_MAX; +} + +static float lut_1d_eval(const uint16_t *lut, size_t len, float x) { + double pos = x * (len - 1); + double int_part; + double frac_part = modf(pos, &int_part); + size_t i = (size_t) int_part; + double a = lut_1d_get(lut, len, i); + double b = lut_1d_get(lut, len, i + 1); + return a * (1 - frac_part) + b * frac_part; +} + +static void color_transform_lut_3x1d_eval(struct wlr_color_transform_lut_3x1d *tr, + float out[static 3], const float in[static 3]) { + for (size_t i = 0; i < 3; i++) { + out[i] = lut_1d_eval(&tr->lut_3x1d[tr->dim * i], tr->dim, in[i]); + } +} + +void wlr_color_transform_eval(struct wlr_color_transform *tr, + float out[static 3], const float in[static 3]) { + switch (tr->type) { + case COLOR_TRANSFORM_INVERSE_EOTF: + color_transform_inverse_eotf_eval(wlr_color_transform_inverse_eotf_from_base(tr), out, in); + break; + case COLOR_TRANSFORM_LCMS2: + color_transform_lcms2_eval(color_transform_lcms2_from_base(tr), out, in); + break; + case COLOR_TRANSFORM_LUT_3X1D: + color_transform_lut_3x1d_eval(color_transform_lut_3x1d_from_base(tr), out, in); + break; + case COLOR_TRANSFORM_PIPELINE:; + struct wlr_color_transform_pipeline *pipeline = + wl_container_of(tr, pipeline, base); + float color[3]; + memcpy(color, in, sizeof(color)); + for (size_t i = 0; i < pipeline->len; i++) { + wlr_color_transform_eval(pipeline->transforms[i], color, color); + } + memcpy(out, color, sizeof(color)); + break; + } +} + +void wlr_color_primaries_from_named(struct wlr_color_primaries *out, + enum wlr_color_named_primaries named) { + switch (named) { + case WLR_COLOR_NAMED_PRIMARIES_SRGB: + *out = COLOR_PRIMARIES_SRGB; + return; + case WLR_COLOR_NAMED_PRIMARIES_BT2020: + *out = COLOR_PRIMARIES_BT2020; + return; + } + abort(); +} + +static void multiply_matrix_vector(float out[static 3], float m[static 9], float v[static 3]) { + float result[3] = { + m[0] * v[0] + m[1] * v[1] + m[2] * v[2], + m[3] * v[0] + m[4] * v[1] + m[5] * v[2], + m[6] * v[0] + m[7] * v[1] + m[8] * v[2], + }; + memcpy(out, result, sizeof(result)); +} + +static void xy_to_xyz(float out[static 3], struct wlr_color_cie1931_xy src) { + if (src.y == 0) { + out[0] = out[1] = out[2] = 0; + return; + } + + out[0] = src.x / src.y; + out[1] = 1; + out[2] = (1 - src.x - src.y) / src.y; +} + +void wlr_color_primaries_to_xyz(const struct wlr_color_primaries *primaries, float matrix[static 9]) { + // See: http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html + + float r[3], g[3], b[3], w[3]; + xy_to_xyz(r, primaries->red); + xy_to_xyz(g, primaries->green); + xy_to_xyz(b, primaries->blue); + xy_to_xyz(w, primaries->white); + + float xyz_matrix[9] = { + r[0], g[0], b[0], + r[1], g[1], b[1], + r[2], g[2], b[2], + }; + matrix_invert(xyz_matrix, xyz_matrix); + + float S[3]; + multiply_matrix_vector(S, xyz_matrix, w); + + float result[] = { + S[0] * r[0], S[1] * g[0], S[2] * b[0], + S[0] * r[1], S[1] * g[1], S[2] * b[1], + S[0] * r[2], S[1] * g[2], S[2] * b[2], + }; + memcpy(matrix, result, sizeof(result)); +} + +void wlr_color_transfer_function_get_default_luminance(enum wlr_color_transfer_function tf, + struct wlr_color_luminances *lum) { + switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: + *lum = (struct wlr_color_luminances){ + .min = 0.005, + .max = 10000, + .reference = 203, + }; + break; + case WLR_COLOR_TRANSFER_FUNCTION_BT1886: + *lum = (struct wlr_color_luminances){ + .min = 0.01, + .max = 100, + .reference = 100, + }; + break; + default: + *lum = (struct wlr_color_luminances){ + .min = 0.2, + .max = 80, + .reference = 80, + }; + break; + } +} diff --git a/render/color_fallback.c b/render/color_fallback.c index 72741c331..1b25881c4 100644 --- a/render/color_fallback.c +++ b/render/color_fallback.c @@ -1,5 +1,6 @@ -#include +#include #include +#include "render/color.h" struct wlr_color_transform *wlr_color_transform_init_linear_to_icc( const void *data, size_t size) { @@ -7,3 +8,17 @@ struct wlr_color_transform *wlr_color_transform_init_linear_to_icc( "LCMS2 is compile-time disabled"); return NULL; } + +struct wlr_color_transform_lcms2 *color_transform_lcms2_from_base( + struct wlr_color_transform *tr) { + abort(); // unreachable +} + +void color_transform_lcms2_finish(struct wlr_color_transform_lcms2 *tr) { + abort(); // unreachable +} + +void color_transform_lcms2_eval(struct wlr_color_transform_lcms2 *tr, + float out[static 3], const float in[static 3]) { + abort(); // unreachable +} diff --git a/render/color_lcms2.c b/render/color_lcms2.c index fae4fb6a8..39e16cdd7 100644 --- a/render/color_lcms2.c +++ b/render/color_lcms2.c @@ -1,9 +1,17 @@ +#include #include #include #include #include #include "render/color.h" +struct wlr_color_transform_lcms2 { + struct wlr_color_transform base; + + cmsContext ctx; + cmsHTRANSFORM lcms; +}; + static const cmsCIExyY srgb_whitepoint = { 0.3127, 0.3291, 1 }; static const cmsCIExyYTRIPLE srgb_primaries = { @@ -18,7 +26,7 @@ static void handle_lcms_error(cmsContext ctx, cmsUInt32Number code, const char * struct wlr_color_transform *wlr_color_transform_init_linear_to_icc( const void *data, size_t size) { - struct wlr_color_transform *tx = NULL; + struct wlr_color_transform_lcms2 *tx = NULL; cmsContext ctx = cmsCreateContext(NULL, NULL); if (ctx == NULL) { @@ -31,18 +39,18 @@ struct wlr_color_transform *wlr_color_transform_init_linear_to_icc( cmsHPROFILE icc_profile = cmsOpenProfileFromMemTHR(ctx, data, size); if (icc_profile == NULL) { wlr_log(WLR_ERROR, "cmsOpenProfileFromMemTHR failed"); - goto out_ctx; + goto error_ctx; } if (cmsGetDeviceClass(icc_profile) != cmsSigDisplayClass) { wlr_log(WLR_ERROR, "ICC profile must have the Display device class"); - goto out_icc_profile; + goto error_icc_profile; } cmsToneCurve *linear_tone_curve = cmsBuildGamma(ctx, 1); if (linear_tone_curve == NULL) { wlr_log(WLR_ERROR, "cmsBuildGamma failed"); - goto out_icc_profile; + goto error_icc_profile; } cmsToneCurve *linear_tf[] = { @@ -52,68 +60,54 @@ struct wlr_color_transform *wlr_color_transform_init_linear_to_icc( }; cmsHPROFILE srgb_profile = cmsCreateRGBProfileTHR(ctx, &srgb_whitepoint, &srgb_primaries, linear_tf); + cmsFreeToneCurve(linear_tone_curve); if (srgb_profile == NULL) { wlr_log(WLR_ERROR, "cmsCreateRGBProfileTHR failed"); - goto out_linear_tone_curve; + goto error_icc_profile; } cmsHTRANSFORM lcms_tr = cmsCreateTransformTHR(ctx, srgb_profile, TYPE_RGB_FLT, icc_profile, TYPE_RGB_FLT, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(srgb_profile); + cmsCloseProfile(icc_profile); if (lcms_tr == NULL) { wlr_log(WLR_ERROR, "cmsCreateTransformTHR failed"); - goto out_srgb_profile; + goto error_ctx; } - size_t dim_len = 33; - float *lut_3d = calloc(3 * dim_len * dim_len * dim_len, sizeof(float)); - if (lut_3d == NULL) { - wlr_log_errno(WLR_ERROR, "Allocation failed"); - goto out_lcms_tr; - } - - float factor = 1.0f / (dim_len - 1); - for (size_t b_index = 0; b_index < dim_len; b_index++) { - for (size_t g_index = 0; g_index < dim_len; g_index++) { - for (size_t r_index = 0; r_index < dim_len; r_index++) { - float rgb_in[3] = { - r_index * factor, - g_index * factor, - b_index * factor, - }; - float rgb_out[3]; - // TODO: use a single call to cmsDoTransform for the entire calculation? - // this does require allocating an extra temp buffer - cmsDoTransform(lcms_tr, rgb_in, rgb_out, 1); - - size_t offset = 3 * (r_index + dim_len * g_index + dim_len * dim_len * b_index); - // TODO: maybe clamp values to [0.0, 1.0] here? - lut_3d[offset] = rgb_out[0]; - lut_3d[offset + 1] = rgb_out[1]; - lut_3d[offset + 2] = rgb_out[2]; - } - } - } - - tx = calloc(1, sizeof(struct wlr_color_transform)); + tx = calloc(1, sizeof(*tx)); if (!tx) { - goto out_lcms_tr; + cmsDeleteTransform(lcms_tr); + goto error_ctx; } - tx->type = COLOR_TRANSFORM_LUT_3D; - tx->lut3d.dim_len = dim_len; - tx->lut3d.lut_3d = lut_3d; - tx->ref_count = 1; - wlr_addon_set_init(&tx->addons); + wlr_color_transform_init(&tx->base, COLOR_TRANSFORM_LCMS2); -out_lcms_tr: - cmsDeleteTransform(lcms_tr); -out_linear_tone_curve: - cmsFreeToneCurve(linear_tone_curve); -out_srgb_profile: - cmsCloseProfile(srgb_profile); -out_icc_profile: + tx->ctx = ctx; + tx->lcms = lcms_tr; + + return &tx->base; + +error_icc_profile: cmsCloseProfile(icc_profile); -out_ctx: +error_ctx: cmsDeleteContext(ctx); - return tx; + return NULL; +} + +void color_transform_lcms2_finish(struct wlr_color_transform_lcms2 *tr) { + cmsDeleteTransform(tr->lcms); + cmsDeleteContext(tr->ctx); +} + +struct wlr_color_transform_lcms2 *color_transform_lcms2_from_base( + struct wlr_color_transform *tr) { + assert(tr->type == COLOR_TRANSFORM_LCMS2); + struct wlr_color_transform_lcms2 *lcms2 = wl_container_of(tr, lcms2, base); + return lcms2; +} + +void color_transform_lcms2_eval(struct wlr_color_transform_lcms2 *tr, + float out[static 3], const float in[static 3]) { + cmsDoTransform(tr->lcms, in, out, 1); } diff --git a/render/drm_format_set.c b/render/drm_format_set.c index dc57ad9bc..58d65290a 100644 --- a/render/drm_format_set.c +++ b/render/drm_format_set.c @@ -86,6 +86,23 @@ bool wlr_drm_format_set_add(struct wlr_drm_format_set *set, uint32_t format, return true; } +bool wlr_drm_format_set_remove(struct wlr_drm_format_set *set, uint32_t format, + uint64_t modifier) { + struct wlr_drm_format *fmt = format_set_get(set, format); + if (fmt == NULL) { + return false; + } + + for (size_t idx = 0; idx < fmt->len; idx++) { + if (fmt->modifiers[idx] == modifier) { + memmove(&fmt->modifiers[idx], &fmt->modifiers[idx+1], (fmt->len - idx - 1) * sizeof(fmt->modifiers[0])); + fmt->len--; + return true; + } + } + return false; +} + void wlr_drm_format_init(struct wlr_drm_format *fmt, uint32_t format) { *fmt = (struct wlr_drm_format){ .format = format, diff --git a/render/drm_syncobj.c b/render/drm_syncobj.c index af3e79fcd..912a2b6e5 100644 --- a/render/drm_syncobj.c +++ b/render/drm_syncobj.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "config.h" @@ -12,36 +13,47 @@ #include #endif -struct wlr_drm_syncobj_timeline *wlr_drm_syncobj_timeline_create(int drm_fd) { +static struct wlr_drm_syncobj_timeline *timeline_create(int drm_fd, uint32_t handle) { struct wlr_drm_syncobj_timeline *timeline = calloc(1, sizeof(*timeline)); if (timeline == NULL) { return NULL; } + timeline->drm_fd = drm_fd; timeline->n_refs = 1; + timeline->handle = handle; - if (drmSyncobjCreate(drm_fd, 0, &timeline->handle) != 0) { + wlr_addon_set_init(&timeline->addons); + + return timeline; +} + +struct wlr_drm_syncobj_timeline *wlr_drm_syncobj_timeline_create(int drm_fd) { + uint32_t handle = 0; + if (drmSyncobjCreate(drm_fd, 0, &handle) != 0) { wlr_log_errno(WLR_ERROR, "drmSyncobjCreate failed"); - free(timeline); return NULL; } + struct wlr_drm_syncobj_timeline *timeline = timeline_create(drm_fd, handle); + if (timeline == NULL) { + drmSyncobjDestroy(drm_fd, handle); + } + return timeline; } struct wlr_drm_syncobj_timeline *wlr_drm_syncobj_timeline_import(int drm_fd, int drm_syncobj_fd) { - struct wlr_drm_syncobj_timeline *timeline = calloc(1, sizeof(*timeline)); - if (timeline == NULL) { + uint32_t handle = 0; + if (drmSyncobjFDToHandle(drm_fd, drm_syncobj_fd, &handle) != 0) { + wlr_log_errno(WLR_ERROR, "drmSyncobjFDToHandle failed"); return NULL; } - timeline->drm_fd = drm_fd; - timeline->n_refs = 1; - if (drmSyncobjFDToHandle(drm_fd, drm_syncobj_fd, &timeline->handle) != 0) { - wlr_log_errno(WLR_ERROR, "drmSyncobjFDToHandle failed"); - free(timeline); - return NULL; + struct wlr_drm_syncobj_timeline *timeline = timeline_create(drm_fd, handle); + if (timeline == NULL) { + drmSyncobjDestroy(drm_fd, handle); } return timeline; @@ -63,10 +75,33 @@ void wlr_drm_syncobj_timeline_unref(struct wlr_drm_syncobj_timeline *timeline) { return; } + wlr_addon_set_finish(&timeline->addons); drmSyncobjDestroy(timeline->drm_fd, timeline->handle); free(timeline); } +int wlr_drm_syncobj_timeline_export(struct wlr_drm_syncobj_timeline *timeline) { + int drm_syncobj_fd = -1; + if (drmSyncobjHandleToFD(timeline->drm_fd, timeline->handle, &drm_syncobj_fd) != 0) { + wlr_log_errno(WLR_ERROR, "drmSyncobjHandleToFD failed"); + return -1; + } + return drm_syncobj_fd; +} + +bool wlr_drm_syncobj_timeline_transfer(struct wlr_drm_syncobj_timeline *dst, + uint64_t dst_point, struct wlr_drm_syncobj_timeline *src, uint64_t src_point) { + assert(dst->drm_fd == src->drm_fd); + + if (drmSyncobjTransfer(dst->drm_fd, dst->handle, dst_point, + src->handle, src_point, 0) != 0) { + wlr_log_errno(WLR_ERROR, "drmSyncobjTransfer failed"); + return false; + } + + return true; +} + int wlr_drm_syncobj_timeline_export_sync_file(struct wlr_drm_syncobj_timeline *timeline, uint64_t src_point) { int sync_file_fd = -1; @@ -101,7 +136,7 @@ bool wlr_drm_syncobj_timeline_import_sync_file(struct wlr_drm_syncobj_timeline * uint32_t syncobj_handle; if (drmSyncobjCreate(timeline->drm_fd, 0, &syncobj_handle) != 0) { wlr_log_errno(WLR_ERROR, "drmSyncobjCreate failed"); - return -1; + return false; } if (drmSyncobjImportSyncFile(timeline->drm_fd, syncobj_handle, @@ -157,13 +192,15 @@ static int handle_eventfd_ready(int ev_fd, uint32_t mask, void *data) { } } - wl_signal_emit_mutable(&waiter->events.ready, NULL); + waiter->callback(waiter); return 0; } bool wlr_drm_syncobj_timeline_waiter_init(struct wlr_drm_syncobj_timeline_waiter *waiter, struct wlr_drm_syncobj_timeline *timeline, uint64_t point, uint32_t flags, - struct wl_event_loop *loop) { + struct wl_event_loop *loop, wlr_drm_syncobj_timeline_ready_callback callback) { + assert(callback); + int ev_fd; #if HAVE_EVENTFD ev_fd = eventfd(0, EFD_CLOEXEC); @@ -175,7 +212,7 @@ bool wlr_drm_syncobj_timeline_waiter_init(struct wlr_drm_syncobj_timeline_waiter wlr_log(WLR_ERROR, "eventfd() is unavailable"); #endif if (ev_fd < 0) { - return NULL; + return false; } struct drm_syncobj_eventfd syncobj_eventfd = { @@ -187,26 +224,25 @@ bool wlr_drm_syncobj_timeline_waiter_init(struct wlr_drm_syncobj_timeline_waiter if (drmIoctl(timeline->drm_fd, DRM_IOCTL_SYNCOBJ_EVENTFD, &syncobj_eventfd) != 0) { wlr_log_errno(WLR_ERROR, "DRM_IOCTL_SYNCOBJ_EVENTFD failed"); close(ev_fd); - return NULL; + return false; } struct wl_event_source *source = wl_event_loop_add_fd(loop, ev_fd, WL_EVENT_READABLE, handle_eventfd_ready, waiter); if (source == NULL) { wlr_log(WLR_ERROR, "Failed to add FD to event loop"); close(ev_fd); - return NULL; + return false; } *waiter = (struct wlr_drm_syncobj_timeline_waiter){ .ev_fd = ev_fd, .event_source = source, + .callback = callback, }; - wl_signal_init(&waiter->events.ready); return true; } void wlr_drm_syncobj_timeline_waiter_finish(struct wlr_drm_syncobj_timeline_waiter *waiter) { - wl_list_remove(&waiter->events.ready.listener_list); wl_event_source_remove(waiter->event_source); close(waiter->ev_fd); } diff --git a/render/egl.c b/render/egl.c index 19868ca84..6f3e9c8ca 100644 --- a/render/egl.c +++ b/render/egl.c @@ -260,7 +260,8 @@ static struct wlr_egl *egl_create(void) { return egl; } -static bool egl_init_display(struct wlr_egl *egl, EGLDisplay display) { +static bool egl_init_display(struct wlr_egl *egl, EGLDisplay display, + bool allow_software) { egl->display = display; EGLint major, minor; @@ -312,17 +313,6 @@ static bool egl_init_display(struct wlr_egl *egl, EGLDisplay display) { return false; } - if (check_egl_ext(device_exts_str, "EGL_MESA_device_software")) { - if (env_parse_bool("WLR_RENDERER_ALLOW_SOFTWARE")) { - wlr_log(WLR_INFO, "Using software rendering"); - } else { - wlr_log(WLR_ERROR, "Software rendering detected, please use " - "the WLR_RENDERER_ALLOW_SOFTWARE environment variable " - "to proceed"); - return false; - } - } - #ifdef EGL_DRIVER_NAME_EXT if (check_egl_ext(device_exts_str, "EGL_EXT_device_persistent_id")) { driver_name = egl->procs.eglQueryDeviceStringEXT(egl->device, @@ -334,6 +324,19 @@ static bool egl_init_display(struct wlr_egl *egl, EGLDisplay display) { check_egl_ext(device_exts_str, "EGL_EXT_device_drm"); egl->exts.EXT_device_drm_render_node = check_egl_ext(device_exts_str, "EGL_EXT_device_drm_render_node"); + + // The only way a non-DRM device is selected is when the user + // explicitly picks software rendering + if (check_egl_ext(device_exts_str, "EGL_MESA_device_software")) { + if (allow_software || env_parse_bool("WLR_RENDERER_ALLOW_SOFTWARE")) { + wlr_log(WLR_INFO, "Using software rendering"); + } else { + wlr_log(WLR_ERROR, "Software rendering detected, please use " + "the WLR_RENDERER_ALLOW_SOFTWARE environment variable " + "to proceed"); + return false; + } + } } if (!check_egl_ext(display_exts_str, "EGL_KHR_no_config_context") && @@ -348,6 +351,18 @@ static bool egl_init_display(struct wlr_egl *egl, EGLDisplay display) { return false; } + if (check_egl_ext(display_exts_str, "EGL_KHR_fence_sync") && + check_egl_ext(display_exts_str, "EGL_ANDROID_native_fence_sync")) { + load_egl_proc(&egl->procs.eglCreateSyncKHR, "eglCreateSyncKHR"); + load_egl_proc(&egl->procs.eglDestroySyncKHR, "eglDestroySyncKHR"); + load_egl_proc(&egl->procs.eglDupNativeFenceFDANDROID, + "eglDupNativeFenceFDANDROID"); + } + + if (check_egl_ext(display_exts_str, "EGL_KHR_wait_sync")) { + load_egl_proc(&egl->procs.eglWaitSyncKHR, "eglWaitSyncKHR"); + } + egl->exts.IMG_context_priority = check_egl_ext(display_exts_str, "EGL_IMG_context_priority"); @@ -367,7 +382,7 @@ static bool egl_init_display(struct wlr_egl *egl, EGLDisplay display) { } static bool egl_init(struct wlr_egl *egl, EGLenum platform, - void *remote_display) { + void *remote_display, bool allow_software) { EGLint display_attribs[3] = {0}; size_t display_attribs_len = 0; @@ -377,7 +392,7 @@ static bool egl_init(struct wlr_egl *egl, EGLenum platform, } display_attribs[display_attribs_len++] = EGL_NONE; - assert(display_attribs_len < sizeof(display_attribs) / sizeof(display_attribs[0])); + assert(display_attribs_len <= sizeof(display_attribs) / sizeof(display_attribs[0])); EGLDisplay display = egl->procs.eglGetPlatformDisplayEXT(platform, remote_display, display_attribs); @@ -386,7 +401,7 @@ static bool egl_init(struct wlr_egl *egl, EGLenum platform, return false; } - if (!egl_init_display(egl, display)) { + if (!egl_init_display(egl, display, allow_software)) { if (egl->exts.KHR_display_reference) { eglTerminate(display); } @@ -464,32 +479,55 @@ static EGLDeviceEXT get_egl_device_from_drm_fd(struct wlr_egl *egl, if (!egl->procs.eglQueryDevicesEXT(nb_devices, devices, &nb_devices)) { wlr_log(WLR_ERROR, "Failed to query EGL devices"); + free(devices); return EGL_NO_DEVICE_EXT; } - drmDevice *device = NULL; - int ret = drmGetDevice(drm_fd, &device); - if (ret < 0) { - wlr_log(WLR_ERROR, "Failed to get DRM device: %s", strerror(-ret)); - return EGL_NO_DEVICE_EXT; + drmDevice *selected_drm_device = NULL; + if (drm_fd >= 0) { + int ret = drmGetDevice(drm_fd, &selected_drm_device); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get DRM device: %s", strerror(-ret)); + free(devices); + return EGL_NO_DEVICE_EXT; + } } EGLDeviceEXT egl_device = NULL; for (int i = 0; i < nb_devices; i++) { - const char *egl_device_name = egl->procs.eglQueryDeviceStringEXT( - devices[i], EGL_DRM_DEVICE_FILE_EXT); - if (egl_device_name == NULL) { + const char *device_exts_str = egl->procs.eglQueryDeviceStringEXT(devices[i], EGL_EXTENSIONS); + if (device_exts_str == NULL) { + wlr_log(WLR_ERROR, "eglQueryDeviceStringEXT(EGL_EXTENSIONS) failed"); continue; } - if (device_has_name(device, egl_device_name)) { - wlr_log(WLR_DEBUG, "Using EGL device %s", egl_device_name); + const char *egl_device_name = NULL; + if (check_egl_ext(device_exts_str, "EGL_EXT_device_drm")) { + egl_device_name = egl->procs.eglQueryDeviceStringEXT(devices[i], EGL_DRM_DEVICE_FILE_EXT); + if (egl_device_name == NULL) { + wlr_log(WLR_ERROR, "eglQueryDeviceStringEXT(EGL_DRM_DEVICE_FILE_EXT) failed"); + continue; + } + } + + bool is_software = check_egl_ext(device_exts_str, "EGL_MESA_device_software"); + + bool found; + if (selected_drm_device != NULL) { + found = egl_device_name != NULL && device_has_name(selected_drm_device, egl_device_name); + } else { + found = is_software; + } + if (found) { + if (egl_device_name != NULL) { + wlr_log(WLR_DEBUG, "Using EGL device %s", egl_device_name); + } egl_device = devices[i]; break; } } - drmFreeDevice(&device); + drmFreeDevice(&selected_drm_device); free(devices); return egl_device; @@ -518,6 +556,8 @@ static int open_render_node(int drm_fd) { } struct wlr_egl *wlr_egl_create_with_drm_fd(int drm_fd) { + bool allow_software = drm_fd < 0; + struct wlr_egl *egl = egl_create(); if (egl == NULL) { wlr_log(WLR_ERROR, "Failed to create EGL context"); @@ -531,7 +571,7 @@ struct wlr_egl *wlr_egl_create_with_drm_fd(int drm_fd) { */ EGLDeviceEXT egl_device = get_egl_device_from_drm_fd(egl, drm_fd); if (egl_device != EGL_NO_DEVICE_EXT) { - if (egl_init(egl, EGL_PLATFORM_DEVICE_EXT, egl_device)) { + if (egl_init(egl, EGL_PLATFORM_DEVICE_EXT, egl_device, allow_software)) { wlr_log(WLR_DEBUG, "Using EGL_PLATFORM_DEVICE_EXT"); return egl; } @@ -542,7 +582,7 @@ struct wlr_egl *wlr_egl_create_with_drm_fd(int drm_fd) { wlr_log(WLR_DEBUG, "EXT_platform_device not supported"); } - if (egl->exts.KHR_platform_gbm) { + if (egl->exts.KHR_platform_gbm && drm_fd >= 0) { int gbm_fd = open_render_node(drm_fd); if (gbm_fd < 0) { wlr_log(WLR_ERROR, "Failed to open DRM render node"); @@ -556,7 +596,7 @@ struct wlr_egl *wlr_egl_create_with_drm_fd(int drm_fd) { goto error; } - if (egl_init(egl, EGL_PLATFORM_GBM_KHR, egl->gbm_device)) { + if (egl_init(egl, EGL_PLATFORM_GBM_KHR, egl->gbm_device, allow_software)) { wlr_log(WLR_DEBUG, "Using EGL_PLATFORM_GBM_KHR"); return egl; } @@ -595,7 +635,7 @@ struct wlr_egl *wlr_egl_create_with_context(EGLDisplay display, return NULL; } - if (!egl_init_display(egl, display)) { + if (!egl_init_display(egl, display, true)) { free(egl); return NULL; } @@ -770,7 +810,7 @@ EGLImageKHR wlr_egl_create_image_from_dmabuf(struct wlr_egl *egl, attribs[atti++] = EGL_TRUE; attribs[atti++] = EGL_NONE; - assert(atti < sizeof(attribs)/sizeof(attribs[0])); + assert(atti <= sizeof(attribs)/sizeof(attribs[0])); EGLImageKHR image = egl->procs.eglCreateImageKHR(egl->display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attribs); @@ -1014,3 +1054,65 @@ int wlr_egl_dup_drm_fd(struct wlr_egl *egl) { } return fd; } + +EGLSyncKHR wlr_egl_create_sync(struct wlr_egl *egl, int fence_fd) { + if (!egl->procs.eglCreateSyncKHR) { + return EGL_NO_SYNC_KHR; + } + + EGLint attribs[3] = { EGL_NONE }; + int dup_fd = -1; + if (fence_fd >= 0) { + dup_fd = fcntl(fence_fd, F_DUPFD_CLOEXEC, 0); + if (dup_fd < 0) { + wlr_log_errno(WLR_ERROR, "dup failed"); + return EGL_NO_SYNC_KHR; + } + + attribs[0] = EGL_SYNC_NATIVE_FENCE_FD_ANDROID; + attribs[1] = dup_fd; + attribs[2] = EGL_NONE; + } + + EGLSyncKHR sync = egl->procs.eglCreateSyncKHR(egl->display, + EGL_SYNC_NATIVE_FENCE_ANDROID, attribs); + if (sync == EGL_NO_SYNC_KHR) { + wlr_log(WLR_ERROR, "eglCreateSyncKHR failed"); + if (dup_fd >= 0) { + close(dup_fd); + } + } + return sync; +} + +void wlr_egl_destroy_sync(struct wlr_egl *egl, EGLSyncKHR sync) { + if (sync == EGL_NO_SYNC_KHR) { + return; + } + assert(egl->procs.eglDestroySyncKHR); + if (egl->procs.eglDestroySyncKHR(egl->display, sync) != EGL_TRUE) { + wlr_log(WLR_ERROR, "eglDestroySyncKHR failed"); + } +} + +int wlr_egl_dup_fence_fd(struct wlr_egl *egl, EGLSyncKHR sync) { + if (!egl->procs.eglDupNativeFenceFDANDROID) { + return -1; + } + + int fd = egl->procs.eglDupNativeFenceFDANDROID(egl->display, sync); + if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { + wlr_log(WLR_ERROR, "eglDupNativeFenceFDANDROID failed"); + return -1; + } + + return fd; +} + +bool wlr_egl_wait_sync(struct wlr_egl *egl, EGLSyncKHR sync) { + if (egl->procs.eglWaitSyncKHR(egl->display, sync, 0) != EGL_TRUE) { + wlr_log(WLR_ERROR, "eglWaitSyncKHR failed"); + return false; + } + return true; +} diff --git a/render/gles2/meson.build b/render/gles2/meson.build index 2a6db9647..a16ec4061 100644 --- a/render/gles2/meson.build +++ b/render/gles2/meson.build @@ -4,6 +4,8 @@ if not (glesv2.found() and internal_features['egl']) subdir_done() endif +glslang = find_program('glslang', 'glslangValidator', native: true, required: false, disabler: true) + features += { 'gles2-renderer': true } wlr_deps += glesv2 diff --git a/render/gles2/pass.c b/render/gles2/pass.c index 9177b0a1f..b10ac047d 100644 --- a/render/gles2/pass.c +++ b/render/gles2/pass.c @@ -2,10 +2,12 @@ #include #include #include -#include +#include +#include #include +#include "render/egl.h" #include "render/gles2.h" -#include "types/wlr_matrix.h" +#include "util/matrix.h" #define MAX_QUADS 86 // 4kb @@ -21,6 +23,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { struct wlr_gles2_render_pass *pass = get_render_pass(wlr_pass); struct wlr_gles2_renderer *renderer = pass->buffer->renderer; struct wlr_gles2_render_timer *timer = pass->timer; + bool ok = false; push_gles2_debug(renderer); @@ -36,16 +39,40 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { clock_gettime(CLOCK_MONOTONIC, &timer->cpu_end); } - glFlush(); + if (pass->signal_timeline != NULL) { + EGLSyncKHR sync = wlr_egl_create_sync(renderer->egl, -1); + if (sync == EGL_NO_SYNC_KHR) { + goto out; + } + + int sync_file_fd = wlr_egl_dup_fence_fd(renderer->egl, sync); + wlr_egl_destroy_sync(renderer->egl, sync); + if (sync_file_fd < 0) { + goto out; + } + + ok = wlr_drm_syncobj_timeline_import_sync_file(pass->signal_timeline, pass->signal_point, sync_file_fd); + close(sync_file_fd); + if (!ok) { + goto out; + } + } else { + glFlush(); + } + + ok = true; + +out: glBindFramebuffer(GL_FRAMEBUFFER, 0); pop_gles2_debug(renderer); wlr_egl_restore_context(&pass->prev_ctx); + wlr_drm_syncobj_timeline_unref(pass->signal_timeline); wlr_buffer_unlock(pass->buffer->buffer); free(pass); - return true; + return ok; } static void render(const struct wlr_box *box, const pixman_region32_t *clip, GLint attrib) { @@ -175,6 +202,27 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, src_fbox.height /= options->texture->height; push_gles2_debug(renderer); + + if (options->wait_timeline != NULL) { + int sync_file_fd = + wlr_drm_syncobj_timeline_export_sync_file(options->wait_timeline, options->wait_point); + if (sync_file_fd < 0) { + return; + } + + EGLSyncKHR sync = wlr_egl_create_sync(renderer->egl, sync_file_fd); + close(sync_file_fd); + if (sync == EGL_NO_SYNC_KHR) { + return; + } + + bool ok = wlr_egl_wait_sync(renderer->egl, sync); + wlr_egl_destroy_sync(renderer->egl, sync); + if (!ok) { + return; + } + } + setup_blending(!texture->has_alpha && alpha == 1.0 ? WLR_RENDER_BLEND_MODE_NONE : options->blend_mode); @@ -247,7 +295,8 @@ static const char *reset_status_str(GLenum status) { } struct wlr_gles2_render_pass *begin_gles2_buffer_pass(struct wlr_gles2_buffer *buffer, - struct wlr_egl_context *prev_ctx, struct wlr_gles2_render_timer *timer) { + struct wlr_egl_context *prev_ctx, struct wlr_gles2_render_timer *timer, + struct wlr_drm_syncobj_timeline *signal_timeline, uint64_t signal_point) { struct wlr_gles2_renderer *renderer = buffer->renderer; struct wlr_buffer *wlr_buffer = buffer->buffer; @@ -275,6 +324,10 @@ struct wlr_gles2_render_pass *begin_gles2_buffer_pass(struct wlr_gles2_buffer *b pass->buffer = buffer; pass->timer = timer; pass->prev_ctx = *prev_ctx; + if (signal_timeline != NULL) { + pass->signal_timeline = wlr_drm_syncobj_timeline_ref(signal_timeline); + pass->signal_point = signal_point; + } matrix_projection(pass->projection_matrix, wlr_buffer->width, wlr_buffer->height, WL_OUTPUT_TRANSFORM_FLIPPED_180); diff --git a/render/gles2/renderer.c b/render/gles2/renderer.c index 4694b2a8f..e362daee8 100644 --- a/render/gles2/renderer.c +++ b/render/gles2/renderer.c @@ -12,13 +12,12 @@ #include #include #include -#include #include #include +#include #include "render/egl.h" #include "render/gles2.h" #include "render/pixel_format.h" -#include "types/wlr_matrix.h" #include "util/time.h" #include "common_vert_src.h" @@ -259,7 +258,8 @@ static struct wlr_render_pass *gles2_begin_buffer_pass(struct wlr_renderer *wlr_ return NULL; } - struct wlr_gles2_render_pass *pass = begin_gles2_buffer_pass(buffer, &prev_ctx, timer); + struct wlr_gles2_render_pass *pass = begin_gles2_buffer_pass(buffer, + &prev_ctx, timer, options->signal_timeline, options->signal_point); if (!pass) { return NULL; } @@ -683,6 +683,13 @@ struct wlr_renderer *wlr_gles2_renderer_create(struct wlr_egl *egl) { get_gles2_shm_formats(renderer, &renderer->shm_texture_formats); + int drm_fd = wlr_renderer_get_drm_fd(&renderer->wlr_renderer); + uint64_t cap_syncobj_timeline; + if (drm_fd >= 0 && drmGetCap(drm_fd, DRM_CAP_SYNCOBJ_TIMELINE, &cap_syncobj_timeline) == 0) { + renderer->wlr_renderer.features.timeline = egl->procs.eglDupNativeFenceFDANDROID && + egl->procs.eglWaitSyncKHR && cap_syncobj_timeline != 0; + } + return &renderer->wlr_renderer; error: diff --git a/render/gles2/shaders/meson.build b/render/gles2/shaders/meson.build index 79454d9aa..64e4e93fb 100644 --- a/render/gles2/shaders/meson.build +++ b/render/gles2/shaders/meson.build @@ -9,6 +9,14 @@ shaders = [ ] foreach name : shaders + custom_target( + 'gles2-' + name, + input: name, + output: name + '_check', + command: [glslang, '@INPUT@'], + build_by_default: true, + ) + output = name.underscorify() + '_src.h' var = name.underscorify() + '_src' wlr_files += custom_target( diff --git a/render/gles2/texture.c b/render/gles2/texture.c index f9ad4cc56..9a967ebdb 100644 --- a/render/gles2/texture.c +++ b/render/gles2/texture.c @@ -13,7 +13,6 @@ #include "render/egl.h" #include "render/gles2.h" #include "render/pixel_format.h" -#include "types/wlr_buffer.h" static const struct wlr_texture_impl texture_impl; diff --git a/render/pass.c b/render/pass.c index 902228273..23bdf96dd 100644 --- a/render/pass.c +++ b/render/pass.c @@ -21,8 +21,8 @@ void wlr_render_pass_add_texture(struct wlr_render_pass *render_pass, if (!wlr_fbox_empty(&options->src_box)) { const struct wlr_fbox *box = &options->src_box; assert(box->x >= 0 && box->y >= 0 && - box->x + box->width <= options->texture->width && - box->y + box->height <= options->texture->height); + (uint32_t)(box->x + box->width) <= options->texture->width && + (uint32_t)(box->y + box->height) <= options->texture->height); } render_pass->impl->add_texture(render_pass, options); diff --git a/render/pixman/pass.c b/render/pixman/pass.c index 30883c8b4..4ae742cab 100644 --- a/render/pixman/pass.c +++ b/render/pixman/pass.c @@ -48,7 +48,7 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, } pixman_op_t op = get_pixman_blending(options->blend_mode); - pixman_image_set_clip_region32(buffer->image, (pixman_region32_t *)options->clip); + pixman_image_set_clip_region32(buffer->image, options->clip); struct wlr_fbox src_fbox; wlr_render_texture_options_get_src_box(options, &src_fbox); @@ -217,7 +217,7 @@ static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, pixman_image_t *fill = pixman_image_create_solid_fill(&color); - pixman_image_set_clip_region32(buffer->image, (pixman_region32_t *)options->clip); + pixman_image_set_clip_region32(buffer->image, options->clip); pixman_image_composite32(op, fill, NULL, buffer->image, 0, 0, 0, 0, box.x, box.y, box.width, box.height); pixman_image_set_clip_region32(buffer->image, NULL); diff --git a/render/pixman/renderer.c b/render/pixman/renderer.c index a5b83e38e..4631a33ab 100644 --- a/render/pixman/renderer.c +++ b/render/pixman/renderer.c @@ -43,7 +43,7 @@ bool begin_pixman_data_ptr_access(struct wlr_buffer *wlr_buffer, pixman_image_t pixman_image_t *new_image = pixman_image_create_bits_no_clear(format, wlr_buffer->width, wlr_buffer->height, data, stride); - if (image == NULL) { + if (new_image == NULL) { wlr_buffer_end_data_ptr_access(wlr_buffer); return false; } diff --git a/render/swapchain.c b/render/swapchain.c index a50dae5c1..24d6f1a87 100644 --- a/render/swapchain.c +++ b/render/swapchain.c @@ -1,9 +1,9 @@ #include #include #include +#include #include #include -#include "render/allocator/allocator.h" #include "render/drm_format_set.h" static void swapchain_handle_allocator_destroy(struct wl_listener *listener, @@ -18,6 +18,8 @@ static void swapchain_handle_allocator_destroy(struct wl_listener *listener, struct wlr_swapchain *wlr_swapchain_create( struct wlr_allocator *alloc, int width, int height, const struct wlr_drm_format *format) { + assert(width > 0 && height > 0); + struct wlr_swapchain *swapchain = calloc(1, sizeof(*swapchain)); if (swapchain == NULL) { return NULL; @@ -65,7 +67,7 @@ static void slot_handle_release(struct wl_listener *listener, void *data) { } static struct wlr_buffer *slot_acquire(struct wlr_swapchain *swapchain, - struct wlr_swapchain_slot *slot, int *age) { + struct wlr_swapchain_slot *slot) { assert(!slot->acquired); assert(slot->buffer != NULL); @@ -74,15 +76,10 @@ static struct wlr_buffer *slot_acquire(struct wlr_swapchain *swapchain, slot->release.notify = slot_handle_release; wl_signal_add(&slot->buffer->events.release, &slot->release); - if (age != NULL) { - *age = slot->age; - } - return wlr_buffer_lock(slot->buffer); } -struct wlr_buffer *wlr_swapchain_acquire(struct wlr_swapchain *swapchain, - int *age) { +struct wlr_buffer *wlr_swapchain_acquire(struct wlr_swapchain *swapchain) { struct wlr_swapchain_slot *free_slot = NULL; for (size_t i = 0; i < WLR_SWAPCHAIN_CAP; i++) { struct wlr_swapchain_slot *slot = &swapchain->slots[i]; @@ -90,7 +87,7 @@ struct wlr_buffer *wlr_swapchain_acquire(struct wlr_swapchain *swapchain, continue; } if (slot->buffer != NULL) { - return slot_acquire(swapchain, slot, age); + return slot_acquire(swapchain, slot); } free_slot = slot; } @@ -110,7 +107,7 @@ struct wlr_buffer *wlr_swapchain_acquire(struct wlr_swapchain *swapchain, wlr_log(WLR_ERROR, "Failed to allocate buffer"); return NULL; } - return slot_acquire(swapchain, free_slot, age); + return slot_acquire(swapchain, free_slot); } bool wlr_swapchain_has_buffer(struct wlr_swapchain *swapchain, @@ -123,23 +120,3 @@ bool wlr_swapchain_has_buffer(struct wlr_swapchain *swapchain, } return false; } - -void wlr_swapchain_set_buffer_submitted(struct wlr_swapchain *swapchain, - struct wlr_buffer *buffer) { - assert(buffer != NULL); - - if (!wlr_swapchain_has_buffer(swapchain, buffer)) { - return; - } - - // See the algorithm described in: - // https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_buffer_age.txt - for (size_t i = 0; i < WLR_SWAPCHAIN_CAP; i++) { - struct wlr_swapchain_slot *slot = &swapchain->slots[i]; - if (slot->buffer == buffer) { - slot->age = 1; - } else if (slot->age > 0) { - slot->age++; - } - } -} diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index c879b71c8..398ee2104 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -1,12 +1,14 @@ #include #include #include +#include #include #include +#include #include "render/color.h" #include "render/vulkan.h" -#include "types/wlr_matrix.h" +#include "util/matrix.h" static const struct wlr_render_pass_impl render_pass_impl; static const struct wlr_addon_interface vk_color_transform_impl; @@ -55,28 +57,98 @@ static void convert_pixman_box_to_vk_rect(const pixman_box32_t *box, VkRect2D *r } static float color_to_linear(float non_linear) { - // See https://www.w3.org/Graphics/Color/srgb - return (non_linear > 0.04045) ? - pow((non_linear + 0.055) / 1.055, 2.4) : - non_linear / 12.92; + return pow(non_linear, 2.2); } static float color_to_linear_premult(float non_linear, float alpha) { return (alpha == 0) ? 0 : color_to_linear(non_linear / alpha) * alpha; } -static void mat3_to_mat4(const float mat3[9], float mat4[4][4]) { - memset(mat4, 0, sizeof(float) * 16); - mat4[0][0] = mat3[0]; - mat4[0][1] = mat3[1]; - mat4[0][3] = mat3[2]; +static void encode_proj_matrix(const float mat3[9], float mat4[4][4]) { + float result[4][4] = { + { mat3[0], mat3[1], 0, mat3[2] }, + { mat3[3], mat3[4], 0, mat3[5] }, + { 0, 0, 1, 0 }, + { 0, 0, 0, 1 }, + }; - mat4[1][0] = mat3[3]; - mat4[1][1] = mat3[4]; - mat4[1][3] = mat3[5]; + memcpy(mat4, result, sizeof(result)); +} - mat4[2][2] = 1.f; - mat4[3][3] = 1.f; +static void encode_color_matrix(const float mat3[9], float mat4[4][4]) { + float result[4][4] = { + { mat3[0], mat3[1], mat3[2], 0 }, + { mat3[3], mat3[4], mat3[5], 0 }, + { mat3[6], mat3[7], mat3[8], 0 }, + { 0, 0, 0, 0 }, + }; + + memcpy(mat4, result, sizeof(result)); +} + +static void render_pass_destroy(struct wlr_vk_render_pass *pass) { + struct wlr_vk_render_pass_texture *pass_texture; + wl_array_for_each(pass_texture, &pass->textures) { + wlr_drm_syncobj_timeline_unref(pass_texture->wait_timeline); + } + + wlr_color_transform_unref(pass->color_transform); + wlr_drm_syncobj_timeline_unref(pass->signal_timeline); + rect_union_finish(&pass->updated_region); + wl_array_release(&pass->textures); + free(pass); +} + +static VkSemaphore render_pass_wait_sync_file(struct wlr_vk_render_pass *pass, + size_t sem_index, int sync_file_fd) { + struct wlr_vk_renderer *renderer = pass->renderer; + struct wlr_vk_command_buffer *render_cb = pass->command_buffer; + VkResult res; + + VkSemaphore *wait_semaphores = render_cb->wait_semaphores.data; + size_t wait_semaphores_len = render_cb->wait_semaphores.size / sizeof(wait_semaphores[0]); + + VkSemaphore *sem_ptr; + if (sem_index >= wait_semaphores_len) { + sem_ptr = wl_array_add(&render_cb->wait_semaphores, sizeof(*sem_ptr)); + if (sem_ptr == NULL) { + return VK_NULL_HANDLE; + } + *sem_ptr = VK_NULL_HANDLE; + } else { + sem_ptr = &wait_semaphores[sem_index]; + } + + if (*sem_ptr == VK_NULL_HANDLE) { + VkSemaphoreCreateInfo semaphore_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + }; + res = vkCreateSemaphore(renderer->dev->dev, &semaphore_info, NULL, sem_ptr); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateSemaphore", res); + return VK_NULL_HANDLE; + } + } + + VkImportSemaphoreFdInfoKHR import_info = { + .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR, + .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + .flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT, + .semaphore = *sem_ptr, + .fd = sync_file_fd, + }; + res = renderer->dev->api.vkImportSemaphoreFdKHR(renderer->dev->dev, &import_info); + if (res != VK_SUCCESS) { + wlr_vk_error("vkImportSemaphoreFdKHR", res); + return VK_NULL_HANDLE; + } + + return *sem_ptr; +} + +static float get_luminance_multiplier(const struct wlr_color_luminances *src_lum, + const struct wlr_color_luminances *dst_lum) { + return (dst_lum->reference / src_lum->reference) * (src_lum->max / dst_lum->max); } static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { @@ -100,7 +172,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { assert(stage_cb != NULL); renderer->stage.cb = NULL; - if (!pass->srgb_pathway) { + if (pass->two_pass) { // Apply output shader to map blend image to actual output image vkCmdNextSubpass(render_cb->vk, VK_SUBPASS_CONTENTS_INLINE); @@ -116,18 +188,76 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { .uv_off = { 0, 0 }, .uv_size = { 1, 1 }, }; - size_t dim = pass->color_transform ? pass->color_transform->lut3d.dim_len : 1; + encode_proj_matrix(final_matrix, vert_pcr_data.mat4); + + struct wlr_vk_color_transform *transform = NULL; + size_t dim = 1; + if (pass->color_transform && pass->color_transform->type != COLOR_TRANSFORM_INVERSE_EOTF) { + transform = get_color_transform(pass->color_transform, renderer); + assert(transform); + dim = transform->lut_3d.dim; + } + struct wlr_vk_frag_output_pcr_data frag_pcr_data = { + .luminance_multiplier = 1, .lut_3d_offset = 0.5f / dim, .lut_3d_scale = (float)(dim - 1) / dim, }; - mat3_to_mat4(final_matrix, vert_pcr_data.mat4); - if (pass->color_transform) { - bind_pipeline(pass, render_buffer->plain.render_setup->output_pipe_lut3d); + float matrix[9]; + if (pass->has_primaries) { + struct wlr_color_primaries srgb; + wlr_color_primaries_from_named(&srgb, WLR_COLOR_NAMED_PRIMARIES_SRGB); + + float srgb_to_xyz[9]; + wlr_color_primaries_to_xyz(&srgb, srgb_to_xyz); + float dst_primaries_to_xyz[9]; + wlr_color_primaries_to_xyz(&pass->primaries, dst_primaries_to_xyz); + float xyz_to_dst_primaries[9]; + matrix_invert(xyz_to_dst_primaries, dst_primaries_to_xyz); + + wlr_matrix_multiply(matrix, xyz_to_dst_primaries, srgb_to_xyz); } else { - bind_pipeline(pass, render_buffer->plain.render_setup->output_pipe_srgb); + wlr_matrix_identity(matrix); } + encode_color_matrix(matrix, frag_pcr_data.matrix); + + VkPipeline pipeline = VK_NULL_HANDLE; + if (pass->color_transform && pass->color_transform->type != COLOR_TRANSFORM_INVERSE_EOTF) { + pipeline = render_buffer->two_pass.render_setup->output_pipe_lut3d; + } else { + enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; + if (pass->color_transform && pass->color_transform->type == COLOR_TRANSFORM_INVERSE_EOTF) { + struct wlr_color_transform_inverse_eotf *inverse_eotf = + wlr_color_transform_inverse_eotf_from_base(pass->color_transform); + tf = inverse_eotf->tf; + } + + switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: + pipeline = render_buffer->two_pass.render_setup->output_pipe_identity; + break; + case WLR_COLOR_TRANSFER_FUNCTION_SRGB: + pipeline = render_buffer->two_pass.render_setup->output_pipe_srgb; + break; + case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: + pipeline = render_buffer->two_pass.render_setup->output_pipe_pq; + break; + case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: + pipeline = render_buffer->two_pass.render_setup->output_pipe_gamma22; + break; + case WLR_COLOR_TRANSFER_FUNCTION_BT1886: + pipeline = render_buffer->two_pass.render_setup->output_pipe_bt1886; + break; + } + + struct wlr_color_luminances srgb_lum, dst_lum; + wlr_color_transfer_function_get_default_luminance( + WLR_COLOR_TRANSFER_FUNCTION_SRGB, &srgb_lum); + wlr_color_transfer_function_get_default_luminance(tf, &dst_lum); + frag_pcr_data.luminance_multiplier = get_luminance_multiplier(&srgb_lum, &dst_lum); + } + bind_pipeline(pass, pipeline); vkCmdPushConstants(render_cb->vk, renderer->output_pipe_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(vert_pcr_data), &vert_pcr_data); vkCmdPushConstants(render_cb->vk, renderer->output_pipe_layout, @@ -135,16 +265,13 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { sizeof(frag_pcr_data), &frag_pcr_data); VkDescriptorSet lut_ds; - if (pass->color_transform && pass->color_transform->type == COLOR_TRANSFORM_LUT_3D) { - struct wlr_vk_color_transform *transform = - get_color_transform(pass->color_transform, renderer); - assert(transform); + if (transform != NULL) { lut_ds = transform->lut_3d.ds; } else { lut_ds = renderer->output_ds_lut3d_dummy; } VkDescriptorSet ds[] = { - render_buffer->plain.blend_descriptor_set, // set 0 + render_buffer->two_pass.blend_descriptor_set, // set 0 lut_ds, // set 1 }; size_t ds_len = sizeof(ds) / sizeof(ds[0]); @@ -166,14 +293,15 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { vkCmdEndRenderPass(render_cb->vk); - // insert acquire and release barriers for dmabuf-images - uint32_t barrier_count = wl_list_length(&renderer->foreign_textures) + 1; - render_wait = calloc(barrier_count * WLR_DMABUF_MAX_PLANES, sizeof(*render_wait)); + size_t pass_textures_len = pass->textures.size / sizeof(struct wlr_vk_render_pass_texture); + size_t render_wait_cap = pass_textures_len * WLR_DMABUF_MAX_PLANES; + render_wait = calloc(render_wait_cap, sizeof(*render_wait)); if (render_wait == NULL) { wlr_log_errno(WLR_ERROR, "Allocation failed"); goto error; } + uint32_t barrier_count = wl_list_length(&renderer->foreign_textures) + 1; VkImageMemoryBarrier *acquire_barriers = calloc(barrier_count, sizeof(*acquire_barriers)); VkImageMemoryBarrier *release_barriers = calloc(barrier_count, sizeof(*release_barriers)); if (acquire_barriers == NULL || release_barriers == NULL) { @@ -185,7 +313,6 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { struct wlr_vk_texture *texture, *tmp_tex; size_t idx = 0; - uint32_t render_wait_len = 0; wl_list_for_each_safe(texture, tmp_tex, &renderer->foreign_textures, foreign_link) { if (!texture->transitioned) { texture->transitioned = true; @@ -223,51 +350,77 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { ++idx; - if (!vulkan_sync_foreign_texture(texture)) { - wlr_log(WLR_ERROR, "Failed to wait for foreign texture DMA-BUF fence"); - } else { - for (size_t i = 0; i < WLR_DMABUF_MAX_PLANES; i++) { - if (texture->foreign_semaphores[i] != VK_NULL_HANDLE) { - assert(render_wait_len < barrier_count * WLR_DMABUF_MAX_PLANES); - render_wait[render_wait_len++] = (VkSemaphoreSubmitInfoKHR){ - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO_KHR, - .semaphore = texture->foreign_semaphores[i], - .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT_KHR, - }; - } - } - } - wl_list_remove(&texture->foreign_link); texture->owned = false; } + uint32_t render_wait_len = 0; + struct wlr_vk_render_pass_texture *pass_texture; + wl_array_for_each(pass_texture, &pass->textures) { + int sync_file_fds[WLR_DMABUF_MAX_PLANES]; + for (size_t i = 0; i < WLR_DMABUF_MAX_PLANES; i++) { + sync_file_fds[i] = -1; + } + + if (pass_texture->wait_timeline) { + int sync_file_fd = wlr_drm_syncobj_timeline_export_sync_file(pass_texture->wait_timeline, pass_texture->wait_point); + if (sync_file_fd < 0) { + wlr_log(WLR_ERROR, "Failed to export wait timeline point as sync_file"); + continue; + } + + sync_file_fds[0] = sync_file_fd; + } else { + struct wlr_vk_texture *texture = pass_texture->texture; + if (!vulkan_sync_foreign_texture(texture, sync_file_fds)) { + wlr_log(WLR_ERROR, "Failed to wait for foreign texture DMA-BUF fence"); + continue; + } + } + + for (size_t i = 0; i < WLR_DMABUF_MAX_PLANES; i++) { + if (sync_file_fds[i] < 0) { + continue; + } + + VkSemaphore sem = render_pass_wait_sync_file(pass, render_wait_len, sync_file_fds[i]); + if (sem == VK_NULL_HANDLE) { + close(sync_file_fds[i]); + continue; + } + + render_wait[render_wait_len] = (VkSemaphoreSubmitInfoKHR){ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO_KHR, + .semaphore = sem, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT_KHR, + }; + + render_wait_len++; + } + } + // also add acquire/release barriers for the current render buffer VkImageLayout src_layout = VK_IMAGE_LAYOUT_GENERAL; - if (pass->srgb_pathway) { - if (!render_buffer->srgb.transitioned) { - src_layout = VK_IMAGE_LAYOUT_PREINITIALIZED; - render_buffer->srgb.transitioned = true; - } - } else { - if (!render_buffer->plain.transitioned) { - src_layout = VK_IMAGE_LAYOUT_PREINITIALIZED; - render_buffer->plain.transitioned = true; - } + if (!pass->render_buffer_out->transitioned) { + src_layout = VK_IMAGE_LAYOUT_PREINITIALIZED; + pass->render_buffer_out->transitioned = true; + } + + if (pass->two_pass) { // The render pass changes the blend image layout from // color attachment to read only, so on each frame, before // the render pass starts, we change it back VkImageLayout blend_src_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - if (!render_buffer->plain.blend_transitioned) { + if (!render_buffer->two_pass.blend_transitioned) { blend_src_layout = VK_IMAGE_LAYOUT_UNDEFINED; - render_buffer->plain.blend_transitioned = true; + render_buffer->two_pass.blend_transitioned = true; } VkImageMemoryBarrier blend_acq_barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = render_buffer->plain.blend_image, + .image = render_buffer->two_pass.blend_image, .oldLayout = blend_src_layout, .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, .srcAccessMask = VK_ACCESS_SHADER_READ_BIT, @@ -381,7 +534,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { .semaphore = renderer->timeline_semaphore, .value = render_timeline_point, }; - if (renderer->dev->implicit_sync_interop) { + if (renderer->dev->implicit_sync_interop || pass->signal_timeline != NULL) { if (render_cb->binary_semaphore == VK_NULL_HANDLE) { VkExportSemaphoreCreateInfo export_info = { .sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO, @@ -439,14 +592,13 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { wl_list_insert(&stage_cb->stage_buffers, &stage_buf->link); } - if (!vulkan_sync_render_buffer(renderer, render_buffer, render_cb)) { + if (!vulkan_sync_render_buffer(renderer, render_buffer, render_cb, + pass->signal_timeline, pass->signal_point)) { wlr_log(WLR_ERROR, "Failed to sync render buffer"); } - wlr_color_transform_unref(pass->color_transform); + render_pass_destroy(pass); wlr_buffer_unlock(render_buffer->wlr_buffer); - rect_union_finish(&pass->updated_region); - free(pass); return true; error: @@ -454,8 +606,7 @@ error: vulkan_reset_command_buffer(stage_cb); vulkan_reset_command_buffer(render_cb); wlr_buffer_unlock(render_buffer->wlr_buffer); - rect_union_finish(&pass->updated_region); - free(pass); + render_pass_destroy(pass); if (device_lost) { wl_signal_emit_mutable(&renderer->wlr_renderer.events.lost, NULL); @@ -466,7 +617,7 @@ error: static void render_pass_mark_box_updated(struct wlr_vk_render_pass *pass, const struct wlr_box *box) { - if (pass->srgb_pathway) { + if (!pass->two_pass) { return; } @@ -523,14 +674,11 @@ static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, case WLR_RENDER_BLEND_MODE_PREMULTIPLIED:; float proj[9], matrix[9]; wlr_matrix_identity(proj); - wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0, proj); + wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, proj); wlr_matrix_multiply(matrix, pass->projection, matrix); - struct wlr_vk_render_format_setup *setup = pass->srgb_pathway ? - pass->render_buffer->srgb.render_setup : - pass->render_buffer->plain.render_setup; struct wlr_vk_pipeline *pipe = setup_get_or_create_pipeline( - setup, + pass->render_setup, &(struct wlr_vk_pipeline_key) { .source = WLR_VK_SHADER_SOURCE_SINGLE_COLOR, .layout = { .ycbcr_format = NULL }, @@ -544,7 +692,7 @@ static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, .uv_off = { 0, 0 }, .uv_size = { 1, 1 }, }; - mat3_to_mat4(matrix, vert_pcr_data.mat4); + encode_proj_matrix(matrix, vert_pcr_data.mat4); bind_pipeline(pass, pipe->vk); vkCmdPushConstants(cb, pipe->layout->vk, @@ -572,16 +720,10 @@ static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, }, }; VkClearRect clear_rect = { - .rect = { - .offset = { box.x, box.y }, - .extent = { box.width, box.height }, - }, .layerCount = 1, }; for (int i = 0; i < clip_rects_len; i++) { - VkRect2D rect; - convert_pixman_box_to_vk_rect(&clip_rects[i], &rect); - vkCmdSetScissor(cb, 0, 1, &rect); + convert_pixman_box_to_vk_rect(&clip_rects[i], &clear_rect.rect); vkCmdClearAttachments(cb, 1, &clear_att, 1, &clear_rect); } break; @@ -620,7 +762,7 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, float proj[9], matrix[9]; wlr_matrix_identity(proj); - wlr_matrix_project_box(matrix, &dst_box, options->transform, 0, proj); + wlr_matrix_project_box(matrix, &dst_box, options->transform, proj); wlr_matrix_multiply(matrix, pass->projection, matrix); struct wlr_vk_vert_pcr_data vert_pcr_data = { @@ -633,20 +775,47 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, src_box.height / options->texture->height, }, }; - mat3_to_mat4(matrix, vert_pcr_data.mat4); + encode_proj_matrix(matrix, vert_pcr_data.mat4); + + enum wlr_color_transfer_function tf = options->transfer_function; + if (tf == 0) { + tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; + } + + bool srgb_image_view = false; + enum wlr_vk_texture_transform tex_transform = 0; + switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_SRGB: + if (texture->using_mutable_srgb) { + tex_transform = WLR_VK_TEXTURE_TRANSFORM_IDENTITY; + srgb_image_view = true; + } else { + tex_transform = WLR_VK_TEXTURE_TRANSFORM_SRGB; + } + break; + case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: + tex_transform = WLR_VK_TEXTURE_TRANSFORM_IDENTITY; + break; + case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: + tex_transform = WLR_VK_TEXTURE_TRANSFORM_ST2084_PQ; + break; + case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: + tex_transform = WLR_VK_TEXTURE_TRANSFORM_GAMMA22; + break; + case WLR_COLOR_TRANSFER_FUNCTION_BT1886: + tex_transform = WLR_VK_TEXTURE_TRANSFORM_BT1886; + break; + } - struct wlr_vk_render_format_setup *setup = pass->srgb_pathway ? - pass->render_buffer->srgb.render_setup : - pass->render_buffer->plain.render_setup; struct wlr_vk_pipeline *pipe = setup_get_or_create_pipeline( - setup, + pass->render_setup, &(struct wlr_vk_pipeline_key) { .source = WLR_VK_SHADER_SOURCE_TEXTURE, .layout = { .ycbcr_format = texture->format->is_ycbcr ? texture->format : NULL, .filter_mode = options->filter_mode, }, - .texture_transform = texture->transform, + .texture_transform = tex_transform, .blend_mode = !texture->has_alpha && alpha == 1.0 ? WLR_RENDER_BLEND_MODE_NONE : options->blend_mode, }); @@ -656,12 +825,45 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, } struct wlr_vk_texture_view *view = - vulkan_texture_get_or_create_view(texture, pipe->layout); + vulkan_texture_get_or_create_view(texture, pipe->layout, srgb_image_view); if (!view) { pass->failed = true; return; } + float color_matrix[9]; + if (options->primaries != NULL) { + struct wlr_color_primaries srgb; + wlr_color_primaries_from_named(&srgb, WLR_COLOR_NAMED_PRIMARIES_SRGB); + + float src_primaries_to_xyz[9]; + wlr_color_primaries_to_xyz(options->primaries, src_primaries_to_xyz); + float srgb_to_xyz[9]; + wlr_color_primaries_to_xyz(&srgb, srgb_to_xyz); + float xyz_to_srgb[9]; + matrix_invert(xyz_to_srgb, srgb_to_xyz); + + wlr_matrix_multiply(color_matrix, xyz_to_srgb, src_primaries_to_xyz); + } else { + wlr_matrix_identity(color_matrix); + } + + float luminance_multiplier = 1; + if (tf != WLR_COLOR_TRANSFER_FUNCTION_SRGB + && tf != WLR_COLOR_TRANSFER_FUNCTION_GAMMA22) { + struct wlr_color_luminances src_lum, srgb_lum; + wlr_color_transfer_function_get_default_luminance(tf, &src_lum); + wlr_color_transfer_function_get_default_luminance( + WLR_COLOR_TRANSFER_FUNCTION_SRGB, &srgb_lum); + luminance_multiplier = get_luminance_multiplier(&src_lum, &srgb_lum); + } + + struct wlr_vk_frag_texture_pcr_data frag_pcr_data = { + .alpha = alpha, + .luminance_multiplier = luminance_multiplier, + }; + encode_color_matrix(color_matrix, frag_pcr_data.matrix); + bind_pipeline(pass, pipe->vk); vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, @@ -670,8 +872,8 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, vkCmdPushConstants(cb, pipe->layout->vk, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(vert_pcr_data), &vert_pcr_data); vkCmdPushConstants(cb, pipe->layout->vk, - VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(vert_pcr_data), sizeof(float), - &alpha); + VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(vert_pcr_data), + sizeof(frag_pcr_data), &frag_pcr_data); pixman_region32_t clip; get_clip_region(pass, options->clip, &clip); @@ -700,6 +902,28 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, texture->last_used_cb = pass->command_buffer; pixman_region32_fini(&clip); + + if (texture->dmabuf_imported || (options != NULL && options->wait_timeline != NULL)) { + struct wlr_vk_render_pass_texture *pass_texture = + wl_array_add(&pass->textures, sizeof(*pass_texture)); + if (pass_texture == NULL) { + pass->failed = true; + return; + } + + struct wlr_drm_syncobj_timeline *wait_timeline = NULL; + uint64_t wait_point = 0; + if (options != NULL && options->wait_timeline != NULL) { + wait_timeline = wlr_drm_syncobj_timeline_ref(options->wait_timeline); + wait_point = options->wait_point; + } + + *pass_texture = (struct wlr_vk_render_pass_texture){ + .texture = texture, + .wait_timeline = wait_timeline, + .wait_point = wait_point, + }; + } } static const struct wlr_render_pass_impl render_pass_impl = { @@ -727,9 +951,9 @@ void vk_color_transform_destroy(struct wlr_addon *addon) { } static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, - const struct wlr_color_transform_lut3d *lut_3d, + struct wlr_color_transform *tr, size_t dim_len, VkImage *image, VkImageView *image_view, - VkDeviceMemory *memory, VkDescriptorSet *ds, + VkDeviceMemory *memory, VkDescriptorSet *ds, struct wlr_vk_descriptor_pool **ds_pool) { VkDevice dev = renderer->dev->dev; VkResult res; @@ -753,7 +977,7 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, .samples = VK_SAMPLE_COUNT_1_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .extent = (VkExtent3D) { lut_3d->dim_len, lut_3d->dim_len, lut_3d->dim_len }, + .extent = (VkExtent3D) { dim_len, dim_len, dim_len }, .tiling = VK_IMAGE_TILING_OPTIMAL, .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, }; @@ -814,7 +1038,7 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, } size_t bytes_per_block = 4 * sizeof(float); - size_t size = lut_3d->dim_len * lut_3d->dim_len * lut_3d->dim_len * bytes_per_block; + size_t size = dim_len * dim_len * dim_len * bytes_per_block; struct wlr_vk_buffer_span span = vulkan_get_stage_span(renderer, size, bytes_per_block); if (!span.buffer || span.alloc.size != size) { @@ -822,18 +1046,26 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, goto fail_imageview; } - char *map = (char*)span.buffer->cpu_mapping + span.alloc.start; - float *dst = (float*)map; - size_t dim_len = lut_3d->dim_len; + float sample_range = 1.0f / (dim_len - 1); + char *map = (char *)span.buffer->cpu_mapping + span.alloc.start; + float *dst = (float *)map; for (size_t b_index = 0; b_index < dim_len; b_index++) { for (size_t g_index = 0; g_index < dim_len; g_index++) { for (size_t r_index = 0; r_index < dim_len; r_index++) { size_t sample_index = r_index + dim_len * g_index + dim_len * dim_len * b_index; - size_t src_offset = 3 * sample_index; size_t dst_offset = 4 * sample_index; - dst[dst_offset] = lut_3d->lut_3d[src_offset]; - dst[dst_offset + 1] = lut_3d->lut_3d[src_offset + 1]; - dst[dst_offset + 2] = lut_3d->lut_3d[src_offset + 2]; + + float rgb_in[3] = { + r_index * sample_range, + g_index * sample_range, + b_index * sample_range, + }; + float rgb_out[3]; + wlr_color_transform_eval(tr, rgb_out, rgb_in); + + dst[dst_offset] = rgb_out[0]; + dst[dst_offset + 1] = rgb_out[1]; + dst[dst_offset + 2] = rgb_out[2]; dst[dst_offset + 3] = 1.0; } } @@ -846,9 +1078,9 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, VK_ACCESS_TRANSFER_WRITE_BIT); VkBufferImageCopy copy = { .bufferOffset = span.alloc.start, - .imageExtent.width = lut_3d->dim_len, - .imageExtent.height = lut_3d->dim_len, - .imageExtent.depth = lut_3d->dim_len, + .imageExtent.width = dim_len, + .imageExtent.height = dim_len, + .imageExtent.depth = dim_len, .imageSubresource.layerCount = 1, .imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, }; @@ -899,8 +1131,10 @@ static struct wlr_vk_color_transform *vk_color_transform_create( return NULL; } - if (transform->type == COLOR_TRANSFORM_LUT_3D) { - if (!create_3d_lut_image(renderer, &transform->lut3d, + if (transform->type != COLOR_TRANSFORM_INVERSE_EOTF) { + vk_transform->lut_3d.dim = 33; + if (!create_3d_lut_image(renderer, transform, + vk_transform->lut_3d.dim, &vk_transform->lut_3d.image, &vk_transform->lut_3d.image_view, &vk_transform->lut_3d.memory, @@ -926,29 +1160,68 @@ static const struct wlr_addon_interface vk_color_transform_impl = { struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *renderer, struct wlr_vk_render_buffer *buffer, const struct wlr_buffer_pass_options *options) { - bool using_srgb_pathway; + uint32_t inv_eotf; if (options != NULL && options->color_transform != NULL) { - using_srgb_pathway = false; + if (options->color_transform->type == COLOR_TRANSFORM_INVERSE_EOTF) { + struct wlr_color_transform_inverse_eotf *tr = + wlr_color_transform_inverse_eotf_from_base(options->color_transform); + inv_eotf = tr->tf; + } else { + // Color transform is not an inverse EOTF + inv_eotf = 0; + } + } else { + // This is the default when unspecified + inv_eotf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; + } - if (!get_color_transform(options->color_transform, renderer)) { + bool using_linear_pathway = inv_eotf == WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; + bool using_srgb_pathway = inv_eotf == WLR_COLOR_TRANSFER_FUNCTION_SRGB && + buffer->srgb.out.framebuffer != VK_NULL_HANDLE; + bool using_two_pass_pathway = !using_linear_pathway && !using_srgb_pathway; + + if (using_linear_pathway && !buffer->linear.out.image_view) { + struct wlr_dmabuf_attributes attribs; + wlr_buffer_get_dmabuf(buffer->wlr_buffer, &attribs); + if (!vulkan_setup_one_pass_framebuffer(buffer, &attribs, false)) { + wlr_log(WLR_ERROR, "Failed to set up blend image"); + return NULL; + } + } + + if (using_two_pass_pathway) { + if (options != NULL && options->color_transform != NULL && + !get_color_transform(options->color_transform, renderer)) { /* Try to create a new color transform */ if (!vk_color_transform_create(renderer, options->color_transform)) { wlr_log(WLR_ERROR, "Failed to create color transform"); return NULL; } } - } else { - // Use srgb pathway if it is the default/has already been set up - using_srgb_pathway = buffer->srgb.framebuffer != VK_NULL_HANDLE; + + if (!buffer->two_pass.out.image_view) { + struct wlr_dmabuf_attributes attribs; + wlr_buffer_get_dmabuf(buffer->wlr_buffer, &attribs); + if (!vulkan_setup_two_pass_framebuffer(buffer, &attribs)) { + wlr_log(WLR_ERROR, "Failed to set up blend image"); + return NULL; + } + } } - if (!using_srgb_pathway && !buffer->plain.image_view) { - struct wlr_dmabuf_attributes attribs; - wlr_buffer_get_dmabuf(buffer->wlr_buffer, &attribs); - if (!vulkan_setup_plain_framebuffer(buffer, &attribs)) { - wlr_log(WLR_ERROR, "Failed to set up blend image"); - return NULL; - } + struct wlr_vk_render_format_setup *render_setup; + struct wlr_vk_render_buffer_out *buffer_out; + if (using_two_pass_pathway) { + render_setup = buffer->two_pass.render_setup; + buffer_out = &buffer->two_pass.out; + } else if (using_srgb_pathway) { + render_setup = buffer->srgb.render_setup; + buffer_out = &buffer->srgb.out; + } else if (using_linear_pathway) { + render_setup = buffer->linear.render_setup; + buffer_out = &buffer->linear.out; + } else { + abort(); // unreachable } struct wlr_vk_render_pass *pass = calloc(1, sizeof(*pass)); @@ -958,10 +1231,17 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend wlr_render_pass_init(&pass->base, &render_pass_impl); pass->renderer = renderer; - pass->srgb_pathway = using_srgb_pathway; + pass->two_pass = using_two_pass_pathway; if (options != NULL && options->color_transform != NULL) { - wlr_color_transform_ref(options->color_transform); - pass->color_transform = options->color_transform; + pass->color_transform = wlr_color_transform_ref(options->color_transform); + } + if (options != NULL && options->signal_timeline != NULL) { + pass->signal_timeline = wlr_drm_syncobj_timeline_ref(options->signal_timeline); + pass->signal_point = options->signal_point; + } + if (options != NULL && options->primaries != NULL) { + pass->has_primaries = true; + pass->primaries = *options->primaries; } rect_union_init(&pass->updated_region); @@ -999,14 +1279,9 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderArea = rect, .clearValueCount = 0, + .renderPass = render_setup->render_pass, + .framebuffer = buffer_out->framebuffer, }; - if (pass->srgb_pathway) { - rp_info.renderPass = buffer->srgb.render_setup->render_pass; - rp_info.framebuffer = buffer->srgb.framebuffer; - } else { - rp_info.renderPass = buffer->plain.render_setup->render_pass; - rp_info.framebuffer = buffer->plain.framebuffer; - } vkCmdBeginRenderPass(cb->vk, &rp_info, VK_SUBPASS_CONTENTS_INLINE); vkCmdSetViewport(cb->vk, 0, 1, &(VkViewport){ @@ -1021,6 +1296,8 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend wlr_buffer_lock(buffer->wlr_buffer); pass->render_buffer = buffer; + pass->render_buffer_out = buffer_out; + pass->render_setup = render_setup; pass->command_buffer = cb; return pass; } diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 32effb5a7..3f880ca6c 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -26,7 +27,7 @@ #include "render/vulkan/shaders/quad.frag.h" #include "render/vulkan/shaders/output.frag.h" #include "types/wlr_buffer.h" -#include "types/wlr_matrix.h" +#include "util/time.h" // TODO: // - simplify stage allocation, don't track allocations but use ringbuffer-like @@ -65,59 +66,72 @@ static struct wlr_vk_descriptor_pool *alloc_ds( struct wl_list *pool_list, size_t *last_pool_size) { VkResult res; - bool found = false; - struct wlr_vk_descriptor_pool *pool; - wl_list_for_each(pool, pool_list, link) { - if (pool->free > 0) { - found = true; - break; - } - } - - if (!found) { // create new pool - pool = calloc(1, sizeof(*pool)); - if (!pool) { - wlr_log_errno(WLR_ERROR, "allocation failed"); - return NULL; - } - - size_t count = 2 * (*last_pool_size); - if (!count) { - count = start_descriptor_pool_size; - } - - pool->free = count; - VkDescriptorPoolSize pool_size = { - .descriptorCount = count, - .type = type, - }; - - VkDescriptorPoolCreateInfo dpool_info = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, - .maxSets = count, - .poolSizeCount = 1, - .pPoolSizes = &pool_size, - .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, - }; - - res = vkCreateDescriptorPool(renderer->dev->dev, &dpool_info, NULL, - &pool->pool); - if (res != VK_SUCCESS) { - wlr_vk_error("vkCreateDescriptorPool", res); - free(pool); - return NULL; - } - - *last_pool_size = count; - wl_list_insert(pool_list, &pool->link); - } - VkDescriptorSetAllocateInfo ds_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorSetCount = 1, .pSetLayouts = layout, - .descriptorPool = pool->pool, }; + + struct wlr_vk_descriptor_pool *pool; + wl_list_for_each(pool, pool_list, link) { + if (pool->free > 0) { + ds_info.descriptorPool = pool->pool; + res = vkAllocateDescriptorSets(renderer->dev->dev, &ds_info, ds); + switch (res) { + case VK_ERROR_FRAGMENTED_POOL: + case VK_ERROR_OUT_OF_POOL_MEMORY: + // Descriptor sets with more than one descriptor can cause us + // to run out of pool memory early or lead to fragmentation + // that makes the pool unable to service our allocation + // request. Try the next pool or allocate a new one. + continue; + case VK_SUCCESS: + --pool->free; + return pool; + default: + wlr_vk_error("vkAllocateDescriptorSets", res); + return NULL; + } + } + } + + pool = calloc(1, sizeof(*pool)); + if (!pool) { + wlr_log_errno(WLR_ERROR, "allocation failed"); + return NULL; + } + + size_t count = 2 * (*last_pool_size); + if (!count) { + count = start_descriptor_pool_size; + } + + pool->free = count; + VkDescriptorPoolSize pool_size = { + .descriptorCount = count, + .type = type, + }; + + VkDescriptorPoolCreateInfo dpool_info = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .maxSets = count, + .poolSizeCount = 1, + .pPoolSizes = &pool_size, + .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, + }; + + res = vkCreateDescriptorPool(renderer->dev->dev, &dpool_info, NULL, + &pool->pool); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateDescriptorPool", res); + free(pool); + return NULL; + } + + *last_pool_size = count; + wl_list_insert(pool_list, &pool->link); + + ds_info.descriptorPool = pool->pool; res = vkAllocateDescriptorSets(renderer->dev->dev, &ds_info, ds); if (res != VK_SUCCESS) { wlr_vk_error("vkAllocateDescriptorSets", res); @@ -157,8 +171,12 @@ static void destroy_render_format_setup(struct wlr_vk_renderer *renderer, VkDevice dev = renderer->dev->dev; vkDestroyRenderPass(dev, setup->render_pass, NULL); + vkDestroyPipeline(dev, setup->output_pipe_identity, NULL); vkDestroyPipeline(dev, setup->output_pipe_srgb, NULL); + vkDestroyPipeline(dev, setup->output_pipe_pq, NULL); vkDestroyPipeline(dev, setup->output_pipe_lut3d, NULL); + vkDestroyPipeline(dev, setup->output_pipe_gamma22, NULL); + vkDestroyPipeline(dev, setup->output_pipe_bt1886, NULL); struct wlr_vk_pipeline *pipeline, *tmp_pipeline; wl_list_for_each_safe(pipeline, tmp_pipeline, &setup->pipelines, link) { @@ -267,6 +285,8 @@ struct wlr_vk_buffer_span vulkan_get_stage_span(struct wlr_vk_renderer *r, goto error_alloc; } + wl_list_init(&buf->link); + VkResult res; VkBufferCreateInfo buf_info = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, @@ -321,7 +341,6 @@ struct wlr_vk_buffer_span vulkan_get_stage_span(struct wlr_vk_renderer *r, goto error; } - wlr_log(WLR_DEBUG, "Created new vk staging buffer of size %" PRIu64, bsize); buf->buf_size = bsize; wl_list_insert(&r->stage.buffers, &buf->link); @@ -455,7 +474,7 @@ bool vulkan_wait_command_buffer(struct wlr_vk_command_buffer *cb, } static void release_command_buffer_resources(struct wlr_vk_command_buffer *cb, - struct wlr_vk_renderer *renderer) { + struct wlr_vk_renderer *renderer, int64_t now) { struct wlr_vk_texture *texture, *texture_tmp; wl_list_for_each_safe(texture, texture_tmp, &cb->destroy_textures, destroy_link) { wl_list_remove(&texture->destroy_link); @@ -466,6 +485,7 @@ static void release_command_buffer_resources(struct wlr_vk_command_buffer *cb, struct wlr_vk_shared_buffer *buf, *buf_tmp; wl_list_for_each_safe(buf, buf_tmp, &cb->stage_buffers, link) { buf->allocs.size = 0; + buf->last_used_ms = now; wl_list_remove(&buf->link); wl_list_insert(&renderer->stage.buffers, &buf->link); @@ -489,12 +509,22 @@ static struct wlr_vk_command_buffer *get_command_buffer( return NULL; } + + // Garbage collect any buffers that have remained unused for too long + int64_t now = get_current_time_msec(); + struct wlr_vk_shared_buffer *buf, *buf_tmp; + wl_list_for_each_safe(buf, buf_tmp, &renderer->stage.buffers, link) { + if (buf->allocs.size == 0 && buf->last_used_ms + 10000 < now) { + shared_buffer_destroy(renderer, buf); + } + } + // Destroy textures for completed command buffers for (size_t i = 0; i < VULKAN_COMMAND_BUFFERS_CAP; i++) { struct wlr_vk_command_buffer *cb = &renderer->command_buffers[i]; if (cb->vk != VK_NULL_HANDLE && !cb->recording && cb->timeline_point <= current_point) { - release_command_buffer_resources(cb, renderer); + release_command_buffer_resources(cb, renderer, now); } } @@ -576,6 +606,12 @@ void vulkan_reset_command_buffer(struct wlr_vk_command_buffer *cb) { } } +static void finish_render_buffer_out(struct wlr_vk_render_buffer_out *out, + VkDevice dev) { + vkDestroyFramebuffer(dev, out->framebuffer, NULL); + vkDestroyImageView(dev, out->image_view, NULL); +} + static void destroy_render_buffer(struct wlr_vk_render_buffer *buffer) { wl_list_remove(&buffer->link); wlr_addon_finish(&buffer->addon); @@ -589,17 +625,16 @@ static void destroy_render_buffer(struct wlr_vk_render_buffer *buffer) { wlr_vk_error("vkQueueWaitIdle", res); } - vkDestroyFramebuffer(dev, buffer->srgb.framebuffer, NULL); - vkDestroyImageView(dev, buffer->srgb.image_view, NULL); + finish_render_buffer_out(&buffer->linear.out, dev); + finish_render_buffer_out(&buffer->srgb.out, dev); - vkDestroyFramebuffer(dev, buffer->plain.framebuffer, NULL); - vkDestroyImageView(dev, buffer->plain.image_view, NULL); - vkDestroyImage(dev, buffer->plain.blend_image, NULL); - vkFreeMemory(dev, buffer->plain.blend_memory, NULL); - vkDestroyImageView(dev, buffer->plain.blend_image_view, NULL); - if (buffer->plain.blend_attachment_pool) { - vulkan_free_ds(buffer->renderer, buffer->plain.blend_attachment_pool, - buffer->plain.blend_descriptor_set); + finish_render_buffer_out(&buffer->two_pass.out, dev); + vkDestroyImage(dev, buffer->two_pass.blend_image, NULL); + vkFreeMemory(dev, buffer->two_pass.blend_memory, NULL); + vkDestroyImageView(dev, buffer->two_pass.blend_image_view, NULL); + if (buffer->two_pass.blend_attachment_pool) { + vulkan_free_ds(buffer->renderer, buffer->two_pass.blend_attachment_pool, + buffer->two_pass.blend_descriptor_set); } vkDestroyImage(dev, buffer->image, NULL); @@ -620,7 +655,7 @@ static struct wlr_addon_interface render_buffer_addon_impl = { .destroy = handle_render_buffer_destroy, }; -bool vulkan_setup_plain_framebuffer(struct wlr_vk_render_buffer *buffer, +bool vulkan_setup_two_pass_framebuffer(struct wlr_vk_render_buffer *buffer, const struct wlr_dmabuf_attributes *dmabuf) { struct wlr_vk_renderer *renderer = buffer->renderer; VkResult res; @@ -648,15 +683,15 @@ bool vulkan_setup_plain_framebuffer(struct wlr_vk_render_buffer *buffer, }, }; - res = vkCreateImageView(dev, &view_info, NULL, &buffer->plain.image_view); + res = vkCreateImageView(dev, &view_info, NULL, &buffer->two_pass.out.image_view); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateImageView failed", res); goto error; } - buffer->plain.render_setup = find_or_create_render_setup( + buffer->two_pass.render_setup = find_or_create_render_setup( renderer, &fmt->format, true); - if (!buffer->plain.render_setup) { + if (!buffer->two_pass.render_setup) { goto error; } @@ -676,14 +711,14 @@ bool vulkan_setup_plain_framebuffer(struct wlr_vk_render_buffer *buffer, .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT, }; - res = vkCreateImage(dev, &img_info, NULL, &buffer->plain.blend_image); + res = vkCreateImage(dev, &img_info, NULL, &buffer->two_pass.blend_image); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateImage failed", res); goto error; } VkMemoryRequirements mem_reqs; - vkGetImageMemoryRequirements(dev, buffer->plain.blend_image, &mem_reqs); + vkGetImageMemoryRequirements(dev, buffer->two_pass.blend_image, &mem_reqs); int mem_type_index = vulkan_find_mem_type(renderer->dev, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mem_reqs.memoryTypeBits); @@ -698,13 +733,13 @@ bool vulkan_setup_plain_framebuffer(struct wlr_vk_render_buffer *buffer, .memoryTypeIndex = mem_type_index, }; - res = vkAllocateMemory(dev, &mem_info, NULL, &buffer->plain.blend_memory); + res = vkAllocateMemory(dev, &mem_info, NULL, &buffer->two_pass.blend_memory); if (res != VK_SUCCESS) { wlr_vk_error("vkAllocatorMemory failed", res); goto error; } - res = vkBindImageMemory(dev, buffer->plain.blend_image, buffer->plain.blend_memory, 0); + res = vkBindImageMemory(dev, buffer->two_pass.blend_image, buffer->two_pass.blend_memory, 0); if (res != VK_SUCCESS) { wlr_vk_error("vkBindMemory failed", res); goto error; @@ -712,7 +747,7 @@ bool vulkan_setup_plain_framebuffer(struct wlr_vk_render_buffer *buffer, VkImageViewCreateInfo blend_view_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .image = buffer->plain.blend_image, + .image = buffer->two_pass.blend_image, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = img_info.format, .components.r = VK_COMPONENT_SWIZZLE_IDENTITY, @@ -728,50 +763,50 @@ bool vulkan_setup_plain_framebuffer(struct wlr_vk_render_buffer *buffer, }, }; - res = vkCreateImageView(dev, &blend_view_info, NULL, &buffer->plain.blend_image_view); + res = vkCreateImageView(dev, &blend_view_info, NULL, &buffer->two_pass.blend_image_view); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateImageView failed", res); goto error; } - buffer->plain.blend_attachment_pool = vulkan_alloc_blend_ds(renderer, - &buffer->plain.blend_descriptor_set); - if (!buffer->plain.blend_attachment_pool) { + buffer->two_pass.blend_attachment_pool = vulkan_alloc_blend_ds(renderer, + &buffer->two_pass.blend_descriptor_set); + if (!buffer->two_pass.blend_attachment_pool) { wlr_log(WLR_ERROR, "failed to allocate descriptor"); goto error; } VkDescriptorImageInfo ds_attach_info = { .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - .imageView = buffer->plain.blend_image_view, + .imageView = buffer->two_pass.blend_image_view, .sampler = VK_NULL_HANDLE, }; VkWriteDescriptorSet ds_write = { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, - .dstSet = buffer->plain.blend_descriptor_set, + .dstSet = buffer->two_pass.blend_descriptor_set, .dstBinding = 0, .pImageInfo = &ds_attach_info, }; vkUpdateDescriptorSets(dev, 1, &ds_write, 0, NULL); - VkImageView attachments[2] = { - buffer->plain.blend_image_view, - buffer->plain.image_view + VkImageView attachments[] = { + buffer->two_pass.blend_image_view, + buffer->two_pass.out.image_view, }; VkFramebufferCreateInfo fb_info = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, - .attachmentCount = 2, + .attachmentCount = sizeof(attachments) / sizeof(attachments[0]), .pAttachments = attachments, .flags = 0u, .width = dmabuf->width, .height = dmabuf->height, .layers = 1u, - .renderPass = buffer->plain.render_setup->render_pass, + .renderPass = buffer->two_pass.render_setup->render_pass, }; - res = vkCreateFramebuffer(dev, &fb_info, NULL, &buffer->plain.framebuffer); + res = vkCreateFramebuffer(dev, &fb_info, NULL, &buffer->two_pass.out.framebuffer); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateFramebuffer", res); goto error; @@ -785,8 +820,8 @@ error: return false; } -static bool vulkan_setup_srgb_framebuffer(struct wlr_vk_render_buffer *buffer, - const struct wlr_dmabuf_attributes *dmabuf) { +bool vulkan_setup_one_pass_framebuffer(struct wlr_vk_render_buffer *buffer, + const struct wlr_dmabuf_attributes *dmabuf, bool srgb) { struct wlr_vk_renderer *renderer = buffer->renderer; VkResult res; VkDevice dev = renderer->dev->dev; @@ -795,14 +830,18 @@ static bool vulkan_setup_srgb_framebuffer(struct wlr_vk_render_buffer *buffer, renderer->dev, dmabuf->format); assert(fmt); - assert(fmt->format.vk_srgb); - // Set up the srgb framebuffer by default; plain framebuffer and + VkFormat vk_fmt = srgb ? fmt->format.vk_srgb : fmt->format.vk; + assert(vk_fmt != VK_FORMAT_UNDEFINED); + + struct wlr_vk_render_buffer_out *out = srgb ? &buffer->srgb.out : &buffer->linear.out; + + // Set up the srgb framebuffer by default; two-pass framebuffer and // blending image will be set up later if necessary VkImageViewCreateInfo view_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = buffer->image, .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = fmt->format.vk_srgb, + .format = vk_fmt, .components.r = VK_COMPONENT_SWIZZLE_IDENTITY, .components.g = VK_COMPONENT_SWIZZLE_IDENTITY, .components.b = VK_COMPONENT_SWIZZLE_IDENTITY, @@ -816,35 +855,43 @@ static bool vulkan_setup_srgb_framebuffer(struct wlr_vk_render_buffer *buffer, }, }; - res = vkCreateImageView(dev, &view_info, NULL, &buffer->srgb.image_view); + res = vkCreateImageView(dev, &view_info, NULL, &out->image_view); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateImageView failed", res); goto error; } - buffer->srgb.render_setup = find_or_create_render_setup( - renderer, &fmt->format, false); - if (!buffer->srgb.render_setup) { + struct wlr_vk_render_format_setup *render_setup = + find_or_create_render_setup(renderer, &fmt->format, false); + if (!render_setup) { goto error; } VkFramebufferCreateInfo fb_info = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .attachmentCount = 1, - .pAttachments = &buffer->srgb.image_view, + .pAttachments = &out->image_view, .flags = 0u, .width = dmabuf->width, .height = dmabuf->height, .layers = 1u, - .renderPass = buffer->srgb.render_setup->render_pass, + .renderPass = render_setup->render_pass, }; - res = vkCreateFramebuffer(dev, &fb_info, NULL, &buffer->srgb.framebuffer); + res = vkCreateFramebuffer(dev, &fb_info, NULL, &out->framebuffer); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateFramebuffer", res); goto error; } + + if (srgb) { + buffer->srgb.render_setup = render_setup; + } else { + buffer->linear.render_setup = render_setup; + } + return true; + error: // cleaning up everything is the caller's responsibility, // since it will need to do this anyway if framebuffer setup fails @@ -888,12 +935,12 @@ static struct wlr_vk_render_buffer *create_render_buffer( } if (using_mutable_srgb) { - if (!vulkan_setup_srgb_framebuffer(buffer, &dmabuf)) { + if (!vulkan_setup_one_pass_framebuffer(buffer, &dmabuf, true)) { goto error; } } else { - // Set up the plain framebuffer & blending image - if (!vulkan_setup_plain_framebuffer(buffer, &dmabuf)) { + // Set up the two-pass framebuffer & blending image + if (!vulkan_setup_two_pass_framebuffer(buffer, &dmabuf)) { goto error; } } @@ -921,9 +968,9 @@ static struct wlr_vk_render_buffer *get_render_buffer( return buffer; } -bool vulkan_sync_foreign_texture(struct wlr_vk_texture *texture) { +bool vulkan_sync_foreign_texture(struct wlr_vk_texture *texture, + int sync_file_fds[static WLR_DMABUF_MAX_PLANES]) { struct wlr_vk_renderer *renderer = texture->renderer; - VkResult res; struct wlr_dmabuf_attributes dmabuf = {0}; if (!wlr_buffer_get_dmabuf(texture->buffer, &dmabuf)) { @@ -960,51 +1007,23 @@ bool vulkan_sync_foreign_texture(struct wlr_vk_texture *texture) { return false; } - if (texture->foreign_semaphores[i] == VK_NULL_HANDLE) { - VkSemaphoreCreateInfo semaphore_info = { - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, - }; - res = vkCreateSemaphore(renderer->dev->dev, &semaphore_info, NULL, - &texture->foreign_semaphores[i]); - if (res != VK_SUCCESS) { - close(sync_file_fd); - wlr_vk_error("vkCreateSemaphore", res); - return false; - } - } - - VkImportSemaphoreFdInfoKHR import_info = { - .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR, - .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, - .flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT, - .semaphore = texture->foreign_semaphores[i], - .fd = sync_file_fd, - }; - res = renderer->dev->api.vkImportSemaphoreFdKHR(renderer->dev->dev, &import_info); - if (res != VK_SUCCESS) { - close(sync_file_fd); - wlr_vk_error("vkImportSemaphoreFdKHR", res); - return false; - } + sync_file_fds[i] = sync_file_fd; } return true; } bool vulkan_sync_render_buffer(struct wlr_vk_renderer *renderer, - struct wlr_vk_render_buffer *render_buffer, struct wlr_vk_command_buffer *cb) { + struct wlr_vk_render_buffer *render_buffer, struct wlr_vk_command_buffer *cb, + struct wlr_drm_syncobj_timeline *signal_timeline, uint64_t signal_point) { VkResult res; - if (!renderer->dev->implicit_sync_interop) { + if (!renderer->dev->implicit_sync_interop && signal_timeline == NULL) { // We have no choice but to block here sadly return vulkan_wait_command_buffer(cb, renderer); } - struct wlr_dmabuf_attributes dmabuf = {0}; - if (!wlr_buffer_get_dmabuf(render_buffer->wlr_buffer, &dmabuf)) { - wlr_log(WLR_ERROR, "wlr_buffer_get_dmabuf failed"); - return false; - } + assert(cb->binary_semaphore != VK_NULL_HANDLE); // Note: vkGetSemaphoreFdKHR implicitly resets the semaphore const VkSemaphoreGetFdInfoKHR get_fence_fd_info = { @@ -1020,17 +1039,32 @@ bool vulkan_sync_render_buffer(struct wlr_vk_renderer *renderer, return false; } - for (int i = 0; i < dmabuf.n_planes; i++) { - if (!dmabuf_import_sync_file(dmabuf.fd[i], DMA_BUF_SYNC_WRITE, - sync_file_fd)) { - close(sync_file_fd); - return false; + bool ok = false; + if (signal_timeline != NULL) { + if (!wlr_drm_syncobj_timeline_import_sync_file(signal_timeline, + signal_point, sync_file_fd)) { + goto out; + } + } else { + struct wlr_dmabuf_attributes dmabuf = {0}; + if (!wlr_buffer_get_dmabuf(render_buffer->wlr_buffer, &dmabuf)) { + wlr_log(WLR_ERROR, "wlr_buffer_get_dmabuf failed"); + goto out; + } + + for (int i = 0; i < dmabuf.n_planes; i++) { + if (!dmabuf_import_sync_file(dmabuf.fd[i], DMA_BUF_SYNC_WRITE, + sync_file_fd)) { + goto out; + } } } - close(sync_file_fd); + ok = true; - return true; +out: + close(sync_file_fd); + return ok; } static const struct wlr_drm_format_set *vulkan_get_texture_formats( @@ -1069,10 +1103,15 @@ static void vulkan_destroy(struct wlr_renderer *wlr_renderer) { if (cb->vk == VK_NULL_HANDLE) { continue; } - release_command_buffer_resources(cb, renderer); + release_command_buffer_resources(cb, renderer, 0); if (cb->binary_semaphore != VK_NULL_HANDLE) { vkDestroySemaphore(renderer->dev->dev, cb->binary_semaphore, NULL); } + VkSemaphore *sem_ptr; + wl_array_for_each(sem_ptr, &cb->wait_semaphores) { + vkDestroySemaphore(renderer->dev->dev, *sem_ptr, NULL); + } + wl_array_release(&cb->wait_semaphores); } // stage.cb automatically freed with command pool @@ -1224,7 +1263,6 @@ bool vulkan_read_pixels(struct wlr_vk_renderer *vk_renderer, int mem_type = vulkan_find_mem_type(vk_renderer->dev, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | - VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, mem_reqs.memoryTypeBits); if (mem_type < 0) { @@ -1361,6 +1399,19 @@ bool vulkan_read_pixels(struct wlr_vk_renderer *vk_renderer, return false; } + VkMappedMemoryRange mem_range = { + .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, + .memory = dst_img_memory, + .offset = 0, + .size = VK_WHOLE_SIZE, + }; + res = vkInvalidateMappedMemoryRanges(dev, 1, &mem_range); + if (res != VK_SUCCESS) { + wlr_vk_error("vkInvalidateMappedMemoryRanges", res); + vkUnmapMemory(dev, dst_img_memory); + return false; + } + const char *d = (const char *)v + img_sub_layout.offset; unsigned char *p = (unsigned char *)data + dst_y * stride; uint32_t bytes_per_pixel = pixel_format_info->bytes_per_block; @@ -1376,6 +1427,7 @@ bool vulkan_read_pixels(struct wlr_vk_renderer *vk_renderer, vkUnmapMemory(dev, dst_img_memory); // Don't need to free anything else, since memory and image are cached return true; + free_memory: vkFreeMemory(dev, dst_img_memory, NULL); destroy_image: @@ -1446,14 +1498,14 @@ static bool init_tex_layouts(struct wlr_vk_renderer *renderer, return false; } - VkPushConstantRange pc_ranges[2] = { + VkPushConstantRange pc_ranges[] = { { .size = sizeof(struct wlr_vk_vert_pcr_data), .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, }, { .offset = pc_ranges[0].size, - .size = sizeof(float) * 4, // alpha or color + .size = sizeof(struct wlr_vk_frag_texture_pcr_data), .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, }, }; @@ -1462,7 +1514,7 @@ static bool init_tex_layouts(struct wlr_vk_renderer *renderer, .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, .pSetLayouts = out_ds_layout, - .pushConstantRangeCount = 2, + .pushConstantRangeCount = sizeof(pc_ranges) / sizeof(pc_ranges[0]), .pPushConstantRanges = pc_ranges, }; @@ -1540,7 +1592,7 @@ static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer) { } // pipeline layout -- standard vertex uniforms, no shader uniforms - VkPushConstantRange pc_ranges[2] = { + VkPushConstantRange pc_ranges[] = { { .offset = 0, .size = sizeof(struct wlr_vk_vert_pcr_data), @@ -1553,16 +1605,16 @@ static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer) { }, }; - VkDescriptorSetLayout out_ds_layouts[2] = { + VkDescriptorSetLayout out_ds_layouts[] = { renderer->output_ds_srgb_layout, renderer->output_ds_lut3d_layout, }; VkPipelineLayoutCreateInfo pl_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, - .setLayoutCount = 2, + .setLayoutCount = sizeof(out_ds_layouts) / sizeof(out_ds_layouts[0]), .pSetLayouts = out_ds_layouts, - .pushConstantRangeCount = 2, + .pushConstantRangeCount = sizeof(pc_ranges) / sizeof(pc_ranges[0]), .pPushConstantRanges = pc_ranges, }; @@ -1735,14 +1787,14 @@ struct wlr_vk_pipeline *setup_get_or_create_pipeline( .scissorCount = 1, }; - VkDynamicState dynStates[2] = { + VkDynamicState dyn_states[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, }; VkPipelineDynamicStateCreateInfo dynamic = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, - .pDynamicStates = dynStates, - .dynamicStateCount = 2, + .pDynamicStates = dyn_states, + .dynamicStateCount = sizeof(dyn_states) / sizeof(dyn_states[0]), }; VkPipelineVertexInputStateCreateInfo vertex = { @@ -1754,7 +1806,7 @@ struct wlr_vk_pipeline *setup_get_or_create_pipeline( .layout = pipeline_layout->vk, .renderPass = setup->render_pass, .subpass = 0, - .stageCount = 2, + .stageCount = sizeof(stages) / sizeof(stages[0]), .pStages = stages, .pInputAssemblyState = &assembly, @@ -1797,7 +1849,7 @@ static bool init_blend_to_output_pipeline(struct wlr_vk_renderer *renderer, .pData = &output_transform_type, }; - VkPipelineShaderStageCreateInfo tex_stages[2] = { + VkPipelineShaderStageCreateInfo tex_stages[] = { { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, @@ -1852,14 +1904,14 @@ static bool init_blend_to_output_pipeline(struct wlr_vk_renderer *renderer, .scissorCount = 1, }; - VkDynamicState dynStates[2] = { + VkDynamicState dyn_states[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, }; VkPipelineDynamicStateCreateInfo dynamic = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, - .pDynamicStates = dynStates, - .dynamicStateCount = 2, + .pDynamicStates = dyn_states, + .dynamicStateCount = sizeof(dyn_states) / sizeof(dyn_states[0]), }; VkPipelineVertexInputStateCreateInfo vertex = { @@ -1872,7 +1924,7 @@ static bool init_blend_to_output_pipeline(struct wlr_vk_renderer *renderer, .layout = pipe_layout, .renderPass = rp, .subpass = 1, // second subpass! - .stageCount = 2, + .stageCount = sizeof(tex_stages) / sizeof(tex_stages[0]), .pStages = tex_stages, .pInputAssemblyState = &assembly, .pRasterizationState = &rasterization, @@ -2165,7 +2217,7 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( VkResult res; if (use_blending_buffer) { - VkAttachmentDescription attachments[2] = { + VkAttachmentDescription attachments[] = { { .format = VK_FORMAT_R16G16B16A16_SFLOAT, .samples = VK_SAMPLE_COUNT_1_BIT, @@ -2203,7 +2255,7 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }; - VkSubpassDescription subpasses[2] = { + VkSubpassDescription subpasses[] = { { .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, @@ -2218,7 +2270,7 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( } }; - VkSubpassDependency deps[3] = { + VkSubpassDependency deps[] = { { .srcSubpass = VK_SUBPASS_EXTERNAL, .srcStageMask = VK_PIPELINE_STAGE_HOST_BIT | @@ -2260,11 +2312,11 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .pNext = NULL, .flags = 0, - .attachmentCount = 2u, + .attachmentCount = sizeof(attachments) / sizeof(attachments[0]), .pAttachments = attachments, - .subpassCount = 2u, + .subpassCount = sizeof(subpasses) / sizeof(subpasses[0]), .pSubpasses = subpasses, - .dependencyCount = 3u, + .dependencyCount = sizeof(deps) / sizeof(deps[0]), .pDependencies = deps, }; @@ -2275,14 +2327,34 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( } // this is only well defined if render pass has a 2nd subpass + if (!init_blend_to_output_pipeline( + renderer, setup->render_pass, renderer->output_pipe_layout, + &setup->output_pipe_identity, WLR_VK_OUTPUT_TRANSFORM_IDENTITY)) { + goto error; + } if (!init_blend_to_output_pipeline( renderer, setup->render_pass, renderer->output_pipe_layout, &setup->output_pipe_lut3d, WLR_VK_OUTPUT_TRANSFORM_LUT3D)) { goto error; } + if (!init_blend_to_output_pipeline( + renderer, setup->render_pass, renderer->output_pipe_layout, + &setup->output_pipe_srgb, WLR_VK_OUTPUT_TRANSFORM_INVERSE_SRGB)) { + goto error; + } + if (!init_blend_to_output_pipeline( + renderer, setup->render_pass, renderer->output_pipe_layout, + &setup->output_pipe_pq, WLR_VK_OUTPUT_TRANSFORM_INVERSE_ST2084_PQ)) { + goto error; + } + if (!init_blend_to_output_pipeline( + renderer, setup->render_pass, renderer->output_pipe_layout, + &setup->output_pipe_gamma22, WLR_VK_OUTPUT_TRANSFORM_INVERSE_GAMMA22)) { + goto error; + } if (!init_blend_to_output_pipeline( renderer, setup->render_pass, renderer->output_pipe_layout, - &setup->output_pipe_srgb, WLR_VK_OUTPUT_TRANSFORM_INVERSE_SRGB)) { + &setup->output_pipe_bt1886, WLR_VK_OUTPUT_TRANSFORM_INVERSE_BT1886)) { goto error; } } else { @@ -2309,7 +2381,7 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( .pColorAttachments = &color_ref, }; - VkSubpassDependency deps[2] = { + VkSubpassDependency deps[] = { { .srcSubpass = VK_SUBPASS_EXTERNAL, .srcStageMask = VK_PIPELINE_STAGE_HOST_BIT | @@ -2344,7 +2416,7 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( .pAttachments = &attachment, .subpassCount = 1, .pSubpasses = &subpass, - .dependencyCount = 2u, + .dependencyCount = sizeof(deps) / sizeof(deps[0]), .pDependencies = deps, }; @@ -2412,6 +2484,7 @@ struct wlr_renderer *vulkan_renderer_create_for_device(struct wlr_vk_device *dev renderer->dev = dev; wlr_renderer_init(&renderer->wlr_renderer, &renderer_impl, WLR_BUFFER_CAP_DMABUF); + renderer->wlr_renderer.features.input_color_transform = true; renderer->wlr_renderer.features.output_color_transform = true; wl_list_init(&renderer->stage.buffers); wl_list_init(&renderer->foreign_textures); @@ -2423,6 +2496,11 @@ struct wlr_renderer *vulkan_renderer_create_for_device(struct wlr_vk_device *dev wl_list_init(&renderer->color_transforms); wl_list_init(&renderer->pipeline_layouts); + uint64_t cap_syncobj_timeline; + if (dev->drm_fd >= 0 && drmGetCap(dev->drm_fd, DRM_CAP_SYNCOBJ_TIMELINE, &cap_syncobj_timeline) == 0) { + renderer->wlr_renderer.features.timeline = dev->sync_file_import_export && cap_syncobj_timeline != 0; + } + if (!init_static_render_data(renderer)) { goto error; } @@ -2478,26 +2556,24 @@ struct wlr_renderer *wlr_vk_renderer_create_with_drm_fd(int drm_fd) { if (!phdev) { // We rather fail here than doing some guesswork wlr_log(WLR_ERROR, "Could not match drm and vulkan device"); - return NULL; + goto error; } struct wlr_vk_device *dev = vulkan_device_create(ini, phdev); if (!dev) { wlr_log(WLR_ERROR, "Failed to create vulkan device"); - vulkan_instance_destroy(ini); - return NULL; + goto error; } // Do not use the drm_fd that was passed in: we should prefer the render // node even if a primary node was provided dev->drm_fd = vulkan_open_phdev_drm_fd(phdev); - if (dev->drm_fd < 0) { - vulkan_device_destroy(dev); - vulkan_instance_destroy(ini); - return NULL; - } return vulkan_renderer_create_for_device(dev); + +error: + vulkan_instance_destroy(ini); + return NULL; } VkInstance wlr_vk_renderer_get_instance(struct wlr_renderer *renderer) { diff --git a/render/vulkan/shaders/output.frag b/render/vulkan/shaders/output.frag index 2a744aa24..5b952fcff 100644 --- a/render/vulkan/shaders/output.frag +++ b/render/vulkan/shaders/output.frag @@ -8,53 +8,88 @@ layout(location = 0) in vec2 uv; layout(location = 0) out vec4 out_color; /* struct wlr_vk_frag_output_pcr_data */ -layout(push_constant) uniform UBO { - layout(offset = 80) float lut_3d_offset; +layout(push_constant, row_major) uniform UBO { + layout(offset = 80) mat4 matrix; + float luminance_multiplier; + float lut_3d_offset; float lut_3d_scale; } data; layout (constant_id = 0) const int OUTPUT_TRANSFORM = 0; // Matches enum wlr_vk_output_transform -#define OUTPUT_TRANSFORM_INVERSE_SRGB 0 -#define OUTPUT_TRANSFORM_LUT_3D 1 +#define OUTPUT_TRANSFORM_IDENTITY 0 +#define OUTPUT_TRANSFORM_INVERSE_SRGB 1 +#define OUTPUT_TRANSFORM_INVERSE_ST2084_PQ 2 +#define OUTPUT_TRANSFORM_LUT_3D 3 +#define OUTPUT_TRANSFORM_INVERSE_GAMMA22 4 +#define OUTPUT_TRANSFORM_INVERSE_BT1886 5 float linear_channel_to_srgb(float x) { return max(min(x * 12.92, 0.04045), 1.055 * pow(x, 1. / 2.4) - 0.055); } -vec4 linear_color_to_srgb(vec4 color) { - if (color.a == 0) { - return vec4(0); - } - color.rgb /= color.a; - color.rgb = vec3( +vec3 linear_color_to_srgb(vec3 color) { + return vec3( linear_channel_to_srgb(color.r), linear_channel_to_srgb(color.g), linear_channel_to_srgb(color.b) ); - color.rgb *= color.a; - return color; +} + +vec3 linear_color_to_pq(vec3 color) { + // H.273 TransferCharacteristics code point 16 + float c1 = 0.8359375; + float c2 = 18.8515625; + float c3 = 18.6875; + float m = 78.84375; + float n = 0.1593017578125; + vec3 pow_n = pow(clamp(color, vec3(0), vec3(1)), vec3(n)); + return pow((vec3(c1) + c2 * pow_n) / (vec3(1) + c3 * pow_n), vec3(m)); +} + +vec3 linear_color_to_bt1886(vec3 color) { + float lb = pow(0.0001, 1.0 / 2.4); + float lw = pow(1.0, 1.0 / 2.4); + float a = pow(lw - lb, 2.4); + float b = lb / (lw - lb); + return pow(color / a, vec3(1.0 / 2.4)) - vec3(b); } void main() { - vec4 val = subpassLoad(in_color).rgba; - if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_LUT_3D) { - if (val.a == 0) { - out_color = vec4(0); - return; - } - // Convert from pre-multiplied alpha to straight alpha - vec3 rgb = val.rgb / val.a; + vec4 in_color = subpassLoad(in_color).rgba; + // Convert from pre-multiplied alpha to straight alpha + float alpha = in_color.a; + vec3 rgb; + if (alpha == 0) { + rgb = vec3(0); + } else { + rgb = in_color.rgb / alpha; + } + + rgb *= data.luminance_multiplier; + + rgb = mat3(data.matrix) * rgb; + + if (OUTPUT_TRANSFORM != OUTPUT_TRANSFORM_IDENTITY) { + rgb = max(rgb, vec3(0)); + } + if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_LUT_3D) { // Apply 3D LUT vec3 pos = data.lut_3d_offset + rgb * data.lut_3d_scale; rgb = texture(lut_3d, pos).rgb; - - // Back to pre-multiplied alpha - out_color = vec4(rgb * val.a, val.a); - } else { // OUTPUT_TRANSFORM_INVERSE_SRGB - // Produce post-premultiplied sRGB encoded values - out_color = linear_color_to_srgb(val); + } else if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_INVERSE_ST2084_PQ) { + rgb = linear_color_to_pq(rgb); + } else if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_INVERSE_SRGB) { + // Produce sRGB encoded values + rgb = linear_color_to_srgb(rgb); + } else if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_INVERSE_GAMMA22) { + rgb = pow(rgb, vec3(1. / 2.2)); + } else if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_INVERSE_BT1886) { + rgb = linear_color_to_bt1886(rgb); } + + // Back to pre-multiplied alpha + out_color = vec4(rgb * alpha, alpha); } diff --git a/render/vulkan/shaders/texture.frag b/render/vulkan/shaders/texture.frag index 6f2f347de..57d2d049c 100644 --- a/render/vulkan/shaders/texture.frag +++ b/render/vulkan/shaders/texture.frag @@ -5,8 +5,11 @@ layout(set = 0, binding = 0) uniform sampler2D tex; layout(location = 0) in vec2 uv; layout(location = 0) out vec4 out_color; -layout(push_constant) uniform UBO { - layout(offset = 80) float alpha; +// struct wlr_vk_frag_texture_pcr_data +layout(push_constant, row_major) uniform UBO { + layout(offset = 80) mat4 matrix; + float alpha; + float luminance_multiplier; } data; layout (constant_id = 0) const int TEXTURE_TRANSFORM = 0; @@ -14,6 +17,9 @@ layout (constant_id = 0) const int TEXTURE_TRANSFORM = 0; // Matches enum wlr_vk_texture_transform #define TEXTURE_TRANSFORM_IDENTITY 0 #define TEXTURE_TRANSFORM_SRGB 1 +#define TEXTURE_TRANSFORM_ST2084_PQ 2 +#define TEXTURE_TRANSFORM_GAMMA22 3 +#define TEXTURE_TRANSFORM_BT1886 4 float srgb_channel_to_linear(float x) { return mix(x / 12.92, @@ -21,27 +27,64 @@ float srgb_channel_to_linear(float x) { x > 0.04045); } -vec4 srgb_color_to_linear(vec4 color) { - if (color.a == 0) { - return vec4(0); - } - color.rgb /= color.a; - color.rgb = vec3( +vec3 srgb_color_to_linear(vec3 color) { + return vec3( srgb_channel_to_linear(color.r), srgb_channel_to_linear(color.g), srgb_channel_to_linear(color.b) ); - color.rgb *= color.a; - return color; +} + +vec3 pq_color_to_linear(vec3 color) { + float inv_m1 = 1 / 0.1593017578125; + float inv_m2 = 1 / 78.84375; + float c1 = 0.8359375; + float c2 = 18.8515625; + float c3 = 18.6875; + vec3 num = max(pow(color, vec3(inv_m2)) - c1, 0); + vec3 denom = c2 - c3 * pow(color, vec3(inv_m2)); + return pow(num / denom, vec3(inv_m1)); +} + +vec3 bt1886_color_to_linear(vec3 color) { + float lb = pow(0.0001, 1.0 / 2.4); + float lw = pow(1.0, 1.0 / 2.4); + float a = pow(lw - lb, 2.4); + float b = lb / (lw - lb); + return a * pow(color + vec3(b), vec3(2.4)); } void main() { - vec4 val = textureLod(tex, uv, 0); - if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_SRGB) { - out_color = srgb_color_to_linear(val); - } else { // TEXTURE_TRANSFORM_IDENTITY - out_color = val; + vec4 in_color = textureLod(tex, uv, 0); + + // Convert from pre-multiplied alpha to straight alpha + float alpha = in_color.a; + vec3 rgb; + if (alpha == 0) { + rgb = vec3(0); + } else { + rgb = in_color.rgb / alpha; } + if (TEXTURE_TRANSFORM != TEXTURE_TRANSFORM_IDENTITY) { + rgb = max(rgb, vec3(0)); + } + if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_SRGB) { + rgb = srgb_color_to_linear(rgb); + } else if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_ST2084_PQ) { + rgb = pq_color_to_linear(rgb); + } else if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_GAMMA22) { + rgb = pow(rgb, vec3(2.2)); + } else if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_BT1886) { + rgb = bt1886_color_to_linear(rgb); + } + + rgb *= data.luminance_multiplier; + + rgb = mat3(data.matrix) * rgb; + + // Back to pre-multiplied alpha + out_color = vec4(rgb * alpha, alpha); + out_color *= data.alpha; } diff --git a/render/vulkan/texture.c b/render/vulkan/texture.c index c40533133..499178f5d 100644 --- a/render/vulkan/texture.c +++ b/render/vulkan/texture.c @@ -208,12 +208,6 @@ void vulkan_texture_destroy(struct wlr_vk_texture *texture) { free(view); } - for (size_t i = 0; i < WLR_DMABUF_MAX_PLANES; i++) { - if (texture->foreign_semaphores[i] != VK_NULL_HANDLE) { - vkDestroySemaphore(dev, texture->foreign_semaphores[i], NULL); - } - } - vkDestroyImage(dev, texture->image, NULL); for (unsigned i = 0u; i < texture->mem_count; ++i) { @@ -275,10 +269,12 @@ static struct wlr_vk_texture *vulkan_texture_create( } struct wlr_vk_texture_view *vulkan_texture_get_or_create_view(struct wlr_vk_texture *texture, - const struct wlr_vk_pipeline_layout *pipeline_layout) { + const struct wlr_vk_pipeline_layout *pipeline_layout, bool srgb) { + assert(texture->using_mutable_srgb || !srgb); + struct wlr_vk_texture_view *view; wl_list_for_each(view, &texture->views, link) { - if (view->layout == pipeline_layout) { + if (view->layout == pipeline_layout && view->srgb == srgb) { return view; } } @@ -289,6 +285,7 @@ struct wlr_vk_texture_view *vulkan_texture_get_or_create_view(struct wlr_vk_text } view->layout = pipeline_layout; + view->srgb = srgb; VkResult res; VkDevice dev = texture->renderer->dev->dev; @@ -296,8 +293,7 @@ struct wlr_vk_texture_view *vulkan_texture_get_or_create_view(struct wlr_vk_text VkImageViewCreateInfo view_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = texture->using_mutable_srgb ? texture->format->vk_srgb - : texture->format->vk, + .format = srgb ? texture->format->vk_srgb : texture->format->vk, .components.r = VK_COMPONENT_SWIZZLE_IDENTITY, .components.g = VK_COMPONENT_SWIZZLE_IDENTITY, .components.b = VK_COMPONENT_SWIZZLE_IDENTITY, @@ -359,10 +355,10 @@ struct wlr_vk_texture_view *vulkan_texture_get_or_create_view(struct wlr_vk_text static void texture_set_format(struct wlr_vk_texture *texture, const struct wlr_vk_format *format, bool has_mutable_srgb) { + assert(!(format->is_ycbcr && has_mutable_srgb)); + texture->format = format; texture->using_mutable_srgb = has_mutable_srgb; - texture->transform = !format->is_ycbcr && has_mutable_srgb ? - WLR_VK_TEXTURE_TRANSFORM_IDENTITY : WLR_VK_TEXTURE_TRANSFORM_SRGB; const struct wlr_pixel_format_info *format_info = drm_get_pixel_format_info(format->drm); @@ -390,6 +386,12 @@ static struct wlr_texture *vulkan_texture_from_pixels( return NULL; } + if (width > fmt->shm.max_extent.width || height > fmt->shm.max_extent.height) { + wlr_log(WLR_ERROR, "Texture is too large to upload (%"PRIu32"x%"PRIu32" > %"PRIu32"x%"PRIu32")", + width, height, fmt->shm.max_extent.width, fmt->shm.max_extent.height); + return NULL; + } + struct wlr_vk_texture *texture = vulkan_texture_create(renderer, width, height); if (texture == NULL) { return NULL; @@ -397,14 +399,14 @@ static struct wlr_texture *vulkan_texture_from_pixels( texture_set_format(texture, &fmt->format, fmt->shm.has_mutable_srgb); - VkFormat view_formats[2] = { + VkFormat view_formats[] = { fmt->format.vk, fmt->format.vk_srgb, }; VkImageFormatListCreateInfoKHR list_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR, .pViewFormats = view_formats, - .viewFormatCount = 2, + .viewFormatCount = sizeof(view_formats) / sizeof(view_formats[0]), }; VkImageCreateInfo img_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, @@ -534,7 +536,8 @@ VkImage vulkan_import_dmabuf(struct wlr_vk_renderer *renderer, if ((uint32_t) attribs->width > mod->max_extent.width || (uint32_t) attribs->height > mod->max_extent.height) { - wlr_log(WLR_ERROR, "DMA-BUF is too large to import"); + wlr_log(WLR_ERROR, "DMA-BUF is too large to import (%"PRIi32"x%"PRIi32" > %"PRIu32"x%"PRIu32")", + attribs->width, attribs->height, mod->max_extent.width, mod->max_extent.height); return VK_NULL_HANDLE; } @@ -597,14 +600,14 @@ VkImage vulkan_import_dmabuf(struct wlr_vk_renderer *renderer, }; eimg.pNext = &mod_info; - VkFormat view_formats[2] = { + VkFormat view_formats[] = { fmt->format.vk, fmt->format.vk_srgb, }; VkImageFormatListCreateInfoKHR list_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR, .pViewFormats = view_formats, - .viewFormatCount = 2, + .viewFormatCount = sizeof(view_formats) / sizeof(view_formats[0]), }; if (mod->has_mutable_srgb) { mod_info.pNext = &list_info; diff --git a/render/vulkan/vulkan.c b/render/vulkan/vulkan.c index 7cdc44a02..78bc25941 100644 --- a/render/vulkan/vulkan.c +++ b/render/vulkan/vulkan.c @@ -274,7 +274,7 @@ VkPhysicalDevice vulkan_find_drm_phdev(struct wlr_vk_instance *ini, int drm_fd) } struct stat drm_stat = {0}; - if (fstat(drm_fd, &drm_stat) != 0) { + if (drm_fd >= 0 && fstat(drm_fd, &drm_stat) != 0) { wlr_log_errno(WLR_ERROR, "fstat failed"); return VK_NULL_HANDLE; } @@ -344,17 +344,23 @@ VkPhysicalDevice vulkan_find_drm_phdev(struct wlr_vk_instance *ini, int drm_fd) wlr_log(WLR_INFO, " Driver name: %s (%s)", driver_props.driverName, driver_props.driverInfo); } - if (!has_drm_props) { - wlr_log(WLR_DEBUG, " Ignoring physical device \"%s\": " - "VK_EXT_physical_device_drm not supported", - phdev_props.deviceName); - continue; + bool found; + if (drm_fd >= 0) { + if (!has_drm_props) { + wlr_log(WLR_DEBUG, " Ignoring physical device \"%s\": " + "VK_EXT_physical_device_drm not supported", + phdev_props.deviceName); + continue; + } + + dev_t primary_devid = makedev(drm_props.primaryMajor, drm_props.primaryMinor); + dev_t render_devid = makedev(drm_props.renderMajor, drm_props.renderMinor); + found = primary_devid == drm_stat.st_rdev || render_devid == drm_stat.st_rdev; + } else { + found = phdev_props.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU; } - dev_t primary_devid = makedev(drm_props.primaryMajor, drm_props.primaryMinor); - dev_t render_devid = makedev(drm_props.renderMajor, drm_props.renderMinor); - if (primary_devid == drm_stat.st_rdev || - render_devid == drm_stat.st_rdev) { + if (found) { wlr_log(WLR_INFO, "Found matching Vulkan physical device: %s", phdev_props.deviceName); return phdev; @@ -382,7 +388,7 @@ int vulkan_open_phdev_drm_fd(VkPhysicalDevice phdev) { } else if (drm_props.hasPrimary) { devid = makedev(drm_props.primaryMajor, drm_props.primaryMinor); } else { - wlr_log(WLR_ERROR, "Physical device is missing both render and primary nodes"); + wlr_log(WLR_INFO, "Physical device is missing both render and primary nodes"); return -1; } @@ -462,7 +468,6 @@ struct wlr_vk_device *vulkan_device_create(struct wlr_vk_instance *ini, const char *extensions[32] = {0}; size_t extensions_len = 0; extensions[extensions_len++] = VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME; - extensions[extensions_len++] = VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME; extensions[extensions_len++] = VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME; // or vulkan 1.2 extensions[extensions_len++] = VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME; extensions[extensions_len++] = VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME; @@ -497,19 +502,25 @@ struct wlr_vk_device *vulkan_device_create(struct wlr_vk_instance *ini, assert(graphics_found); } - const VkPhysicalDeviceExternalSemaphoreInfo ext_semaphore_info = { - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO, - .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, - }; - VkExternalSemaphoreProperties ext_semaphore_props = { - .sType = VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES, - }; - vkGetPhysicalDeviceExternalSemaphoreProperties(phdev, - &ext_semaphore_info, &ext_semaphore_props); - bool exportable_semaphore = ext_semaphore_props.externalSemaphoreFeatures & - VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT; - bool importable_semaphore = ext_semaphore_props.externalSemaphoreFeatures & - VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT; + bool exportable_semaphore = false, importable_semaphore = false; + bool has_external_semaphore_fd = + check_extension(avail_ext_props, avail_extc, VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME); + if (has_external_semaphore_fd) { + const VkPhysicalDeviceExternalSemaphoreInfo ext_semaphore_info = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO, + .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + }; + VkExternalSemaphoreProperties ext_semaphore_props = { + .sType = VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES, + }; + vkGetPhysicalDeviceExternalSemaphoreProperties(phdev, + &ext_semaphore_info, &ext_semaphore_props); + exportable_semaphore = ext_semaphore_props.externalSemaphoreFeatures & + VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT; + importable_semaphore = ext_semaphore_props.externalSemaphoreFeatures & + VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT; + extensions[extensions_len++] = VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME; + } if (!exportable_semaphore) { wlr_log(WLR_DEBUG, "VkSemaphore is not exportable to a sync_file"); } @@ -522,6 +533,7 @@ struct wlr_vk_device *vulkan_device_create(struct wlr_vk_instance *ini, wlr_log(WLR_DEBUG, "DMA-BUF sync_file import/export not supported"); } + dev->sync_file_import_export = exportable_semaphore && importable_semaphore; dev->implicit_sync_interop = exportable_semaphore && importable_semaphore && dmabuf_sync_file_import_export; if (dev->implicit_sync_interop) { @@ -592,7 +604,7 @@ struct wlr_vk_device *vulkan_device_create(struct wlr_vk_instance *ini, .ppEnabledExtensionNames = extensions, }; - assert(extensions_len < sizeof(extensions) / sizeof(extensions[0])); + assert(extensions_len <= sizeof(extensions) / sizeof(extensions[0])); res = vkCreateDevice(phdev, &dev_info, NULL, &dev->dev); @@ -617,10 +629,13 @@ struct wlr_vk_device *vulkan_device_create(struct wlr_vk_instance *ini, load_device_proc(dev, "vkWaitSemaphoresKHR", &dev->api.vkWaitSemaphoresKHR); load_device_proc(dev, "vkGetSemaphoreCounterValueKHR", &dev->api.vkGetSemaphoreCounterValueKHR); - load_device_proc(dev, "vkGetSemaphoreFdKHR", &dev->api.vkGetSemaphoreFdKHR); - load_device_proc(dev, "vkImportSemaphoreFdKHR", &dev->api.vkImportSemaphoreFdKHR); load_device_proc(dev, "vkQueueSubmit2KHR", &dev->api.vkQueueSubmit2KHR); + if (has_external_semaphore_fd) { + load_device_proc(dev, "vkGetSemaphoreFdKHR", &dev->api.vkGetSemaphoreFdKHR); + load_device_proc(dev, "vkImportSemaphoreFdKHR", &dev->api.vkImportSemaphoreFdKHR); + } + size_t max_fmts; const struct wlr_vk_format *fmts = vulkan_get_format_list(&max_fmts); dev->format_props = calloc(max_fmts, sizeof(*dev->format_props)); diff --git a/render/wlr_renderer.c b/render/wlr_renderer.c index 6a28908c4..e65314ccc 100644 --- a/render/wlr_renderer.c +++ b/render/wlr_renderer.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -24,8 +25,6 @@ #include #endif // WLR_HAS_VULKAN_RENDERER -#include "backend/backend.h" -#include "render/pixel_format.h" #include "render/wlr_renderer.h" #include "util/env.h" @@ -51,6 +50,9 @@ void wlr_renderer_destroy(struct wlr_renderer *r) { wl_signal_emit_mutable(&r->events.destroy, r); + assert(wl_list_empty(&r->events.destroy.listener_list)); + assert(wl_list_empty(&r->events.lost.listener_list)); + if (r->impl && r->impl->destroy) { r->impl->destroy(r); } else { @@ -143,6 +145,12 @@ static bool open_preferred_drm_fd(struct wlr_backend *backend, int *drm_fd_ptr, return true; } + if (env_parse_bool("WLR_RENDERER_FORCE_SOFTWARE")) { + *drm_fd_ptr = -1; + *own_drm_fd = false; + return true; + } + // Allow the user to override the render node const char *render_name = getenv("WLR_RENDER_DRM_DEVICE"); if (render_name != NULL) { @@ -174,8 +182,7 @@ static bool open_preferred_drm_fd(struct wlr_backend *backend, int *drm_fd_ptr, // If the backend hasn't picked a DRM FD, but accepts DMA-BUFs, pick an // arbitrary render node - uint32_t backend_caps = backend_get_buffer_caps(backend); - if (backend_caps & WLR_BUFFER_CAP_DMABUF) { + if (backend->buffer_caps & WLR_BUFFER_CAP_DMABUF) { int drm_fd = open_drm_render_node(); if (drm_fd < 0) { return false; @@ -241,7 +248,7 @@ static struct wlr_renderer *renderer_autocreate(struct wlr_backend *backend, int } } - if (strcmp(renderer_name, "vulkan") == 0) { + if ((is_auto && WLR_HAS_VULKAN_RENDERER) || strcmp(renderer_name, "vulkan") == 0) { if (!open_preferred_drm_fd(backend, &drm_fd, &own_drm_fd)) { log_creation_failure(is_auto, "Cannot create Vulkan renderer: no DRM FD available"); } else { @@ -274,6 +281,9 @@ out: if (own_drm_fd && drm_fd >= 0) { close(drm_fd); } + if (renderer != NULL && env_parse_bool("WLR_RENDER_NO_EXPLICIT_SYNC")) { + renderer->features.timeline = false; + } return renderer; } diff --git a/subprojects/libdisplay-info.wrap b/subprojects/libdisplay-info.wrap new file mode 100644 index 000000000..f92d3041f --- /dev/null +++ b/subprojects/libdisplay-info.wrap @@ -0,0 +1,3 @@ +[wrap-git] +url = https://gitlab.freedesktop.org/emersion/libdisplay-info.git +revision = HEAD diff --git a/subprojects/libdrm.wrap b/subprojects/libdrm.wrap new file mode 100644 index 000000000..2b5ca29c8 --- /dev/null +++ b/subprojects/libdrm.wrap @@ -0,0 +1,3 @@ +[wrap-git] +url = https://gitlab.freedesktop.org/mesa/drm.git +revision = HEAD diff --git a/subprojects/libliftoff.wrap b/subprojects/libliftoff.wrap new file mode 100644 index 000000000..57e6bb231 --- /dev/null +++ b/subprojects/libliftoff.wrap @@ -0,0 +1,3 @@ +[wrap-git] +url = https://gitlab.freedesktop.org/emersion/libliftoff.git +revision = HEAD diff --git a/subprojects/libxkbcommon.wrap b/subprojects/libxkbcommon.wrap new file mode 100644 index 000000000..102b84496 --- /dev/null +++ b/subprojects/libxkbcommon.wrap @@ -0,0 +1,3 @@ +[wrap-git] +url = https://github.com/xkbcommon/libxkbcommon.git +revision = HEAD diff --git a/subprojects/pixman.wrap b/subprojects/pixman.wrap new file mode 100644 index 000000000..e34794a92 --- /dev/null +++ b/subprojects/pixman.wrap @@ -0,0 +1,3 @@ +[wrap-git] +url = https://gitlab.freedesktop.org/pixman/pixman.git +revision = HEAD diff --git a/subprojects/seatd.wrap b/subprojects/seatd.wrap new file mode 100644 index 000000000..2116366b8 --- /dev/null +++ b/subprojects/seatd.wrap @@ -0,0 +1,3 @@ +[wrap-git] +url = https://git.sr.ht/~kennylevinsen/seatd +revision = HEAD diff --git a/subprojects/wayland-protocols.wrap b/subprojects/wayland-protocols.wrap new file mode 100644 index 000000000..7b093efce --- /dev/null +++ b/subprojects/wayland-protocols.wrap @@ -0,0 +1,3 @@ +[wrap-git] +url = https://gitlab.freedesktop.org/wayland/wayland-protocols.git +revision = HEAD diff --git a/subprojects/wayland.wrap b/subprojects/wayland.wrap new file mode 100644 index 000000000..18d265671 --- /dev/null +++ b/subprojects/wayland.wrap @@ -0,0 +1,3 @@ +[wrap-git] +url = https://gitlab.freedesktop.org/wayland/wayland.git +revision = HEAD diff --git a/tinywl/.gitignore b/tinywl/.gitignore index 2b5bb6f84..5de73fac9 100644 --- a/tinywl/.gitignore +++ b/tinywl/.gitignore @@ -1,3 +1,3 @@ tinywl -*-protocol.c +tinywl.o *-protocol.h diff --git a/tinywl/Makefile b/tinywl/Makefile index 82cbebd28..9c7af540e 100644 --- a/tinywl/Makefile +++ b/tinywl/Makefile @@ -1,25 +1,18 @@ PKG_CONFIG?=pkg-config -WAYLAND_PROTOCOLS=$(shell $(PKG_CONFIG) --variable=pkgdatadir wayland-protocols) -WAYLAND_SCANNER=$(shell $(PKG_CONFIG) --variable=wayland_scanner wayland-scanner) -PKGS="wlroots-0.18" wayland-server xkbcommon -CFLAGS+=$(shell $(PKG_CONFIG) --cflags $(PKGS)) -LIBS=$(shell $(PKG_CONFIG) --libs $(PKGS)) +PKGS="wlroots-0.20" wayland-server xkbcommon +CFLAGS_PKG_CONFIG!=$(PKG_CONFIG) --cflags $(PKGS) +CFLAGS+=$(CFLAGS_PKG_CONFIG) +LIBS!=$(PKG_CONFIG) --libs $(PKGS) -# wayland-scanner is a tool which generates C headers and rigging for Wayland -# protocols, which are specified in XML. wlroots requires you to rig these up -# to your build system yourself and provide them in the include path. -xdg-shell-protocol.h: - $(WAYLAND_SCANNER) server-header \ - $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ +all: tinywl -tinywl.o: tinywl.c xdg-shell-protocol.h - $(CC) -g -Werror $(CFLAGS) -I. -DWLR_USE_UNSTABLE -o $@ -c $< +tinywl.o: tinywl.c + $(CC) -c $< -g -Werror $(CFLAGS) -I. -DWLR_USE_UNSTABLE -o $@ tinywl: tinywl.o - $(CC) $< -g -Werror $(CFLAGS) $(LDFLAGS) $(LIBS) -o $@ + $(CC) $^ $> -g -Werror $(CFLAGS) $(LDFLAGS) $(LIBS) -o $@ clean: - rm -f tinywl tinywl.o xdg-shell-protocol.h + rm -f tinywl tinywl.o -.DEFAULT_GOAL=tinywl -.PHONY: clean +.PHONY: all clean diff --git a/tinywl/meson.build b/tinywl/meson.build index e7271458b..07b4a5e99 100644 --- a/tinywl/meson.build +++ b/tinywl/meson.build @@ -1,5 +1,5 @@ executable( 'tinywl', - ['tinywl.c', protocols_server_header['xdg-shell']], + 'tinywl.c', dependencies: wlroots, ) diff --git a/tinywl/tinywl.c b/tinywl/tinywl.c index 6c043ed51..b3d902c7e 100644 --- a/tinywl/tinywl.c +++ b/tinywl/tinywl.c @@ -56,6 +56,7 @@ struct tinywl_server { struct wlr_seat *seat; struct wl_listener new_input; struct wl_listener request_cursor; + struct wl_listener pointer_focus_change; struct wl_listener request_set_selection; struct wl_list keyboards; enum tinywl_cursor_mode cursor_mode; @@ -109,7 +110,7 @@ struct tinywl_keyboard { struct wl_listener destroy; }; -static void focus_toplevel(struct tinywl_toplevel *toplevel, struct wlr_surface *surface) { +static void focus_toplevel(struct tinywl_toplevel *toplevel) { /* Note: this function only deals with keyboard focus. */ if (toplevel == NULL) { return; @@ -117,6 +118,7 @@ static void focus_toplevel(struct tinywl_toplevel *toplevel, struct wlr_surface struct tinywl_server *server = toplevel->server; struct wlr_seat *seat = server->seat; struct wlr_surface *prev_surface = seat->keyboard_state.focused_surface; + struct wlr_surface *surface = toplevel->xdg_toplevel->base->surface; if (prev_surface == surface) { /* Don't re-focus an already focused surface. */ return; @@ -146,7 +148,7 @@ static void focus_toplevel(struct tinywl_toplevel *toplevel, struct wlr_surface * clients without additional work on your part. */ if (keyboard != NULL) { - wlr_seat_keyboard_notify_enter(seat, toplevel->xdg_toplevel->base->surface, + wlr_seat_keyboard_notify_enter(seat, surface, keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers); } } @@ -188,7 +190,7 @@ static bool handle_keybinding(struct tinywl_server *server, xkb_keysym_t sym) { } struct tinywl_toplevel *next_toplevel = wl_container_of(server->toplevels.prev, next_toplevel, link); - focus_toplevel(next_toplevel, next_toplevel->xdg_toplevel->base->surface); + focus_toplevel(next_toplevel); break; default: return false; @@ -332,6 +334,18 @@ static void seat_request_cursor(struct wl_listener *listener, void *data) { } } +static void seat_pointer_focus_change(struct wl_listener *listener, void *data) { + struct tinywl_server *server = wl_container_of( + listener, server, pointer_focus_change); + /* This event is raised when the pointer focus is changed, including when the + * client is closed. We set the cursor image to its default if target surface + * is NULL */ + struct wlr_seat_pointer_focus_change_event *event = data; + if (event->new_surface == NULL) { + wlr_cursor_set_xcursor(server->cursor, server->cursor_mgr, "default"); + } +} + static void seat_request_set_selection(struct wl_listener *listener, void *data) { /* This event is raised by the seat when a client wants to set the selection, * usually when the user copies something. wlroots allows compositors to @@ -377,7 +391,7 @@ static void reset_cursor_mode(struct tinywl_server *server) { server->grabbed_toplevel = NULL; } -static void process_cursor_move(struct tinywl_server *server, uint32_t time) { +static void process_cursor_move(struct tinywl_server *server) { /* Move the grabbed toplevel to the new position. */ struct tinywl_toplevel *toplevel = server->grabbed_toplevel; wlr_scene_node_set_position(&toplevel->scene_tree->node, @@ -385,7 +399,7 @@ static void process_cursor_move(struct tinywl_server *server, uint32_t time) { server->cursor->y - server->grab_y); } -static void process_cursor_resize(struct tinywl_server *server, uint32_t time) { +static void process_cursor_resize(struct tinywl_server *server) { /* * Resizing the grabbed toplevel can be a little bit complicated, because we * could be resizing from any corner or edge. This not only resizes the @@ -427,10 +441,9 @@ static void process_cursor_resize(struct tinywl_server *server, uint32_t time) { } } - struct wlr_box geo_box; - wlr_xdg_surface_get_geometry(toplevel->xdg_toplevel->base, &geo_box); + struct wlr_box *geo_box = &toplevel->xdg_toplevel->base->geometry; wlr_scene_node_set_position(&toplevel->scene_tree->node, - new_left - geo_box.x, new_top - geo_box.y); + new_left - geo_box->x, new_top - geo_box->y); int new_width = new_right - new_left; int new_height = new_bottom - new_top; @@ -440,10 +453,10 @@ static void process_cursor_resize(struct tinywl_server *server, uint32_t time) { static void process_cursor_motion(struct tinywl_server *server, uint32_t time) { /* If the mode is non-passthrough, delegate to those functions. */ if (server->cursor_mode == TINYWL_CURSOR_MOVE) { - process_cursor_move(server, time); + process_cursor_move(server); return; } else if (server->cursor_mode == TINYWL_CURSOR_RESIZE) { - process_cursor_resize(server, time); + process_cursor_resize(server); return; } @@ -521,16 +534,16 @@ static void server_cursor_button(struct wl_listener *listener, void *data) { /* Notify the client with pointer focus that a button press has occurred */ wlr_seat_pointer_notify_button(server->seat, event->time_msec, event->button, event->state); - double sx, sy; - struct wlr_surface *surface = NULL; - struct tinywl_toplevel *toplevel = desktop_toplevel_at(server, - server->cursor->x, server->cursor->y, &surface, &sx, &sy); if (event->state == WL_POINTER_BUTTON_STATE_RELEASED) { /* If you released any buttons, we exit interactive move/resize mode. */ reset_cursor_mode(server); } else { /* Focus that client if the button was _pressed_ */ - focus_toplevel(toplevel, surface); + double sx, sy; + struct wlr_surface *surface = NULL; + struct tinywl_toplevel *toplevel = desktop_toplevel_at(server, + server->cursor->x, server->cursor->y, &surface, &sx, &sy); + focus_toplevel(toplevel); } } @@ -663,7 +676,7 @@ static void xdg_toplevel_map(struct wl_listener *listener, void *data) { wl_list_insert(&toplevel->server->toplevels, &toplevel->link); - focus_toplevel(toplevel, toplevel->xdg_toplevel->base->surface); + focus_toplevel(toplevel); } static void xdg_toplevel_unmap(struct wl_listener *listener, void *data) { @@ -713,13 +726,7 @@ static void begin_interactive(struct tinywl_toplevel *toplevel, * compositor stops propegating pointer events to clients and instead * consumes them itself, to move or resize windows. */ struct tinywl_server *server = toplevel->server; - struct wlr_surface *focused_surface = - server->seat->pointer_state.focused_surface; - if (toplevel->xdg_toplevel->base->surface != - wlr_surface_get_root_surface(focused_surface)) { - /* Deny move/resize requests from unfocused clients. */ - return; - } + server->grabbed_toplevel = toplevel; server->cursor_mode = mode; @@ -727,17 +734,16 @@ static void begin_interactive(struct tinywl_toplevel *toplevel, server->grab_x = server->cursor->x - toplevel->scene_tree->node.x; server->grab_y = server->cursor->y - toplevel->scene_tree->node.y; } else { - struct wlr_box geo_box; - wlr_xdg_surface_get_geometry(toplevel->xdg_toplevel->base, &geo_box); + struct wlr_box *geo_box = &toplevel->xdg_toplevel->base->geometry; - double border_x = (toplevel->scene_tree->node.x + geo_box.x) + - ((edges & WLR_EDGE_RIGHT) ? geo_box.width : 0); - double border_y = (toplevel->scene_tree->node.y + geo_box.y) + - ((edges & WLR_EDGE_BOTTOM) ? geo_box.height : 0); + double border_x = (toplevel->scene_tree->node.x + geo_box->x) + + ((edges & WLR_EDGE_RIGHT) ? geo_box->width : 0); + double border_y = (toplevel->scene_tree->node.y + geo_box->y) + + ((edges & WLR_EDGE_BOTTOM) ? geo_box->height : 0); server->grab_x = server->cursor->x - border_x; server->grab_y = server->cursor->y - border_y; - server->grab_geobox = geo_box; + server->grab_geobox = *geo_box; server->grab_geobox.x += toplevel->scene_tree->node.x; server->grab_geobox.y += toplevel->scene_tree->node.y; @@ -1025,6 +1031,9 @@ int main(int argc, char *argv[]) { server.request_cursor.notify = seat_request_cursor; wl_signal_add(&server.seat->events.request_set_cursor, &server.request_cursor); + server.pointer_focus_change.notify = seat_pointer_focus_change; + wl_signal_add(&server.seat->pointer_state.events.focus_change, + &server.pointer_focus_change); server.request_set_selection.notify = seat_request_set_selection; wl_signal_add(&server.seat->events.request_set_selection, &server.request_set_selection); @@ -1060,9 +1069,27 @@ int main(int argc, char *argv[]) { socket); wl_display_run(server.wl_display); + /* Once wl_display_run returns, we destroy all clients then shut down the * server. */ wl_display_destroy_clients(server.wl_display); + + wl_list_remove(&server.new_xdg_toplevel.link); + wl_list_remove(&server.new_xdg_popup.link); + + wl_list_remove(&server.cursor_motion.link); + wl_list_remove(&server.cursor_motion_absolute.link); + wl_list_remove(&server.cursor_button.link); + wl_list_remove(&server.cursor_axis.link); + wl_list_remove(&server.cursor_frame.link); + + wl_list_remove(&server.new_input.link); + wl_list_remove(&server.request_cursor.link); + wl_list_remove(&server.pointer_focus_change.link); + wl_list_remove(&server.request_set_selection.link); + + wl_list_remove(&server.new_output.link); + wlr_scene_node_destroy(&server.scene->tree.node); wlr_xcursor_manager_destroy(server.cursor_mgr); wlr_cursor_destroy(server.cursor); diff --git a/types/buffer/buffer.c b/types/buffer/buffer.c index 953207a2c..d56255b0d 100644 --- a/types/buffer/buffer.c +++ b/types/buffer/buffer.c @@ -17,11 +17,21 @@ void wlr_buffer_init(struct wlr_buffer *buffer, .width = width, .height = height, }; + wl_signal_init(&buffer->events.destroy); wl_signal_init(&buffer->events.release); + wlr_addon_set_init(&buffer->addons); } +void wlr_buffer_finish(struct wlr_buffer *buffer) { + wl_signal_emit_mutable(&buffer->events.destroy, NULL); + wlr_addon_set_finish(&buffer->addons); + + assert(wl_list_empty(&buffer->events.destroy.listener_list)); + assert(wl_list_empty(&buffer->events.release.listener_list)); +} + static void buffer_consider_destroy(struct wlr_buffer *buffer) { if (!buffer->dropped || buffer->n_locks > 0) { return; @@ -29,9 +39,6 @@ static void buffer_consider_destroy(struct wlr_buffer *buffer) { assert(!buffer->accessing_data_ptr); - wl_signal_emit_mutable(&buffer->events.destroy, NULL); - wlr_addon_set_finish(&buffer->addons); - buffer->impl->destroy(buffer); } @@ -100,7 +107,7 @@ bool wlr_buffer_get_shm(struct wlr_buffer *buffer, return buffer->impl->get_shm(buffer, attribs); } -bool buffer_is_opaque(struct wlr_buffer *buffer) { +bool wlr_buffer_is_opaque(struct wlr_buffer *buffer) { void *data; uint32_t format; size_t stride; diff --git a/types/buffer/client.c b/types/buffer/client.c index d96603a45..5bc670334 100644 --- a/types/buffer/client.c +++ b/types/buffer/client.c @@ -24,6 +24,9 @@ static struct wlr_client_buffer *client_buffer_from_buffer( static void client_buffer_destroy(struct wlr_buffer *buffer) { struct wlr_client_buffer *client_buffer = client_buffer_from_buffer(buffer); + + wlr_buffer_finish(buffer); + wl_list_remove(&client_buffer->source_destroy.link); wl_list_remove(&client_buffer->renderer_destroy.link); wlr_texture_destroy(client_buffer->texture); @@ -41,9 +44,44 @@ static bool client_buffer_get_dmabuf(struct wlr_buffer *buffer, return wlr_buffer_get_dmabuf(client_buffer->source, attribs); } +static bool client_buffer_get_shm(struct wlr_buffer *buffer, + struct wlr_shm_attributes *attribs) { + struct wlr_client_buffer *client_buffer = client_buffer_from_buffer(buffer); + + if (client_buffer->source == NULL) { + return false; + } + + return wlr_buffer_get_shm(client_buffer->source, attribs); +} + +static bool client_buffer_begin_data_ptr_access(struct wlr_buffer *buffer, uint32_t flags, + void **data, uint32_t *format, size_t *stride) { + struct wlr_client_buffer *client_buffer = client_buffer_from_buffer(buffer); + + if (client_buffer->source == NULL) { + return false; + } + + return wlr_buffer_begin_data_ptr_access(client_buffer->source, flags, data, format, stride); +} + +static void client_buffer_end_data_ptr_access(struct wlr_buffer *buffer) { + struct wlr_client_buffer *client_buffer = client_buffer_from_buffer(buffer); + + if (client_buffer->source == NULL) { + return; + } + + wlr_buffer_end_data_ptr_access(client_buffer->source); +} + static const struct wlr_buffer_impl client_buffer_impl = { .destroy = client_buffer_destroy, .get_dmabuf = client_buffer_get_dmabuf, + .get_shm = client_buffer_get_shm, + .begin_data_ptr_access = client_buffer_begin_data_ptr_access, + .end_data_ptr_access = client_buffer_end_data_ptr_access, }; static void client_buffer_handle_source_destroy(struct wl_listener *listener, diff --git a/types/buffer/dmabuf.c b/types/buffer/dmabuf.c index af0c8c5c8..078821e43 100644 --- a/types/buffer/dmabuf.c +++ b/types/buffer/dmabuf.c @@ -15,6 +15,7 @@ static struct wlr_dmabuf_buffer *dmabuf_buffer_from_buffer( static void dmabuf_buffer_destroy(struct wlr_buffer *wlr_buffer) { struct wlr_dmabuf_buffer *buffer = dmabuf_buffer_from_buffer(wlr_buffer); + wlr_buffer_finish(wlr_buffer); if (buffer->saved) { wlr_dmabuf_attributes_finish(&buffer->dmabuf); } diff --git a/types/buffer/readonly_data.c b/types/buffer/readonly_data.c index f934ddc77..1c8fcf126 100644 --- a/types/buffer/readonly_data.c +++ b/types/buffer/readonly_data.c @@ -17,6 +17,7 @@ static struct wlr_readonly_data_buffer *readonly_data_buffer_from_buffer( static void readonly_data_buffer_destroy(struct wlr_buffer *wlr_buffer) { struct wlr_readonly_data_buffer *buffer = readonly_data_buffer_from_buffer(wlr_buffer); + wlr_buffer_finish(wlr_buffer); free(buffer->saved_data); free(buffer); } diff --git a/types/data_device/wlr_data_device.c b/types/data_device/wlr_data_device.c index 3b5b8a668..ad7966d36 100644 --- a/types/data_device/wlr_data_device.c +++ b/types/data_device/wlr_data_device.c @@ -284,6 +284,9 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_data_device_manager *manager = wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.destroy.listener_list)); + wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); diff --git a/types/data_device/wlr_data_source.c b/types/data_device/wlr_data_source.c index 15fb9929e..64b73c56e 100644 --- a/types/data_device/wlr_data_source.c +++ b/types/data_device/wlr_data_source.c @@ -41,6 +41,8 @@ void wlr_data_source_destroy(struct wlr_data_source *source) { wl_signal_emit_mutable(&source->events.destroy, source); + assert(wl_list_empty(&source->events.destroy.listener_list)); + char **p; wl_array_for_each(p, &source->mime_types) { free(*p); diff --git a/types/data_device/wlr_drag.c b/types/data_device/wlr_drag.c index 9ccd53c94..8c17eeb68 100644 --- a/types/data_device/wlr_drag.c +++ b/types/data_device/wlr_drag.c @@ -19,6 +19,14 @@ static void drag_handle_seat_client_destroy(struct wl_listener *listener, wl_list_remove(&drag->seat_client_destroy.link); } +static void drag_set_focus(struct wlr_drag *drag, + struct wlr_surface *surface, double sx, double sy); + +static void drag_handle_focus_destroy(struct wl_listener *listener, void *data) { + struct wlr_drag *drag = wl_container_of(listener, drag, focus_destroy); + drag_set_focus(drag, NULL, 0, 0); +} + static void drag_set_focus(struct wlr_drag *drag, struct wlr_surface *surface, double sx, double sy) { if (drag->focus == surface) { @@ -48,9 +56,12 @@ static void drag_set_focus(struct wlr_drag *drag, } drag->focus_client = NULL; - drag->focus = NULL; } + wl_list_remove(&drag->focus_destroy.link); + wl_list_init(&drag->focus_destroy.link); + drag->focus = NULL; + if (!surface) { goto out; } @@ -99,6 +110,8 @@ static void drag_set_focus(struct wlr_drag *drag, drag->focus = surface; drag->focus_client = focus_client; + drag->focus_destroy.notify = drag_handle_focus_destroy; + wl_signal_add(&surface->events.destroy, &drag->focus_destroy); drag->seat_client_destroy.notify = drag_handle_seat_client_destroy; wl_signal_add(&focus_client->events.destroy, &drag->seat_client_destroy); @@ -110,6 +123,9 @@ static void drag_icon_destroy(struct wlr_drag_icon *icon) { icon->drag->icon = NULL; wl_list_remove(&icon->surface_destroy.link); wl_signal_emit_mutable(&icon->events.destroy, icon); + + assert(wl_list_empty(&icon->events.destroy.listener_list)); + free(icon); } @@ -147,9 +163,15 @@ static void drag_destroy(struct wlr_drag *drag) { // signal handler. wl_signal_emit_mutable(&drag->events.destroy, drag); + assert(wl_list_empty(&drag->events.focus.listener_list)); + assert(wl_list_empty(&drag->events.motion.listener_list)); + assert(wl_list_empty(&drag->events.drop.listener_list)); + assert(wl_list_empty(&drag->events.destroy.listener_list)); + if (drag->source) { wl_list_remove(&drag->source_destroy.link); } + wl_list_remove(&drag->focus_destroy.link); if (drag->icon != NULL) { drag_icon_destroy(drag->icon); @@ -286,6 +308,14 @@ static void drag_handle_touch_motion(struct wlr_seat_touch_grab *grab, wl_fixed_from_double(point->sx), wl_fixed_from_double(point->sy)); } + + struct wlr_drag_motion_event event = { + .drag = drag, + .time = time, + .sx = point->sx, + .sy = point->sy, + }; + wl_signal_emit_mutable(&drag->events.motion, &event); } } @@ -348,6 +378,7 @@ static const struct wlr_keyboard_grab_interface static void drag_handle_icon_destroy(struct wl_listener *listener, void *data) { struct wlr_drag *drag = wl_container_of(listener, drag, icon_destroy); drag->icon = NULL; + wl_list_remove(&drag->icon_destroy.link); } static void drag_handle_drag_source_destroy(struct wl_listener *listener, @@ -410,6 +441,8 @@ struct wlr_drag *wlr_drag_create(struct wlr_seat_client *seat_client, wl_signal_init(&drag->events.drop); wl_signal_init(&drag->events.destroy); + wl_list_init(&drag->focus_destroy.link); + drag->seat = seat_client->seat; drag->seat_client = seat_client; diff --git a/types/ext_image_capture_source_v1/base.c b/types/ext_image_capture_source_v1/base.c new file mode 100644 index 000000000..eccbb2884 --- /dev/null +++ b/types/ext_image_capture_source_v1/base.c @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ext-image-capture-source-v1-protocol.h" +#include "render/wlr_renderer.h" + +static void source_handle_destroy(struct wl_client *client, + struct wl_resource *source_resource) { + wl_resource_destroy(source_resource); +} + +static const struct ext_image_capture_source_v1_interface source_impl = { + .destroy = source_handle_destroy, +}; + +struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_image_capture_source_v1_interface, &source_impl)); + return wl_resource_get_user_data(resource); +} + +static void source_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +void wlr_ext_image_capture_source_v1_init(struct wlr_ext_image_capture_source_v1 *source, + const struct wlr_ext_image_capture_source_v1_interface *impl) { + *source = (struct wlr_ext_image_capture_source_v1){ + .impl = impl, + }; + wl_list_init(&source->resources); + wl_signal_init(&source->events.destroy); + wl_signal_init(&source->events.constraints_update); + wl_signal_init(&source->events.frame); +} + +void wlr_ext_image_capture_source_v1_finish(struct wlr_ext_image_capture_source_v1 *source) { + wl_signal_emit_mutable(&source->events.destroy, NULL); + + assert(wl_list_empty(&source->events.destroy.listener_list)); + assert(wl_list_empty(&source->events.constraints_update.listener_list)); + assert(wl_list_empty(&source->events.frame.listener_list)); + + struct wl_resource *resource, *resource_tmp; + wl_resource_for_each_safe(resource, resource_tmp, &source->resources) { + wl_resource_set_user_data(resource, NULL); + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + } + + free(source->shm_formats); + wlr_drm_format_set_finish(&source->dmabuf_formats); +} + +bool wlr_ext_image_capture_source_v1_create_resource(struct wlr_ext_image_capture_source_v1 *source, + struct wl_client *client, uint32_t new_id) { + struct wl_resource *resource = wl_resource_create(client, + &ext_image_capture_source_v1_interface, 1, new_id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return false; + } + wl_resource_set_implementation(resource, &source_impl, source, + source_handle_resource_destroy); + if (source != NULL) { + wl_list_insert(&source->resources, wl_resource_get_link(resource)); + } else { + wl_list_init(wl_resource_get_link(resource)); + } + return true; +} + +static uint32_t get_swapchain_shm_format(struct wlr_swapchain *swapchain, + struct wlr_renderer *renderer) { + struct wlr_buffer *buffer = wlr_swapchain_acquire(swapchain); + if (buffer == NULL) { + return DRM_FORMAT_INVALID; + } + + struct wlr_texture *texture = wlr_texture_from_buffer(renderer, buffer); + wlr_buffer_unlock(buffer); + if (texture == NULL) { + return DRM_FORMAT_INVALID; + } + + uint32_t format = wlr_texture_preferred_read_format(texture); + wlr_texture_destroy(texture); + + return format; +} + +static void add_drm_format(struct wlr_drm_format_set *set, const struct wlr_drm_format *fmt) { + for (size_t i = 0; i < fmt->len; i++) { + wlr_drm_format_set_add(set, fmt->format, fmt->modifiers[i]); + } +} + +bool wlr_ext_image_capture_source_v1_set_constraints_from_swapchain(struct wlr_ext_image_capture_source_v1 *source, + struct wlr_swapchain *swapchain, struct wlr_renderer *renderer) { + source->width = swapchain->width; + source->height = swapchain->height; + + uint32_t shm_format = get_swapchain_shm_format(swapchain, renderer); + if (shm_format != DRM_FORMAT_INVALID) { + uint32_t *shm_formats = calloc(1, sizeof(shm_formats[0])); + if (shm_formats == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + return false; + } + shm_formats[0] = shm_format; + + source->shm_formats_len = 1; + free(source->shm_formats); + source->shm_formats = shm_formats; + } + + int drm_fd = wlr_renderer_get_drm_fd(renderer); + if (swapchain->allocator != NULL && + (swapchain->allocator->buffer_caps & WLR_BUFFER_CAP_DMABUF) && + drm_fd >= 0) { + struct stat dev_stat; + if (fstat(drm_fd, &dev_stat) != 0) { + wlr_log_errno(WLR_ERROR, "fstat() failed"); + return false; + } + + source->dmabuf_device = dev_stat.st_rdev; + + wlr_drm_format_set_finish(&source->dmabuf_formats); + source->dmabuf_formats = (struct wlr_drm_format_set){0}; + + add_drm_format(&source->dmabuf_formats, &swapchain->format); + + const struct wlr_drm_format_set *render_formats = + wlr_renderer_get_render_formats(renderer); + assert(render_formats != NULL); + + // Not all clients support fancy formats. Always ensure we provide + // support for ARGB8888 and XRGB8888 for simple clients. + uint32_t fallback_formats[] = { DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB8888 }; + for (size_t i = 0; i < sizeof(fallback_formats) / sizeof(fallback_formats[0]); i++) { + const struct wlr_drm_format *fmt = + wlr_drm_format_set_get(render_formats, fallback_formats[i]); + if (fmt != NULL && swapchain->format.format != fmt->format) { + add_drm_format(&source->dmabuf_formats, fmt); + } + } + } + + wl_signal_emit_mutable(&source->events.constraints_update, NULL); + return true; +} + +void wlr_ext_image_capture_source_v1_cursor_init(struct wlr_ext_image_capture_source_v1_cursor *source_cursor, + const struct wlr_ext_image_capture_source_v1_interface *impl) { + *source_cursor = (struct wlr_ext_image_capture_source_v1_cursor){0}; + wlr_ext_image_capture_source_v1_init(&source_cursor->base, impl); + wl_signal_init(&source_cursor->events.update); +} + +void wlr_ext_image_capture_source_v1_cursor_finish(struct wlr_ext_image_capture_source_v1_cursor *source_cursor) { + wlr_ext_image_capture_source_v1_finish(&source_cursor->base); + assert(wl_list_empty(&source_cursor->events.update.listener_list)); +} diff --git a/types/ext_image_capture_source_v1/foreign_toplevel.c b/types/ext_image_capture_source_v1/foreign_toplevel.c new file mode 100644 index 000000000..e5c75e280 --- /dev/null +++ b/types/ext_image_capture_source_v1/foreign_toplevel.c @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include "ext-image-capture-source-v1-protocol.h" + +#define FOREIGN_TOPLEVEL_IMAGE_SOURCE_MANAGER_V1_VERSION 1 + +static const struct ext_foreign_toplevel_image_capture_source_manager_v1_interface foreign_toplevel_manager_impl; + +static struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 * +foreign_toplevel_manager_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_foreign_toplevel_image_capture_source_manager_v1_interface, + &foreign_toplevel_manager_impl)); + return wl_resource_get_user_data(resource); +} + +bool wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_accept( + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request *request, + struct wlr_ext_image_capture_source_v1 *source) { + return wlr_ext_image_capture_source_v1_create_resource(source, request->client, request->new_id); +} + +static void foreign_toplevel_manager_handle_create_source(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t new_id, + struct wl_resource *foreign_toplevel_resource) { + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 *manager = + foreign_toplevel_manager_from_resource(manager_resource); + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel_handle = + wlr_ext_foreign_toplevel_handle_v1_from_resource(foreign_toplevel_resource); + if (toplevel_handle == NULL) { + wlr_ext_image_capture_source_v1_create_resource(NULL, client, new_id); + return; + } + + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request *request = + calloc(1, sizeof(*request)); + if (request == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + request->toplevel_handle = toplevel_handle; + request->client = client; + request->new_id = new_id; + + wl_signal_emit_mutable(&manager->events.new_request, request); +} + +static void foreign_toplevel_manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct ext_foreign_toplevel_image_capture_source_manager_v1_interface foreign_toplevel_manager_impl = { + .create_source = foreign_toplevel_manager_handle_create_source, + .destroy = foreign_toplevel_manager_handle_destroy, +}; + +static void foreign_toplevel_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &ext_foreign_toplevel_image_capture_source_manager_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &foreign_toplevel_manager_impl, manager, NULL); +} + +static void foreign_toplevel_manager_handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, NULL); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + assert(wl_list_empty(&manager->events.new_request.listener_list)); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 * +wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create(struct wl_display *display, + uint32_t version) { + assert(version <= FOREIGN_TOPLEVEL_IMAGE_SOURCE_MANAGER_V1_VERSION); + + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 *manager = + calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, + &ext_foreign_toplevel_image_capture_source_manager_v1_interface, + version, manager, foreign_toplevel_manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.destroy); + wl_signal_init(&manager->events.new_request); + + manager->display_destroy.notify = foreign_toplevel_manager_handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/ext_image_capture_source_v1/output.c b/types/ext_image_capture_source_v1/output.c new file mode 100644 index 000000000..1112b64d5 --- /dev/null +++ b/types/ext_image_capture_source_v1/output.c @@ -0,0 +1,388 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ext-image-capture-source-v1-protocol.h" + +#define OUTPUT_IMAGE_SOURCE_MANAGER_V1_VERSION 1 + +struct output_cursor_source { + struct wlr_ext_image_capture_source_v1_cursor base; + + struct wlr_output *output; + struct wlr_buffer *prev_buffer; + bool initialized; + bool needs_frame; + + struct wl_listener output_commit; + struct wl_listener prev_buffer_release; +}; + +struct wlr_ext_output_image_capture_source_v1 { + struct wlr_ext_image_capture_source_v1 base; + struct wlr_addon addon; + + struct wlr_output *output; + + struct wl_listener output_commit; + + struct output_cursor_source cursor; + + size_t num_started; + bool software_cursors_locked; +}; + +struct wlr_ext_output_image_capture_source_v1_frame_event { + struct wlr_ext_image_capture_source_v1_frame_event base; + struct wlr_buffer *buffer; + struct timespec when; +}; + +static void output_source_start(struct wlr_ext_image_capture_source_v1 *base, + bool with_cursors) { + struct wlr_ext_output_image_capture_source_v1 *source = wl_container_of(base, source, base); + source->num_started++; + if (source->num_started > 1) { + return; + } + wlr_output_lock_attach_render(source->output, true); + if (with_cursors) { + wlr_output_lock_software_cursors(source->output, true); + } + source->software_cursors_locked = with_cursors; +} + +static void output_source_stop(struct wlr_ext_image_capture_source_v1 *base) { + struct wlr_ext_output_image_capture_source_v1 *source = wl_container_of(base, source, base); + assert(source->num_started > 0); + source->num_started--; + if (source->num_started > 0) { + return; + } + wlr_output_lock_attach_render(source->output, false); + if (source->software_cursors_locked) { + wlr_output_lock_software_cursors(source->output, false); + } +} + +static void output_source_schedule_frame(struct wlr_ext_image_capture_source_v1 *base) { + struct wlr_ext_output_image_capture_source_v1 *source = wl_container_of(base, source, base); + wlr_output_update_needs_frame(source->output); +} + +static void output_source_copy_frame(struct wlr_ext_image_capture_source_v1 *base, + struct wlr_ext_image_copy_capture_frame_v1 *frame, + struct wlr_ext_image_capture_source_v1_frame_event *base_event) { + struct wlr_ext_output_image_capture_source_v1 *source = wl_container_of(base, source, base); + struct wlr_ext_output_image_capture_source_v1_frame_event *event = + wl_container_of(base_event, event, base); + + if (wlr_ext_image_copy_capture_frame_v1_copy_buffer(frame, + event->buffer, source->output->renderer)) { + wlr_ext_image_copy_capture_frame_v1_ready(frame, + source->output->transform, &event->when); + } +} + +static struct wlr_ext_image_capture_source_v1_cursor *output_source_get_pointer_cursor( + struct wlr_ext_image_capture_source_v1 *base, struct wlr_seat *seat) { + // TODO: handle seat + struct wlr_ext_output_image_capture_source_v1 *source = wl_container_of(base, source, base); + return &source->cursor.base; +} + +static const struct wlr_ext_image_capture_source_v1_interface output_source_impl = { + .start = output_source_start, + .stop = output_source_stop, + .schedule_frame = output_source_schedule_frame, + .copy_frame = output_source_copy_frame, + .get_pointer_cursor = output_source_get_pointer_cursor, +}; + +static void source_update_buffer_constraints(struct wlr_ext_output_image_capture_source_v1 *source) { + struct wlr_output *output = source->output; + + if (!wlr_output_configure_primary_swapchain(output, NULL, &output->swapchain)) { + return; + } + + wlr_ext_image_capture_source_v1_set_constraints_from_swapchain(&source->base, + output->swapchain, output->renderer); +} + +static void source_handle_output_commit(struct wl_listener *listener, + void *data) { + struct wlr_ext_output_image_capture_source_v1 *source = wl_container_of(listener, source, output_commit); + struct wlr_output_event_commit *event = data; + + if (event->state->committed & (WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_RENDER_FORMAT)) { + source_update_buffer_constraints(source); + } + + if (event->state->committed & WLR_OUTPUT_STATE_BUFFER) { + struct wlr_buffer *buffer = event->state->buffer; + + pixman_region32_t full_damage; + pixman_region32_init_rect(&full_damage, 0, 0, buffer->width, buffer->height); + + const pixman_region32_t *damage; + if (event->state->committed & WLR_OUTPUT_STATE_DAMAGE) { + damage = &event->state->damage; + } else { + damage = &full_damage; + } + + struct wlr_ext_output_image_capture_source_v1_frame_event frame_event = { + .base = { + .damage = damage, + }, + .buffer = buffer, + .when = event->when, // TODO: predict next presentation time instead + }; + wl_signal_emit_mutable(&source->base.events.frame, &frame_event); + + pixman_region32_fini(&full_damage); + } +} + +static void output_cursor_source_init(struct output_cursor_source *cursor_source, + struct wlr_output *output); +static void output_cursor_source_finish(struct output_cursor_source *cursor_source); + +static void output_addon_destroy(struct wlr_addon *addon) { + struct wlr_ext_output_image_capture_source_v1 *source = wl_container_of(addon, source, addon); + wlr_ext_image_capture_source_v1_finish(&source->base); + output_cursor_source_finish(&source->cursor); + wl_list_remove(&source->output_commit.link); + wlr_addon_finish(&source->addon); + free(source); +} + +static const struct wlr_addon_interface output_addon_impl = { + .name = "wlr_ext_output_image_capture_source_v1", + .destroy = output_addon_destroy, +}; + +static void output_manager_handle_create_source(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t new_id, + struct wl_resource *output_resource) { + struct wlr_output *output = wlr_output_from_resource(output_resource); + if (output == NULL) { + wlr_ext_image_capture_source_v1_create_resource(NULL, client, new_id); + return; + } + + struct wlr_ext_output_image_capture_source_v1 *source; + struct wlr_addon *addon = wlr_addon_find(&output->addons, NULL, &output_addon_impl); + if (addon != NULL) { + source = wl_container_of(addon, source, addon); + } else { + source = calloc(1, sizeof(*source)); + if (source == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + wlr_ext_image_capture_source_v1_init(&source->base, &output_source_impl); + wlr_addon_init(&source->addon, &output->addons, NULL, &output_addon_impl); + source->output = output; + + source->output_commit.notify = source_handle_output_commit; + wl_signal_add(&output->events.commit, &source->output_commit); + + source_update_buffer_constraints(source); + + output_cursor_source_init(&source->cursor, output); + } + + if (!wlr_ext_image_capture_source_v1_create_resource(&source->base, client, new_id)) { + return; + } +} + +static void output_manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct ext_output_image_capture_source_manager_v1_interface output_manager_impl = { + .create_source = output_manager_handle_create_source, + .destroy = output_manager_handle_destroy, +}; + +static void output_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_ext_output_image_capture_source_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &ext_output_image_capture_source_manager_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &output_manager_impl, manager, NULL); +} + +static void output_manager_handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_ext_output_image_capture_source_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_ext_output_image_capture_source_manager_v1 *wlr_ext_output_image_capture_source_manager_v1_create( + struct wl_display *display, uint32_t version) { + assert(version <= OUTPUT_IMAGE_SOURCE_MANAGER_V1_VERSION); + + struct wlr_ext_output_image_capture_source_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, + &ext_output_image_capture_source_manager_v1_interface, version, manager, output_manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = output_manager_handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} + +static void output_cursor_source_schedule_frame(struct wlr_ext_image_capture_source_v1 *base) { + struct output_cursor_source *cursor_source = wl_container_of(base, cursor_source, base); + wlr_output_update_needs_frame(cursor_source->output); + cursor_source->needs_frame = true; +} + +static void output_cursor_source_copy_frame(struct wlr_ext_image_capture_source_v1 *base, + struct wlr_ext_image_copy_capture_frame_v1 *frame, + struct wlr_ext_image_capture_source_v1_frame_event *base_event) { + struct output_cursor_source *cursor_source = wl_container_of(base, cursor_source, base); + + struct wlr_buffer *src_buffer = cursor_source->output->cursor_front_buffer; + if (src_buffer == NULL) { + wlr_ext_image_copy_capture_frame_v1_fail(frame, EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); + return; + } + + if (!wlr_ext_image_copy_capture_frame_v1_copy_buffer(frame, + src_buffer, cursor_source->output->renderer)) { + return; + } + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + wlr_ext_image_copy_capture_frame_v1_ready(frame, + cursor_source->output->transform, &now); +} + +static const struct wlr_ext_image_capture_source_v1_interface output_cursor_source_impl = { + .schedule_frame = output_cursor_source_schedule_frame, + .copy_frame = output_cursor_source_copy_frame, +}; + +static void output_cursor_source_update(struct output_cursor_source *cursor_source) { + struct wlr_output *output = cursor_source->output; + + if (output->cursor_swapchain != NULL && !cursor_source->initialized) { + wlr_ext_image_capture_source_v1_set_constraints_from_swapchain(&cursor_source->base.base, + output->cursor_swapchain, output->renderer); + cursor_source->initialized = true; + } + + struct wlr_output_cursor *output_cursor = output->hardware_cursor; + if (output_cursor == NULL || !output_cursor->visible) { + cursor_source->base.entered = false; + wl_signal_emit_mutable(&cursor_source->base.events.update, NULL); + return; + } + + if (output->cursor_swapchain != NULL && + ((int)cursor_source->base.base.width != output->cursor_swapchain->width || + (int)cursor_source->base.base.height != output->cursor_swapchain->height)) { + cursor_source->base.base.width = output->cursor_swapchain->width; + cursor_source->base.base.height = output->cursor_swapchain->height; + wl_signal_emit_mutable(&cursor_source->base.base.events.constraints_update, NULL); + } + + cursor_source->base.entered = true; + cursor_source->base.x = round(output_cursor->x); + cursor_source->base.y = round(output_cursor->y); + cursor_source->base.hotspot.x = output_cursor->hotspot_x; + cursor_source->base.hotspot.y = output_cursor->hotspot_y; + wl_signal_emit_mutable(&cursor_source->base.events.update, NULL); +} + +static void output_cursor_source_handle_prev_buffer_release(struct wl_listener *listener, + void *data) { + struct output_cursor_source *cursor_source = wl_container_of(listener, cursor_source, prev_buffer_release); + wl_list_remove(&cursor_source->prev_buffer_release.link); + wl_list_init(&cursor_source->prev_buffer_release.link); + cursor_source->prev_buffer = NULL; +} + +static void output_cursor_source_handle_output_commit(struct wl_listener *listener, + void *data) { + struct output_cursor_source *cursor_source = wl_container_of(listener, cursor_source, output_commit); + struct wlr_output_event_commit *event = data; + + output_cursor_source_update(cursor_source); + + struct wlr_buffer *buffer = cursor_source->output->cursor_front_buffer; + if (buffer != NULL && (buffer != cursor_source->prev_buffer || cursor_source->needs_frame)) { + pixman_region32_t full_damage; + pixman_region32_init_rect(&full_damage, 0, 0, buffer->width, buffer->height); + + struct wlr_ext_output_image_capture_source_v1_frame_event frame_event = { + .base = { + .damage = &full_damage, + }, + .buffer = buffer, + .when = event->when, // TODO: predict next presentation time instead + }; + wl_signal_emit_mutable(&cursor_source->base.base.events.frame, &frame_event); + + pixman_region32_fini(&full_damage); + + assert(buffer->n_locks > 0); + cursor_source->prev_buffer = buffer; + wl_list_remove(&cursor_source->prev_buffer_release.link); + cursor_source->prev_buffer_release.notify = output_cursor_source_handle_prev_buffer_release; + wl_signal_add(&buffer->events.release, &cursor_source->prev_buffer_release); + } + + cursor_source->needs_frame = false; +} + +static void output_cursor_source_init(struct output_cursor_source *cursor_source, + struct wlr_output *output) { + wlr_ext_image_capture_source_v1_cursor_init(&cursor_source->base, &output_cursor_source_impl); + + // Caller is responsible for destroying the output cursor source when the + // output is destroyed + cursor_source->output = output; + + cursor_source->output_commit.notify = output_cursor_source_handle_output_commit; + wl_signal_add(&output->events.commit, &cursor_source->output_commit); + + wl_list_init(&cursor_source->prev_buffer_release.link); + + output_cursor_source_update(cursor_source); +} + +static void output_cursor_source_finish(struct output_cursor_source *cursor_source) { + wlr_ext_image_capture_source_v1_cursor_finish(&cursor_source->base); + wl_list_remove(&cursor_source->output_commit.link); + wl_list_remove(&cursor_source->prev_buffer_release.link); +} diff --git a/types/ext_image_capture_source_v1/scene.c b/types/ext_image_capture_source_v1/scene.c new file mode 100644 index 000000000..a8bce9d3d --- /dev/null +++ b/types/ext_image_capture_source_v1/scene.c @@ -0,0 +1,325 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "types/wlr_output.h" +#include "types/wlr_scene.h" + +struct scene_node_source { + struct wlr_ext_image_capture_source_v1 base; + + struct wlr_scene_node *node; + + struct wlr_backend backend; + struct wlr_output output; + struct wlr_scene_output *scene_output; + + struct wl_event_source *idle_frame; + + struct wl_listener node_destroy; + struct wl_listener scene_output_destroy; + struct wl_listener output_frame; +}; + +struct scene_node_source_frame_event { + struct wlr_ext_image_capture_source_v1_frame_event base; + struct wlr_buffer *buffer; + struct timespec when; +}; + +static size_t last_output_num = 0; + +static void _get_scene_node_extents(struct wlr_scene_node *node, struct wlr_box *box, int lx, int ly) { + switch (node->type) { + case WLR_SCENE_NODE_TREE:; + struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); + struct wlr_scene_node *child; + wl_list_for_each(child, &scene_tree->children, link) { + _get_scene_node_extents(child, box, lx + child->x, ly + child->y); + } + break; + case WLR_SCENE_NODE_RECT: + case WLR_SCENE_NODE_BUFFER:; + struct wlr_box node_box = { .x = lx, .y = ly }; + scene_node_get_size(node, &node_box.width, &node_box.height); + + if (node_box.x < box->x) { + box->x = node_box.x; + } + if (node_box.y < box->y) { + box->y = node_box.y; + } + if (node_box.x + node_box.width > box->x + box->width) { + box->width = node_box.x + node_box.width - box->x; + } + if (node_box.y + node_box.height > box->y + box->height) { + box->height = node_box.y + node_box.height - box->y; + } + break; + } +} + +static void get_scene_node_extents(struct wlr_scene_node *node, struct wlr_box *box) { + *box = (struct wlr_box){ .x = INT_MAX, .y = INT_MAX }; + int lx = 0, ly = 0; + wlr_scene_node_coords(node, &lx, &ly); + _get_scene_node_extents(node, box, lx, ly); +} + +static void source_render(struct scene_node_source *source) { + struct wlr_scene_output *scene_output = source->scene_output; + + struct wlr_box extents; + get_scene_node_extents(source->node, &extents); + + if (extents.width == 0 || extents.height == 0) { + return; + } + + wlr_scene_output_set_position(scene_output, extents.x, extents.y); + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + wlr_output_state_set_custom_mode(&state, extents.width, extents.height, 0); + bool ok = wlr_scene_output_build_state(scene_output, &state, NULL) && + wlr_output_commit_state(scene_output->output, &state); + wlr_output_state_finish(&state); + + if (!ok) { + // TODO: send failure + return; + } + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done(scene_output, &now); +} + +static void source_start(struct wlr_ext_image_capture_source_v1 *base, bool with_cursors) { + struct scene_node_source *source = wl_container_of(base, source, base); + source_render(source); +} + +static void source_stop(struct wlr_ext_image_capture_source_v1 *base) { + struct scene_node_source *source = wl_container_of(base, source, base); + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, false); + wlr_output_commit_state(&source->output, &state); + wlr_output_state_finish(&state); +} + +static void source_schedule_frame(struct wlr_ext_image_capture_source_v1 *base) { + struct scene_node_source *source = wl_container_of(base, source, base); + wlr_output_update_needs_frame(&source->output); +} + +static void source_copy_frame(struct wlr_ext_image_capture_source_v1 *base, + struct wlr_ext_image_copy_capture_frame_v1 *frame, + struct wlr_ext_image_capture_source_v1_frame_event *base_event) { + struct scene_node_source *source = wl_container_of(base, source, base); + struct scene_node_source_frame_event *event = wl_container_of(base_event, event, base); + + if (wlr_ext_image_copy_capture_frame_v1_copy_buffer(frame, + event->buffer, source->output.renderer)) { + wlr_ext_image_copy_capture_frame_v1_ready(frame, + source->output.transform, &event->when); + } +} + +static const struct wlr_ext_image_capture_source_v1_interface source_impl = { + .start = source_start, + .stop = source_stop, + .schedule_frame = source_schedule_frame, + .copy_frame = source_copy_frame, +}; + +static const struct wlr_backend_impl backend_impl = {0}; + +static void source_update_buffer_constraints(struct scene_node_source *source, + const struct wlr_output_state *state) { + struct wlr_output *output = &source->output; + + if (!wlr_output_configure_primary_swapchain(output, state, &output->swapchain)) { + return; + } + + wlr_ext_image_capture_source_v1_set_constraints_from_swapchain(&source->base, + output->swapchain, output->renderer); +} + +static void source_handle_idle_frame(void *data) { + struct scene_node_source *source = data; + source->idle_frame = NULL; + wlr_output_send_frame(&source->output); +} + +static bool output_test(struct wlr_output *output, const struct wlr_output_state *state) { + struct scene_node_source *source = wl_container_of(output, source, output); + + uint32_t supported = + WLR_OUTPUT_STATE_BACKEND_OPTIONAL | + WLR_OUTPUT_STATE_BUFFER | + WLR_OUTPUT_STATE_ENABLED | + WLR_OUTPUT_STATE_MODE; + if ((state->committed & ~supported) != 0) { + return false; + } + + if (state->committed & WLR_OUTPUT_STATE_BUFFER) { + int pending_width, pending_height; + output_pending_resolution(output, state, + &pending_width, &pending_height); + if (state->buffer->width != pending_width || + state->buffer->height != pending_height) { + return false; + } + struct wlr_fbox src_box; + output_state_get_buffer_src_box(state, &src_box); + if (src_box.x != 0.0 || src_box.y != 0.0 || + src_box.width != (double)state->buffer->width || + src_box.height != (double)state->buffer->height) { + return false; + } + } + + return true; +} + +static bool output_commit(struct wlr_output *output, const struct wlr_output_state *state) { + struct scene_node_source *source = wl_container_of(output, source, output); + + if (source->idle_frame != NULL) { + wlr_log(WLR_DEBUG, "Failed to commit capture output: a frame is still pending"); + return false; + } + + if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && !state->enabled) { + return true; + } + + if (state->committed & WLR_OUTPUT_STATE_MODE) { + source_update_buffer_constraints(source, state); + } + + if (!(state->committed & WLR_OUTPUT_STATE_BUFFER)) { + wlr_log(WLR_DEBUG, "Failed to commit capture output: missing buffer"); + return false; + } + + struct wlr_buffer *buffer = state->buffer; + + pixman_region32_t full_damage; + pixman_region32_init_rect(&full_damage, 0, 0, buffer->width, buffer->height); + + const pixman_region32_t *damage; + if (state->committed & WLR_OUTPUT_STATE_DAMAGE) { + damage = &state->damage; + } else { + damage = &full_damage; + } + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + struct scene_node_source_frame_event frame_event = { + .base = { .damage = damage }, + .buffer = buffer, + .when = now, + }; + wl_signal_emit_mutable(&source->base.events.frame, &frame_event.base); + + pixman_region32_fini(&full_damage); + + source->idle_frame = + wl_event_loop_add_idle(output->event_loop, source_handle_idle_frame, source); + + return true; +} + +static const struct wlr_output_impl output_impl = { + .test = output_test, + .commit = output_commit, +}; + +static void source_destroy(struct scene_node_source *source) { + wl_list_remove(&source->node_destroy.link); + wl_list_remove(&source->scene_output_destroy.link); + wl_list_remove(&source->output_frame.link); + wlr_ext_image_capture_source_v1_finish(&source->base); + wlr_scene_output_destroy(source->scene_output); + wlr_output_finish(&source->output); + wlr_backend_finish(&source->backend); + free(source); +} + +static void source_handle_node_destroy(struct wl_listener *listener, void *data) { + struct scene_node_source *source = wl_container_of(listener, source, node_destroy); + source_destroy(source); +} + +static void source_handle_scene_output_destroy(struct wl_listener *listener, void *data) { + struct scene_node_source *source = wl_container_of(listener, source, scene_output_destroy); + source->scene_output = NULL; + wl_list_remove(&source->scene_output_destroy.link); + wl_list_init(&source->scene_output_destroy.link); +} + +static void source_handle_output_frame(struct wl_listener *listener, void *data) { + struct scene_node_source *source = wl_container_of(listener, source, output_frame); + if (source->scene_output == NULL) { + return; + } + + if (!wlr_scene_output_needs_frame(source->scene_output)) { + return; + } + + source_render(source); +} + +struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_create_with_scene_node( + struct wlr_scene_node *node, struct wl_event_loop *event_loop, + struct wlr_allocator *allocator, struct wlr_renderer *renderer) { + struct scene_node_source *source = calloc(1, sizeof(*source)); + if (source == NULL) { + return NULL; + } + + source->node = node; + + wlr_ext_image_capture_source_v1_init(&source->base, &source_impl); + + wlr_backend_init(&source->backend, &backend_impl); + source->backend.buffer_caps = WLR_BUFFER_CAP_DMABUF | WLR_BUFFER_CAP_SHM; + + wlr_output_init(&source->output, &source->backend, &output_impl, event_loop, NULL); + + size_t output_num = ++last_output_num; + char name[64]; + snprintf(name, sizeof(name), "CAPTURE-%zu", output_num); + wlr_output_set_name(&source->output, name); + + wlr_output_init_render(&source->output, allocator, renderer); + + struct wlr_scene *scene = scene_node_get_root(node); + source->scene_output = wlr_scene_output_create(scene, &source->output); + + source->node_destroy.notify = source_handle_node_destroy; + wl_signal_add(&node->events.destroy, &source->node_destroy); + + source->scene_output_destroy.notify = source_handle_scene_output_destroy; + wl_signal_add(&source->scene_output->events.destroy, &source->scene_output_destroy); + + source->output_frame.notify = source_handle_output_frame; + wl_signal_add(&source->output.events.frame, &source->output_frame); + + return &source->base; +} diff --git a/types/meson.build b/types/meson.build index ec70d4b7c..402fd3e11 100644 --- a/types/meson.build +++ b/types/meson.build @@ -3,6 +3,10 @@ wlr_files += files( 'data_device/wlr_data_offer.c', 'data_device/wlr_data_source.c', 'data_device/wlr_drag.c', + 'ext_image_capture_source_v1/base.c', + 'ext_image_capture_source_v1/output.c', + 'ext_image_capture_source_v1/foreign_toplevel.c', + 'ext_image_capture_source_v1/scene.c', 'output/cursor.c', 'output/output.c', 'output/render.c', @@ -34,17 +38,22 @@ wlr_files += files( 'buffer/readonly_data.c', 'buffer/resource.c', 'wlr_alpha_modifier_v1.c', + 'wlr_color_management_v1.c', + 'wlr_color_representation_v1.c', 'wlr_compositor.c', 'wlr_content_type_v1.c', - 'wlr_cursor_shape_v1.c', 'wlr_cursor.c', + 'wlr_cursor_shape_v1.c', 'wlr_damage_ring.c', 'wlr_data_control_v1.c', 'wlr_drm.c', 'wlr_export_dmabuf_v1.c', - 'wlr_foreign_toplevel_management_v1.c', + 'wlr_ext_data_control_v1.c', 'wlr_ext_foreign_toplevel_list_v1.c', - 'wlr_fullscreen_shell_v1.c', + 'wlr_ext_image_copy_capture_v1.c', + 'wlr_fixes.c', + 'wlr_foreign_toplevel_management_v1.c', + 'wlr_fractional_scale_v1.c', 'wlr_gamma_control_v1.c', 'wlr_idle_inhibit_v1.c', 'wlr_idle_notify_v1.c', @@ -56,7 +65,6 @@ wlr_files += files( 'wlr_layer_shell_v1.c', 'wlr_linux_dmabuf_v1.c', 'wlr_linux_drm_syncobj_v1.c', - 'wlr_matrix.c', 'wlr_output_layer.c', 'wlr_output_layout.c', 'wlr_output_management_v1.c', @@ -77,10 +85,10 @@ wlr_files += files( 'wlr_shm.c', 'wlr_single_pixel_buffer_v1.c', 'wlr_subcompositor.c', - 'wlr_fractional_scale_v1.c', 'wlr_switch.c', 'wlr_tablet_pad.c', 'wlr_tablet_tool.c', + 'wlr_tearing_control_v1.c', 'wlr_text_input_v3.c', 'wlr_touch.c', 'wlr_transient_seat_v1.c', @@ -90,11 +98,14 @@ wlr_files += files( 'wlr_xcursor_manager.c', 'wlr_xdg_activation_v1.c', 'wlr_xdg_decoration_v1.c', + 'wlr_xdg_dialog_v1.c', 'wlr_xdg_foreign_v1.c', 'wlr_xdg_foreign_v2.c', 'wlr_xdg_foreign_registry.c', 'wlr_xdg_output_v1.c', - 'wlr_tearing_control_v1.c', + 'wlr_xdg_system_bell_v1.c', + 'wlr_xdg_toplevel_icon_v1.c', + 'wlr_xdg_toplevel_tag_v1.c', ) if features.get('drm-backend') diff --git a/types/output/cursor.c b/types/output/cursor.c index 2bf785283..70647afb7 100644 --- a/types/output/cursor.c +++ b/types/output/cursor.c @@ -2,13 +2,14 @@ #include #include #include +#include #include +#include #include #include #include #include #include -#include "render/allocator/allocator.h" #include "types/wlr_buffer.h" #include "types/wlr_output.h" @@ -22,6 +23,8 @@ static bool output_set_hardware_cursor(struct wlr_output *output, return false; } + wlr_output_update_needs_frame(output); + wlr_buffer_unlock(output->cursor_front_buffer); output->cursor_front_buffer = NULL; @@ -32,6 +35,15 @@ static bool output_set_hardware_cursor(struct wlr_output *output, return true; } +static bool output_move_hardware_cursor(struct wlr_output *output, int x, int y) { + assert(output->impl->move_cursor); + if (!output->impl->move_cursor(output, x, y)) { + return false; + } + wlr_output_update_needs_frame(output); + return true; +} + static void output_cursor_damage_whole(struct wlr_output_cursor *cursor); static void output_disable_hardware_cursor(struct wlr_output *output) { @@ -80,12 +92,6 @@ void wlr_output_add_software_cursors_to_render_pass(struct wlr_output *output, int width, height; wlr_output_transformed_resolution(output, &width, &height); - pixman_region32_t render_damage; - pixman_region32_init_rect(&render_damage, 0, 0, width, height); - if (damage != NULL) { - pixman_region32_intersect(&render_damage, &render_damage, damage); - } - struct wlr_output_cursor *cursor; wl_list_for_each(cursor, &output->cursors, link) { if (!cursor->enabled || !cursor->visible || @@ -100,20 +106,21 @@ void wlr_output_add_software_cursors_to_render_pass(struct wlr_output *output, struct wlr_box box; output_cursor_get_box(cursor, &box); + wlr_box_transform(&box, &box, + wlr_output_transform_invert(output->transform), width, height); pixman_region32_t cursor_damage; - pixman_region32_init_rect(&cursor_damage, box.x, box.y, box.width, box.height); - pixman_region32_intersect(&cursor_damage, &cursor_damage, &render_damage); - if (!pixman_region32_not_empty(&cursor_damage)) { + pixman_region32_init_rect(&cursor_damage, + box.x, box.y, box.width, box.height); + if (damage != NULL) { + pixman_region32_intersect(&cursor_damage, &cursor_damage, damage); + } + + if (pixman_region32_empty(&cursor_damage)) { pixman_region32_fini(&cursor_damage); continue; } - enum wl_output_transform transform = - wlr_output_transform_invert(output->transform); - wlr_box_transform(&box, &box, transform, width, height); - wlr_region_transform(&cursor_damage, &cursor_damage, transform, width, height); - wlr_render_pass_add_texture(render_pass, &(struct wlr_render_texture_options) { .texture = texture, .src_box = cursor->src_box, @@ -124,8 +131,6 @@ void wlr_output_add_software_cursors_to_render_pass(struct wlr_output *output, pixman_region32_fini(&cursor_damage); } - - pixman_region32_fini(&render_damage); } static void output_cursor_damage_whole(struct wlr_output_cursor *cursor) { @@ -144,12 +149,6 @@ static void output_cursor_damage_whole(struct wlr_output_cursor *cursor) { pixman_region32_fini(&damage); } -static void output_cursor_reset(struct wlr_output_cursor *cursor) { - if (cursor->output->hardware_cursor != cursor) { - output_cursor_damage_whole(cursor); - } -} - static void output_cursor_update_visible(struct wlr_output_cursor *cursor) { struct wlr_box output_box; output_box.x = output_box.y = 0; @@ -201,6 +200,10 @@ static struct wlr_buffer *render_cursor_buffer(struct wlr_output_cursor *cursor) size_t sizes_len = 0; const struct wlr_output_cursor_size *sizes = output->impl->get_cursor_sizes(cursor->output, &sizes_len); + if (sizes_len == 0) { + wlr_log(WLR_DEBUG, "Hardware cursor not supported"); + return NULL; + } bool found = false; for (size_t i = 0; i < sizes_len; i++) { @@ -240,8 +243,7 @@ static struct wlr_buffer *render_cursor_buffer(struct wlr_output_cursor *cursor) } } - struct wlr_buffer *buffer = - wlr_swapchain_acquire(output->cursor_swapchain, NULL); + struct wlr_buffer *buffer = wlr_swapchain_acquire(output->cursor_swapchain); if (buffer == NULL) { return NULL; } @@ -271,6 +273,8 @@ static struct wlr_buffer *render_cursor_buffer(struct wlr_output_cursor *cursor) .src_box = cursor->src_box, .dst_box = dst_box, .transform = transform, + .wait_timeline = cursor->wait_timeline, + .wait_point = cursor->wait_point, }); if (!wlr_render_pass_submit(pass)) { @@ -284,24 +288,15 @@ static struct wlr_buffer *render_cursor_buffer(struct wlr_output_cursor *cursor) static bool output_cursor_attempt_hardware(struct wlr_output_cursor *cursor) { struct wlr_output *output = cursor->output; - if (!output->impl->set_cursor || - output->software_cursor_locks > 0) { + if (!output->impl->set_cursor || output->software_cursor_locks > 0) { return false; } - struct wlr_output_cursor *hwcur = output->hardware_cursor; - if (hwcur != NULL && hwcur != cursor) { - return false; - } - - output->hardware_cursor = NULL; - struct wlr_texture *texture = cursor->texture; // If the cursor was hidden or was a software cursor, the hardware // cursor position is outdated - output->impl->move_cursor(cursor->output, - (int)cursor->x, (int)cursor->y); + output_move_hardware_cursor(cursor->output, (int)cursor->x, (int)cursor->y); struct wlr_buffer *buffer = NULL; if (texture != NULL) { @@ -355,23 +350,32 @@ bool wlr_output_cursor_set_buffer(struct wlr_output_cursor *cursor, hotspot_y /= cursor->output->scale; return output_cursor_set_texture(cursor, texture, true, &src_box, - dst_width, dst_height, WL_OUTPUT_TRANSFORM_NORMAL, hotspot_x, hotspot_y); + dst_width, dst_height, WL_OUTPUT_TRANSFORM_NORMAL, hotspot_x, hotspot_y, + NULL, 0); } static void output_cursor_handle_renderer_destroy(struct wl_listener *listener, void *data) { struct wlr_output_cursor *cursor = wl_container_of(listener, cursor, renderer_destroy); output_cursor_set_texture(cursor, NULL, false, NULL, 0, 0, - WL_OUTPUT_TRANSFORM_NORMAL, 0, 0); + WL_OUTPUT_TRANSFORM_NORMAL, 0, 0, NULL, 0); } bool output_cursor_set_texture(struct wlr_output_cursor *cursor, struct wlr_texture *texture, bool own_texture, const struct wlr_fbox *src_box, int dst_width, int dst_height, enum wl_output_transform transform, - int32_t hotspot_x, int32_t hotspot_y) { + int32_t hotspot_x, int32_t hotspot_y, + struct wlr_drm_syncobj_timeline *wait_timeline, uint64_t wait_point) { + if (texture == NULL && !cursor->enabled) { + // Cursor is still disabled, do nothing + return true; + } + struct wlr_output *output = cursor->output; - output_cursor_reset(cursor); + if (cursor->output->hardware_cursor != cursor) { + output_cursor_damage_whole(cursor); + } cursor->enabled = texture != NULL; if (texture != NULL) { @@ -395,6 +399,15 @@ bool output_cursor_set_texture(struct wlr_output_cursor *cursor, cursor->texture = texture; cursor->own_texture = own_texture; + wlr_drm_syncobj_timeline_unref(cursor->wait_timeline); + if (wait_timeline != NULL) { + cursor->wait_timeline = wlr_drm_syncobj_timeline_ref(wait_timeline); + cursor->wait_point = wait_point; + } else { + cursor->wait_timeline = NULL; + cursor->wait_point = 0; + } + wl_list_remove(&cursor->renderer_destroy.link); if (texture != NULL) { cursor->renderer_destroy.notify = output_cursor_handle_renderer_destroy; @@ -403,12 +416,15 @@ bool output_cursor_set_texture(struct wlr_output_cursor *cursor, wl_list_init(&cursor->renderer_destroy.link); } - if (output_cursor_attempt_hardware(cursor)) { - return true; + if (output->hardware_cursor == NULL || output->hardware_cursor == cursor) { + if (output_cursor_attempt_hardware(cursor)) { + return true; + } + + wlr_log(WLR_DEBUG, "Falling back to software cursor on output '%s'", output->name); + output_disable_hardware_cursor(output); } - wlr_log(WLR_DEBUG, "Falling back to software cursor on output '%s'", output->name); - output_disable_hardware_cursor(output); output_cursor_damage_whole(cursor); return true; } @@ -442,8 +458,7 @@ bool wlr_output_cursor_move(struct wlr_output_cursor *cursor, return true; } - assert(cursor->output->impl->move_cursor); - return cursor->output->impl->move_cursor(cursor->output, (int)x, (int)y); + return output_move_hardware_cursor(cursor->output, (int)x, (int)y); } struct wlr_output_cursor *wlr_output_cursor_create(struct wlr_output *output) { @@ -462,15 +477,17 @@ void wlr_output_cursor_destroy(struct wlr_output_cursor *cursor) { if (cursor == NULL) { return; } - output_cursor_reset(cursor); if (cursor->output->hardware_cursor == cursor) { // If this cursor was the hardware cursor, disable it output_disable_hardware_cursor(cursor->output); + } else { + output_cursor_damage_whole(cursor); } wl_list_remove(&cursor->renderer_destroy.link); if (cursor->own_texture) { wlr_texture_destroy(cursor->texture); } + wlr_drm_syncobj_timeline_unref(cursor->wait_timeline); wl_list_remove(&cursor->link); free(cursor); } diff --git a/types/output/output.c b/types/output/output.c index 818f4549e..ca2e55538 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -1,13 +1,13 @@ #include -#include #include #include +#include #include +#include #include #include #include #include -#include "render/allocator/allocator.h" #include "types/wlr_output.h" #include "util/env.h" #include "util/global.h" @@ -16,20 +16,9 @@ static void send_geometry(struct wl_resource *resource) { struct wlr_output *output = wlr_output_from_resource(resource); - - const char *make = output->make; - if (make == NULL) { - make = "Unknown"; - } - - const char *model = output->model; - if (model == NULL) { - model = "Unknown"; - } - wl_output_send_geometry(resource, 0, 0, output->phys_width, output->phys_height, output->subpixel, - make, model, output->transform); + "Unknown", "Unknown", output->transform); } static void send_current_mode(struct wl_resource *resource) { @@ -244,6 +233,24 @@ static void output_apply_state(struct wlr_output *output, output->transform = state->transform; } + if (state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) { + if (state->image_description != NULL) { + output->image_description_value = *state->image_description; + output->image_description = &output->image_description_value; + } else { + output->image_description = NULL; + } + } + + if (state->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) { + wlr_color_transform_unref(output->color_transform); + if (state->color_transform != NULL) { + output->color_transform = wlr_color_transform_ref(state->color_transform); + } else { + output->color_transform = NULL; + } + } + bool geometry_updated = state->committed & (WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_TRANSFORM | WLR_OUTPUT_STATE_SUBPIXEL); @@ -271,11 +278,6 @@ static void output_apply_state(struct wlr_output *output, } } - if ((state->committed & WLR_OUTPUT_STATE_BUFFER) && - output->swapchain != NULL) { - wlr_swapchain_set_buffer_submitted(output->swapchain, state->buffer); - } - bool mode_updated = false; if (state->committed & WLR_OUTPUT_STATE_MODE) { int width = 0, height = 0, refresh = 0; @@ -354,6 +356,7 @@ void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend, wl_list_init(&output->cursors); wl_list_init(&output->layers); wl_list_init(&output->resources); + wl_signal_init(&output->events.frame); wl_signal_init(&output->events.damage); wl_signal_init(&output->events.needs_frame); @@ -380,19 +383,25 @@ void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend, } } -void wlr_output_destroy(struct wlr_output *output) { - if (!output) { - return; - } - +void wlr_output_finish(struct wlr_output *output) { wl_signal_emit_mutable(&output->events.destroy, output); + wlr_addon_set_finish(&output->addons); + + assert(wl_list_empty(&output->events.frame.listener_list)); + assert(wl_list_empty(&output->events.damage.listener_list)); + assert(wl_list_empty(&output->events.needs_frame.listener_list)); + assert(wl_list_empty(&output->events.precommit.listener_list)); + assert(wl_list_empty(&output->events.commit.listener_list)); + assert(wl_list_empty(&output->events.present.listener_list)); + assert(wl_list_empty(&output->events.bind.listener_list)); + assert(wl_list_empty(&output->events.description.listener_list)); + assert(wl_list_empty(&output->events.request_state.listener_list)); + assert(wl_list_empty(&output->events.destroy.listener_list)); wlr_output_destroy_global(output); wl_list_remove(&output->display_destroy.link); - wlr_addon_set_finish(&output->addons); - // The backend is responsible for free-ing the list of modes struct wlr_output_cursor *cursor, *tmp_cursor; @@ -407,6 +416,7 @@ void wlr_output_destroy(struct wlr_output *output) { wlr_swapchain_destroy(output->cursor_swapchain); wlr_buffer_unlock(output->cursor_front_buffer); + wlr_color_transform_unref(output->color_transform); wlr_swapchain_destroy(output->swapchain); @@ -423,10 +433,17 @@ void wlr_output_destroy(struct wlr_output *output) { free(output->make); free(output->model); free(output->serial); +} + +void wlr_output_destroy(struct wlr_output *output) { + if (!output) { + return; + } if (output->impl && output->impl->destroy) { output->impl->destroy(output); } else { + wlr_output_finish(output); free(output); } } @@ -493,6 +510,14 @@ bool output_pending_enabled(struct wlr_output *output, return output->enabled; } +const struct wlr_output_image_description *output_pending_image_description( + struct wlr_output *output, const struct wlr_output_state *state) { + if (state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) { + return state->image_description; + } + return output->image_description; +} + /** * Compare a struct wlr_output_state with the current state of a struct * wlr_output. @@ -500,8 +525,7 @@ bool output_pending_enabled(struct wlr_output *output, * Returns a bitfield of the unchanged fields. * * Some fields are not checked: damage always changes in-between frames, the - * gamma LUT is too expensive to check, the contents of the buffer might have - * changed, etc. + * contents of the buffer might have changed, etc. */ static uint32_t output_compare_state(struct wlr_output *output, const struct wlr_output_state *state) { @@ -547,25 +571,62 @@ static uint32_t output_compare_state(struct wlr_output *output, output->subpixel == state->subpixel) { fields |= WLR_OUTPUT_STATE_SUBPIXEL; } + if ((state->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) && + output->color_transform == state->color_transform) { + fields |= WLR_OUTPUT_STATE_COLOR_TRANSFORM; + } return fields; } static bool output_basic_test(struct wlr_output *output, const struct wlr_output_state *state) { if (state->committed & WLR_OUTPUT_STATE_BUFFER) { - // If the size doesn't match, reject buffer (scaling is not - // supported) - int pending_width, pending_height; - output_pending_resolution(output, state, - &pending_width, &pending_height); - if (state->buffer->width != pending_width || - state->buffer->height != pending_height) { - wlr_log(WLR_DEBUG, "Primary buffer size mismatch"); + struct wlr_fbox src_box; + output_state_get_buffer_src_box(state, &src_box); + + // Source box must be contained within the buffer + if (src_box.x < 0.0 || src_box.y < 0.0 || + src_box.x + src_box.width > state->buffer->width || + src_box.y + src_box.height > state->buffer->height) { + wlr_log(WLR_ERROR, "Tried to commit with invalid buffer_src_box"); + return false; + } + + // Source box must not be empty (but it can be smaller than 1 pixel, + // some DRM devices support sub-pixel crops) + if (wlr_fbox_empty(&src_box)) { + wlr_log(WLR_ERROR, "Tried to commit with an empty buffer_src_box"); + return false; + } + + // Destination box cannot be entirely off-screen (but it also doesn't + // have to be entirely on-screen). This also checks the dst box is + // not empty. + int pending_width, pending_height; + output_pending_resolution(output, state, &pending_width, &pending_height); + struct wlr_box output_box = { + .width = pending_width, + .height = pending_height + }; + struct wlr_box dst_box; + output_state_get_buffer_dst_box(state, &dst_box); + if (!wlr_box_intersection(&output_box, &output_box, &dst_box)) { + wlr_log(WLR_ERROR, "Primary buffer is entirely off-screen or 0-sized"); + return false; + } + } else { + if (state->tearing_page_flip) { + wlr_log(WLR_ERROR, "Tried to commit a tearing page flip without a buffer"); + return false; + } + if (state->committed & WLR_OUTPUT_STATE_WAIT_TIMELINE) { + wlr_log(WLR_DEBUG, "Tried to set wait timeline without a buffer"); + return false; + } + if (state->committed & WLR_OUTPUT_STATE_SIGNAL_TIMELINE) { + wlr_log(WLR_DEBUG, "Tried to set signal timeline without a buffer"); return false; } - } else if (state->tearing_page_flip) { - wlr_log(WLR_ERROR, "Trying to commit a tearing page flip without a buffer?"); - return false; } if (state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) { @@ -596,29 +657,25 @@ static bool output_basic_test(struct wlr_output *output, } } - if (!enabled && state->committed & WLR_OUTPUT_STATE_BUFFER) { - wlr_log(WLR_DEBUG, "Tried to commit a buffer on a disabled output"); - return false; - } - if (!enabled && state->committed & WLR_OUTPUT_STATE_MODE) { - wlr_log(WLR_DEBUG, "Tried to modeset a disabled output"); - return false; - } - if (!enabled && state->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) { - wlr_log(WLR_DEBUG, "Tried to enable adaptive sync on a disabled output"); - return false; - } - if (!enabled && state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) { - wlr_log(WLR_DEBUG, "Tried to set format for a disabled output"); - return false; - } - if (!enabled && state->committed & WLR_OUTPUT_STATE_GAMMA_LUT) { - wlr_log(WLR_DEBUG, "Tried to set the gamma lut on a disabled output"); - return false; - } - if (!enabled && state->committed & WLR_OUTPUT_STATE_SUBPIXEL) { - wlr_log(WLR_DEBUG, "Tried to set the subpixel layout on a disabled output"); - return false; + const struct { + enum wlr_output_state_field field; + const char *name; + } needs_enabled[] = { + { WLR_OUTPUT_STATE_BUFFER, "buffer" }, + { WLR_OUTPUT_STATE_MODE, "mode" }, + { WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED, "adaptive sync" }, + { WLR_OUTPUT_STATE_RENDER_FORMAT, "render format" }, + { WLR_OUTPUT_STATE_SUBPIXEL, "subpixel" }, + { WLR_OUTPUT_STATE_COLOR_TRANSFORM, "color transform" }, + { WLR_OUTPUT_STATE_IMAGE_DESCRIPTION, "image description" }, + }; + if (!enabled) { + for (size_t i = 0; i < sizeof(needs_enabled) / sizeof(needs_enabled[0]); i++) { + if (state->committed & needs_enabled[i].field) { + wlr_log(WLR_DEBUG, "Tried to set %s on a disabled output", needs_enabled[i].name); + return false; + } + } } if (state->committed & WLR_OUTPUT_STATE_LAYERS) { @@ -632,6 +689,24 @@ static bool output_basic_test(struct wlr_output *output, } } + if ((state->committed & (WLR_OUTPUT_STATE_WAIT_TIMELINE | WLR_OUTPUT_STATE_SIGNAL_TIMELINE)) && + !output->backend->features.timeline) { + wlr_log(WLR_DEBUG, "Wait/signal timelines are not supported for this output"); + return false; + } + + if ((state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) && + state->image_description != NULL) { + if (!(output->supported_primaries & state->image_description->primaries)) { + wlr_log(WLR_DEBUG, "Unsupported image description primaries"); + return false; + } + if (!(output->supported_transfer_functions & state->image_description->transfer_function)) { + wlr_log(WLR_DEBUG, "Unsupported image description transfer function"); + return false; + } + } + return true; } @@ -680,7 +755,7 @@ bool output_prepare_commit(struct wlr_output *output, const struct wlr_output_st struct wlr_output_event_precommit pre_event = { .output = output, - .when = &now, + .when = now, .state = state, }; wl_signal_emit_mutable(&output->events.precommit, &pre_event); @@ -697,12 +772,14 @@ void output_apply_commit(struct wlr_output *output, const struct wlr_output_stat } output_apply_state(output, state); +} +void output_send_commit_event(struct wlr_output *output, const struct wlr_output_state *state) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); struct wlr_output_event_commit event = { .output = output, - .when = &now, + .when = now, .state = state, }; wl_signal_emit_mutable(&output->events.commit, &event); @@ -739,6 +816,7 @@ bool wlr_output_commit_state(struct wlr_output *output, } output_apply_commit(output, &pending); + output_send_commit_event(output, &pending); if (new_back_buffer) { wlr_buffer_unlock(pending.buffer); @@ -783,14 +861,12 @@ void wlr_output_send_present(struct wlr_output *output, assert(event); event->output = output; - struct timespec now; - if (event->presented && event->when == NULL) { - if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) { + if (event->presented && (event->when.tv_sec == 0 && event->when.tv_nsec == 0)) { + if (clock_gettime(CLOCK_MONOTONIC, &event->when) != 0) { wlr_log_errno(WLR_ERROR, "failed to send output present event: " "failed to read clock"); return; } - event->when = &now; } wl_signal_emit_mutable(&output->events.present, event); @@ -836,6 +912,39 @@ void output_defer_present(struct wlr_output *output, struct wlr_output_event_pre deferred_present_event_handle_idle, deferred); } +void output_state_get_buffer_src_box(const struct wlr_output_state *state, + struct wlr_fbox *out) { + out->x = state->buffer_src_box.x; + out->y = state->buffer_src_box.y; + // If the source box is unset then default to the whole buffer. + if (state->buffer_src_box.width == 0.0 && + state->buffer_src_box.height == 0.0) { + out->width = (double)state->buffer->width; + out->height = (double)state->buffer->height; + } else { + out->width = state->buffer_src_box.width; + out->height = state->buffer_src_box.height; + } +} + +void output_state_get_buffer_dst_box(const struct wlr_output_state *state, + struct wlr_box *out) { + out->x = state->buffer_dst_box.x; + out->y = state->buffer_dst_box.y; + // If the dst box is unset then default to source crop size (which itself + // defaults to the whole buffer size if unset) + if (state->buffer_dst_box.width == 0 && + state->buffer_dst_box.height == 0) { + struct wlr_fbox src_box; + output_state_get_buffer_src_box(state, &src_box); + out->width = (int)src_box.width; + out->height = (int)src_box.height; + } else { + out->width = state->buffer_dst_box.width; + out->height = state->buffer_dst_box.height; + } +} + void wlr_output_send_request_state(struct wlr_output *output, const struct wlr_output_state *state) { uint32_t unchanged = output_compare_state(output, state); diff --git a/types/output/render.c b/types/output/render.c index 709646a10..155da5cf8 100644 --- a/types/output/render.c +++ b/types/output/render.c @@ -2,12 +2,11 @@ #include #include #include +#include #include #include #include #include -#include "backend/backend.h" -#include "render/allocator/allocator.h" #include "render/drm_format_set.h" #include "render/wlr_renderer.h" #include "render/pixel_format.h" @@ -17,14 +16,11 @@ bool wlr_output_init_render(struct wlr_output *output, struct wlr_allocator *allocator, struct wlr_renderer *renderer) { assert(allocator != NULL && renderer != NULL); - uint32_t backend_caps = backend_get_buffer_caps(output->backend); - uint32_t renderer_caps = renderer->render_buffer_caps; - - if (!(backend_caps & allocator->buffer_caps)) { + if (!(output->backend->buffer_caps & allocator->buffer_caps)) { wlr_log(WLR_ERROR, "output backend and allocator buffer capabilities " "don't match"); return false; - } else if (!(renderer_caps & allocator->buffer_caps)) { + } else if (!(renderer->render_buffer_caps & allocator->buffer_caps)) { wlr_log(WLR_ERROR, "renderer and allocator buffer capabilities " "don't match"); return false; @@ -55,7 +51,7 @@ static struct wlr_buffer *output_acquire_empty_buffer(struct wlr_output *output, return NULL; } - struct wlr_buffer *buffer = wlr_swapchain_acquire(output->swapchain, NULL); + struct wlr_buffer *buffer = wlr_swapchain_acquire(output->swapchain); if (buffer == NULL) { return NULL; } @@ -199,13 +195,12 @@ bool output_pick_format(struct wlr_output *output, } struct wlr_render_pass *wlr_output_begin_render_pass(struct wlr_output *output, - struct wlr_output_state *state, int *buffer_age, - struct wlr_buffer_pass_options *render_options) { + struct wlr_output_state *state, struct wlr_buffer_pass_options *render_options) { if (!wlr_output_configure_primary_swapchain(output, state, &output->swapchain)) { return NULL; } - struct wlr_buffer *buffer = wlr_swapchain_acquire(output->swapchain, buffer_age); + struct wlr_buffer *buffer = wlr_swapchain_acquire(output->swapchain); if (buffer == NULL) { return NULL; } diff --git a/types/output/state.c b/types/output/state.c index 0909b3e8a..a2e8a0042 100644 --- a/types/output/state.c +++ b/types/output/state.c @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include "types/wlr_output.h" @@ -15,7 +17,9 @@ void wlr_output_state_finish(struct wlr_output_state *state) { // reads it after output_state_finish(). state->buffer = NULL; pixman_region32_fini(&state->damage); - free(state->gamma_lut); + wlr_drm_syncobj_timeline_unref(state->wait_timeline); + wlr_drm_syncobj_timeline_unref(state->signal_timeline); + free(state->image_description); } void wlr_output_state_set_enabled(struct wlr_output_state *state, @@ -85,28 +89,6 @@ void wlr_output_state_set_damage(struct wlr_output_state *state, pixman_region32_copy(&state->damage, damage); } -bool wlr_output_state_set_gamma_lut(struct wlr_output_state *state, - size_t ramp_size, const uint16_t *r, const uint16_t *g, const uint16_t *b) { - uint16_t *gamma_lut = NULL; - if (ramp_size > 0) { - gamma_lut = realloc(state->gamma_lut, 3 * ramp_size * sizeof(uint16_t)); - if (gamma_lut == NULL) { - wlr_log_errno(WLR_ERROR, "Allocation failed"); - return false; - } - memcpy(gamma_lut, r, ramp_size * sizeof(uint16_t)); - memcpy(gamma_lut + ramp_size, g, ramp_size * sizeof(uint16_t)); - memcpy(gamma_lut + 2 * ramp_size, b, ramp_size * sizeof(uint16_t)); - } else { - free(state->gamma_lut); - } - - state->committed |= WLR_OUTPUT_STATE_GAMMA_LUT; - state->gamma_lut_size = ramp_size; - state->gamma_lut = gamma_lut; - return true; -} - void wlr_output_state_set_layers(struct wlr_output_state *state, struct wlr_output_layer_state *layers, size_t layers_len) { state->committed |= WLR_OUTPUT_STATE_LAYERS; @@ -114,30 +96,92 @@ void wlr_output_state_set_layers(struct wlr_output_state *state, state->layers_len = layers_len; } +void wlr_output_state_set_wait_timeline(struct wlr_output_state *state, + struct wlr_drm_syncobj_timeline *timeline, uint64_t src_point) { + state->committed |= WLR_OUTPUT_STATE_WAIT_TIMELINE; + wlr_drm_syncobj_timeline_unref(state->wait_timeline); + state->wait_timeline = wlr_drm_syncobj_timeline_ref(timeline); + state->wait_point = src_point; +} + +void wlr_output_state_set_signal_timeline(struct wlr_output_state *state, + struct wlr_drm_syncobj_timeline *timeline, uint64_t dst_point) { + state->committed |= WLR_OUTPUT_STATE_SIGNAL_TIMELINE; + wlr_drm_syncobj_timeline_unref(state->signal_timeline); + state->signal_timeline = wlr_drm_syncobj_timeline_ref(timeline); + state->signal_point = dst_point; +} + +void wlr_output_state_set_color_transform(struct wlr_output_state *state, + struct wlr_color_transform *tr) { + state->committed |= WLR_OUTPUT_STATE_COLOR_TRANSFORM; + wlr_color_transform_unref(state->color_transform); + if (tr) { + state->color_transform = wlr_color_transform_ref(tr); + } else { + state->color_transform = NULL; + } +} + +bool wlr_output_state_set_image_description(struct wlr_output_state *state, + const struct wlr_output_image_description *image_desc) { + struct wlr_output_image_description *copy = NULL; + if (image_desc != NULL) { + copy = malloc(sizeof(*copy)); + if (copy == NULL) { + return false; + } + *copy = *image_desc; + } + + state->committed |= WLR_OUTPUT_STATE_IMAGE_DESCRIPTION; + free(state->image_description); + state->image_description = copy; + return true; +} + bool wlr_output_state_copy(struct wlr_output_state *dst, const struct wlr_output_state *src) { struct wlr_output_state copy = *src; copy.committed &= ~(WLR_OUTPUT_STATE_BUFFER | WLR_OUTPUT_STATE_DAMAGE | - WLR_OUTPUT_STATE_GAMMA_LUT); + WLR_OUTPUT_STATE_WAIT_TIMELINE | + WLR_OUTPUT_STATE_SIGNAL_TIMELINE | + WLR_OUTPUT_STATE_COLOR_TRANSFORM | + WLR_OUTPUT_STATE_IMAGE_DESCRIPTION); copy.buffer = NULL; + copy.buffer_src_box = (struct wlr_fbox){0}; + copy.buffer_dst_box = (struct wlr_box){0}; pixman_region32_init(©.damage); - copy.gamma_lut = NULL; - copy.gamma_lut_size = 0; + copy.wait_timeline = NULL; + copy.signal_timeline = NULL; + copy.color_transform = NULL; + copy.image_description = NULL; if (src->committed & WLR_OUTPUT_STATE_BUFFER) { wlr_output_state_set_buffer(©, src->buffer); + copy.buffer_src_box = src->buffer_src_box; + copy.buffer_dst_box = src->buffer_dst_box; } if (src->committed & WLR_OUTPUT_STATE_DAMAGE) { wlr_output_state_set_damage(©, &src->damage); } - if (src->committed & WLR_OUTPUT_STATE_GAMMA_LUT) { - const uint16_t *r = src->gamma_lut; - const uint16_t *g = src->gamma_lut + src->gamma_lut_size; - const uint16_t *b = src->gamma_lut + 2 * src->gamma_lut_size; - if (!wlr_output_state_set_gamma_lut(©, src->gamma_lut_size, r, g, b)) { + if (src->committed & WLR_OUTPUT_STATE_WAIT_TIMELINE) { + wlr_output_state_set_wait_timeline(©, src->wait_timeline, + src->wait_point); + } + if (src->committed & WLR_OUTPUT_STATE_SIGNAL_TIMELINE) { + wlr_output_state_set_signal_timeline(©, src->signal_timeline, + src->signal_point); + } + + if (src->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) { + wlr_output_state_set_color_transform(©, src->color_transform); + } + if (src->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) { + if (!wlr_output_state_set_image_description(©, src->image_description)) { goto err; } } @@ -147,6 +191,6 @@ bool wlr_output_state_copy(struct wlr_output_state *dst, return true; err: - wlr_output_state_finish(©); + wlr_output_state_finish(dst); return false; } diff --git a/types/output/swapchain.c b/types/output/swapchain.c index 5a16610d8..01857a204 100644 --- a/types/output/swapchain.c +++ b/types/output/swapchain.c @@ -50,7 +50,7 @@ static struct wlr_swapchain *create_swapchain(struct wlr_output *output, static bool test_swapchain(struct wlr_output *output, struct wlr_swapchain *swapchain, const struct wlr_output_state *state) { - struct wlr_buffer *buffer = wlr_swapchain_acquire(swapchain, NULL); + struct wlr_buffer *buffer = wlr_swapchain_acquire(swapchain); if (buffer == NULL) { return false; } diff --git a/types/scene/surface.c b/types/scene/surface.c index 1905b4dfb..68a445b98 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -1,26 +1,110 @@ #include #include #include +#include #include #include #include +#include +#include #include +#include #include #include "types/wlr_scene.h" +static double get_surface_preferred_buffer_scale(struct wlr_surface *surface) { + double scale = 1; + struct wlr_surface_output *surface_output; + wl_list_for_each(surface_output, &surface->current_outputs, link) { + if (surface_output->output->scale > scale) { + scale = surface_output->output->scale; + } + } + return scale; +} + +static struct wlr_output *get_surface_frame_pacing_output(struct wlr_surface *surface) { + struct wlr_output *frame_pacing_output = NULL; + struct wlr_surface_output *surface_output; + wl_list_for_each(surface_output, &surface->current_outputs, link) { + if (frame_pacing_output == NULL || + surface_output->output->refresh > frame_pacing_output->refresh) { + frame_pacing_output = surface_output->output; + } + } + return frame_pacing_output; +} + +static bool get_tf_preference(enum wlr_color_transfer_function tf) { + switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: + return 0; + case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: + return 1; + case WLR_COLOR_TRANSFER_FUNCTION_BT1886: + case WLR_COLOR_TRANSFER_FUNCTION_SRGB: + case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: + return -1; + } + abort(); // unreachable +} + +static bool get_primaries_preference(enum wlr_color_named_primaries primaries) { + switch (primaries) { + case WLR_COLOR_NAMED_PRIMARIES_SRGB: + return 0; + case WLR_COLOR_NAMED_PRIMARIES_BT2020: + return 1; + } + abort(); // unreachable +} + +static void get_surface_preferred_image_description(struct wlr_surface *surface, + struct wlr_image_description_v1_data *out) { + struct wlr_output_image_description preferred = { + .transfer_function = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22, + .primaries = WLR_COLOR_NAMED_PRIMARIES_SRGB, + }; + + struct wlr_surface_output *surface_output; + wl_list_for_each(surface_output, &surface->current_outputs, link) { + const struct wlr_output_image_description *img_desc = + surface_output->output->image_description; + if (img_desc == NULL) { + continue; + } + if (get_tf_preference(preferred.transfer_function) < get_tf_preference(img_desc->transfer_function)) { + preferred.transfer_function = img_desc->transfer_function; + } + if (get_primaries_preference(preferred.primaries) < get_primaries_preference(img_desc->primaries)) { + preferred.primaries = img_desc->primaries; + } + } + + *out = (struct wlr_image_description_v1_data){ + .tf_named = wlr_color_manager_v1_transfer_function_from_wlr(preferred.transfer_function), + .primaries_named = wlr_color_manager_v1_primaries_from_wlr(preferred.primaries), + }; +} + static void handle_scene_buffer_outputs_update( struct wl_listener *listener, void *data) { struct wlr_scene_surface *surface = wl_container_of(listener, surface, outputs_update); + struct wlr_scene *scene = scene_node_get_root(&surface->buffer->node); - if (surface->buffer->primary_output == NULL) { - return; - } - double scale = surface->buffer->primary_output->output->scale; + surface->frame_pacing_output = get_surface_frame_pacing_output(surface->surface); + + double scale = get_surface_preferred_buffer_scale(surface->surface); wlr_fractional_scale_v1_notify_scale(surface->surface, scale); wlr_surface_set_preferred_buffer_scale(surface->surface, ceil(scale)); - wlr_surface_set_preferred_buffer_transform(surface->surface, - surface->buffer->primary_output->output->transform); + + if (scene->color_manager_v1 != NULL) { + struct wlr_image_description_v1_data img_desc = {0}; + get_surface_preferred_image_description(surface->surface, &img_desc); + wlr_color_manager_v1_set_surface_preferred_image_description(scene->color_manager_v1, + surface->surface, &img_desc); + } } static void handle_scene_buffer_output_enter( @@ -46,15 +130,15 @@ static void handle_scene_buffer_output_sample( struct wlr_scene_surface *surface = wl_container_of(listener, surface, output_sample); const struct wlr_scene_output_sample_event *event = data; - struct wlr_scene_output *scene_output = event->output; - if (surface->buffer->primary_output != scene_output) { + struct wlr_output *output = event->output->output; + if (surface->frame_pacing_output != output) { return; } if (event->direct_scanout) { - wlr_presentation_surface_scanned_out_on_output(surface->surface, scene_output->output); + wlr_presentation_surface_scanned_out_on_output(surface->surface, output); } else { - wlr_presentation_surface_textured_on_output(surface->surface, scene_output->output); + wlr_presentation_surface_textured_on_output(surface->surface, output); } } @@ -62,9 +146,19 @@ static void handle_scene_buffer_frame_done( struct wl_listener *listener, void *data) { struct wlr_scene_surface *surface = wl_container_of(listener, surface, frame_done); - struct timespec *now = data; + struct wlr_scene_frame_done_event *event = data; + if (surface->frame_pacing_output != event->output->output) { + return; + } - wlr_surface_send_frame_done(surface->surface, now); + wlr_surface_send_frame_done(surface->surface, &event->when); +} + +void wlr_scene_surface_send_frame_done(struct wlr_scene_surface *scene_surface, + const struct timespec *when) { + if (!pixman_region32_empty(&scene_surface->buffer->node.visible)) { + wlr_surface_send_frame_done(scene_surface->surface, when); + } } static void scene_surface_handle_surface_destroy( @@ -93,8 +187,11 @@ static void scene_buffer_unmark_client_buffer(struct wlr_scene_buffer *scene_buf return; } - assert(buffer->n_ignore_locks > 0); - buffer->n_ignore_locks--; + // If the buffer was a single-pixel buffer where we cached its color + // then it won't have been marked as damage-allowed. + if (buffer->n_ignore_locks > 0) { + buffer->n_ignore_locks--; + } } static int min(int a, int b) { @@ -128,8 +225,8 @@ static void surface_reconfigure(struct wlr_scene_surface *scene_surface) { buffer_width, buffer_height); wlr_output_transform_coords(state->transform, &buffer_width, &buffer_height); - src_box.x += (double)(clip->x * buffer_width) / state->width; - src_box.y += (double)(clip->y * buffer_height) / state->height; + src_box.x += (double)(clip->x * src_box.width) / state->width; + src_box.y += (double)(clip->y * src_box.height) / state->height; src_box.width *= (double)width / state->width; src_box.height *= (double)height / state->height; @@ -153,19 +250,66 @@ static void surface_reconfigure(struct wlr_scene_surface *scene_surface) { opacity = (float)alpha_modifier_state->multiplier; } + enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; + enum wlr_color_named_primaries primaries = WLR_COLOR_NAMED_PRIMARIES_SRGB; + const struct wlr_image_description_v1_data *img_desc = + wlr_surface_get_image_description_v1_data(surface); + if (img_desc != NULL) { + tf = wlr_color_manager_v1_transfer_function_to_wlr(img_desc->tf_named); + primaries = wlr_color_manager_v1_primaries_to_wlr(img_desc->primaries_named); + } + wlr_scene_buffer_set_opaque_region(scene_buffer, &opaque); wlr_scene_buffer_set_source_box(scene_buffer, &src_box); wlr_scene_buffer_set_dest_size(scene_buffer, width, height); wlr_scene_buffer_set_transform(scene_buffer, state->transform); wlr_scene_buffer_set_opacity(scene_buffer, opacity); + wlr_scene_buffer_set_transfer_function(scene_buffer, tf); + wlr_scene_buffer_set_primaries(scene_buffer, primaries); scene_buffer_unmark_client_buffer(scene_buffer); if (surface->buffer) { - client_buffer_mark_next_can_damage(surface->buffer); + // If we've cached the buffer's single-pixel buffer color + // then any in-place updates to the texture wouldn't be + // reflected in rendering. So only allow in-place texture + // updates if it's not a single pixel buffer. Note that we + // can't use the cached scene_buffer->is_single_pixel_buffer + // because that's only set later on. + bool is_single_pixel_buffer = false; + if (surface->buffer->source != NULL) { + struct wlr_single_pixel_buffer_v1 *spb = + wlr_single_pixel_buffer_v1_try_from_buffer(surface->buffer->source); + is_single_pixel_buffer = spb != NULL; + } + if (!is_single_pixel_buffer) { + client_buffer_mark_next_can_damage(surface->buffer); + } - wlr_scene_buffer_set_buffer_with_damage(scene_buffer, - &surface->buffer->base, &surface->buffer_damage); + struct wlr_linux_drm_syncobj_surface_v1_state *syncobj_surface_state = + wlr_linux_drm_syncobj_v1_get_surface_state(surface); + + struct wlr_drm_syncobj_timeline *wait_timeline = NULL; + uint64_t wait_point = 0; + if (syncobj_surface_state != NULL) { + wait_timeline = syncobj_surface_state->acquire_timeline; + wait_point = syncobj_surface_state->acquire_point; + } + + struct wlr_scene_buffer_set_buffer_options options = { + .damage = &surface->buffer_damage, + .wait_timeline = wait_timeline, + .wait_point = wait_point, + }; + wlr_scene_buffer_set_buffer_with_options(scene_buffer, + &surface->buffer->base, &options); + + if (syncobj_surface_state != NULL && + (surface->current.committed & WLR_SURFACE_STATE_BUFFER) && + surface->buffer->source != NULL) { + wlr_linux_drm_syncobj_v1_state_signal_release_with_buffer(syncobj_surface_state, + surface->buffer->source); + } } else { wlr_scene_buffer_set_buffer(scene_buffer, NULL); } diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 46768aabe..c5cbcd29f 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -3,23 +3,33 @@ #include #include #include +#include #include +#include #include #include +#include #include #include #include #include #include #include -#include "types/wlr_buffer.h" +#include "render/color.h" #include "types/wlr_output.h" #include "types/wlr_scene.h" #include "util/array.h" #include "util/env.h" #include "util/time.h" -#define HIGHLIGHT_DAMAGE_FADEOUT_TIME 250 +#include + +#if WLR_HAS_XWAYLAND +#include +#endif + +#define DMABUF_FEEDBACK_DEBOUNCE_FRAMES 30 +#define HIGHLIGHT_DAMAGE_FADEOUT_TIME 250 struct wlr_scene_tree *wlr_scene_tree_from_node(struct wlr_scene_node *node) { assert(node->type == WLR_SCENE_NODE_TREE); @@ -117,6 +127,13 @@ void wlr_scene_node_destroy(struct wlr_scene_node *node) { scene_buffer_set_buffer(scene_buffer, NULL); scene_buffer_set_texture(scene_buffer, NULL); pixman_region32_fini(&scene_buffer->opaque_region); + wlr_drm_syncobj_timeline_unref(scene_buffer->wait_timeline); + + assert(wl_list_empty(&scene_buffer->events.output_leave.listener_list)); + assert(wl_list_empty(&scene_buffer->events.output_enter.listener_list)); + assert(wl_list_empty(&scene_buffer->events.outputs_update.listener_list)); + assert(wl_list_empty(&scene_buffer->events.output_sample.listener_list)); + assert(wl_list_empty(&scene_buffer->events.frame_done.listener_list)); } else if (node->type == WLR_SCENE_NODE_TREE) { struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); @@ -128,6 +145,8 @@ void wlr_scene_node_destroy(struct wlr_scene_node *node) { } wl_list_remove(&scene->linux_dmabuf_v1_destroy.link); + wl_list_remove(&scene->gamma_control_manager_v1_destroy.link); + wl_list_remove(&scene->gamma_control_manager_v1_set_gamma.link); } else { assert(node->parent); } @@ -139,6 +158,8 @@ void wlr_scene_node_destroy(struct wlr_scene_node *node) { } } + assert(wl_list_empty(&node->events.destroy.listener_list)); + wl_list_remove(&node->link); pixman_region32_fini(&node->visible); free(node); @@ -161,6 +182,8 @@ struct wlr_scene *wlr_scene_create(void) { wl_list_init(&scene->outputs); wl_list_init(&scene->linux_dmabuf_v1_destroy.link); + wl_list_init(&scene->gamma_control_manager_v1_destroy.link); + wl_list_init(&scene->gamma_control_manager_v1_set_gamma.link); const char *debug_damage_options[] = { "none", @@ -189,8 +212,6 @@ struct wlr_scene_tree *wlr_scene_tree_create(struct wlr_scene_tree *parent) { return tree; } -static void scene_node_get_size(struct wlr_scene_node *node, int *lx, int *ly); - typedef bool (*scene_node_box_iterator_func_t)(struct wlr_scene_node *node, int sx, int sy, void *data); @@ -269,8 +290,13 @@ static void scene_node_opaque_region(struct wlr_scene_node *node, int x, int y, struct scene_update_data { pixman_region32_t *visible; pixman_region32_t *update_region; + struct wlr_box update_box; struct wl_list *outputs; bool calculate_visibility; + +#if WLR_HAS_XWAYLAND + struct wlr_xwayland_surface *restack_above; +#endif }; static uint32_t region_area(pixman_region32_t *region) { @@ -285,11 +311,11 @@ static uint32_t region_area(pixman_region32_t *region) { return area; } -static void scale_output_damage(pixman_region32_t *damage, float scale) { - wlr_region_scale(damage, damage, scale); +static void scale_region(pixman_region32_t *region, float scale, bool round_up) { + wlr_region_scale(region, region, scale); - if (floor(scale) != scale) { - wlr_region_expand(damage, damage, 1); + if (round_up && floor(scale) != scale) { + wlr_region_expand(region, region, 1); } } @@ -305,18 +331,68 @@ struct render_data { pixman_region32_t damage; }; -static void transform_output_damage(pixman_region32_t *damage, const struct render_data *data) { +static void logical_to_buffer_coords(pixman_region32_t *region, const struct render_data *data, + bool round_up) { enum wl_output_transform transform = wlr_output_transform_invert(data->transform); - wlr_region_transform(damage, damage, transform, data->trans_width, data->trans_height); + scale_region(region, data->scale, round_up); + wlr_region_transform(region, region, transform, data->trans_width, data->trans_height); +} + +static void output_to_buffer_coords(pixman_region32_t *damage, struct wlr_output *output) { + int width, height; + wlr_output_transformed_resolution(output, &width, &height); + + wlr_region_transform(damage, damage, + wlr_output_transform_invert(output->transform), width, height); +} + +static int scale_length(int length, int offset, float scale) { + return round((offset + length) * scale) - round(offset * scale); +} + +static void scale_box(struct wlr_box *box, float scale) { + box->width = scale_length(box->width, box->x, scale); + box->height = scale_length(box->height, box->y, scale); + box->x = round(box->x * scale); + box->y = round(box->y * scale); } static void transform_output_box(struct wlr_box *box, const struct render_data *data) { enum wl_output_transform transform = wlr_output_transform_invert(data->transform); + scale_box(box, data->scale); wlr_box_transform(box, box, transform, data->trans_width, data->trans_height); } +static void scene_output_damage(struct wlr_scene_output *scene_output, + const pixman_region32_t *damage) { + struct wlr_output *output = scene_output->output; + + pixman_region32_t clipped; + pixman_region32_init(&clipped); + pixman_region32_intersect_rect(&clipped, damage, 0, 0, output->width, output->height); + + if (!pixman_region32_empty(&clipped)) { + wlr_output_schedule_frame(scene_output->output); + wlr_damage_ring_add(&scene_output->damage_ring, &clipped); + + pixman_region32_union(&scene_output->pending_commit_damage, + &scene_output->pending_commit_damage, &clipped); + } + + pixman_region32_fini(&clipped); +} + +static void scene_output_damage_whole(struct wlr_scene_output *scene_output) { + struct wlr_output *output = scene_output->output; + + pixman_region32_t damage; + pixman_region32_init_rect(&damage, 0, 0, output->width, output->height); + scene_output_damage(scene_output, &damage); + pixman_region32_fini(&damage); +} + static void scene_damage_outputs(struct wlr_scene *scene, pixman_region32_t *damage) { - if (!pixman_region32_not_empty(damage)) { + if (pixman_region32_empty(damage)) { return; } @@ -327,10 +403,9 @@ static void scene_damage_outputs(struct wlr_scene *scene, pixman_region32_t *dam pixman_region32_copy(&output_damage, damage); pixman_region32_translate(&output_damage, -scene_output->x, -scene_output->y); - scale_output_damage(&output_damage, scene_output->output->scale); - if (wlr_damage_ring_add(&scene_output->damage_ring, &output_damage)) { - wlr_output_schedule_frame(scene_output->output); - } + scale_region(&output_damage, scene_output->output->scale, true); + output_to_buffer_coords(&output_damage, scene_output->output); + scene_output_damage(scene_output, &output_damage); pixman_region32_fini(&output_damage); } } @@ -351,6 +426,8 @@ static void update_node_update_outputs(struct wlr_scene_node *node, size_t count = 0; uint64_t active_outputs = 0; + uint32_t visible_area = region_area(&node->visible); + // let's update the outputs in two steps: // - the primary outputs // - the enter/leave signals @@ -378,9 +455,12 @@ static void update_node_update_outputs(struct wlr_scene_node *node, pixman_region32_init(&intersection); pixman_region32_intersect_rect(&intersection, &node->visible, output_box.x, output_box.y, output_box.width, output_box.height); + uint32_t overlap = region_area(&intersection); + pixman_region32_fini(&intersection); - if (pixman_region32_not_empty(&intersection)) { - uint32_t overlap = region_area(&intersection); + // If the overlap accounts for less than 10% of the visible node area, + // ignore this output + if (overlap >= 0.1 * visible_area) { if (overlap >= largest_overlap) { largest_overlap = overlap; scene_buffer->primary_output = scene_output; @@ -389,8 +469,6 @@ static void update_node_update_outputs(struct wlr_scene_node *node, active_outputs |= 1ull << scene_output->index; count++; } - - pixman_region32_fini(&intersection); } if (old_primary_output != scene_buffer->primary_output) { @@ -443,6 +521,69 @@ static void update_node_update_outputs(struct wlr_scene_node *node, wl_signal_emit_mutable(&scene_buffer->events.outputs_update, &event); } +#if WLR_HAS_XWAYLAND +static struct wlr_xwayland_surface *scene_node_try_get_managed_xwayland_surface( + struct wlr_scene_node *node) { + if (node->type != WLR_SCENE_NODE_BUFFER) { + return NULL; + } + + struct wlr_scene_buffer *buffer_node = wlr_scene_buffer_from_node(node); + struct wlr_scene_surface *surface_node = wlr_scene_surface_try_from_buffer(buffer_node); + if (!surface_node) { + return NULL; + } + + struct wlr_xwayland_surface *xwayland_surface = + wlr_xwayland_surface_try_from_wlr_surface(surface_node->surface); + if (!xwayland_surface || xwayland_surface->override_redirect) { + return NULL; + } + + return xwayland_surface; +} + +static void restack_xwayland_surface(struct wlr_scene_node *node, + struct wlr_box *box, struct scene_update_data *data) { + struct wlr_xwayland_surface *xwayland_surface = + scene_node_try_get_managed_xwayland_surface(node); + if (!xwayland_surface) { + return; + } + + // ensure this node is entirely inside the update region. If not, we can't + // restack this node since we're not considering the whole thing. + if (wlr_box_contains_box(&data->update_box, box)) { + if (data->restack_above) { + wlr_xwayland_surface_restack(xwayland_surface, data->restack_above, XCB_STACK_MODE_BELOW); + } else { + wlr_xwayland_surface_restack(xwayland_surface, NULL, XCB_STACK_MODE_ABOVE); + } + } + + data->restack_above = xwayland_surface; +} + +static void restack_xwayland_surface_below(struct wlr_scene_node *node) { + if (node->type == WLR_SCENE_NODE_TREE) { + struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); + struct wlr_scene_node *child; + wl_list_for_each(child, &scene_tree->children, link) { + restack_xwayland_surface_below(child); + } + return; + } + + struct wlr_xwayland_surface *xwayland_surface = + scene_node_try_get_managed_xwayland_surface(node); + if (!xwayland_surface) { + return; + } + + wlr_xwayland_surface_restack(xwayland_surface, NULL, XCB_STACK_MODE_BELOW); +} +#endif + static bool scene_node_update_iterator(struct wlr_scene_node *node, int lx, int ly, void *_data) { struct scene_update_data *data = _data; @@ -464,6 +605,9 @@ static bool scene_node_update_iterator(struct wlr_scene_node *node, } update_node_update_outputs(node, data->outputs, NULL, NULL); +#if WLR_HAS_XWAYLAND + restack_xwayland_surface(node, &box, data); +#endif return false; } @@ -512,23 +656,22 @@ static void scene_update_region(struct wlr_scene *scene, pixman_region32_init(&visible); pixman_region32_copy(&visible, update_region); + struct pixman_box32 *region_box = pixman_region32_extents(update_region); struct scene_update_data data = { .visible = &visible, .update_region = update_region, + .update_box = { + .x = region_box->x1, + .y = region_box->y1, + .width = region_box->x2 - region_box->x1, + .height = region_box->y2 - region_box->y1, + }, .outputs = &scene->outputs, .calculate_visibility = scene->calculate_visibility, }; - struct pixman_box32 *region_box = pixman_region32_extents(update_region); - struct wlr_box box = { - .x = region_box->x1, - .y = region_box->y1, - .width = region_box->x2 - region_box->x1, - .height = region_box->y2 - region_box->y1, - }; - // update node visibility and output enter/leave events - scene_nodes_in_box(&scene->tree.node, &box, scene_node_update_iterator, &data); + scene_nodes_in_box(&scene->tree.node, &data.update_box, scene_node_update_iterator, &data); pixman_region32_fini(&visible); } @@ -539,6 +682,9 @@ static void scene_node_update(struct wlr_scene_node *node, int x, y; if (!wlr_scene_node_coords(node, &x, &y)) { +#if WLR_HAS_XWAYLAND + restack_xwayland_surface_below(node); +#endif if (damage) { scene_update_region(scene, damage); scene_damage_outputs(scene, damage); @@ -570,11 +716,13 @@ static void scene_node_update(struct wlr_scene_node *node, struct wlr_scene_rect *wlr_scene_rect_create(struct wlr_scene_tree *parent, int width, int height, const float color[static 4]) { + assert(parent); + assert(width >= 0 && height >= 0); + struct wlr_scene_rect *scene_rect = calloc(1, sizeof(*scene_rect)); if (scene_rect == NULL) { return NULL; } - assert(parent); scene_node_init(&scene_rect->node, WLR_SCENE_NODE_RECT, parent); scene_rect->width = width; @@ -591,6 +739,8 @@ void wlr_scene_rect_set_size(struct wlr_scene_rect *rect, int width, int height) return; } + assert(width >= 0 && height >= 0); + rect->width = width; rect->height = height; scene_node_update(&rect->node, NULL); @@ -635,7 +785,7 @@ static void scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer, scene_buffer->buffer = wlr_buffer_lock(buffer); scene_buffer->buffer_width = buffer->width; scene_buffer->buffer_height = buffer->height; - scene_buffer->buffer_is_opaque = buffer_is_opaque(buffer); + scene_buffer->buffer_is_opaque = wlr_buffer_is_opaque(buffer); scene_buffer->buffer_release.notify = scene_buffer_handle_buffer_release; wl_signal_add(&buffer->events.release, &scene_buffer->buffer_release); @@ -661,6 +811,18 @@ static void scene_buffer_set_texture(struct wlr_scene_buffer *scene_buffer, } } +static void scene_buffer_set_wait_timeline(struct wlr_scene_buffer *scene_buffer, + struct wlr_drm_syncobj_timeline *timeline, uint64_t point) { + wlr_drm_syncobj_timeline_unref(scene_buffer->wait_timeline); + if (timeline != NULL) { + scene_buffer->wait_timeline = wlr_drm_syncobj_timeline_ref(timeline); + scene_buffer->wait_point = point; + } else { + scene_buffer->wait_timeline = NULL; + scene_buffer->wait_point = 0; + } +} + struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent, struct wlr_buffer *buffer) { struct wlr_scene_buffer *scene_buffer = calloc(1, sizeof(*scene_buffer)); @@ -675,6 +837,7 @@ struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent, wl_signal_init(&scene_buffer->events.output_leave); wl_signal_init(&scene_buffer->events.output_sample); wl_signal_init(&scene_buffer->events.frame_done); + pixman_region32_init(&scene_buffer->opaque_region); wl_list_init(&scene_buffer->buffer_release.link); wl_list_init(&scene_buffer->renderer_destroy.link); @@ -686,12 +849,17 @@ struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent, return scene_buffer; } -void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buffer, - struct wlr_buffer *buffer, const pixman_region32_t *damage) { +void wlr_scene_buffer_set_buffer_with_options(struct wlr_scene_buffer *scene_buffer, + struct wlr_buffer *buffer, const struct wlr_scene_buffer_set_buffer_options *options) { + const struct wlr_scene_buffer_set_buffer_options default_options = {0}; + if (options == NULL) { + options = &default_options; + } + // specifying a region for a NULL buffer doesn't make sense. We need to know // about the buffer to scale the buffer local coordinates down to scene // coordinates. - assert(buffer || !damage); + assert(buffer || !options->damage); bool mapped = buffer != NULL; bool prev_mapped = scene_buffer->buffer != NULL || scene_buffer->texture != NULL; @@ -710,8 +878,32 @@ void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buff scene_buffer->buffer_height != buffer->height; } + // If this is a buffer change, check if it's a single pixel buffer. + // Cache that so we can still apply rendering optimisations even when + // the original buffer has been freed after texture upload. + if (buffer != scene_buffer->buffer) { + scene_buffer->is_single_pixel_buffer = false; + struct wlr_client_buffer *client_buffer = NULL; + if (buffer != NULL) { + client_buffer = wlr_client_buffer_get(buffer); + } + if (client_buffer != NULL && client_buffer->source != NULL) { + struct wlr_single_pixel_buffer_v1 *single_pixel_buffer = + wlr_single_pixel_buffer_v1_try_from_buffer(client_buffer->source); + if (single_pixel_buffer != NULL) { + scene_buffer->is_single_pixel_buffer = true; + scene_buffer->single_pixel_buffer_color[0] = single_pixel_buffer->r; + scene_buffer->single_pixel_buffer_color[1] = single_pixel_buffer->g; + scene_buffer->single_pixel_buffer_color[2] = single_pixel_buffer->b; + scene_buffer->single_pixel_buffer_color[3] = single_pixel_buffer->a; + } + } + } + scene_buffer_set_buffer(scene_buffer, buffer); scene_buffer_set_texture(scene_buffer, NULL); + scene_buffer_set_wait_timeline(scene_buffer, + options->wait_timeline, options->wait_point); if (update) { scene_node_update(&scene_buffer->node, NULL); @@ -727,6 +919,7 @@ void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buff pixman_region32_t fallback_damage; pixman_region32_init_rect(&fallback_damage, 0, 0, buffer->width, buffer->height); + const pixman_region32_t *damage = options->damage; if (!damage) { damage = &fallback_damage; } @@ -792,7 +985,7 @@ void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buff pixman_region32_t cull_region; pixman_region32_init(&cull_region); pixman_region32_copy(&cull_region, &scene_buffer->node.visible); - scale_output_damage(&cull_region, output_scale); + scale_region(&cull_region, output_scale, true); pixman_region32_translate(&cull_region, -lx * output_scale, -ly * output_scale); pixman_region32_intersect(&output_damage, &output_damage, &cull_region); pixman_region32_fini(&cull_region); @@ -800,9 +993,8 @@ void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buff pixman_region32_translate(&output_damage, (int)round((lx - scene_output->x) * output_scale), (int)round((ly - scene_output->y) * output_scale)); - if (wlr_damage_ring_add(&scene_output->damage_ring, &output_damage)) { - wlr_output_schedule_frame(scene_output->output); - } + output_to_buffer_coords(&output_damage, scene_output->output); + scene_output_damage(scene_output, &output_damage); pixman_region32_fini(&output_damage); } @@ -810,9 +1002,17 @@ void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buff pixman_region32_fini(&fallback_damage); } +void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buffer, + struct wlr_buffer *buffer, const pixman_region32_t *damage) { + const struct wlr_scene_buffer_set_buffer_options options = { + .damage = damage, + }; + wlr_scene_buffer_set_buffer_with_options(scene_buffer, buffer, &options); +} + void wlr_scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer, struct wlr_buffer *buffer) { - wlr_scene_buffer_set_buffer_with_damage(scene_buffer, buffer, NULL); + wlr_scene_buffer_set_buffer_with_options(scene_buffer, buffer, NULL); } void wlr_scene_buffer_set_opaque_region(struct wlr_scene_buffer *scene_buffer, @@ -842,6 +1042,7 @@ void wlr_scene_buffer_set_source_box(struct wlr_scene_buffer *scene_buffer, } if (box != NULL) { + assert(box->x >= 0 && box->y >= 0 && box->width >= 0 && box->height >= 0); scene_buffer->src_box = *box; } else { scene_buffer->src_box = (struct wlr_fbox){0}; @@ -856,6 +1057,7 @@ void wlr_scene_buffer_set_dest_size(struct wlr_scene_buffer *scene_buffer, return; } + assert(width >= 0 && height >= 0); scene_buffer->dst_width = width; scene_buffer->dst_height = height; scene_node_update(&scene_buffer->node, NULL); @@ -872,9 +1074,9 @@ void wlr_scene_buffer_set_transform(struct wlr_scene_buffer *scene_buffer, } void wlr_scene_buffer_send_frame_done(struct wlr_scene_buffer *scene_buffer, - struct timespec *now) { - if (pixman_region32_not_empty(&scene_buffer->node.visible)) { - wl_signal_emit_mutable(&scene_buffer->events.frame_done, now); + struct wlr_scene_frame_done_event *event) { + if (!pixman_region32_empty(&scene_buffer->node.visible)) { + wl_signal_emit_mutable(&scene_buffer->events.frame_done, event); } } @@ -884,6 +1086,7 @@ void wlr_scene_buffer_set_opacity(struct wlr_scene_buffer *scene_buffer, return; } + assert(opacity >= 0 && opacity <= 1); scene_buffer->opacity = opacity; scene_node_update(&scene_buffer->node, NULL); } @@ -898,6 +1101,26 @@ void wlr_scene_buffer_set_filter_mode(struct wlr_scene_buffer *scene_buffer, scene_node_update(&scene_buffer->node, NULL); } +void wlr_scene_buffer_set_transfer_function(struct wlr_scene_buffer *scene_buffer, + enum wlr_color_transfer_function transfer_function) { + if (scene_buffer->transfer_function == transfer_function) { + return; + } + + scene_buffer->transfer_function = transfer_function; + scene_node_update(&scene_buffer->node, NULL); +} + +void wlr_scene_buffer_set_primaries(struct wlr_scene_buffer *scene_buffer, + enum wlr_color_named_primaries primaries) { + if (scene_buffer->primaries == primaries) { + return; + } + + scene_buffer->primaries = primaries; + scene_node_update(&scene_buffer->node, NULL); +} + static struct wlr_texture *scene_buffer_get_texture( struct wlr_scene_buffer *scene_buffer, struct wlr_renderer *renderer) { if (scene_buffer->buffer == NULL || scene_buffer->texture != NULL) { @@ -920,8 +1143,7 @@ static struct wlr_texture *scene_buffer_get_texture( return texture; } -static void scene_node_get_size(struct wlr_scene_node *node, - int *width, int *height) { +void scene_node_get_size(struct wlr_scene_node *node, int *width, int *height) { *width = 0; *height = 0; @@ -947,17 +1169,6 @@ static void scene_node_get_size(struct wlr_scene_node *node, } } -static int scale_length(int length, int offset, float scale) { - return round((offset + length) * scale) - round(offset * scale); -} - -static void scale_box(struct wlr_box *box, float scale) { - box->width = scale_length(box->width, box->x, scale); - box->height = scale_length(box->height, box->y, scale); - box->x = round(box->x * scale); - box->y = round(box->y * scale); -} - void wlr_scene_node_set_enabled(struct wlr_scene_node *node, bool enabled) { if (node->enabled == enabled) { return; @@ -1164,7 +1375,6 @@ struct wlr_scene_node *wlr_scene_node_at(struct wlr_scene_node *node, struct render_list_entry { struct wlr_scene_node *node; - bool sent_dmabuf_feedback; bool highlight_transparent_region; int x, y; }; @@ -1176,9 +1386,9 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren pixman_region32_init(&render_region); pixman_region32_copy(&render_region, &node->visible); pixman_region32_translate(&render_region, -data->logical.x, -data->logical.y); - scale_output_damage(&render_region, data->scale); + logical_to_buffer_coords(&render_region, data, true); pixman_region32_intersect(&render_region, &render_region, &data->damage); - if (!pixman_region32_not_empty(&render_region)) { + if (pixman_region32_empty(&render_region)) { pixman_region32_fini(&render_region); return; } @@ -1191,17 +1401,14 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren .y = y, }; scene_node_get_size(node, &dst_box.width, &dst_box.height); - scale_box(&dst_box, data->scale); + transform_output_box(&dst_box, data); pixman_region32_t opaque; pixman_region32_init(&opaque); scene_node_opaque_region(node, x, y, &opaque); - scale_output_damage(&opaque, data->scale); + logical_to_buffer_coords(&opaque, data, false); pixman_region32_subtract(&opaque, &render_region, &opaque); - transform_output_box(&dst_box, data); - transform_output_damage(&render_region, data); - switch (node->type) { case WLR_SCENE_NODE_TREE: assert(false); @@ -1223,10 +1430,26 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren case WLR_SCENE_NODE_BUFFER:; struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); + if (scene_buffer->is_single_pixel_buffer) { + // Render the buffer as a rect, this is likely to be more efficient + wlr_render_pass_add_rect(data->render_pass, &(struct wlr_render_rect_options){ + .box = dst_box, + .color = { + .r = (float)scene_buffer->single_pixel_buffer_color[0] / (float)UINT32_MAX, + .g = (float)scene_buffer->single_pixel_buffer_color[1] / (float)UINT32_MAX, + .b = (float)scene_buffer->single_pixel_buffer_color[2] / (float)UINT32_MAX, + .a = (float)scene_buffer->single_pixel_buffer_color[3] / + (float)UINT32_MAX * scene_buffer->opacity, + }, + .clip = &render_region, + }); + break; + } + struct wlr_texture *texture = scene_buffer_get_texture(scene_buffer, data->output->output->renderer); if (texture == NULL) { - wlr_damage_ring_add(&data->output->damage_ring, &render_region); + scene_output_damage(data->output, &render_region); break; } @@ -1234,6 +1457,11 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren wlr_output_transform_invert(scene_buffer->transform); transform = wlr_output_transform_compose(transform, data->transform); + struct wlr_color_primaries primaries = {0}; + if (scene_buffer->primaries != 0) { + wlr_color_primaries_from_named(&primaries, scene_buffer->primaries); + } + wlr_render_pass_add_texture(data->render_pass, &(struct wlr_render_texture_options) { .texture = texture, .src_box = scene_buffer->src_box, @@ -1242,8 +1470,13 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren .clip = &render_region, .alpha = &scene_buffer->opacity, .filter_mode = scene_buffer->filter_mode, - .blend_mode = pixman_region32_not_empty(&opaque) ? + .blend_mode = !data->output->scene->calculate_visibility || + !pixman_region32_empty(&opaque) ? WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE, + .transfer_function = scene_buffer->transfer_function, + .primaries = scene_buffer->primaries != 0 ? &primaries : NULL, + .wait_timeline = scene_buffer->wait_timeline, + .wait_point = scene_buffer->wait_point, }); struct wlr_scene_output_sample_event sample_event = { @@ -1284,6 +1517,71 @@ void wlr_scene_set_linux_dmabuf_v1(struct wlr_scene *scene, wl_signal_add(&linux_dmabuf_v1->events.destroy, &scene->linux_dmabuf_v1_destroy); } +static void scene_handle_gamma_control_manager_v1_set_gamma(struct wl_listener *listener, + void *data) { + const struct wlr_gamma_control_manager_v1_set_gamma_event *event = data; + struct wlr_scene *scene = + wl_container_of(listener, scene, gamma_control_manager_v1_set_gamma); + struct wlr_scene_output *output = wlr_scene_get_scene_output(scene, event->output); + if (!output) { + // this scene might not own this output. + return; + } + + output->gamma_lut_changed = true; + output->gamma_lut = event->control; + wlr_color_transform_unref(output->gamma_lut_color_transform); + output->gamma_lut_color_transform = wlr_gamma_control_v1_get_color_transform(event->control); + wlr_output_schedule_frame(output->output); +} + +static void scene_handle_gamma_control_manager_v1_destroy(struct wl_listener *listener, + void *data) { + struct wlr_scene *scene = + wl_container_of(listener, scene, gamma_control_manager_v1_destroy); + wl_list_remove(&scene->gamma_control_manager_v1_destroy.link); + wl_list_init(&scene->gamma_control_manager_v1_destroy.link); + wl_list_remove(&scene->gamma_control_manager_v1_set_gamma.link); + wl_list_init(&scene->gamma_control_manager_v1_set_gamma.link); + scene->gamma_control_manager_v1 = NULL; + + struct wlr_scene_output *output; + wl_list_for_each(output, &scene->outputs, link) { + output->gamma_lut_changed = false; + output->gamma_lut = NULL; + wlr_color_transform_unref(output->gamma_lut_color_transform); + output->gamma_lut_color_transform = NULL; + } +} + +void wlr_scene_set_gamma_control_manager_v1(struct wlr_scene *scene, + struct wlr_gamma_control_manager_v1 *gamma_control) { + assert(scene->gamma_control_manager_v1 == NULL); + scene->gamma_control_manager_v1 = gamma_control; + + scene->gamma_control_manager_v1_destroy.notify = + scene_handle_gamma_control_manager_v1_destroy; + wl_signal_add(&gamma_control->events.destroy, &scene->gamma_control_manager_v1_destroy); + scene->gamma_control_manager_v1_set_gamma.notify = + scene_handle_gamma_control_manager_v1_set_gamma; + wl_signal_add(&gamma_control->events.set_gamma, &scene->gamma_control_manager_v1_set_gamma); +} + +static void scene_handle_color_manager_v1_destroy(struct wl_listener *listener, void *data) { + struct wlr_scene *scene = wl_container_of(listener, scene, color_manager_v1_destroy); + wl_list_remove(&scene->color_manager_v1_destroy.link); + wl_list_init(&scene->color_manager_v1_destroy.link); + scene->color_manager_v1 = NULL; +} + +void wlr_scene_set_color_manager_v1(struct wlr_scene *scene, struct wlr_color_manager_v1 *manager) { + assert(scene->color_manager_v1 == NULL); + scene->color_manager_v1 = manager; + + scene->color_manager_v1_destroy.notify = scene_handle_color_manager_v1_destroy; + wl_signal_add(&manager->events.destroy, &scene->color_manager_v1_destroy); +} + static void scene_output_handle_destroy(struct wlr_addon *addon) { struct wlr_scene_output *scene_output = wl_container_of(addon, scene_output, addon); @@ -1312,8 +1610,7 @@ static void scene_node_output_update(struct wlr_scene_node *node, static void scene_output_update_geometry(struct wlr_scene_output *scene_output, bool force_update) { - wlr_damage_ring_add_whole(&scene_output->damage_ring); - wlr_output_schedule_frame(scene_output->output); + scene_output_damage_whole(scene_output); scene_node_output_update(&scene_output->scene->tree.node, &scene_output->scene->outputs, NULL, force_update ? scene_output : NULL); @@ -1325,6 +1622,19 @@ static void scene_output_handle_commit(struct wl_listener *listener, void *data) struct wlr_output_event_commit *event = data; const struct wlr_output_state *state = event->state; + // if the output has been committed with a certain damage, we know that region + // will be acknowledged by the backend so we don't need to keep track of it + // anymore + if (state->committed & WLR_OUTPUT_STATE_BUFFER) { + if (state->committed & WLR_OUTPUT_STATE_DAMAGE) { + pixman_region32_subtract(&scene_output->pending_commit_damage, + &scene_output->pending_commit_damage, &state->damage); + } else { + pixman_region32_fini(&scene_output->pending_commit_damage); + pixman_region32_init(&scene_output->pending_commit_damage); + } + } + bool force_update = state->committed & ( WLR_OUTPUT_STATE_TRANSFORM | WLR_OUTPUT_STATE_SCALE | @@ -1335,41 +1645,35 @@ static void scene_output_handle_commit(struct wl_listener *listener, void *data) scene_output_update_geometry(scene_output, force_update); } - // if the output has been committed with a certain damage, we know that region - // will be acknowledged by the backend so we don't need to keep track of it - // anymore - if (state->committed & WLR_OUTPUT_STATE_DAMAGE) { - bool tracking_buffer = false; - struct wlr_damage_ring_buffer *buffer; - wl_list_for_each(buffer, &scene_output->damage_ring.buffers, link) { - if (buffer->buffer == state->buffer) { - tracking_buffer = true; - break; - } - } - - if (tracking_buffer) { - pixman_region32_subtract(&scene_output->pending_commit_damage, - &scene_output->pending_commit_damage, &state->damage); - } else { - pixman_region32_union(&scene_output->pending_commit_damage, - &scene_output->pending_commit_damage, &state->damage); - } - } - if (scene_output->scene->debug_damage_option == WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT && !wl_list_empty(&scene_output->damage_highlight_regions)) { wlr_output_schedule_frame(scene_output->output); } + + // Next time the output is enabled, try to re-apply the gamma LUT + if (scene_output->scene->gamma_control_manager_v1 && + (state->committed & WLR_OUTPUT_STATE_ENABLED) && + !scene_output->output->enabled) { + scene_output->gamma_lut_changed = true; + } } static void scene_output_handle_damage(struct wl_listener *listener, void *data) { struct wlr_scene_output *scene_output = wl_container_of(listener, scene_output, output_damage); + struct wlr_output *output = scene_output->output; struct wlr_output_event_damage *event = data; - if (wlr_damage_ring_add(&scene_output->damage_ring, event->damage)) { - wlr_output_schedule_frame(scene_output->output); - } + + int width, height; + wlr_output_transformed_resolution(output, &width, &height); + + pixman_region32_t damage; + pixman_region32_init(&damage); + pixman_region32_copy(&damage, event->damage); + wlr_region_transform(&damage, &damage, + wlr_output_transform_invert(output->transform), width, height); + scene_output_damage(scene_output, &damage); + pixman_region32_fini(&damage); } static void scene_output_handle_needs_frame(struct wl_listener *listener, void *data) { @@ -1406,6 +1710,15 @@ struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene, prev_output_link = ¤t_output->link; } + int drm_fd = wlr_backend_get_drm_fd(output->backend); + if (drm_fd >= 0 && output->backend->features.timeline && + output->renderer != NULL && output->renderer->features.timeline) { + scene_output->in_timeline = wlr_drm_syncobj_timeline_create(drm_fd); + if (scene_output->in_timeline == NULL) { + return NULL; + } + } + scene_output->index = prev_output_index + 1; assert(scene_output->index < 64); wl_list_insert(prev_output_link, &scene_output->link); @@ -1442,6 +1755,8 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) { scene_node_output_update(&scene_output->scene->tree.node, &scene_output->scene->outputs, scene_output, NULL); + assert(wl_list_empty(&scene_output->events.destroy.listener_list)); + struct highlight_region *damage, *tmp_damage; wl_list_for_each_safe(damage, tmp_damage, &scene_output->damage_highlight_regions, link) { highlight_region_destroy(damage); @@ -1454,7 +1769,11 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) { wl_list_remove(&scene_output->output_commit.link); wl_list_remove(&scene_output->output_damage.link); wl_list_remove(&scene_output->output_needs_frame.link); - + wlr_drm_syncobj_timeline_unref(scene_output->in_timeline); + wlr_color_transform_unref(scene_output->gamma_lut_color_transform); + wlr_color_transform_unref(scene_output->prev_gamma_lut_color_transform); + wlr_color_transform_unref(scene_output->prev_supplied_color_transform); + wlr_color_transform_unref(scene_output->prev_combined_color_transform); wl_array_release(&scene_output->render_list); free(scene_output); } @@ -1507,6 +1826,15 @@ struct render_list_constructor_data { bool fractional_scale; }; +static bool scene_buffer_is_black_opaque(struct wlr_scene_buffer *scene_buffer) { + return scene_buffer->is_single_pixel_buffer && + scene_buffer->single_pixel_buffer_color[0] == 0 && + scene_buffer->single_pixel_buffer_color[1] == 0 && + scene_buffer->single_pixel_buffer_color[2] == 0 && + scene_buffer->single_pixel_buffer_color[3] == UINT32_MAX && + scene_buffer->opacity == 1.0; +} + static bool construct_render_list_iterator(struct wlr_scene_node *node, int lx, int ly, void *_data) { struct render_list_constructor_data *data = _data; @@ -1529,12 +1857,22 @@ static bool construct_render_list_iterator(struct wlr_scene_node *node, } } + // Apply the same special-case to black opaque single-pixel buffers + if (node->type == WLR_SCENE_NODE_BUFFER && data->calculate_visibility && + (!data->fractional_scale || data->render_list->size == 0)) { + struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); + + if (scene_buffer_is_black_opaque(scene_buffer)) { + return false; + } + } + pixman_region32_t intersection; pixman_region32_init(&intersection); pixman_region32_intersect_rect(&intersection, &node->visible, data->box.x, data->box.y, data->box.width, data->box.height); - if (!pixman_region32_not_empty(&intersection)) { + if (pixman_region32_empty(&intersection)) { pixman_region32_fini(&intersection); return false; } @@ -1556,21 +1894,6 @@ static bool construct_render_list_iterator(struct wlr_scene_node *node, return false; } -static void output_state_apply_damage(const struct render_data *data, - struct wlr_output_state *state) { - struct wlr_scene_output *output = data->output; - - pixman_region32_t frame_damage; - pixman_region32_init(&frame_damage); - pixman_region32_copy(&frame_damage, &output->damage_ring.current); - transform_output_damage(&frame_damage, data); - pixman_region32_union(&output->pending_commit_damage, - &output->pending_commit_damage, &frame_damage); - pixman_region32_fini(&frame_damage); - - wlr_output_state_set_damage(state, &output->pending_commit_damage); -} - static void scene_buffer_send_dmabuf_feedback(const struct wlr_scene *scene, struct wlr_scene_buffer *scene_buffer, const struct wlr_linux_dmabuf_feedback_v1_init_options *options) { @@ -1596,41 +1919,85 @@ static void scene_buffer_send_dmabuf_feedback(const struct wlr_scene *scene, return; } + enum wl_output_transform preferred_buffer_transform = WL_OUTPUT_TRANSFORM_NORMAL; + if (options->scanout_primary_output != NULL) { + preferred_buffer_transform = options->scanout_primary_output->transform; + } + + // TODO: also send wl_surface.preferred_buffer_transform when running with + // pure software rendering + wlr_surface_set_preferred_buffer_transform(surface->surface, preferred_buffer_transform); wlr_linux_dmabuf_v1_set_surface_feedback(scene->linux_dmabuf_v1, surface->surface, &feedback); wlr_linux_dmabuf_feedback_v1_finish(&feedback); } -static bool scene_entry_try_direct_scanout(struct render_list_entry *entry, - struct wlr_output_state *state, const struct render_data *data) { +static bool color_management_is_scanout_allowed(const struct wlr_output_image_description *img_desc, + const struct wlr_scene_buffer *buffer) { + // Disallow scanout if the output has colorimetry information but buffer + // doesn't; allow it only if the output also lacks it. + if (buffer->transfer_function == 0 && buffer->primaries == 0) { + return img_desc == NULL; + } + + // If the output has colorimetry information, the buffer must match it for + // direct scanout to be allowed. + if (img_desc != NULL) { + return img_desc->transfer_function == buffer->transfer_function && + img_desc->primaries == buffer->primaries; + } + // If the output doesn't have colorimetry image description set, we can only + // scan out buffers with default colorimetry (gamma2.2 transfer and sRGB + // primaries) used in wlroots. + return buffer->transfer_function == WLR_COLOR_TRANSFER_FUNCTION_GAMMA22 && + buffer->primaries == WLR_COLOR_NAMED_PRIMARIES_SRGB; +} + +enum scene_direct_scanout_result { + // This scene node is not a candidate for scanout + SCANOUT_INELIGIBLE, + + // This scene node is a candidate for scanout, but is currently + // incompatible due to e.g. buffer mismatch, and if possible we'd like to + // resolve this incompatibility. + SCANOUT_CANDIDATE, + + // Scanout is successful. + SCANOUT_SUCCESS, +}; + +static enum scene_direct_scanout_result scene_entry_try_direct_scanout( + struct render_list_entry *entry, struct wlr_output_state *state, + const struct render_data *data) { struct wlr_scene_output *scene_output = data->output; struct wlr_scene_node *node = entry->node; if (!scene_output->scene->direct_scanout) { - return false; + return SCANOUT_INELIGIBLE; } if (node->type != WLR_SCENE_NODE_BUFFER) { - return false; + return SCANOUT_INELIGIBLE; } if (state->committed & (WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_ENABLED | WLR_OUTPUT_STATE_RENDER_FORMAT)) { // Legacy DRM will explode if we try to modeset with a direct scanout buffer - return false; + return SCANOUT_INELIGIBLE; } if (!wlr_output_is_direct_scanout_allowed(scene_output->output)) { - return false; + return SCANOUT_INELIGIBLE; } struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); if (buffer->buffer == NULL) { - return false; + return SCANOUT_INELIGIBLE; } + // The native size of the buffer after any transform is applied int default_width = buffer->buffer->width; int default_height = buffer->buffer->height; wlr_output_transform_coords(buffer->transform, &default_width, &default_height); @@ -1639,43 +2006,59 @@ static bool scene_entry_try_direct_scanout(struct render_list_entry *entry, .height = default_height, }; - if (!wlr_fbox_empty(&buffer->src_box) && - !wlr_fbox_equal(&buffer->src_box, &default_box)) { - return false; - } - if (buffer->transform != data->transform) { - return false; + return SCANOUT_INELIGIBLE; } - struct wlr_box node_box = { .x = entry->x, .y = entry->y }; - scene_node_get_size(node, &node_box.width, &node_box.height); - - if (!wlr_box_equal(&data->logical, &node_box)) { - return false; + const struct wlr_output_image_description *img_desc = output_pending_image_description(scene_output->output, state); + if (!color_management_is_scanout_allowed(img_desc, buffer)) { + return SCANOUT_INELIGIBLE; } - if (buffer->primary_output == scene_output) { + // We want to ensure optimal buffer selection, but as direct-scanout can be enabled and disabled + // on a frame-by-frame basis, we wait for a few frames to send the new format recommendations. + // Maybe we should only send feedback in this case if tests fail. + if (scene_output->dmabuf_feedback_debounce >= DMABUF_FEEDBACK_DEBOUNCE_FRAMES + && buffer->primary_output == scene_output) { struct wlr_linux_dmabuf_feedback_v1_init_options options = { .main_renderer = scene_output->output->renderer, .scanout_primary_output = scene_output->output, }; scene_buffer_send_dmabuf_feedback(scene_output->scene, buffer, &options); - entry->sent_dmabuf_feedback = true; } struct wlr_output_state pending; wlr_output_state_init(&pending); if (!wlr_output_state_copy(&pending, state)) { - return false; + return SCANOUT_CANDIDATE; } - wlr_output_state_set_buffer(&pending, buffer->buffer); + if (!wlr_fbox_empty(&buffer->src_box) && + !wlr_fbox_equal(&buffer->src_box, &default_box)) { + pending.buffer_src_box = buffer->src_box; + } + // Translate the position from scene coordinates to output coordinates + pending.buffer_dst_box.x = entry->x - scene_output->x; + pending.buffer_dst_box.y = entry->y - scene_output->y; + + scene_node_get_size(node, &pending.buffer_dst_box.width, &pending.buffer_dst_box.height); + transform_output_box(&pending.buffer_dst_box, data); + + struct wlr_buffer *wlr_buffer = buffer->buffer; + struct wlr_client_buffer *client_buffer = wlr_client_buffer_get(wlr_buffer); + if (client_buffer != NULL && client_buffer->source != NULL && client_buffer->source->n_locks > 0) { + wlr_buffer = client_buffer->source; + } + + wlr_output_state_set_buffer(&pending, wlr_buffer); + if (buffer->wait_timeline != NULL) { + wlr_output_state_set_wait_timeline(&pending, buffer->wait_timeline, buffer->wait_point); + } if (!wlr_output_test_state(scene_output->output, &pending)) { wlr_output_state_finish(&pending); - return false; + return SCANOUT_CANDIDATE; } wlr_output_state_copy(state, &pending); @@ -1686,13 +2069,18 @@ static bool scene_entry_try_direct_scanout(struct render_list_entry *entry, .direct_scanout = true, }; wl_signal_emit_mutable(&buffer->events.output_sample, &sample_event); - return true; + return SCANOUT_SUCCESS; +} + +bool wlr_scene_output_needs_frame(struct wlr_scene_output *scene_output) { + return scene_output->output->needs_frame || + !pixman_region32_empty(&scene_output->pending_commit_damage) || + scene_output->gamma_lut_changed; } bool wlr_scene_output_commit(struct wlr_scene_output *scene_output, const struct wlr_scene_output_state_options *options) { - if (!scene_output->output->needs_frame && !pixman_region32_not_empty( - &scene_output->pending_commit_damage)) { + if (!wlr_scene_output_needs_frame(scene_output)) { return true; } @@ -1713,6 +2101,69 @@ out: return ok; } +static void scene_output_state_attempt_gamma(struct wlr_scene_output *scene_output, + struct wlr_output_state *state) { + if (!scene_output->gamma_lut_changed) { + return; + } + + struct wlr_output_state gamma_pending = {0}; + if (!wlr_output_state_copy(&gamma_pending, state)) { + return; + } + + wlr_output_state_set_color_transform(&gamma_pending, scene_output->gamma_lut_color_transform); + scene_output->gamma_lut_changed = false; + + if (!wlr_output_test_state(scene_output->output, &gamma_pending)) { + wlr_gamma_control_v1_send_failed_and_destroy(scene_output->gamma_lut); + + scene_output->gamma_lut = NULL; + wlr_color_transform_unref(scene_output->gamma_lut_color_transform); + scene_output->gamma_lut_color_transform = NULL; + wlr_output_state_finish(&gamma_pending); + return; + } + + wlr_output_state_copy(state, &gamma_pending); + wlr_output_state_finish(&gamma_pending); +} + +static struct wlr_color_transform *scene_output_combine_color_transforms( + struct wlr_scene_output *scene_output, struct wlr_color_transform *supplied) { + struct wlr_color_transform *gamma_lut = scene_output->gamma_lut_color_transform; + assert(gamma_lut != NULL); + + if (gamma_lut == scene_output->prev_gamma_lut_color_transform && + supplied == scene_output->prev_supplied_color_transform) { + return wlr_color_transform_ref(scene_output->prev_combined_color_transform); + } + + struct wlr_color_transform *combined; + if (supplied == NULL) { + combined = wlr_color_transform_ref(gamma_lut); + } else { + struct wlr_color_transform *transforms[] = { + gamma_lut, + supplied, + }; + size_t transforms_len = sizeof(transforms) / sizeof(transforms[0]); + combined = wlr_color_transform_init_pipeline(transforms, transforms_len); + if (combined == NULL) { + return NULL; + } + } + + wlr_color_transform_unref(scene_output->prev_gamma_lut_color_transform); + scene_output->prev_gamma_lut_color_transform = wlr_color_transform_ref(gamma_lut); + wlr_color_transform_unref(scene_output->prev_supplied_color_transform); + scene_output->prev_supplied_color_transform = supplied ? wlr_color_transform_ref(supplied) : NULL; + wlr_color_transform_unref(scene_output->prev_combined_color_transform); + scene_output->prev_combined_color_transform = wlr_color_transform_ref(combined); + + return combined; +} + bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, struct wlr_output_state *state, const struct wlr_scene_output_state_options *options) { struct wlr_scene_output_state_options default_options = {0}; @@ -1736,6 +2187,16 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, enum wlr_scene_debug_damage_option debug_damage = scene_output->scene->debug_damage_option; + bool render_gamma_lut = false; + if (wlr_output_get_gamma_size(output) == 0 && output->renderer->features.output_color_transform) { + if (scene_output->gamma_lut_color_transform != scene_output->prev_gamma_lut_color_transform) { + scene_output_damage_whole(scene_output); + } + if (scene_output->gamma_lut_color_transform != NULL) { + render_gamma_lut = true; + } + } + struct render_data render_data = { .transform = output->transform, .scale = output->scale, @@ -1749,7 +2210,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, if (state->committed & WLR_OUTPUT_STATE_TRANSFORM) { if (render_data.transform != state->transform) { - wlr_damage_ring_add_whole(&scene_output->damage_ring); + scene_output_damage_whole(scene_output); } render_data.transform = state->transform; @@ -1757,7 +2218,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, if (state->committed & WLR_OUTPUT_STATE_SCALE) { if (render_data.scale != state->scale) { - wlr_damage_ring_add_whole(&scene_output->damage_ring); + scene_output_damage_whole(scene_output); } render_data.scale = state->scale; @@ -1787,11 +2248,8 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, struct render_list_entry *list_data = list_con.render_list->data; int list_len = list_con.render_list->size / sizeof(*list_data); - wlr_damage_ring_set_bounds(&scene_output->damage_ring, - render_data.trans_width, render_data.trans_height); - if (debug_damage == WLR_SCENE_DEBUG_DAMAGE_RERENDER) { - wlr_damage_ring_add_whole(&scene_output->damage_ring); + scene_output_damage_whole(scene_output); } struct timespec now; @@ -1800,7 +2258,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, clock_gettime(CLOCK_MONOTONIC, &now); // add the current frame's damage if there is damage - if (pixman_region32_not_empty(&scene_output->damage_ring.current)) { + if (!pixman_region32_empty(&scene_output->damage_ring.current)) { struct highlight_region *current_damage = calloc(1, sizeof(*current_damage)); if (current_damage) { pixman_region32_init(¤t_damage->region); @@ -1823,25 +2281,39 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, struct timespec time_diff; timespec_sub(&time_diff, &now, &damage->when); if (timespec_to_msec(&time_diff) >= HIGHLIGHT_DAMAGE_FADEOUT_TIME || - !pixman_region32_not_empty(&damage->region)) { + pixman_region32_empty(&damage->region)) { highlight_region_destroy(damage); } } - wlr_damage_ring_add(&scene_output->damage_ring, &acc_damage); + scene_output_damage(scene_output, &acc_damage); pixman_region32_fini(&acc_damage); } - output_state_apply_damage(&render_data, state); + wlr_output_state_set_damage(state, &scene_output->pending_commit_damage); // We only want to try direct scanout if: // - There is only one entry in the render list // - There are no color transforms that need to be applied // - Damage highlight debugging is not enabled - bool scanout = options->color_transform == NULL && - list_len == 1 && debug_damage != WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT && - scene_entry_try_direct_scanout(&list_data[0], state, &render_data); + enum scene_direct_scanout_result scanout_result = SCANOUT_INELIGIBLE; + if (options->color_transform == NULL && !render_gamma_lut && list_len == 1 + && debug_damage != WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) { + scanout_result = scene_entry_try_direct_scanout(&list_data[0], state, &render_data); + } + if (scanout_result == SCANOUT_INELIGIBLE) { + if (scene_output->dmabuf_feedback_debounce > 0) { + // We cannot scan out, so count down towards sending composition dmabuf feedback + scene_output->dmabuf_feedback_debounce--; + } + } else if (scene_output->dmabuf_feedback_debounce < DMABUF_FEEDBACK_DEBOUNCE_FRAMES) { + // We either want to scan out or successfully scanned out, so count up towards sending + // scanout dmabuf feedback + scene_output->dmabuf_feedback_debounce++; + } + + bool scanout = scanout_result == SCANOUT_SUCCESS; if (scene_output->prev_scanout != scanout) { scene_output->prev_scanout = scanout; wlr_log(WLR_DEBUG, "Direct scan-out %s", @@ -1849,6 +2321,8 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, } if (scanout) { + scene_output_state_attempt_gamma(scene_output, state); + if (timer) { struct timespec end_time, duration; clock_gettime(CLOCK_MONOTONIC, &end_time); @@ -1867,7 +2341,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, swapchain = output->swapchain; } - struct wlr_buffer *buffer = wlr_swapchain_acquire(swapchain, NULL); + struct wlr_buffer *buffer = wlr_swapchain_acquire(swapchain); if (buffer == NULL) { return false; } @@ -1883,11 +2357,41 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, timer->pre_render_duration = timespec_to_nsec(&duration); } + struct wlr_color_transform *color_transform = NULL; + const struct wlr_color_primaries *primaries = NULL; + struct wlr_color_primaries primaries_value; + const struct wlr_output_image_description *img_desc = output_pending_image_description(output, state); + if (img_desc != NULL) { + color_transform = wlr_color_transform_init_linear_to_inverse_eotf(img_desc->transfer_function); + wlr_color_primaries_from_named(&primaries_value, img_desc->primaries); + primaries = &primaries_value; + } + if (options->color_transform != NULL) { + assert(color_transform == NULL); + color_transform = wlr_color_transform_ref(options->color_transform); + } + + if (render_gamma_lut) { + struct wlr_color_transform *combined = + scene_output_combine_color_transforms(scene_output, color_transform); + wlr_color_transform_unref(color_transform); + if (combined == NULL) { + wlr_buffer_unlock(buffer); + return false; + } + color_transform = combined; + } + + scene_output->in_point++; struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(output->renderer, buffer, &(struct wlr_buffer_pass_options){ .timer = timer ? timer->render_timer : NULL, - .color_transform = options->color_transform, + .color_transform = color_transform, + .primaries = primaries, + .signal_timeline = scene_output->in_timeline, + .signal_point = scene_output->in_point, }); + wlr_color_transform_unref(color_transform); if (render_pass == NULL) { wlr_buffer_unlock(buffer); return false; @@ -1921,7 +2425,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, pixman_region32_intersect(&opaque, &opaque, &entry->node->visible); pixman_region32_translate(&opaque, -scene_output->x, -scene_output->y); - wlr_region_scale(&opaque, &opaque, render_data.scale); + logical_to_buffer_coords(&opaque, &render_data, false); pixman_region32_subtract(&background, &background, &opaque); pixman_region32_fini(&opaque); } @@ -1935,7 +2439,6 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, } } - transform_output_damage(&background, &render_data); wlr_render_pass_add_rect(render_pass, &(struct wlr_render_rect_options){ .box = { .width = buffer->width, .height = buffer->height }, .color = { .r = 0, .g = 0, .b = 0, .a = 1 }, @@ -1950,7 +2453,11 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, if (entry->node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(entry->node); - if (buffer->primary_output == scene_output && !entry->sent_dmabuf_feedback) { + // Direct scanout counts up to DMABUF_FEEDBACK_DEBOUNCE_FRAMES before sending new dmabuf + // feedback, and on composition we wait until it hits zero again. If we knew that an + // entry could never be a scanout candidate, we could send feedback to it + // unconditionally without debounce, but for now it is all or nothing + if (scene_output->dmabuf_feedback_debounce == 0 && buffer->primary_output == scene_output) { struct wlr_linux_dmabuf_feedback_v1_init_options options = { .main_renderer = output->renderer, .scanout_primary_output = NULL, @@ -1978,7 +2485,6 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, } wlr_output_add_software_cursors_to_render_pass(output, render_pass, &render_data.damage); - pixman_region32_fini(&render_data.damage); if (!wlr_render_pass_submit(render_pass)) { @@ -1993,6 +2499,15 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, wlr_output_state_set_buffer(state, buffer); wlr_buffer_unlock(buffer); + if (scene_output->in_timeline != NULL) { + wlr_output_state_set_wait_timeline(state, scene_output->in_timeline, + scene_output->in_point); + } + + if (!render_gamma_lut) { + scene_output_state_attempt_gamma(scene_output, state); + } + return true; } @@ -2020,10 +2535,11 @@ static void scene_node_send_frame_done(struct wlr_scene_node *node, if (node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); - - if (scene_buffer->primary_output == scene_output) { - wlr_scene_buffer_send_frame_done(scene_buffer, now); - } + struct wlr_scene_frame_done_event event = { + .output = scene_output, + .when = *now, + }; + wlr_scene_buffer_send_frame_done(scene_buffer, &event); } else if (node->type == WLR_SCENE_NODE_TREE) { struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); struct wlr_scene_node *child; diff --git a/types/scene/xdg_shell.c b/types/scene/xdg_shell.c index ed792633d..73d0acb79 100644 --- a/types/scene/xdg_shell.c +++ b/types/scene/xdg_shell.c @@ -34,10 +34,8 @@ static void scene_xdg_surface_update_position( struct wlr_scene_xdg_surface *scene_xdg_surface) { struct wlr_xdg_surface *xdg_surface = scene_xdg_surface->xdg_surface; - struct wlr_box geo = {0}; - wlr_xdg_surface_get_geometry(xdg_surface, &geo); wlr_scene_node_set_position(&scene_xdg_surface->surface_tree->node, - -geo.x, -geo.y); + -xdg_surface->geometry.x, -xdg_surface->geometry.y); if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) { struct wlr_xdg_popup *popup = xdg_surface->popup; diff --git a/types/seat/wlr_seat.c b/types/seat/wlr_seat.c index 2e8e4a844..015a57b52 100644 --- a/types/seat/wlr_seat.c +++ b/types/seat/wlr_seat.c @@ -71,6 +71,8 @@ static void seat_handle_get_touch(struct wl_client *client, static void seat_client_destroy(struct wlr_seat_client *client) { wl_signal_emit_mutable(&client->events.destroy, client); + assert(wl_list_empty(&client->events.destroy.listener_list)); + if (client == client->seat->pointer_state.focused_client) { client->seat->pointer_state.focused_client = NULL; } @@ -151,6 +153,7 @@ static struct wlr_seat_client *seat_client_create(struct wlr_seat *wlr_seat, wl_list_init(&seat_client->keyboards); wl_list_init(&seat_client->touches); wl_list_init(&seat_client->data_devices); + wl_signal_init(&seat_client->events.destroy); wl_list_insert(&wlr_seat->clients, &seat_client->link); @@ -227,6 +230,31 @@ void wlr_seat_destroy(struct wlr_seat *seat) { wl_signal_emit_mutable(&seat->events.destroy, seat); + assert(wl_list_empty(&seat->pointer_state.events.focus_change.listener_list)); + + assert(wl_list_empty(&seat->keyboard_state.events.focus_change.listener_list)); + + assert(wl_list_empty(&seat->events.request_start_drag.listener_list)); + assert(wl_list_empty(&seat->events.start_drag.listener_list)); + + assert(wl_list_empty(&seat->events.request_set_cursor.listener_list)); + + assert(wl_list_empty(&seat->events.request_set_selection.listener_list)); + assert(wl_list_empty(&seat->events.set_selection.listener_list)); + assert(wl_list_empty(&seat->events.request_set_primary_selection.listener_list)); + assert(wl_list_empty(&seat->events.set_primary_selection.listener_list)); + + assert(wl_list_empty(&seat->events.pointer_grab_begin.listener_list)); + assert(wl_list_empty(&seat->events.pointer_grab_end.listener_list)); + + assert(wl_list_empty(&seat->events.keyboard_grab_begin.listener_list)); + assert(wl_list_empty(&seat->events.keyboard_grab_end.listener_list)); + + assert(wl_list_empty(&seat->events.touch_grab_begin.listener_list)); + assert(wl_list_empty(&seat->events.touch_grab_end.listener_list)); + + assert(wl_list_empty(&seat->events.destroy.listener_list)); + wl_list_remove(&seat->display_destroy.link); wlr_data_source_destroy(seat->selection_source); @@ -443,9 +471,13 @@ struct wlr_seat_client *wlr_seat_client_from_resource( } uint32_t wlr_seat_client_next_serial(struct wlr_seat_client *client) { - uint32_t serial = wl_display_next_serial(wl_client_get_display(client->client)); - struct wlr_serial_ringset *set = &client->serials; + struct wl_display *display = wl_client_get_display(client->client); + uint32_t serial = wl_display_next_serial(display); + if (serial == 0) { + serial = wl_display_next_serial(display); + } + struct wlr_serial_ringset *set = &client->serials; if (set->count == 0) { set->data[0].min_incl = serial; set->data[0].max_incl = serial; diff --git a/types/seat/wlr_seat_keyboard.c b/types/seat/wlr_seat_keyboard.c index cae4c0db1..3227035e2 100644 --- a/types/seat/wlr_seat_keyboard.c +++ b/types/seat/wlr_seat_keyboard.c @@ -122,6 +122,11 @@ void wlr_seat_set_keyboard(struct wlr_seat *seat, return; } + // send the keymap only if it has changed + bool needs_keymap_update = + !seat->keyboard_state.keyboard || !keyboard || + seat->keyboard_state.keyboard->keymap != keyboard->keymap; + if (seat->keyboard_state.keyboard) { wl_list_remove(&seat->keyboard_state.keyboard_destroy.link); wl_list_remove(&seat->keyboard_state.keyboard_keymap.link); @@ -145,7 +150,9 @@ void wlr_seat_set_keyboard(struct wlr_seat *seat, struct wlr_seat_client *client; wl_list_for_each(client, &seat->clients, link) { - seat_client_send_keymap(client, keyboard); + if (needs_keymap_update) { + seat_client_send_keymap(client, keyboard); + } seat_client_send_repeat_info(client, keyboard); } @@ -319,19 +326,16 @@ bool wlr_seat_keyboard_has_grab(struct wlr_seat *seat) { void wlr_seat_keyboard_notify_modifiers(struct wlr_seat *seat, const struct wlr_keyboard_modifiers *modifiers) { - clock_gettime(CLOCK_MONOTONIC, &seat->last_event); struct wlr_seat_keyboard_grab *grab = seat->keyboard_state.grab; grab->interface->modifiers(grab, modifiers); } void wlr_seat_keyboard_notify_key(struct wlr_seat *seat, uint32_t time, uint32_t key, uint32_t state) { - clock_gettime(CLOCK_MONOTONIC, &seat->last_event); struct wlr_seat_keyboard_grab *grab = seat->keyboard_state.grab; grab->interface->key(grab, time, key, state); } - static void seat_client_send_keymap(struct wlr_seat_client *client, struct wlr_keyboard *keyboard) { if (!keyboard) { diff --git a/types/seat/wlr_seat_pointer.c b/types/seat/wlr_seat_pointer.c index 9618d52b4..41729dcd9 100644 --- a/types/seat/wlr_seat_pointer.c +++ b/types/seat/wlr_seat_pointer.c @@ -6,7 +6,6 @@ #include #include #include "types/wlr_seat.h" -#include "util/set.h" static void default_pointer_enter(struct wlr_seat_pointer_grab *grab, struct wlr_surface *surface, double sx, double sy) { @@ -431,31 +430,46 @@ void wlr_seat_pointer_end_grab(struct wlr_seat *wlr_seat) { } } +// Switching focus means the new surface doesn't know about the currently +// pressed buttons. This function allows to reset them. +static void reset_buttons(struct wlr_seat *wlr_seat) { + wlr_seat->pointer_state.button_count = 0; +} + void wlr_seat_pointer_notify_enter(struct wlr_seat *wlr_seat, struct wlr_surface *surface, double sx, double sy) { // NULL surfaces are prohibited in the grab-compatible API. Use // wlr_seat_pointer_notify_clear_focus() instead. assert(surface); struct wlr_seat_pointer_grab *grab = wlr_seat->pointer_state.grab; + struct wlr_surface *focused_surface = wlr_seat->pointer_state.focused_surface; + grab->interface->enter(grab, surface, sx, sy); + + if (focused_surface != wlr_seat->pointer_state.focused_surface) { + reset_buttons(wlr_seat); + } } void wlr_seat_pointer_notify_clear_focus(struct wlr_seat *wlr_seat) { struct wlr_seat_pointer_grab *grab = wlr_seat->pointer_state.grab; + struct wlr_surface *focused_surface = wlr_seat->pointer_state.focused_surface; + grab->interface->clear_focus(grab); + + if (focused_surface != wlr_seat->pointer_state.focused_surface) { + reset_buttons(wlr_seat); + } } void wlr_seat_pointer_notify_motion(struct wlr_seat *wlr_seat, uint32_t time, double sx, double sy) { - clock_gettime(CLOCK_MONOTONIC, &wlr_seat->last_event); struct wlr_seat_pointer_grab *grab = wlr_seat->pointer_state.grab; grab->interface->motion(grab, time, sx, sy); } uint32_t wlr_seat_pointer_notify_button(struct wlr_seat *wlr_seat, uint32_t time, uint32_t button, enum wl_pointer_button_state state) { - clock_gettime(CLOCK_MONOTONIC, &wlr_seat->last_event); - struct wlr_seat_pointer_state* pointer_state = &wlr_seat->pointer_state; if (state == WL_POINTER_BUTTON_STATE_PRESSED) { @@ -463,14 +477,38 @@ uint32_t wlr_seat_pointer_notify_button(struct wlr_seat *wlr_seat, pointer_state->grab_button = button; pointer_state->grab_time = time; } - set_add(pointer_state->buttons, &pointer_state->button_count, - WLR_POINTER_BUTTONS_CAP, button); + for (size_t i = 0; i < pointer_state->button_count; i++) { + struct wlr_seat_pointer_button *pointer_button = &pointer_state->buttons[i]; + if (pointer_button->button == button) { + ++pointer_button->n_pressed; + return 0; + } + } + if (pointer_state->button_count == WLR_POINTER_BUTTONS_CAP) { + return 0; + } + pointer_state->buttons[pointer_state->button_count++] = (struct wlr_seat_pointer_button){ + .button = button, + .n_pressed = 1, + }; } else { - set_remove(pointer_state->buttons, &pointer_state->button_count, - WLR_POINTER_BUTTONS_CAP, button); + bool found = false; + for (size_t i = 0; i < pointer_state->button_count; i++) { + struct wlr_seat_pointer_button *pointer_button = &pointer_state->buttons[i]; + if (pointer_button->button == button) { + if (--pointer_button->n_pressed > 0) { + return 0; + } + *pointer_button = pointer_state->buttons[--pointer_state->button_count]; + found = true; + break; + } + } + if (!found) { + return 0; + } } - struct wlr_seat_pointer_grab *grab = pointer_state->grab; uint32_t serial = grab->interface->button(grab, time, button, state); @@ -486,14 +524,12 @@ void wlr_seat_pointer_notify_axis(struct wlr_seat *wlr_seat, uint32_t time, enum wl_pointer_axis orientation, double value, int32_t value_discrete, enum wl_pointer_axis_source source, enum wl_pointer_axis_relative_direction relative_direction) { - clock_gettime(CLOCK_MONOTONIC, &wlr_seat->last_event); struct wlr_seat_pointer_grab *grab = wlr_seat->pointer_state.grab; grab->interface->axis(grab, time, orientation, value, value_discrete, source, relative_direction); } void wlr_seat_pointer_notify_frame(struct wlr_seat *wlr_seat) { - clock_gettime(CLOCK_MONOTONIC, &wlr_seat->last_event); struct wlr_seat_pointer_grab *grab = wlr_seat->pointer_state.grab; if (grab->interface->frame) { grab->interface->frame(grab); diff --git a/types/seat/wlr_seat_touch.c b/types/seat/wlr_seat_touch.c index 09f33d667..192ae5890 100644 --- a/types/seat/wlr_seat_touch.c +++ b/types/seat/wlr_seat_touch.c @@ -107,6 +107,8 @@ static void touch_point_clear_focus(struct wlr_touch_point *point) { static void touch_point_destroy(struct wlr_touch_point *point) { wl_signal_emit_mutable(&point->events.destroy, point); + assert(wl_list_empty(&point->events.destroy.listener_list)); + touch_point_clear_focus(point); wl_list_remove(&point->surface_destroy.link); wl_list_remove(&point->client_destroy.link); @@ -181,7 +183,6 @@ struct wlr_touch_point *wlr_seat_touch_get_point( uint32_t wlr_seat_touch_notify_down(struct wlr_seat *seat, struct wlr_surface *surface, uint32_t time, int32_t touch_id, double sx, double sy) { - clock_gettime(CLOCK_MONOTONIC, &seat->last_event); struct wlr_seat_touch_grab *grab = seat->touch_state.grab; struct wlr_touch_point *point = touch_point_create(seat, touch_id, surface, sx, sy); @@ -207,21 +208,20 @@ uint32_t wlr_seat_touch_notify_down(struct wlr_seat *seat, uint32_t wlr_seat_touch_notify_up(struct wlr_seat *seat, uint32_t time, int32_t touch_id) { - clock_gettime(CLOCK_MONOTONIC, &seat->last_event); struct wlr_seat_touch_grab *grab = seat->touch_state.grab; struct wlr_touch_point *point = wlr_seat_touch_get_point(seat, touch_id); if (!point) { return 0; } - return grab->interface->up(grab, time, point); + uint32_t serial = grab->interface->up(grab, time, point); touch_point_destroy(point); + return serial; } void wlr_seat_touch_notify_motion(struct wlr_seat *seat, uint32_t time, int32_t touch_id, double sx, double sy) { - clock_gettime(CLOCK_MONOTONIC, &seat->last_event); struct wlr_seat_touch_grab *grab = seat->touch_state.grab; struct wlr_touch_point *point = wlr_seat_touch_get_point(seat, touch_id); if (!point) { @@ -476,7 +476,7 @@ bool wlr_seat_validate_touch_grab_serial(struct wlr_seat *seat, return false; } -bool wlr_surface_accepts_touch(struct wlr_seat *wlr_seat, struct wlr_surface *surface) { +bool wlr_surface_accepts_touch(struct wlr_surface *surface, struct wlr_seat *wlr_seat) { struct wl_client *client = wl_resource_get_client(surface->resource); struct wlr_seat_client *seat_client = wlr_seat_client_for_wl_client(wlr_seat, client); if (!seat_client) { diff --git a/types/tablet_v2/wlr_tablet_v2.c b/types/tablet_v2/wlr_tablet_v2.c index 28671c9b0..a0cf2fa67 100644 --- a/types/tablet_v2/wlr_tablet_v2.c +++ b/types/tablet_v2/wlr_tablet_v2.c @@ -270,6 +270,9 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_tablet_manager_v2 *manager = wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.destroy.listener_list)); + wl_list_remove(&manager->display_destroy.link); struct wlr_tablet_seat_v2 *seat, *tmp; @@ -296,6 +299,7 @@ struct wlr_tablet_manager_v2 *wlr_tablet_v2_create(struct wl_display *display) { } wl_signal_init(&tablet->events.destroy); + wl_list_init(&tablet->clients); wl_list_init(&tablet->seats); diff --git a/types/tablet_v2/wlr_tablet_v2_pad.c b/types/tablet_v2/wlr_tablet_v2_pad.c index ab7c3fc49..2e0439a36 100644 --- a/types/tablet_v2/wlr_tablet_v2_pad.c +++ b/types/tablet_v2/wlr_tablet_v2_pad.c @@ -7,6 +7,7 @@ #include #include #include +#include "util/time.h" #include "tablet-v2-protocol.h" static const struct wlr_tablet_pad_v2_grab_interface default_pad_grab_interface; @@ -457,10 +458,7 @@ uint32_t wlr_send_tablet_v2_tablet_pad_enter( zwp_tablet_pad_v2_send_enter(pad_client->resource, serial, tablet_client->resource, surface->resource); - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - uint32_t time = now.tv_nsec / 1000; - + uint32_t time = get_current_time_msec(); for (size_t i = 0; i < pad->group_count; ++i) { if (pad_client->groups[i]) { zwp_tablet_pad_group_v2_send_mode_switch( @@ -560,8 +558,8 @@ uint32_t wlr_send_tablet_v2_tablet_pad_mode(struct wlr_tablet_v2_tablet_pad *pad return serial; } -bool wlr_surface_accepts_tablet_v2(struct wlr_tablet_v2_tablet *tablet, - struct wlr_surface *surface) { +bool wlr_surface_accepts_tablet_v2(struct wlr_surface *surface, + struct wlr_tablet_v2_tablet *tablet) { struct wl_client *client = wl_resource_get_client(surface->resource); if (tablet->current_client && diff --git a/types/tablet_v2/wlr_tablet_v2_tool.c b/types/tablet_v2/wlr_tablet_v2_tool.c index 8574a9a2f..e73aa767b 100644 --- a/types/tablet_v2/wlr_tablet_v2_tool.c +++ b/types/tablet_v2/wlr_tablet_v2_tool.c @@ -103,7 +103,10 @@ void destroy_tablet_tool_v2(struct wl_resource *resource) { } if (client->tool && client->tool->current_client == client) { + wl_list_remove(&client->tool->surface_destroy.link); + wl_list_init(&client->tool->surface_destroy.link); client->tool->current_client = NULL; + client->tool->focused_surface = NULL; } wl_list_remove(&client->seat_link); diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c new file mode 100644 index 000000000..c69a69c81 --- /dev/null +++ b/types/wlr_color_management_v1.c @@ -0,0 +1,1043 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include "color-management-v1-protocol.h" +#include "render/color.h" +#include "util/mem.h" + +#define COLOR_MANAGEMENT_V1_VERSION 1 + +struct wlr_color_management_output_v1 { + struct wl_resource *resource; + struct wlr_output *output; + struct wlr_color_manager_v1 *manager; + struct wl_list link; + + struct wl_listener output_destroy; +}; + +struct wlr_color_management_surface_v1_state { + bool has_image_desc_data; + struct wlr_image_description_v1_data image_desc_data; +}; + +struct wlr_color_management_surface_v1 { + struct wl_resource *resource; + struct wlr_surface *surface; + struct wlr_color_manager_v1 *manager; + + struct wlr_addon addon; + struct wlr_surface_synced synced; + + struct wlr_color_management_surface_v1_state current, pending; +}; + +struct wlr_color_management_surface_feedback_v1 { + struct wl_resource *resource; + struct wlr_surface *surface; + struct wlr_color_manager_v1 *manager; + struct wl_list link; + + struct wlr_image_description_v1_data data; + + struct wl_listener surface_destroy; +}; + +struct wlr_image_description_v1 { + struct wl_resource *resource; + bool get_info_allowed; + struct wlr_image_description_v1_data data; // immutable +}; + +struct wlr_image_description_creator_params_v1 { + struct wl_resource *resource; + struct wlr_color_manager_v1 *manager; + struct wlr_image_description_v1_data data; +}; + +static void resource_handle_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static int32_t encode_cie1931_coord(float value) { + return round(value * 1000 * 1000); +} + +static float decode_cie1931_coord(int32_t raw) { + return (float)raw / (1000 * 1000); +} + +static const struct wp_image_description_v1_interface image_desc_impl; + +static struct wlr_image_description_v1 *image_desc_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wp_image_description_v1_interface, &image_desc_impl)); + return wl_resource_get_user_data(resource); +} + +static void image_desc_handle_get_information(struct wl_client *client, + struct wl_resource *image_desc_resource, uint32_t id) { + struct wlr_image_description_v1 *image_desc = image_desc_from_resource(image_desc_resource); + if (image_desc == NULL) { + wl_resource_post_error(image_desc_resource, + WP_IMAGE_DESCRIPTION_V1_ERROR_NOT_READY, + "image description is in failed state"); + return; + } + + if (!image_desc->get_info_allowed) { + wl_resource_post_error(image_desc_resource, + WP_IMAGE_DESCRIPTION_V1_ERROR_NO_INFORMATION, + "get_information not allowed"); + return; + } + + uint32_t version = wl_resource_get_version(image_desc_resource); + struct wl_resource *resource = wl_resource_create(client, + &wp_image_description_info_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + struct wlr_color_primaries primaries; + wlr_color_primaries_from_named(&primaries, + wlr_color_manager_v1_primaries_to_wlr(image_desc->data.primaries_named)); + + struct wlr_color_luminances luminances; + wlr_color_transfer_function_get_default_luminance( + wlr_color_manager_v1_transfer_function_to_wlr(image_desc->data.tf_named), &luminances); + + wp_image_description_info_v1_send_primaries_named(resource, image_desc->data.primaries_named); + wp_image_description_info_v1_send_primaries(resource, + encode_cie1931_coord(primaries.red.x), encode_cie1931_coord(primaries.red.y), + encode_cie1931_coord(primaries.green.x), encode_cie1931_coord(primaries.green.y), + encode_cie1931_coord(primaries.blue.x), encode_cie1931_coord(primaries.blue.y), + encode_cie1931_coord(primaries.white.x), encode_cie1931_coord(primaries.white.y)); + wp_image_description_info_v1_send_tf_named(resource, image_desc->data.tf_named); + wp_image_description_info_v1_send_luminances(resource, + round(luminances.min * 10000), round(luminances.max), + round(luminances.reference)); + // TODO: send mastering display primaries and luminances here when we add + // support for features.set_mastering_display_primaries + wp_image_description_info_v1_send_target_primaries(resource, + encode_cie1931_coord(primaries.red.x), encode_cie1931_coord(primaries.red.y), + encode_cie1931_coord(primaries.green.x), encode_cie1931_coord(primaries.green.y), + encode_cie1931_coord(primaries.blue.x), encode_cie1931_coord(primaries.blue.y), + encode_cie1931_coord(primaries.white.x), encode_cie1931_coord(primaries.white.y)); + wp_image_description_info_v1_send_target_luminance(resource, + round(luminances.min * 10000), round(luminances.max)); + // TODO: send target_max_cll and target_max_fall + wp_image_description_info_v1_send_done(resource); + wl_resource_destroy(resource); +} + +static const struct wp_image_description_v1_interface image_desc_impl = { + .destroy = resource_handle_destroy, + .get_information = image_desc_handle_get_information, +}; + +static void image_desc_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_image_description_v1 *image_desc = image_desc_from_resource(resource); + free(image_desc); +} + +static struct wl_resource *image_desc_create_resource( + struct wl_resource *parent_resource, uint32_t id) { + struct wl_client *client = wl_resource_get_client(parent_resource); + uint32_t version = wl_resource_get_version(parent_resource); + return wl_resource_create(client, &wp_image_description_v1_interface, + version, id); +} + +static void image_desc_create_ready(struct wlr_color_manager_v1 *manager, + struct wl_resource *parent_resource, uint32_t id, + const struct wlr_image_description_v1_data *data, + bool get_info_allowed) { + struct wlr_image_description_v1 *image_desc = calloc(1, sizeof(*image_desc)); + if (image_desc == NULL) { + wl_resource_post_no_memory(parent_resource); + return; + } + + image_desc->data = *data; + image_desc->get_info_allowed = get_info_allowed; + + image_desc->resource = image_desc_create_resource(parent_resource, id); + if (!image_desc->resource) { + wl_resource_post_no_memory(parent_resource); + free(image_desc); + return; + } + wl_resource_set_implementation(image_desc->resource, &image_desc_impl, + image_desc, image_desc_handle_resource_destroy); + + // TODO: de-duplicate identity + uint32_t identity = ++manager->last_image_desc_identity; + wp_image_description_v1_send_ready(image_desc->resource, identity); +} + +static void image_desc_create_failed(struct wl_resource *parent_resource, uint32_t id, + enum wp_image_description_v1_cause cause, const char *msg) { + struct wl_resource *resource = image_desc_create_resource(parent_resource, id); + if (resource == NULL) { + wl_resource_post_no_memory(parent_resource); + return; + } + wl_resource_set_implementation(resource, &image_desc_impl, NULL, NULL); + + wp_image_description_v1_send_failed(resource, cause, msg); +} + +static const struct wp_color_management_output_v1_interface cm_output_impl; + +static struct wlr_color_management_output_v1 *cm_output_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wp_color_management_output_v1_interface, &cm_output_impl)); + return wl_resource_get_user_data(resource); +} + +static void cm_output_handle_get_image_description(struct wl_client *client, + struct wl_resource *cm_output_resource, uint32_t id) { + struct wlr_color_management_output_v1 *cm_output = cm_output_from_resource(cm_output_resource); + if (cm_output == NULL) { + image_desc_create_failed(cm_output_resource, id, + WP_IMAGE_DESCRIPTION_V1_CAUSE_NO_OUTPUT, + "output has been destroyed"); + return; + } + + struct wlr_image_description_v1_data data = { + .tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22, + .primaries_named = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB, + }; + const struct wlr_output_image_description *image_desc = cm_output->output->image_description; + if (image_desc != NULL) { + data.tf_named = wlr_color_manager_v1_transfer_function_from_wlr(image_desc->transfer_function); + data.primaries_named = wlr_color_manager_v1_primaries_from_wlr(image_desc->primaries); + } + image_desc_create_ready(cm_output->manager, cm_output_resource, id, &data, true); +} + +static const struct wp_color_management_output_v1_interface cm_output_impl = { + .destroy = resource_handle_destroy, + .get_image_description = cm_output_handle_get_image_description, +}; + +static void cm_output_destroy(struct wlr_color_management_output_v1 *cm_output) { + if (cm_output == NULL) { + return; + } + wl_resource_set_user_data(cm_output->resource, NULL); // make inert + wl_list_remove(&cm_output->output_destroy.link); + wl_list_remove(&cm_output->link); + free(cm_output); +} + +static void cm_output_handle_output_destroy(struct wl_listener *listener, void *data) { + struct wlr_color_management_output_v1 *cm_output = wl_container_of(listener, cm_output, output_destroy); + cm_output_destroy(cm_output); +} + +static void cm_output_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_color_management_output_v1 *cm_output = cm_output_from_resource(resource); + cm_output_destroy(cm_output); +} + +static void cm_surface_destroy(struct wlr_color_management_surface_v1 *cm_surface) { + if (cm_surface == NULL) { + return; + } + wl_resource_set_user_data(cm_surface->resource, NULL); // make inert + wlr_surface_synced_finish(&cm_surface->synced); + wlr_addon_finish(&cm_surface->addon); + free(cm_surface); +} + +static const struct wlr_surface_synced_impl cm_surface_synced_impl = { + .state_size = sizeof(struct wlr_color_management_surface_v1_state), +}; + +static void cm_surface_handle_addon_destroy(struct wlr_addon *addon) { + struct wlr_color_management_surface_v1 *cm_surface = wl_container_of(addon, cm_surface, addon); + cm_surface_destroy(cm_surface); +} + +static const struct wlr_addon_interface cm_surface_addon_impl = { + .name = "wlr_color_management_surface_v1", + .destroy = cm_surface_handle_addon_destroy, +}; + +static const struct wp_color_management_surface_v1_interface cm_surface_impl; + +static struct wlr_color_management_surface_v1 *cm_surface_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wp_color_management_surface_v1_interface, &cm_surface_impl)); + return wl_resource_get_user_data(resource); +} + +static void cm_surface_handle_set_image_description(struct wl_client *client, + struct wl_resource *cm_surface_resource, + struct wl_resource *image_desc_resource, uint32_t render_intent) { + struct wlr_color_management_surface_v1 *cm_surface = cm_surface_from_resource(cm_surface_resource); + if (cm_surface == NULL) { + wl_resource_post_error(cm_surface_resource, + WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_INERT, + "set_image_description cannot be sent on an inert object"); + return; + } + + struct wlr_image_description_v1 *image_desc = image_desc_from_resource(image_desc_resource); + + if (image_desc == NULL) { + wl_resource_post_error(cm_surface_resource, + WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_IMAGE_DESCRIPTION, + "Image description to be set is invalid"); + return; + } + + bool found = false; + for (size_t i = 0; i < cm_surface->manager->render_intents_len; i++) { + if (cm_surface->manager->render_intents[i] == render_intent) { + found = true; + break; + } + } + if (!found) { + wl_resource_post_error(cm_surface_resource, + WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_RENDER_INTENT, + "invalid render intent"); + return; + } + + cm_surface->pending.has_image_desc_data = true; + cm_surface->pending.image_desc_data = image_desc->data; +} + +static void cm_surface_handle_unset_image_description(struct wl_client *client, + struct wl_resource *cm_surface_resource) { + struct wlr_color_management_surface_v1 *cm_surface = cm_surface_from_resource(cm_surface_resource); + if (cm_surface == NULL) { + wl_resource_post_error(cm_surface_resource, + WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_INERT, + "set_image_description cannot be sent on an inert object"); + return; + } + + cm_surface->pending.has_image_desc_data = false; +} + +static const struct wp_color_management_surface_v1_interface cm_surface_impl = { + .destroy = resource_handle_destroy, + .set_image_description = cm_surface_handle_set_image_description, + .unset_image_description = cm_surface_handle_unset_image_description, +}; + +static void cm_surface_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_color_management_surface_v1 *cm_surface = cm_surface_from_resource(resource); + cm_surface_destroy(cm_surface); +} + +static const struct wp_color_management_surface_feedback_v1_interface surface_feedback_impl; + +static struct wlr_color_management_surface_feedback_v1 *surface_feedback_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wp_color_management_surface_feedback_v1_interface, &surface_feedback_impl)); + return wl_resource_get_user_data(resource); +} + +static void surface_feedback_handle_get_preferred(struct wl_client *client, + struct wl_resource *surface_feedback_resource, uint32_t id) { + struct wlr_color_management_surface_feedback_v1 *surface_feedback = + surface_feedback_from_resource(surface_feedback_resource); + if (surface_feedback == NULL) { + wl_resource_post_error(surface_feedback_resource, + WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_ERROR_INERT, + "get_preferred sent on inert feedback surface"); + return; + } + + image_desc_create_ready(surface_feedback->manager, + surface_feedback_resource, id, &surface_feedback->data, true); +} + +static void surface_feedback_handle_get_preferred_parametric(struct wl_client *client, + struct wl_resource *surface_feedback_resource, uint32_t id) { + struct wlr_color_management_surface_feedback_v1 *surface_feedback = + surface_feedback_from_resource(surface_feedback_resource); + if (surface_feedback == NULL) { + wl_resource_post_error(surface_feedback_resource, + WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_ERROR_INERT, + "get_preferred_parametric sent on inert feedback surface"); + return; + } + + image_desc_create_ready(surface_feedback->manager, + surface_feedback_resource, id, &surface_feedback->data, true); +} + +static const struct wp_color_management_surface_feedback_v1_interface surface_feedback_impl = { + .destroy = resource_handle_destroy, + .get_preferred = surface_feedback_handle_get_preferred, + .get_preferred_parametric = surface_feedback_handle_get_preferred_parametric, +}; + +static void surface_feedback_destroy(struct wlr_color_management_surface_feedback_v1 *surface_feedback) { + if (surface_feedback == NULL) { + return; + } + wl_resource_set_user_data(surface_feedback->resource, NULL); // make inert + wl_list_remove(&surface_feedback->surface_destroy.link); + wl_list_remove(&surface_feedback->link); + free(surface_feedback); +} + +static void surface_feedback_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_color_management_surface_feedback_v1 *surface_feedback = + surface_feedback_from_resource(resource); + surface_feedback_destroy(surface_feedback); +} + +static void surface_feedback_handle_surface_destroy(struct wl_listener *listener, void *data) { + struct wlr_color_management_surface_feedback_v1 *surface_feedback = + wl_container_of(listener, surface_feedback, surface_destroy); + surface_feedback_destroy(surface_feedback); +} + +static const struct wp_image_description_creator_params_v1_interface image_desc_creator_params_impl; + +static struct wlr_image_description_creator_params_v1 * +image_desc_creator_params_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &wp_image_description_creator_params_v1_interface, + &image_desc_creator_params_impl)); + return wl_resource_get_user_data(resource); +} + +static bool check_mastering_luminance_range(struct wl_resource *params_resource, + const struct wlr_image_description_v1_data *data, + float value, const char *name) { + if (!data->has_mastering_luminance || value == 0) { + return true; + } + + if (value <= data->mastering_luminance.min) { + wl_resource_post_error(params_resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_LUMINANCE, + "%s must be greater than min L of the mastering luminance range", + name); + return false; + } + if (value > data->mastering_luminance.max) { + wl_resource_post_error(params_resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_LUMINANCE, + "%s must be less or equal to max L of the mastering luminance range", + name); + return false; + } + + return true; +} + +static void image_desc_creator_params_handle_create(struct wl_client *client, + struct wl_resource *params_resource, uint32_t id) { + struct wlr_image_description_creator_params_v1 *params = + image_desc_creator_params_from_resource(params_resource); + + if (params->data.tf_named == 0) { + wl_resource_post_error(params_resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INCOMPLETE_SET, + "missing transfer function"); + return; + } + if (params->data.primaries_named == 0) { + wl_resource_post_error(params_resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INCOMPLETE_SET, + "missing primaries"); + return; + } + + if (params->data.max_cll != 0 && params->data.max_fall != 0 && + params->data.max_fall > params->data.max_cll) { + wl_resource_post_error(params_resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_LUMINANCE, + "max_fall must be less or equal to max_cll"); + return; + } + + if (!check_mastering_luminance_range(params_resource, ¶ms->data, params->data.max_cll, "max_cll") || + !check_mastering_luminance_range(params_resource, ¶ms->data, params->data.max_fall, "max_fall")) { + return; + } + + // TODO: check that the target color volume is contained within the + // primary color volume + + image_desc_create_ready(params->manager, params_resource, id, ¶ms->data, false); +} + +static void image_desc_creator_params_handle_set_tf_named(struct wl_client *client, + struct wl_resource *params_resource, uint32_t tf) { + struct wlr_image_description_creator_params_v1 *params = + image_desc_creator_params_from_resource(params_resource); + + if (params->data.tf_named != 0) { + wl_resource_post_error(params_resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, + "transfer function already set"); + return; + } + + bool found = false; + for (size_t i = 0; i < params->manager->transfer_functions_len; i++) { + if (params->manager->transfer_functions[i] == tf) { + found = true; + break; + } + } + if (!found) { + wl_resource_post_error(params_resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_TF, + "invalid transfer function"); + return; + } + + params->data.tf_named = tf; +} + +static void image_desc_creator_params_handle_set_tf_power(struct wl_client *client, + struct wl_resource *params_resource, uint32_t eexp) { + wl_resource_post_error(params_resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_UNSUPPORTED_FEATURE, + "set_tf_power is not supported"); +} + +static void image_desc_creator_params_handle_set_primaries_named(struct wl_client *client, + struct wl_resource *params_resource, uint32_t primaries) { + struct wlr_image_description_creator_params_v1 *params = + image_desc_creator_params_from_resource(params_resource); + + if (params->data.primaries_named != 0) { + wl_resource_post_error(params_resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, + "primaries already set"); + return; + } + + bool found = false; + for (size_t i = 0; i < params->manager->primaries_len; i++) { + if (params->manager->primaries[i] == primaries) { + found = true; + break; + } + } + if (!found) { + wl_resource_post_error(params_resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_PRIMARIES_NAMED, + "invalid primaries"); + return; + } + + params->data.primaries_named = primaries; +} + +static void image_desc_creator_params_handle_set_primaries(struct wl_client *client, + struct wl_resource *params_resource, int32_t r_x, int32_t r_y, + int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, + int32_t w_x, int32_t w_y) { + wl_resource_post_error(params_resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_UNSUPPORTED_FEATURE, + "set_primaries is not supported"); +} + +static void image_desc_creator_params_handle_set_luminances(struct wl_client *client, + struct wl_resource *params_resource, uint32_t min_lum, + uint32_t max_lum, uint32_t reference_lum) { + wl_resource_post_error(params_resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_UNSUPPORTED_FEATURE, + "set_luminances is not supported"); +} + +static void image_desc_creator_params_handle_set_mastering_display_primaries( + struct wl_client *client, struct wl_resource *params_resource, + int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, + int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) { + struct wlr_image_description_creator_params_v1 *params = + image_desc_creator_params_from_resource(params_resource); + if (!params->manager->features.set_mastering_display_primaries) { + wl_resource_post_error(params_resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_UNSUPPORTED_FEATURE, + "set_mastering_display_primaries is not supported"); + return; + } + + if (params->data.has_mastering_display_primaries) { + wl_resource_post_error(params_resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, + "mastering display primaries already set"); + return; + } + + params->data.has_mastering_display_primaries = true; + params->data.mastering_display_primaries = (struct wlr_color_primaries){ + .red = { decode_cie1931_coord(r_x), decode_cie1931_coord(r_y) }, + .green = { decode_cie1931_coord(g_x), decode_cie1931_coord(g_y) }, + .blue = { decode_cie1931_coord(b_x), decode_cie1931_coord(b_y) }, + .white = { decode_cie1931_coord(w_x), decode_cie1931_coord(w_y) }, + }; +} + +static void image_desc_creator_params_handle_set_mastering_luminance(struct wl_client *client, + struct wl_resource *params_resource, uint32_t min_lum, uint32_t max_lum) { + struct wlr_image_description_creator_params_v1 *params = + image_desc_creator_params_from_resource(params_resource); + if (!params->manager->features.set_mastering_display_primaries) { + wl_resource_post_error(params_resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_UNSUPPORTED_FEATURE, + "set_mastering_luminance is not supported"); + return; + } + + if (params->data.has_mastering_luminance) { + wl_resource_post_error(params_resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, + "mastering luminance already set"); + return; + } + + params->data.has_mastering_luminance = true; + params->data.mastering_luminance.min = (float)min_lum / 10000; + params->data.mastering_luminance.max = max_lum; + + if (params->data.mastering_luminance.max <= params->data.mastering_luminance.min) { + wl_resource_post_error(params_resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_LUMINANCE, + "max luminance must be greater than min luminance"); + return; + } +} + +static void image_desc_creator_params_handle_set_max_cll(struct wl_client *client, + struct wl_resource *params_resource, uint32_t max_cll) { + struct wlr_image_description_creator_params_v1 *params = + image_desc_creator_params_from_resource(params_resource); + params->data.max_cll = max_cll; +} + +static void image_desc_creator_params_handle_set_max_fall(struct wl_client *client, + struct wl_resource *params_resource, uint32_t max_fall) { + struct wlr_image_description_creator_params_v1 *params = + image_desc_creator_params_from_resource(params_resource); + params->data.max_fall = max_fall; +} + +static const struct wp_image_description_creator_params_v1_interface image_desc_creator_params_impl = { + .create = image_desc_creator_params_handle_create, + .set_tf_named = image_desc_creator_params_handle_set_tf_named, + .set_tf_power = image_desc_creator_params_handle_set_tf_power, + .set_primaries_named = image_desc_creator_params_handle_set_primaries_named, + .set_primaries = image_desc_creator_params_handle_set_primaries, + .set_luminances = image_desc_creator_params_handle_set_luminances, + .set_mastering_display_primaries = image_desc_creator_params_handle_set_mastering_display_primaries, + .set_mastering_luminance = image_desc_creator_params_handle_set_mastering_luminance, + .set_max_cll = image_desc_creator_params_handle_set_max_cll, + .set_max_fall = image_desc_creator_params_handle_set_max_fall, +}; + +static void image_desc_creator_params_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_image_description_creator_params_v1 *params = + image_desc_creator_params_from_resource(resource); + free(params); +} + +static const struct wp_color_manager_v1_interface manager_impl; + +static struct wlr_color_manager_v1 *manager_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wp_color_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void manager_handle_get_output(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *output_resource) { + struct wlr_color_manager_v1 *manager = manager_from_resource(manager_resource); + struct wlr_output *output = wlr_output_from_resource(output_resource); + + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *cm_output_resource = wl_resource_create(client, + &wp_color_management_output_v1_interface, version, id); + if (!cm_output_resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(cm_output_resource, &cm_output_impl, + NULL, cm_output_handle_resource_destroy); + + if (output == NULL) { + return; // leave the wp_color_management_output_v1 resource inert + } + + struct wlr_color_management_output_v1 *cm_output = calloc(1, sizeof(*cm_output)); + if (cm_output == NULL) { + wl_client_post_no_memory(client); + return; + } + + cm_output->resource = cm_output_resource; + cm_output->manager = manager; + cm_output->output = output; + + cm_output->output_destroy.notify = cm_output_handle_output_destroy; + wl_signal_add(&output->events.destroy, &cm_output->output_destroy); + + wl_list_insert(&manager->outputs, &cm_output->link); + wl_resource_set_user_data(cm_output->resource, cm_output); +} + +static struct wlr_color_management_surface_v1 *cm_surface_from_surface(struct wlr_surface *surface) { + struct wlr_addon *addon = wlr_addon_find(&surface->addons, NULL, &cm_surface_addon_impl); + if (addon == NULL) { + return NULL; + } + struct wlr_color_management_surface_v1 *cm_surface = wl_container_of(addon, cm_surface, addon); + return cm_surface; +} + +static void manager_handle_get_surface(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_color_manager_v1 *manager = manager_from_resource(manager_resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + if (cm_surface_from_surface(surface) != NULL) { + wl_resource_post_error(manager_resource, + WP_COLOR_MANAGER_V1_ERROR_SURFACE_EXISTS, + "wp_color_management_surface_v1 already constructed for this surface"); + return; + } + + struct wlr_color_management_surface_v1 *cm_surface = calloc(1, sizeof(*cm_surface)); + if (cm_surface == NULL) { + wl_client_post_no_memory(client); + return; + } + + if (!wlr_surface_synced_init(&cm_surface->synced, surface, &cm_surface_synced_impl, + &cm_surface->pending, &cm_surface->current)) { + wl_client_post_no_memory(client); + free(cm_surface); + return; + } + + uint32_t version = wl_resource_get_version(manager_resource); + cm_surface->resource = wl_resource_create(client, + &wp_color_management_surface_v1_interface, version, id); + if (!cm_surface->resource) { + wl_client_post_no_memory(client); + wlr_surface_synced_finish(&cm_surface->synced); + free(cm_surface); + return; + } + wl_resource_set_implementation(cm_surface->resource, &cm_surface_impl, cm_surface, cm_surface_handle_resource_destroy); + + cm_surface->manager = manager; + cm_surface->surface = surface; + + wlr_addon_init(&cm_surface->addon, &surface->addons, NULL, &cm_surface_addon_impl); +} + +static void manager_handle_get_surface_feedback(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *surface_resource) { + struct wlr_color_manager_v1 *manager = manager_from_resource(manager_resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + struct wlr_color_management_surface_feedback_v1 *surface_feedback = + calloc(1, sizeof(*surface_feedback)); + if (surface_feedback == NULL) { + wl_client_post_no_memory(client); + return; + } + + surface_feedback->manager = manager; + + uint32_t version = wl_resource_get_version(manager_resource); + surface_feedback->resource = wl_resource_create(client, + &wp_color_management_surface_feedback_v1_interface, version, id); + if (!surface_feedback->resource) { + wl_client_post_no_memory(client); + free(surface_feedback); + return; + } + wl_resource_set_implementation(surface_feedback->resource, &surface_feedback_impl, + surface_feedback, surface_feedback_handle_resource_destroy); + + surface_feedback->surface = surface; + surface_feedback->data = (struct wlr_image_description_v1_data){ + .tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22, + .primaries_named = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB, + }; + + surface_feedback->surface_destroy.notify = surface_feedback_handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &surface_feedback->surface_destroy); + + wl_list_insert(&manager->surface_feedbacks, &surface_feedback->link); +} + +static void manager_handle_create_icc_creator(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id) { + wl_resource_post_error(manager_resource, + WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, + "new_icc_creator is not supported"); +} + +static void manager_handle_create_parametric_creator(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id) { + struct wlr_color_manager_v1 *manager = manager_from_resource(manager_resource); + if (!manager->features.parametric) { + wl_resource_post_error(manager_resource, + WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, + "new_parametric_creator is not supported"); + return; + } + + struct wlr_image_description_creator_params_v1 *params = calloc(1, sizeof(*params)); + if (params == NULL) { + wl_client_post_no_memory(client); + return; + } + + params->manager = manager; + + uint32_t version = wl_resource_get_version(manager_resource); + params->resource = wl_resource_create(client, + &wp_image_description_creator_params_v1_interface, version, id); + if (!params->resource) { + wl_client_post_no_memory(client); + free(params); + return; + } + wl_resource_set_implementation(params->resource, &image_desc_creator_params_impl, + params, image_desc_creator_params_handle_resource_destroy); +} + +static void manager_handle_create_windows_scrgb(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id) { + wl_resource_post_error(manager_resource, + WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, + "get_windows_scrgb is not supported"); +} + +static const struct wp_color_manager_v1_interface manager_impl = { + .destroy = resource_handle_destroy, + .get_output = manager_handle_get_output, + .get_surface = manager_handle_get_surface, + .get_surface_feedback = manager_handle_get_surface_feedback, + .create_icc_creator = manager_handle_create_icc_creator, + .create_parametric_creator = manager_handle_create_parametric_creator, + .create_windows_scrgb = manager_handle_create_windows_scrgb, +}; + +static void manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_color_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &wp_color_manager_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, NULL); + + const bool features[] = { + [WP_COLOR_MANAGER_V1_FEATURE_ICC_V2_V4] = manager->features.icc_v2_v4, + [WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC] = manager->features.parametric, + [WP_COLOR_MANAGER_V1_FEATURE_SET_PRIMARIES] = manager->features.set_primaries, + [WP_COLOR_MANAGER_V1_FEATURE_SET_TF_POWER] = manager->features.set_tf_power, + [WP_COLOR_MANAGER_V1_FEATURE_SET_LUMINANCES] = manager->features.set_luminances, + [WP_COLOR_MANAGER_V1_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES] = manager->features.set_mastering_display_primaries, + [WP_COLOR_MANAGER_V1_FEATURE_EXTENDED_TARGET_VOLUME] = manager->features.extended_target_volume, + [WP_COLOR_MANAGER_V1_FEATURE_WINDOWS_SCRGB] = manager->features.windows_scrgb, + }; + + for (uint32_t i = 0; i < sizeof(features) / sizeof(features[0]); i++) { + if (features[i]) { + wp_color_manager_v1_send_supported_feature(resource, i); + } + } + for (size_t i = 0; i < manager->render_intents_len; i++) { + wp_color_manager_v1_send_supported_intent(resource, + manager->render_intents[i]); + } + for (size_t i = 0; i < manager->transfer_functions_len; i++) { + wp_color_manager_v1_send_supported_tf_named(resource, + manager->transfer_functions[i]); + } + for (size_t i = 0; i < manager->primaries_len; i++) { + wp_color_manager_v1_send_supported_primaries_named(resource, + manager->primaries[i]); + } + + wp_color_manager_v1_send_done(resource); +} + +static void manager_handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_color_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, NULL); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager->render_intents); + free(manager->transfer_functions); + free(manager->primaries); + free(manager); +} + +struct wlr_color_manager_v1 *wlr_color_manager_v1_create(struct wl_display *display, + uint32_t version, const struct wlr_color_manager_v1_options *options) { + assert(version <= COLOR_MANAGEMENT_V1_VERSION); + + bool has_perceptual_render_intent = false; + for (size_t i = 0; i < options->render_intents_len; i++) { + if (options->render_intents[i] == WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL) { + has_perceptual_render_intent = true; + } + } + assert(has_perceptual_render_intent); + + // TODO: add support for all of these features + assert(!options->features.icc_v2_v4); + assert(!options->features.set_primaries); + assert(!options->features.set_tf_power); + assert(!options->features.set_luminances); + assert(!options->features.extended_target_volume); + assert(!options->features.windows_scrgb); + + struct wlr_color_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->features = options->features; + + bool ok = + memdup(&manager->render_intents, options->render_intents, sizeof(options->render_intents[0]) * options->render_intents_len) && + memdup(&manager->transfer_functions, options->transfer_functions, sizeof(options->transfer_functions[0]) * options->transfer_functions_len) && + memdup(&manager->primaries, options->primaries, sizeof(options->primaries[0]) * options->primaries_len); + if (!ok) { + goto err_options; + } + + manager->render_intents_len = options->render_intents_len; + manager->transfer_functions_len = options->transfer_functions_len; + manager->primaries_len = options->primaries_len; + + wl_signal_init(&manager->events.destroy); + wl_list_init(&manager->outputs); + wl_list_init(&manager->surface_feedbacks); + + manager->global = wl_global_create(display, &wp_color_manager_v1_interface, + version, manager, manager_bind); + if (manager->global == NULL) { + goto err_options; + } + + manager->display_destroy.notify = manager_handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; + +err_options: + free(manager->render_intents); + free(manager->transfer_functions); + free(manager->primaries); + free(manager); + return NULL; +} + +const struct wlr_image_description_v1_data * +wlr_surface_get_image_description_v1_data(struct wlr_surface *surface) { + struct wlr_color_management_surface_v1 *cm_surface = cm_surface_from_surface(surface); + if (cm_surface == NULL || !cm_surface->current.has_image_desc_data) { + return NULL; + } + return &cm_surface->current.image_desc_data; +} + +void wlr_color_manager_v1_set_surface_preferred_image_description( + struct wlr_color_manager_v1 *manager, struct wlr_surface *surface, + const struct wlr_image_description_v1_data *data) { + // TODO: de-duplicate identity + uint32_t identity = ++manager->last_image_desc_identity; + + struct wlr_color_management_surface_feedback_v1 *surface_feedback; + wl_list_for_each(surface_feedback, &manager->surface_feedbacks, link) { + if (surface_feedback->surface == surface) { + surface_feedback->data = *data; + wp_color_management_surface_feedback_v1_send_preferred_changed( + surface_feedback->resource, identity); + } + } +} + +enum wlr_color_transfer_function +wlr_color_manager_v1_transfer_function_to_wlr(enum wp_color_manager_v1_transfer_function tf) { + switch (tf) { + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB: + return WLR_COLOR_TRANSFER_FUNCTION_SRGB; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ: + return WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR: + return WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22: + return WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886: + return WLR_COLOR_TRANSFER_FUNCTION_BT1886; + default: + abort(); + } +} + +enum wp_color_manager_v1_transfer_function +wlr_color_manager_v1_transfer_function_from_wlr(enum wlr_color_transfer_function tf) { + switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_SRGB: + return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB; + case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: + return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ; + case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: + return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR; + case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: + return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22; + case WLR_COLOR_TRANSFER_FUNCTION_BT1886: + return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886; + } + abort(); +} + +enum wlr_color_named_primaries +wlr_color_manager_v1_primaries_to_wlr(enum wp_color_manager_v1_primaries primaries) { + switch (primaries) { + case WP_COLOR_MANAGER_V1_PRIMARIES_SRGB: + return WLR_COLOR_NAMED_PRIMARIES_SRGB; + case WP_COLOR_MANAGER_V1_PRIMARIES_BT2020: + return WLR_COLOR_NAMED_PRIMARIES_BT2020; + default: + abort(); + } +} + +enum wp_color_manager_v1_primaries +wlr_color_manager_v1_primaries_from_wlr(enum wlr_color_named_primaries primaries) { + switch (primaries) { + case WLR_COLOR_NAMED_PRIMARIES_SRGB: + return WP_COLOR_MANAGER_V1_PRIMARIES_SRGB; + case WLR_COLOR_NAMED_PRIMARIES_BT2020: + return WP_COLOR_MANAGER_V1_PRIMARIES_BT2020; + } + abort(); +} diff --git a/types/wlr_color_representation_v1.c b/types/wlr_color_representation_v1.c new file mode 100644 index 000000000..6590ec4e7 --- /dev/null +++ b/types/wlr_color_representation_v1.c @@ -0,0 +1,410 @@ +#include +#include + +#include +#include +#include +#include + +#include "color-representation-v1-protocol.h" +#include "util/mem.h" + +#define WP_COLOR_REPRESENTATION_VERSION 1 + +enum wlr_alpha_mode wlr_color_representation_v1_alpha_mode_to_wlr( + enum wp_color_representation_surface_v1_alpha_mode wp_val) { + switch (wp_val) { + case WP_COLOR_REPRESENTATION_SURFACE_V1_ALPHA_MODE_PREMULTIPLIED_ELECTRICAL: + return WLR_COLOR_ALPHA_MODE_PREMULTIPLIED_ELECTRICAL; + case WP_COLOR_REPRESENTATION_SURFACE_V1_ALPHA_MODE_PREMULTIPLIED_OPTICAL: + return WLR_COLOR_ALPHA_MODE_PREMULTIPLIED_OPTICAL; + case WP_COLOR_REPRESENTATION_SURFACE_V1_ALPHA_MODE_STRAIGHT: + return WLR_COLOR_ALPHA_MODE_STRAIGHT; + } + abort(); // unreachable +} + +enum wlr_color_encoding wlr_color_representation_v1_color_encoding_to_wlr( + enum wp_color_representation_surface_v1_coefficients wp_val) { + switch (wp_val) { + case WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_IDENTITY: + return WLR_COLOR_ENCODING_IDENTITY; + case WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT709: + return WLR_COLOR_ENCODING_BT709; + case WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_FCC: + return WLR_COLOR_ENCODING_FCC; + case WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT601: + return WLR_COLOR_ENCODING_BT601; + case WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_SMPTE240: + return WLR_COLOR_ENCODING_SMPTE240; + case WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT2020: + return WLR_COLOR_ENCODING_BT2020; + case WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT2020_CL: + return WLR_COLOR_ENCODING_BT2020_CL; + case WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_ICTCP: + return WLR_COLOR_ENCODING_ICTCP; + } + abort(); // unreachable +} + +enum wlr_color_range wlr_color_representation_v1_color_range_to_wlr( + enum wp_color_representation_surface_v1_range wp_val) { + switch (wp_val) { + case WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_LIMITED: + return WLR_COLOR_RANGE_LIMITED; + case WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_FULL: + return WLR_COLOR_RANGE_FULL; + } + abort(); // unreachable +} + +enum wlr_color_chroma_location wlr_color_representation_v1_chroma_location_to_wlr( + enum wp_color_representation_surface_v1_chroma_location wp_val) { + switch (wp_val) { + case WP_COLOR_REPRESENTATION_SURFACE_V1_CHROMA_LOCATION_TYPE_0: + return WLR_COLOR_CHROMA_LOCATION_TYPE0; + case WP_COLOR_REPRESENTATION_SURFACE_V1_CHROMA_LOCATION_TYPE_1: + return WLR_COLOR_CHROMA_LOCATION_TYPE1; + case WP_COLOR_REPRESENTATION_SURFACE_V1_CHROMA_LOCATION_TYPE_2: + return WLR_COLOR_CHROMA_LOCATION_TYPE2; + case WP_COLOR_REPRESENTATION_SURFACE_V1_CHROMA_LOCATION_TYPE_3: + return WLR_COLOR_CHROMA_LOCATION_TYPE3; + case WP_COLOR_REPRESENTATION_SURFACE_V1_CHROMA_LOCATION_TYPE_4: + return WLR_COLOR_CHROMA_LOCATION_TYPE4; + case WP_COLOR_REPRESENTATION_SURFACE_V1_CHROMA_LOCATION_TYPE_5: + return WLR_COLOR_CHROMA_LOCATION_TYPE5; + } + abort(); // unreachable +} + +struct wlr_color_representation_v1 { + struct wl_resource *resource; + struct wlr_surface *surface; + + struct wlr_color_representation_manager_v1 *manager; + + // Associate the wlr_color_representation_v1 with a wlr_surface + struct wlr_addon addon; + + struct wlr_surface_synced synced; + struct wlr_color_representation_v1_surface_state pending, current; +}; + +static const struct wp_color_representation_surface_v1_interface color_repr_impl; + +static struct wlr_color_representation_v1 *color_repr_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &wp_color_representation_surface_v1_interface, + &color_repr_impl)); + return wl_resource_get_user_data(resource); +} + +static void color_repr_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + // Actual destroying is done by the resource-destroy handler + wl_resource_destroy(resource); +} + +static void color_repr_handle_set_alpha_mode(struct wl_client *client, + struct wl_resource *resource, uint32_t alpha_mode) { + struct wlr_color_representation_v1 *color_repr = + color_repr_from_resource(resource); + if (color_repr == NULL) { + wl_resource_post_error(resource, WP_COLOR_REPRESENTATION_SURFACE_V1_ERROR_INERT, + "Associated surface has been destroyed, object is inert"); + return; + } + + bool found = false; + for (size_t i = 0; i < color_repr->manager->supported_alpha_modes_len; i++) { + if (color_repr->manager->supported_alpha_modes[i] == alpha_mode) { + found = true; + break; + } + } + if (!found) { + wl_resource_post_error(resource, + WP_COLOR_REPRESENTATION_SURFACE_V1_ERROR_ALPHA_MODE, + "Unsupported alpha mode"); + return; + } + + color_repr->pending.alpha_mode = alpha_mode; +} + +static void color_repr_handle_set_coefficients_and_range(struct wl_client *client, + struct wl_resource *resource, uint32_t coefficients, + uint32_t range) { + struct wlr_color_representation_v1 *color_repr = + color_repr_from_resource(resource); + if (color_repr == NULL) { + wl_resource_post_error(resource, WP_COLOR_REPRESENTATION_SURFACE_V1_ERROR_INERT, + "Associated surface has been destroyed, object is inert"); + return; + } + + bool found = false; + for (size_t i = 0; i < color_repr->manager->supported_coeffs_and_ranges_len; i++) { + struct wlr_color_representation_v1_coeffs_and_range *supported = + &color_repr->manager->supported_coeffs_and_ranges[i]; + if (supported->coeffs == coefficients && supported->range == range) { + found = true; + break; + } + } + if (!found) { + wl_resource_post_error(resource, + WP_COLOR_REPRESENTATION_SURFACE_V1_ERROR_COEFFICIENTS, + "Unsupported coefficients/range pair"); + return; + } + + color_repr->pending.coefficients = coefficients; + color_repr->pending.range = range; +} + +static void color_repr_handle_set_chroma_location(struct wl_client *client, + struct wl_resource *resource, uint32_t chroma_location) { + struct wlr_color_representation_v1 *color_repr = + color_repr_from_resource(resource); + if (color_repr == NULL) { + wl_resource_post_error(resource, WP_COLOR_REPRESENTATION_SURFACE_V1_ERROR_INERT, + "Associated surface has been destroyed, object is inert"); + return; + } + + uint32_t version = wl_resource_get_version(resource); + if (!wp_color_representation_surface_v1_chroma_location_is_valid( + version, chroma_location)) { + wlr_log(WLR_ERROR, "Client sent chroma location which isn't a valid enum value"); + // TODO: Post actual error once + // https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/429 + // is merged and wlroots depends on a new enough wayland-protocols. + wl_client_post_implementation_error(resource->client, + "Chroma location is not a valid enum value"); + return; + } + + // In this protocol there's no concept of supported chroma locations + // from a client point-of-view. The compositor should just ignore any + // chroma locations it doesn't know what to do with. + + color_repr->pending.chroma_location = chroma_location; +} + +static const struct wp_color_representation_surface_v1_interface color_repr_impl = { + .destroy = color_repr_handle_destroy, + .set_alpha_mode = color_repr_handle_set_alpha_mode, + .set_coefficients_and_range = color_repr_handle_set_coefficients_and_range, + .set_chroma_location = color_repr_handle_set_chroma_location, +}; + +static void color_repr_destroy(struct wlr_color_representation_v1 *color_repr) { + if (color_repr == NULL) { + return; + } + wlr_surface_synced_finish(&color_repr->synced); + wlr_addon_finish(&color_repr->addon); + wl_resource_set_user_data(color_repr->resource, NULL); + free(color_repr); +} + +static void color_repr_addon_destroy(struct wlr_addon *addon) { + struct wlr_color_representation_v1 *color_repr = + wl_container_of(addon, color_repr, addon); + color_repr_destroy(color_repr); +} + +static const struct wlr_addon_interface surface_addon_impl = { + .name = "wlr_color_representation_v1", + .destroy = color_repr_addon_destroy, +}; + +static void color_repr_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_color_representation_v1 *color_repr = + color_repr_from_resource(resource); + color_repr_destroy(color_repr); +} + +static void color_repr_manager_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + // Actual destroying is done by the resource-destroy handler + wl_resource_destroy(resource); +} + +static const struct wlr_surface_synced_impl surface_synced_impl = { + .state_size = sizeof(struct wlr_color_representation_v1_surface_state), +}; + +static struct wlr_color_representation_v1 *color_repr_from_surface( + struct wlr_surface *surface) { + struct wlr_addon *addon = wlr_addon_find(&surface->addons, NULL, &surface_addon_impl); + if (addon == NULL) { + return NULL; + } + struct wlr_color_representation_v1 *color_repr = wl_container_of(addon, color_repr, addon); + return color_repr; +} + +static const struct wp_color_representation_manager_v1_interface color_repr_manager_impl; + +static struct wlr_color_representation_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &wp_color_representation_manager_v1_interface, + &color_repr_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void color_repr_manager_handle_get_surface(struct wl_client *client, + struct wl_resource *manager_resource, + uint32_t color_repr_id, + struct wl_resource *surface_resource) { + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + // Check if there's already a color-representation attached to + // this surface + if (color_repr_from_surface(surface) != NULL) { + wl_resource_post_error(manager_resource, + WP_COLOR_REPRESENTATION_MANAGER_V1_ERROR_SURFACE_EXISTS, + "wp_color_representation_surface_v1 already exists for this surface"); + return; + } + + struct wlr_color_representation_v1 *color_repr = calloc(1, sizeof(*color_repr)); + if (!color_repr) { + wl_resource_post_no_memory(manager_resource); + return; + } + + color_repr->manager = manager_from_resource(manager_resource); + + if (!wlr_surface_synced_init(&color_repr->synced, surface, + &surface_synced_impl, &color_repr->pending, &color_repr->current)) { + free(color_repr); + wl_resource_post_no_memory(manager_resource); + return; + } + + uint32_t version = wl_resource_get_version(manager_resource); + color_repr->resource = wl_resource_create(client, + &wp_color_representation_surface_v1_interface, version, color_repr_id); + if (color_repr->resource == NULL) { + wlr_surface_synced_finish(&color_repr->synced); + free(color_repr); + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_implementation(color_repr->resource, + &color_repr_impl, color_repr, color_repr_handle_resource_destroy); + + wlr_addon_init(&color_repr->addon, &surface->addons, NULL, &surface_addon_impl); +} + +static const struct wp_color_representation_manager_v1_interface color_repr_manager_impl = { + .destroy = color_repr_manager_handle_destroy, + .get_surface = color_repr_manager_handle_get_surface, +}; + +static void send_supported(struct wlr_color_representation_manager_v1 *manager, + struct wl_resource *resource) { + for (size_t i = 0; i < manager->supported_alpha_modes_len; i++) { + wp_color_representation_manager_v1_send_supported_alpha_mode( + resource, manager->supported_alpha_modes[i]); + } + + for (size_t i = 0; i < manager->supported_coeffs_and_ranges_len; i++) { + struct wlr_color_representation_v1_coeffs_and_range *supported = + &manager->supported_coeffs_and_ranges[i]; + wp_color_representation_manager_v1_send_supported_coefficients_and_ranges( + resource, supported->coeffs, supported->range); + } + + // Note that there is no event for supported chroma locations in the + // v1 protocol. + + wp_color_representation_manager_v1_send_done(resource); +} + +static void manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_color_representation_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &wp_color_representation_manager_v1_interface, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &color_repr_manager_impl, manager, NULL); + + send_supported(manager, resource); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_color_representation_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + + wl_signal_emit_mutable(&manager->events.destroy, NULL); + + assert(wl_list_empty(&manager->events.destroy.listener_list)); + + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_color_representation_manager_v1 *wlr_color_representation_manager_v1_create( + struct wl_display *display, uint32_t version, + const struct wlr_color_representation_v1_options *options) { + assert(version <= WP_COLOR_REPRESENTATION_VERSION); + + struct wlr_color_representation_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + bool ok = true; + ok &= memdup(&manager->supported_alpha_modes, + options->supported_alpha_modes, + sizeof(options->supported_alpha_modes[0]) * options->supported_alpha_modes_len); + manager->supported_alpha_modes_len = options->supported_alpha_modes_len; + ok &= memdup(&manager->supported_coeffs_and_ranges, + options->supported_coeffs_and_ranges, + sizeof(options->supported_coeffs_and_ranges[0]) * options->supported_coeffs_and_ranges_len); + manager->supported_coeffs_and_ranges_len = options->supported_coeffs_and_ranges_len; + if (!ok) { + goto err_options; + } + + manager->global = wl_global_create(display, + &wp_color_representation_manager_v1_interface, + version, manager, manager_bind); + if (manager->global == NULL) { + goto err_options; + } + + wl_signal_init(&manager->events.destroy); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; + +err_options: + free(manager->supported_alpha_modes); + free(manager->supported_coeffs_and_ranges); + free(manager); + return NULL; +} + +const struct wlr_color_representation_v1_surface_state *wlr_color_representation_v1_get_surface_state( + struct wlr_surface *surface) { + struct wlr_color_representation_v1 *color_repr = color_repr_from_surface(surface); + if (color_repr == NULL) { + return NULL; + } + return &color_repr->current; +} diff --git a/types/wlr_compositor.c b/types/wlr_compositor.c index c5044ce44..6b31ab857 100644 --- a/types/wlr_compositor.c +++ b/types/wlr_compositor.c @@ -415,7 +415,7 @@ static void surface_apply_damage(struct wlr_surface *surface) { return; } - surface->opaque = buffer_is_opaque(surface->current.buffer); + surface->opaque = wlr_buffer_is_opaque(surface->current.buffer); if (surface->buffer != NULL) { if (wlr_client_buffer_apply_damage(surface->buffer, @@ -552,6 +552,13 @@ static void surface_commit_state(struct wlr_surface *surface, surface->pending.seq++; } + struct wlr_surface_synced *synced; + wl_list_for_each(synced, &surface->synced, link) { + if (synced->impl->commit) { + synced->impl->commit(synced); + } + } + if (surface->role != NULL && surface->role->commit != NULL && (surface->role_resource != NULL || surface->role->no_object)) { surface->role->commit(surface); @@ -718,17 +725,18 @@ static void surface_destroy_role_object(struct wlr_surface *surface); static void surface_handle_resource_destroy(struct wl_resource *resource) { struct wlr_surface *surface = wlr_surface_from_resource(resource); - struct wlr_surface_output *surface_output, *surface_output_tmp; - wl_list_for_each_safe(surface_output, surface_output_tmp, - &surface->current_outputs, link) { - surface_output_destroy(surface_output); - } - surface_destroy_role_object(surface); wl_signal_emit_mutable(&surface->events.destroy, surface); - wlr_addon_set_finish(&surface->addons); + + assert(wl_list_empty(&surface->events.client_commit.listener_list)); + assert(wl_list_empty(&surface->events.commit.listener_list)); + assert(wl_list_empty(&surface->events.map.listener_list)); + assert(wl_list_empty(&surface->events.unmap.listener_list)); + assert(wl_list_empty(&surface->events.destroy.listener_list)); + assert(wl_list_empty(&surface->events.new_subsurface.listener_list)); + assert(wl_list_empty(&surface->synced)); struct wlr_surface_state *cached, *cached_tmp; @@ -748,6 +756,13 @@ static void surface_handle_resource_destroy(struct wl_resource *resource) { if (surface->buffer != NULL) { wlr_buffer_unlock(&surface->buffer->base); } + + struct wlr_surface_output *surface_output, *surface_output_tmp; + wl_list_for_each_safe(surface_output, surface_output_tmp, + &surface->current_outputs, link) { + surface_output_destroy(surface_output); + } + free(surface); } @@ -782,6 +797,7 @@ static struct wlr_surface *surface_create(struct wl_client *client, wl_signal_init(&surface->events.unmap); wl_signal_init(&surface->events.destroy); wl_signal_init(&surface->events.new_subsurface); + wl_list_init(&surface->current_outputs); wl_list_init(&surface->cached); pixman_region32_init(&surface->buffer_damage); @@ -828,6 +844,11 @@ void wlr_surface_map(struct wlr_surface *surface) { subsurface_consider_map(subsurface); } + if (surface->role != NULL && surface->role->map != NULL && + (surface->role_resource != NULL || surface->role->no_object)) { + surface->role->map(surface); + } + wl_signal_emit_mutable(&surface->events.map, NULL); } @@ -861,11 +882,7 @@ void wlr_surface_reject_pending(struct wlr_surface *surface, struct wl_resource va_list args; va_start(args, msg); - // XXX: libwayland could expose wl_resource_post_error_vargs() instead - char buffer[128]; // Matches the size of the buffer used in libwayland - vsnprintf(buffer, sizeof(buffer), msg, args); - - wl_resource_post_error(resource, code, "%s", buffer); + wl_resource_post_error_vargs(resource, code, msg, args); surface->pending_rejected = true; va_end(args); @@ -1175,7 +1192,7 @@ static void handle_bounding_box_surface(struct wlr_surface *surface, acc->max_y = max(y + surface->current.height, acc->max_y); } -void wlr_surface_get_extends(struct wlr_surface *surface, struct wlr_box *box) { +void wlr_surface_get_extents(struct wlr_surface *surface, struct wlr_box *box) { struct bound_acc acc = { .min_x = 0, .min_y = 0, @@ -1332,6 +1349,10 @@ static void compositor_handle_display_destroy( struct wlr_compositor *compositor = wl_container_of(listener, compositor, display_destroy); wl_signal_emit_mutable(&compositor->events.destroy, NULL); + + assert(wl_list_empty(&compositor->events.new_surface.listener_list)); + assert(wl_list_empty(&compositor->events.destroy.listener_list)); + wl_list_remove(&compositor->display_destroy.link); wl_list_remove(&compositor->renderer_destroy.link); wl_global_destroy(compositor->global); @@ -1363,6 +1384,7 @@ struct wlr_compositor *wlr_compositor_create(struct wl_display *display, wl_signal_init(&compositor->events.new_surface); wl_signal_init(&compositor->events.destroy); + wl_list_init(&compositor->renderer_destroy.link); compositor->display_destroy.notify = compositor_handle_display_destroy; diff --git a/types/wlr_content_type_v1.c b/types/wlr_content_type_v1.c index 0c59859df..d3302aef1 100644 --- a/types/wlr_content_type_v1.c +++ b/types/wlr_content_type_v1.c @@ -3,6 +3,8 @@ #include #include +#include "content-type-v1-protocol.h" + #define CONTENT_TYPE_VERSION 1 struct wlr_content_type_v1_surface { diff --git a/types/wlr_cursor.c b/types/wlr_cursor.c index 77ab2fb70..5f4aac398 100644 --- a/types/wlr_cursor.c +++ b/types/wlr_cursor.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -246,6 +247,34 @@ static void cursor_reset_image(struct wlr_cursor *cur) { } void wlr_cursor_destroy(struct wlr_cursor *cur) { + // pointer signals + assert(wl_list_empty(&cur->events.motion.listener_list)); + assert(wl_list_empty(&cur->events.motion_absolute.listener_list)); + assert(wl_list_empty(&cur->events.button.listener_list)); + assert(wl_list_empty(&cur->events.axis.listener_list)); + assert(wl_list_empty(&cur->events.frame.listener_list)); + assert(wl_list_empty(&cur->events.swipe_begin.listener_list)); + assert(wl_list_empty(&cur->events.swipe_update.listener_list)); + assert(wl_list_empty(&cur->events.swipe_end.listener_list)); + assert(wl_list_empty(&cur->events.pinch_begin.listener_list)); + assert(wl_list_empty(&cur->events.pinch_update.listener_list)); + assert(wl_list_empty(&cur->events.pinch_end.listener_list)); + assert(wl_list_empty(&cur->events.hold_begin.listener_list)); + assert(wl_list_empty(&cur->events.hold_end.listener_list)); + + // touch signals + assert(wl_list_empty(&cur->events.touch_up.listener_list)); + assert(wl_list_empty(&cur->events.touch_down.listener_list)); + assert(wl_list_empty(&cur->events.touch_motion.listener_list)); + assert(wl_list_empty(&cur->events.touch_cancel.listener_list)); + assert(wl_list_empty(&cur->events.touch_frame.listener_list)); + + // tablet tool signals + assert(wl_list_empty(&cur->events.tablet_tool_tip.listener_list)); + assert(wl_list_empty(&cur->events.tablet_tool_axis.listener_list)); + assert(wl_list_empty(&cur->events.tablet_tool_button.listener_list)); + assert(wl_list_empty(&cur->events.tablet_tool_proximity.listener_list)); + cursor_reset_image(cur); cursor_detach_output_layout(cur); @@ -501,10 +530,6 @@ static void cursor_output_cursor_update(struct wlr_cursor_output_cursor *output_ struct wlr_cursor *cur = output_cursor->cursor; struct wlr_output *output = output_cursor->output_cursor->output; - if (!output->enabled) { - return; - } - cursor_output_cursor_reset_image(output_cursor); if (cur->state->buffer != NULL) { @@ -534,7 +559,7 @@ static void cursor_output_cursor_update(struct wlr_cursor_output_cursor *output_ output_cursor_set_texture(output_cursor->output_cursor, texture, true, &src_box, dst_width, dst_height, WL_OUTPUT_TRANSFORM_NORMAL, - hotspot_x, hotspot_y); + hotspot_x, hotspot_y, NULL, 0); } else if (cur->state->surface != NULL) { struct wlr_surface *surface = cur->state->surface; @@ -547,9 +572,25 @@ static void cursor_output_cursor_update(struct wlr_cursor_output_cursor *output_ int dst_width = surface->current.width; int dst_height = surface->current.height; + struct wlr_linux_drm_syncobj_surface_v1_state *syncobj_surface_state = + wlr_linux_drm_syncobj_v1_get_surface_state(surface); + struct wlr_drm_syncobj_timeline *wait_timeline = NULL; + uint64_t wait_point = 0; + if (syncobj_surface_state != NULL) { + wait_timeline = syncobj_surface_state->acquire_timeline; + wait_point = syncobj_surface_state->acquire_point; + } + output_cursor_set_texture(output_cursor->output_cursor, texture, false, &src_box, dst_width, dst_height, surface->current.transform, - hotspot_x, hotspot_y); + hotspot_x, hotspot_y, wait_timeline, wait_point); + + if (syncobj_surface_state != NULL && + surface->buffer != NULL && surface->buffer->source != NULL && + (surface->current.committed & WLR_SURFACE_STATE_BUFFER)) { + wlr_linux_drm_syncobj_v1_state_signal_release_with_buffer(syncobj_surface_state, + surface->buffer->source); + } if (output_cursor->output_cursor->visible) { wlr_surface_send_enter(surface, output); @@ -574,9 +615,15 @@ static void cursor_output_cursor_update(struct wlr_cursor_output_cursor *output_ wlr_xcursor_manager_load(manager, scale); struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor(manager, name, scale); if (xcursor == NULL) { - wlr_log(WLR_DEBUG, "XCursor theme is missing '%s' cursor", name); - wlr_output_cursor_set_buffer(output_cursor->output_cursor, NULL, 0, 0); - return; + /* Try the default cursor: better the wrong image than an invisible + * (and therefore practically unusable) cursor */ + wlr_log(WLR_DEBUG, "XCursor theme is missing '%s' cursor, falling back to 'default'", name); + xcursor = wlr_xcursor_manager_get_xcursor(manager, "default", scale); + if (xcursor == NULL) { + wlr_log(WLR_DEBUG, "XCursor theme is missing a 'default' cursor"); + wlr_output_cursor_set_buffer(output_cursor->output_cursor, NULL, 0, 0); + return; + } } output_cursor->xcursor = xcursor; @@ -600,7 +647,7 @@ static void output_cursor_output_handle_output_commit( struct wlr_surface *surface = output_cursor->cursor->state->surface; if (surface && output_cursor->output_cursor->visible && (event->state->committed & WLR_OUTPUT_STATE_BUFFER)) { - wlr_surface_send_frame_done(surface, event->when); + wlr_surface_send_frame_done(surface, &event->when); } } diff --git a/types/wlr_cursor_shape_v1.c b/types/wlr_cursor_shape_v1.c index 1bd1126bd..563b326e0 100644 --- a/types/wlr_cursor_shape_v1.c +++ b/types/wlr_cursor_shape_v1.c @@ -5,9 +5,11 @@ #include #include #include + +#include "cursor-shape-v1-protocol.h" #include "types/wlr_tablet_v2.h" -#define CURSOR_SHAPE_MANAGER_V1_VERSION 1 +#define CURSOR_SHAPE_MANAGER_V1_VERSION 2 struct wlr_cursor_shape_device_v1 { struct wl_resource *resource; @@ -46,7 +48,8 @@ static void device_handle_set_shape(struct wl_client *client, struct wl_resource return; } - if (shape == 0 || shape > WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT) { + uint32_t version = wl_resource_get_version(device_resource); + if (!wp_cursor_shape_device_v1_shape_is_valid(shape, version)) { wl_resource_post_error(device_resource, WP_CURSOR_SHAPE_DEVICE_V1_ERROR_INVALID_SHAPE, "Invalid shape %"PRIu32, shape); return; @@ -187,6 +190,8 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, NULL); + + assert(wl_list_empty(&manager->events.request_set_shape.listener_list)); assert(wl_list_empty(&manager->events.destroy.listener_list)); wl_global_destroy(manager->global); @@ -254,6 +259,8 @@ static const char *const shape_names[] = { [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL] = "all-scroll", [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_IN] = "zoom-in", [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT] = "zoom-out", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK] = "dnd-ask", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE] = "all-resize", }; const char *wlr_cursor_shape_v1_name(enum wp_cursor_shape_device_v1_shape shape) { diff --git a/types/wlr_damage_ring.c b/types/wlr_damage_ring.c index 2934de8b8..b5be7391b 100644 --- a/types/wlr_damage_ring.c +++ b/types/wlr_damage_ring.c @@ -9,16 +9,8 @@ #define WLR_DAMAGE_RING_MAX_RECTS 20 void wlr_damage_ring_init(struct wlr_damage_ring *ring) { - *ring = (struct wlr_damage_ring){ - .width = INT_MAX, - .height = INT_MAX, - }; - + *ring = (struct wlr_damage_ring){ 0 }; pixman_region32_init(&ring->current); - for (size_t i = 0; i < WLR_DAMAGE_RING_PREVIOUS_LEN; ++i) { - pixman_region32_init(&ring->previous[i]); - } - wl_list_init(&ring->buffers); } @@ -31,102 +23,36 @@ static void buffer_destroy(struct wlr_damage_ring_buffer *entry) { void wlr_damage_ring_finish(struct wlr_damage_ring *ring) { pixman_region32_fini(&ring->current); - for (size_t i = 0; i < WLR_DAMAGE_RING_PREVIOUS_LEN; ++i) { - pixman_region32_fini(&ring->previous[i]); - } struct wlr_damage_ring_buffer *entry, *tmp_entry; wl_list_for_each_safe(entry, tmp_entry, &ring->buffers, link) { buffer_destroy(entry); } } -void wlr_damage_ring_set_bounds(struct wlr_damage_ring *ring, - int32_t width, int32_t height) { - if (width == 0 || height == 0) { - width = INT_MAX; - height = INT_MAX; - } - - if (ring->width == width && ring->height == height) { - return; - } - - ring->width = width; - ring->height = height; - wlr_damage_ring_add_whole(ring); -} - -bool wlr_damage_ring_add(struct wlr_damage_ring *ring, +void wlr_damage_ring_add(struct wlr_damage_ring *ring, const pixman_region32_t *damage) { - pixman_region32_t clipped; - pixman_region32_init(&clipped); - pixman_region32_intersect_rect(&clipped, damage, - 0, 0, ring->width, ring->height); - bool intersects = pixman_region32_not_empty(&clipped); - if (intersects) { - pixman_region32_union(&ring->current, &ring->current, &clipped); - } - pixman_region32_fini(&clipped); - return intersects; + pixman_region32_union(&ring->current, &ring->current, damage); } -bool wlr_damage_ring_add_box(struct wlr_damage_ring *ring, +void wlr_damage_ring_add_box(struct wlr_damage_ring *ring, const struct wlr_box *box) { - struct wlr_box clipped = { - .x = 0, - .y = 0, - .width = ring->width, - .height = ring->height, - }; - if (wlr_box_intersection(&clipped, &clipped, box)) { - pixman_region32_union_rect(&ring->current, - &ring->current, clipped.x, clipped.y, - clipped.width, clipped.height); - return true; - } - return false; + pixman_region32_union_rect(&ring->current, + &ring->current, box->x, box->y, + box->width, box->height); } void wlr_damage_ring_add_whole(struct wlr_damage_ring *ring) { - pixman_region32_union_rect(&ring->current, - &ring->current, 0, 0, ring->width, ring->height); -} + int width = 0; + int height = 0; -void wlr_damage_ring_rotate(struct wlr_damage_ring *ring) { - // modular decrement - ring->previous_idx = ring->previous_idx + - WLR_DAMAGE_RING_PREVIOUS_LEN - 1; - ring->previous_idx %= WLR_DAMAGE_RING_PREVIOUS_LEN; - - pixman_region32_copy(&ring->previous[ring->previous_idx], &ring->current); - pixman_region32_clear(&ring->current); -} - -void wlr_damage_ring_get_buffer_damage(struct wlr_damage_ring *ring, - int buffer_age, pixman_region32_t *damage) { - if (buffer_age <= 0 || buffer_age - 1 > WLR_DAMAGE_RING_PREVIOUS_LEN) { - pixman_region32_clear(damage); - pixman_region32_union_rect(damage, damage, - 0, 0, ring->width, ring->height); - } else { - pixman_region32_copy(damage, &ring->current); - - // Accumulate damage from old buffers - for (int i = 0; i < buffer_age - 1; ++i) { - int j = (ring->previous_idx + i) % WLR_DAMAGE_RING_PREVIOUS_LEN; - pixman_region32_union(damage, damage, &ring->previous[j]); - } - - // Check the number of rectangles - int n_rects = pixman_region32_n_rects(damage); - if (n_rects > WLR_DAMAGE_RING_MAX_RECTS) { - pixman_box32_t *extents = pixman_region32_extents(damage); - pixman_region32_union_rect(damage, damage, - extents->x1, extents->y1, - extents->x2 - extents->x1, - extents->y2 - extents->y1); - } + struct wlr_damage_ring_buffer *entry; + wl_list_for_each(entry, &ring->buffers, link) { + width = width < entry->buffer->width ? entry->buffer->width : width; + height = height < entry->buffer->height ? entry->buffer->height : height; } + + pixman_region32_union_rect(&ring->current, + &ring->current, 0, 0, width, height); } static void entry_squash_damage(struct wlr_damage_ring_buffer *entry) { @@ -160,6 +86,8 @@ void wlr_damage_ring_rotate_buffer(struct wlr_damage_ring *ring, continue; } + pixman_region32_intersect_rect(damage, damage, 0, 0, buffer->width, buffer->height); + // Check the number of rectangles int n_rects = pixman_region32_n_rects(damage); if (n_rects > WLR_DAMAGE_RING_MAX_RECTS) { @@ -182,7 +110,7 @@ void wlr_damage_ring_rotate_buffer(struct wlr_damage_ring *ring, pixman_region32_clear(damage); pixman_region32_union_rect(damage, damage, - 0, 0, ring->width, ring->height); + 0, 0, buffer->width, buffer->height); entry = calloc(1, sizeof(*entry)); if (!entry) { diff --git a/types/wlr_data_control_v1.c b/types/wlr_data_control_v1.c index 88f23ae46..2e26d703d 100644 --- a/types/wlr_data_control_v1.c +++ b/types/wlr_data_control_v1.c @@ -666,6 +666,10 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_data_control_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.destroy.listener_list)); + assert(wl_list_empty(&manager->events.new_device.listener_list)); + wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); @@ -678,6 +682,7 @@ struct wlr_data_control_manager_v1 *wlr_data_control_manager_v1_create( return NULL; } wl_list_init(&manager->devices); + wl_signal_init(&manager->events.destroy); wl_signal_init(&manager->events.new_device); diff --git a/types/wlr_drm.c b/types/wlr_drm.c index c58cdb4ec..f8db8291b 100644 --- a/types/wlr_drm.c +++ b/types/wlr_drm.c @@ -34,11 +34,14 @@ static struct wlr_drm_buffer *drm_buffer_from_buffer( static void buffer_destroy(struct wlr_buffer *wlr_buffer) { struct wlr_drm_buffer *buffer = drm_buffer_from_buffer(wlr_buffer); + wl_list_remove(&buffer->release.link); + + wlr_buffer_finish(wlr_buffer); + if (buffer->resource != NULL) { wl_resource_set_user_data(buffer->resource, NULL); } wlr_dmabuf_attributes_finish(&buffer->dmabuf); - wl_list_remove(&buffer->release.link); free(buffer); } @@ -186,6 +189,8 @@ static const struct wlr_buffer_resource_interface buffer_resource_interface = { static void drm_destroy(struct wlr_drm *drm) { wl_signal_emit_mutable(&drm->events.destroy, NULL); + assert(wl_list_empty(&drm->events.destroy.listener_list)); + wl_list_remove(&drm->display_destroy.link); wlr_drm_format_set_finish(&drm->formats); diff --git a/types/wlr_drm_lease_v1.c b/types/wlr_drm_lease_v1.c index d3c9ba1ff..14846f21d 100644 --- a/types/wlr_drm_lease_v1.c +++ b/types/wlr_drm_lease_v1.c @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -69,10 +68,6 @@ static void drm_lease_connector_v1_destroy( wlr_log(WLR_DEBUG, "Destroying connector %s", connector->output->name); - if (connector->active_lease) { - wlr_drm_lease_terminate(connector->active_lease->drm_lease); - } - struct wl_resource *resource, *tmp; wl_resource_for_each_safe(resource, tmp, &connector->resources) { wp_drm_lease_connector_v1_send_withdrawn(resource); @@ -141,14 +136,9 @@ static void lease_handle_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&lease->destroy.link); - for (size_t i = 0; i < lease->n_connectors; ++i) { - lease->connectors[i]->active_lease = NULL; - } - wl_list_remove(&lease->link); wl_resource_set_user_data(lease->resource, NULL); - free(lease->connectors); free(lease); } @@ -181,20 +171,6 @@ struct wlr_drm_lease_v1 *wlr_drm_lease_request_v1_grant( return NULL; } - lease->connectors = calloc(request->n_connectors, sizeof(*lease->connectors)); - if (!lease->connectors) { - wlr_log(WLR_ERROR, "Failed to allocate lease connectors list"); - close(fd); - wp_drm_lease_v1_send_finished(lease->resource); - free(lease); - return NULL; - } - lease->n_connectors = request->n_connectors; - for (size_t i = 0; i < request->n_connectors; ++i) { - lease->connectors[i] = request->connectors[i]; - lease->connectors[i]->active_lease = lease; - } - lease->destroy.notify = lease_handle_destroy; wl_signal_add(&lease->drm_lease->events.destroy, &lease->destroy); @@ -339,16 +315,6 @@ static void drm_lease_request_v1_handle_submit( return; } - for (size_t i = 0; i < request->n_connectors; ++i) { - struct wlr_drm_lease_connector_v1 *conn = request->connectors[i]; - if (conn->active_lease) { - wlr_log(WLR_ERROR, "Failed to create lease, connector %s has " - "already been leased", conn->output->name); - wp_drm_lease_v1_send_finished(lease_resource); - return; - } - } - request->lease_resource = lease_resource; wl_signal_emit_mutable(&request->device->manager->events.request, @@ -441,10 +407,6 @@ static struct wp_drm_lease_connector_v1_interface lease_connector_impl = { static void drm_lease_connector_v1_send_to_client( struct wlr_drm_lease_connector_v1 *connector, struct wl_resource *resource) { - if (connector->active_lease) { - return; - } - struct wl_client *client = wl_resource_get_client(resource); uint32_t version = wl_resource_get_version(resource); @@ -491,10 +453,12 @@ static void lease_device_bind(struct wl_client *wl_client, void *data, if (!device) { wlr_log(WLR_DEBUG, "Failed to bind lease device, " "the wlr_drm_lease_device_v1 has been destroyed"); + wl_list_init(wl_resource_get_link(device_resource)); return; } wl_resource_set_user_data(device_resource, device); + wl_list_insert(&device->resources, wl_resource_get_link(device_resource)); int fd = wlr_drm_backend_get_non_master_fd(device->backend); if (fd < 0) { @@ -506,8 +470,6 @@ static void lease_device_bind(struct wl_client *wl_client, void *data, wp_drm_lease_device_v1_send_drm_fd(device_resource, fd); close(fd); - wl_list_insert(&device->resources, wl_resource_get_link(device_resource)); - struct wlr_drm_lease_connector_v1 *connector; wl_list_for_each(connector, &device->connectors, link) { drm_lease_connector_v1_send_to_client(connector, device_resource); @@ -682,6 +644,11 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { display_destroy); wlr_log(WLR_DEBUG, "Destroying wlr_drm_lease_v1_manager"); + wl_signal_emit_mutable(&manager->events.destroy, NULL); + + assert(wl_list_empty(&manager->events.destroy.listener_list)); + assert(wl_list_empty(&manager->events.request.listener_list)); + struct wlr_drm_lease_device_v1 *device, *tmp; wl_list_for_each_safe(device, tmp, &manager->devices, link) { drm_lease_device_v1_destroy(device); @@ -718,6 +685,7 @@ struct wlr_drm_lease_v1_manager *wlr_drm_lease_v1_manager_create( manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(display, &manager->display_destroy); + wl_signal_init(&manager->events.destroy); wl_signal_init(&manager->events.request); return manager; diff --git a/types/wlr_export_dmabuf_v1.c b/types/wlr_export_dmabuf_v1.c index 69245e373..4e629dcfd 100644 --- a/types/wlr_export_dmabuf_v1.c +++ b/types/wlr_export_dmabuf_v1.c @@ -85,11 +85,11 @@ static void frame_output_handle_commit(struct wl_listener *listener, attribs.fd[i], size, attribs.offset[i], attribs.stride[i], i); } - time_t tv_sec = event->when->tv_sec; + time_t tv_sec = event->when.tv_sec; uint32_t tv_sec_hi = (sizeof(tv_sec) > 4) ? tv_sec >> 32 : 0; uint32_t tv_sec_lo = tv_sec & 0xFFFFFFFF; zwlr_export_dmabuf_frame_v1_send_ready(frame->resource, - tv_sec_hi, tv_sec_lo, event->when->tv_nsec); + tv_sec_hi, tv_sec_lo, event->when.tv_nsec); frame_destroy(frame); } @@ -192,6 +192,9 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_export_dmabuf_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.destroy.listener_list)); + wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); @@ -204,6 +207,7 @@ struct wlr_export_dmabuf_manager_v1 *wlr_export_dmabuf_manager_v1_create( return NULL; } wl_list_init(&manager->frames); + wl_signal_init(&manager->events.destroy); manager->global = wl_global_create(display, diff --git a/types/wlr_ext_data_control_v1.c b/types/wlr_ext_data_control_v1.c new file mode 100644 index 000000000..0c487e674 --- /dev/null +++ b/types/wlr_ext_data_control_v1.c @@ -0,0 +1,703 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "ext-data-control-v1-protocol.h" + +#define EXT_DATA_CONTROL_MANAGER_VERSION 1 + +struct data_control_source { + struct wl_resource *resource; + struct wl_array mime_types; + bool finalized; + + // Only one of these is non-NULL. + struct wlr_data_source *active_source; + struct wlr_primary_selection_source *active_primary_source; +}; + +static const struct ext_data_control_source_v1_interface source_impl; + +static struct data_control_source *source_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_data_control_source_v1_interface, &source_impl)); + return wl_resource_get_user_data(resource); +} + +static void source_handle_offer(struct wl_client *client, + struct wl_resource *resource, const char *mime_type) { + struct data_control_source *source = source_from_resource(resource); + if (source == NULL) { + return; + } + + if (source->finalized) { + wl_resource_post_error(resource, + EXT_DATA_CONTROL_SOURCE_V1_ERROR_INVALID_OFFER, + "cannot mutate offer after set_selection or " + "set_primary_selection"); + return; + } + + const char **mime_type_ptr; + wl_array_for_each(mime_type_ptr, &source->mime_types) { + if (strcmp(*mime_type_ptr, mime_type) == 0) { + wlr_log(WLR_DEBUG, "Ignoring duplicate MIME type offer %s", + mime_type); + return; + } + } + + char *dup_mime_type = strdup(mime_type); + if (dup_mime_type == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + char **p = wl_array_add(&source->mime_types, sizeof(char *)); + if (p == NULL) { + free(dup_mime_type); + wl_resource_post_no_memory(resource); + return; + } + + *p = dup_mime_type; +} + +static void source_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct ext_data_control_source_v1_interface source_impl = { + .offer = source_handle_offer, + .destroy = source_handle_destroy, +}; + +static void data_control_source_destroy(struct data_control_source *source) { + if (source == NULL) { + return; + } + + char **p; + wl_array_for_each(p, &source->mime_types) { + free(*p); + } + wl_array_release(&source->mime_types); + + // Prevent destructors below from calling this recursively. + wl_resource_set_user_data(source->resource, NULL); + + if (source->active_source != NULL) { + wlr_data_source_destroy(source->active_source); + } else if (source->active_primary_source != NULL) { + wlr_primary_selection_source_destroy( + source->active_primary_source); + } + + free(source); +} + +static void source_handle_resource_destroy(struct wl_resource *resource) { + struct data_control_source *source = source_from_resource(resource); + data_control_source_destroy(source); +} + + +struct client_data_source { + struct wlr_data_source source; + struct wl_resource *resource; +}; + +static const struct wlr_data_source_impl client_source_impl; + +static struct client_data_source * + client_data_source_from_source(struct wlr_data_source *wlr_source) { + assert(wlr_source->impl == &client_source_impl); + struct client_data_source *source = wl_container_of(wlr_source, source, source); + return source; +} + +static void client_source_send(struct wlr_data_source *wlr_source, + const char *mime_type, int fd) { + struct client_data_source *source = + client_data_source_from_source(wlr_source); + ext_data_control_source_v1_send_send(source->resource, mime_type, fd); + close(fd); +} + +static void client_source_destroy(struct wlr_data_source *wlr_source) { + struct client_data_source *client_source = + client_data_source_from_source(wlr_source); + struct data_control_source *source = + source_from_resource(client_source->resource); + free(client_source); + + if (source == NULL) { + return; + } + + source->active_source = NULL; + + ext_data_control_source_v1_send_cancelled(source->resource); + data_control_source_destroy(source); +} + +static const struct wlr_data_source_impl client_source_impl = { + .send = client_source_send, + .destroy = client_source_destroy, +}; + + +struct client_primary_selection_source { + struct wlr_primary_selection_source source; + struct wl_resource *resource; +}; + +static const struct wlr_primary_selection_source_impl +client_primary_selection_source_impl; + +static struct client_primary_selection_source * + client_primary_selection_source_from_source( + struct wlr_primary_selection_source *wlr_source) { + assert(wlr_source->impl == &client_primary_selection_source_impl); + struct client_primary_selection_source *source = wl_container_of(wlr_source, source, source); + return source; +} + +static void client_primary_selection_source_send( + struct wlr_primary_selection_source *wlr_source, + const char *mime_type, int fd) { + struct client_primary_selection_source *source = + client_primary_selection_source_from_source(wlr_source); + ext_data_control_source_v1_send_send(source->resource, mime_type, fd); + close(fd); +} + +static void client_primary_selection_source_destroy( + struct wlr_primary_selection_source *wlr_source) { + struct client_primary_selection_source *client_source = + client_primary_selection_source_from_source(wlr_source); + struct data_control_source *source = + source_from_resource(client_source->resource); + free(client_source); + + if (source == NULL) { + return; + } + + source->active_primary_source = NULL; + + ext_data_control_source_v1_send_cancelled(source->resource); + data_control_source_destroy(source); +} + +static const struct wlr_primary_selection_source_impl +client_primary_selection_source_impl = { + .send = client_primary_selection_source_send, + .destroy = client_primary_selection_source_destroy, +}; + + +struct data_offer { + struct wl_resource *resource; + struct wlr_ext_data_control_device_v1 *device; + bool is_primary; +}; + +static void data_offer_destroy(struct data_offer *offer) { + if (offer == NULL) { + return; + } + + struct wlr_ext_data_control_device_v1 *device = offer->device; + if (device != NULL) { + if (offer->is_primary) { + device->primary_selection_offer_resource = NULL; + } else { + device->selection_offer_resource = NULL; + } + } + + wl_resource_set_user_data(offer->resource, NULL); + free(offer); +} + +static const struct ext_data_control_offer_v1_interface offer_impl; + +static struct data_offer *data_offer_from_offer_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_data_control_offer_v1_interface, &offer_impl)); + return wl_resource_get_user_data(resource); +} + +static void offer_handle_receive(struct wl_client *client, + struct wl_resource *resource, const char *mime_type, int fd) { + struct data_offer *offer = data_offer_from_offer_resource(resource); + if (offer == NULL) { + close(fd); + return; + } + + struct wlr_ext_data_control_device_v1 *device = offer->device; + if (device == NULL) { + close(fd); + return; + } + + if (offer->is_primary) { + if (device->seat->primary_selection_source == NULL) { + close(fd); + return; + } + wlr_primary_selection_source_send( + device->seat->primary_selection_source, + mime_type, fd); + } else { + if (device->seat->selection_source == NULL) { + close(fd); + return; + } + wlr_data_source_send(device->seat->selection_source, mime_type, fd); + } +} + +static void offer_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct ext_data_control_offer_v1_interface offer_impl = { + .receive = offer_handle_receive, + .destroy = offer_handle_destroy, +}; + +static void offer_handle_resource_destroy(struct wl_resource *resource) { + struct data_offer *offer = data_offer_from_offer_resource(resource); + data_offer_destroy(offer); +} + +static struct wl_resource *create_offer(struct wlr_ext_data_control_device_v1 *device, + struct wl_array *mime_types, bool is_primary) { + struct wl_client *client = wl_resource_get_client(device->resource); + + struct data_offer *offer = calloc(1, sizeof(*offer)); + if (offer == NULL) { + wl_client_post_no_memory(client); + return NULL; + } + + offer->device = device; + offer->is_primary = is_primary; + + uint32_t version = wl_resource_get_version(device->resource); + struct wl_resource *resource = wl_resource_create(client, + &ext_data_control_offer_v1_interface, version, 0); + if (resource == NULL) { + free(offer); + return NULL; + } + + offer->resource = resource; + + wl_resource_set_implementation(resource, &offer_impl, offer, + offer_handle_resource_destroy); + + ext_data_control_device_v1_send_data_offer(device->resource, resource); + + char **p; + wl_array_for_each(p, mime_types) { + ext_data_control_offer_v1_send_offer(resource, *p); + } + + return resource; +} + + +static const struct ext_data_control_device_v1_interface control_impl; + +static struct wlr_ext_data_control_device_v1 *control_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_data_control_device_v1_interface, &control_impl)); + return wl_resource_get_user_data(resource); +} + +static void control_handle_set_selection(struct wl_client *client, + struct wl_resource *control_resource, + struct wl_resource *source_resource) { + struct wlr_ext_data_control_device_v1 *device = + control_from_resource(control_resource); + if (device == NULL) { + return; + } + + struct data_control_source *source = NULL; + if (source_resource != NULL) { + source = source_from_resource(source_resource); + } + + if (source == NULL) { + wlr_seat_request_set_selection(device->seat, NULL, NULL, + wl_display_next_serial(device->seat->display)); + + return; + } + + if (source->active_source != NULL || + source->active_primary_source != NULL) { + wl_resource_post_error(control_resource, + EXT_DATA_CONTROL_DEVICE_V1_ERROR_USED_SOURCE, + "cannot use a data source in set_selection or " + "set_primary_selection more than once"); + + return; + } + + struct client_data_source *client_source = calloc(1, sizeof(*client_source)); + if (client_source == NULL) { + wl_client_post_no_memory(client); + return; + } + client_source->resource = source_resource; + + struct wlr_data_source *wlr_source = &client_source->source; + wlr_data_source_init(wlr_source, &client_source_impl); + source->active_source = wlr_source; + + wl_array_release(&wlr_source->mime_types); + wlr_source->mime_types = source->mime_types; + wl_array_init(&source->mime_types); + + source->finalized = true; + + wlr_seat_request_set_selection(device->seat, NULL, wlr_source, + wl_display_next_serial(device->seat->display)); +} + +static void control_handle_set_primary_selection(struct wl_client *client, + struct wl_resource *control_resource, + struct wl_resource *source_resource) { + struct wlr_ext_data_control_device_v1 *device = + control_from_resource(control_resource); + if (device == NULL) { + return; + } + + struct data_control_source *source = NULL; + if (source_resource != NULL) { + source = source_from_resource(source_resource); + } + + if (source == NULL) { + wlr_seat_request_set_primary_selection(device->seat, NULL, NULL, + wl_display_next_serial(device->seat->display)); + + return; + } + + if (source->active_source != NULL || + source->active_primary_source != NULL) { + wl_resource_post_error(control_resource, + EXT_DATA_CONTROL_DEVICE_V1_ERROR_USED_SOURCE, + "cannot use a data source in set_selection or " + "set_primary_selection more than once"); + + return; + } + + struct client_primary_selection_source *client_source = calloc(1, sizeof(*client_source)); + if (client_source == NULL) { + wl_client_post_no_memory(client); + return; + } + client_source->resource = source_resource; + + struct wlr_primary_selection_source *wlr_source = &client_source->source; + wlr_primary_selection_source_init(wlr_source, &client_primary_selection_source_impl); + source->active_primary_source = wlr_source; + + wl_array_release(&wlr_source->mime_types); + wlr_source->mime_types = source->mime_types; + wl_array_init(&source->mime_types); + + source->finalized = true; + + wlr_seat_request_set_primary_selection(device->seat, NULL, wlr_source, + wl_display_next_serial(device->seat->display)); +} + +static void control_handle_destroy(struct wl_client *client, + struct wl_resource *control_resource) { + wl_resource_destroy(control_resource); +} + +static const struct ext_data_control_device_v1_interface control_impl = { + .set_selection = control_handle_set_selection, + .set_primary_selection = control_handle_set_primary_selection, + .destroy = control_handle_destroy, +}; + +static void control_send_selection(struct wlr_ext_data_control_device_v1 *device) { + struct wlr_data_source *source = device->seat->selection_source; + + if (device->selection_offer_resource != NULL) { + // Make the offer inert + struct data_offer *offer = data_offer_from_offer_resource( + device->selection_offer_resource); + data_offer_destroy(offer); + } + + device->selection_offer_resource = NULL; + if (source != NULL) { + device->selection_offer_resource = + create_offer(device, &source->mime_types, false); + if (device->selection_offer_resource == NULL) { + wl_resource_post_no_memory(device->resource); + return; + } + } + + ext_data_control_device_v1_send_selection(device->resource, + device->selection_offer_resource); +} + +static void control_send_primary_selection( + struct wlr_ext_data_control_device_v1 *device) { + uint32_t version = wl_resource_get_version(device->resource); + if (version < EXT_DATA_CONTROL_DEVICE_V1_PRIMARY_SELECTION_SINCE_VERSION) { + return; + } + + struct wlr_primary_selection_source *source = + device->seat->primary_selection_source; + + if (device->primary_selection_offer_resource != NULL) { + // Make the offer inert + struct data_offer *offer = data_offer_from_offer_resource( + device->primary_selection_offer_resource); + data_offer_destroy(offer); + } + + device->primary_selection_offer_resource = NULL; + if (source != NULL) { + device->primary_selection_offer_resource = + create_offer(device, &source->mime_types, true); + if (device->primary_selection_offer_resource == NULL) { + wl_resource_post_no_memory(device->resource); + return; + } + } + + ext_data_control_device_v1_send_primary_selection(device->resource, + device->primary_selection_offer_resource); +} + +static void control_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_ext_data_control_device_v1 *device = control_from_resource(resource); + wlr_ext_data_control_device_v1_destroy(device); +} + +static void control_handle_seat_destroy(struct wl_listener *listener, + void *data) { + struct wlr_ext_data_control_device_v1 *device = + wl_container_of(listener, device, seat_destroy); + wlr_ext_data_control_device_v1_destroy(device); +} + +static void control_handle_seat_set_selection(struct wl_listener *listener, + void *data) { + struct wlr_ext_data_control_device_v1 *device = + wl_container_of(listener, device, seat_set_selection); + control_send_selection(device); +} + +static void control_handle_seat_set_primary_selection( + struct wl_listener *listener, + void *data) { + struct wlr_ext_data_control_device_v1 *device = + wl_container_of(listener, device, seat_set_primary_selection); + control_send_primary_selection(device); +} + +void wlr_ext_data_control_device_v1_destroy(struct wlr_ext_data_control_device_v1 *device) { + if (device == NULL) { + return; + } + ext_data_control_device_v1_send_finished(device->resource); + // Make the resources inert + wl_resource_set_user_data(device->resource, NULL); + if (device->selection_offer_resource != NULL) { + struct data_offer *offer = data_offer_from_offer_resource( + device->selection_offer_resource); + data_offer_destroy(offer); + } + if (device->primary_selection_offer_resource != NULL) { + struct data_offer *offer = data_offer_from_offer_resource( + device->primary_selection_offer_resource); + data_offer_destroy(offer); + } + wl_list_remove(&device->seat_destroy.link); + wl_list_remove(&device->seat_set_selection.link); + wl_list_remove(&device->seat_set_primary_selection.link); + wl_list_remove(&device->link); + free(device); +} + + +static const struct ext_data_control_manager_v1_interface manager_impl; + +static struct wlr_ext_data_control_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_data_control_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void manager_handle_create_data_source(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id) { + struct data_control_source *source = calloc(1, sizeof(*source)); + if (source == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + wl_array_init(&source->mime_types); + + uint32_t version = wl_resource_get_version(manager_resource); + source->resource = wl_resource_create(client, + &ext_data_control_source_v1_interface, version, id); + if (source->resource == NULL) { + wl_resource_post_no_memory(manager_resource); + wl_array_release(&source->mime_types); + free(source); + return; + } + wl_resource_set_implementation(source->resource, &source_impl, source, + source_handle_resource_destroy); +} + +static void manager_handle_get_data_device(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t id, + struct wl_resource *seat_resource) { + struct wlr_ext_data_control_manager_v1 *manager = + manager_from_resource(manager_resource); + struct wlr_seat_client *seat_client = + wlr_seat_client_from_resource(seat_resource); + + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *resource = wl_resource_create(client, + &ext_data_control_device_v1_interface, version, id); + if (resource == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_implementation(resource, &control_impl, NULL, + control_handle_resource_destroy); + if (seat_client == NULL) { + return; + } + + struct wlr_ext_data_control_device_v1 *device = calloc(1, sizeof(*device)); + if (device == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + device->manager = manager; + device->seat = seat_client->seat; + device->resource = resource; + wl_resource_set_user_data(resource, device); + + device->seat_destroy.notify = control_handle_seat_destroy; + wl_signal_add(&device->seat->events.destroy, &device->seat_destroy); + + device->seat_set_selection.notify = control_handle_seat_set_selection; + wl_signal_add(&device->seat->events.set_selection, + &device->seat_set_selection); + + device->seat_set_primary_selection.notify = + control_handle_seat_set_primary_selection; + wl_signal_add(&device->seat->events.set_primary_selection, + &device->seat_set_primary_selection); + + wl_list_insert(&manager->devices, &device->link); + wl_signal_emit_mutable(&manager->events.new_device, device); + + // At this point maybe the compositor decided to destroy the device. If + // it's the case then the resource will be inert. + device = control_from_resource(resource); + if (device != NULL) { + control_send_selection(device); + control_send_primary_selection(device); + } +} + +static void manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct ext_data_control_manager_v1_interface manager_impl = { + .create_data_source = manager_handle_create_data_source, + .get_data_device = manager_handle_get_data_device, + .destroy = manager_handle_destroy, +}; + +static void manager_bind(struct wl_client *client, void *data, uint32_t version, + uint32_t id) { + struct wlr_ext_data_control_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &ext_data_control_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_ext_data_control_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + + wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.destroy.listener_list)); + assert(wl_list_empty(&manager->events.new_device.listener_list)); + + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_ext_data_control_manager_v1 *wlr_ext_data_control_manager_v1_create( + struct wl_display *display, uint32_t version) { + assert(version <= EXT_DATA_CONTROL_MANAGER_VERSION); + + struct wlr_ext_data_control_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + wl_list_init(&manager->devices); + wl_signal_init(&manager->events.destroy); + wl_signal_init(&manager->events.new_device); + + manager->global = wl_global_create(display, + &ext_data_control_manager_v1_interface, + version, manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/wlr_ext_foreign_toplevel_list_v1.c b/types/wlr_ext_foreign_toplevel_list_v1.c index 6c059e64e..f20ddc877 100644 --- a/types/wlr_ext_foreign_toplevel_list_v1.c +++ b/types/wlr_ext_foreign_toplevel_list_v1.c @@ -1,4 +1,3 @@ - #include #include #include @@ -12,14 +11,18 @@ #define FOREIGN_TOPLEVEL_LIST_V1_VERSION 1 -static const struct ext_foreign_toplevel_list_v1_interface toplevel_handle_impl; +static const struct ext_foreign_toplevel_handle_v1_interface toplevel_handle_impl; static void foreign_toplevel_handle_destroy(struct wl_client *client, struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_foreign_toplevel_handle_v1_interface, + &toplevel_handle_impl)); + wl_resource_destroy(resource); } -static const struct ext_foreign_toplevel_list_v1_interface toplevel_handle_impl = { +static const struct ext_foreign_toplevel_handle_v1_interface toplevel_handle_impl = { .destroy = foreign_toplevel_handle_destroy, }; @@ -50,6 +53,13 @@ static bool update_string(struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, return true; } +struct wlr_ext_foreign_toplevel_handle_v1 *wlr_ext_foreign_toplevel_handle_v1_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &ext_foreign_toplevel_handle_v1_interface, + &toplevel_handle_impl)); + return wl_resource_get_user_data(resource); +} + void wlr_ext_foreign_toplevel_handle_v1_update_state( struct wlr_ext_foreign_toplevel_handle_v1 *toplevel, const struct wlr_ext_foreign_toplevel_handle_v1_state *state) { @@ -80,6 +90,8 @@ void wlr_ext_foreign_toplevel_handle_v1_destroy( wl_signal_emit_mutable(&toplevel->events.destroy, NULL); + assert(wl_list_empty(&toplevel->events.destroy.listener_list)); + struct wl_resource *resource, *tmp; wl_resource_for_each_safe(resource, tmp, &toplevel->resources) { ext_foreign_toplevel_handle_v1_send_closed(resource); @@ -191,12 +203,23 @@ static void foreign_toplevel_list_handle_stop(struct wl_client *client, &foreign_toplevel_list_impl)); ext_foreign_toplevel_list_v1_send_finished(resource); + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); +} + +static void foreign_toplevel_list_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_foreign_toplevel_list_v1_interface, + &foreign_toplevel_list_impl)); + wl_resource_destroy(resource); } static const struct ext_foreign_toplevel_list_v1_interface foreign_toplevel_list_impl = { - .stop = foreign_toplevel_list_handle_stop + .stop = foreign_toplevel_list_handle_stop, + .destroy = foreign_toplevel_list_handle_destroy }; static void foreign_toplevel_list_resource_destroy( @@ -231,6 +254,9 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_ext_foreign_toplevel_list_v1 *list = wl_container_of(listener, list, display_destroy); wl_signal_emit_mutable(&list->events.destroy, NULL); + + assert(wl_list_empty(&list->events.destroy.listener_list)); + wl_list_remove(&list->display_destroy.link); wl_global_destroy(list->global); free(list); @@ -255,6 +281,7 @@ struct wlr_ext_foreign_toplevel_list_v1 *wlr_ext_foreign_toplevel_list_v1_create } wl_signal_init(&list->events.destroy); + wl_list_init(&list->resources); wl_list_init(&list->toplevels); diff --git a/types/wlr_ext_image_copy_capture_v1.c b/types/wlr_ext_image_copy_capture_v1.c new file mode 100644 index 000000000..c4cfd743e --- /dev/null +++ b/types/wlr_ext_image_copy_capture_v1.c @@ -0,0 +1,689 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "ext-image-copy-capture-v1-protocol.h" +#include "render/pixel_format.h" + +#define IMAGE_COPY_CAPTURE_MANAGER_V1_VERSION 1 + +struct wlr_ext_image_copy_capture_session_v1 { + struct wl_resource *resource; + struct wlr_ext_image_capture_source_v1 *source; + struct wlr_ext_image_copy_capture_frame_v1 *frame; + + struct wl_listener source_destroy; + struct wl_listener source_constraints_update; + struct wl_listener source_frame; + + pixman_region32_t damage; +}; + +struct wlr_ext_image_copy_capture_cursor_session_v1 { + struct wl_resource *resource; + struct wlr_ext_image_capture_source_v1_cursor *source; + bool capture_session_created; + + struct { + bool entered; + int32_t x, y; + struct { + int32_t x, y; + } hotspot; + } prev; + + struct wl_listener source_destroy; + struct wl_listener source_update; +}; + +static const struct ext_image_copy_capture_frame_v1_interface frame_impl; +static const struct ext_image_copy_capture_session_v1_interface session_impl; +static const struct ext_image_copy_capture_cursor_session_v1_interface cursor_session_impl; + +static struct wlr_ext_image_copy_capture_frame_v1 *frame_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_image_copy_capture_frame_v1_interface, &frame_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_ext_image_copy_capture_session_v1 *session_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_image_copy_capture_session_v1_interface, &session_impl)); + return wl_resource_get_user_data(resource); +} + +static struct wlr_ext_image_copy_capture_cursor_session_v1 *cursor_session_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_image_copy_capture_cursor_session_v1_interface, &cursor_session_impl)); + return wl_resource_get_user_data(resource); +} + +static void frame_destroy(struct wlr_ext_image_copy_capture_frame_v1 *frame) { + if (frame == NULL) { + return; + } + wl_signal_emit_mutable(&frame->events.destroy, NULL); + assert(wl_list_empty(&frame->events.destroy.listener_list)); + wl_resource_set_user_data(frame->resource, NULL); + wlr_buffer_unlock(frame->buffer); + pixman_region32_fini(&frame->buffer_damage); + if (frame->session->frame == frame) { + frame->session->frame = NULL; + } + free(frame); +} + +static void frame_handle_resource_destroy(struct wl_resource *resource) { + frame_destroy(wl_resource_get_user_data(resource)); +} + +void wlr_ext_image_copy_capture_frame_v1_ready(struct wlr_ext_image_copy_capture_frame_v1 *frame, + enum wl_output_transform transform, + const struct timespec *presentation_time) { + assert(frame->capturing); + + int rects_len = 0; + const pixman_box32_t *rects = + pixman_region32_rectangles(&frame->session->damage, &rects_len); + for (int i = 0; i < rects_len; i++) { + const pixman_box32_t *rect = &rects[i]; + ext_image_copy_capture_frame_v1_send_damage(frame->resource, + rect->x1, rect->y1, rect->x2 - rect->x1, rect->y2 - rect->y1); + } + + pixman_region32_clear(&frame->session->damage); + + uint64_t pres_time_sec = (uint64_t)presentation_time->tv_sec; + ext_image_copy_capture_frame_v1_send_transform(frame->resource, transform); + ext_image_copy_capture_frame_v1_send_presentation_time(frame->resource, + pres_time_sec >> 32, (uint32_t)pres_time_sec, presentation_time->tv_nsec); + ext_image_copy_capture_frame_v1_send_ready(frame->resource); + frame_destroy(frame); +} + +static bool copy_dmabuf(struct wlr_buffer *dst, + struct wlr_buffer *src, struct wlr_renderer *renderer, + const pixman_region32_t *clip) { + struct wlr_texture *texture = wlr_texture_from_buffer(renderer, src); + if (texture == NULL) { + return false; + } + + bool ok = false; + struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, dst, NULL); + if (!pass) { + goto out; + } + + wlr_render_pass_add_texture(pass, &(struct wlr_render_texture_options) { + .texture = texture, + .clip = clip, + .blend_mode = WLR_RENDER_BLEND_MODE_NONE, + }); + + ok = wlr_render_pass_submit(pass); + +out: + wlr_texture_destroy(texture); + return ok; +} + +static bool copy_shm(void *data, uint32_t format, size_t stride, + struct wlr_buffer *src, struct wlr_renderer *renderer) { + // TODO: bypass renderer if source buffer supports data ptr access + struct wlr_texture *texture = wlr_texture_from_buffer(renderer, src); + if (!texture) { + return false; + } + + // TODO: only copy damaged region + bool ok = wlr_texture_read_pixels(texture, &(struct wlr_texture_read_pixels_options){ + .data = data, + .format = format, + .stride = stride, + }); + + wlr_texture_destroy(texture); + + return ok; +} + +bool wlr_ext_image_copy_capture_frame_v1_copy_buffer(struct wlr_ext_image_copy_capture_frame_v1 *frame, + struct wlr_buffer *src, struct wlr_renderer *renderer) { + struct wlr_buffer *dst = frame->buffer; + + if (src->width != dst->width || src->height != dst->height) { + wlr_ext_image_copy_capture_frame_v1_fail(frame, + EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS); + return false; + } + + bool ok = false; + enum ext_image_copy_capture_frame_v1_failure_reason failure_reason = + EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN; + struct wlr_dmabuf_attributes dmabuf; + void *data; + uint32_t format; + size_t stride; + if (wlr_buffer_get_dmabuf(dst, &dmabuf)) { + if (frame->session->source->dmabuf_formats.len == 0) { + ok = false; + failure_reason = EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS; + } else { + ok = copy_dmabuf(dst, src, renderer, &frame->buffer_damage); + } + } else if (wlr_buffer_begin_data_ptr_access(dst, + WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &data, &format, &stride)) { + if (frame->session->source->shm_formats_len == 0) { + ok = false; + failure_reason = EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS; + } else { + ok = copy_shm(data, format, stride, src, renderer); + } + wlr_buffer_end_data_ptr_access(dst); + } + if (!ok) { + wlr_ext_image_copy_capture_frame_v1_fail(frame, failure_reason); + return false; + } + + return true; +} + +void wlr_ext_image_copy_capture_frame_v1_fail(struct wlr_ext_image_copy_capture_frame_v1 *frame, + enum ext_image_copy_capture_frame_v1_failure_reason reason) { + ext_image_copy_capture_frame_v1_send_failed(frame->resource, reason); + frame_destroy(frame); +} + +static void frame_handle_destroy(struct wl_client *client, + struct wl_resource *frame_resource) { + wl_resource_destroy(frame_resource); +} + +static void frame_handle_attach_buffer(struct wl_client *client, + struct wl_resource *frame_resource, struct wl_resource *buffer_resource) { + struct wlr_ext_image_copy_capture_frame_v1 *frame = frame_from_resource(frame_resource); + if (frame == NULL) { + return; + } + + if (frame->capturing) { + wl_resource_post_error(frame->resource, + EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, + "attach_buffer sent after capture"); + return; + } + + struct wlr_buffer *buffer = wlr_buffer_try_from_resource(buffer_resource); + if (buffer == NULL) { + wl_resource_post_no_memory(frame_resource); + return; + } + + wlr_buffer_unlock(frame->buffer); + frame->buffer = buffer; +} + +static void frame_handle_damage_buffer(struct wl_client *client, + struct wl_resource *frame_resource, int32_t x, int32_t y, + int32_t width, int32_t height) { + struct wlr_ext_image_copy_capture_frame_v1 *frame = frame_from_resource(frame_resource); + if (frame == NULL) { + return; + } + + if (frame->capturing) { + wl_resource_post_error(frame->resource, + EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, + "damage_buffer sent after capture"); + return; + } + + if (x < 0 || y < 0 || width <= 0 || height <= 0) { + wl_resource_post_error(frame->resource, + EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_INVALID_BUFFER_DAMAGE, + "Invalid buffer damage coordinates"); + return; + } + + pixman_region32_union_rect(&frame->buffer_damage, &frame->buffer_damage, + x, y, width, height); +} + +static void frame_handle_capture(struct wl_client *client, + struct wl_resource *frame_resource) { + struct wlr_ext_image_copy_capture_frame_v1 *frame = frame_from_resource(frame_resource); + if (frame == NULL) { + return; + } + + if (frame->capturing) { + wl_resource_post_error(frame->resource, + EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, + "capture sent twice"); + return; + } + + if (frame->buffer == NULL) { + wl_resource_post_error(frame->resource, + EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_NO_BUFFER, + "No buffer attached"); + return; + } + + frame->capturing = true; + + bool need_frame = !pixman_region32_empty(&frame->session->damage); + struct wlr_ext_image_capture_source_v1 *source = frame->session->source; + if (need_frame && source->impl->schedule_frame) { + source->impl->schedule_frame(source); + } +} + +static const struct ext_image_copy_capture_frame_v1_interface frame_impl = { + .destroy = frame_handle_destroy, + .attach_buffer = frame_handle_attach_buffer, + .damage_buffer = frame_handle_damage_buffer, + .capture = frame_handle_capture, +}; + +static void session_handle_destroy(struct wl_client *client, + struct wl_resource *session_resource) { + wl_resource_destroy(session_resource); +} + +static void session_handle_create_frame(struct wl_client *client, + struct wl_resource *session_resource, uint32_t new_id) { + struct wlr_ext_image_copy_capture_session_v1 *session = session_from_resource(session_resource); + + if (session != NULL && session->frame != NULL) { + wl_resource_post_error(session_resource, + EXT_IMAGE_COPY_CAPTURE_SESSION_V1_ERROR_DUPLICATE_FRAME, + "session already has a frame object"); + return; + } + + uint32_t version = wl_resource_get_version(session_resource); + struct wl_resource *frame_resource = wl_resource_create(client, + &ext_image_copy_capture_frame_v1_interface, version, new_id); + if (frame_resource == NULL) { + wl_resource_post_no_memory(frame_resource); + return; + } + wl_resource_set_implementation(frame_resource, &frame_impl, NULL, + frame_handle_resource_destroy); + + if (session == NULL) { + ext_image_copy_capture_frame_v1_send_failed(frame_resource, + EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); + return; + } + + struct wlr_ext_image_copy_capture_frame_v1 *frame = calloc(1, sizeof(*frame)); + if (frame == NULL) { + wl_resource_post_no_memory(session_resource); + return; + } + + frame->resource = frame_resource; + frame->session = session; + pixman_region32_init(&frame->buffer_damage); + wl_signal_init(&frame->events.destroy); + + wl_resource_set_user_data(frame_resource, frame); + + session->frame = frame; +} + +static const struct ext_image_copy_capture_session_v1_interface session_impl = { + .destroy = session_handle_destroy, + .create_frame = session_handle_create_frame, +}; + +static void session_send_constraints(struct wlr_ext_image_copy_capture_session_v1 *session) { + struct wlr_ext_image_capture_source_v1 *source = session->source; + + ext_image_copy_capture_session_v1_send_buffer_size(session->resource, + source->width, source->height); + + for (size_t i = 0; i < source->shm_formats_len; i++) { + ext_image_copy_capture_session_v1_send_shm_format(session->resource, + convert_drm_format_to_wl_shm(source->shm_formats[i])); + } + + if (source->dmabuf_formats.len > 0) { + struct wl_array dev_id_array = { + .data = &source->dmabuf_device, + .size = sizeof(source->dmabuf_device), + }; + ext_image_copy_capture_session_v1_send_dmabuf_device(session->resource, + &dev_id_array); + } + for (size_t i = 0; i < source->dmabuf_formats.len; i++) { + struct wlr_drm_format *fmt = &source->dmabuf_formats.formats[i]; + struct wl_array modifiers_array = { + .data = fmt->modifiers, + .size = fmt->len * sizeof(fmt->modifiers[0]), + }; + ext_image_copy_capture_session_v1_send_dmabuf_format(session->resource, + fmt->format, &modifiers_array); + } + + ext_image_copy_capture_session_v1_send_done(session->resource); +} + +static void session_destroy(struct wlr_ext_image_copy_capture_session_v1 *session) { + if (session == NULL) { + return; + } + + if (session->frame != NULL) { + wlr_ext_image_copy_capture_frame_v1_fail(session->frame, + EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); + } + + if (session->source->impl->stop) { + session->source->impl->stop(session->source); + } + + ext_image_copy_capture_session_v1_send_stopped(session->resource); + wl_resource_set_user_data(session->resource, NULL); + + pixman_region32_fini(&session->damage); + wl_list_remove(&session->source_destroy.link); + wl_list_remove(&session->source_constraints_update.link); + wl_list_remove(&session->source_frame.link); + free(session); +} + +static void session_handle_source_destroy(struct wl_listener *listener, void *data) { + struct wlr_ext_image_copy_capture_session_v1 *session = wl_container_of(listener, session, source_destroy); + session_destroy(session); +} + +static void session_handle_source_constraints_update(struct wl_listener *listener, + void *data) { + struct wlr_ext_image_copy_capture_session_v1 *session = + wl_container_of(listener, session, source_constraints_update); + session_send_constraints(session); +} + +static void session_handle_source_frame(struct wl_listener *listener, void *data) { + struct wlr_ext_image_copy_capture_session_v1 *session = wl_container_of(listener, session, source_frame); + struct wlr_ext_image_capture_source_v1_frame_event *event = data; + + pixman_region32_union(&session->damage, &session->damage, event->damage); + + struct wlr_ext_image_copy_capture_frame_v1 *frame = session->frame; + if (frame != NULL && frame->capturing && + !pixman_region32_empty(&session->damage)) { + pixman_region32_union(&frame->buffer_damage, + &frame->buffer_damage, &session->damage); + + struct wlr_ext_image_capture_source_v1 *source = frame->session->source; + source->impl->copy_frame(source, frame, event); + } +} + +static void session_handle_resource_destroy(struct wl_resource *resource) { + session_destroy(session_from_resource(resource)); +} + +static void session_create(struct wl_resource *parent_resource, uint32_t new_id, + struct wlr_ext_image_capture_source_v1 *source, uint32_t options) { + struct wl_client *client = wl_resource_get_client(parent_resource); + uint32_t version = wl_resource_get_version(parent_resource); + struct wl_resource *session_resource = wl_resource_create(client, + &ext_image_copy_capture_session_v1_interface, version, new_id); + if (session_resource == NULL) { + wl_resource_post_no_memory(parent_resource); + return; + } + wl_resource_set_implementation(session_resource, &session_impl, NULL, + session_handle_resource_destroy); + + if (source == NULL) { + ext_image_copy_capture_session_v1_send_stopped(session_resource); + return; + } + + struct wlr_ext_image_copy_capture_session_v1 *session = calloc(1, sizeof(*session)); + if (session == NULL) { + wl_resource_post_no_memory(parent_resource); + return; + } + + if (source->impl->start) { + source->impl->start(source, options & EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_OPTIONS_PAINT_CURSORS); + } + + session->resource = session_resource; + session->source = source; + pixman_region32_init_rect(&session->damage, 0, 0, source->width, + source->height); + + session->source_destroy.notify = session_handle_source_destroy; + wl_signal_add(&source->events.destroy, &session->source_destroy); + + session->source_constraints_update.notify = session_handle_source_constraints_update; + wl_signal_add(&source->events.constraints_update, &session->source_constraints_update); + + session->source_frame.notify = session_handle_source_frame; + wl_signal_add(&source->events.frame, &session->source_frame); + + wl_resource_set_user_data(session_resource, session); + session_send_constraints(session); +} + +static void cursor_session_destroy(struct wlr_ext_image_copy_capture_cursor_session_v1 *cursor_session) { + if (cursor_session == NULL) { + return; + } + if (cursor_session->source->entered) { + ext_image_copy_capture_cursor_session_v1_send_leave(cursor_session->resource); + } + wl_resource_set_user_data(cursor_session->resource, NULL); + wl_list_remove(&cursor_session->source_destroy.link); + wl_list_remove(&cursor_session->source_update.link); + free(cursor_session); +} + +static void cursor_session_handle_destroy(struct wl_client *client, + struct wl_resource *cursor_session_resource) { + wl_resource_destroy(cursor_session_resource); +} + +static void cursor_session_handle_get_capture_session(struct wl_client *client, + struct wl_resource *cursor_session_resource, uint32_t new_id) { + struct wlr_ext_image_copy_capture_cursor_session_v1 *cursor_session = + cursor_session_from_resource(cursor_session_resource); + + if (cursor_session != NULL && cursor_session->capture_session_created) { + wl_resource_post_error(cursor_session_resource, + EXT_IMAGE_COPY_CAPTURE_CURSOR_SESSION_V1_ERROR_DUPLICATE_SESSION, + "get_capture_session sent twice"); + return; + } + + cursor_session->capture_session_created = true; + + struct wlr_ext_image_capture_source_v1 *source = NULL; + if (cursor_session != NULL) { + source = &cursor_session->source->base; + } + + session_create(cursor_session_resource, new_id, source, 0); +} + +static const struct ext_image_copy_capture_cursor_session_v1_interface cursor_session_impl = { + .destroy = cursor_session_handle_destroy, + .get_capture_session = cursor_session_handle_get_capture_session, +}; + +static void cursor_session_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_ext_image_copy_capture_cursor_session_v1 *cursor_session = + cursor_session_from_resource(resource); + cursor_session_destroy(cursor_session); +} + +static void cursor_session_handle_source_destroy(struct wl_listener *listener, + void *data) { + struct wlr_ext_image_copy_capture_cursor_session_v1 *cursor_session = + wl_container_of(listener, cursor_session, source_destroy); + cursor_session_destroy(cursor_session); +} + +static void cursor_session_update( + struct wlr_ext_image_copy_capture_cursor_session_v1 *cursor_session) { + struct wlr_ext_image_capture_source_v1_cursor *cursor_source = cursor_session->source; + + if (cursor_source->entered && !cursor_session->prev.entered) { + ext_image_copy_capture_cursor_session_v1_send_enter(cursor_session->resource); + } + if (!cursor_source->entered && cursor_session->prev.entered) { + ext_image_copy_capture_cursor_session_v1_send_leave(cursor_session->resource); + } + + if (cursor_source->x != cursor_session->prev.x || + cursor_source->y != cursor_session->prev.y) { + ext_image_copy_capture_cursor_session_v1_send_position(cursor_session->resource, + cursor_source->x, cursor_source->y); + } + + if (cursor_source->hotspot.x != cursor_session->prev.hotspot.x || + cursor_source->hotspot.y != cursor_session->prev.hotspot.y) { + ext_image_copy_capture_cursor_session_v1_send_hotspot(cursor_session->resource, + cursor_source->hotspot.x, cursor_source->hotspot.y); + } + + cursor_session->prev.entered = cursor_source->entered; + cursor_session->prev.x = cursor_source->x; + cursor_session->prev.y = cursor_source->y; + cursor_session->prev.hotspot.y = cursor_source->hotspot.y; + cursor_session->prev.hotspot.y = cursor_source->hotspot.y; +} + +static void cursor_session_handle_source_update(struct wl_listener *listener, + void *data) { + struct wlr_ext_image_copy_capture_cursor_session_v1 *cursor_session = + wl_container_of(listener, cursor_session, source_update); + cursor_session_update(cursor_session); +} + +static void manager_handle_create_session(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t new_id, + struct wl_resource *source_resource, uint32_t options) { + struct wlr_ext_image_capture_source_v1 *source = + wlr_ext_image_capture_source_v1_from_resource(source_resource); + session_create(manager_resource, new_id, source, options); +} + +static void manager_handle_create_pointer_cursor_session(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t new_id, + struct wl_resource *source_resource, struct wl_resource *pointer_resource) { + struct wlr_ext_image_capture_source_v1 *source = wlr_ext_image_capture_source_v1_from_resource(source_resource); + struct wlr_seat_client *seat_client = wlr_seat_client_from_pointer_resource(pointer_resource); + + struct wlr_seat *seat = NULL; + if (seat_client != NULL) { + seat = seat_client->seat; + } + + struct wlr_ext_image_capture_source_v1_cursor *source_cursor = NULL; + if (source != NULL && seat != NULL && source->impl->get_pointer_cursor) { + source_cursor = source->impl->get_pointer_cursor(source, seat); + } + + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *cursor_session_resource = wl_resource_create(client, + &ext_image_copy_capture_cursor_session_v1_interface, version, new_id); + if (cursor_session_resource == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_implementation(cursor_session_resource, &cursor_session_impl, NULL, + cursor_session_handle_resource_destroy); + + if (source_cursor == NULL) { + return; // leave inert + } + + struct wlr_ext_image_copy_capture_cursor_session_v1 *cursor_session = calloc(1, sizeof(*cursor_session)); + if (cursor_session == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + cursor_session->resource = cursor_session_resource; + cursor_session->source = source_cursor; + + cursor_session->source_destroy.notify = cursor_session_handle_source_destroy; + wl_signal_add(&source_cursor->base.events.destroy, &cursor_session->source_destroy); + + cursor_session->source_update.notify = cursor_session_handle_source_update; + wl_signal_add(&source_cursor->events.update, &cursor_session->source_update); + + wl_resource_set_user_data(cursor_session_resource, cursor_session); + + cursor_session_update(cursor_session); +} + +static void manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct ext_image_copy_capture_manager_v1_interface manager_impl = { + .create_session = manager_handle_create_session, + .create_pointer_cursor_session = manager_handle_create_pointer_cursor_session, + .destroy = manager_handle_destroy, +}; + +static void manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wl_resource *resource = wl_resource_create(client, + &ext_image_copy_capture_manager_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, NULL, NULL); +} + +static void manager_handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_ext_image_copy_capture_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_ext_image_copy_capture_manager_v1 *wlr_ext_image_copy_capture_manager_v1_create( + struct wl_display *display, uint32_t version) { + assert(version <= IMAGE_COPY_CAPTURE_MANAGER_V1_VERSION); + + struct wlr_ext_image_copy_capture_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, + &ext_image_copy_capture_manager_v1_interface, version, manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + manager->display_destroy.notify = manager_handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/wlr_fixes.c b/types/wlr_fixes.c new file mode 100644 index 000000000..b5435aaf8 --- /dev/null +++ b/types/wlr_fixes.c @@ -0,0 +1,65 @@ +#include +#include +#include + +#include + +#define FIXES_VERSION 1 + +static void fixes_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void fixes_destroy_registry(struct wl_client *client, struct wl_resource *resource, + struct wl_resource *registry) { + wl_resource_destroy(registry); +} + +static const struct wl_fixes_interface fixes_impl = { + .destroy = fixes_destroy, + .destroy_registry = fixes_destroy_registry, +}; + +static void fixes_bind(struct wl_client *wl_client, void *data, uint32_t version, uint32_t id) { + struct wlr_fixes *fixes = data; + + struct wl_resource *resource = wl_resource_create(wl_client, &wl_fixes_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(resource, &fixes_impl, fixes, NULL); +} + +static void fixes_handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_fixes *fixes = wl_container_of(listener, fixes, display_destroy); + wl_signal_emit_mutable(&fixes->events.destroy, NULL); + + assert(wl_list_empty(&fixes->events.destroy.listener_list)); + + wl_list_remove(&fixes->display_destroy.link); + wl_global_destroy(fixes->global); + free(fixes); +} + +struct wlr_fixes *wlr_fixes_create(struct wl_display *display, uint32_t version) { + assert(version <= FIXES_VERSION); + + struct wlr_fixes *fixes = calloc(1, sizeof(*fixes)); + if (fixes == NULL) { + return NULL; + } + + fixes->global = wl_global_create(display, &wl_fixes_interface, version, fixes, fixes_bind); + if (fixes->global == NULL) { + free(fixes); + return NULL; + } + + wl_signal_init(&fixes->events.destroy); + + fixes->display_destroy.notify = fixes_handle_display_destroy; + wl_display_add_destroy_listener(display, &fixes->display_destroy); + + return fixes; +} diff --git a/types/wlr_foreign_toplevel_management_v1.c b/types/wlr_foreign_toplevel_management_v1.c index 23935d489..15b18000c 100644 --- a/types/wlr_foreign_toplevel_management_v1.c +++ b/types/wlr_foreign_toplevel_management_v1.c @@ -1,4 +1,3 @@ - #include #include #include @@ -10,6 +9,8 @@ #define FOREIGN_TOPLEVEL_MANAGEMENT_V1_VERSION 3 +#define FOREIGN_TOPLEVEL_HANDLE_V1_STATE_COUNT 32 + static const struct zwlr_foreign_toplevel_handle_v1_interface toplevel_handle_impl; static struct wlr_foreign_toplevel_handle_v1 *toplevel_handle_from_resource( @@ -272,12 +273,27 @@ static void toplevel_handle_output_bind(struct wl_listener *listener, toplevel_update_idle_source(toplevel_output->toplevel); } +static void toplevel_output_destroy( + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output) { + wl_list_remove(&toplevel_output->link); + wl_list_remove(&toplevel_output->output_bind.link); + wl_list_remove(&toplevel_output->output_destroy.link); + free(toplevel_output); +} + +static void toplevel_output_leave( + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output) { + struct wlr_foreign_toplevel_handle_v1 *toplevel = toplevel_output->toplevel; + struct wlr_output *output = toplevel_output->output; + toplevel_send_output(toplevel, output, false); + toplevel_output_destroy(toplevel_output); +} + static void toplevel_handle_output_destroy(struct wl_listener *listener, void *data) { struct wlr_foreign_toplevel_handle_v1_output *toplevel_output = wl_container_of(listener, toplevel_output, output_destroy); - wlr_foreign_toplevel_handle_v1_output_leave(toplevel_output->toplevel, - toplevel_output->output); + toplevel_output_leave(toplevel_output); } void wlr_foreign_toplevel_handle_v1_output_enter( @@ -309,146 +325,88 @@ void wlr_foreign_toplevel_handle_v1_output_enter( toplevel_send_output(toplevel, output, true); } -static void toplevel_output_destroy( - struct wlr_foreign_toplevel_handle_v1_output *toplevel_output) { - wl_list_remove(&toplevel_output->link); - wl_list_remove(&toplevel_output->output_bind.link); - wl_list_remove(&toplevel_output->output_destroy.link); - free(toplevel_output); -} - void wlr_foreign_toplevel_handle_v1_output_leave( struct wlr_foreign_toplevel_handle_v1 *toplevel, struct wlr_output *output) { struct wlr_foreign_toplevel_handle_v1_output *toplevel_output_iterator; - struct wlr_foreign_toplevel_handle_v1_output *toplevel_output = NULL; - wl_list_for_each(toplevel_output_iterator, &toplevel->outputs, link) { if (toplevel_output_iterator->output == output) { - toplevel_output = toplevel_output_iterator; - break; + toplevel_output_leave(toplevel_output_iterator); + return; } } - if (toplevel_output) { - toplevel_send_output(toplevel, output, false); - toplevel_output_destroy(toplevel_output); - } else { - // XXX: log an error? crash? - } + // XXX: log an error? crash? } -static bool fill_array_from_toplevel_state(struct wl_array *array, - uint32_t state) { +static void fill_array_from_toplevel_state(struct wl_array *states, + uint32_t data[], uint32_t state, uint32_t version) { + size_t nstates = 0; if (state & WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED) { - uint32_t *index = wl_array_add(array, sizeof(uint32_t)); - if (index == NULL) { - return false; - } - *index = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED; + data[nstates++] = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED; } if (state & WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED) { - uint32_t *index = wl_array_add(array, sizeof(uint32_t)); - if (index == NULL) { - return false; - } - *index = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED; + data[nstates++] = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED; } if (state & WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) { - uint32_t *index = wl_array_add(array, sizeof(uint32_t)); - if (index == NULL) { - return false; - } - *index = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED; + data[nstates++] = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED; } - if (state & WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN) { - uint32_t *index = wl_array_add(array, sizeof(uint32_t)); - if (index == NULL) { - return false; - } - *index = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN; + if (version >= ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN_SINCE_VERSION + && (state & WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN)) { + data[nstates++] = ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN; } + assert(nstates <= FOREIGN_TOPLEVEL_HANDLE_V1_STATE_COUNT); - return true; + *states = (struct wl_array){ + .data = data, + .size = nstates * sizeof(data[0]) + }; } static void toplevel_send_state(struct wlr_foreign_toplevel_handle_v1 *toplevel) { - struct wl_array states; - wl_array_init(&states); - bool r = fill_array_from_toplevel_state(&states, toplevel->state); - if (!r) { - struct wl_resource *resource; - wl_resource_for_each(resource, &toplevel->resources) { - wl_resource_post_no_memory(resource); - } - - wl_array_release(&states); - return; - } + struct wl_array state_array; + uint32_t states[FOREIGN_TOPLEVEL_HANDLE_V1_STATE_COUNT]; struct wl_resource *resource; wl_resource_for_each(resource, &toplevel->resources) { - zwlr_foreign_toplevel_handle_v1_send_state(resource, &states); + fill_array_from_toplevel_state(&state_array, states, + toplevel->state, wl_resource_get_version(resource)); + zwlr_foreign_toplevel_handle_v1_send_state(resource, &state_array); } - - wl_array_release(&states); toplevel_update_idle_source(toplevel); } +static void set_state(struct wlr_foreign_toplevel_handle_v1 *toplevel, + bool new_state_val, enum wlr_foreign_toplevel_handle_v1_state state) { + if (new_state_val == !!(toplevel->state & state)) { + return; + } + if (new_state_val) { + toplevel->state |= state; + } else { + toplevel->state &= ~state; + } + toplevel_send_state(toplevel); +} + void wlr_foreign_toplevel_handle_v1_set_maximized( struct wlr_foreign_toplevel_handle_v1 *toplevel, bool maximized) { - if (maximized == !!(toplevel->state & - WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED)) { - return; - } - if (maximized) { - toplevel->state |= WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED; - } else { - toplevel->state &= ~WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED; - } - toplevel_send_state(toplevel); + set_state(toplevel, maximized, WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED); } void wlr_foreign_toplevel_handle_v1_set_minimized( struct wlr_foreign_toplevel_handle_v1 *toplevel, bool minimized) { - if (minimized == !!(toplevel->state & - WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED)) { - return; - } - if (minimized) { - toplevel->state |= WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED; - } else { - toplevel->state &= ~WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED; - } - toplevel_send_state(toplevel); + set_state(toplevel, minimized, WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED); } void wlr_foreign_toplevel_handle_v1_set_activated( struct wlr_foreign_toplevel_handle_v1 *toplevel, bool activated) { - if (activated == !!(toplevel->state & - WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED)) { - return; - } - if (activated) { - toplevel->state |= WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED; - } else { - toplevel->state &= ~WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED; - } - toplevel_send_state(toplevel); + set_state(toplevel, activated, WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED); } void wlr_foreign_toplevel_handle_v1_set_fullscreen( struct wlr_foreign_toplevel_handle_v1 * toplevel, bool fullscreen) { - if (fullscreen == !!(toplevel->state & - WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN)) { - return; - } - if (fullscreen) { - toplevel->state |= WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN; - } else { - toplevel->state &= ~WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN; - } - toplevel_send_state(toplevel); + set_state(toplevel, fullscreen, WLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN); } static void toplevel_resource_send_parent( @@ -494,6 +452,14 @@ void wlr_foreign_toplevel_handle_v1_destroy( wl_signal_emit_mutable(&toplevel->events.destroy, toplevel); + assert(wl_list_empty(&toplevel->events.request_maximize.listener_list)); + assert(wl_list_empty(&toplevel->events.request_minimize.listener_list)); + assert(wl_list_empty(&toplevel->events.request_activate.listener_list)); + assert(wl_list_empty(&toplevel->events.request_fullscreen.listener_list)); + assert(wl_list_empty(&toplevel->events.request_close.listener_list)); + assert(wl_list_empty(&toplevel->events.set_rectangle.listener_list)); + assert(wl_list_empty(&toplevel->events.destroy.listener_list)); + struct wl_resource *resource, *tmp; wl_resource_for_each_safe(resource, tmp, &toplevel->resources) { zwlr_foreign_toplevel_handle_v1_send_closed(resource); @@ -623,17 +589,11 @@ static void toplevel_send_details_to_toplevel_resource( send_output_to_resource(resource, output->output, true); } - struct wl_array states; - wl_array_init(&states); - bool r = fill_array_from_toplevel_state(&states, toplevel->state); - if (!r) { - wl_resource_post_no_memory(resource); - wl_array_release(&states); - return; - } - - zwlr_foreign_toplevel_handle_v1_send_state(resource, &states); - wl_array_release(&states); + struct wl_array state_array; + uint32_t states[FOREIGN_TOPLEVEL_HANDLE_V1_STATE_COUNT]; + fill_array_from_toplevel_state(&state_array, states, + toplevel->state, wl_resource_get_version(resource)); + zwlr_foreign_toplevel_handle_v1_send_state(resource, &state_array); toplevel_resource_send_parent(resource, toplevel->parent); @@ -675,6 +635,9 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_foreign_toplevel_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.destroy.listener_list)); + wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); @@ -698,6 +661,7 @@ struct wlr_foreign_toplevel_manager_v1 *wlr_foreign_toplevel_manager_v1_create( } wl_signal_init(&manager->events.destroy); + wl_list_init(&manager->resources); wl_list_init(&manager->toplevels); diff --git a/types/wlr_fullscreen_shell_v1.c b/types/wlr_fullscreen_shell_v1.c deleted file mode 100644 index d59e94a47..000000000 --- a/types/wlr_fullscreen_shell_v1.c +++ /dev/null @@ -1,138 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#define FULLSCREEN_SHELL_VERSION 1 - -static const struct zwp_fullscreen_shell_v1_interface shell_impl; - -static void shell_handle_release(struct wl_client *client, - struct wl_resource *resource) { - wl_resource_destroy(resource); -} - -static struct wlr_fullscreen_shell_v1 *shell_from_resource( - struct wl_resource *resource) { - assert(wl_resource_instance_of(resource, - &zwp_fullscreen_shell_v1_interface, &shell_impl)); - return wl_resource_get_user_data(resource); -} - -static void fullscreen_shell_surface_handle_commit(struct wlr_surface *surface) { - if (wlr_surface_has_buffer(surface)) { - wlr_surface_map(surface); - } -} - -static const struct wlr_surface_role fullscreen_shell_surface_role = { - .name = "zwp_fullscreen_shell_v1-surface", - .no_object = true, - .commit = fullscreen_shell_surface_handle_commit, -}; - -static void shell_handle_present_surface(struct wl_client *client, - struct wl_resource *shell_resource, - struct wl_resource *surface_resource, uint32_t method, - struct wl_resource *output_resource) { - struct wlr_fullscreen_shell_v1 *shell = shell_from_resource(shell_resource); - struct wlr_surface *surface = NULL; - if (surface_resource != NULL) { - surface = wlr_surface_from_resource(surface_resource); - } - struct wlr_output *output = NULL; - if (output_resource != NULL) { - output = wlr_output_from_resource(output_resource); - } - - if (!wlr_surface_set_role(surface, &fullscreen_shell_surface_role, - shell_resource, ZWP_FULLSCREEN_SHELL_V1_ERROR_ROLE)) { - return; - } - - struct wlr_fullscreen_shell_v1_present_surface_event event = { - .client = client, - .surface = surface, - .method = method, - .output = output, - }; - wl_signal_emit_mutable(&shell->events.present_surface, &event); -} - -static void shell_handle_present_surface_for_mode(struct wl_client *client, - struct wl_resource *shell_resource, - struct wl_resource *surface_resource, - struct wl_resource *output_resource, int32_t framerate, - uint32_t feedback_id) { - struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); - - if (!wlr_surface_set_role(surface, &fullscreen_shell_surface_role, - shell_resource, ZWP_FULLSCREEN_SHELL_V1_ERROR_ROLE)) { - return; - } - - uint32_t version = wl_resource_get_version(shell_resource); - struct wl_resource *feedback_resource = - wl_resource_create(client, NULL, version, feedback_id); - if (feedback_resource == NULL) { - wl_resource_post_no_memory(shell_resource); - return; - } - - // TODO: add support for mode switch - zwp_fullscreen_shell_mode_feedback_v1_send_mode_failed(feedback_resource); - wl_resource_destroy(feedback_resource); -} - -static const struct zwp_fullscreen_shell_v1_interface shell_impl = { - .release = shell_handle_release, - .present_surface = shell_handle_present_surface, - .present_surface_for_mode = shell_handle_present_surface_for_mode, -}; - -static void shell_bind(struct wl_client *client, void *data, uint32_t version, - uint32_t id) { - struct wlr_fullscreen_shell_v1 *shell = data; - - struct wl_resource *resource = wl_resource_create(client, - &zwp_fullscreen_shell_v1_interface, version, id); - if (resource == NULL) { - wl_client_post_no_memory(client); - return; - } - wl_resource_set_implementation(resource, &shell_impl, shell, NULL); -} - -static void handle_display_destroy(struct wl_listener *listener, void *data) { - struct wlr_fullscreen_shell_v1 *shell = - wl_container_of(listener, shell, display_destroy); - wl_signal_emit_mutable(&shell->events.destroy, shell); - wl_list_remove(&shell->display_destroy.link); - wl_global_destroy(shell->global); - free(shell); -} - -struct wlr_fullscreen_shell_v1 *wlr_fullscreen_shell_v1_create( - struct wl_display *display) { - struct wlr_fullscreen_shell_v1 *shell = calloc(1, sizeof(*shell)); - if (shell == NULL) { - return NULL; - } - wl_signal_init(&shell->events.destroy); - wl_signal_init(&shell->events.present_surface); - - shell->global = wl_global_create(display, - &zwp_fullscreen_shell_v1_interface, FULLSCREEN_SHELL_VERSION, - shell, shell_bind); - if (shell->global == NULL) { - free(shell); - return NULL; - } - - shell->display_destroy.notify = handle_display_destroy; - wl_display_add_destroy_listener(display, &shell->display_destroy); - - return shell; -} diff --git a/types/wlr_gamma_control_v1.c b/types/wlr_gamma_control_v1.c index 732439de4..42d14799d 100644 --- a/types/wlr_gamma_control_v1.c +++ b/types/wlr_gamma_control_v1.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -156,6 +157,9 @@ static void gamma_control_manager_get_gamma_control(struct wl_client *client, } size_t gamma_size = wlr_output_get_gamma_size(output); + if (gamma_size == 0) { + gamma_size = manager->fallback_gamma_size; + } if (gamma_size == 0) { zwlr_gamma_control_v1_send_failed(resource); return; @@ -215,6 +219,10 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_gamma_control_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.destroy.listener_list)); + assert(wl_list_empty(&manager->events.set_gamma.listener_list)); + wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); @@ -237,6 +245,7 @@ struct wlr_gamma_control_manager_v1 *wlr_gamma_control_manager_v1_create( wl_signal_init(&manager->events.destroy); wl_signal_init(&manager->events.set_gamma); + wl_list_init(&manager->controls); manager->display_destroy.notify = handle_display_destroy; @@ -256,17 +265,31 @@ struct wlr_gamma_control_v1 *wlr_gamma_control_manager_v1_get_control( return NULL; } -bool wlr_gamma_control_v1_apply(struct wlr_gamma_control_v1 *gamma_control, - struct wlr_output_state *output_state) { +struct wlr_color_transform *wlr_gamma_control_v1_get_color_transform( + struct wlr_gamma_control_v1 *gamma_control) { if (gamma_control == NULL || gamma_control->table == NULL) { - return wlr_output_state_set_gamma_lut(output_state, 0, NULL, NULL, NULL); + return NULL; } const uint16_t *r = gamma_control->table; const uint16_t *g = gamma_control->table + gamma_control->ramp_size; const uint16_t *b = gamma_control->table + 2 * gamma_control->ramp_size; - return wlr_output_state_set_gamma_lut(output_state, - gamma_control->ramp_size, r, g, b); + + return wlr_color_transform_init_lut_3x1d(gamma_control->ramp_size, r, g, b); +} + +bool wlr_gamma_control_v1_apply(struct wlr_gamma_control_v1 *gamma_control, + struct wlr_output_state *output_state) { + struct wlr_color_transform *tr = NULL; + if (gamma_control != NULL && gamma_control->table != NULL) { + tr = wlr_gamma_control_v1_get_color_transform(gamma_control); + if (tr == NULL) { + return false; + } + } + + wlr_output_state_set_color_transform(output_state, tr); + return true; } void wlr_gamma_control_v1_send_failed_and_destroy(struct wlr_gamma_control_v1 *gamma_control) { diff --git a/types/wlr_idle_inhibit_v1.c b/types/wlr_idle_inhibit_v1.c index 93e8e8546..388778800 100644 --- a/types/wlr_idle_inhibit_v1.c +++ b/types/wlr_idle_inhibit_v1.c @@ -34,6 +34,8 @@ static void idle_inhibitor_v1_destroy(struct wlr_idle_inhibitor_v1 *inhibitor) { wl_signal_emit_mutable(&inhibitor->events.destroy, inhibitor->surface); + assert(wl_list_empty(&inhibitor->events.destroy.listener_list)); + wl_resource_set_user_data(inhibitor->resource, NULL); wl_list_remove(&inhibitor->link); wl_list_remove(&inhibitor->surface_destroy.link); @@ -87,6 +89,7 @@ static void manager_handle_create_inhibitor(struct wl_client *client, inhibitor->resource = inhibitor_resource; inhibitor->surface = surface; + wl_signal_init(&inhibitor->events.destroy); inhibitor->surface_destroy.notify = idle_inhibitor_handle_surface_destroy; @@ -113,6 +116,10 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_idle_inhibit_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.new_inhibitor.listener_list)); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); @@ -140,6 +147,7 @@ struct wlr_idle_inhibit_manager_v1 *wlr_idle_inhibit_v1_create(struct wl_display } wl_list_init(&manager->inhibitors); + wl_signal_init(&manager->events.new_inhibitor); wl_signal_init(&manager->events.destroy); diff --git a/types/wlr_idle_notify_v1.c b/types/wlr_idle_notify_v1.c index 686ddccc5..0488225a8 100644 --- a/types/wlr_idle_notify_v1.c +++ b/types/wlr_idle_notify_v1.c @@ -4,7 +4,7 @@ #include #include "ext-idle-notify-v1-protocol.h" -#define IDLE_NOTIFIER_VERSION 1 +#define IDLE_NOTIFIER_VERSION 2 struct wlr_idle_notification_v1 { struct wl_resource *resource; @@ -16,6 +16,7 @@ struct wlr_idle_notification_v1 { struct wl_event_source *timer; bool idle; + bool obey_inhibitors; struct wl_listener seat_destroy; }; @@ -81,7 +82,7 @@ static void notification_destroy(struct wlr_idle_notification_v1 *notification) } static void notification_reset_timer(struct wlr_idle_notification_v1 *notification) { - if (notification->notifier->inhibited) { + if (notification->notifier->inhibited && notification->obey_inhibitors) { notification_set_idle(notification, false); if (notification->timer != NULL) { wl_event_source_timer_update(notification->timer, 0); @@ -115,9 +116,9 @@ static void notification_handle_resource_destroy(struct wl_resource *resource) { notification_destroy(notification); } -static void notifier_handle_get_idle_notification(struct wl_client *client, +static void construct_idle_notification(struct wl_client *client, struct wl_resource *notifier_resource, uint32_t id, uint32_t timeout, - struct wl_resource *seat_resource) { + struct wl_resource *seat_resource, bool obey_inhibitors) { struct wlr_idle_notifier_v1 *notifier = notifier_from_resource(notifier_resource); struct wlr_seat_client *seat_client = @@ -148,6 +149,7 @@ static void notifier_handle_get_idle_notification(struct wl_client *client, notification->resource = resource; notification->timeout_ms = timeout; notification->seat = seat_client->seat; + notification->obey_inhibitors = obey_inhibitors; if (timeout > 0) { struct wl_display *display = wl_client_get_display(client); @@ -170,9 +172,26 @@ static void notifier_handle_get_idle_notification(struct wl_client *client, notification_reset_timer(notification); } +static void notifier_handle_get_input_idle_notification( + struct wl_client *client, + struct wl_resource *notifier_resource, uint32_t id, + uint32_t timeout, struct wl_resource *seat_resource) { + construct_idle_notification(client, notifier_resource, id, + timeout, seat_resource, false); +} + +static void notifier_handle_get_idle_notification( + struct wl_client *client, + struct wl_resource *notifier_resource, uint32_t id, + uint32_t timeout, struct wl_resource *seat_resource) { + construct_idle_notification(client, notifier_resource, id, + timeout, seat_resource, true); +} + static const struct ext_idle_notifier_v1_interface notifier_impl = { .destroy = resource_handle_destroy, .get_idle_notification = notifier_handle_get_idle_notification, + .get_input_idle_notification = notifier_handle_get_input_idle_notification, }; static void notifier_bind(struct wl_client *client, void *data, @@ -227,19 +246,17 @@ void wlr_idle_notifier_v1_set_inhibited(struct wlr_idle_notifier_v1 *notifier, struct wlr_idle_notification_v1 *notification; wl_list_for_each(notification, ¬ifier->notifications, link) { - notification_reset_timer(notification); + if (notification->obey_inhibitors) { + notification_reset_timer(notification); + } } } void wlr_idle_notifier_v1_notify_activity(struct wlr_idle_notifier_v1 *notifier, struct wlr_seat *seat) { - if (notifier->inhibited) { - return; - } - struct wlr_idle_notification_v1 *notification; wl_list_for_each(notification, ¬ifier->notifications, link) { - if (notification->seat == seat) { + if (notification->seat == seat && !(notifier->inhibited && notification->obey_inhibitors)) { notification_handle_activity(notification); } } diff --git a/types/wlr_input_device.c b/types/wlr_input_device.c index ede2e04d8..8d6301589 100644 --- a/types/wlr_input_device.c +++ b/types/wlr_input_device.c @@ -1,4 +1,4 @@ - +#include #include #include #include "interfaces/wlr_input_device.h" @@ -20,7 +20,7 @@ void wlr_input_device_finish(struct wlr_input_device *wlr_device) { wl_signal_emit_mutable(&wlr_device->events.destroy, wlr_device); - wl_list_remove(&wlr_device->events.destroy.listener_list); + assert(wl_list_empty(&wlr_device->events.destroy.listener_list)); free(wlr_device->name); } diff --git a/types/wlr_input_method_v2.c b/types/wlr_input_method_v2.c index 257666c46..a9501654c 100644 --- a/types/wlr_input_method_v2.c +++ b/types/wlr_input_method_v2.c @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -8,7 +10,6 @@ #include #include #include "input-method-unstable-v2-protocol.h" -#include "util/shm.h" // Note: zwp_input_popup_surface_v2 and zwp_input_method_keyboard_grab_v2 objects // become inert when the corresponding zwp_input_method_v2 is destroyed @@ -26,6 +27,9 @@ static void popup_surface_destroy(struct wlr_input_popup_surface_v2 *popup_surfa wlr_surface_unmap(popup_surface->surface); wl_signal_emit_mutable(&popup_surface->events.destroy, NULL); + + assert(wl_list_empty(&popup_surface->events.destroy.listener_list)); + wl_list_remove(&popup_surface->link); wl_resource_set_user_data(popup_surface->resource, NULL); free(popup_surface); @@ -52,10 +56,16 @@ static void input_method_destroy(struct wlr_input_method_v2 *input_method) { popup_surface, tmp, &input_method->popup_surfaces, link) { popup_surface_destroy(popup_surface); } - wl_signal_emit_mutable(&input_method->events.destroy, input_method); + wlr_input_method_keyboard_grab_v2_destroy(input_method->keyboard_grab); + wl_signal_emit_mutable(&input_method->events.destroy, NULL); + + assert(wl_list_empty(&input_method->events.commit.listener_list)); + assert(wl_list_empty(&input_method->events.new_popup_surface.listener_list)); + assert(wl_list_empty(&input_method->events.grab_keyboard.listener_list)); + assert(wl_list_empty(&input_method->events.destroy.listener_list)); + wl_list_remove(wl_resource_get_link(input_method->resource)); wl_list_remove(&input_method->seat_client_destroy.link); - wlr_input_method_keyboard_grab_v2_destroy(input_method->keyboard_grab); input_state_reset(&input_method->pending); input_state_reset(&input_method->current); free(input_method); @@ -92,7 +102,7 @@ static void im_commit(struct wl_client *client, struct wl_resource *resource, input_method->current = input_method->pending; input_method->pending = (struct wlr_input_method_v2_state){0}; - wl_signal_emit_mutable(&input_method->events.commit, input_method); + wl_signal_emit_mutable(&input_method->events.commit, NULL); } static void im_commit_string(struct wl_client *client, @@ -261,7 +271,9 @@ void wlr_input_method_keyboard_grab_v2_destroy( if (!keyboard_grab) { return; } - wl_signal_emit_mutable(&keyboard_grab->events.destroy, keyboard_grab); + wl_signal_emit_mutable(&keyboard_grab->events.destroy, NULL); + assert(wl_list_empty(&keyboard_grab->events.destroy.listener_list)); + keyboard_grab->input_method->keyboard_grab = NULL; if (keyboard_grab->keyboard) { wl_list_remove(&keyboard_grab->keyboard_keymap.link); @@ -306,34 +318,30 @@ void wlr_input_method_keyboard_grab_v2_send_modifiers( modifiers->locked, modifiers->group); } -static bool keyboard_grab_send_keymap( +static void keyboard_grab_send_keymap( struct wlr_input_method_keyboard_grab_v2 *keyboard_grab, struct wlr_keyboard *keyboard) { - int keymap_fd = allocate_shm_file(keyboard->keymap_size); - if (keymap_fd < 0) { - wlr_log(WLR_ERROR, "creating a keymap file for %zu bytes failed", - keyboard->keymap_size); - return false; + enum wl_keyboard_keymap_format format; + int fd, devnull = -1; + if (keyboard->keymap != NULL) { + format = WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1; + fd = keyboard->keymap_fd; + } else { + format = WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP; + devnull = open("/dev/null", O_RDONLY | O_CLOEXEC); + if (devnull < 0) { + wlr_log_errno(WLR_ERROR, "Failed to open /dev/null"); + return; + } + fd = devnull; } - void *ptr = mmap(NULL, keyboard->keymap_size, PROT_READ | PROT_WRITE, - MAP_SHARED, keymap_fd, 0); - if (ptr == MAP_FAILED) { - wlr_log(WLR_ERROR, "failed to mmap() %zu bytes", - keyboard->keymap_size); - close(keymap_fd); - return false; - } - - memcpy(ptr, keyboard->keymap_string, keyboard->keymap_size); - munmap(ptr, keyboard->keymap_size); - zwp_input_method_keyboard_grab_v2_send_keymap(keyboard_grab->resource, - WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, keymap_fd, - keyboard->keymap_size); + format, fd, keyboard->keymap_size); - close(keymap_fd); - return true; + if (devnull >= 0) { + close(devnull); + } } static void keyboard_grab_send_repeat_info( @@ -379,15 +387,12 @@ void wlr_input_method_keyboard_grab_v2_set_keyboard( if (keyboard) { if (keyboard_grab->keyboard == NULL || - strcmp(keyboard_grab->keyboard->keymap_string, - keyboard->keymap_string) != 0) { - // send keymap only if it is changed, or if input method is not - // aware that it did not change and blindly send it back with - // virtual keyboard, it may cause an infinite recursion. - if (!keyboard_grab_send_keymap(keyboard_grab, keyboard)) { - wlr_log(WLR_ERROR, "Failed to send keymap for input-method keyboard grab"); - return; - } + !wlr_keyboard_keymaps_match(keyboard_grab->keyboard->keymap, + keyboard->keymap)) { + // Only send keymap if it changed, otherwise if the input-method + // client sent back the same keymap with virtual-keyboard, it would + // result in an infinite loop of keymap updates. + keyboard_grab_send_keymap(keyboard_grab, keyboard); } keyboard_grab_send_repeat_info(keyboard_grab, keyboard); keyboard_grab->keyboard_keymap.notify = handle_keyboard_keymap; @@ -439,7 +444,9 @@ static void im_grab_keyboard(struct wl_client *client, keyboard_grab->resource = keyboard_grab_resource; keyboard_grab->input_method = input_method; input_method->keyboard_grab = keyboard_grab; + wl_signal_init(&keyboard_grab->events.destroy); + wl_signal_emit_mutable(&input_method->events.grab_keyboard, keyboard_grab); } @@ -550,6 +557,7 @@ static void manager_get_input_method(struct wl_client *client, return; } wl_list_init(&input_method->popup_surfaces); + wl_signal_init(&input_method->events.commit); wl_signal_init(&input_method->events.new_popup_surface); wl_signal_init(&input_method->events.grab_keyboard); @@ -566,7 +574,7 @@ static void manager_get_input_method(struct wl_client *client, wl_resource_set_user_data(im_resource, input_method); wl_list_insert(&im_manager->input_methods, wl_resource_get_link(input_method->resource)); - wl_signal_emit_mutable(&im_manager->events.input_method, input_method); + wl_signal_emit_mutable(&im_manager->events.new_input_method, input_method); } static void manager_destroy(struct wl_client *client, @@ -597,7 +605,11 @@ static void input_method_manager_bind(struct wl_client *wl_client, void *data, static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_input_method_manager_v2 *manager = wl_container_of(listener, manager, display_destroy); - wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_signal_emit_mutable(&manager->events.destroy, NULL); + + assert(wl_list_empty(&manager->events.new_input_method.listener_list)); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); @@ -609,8 +621,10 @@ struct wlr_input_method_manager_v2 *wlr_input_method_manager_v2_create( if (!im_manager) { return NULL; } - wl_signal_init(&im_manager->events.input_method); + + wl_signal_init(&im_manager->events.new_input_method); wl_signal_init(&im_manager->events.destroy); + wl_list_init(&im_manager->input_methods); im_manager->global = wl_global_create(display, diff --git a/types/wlr_keyboard.c b/types/wlr_keyboard.c index 98978ee1d..a24335b3a 100644 --- a/types/wlr_keyboard.c +++ b/types/wlr_keyboard.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -153,12 +154,11 @@ static void keyboard_unset_keymap(struct wlr_keyboard *kb) { void wlr_keyboard_finish(struct wlr_keyboard *kb) { /* Release pressed keys */ - size_t orig_num_keycodes = kb->num_keycodes; - for (size_t i = 0; i < orig_num_keycodes; ++i) { - assert(kb->num_keycodes == orig_num_keycodes - i); + int64_t time_msec = get_current_time_msec(); + while (kb->num_keycodes > 0) { struct wlr_keyboard_key_event event = { - .time_msec = get_current_time_msec(), - .keycode = kb->keycodes[orig_num_keycodes - i - 1], + .time_msec = time_msec, + .keycode = kb->keycodes[kb->num_keycodes - 1], .update_state = false, .state = WL_KEYBOARD_KEY_STATE_RELEASED, }; @@ -167,6 +167,11 @@ void wlr_keyboard_finish(struct wlr_keyboard *kb) { wlr_input_device_finish(&kb->base); + assert(wl_list_empty(&kb->events.key.listener_list)); + assert(wl_list_empty(&kb->events.modifiers.listener_list)); + assert(wl_list_empty(&kb->events.keymap.listener_list)); + assert(wl_list_empty(&kb->events.repeat_info.listener_list)); + keyboard_unset_keymap(kb); } @@ -230,6 +235,8 @@ bool wlr_keyboard_set_keymap(struct wlr_keyboard *kb, struct xkb_keymap *keymap) XKB_LED_NAME_NUM, XKB_LED_NAME_CAPS, XKB_LED_NAME_SCROLL, + XKB_LED_NAME_COMPOSE, + XKB_LED_NAME_KANA, }; for (size_t i = 0; i < WLR_LED_COUNT; ++i) { kb->led_indexes[i] = xkb_map_led_get_index(kb->keymap, led_names[i]); @@ -305,3 +312,46 @@ bool wlr_keyboard_keymaps_match(struct xkb_keymap *km1, free(km2_str); return result; } + +uint32_t wlr_keyboard_keysym_to_pointer_button(xkb_keysym_t keysym) { + switch (keysym) { + case XKB_KEY_Pointer_Button1: + return BTN_LEFT; + case XKB_KEY_Pointer_Button2: + return BTN_MIDDLE; + case XKB_KEY_Pointer_Button3: + return BTN_RIGHT; + default: + return 0; + } +} + +void wlr_keyboard_keysym_to_pointer_motion(xkb_keysym_t keysym, int *dx, int *dy) { + *dx = 0; + switch (keysym) { + case XKB_KEY_Pointer_Right: + case XKB_KEY_Pointer_DownRight: + case XKB_KEY_Pointer_UpRight: + *dx = 1; + break; + case XKB_KEY_Pointer_Left: + case XKB_KEY_Pointer_DownLeft: + case XKB_KEY_Pointer_UpLeft: + *dx = -1; + break; + } + + *dy = 0; + switch (keysym) { + case XKB_KEY_Pointer_Down: + case XKB_KEY_Pointer_DownLeft: + case XKB_KEY_Pointer_DownRight: + *dy = 1; + break; + case XKB_KEY_Pointer_Up: + case XKB_KEY_Pointer_UpLeft: + case XKB_KEY_Pointer_UpRight: + *dy = -1; + break; + } +} diff --git a/types/wlr_keyboard_group.c b/types/wlr_keyboard_group.c index ef557a958..0ff6d93bb 100644 --- a/types/wlr_keyboard_group.c +++ b/types/wlr_keyboard_group.c @@ -10,6 +10,7 @@ #include "wlr/types/wlr_keyboard.h" #include "wlr/types/wlr_keyboard_group.h" #include "wlr/util/log.h" +#include "util/time.h" struct keyboard_group_device { struct wlr_keyboard *keyboard; @@ -183,10 +184,8 @@ static void refresh_state(struct keyboard_group_device *device, wl_array_init(&keys); for (size_t i = 0; i < device->keyboard->num_keycodes; i++) { - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); struct wlr_keyboard_key_event event = { - .time_msec = (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000, + .time_msec = get_current_time_msec(), .keycode = device->keyboard->keycodes[i], .update_state = true, .state = state @@ -307,12 +306,23 @@ void wlr_keyboard_group_remove_keyboard(struct wlr_keyboard_group *group, } void wlr_keyboard_group_destroy(struct wlr_keyboard_group *group) { - struct keyboard_group_device *device, *tmp; - wl_list_for_each_safe(device, tmp, &group->devices, link) { + struct keyboard_group_device *device, *tmp_device; + wl_list_for_each_safe(device, tmp_device, &group->devices, link) { wlr_keyboard_group_remove_keyboard(group, device->keyboard); } + + // Now group->keys might not be empty if a wlr_keyboard has emitted + // duplicated key presses + struct keyboard_group_key *key, *tmp_key; + wl_list_for_each_safe(key, tmp_key, &group->keys, link) { + wl_list_remove(&key->link); + free(key); + } + wlr_keyboard_finish(&group->keyboard); - wl_list_remove(&group->events.enter.listener_list); - wl_list_remove(&group->events.leave.listener_list); + + assert(wl_list_empty(&group->events.enter.listener_list)); + assert(wl_list_empty(&group->events.leave.listener_list)); + free(group); } diff --git a/types/wlr_keyboard_shortcuts_inhibit_v1.c b/types/wlr_keyboard_shortcuts_inhibit_v1.c index 0e4355616..db4f678f7 100644 --- a/types/wlr_keyboard_shortcuts_inhibit_v1.c +++ b/types/wlr_keyboard_shortcuts_inhibit_v1.c @@ -36,6 +36,8 @@ static void keyboard_shortcuts_inhibitor_v1_destroy( wl_signal_emit_mutable(&inhibitor->events.destroy, inhibitor); + assert(wl_list_empty(&inhibitor->events.destroy.listener_list)); + wl_resource_set_user_data(inhibitor->resource, NULL); wl_list_remove(&inhibitor->link); wl_list_remove(&inhibitor->surface_destroy.link); @@ -132,6 +134,7 @@ static void manager_handle_inhibit_shortcuts(struct wl_client *client, inhibitor->surface = surface; inhibitor->seat = seat; inhibitor->active = false; + wl_signal_init(&inhibitor->events.destroy); inhibitor->surface_destroy.notify = @@ -162,6 +165,10 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_keyboard_shortcuts_inhibit_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.new_inhibitor.listener_list)); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); @@ -191,6 +198,7 @@ wlr_keyboard_shortcuts_inhibit_v1_create(struct wl_display *display) { } wl_list_init(&manager->inhibitors); + wl_signal_init(&manager->events.new_inhibitor); wl_signal_init(&manager->events.destroy); diff --git a/types/wlr_layer_shell_v1.c b/types/wlr_layer_shell_v1.c index a59f1104a..2d8384981 100644 --- a/types/wlr_layer_shell_v1.c +++ b/types/wlr_layer_shell_v1.c @@ -11,7 +11,7 @@ // Note: zwlr_layer_surface_v1 becomes inert on wlr_layer_surface_v1_destroy() -#define LAYER_SHELL_VERSION 4 +#define LAYER_SHELL_VERSION 5 static void resource_handle_destroy(struct wl_client *client, struct wl_resource *resource) { @@ -50,6 +50,10 @@ static void layer_surface_destroy(struct wlr_layer_surface_v1 *surface) { layer_surface_reset(surface); wl_signal_emit_mutable(&surface->events.destroy, surface); + + assert(wl_list_empty(&surface->events.destroy.listener_list)); + assert(wl_list_empty(&surface->events.new_popup.listener_list)); + wlr_surface_synced_finish(&surface->synced); wl_resource_set_user_data(surface->resource, NULL); free(surface->namespace); @@ -148,12 +152,8 @@ static void layer_surface_handle_set_size(struct wl_client *client, static void layer_surface_handle_set_anchor(struct wl_client *client, struct wl_resource *resource, uint32_t anchor) { - const uint32_t max_anchor = - ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | - ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | - ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; - if (anchor > max_anchor) { + uint32_t version = wl_resource_get_version(resource); + if (!zwlr_layer_surface_v1_anchor_is_valid(anchor, version)) { wl_resource_post_error(resource, ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_ANCHOR, "invalid anchor %" PRIu32, anchor); @@ -223,10 +223,11 @@ static void layer_surface_handle_set_keyboard_interactivity( } surface->pending.committed |= WLR_LAYER_SURFACE_V1_STATE_KEYBOARD_INTERACTIVITY; - if (wl_resource_get_version(resource) < ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND_SINCE_VERSION) { + uint32_t version = wl_resource_get_version(resource); + if (version < ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND_SINCE_VERSION) { surface->pending.keyboard_interactive = !!interactive; } else { - if (interactive > ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND) { + if (!zwlr_layer_surface_v1_keyboard_interactivity_is_valid(interactive, version)) { wl_resource_post_error(resource, ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_KEYBOARD_INTERACTIVITY, "wrong keyboard interactivity value: %" PRIu32, interactive); @@ -263,7 +264,9 @@ static void layer_surface_set_layer(struct wl_client *client, if (!surface) { return; } - if (layer > ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY) { + uint32_t version = wl_resource_get_version(surface->resource); + if (!zwlr_layer_shell_v1_layer_is_valid(layer, version)) { + // XXX: this sends a zwlr_layer_shell_v1 error to a zwlr_layer_surface_v1 object wl_resource_post_error(surface->resource, ZWLR_LAYER_SHELL_V1_ERROR_INVALID_LAYER, "Invalid layer %" PRIu32, layer); @@ -278,6 +281,25 @@ static void layer_surface_set_layer(struct wl_client *client, surface->pending.layer = layer; } +static void layer_surface_set_exclusive_edge(struct wl_client *client, + struct wl_resource *surface_resource, uint32_t edge) { + struct wlr_layer_surface_v1 *surface = + wlr_layer_surface_v1_from_resource(surface_resource); + if (!surface) { + return; + } + uint32_t version = wl_resource_get_version(surface->resource); + if (!zwlr_layer_surface_v1_anchor_is_valid(edge, version)) { + wl_resource_post_error(surface->resource, + ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_EXCLUSIVE_EDGE, + "invalid exclusive edge %" PRIu32, edge); + return; + } + + surface->pending.committed |= WLR_LAYER_SURFACE_V1_STATE_EXCLUSIVE_EDGE; + surface->pending.exclusive_edge = edge; +} + static const struct zwlr_layer_surface_v1_interface layer_surface_implementation = { .destroy = resource_handle_destroy, .ack_configure = layer_surface_handle_ack_configure, @@ -288,14 +310,12 @@ static const struct zwlr_layer_surface_v1_interface layer_surface_implementation .set_keyboard_interactivity = layer_surface_handle_set_keyboard_interactivity, .get_popup = layer_surface_handle_get_popup, .set_layer = layer_surface_set_layer, + .set_exclusive_edge = layer_surface_set_exclusive_edge, }; uint32_t wlr_layer_surface_v1_configure(struct wlr_layer_surface_v1 *surface, uint32_t width, uint32_t height) { - if (!surface->initialized) { - wlr_log(WLR_ERROR, "A configure is sent to an uninitialized wlr_layer_surface_v1 %p", - surface); - } + assert(surface->initialized); struct wl_display *display = wl_client_get_display(wl_resource_get_client(surface->resource)); @@ -336,10 +356,11 @@ static void layer_surface_role_client_commit(struct wlr_surface *wlr_surface) { return; } + uint32_t anchor = surface->pending.anchor; + const uint32_t horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; - if (surface->pending.desired_width == 0 && - (surface->pending.anchor & horiz) != horiz) { + if (surface->pending.desired_width == 0 && (anchor & horiz) != horiz) { wlr_surface_reject_pending(wlr_surface, surface->resource, ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE, "width 0 requested without setting left and right anchors"); @@ -348,13 +369,18 @@ static void layer_surface_role_client_commit(struct wlr_surface *wlr_surface) { const uint32_t vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; - if (surface->pending.desired_height == 0 && - (surface->pending.anchor & vert) != vert) { + if (surface->pending.desired_height == 0 && (anchor & vert) != vert) { wlr_surface_reject_pending(wlr_surface, surface->resource, ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE, "height 0 requested without setting top and bottom anchors"); return; } + + if ((anchor & surface->pending.exclusive_edge) != surface->pending.exclusive_edge) { + wlr_surface_reject_pending(wlr_surface, surface->resource, + ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_EXCLUSIVE_EDGE, + "exclusive edge is invalid given the surface anchors"); + } } static void layer_surface_role_commit(struct wlr_surface *wlr_surface) { @@ -417,7 +443,8 @@ static void layer_shell_handle_get_layer_surface(struct wl_client *wl_client, struct wlr_surface *wlr_surface = wlr_surface_from_resource(surface_resource); - if (layer > ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY) { + uint32_t version = wl_resource_get_version(client_resource); + if (!zwlr_layer_shell_v1_layer_is_valid(layer, version)) { wl_resource_post_error(client_resource, ZWLR_LAYER_SHELL_V1_ERROR_INVALID_LAYER, "Invalid layer %" PRIu32, layer); @@ -515,6 +542,10 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_layer_shell_v1 *layer_shell = wl_container_of(listener, layer_shell, display_destroy); wl_signal_emit_mutable(&layer_shell->events.destroy, layer_shell); + + assert(wl_list_empty(&layer_shell->events.new_surface.listener_list)); + assert(wl_list_empty(&layer_shell->events.destroy.listener_list)); + wl_list_remove(&layer_shell->display_destroy.link); wl_global_destroy(layer_shell->global); free(layer_shell); @@ -574,8 +605,8 @@ void wlr_layer_surface_v1_for_each_popup_surface(struct wlr_layer_surface_v1 *su } double popup_sx, popup_sy; - popup_sx = popup->current.geometry.x - popup->base->current.geometry.x; - popup_sy = popup->current.geometry.y - popup->base->current.geometry.y; + popup_sx = popup->current.geometry.x - popup->base->geometry.x; + popup_sy = popup->current.geometry.y - popup->base->geometry.y; struct layer_surface_iterator_data data = { .user_iterator = iterator, @@ -609,8 +640,8 @@ struct wlr_surface *wlr_layer_surface_v1_popup_surface_at( } double popup_sx, popup_sy; - popup_sx = popup->current.geometry.x - popup->base->current.geometry.x; - popup_sy = popup->current.geometry.y - popup->base->current.geometry.y; + popup_sx = popup->current.geometry.x - popup->base->geometry.x; + popup_sy = popup->current.geometry.y - popup->base->geometry.y; struct wlr_surface *sub = wlr_xdg_surface_surface_at( popup->base, sx - popup_sx, sy - popup_sy, sub_x, sub_y); @@ -621,3 +652,33 @@ struct wlr_surface *wlr_layer_surface_v1_popup_surface_at( return NULL; } + +enum wlr_edges wlr_layer_surface_v1_get_exclusive_edge(struct wlr_layer_surface_v1 *surface) { + if (surface->current.exclusive_zone <= 0) { + return WLR_EDGE_NONE; + } + uint32_t anchor = surface->current.anchor; + if (surface->current.exclusive_edge != 0) { + anchor = surface->current.exclusive_edge; + } + switch (anchor) { + case ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP: + case ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP: + return WLR_EDGE_TOP; + case ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM: + case ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM: + return WLR_EDGE_BOTTOM; + case ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT: + case ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT: + return WLR_EDGE_LEFT; + case ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT: + case ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT: + return WLR_EDGE_RIGHT; + default: + return WLR_EDGE_NONE; + } +} diff --git a/types/wlr_linux_dmabuf_v1.c b/types/wlr_linux_dmabuf_v1.c index bfd97637a..3165e8805 100644 --- a/types/wlr_linux_dmabuf_v1.c +++ b/types/wlr_linux_dmabuf_v1.c @@ -51,9 +51,7 @@ struct wlr_linux_dmabuf_feedback_v1_table_entry { uint64_t modifier; }; -// TODO: switch back to static_assert once this fix propagates in stable trees: -// https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=255290 -_Static_assert(sizeof(struct wlr_linux_dmabuf_feedback_v1_table_entry) == 16, +static_assert(sizeof(struct wlr_linux_dmabuf_feedback_v1_table_entry) == 16, "Expected wlr_linux_dmabuf_feedback_v1_table_entry to be tightly packed"); struct wlr_linux_dmabuf_v1_surface { @@ -102,11 +100,14 @@ static struct wlr_dmabuf_v1_buffer *dmabuf_v1_buffer_from_buffer( static void buffer_destroy(struct wlr_buffer *wlr_buffer) { struct wlr_dmabuf_v1_buffer *buffer = dmabuf_v1_buffer_from_buffer(wlr_buffer); + wl_list_remove(&buffer->release.link); + + wlr_buffer_finish(wlr_buffer); + if (buffer->resource != NULL) { wl_resource_set_user_data(buffer->resource, NULL); } wlr_dmabuf_attributes_finish(&buffer->attributes); - wl_list_remove(&buffer->release.link); free(buffer); } @@ -215,11 +216,11 @@ static bool check_import_dmabuf(struct wlr_dmabuf_attributes *attribs, void *dat for (int i = 0; i < attribs->n_planes; i++) { uint32_t handle = 0; if (drmPrimeFDToHandle(linux_dmabuf->main_device_fd, attribs->fd[i], &handle) != 0) { - wlr_log_errno(WLR_DEBUG, "Failed to import DMA-BUF FD"); + wlr_log_errno(WLR_ERROR, "Failed to import DMA-BUF FD for plane %d", i); return false; } if (drmCloseBufferHandle(linux_dmabuf->main_device_fd, handle) != 0) { - wlr_log_errno(WLR_ERROR, "Failed to close buffer handle"); + wlr_log_errno(WLR_ERROR, "Failed to close buffer handle for plane %d", i); return false; } } @@ -268,10 +269,8 @@ static void params_create_common(struct wl_resource *params_resource, } /* reject unknown flags */ - uint32_t all_flags = ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT | - ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_INTERLACED | - ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_BOTTOM_FIRST; - if (flags & ~all_flags) { + uint32_t version = wl_resource_get_version(params_resource); + if (!zwp_linux_buffer_params_v1_flags_is_valid(flags, version)) { wl_resource_post_error(params_resource, ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT, "Unknown dmabuf flags %"PRIu32, flags); @@ -860,6 +859,8 @@ static const struct wlr_buffer_resource_interface buffer_resource_interface = { static void linux_dmabuf_v1_destroy(struct wlr_linux_dmabuf_v1 *linux_dmabuf) { wl_signal_emit_mutable(&linux_dmabuf->events.destroy, linux_dmabuf); + assert(wl_list_empty(&linux_dmabuf->events.destroy.listener_list)); + struct wlr_linux_dmabuf_v1_surface *surface, *surface_tmp; wl_list_for_each_safe(surface, surface_tmp, &linux_dmabuf->surfaces, link) { surface_destroy(surface); @@ -959,6 +960,7 @@ struct wlr_linux_dmabuf_v1 *wlr_linux_dmabuf_v1_create(struct wl_display *displa linux_dmabuf->main_device_fd = -1; wl_list_init(&linux_dmabuf->surfaces); + wl_signal_init(&linux_dmabuf->events.destroy); linux_dmabuf->global = wl_global_create(display, &zwp_linux_dmabuf_v1_interface, diff --git a/types/wlr_linux_drm_syncobj_v1.c b/types/wlr_linux_drm_syncobj_v1.c index 82ace2d78..988d44e01 100644 --- a/types/wlr_linux_drm_syncobj_v1.c +++ b/types/wlr_linux_drm_syncobj_v1.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -25,11 +26,10 @@ struct wlr_linux_drm_syncobj_surface_v1 { }; struct wlr_linux_drm_syncobj_surface_v1_commit { - struct wlr_linux_drm_syncobj_surface_v1 *surface; + struct wlr_surface *surface; struct wlr_drm_syncobj_timeline_waiter waiter; uint32_t cached_seq; - struct wl_listener waiter_ready; struct wl_listener surface_destroy; }; @@ -192,16 +192,15 @@ static struct wlr_linux_drm_syncobj_surface_v1 *surface_from_wlr_surface( } static void surface_commit_destroy(struct wlr_linux_drm_syncobj_surface_v1_commit *commit) { - wlr_surface_unlock_cached(commit->surface->surface, commit->cached_seq); + wlr_surface_unlock_cached(commit->surface, commit->cached_seq); wl_list_remove(&commit->surface_destroy.link); - wl_list_remove(&commit->waiter_ready.link); wlr_drm_syncobj_timeline_waiter_finish(&commit->waiter); free(commit); } -static void surface_commit_handle_waiter_ready(struct wl_listener *listener, void *data) { +static void surface_commit_handle_waiter_ready(struct wlr_drm_syncobj_timeline_waiter *waiter) { struct wlr_linux_drm_syncobj_surface_v1_commit *commit = - wl_container_of(listener, commit, waiter_ready); + wl_container_of(waiter, commit, waiter); surface_commit_destroy(commit); } @@ -233,17 +232,14 @@ static bool lock_surface_commit(struct wlr_linux_drm_syncobj_surface_v1 *surface struct wl_display *display = wl_client_get_display(client); struct wl_event_loop *loop = wl_display_get_event_loop(display); if (!wlr_drm_syncobj_timeline_waiter_init(&commit->waiter, timeline, point, - flags, loop)) { + flags, loop, surface_commit_handle_waiter_ready)) { free(commit); return false; } - commit->surface = surface; + commit->surface = surface->surface; commit->cached_seq = wlr_surface_lock_pending(surface->surface); - commit->waiter_ready.notify = surface_commit_handle_waiter_ready; - wl_signal_add(&commit->waiter.events.ready, &commit->waiter_ready); - commit->surface_destroy.notify = surface_commit_handle_surface_destroy; wl_signal_add(&surface->surface->events.destroy, &commit->surface_destroy); @@ -424,6 +420,8 @@ static bool check_syncobj_eventfd(int drm_fd) { struct wlr_linux_drm_syncobj_manager_v1 *wlr_linux_drm_syncobj_manager_v1_create( struct wl_display *display, uint32_t version, int drm_fd) { + assert(version <= LINUX_DRM_SYNCOBJ_V1_VERSION); + if (!check_syncobj_eventfd(drm_fd)) { wlr_log(WLR_INFO, "DRM syncobj eventfd unavailable, disabling linux-drm-syncobj-v1"); return NULL; @@ -441,7 +439,7 @@ struct wlr_linux_drm_syncobj_manager_v1 *wlr_linux_drm_syncobj_manager_v1_create manager->global = wl_global_create(display, &wp_linux_drm_syncobj_manager_v1_interface, - LINUX_DRM_SYNCOBJ_V1_VERSION, manager, manager_bind); + version, manager, manager_bind); if (manager->global == NULL) { goto error_drm_fd; } @@ -467,3 +465,46 @@ wlr_linux_drm_syncobj_v1_get_surface_state(struct wlr_surface *wlr_surface) { } return &surface->current; } + +struct release_signaller { + struct wlr_drm_syncobj_timeline *timeline; + uint64_t point; + struct wl_listener buffer_release; +}; + +static void release_signaller_handle_buffer_release(struct wl_listener *listener, void *data) { + struct release_signaller *signaller = wl_container_of(listener, signaller, buffer_release); + + if (drmSyncobjTimelineSignal(signaller->timeline->drm_fd, &signaller->timeline->handle, + &signaller->point, 1) != 0) { + wlr_log(WLR_ERROR, "drmSyncobjTimelineSignal() failed"); + } + + wlr_drm_syncobj_timeline_unref(signaller->timeline); + wl_list_remove(&signaller->buffer_release.link); + free(signaller); +} + +bool wlr_linux_drm_syncobj_v1_state_signal_release_with_buffer( + struct wlr_linux_drm_syncobj_surface_v1_state *state, struct wlr_buffer *buffer) { + assert(buffer->n_locks > 0); + if (state->release_timeline == NULL) { + // This can happen if an existing surface with a buffer has a + // syncobj_surface_v1_state created but no new buffer with release + // timeline committed. + return true; + } + + struct release_signaller *signaller = calloc(1, sizeof(*signaller)); + if (signaller == NULL) { + return false; + } + + signaller->timeline = wlr_drm_syncobj_timeline_ref(state->release_timeline); + signaller->point = state->release_point; + + signaller->buffer_release.notify = release_signaller_handle_buffer_release; + wl_signal_add(&buffer->events.release, &signaller->buffer_release); + + return true; +} diff --git a/types/wlr_output_layer.c b/types/wlr_output_layer.c index 7624846f8..068cf58a5 100644 --- a/types/wlr_output_layer.c +++ b/types/wlr_output_layer.c @@ -1,3 +1,4 @@ +#include #include #include @@ -9,6 +10,7 @@ struct wlr_output_layer *wlr_output_layer_create(struct wlr_output *output) { wl_list_insert(&output->layers, &layer->link); wlr_addon_set_init(&layer->addons); + wl_signal_init(&layer->events.feedback); return layer; @@ -20,6 +22,9 @@ void wlr_output_layer_destroy(struct wlr_output_layer *layer) { } wlr_addon_set_finish(&layer->addons); + + assert(wl_list_empty(&layer->events.feedback.listener_list)); + wl_list_remove(&layer->link); free(layer); } diff --git a/types/wlr_output_layout.c b/types/wlr_output_layout.c index 853ff5944..ef2751179 100644 --- a/types/wlr_output_layout.c +++ b/types/wlr_output_layout.c @@ -36,6 +36,9 @@ struct wlr_output_layout *wlr_output_layout_create(struct wl_display *display) { static void output_layout_output_destroy( struct wlr_output_layout_output *l_output) { wl_signal_emit_mutable(&l_output->events.destroy, l_output); + + assert(wl_list_empty(&l_output->events.destroy.listener_list)); + wlr_output_destroy_global(l_output->output); wl_list_remove(&l_output->commit.link); wl_list_remove(&l_output->link); @@ -50,6 +53,10 @@ void wlr_output_layout_destroy(struct wlr_output_layout *layout) { wl_signal_emit_mutable(&layout->events.destroy, layout); + assert(wl_list_empty(&layout->events.add.listener_list)); + assert(wl_list_empty(&layout->events.change.listener_list)); + assert(wl_list_empty(&layout->events.destroy.listener_list)); + struct wlr_output_layout_output *l_output, *temp; wl_list_for_each_safe(l_output, temp, &layout->outputs, link) { output_layout_output_destroy(l_output); @@ -160,6 +167,7 @@ static struct wlr_output_layout_output *output_layout_output_create( } l_output->layout = layout; l_output->output = output; + wl_signal_init(&l_output->events.destroy); /* diff --git a/types/wlr_output_management_v1.c b/types/wlr_output_management_v1.c index ecc32dede..910bcbe5e 100644 --- a/types/wlr_output_management_v1.c +++ b/types/wlr_output_management_v1.c @@ -255,8 +255,7 @@ static void config_head_handle_set_transform(struct wl_client *client, return; } - if (transform < WL_OUTPUT_TRANSFORM_NORMAL || - transform > WL_OUTPUT_TRANSFORM_FLIPPED_270) { + if (!wl_output_transform_is_valid(transform, 1)) { wl_resource_post_error(config_head_resource, ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_TRANSFORM, "invalid transform"); @@ -648,6 +647,11 @@ static void manager_handle_display_destroy(struct wl_listener *listener, struct wlr_output_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.destroy.listener_list)); + assert(wl_list_empty(&manager->events.apply.listener_list)); + assert(wl_list_empty(&manager->events.test.listener_list)); + wl_list_remove(&manager->display_destroy.link); struct wlr_output_head_v1 *head, *tmp; wl_list_for_each_safe(head, tmp, &manager->heads, link) { @@ -667,6 +671,7 @@ struct wlr_output_manager_v1 *wlr_output_manager_v1_create( wl_list_init(&manager->resources); wl_list_init(&manager->heads); + wl_signal_init(&manager->events.destroy); wl_signal_init(&manager->events.apply); wl_signal_init(&manager->events.test); @@ -867,7 +872,7 @@ static void manager_send_head(struct wlr_output_manager_v1 *manager, head_send_mode(head, head_resource, mode); } - if (output->current_mode == NULL) { + if (head->state.mode == NULL && head->state.enabled) { // Output doesn't have a fixed mode set. Send a virtual one. head_send_mode(head, head_resource, NULL); } @@ -927,7 +932,7 @@ static bool manager_update_head(struct wlr_output_manager_v1 *manager, } } - if (next->mode == NULL && !head_has_custom_mode_resources(head)) { + if (next->mode == NULL && next->enabled && !head_has_custom_mode_resources(head)) { struct wl_resource *resource; wl_resource_for_each(resource, &head->resources) { head_send_mode(head, resource, NULL); diff --git a/types/wlr_output_power_management_v1.c b/types/wlr_output_power_management_v1.c index 763d0d6af..2a9948393 100644 --- a/types/wlr_output_power_management_v1.c +++ b/types/wlr_output_power_management_v1.c @@ -46,6 +46,7 @@ static void output_power_handle_output_destroy(struct wl_listener *listener, void *data) { struct wlr_output_power_v1 *output_power = wl_container_of(listener, output_power, output_destroy_listener); + zwlr_output_power_v1_send_failed(output_power->resource); output_power_destroy(output_power); } @@ -76,11 +77,8 @@ static void output_power_handle_set_mode(struct wl_client *client, return; } - switch (mode) { - case ZWLR_OUTPUT_POWER_V1_MODE_OFF: - case ZWLR_OUTPUT_POWER_V1_MODE_ON: - break; - default: + uint32_t version = wl_resource_get_version(output_power_resource); + if (!zwlr_output_power_v1_mode_is_valid(mode, version)) { wlr_log(WLR_ERROR, "Invalid power mode %d", mode); wl_resource_post_error(output_power_resource, ZWLR_OUTPUT_POWER_V1_ERROR_INVALID_MODE, @@ -195,6 +193,10 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_output_power_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.set_mode.listener_list)); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + wl_global_destroy(manager->global); free(manager); } @@ -216,6 +218,7 @@ struct wlr_output_power_manager_v1 *wlr_output_power_manager_v1_create( wl_signal_init(&manager->events.set_mode); wl_signal_init(&manager->events.destroy); + wl_list_init(&manager->output_powers); manager->display_destroy.notify = handle_display_destroy; diff --git a/types/wlr_output_swapchain_manager.c b/types/wlr_output_swapchain_manager.c index c2cca43c6..9df3a6524 100644 --- a/types/wlr_output_swapchain_manager.c +++ b/types/wlr_output_swapchain_manager.c @@ -143,7 +143,7 @@ static bool manager_output_prepare(struct wlr_output_swapchain_manager_output *m return false; } - struct wlr_buffer *buffer = wlr_swapchain_acquire(swapchain, NULL); + struct wlr_buffer *buffer = wlr_swapchain_acquire(swapchain); if (buffer == NULL) { return false; } diff --git a/types/wlr_pointer.c b/types/wlr_pointer.c index 150b90133..e99981907 100644 --- a/types/wlr_pointer.c +++ b/types/wlr_pointer.c @@ -6,6 +6,8 @@ #include #include "interfaces/wlr_input_device.h" +#include "util/set.h" +#include "util/time.h" struct wlr_pointer *wlr_pointer_from_input_device( struct wlr_input_device *input_device) { @@ -36,7 +38,45 @@ void wlr_pointer_init(struct wlr_pointer *pointer, } void wlr_pointer_finish(struct wlr_pointer *pointer) { + int64_t time_msec = get_current_time_msec(); + while (pointer->button_count > 0) { + struct wlr_pointer_button_event event = { + .pointer = pointer, + .time_msec = time_msec, + .button = pointer->buttons[pointer->button_count - 1], + .state = WL_POINTER_BUTTON_STATE_RELEASED, + }; + wlr_pointer_notify_button(pointer, &event); + } + wlr_input_device_finish(&pointer->base); + assert(wl_list_empty(&pointer->events.motion.listener_list)); + assert(wl_list_empty(&pointer->events.motion_absolute.listener_list)); + assert(wl_list_empty(&pointer->events.button.listener_list)); + assert(wl_list_empty(&pointer->events.axis.listener_list)); + assert(wl_list_empty(&pointer->events.frame.listener_list)); + assert(wl_list_empty(&pointer->events.swipe_begin.listener_list)); + assert(wl_list_empty(&pointer->events.swipe_update.listener_list)); + assert(wl_list_empty(&pointer->events.swipe_end.listener_list)); + assert(wl_list_empty(&pointer->events.pinch_begin.listener_list)); + assert(wl_list_empty(&pointer->events.pinch_update.listener_list)); + assert(wl_list_empty(&pointer->events.pinch_end.listener_list)); + assert(wl_list_empty(&pointer->events.hold_begin.listener_list)); + assert(wl_list_empty(&pointer->events.hold_end.listener_list)); + free(pointer->output_name); } + +void wlr_pointer_notify_button(struct wlr_pointer *pointer, + struct wlr_pointer_button_event *event) { + if (event->state == WL_POINTER_BUTTON_STATE_PRESSED) { + set_add(pointer->buttons, &pointer->button_count, + WLR_POINTER_BUTTONS_CAP, event->button); + } else { + set_remove(pointer->buttons, &pointer->button_count, + WLR_POINTER_BUTTONS_CAP, event->button); + } + + wl_signal_emit_mutable(&pointer->events.button, event); +} diff --git a/types/wlr_pointer_constraints_v1.c b/types/wlr_pointer_constraints_v1.c index f3c8fb74b..51a2304d8 100644 --- a/types/wlr_pointer_constraints_v1.c +++ b/types/wlr_pointer_constraints_v1.c @@ -9,6 +9,8 @@ #include #include +#include "pointer-constraints-unstable-v1-protocol.h" + static const struct zwp_locked_pointer_v1_interface locked_pointer_impl; static const struct zwp_confined_pointer_v1_interface confined_pointer_impl; static const struct zwp_pointer_constraints_v1_interface pointer_constraints_impl; @@ -39,22 +41,27 @@ static void resource_destroy(struct wl_client *client, } static void pointer_constraint_destroy(struct wlr_pointer_constraint_v1 *constraint) { - if (constraint == NULL) { + if (constraint == NULL || constraint->destroying) { return; } + // Calling wlr_pointer_constraint_v1_send_deactivated() for a oneshot constraint + // that is being destroyed results in another pointer_constraint_destroy() call. + // Avoid finalizing the state twice by setting a flag. + constraint->destroying = true; + wlr_log(WLR_DEBUG, "destroying constraint %p", constraint); wl_signal_emit_mutable(&constraint->events.destroy, constraint); + assert(wl_list_empty(&constraint->events.set_region.listener_list)); + assert(wl_list_empty(&constraint->events.destroy.listener_list)); + wl_resource_set_user_data(constraint->resource, NULL); wlr_surface_synced_finish(&constraint->synced); wl_list_remove(&constraint->link); - wl_list_remove(&constraint->surface_commit.link); wl_list_remove(&constraint->surface_destroy.link); wl_list_remove(&constraint->seat_destroy.link); - pixman_region32_fini(&constraint->current.region); - pixman_region32_fini(&constraint->pending.region); pixman_region32_fini(&constraint->region); free(constraint); } @@ -66,19 +73,6 @@ static void pointer_constraint_destroy_resource(struct wl_resource *resource) { pointer_constraint_destroy(constraint); } -static void pointer_constraint_set_region( - struct wlr_pointer_constraint_v1 *constraint, - struct wl_resource *region_resource) { - pixman_region32_clear(&constraint->pending.region); - - if (region_resource) { - const pixman_region32_t *region = wlr_region_from_resource(region_resource); - pixman_region32_copy(&constraint->pending.region, region); - } - - constraint->pending.committed |= WLR_POINTER_CONSTRAINT_V1_STATE_REGION; -} - static void pointer_constraint_handle_set_region(struct wl_client *client, struct wl_resource *resource, struct wl_resource *region_resource) { struct wlr_pointer_constraint_v1 *constraint = @@ -87,7 +81,13 @@ static void pointer_constraint_handle_set_region(struct wl_client *client, return; } - pointer_constraint_set_region(constraint, region_resource); + pixman_region32_clear(&constraint->pending.region); + if (region_resource) { + const pixman_region32_t *region = wlr_region_from_resource(region_resource); + pixman_region32_copy(&constraint->pending.region, region); + } + + constraint->pending.committed |= WLR_POINTER_CONSTRAINT_V1_STATE_REGION; } static void pointer_constraint_set_cursor_position_hint(struct wl_client *client, @@ -104,27 +104,27 @@ static void pointer_constraint_set_cursor_position_hint(struct wl_client *client constraint->pending.committed |= WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT; } -static void pointer_constraint_commit( - struct wlr_pointer_constraint_v1 *constraint) { - pixman_region32_clear(&constraint->region); - if (pixman_region32_not_empty(&constraint->current.region)) { - pixman_region32_intersect(&constraint->region, +// Returns true if the region has changed +static bool update_region(struct wlr_pointer_constraint_v1 *constraint) { + pixman_region32_t region; + pixman_region32_init(®ion); + + if (!pixman_region32_empty(&constraint->current.region)) { + pixman_region32_intersect(®ion, &constraint->surface->input_region, &constraint->current.region); } else { - pixman_region32_copy(&constraint->region, - &constraint->surface->input_region); + pixman_region32_copy(®ion, &constraint->surface->input_region); } - if (constraint->current.committed & WLR_POINTER_CONSTRAINT_V1_STATE_REGION) { - wl_signal_emit_mutable(&constraint->events.set_region, NULL); + if (pixman_region32_equal(®ion, &constraint->region)) { + pixman_region32_fini(®ion); + return false; } -} -static void handle_surface_commit(struct wl_listener *listener, void *data) { - struct wlr_pointer_constraint_v1 *constraint = - wl_container_of(listener, constraint, surface_commit); + pixman_region32_fini(&constraint->region); + constraint->region = region; - pointer_constraint_commit(constraint); + return true; } static void handle_surface_destroy(struct wl_listener *listener, void *data) { @@ -176,11 +176,20 @@ static void surface_synced_move_state(void *_dst, void *_src) { src->committed = 0; } +static void surface_synced_commit(struct wlr_surface_synced *synced) { + struct wlr_pointer_constraint_v1 *constraint = wl_container_of(synced, constraint, synced); + + if (update_region(constraint)) { + wl_signal_emit_mutable(&constraint->events.set_region, NULL); + } +} + static const struct wlr_surface_synced_impl surface_synced_impl = { .state_size = sizeof(struct wlr_pointer_constraint_v1_state), .init_state = surface_synced_init_state, .finish_state = surface_synced_finish_state, .move_state = surface_synced_move_state, + .commit = surface_synced_commit, }; static void pointer_constraint_create(struct wl_client *client, @@ -258,14 +267,11 @@ static void pointer_constraint_create(struct wl_client *client, pixman_region32_init(&constraint->region); - pixman_region32_init(&constraint->pending.region); - pixman_region32_init(&constraint->current.region); - - pointer_constraint_set_region(constraint, region_resource); - pointer_constraint_commit(constraint); - - constraint->surface_commit.notify = handle_surface_commit; - wl_signal_add(&surface->events.commit, &constraint->surface_commit); + if (region_resource) { + pixman_region32_copy(&constraint->current.region, + wlr_region_from_resource(region_resource)); + update_region(constraint); + } constraint->surface_destroy.notify = handle_surface_destroy; wl_signal_add(&surface->events.destroy, &constraint->surface_destroy); @@ -327,6 +333,11 @@ static void pointer_constraints_bind(struct wl_client *client, void *data, static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_pointer_constraints_v1 *pointer_constraints = wl_container_of(listener, pointer_constraints, display_destroy); + wl_signal_emit_mutable(&pointer_constraints->events.destroy, NULL); + + assert(wl_list_empty(&pointer_constraints->events.destroy.listener_list)); + assert(wl_list_empty(&pointer_constraints->events.new_constraint.listener_list)); + wl_list_remove(&pointer_constraints->display_destroy.link); wl_global_destroy(pointer_constraints->global); free(pointer_constraints); @@ -350,6 +361,8 @@ struct wlr_pointer_constraints_v1 *wlr_pointer_constraints_v1_create( pointer_constraints->global = wl_global; wl_list_init(&pointer_constraints->constraints); + + wl_signal_init(&pointer_constraints->events.destroy); wl_signal_init(&pointer_constraints->events.new_constraint); pointer_constraints->display_destroy.notify = handle_display_destroy; diff --git a/types/wlr_pointer_gestures_v1.c b/types/wlr_pointer_gestures_v1.c index ebb1c8689..a027faac9 100644 --- a/types/wlr_pointer_gestures_v1.c +++ b/types/wlr_pointer_gestures_v1.c @@ -397,6 +397,9 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_pointer_gestures_v1 *gestures = wl_container_of(listener, gestures, display_destroy); wl_signal_emit_mutable(&gestures->events.destroy, NULL); + + assert(wl_list_empty(&gestures->events.destroy.listener_list)); + wl_list_remove(&gestures->display_destroy.link); wl_global_destroy(gestures->global); free(gestures); diff --git a/types/wlr_presentation_time.c b/types/wlr_presentation_time.c index 35b960d02..c4366f0fe 100644 --- a/types/wlr_presentation_time.c +++ b/types/wlr_presentation_time.c @@ -7,7 +7,7 @@ #include #include "presentation-time-protocol.h" -#define PRESENTATION_VERSION 1 +#define PRESENTATION_VERSION 2 struct wlr_presentation_surface_state { struct wlr_presentation_feedback *feedback; @@ -166,20 +166,25 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_presentation *presentation = wl_container_of(listener, presentation, display_destroy); wl_signal_emit_mutable(&presentation->events.destroy, presentation); + + assert(wl_list_empty(&presentation->events.destroy.listener_list)); + wl_list_remove(&presentation->display_destroy.link); wl_global_destroy(presentation->global); free(presentation); } struct wlr_presentation *wlr_presentation_create(struct wl_display *display, - struct wlr_backend *backend) { + struct wlr_backend *backend, uint32_t version) { + assert(version <= PRESENTATION_VERSION); + struct wlr_presentation *presentation = calloc(1, sizeof(*presentation)); if (presentation == NULL) { return NULL; } presentation->global = wl_global_create(display, &wp_presentation_interface, - PRESENTATION_VERSION, NULL, presentation_bind); + version, NULL, presentation_bind); if (presentation->global == NULL) { free(presentation); return NULL; @@ -239,8 +244,8 @@ void wlr_presentation_event_from_output(struct wlr_presentation_event *event, const struct wlr_output_event_present *output_event) { *event = (struct wlr_presentation_event){ .output = output_event->output, - .tv_sec = (uint64_t)output_event->when->tv_sec, - .tv_nsec = (uint32_t)output_event->when->tv_nsec, + .tv_sec = (uint64_t)output_event->when.tv_sec, + .tv_nsec = (uint32_t)output_event->when.tv_nsec, .refresh = (uint32_t)output_event->refresh, .seq = (uint64_t)output_event->seq, .flags = output_event->flags, @@ -283,6 +288,11 @@ static void feedback_handle_output_present(struct wl_listener *listener, if (output_event->presented) { struct wlr_presentation_event event = {0}; wlr_presentation_event_from_output(&event, output_event); + struct wl_resource *resource = wl_resource_from_link(feedback->resources.next); + if (wl_resource_get_version(resource) == 1 && + event.output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED) { + event.refresh = 0; + } if (!feedback->zero_copy) { event.flags &= ~WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY; } diff --git a/types/wlr_primary_selection.c b/types/wlr_primary_selection.c index e3212d6fe..1940a2d82 100644 --- a/types/wlr_primary_selection.c +++ b/types/wlr_primary_selection.c @@ -11,6 +11,7 @@ void wlr_primary_selection_source_init( .impl = impl, }; wl_array_init(&source->mime_types); + wl_signal_init(&source->events.destroy); } @@ -22,6 +23,8 @@ void wlr_primary_selection_source_destroy( wl_signal_emit_mutable(&source->events.destroy, source); + assert(wl_list_empty(&source->events.destroy.listener_list)); + char **p; wl_array_for_each(p, &source->mime_types) { free(*p); diff --git a/types/wlr_primary_selection_v1.c b/types/wlr_primary_selection_v1.c index 0a5f0fbdc..fe70b7784 100644 --- a/types/wlr_primary_selection_v1.c +++ b/types/wlr_primary_selection_v1.c @@ -462,6 +462,9 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { } wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.destroy.listener_list)); + wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); @@ -483,6 +486,7 @@ struct wlr_primary_selection_v1_device_manager * } wl_list_init(&manager->devices); + wl_signal_init(&manager->events.destroy); manager->display_destroy.notify = handle_display_destroy; diff --git a/types/wlr_relative_pointer_v1.c b/types/wlr_relative_pointer_v1.c index 94fb1555e..9f4ab6fd7 100644 --- a/types/wlr_relative_pointer_v1.c +++ b/types/wlr_relative_pointer_v1.c @@ -28,6 +28,8 @@ static struct wlr_relative_pointer_manager_v1 *relative_pointer_manager_from_res static void relative_pointer_destroy(struct wlr_relative_pointer_v1 *relative_pointer) { wl_signal_emit_mutable(&relative_pointer->events.destroy, relative_pointer); + assert(wl_list_empty(&relative_pointer->events.destroy.listener_list)); + wl_list_remove(&relative_pointer->link); wl_list_remove(&relative_pointer->seat_destroy.link); wl_list_remove(&relative_pointer->pointer_destroy.link); @@ -139,6 +141,10 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_relative_pointer_manager_v1 *manager = wl_container_of(listener, manager, display_destroy_listener); wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.destroy.listener_list)); + assert(wl_list_empty(&manager->events.new_relative_pointer.listener_list)); + wl_list_remove(&manager->display_destroy_listener.link); wl_global_destroy(manager->global); free(manager); diff --git a/types/wlr_screencopy_v1.c b/types/wlr_screencopy_v1.c index acd584d5d..58e8dc8ba 100644 --- a/types/wlr_screencopy_v1.c +++ b/types/wlr_screencopy_v1.c @@ -146,7 +146,6 @@ static void frame_destroy(struct wlr_screencopy_frame_v1 *frame) { wl_list_remove(&frame->link); wl_list_remove(&frame->output_commit.link); wl_list_remove(&frame->output_destroy.link); - wl_list_remove(&frame->output_enable.link); // Make the frame resource inert wl_resource_set_user_data(frame->resource, NULL); wlr_buffer_unlock(frame->buffer); @@ -165,17 +164,19 @@ static void frame_send_damage(struct wlr_screencopy_frame_v1 *frame) { return; } - // TODO: send fine-grained damage events - struct pixman_box32 *damage_box = - pixman_region32_extents(&damage->damage); + int n_boxes; + const pixman_box32_t *boxes = pixman_region32_rectangles(&damage->damage, &n_boxes); + for (int i = 0; i < n_boxes; i++) { + const pixman_box32_t *box = &boxes[i]; - int damage_x = damage_box->x1; - int damage_y = damage_box->y1; - int damage_width = damage_box->x2 - damage_box->x1; - int damage_height = damage_box->y2 - damage_box->y1; + int damage_x = box->x1; + int damage_y = box->y1; + int damage_width = box->x2 - box->x1; + int damage_height = box->y2 - box->y1; - zwlr_screencopy_frame_v1_send_damage(frame->resource, - damage_x, damage_y, damage_width, damage_height); + zwlr_screencopy_frame_v1_send_damage(frame->resource, + damage_x, damage_y, damage_width, damage_height); + } pixman_region32_clear(&damage->damage); } @@ -286,6 +287,10 @@ static void frame_handle_output_commit(struct wl_listener *listener, struct wlr_output_event_commit *event = data; struct wlr_output *output = frame->output; + if (event->state->committed & WLR_OUTPUT_STATE_ENABLED && !output->enabled) { + goto err; + } + if (!(event->state->committed & WLR_OUTPUT_STATE_BUFFER)) { return; } @@ -297,7 +302,7 @@ static void frame_handle_output_commit(struct wl_listener *listener, if (frame->with_damage) { struct screencopy_damage *damage = screencopy_damage_get_or_create(frame->client, output); - if (damage && !pixman_region32_not_empty(&damage->damage)) { + if (damage && pixman_region32_empty(&damage->damage)) { return; } } @@ -329,7 +334,7 @@ static void frame_handle_output_commit(struct wl_listener *listener, zwlr_screencopy_frame_v1_send_flags(frame->resource, 0); frame_send_damage(frame); - frame_send_ready(frame, event->when); + frame_send_ready(frame, &event->when); frame_destroy(frame); return; @@ -338,16 +343,6 @@ err: frame_destroy(frame); } -static void frame_handle_output_enable(struct wl_listener *listener, - void *data) { - struct wlr_screencopy_frame_v1 *frame = - wl_container_of(listener, frame, output_enable); - if (!frame->output->enabled) { - zwlr_screencopy_frame_v1_send_failed(frame->resource); - frame_destroy(frame); - } -} - static void frame_handle_output_destroy(struct wl_listener *listener, void *data) { struct wlr_screencopy_frame_v1 *frame = @@ -439,9 +434,6 @@ static void frame_handle_copy(struct wl_client *wl_client, wl_signal_add(&output->events.commit, &frame->output_commit); frame->output_commit.notify = frame_handle_output_commit; - wl_signal_add(&output->events.destroy, &frame->output_enable); - frame->output_enable.notify = frame_handle_output_enable; - // Request a frame because we can't assume that the current front buffer is still usable. It may // have been released already, and we shouldn't lock it here because compositors want to render // into the least damaged buffer. @@ -526,7 +518,6 @@ static void capture_output(struct wl_client *wl_client, wl_list_insert(&client->manager->frames, &frame->link); wl_list_init(&frame->output_commit.link); - wl_list_init(&frame->output_enable.link); wl_signal_add(&output->events.destroy, &frame->output_destroy); frame->output_destroy.notify = frame_handle_output_destroy; @@ -542,8 +533,7 @@ static void capture_output(struct wl_client *wl_client, goto error; } - int buffer_age; - struct wlr_buffer *buffer = wlr_swapchain_acquire(output->swapchain, &buffer_age); + struct wlr_buffer *buffer = wlr_swapchain_acquire(output->swapchain); if (buffer == NULL) { goto error; } @@ -699,6 +689,9 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_screencopy_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.destroy.listener_list)); + wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); diff --git a/types/wlr_security_context_v1.c b/types/wlr_security_context_v1.c index 337114487..da430c012 100644 --- a/types/wlr_security_context_v1.c +++ b/types/wlr_security_context_v1.c @@ -390,6 +390,7 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_security_context_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); + assert(wl_list_empty(&manager->events.destroy.listener_list)); assert(wl_list_empty(&manager->events.commit.listener_list)); @@ -419,6 +420,7 @@ struct wlr_security_context_manager_v1 *wlr_security_context_manager_v1_create( } wl_list_init(&manager->contexts); + wl_signal_init(&manager->events.destroy); wl_signal_init(&manager->events.commit); diff --git a/types/wlr_server_decoration.c b/types/wlr_server_decoration.c index 3d3d1ff3f..a6f33b469 100644 --- a/types/wlr_server_decoration.c +++ b/types/wlr_server_decoration.c @@ -36,6 +36,10 @@ static void server_decoration_handle_request_mode(struct wl_client *client, static void server_decoration_destroy( struct wlr_server_decoration *decoration) { wl_signal_emit_mutable(&decoration->events.destroy, decoration); + + assert(wl_list_empty(&decoration->events.destroy.listener_list)); + assert(wl_list_empty(&decoration->events.mode.listener_list)); + wl_list_remove(&decoration->surface_destroy_listener.link); wl_resource_set_user_data(decoration->resource, NULL); wl_list_remove(&decoration->link); @@ -164,6 +168,10 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_server_decoration_manager *manager = wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.new_decoration.listener_list)); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); @@ -185,6 +193,7 @@ struct wlr_server_decoration_manager *wlr_server_decoration_manager_create( manager->default_mode = ORG_KDE_KWIN_SERVER_DECORATION_MANAGER_MODE_NONE; wl_list_init(&manager->resources); wl_list_init(&manager->decorations); + wl_signal_init(&manager->events.new_decoration); wl_signal_init(&manager->events.destroy); diff --git a/types/wlr_shm.c b/types/wlr_shm.c index 1f45c88a3..171724471 100644 --- a/types/wlr_shm.c +++ b/types/wlr_shm.c @@ -155,8 +155,11 @@ static void pool_consider_destroy(struct wlr_shm_pool *pool); static void buffer_destroy(struct wlr_buffer *wlr_buffer) { struct wlr_shm_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); - assert(buffer->resource == NULL); wl_list_remove(&buffer->release.link); + + wlr_buffer_finish(wlr_buffer); + + assert(buffer->resource == NULL); wl_list_remove(&buffer->link); pool_consider_destroy(buffer->pool); free(buffer); diff --git a/types/wlr_single_pixel_buffer_v1.c b/types/wlr_single_pixel_buffer_v1.c index 2dab6c53c..9b82717fa 100644 --- a/types/wlr_single_pixel_buffer_v1.c +++ b/types/wlr_single_pixel_buffer_v1.c @@ -8,15 +8,6 @@ #define SINGLE_PIXEL_MANAGER_VERSION 1 -struct wlr_single_pixel_buffer_v1 { - struct wlr_buffer base; - struct wl_resource *resource; - uint32_t r, g, b, a; - uint8_t argb8888[4]; // packed little-endian DRM_FORMAT_ARGB8888 - - struct wl_listener release; -}; - static void destroy_resource(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); @@ -53,10 +44,13 @@ static const struct wlr_buffer_resource_interface buffer_resource_interface = { static void buffer_destroy(struct wlr_buffer *wlr_buffer) { struct wlr_single_pixel_buffer_v1 *buffer = wl_container_of(wlr_buffer, buffer, base); + wl_list_remove(&buffer->release.link); + + wlr_buffer_finish(wlr_buffer); + if (buffer->resource != NULL) { wl_resource_set_user_data(buffer->resource, NULL); } - wl_list_remove(&buffer->release.link); free(buffer); } @@ -177,3 +171,14 @@ struct wlr_single_pixel_buffer_manager_v1 *wlr_single_pixel_buffer_manager_v1_cr return manager; } + +struct wlr_single_pixel_buffer_v1 *wlr_single_pixel_buffer_v1_try_from_buffer( + struct wlr_buffer *buffer) { + + if (buffer->impl != &buffer_impl) { + return NULL; + } + + return wl_container_of(buffer, + (struct wlr_single_pixel_buffer_v1 *)NULL, base); +} diff --git a/types/wlr_subcompositor.c b/types/wlr_subcompositor.c index 771b98739..062fa69b8 100644 --- a/types/wlr_subcompositor.c +++ b/types/wlr_subcompositor.c @@ -33,6 +33,8 @@ static void subsurface_destroy(struct wlr_subsurface *subsurface) { wl_signal_emit_mutable(&subsurface->events.destroy, subsurface); + assert(wl_list_empty(&subsurface->events.destroy.listener_list)); + wlr_surface_synced_finish(&subsurface->parent_synced); wl_list_remove(&subsurface->surface_client_commit.link); @@ -289,9 +291,6 @@ void subsurface_handle_parent_commit(struct wlr_subsurface *subsurface) { subsurface); subsurface_consider_map(subsurface); } - - subsurface->previous.x = subsurface->current.x; - subsurface->previous.y = subsurface->current.y; } struct wlr_subsurface *wlr_subsurface_try_from_wlr_surface(struct wlr_surface *surface) { @@ -409,6 +408,9 @@ static void subcompositor_handle_display_destroy( struct wlr_subcompositor *subcompositor = wl_container_of(listener, subcompositor, display_destroy); wl_signal_emit_mutable(&subcompositor->events.destroy, NULL); + + assert(wl_list_empty(&subcompositor->events.destroy.listener_list)); + wl_list_remove(&subcompositor->display_destroy.link); wl_global_destroy(subcompositor->global); free(subcompositor); diff --git a/types/wlr_switch.c b/types/wlr_switch.c index 93033aaba..439c29333 100644 --- a/types/wlr_switch.c +++ b/types/wlr_switch.c @@ -25,4 +25,6 @@ void wlr_switch_init(struct wlr_switch *switch_device, void wlr_switch_finish(struct wlr_switch *switch_device) { wlr_input_device_finish(&switch_device->base); + + assert(wl_list_empty(&switch_device->events.toggle.listener_list)); } diff --git a/types/wlr_tablet_pad.c b/types/wlr_tablet_pad.c index 2c4c04ac3..bfe17e68d 100644 --- a/types/wlr_tablet_pad.c +++ b/types/wlr_tablet_pad.c @@ -33,6 +33,11 @@ void wlr_tablet_pad_init(struct wlr_tablet_pad *pad, void wlr_tablet_pad_finish(struct wlr_tablet_pad *pad) { wlr_input_device_finish(&pad->base); + assert(wl_list_empty(&pad->events.button.listener_list)); + assert(wl_list_empty(&pad->events.ring.listener_list)); + assert(wl_list_empty(&pad->events.strip.listener_list)); + assert(wl_list_empty(&pad->events.attach_tablet.listener_list)); + char **path_ptr; wl_array_for_each(path_ptr, &pad->paths) { free(*path_ptr); diff --git a/types/wlr_tablet_tool.c b/types/wlr_tablet_tool.c index 77f4631e8..2b15f57ee 100644 --- a/types/wlr_tablet_tool.c +++ b/types/wlr_tablet_tool.c @@ -24,12 +24,18 @@ void wlr_tablet_init(struct wlr_tablet *tablet, wl_signal_init(&tablet->events.proximity); wl_signal_init(&tablet->events.tip); wl_signal_init(&tablet->events.button); + wl_array_init(&tablet->paths); } void wlr_tablet_finish(struct wlr_tablet *tablet) { wlr_input_device_finish(&tablet->base); + assert(wl_list_empty(&tablet->events.axis.listener_list)); + assert(wl_list_empty(&tablet->events.proximity.listener_list)); + assert(wl_list_empty(&tablet->events.tip.listener_list)); + assert(wl_list_empty(&tablet->events.button.listener_list)); + char **path_ptr; wl_array_for_each(path_ptr, &tablet->paths) { free(*path_ptr); diff --git a/types/wlr_tearing_control_v1.c b/types/wlr_tearing_control_v1.c index cd7cd5a59..826cf5471 100644 --- a/types/wlr_tearing_control_v1.c +++ b/types/wlr_tearing_control_v1.c @@ -1,4 +1,3 @@ - #include #include #include @@ -34,6 +33,9 @@ static void destroy_tearing_hint(struct wlr_tearing_control_v1 *hint) { wl_signal_emit_mutable(&hint->events.destroy, NULL); + assert(wl_list_empty(&hint->events.set_hint.listener_list)); + assert(wl_list_empty(&hint->events.destroy.listener_list)); + wl_list_remove(&hint->link); wl_resource_set_user_data(hint->resource, NULL); @@ -122,6 +124,7 @@ static void tearing_control_manager_handle_get_tearing_control( wl_resource_get_version(resource), id); if (created_resource == NULL) { + free(hint); wl_resource_post_no_memory(resource); return; } @@ -169,6 +172,9 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_signal_emit_mutable(&manager->events.destroy, NULL); + assert(wl_list_empty(&manager->events.new_object.listener_list)); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + struct wlr_tearing_control_v1 *hint, *tmp; wl_list_for_each_safe(hint, tmp, &manager->surface_hints, link) { destroy_tearing_hint(hint); diff --git a/types/wlr_text_input_v3.c b/types/wlr_text_input_v3.c index 0b05ca656..bbae3a076 100644 --- a/types/wlr_text_input_v3.c +++ b/types/wlr_text_input_v3.c @@ -64,7 +64,13 @@ void wlr_text_input_v3_send_done(struct wlr_text_input_v3 *text_input) { } static void wlr_text_input_destroy(struct wlr_text_input_v3 *text_input) { - wl_signal_emit_mutable(&text_input->events.destroy, text_input); + wl_signal_emit_mutable(&text_input->events.destroy, NULL); + + assert(wl_list_empty(&text_input->events.enable.listener_list)); + assert(wl_list_empty(&text_input->events.commit.listener_list)); + assert(wl_list_empty(&text_input->events.disable.listener_list)); + assert(wl_list_empty(&text_input->events.destroy.listener_list)); + text_input_clear_focused_surface(text_input); wl_list_remove(&text_input->seat_destroy.link); // remove from manager.text_inputs @@ -186,12 +192,12 @@ static void text_input_commit(struct wl_client *client, if (!old_enabled && text_input->current_enabled) { text_input->active_features = text_input->current.features; - wl_signal_emit_mutable(&text_input->events.enable, text_input); + wl_signal_emit_mutable(&text_input->events.enable, NULL); } else if (old_enabled && !text_input->current_enabled) { text_input->active_features = 0; - wl_signal_emit_mutable(&text_input->events.disable, text_input); + wl_signal_emit_mutable(&text_input->events.disable, NULL); } else { // including never enabled - wl_signal_emit_mutable(&text_input->events.commit, text_input); + wl_signal_emit_mutable(&text_input->events.commit, NULL); } } @@ -281,7 +287,7 @@ static void text_input_manager_get_text_input(struct wl_client *client, text_input_manager_from_resource(resource); wl_list_insert(&manager->text_inputs, &text_input->link); - wl_signal_emit_mutable(&manager->events.text_input, text_input); + wl_signal_emit_mutable(&manager->events.new_text_input, text_input); } static const struct zwp_text_input_manager_v3_interface @@ -307,7 +313,11 @@ static void text_input_manager_bind(struct wl_client *wl_client, void *data, static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_text_input_manager_v3 *manager = wl_container_of(listener, manager, display_destroy); - wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_signal_emit_mutable(&manager->events.destroy, NULL); + + assert(wl_list_empty(&manager->events.new_text_input.listener_list)); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); @@ -321,7 +331,8 @@ struct wlr_text_input_manager_v3 *wlr_text_input_manager_v3_create( } wl_list_init(&manager->text_inputs); - wl_signal_init(&manager->events.text_input); + + wl_signal_init(&manager->events.new_text_input); wl_signal_init(&manager->events.destroy); manager->global = wl_global_create(display, diff --git a/types/wlr_touch.c b/types/wlr_touch.c index 66b35e544..f8c4175fb 100644 --- a/types/wlr_touch.c +++ b/types/wlr_touch.c @@ -30,5 +30,11 @@ void wlr_touch_init(struct wlr_touch *touch, void wlr_touch_finish(struct wlr_touch *touch) { wlr_input_device_finish(&touch->base); + assert(wl_list_empty(&touch->events.down.listener_list)); + assert(wl_list_empty(&touch->events.up.listener_list)); + assert(wl_list_empty(&touch->events.motion.listener_list)); + assert(wl_list_empty(&touch->events.cancel.listener_list)); + assert(wl_list_empty(&touch->events.frame.listener_list)); + free(touch->output_name); } diff --git a/types/wlr_transient_seat_v1.c b/types/wlr_transient_seat_v1.c index 379ce0a37..d99ee6edb 100644 --- a/types/wlr_transient_seat_v1.c +++ b/types/wlr_transient_seat_v1.c @@ -64,6 +64,7 @@ static void manager_create_transient_seat(struct wl_client *client, wl_resource_set_implementation(seat->resource, &transient_seat_impl, seat, transient_seat_handle_resource_destroy); + wl_list_init(&seat->seat_destroy.link); wl_signal_emit_mutable(&manager->events.create_seat, seat); return; @@ -114,6 +115,10 @@ static const struct ext_transient_seat_manager_v1_interface manager_impl = { static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_transient_seat_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, NULL); + + assert(wl_list_empty(&manager->events.destroy.listener_list)); + assert(wl_list_empty(&manager->events.create_seat.listener_list)); wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); @@ -154,6 +159,7 @@ struct wlr_transient_seat_manager_v1 *wlr_transient_seat_manager_v1_create( manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(display, &manager->display_destroy); + wl_signal_init(&manager->events.destroy); wl_signal_init(&manager->events.create_seat); return manager; diff --git a/types/wlr_viewporter.c b/types/wlr_viewporter.c index 38c204380..4a755113a 100644 --- a/types/wlr_viewporter.c +++ b/types/wlr_viewporter.c @@ -230,6 +230,9 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_viewporter *viewporter = wl_container_of(listener, viewporter, display_destroy); wl_signal_emit_mutable(&viewporter->events.destroy, NULL); + + assert(wl_list_empty(&viewporter->events.destroy.listener_list)); + wl_global_destroy(viewporter->global); free(viewporter); } diff --git a/types/wlr_virtual_keyboard_v1.c b/types/wlr_virtual_keyboard_v1.c index 8a6d107d5..e7dcb3ec3 100644 --- a/types/wlr_virtual_keyboard_v1.c +++ b/types/wlr_virtual_keyboard_v1.c @@ -209,6 +209,10 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_virtual_keyboard_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.new_virtual_keyboard.listener_list)); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); diff --git a/types/wlr_virtual_pointer_v1.c b/types/wlr_virtual_pointer_v1.c index 2ae39a0a8..b867c8fb1 100644 --- a/types/wlr_virtual_pointer_v1.c +++ b/types/wlr_virtual_pointer_v1.c @@ -73,7 +73,7 @@ static void virtual_pointer_button(struct wl_client *client, .button = button, .state = state ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED, }; - wl_signal_emit_mutable(&pointer->pointer.events.button, &event); + wlr_pointer_notify_button(&pointer->pointer, &event); } static void virtual_pointer_axis(struct wl_client *client, @@ -308,6 +308,10 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_virtual_pointer_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.new_virtual_pointer.listener_list)); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); struct wlr_virtual_pointer_v1 *pointer, *pointer_tmp; @@ -329,6 +333,7 @@ struct wlr_virtual_pointer_manager_v1* wlr_virtual_pointer_manager_v1_create( wl_signal_init(&manager->events.new_virtual_pointer); wl_signal_init(&manager->events.destroy); + manager->global = wl_global_create(display, &zwlr_virtual_pointer_manager_v1_interface, 2, manager, virtual_pointer_manager_bind); diff --git a/types/wlr_xdg_activation_v1.c b/types/wlr_xdg_activation_v1.c index 8e0fb0618..4645ccc0e 100644 --- a/types/wlr_xdg_activation_v1.c +++ b/types/wlr_xdg_activation_v1.c @@ -33,6 +33,8 @@ void wlr_xdg_activation_token_v1_destroy( wl_signal_emit_mutable(&token->events.destroy, NULL); + assert(wl_list_empty(&token->events.destroy.listener_list)); + wl_list_remove(&token->link); wl_list_remove(&token->seat_destroy.link); wl_list_remove(&token->surface_destroy.link); @@ -256,6 +258,7 @@ static struct wlr_xdg_activation_token_v1 *activation_token_create( wl_list_init(&token->link); wl_list_init(&token->seat_destroy.link); wl_list_init(&token->surface_destroy.link); + wl_signal_init(&token->events.destroy); token->activation = activation; @@ -340,6 +343,10 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_container_of(listener, activation, display_destroy); wl_signal_emit_mutable(&activation->events.destroy, NULL); + assert(wl_list_empty(&activation->events.destroy.listener_list)); + assert(wl_list_empty(&activation->events.request_activate.listener_list)); + assert(wl_list_empty(&activation->events.new_token.listener_list)); + struct wlr_xdg_activation_token_v1 *token, *token_tmp; wl_list_for_each_safe(token, token_tmp, &activation->tokens, link) { wlr_xdg_activation_token_v1_destroy(token); @@ -359,6 +366,7 @@ struct wlr_xdg_activation_v1 *wlr_xdg_activation_v1_create( activation->token_timeout_msec = 30000; // 30s wl_list_init(&activation->tokens); + wl_signal_init(&activation->events.destroy); wl_signal_init(&activation->events.request_activate); wl_signal_init(&activation->events.new_token); diff --git a/types/wlr_xdg_decoration_v1.c b/types/wlr_xdg_decoration_v1.c index d16ac7c5a..4194942a2 100644 --- a/types/wlr_xdg_decoration_v1.c +++ b/types/wlr_xdg_decoration_v1.c @@ -62,6 +62,10 @@ static void toplevel_decoration_handle_resource_destroy( struct wlr_xdg_toplevel_decoration_v1 *decoration = toplevel_decoration_from_resource(resource); wl_signal_emit_mutable(&decoration->events.destroy, decoration); + + assert(wl_list_empty(&decoration->events.destroy.listener_list)); + assert(wl_list_empty(&decoration->events.request_mode.listener_list)); + wlr_surface_synced_finish(&decoration->synced); wl_list_remove(&decoration->toplevel_destroy.link); wl_list_remove(&decoration->surface_configure.link); @@ -217,6 +221,7 @@ static void decoration_manager_handle_get_toplevel_decoration( decoration->resource); wl_list_init(&decoration->configure_list); + wl_signal_init(&decoration->events.destroy); wl_signal_init(&decoration->events.request_mode); @@ -256,6 +261,10 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_xdg_decoration_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.new_toplevel_decoration.listener_list)); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); @@ -275,6 +284,7 @@ struct wlr_xdg_decoration_manager_v1 * return NULL; } wl_list_init(&manager->decorations); + wl_signal_init(&manager->events.new_toplevel_decoration); wl_signal_init(&manager->events.destroy); diff --git a/types/wlr_xdg_dialog_v1.c b/types/wlr_xdg_dialog_v1.c new file mode 100644 index 000000000..dc9cb2470 --- /dev/null +++ b/types/wlr_xdg_dialog_v1.c @@ -0,0 +1,201 @@ +#include +#include + +#include +#include + +#include "xdg-dialog-v1-protocol.h" + +// NOTE: xdg_dialog_v1 becomes inert when the corresponding xdg_toplevel is destroyed + +#define XDG_WM_DIALOG_V1_VERSION 1 + +static const struct xdg_dialog_v1_interface dialog_impl; + +static struct wlr_xdg_dialog_v1 *dialog_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &xdg_dialog_v1_interface, &dialog_impl)); + return wl_resource_get_user_data(resource); +} + +static const struct xdg_wm_dialog_v1_interface wm_impl; + +static struct wlr_xdg_wm_dialog_v1 *wm_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &xdg_wm_dialog_v1_interface, &wm_impl)); + return wl_resource_get_user_data(resource); +} + +static void resource_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void surface_addon_destroy(struct wlr_addon *addon) { + // As wlr_xdg_toplevel is always destroyed before the surface, this should + // never be reached. + abort(); +} + +static const struct wlr_addon_interface surface_addon_impl = { + .name = "xdg_wm_dialog_v1", + .destroy = surface_addon_destroy, +}; + +static void set_modal(struct wlr_xdg_dialog_v1 *dialog, bool modal) { + if (dialog->modal == modal) { + return; + } + dialog->modal = modal; + wl_signal_emit_mutable(&dialog->events.set_modal, NULL); +} + +static void dialog_handle_set_modal(struct wl_client *client, struct wl_resource *resource) { + struct wlr_xdg_dialog_v1 *dialog = dialog_from_resource(resource); + if (dialog == NULL) { + return; + } + set_modal(dialog, true); +} + +static void dialog_handle_unset_modal(struct wl_client *client, struct wl_resource *resource) { + struct wlr_xdg_dialog_v1 *dialog = dialog_from_resource(resource); + if (dialog == NULL) { + return; + } + set_modal(dialog, false); +}; + +static const struct xdg_dialog_v1_interface dialog_impl = { + .destroy = resource_destroy, + .set_modal = dialog_handle_set_modal, + .unset_modal = dialog_handle_unset_modal, +}; + +static void dialog_destroy(struct wlr_xdg_dialog_v1 *dialog) { + if (dialog == NULL) { + return; + } + + wl_signal_emit_mutable(&dialog->events.destroy, NULL); + + assert(wl_list_empty(&dialog->events.destroy.listener_list)); + assert(wl_list_empty(&dialog->events.set_modal.listener_list)); + + wlr_addon_finish(&dialog->surface_addon); + wl_list_remove(&dialog->xdg_toplevel_destroy.link); + + wl_resource_set_user_data(dialog->resource, NULL); + free(dialog); +} + +static void handle_xdg_toplevel_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_dialog_v1 *dialog = wl_container_of(listener, dialog, xdg_toplevel_destroy); + dialog_destroy(dialog); +} + +static void handle_resource_destroy(struct wl_resource *resource) { + struct wlr_xdg_dialog_v1 *dialog = dialog_from_resource(resource); + dialog_destroy(dialog); +} + +static void wm_get_xdg_dialog(struct wl_client *client, struct wl_resource *wm_resource, + uint32_t id, struct wl_resource *toplevel_resource) { + struct wlr_xdg_wm_dialog_v1 *wm = wm_from_resource(wm_resource); + struct wlr_xdg_toplevel *xdg_toplevel = wlr_xdg_toplevel_from_resource(toplevel_resource); + + struct wlr_addon_set *addon_set = &xdg_toplevel->base->surface->addons; + + if (wlr_addon_find(addon_set, NULL, &surface_addon_impl) != NULL) { + wl_resource_post_error(wm_resource, XDG_WM_DIALOG_V1_ERROR_ALREADY_USED, + "the xdg_toplevel object has already been used to create a xdg_dialog_v1"); + return; + } + + struct wlr_xdg_dialog_v1 *dialog = calloc(1, sizeof(*dialog)); + if (dialog == NULL) { + wl_resource_post_no_memory(wm_resource); + return; + } + + dialog->resource = wl_resource_create(client, &xdg_dialog_v1_interface, + wl_resource_get_version(wm_resource), id); + if (dialog->resource == NULL) { + free(dialog); + wl_resource_post_no_memory(wm_resource); + return; + } + wl_resource_set_implementation(dialog->resource, &dialog_impl, + dialog, handle_resource_destroy); + + dialog->xdg_toplevel = xdg_toplevel; + wlr_addon_init(&dialog->surface_addon, addon_set, NULL, &surface_addon_impl); + + dialog->xdg_toplevel_destroy.notify = handle_xdg_toplevel_destroy; + wl_signal_add(&xdg_toplevel->events.destroy, &dialog->xdg_toplevel_destroy); + + wl_signal_init(&dialog->events.destroy); + wl_signal_init(&dialog->events.set_modal); + + wl_signal_emit_mutable(&wm->events.new_dialog, dialog); +} + +static const struct xdg_wm_dialog_v1_interface wm_impl = { + .destroy = resource_destroy, + .get_xdg_dialog = wm_get_xdg_dialog, +}; + +static void wm_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { + struct wlr_xdg_wm_dialog_v1 *wm = data; + struct wl_resource *resource = + wl_resource_create(client, &xdg_wm_dialog_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &wm_impl, wm, NULL); +} + +static void xdg_wm_dialog_handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_wm_dialog_v1 *wm = wl_container_of(listener, wm, display_destroy); + wl_signal_emit_mutable(&wm->events.destroy, NULL); + + assert(wl_list_empty(&wm->events.destroy.listener_list)); + assert(wl_list_empty(&wm->events.new_dialog.listener_list)); + + wl_list_remove(&wm->display_destroy.link); + wl_global_destroy(wm->global); + free(wm); +} + +struct wlr_xdg_dialog_v1 *wlr_xdg_dialog_v1_try_from_wlr_xdg_toplevel( + struct wlr_xdg_toplevel *xdg_toplevel) { + struct wlr_addon *addon = + wlr_addon_find(&xdg_toplevel->base->surface->addons, NULL, &surface_addon_impl); + if (addon == NULL) { + return NULL; + } + struct wlr_xdg_dialog_v1 *dialog = wl_container_of(addon, dialog, surface_addon); + return dialog; +} + +struct wlr_xdg_wm_dialog_v1 *wlr_xdg_wm_dialog_v1_create(struct wl_display *display, + uint32_t version) { + assert(version <= XDG_WM_DIALOG_V1_VERSION); + + struct wlr_xdg_wm_dialog_v1 *wm = calloc(1, sizeof(*wm)); + if (wm == NULL) { + return NULL; + } + + wm->global = wl_global_create(display, &xdg_wm_dialog_v1_interface, version, wm, wm_bind); + if (wm->global == NULL) { + free(wm); + return NULL; + } + + wm->display_destroy.notify = xdg_wm_dialog_handle_display_destroy; + wl_display_add_destroy_listener(display, &wm->display_destroy); + + wl_signal_init(&wm->events.destroy); + wl_signal_init(&wm->events.new_dialog); + + return wm; +} diff --git a/types/wlr_xdg_foreign_registry.c b/types/wlr_xdg_foreign_registry.c index c27d84069..ef9c3135c 100644 --- a/types/wlr_xdg_foreign_registry.c +++ b/types/wlr_xdg_foreign_registry.c @@ -17,6 +17,7 @@ bool wlr_xdg_foreign_exported_init( wl_list_insert(®istry->exported_surfaces, &exported->link); wl_signal_init(&exported->events.destroy); + return true; } @@ -38,6 +39,9 @@ struct wlr_xdg_foreign_exported *wlr_xdg_foreign_registry_find_by_handle( void wlr_xdg_foreign_exported_finish(struct wlr_xdg_foreign_exported *surface) { wl_signal_emit_mutable(&surface->events.destroy, NULL); + + assert(wl_list_empty(&surface->events.destroy.listener_list)); + surface->registry = NULL; wl_list_remove(&surface->link); wl_list_init(&surface->link); @@ -50,6 +54,8 @@ static void foreign_registry_handle_display_destroy(struct wl_listener *listener wl_signal_emit_mutable(®istry->events.destroy, NULL); + assert(wl_list_empty(®istry->events.destroy.listener_list)); + // Implementations are supposed to remove all surfaces assert(wl_list_empty(®istry->exported_surfaces)); free(registry); @@ -67,6 +73,8 @@ struct wlr_xdg_foreign_registry *wlr_xdg_foreign_registry_create( wl_display_add_destroy_listener(display, ®istry->display_destroy); wl_list_init(®istry->exported_surfaces); + wl_signal_init(®istry->events.destroy); + return registry; } diff --git a/types/wlr_xdg_foreign_v1.c b/types/wlr_xdg_foreign_v1.c index 12cd4fc09..1c5983951 100644 --- a/types/wlr_xdg_foreign_v1.c +++ b/types/wlr_xdg_foreign_v1.c @@ -1,4 +1,3 @@ - #include #include #include @@ -26,15 +25,17 @@ static void xdg_imported_handle_destroy(struct wl_client *client, wl_resource_destroy(resource); } -static struct wlr_xdg_toplevel *verify_is_toplevel(struct wl_resource *resource, +static struct wlr_xdg_toplevel *get_toplevel(struct wl_resource *resource, struct wlr_surface *surface) { - struct wlr_xdg_surface *xdg_surface = wlr_xdg_surface_try_from_wlr_surface(surface); - if (xdg_surface == NULL || xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { + // Note: xdg_surface and xdg_toplevel are never inert, so if this fails, + // the surface isn't a toplevel + struct wlr_xdg_toplevel *toplevel = wlr_xdg_toplevel_try_from_wlr_surface(surface); + if (toplevel == NULL) { wl_resource_post_error(resource, -1, "surface must be an xdg_toplevel"); return NULL; } - return xdg_surface->toplevel; + return toplevel; } static void destroy_imported_child(struct wlr_xdg_imported_child_v1 *child) { @@ -51,7 +52,7 @@ static void handle_child_xdg_toplevel_destroy( destroy_imported_child(child); } -static void handle_xdg_toplevel_set_parent( +static void handle_child_xdg_toplevel_set_parent( struct wl_listener *listener, void *data) { struct wlr_xdg_imported_child_v1 *child = wl_container_of(listener, child, xdg_toplevel_set_parent); @@ -59,34 +60,29 @@ static void handle_xdg_toplevel_set_parent( } static void xdg_imported_handle_set_parent_of(struct wl_client *client, - struct wl_resource *resource, - struct wl_resource *child_resource) { + struct wl_resource *resource, struct wl_resource *child_resource) { struct wlr_xdg_imported_v1 *imported = xdg_imported_from_resource(resource); if (imported == NULL) { return; } - struct wlr_surface *wlr_surface = imported->exported->surface; - struct wlr_surface *wlr_surface_child = - wlr_surface_from_resource(child_resource); - struct wlr_xdg_toplevel *child_toplevel = - verify_is_toplevel(resource, wlr_surface_child); + struct wlr_xdg_toplevel *toplevel = imported->exported->toplevel; + struct wlr_surface *child_surface = wlr_surface_from_resource(child_resource); + + struct wlr_xdg_toplevel *child_toplevel = get_toplevel(resource, child_surface); if (!child_toplevel) { return; } - struct wlr_xdg_surface *surface = - wlr_xdg_surface_try_from_wlr_surface(wlr_surface); - - if (!surface->surface->mapped) { + if (!toplevel->base->surface->mapped) { wlr_xdg_toplevel_set_parent(child_toplevel, NULL); return; } struct wlr_xdg_imported_child_v1 *child; wl_list_for_each(child, &imported->children, link) { - if (child->surface == wlr_surface_child) { + if (child->toplevel == child_toplevel) { return; } } @@ -96,19 +92,19 @@ static void xdg_imported_handle_set_parent_of(struct wl_client *client, wl_client_post_no_memory(client); return; } - child->surface = wlr_surface_child; - child->xdg_toplevel_destroy.notify = handle_child_xdg_toplevel_destroy; - child->xdg_toplevel_set_parent.notify = handle_xdg_toplevel_set_parent; - if (!wlr_xdg_toplevel_set_parent(child_toplevel, surface->toplevel)) { - wl_resource_post_error(surface->toplevel->resource, + child->toplevel = child_toplevel; + child->xdg_toplevel_destroy.notify = handle_child_xdg_toplevel_destroy; + child->xdg_toplevel_set_parent.notify = handle_child_xdg_toplevel_set_parent; + + if (!wlr_xdg_toplevel_set_parent(child_toplevel, toplevel)) { + wl_resource_post_error(toplevel->resource, XDG_TOPLEVEL_ERROR_INVALID_PARENT, "a toplevel cannot be a parent of itself or its ancestor"); free(child); return; } - wlr_xdg_toplevel_set_parent(child_toplevel, surface->toplevel); wl_signal_add(&child_toplevel->events.destroy, &child->xdg_toplevel_destroy); wl_signal_add(&child_toplevel->events.set_parent, &child->xdg_toplevel_set_parent); @@ -152,10 +148,7 @@ static void destroy_imported(struct wlr_xdg_imported_v1 *imported) { imported->exported = NULL; struct wlr_xdg_imported_child_v1 *child, *child_tmp; wl_list_for_each_safe(child, child_tmp, &imported->children, link) { - struct wlr_xdg_surface *xdg_child = - wlr_xdg_surface_try_from_wlr_surface(child->surface); - assert(xdg_child != NULL); - wlr_xdg_toplevel_set_parent(xdg_child->toplevel, NULL); + wlr_xdg_toplevel_set_parent(child->toplevel, NULL); } wl_list_remove(&imported->exported_destroyed.link); @@ -194,15 +187,12 @@ static void handle_xdg_toplevel_destroy(struct wl_listener *listener, void *data } static void xdg_exporter_handle_export(struct wl_client *wl_client, - struct wl_resource *client_resource, - uint32_t id, - struct wl_resource *surface_resource) { + struct wl_resource *client_resource, uint32_t id, struct wl_resource *surface_resource) { struct wlr_xdg_foreign_v1 *foreign = xdg_foreign_from_exporter_resource(client_resource); struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); - struct wlr_xdg_toplevel *xdg_toplevel = - verify_is_toplevel(client_resource, surface); + struct wlr_xdg_toplevel *xdg_toplevel = get_toplevel(client_resource, surface); if (!xdg_toplevel) { return; } @@ -219,7 +209,7 @@ static void xdg_exporter_handle_export(struct wl_client *wl_client, return; } - exported->base.surface = surface; + exported->base.toplevel = xdg_toplevel; exported->resource = wl_resource_create(wl_client, &zxdg_exported_v1_interface, wl_resource_get_version(client_resource), id); if (exported->resource == NULL) { @@ -291,9 +281,7 @@ static void xdg_imported_handle_exported_destroy(struct wl_listener *listener, } static void xdg_importer_handle_import(struct wl_client *wl_client, - struct wl_resource *client_resource, - uint32_t id, - const char *handle) { + struct wl_resource *client_resource, uint32_t id, const char *handle) { struct wlr_xdg_foreign_v1 *foreign = xdg_foreign_from_importer_resource(client_resource); @@ -357,6 +345,9 @@ static void xdg_foreign_destroy(struct wlr_xdg_foreign_v1 *foreign) { } wl_signal_emit_mutable(&foreign->events.destroy, NULL); + + assert(wl_list_empty(&foreign->events.destroy.listener_list)); + wl_list_remove(&foreign->foreign_registry_destroy.link); wl_list_remove(&foreign->display_destroy.link); @@ -407,6 +398,7 @@ struct wlr_xdg_foreign_v1 *wlr_xdg_foreign_v1_create( foreign->registry = registry; wl_signal_init(&foreign->events.destroy); + wl_list_init(&foreign->exporter.objects); wl_list_init(&foreign->importer.objects); diff --git a/types/wlr_xdg_foreign_v2.c b/types/wlr_xdg_foreign_v2.c index 4cfbe77b6..58071f4a1 100644 --- a/types/wlr_xdg_foreign_v2.c +++ b/types/wlr_xdg_foreign_v2.c @@ -1,4 +1,3 @@ - #include #include #include @@ -26,20 +25,20 @@ static void xdg_imported_handle_destroy(struct wl_client *client, wl_resource_destroy(resource); } -static struct wlr_xdg_toplevel *verify_is_toplevel(struct wl_resource *resource, +static struct wlr_xdg_toplevel *get_toplevel(struct wl_resource *resource, struct wlr_surface *surface) { - // Note: the error codes are the same for zxdg_exporter_v2 and - // zxdg_importer_v2 - - struct wlr_xdg_surface *xdg_surface = wlr_xdg_surface_try_from_wlr_surface(surface); - if (xdg_surface == NULL || xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { - wl_resource_post_error(resource, - ZXDG_EXPORTER_V2_ERROR_INVALID_SURFACE, + // Note: xdg_surface and xdg_toplevel are never inert, so if this fails, + // the surface isn't a toplevel + struct wlr_xdg_toplevel *toplevel = wlr_xdg_toplevel_try_from_wlr_surface(surface); + if (toplevel == NULL) { + // Note: the error codes are the same for zxdg_exporter_v2 and + // zxdg_importer_v2 + wl_resource_post_error(resource, ZXDG_EXPORTER_V2_ERROR_INVALID_SURFACE, "surface must be an xdg_toplevel"); return NULL; } - return xdg_surface->toplevel; + return toplevel; } static void destroy_imported_child(struct wlr_xdg_imported_child_v2 *child) { @@ -56,7 +55,7 @@ static void handle_child_xdg_toplevel_destroy( destroy_imported_child(child); } -static void handle_xdg_toplevel_set_parent( +static void handle_child_xdg_toplevel_set_parent( struct wl_listener *listener, void *data) { struct wlr_xdg_imported_child_v2 *child = wl_container_of(listener, child, xdg_toplevel_set_parent); @@ -64,32 +63,29 @@ static void handle_xdg_toplevel_set_parent( } static void xdg_imported_handle_set_parent_of(struct wl_client *client, - struct wl_resource *resource, - struct wl_resource *child_resource) { + struct wl_resource *resource, struct wl_resource *child_resource) { struct wlr_xdg_imported_v2 *imported = xdg_imported_from_resource(resource); if (imported == NULL) { return; } - struct wlr_surface *wlr_surface = imported->exported->surface; - struct wlr_surface *wlr_surface_child = - wlr_surface_from_resource(child_resource); - struct wlr_xdg_surface *surface = wlr_xdg_surface_try_from_wlr_surface(wlr_surface); - struct wlr_xdg_toplevel *child_toplevel = - verify_is_toplevel(resource, wlr_surface_child); + struct wlr_xdg_toplevel *toplevel = imported->exported->toplevel; + struct wlr_surface *child_surface = wlr_surface_from_resource(child_resource); + + struct wlr_xdg_toplevel *child_toplevel = get_toplevel(resource, child_surface); if (!child_toplevel) { return; } - if (!surface->surface->mapped) { + if (!toplevel->base->surface->mapped) { wlr_xdg_toplevel_set_parent(child_toplevel, NULL); return; } struct wlr_xdg_imported_child_v2 *child; wl_list_for_each(child, &imported->children, link) { - if (child->surface == wlr_surface_child) { + if (child->toplevel == child_toplevel) { return; } } @@ -99,19 +95,18 @@ static void xdg_imported_handle_set_parent_of(struct wl_client *client, wl_client_post_no_memory(client); return; } - child->surface = wlr_surface_child; + child->toplevel = child_toplevel; child->xdg_toplevel_destroy.notify = handle_child_xdg_toplevel_destroy; - child->xdg_toplevel_set_parent.notify = handle_xdg_toplevel_set_parent; + child->xdg_toplevel_set_parent.notify = handle_child_xdg_toplevel_set_parent; - if (!wlr_xdg_toplevel_set_parent(child_toplevel, surface->toplevel)) { - wl_resource_post_error(surface->toplevel->resource, + if (!wlr_xdg_toplevel_set_parent(child_toplevel, toplevel)) { + wl_resource_post_error(toplevel->resource, XDG_TOPLEVEL_ERROR_INVALID_PARENT, "a toplevel cannot be a parent of itself or its ancestor"); free(child); return; } - wlr_xdg_toplevel_set_parent(child_toplevel, surface->toplevel); wl_signal_add(&child_toplevel->events.destroy, &child->xdg_toplevel_destroy); wl_signal_add(&child_toplevel->events.set_parent, &child->xdg_toplevel_set_parent); @@ -155,10 +150,7 @@ static void destroy_imported(struct wlr_xdg_imported_v2 *imported) { imported->exported = NULL; struct wlr_xdg_imported_child_v2 *child, *child_tmp; wl_list_for_each_safe(child, child_tmp, &imported->children, link) { - struct wlr_xdg_surface *xdg_child = - wlr_xdg_surface_try_from_wlr_surface(child->surface); - assert(xdg_child != NULL); - wlr_xdg_toplevel_set_parent(xdg_child->toplevel, NULL); + wlr_xdg_toplevel_set_parent(child->toplevel, NULL); } wl_list_remove(&imported->exported_destroyed.link); @@ -197,15 +189,12 @@ static void handle_xdg_toplevel_destroy(struct wl_listener *listener, void *data } static void xdg_exporter_handle_export(struct wl_client *wl_client, - struct wl_resource *client_resource, - uint32_t id, - struct wl_resource *surface_resource) { + struct wl_resource *client_resource, uint32_t id, struct wl_resource *surface_resource) { struct wlr_xdg_foreign_v2 *foreign = xdg_foreign_from_exporter_resource(client_resource); struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); - struct wlr_xdg_toplevel *xdg_toplevel = - verify_is_toplevel(client_resource, surface); + struct wlr_xdg_toplevel *xdg_toplevel = get_toplevel(client_resource, surface); if (!xdg_toplevel) { return; } @@ -222,7 +211,7 @@ static void xdg_exporter_handle_export(struct wl_client *wl_client, return; } - exported->base.surface = surface; + exported->base.toplevel = xdg_toplevel; exported->resource = wl_resource_create(wl_client, &zxdg_exported_v2_interface, wl_resource_get_version(client_resource), id); if (exported->resource == NULL) { @@ -294,9 +283,7 @@ static void xdg_imported_handle_exported_destroy(struct wl_listener *listener, } static void xdg_importer_handle_import(struct wl_client *wl_client, - struct wl_resource *client_resource, - uint32_t id, - const char *handle) { + struct wl_resource *client_resource, uint32_t id, const char *handle) { struct wlr_xdg_foreign_v2 *foreign = xdg_foreign_from_importer_resource(client_resource); @@ -360,6 +347,9 @@ static void xdg_foreign_destroy(struct wlr_xdg_foreign_v2 *foreign) { } wl_signal_emit_mutable(&foreign->events.destroy, NULL); + + assert(wl_list_empty(&foreign->events.destroy.listener_list)); + wl_list_remove(&foreign->foreign_registry_destroy.link); wl_list_remove(&foreign->display_destroy.link); @@ -410,6 +400,7 @@ struct wlr_xdg_foreign_v2 *wlr_xdg_foreign_v2_create( foreign->registry = registry; wl_signal_init(&foreign->events.destroy); + wl_list_init(&foreign->exporter.objects); wl_list_init(&foreign->importer.objects); diff --git a/types/wlr_xdg_output_v1.c b/types/wlr_xdg_output_v1.c index 895a0f0aa..262123987 100644 --- a/types/wlr_xdg_output_v1.c +++ b/types/wlr_xdg_output_v1.c @@ -140,7 +140,7 @@ static void output_manager_handle_get_xdg_output(struct wl_client *client, output_send_details(xdg_output, xdg_output_resource); uint32_t wl_version = wl_resource_get_version(output_resource); - if (wl_version >= WL_OUTPUT_DONE_SINCE_VERSION && + if (wl_version >= WL_OUTPUT_DONE_SINCE_VERSION && xdg_version >= OUTPUT_DONE_DEPRECATED_SINCE_VERSION) { wl_output_send_done(output_resource); } @@ -234,7 +234,11 @@ static void manager_destroy(struct wlr_xdg_output_manager_v1 *manager) { wl_list_for_each_safe(output, tmp, &manager->outputs, link) { output_destroy(output); } + wl_signal_emit_mutable(&manager->events.destroy, manager); + + assert(wl_list_empty(&manager->events.destroy.listener_list)); + wl_list_remove(&manager->display_destroy.link); wl_list_remove(&manager->layout_add.link); wl_list_remove(&manager->layout_change.link); diff --git a/types/wlr_xdg_system_bell_v1.c b/types/wlr_xdg_system_bell_v1.c new file mode 100644 index 000000000..5ab9b5a19 --- /dev/null +++ b/types/wlr_xdg_system_bell_v1.c @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include "xdg-system-bell-v1-protocol.h" + +#define XDG_SYSTEM_BELL_V1_VERSION 1 + +static const struct xdg_system_bell_v1_interface bell_impl; + +static struct wlr_xdg_system_bell_v1 *bell_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &xdg_system_bell_v1_interface, &bell_impl)); + return wl_resource_get_user_data(resource); +} + +static void bell_handle_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void bell_handle_ring(struct wl_client *client, + struct wl_resource *bell_resource, struct wl_resource *surface_resource) { + struct wlr_xdg_system_bell_v1 *bell = bell_from_resource(bell_resource); + + struct wlr_surface *surface = NULL; + if (surface_resource != NULL) { + surface = wlr_surface_from_resource(surface_resource); + } + + struct wlr_xdg_system_bell_v1_ring_event event = { + .client = client, + .surface = surface, + }; + wl_signal_emit_mutable(&bell->events.ring, &event); +} + +static const struct xdg_system_bell_v1_interface bell_impl = { + .destroy = bell_handle_destroy, + .ring = bell_handle_ring, +}; + +static void bell_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_xdg_system_bell_v1 *bell = data; + + struct wl_resource *resource = wl_resource_create(client, + &xdg_system_bell_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &bell_impl, bell, NULL); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_system_bell_v1 *bell = wl_container_of(listener, bell, display_destroy); + wl_signal_emit_mutable(&bell->events.destroy, NULL); + + assert(wl_list_empty(&bell->events.destroy.listener_list)); + assert(wl_list_empty(&bell->events.ring.listener_list)); + + wl_list_remove(&bell->display_destroy.link); + wl_global_destroy(bell->global); + free(bell); +} + +struct wlr_xdg_system_bell_v1 *wlr_xdg_system_bell_v1_create(struct wl_display *display, + uint32_t version) { + assert(version <= XDG_SYSTEM_BELL_V1_VERSION); + + struct wlr_xdg_system_bell_v1 *bell = calloc(1, sizeof(*bell)); + if (bell == NULL) { + return NULL; + } + + bell->global = wl_global_create(display, &xdg_system_bell_v1_interface, + version, bell, bell_bind); + if (bell->global == NULL) { + free(bell); + return NULL; + } + + bell->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &bell->display_destroy); + + wl_signal_init(&bell->events.destroy); + wl_signal_init(&bell->events.ring); + + return bell; +} diff --git a/types/wlr_xdg_toplevel_icon_v1.c b/types/wlr_xdg_toplevel_icon_v1.c new file mode 100644 index 000000000..e67ad5378 --- /dev/null +++ b/types/wlr_xdg_toplevel_icon_v1.c @@ -0,0 +1,296 @@ +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "xdg-toplevel-icon-v1-protocol.h" + +#define MANAGER_VERSION 1 + +static const struct xdg_toplevel_icon_v1_interface icon_impl; + +static struct wlr_xdg_toplevel_icon_v1 *icon_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &xdg_toplevel_icon_v1_interface, &icon_impl)); + return wl_resource_get_user_data(resource); +} + +static void icon_destroy(struct wlr_xdg_toplevel_icon_v1 *icon) { + struct wlr_xdg_toplevel_icon_v1_buffer *icon_buffer, *tmp; + wl_list_for_each_safe(icon_buffer, tmp, &icon->buffers, link) { + wlr_buffer_unlock(icon_buffer->buffer); + wl_list_remove(&icon_buffer->link); + free(icon_buffer); + } + + free(icon->name); + free(icon); +} + +static void icon_handle_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void icon_handle_set_name(struct wl_client *client, struct wl_resource *resource, + const char *name) { + struct wlr_xdg_toplevel_icon_v1 *icon = icon_from_resource(resource); + if (icon->immutable) { + wl_resource_post_error(resource, XDG_TOPLEVEL_ICON_V1_ERROR_IMMUTABLE, + "the icon has already been assigned to a toplevel and must not be changed"); + return; + } + + char *dup = strdup(name); + if (dup == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + free(icon->name); + icon->name = dup; +} + +static void icon_handle_add_buffer(struct wl_client *client, struct wl_resource *resource, + struct wl_resource *buffer_resource, int32_t scale) { + struct wlr_xdg_toplevel_icon_v1 *icon = icon_from_resource(resource); + if (icon->immutable) { + wl_resource_post_error(resource, XDG_TOPLEVEL_ICON_V1_ERROR_IMMUTABLE, + "the icon has already been assigned to a toplevel and must not be changed"); + return; + } + + struct wlr_buffer *buffer = wlr_buffer_try_from_resource(buffer_resource); + + const char *bad_buffer_msg = NULL; + + struct wlr_shm_attributes shm_attribs; + if (!wlr_buffer_get_shm(buffer, &shm_attribs)) { + bad_buffer_msg = "not backed by wl_shm"; + } else if (buffer->width != buffer->height) { + bad_buffer_msg = "not square"; + } + + if (bad_buffer_msg != NULL) { + wl_resource_post_error(resource, XDG_TOPLEVEL_ICON_V1_ERROR_INVALID_BUFFER, + "the provided buffer does not satisfy requirements: %s", bad_buffer_msg); + wlr_buffer_unlock(buffer); + return; + } + + struct wlr_xdg_toplevel_icon_v1_buffer *icon_buffer; + wl_list_for_each(icon_buffer, &icon->buffers, link) { + if (icon_buffer->buffer->width == buffer->width && icon_buffer->scale == scale) { + wlr_buffer_unlock(icon_buffer->buffer); + icon_buffer->buffer = buffer; + return; + } + } + + icon_buffer = calloc(1, sizeof(*icon_buffer)); + if (icon_buffer == NULL) { + wl_resource_post_no_memory(resource); + } + + icon_buffer->buffer = buffer; + icon_buffer->scale = scale; + + wl_list_insert(&icon->buffers, &icon_buffer->link); +} + +static const struct xdg_toplevel_icon_v1_interface icon_impl = { + .destroy = icon_handle_destroy, + .set_name = icon_handle_set_name, + .add_buffer = icon_handle_add_buffer, +}; + +static void icon_handle_resource_destroy(struct wl_resource *resource) { + wlr_xdg_toplevel_icon_v1_unref(icon_from_resource(resource)); +} + +struct wlr_xdg_toplevel_icon_v1 *wlr_xdg_toplevel_icon_v1_ref( + struct wlr_xdg_toplevel_icon_v1 *icon) { + ++icon->n_refs; + return icon; +} + +void wlr_xdg_toplevel_icon_v1_unref(struct wlr_xdg_toplevel_icon_v1 *icon) { + if (icon == NULL) { + return; + } + + assert(icon->n_refs > 0); + --icon->n_refs; + if (icon->n_refs == 0) { + icon_destroy(icon); + }; +} + +static const struct xdg_toplevel_icon_manager_v1_interface manager_impl; + +static struct wlr_xdg_toplevel_icon_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &xdg_toplevel_icon_manager_v1_interface, + &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void manager_handle_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void manager_handle_create_icon(struct wl_client *client, struct wl_resource *resource, + uint32_t id) { + struct wlr_xdg_toplevel_icon_v1 *icon = calloc(1, sizeof(*icon)); + if (icon == NULL) { + wl_client_post_no_memory(client); + return; + } + + struct wl_resource *icon_resource = wl_resource_create(client, + &xdg_toplevel_icon_v1_interface, wl_resource_get_version(resource), id); + if (icon_resource == NULL) { + wl_client_post_no_memory(client); + free(icon); + return; + } + + wl_list_init(&icon->buffers); + icon->n_refs = 1; + + wl_resource_set_implementation(icon_resource, &icon_impl, icon, icon_handle_resource_destroy); +} + +static void manager_handle_set_icon(struct wl_client *client, struct wl_resource *resource, + struct wl_resource *toplevel_resource, struct wl_resource *icon_resource) { + struct wlr_xdg_toplevel_icon_manager_v1 *manager = manager_from_resource(resource); + struct wlr_xdg_toplevel *toplevel = wlr_xdg_toplevel_from_resource(toplevel_resource); + + struct wlr_xdg_toplevel_icon_v1 *icon = NULL; + if (icon_resource != NULL) { + icon = icon_from_resource(icon_resource); + icon->immutable = true; + + if (icon->name == NULL && wl_list_empty(&icon->buffers)) { + // Same as supplying null icon + icon = NULL; + } + } + + struct wlr_xdg_toplevel_icon_manager_v1_set_icon_event event = { + .toplevel = toplevel, + .icon = icon, + }; + + wl_signal_emit_mutable(&manager->events.set_icon, &event); +} + +static const struct xdg_toplevel_icon_manager_v1_interface manager_impl = { + .destroy = manager_handle_destroy, + .create_icon = manager_handle_create_icon, + .set_icon = manager_handle_set_icon, +}; + +static void manager_send_sizes(struct wlr_xdg_toplevel_icon_manager_v1 *manager, + struct wl_resource *resource) { + for (size_t i = 0; i < manager->n_sizes; i++) { + xdg_toplevel_icon_manager_v1_send_icon_size(resource, manager->sizes[i]); + } + xdg_toplevel_icon_manager_v1_send_done(resource); +} + +static void manager_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { + struct wlr_xdg_toplevel_icon_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &xdg_toplevel_icon_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, manager_handle_resource_destroy); + + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); + + manager_send_sizes(manager, resource); +} + +static void manager_handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_toplevel_icon_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + + wl_signal_emit_mutable(&manager->events.destroy, NULL); + + assert(wl_list_empty(&manager->events.set_icon.listener_list)); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + + wl_list_remove(&manager->resources); + + free(manager->sizes); + free(manager); +} + +void wlr_xdg_toplevel_icon_manager_v1_set_sizes(struct wlr_xdg_toplevel_icon_manager_v1 *manager, + int *sizes, size_t n_sizes) { + if (n_sizes != manager->n_sizes) { + int *dup_sizes = NULL; + if (n_sizes > 0) { + dup_sizes = calloc(n_sizes, sizeof(*dup_sizes)); + if (dup_sizes == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + return; + } + } + + free(manager->sizes); + manager->sizes = dup_sizes; + manager->n_sizes = n_sizes; + } + + for (size_t i = 0; i < n_sizes; i++) { + manager->sizes[i] = sizes[i]; + } + + struct wl_resource *resource; + wl_resource_for_each(resource, &manager->resources) { + manager_send_sizes(manager, resource); + } +} + +struct wlr_xdg_toplevel_icon_manager_v1 *wlr_xdg_toplevel_icon_manager_v1_create( + struct wl_display *display, uint32_t version) { + assert(version <= MANAGER_VERSION); + + struct wlr_xdg_toplevel_icon_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, &xdg_toplevel_icon_manager_v1_interface, + version, manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.set_icon); + wl_signal_init(&manager->events.destroy); + + wl_list_init(&manager->resources); + + manager->display_destroy.notify = manager_handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/wlr_xdg_toplevel_tag_v1.c b/types/wlr_xdg_toplevel_tag_v1.c new file mode 100644 index 000000000..da8cfa9a0 --- /dev/null +++ b/types/wlr_xdg_toplevel_tag_v1.c @@ -0,0 +1,104 @@ +#include +#include + +#include +#include + +#include "xdg-toplevel-tag-v1-protocol.h" + +#define MANAGER_VERSION 1 + +static const struct xdg_toplevel_tag_manager_v1_interface manager_impl; + +static struct wlr_xdg_toplevel_tag_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &xdg_toplevel_tag_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void manager_handle_set_tag(struct wl_client *client, struct wl_resource *manager_resource, + struct wl_resource *toplevel_resource, const char *tag) { + struct wlr_xdg_toplevel_tag_manager_v1 *manager = manager_from_resource(manager_resource); + struct wlr_xdg_toplevel *toplevel = wlr_xdg_toplevel_from_resource(toplevel_resource); + + struct wlr_xdg_toplevel_tag_manager_v1_set_tag_event event = { + .toplevel = toplevel, + .tag = tag, + }; + wl_signal_emit_mutable(&manager->events.set_tag, &event); +} + +static void manager_handle_set_description(struct wl_client *client, struct wl_resource *manager_resource, + struct wl_resource *toplevel_resource, const char *description) { + struct wlr_xdg_toplevel_tag_manager_v1 *manager = manager_from_resource(manager_resource); + struct wlr_xdg_toplevel *toplevel = wlr_xdg_toplevel_from_resource(toplevel_resource); + + struct wlr_xdg_toplevel_tag_manager_v1_set_description_event event = { + .toplevel = toplevel, + .description = description, + }; + wl_signal_emit_mutable(&manager->events.set_description, &event); +} + +static void manager_handle_destroy(struct wl_client *client, struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct xdg_toplevel_tag_manager_v1_interface manager_impl = { + .destroy = manager_handle_destroy, + .set_toplevel_tag = manager_handle_set_tag, + .set_toplevel_description = manager_handle_set_description, +}; + +static void manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { + struct wlr_xdg_toplevel_tag_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &xdg_toplevel_tag_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, NULL); +} + +static void manager_handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_toplevel_tag_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + + wl_signal_emit_mutable(&manager->events.destroy, NULL); + + assert(wl_list_empty(&manager->events.set_tag.listener_list)); + assert(wl_list_empty(&manager->events.set_description.listener_list)); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_xdg_toplevel_tag_manager_v1 *wlr_xdg_toplevel_tag_manager_v1_create( + struct wl_display *display, uint32_t version) { + assert(version <= MANAGER_VERSION); + + struct wlr_xdg_toplevel_tag_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, &xdg_toplevel_tag_manager_v1_interface, + version, manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.set_tag); + wl_signal_init(&manager->events.set_description); + wl_signal_init(&manager->events.destroy); + + manager->display_destroy.notify = manager_handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/xdg_shell/wlr_xdg_popup.c b/types/xdg_shell/wlr_xdg_popup.c index 082e06fbb..25c07c8c5 100644 --- a/types/xdg_shell/wlr_xdg_popup.c +++ b/types/xdg_shell/wlr_xdg_popup.c @@ -361,15 +361,6 @@ static const struct wlr_surface_synced_impl surface_synced_impl = { .state_size = sizeof(struct wlr_xdg_popup_state), }; -static void xdg_popup_handle_resource_destroy(struct wl_resource *resource) { - struct wlr_xdg_popup *popup = - wlr_xdg_popup_from_resource(resource); - if (popup == NULL) { - return; - } - wlr_xdg_popup_destroy(popup); -} - void create_xdg_popup(struct wlr_xdg_surface *surface, struct wlr_xdg_surface *parent, struct wlr_xdg_positioner *positioner, uint32_t id) { if (!wlr_xdg_positioner_is_complete(positioner)) { @@ -409,8 +400,7 @@ void create_xdg_popup(struct wlr_xdg_surface *surface, struct wlr_xdg_surface *p goto error_synced; } wl_resource_set_implementation(surface->popup->resource, - &xdg_popup_implementation, surface->popup, - xdg_popup_handle_resource_destroy); + &xdg_popup_implementation, surface->popup, NULL); surface->role = WLR_XDG_SURFACE_ROLE_POPUP; @@ -477,6 +467,9 @@ void destroy_xdg_popup(struct wlr_xdg_popup *popup) { wl_signal_emit_mutable(&popup->events.destroy, NULL); + assert(wl_list_empty(&popup->events.destroy.listener_list)); + assert(wl_list_empty(&popup->events.reposition.listener_list)); + wlr_surface_synced_finish(&popup->synced); popup->base->popup = NULL; wl_list_remove(&popup->link); @@ -508,8 +501,8 @@ void wlr_xdg_popup_get_toplevel_coords(struct wlr_xdg_popup *popup, popup_sy += xdg_surface->popup->current.geometry.y; parent = xdg_surface->popup->parent; } else { - popup_sx += xdg_surface->current.geometry.x; - popup_sy += xdg_surface->current.geometry.y; + popup_sx += xdg_surface->geometry.x; + popup_sy += xdg_surface->geometry.y; break; } } diff --git a/types/xdg_shell/wlr_xdg_positioner.c b/types/xdg_shell/wlr_xdg_positioner.c index 380c11310..e98009187 100644 --- a/types/xdg_shell/wlr_xdg_positioner.c +++ b/types/xdg_shell/wlr_xdg_positioner.c @@ -52,7 +52,8 @@ static void xdg_positioner_handle_set_anchor(struct wl_client *client, struct wlr_xdg_positioner *positioner = wlr_xdg_positioner_from_resource(resource); - if (anchor > XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT) { + uint32_t version = wl_resource_get_version(resource); + if (!xdg_positioner_anchor_is_valid(anchor, version)) { wl_resource_post_error(resource, XDG_POSITIONER_ERROR_INVALID_INPUT, "invalid anchor value"); @@ -67,7 +68,8 @@ static void xdg_positioner_handle_set_gravity(struct wl_client *client, struct wlr_xdg_positioner *positioner = wlr_xdg_positioner_from_resource(resource); - if (gravity > XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT) { + uint32_t version = wl_resource_get_version(resource); + if (!xdg_positioner_gravity_is_valid(gravity, version)) { wl_resource_post_error(resource, XDG_POSITIONER_ERROR_INVALID_INPUT, "invalid gravity value"); @@ -83,6 +85,14 @@ static void xdg_positioner_handle_set_constraint_adjustment( struct wlr_xdg_positioner *positioner = wlr_xdg_positioner_from_resource(resource); + uint32_t version = wl_resource_get_version(resource); + if (!xdg_positioner_constraint_adjustment_is_valid(constraint_adjustment, version)) { + wl_resource_post_error(resource, + XDG_POSITIONER_ERROR_INVALID_INPUT, + "invalid constraint_adjustment value"); + return; + } + positioner->rules.constraint_adjustment = constraint_adjustment; } diff --git a/types/xdg_shell/wlr_xdg_shell.c b/types/xdg_shell/wlr_xdg_shell.c index 3baea04eb..2f048ac2c 100644 --- a/types/xdg_shell/wlr_xdg_shell.c +++ b/types/xdg_shell/wlr_xdg_shell.c @@ -2,7 +2,7 @@ #include #include "types/wlr_xdg_shell.h" -#define WM_BASE_VERSION 6 +#define WM_BASE_VERSION 7 static const struct xdg_wm_base_interface xdg_shell_impl; @@ -129,6 +129,12 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_xdg_shell *xdg_shell = wl_container_of(listener, xdg_shell, display_destroy); wl_signal_emit_mutable(&xdg_shell->events.destroy, xdg_shell); + + assert(wl_list_empty(&xdg_shell->events.new_surface.listener_list)); + assert(wl_list_empty(&xdg_shell->events.new_toplevel.listener_list)); + assert(wl_list_empty(&xdg_shell->events.new_popup.listener_list)); + assert(wl_list_empty(&xdg_shell->events.destroy.listener_list)); + wl_list_remove(&xdg_shell->display_destroy.link); wl_global_destroy(xdg_shell->global); free(xdg_shell); diff --git a/types/xdg_shell/wlr_xdg_surface.c b/types/xdg_shell/wlr_xdg_surface.c index 029ab81a6..8a252d2f8 100644 --- a/types/xdg_shell/wlr_xdg_surface.c +++ b/types/xdg_shell/wlr_xdg_surface.c @@ -165,10 +165,7 @@ uint32_t wlr_xdg_surface_schedule_configure(struct wlr_xdg_surface *surface) { struct wl_display *display = wl_client_get_display(surface->client->client); struct wl_event_loop *loop = wl_display_get_event_loop(display); - if (!surface->initialized) { - wlr_log(WLR_ERROR, "A configure is scheduled for an uninitialized xdg_surface %p", - surface); - } + assert(surface->initialized); if (surface->configure_idle == NULL) { surface->scheduled_serial = wl_display_next_serial(display); @@ -219,12 +216,12 @@ static void xdg_surface_handle_set_window_geometry(struct wl_client *client, } if (width <= 0 || height <= 0) { - wl_resource_post_error(resource, - XDG_SURFACE_ERROR_INVALID_SIZE, + wl_resource_post_error(resource, XDG_SURFACE_ERROR_INVALID_SIZE, "Tried to set invalid xdg-surface geometry"); return; } + surface->pending.committed |= WLR_XDG_SURFACE_STATE_WINDOW_GEOMETRY; surface->pending.geometry.x = x; surface->pending.geometry.y = y; surface->pending.geometry.width = width; @@ -256,6 +253,32 @@ static const struct xdg_surface_interface xdg_surface_implementation = { .set_window_geometry = xdg_surface_handle_set_window_geometry, }; +// The window geometry is updated on commit, unless the commit is going to map +// the surface, in which case it's updated on map, so that subsurfaces are +// mapped and surface extents are computed correctly. +static void update_geometry(struct wlr_xdg_surface *surface) { + if (!wlr_box_empty(&surface->current.geometry)) { + if ((surface->current.committed & WLR_XDG_SURFACE_STATE_WINDOW_GEOMETRY) != 0) { + struct wlr_box *geom = &surface->geometry; + wlr_surface_get_extents(surface->surface, geom); + + wlr_box_intersection(geom, geom, &surface->current.geometry); + if (wlr_box_empty(geom)) { + wlr_log(WLR_INFO, + "A client has committed an invalid effective window geometry (%d,%d %dx%d); " + "this will result in client disconnection in the future", + geom->x, geom->y, geom->width, geom->height); + + // Fall back to the explicitly set window geometry as extents could be empty which + // would result in strange state when the client commits a buffer later + *geom = surface->current.geometry; + } + } + } else { + wlr_surface_get_extents(surface->surface, &surface->geometry); + } +} + static void xdg_surface_role_client_commit(struct wlr_surface *wlr_surface) { struct wlr_xdg_surface *surface = wlr_xdg_surface_try_from_wlr_surface(wlr_surface); assert(surface != NULL); @@ -320,11 +343,20 @@ static void xdg_surface_role_commit(struct wlr_surface *wlr_surface) { break; } - if (wlr_surface_has_buffer(wlr_surface)) { + if (!wlr_surface->mapped && wlr_surface_has_buffer(wlr_surface)) { wlr_surface_map(wlr_surface); + } else { + update_geometry(surface); } } +static void xdg_surface_role_map(struct wlr_surface *wlr_surface) { + struct wlr_xdg_surface *surface = wlr_xdg_surface_try_from_wlr_surface(wlr_surface); + assert(surface != NULL); + + update_geometry(surface); +} + static void xdg_surface_role_destroy(struct wlr_surface *wlr_surface) { struct wlr_xdg_surface *surface = wlr_xdg_surface_try_from_wlr_surface(wlr_surface); if (surface == NULL) { @@ -339,11 +371,19 @@ static const struct wlr_surface_role xdg_surface_role = { .name = "xdg_surface", .client_commit = xdg_surface_role_client_commit, .commit = xdg_surface_role_commit, + .map = xdg_surface_role_map, .destroy = xdg_surface_role_destroy, }; +static void surface_synced_move_state(void *_dst, void *_src) { + struct wlr_xdg_surface_state *dst = _dst, *src = _src; + *dst = *src; + src->committed = 0; +} + static const struct wlr_surface_synced_impl surface_synced_impl = { .state_size = sizeof(struct wlr_xdg_surface_state), + .move_state = surface_synced_move_state, }; struct wlr_xdg_surface *wlr_xdg_surface_try_from_wlr_surface( @@ -491,6 +531,12 @@ void destroy_xdg_surface(struct wlr_xdg_surface *surface) { wl_signal_emit_mutable(&surface->events.destroy, NULL); + assert(wl_list_empty(&surface->events.destroy.listener_list)); + assert(wl_list_empty(&surface->events.ping_timeout.listener_list)); + assert(wl_list_empty(&surface->events.new_popup.listener_list)); + assert(wl_list_empty(&surface->events.configure.listener_list)); + assert(wl_list_empty(&surface->events.ack_configure.listener_list)); + wl_list_remove(&surface->link); wlr_surface_synced_finish(&surface->synced); wl_resource_set_user_data(surface->resource, NULL); @@ -522,12 +568,8 @@ void wlr_xdg_popup_get_position(struct wlr_xdg_popup *popup, double *popup_sx, double *popup_sy) { struct wlr_xdg_surface *parent = wlr_xdg_surface_try_from_wlr_surface(popup->parent); assert(parent != NULL); - struct wlr_box parent_geo; - wlr_xdg_surface_get_geometry(parent, &parent_geo); - *popup_sx = parent_geo.x + popup->current.geometry.x - - popup->base->current.geometry.x; - *popup_sy = parent_geo.y + popup->current.geometry.y - - popup->base->current.geometry.y; + *popup_sx = parent->geometry.x + popup->current.geometry.x - popup->base->geometry.x; + *popup_sy = parent->geometry.y + popup->current.geometry.y - popup->base->geometry.y; } struct wlr_surface *wlr_xdg_surface_surface_at( @@ -611,15 +653,3 @@ void wlr_xdg_surface_for_each_popup_surface(struct wlr_xdg_surface *surface, wlr_surface_iterator_func_t iterator, void *user_data) { xdg_surface_for_each_popup_surface(surface, 0, 0, iterator, user_data); } - -void wlr_xdg_surface_get_geometry(struct wlr_xdg_surface *surface, - struct wlr_box *box) { - wlr_surface_get_extends(surface->surface, box); - - /* The client never set the geometry */ - if (wlr_box_empty(&surface->current.geometry)) { - return; - } - - wlr_box_intersection(box, &surface->current.geometry, box); -} diff --git a/types/xdg_shell/wlr_xdg_toplevel.c b/types/xdg_shell/wlr_xdg_toplevel.c index 1c4fa30c4..1b33916e7 100644 --- a/types/xdg_shell/wlr_xdg_toplevel.c +++ b/types/xdg_shell/wlr_xdg_toplevel.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include "types/wlr_xdg_shell.h" @@ -14,6 +15,7 @@ void handle_xdg_toplevel_ack_configure( toplevel->pending.resizing = configure->resizing; toplevel->pending.activated = configure->activated; toplevel->pending.tiled = configure->tiled; + toplevel->pending.constrained = configure->constrained; toplevel->pending.suspended = configure->suspended; toplevel->pending.width = configure->width; @@ -79,7 +81,7 @@ struct wlr_xdg_toplevel_configure *send_xdg_toplevel_configure( states[nstates++] = XDG_TOPLEVEL_STATE_ACTIVATED; } if (configure->tiled && version >= XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION) { - const struct { + static const struct { enum wlr_edges edge; enum xdg_toplevel_state state; } tiled[] = { @@ -89,7 +91,7 @@ struct wlr_xdg_toplevel_configure *send_xdg_toplevel_configure( { WLR_EDGE_BOTTOM, XDG_TOPLEVEL_STATE_TILED_BOTTOM }, }; - for (size_t i = 0; i < sizeof(tiled)/sizeof(tiled[0]); ++i) { + for (size_t i = 0; i < sizeof(tiled) / sizeof(tiled[0]); ++i) { if ((configure->tiled & tiled[i].edge) == 0) { continue; } @@ -99,6 +101,24 @@ struct wlr_xdg_toplevel_configure *send_xdg_toplevel_configure( if (configure->suspended && version >= XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION) { states[nstates++] = XDG_TOPLEVEL_STATE_SUSPENDED; } + if (configure->constrained && version >= XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION) { + static const struct { + enum wlr_edges edge; + enum xdg_toplevel_state state; + } constrained[] = { + { WLR_EDGE_LEFT, XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT }, + { WLR_EDGE_RIGHT, XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT }, + { WLR_EDGE_TOP, XDG_TOPLEVEL_STATE_CONSTRAINED_TOP }, + { WLR_EDGE_BOTTOM, XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM }, + }; + + for (size_t i = 0; i < sizeof(constrained) / sizeof(constrained[0]); ++i) { + if ((configure->constrained & constrained[i].edge) == 0) { + continue; + } + states[nstates++] = constrained[i].state; + } + } assert(nstates <= sizeof(states) / sizeof(states[0])); int32_t width = configure->width; @@ -297,18 +317,8 @@ static void xdg_toplevel_handle_resize(struct wl_client *client, struct wlr_seat_client *seat = wlr_seat_client_from_resource(seat_resource); - switch (edges) { - case XDG_TOPLEVEL_RESIZE_EDGE_NONE: - case XDG_TOPLEVEL_RESIZE_EDGE_TOP: - case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM: - case XDG_TOPLEVEL_RESIZE_EDGE_LEFT: - case XDG_TOPLEVEL_RESIZE_EDGE_RIGHT: - case XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT: - case XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT: - case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT: - case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT: - break; - default: + uint32_t version = wl_resource_get_version(toplevel->base->resource); + if (!xdg_toplevel_resize_edge_is_valid(edges, version)) { wl_resource_post_error(toplevel->base->resource, XDG_TOPLEVEL_ERROR_INVALID_RESIZE_EDGE, "provided value is not a valid variant of the resize_edge enum"); @@ -535,6 +545,17 @@ void destroy_xdg_toplevel(struct wlr_xdg_toplevel *toplevel) { wl_signal_emit_mutable(&toplevel->events.destroy, NULL); + assert(wl_list_empty(&toplevel->events.destroy.listener_list)); + assert(wl_list_empty(&toplevel->events.request_maximize.listener_list)); + assert(wl_list_empty(&toplevel->events.request_fullscreen.listener_list)); + assert(wl_list_empty(&toplevel->events.request_minimize.listener_list)); + assert(wl_list_empty(&toplevel->events.request_move.listener_list)); + assert(wl_list_empty(&toplevel->events.request_resize.listener_list)); + assert(wl_list_empty(&toplevel->events.request_show_window_menu.listener_list)); + assert(wl_list_empty(&toplevel->events.set_parent.listener_list)); + assert(wl_list_empty(&toplevel->events.set_title.listener_list)); + assert(wl_list_empty(&toplevel->events.set_app_id.listener_list)); + wlr_surface_synced_finish(&toplevel->synced); toplevel->base->toplevel = NULL; wl_resource_set_user_data(toplevel->resource, NULL); @@ -545,6 +566,30 @@ void wlr_xdg_toplevel_send_close(struct wlr_xdg_toplevel *toplevel) { xdg_toplevel_send_close(toplevel->resource); } +uint32_t wlr_xdg_toplevel_configure(struct wlr_xdg_toplevel *toplevel, + const struct wlr_xdg_toplevel_configure *configure) { + toplevel->scheduled.width = configure->width; + toplevel->scheduled.height = configure->height; + toplevel->scheduled.maximized = configure->maximized; + toplevel->scheduled.fullscreen = configure->fullscreen; + toplevel->scheduled.resizing = configure->resizing; + toplevel->scheduled.activated = configure->activated; + toplevel->scheduled.suspended = configure->suspended; + toplevel->scheduled.tiled = configure->tiled; + + if (configure->fields & WLR_XDG_TOPLEVEL_CONFIGURE_BOUNDS) { + toplevel->scheduled.fields |= WLR_XDG_TOPLEVEL_CONFIGURE_BOUNDS; + toplevel->scheduled.bounds = configure->bounds; + } + + if (configure->fields & WLR_XDG_TOPLEVEL_CONFIGURE_WM_CAPABILITIES) { + toplevel->scheduled.fields |= WLR_XDG_TOPLEVEL_CONFIGURE_WM_CAPABILITIES; + toplevel->scheduled.wm_capabilities = configure->wm_capabilities; + } + + return wlr_xdg_surface_schedule_configure(toplevel->base); +} + uint32_t wlr_xdg_toplevel_set_size(struct wlr_xdg_toplevel *toplevel, int32_t width, int32_t height) { assert(width >= 0 && height >= 0); @@ -612,3 +657,10 @@ uint32_t wlr_xdg_toplevel_set_suspended(struct wlr_xdg_toplevel *toplevel, toplevel->scheduled.suspended = suspended; return wlr_xdg_surface_schedule_configure(toplevel->base); } + +uint32_t wlr_xdg_toplevel_set_constrained(struct wlr_xdg_toplevel *toplevel, uint32_t constrained) { + assert(toplevel->base->client->shell->version >= + XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION); + toplevel->scheduled.constrained = constrained; + return wlr_xdg_surface_schedule_configure(toplevel->base); +} diff --git a/util/box.c b/util/box.c index a615e2f3e..aae09888f 100644 --- a/util/box.c +++ b/util/box.c @@ -19,16 +19,15 @@ void wlr_box_closest_point(const struct wlr_box *box, double x, double y, // // In order to be consistent with e.g. wlr_box_contains_point(), // this function returns a point inside the bottom and right edges - // of the box by at least 1/65536 of a unit (pixel). 1/65536 is + // of the box by at least 1/256 of a unit (pixel). 1/256 is // small enough to avoid a "dead zone" with high-resolution mice - // but large enough to avoid rounding to zero (due to loss of - // significant digits) in simple floating-point calculations. + // but large enough to avoid rounding to zero in wl_fixed_from_double(). // find the closest x point if (x < box->x) { *dest_x = box->x; - } else if (x > box->x + box->width - 1/65536.0) { - *dest_x = box->x + box->width - 1/65536.0; + } else if (x > box->x + box->width - 1/256.0) { + *dest_x = box->x + box->width - 1/256.0; } else { *dest_x = x; } @@ -36,8 +35,8 @@ void wlr_box_closest_point(const struct wlr_box *box, double x, double y, // find closest y point if (y < box->y) { *dest_y = box->y; - } else if (y > box->y + box->height - 1/65536.0) { - *dest_y = box->y + box->height - 1/65536.0; + } else if (y > box->y + box->height - 1/256.0) { + *dest_y = box->y + box->height - 1/256.0; } else { *dest_y = y; } @@ -67,7 +66,12 @@ bool wlr_box_intersection(struct wlr_box *dest, const struct wlr_box *box_a, dest->width = x2 - x1; dest->height = y2 - y1; - return !wlr_box_empty(dest); + if (wlr_box_empty(dest)) { + *dest = (struct wlr_box){0}; + return false; + } + + return true; } bool wlr_box_contains_point(const struct wlr_box *box, double x, double y) { @@ -79,6 +83,17 @@ bool wlr_box_contains_point(const struct wlr_box *box, double x, double y) { } } +bool wlr_box_contains_box(const struct wlr_box *bigger, const struct wlr_box *smaller) { + if (wlr_box_empty(bigger) || wlr_box_empty(smaller)) { + return false; + } + + return smaller->x >= bigger->x && + smaller->x + smaller->width <= bigger->x + bigger->width && + smaller->y >= bigger->y && + smaller->y + smaller->height <= bigger->y + bigger->height; +} + void wlr_box_transform(struct wlr_box *dest, const struct wlr_box *box, enum wl_output_transform transform, int width, int height) { struct wlr_box src = {0}; diff --git a/types/wlr_matrix.c b/util/matrix.c similarity index 80% rename from types/wlr_matrix.c rename to util/matrix.c index 7b6671f57..640787c95 100644 --- a/types/wlr_matrix.c +++ b/util/matrix.c @@ -1,10 +1,8 @@ -#include +#include #include #include -#include -#include #include -#include "types/wlr_matrix.h" +#include "util/matrix.h" void wlr_matrix_identity(float mat[static 9]) { static const float identity[9] = { @@ -34,15 +32,6 @@ void wlr_matrix_multiply(float mat[static 9], const float a[static 9], memcpy(mat, product, sizeof(product)); } -void wlr_matrix_transpose(float mat[static 9], const float a[static 9]) { - float transposition[9] = { - a[0], a[3], a[6], - a[1], a[4], a[7], - a[2], a[5], a[8], - }; - memcpy(mat, transposition, sizeof(transposition)); -} - void wlr_matrix_translate(float mat[static 9], float x, float y) { float translate[9] = { 1.0f, 0.0f, x, @@ -61,15 +50,6 @@ void wlr_matrix_scale(float mat[static 9], float x, float y) { wlr_matrix_multiply(mat, mat, scale); } -void wlr_matrix_rotate(float mat[static 9], float rad) { - float rotate[9] = { - cos(rad), -sin(rad), 0.0f, - sin(rad), cos(rad), 0.0f, - 0.0f, 0.0f, 1.0f, - }; - wlr_matrix_multiply(mat, mat, rotate); -} - static const float transforms[][9] = { [WL_OUTPUT_TRANSFORM_NORMAL] = { 1.0f, 0.0f, 0.0f, @@ -141,8 +121,7 @@ void matrix_projection(float mat[static 9], int width, int height, } void wlr_matrix_project_box(float mat[static 9], const struct wlr_box *box, - enum wl_output_transform transform, float rotation, - const float projection[static 9]) { + enum wl_output_transform transform, const float projection[static 9]) { int x = box->x; int y = box->y; int width = box->width; @@ -151,12 +130,6 @@ void wlr_matrix_project_box(float mat[static 9], const struct wlr_box *box, wlr_matrix_identity(mat); wlr_matrix_translate(mat, x, y); - if (rotation != 0) { - wlr_matrix_translate(mat, width/2, height/2); - wlr_matrix_rotate(mat, rotation); - wlr_matrix_translate(mat, -width/2, -height/2); - } - wlr_matrix_scale(mat, width, height); if (transform != WL_OUTPUT_TRANSFORM_NORMAL) { @@ -167,3 +140,26 @@ void wlr_matrix_project_box(float mat[static 9], const struct wlr_box *box, wlr_matrix_multiply(mat, projection, mat); } + +void matrix_invert(float out[static 9], float m[static 9]) { + float a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5], g = m[6], h = m[7], i = m[8]; + + // See: https://en.wikipedia.org/wiki/Determinant + float det = a*e*i + b*f*g + c*d*h - c*e*g - b*d*i - a*f*h; + assert(det != 0); + float inv_det = 1 / det; + + // See: https://en.wikipedia.org/wiki/Invertible_matrix#Inversion_of_3_%C3%97_3_matrices + float result[] = { + inv_det * (e*i - f*h), + inv_det * -(b*i - c*h), + inv_det * (b*f - c*e), + inv_det * -(d*i - f*g), + inv_det * (a*i - c*g), + inv_det * -(a*f - c*d), + inv_det * (d*h - e*g), + inv_det * -(a*h - b*g), + inv_det * (a*e - b*d), + }; + memcpy(out, result, sizeof(result)); +} diff --git a/util/mem.c b/util/mem.c new file mode 100644 index 000000000..cfd2f80e4 --- /dev/null +++ b/util/mem.c @@ -0,0 +1,16 @@ +#include +#include +#include + +#include "util/mem.h" + +bool memdup(void *out, const void *src, size_t size) { + void *dst = malloc(size); + if (dst == NULL) { + return false; + } + memcpy(dst, src, size); + void **dst_ptr = out; + *dst_ptr = dst; + return true; +} diff --git a/util/meson.build b/util/meson.build index 053e2c5eb..d67911e52 100644 --- a/util/meson.build +++ b/util/meson.build @@ -5,6 +5,8 @@ wlr_files += files( 'env.c', 'global.c', 'log.c', + 'matrix.c', + 'mem.c', 'rect_union.c', 'region.c', 'set.c', diff --git a/util/time.c b/util/time.c index bc4a10637..1a8f32969 100644 --- a/util/time.c +++ b/util/time.c @@ -3,8 +3,6 @@ #include "util/time.h" -static const long NSEC_PER_SEC = 1000000000; - int64_t timespec_to_msec(const struct timespec *a) { return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000; } diff --git a/xwayland/meson.build b/xwayland/meson.build index 81cb018eb..3d29dc6c5 100644 --- a/xwayland/meson.build +++ b/xwayland/meson.build @@ -1,13 +1,13 @@ xwayland_libs = [] -xwayland_required = [ - 'xcb', - 'xcb-composite', - 'xcb-ewmh', - 'xcb-icccm', - 'xcb-render', - 'xcb-res', - 'xcb-xfixes', -] +xwayland_required = { + 'xcb': [], + 'xcb-composite': [], + 'xcb-ewmh': [], + 'xcb-icccm': [], + 'xcb-render': [], + 'xcb-res': [], + 'xcb-xfixes': '>=1.15', +} xwayland_optional = { 'xcb-errors': 'Required for printing X11 errors.', } @@ -37,8 +37,9 @@ if not xwayland.found() subdir_done() endif -foreach lib : xwayland_required +foreach lib, version : xwayland_required dep = dependency(lib, + version: version, required: get_option('xwayland'), not_found_message: '\n'.join(msg).format(lib), ) @@ -93,6 +94,3 @@ wlr_files += files( ) wlr_deps += xwayland_libs features += { 'xwayland': true } - -have = cc.has_function('xcb_xfixes_set_client_disconnect_mode', dependencies: xwayland_libs) -internal_config.set10('HAVE_XCB_XFIXES_SET_CLIENT_DISCONNECT_MODE', have) diff --git a/xwayland/selection/dnd.c b/xwayland/selection/dnd.c index e9cc63d9b..70792b5f4 100644 --- a/xwayland/selection/dnd.c +++ b/xwayland/selection/dnd.c @@ -55,7 +55,7 @@ static void xwm_dnd_send_event(struct wlr_xwm *xwm, xcb_atom_t type, XCB_EVENT_MASK_NO_EVENT, &event, sizeof(event)); - xcb_flush(xwm->xcb_conn); + xwm_schedule_flush(xwm); } static void xwm_dnd_send_enter(struct wlr_xwm *xwm) { @@ -212,20 +212,17 @@ int xwm_handle_selection_client_message(struct wlr_xwm *xwm, bool performed = data->data32[1] & 1; xcb_atom_t action_atom = data->data32[2]; - if (xwm->drag_focus == NULL || - target_window != xwm->drag_focus->window_id) { + if (xwm->drop_focus == NULL || + target_window != xwm->drop_focus->window_id) { wlr_log(WLR_DEBUG, "ignoring XdndFinished client message because " - "it doesn't match the finished drag focus window ID"); + "it doesn't match the finished drop focus window ID"); return 1; } enum wl_data_device_manager_dnd_action action = data_device_manager_dnd_action_from_atom(xwm, action_atom); - if (performed) { - wlr_data_source_dnd_finish(source); - } - + wlr_data_source_dnd_finish(source); wlr_log(WLR_DEBUG, "DND_FINISH window=%" PRIu32 " performed=%d action=%d", target_window, performed, action); return 1; @@ -234,37 +231,54 @@ int xwm_handle_selection_client_message(struct wlr_xwm *xwm, } } +static void xwm_set_drag_focus(struct wlr_xwm *xwm, struct wlr_xwayland_surface *focus); + +static void drag_focus_handle_destroy(struct wl_listener *listener, void *data) { + struct wlr_xwm *xwm = wl_container_of(listener, xwm, drag_focus_destroy); + xwm_set_drag_focus(xwm, NULL); +} + +static void drop_focus_handle_destroy(struct wl_listener *listener, void *data) { + struct wlr_xwm *xwm = wl_container_of(listener, xwm, drop_focus_destroy); + wl_list_remove(&xwm->drop_focus_destroy.link); + wl_list_init(&xwm->drop_focus_destroy.link); + xwm->drop_focus = NULL; +} + +static void xwm_set_drag_focus(struct wlr_xwm *xwm, struct wlr_xwayland_surface *focus) { + if (focus == xwm->drag_focus) { + return; + } + + if (xwm->drag_focus != NULL) { + wlr_data_source_dnd_action(xwm->drag->source, + WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE); + xwm_dnd_send_leave(xwm); + } + + wl_list_remove(&xwm->drag_focus_destroy.link); + wl_list_init(&xwm->drag_focus_destroy.link); + + xwm->drag_focus = focus; + + if (xwm->drag_focus != NULL) { + xwm->drag_focus_destroy.notify = drag_focus_handle_destroy; + wl_signal_add(&xwm->drag_focus->events.destroy, &xwm->drag_focus_destroy); + + xwm_dnd_send_enter(xwm); + } +} + static void seat_handle_drag_focus(struct wl_listener *listener, void *data) { struct wlr_drag *drag = data; struct wlr_xwm *xwm = wl_container_of(listener, xwm, seat_drag_focus); struct wlr_xwayland_surface *focus = NULL; if (drag->focus != NULL) { - // TODO: check for subsurfaces? - struct wlr_xwayland_surface *surface; - wl_list_for_each(surface, &xwm->surfaces, link) { - if (surface->surface == drag->focus) { - focus = surface; - break; - } - } + focus = wlr_xwayland_surface_try_from_wlr_surface(drag->focus); } - if (focus == xwm->drag_focus) { - return; - } - - if (xwm->drag_focus != NULL) { - wlr_data_source_dnd_action(drag->source, - WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE); - xwm_dnd_send_leave(xwm); - } - - xwm->drag_focus = focus; - - if (xwm->drag_focus != NULL) { - xwm_dnd_send_enter(xwm); - } + xwm_set_drag_focus(xwm, focus); } static void seat_handle_drag_motion(struct wl_listener *listener, void *data) { @@ -289,6 +303,12 @@ static void seat_handle_drag_drop(struct wl_listener *listener, void *data) { } wlr_log(WLR_DEBUG, "Wayland drag dropped over an Xwayland window"); + + xwm->drop_focus = xwm->drag_focus; + xwm->drop_focus_destroy.notify = drop_focus_handle_destroy; + wl_list_remove(&xwm->drop_focus_destroy.link); + wl_signal_add(&xwm->drop_focus->events.destroy, &xwm->drop_focus_destroy); + xwm_dnd_send_drop(xwm, event->time); } @@ -315,12 +335,25 @@ static void seat_handle_drag_source_destroy(struct wl_listener *listener, wl_container_of(listener, xwm, seat_drag_source_destroy); wl_list_remove(&xwm->seat_drag_source_destroy.link); + wl_list_init(&xwm->seat_drag_source_destroy.link); + wl_list_remove(&xwm->drag_focus_destroy.link); + wl_list_init(&xwm->drag_focus_destroy.link); xwm->drag_focus = NULL; + + wl_list_remove(&xwm->drop_focus_destroy.link); + wl_list_init(&xwm->drop_focus_destroy.link); + xwm->drop_focus = NULL; } void xwm_seat_handle_start_drag(struct wlr_xwm *xwm, struct wlr_drag *drag) { + wl_list_remove(&xwm->drag_focus_destroy.link); + wl_list_init(&xwm->drag_focus_destroy.link); + wl_list_remove(&xwm->drop_focus_destroy.link); + wl_list_init(&xwm->drop_focus_destroy.link); + xwm->drag = drag; xwm->drag_focus = NULL; + xwm->drop_focus = NULL; if (drag != NULL) { wl_signal_add(&drag->events.focus, &xwm->seat_drag_focus); @@ -337,3 +370,17 @@ void xwm_seat_handle_start_drag(struct wlr_xwm *xwm, struct wlr_drag *drag) { xwm->seat_drag_source_destroy.notify = seat_handle_drag_source_destroy; } } + +void xwm_seat_unlink_drag_handlers(struct wlr_xwm *xwm) { + wl_list_remove(&xwm->seat_drag_source_destroy.link); + wl_list_remove(&xwm->drag_focus_destroy.link); + wl_list_remove(&xwm->drop_focus_destroy.link); + + if (!xwm->drag) { + return; + } + wl_list_remove(&xwm->seat_drag_focus.link); + wl_list_remove(&xwm->seat_drag_motion.link); + wl_list_remove(&xwm->seat_drag_drop.link); + wl_list_remove(&xwm->seat_drag_destroy.link); +} diff --git a/xwayland/selection/incoming.c b/xwayland/selection/incoming.c index 4a0b450c1..72f82c279 100644 --- a/xwayland/selection/incoming.c +++ b/xwayland/selection/incoming.c @@ -37,7 +37,7 @@ xwm_selection_transfer_create_incoming(struct wlr_xwm_selection *selection) { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE } ); - xcb_flush(xwm->xcb_conn); + xwm_schedule_flush(xwm); return transfer; } @@ -89,7 +89,7 @@ static void xwm_notify_ready_for_next_incr_chunk( wlr_log(WLR_DEBUG, "deleting property"); xcb_delete_property(xwm->xcb_conn, transfer->incoming_window, xwm->atoms[WL_SELECTION]); - xcb_flush(xwm->xcb_conn); + xwm_schedule_flush(xwm); xwm_selection_transfer_remove_event_source(transfer); xwm_selection_transfer_destroy_property_reply(transfer); @@ -234,7 +234,7 @@ static void source_send(struct wlr_xwm_selection *selection, xwm->atoms[WL_SELECTION], XCB_TIME_CURRENT_TIME); - xcb_flush(xwm->xcb_conn); + xwm_schedule_flush(xwm); fcntl(fd, F_SETFL, O_WRONLY | O_NONBLOCK); transfer->wl_client_fd = fd; @@ -533,7 +533,7 @@ int xwm_handle_xfixes_selection_notify(struct wlr_xwm *xwm, xwm->atoms[WL_SELECTION], event->timestamp ); - xcb_flush(xwm->xcb_conn); + xwm_schedule_flush(xwm); return 1; } diff --git a/xwayland/selection/outgoing.c b/xwayland/selection/outgoing.c index 3f2e2c711..795a4768b 100644 --- a/xwayland/selection/outgoing.c +++ b/xwayland/selection/outgoing.c @@ -33,7 +33,7 @@ static void xwm_selection_send_notify(struct wlr_xwm *xwm, XCB_EVENT_MASK_NO_EVENT, &selection_notify, sizeof(selection_notify)); - xcb_flush(xwm->xcb_conn); + xwm_schedule_flush(xwm); } static int xwm_selection_flush_source_data( @@ -46,7 +46,7 @@ static int xwm_selection_flush_source_data( 8, // format transfer->source_data.size, transfer->source_data.data); - xcb_flush(transfer->selection->xwm->xcb_conn); + xwm_schedule_flush(transfer->selection->xwm); transfer->property_set = true; size_t length = transfer->source_data.size; transfer->source_data.size = 0; @@ -410,8 +410,11 @@ void xwm_handle_selection_request(struct wlr_xwm *xwm, return; } + bool dnd_allowed = selection == &xwm->dnd_selection + && (xwm->drag_focus != NULL || xwm->drop_focus != NULL); + // No xwayland surface focused, deny access to clipboard - if (xwm->focus_surface == NULL && xwm->drag_focus == NULL) { + if (xwm->focus_surface == NULL && !dnd_allowed) { if (wlr_log_get_verbosity() >= WLR_DEBUG) { char *selection_name = xwm_get_atom_name(xwm, selection->atom); wlr_log(WLR_DEBUG, "denying read access to selection %u (%s): " diff --git a/xwayland/selection/selection.c b/xwayland/selection/selection.c index 4d1e366eb..deb695e24 100644 --- a/xwayland/selection/selection.c +++ b/xwayland/selection/selection.c @@ -52,7 +52,7 @@ void xwm_selection_transfer_destroy( if (transfer->incoming_window) { struct wlr_xwm *xwm = transfer->selection->xwm; xcb_destroy_window(xwm->xcb_conn, transfer->incoming_window); - xcb_flush(xwm->xcb_conn); + xwm_schedule_flush(xwm); } wl_list_remove(&transfer->link); @@ -269,14 +269,14 @@ static void xwm_selection_set_owner(struct wlr_xwm_selection *selection, selection->window, selection->atom, XCB_TIME_CURRENT_TIME); - xcb_flush(selection->xwm->xcb_conn); + xwm_schedule_flush(selection->xwm); } else { if (selection->owner == selection->window) { xcb_set_selection_owner(selection->xwm->xcb_conn, XCB_WINDOW_NONE, selection->atom, selection->timestamp); - xcb_flush(selection->xwm->xcb_conn); + xwm_schedule_flush(selection->xwm); } } } diff --git a/xwayland/server.c b/xwayland/server.c index 4f92879d1..0e8ad44fe 100644 --- a/xwayland/server.c +++ b/xwayland/server.c @@ -98,7 +98,7 @@ noreturn static void exec_xwayland(struct wlr_xwayland_server *server, argv[i++] = NULL; - assert(i < sizeof(argv) / sizeof(argv[0])); + assert(i <= sizeof(argv) / sizeof(argv[0])); char wayland_socket_str[16]; snprintf(wayland_socket_str, sizeof(wayland_socket_str), "%d", server->wl_fd[1]); @@ -257,6 +257,17 @@ static int xserver_handle_ready(int fd, uint32_t mask, void *data) { if (errno == EINTR) { continue; } + + /* If some application has installed a SIGCHLD handler, they + * may race and waitpid() on our child, which will cause this + * waitpid() to fail. We have a signal from the + * notify pipe that things are ready, so this waitpid() is only + * to prevent zombies, which will have already been reaped by + * the application's SIGCHLD handler. + */ + if (errno == ECHILD) { + break; + } wlr_log_errno(WLR_ERROR, "waitpid for Xwayland fork failed"); goto error; } @@ -444,7 +455,13 @@ void wlr_xwayland_server_destroy(struct wlr_xwayland_server *server) { } server_finish_process(server); server_finish_display(server); + wl_signal_emit_mutable(&server->events.destroy, NULL); + + assert(wl_list_empty(&server->events.start.listener_list)); + assert(wl_list_empty(&server->events.ready.listener_list)); + assert(wl_list_empty(&server->events.destroy.listener_list)); + free(server); } diff --git a/xwayland/shell.c b/xwayland/shell.c index 1f7e11aff..f048eb627 100644 --- a/xwayland/shell.c +++ b/xwayland/shell.c @@ -178,6 +178,7 @@ struct wlr_xwayland_shell_v1 *wlr_xwayland_shell_v1_create( } wl_list_init(&shell->surfaces); + wl_signal_init(&shell->events.new_surface); wl_signal_init(&shell->events.destroy); @@ -196,6 +197,9 @@ void wlr_xwayland_shell_v1_destroy(struct wlr_xwayland_shell_v1 *shell) { wl_signal_emit_mutable(&shell->events.destroy, NULL); + assert(wl_list_empty(&shell->events.new_surface.listener_list)); + assert(wl_list_empty(&shell->events.destroy.listener_list)); + struct wlr_xwayland_surface_v1 *xwl_surface, *tmp; wl_list_for_each_safe(xwl_surface, tmp, &shell->surfaces, link) { xwl_surface_destroy(xwl_surface); diff --git a/xwayland/xwayland.c b/xwayland/xwayland.c index c4ca9ae44..3aa47bac2 100644 --- a/xwayland/xwayland.c +++ b/xwayland/xwayland.c @@ -1,7 +1,5 @@ #include -#include #include -#include #include #include #include @@ -14,7 +12,6 @@ #include #include #include -#include "sockets.h" #include "xwayland/xwm.h" struct wlr_xwayland_cursor { @@ -45,6 +42,9 @@ static void handle_server_start(struct wl_listener *listener, void *data) { static void xwayland_mark_ready(struct wlr_xwayland *xwayland) { assert(xwayland->server->wm_fd[0] >= 0); xwayland->xwm = xwm_create(xwayland, xwayland->server->wm_fd[0]); + // xwm_create takes ownership of wm_fd[0] under all circumstances + xwayland->server->wm_fd[0] = -1; + if (!xwayland->xwm) { return; } @@ -72,6 +72,11 @@ static void handle_shell_destroy(struct wl_listener *listener, void *data) { struct wlr_xwayland *xwayland = wl_container_of(listener, xwayland, shell_destroy); xwayland->shell_v1 = NULL; + wl_list_remove(&xwayland->shell_destroy.link); + // Will remove this list in handle_shell_destroy(). + // This ensures the link is always initialized and + // avoids the need to keep check conditions in sync. + wl_list_init(&xwayland->shell_destroy.link); } void wlr_xwayland_destroy(struct wlr_xwayland *xwayland) { @@ -79,6 +84,13 @@ void wlr_xwayland_destroy(struct wlr_xwayland *xwayland) { return; } + wl_signal_emit_mutable(&xwayland->events.destroy, NULL); + + assert(wl_list_empty(&xwayland->events.destroy.listener_list)); + assert(wl_list_empty(&xwayland->events.new_surface.listener_list)); + assert(wl_list_empty(&xwayland->events.ready.listener_list)); + assert(wl_list_empty(&xwayland->events.remove_startup_info.listener_list)); + wl_list_remove(&xwayland->server_destroy.link); wl_list_remove(&xwayland->server_start.link); wl_list_remove(&xwayland->server_ready.link); @@ -91,6 +103,7 @@ void wlr_xwayland_destroy(struct wlr_xwayland *xwayland) { } xwayland->server = NULL; wlr_xwayland_shell_v1_destroy(xwayland->shell_v1); + xwm_destroy(xwayland->xwm); free(xwayland); } @@ -104,6 +117,7 @@ struct wlr_xwayland *wlr_xwayland_create_with_server(struct wl_display *wl_displ xwayland->wl_display = wl_display; xwayland->compositor = compositor; + wl_signal_init(&xwayland->events.destroy); wl_signal_init(&xwayland->events.new_surface); wl_signal_init(&xwayland->events.ready); wl_signal_init(&xwayland->events.remove_startup_info); @@ -139,9 +153,7 @@ struct wlr_xwayland *wlr_xwayland_create(struct wl_display *wl_display, struct wlr_xwayland_server_options options = { .lazy = lazy, .enable_wm = true, -#if HAVE_XCB_XFIXES_SET_CLIENT_DISCONNECT_MODE .terminate_delay = lazy ? 10 : 0, -#endif }; struct wlr_xwayland_server *server = wlr_xwayland_server_create(wl_display, &options); if (server == NULL) { diff --git a/xwayland/xwm.c b/xwayland/xwm.c index c65851bb8..2bb4e4c64 100644 --- a/xwayland/xwm.c +++ b/xwayland/xwm.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include @@ -35,9 +34,11 @@ static const char *const atom_map[ATOM_LAST] = { [NET_WM_STATE] = "_NET_WM_STATE", [NET_WM_STRUT_PARTIAL] = "_NET_WM_STRUT_PARTIAL", [NET_WM_WINDOW_TYPE] = "_NET_WM_WINDOW_TYPE", + [NET_WM_ICON] = "_NET_WM_ICON", [WM_TAKE_FOCUS] = "WM_TAKE_FOCUS", [WINDOW] = "WINDOW", [NET_ACTIVE_WINDOW] = "_NET_ACTIVE_WINDOW", + [NET_CLOSE_WINDOW] = "_NET_CLOSE_WINDOW", [NET_WM_MOVERESIZE] = "_NET_WM_MOVERESIZE", [NET_SUPPORTING_WM_CHECK] = "_NET_SUPPORTING_WM_CHECK", [NET_WM_STATE_FOCUSED] = "_NET_WM_STATE_FOCUSED", @@ -46,6 +47,13 @@ static const char *const atom_map[ATOM_LAST] = { [NET_WM_STATE_MAXIMIZED_VERT] = "_NET_WM_STATE_MAXIMIZED_VERT", [NET_WM_STATE_MAXIMIZED_HORZ] = "_NET_WM_STATE_MAXIMIZED_HORZ", [NET_WM_STATE_HIDDEN] = "_NET_WM_STATE_HIDDEN", + [NET_WM_STATE_STICKY] = "_NET_WM_STATE_STICKY", + [NET_WM_STATE_SHADED] = "_NET_WM_STATE_SHADED", + [NET_WM_STATE_SKIP_TASKBAR] = "_NET_WM_STATE_SKIP_TASKBAR", + [NET_WM_STATE_SKIP_PAGER] = "_NET_WM_STATE_SKIP_PAGER", + [NET_WM_STATE_ABOVE] = "_NET_WM_STATE_ABOVE", + [NET_WM_STATE_BELOW] = "_NET_WM_STATE_BELOW", + [NET_WM_STATE_DEMANDS_ATTENTION] = "_NET_WM_STATE_DEMANDS_ATTENTION", [NET_WM_PING] = "_NET_WM_PING", [WM_CHANGE_STATE] = "WM_CHANGE_STATE", [WM_STATE] = "WM_STATE", @@ -61,6 +69,7 @@ static const char *const atom_map[ATOM_LAST] = { [NET_STARTUP_ID] = "_NET_STARTUP_ID", [NET_STARTUP_INFO] = "_NET_STARTUP_INFO", [NET_STARTUP_INFO_BEGIN] = "_NET_STARTUP_INFO_BEGIN", + [NET_WM_WINDOW_OPACITY] = "_NET_WM_WINDOW_OPACITY", [NET_WM_WINDOW_TYPE_NORMAL] = "_NET_WM_WINDOW_TYPE_NORMAL", [NET_WM_WINDOW_TYPE_UTILITY] = "_NET_WM_WINDOW_TYPE_UTILITY", [NET_WM_WINDOW_TYPE_TOOLTIP] = "_NET_WM_WINDOW_TYPE_TOOLTIP", @@ -72,6 +81,9 @@ static const char *const atom_map[ATOM_LAST] = { [NET_WM_WINDOW_TYPE_NOTIFICATION] = "_NET_WM_WINDOW_TYPE_NOTIFICATION", [NET_WM_WINDOW_TYPE_SPLASH] = "_NET_WM_WINDOW_TYPE_SPLASH", [NET_WM_WINDOW_TYPE_DESKTOP] = "_NET_WM_WINDOW_TYPE_DESKTOP", + [NET_WM_WINDOW_TYPE_DOCK] = "_NET_WM_WINDOW_TYPE_DOCK", + [NET_WM_WINDOW_TYPE_TOOLBAR] = "_NET_WM_WINDOW_TYPE_TOOLBAR", + [NET_WM_WINDOW_TYPE_DIALOG] = "_NET_WM_WINDOW_TYPE_DIALOG", [DND_SELECTION] = "XdndSelection", [DND_AWARE] = "XdndAware", [DND_STATUS] = "XdndStatus", @@ -194,10 +206,12 @@ static struct wlr_xwayland_surface *xwayland_surface_create( surface->width = width; surface->height = height; surface->override_redirect = override_redirect; + surface->opacity = 1.0; wl_list_init(&surface->children); wl_list_init(&surface->stack_link); wl_list_init(&surface->parent_link); wl_list_init(&surface->unpaired_link); + wl_signal_init(&surface->events.destroy); wl_signal_init(&surface->events.request_configure); wl_signal_init(&surface->events.request_move); @@ -206,6 +220,14 @@ static struct wlr_xwayland_surface *xwayland_surface_create( wl_signal_init(&surface->events.request_maximize); wl_signal_init(&surface->events.request_fullscreen); wl_signal_init(&surface->events.request_activate); + wl_signal_init(&surface->events.request_close); + wl_signal_init(&surface->events.request_sticky); + wl_signal_init(&surface->events.request_shaded); + wl_signal_init(&surface->events.request_skip_taskbar); + wl_signal_init(&surface->events.request_skip_pager); + wl_signal_init(&surface->events.request_above); + wl_signal_init(&surface->events.request_below); + wl_signal_init(&surface->events.request_demands_attention); wl_signal_init(&surface->events.associate); wl_signal_init(&surface->events.dissociate); wl_signal_init(&surface->events.set_class); @@ -219,6 +241,10 @@ static struct wlr_xwayland_surface *xwayland_surface_create( wl_signal_init(&surface->events.set_strut_partial); wl_signal_init(&surface->events.set_override_redirect); wl_signal_init(&surface->events.set_geometry); + wl_signal_init(&surface->events.set_opacity); + wl_signal_init(&surface->events.set_icon); + wl_signal_init(&surface->events.focus_in); + wl_signal_init(&surface->events.grab_focus); wl_signal_init(&surface->events.map_request); wl_signal_init(&surface->events.ping_timeout); @@ -262,8 +288,7 @@ static void xwm_set_net_active_window(struct wlr_xwm *xwm, */ xcb_void_cookie_t xwm_send_event_with_size(xcb_connection_t *c, uint8_t propagate, xcb_window_t destination, - uint32_t event_mask, const void *event, uint32_t length) -{ + uint32_t event_mask, const void *event, uint32_t length) { if (length == 32) { return xcb_send_event(c, propagate, destination, event_mask, event); } else if (length < 32) { @@ -295,7 +320,7 @@ static void xwm_send_wm_message(struct wlr_xwayland_surface *surface, event_mask, &event, sizeof(event)); - xcb_flush(xwm->xcb_conn); + xwm_schedule_flush(xwm); } static void xwm_set_net_client_list(struct wlr_xwm *xwm) { @@ -353,22 +378,21 @@ static void xwm_set_net_client_list_stacking(struct wlr_xwm *xwm) { static void xsurface_set_net_wm_state(struct wlr_xwayland_surface *xsurface); -static void xwm_set_focus_window(struct wlr_xwm *xwm, +// Gives input (keyboard) focus to a window. +// Normally followed by xwm_set_focused_window(). +static void xwm_focus_window(struct wlr_xwm *xwm, struct wlr_xwayland_surface *xsurface) { - struct wlr_xwayland_surface *unfocus_surface = xwm->focus_surface; // We handle cases where focus_surface == xsurface because we // want to be able to deny FocusIn events. - xwm->focus_surface = xsurface; - - if (unfocus_surface) { - xsurface_set_net_wm_state(unfocus_surface); - } - if (!xsurface) { + // XCB_POINTER_ROOT is described in xcb documentation but isn't + // actually defined in the headers. It's distinct from XCB_NONE + // (which disables keyboard input entirely and causes issues + // with keyboard grabs for e.g. popups). xcb_set_input_focus_checked(xwm->xcb_conn, XCB_INPUT_FOCUS_POINTER_ROOT, - XCB_NONE, XCB_CURRENT_TIME); + 1L /*XCB_POINTER_ROOT*/, XCB_CURRENT_TIME); return; } @@ -391,26 +415,70 @@ static void xwm_set_focus_window(struct wlr_xwm *xwm, XCB_INPUT_FOCUS_POINTER_ROOT, xsurface->window_id, XCB_CURRENT_TIME); xwm->last_focus_seq = cookie.sequence; } +} - xsurface_set_net_wm_state(xsurface); +// Updates _NET_ACTIVE_WINDOW and _NET_WM_STATE when focus changes. +static void xwm_set_focused_window(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface) { + struct wlr_xwayland_surface *unfocus_surface = xwm->focus_surface; + + if (xsurface && xsurface->override_redirect) { + return; + } + + xwm->focus_surface = xsurface; + // cancel any pending focus offer + xwm->offered_focus = xsurface; + + if (xsurface == unfocus_surface) { + return; + } + + if (unfocus_surface) { + xsurface_set_net_wm_state(unfocus_surface); + } + + if (xsurface) { + xsurface_set_net_wm_state(xsurface); + xwm_set_net_active_window(xwm, xsurface->window_id); + } else { + xwm_set_net_active_window(xwm, xwm->no_focus_window); + } +} + +void wlr_xwayland_surface_offer_focus(struct wlr_xwayland_surface *xsurface) { + if (!xsurface || xsurface->override_redirect) { + return; + } + + struct wlr_xwm *xwm = xsurface->xwm; + if (!xwm_atoms_contains(xwm, xsurface->protocols, + xsurface->protocols_len, WM_TAKE_FOCUS)) { + return; + } + + xwm->offered_focus = xsurface; + + xcb_client_message_data_t message_data = { 0 }; + message_data.data32[0] = xwm->atoms[WM_TAKE_FOCUS]; + message_data.data32[1] = XCB_TIME_CURRENT_TIME; + xwm_send_wm_message(xsurface, &message_data, XCB_EVENT_MASK_NO_EVENT); + + xcb_flush(xwm->xcb_conn); } static void xwm_surface_activate(struct wlr_xwm *xwm, struct wlr_xwayland_surface *xsurface) { - if (xwm->focus_surface == xsurface || - (xsurface && xsurface->override_redirect)) { + if (xsurface && xsurface->override_redirect) { return; } - if (xsurface) { - xwm_set_net_active_window(xwm, xsurface->window_id); - } else { - xwm_set_net_active_window(xwm, XCB_WINDOW_NONE); + if (xsurface != xwm->focus_surface && xsurface != xwm->offered_focus) { + xwm_focus_window(xwm, xsurface); } - xwm_set_focus_window(xwm, xsurface); - - xcb_flush(xwm->xcb_conn); + xwm_set_focused_window(xwm, xsurface); + xwm_schedule_flush(xwm); } static void xsurface_set_net_wm_state(struct wlr_xwayland_surface *xsurface) { @@ -424,7 +492,7 @@ static void xsurface_set_net_wm_state(struct wlr_xwayland_surface *xsurface) { return; } - uint32_t property[6]; + uint32_t property[13]; size_t i = 0; if (xsurface->modal) { property[i++] = xwm->atoms[NET_WM_STATE_MODAL]; @@ -441,6 +509,27 @@ static void xsurface_set_net_wm_state(struct wlr_xwayland_surface *xsurface) { if (xsurface->minimized) { property[i++] = xwm->atoms[NET_WM_STATE_HIDDEN]; } + if (xsurface->sticky) { + property[i++] = xwm->atoms[NET_WM_STATE_STICKY]; + } + if (xsurface->shaded) { + property[i++] = xwm->atoms[NET_WM_STATE_SHADED]; + } + if (xsurface->skip_taskbar) { + property[i++] = xwm->atoms[NET_WM_STATE_SKIP_TASKBAR]; + } + if (xsurface->skip_pager) { + property[i++] = xwm->atoms[NET_WM_STATE_SKIP_PAGER]; + } + if (xsurface->above) { + property[i++] = xwm->atoms[NET_WM_STATE_ABOVE]; + } + if (xsurface->below) { + property[i++] = xwm->atoms[NET_WM_STATE_BELOW]; + } + if (xsurface->demands_attention) { + property[i++] = xwm->atoms[NET_WM_STATE_DEMANDS_ATTENTION]; + } if (xsurface == xwm->focus_surface) { property[i++] = xwm->atoms[NET_WM_STATE_FOCUSED]; } @@ -485,9 +574,48 @@ static void xwayland_surface_destroy(struct wlr_xwayland_surface *xsurface) { wl_signal_emit_mutable(&xsurface->events.destroy, NULL); + assert(wl_list_empty(&xsurface->events.destroy.listener_list)); + assert(wl_list_empty(&xsurface->events.request_configure.listener_list)); + assert(wl_list_empty(&xsurface->events.request_move.listener_list)); + assert(wl_list_empty(&xsurface->events.request_resize.listener_list)); + assert(wl_list_empty(&xsurface->events.request_minimize.listener_list)); + assert(wl_list_empty(&xsurface->events.request_maximize.listener_list)); + assert(wl_list_empty(&xsurface->events.request_fullscreen.listener_list)); + assert(wl_list_empty(&xsurface->events.request_activate.listener_list)); + assert(wl_list_empty(&xsurface->events.request_close.listener_list)); + assert(wl_list_empty(&xsurface->events.request_sticky.listener_list)); + assert(wl_list_empty(&xsurface->events.request_shaded.listener_list)); + assert(wl_list_empty(&xsurface->events.request_skip_taskbar.listener_list)); + assert(wl_list_empty(&xsurface->events.request_skip_pager.listener_list)); + assert(wl_list_empty(&xsurface->events.request_above.listener_list)); + assert(wl_list_empty(&xsurface->events.request_below.listener_list)); + assert(wl_list_empty(&xsurface->events.request_demands_attention.listener_list)); + assert(wl_list_empty(&xsurface->events.associate.listener_list)); + assert(wl_list_empty(&xsurface->events.dissociate.listener_list)); + assert(wl_list_empty(&xsurface->events.set_class.listener_list)); + assert(wl_list_empty(&xsurface->events.set_role.listener_list)); + assert(wl_list_empty(&xsurface->events.set_title.listener_list)); + assert(wl_list_empty(&xsurface->events.set_parent.listener_list)); + assert(wl_list_empty(&xsurface->events.set_startup_id.listener_list)); + assert(wl_list_empty(&xsurface->events.set_window_type.listener_list)); + assert(wl_list_empty(&xsurface->events.set_hints.listener_list)); + assert(wl_list_empty(&xsurface->events.set_decorations.listener_list)); + assert(wl_list_empty(&xsurface->events.set_strut_partial.listener_list)); + assert(wl_list_empty(&xsurface->events.set_override_redirect.listener_list)); + assert(wl_list_empty(&xsurface->events.set_geometry.listener_list)); + assert(wl_list_empty(&xsurface->events.set_opacity.listener_list)); + assert(wl_list_empty(&xsurface->events.set_icon.listener_list)); + assert(wl_list_empty(&xsurface->events.focus_in.listener_list)); + assert(wl_list_empty(&xsurface->events.grab_focus.listener_list)); + assert(wl_list_empty(&xsurface->events.map_request.listener_list)); + assert(wl_list_empty(&xsurface->events.ping_timeout.listener_list)); + if (xsurface == xsurface->xwm->focus_surface) { xwm_surface_activate(xsurface->xwm, NULL); } + if (xsurface == xsurface->xwm->offered_focus) { + xsurface->xwm->offered_focus = NULL; + } wl_list_remove(&xsurface->link); wl_list_remove(&xsurface->parent_link); @@ -503,7 +631,8 @@ static void xwayland_surface_destroy(struct wlr_xwayland_surface *xsurface) { wl_event_source_remove(xsurface->ping_timer); - free(xsurface->title); + free(xsurface->wm_name); + free(xsurface->net_wm_name); free(xsurface->class); free(xsurface->instance); free(xsurface->role); @@ -518,8 +647,9 @@ static void xwayland_surface_destroy(struct wlr_xwayland_surface *xsurface) { static void read_surface_class(struct wlr_xwm *xwm, struct wlr_xwayland_surface *surface, xcb_get_property_reply_t *reply) { - if (reply->type != XCB_ATOM_STRING && - reply->type != xwm->atoms[UTF8_STRING]) { + if (reply->type != XCB_ATOM_STRING && reply->type != xwm->atoms[UTF8_STRING] && + reply->type != XCB_ATOM_NONE) { + wlr_log(WLR_DEBUG, "Invalid WM_CLASS property type"); return; } @@ -548,8 +678,9 @@ static void read_surface_class(struct wlr_xwm *xwm, static void read_surface_startup_id(struct wlr_xwm *xwm, struct wlr_xwayland_surface *xsurface, xcb_get_property_reply_t *reply) { - if (reply->type != XCB_ATOM_STRING && - reply->type != xwm->atoms[UTF8_STRING]) { + if (reply->type != XCB_ATOM_STRING && reply->type != xwm->atoms[UTF8_STRING] && + reply->type != XCB_ATOM_NONE) { + wlr_log(WLR_DEBUG, "Invalid NET_STARTUP_ID property type"); return; } @@ -568,11 +699,32 @@ static void read_surface_startup_id(struct wlr_xwm *xwm, wl_signal_emit_mutable(&xsurface->events.set_startup_id, NULL); } +static void read_surface_opacity(struct wlr_xwm *xwm, + struct wlr_xwayland_surface *xsurface, + xcb_get_property_reply_t *reply) { + if (reply->type == XCB_ATOM_NONE) { + xsurface->opacity = 1.0; + wl_signal_emit_mutable(&xsurface->events.set_opacity, NULL); + return; + } + + if (reply->type != XCB_ATOM_CARDINAL || reply->format != 32 || + xcb_get_property_value_length(reply) != sizeof(uint32_t)) { + wlr_log(WLR_DEBUG, "Invalid NET_WINDOW_OPACITY property type"); + return; + } + + uint32_t *val = xcb_get_property_value(reply); + xsurface->opacity = (double)*val / UINT32_MAX; + wl_signal_emit_mutable(&xsurface->events.set_opacity, NULL); +} + static void read_surface_role(struct wlr_xwm *xwm, struct wlr_xwayland_surface *xsurface, xcb_get_property_reply_t *reply) { - if (reply->type != XCB_ATOM_STRING && - reply->type != xwm->atoms[UTF8_STRING]) { + if (reply->type != XCB_ATOM_STRING && reply->type != xwm->atoms[UTF8_STRING] && + reply->type != XCB_ATOM_NONE) { + wlr_log(WLR_DEBUG, "Invalid WM_WINDOW_ROLE property type"); return; } @@ -590,28 +742,39 @@ static void read_surface_role(struct wlr_xwm *xwm, } static void read_surface_title(struct wlr_xwm *xwm, - struct wlr_xwayland_surface *xsurface, + struct wlr_xwayland_surface *xsurface, xcb_atom_t property, xcb_get_property_reply_t *reply) { - if (reply->type != XCB_ATOM_STRING && - reply->type != xwm->atoms[UTF8_STRING]) { - return; - } - - bool is_utf8 = reply->type == xwm->atoms[UTF8_STRING]; - if (!is_utf8 && xsurface->has_utf8_title) { + if (reply->type != XCB_ATOM_STRING && reply->type != xwm->atoms[UTF8_STRING] && + reply->type != XCB_ATOM_NONE) { + wlr_log(WLR_DEBUG, "Invalid WM_NAME/NET_WM_NAME property type"); return; } size_t len = xcb_get_property_value_length(reply); - char *title = xcb_get_property_value(reply); - - free(xsurface->title); + const char *title_buffer = xcb_get_property_value(reply); + char *title = NULL; if (len > 0) { - xsurface->title = strndup(title, len); + title = strndup(title_buffer, len); + } + + if (property == XCB_ATOM_WM_NAME) { + free(xsurface->wm_name); + xsurface->wm_name = title; + } else if (property == xwm->atoms[NET_WM_NAME]) { + free(xsurface->net_wm_name); + xsurface->net_wm_name = title; + } else { + abort(); // unreachable + } + + // Prefer _NET_WM_NAME over WM_NAME if available + if (xsurface->net_wm_name != NULL) { + xsurface->title = xsurface->net_wm_name; + } else if (xsurface->wm_name != NULL) { + xsurface->title = xsurface->wm_name; } else { xsurface->title = NULL; } - xsurface->has_utf8_title = is_utf8; wl_signal_emit_mutable(&xsurface->events.set_title, NULL); } @@ -632,13 +795,14 @@ static bool has_parent(struct wlr_xwayland_surface *parent, static void read_surface_parent(struct wlr_xwm *xwm, struct wlr_xwayland_surface *xsurface, xcb_get_property_reply_t *reply) { - struct wlr_xwayland_surface *found_parent = NULL; - if (reply->type != XCB_ATOM_WINDOW) { + if (reply->type != XCB_ATOM_WINDOW && reply->type != XCB_ATOM_NONE) { + wlr_log(WLR_DEBUG, "Invalid WM_TRANSIENT_FOR property type"); return; } + struct wlr_xwayland_surface *found_parent = NULL; xcb_window_t *xid = xcb_get_property_value(reply); - if (xid != NULL) { + if (reply->type != XCB_ATOM_NONE && xid != NULL) { found_parent = lookup_surface(xwm, *xid); if (!has_parent(found_parent, xsurface)) { xsurface->parent = found_parent; @@ -650,7 +814,6 @@ static void read_surface_parent(struct wlr_xwm *xwm, xsurface->parent = NULL; } - wl_list_remove(&xsurface->parent_link); if (xsurface->parent != NULL) { wl_list_insert(&xsurface->parent->children, &xsurface->parent_link); @@ -664,7 +827,8 @@ static void read_surface_parent(struct wlr_xwm *xwm, static void read_surface_window_type(struct wlr_xwm *xwm, struct wlr_xwayland_surface *xsurface, xcb_get_property_reply_t *reply) { - if (reply->type != XCB_ATOM_ATOM) { + if (reply->type != XCB_ATOM_ATOM && reply->type != XCB_ATOM_NONE) { + wlr_log(WLR_DEBUG, "Invalid NET_WM_WINDOW_TYPE property type"); return; } @@ -673,11 +837,15 @@ static void read_surface_window_type(struct wlr_xwm *xwm, size_t atoms_size = sizeof(xcb_atom_t) * atoms_len; free(xsurface->window_type); - xsurface->window_type = malloc(atoms_size); - if (xsurface->window_type == NULL) { - return; + if (atoms_len > 0) { + xsurface->window_type = malloc(atoms_size); + if (xsurface->window_type == NULL) { + return; + } + memcpy(xsurface->window_type, atoms, atoms_size); + } else { + xsurface->window_type = NULL; } - memcpy(xsurface->window_type, atoms, atoms_size); xsurface->window_type_len = atoms_len; wl_signal_emit_mutable(&xsurface->events.set_window_type, NULL); @@ -686,7 +854,8 @@ static void read_surface_window_type(struct wlr_xwm *xwm, static void read_surface_protocols(struct wlr_xwm *xwm, struct wlr_xwayland_surface *xsurface, xcb_get_property_reply_t *reply) { - if (reply->type != XCB_ATOM_ATOM) { + if (reply->type != XCB_ATOM_ATOM && reply->type != XCB_ATOM_NONE) { + wlr_log(WLR_DEBUG, "Invalid WM_PROTOCOLS property type"); return; } @@ -695,11 +864,15 @@ static void read_surface_protocols(struct wlr_xwm *xwm, size_t atoms_size = sizeof(xcb_atom_t) * atoms_len; free(xsurface->protocols); - xsurface->protocols = malloc(atoms_size); - if (xsurface->protocols == NULL) { - return; + if (atoms_len > 0) { + xsurface->protocols = malloc(atoms_size); + if (xsurface->protocols == NULL) { + return; + } + memcpy(xsurface->protocols, atoms, atoms_size); + } else { + xsurface->protocols = NULL; } - memcpy(xsurface->protocols, atoms, atoms_size); xsurface->protocols_len = atoms_len; } @@ -708,21 +881,27 @@ static void read_surface_hints(struct wlr_xwm *xwm, xcb_get_property_reply_t *reply) { // According to the docs, reply->type == xwm->atoms[WM_HINTS] // In practice, reply->type == XCB_ATOM_ATOM - if (reply->value_len == 0) { + if (reply->type != xwm->atoms[WM_HINTS] && reply->type != XCB_ATOM_ATOM && + reply->type != XCB_ATOM_NONE) { + wlr_log(WLR_DEBUG, "Invalid WM_HINTS property type"); return; } free(xsurface->hints); - xsurface->hints = calloc(1, sizeof(*xsurface->hints)); - if (xsurface->hints == NULL) { - return; - } - xcb_icccm_get_wm_hints_from_reply(xsurface->hints, reply); + if (reply->value_len > 0) { + xsurface->hints = calloc(1, sizeof(*xsurface->hints)); + if (xsurface->hints == NULL) { + return; + } + xcb_icccm_get_wm_hints_from_reply(xsurface->hints, reply); - if (!(xsurface->hints->flags & XCB_ICCCM_WM_HINT_INPUT)) { - // The client didn't specify whether it wants input. - // Assume it does. - xsurface->hints->input = true; + if (!(xsurface->hints->flags & XCB_ICCCM_WM_HINT_INPUT)) { + // The client didn't specify whether it wants input. + // Assume it does. + xsurface->hints->input = true; + } + } else { + xsurface->hints = NULL; } wl_signal_emit_mutable(&xsurface->events.set_hints, NULL); @@ -731,11 +910,18 @@ static void read_surface_hints(struct wlr_xwm *xwm, static void read_surface_normal_hints(struct wlr_xwm *xwm, struct wlr_xwayland_surface *xsurface, xcb_get_property_reply_t *reply) { - if (reply->type != xwm->atoms[WM_SIZE_HINTS] || reply->value_len == 0) { + if (reply->type != xwm->atoms[WM_SIZE_HINTS] && reply->type != XCB_ATOM_NONE) { + wlr_log(WLR_DEBUG, "Invalid WM_NORMAL_HINTS property type"); return; } free(xsurface->size_hints); + xsurface->size_hints = NULL; + + if (reply->value_len == 0) { + return; + } + xsurface->size_hints = calloc(1, sizeof(*xsurface->size_hints)); if (xsurface->size_hints == NULL) { return; @@ -777,7 +963,14 @@ static void read_surface_normal_hints(struct wlr_xwm *xwm, static void read_surface_motif_hints(struct wlr_xwm *xwm, struct wlr_xwayland_surface *xsurface, xcb_get_property_reply_t *reply) { + if (reply->value_len == 0) { + xsurface->decorations = 0; + wl_signal_emit_mutable(&xsurface->events.set_decorations, NULL); + return; + } + if (reply->value_len < 5) { + wlr_log(WLR_DEBUG, "Invalid MOTIF_WM_HINTS property type"); return; } @@ -802,13 +995,21 @@ static void read_surface_motif_hints(struct wlr_xwm *xwm, static void read_surface_strut_partial(struct wlr_xwm *xwm, struct wlr_xwayland_surface *xsurface, xcb_get_property_reply_t *reply) { - if (reply->type != XCB_ATOM_CARDINAL || reply->format != 32 || - xcb_get_property_value_length(reply) != - sizeof(xcb_ewmh_wm_strut_partial_t)) { + free(xsurface->strut_partial); + xsurface->strut_partial = NULL; + + if (reply->type == XCB_ATOM_NONE) { + wl_signal_emit_mutable(&xsurface->events.set_strut_partial, NULL); + return; + } + + if (reply->type != XCB_ATOM_CARDINAL || reply->format != 32 || + xcb_get_property_value_length(reply) != + sizeof(xcb_ewmh_wm_strut_partial_t)) { + wlr_log(WLR_DEBUG, "Invalid NET_WM_STRUT_PARTIAL property type"); return; } - free(xsurface->strut_partial); xsurface->strut_partial = calloc(1, sizeof(*xsurface->strut_partial)); if (xsurface->strut_partial == NULL) { return; @@ -833,6 +1034,20 @@ static void read_surface_net_wm_state(struct wlr_xwm *xwm, xsurface->maximized_horz = true; } else if (atom[i] == xwm->atoms[NET_WM_STATE_HIDDEN]) { xsurface->minimized = true; + } else if (atom[i] == xwm->atoms[NET_WM_STATE_STICKY]) { + xsurface->sticky = true; + } else if (atom[i] == xwm->atoms[NET_WM_STATE_SHADED]) { + xsurface->shaded = true; + } else if (atom[i] == xwm->atoms[NET_WM_STATE_SKIP_TASKBAR]) { + xsurface->skip_taskbar = true; + } else if (atom[i] == xwm->atoms[NET_WM_STATE_SKIP_PAGER]) { + xsurface->skip_pager = true; + } else if (atom[i] == xwm->atoms[NET_WM_STATE_ABOVE]) { + xsurface->above = true; + } else if (atom[i] == xwm->atoms[NET_WM_STATE_BELOW]) { + xsurface->below = true; + } else if (atom[i] == xwm->atoms[NET_WM_STATE_DEMANDS_ATTENTION]) { + xsurface->demands_attention = true; } } } @@ -859,13 +1074,15 @@ static void read_surface_property(struct wlr_xwm *xwm, read_surface_class(xwm, xsurface, reply); } else if (property == XCB_ATOM_WM_NAME || property == xwm->atoms[NET_WM_NAME]) { - read_surface_title(xwm, xsurface, reply); + read_surface_title(xwm, xsurface, property, reply); } else if (property == XCB_ATOM_WM_TRANSIENT_FOR) { read_surface_parent(xwm, xsurface, reply); } else if (property == xwm->atoms[NET_WM_PID]) { // intentionally ignored } else if (property == xwm->atoms[NET_WM_WINDOW_TYPE]) { read_surface_window_type(xwm, xsurface, reply); + } else if (property == xwm->atoms[NET_WM_ICON]) { + wl_signal_emit_mutable(&xsurface->events.set_icon, NULL); } else if (property == xwm->atoms[WM_PROTOCOLS]) { read_surface_protocols(xwm, xsurface, reply); } else if (property == xwm->atoms[NET_WM_STATE]) { @@ -882,6 +1099,8 @@ static void read_surface_property(struct wlr_xwm *xwm, read_surface_role(xwm, xsurface, reply); } else if (property == xwm->atoms[NET_STARTUP_ID]) { read_surface_startup_id(xwm, xsurface, reply); + } else if (property == xwm->atoms[NET_WM_WINDOW_OPACITY]) { + read_surface_opacity(xwm, xsurface, reply); } else if (wlr_log_get_verbosity() >= WLR_DEBUG) { char *prop_name = xwm_get_atom_name(xwm, property); wlr_log(WLR_DEBUG, "unhandled X11 property %" PRIu32 " (%s) for window %" PRIu32, @@ -917,6 +1136,38 @@ static const struct wlr_addon_interface surface_addon_impl = { .destroy = xwayland_surface_handle_addon_destroy, }; +bool wlr_xwayland_surface_fetch_icon( + const struct wlr_xwayland_surface *xsurface, + xcb_ewmh_get_wm_icon_reply_t *icon_reply) { + struct wlr_xwm *xwm = xsurface->xwm; + + xcb_get_property_cookie_t cookie = xcb_get_property(xwm->xcb_conn, 0, + xsurface->window_id, xwm->atoms[NET_WM_ICON], XCB_ATOM_CARDINAL, + 0, UINT32_MAX); + xcb_get_property_reply_t *reply = + xcb_get_property_reply(xwm->xcb_conn, cookie, NULL); + if (!reply) { + return false; + } + + if (!xcb_ewmh_get_wm_icon_from_reply(icon_reply, reply)) { + free(reply); + return false; + } + + return true; +} + +static xcb_get_property_cookie_t get_property(struct wlr_xwm *xwm, + xcb_window_t window_id, xcb_atom_t atom) { + uint32_t len = 2048; + if (atom == xwm->atoms[NET_WM_ICON]) { + /* Compositors need to fetch icon data wlr_xwayland_surface_fetch_icon() */ + len = 0; + } + return xcb_get_property(xwm->xcb_conn, 0, window_id, atom, XCB_ATOM_ANY, 0, len); +} + static void xwayland_surface_associate(struct wlr_xwm *xwm, struct wlr_xwayland_surface *xsurface, struct wlr_surface *surface) { assert(xsurface->surface == NULL); @@ -951,12 +1202,12 @@ static void xwayland_surface_associate(struct wlr_xwm *xwm, xwm->atoms[NET_WM_STRUT_PARTIAL], xwm->atoms[NET_WM_WINDOW_TYPE], xwm->atoms[NET_WM_NAME], + xwm->atoms[NET_WM_ICON], }; xcb_get_property_cookie_t cookies[sizeof(props) / sizeof(props[0])] = {0}; for (size_t i = 0; i < sizeof(props) / sizeof(props[0]); i++) { - cookies[i] = xcb_get_property(xwm->xcb_conn, 0, xsurface->window_id, - props[i], XCB_ATOM_ANY, 0, 2048); + cookies[i] = get_property(xwm, xsurface->window_id, props[i]); } for (size_t i = 0; i < sizeof(props) / sizeof(props[0]); i++) { @@ -1134,7 +1385,7 @@ void wlr_xwayland_surface_restack(struct wlr_xwayland_surface *xsurface, wl_list_insert(node, &xsurface->stack_link); xwm_set_net_client_list_stacking(xwm); - xcb_flush(xwm->xcb_conn); + xwm_schedule_flush(xwm); } static void xwm_handle_map_request(struct wlr_xwm *xwm, @@ -1184,8 +1435,7 @@ static void xwm_handle_property_notify(struct wlr_xwm *xwm, return; } - xcb_get_property_cookie_t cookie = - xcb_get_property(xwm->xcb_conn, 0, xsurface->window_id, ev->atom, XCB_ATOM_ANY, 0, 2048); + xcb_get_property_cookie_t cookie = get_property(xwm, xsurface->window_id, ev->atom); xcb_get_property_reply_t *reply = xcb_get_property_reply(xwm->xcb_conn, cookie, NULL); if (reply == NULL) { @@ -1344,11 +1594,6 @@ static bool update_state(int action, bool *state) { return changed; } -static bool xsurface_is_maximized( - struct wlr_xwayland_surface *xsurface) { - return xsurface->maximized_horz && xsurface->maximized_vert; -} - static void xwm_handle_net_wm_state_message(struct wlr_xwm *xwm, xcb_client_message_event_t *client_message) { struct wlr_xwayland_surface *xsurface = @@ -1361,8 +1606,16 @@ static void xwm_handle_net_wm_state_message(struct wlr_xwm *xwm, } bool fullscreen = xsurface->fullscreen; - bool maximized = xsurface_is_maximized(xsurface); + bool maximized_vert = xsurface->maximized_vert; + bool maximized_horz = xsurface->maximized_horz; bool minimized = xsurface->minimized; + bool sticky = xsurface->sticky; + bool shaded = xsurface->shaded; + bool skip_taskbar = xsurface->skip_taskbar; + bool skip_pager = xsurface->skip_pager; + bool above = xsurface->above; + bool below = xsurface->below; + bool demands_attention = xsurface->demands_attention; uint32_t action = client_message->data.data32[0]; for (size_t i = 0; i < 2; ++i) { @@ -1379,6 +1632,20 @@ static void xwm_handle_net_wm_state_message(struct wlr_xwm *xwm, changed = update_state(action, &xsurface->maximized_horz); } else if (property == xwm->atoms[NET_WM_STATE_HIDDEN]) { changed = update_state(action, &xsurface->minimized); + } else if (property == xwm->atoms[NET_WM_STATE_STICKY]) { + changed = update_state(action, &xsurface->sticky); + } else if (property == xwm->atoms[NET_WM_STATE_SHADED]) { + changed = update_state(action, &xsurface->shaded); + } else if (property == xwm->atoms[NET_WM_STATE_SKIP_TASKBAR]) { + changed = update_state(action, &xsurface->skip_taskbar); + } else if (property == xwm->atoms[NET_WM_STATE_SKIP_PAGER]) { + changed = update_state(action, &xsurface->skip_pager); + } else if (property == xwm->atoms[NET_WM_STATE_ABOVE]) { + changed = update_state(action, &xsurface->above); + } else if (property == xwm->atoms[NET_WM_STATE_BELOW]) { + changed = update_state(action, &xsurface->below); + } else if (property == xwm->atoms[NET_WM_STATE_DEMANDS_ATTENTION]) { + changed = update_state(action, &xsurface->demands_attention); } else if (property != XCB_ATOM_NONE && wlr_log_get_verbosity() >= WLR_DEBUG) { char *prop_name = xwm_get_atom_name(xwm, property); wlr_log(WLR_DEBUG, "Unhandled NET_WM_STATE property change " @@ -1397,7 +1664,8 @@ static void xwm_handle_net_wm_state_message(struct wlr_xwm *xwm, wl_signal_emit_mutable(&xsurface->events.request_fullscreen, NULL); } - if (maximized != xsurface_is_maximized(xsurface)) { + if (maximized_vert != xsurface->maximized_vert + || maximized_horz != xsurface->maximized_horz) { wl_signal_emit_mutable(&xsurface->events.request_maximize, NULL); } @@ -1408,6 +1676,34 @@ static void xwm_handle_net_wm_state_message(struct wlr_xwm *xwm, }; wl_signal_emit_mutable(&xsurface->events.request_minimize, &minimize_event); } + + if (sticky != xsurface->sticky) { + wl_signal_emit_mutable(&xsurface->events.request_sticky, NULL); + } + + if (shaded != xsurface->shaded) { + wl_signal_emit_mutable(&xsurface->events.request_shaded, NULL); + } + + if (skip_taskbar != xsurface->skip_taskbar) { + wl_signal_emit_mutable(&xsurface->events.request_skip_taskbar, NULL); + } + + if (skip_pager != xsurface->skip_pager) { + wl_signal_emit_mutable(&xsurface->events.request_skip_pager, NULL); + } + + if (above != xsurface->above) { + wl_signal_emit_mutable(&xsurface->events.request_above, NULL); + } + + if (below != xsurface->below) { + wl_signal_emit_mutable(&xsurface->events.request_below, NULL); + } + + if (demands_attention != xsurface->demands_attention) { + wl_signal_emit_mutable(&xsurface->events.request_demands_attention, NULL); + } } static void xwm_handle_wm_protocols_message(struct wlr_xwm *xwm, @@ -1445,6 +1741,15 @@ static void xwm_handle_net_active_window_message(struct wlr_xwm *xwm, wl_signal_emit_mutable(&surface->events.request_activate, NULL); } +static void xwm_handle_net_close_window_message(struct wlr_xwm *xwm, + xcb_client_message_event_t *ev) { + struct wlr_xwayland_surface *surface = lookup_surface(xwm, ev->window); + if (surface == NULL) { + return; + } + wl_signal_emit_mutable(&surface->events.request_close, NULL); +} + static void pending_startup_id_destroy(struct pending_startup_id *pending) { wl_list_remove(&pending->link); free(pending->msg); @@ -1552,6 +1857,8 @@ static void xwm_handle_client_message(struct wlr_xwm *xwm, xwm_handle_wm_protocols_message(xwm, ev); } else if (ev->type == xwm->atoms[NET_ACTIVE_WINDOW]) { xwm_handle_net_active_window_message(xwm, ev); + } else if (ev->type == xwm->atoms[NET_CLOSE_WINDOW]) { + xwm_handle_net_close_window_message(xwm, ev); } else if (ev->type == xwm->atoms[NET_STARTUP_INFO] || ev->type == xwm->atoms[NET_STARTUP_INFO_BEGIN]) { xwm_handle_net_startup_info_message(xwm, ev); @@ -1578,32 +1885,44 @@ static bool validate_focus_serial(uint16_t last_focus_seq, uint16_t event_seq) { static void xwm_handle_focus_in(struct wlr_xwm *xwm, xcb_focus_in_event_t *ev) { - // Do not interfere with grabs - if (ev->mode == XCB_NOTIFY_MODE_GRAB || - ev->mode == XCB_NOTIFY_MODE_UNGRAB) { - return; - } // Ignore pointer focus change events if (ev->detail == XCB_NOTIFY_DETAIL_POINTER) { return; } - // Do not let X clients change the focus behind the compositor's - // back. Reset the focus to the old one if it changed. - // - // Note: Some applications rely on being able to change focus, for ex. Steam: - // https://github.com/swaywm/sway/issues/1865 - // Because of that, we allow changing focus between surfaces belonging to the - // same application. We must be careful to ignore requests that are too old - // though, because otherwise it may lead to race conditions: + // Do not interfere with keyboard grabs, but notify the + // compositor. Note that many legitimate X11 applications use + // keyboard grabs to "steal" focus for e.g. popup menus. + struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->event); + if (ev->mode == XCB_NOTIFY_MODE_GRAB) { + if (xsurface) { + wl_signal_emit_mutable(&xsurface->events.grab_focus, NULL); + } + return; + } + if (ev->mode == XCB_NOTIFY_MODE_UNGRAB) { + /* Do we need an ungrab_focus event? */ + return; + } + + // Ignore any out-of-date FocusIn event (older than the last + // known WM-initiated focus change) to avoid race conditions. // https://github.com/swaywm/wlroots/issues/2324 - struct wlr_xwayland_surface *requested_focus = lookup_surface(xwm, ev->event); - if (xwm->focus_surface && requested_focus && - requested_focus->pid == xwm->focus_surface->pid && - validate_focus_serial(xwm->last_focus_seq, ev->sequence)) { - xwm_set_focus_window(xwm, requested_focus); + if (!validate_focus_serial(xwm->last_focus_seq, ev->sequence)) { + return; + } + + // Allow focus changes between surfaces belonging to the same + // application. Steam for example relies on this: + // https://github.com/swaywm/sway/issues/1865 + if (xsurface && ((xwm->focus_surface && xsurface->pid == xwm->focus_surface->pid) || + (xwm->offered_focus && xsurface->pid == xwm->offered_focus->pid))) { + xwm_set_focused_window(xwm, xsurface); + wl_signal_emit_mutable(&xsurface->events.focus_in, NULL); } else { - xwm_set_focus_window(xwm, xwm->focus_surface); + // Try to prevent clients from changing focus between + // applications, by refocusing the previous surface. + xwm_focus_window(xwm, xwm->focus_surface); } } @@ -1663,21 +1982,15 @@ static void xwm_handle_unhandled_event(struct wlr_xwm *xwm, xcb_generic_event_t #endif } -static int x11_event_handler(int fd, uint32_t mask, void *data) { +static int read_x11_events(struct wlr_xwm *xwm) { int count = 0; + xcb_generic_event_t *event; - struct wlr_xwm *xwm = data; - - if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { - xwm_destroy(xwm); - return 0; - } - while ((event = xcb_poll_for_event(xwm->xcb_conn))) { count++; if (xwm->xwayland->user_event_handler && - xwm->xwayland->user_event_handler(xwm, event)) { + xwm->xwayland->user_event_handler(xwm->xwayland, event)) { free(event); continue; } @@ -1731,8 +2044,30 @@ static int x11_event_handler(int fd, uint32_t mask, void *data) { free(event); } - if (count) { + return count; +} + +static int x11_event_handler(int fd, uint32_t mask, void *data) { + struct wlr_xwm *xwm = data; + + if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { + xwm_destroy(xwm); + return 0; + } + + int count = 0; + if (mask & WL_EVENT_READABLE) { + count = read_x11_events(xwm); + if (count) { + xwm_schedule_flush(xwm); + } + } + + if (mask & WL_EVENT_WRITABLE) { + // xcb_flush() always blocks until it's written all pending requests, + // but it's the only thing we have xcb_flush(xwm->xcb_conn); + wl_event_source_fd_update(xwm->event_source, WL_EVENT_READABLE); } return count; @@ -1756,7 +2091,7 @@ static void handle_compositor_new_surface(struct wl_listener *listener, wl_list_for_each(xsurface, &xwm->unpaired_surfaces, unpaired_link) { if (xsurface->surface_id == surface_id) { xwayland_surface_associate(xwm, xsurface, surface); - xcb_flush(xwm->xcb_conn); + xwm_schedule_flush(xwm); return; } } @@ -1786,6 +2121,16 @@ static void handle_shell_v1_new_surface(struct wl_listener *listener, } } +static void handle_shell_v1_destroy(struct wl_listener *listener, + void *data) { + struct wlr_xwm *xwm = + wl_container_of(listener, xwm, shell_v1_destroy); + wl_list_remove(&xwm->shell_v1_new_surface.link); + wl_list_remove(&xwm->shell_v1_destroy.link); + wl_list_init(&xwm->shell_v1_new_surface.link); + wl_list_init(&xwm->shell_v1_destroy.link); +} + void wlr_xwayland_surface_activate(struct wlr_xwayland_surface *xsurface, bool activated) { struct wlr_xwayland_surface *focused = xsurface->xwm->focus_surface; @@ -1835,7 +2180,7 @@ void wlr_xwayland_surface_configure(struct wlr_xwayland_surface *xsurface, sizeof(configure_notify)); } - xcb_flush(xwm->xcb_conn); + xwm_schedule_flush(xwm); } void wlr_xwayland_surface_close(struct wlr_xwayland_surface *xsurface) { @@ -1856,7 +2201,7 @@ void wlr_xwayland_surface_close(struct wlr_xwayland_surface *xsurface) { xwm_send_wm_message(xsurface, &message_data, XCB_EVENT_MASK_NO_EVENT); } else { xcb_kill_client(xwm->xcb_conn, xsurface->window_id); - xcb_flush(xwm->xcb_conn); + xwm_schedule_flush(xwm); } } @@ -1869,6 +2214,8 @@ void xwm_destroy(struct wlr_xwm *xwm) { xwm_selection_finish(&xwm->primary_selection); xwm_selection_finish(&xwm->dnd_selection); + xwm_seat_unlink_drag_handlers(xwm); + if (xwm->seat) { if (xwm->seat->selection_source && data_source_is_xwayland(xwm->seat->selection_source)) { @@ -1892,6 +2239,9 @@ void xwm_destroy(struct wlr_xwm *xwm) { if (xwm->colormap) { xcb_free_colormap(xwm->xcb_conn, xwm->colormap); } + if (xwm->no_focus_window) { + xcb_destroy_window(xwm->xcb_conn, xwm->no_focus_window); + } if (xwm->window) { xcb_destroy_window(xwm->xcb_conn, xwm->window); } @@ -1913,6 +2263,7 @@ void xwm_destroy(struct wlr_xwm *xwm) { wl_list_remove(&xwm->compositor_new_surface.link); wl_list_remove(&xwm->compositor_destroy.link); wl_list_remove(&xwm->shell_v1_new_surface.link); + wl_list_remove(&xwm->shell_v1_destroy.link); xcb_disconnect(xwm->xcb_conn); struct pending_startup_id *pending, *next; @@ -2048,6 +2399,31 @@ static void xwm_create_wm_window(struct wlr_xwm *xwm) { XCB_CURRENT_TIME); } +static void xwm_create_no_focus_window(struct wlr_xwm *xwm) { + xwm->no_focus_window = xcb_generate_id(xwm->xcb_conn); + + uint32_t values[2] = { + 1, + XCB_EVENT_MASK_KEY_PRESS | + XCB_EVENT_MASK_KEY_RELEASE | + XCB_EVENT_MASK_FOCUS_CHANGE + }; + xcb_create_window(xwm->xcb_conn, + XCB_COPY_FROM_PARENT, + xwm->no_focus_window, + xwm->screen->root, + -100, -100, + 1, 1, + 0, + XCB_WINDOW_CLASS_COPY_FROM_PARENT, + XCB_COPY_FROM_PARENT, + XCB_CW_OVERRIDE_REDIRECT | + XCB_CW_EVENT_MASK, + values); + + xcb_map_window(xwm->xcb_conn, xwm->no_focus_window); +} + // TODO use me to support 32 bit color somehow static void xwm_get_visual_and_colormap(struct wlr_xwm *xwm) { xcb_depth_iterator_t d_iter; @@ -2148,12 +2524,13 @@ void xwm_set_cursor(struct wlr_xwm *xwm, const uint8_t *pixels, uint32_t stride, uint32_t values[] = {xwm->cursor}; xcb_change_window_attributes(xwm->xcb_conn, xwm->screen->root, XCB_CW_CURSOR, values); - xcb_flush(xwm->xcb_conn); + xwm_schedule_flush(xwm); } struct wlr_xwm *xwm_create(struct wlr_xwayland *xwayland, int wm_fd) { struct wlr_xwm *xwm = calloc(1, sizeof(*xwm)); if (xwm == NULL) { + close(wm_fd); return NULL; } @@ -2162,13 +2539,19 @@ struct wlr_xwm *xwm_create(struct wlr_xwayland *xwayland, int wm_fd) { wl_list_init(&xwm->surfaces_in_stack_order); wl_list_init(&xwm->unpaired_surfaces); wl_list_init(&xwm->pending_startup_ids); + wl_list_init(&xwm->seat_drag_source_destroy.link); + wl_list_init(&xwm->drag_focus_destroy.link); + wl_list_init(&xwm->drop_focus_destroy.link); + xwm->ping_timeout = 10000; + // xcb_connect_to_fd takes ownership of the FD regardless of success/failure xwm->xcb_conn = xcb_connect_to_fd(wm_fd, NULL); int rc = xcb_connection_has_error(xwm->xcb_conn); if (rc) { wlr_log(WLR_ERROR, "xcb connect failed: %d", rc); + xcb_disconnect(xwm->xcb_conn); free(xwm); return NULL; } @@ -2212,6 +2595,7 @@ struct wlr_xwm *xwm_create(struct wlr_xwayland *xwayland, int wm_fd) { xcb_atom_t supported[] = { xwm->atoms[NET_WM_STATE], xwm->atoms[NET_ACTIVE_WINDOW], + xwm->atoms[NET_CLOSE_WINDOW], xwm->atoms[NET_WM_MOVERESIZE], xwm->atoms[NET_WM_STATE_FOCUSED], xwm->atoms[NET_WM_STATE_MODAL], @@ -2219,6 +2603,13 @@ struct wlr_xwm *xwm_create(struct wlr_xwayland *xwayland, int wm_fd) { xwm->atoms[NET_WM_STATE_MAXIMIZED_VERT], xwm->atoms[NET_WM_STATE_MAXIMIZED_HORZ], xwm->atoms[NET_WM_STATE_HIDDEN], + xwm->atoms[NET_WM_STATE_STICKY], + xwm->atoms[NET_WM_STATE_SHADED], + xwm->atoms[NET_WM_STATE_SKIP_TASKBAR], + xwm->atoms[NET_WM_STATE_SKIP_PAGER], + xwm->atoms[NET_WM_STATE_ABOVE], + xwm->atoms[NET_WM_STATE_BELOW], + xwm->atoms[NET_WM_STATE_DEMANDS_ATTENTION], xwm->atoms[NET_CLIENT_LIST], xwm->atoms[NET_CLIENT_LIST_STACKING], }; @@ -2231,13 +2622,11 @@ struct wlr_xwm *xwm_create(struct wlr_xwayland *xwayland, int wm_fd) { sizeof(supported)/sizeof(*supported), supported); -#if HAVE_XCB_XFIXES_SET_CLIENT_DISCONNECT_MODE if (xwm->xwayland->server->options.terminate_delay > 0 && xwm->xfixes_major_version >= 6) { xcb_xfixes_set_client_disconnect_mode(xwm->xcb_conn, XCB_XFIXES_CLIENT_DISCONNECT_FLAGS_TERMINATE); } -#endif xcb_flush(xwm->xcb_conn); @@ -2257,8 +2646,12 @@ struct wlr_xwm *xwm_create(struct wlr_xwayland *xwayland, int wm_fd) { xwm->shell_v1_new_surface.notify = handle_shell_v1_new_surface; wl_signal_add(&xwayland->shell_v1->events.new_surface, &xwm->shell_v1_new_surface); + xwm->shell_v1_destroy.notify = handle_shell_v1_destroy; + wl_signal_add(&xwayland->shell_v1->events.destroy, + &xwm->shell_v1_destroy); xwm_create_wm_window(xwm); + xwm_create_no_focus_window(xwm); xcb_flush(xwm->xcb_conn); @@ -2270,7 +2663,7 @@ void wlr_xwayland_surface_set_withdrawn(struct wlr_xwayland_surface *surface, surface->withdrawn = withdrawn; xsurface_set_wm_state(surface); xsurface_set_net_wm_state(surface); - xcb_flush(surface->xwm->xcb_conn); + xwm_schedule_flush(surface->xwm); } void wlr_xwayland_surface_set_minimized(struct wlr_xwayland_surface *surface, @@ -2278,22 +2671,67 @@ void wlr_xwayland_surface_set_minimized(struct wlr_xwayland_surface *surface, surface->minimized = minimized; xsurface_set_wm_state(surface); xsurface_set_net_wm_state(surface); - xcb_flush(surface->xwm->xcb_conn); + xwm_schedule_flush(surface->xwm); } void wlr_xwayland_surface_set_maximized(struct wlr_xwayland_surface *surface, - bool maximized) { - surface->maximized_horz = maximized; - surface->maximized_vert = maximized; + bool maximized_horz, bool maximized_vert) { + surface->maximized_horz = maximized_horz; + surface->maximized_vert = maximized_vert; xsurface_set_net_wm_state(surface); - xcb_flush(surface->xwm->xcb_conn); + xwm_schedule_flush(surface->xwm); } void wlr_xwayland_surface_set_fullscreen(struct wlr_xwayland_surface *surface, bool fullscreen) { surface->fullscreen = fullscreen; xsurface_set_net_wm_state(surface); - xcb_flush(surface->xwm->xcb_conn); + xwm_schedule_flush(surface->xwm); +} + +void wlr_xwayland_surface_set_sticky(struct wlr_xwayland_surface *surface, bool sticky) { + surface->sticky = sticky; + xsurface_set_net_wm_state(surface); + xwm_schedule_flush(surface->xwm); +} + +void wlr_xwayland_surface_set_shaded(struct wlr_xwayland_surface *surface, bool shaded) { + surface->shaded = shaded; + xsurface_set_net_wm_state(surface); + xwm_schedule_flush(surface->xwm); +} + +void wlr_xwayland_surface_set_skip_taskbar(struct wlr_xwayland_surface *surface, + bool skip_taskbar) { + surface->skip_taskbar = skip_taskbar; + xsurface_set_net_wm_state(surface); + xwm_schedule_flush(surface->xwm); +} + +void wlr_xwayland_surface_set_skip_pager(struct wlr_xwayland_surface *surface, + bool skip_pager) { + surface->skip_pager = skip_pager; + xsurface_set_net_wm_state(surface); + xwm_schedule_flush(surface->xwm); +} + +void wlr_xwayland_surface_set_above(struct wlr_xwayland_surface *surface, bool above) { + surface->above = above; + xsurface_set_net_wm_state(surface); + xwm_schedule_flush(surface->xwm); +} + +void wlr_xwayland_surface_set_below(struct wlr_xwayland_surface *surface, bool below) { + surface->below = below; + xsurface_set_net_wm_state(surface); + xwm_schedule_flush(surface->xwm); +} + +void wlr_xwayland_surface_set_demands_attention(struct wlr_xwayland_surface *surface, + bool demands_attention) { + surface->demands_attention = demands_attention; + xsurface_set_net_wm_state(surface); + xwm_schedule_flush(surface->xwm); } bool xwm_atoms_contains(struct wlr_xwm *xwm, xcb_atom_t *atoms, @@ -2322,7 +2760,35 @@ void wlr_xwayland_surface_ping(struct wlr_xwayland_surface *surface) { surface->pinging = true; } -bool wlr_xwayland_or_surface_wants_focus( +bool wlr_xwayland_surface_has_window_type( + const struct wlr_xwayland_surface *xsurface, + enum wlr_xwayland_net_wm_window_type window_type) { + static const enum atom_name atom_names[] = { + [WLR_XWAYLAND_NET_WM_WINDOW_TYPE_DESKTOP] = NET_WM_WINDOW_TYPE_DESKTOP, + [WLR_XWAYLAND_NET_WM_WINDOW_TYPE_DOCK] = NET_WM_WINDOW_TYPE_DOCK, + [WLR_XWAYLAND_NET_WM_WINDOW_TYPE_TOOLBAR] = NET_WM_WINDOW_TYPE_TOOLBAR, + [WLR_XWAYLAND_NET_WM_WINDOW_TYPE_MENU] = NET_WM_WINDOW_TYPE_MENU, + [WLR_XWAYLAND_NET_WM_WINDOW_TYPE_UTILITY] = NET_WM_WINDOW_TYPE_UTILITY, + [WLR_XWAYLAND_NET_WM_WINDOW_TYPE_SPLASH] = NET_WM_WINDOW_TYPE_SPLASH, + [WLR_XWAYLAND_NET_WM_WINDOW_TYPE_DIALOG] = NET_WM_WINDOW_TYPE_DIALOG, + [WLR_XWAYLAND_NET_WM_WINDOW_TYPE_DROPDOWN_MENU] = NET_WM_WINDOW_TYPE_DROPDOWN_MENU, + [WLR_XWAYLAND_NET_WM_WINDOW_TYPE_POPUP_MENU] = NET_WM_WINDOW_TYPE_POPUP_MENU, + [WLR_XWAYLAND_NET_WM_WINDOW_TYPE_TOOLTIP] = NET_WM_WINDOW_TYPE_TOOLTIP, + [WLR_XWAYLAND_NET_WM_WINDOW_TYPE_NOTIFICATION] = NET_WM_WINDOW_TYPE_NOTIFICATION, + [WLR_XWAYLAND_NET_WM_WINDOW_TYPE_COMBO] = NET_WM_WINDOW_TYPE_COMBO, + [WLR_XWAYLAND_NET_WM_WINDOW_TYPE_DND] = NET_WM_WINDOW_TYPE_DND, + [WLR_XWAYLAND_NET_WM_WINDOW_TYPE_NORMAL] = NET_WM_WINDOW_TYPE_NORMAL, + }; + + if (window_type >= 0 && window_type < sizeof(atom_names) / sizeof(atom_names[0])) { + return xwm_atoms_contains(xsurface->xwm, xsurface->window_type, + xsurface->window_type_len, atom_names[window_type]); + } + + return false; +} + +bool wlr_xwayland_surface_override_redirect_wants_focus( const struct wlr_xwayland_surface *xsurface) { static const enum atom_name needles[] = { NET_WM_WINDOW_TYPE_COMBO, @@ -2347,8 +2813,8 @@ bool wlr_xwayland_or_surface_wants_focus( return true; } -enum wlr_xwayland_icccm_input_model wlr_xwayland_icccm_input_model( - const struct wlr_xwayland_surface *xsurface) { +enum wlr_xwayland_icccm_input_model wlr_xwayland_surface_icccm_input_model( + const struct wlr_xwayland_surface *xsurface) { bool take_focus = xwm_atoms_contains(xsurface->xwm, xsurface->protocols, xsurface->protocols_len, WM_TAKE_FOCUS); @@ -2391,3 +2857,7 @@ xcb_connection_t *wlr_xwayland_get_xwm_connection( struct wlr_xwayland *wlr_xwayland) { return wlr_xwayland->xwm ? wlr_xwayland->xwm->xcb_conn : NULL; } + +void xwm_schedule_flush(struct wlr_xwm *xwm) { + wl_event_source_fd_update(xwm->event_source, WL_EVENT_READABLE | WL_EVENT_WRITABLE); +}