From 2b9b7a0f7eed595e9481b402830ec724a3f9db90 Mon Sep 17 00:00:00 2001 From: Sunderland93 Date: Thu, 15 May 2025 16:59:54 +0400 Subject: [PATCH 1/2] Bump required wayland-protocols --- qb/config.libs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qb/config.libs.sh b/qb/config.libs.sh index c19fe32a9dc8..3553b997d5d7 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -564,7 +564,7 @@ check_header '' XSHM X11/Xlib.h X11/extensions/XShm.h check_val '' XKBCOMMON -lxkbcommon '' xkbcommon 0.3.2 '' false check_val '' WAYLAND '-lwayland-egl -lwayland-client' '' wayland-egl 10.1.0 '' false check_val '' WAYLAND_CURSOR -lwayland-cursor '' wayland-cursor 1.12 '' false -check_pkgconf WAYLAND_PROTOS wayland-protocols 1.32 +check_pkgconf WAYLAND_PROTOS wayland-protocols 1.37 check_pkgconf WAYLAND_SCANNER wayland-scanner '1.15 1.12' if [ "$HAVE_WAYLAND_SCANNER" = yes ] && From b5f2df5081a35db3c73e96ce19f77d3e465a736f Mon Sep 17 00:00:00 2001 From: Aleksey Samoilov Date: Fri, 9 May 2025 10:54:48 +0400 Subject: [PATCH 2/2] [Wayland] Add support for wp_presentation protocol correctly handle clock_id bind to wp_presentation_v1 atm more precise synchronization Ensure that presented() is reached wp_presentation: correctly set clock_type wp_presentation: deduplicate code use only nanoseconds wp_presentation: use clock_nanosleep instead of usleep don't replace wl_callback with presentation feedback Remove presentation feedback more gracefully Fix gap between request feedback and presented --- .gitignore | 2 + Makefile.common | 1 + .../stable/presentation-time/README | 5 + .../presentation-time/presentation-time.xml | 268 ++++++++++++++++++ gfx/common/wayland/generate_wayland_protos.sh | 1 + gfx/common/wayland_common.c | 139 +++++++++ gfx/drivers_context/wayland_ctx.c | 15 +- gfx/drivers_context/wayland_vk_ctx.c | 6 + input/common/wayland_common.c | 6 + input/common/wayland_common.h | 22 ++ 10 files changed, 460 insertions(+), 5 deletions(-) create mode 100644 deps/wayland-protocols/stable/presentation-time/README create mode 100644 deps/wayland-protocols/stable/presentation-time/presentation-time.xml diff --git a/.gitignore b/.gitignore index 1a1c0af878d1..13f12834385e 100644 --- a/.gitignore +++ b/.gitignore @@ -228,6 +228,8 @@ gfx/common/wayland/xdg-shell.c gfx/common/wayland/xdg-shell.h gfx/common/wayland/pointer-constraints-unstable-v1.c gfx/common/wayland/pointer-constraints-unstable-v1.h +gfx/common/wayland/presentation-time.h +gfx/common/wayland/presentation-time.c gfx/common/wayland/relative-pointer-unstable-v1.c gfx/common/wayland/relative-pointer-unstable-v1.h gfx/common/wayland/viewporter.c diff --git a/Makefile.common b/Makefile.common index 01738131e2e8..6cb0d31bead9 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1270,6 +1270,7 @@ ifeq ($(HAVE_WAYLAND), 1) gfx/common/wayland/idle-inhibit-unstable-v1.o \ gfx/common/wayland/xdg-decoration-unstable-v1.o \ gfx/common/wayland/pointer-constraints-unstable-v1.o \ + gfx/common/wayland/presentation-time.o \ gfx/common/wayland/relative-pointer-unstable-v1.o \ gfx/common/wayland/cursor-shape-v1.o \ gfx/common/wayland/tablet-unstable-v2.o \ diff --git a/deps/wayland-protocols/stable/presentation-time/README b/deps/wayland-protocols/stable/presentation-time/README new file mode 100644 index 000000000000..b0ecbe0d329b --- /dev/null +++ b/deps/wayland-protocols/stable/presentation-time/README @@ -0,0 +1,5 @@ +Presentation time protocol + +Maintainers: +Pekka Paalanen (@pq) + diff --git a/deps/wayland-protocols/stable/presentation-time/presentation-time.xml b/deps/wayland-protocols/stable/presentation-time/presentation-time.xml new file mode 100644 index 000000000000..c2431c500367 --- /dev/null +++ b/deps/wayland-protocols/stable/presentation-time/presentation-time.xml @@ -0,0 +1,268 @@ + + + + + + Copyright © 2013-2014 Collabora, Ltd. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + + + + The main feature of this interface is accurate presentation + timing feedback to ensure smooth video playback while maintaining + audio/video synchronization. Some features use the concept of a + presentation clock, which is defined in the + presentation.clock_id event. + + A content update for a wl_surface is submitted by a + wl_surface.commit request. Request 'feedback' associates with + the wl_surface.commit and provides feedback on the content + update, particularly the final realized presentation time. + + + + When the final realized presentation time is available, e.g. + after a framebuffer flip completes, the requested + presentation_feedback.presented events are sent. The final + presentation time can differ from the compositor's predicted + display update time and the update's target time, especially + when the compositor misses its target vertical blanking period. + + + + + These fatal protocol errors may be emitted in response to + illegal presentation requests. + + + + + + + + Informs the server that the client will no longer be using + this protocol object. Existing objects created by this object + are not affected. + + + + + + Request presentation feedback for the current content submission + on the given surface. This creates a new presentation_feedback + object, which will deliver the feedback information once. If + multiple presentation_feedback objects are created for the same + submission, they will all deliver the same information. + + For details on what information is returned, see the + presentation_feedback interface. + + + + + + + + This event tells the client in which clock domain the + compositor interprets the timestamps used by the presentation + extension. This clock is called the presentation clock. + + The compositor sends this event when the client binds to the + presentation interface. The presentation clock does not change + during the lifetime of the client connection. + + The clock identifier is platform dependent. On POSIX platforms, the + identifier value is one of the clockid_t values accepted by + clock_gettime(). clock_gettime() is defined by POSIX.1-2001. + + Timestamps in this clock domain are expressed as tv_sec_hi, + tv_sec_lo, tv_nsec triples, each component being an unsigned + 32-bit value. Whole seconds are in tv_sec which is a 64-bit + value combined from tv_sec_hi and tv_sec_lo, and the + additional fractional part in tv_nsec as nanoseconds. Hence, + for valid timestamps tv_nsec must be in [0, 999999999]. + + Note that clock_id applies only to the presentation clock, + and implies nothing about e.g. the timestamps used in the + Wayland core protocol input events. + + Compositors should prefer a clock which does not jump and is + not slewed e.g. by NTP. The absolute value of the clock is + irrelevant. Precision of one millisecond or better is + recommended. Clients must be able to query the current clock + value directly, not by asking the compositor. + + + + + + + + + A presentation_feedback object returns an indication that a + wl_surface content update has become visible to the user. + One object corresponds to one content update submission + (wl_surface.commit). There are two possible outcomes: the + content update is presented to the user, and a presentation + timestamp delivered; or, the user did not see the content + update because it was superseded or its surface destroyed, + and the content update is discarded. + + Once a presentation_feedback object has delivered a 'presented' + or 'discarded' event it is automatically destroyed. + + + + + As presentation can be synchronized to only one output at a + time, this event tells which output it was. This event is only + sent prior to the presented event. + + As clients may bind to the same global wl_output multiple + times, this event is sent for each bound instance that matches + the synchronized output. If a client has not bound to the + right wl_output global at all, this event is not sent. + + + + + + + These flags provide information about how the presentation of + the related content update was done. The intent is to help + clients assess the reliability of the feedback and the visual + quality with respect to possible tearing and timings. + + + + The presentation was synchronized to the "vertical retrace" by + the display hardware such that tearing does not happen. + Relying on software scheduling is not acceptable for this + flag. If presentation is done by a copy to the active + frontbuffer, then it must guarantee that tearing cannot + happen. + + + + + The display hardware provided measurements that the hardware + driver converted into a presentation timestamp. Sampling a + clock in software is not acceptable for this flag. + + + + + The display hardware signalled that it started using the new + image content. The opposite of this is e.g. a timer being used + to guess when the display hardware has switched to the new + image content. + + + + + The presentation of this update was done zero-copy. This means + the buffer from the client was given to display hardware as + is, without copying it. Compositing with OpenGL counts as + copying, even if textured directly from the client buffer. + Possible zero-copy cases include direct scanout of a + fullscreen surface and a surface on a hardware overlay. + + + + + + + The associated content update was displayed to the user at the + indicated time (tv_sec_hi/lo, tv_nsec). For the interpretation of + the timestamp, see presentation.clock_id event. + + The timestamp corresponds to the time when the content update + turned into light the first time on the surface's main output. + Compositors may approximate this from the framebuffer flip + completion events from the system, and the latency of the + physical display path if known. + + This event is preceded by all related sync_output events + telling which output's refresh cycle the feedback corresponds + to, i.e. the main output for the surface. Compositors are + recommended to choose the output containing the largest part + of the wl_surface, or keeping the output they previously + chose. Having a stable presentation output association helps + clients predict future output refreshes (vblank). + + The 'refresh' argument gives the compositor's prediction of how + many nanoseconds after tv_sec, tv_nsec the very next output + refresh may occur. This is to further aid clients in + predicting future refreshes, i.e., estimating the timestamps + targeting the next few vblanks. If such prediction cannot + usefully be done, the argument is zero. + + For version 2 and later, if the output does not have a constant + refresh rate, explicit video mode switches excluded, then the + refresh argument must be either an appropriate rate picked by the + compositor (e.g. fastest rate), or 0 if no such rate exists. + For version 1, if the output does not have a constant refresh rate, + the refresh argument must be zero. + + The 64-bit value combined from seq_hi and seq_lo is the value + of the output's vertical retrace counter when the content + update was first scanned out to the display. This value must + be compatible with the definition of MSC in + GLX_OML_sync_control specification. Note, that if the display + path has a non-zero latency, the time instant specified by + this counter may differ from the timestamp's. + + If the output does not have a concept of vertical retrace or a + refresh cycle, or the output device is self-refreshing without + a way to query the refresh count, then the arguments seq_hi + and seq_lo must be zero. + + + + + + + + + + + + + The content update was never displayed to the user. + + + + + diff --git a/gfx/common/wayland/generate_wayland_protos.sh b/gfx/common/wayland/generate_wayland_protos.sh index d45de792189f..f89eb97c4d15 100755 --- a/gfx/common/wayland/generate_wayland_protos.sh +++ b/gfx/common/wayland/generate_wayland_protos.sh @@ -63,6 +63,7 @@ generate_source () { generate_source 'stable/viewporter' 'viewporter' generate_source 'stable/xdg-shell' 'xdg-shell' +generate_source 'stable/presentation-time' 'presentation-time' generate_source 'unstable/xdg-decoration' 'xdg-decoration-unstable-v1' generate_source 'unstable/idle-inhibit' 'idle-inhibit-unstable-v1' generate_source 'unstable/pointer-constraints' 'pointer-constraints-unstable-v1' diff --git a/gfx/common/wayland_common.c b/gfx/common/wayland_common.c index 59bba26e824f..41bc7223523b 100644 --- a/gfx/common/wayland_common.c +++ b/gfx/common/wayland_common.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "wayland_common.h" @@ -40,6 +41,10 @@ #include #endif +#ifndef CLOCK_MONOTONIC_RAW +#define CLOCK_MONOTONIC_RAW 4 +#endif + #define DEFAULT_WINDOWED_WIDTH 640 #define DEFAULT_WINDOWED_HEIGHT 480 @@ -287,6 +292,8 @@ void gfx_ctx_wl_destroy_resources_common(gfx_ctx_wayland_data_t *wl) if (wl->viewport) wp_viewport_destroy(wl->viewport); + if (wl->presentation) + wp_presentation_destroy(wl->presentation); if (wl->fractional_scale) wp_fractional_scale_v1_destroy(wl->fractional_scale); if (wl->idle_inhibitor) @@ -376,6 +383,7 @@ void gfx_ctx_wl_destroy_resources_common(gfx_ctx_wayland_data_t *wl) wl->seat = NULL; wl->relative_pointer_manager = NULL; wl->pointer_constraints = NULL; + wl->presentation = NULL; wl->content_type = NULL; wl->content_type_manager = NULL; wl->cursor_shape_manager = NULL; @@ -468,6 +476,124 @@ bool gfx_ctx_wl_get_metrics_common(void *data, return true; } +static void presentation_handle_clock_id(void *data, + struct wp_presentation *presentation, + uint32_t clock_id) +{ + gfx_ctx_wayland_data_t *wl = data; + + if (clock_id == CLOCK_MONOTONIC || clock_id == CLOCK_MONOTONIC_RAW) + { + wl->present_clock = true; + wl->present_clock_id = (clockid_t)clock_id; + } +} + +static void presentation_feedback_sync_output(void *data, + struct wp_presentation_feedback *feedback, + struct wl_output *output) +{ +} + +static void presentation_feedback_remove(gfx_ctx_wayland_data_t *wl, + struct wp_presentation_feedback *feedback) +{ + wp_presentation_feedback_t *fb, *tmp; + wl_list_for_each_safe(fb, tmp, &wl->feedbacks, link) + { + if (fb->feedback == feedback) + { + wl_list_remove(&fb->link); + wp_presentation_feedback_destroy(fb->feedback); + free(fb); + return; + } + } +} + +static void presentation_feedback_presented(void *data, + struct wp_presentation_feedback *feedback, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, + uint32_t refresh, uint32_t seq_hi, uint32_t seq_lo, + uint32_t flags) +{ + gfx_ctx_wayland_data_t *wl = data; + presentation_feedback_remove(wl, feedback); + + uint64_t sec = ((uint64_t)tv_sec_hi << 32) | (uint64_t)tv_sec_lo; + wl->last_ust = sec * 1000000000ULL + (uint64_t)tv_nsec; + wl->last_msc = ((uint64_t)seq_hi << 32) | (uint64_t)seq_lo; + wl->refresh_interval = (int64_t)refresh; + wl->is_presented = true; +} + +static void presentation_feedback_discarded(void *data, + struct wp_presentation_feedback *feedback) +{ + gfx_ctx_wayland_data_t *wl = data; + presentation_feedback_remove(wl, feedback); +} + +const struct wp_presentation_listener presentation_listener = { + presentation_handle_clock_id, +}; + +static const struct wp_presentation_feedback_listener presentation_feedback_listener = { + presentation_feedback_sync_output, + presentation_feedback_presented, + presentation_feedback_discarded, +}; + +void wl_request_presentation_feedback(gfx_ctx_wayland_data_t *wl) +{ + wp_presentation_feedback_t *fb = calloc(1, sizeof(*fb)); + if (!fb) + { + RARCH_ERR("[Wayland] Failed to allocate feedback struct\n"); + return; + } + + fb->feedback = wp_presentation_feedback(wl->presentation, wl->surface); + if (!fb->feedback) + { + RARCH_ERR("[Wayland] Failed to create feedback object\n"); + free(fb); + return; + } + + wl->is_presented = false; + wp_presentation_feedback_add_listener(fb->feedback, + &presentation_feedback_listener, wl); + wl_list_insert(&wl->feedbacks, &fb->link); +} + +void wait_for_next_frame(gfx_ctx_wayland_data_t *wl) +{ + if (!wl->present_clock || wl->refresh_interval <= 0 || !wl->is_presented) + return; + + clockid_t clock_type = (wl->present_clock_id == CLOCK_MONOTONIC || + wl->present_clock_id == CLOCK_MONOTONIC_RAW) + ? wl->present_clock_id + : CLOCK_MONOTONIC; + + uint64_t next_frame_ns = wl->last_ust + wl->refresh_interval; + + struct timespec ts; + ts.tv_sec = next_frame_ns / 1000000000ULL; + ts.tv_nsec = next_frame_ns % 1000000000ULL; + + struct timespec now; + if (clock_gettime(clock_type, &now) == 0) + { + uint64_t now_ns = (uint64_t)now.tv_sec * 1000000000ULL + now.tv_nsec; + if (now_ns >= next_frame_ns) + return; + } + + clock_nanosleep(clock_type, TIMER_ABSTIME, &ts, NULL); +} + static int create_shm_file(off_t size) { int fd, ret; @@ -828,6 +954,19 @@ bool gfx_ctx_wl_init_common( RARCH_LOG("[Wayland] Compositor doesn't support the %s protocol.\n", xdg_toplevel_icon_manager_v1_interface.name); } + if (wl->presentation) + { + wl_list_init(&wl->feedbacks); + wl->last_ust = 0; + wl->last_sbc = 0; + wl->last_msc = 0; + wl->refresh_interval = 0; + } + else + { + RARCH_LOG("[Wayland]: Compositor doesn't support the %s protocol!\n", wp_presentation_interface.name); + } + wl->surface = wl_compositor_create_surface(wl->compositor); if (wl->viewporter) wl->viewport = wp_viewporter_get_viewport(wl->viewporter, wl->surface); diff --git a/gfx/drivers_context/wayland_ctx.c b/gfx/drivers_context/wayland_ctx.c index 4e2fa98c2a33..930d85019da5 100644 --- a/gfx/drivers_context/wayland_ctx.c +++ b/gfx/drivers_context/wayland_ctx.c @@ -503,10 +503,10 @@ static const struct wl_callback_listener wl_surface_frame_listener = { static void gfx_ctx_wl_swap_buffers(void *data) { #ifdef HAVE_EGL - struct wl_callback *cb; - gfx_ctx_wayland_data_t *wl = (gfx_ctx_wayland_data_t*)data; - settings_t *settings = config_get_ptr(); - unsigned max_swapchain_images = settings->uints.video_max_swapchain_images; + gfx_ctx_wayland_data_t *wl = (gfx_ctx_wayland_data_t*)data; + settings_t *settings = config_get_ptr(); + unsigned max_swapchain_images = settings->uints.video_max_swapchain_images; + struct wl_callback *cb = NULL; if (max_swapchain_images <= 2) { @@ -517,7 +517,12 @@ static void gfx_ctx_wl_swap_buffers(void *data) egl_swap_buffers(&wl->egl); - if (max_swapchain_images <= 2) + if (wl->present_clock) + wl_request_presentation_feedback(wl); + + wait_for_next_frame(wl); + + if (cb) { /* Wait for the frame callback we set earlier. */ struct pollfd pollfd = {.fd = wl->input.fd, .events = POLLIN}; diff --git a/gfx/drivers_context/wayland_vk_ctx.c b/gfx/drivers_context/wayland_vk_ctx.c index 544568d70d9b..d76ff2859d39 100644 --- a/gfx/drivers_context/wayland_vk_ctx.c +++ b/gfx/drivers_context/wayland_vk_ctx.c @@ -56,6 +56,7 @@ static void gfx_ctx_wl_destroy_resources(gfx_ctx_wayland_data_t *wl) { if (!wl) return; + vulkan_context_destroy(&wl->vk, wl->surface); gfx_ctx_wl_destroy_resources_common(wl); } @@ -259,6 +260,11 @@ static void gfx_ctx_wl_swap_buffers(void *data) else vulkan_present(&wl->vk, wl->vk.context.current_swapchain_index); } + + if (wl->present_clock) + wl_request_presentation_feedback(wl); + + wait_for_next_frame(wl); vulkan_acquire_next_image(&wl->vk); flush_wayland_fd(&wl->input); } diff --git a/input/common/wayland_common.c b/input/common/wayland_common.c index 202d2f401fb7..ff781fa472c8 100644 --- a/input/common/wayland_common.c +++ b/input/common/wayland_common.c @@ -739,6 +739,12 @@ static void wl_registry_handle_global(void *data, struct wl_registry *reg, else if (string_is_equal(interface, wp_viewporter_interface.name) && found++) wl->viewporter = (struct wp_viewporter*)wl_registry_bind(reg, id, &wp_viewporter_interface, MIN(version, 1)); + else if (string_is_equal(interface, wp_presentation_interface.name) && found++) + { + wl->presentation = (struct wp_presentation*)wl_registry_bind(reg, + id, &wp_presentation_interface, MIN(version, 1)); + wp_presentation_add_listener(wl->presentation, &presentation_listener, wl); + } else if (string_is_equal(interface, wp_fractional_scale_manager_v1_interface.name) && found++) wl->fractional_scale_manager = (struct wp_fractional_scale_manager_v1*) wl_registry_bind(reg, id, &wp_fractional_scale_manager_v1_interface, MIN(version, 1)); diff --git a/input/common/wayland_common.h b/input/common/wayland_common.h index 7f685b61e6d9..6e365f830b50 100644 --- a/input/common/wayland_common.h +++ b/input/common/wayland_common.h @@ -39,6 +39,7 @@ #include "../../gfx/common/wayland/fractional-scale-v1.h" #include "../../gfx/common/wayland/idle-inhibit-unstable-v1.h" #include "../../gfx/common/wayland/pointer-constraints-unstable-v1.h" +#include "../../gfx/common/wayland/presentation-time.h" #include "../../gfx/common/wayland/relative-pointer-unstable-v1.h" #include "../../gfx/common/wayland/single-pixel-buffer-v1.h" #include "../../gfx/common/wayland/viewporter.h" @@ -157,6 +158,7 @@ typedef struct gfx_ctx_wayland_data struct wl_surface *surface; struct xdg_surface *xdg_surface; struct wp_viewport *viewport; + struct wp_presentation *presentation; struct wp_fractional_scale_v1 *fractional_scale; struct xdg_wm_base *xdg_shell; struct xdg_toplevel *xdg_toplevel; @@ -200,6 +202,7 @@ typedef struct gfx_ctx_wayland_data input_ctx_wayland_data_t input; /* ptr alignment */ struct wl_list all_outputs; struct wl_list current_outputs; + struct wl_list feedbacks; struct { @@ -212,7 +215,12 @@ typedef struct gfx_ctx_wayland_data int num_active_touches; int swap_interval; + int64_t last_ust; + int64_t last_sbc; + int64_t last_msc; + int64_t refresh_interval; touch_pos_t active_touch_positions[MAX_TOUCHES]; /* int32_t alignment */ + clockid_t present_clock_id; unsigned width; unsigned height; unsigned buffer_width; @@ -235,8 +243,16 @@ typedef struct gfx_ctx_wayland_data bool activated; bool reported_display_size; bool swap_complete; + bool present_clock; + bool is_presented; } gfx_ctx_wayland_data_t; +typedef struct wp_presentation_feedback +{ + struct wp_presentation_feedback *feedback; + struct wl_list link; +} wp_presentation_feedback_t; + #ifdef HAVE_XKBCOMMON /* FIXME: Move this into a header? */ int init_xkb(int fd, size_t len); @@ -250,6 +266,10 @@ void gfx_ctx_wl_show_mouse(void *data, bool state); void flush_wayland_fd(void *data); +void wl_request_presentation_feedback(gfx_ctx_wayland_data_t *wl); + +void wait_for_next_frame(gfx_ctx_wayland_data_t *wl); + extern const struct wl_keyboard_listener keyboard_listener; extern const struct wl_pointer_listener pointer_listener; @@ -264,6 +284,8 @@ extern const struct wl_seat_listener seat_listener; extern const struct wp_fractional_scale_v1_listener wp_fractional_scale_v1_listener; +extern const struct wp_presentation_listener presentation_listener; + extern const struct wl_surface_listener wl_surface_listener; extern const struct xdg_wm_base_listener xdg_shell_listener;