diff options
30 files changed, 833 insertions, 140 deletions
diff --git a/backend/backend.c b/backend/backend.c index 78b90007..4b186767 100644 --- a/backend/backend.c +++ b/backend/backend.c @@ -15,6 +15,7 @@ #include <wlr/backend/wayland.h> #include <wlr/config.h> #include <wlr/util/log.h> +#include "backend/multi.h" /* WLR_HAS_X11_BACKEND needs to be after wlr/config.h */ #ifdef WLR_HAS_X11_BACKEND @@ -56,6 +57,20 @@ struct wlr_renderer *wlr_backend_get_renderer(struct wlr_backend *backend) { return NULL; } +struct wlr_session *wlr_backend_get_session(struct wlr_backend *backend) { + if (backend->impl->get_session) { + return backend->impl->get_session(backend); + } + return NULL; +} + +clockid_t wlr_backend_get_presentation_clock(struct wlr_backend *backend) { + if (backend->impl->get_presentation_clock) { + return backend->impl->get_presentation_clock(backend); + } + return CLOCK_MONOTONIC; +} + static size_t parse_outputs_env(const char *name) { const char *outputs_str = getenv(name); if (outputs_str == NULL) { @@ -158,10 +173,12 @@ static struct wlr_backend *attempt_backend_by_name(struct wl_display *display, return attempt_headless_backend(display, create_renderer_func); } else if (strcmp(name, "drm") == 0 || strcmp(name, "libinput") == 0) { // DRM and libinput need a session - *session = wlr_session_create(display); if (!*session) { - wlr_log(WLR_ERROR, "failed to start a session"); - return NULL; + *session = wlr_session_create(display); + if (!*session) { + wlr_log(WLR_ERROR, "failed to start a session"); + return NULL; + } } if (strcmp(name, "libinput") == 0) { @@ -178,13 +195,12 @@ static struct wlr_backend *attempt_backend_by_name(struct wl_display *display, struct wlr_backend *wlr_backend_autocreate(struct wl_display *display, wlr_renderer_create_func_t create_renderer_func) { struct wlr_backend *backend = wlr_multi_backend_create(display); + struct wlr_multi_backend *multi = (struct wlr_multi_backend *)backend; if (!backend) { wlr_log(WLR_ERROR, "could not allocate multibackend"); return NULL; } - struct wlr_session *session = NULL; - char *names = getenv("WLR_BACKENDS"); if (names) { names = strdup(names); @@ -197,12 +213,12 @@ struct wlr_backend *wlr_backend_autocreate(struct wl_display *display, char *saveptr; char *name = strtok_r(names, ",", &saveptr); while (name != NULL) { - struct wlr_backend *subbackend = - attempt_backend_by_name(display, backend, &session, name, create_renderer_func); + struct wlr_backend *subbackend = attempt_backend_by_name(display, + backend, &multi->session, name, create_renderer_func); if (subbackend == NULL) { wlr_log(WLR_ERROR, "failed to start backend '%s'", name); wlr_backend_destroy(backend); - wlr_session_destroy(session); + wlr_session_destroy(multi->session); free(names); return NULL; } @@ -210,7 +226,7 @@ struct wlr_backend *wlr_backend_autocreate(struct wl_display *display, if (!wlr_multi_backend_add(backend, subbackend)) { wlr_log(WLR_ERROR, "failed to add backend '%s'", name); wlr_backend_destroy(backend); - wlr_session_destroy(session); + wlr_session_destroy(multi->session); free(names); return NULL; } @@ -245,29 +261,30 @@ struct wlr_backend *wlr_backend_autocreate(struct wl_display *display, #endif // Attempt DRM+libinput - session = wlr_session_create(display); - if (!session) { + multi->session = wlr_session_create(display); + if (!multi->session) { wlr_log(WLR_ERROR, "Failed to start a DRM session"); wlr_backend_destroy(backend); return NULL; } - struct wlr_backend *libinput = wlr_libinput_backend_create(display, session); + struct wlr_backend *libinput = wlr_libinput_backend_create(display, + multi->session); if (!libinput) { wlr_log(WLR_ERROR, "Failed to start libinput backend"); wlr_backend_destroy(backend); - wlr_session_destroy(session); + wlr_session_destroy(multi->session); return NULL; } wlr_multi_backend_add(backend, libinput); - struct wlr_backend *primary_drm = - attempt_drm_backend(display, backend, session, create_renderer_func); + struct wlr_backend *primary_drm = attempt_drm_backend(display, backend, + multi->session, create_renderer_func); if (!primary_drm) { wlr_log(WLR_ERROR, "Failed to open any DRM device"); wlr_backend_destroy(libinput); wlr_backend_destroy(backend); - wlr_session_destroy(session); + wlr_session_destroy(multi->session); return NULL; } diff --git a/backend/drm/backend.c b/backend/drm/backend.c index 76918e7d..a9082077 100644 --- a/backend/drm/backend.c +++ b/backend/drm/backend.c @@ -65,10 +65,16 @@ static struct wlr_renderer *backend_get_renderer( } } +static clockid_t backend_get_presentation_clock(struct wlr_backend *backend) { + struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); + return drm->clock; +} + static struct wlr_backend_impl backend_impl = { .start = backend_start, .destroy = backend_destroy, .get_renderer = backend_get_renderer, + .get_presentation_clock = backend_get_presentation_clock, }; bool wlr_backend_is_drm(struct wlr_backend *b) { @@ -203,8 +209,3 @@ error_fd: free(drm); return NULL; } - -struct wlr_session *wlr_drm_backend_get_session(struct wlr_backend *backend) { - struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); - return drm->session; -} diff --git a/backend/drm/drm.c b/backend/drm/drm.c index 784a0233..00525762 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -1,3 +1,4 @@ +#define _POSIX_C_SOURCE 199309L #include <assert.h> #include <drm_mode.h> #include <EGL/egl.h> @@ -27,8 +28,8 @@ #include "util/signal.h" bool check_drm_features(struct wlr_drm_backend *drm) { + uint64_t cap; if (drm->parent) { - uint64_t cap; if (drmGetCap(drm->fd, DRM_CAP_PRIME, &cap) || !(cap & DRM_PRIME_CAP_IMPORT)) { wlr_log(WLR_ERROR, @@ -51,16 +52,21 @@ bool check_drm_features(struct wlr_drm_backend *drm) { const char *no_atomic = getenv("WLR_DRM_NO_ATOMIC"); if (no_atomic && strcmp(no_atomic, "1") == 0) { - wlr_log(WLR_DEBUG, "WLR_DRM_NO_ATOMIC set, forcing legacy DRM interface"); + wlr_log(WLR_DEBUG, + "WLR_DRM_NO_ATOMIC set, forcing legacy DRM interface"); drm->iface = &legacy_iface; } else if (drmSetClientCap(drm->fd, DRM_CLIENT_CAP_ATOMIC, 1)) { - wlr_log(WLR_DEBUG, "Atomic modesetting unsupported, using legacy DRM interface"); + wlr_log(WLR_DEBUG, + "Atomic modesetting unsupported, using legacy DRM interface"); drm->iface = &legacy_iface; } else { wlr_log(WLR_DEBUG, "Using atomic DRM interface"); drm->iface = &atomic_iface; } + int ret = drmGetCap(drm->fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap); + drm->clock = (ret == 0 && cap == 1) ? CLOCK_MONOTONIC : CLOCK_REALTIME; + return true; } @@ -150,6 +156,11 @@ bool init_drm_resources(struct wlr_drm_backend *drm) { wlr_log(WLR_INFO, "Found %d DRM CRTCs", res->count_crtcs); drm->num_crtcs = res->count_crtcs; + if (drm->num_crtcs == 0) { + drmModeFreeResources(res); + return true; + } + drm->crtcs = calloc(drm->num_crtcs, sizeof(drm->crtcs[0])); if (!drm->crtcs) { wlr_log_errno(WLR_ERROR, "Allocation failed"); @@ -427,9 +438,9 @@ static void realloc_planes(struct wlr_drm_backend *drm, const uint32_t *crtc_in, continue; } - uint32_t possible[drm->num_type_planes[type]]; - uint32_t crtc[drm->num_crtcs]; - uint32_t crtc_res[drm->num_crtcs]; + uint32_t possible[drm->num_type_planes[type] + 1]; + uint32_t crtc[drm->num_crtcs + 1]; + uint32_t crtc_res[drm->num_crtcs + 1]; for (size_t i = 0; i < drm->num_type_planes[type]; ++i) { possible[i] = drm->type_planes[type][i].possible_crtcs; @@ -724,6 +735,43 @@ static bool drm_connector_move_cursor(struct wlr_output *output, return ok; } +static void drm_connector_schedule_frame(struct wlr_output *output) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + if (!drm->session->active) { + return; + } + + // We need to figure out where we are in the vblank cycle + // TODO: try using drmWaitVBlank and fallback to pageflipping + + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc) { + return; + } + struct wlr_drm_plane *plane = crtc->primary; + struct gbm_bo *bo = plane->surf.back; + if (!bo) { + // We haven't swapped buffers yet -- can't do a pageflip + wlr_output_send_frame(output); + return; + } + uint32_t fb_id = get_fb_for_bo(bo); + + if (conn->pageflip_pending) { + wlr_log(WLR_ERROR, "Skipping pageflip on output '%s'", + conn->output.name); + return; + } + + if (!drm->iface->crtc_pageflip(drm, conn, crtc, fb_id, NULL)) { + return; + } + + conn->pageflip_pending = true; + wlr_output_update_enabled(output, true); +} + static void drm_connector_destroy(struct wlr_output *output) { struct wlr_drm_connector *conn = get_drm_connector_from_output(output); drm_connector_cleanup(conn); @@ -745,6 +793,7 @@ static const struct wlr_output_impl output_impl = { .set_gamma = set_drm_connector_gamma, .get_gamma_size = drm_connector_get_gamma_size, .export_dmabuf = drm_connector_export_dmabuf, + .schedule_frame = drm_connector_schedule_frame, }; bool wlr_output_is_drm(struct wlr_output *output) { @@ -1134,7 +1183,7 @@ void scan_drm_connectors(struct wlr_drm_backend *drm) { } } - bool changed_outputs[wl_list_length(&drm->outputs)]; + bool changed_outputs[wl_list_length(&drm->outputs) + 1]; memset(changed_outputs, false, sizeof(changed_outputs)); for (size_t i = 0; i < new_outputs_len; ++i) { struct wlr_drm_connector *conn = new_outputs[i]; @@ -1166,9 +1215,13 @@ void scan_drm_connectors(struct wlr_drm_backend *drm) { attempt_enable_needs_modeset(drm); } +static int mhz_to_nsec(int mhz) { + return 1000000000000LL / mhz; +} + static void page_flip_handler(int fd, unsigned seq, - unsigned tv_sec, unsigned tv_usec, void *user) { - struct wlr_drm_connector *conn = user; + unsigned tv_sec, unsigned tv_usec, void *data) { + struct wlr_drm_connector *conn = data; struct wlr_drm_backend *drm = get_drm_backend_from_backend(conn->output.backend); @@ -1188,6 +1241,19 @@ static void page_flip_handler(int fd, unsigned seq, post_drm_surface(&conn->crtc->primary->mgpu_surf); } + struct timespec present_time = { + .tv_sec = tv_sec, + .tv_nsec = tv_usec * 1000, + }; + struct wlr_output_event_present present_event = { + .when = &present_time, + .seq = seq, + .refresh = mhz_to_nsec(conn->output.refresh), + .flags = WLR_OUTPUT_PRESENT_VSYNC | WLR_OUTPUT_PRESENT_HW_CLOCK | + WLR_OUTPUT_PRESENT_HW_COMPLETION, + }; + wlr_output_send_present(&conn->output, &present_event); + if (drm->session->active) { wlr_output_send_frame(&conn->output); } diff --git a/backend/drm/util.c b/backend/drm/util.c index f9637c33..6b2c0981 100644 --- a/backend/drm/util.c +++ b/backend/drm/util.c @@ -32,21 +32,30 @@ static const char *get_manufacturer(uint16_t id) { case ID('A', 'A', 'A'): return "Avolites Ltd"; case ID('A', 'C', 'I'): return "Ancor Communications Inc"; case ID('A', 'C', 'R'): return "Acer Technologies"; + case ID('A', 'D', 'A'): return "Addi-Data GmbH"; case ID('A', 'P', 'P'): return "Apple Computer Inc"; + case ID('A', 'S', 'K'): return "Ask A/S"; + case ID('A', 'V', 'T'): return "Avtek (Electronics) Pty Ltd"; case ID('B', 'N', 'O'): return "Bang & Olufsen"; case ID('C', 'M', 'N'): return "Chimei Innolux Corporation"; case ID('C', 'M', 'O'): return "Chi Mei Optoelectronics corp."; case ID('C', 'R', 'O'): return "Extraordinary Technologies PTY Limited"; case ID('D', 'E', 'L'): return "Dell Inc."; + case ID('D', 'G', 'C'): return "Data General Corporation"; case ID('D', 'O', 'N'): return "DENON, Ltd."; case ID('E', 'N', 'C'): return "Eizo Nanao Corporation"; case ID('E', 'P', 'H'): return "Epiphan Systems Inc."; + case ID('E', 'X', 'P'): return "Data Export Corporation"; + case ID('F', 'N', 'I'): return "Funai Electric Co., Ltd."; case ID('F', 'U', 'S'): return "Fujitsu Siemens Computers GmbH"; case ID('G', 'S', 'M'): return "Goldstar Company Ltd"; case ID('H', 'I', 'Q'): return "Kaohsiung Opto Electronics Americas, Inc."; case ID('H', 'S', 'D'): return "HannStar Display Corp"; + case ID('H', 'T', 'C'): return "Hitachi Ltd"; case ID('H', 'W', 'P'): return "Hewlett Packard"; case ID('I', 'N', 'T'): return "Interphase Corporation"; + case ID('I', 'N', 'X'): return "Communications Supply Corporation (A division of WESCO)"; + case ID('I', 'T', 'E'): return "Integrated Tech Express Inc"; case ID('I', 'V', 'M'): return "Iiyama North America"; case ID('L', 'E', 'N'): return "Lenovo Group Limited"; case ID('M', 'A', 'X'): return "Rogen Tech Distribution Inc"; @@ -55,6 +64,7 @@ static const char *get_manufacturer(uint16_t id) { case ID('M', 'T', 'C'): return "Mars-Tech Corporation"; case ID('M', 'T', 'X'): return "Matrox"; case ID('N', 'E', 'C'): return "NEC Corporation"; + case ID('N', 'E', 'X'): return "Nexgen Mediatech Inc."; case ID('O', 'N', 'K'): return "ONKYO Corporation"; case ID('O', 'R', 'N'): return "ORION ELECTRIC CO., LTD."; case ID('O', 'T', 'M'): return "Optoma Corporation"; @@ -63,15 +73,24 @@ static const char *get_manufacturer(uint16_t id) { case ID('P', 'I', 'O'): return "Pioneer Electronic Corporation"; case ID('P', 'N', 'R'): return "Planar Systems, Inc."; case ID('Q', 'D', 'S'): return "Quanta Display Inc."; + case ID('R', 'A', 'T'): return "Rent-A-Tech"; + case ID('R', 'E', 'N'): return "Renesas Technology Corp."; case ID('S', 'A', 'M'): return "Samsung Electric Company"; + case ID('S', 'A', 'N'): return "Sanyo Electric Co., Ltd."; case ID('S', 'E', 'C'): return "Seiko Epson Corporation"; case ID('S', 'H', 'P'): return "Sharp Corporation"; case ID('S', 'I', 'I'): return "Silicon Image, Inc."; case ID('S', 'N', 'Y'): return "Sony"; + case ID('S', 'T', 'D'): return "STD Computer Inc"; + case ID('S', 'V', 'S'): return "SVSI"; + case ID('S', 'Y', 'N'): return "Synaptics Inc"; + case ID('T', 'C', 'L'): return "Technical Concepts Ltd"; case ID('T', 'O', 'P'): return "Orion Communications Co., Ltd."; case ID('T', 'S', 'B'): return "Toshiba America Info Systems Inc"; case ID('T', 'S', 'T'): return "Transtream Inc"; case ID('U', 'N', 'K'): return "Unknown"; + case ID('V', 'E', 'S'): return "Vestel Elektronik Sanayi ve Ticaret A. S."; + case ID('V', 'I', 'T'): return "Visitech AS"; case ID('V', 'I', 'Z'): return "VIZIO, Inc"; case ID('V', 'S', 'C'): return "ViewSonic Corporation"; case ID('Y', 'M', 'H'): return "Yamaha Corporation"; diff --git a/backend/headless/output.c b/backend/headless/output.c index 143900d4..3cb35dce 100644 --- a/backend/headless/output.c +++ b/backend/headless/output.c @@ -67,7 +67,9 @@ static bool output_make_current(struct wlr_output *wlr_output, int *buffer_age) static bool output_swap_buffers(struct wlr_output *wlr_output, pixman_region32_t *damage) { - return true; // No-op + // Nothing needs to be done for pbuffers + wlr_output_send_present(wlr_output, NULL); + return true; } static void output_destroy(struct wlr_output *wlr_output) { diff --git a/backend/multi/backend.c b/backend/multi/backend.c index 3707fe34..cefaa361 100644 --- a/backend/multi/backend.c +++ b/backend/multi/backend.c @@ -1,7 +1,8 @@ +#define _POSIX_C_SOURCE 199309L #include <assert.h> #include <stdbool.h> #include <stdlib.h> -#include <wlr/backend/drm.h> +#include <time.h> #include <wlr/backend/interface.h> #include <wlr/backend/session.h> #include <wlr/util/log.h> @@ -72,10 +73,32 @@ static struct wlr_renderer *multi_backend_get_renderer( return NULL; } +static struct wlr_session *multi_backend_get_session( + struct wlr_backend *_backend) { + struct wlr_multi_backend *backend = multi_backend_from_backend(_backend); + return backend->session; +} + +static clockid_t multi_backend_get_presentation_clock( + struct wlr_backend *backend) { + struct wlr_multi_backend *multi = multi_backend_from_backend(backend); + + struct subbackend_state *sub; + wl_list_for_each(sub, &multi->backends, link) { + if (sub->backend->impl->get_presentation_clock) { + return wlr_backend_get_presentation_clock(sub->backend); + } + } + + return CLOCK_MONOTONIC; +} + struct wlr_backend_impl backend_impl = { .start = multi_backend_start, .destroy = multi_backend_destroy, .get_renderer = multi_backend_get_renderer, + .get_session = multi_backend_get_session, + .get_presentation_clock = multi_backend_get_presentation_clock, }; static void handle_display_destroy(struct wl_listener *listener, void *data) { @@ -191,17 +214,6 @@ void wlr_multi_backend_remove(struct wlr_backend *_multi, } } -struct wlr_session *wlr_multi_get_session(struct wlr_backend *_backend) { - struct wlr_multi_backend *backend = multi_backend_from_backend(_backend); - struct subbackend_state *sub; - wl_list_for_each(sub, &backend->backends, link) { - if (wlr_backend_is_drm(sub->backend)) { - return wlr_drm_backend_get_session(sub->backend); - } - } - return NULL; -} - bool wlr_multi_is_empty(struct wlr_backend *_backend) { assert(wlr_backend_is_multi(_backend)); struct wlr_multi_backend *backend = (struct wlr_multi_backend *)_backend; diff --git a/backend/session/logind.c b/backend/session/logind.c index 39143a2f..b44112e6 100644 --- a/backend/session/logind.c +++ b/backend/session/logind.c @@ -35,6 +35,11 @@ struct logind_session { char *id; char *path; + + // specifies whether a drm device was taken + // if so, the session will be (de)activated with the drm fd, + // otherwise with the dbus PropertiesChanged on "active" signal + bool has_drm; }; static struct logind_session *logind_session_from_session( @@ -57,6 +62,10 @@ static int logind_take_device(struct wlr_session *base, const char *path) { return -1; } + if (major(st.st_rdev) == DRM_MAJOR) { + session->has_drm = true; + } + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", session->path, "org.freedesktop.login1.Session", "TakeDevice", &error, &msg, "uu", major(st.st_rdev), minor(st.st_rdev)); @@ -262,6 +271,7 @@ static int pause_device(sd_bus_message *msg, void *userdata, sd_bus_error *ret_e } if (major == DRM_MAJOR) { + assert(session->has_drm); session->base.active = false; wlr_signal_emit_safe(&session->base.session_signal, session); } @@ -307,6 +317,115 @@ error: return 0; } +static int properties_changed(sd_bus_message *msg, void *userdata, + sd_bus_error *ret_error) { + struct logind_session *session = userdata; + int ret = 0; + + // if we have a drm fd we don't depend on this + if (session->has_drm) { + return 0; + } + + // PropertiesChanged arg 1: interface + const char *interface; + ret = sd_bus_message_read_basic(msg, 's', &interface); // skip path + if (ret < 0) { + goto error; + } + + if (strcmp(interface, "org.freedesktop.login1.Session") != 0) { + // not interesting for us; ignore + wlr_log(WLR_DEBUG, "ignoring PropertiesChanged from %s", interface); + return 0; + } + + // PropertiesChanged arg 2: changed properties with values + ret = sd_bus_message_enter_container(msg, 'a', "{sv}"); + if (ret < 0) { + goto error; + } + + const char *s; + while ((ret = sd_bus_message_enter_container(msg, 'e', "sv")) > 0) { + ret = sd_bus_message_read_basic(msg, 's', &s); + if (ret < 0) { + goto error; + } + + if (strcmp(s, "Active") == 0) { + int ret; + ret = sd_bus_message_enter_container(msg, 'v', "b"); + if (ret < 0) { + goto error; + } + + bool active; + ret = sd_bus_message_read_basic(msg, 'b', &active); + if (ret < 0) { + goto error; + } + + if (session->base.active != active) { + session->base.active = active; + wlr_signal_emit_safe(&session->base.session_signal, session); + } + return 0; + } else { + sd_bus_message_skip(msg, "{sv}"); + } + + ret = sd_bus_message_exit_container(msg); + if (ret < 0) { + goto error; + } + } + + if (ret < 0) { + goto error; + } + + ret = sd_bus_message_exit_container(msg); + if (ret < 0) { + goto error; + } + + // PropertiesChanged arg 3: changed properties without values + sd_bus_message_enter_container(msg, 'a', "s"); + while ((ret = sd_bus_message_read_basic(msg, 's', &s)) > 0) { + if (strcmp(s, "Active") == 0) { + sd_bus_error error = SD_BUS_ERROR_NULL; + bool active; + ret = sd_bus_get_property_trivial(session->bus, + "org.freedesktop.login1", session->path, + "org.freedesktop.login1.Session", "Active", &error, + 'b', &active); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get 'Active' property: '%s' (%s)", + error.message, strerror(ret)); + return 0; + } + + if (session->base.active != active) { + session->base.active = active; + wlr_signal_emit_safe(&session->base.session_signal, session); + } + return 0; + } + } + + if (ret < 0) { + goto error; + } + + return 0; + +error: + wlr_log(WLR_ERROR, "Failed to parse D-Bus PropertiesChanged %s", + strerror(-ret)); + return 0; +} + static bool add_signal_matches(struct logind_session *session) { int ret; @@ -338,6 +457,14 @@ static bool add_signal_matches(struct logind_session *session) { return false; } + ret = sd_bus_match_signal(session->bus, NULL, "org.freedesktop.login1", + session->path, "org.freedesktop.DBus.Properties", "PropertiesChanged", + properties_changed, session); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); + return false; + } + return true; } diff --git a/backend/wayland/output.c b/backend/wayland/output.c index 4eb99a3b..bc4067ab 100644 --- a/backend/wayland/output.c +++ b/backend/wayland/output.c @@ -66,8 +66,14 @@ static bool output_swap_buffers(struct wlr_output *wlr_output, output->frame_callback = wl_surface_frame(output->surface); wl_callback_add_listener(output->frame_callback, &frame_listener, output); - return wlr_egl_swap_buffers(&output->backend->egl, output->egl_surface, - damage); + if (!wlr_egl_swap_buffers(&output->backend->egl, + output->egl_surface, damage)) { + return false; + } + + // TODO: if available, use the presentation-time protocol + wlr_output_send_present(wlr_output, NULL); + return true; } static void output_transform(struct wlr_output *wlr_output, @@ -186,11 +192,24 @@ void update_wl_output_cursor(struct wlr_wl_output *output) { } } -bool output_move_cursor(struct wlr_output *_output, int x, int y) { +static bool output_move_cursor(struct wlr_output *_output, int x, int y) { // TODO: only return true if x == current x and y == current y return true; } +static void output_schedule_frame(struct wlr_output *wlr_output) { + struct wlr_wl_output *output = get_wl_output_from_output(wlr_output); + + if (output->frame_callback != NULL) { + wlr_log(WLR_ERROR, "Skipping frame scheduling"); + return; + } + + output->frame_callback = wl_surface_frame(output->surface); + wl_callback_add_listener(output->frame_callback, &frame_listener, output); + wl_surface_commit(output->surface); +} + static const struct wlr_output_impl output_impl = { .set_custom_mode = output_set_custom_mode, .transform = output_transform, @@ -199,6 +218,7 @@ static const struct wlr_output_impl output_impl = { .swap_buffers = output_swap_buffers, .set_cursor = output_set_cursor, .move_cursor = output_move_cursor, + .schedule_frame = output_schedule_frame, }; bool wlr_output_is_wl(struct wlr_output *wlr_output) { diff --git a/backend/x11/output.c b/backend/x11/output.c index e0210b1a..1ac12a8d 100644 --- a/backend/x11/output.c +++ b/backend/x11/output.c @@ -102,7 +102,12 @@ static bool output_swap_buffers(struct wlr_output *wlr_output, struct wlr_x11_output *output = (struct wlr_x11_output *)wlr_output; struct wlr_x11_backend *x11 = output->x11; - return wlr_egl_swap_buffers(&x11->egl, output->surf, damage); + if (!wlr_egl_swap_buffers(&x11->egl, output->surf, damage)) { + return false; + } + + wlr_output_send_present(wlr_output, NULL); + return true; } static const struct wlr_output_impl output_impl = { diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h index 3c728808..de5212d3 100644 --- a/include/backend/drm/drm.h +++ b/include/backend/drm/drm.h @@ -6,6 +6,7 @@ #include <stdbool.h> #include <stddef.h> #include <stdint.h> +#include <time.h> #include <wayland-server.h> #include <wayland-util.h> #include <wlr/backend/drm.h> @@ -67,6 +68,7 @@ struct wlr_drm_backend { struct wlr_drm_backend *parent; const struct wlr_drm_interface *iface; + clockid_t clock; int fd; diff --git a/include/backend/multi.h b/include/backend/multi.h index c57c48f3..2f5f1bd4 100644 --- a/include/backend/multi.h +++ b/include/backend/multi.h @@ -8,6 +8,7 @@ struct wlr_multi_backend { struct wlr_backend backend; + struct wlr_session *session; struct wl_list backends; diff --git a/include/rootston/desktop.h b/include/rootston/desktop.h index 90851a17..8cee170f 100644 --- a/include/rootston/desktop.h +++ b/include/rootston/desktop.h @@ -13,6 +13,7 @@ #include <wlr/types/wlr_list.h> #include <wlr/types/wlr_output_layout.h> #include <wlr/types/wlr_output.h> +#include <wlr/types/wlr_presentation_time.h> #include <wlr/types/wlr_primary_selection.h> #include <wlr/types/wlr_screencopy_v1.h> #include <wlr/types/wlr_screenshooter.h> @@ -57,6 +58,7 @@ struct roots_desktop { struct wlr_screencopy_manager_v1 *screencopy; struct wlr_tablet_manager_v2 *tablet_v2; struct wlr_pointer_constraints_v1 *pointer_constraints; + struct wlr_presentation *presentation; struct wl_listener new_output; struct wl_listener layout_change; diff --git a/include/rootston/output.h b/include/rootston/output.h index 69bc5126..3f07ab6f 100644 --- a/include/rootston/output.h +++ b/include/rootston/output.h @@ -24,6 +24,7 @@ struct roots_output { struct wl_listener destroy; struct wl_listener mode; struct wl_listener transform; + struct wl_listener present; struct wl_listener damage_frame; struct wl_listener damage_destroy; }; diff --git a/include/wlr/backend.h b/include/wlr/backend.h index 39d072e2..54f2b5e8 100644 --- a/include/wlr/backend.h +++ b/include/wlr/backend.h @@ -57,5 +57,14 @@ void wlr_backend_destroy(struct wlr_backend *backend); * Obtains the wlr_renderer reference this backend is using. */ struct wlr_renderer *wlr_backend_get_renderer(struct wlr_backend *backend); +/** + * Obtains the wlr_session reference from this backend if there is any. + * Might return NULL for backends that don't use a session. + */ +struct wlr_session *wlr_backend_get_session(struct wlr_backend *backend); +/** + * Returns the clock used by the backend for presentation feedback. + */ +clockid_t wlr_backend_get_presentation_clock(struct wlr_backend *backend); #endif diff --git a/include/wlr/backend/drm.h b/include/wlr/backend/drm.h index 5d47647d..3724adfb 100644 --- a/include/wlr/backend/drm.h +++ b/include/wlr/backend/drm.h @@ -34,6 +34,4 @@ bool wlr_output_is_drm(struct wlr_output *output); typedef struct _drmModeModeInfo drmModeModeInfo; bool wlr_drm_connector_add_mode(struct wlr_output *output, const drmModeModeInfo *mode); -struct wlr_session *wlr_drm_backend_get_session(struct wlr_backend *backend); - #endif diff --git a/include/wlr/backend/interface.h b/include/wlr/backend/interface.h index f3dee69b..4a6a5cbb 100644 --- a/include/wlr/backend/interface.h +++ b/include/wlr/backend/interface.h @@ -10,6 +10,7 @@ #define WLR_BACKEND_INTERFACE_H #include <stdbool.h> +#include <time.h> #include <wlr/backend.h> #include <wlr/render/egl.h> @@ -17,6 +18,8 @@ struct wlr_backend_impl { bool (*start)(struct wlr_backend *backend); void (*destroy)(struct wlr_backend *backend); struct wlr_renderer *(*get_renderer)(struct wlr_backend *backend); + struct wlr_session *(*get_session)(struct wlr_backend *backend); + clockid_t (*get_presentation_clock)(struct wlr_backend *backend); }; /** diff --git a/include/wlr/backend/multi.h b/include/wlr/backend/multi.h index ef908d26..0687f4b6 100644 --- a/include/wlr/backend/multi.h +++ b/include/wlr/backend/multi.h @@ -28,7 +28,6 @@ void wlr_multi_backend_remove(struct wlr_backend *multi, struct wlr_backend *backend); bool wlr_backend_is_multi(struct wlr_backend *backend); -struct wlr_session *wlr_multi_get_session(struct wlr_backend *base); bool wlr_multi_is_empty(struct wlr_backend *backend); void wlr_multi_for_each_backend(struct wlr_backend *backend, diff --git a/include/wlr/interfaces/wlr_output.h b/include/wlr/interfaces/wlr_output.h index bfb3bc9d..da37cba1 100644 --- a/include/wlr/interfaces/wlr_output.h +++ b/include/wlr/interfaces/wlr_output.h @@ -33,6 +33,7 @@ struct wlr_output_impl { size_t (*get_gamma_size)(struct wlr_output *output); bool (*export_dmabuf)(struct wlr_output *output, struct wlr_dmabuf_attributes *attribs); + void (*schedule_frame)(struct wlr_output *output); }; void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend, @@ -45,5 +46,7 @@ void wlr_output_update_enabled(struct wlr_output *output, bool enabled); void wlr_output_update_needs_swap(struct wlr_output *output); void wlr_output_damage_whole(struct wlr_output *output); void wlr_output_send_frame(struct wlr_output *output); +void wlr_output_send_present(struct wlr_output *output, + struct wlr_output_event_present *event); #endif diff --git a/include/wlr/types/meson.build b/include/wlr/types/meson.build index 6a8955c3..7a02c3da 100644 --- a/include/wlr/types/meson.build +++ b/include/wlr/types/meson.build @@ -5,10 +5,10 @@ install_headers( 'wlr_cursor.h', 'wlr_data_device.h', 'wlr_export_dmabuf_v1.h', - 'wlr_gamma_control.h', 'wlr_gamma_control_v1.h', - 'wlr_idle.h', + 'wlr_gamma_control.h', 'wlr_idle_inhibit_v1.h', + 'wlr_idle.h', 'wlr_input_device.h', 'wlr_input_inhibitor.h', 'wlr_keyboard.h', @@ -16,10 +16,11 @@ install_headers( 'wlr_linux_dmabuf_v1.h', 'wlr_list.h', 'wlr_matrix.h', - 'wlr_output.h', 'wlr_output_damage.h', 'wlr_output_layout.h', + 'wlr_output.h', 'wlr_pointer.h', + 'wlr_presentation_time.h', 'wlr_primary_selection.h', 'wlr_region.h', 'wlr_screencopy_v1.h', @@ -36,7 +37,7 @@ install_headers( 'wlr_xcursor_manager.h', 'wlr_xdg_decoration_v1.h', 'wlr_xdg_output_v1.h', - 'wlr_xdg_shell.h', 'wlr_xdg_shell_v6.h', + 'wlr_xdg_shell.h', subdir: 'wlr/types', ) diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index 9ede7ab7..9ccfbbb5 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -65,7 +65,7 @@ struct wlr_output { struct wl_list resources; char name[24]; - char make[48]; + char make[56]; char model[16]; char serial[16]; int32_t phys_width, phys_height; // mm @@ -88,9 +88,15 @@ struct wlr_output { float transform_matrix[9]; struct { + // Request to render a frame struct wl_signal frame; + // Emitted when buffers need to be swapped (because software cursors or + // fullscreen damage or because of backend-specific logic) struct wl_signal needs_swap; + // Emitted right before buffer swap struct wl_signal swap_buffers; // wlr_output_event_swap_buffers + // Emitted right after the buffer has been presented to the user + struct wl_signal present; // wlr_output_event_present struct wl_signal enable; struct wl_signal mode; struct wl_signal scale; @@ -123,6 +129,32 @@ struct wlr_output_event_swap_buffers { pixman_region32_t *damage; }; +enum wlr_output_present_flag { + // The presentation was synchronized to the "vertical retrace" by the + // display hardware such that tearing does not happen. + WLR_OUTPUT_PRESENT_VSYNC = 0x1, + // The display hardware provided measurements that the hardware driver + // converted into a presentation timestamp. + WLR_OUTPUT_PRESENT_HW_CLOCK = 0x2, + // The display hardware signalled that it started using the new image + // content. + WLR_OUTPUT_PRESENT_HW_COMPLETION = 0x4, + // The presentation of this update was done zero-copy. + WLR_OUTPUT_PRESENT_ZERO_COPY = 0x8, +}; + +struct wlr_output_event_present { + struct wlr_output *output; + // Time when the content update turned into light the first time. + struct timespec *when; + // Vertical retrace counter. Zero if unavailable. + unsigned seq; + // Prediction of how many nanoseconds after `when` the very next output + // refresh may occur. Zero if unknown. + int refresh; // nsec + uint32_t flags; // enum wlr_output_present_flag +}; + struct wlr_surface; /** diff --git a/include/wlr/types/wlr_presentation_time.h b/include/wlr/types/wlr_presentation_time.h new file mode 100644 index 00000000..9f9f0e87 --- /dev/null +++ b/include/wlr/types/wlr_presentation_time.h @@ -0,0 +1,59 @@ +/* + * 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_PRESENTATION_TIME_H +#define WLR_TYPES_WLR_PRESENTATION_TIME_H + +#include <stdbool.h> +#include <stddef.h> +#include <time.h> +#include <wayland-server.h> + +struct wlr_presentation { + struct wl_global *global; + struct wl_list resources; // wl_resource_get_link + struct wl_list feedbacks; // wlr_presentation_feedback::link + clockid_t clock; + + struct { + struct wl_signal destroy; + } events; + + struct wl_listener display_destroy; +}; + +struct wlr_presentation_feedback { + struct wl_resource *resource; + struct wlr_presentation *presentation; + struct wlr_surface *surface; + bool committed; + struct wl_list link; // wlr_presentation::feedbacks + + struct wl_listener surface_commit; + struct wl_listener surface_destroy; +}; + +struct wlr_presentation_event { + struct wlr_output *output; + uint64_t tv_sec; + uint32_t tv_nsec; + uint32_t refresh; + uint64_t seq; + uint32_t flags; // wp_presentation_feedback_kind +}; + +struct wlr_backend; + +struct wlr_presentation *wlr_presentation_create(struct wl_display *display, + struct wlr_backend *backend); +void wlr_presentation_destroy(struct wlr_presentation *presentation); +void wlr_presentation_send_surface_presented( + struct wlr_presentation *presentation, struct wlr_surface *surface, + struct wlr_presentation_event *event); + +#endif diff --git a/meson.build b/meson.build index 9bd71191..d1098d53 100644 --- a/meson.build +++ b/meson.build @@ -55,7 +55,7 @@ xkbcommon = dependency('xkbcommon') udev = dependency('libudev') pixman = dependency('pixman-1') libcap = dependency('libcap', required: get_option('libcap')) -logind = dependency('lib' + get_option('logind-provider'), required: get_option('logind')) +logind = dependency('lib' + get_option('logind-provider'), required: get_option('logind'), version: '>=237') math = cc.find_library('m', required: false) wlr_parts = [] diff --git a/protocol/meson.build b/protocol/meson.build index e03b88e8..82131ff6 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -11,6 +11,7 @@ else endif protocols = [ + [wl_protocol_dir, 'stable/presentation-time/presentation-time.xml'], [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], [wl_protocol_dir, 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml'], diff --git a/rootston/config.c b/rootston/config.c index 5c168679..119a9e2c 100644 --- a/rootston/config.c +++ b/rootston/config.c @@ -577,7 +577,7 @@ void roots_config_destroy(struct roots_config *config) { struct roots_output_config *roots_config_get_output(struct roots_config *config, struct wlr_output *output) { - char name[83]; + char name[88]; snprintf(name, sizeof(name), "%s %s %s", output->make, output->model, output->serial); diff --git a/rootston/desktop.c b/rootston/desktop.c index c180c839..7f749050 100644 --- a/rootston/desktop.c +++ b/rootston/desktop.c @@ -969,6 +969,9 @@ struct roots_desktop *desktop_create(struct roots_server *server, wl_signal_add(&desktop->pointer_constraints->events.new_constraint, &desktop->pointer_constraint); + desktop->presentation = + wlr_presentation_create(server->wl_display, server->backend); + return desktop; } diff --git a/rootston/keyboard.c b/rootston/keyboard.c index 6ba0bd6d..66c373cf 100644 --- a/rootston/keyboard.c +++ b/rootston/keyboard.c @@ -5,7 +5,6 @@ #include <sys/wait.h> #include <unistd.h> #include <wayland-server.h> -#include <wlr/backend/multi.h> #include <wlr/backend/session.h> #include <wlr/types/wlr_input_device.h> #include <wlr/types/wlr_pointer_constraints_v1.h> @@ -201,14 +200,13 @@ static bool keyboard_execute_compositor_binding(struct roots_keyboard *keyboard, if (keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { struct roots_server *server = keyboard->input->server; - if (wlr_backend_is_multi(server->backend)) { - struct wlr_session *session = - wlr_multi_get_session(server->backend); - if (session) { - unsigned vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1; - wlr_session_change_vt(session, vt); - } + + struct wlr_session *session = wlr_backend_get_session(server->backend); + if (session) { + unsigned vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1; + wlr_session_change_vt(session, vt); } + return true; } diff --git a/rootston/output.c b/rootston/output.c index e85612fa..9d376f8e 100644 --- a/rootston/output.c +++ b/rootston/output.c @@ -5,9 +5,10 @@ #include <time.h> #include <wlr/backend/drm.h> #include <wlr/config.h> -#include <wlr/types/wlr_matrix.h> #include <wlr/types/wlr_compositor.h> +#include <wlr/types/wlr_matrix.h> #include <wlr/types/wlr_output_layout.h> +#include <wlr/types/wlr_presentation_time.h> #include <wlr/types/wlr_wl_shell.h> #include <wlr/types/wlr_xdg_shell_v6.h> #include <wlr/types/wlr_xdg_shell.h> @@ -127,6 +128,65 @@ static void drag_icons_for_each_surface(struct roots_input *input, } } +static void layer_for_each_surface(struct wl_list *layer, + const struct wlr_box *output_layout_box, + wlr_surface_iterator_func_t iterator, struct layout_data *layout_data, + void *user_data) { + struct roots_layer_surface *roots_surface; + wl_list_for_each(roots_surface, layer, link) { + struct wlr_layer_surface_v1 *layer = roots_surface->layer_surface; + + layout_data->x = roots_surface->geo.x + output_layout_box->x; + layout_data->y = roots_surface->geo.y + output_layout_box->y; + layout_data->width = roots_surface->geo.width; + layout_data->height = roots_surface->geo.height; + layout_data->rotation = 0; + wlr_layer_surface_v1_for_each_surface(layer, iterator, user_data); + } +} + +static void output_for_each_surface(struct roots_output *output, + wlr_surface_iterator_func_t iterator, struct layout_data *layout_data, + void *user_data) { + struct wlr_output *wlr_output = output->wlr_output; + struct roots_desktop *desktop = output->desktop; + struct roots_server *server = desktop->server; + + const struct wlr_box *output_box = + wlr_output_layout_get_box(desktop->layout, wlr_output); + + if (output->fullscreen_view != NULL) { + struct roots_view *view = output->fullscreen_view; + if (wlr_output->fullscreen_surface == view->wlr_surface) { + // The surface is managed by the wlr_output + return; + } + + view_for_each_surface(view, layout_data, iterator, user_data); + +#ifdef WLR_HAS_XWAYLAND + if (view->type == ROOTS_XWAYLAND_VIEW) { + xwayland_children_for_each_surface(view->xwayland_surface, + iterator, layout_data, user_data); + } +#endif + } else { + struct roots_view *view; + wl_list_for_each_reverse(view, &desktop->views, link) { + view_for_each_surface(view, layout_data, iterator, user_data); + } + + drag_icons_for_each_surface(server->input, iterator, + layout_data, user_data); + } + + size_t len = sizeof(output->layers) / sizeof(output->layers[0]); + for (size_t i = 0; i < len; ++i) { + layer_for_each_surface(&output->layers[i], output_box, + iterator, layout_data, user_data); + } +} + struct render_data { struct layout_data layout; @@ -320,6 +380,14 @@ static void render_view(struct roots_view *view, struct render_data *data) { view_for_each_surface(view, &data->layout, render_surface, data); } +static void render_layer(struct roots_output *output, + const struct wlr_box *output_layout_box, struct render_data *data, + struct wl_list *layer) { + data->alpha = 1; + layer_for_each_surface(layer, output_layout_box, render_surface, + &data->layout, data); +} + static bool has_standalone_surface(struct roots_view *view) { if (!wl_list_empty(&view->wlr_surface->subsurfaces)) { return false; @@ -358,38 +426,6 @@ static void surface_send_frame_done(struct wlr_surface *surface, int sx, int sy, wlr_surface_send_frame_done(surface, when); } -static void render_layer(struct roots_output *output, - const struct wlr_box *output_layout_box, struct render_data *data, - struct wl_list *layer) { - struct roots_layer_surface *roots_surface; - wl_list_for_each(roots_surface, layer, link) { - struct wlr_layer_surface_v1 *layer = roots_surface->layer_surface; - - surface_for_each_surface(layer->surface, - roots_surface->geo.x + output_layout_box->x, - roots_surface->geo.y + output_layout_box->y, - 0, &data->layout, render_surface, data); - - wlr_layer_surface_v1_for_each_surface(layer, render_surface, data); - } -} - -static void layers_send_done( - struct roots_output *output, struct timespec *when) { - size_t len = sizeof(output->layers) / sizeof(output->layers[0]); - for (size_t i = 0; i < len; ++i) { - struct roots_layer_surface *roots_surface; - wl_list_for_each(roots_surface, &output->layers[i], link) { - struct wlr_layer_surface_v1 *layer = roots_surface->layer_surface; - wlr_surface_send_frame_done(layer->surface, when); - struct wlr_xdg_popup *popup; - wl_list_for_each(popup, &roots_surface->layer_surface->popups, link) { - wlr_surface_send_frame_done(popup->base->surface, when); - } - } - } -} - static void render_output(struct roots_output *output) { struct wlr_output *wlr_output = output->wlr_output; struct roots_desktop *desktop = output->desktop; @@ -537,33 +573,8 @@ damage_finish: pixman_region32_fini(&damage); // Send frame done events to all surfaces - if (output->fullscreen_view != NULL) { - struct roots_view *view = output->fullscreen_view; - if (wlr_output->fullscreen_surface == view->wlr_surface) { - // The surface is managed by the wlr_output - return; - } - - view_for_each_surface(view, &data.layout, surface_send_frame_done, - &data); - -#ifdef WLR_HAS_XWAYLAND - if (view->type == ROOTS_XWAYLAND_VIEW) { - xwayland_children_for_each_surface(view->xwayland_surface, - surface_send_frame_done, &data.layout, &data); - } -#endif - } else { - struct roots_view *view; - wl_list_for_each_reverse(view, &desktop->views, link) { - view_for_each_surface(view, &data.layout, surface_send_frame_done, - &data); - } - - drag_icons_for_each_surface(server->input, surface_send_frame_done, - &data.layout, &data); - } - layers_send_done(output, data.when); + output_for_each_surface(output, surface_send_frame_done, + &data.layout, &data); } void output_damage_whole(struct roots_output *output) { @@ -774,6 +785,7 @@ static void output_destroy(struct roots_output *output) { wl_list_remove(&output->destroy.link); wl_list_remove(&output->mode.link); wl_list_remove(&output->transform.link); + wl_list_remove(&output->present.link); wl_list_remove(&output->damage_frame.link); wl_list_remove(&output->damage_destroy.link); free(output); @@ -810,6 +822,52 @@ static void output_handle_transform(struct wl_listener *listener, void *data) { arrange_layers(output); } +struct presentation_data { + struct layout_data layout; + struct roots_output *output; + struct wlr_presentation_event *event; +}; + +static void surface_send_presented(struct wlr_surface *surface, int sx, int sy, + void *_data) { + struct presentation_data *data = _data; + struct roots_output *output = data->output; + float rotation = data->layout.rotation; + + double lx, ly; + get_layout_position(&data->layout, &lx, &ly, surface, sx, sy); + + if (!surface_intersect_output(surface, output->desktop->layout, + output->wlr_output, lx, ly, rotation, NULL)) { + return; + } + + wlr_presentation_send_surface_presented(output->desktop->presentation, + surface, data->event); +} + +static void output_handle_present(struct wl_listener *listener, void *data) { + struct roots_output *output = + wl_container_of(listener, output, present); + struct wlr_output_event_present *output_event = data; + + struct wlr_presentation_event event = { + .output = output->wlr_output, + .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, + }; + + struct presentation_data presentation_data = { + .output = output, + .event = &event, + }; + output_for_each_surface(output, surface_send_presented, + &presentation_data.layout, &presentation_data); +} + void handle_new_output(struct wl_listener *listener, void *data) { struct roots_desktop *desktop = wl_container_of(listener, desktop, new_output); @@ -837,6 +895,8 @@ void handle_new_output(struct wl_listener *listener, void *data) { wl_signal_add(&wlr_output->events.mode, &output->mode); output->transform.notify = output_handle_transform; wl_signal_add(&wlr_output->events.transform, &output->transform); + output->present.notify = output_handle_present; + wl_signal_add(&wlr_output->events.present, &output->present); output->damage_frame.notify = output_damage_handle_frame; wl_signal_add(&output->damage->events.frame, &output->damage_frame); diff --git a/types/meson.build b/types/meson.build index 038f7de4..9e2e74a8 100644 --- a/types/meson.build +++ b/types/meson.build @@ -9,23 +9,27 @@ lib_wlr_types = static_library( 'seat/wlr_seat_pointer.c', 'seat/wlr_seat_touch.c', 'seat/wlr_seat.c', - 'xdg_shell/wlr_xdg_popup.c', - 'xdg_shell/wlr_xdg_positioner.c', - 'xdg_shell/wlr_xdg_shell.c', - 'xdg_shell/wlr_xdg_surface.c', - 'xdg_shell/wlr_xdg_toplevel.c', + 'tablet_v2/wlr_tablet_v2_pad.c', + 'tablet_v2/wlr_tablet_v2_tablet.c', + 'tablet_v2/wlr_tablet_v2_tool.c', + 'tablet_v2/wlr_tablet_v2.c', 'xdg_shell_v6/wlr_xdg_popup_v6.c', 'xdg_shell_v6/wlr_xdg_positioner_v6.c', 'xdg_shell_v6/wlr_xdg_shell_v6.c', 'xdg_shell_v6/wlr_xdg_surface_v6.c', 'xdg_shell_v6/wlr_xdg_toplevel_v6.c', + 'xdg_shell/wlr_xdg_popup.c', + 'xdg_shell/wlr_xdg_positioner.c', + 'xdg_shell/wlr_xdg_shell.c', + 'xdg_shell/wlr_xdg_surface.c', + 'xdg_shell/wlr_xdg_toplevel.c', 'wlr_box.c', 'wlr_buffer.c', 'wlr_compositor.c', 'wlr_cursor.c', 'wlr_export_dmabuf_v1.c', - 'wlr_gamma_control.c', 'wlr_gamma_control_v1.c', + 'wlr_gamma_control.c', 'wlr_idle_inhibit_v1.c', 'wlr_idle.c', 'wlr_input_device.c', @@ -38,17 +42,15 @@ lib_wlr_types = static_library( 'wlr_output_damage.c', 'wlr_output_layout.c', 'wlr_output.c', - 'wlr_pointer.c', 'wlr_pointer_constraints_v1.c', + 'wlr_pointer.c', + 'wlr_presentation_time.c', 'wlr_primary_selection.c', 'wlr_region.c', + 'wlr_screencopy_v1.c', 'wlr_screenshooter.c', 'wlr_server_decoration.c', 'wlr_surface.c', - 'tablet_v2/wlr_tablet_v2.c', - 'tablet_v2/wlr_tablet_v2_pad.c', - 'tablet_v2/wlr_tablet_v2_tablet.c', - 'tablet_v2/wlr_tablet_v2_tool.c', 'wlr_tablet_pad.c', 'wlr_tablet_tool.c', 'wlr_touch.c', @@ -57,7 +59,6 @@ lib_wlr_types = static_library( 'wlr_xcursor_manager.c', 'wlr_xdg_decoration_v1.c', 'wlr_xdg_output_v1.c', - 'wlr_screencopy_v1.c', ), include_directories: wlr_inc, dependencies: [pixman, xkbcommon, wayland_server, wlr_protos, libinput], diff --git a/types/wlr_output.c b/types/wlr_output.c index ac10cf7e..35a3ab14 100644 --- a/types/wlr_output.c +++ b/types/wlr_output.c @@ -266,6 +266,7 @@ void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend, wl_signal_init(&output->events.frame); wl_signal_init(&output->events.needs_swap); wl_signal_init(&output->events.swap_buffers); + wl_signal_init(&output->events.present); wl_signal_init(&output->events.enable); wl_signal_init(&output->events.mode); wl_signal_init(&output->events.scale); @@ -544,8 +545,10 @@ void wlr_output_send_frame(struct wlr_output *output) { static void schedule_frame_handle_idle_timer(void *data) { struct wlr_output *output = data; output->idle_frame = NULL; - if (!output->frame_pending) { - wlr_output_send_frame(output); + if (!output->frame_pending && output->impl->schedule_frame) { + // Ask the backend to send a frame event when appropriate + output->frame_pending = true; + output->impl->schedule_frame(output); } } @@ -554,12 +557,37 @@ void wlr_output_schedule_frame(struct wlr_output *output) { return; } - // TODO: ask the backend to send a frame event when appropriate instead + // We're using an idle timer here in case a buffer swap happens right after + // this function is called struct wl_event_loop *ev = wl_display_get_event_loop(output->display); output->idle_frame = wl_event_loop_add_idle(ev, schedule_frame_handle_idle_timer, output); } +void wlr_output_send_present(struct wlr_output *output, + struct wlr_output_event_present *event) { + struct wlr_output_event_present _event = {0}; + if (event == NULL) { + event = &_event; + } + + event->output = output; + + struct timespec now; + if (event->when == NULL) { + clockid_t clock = wlr_backend_get_presentation_clock(output->backend); + errno = 0; + if (clock_gettime(clock, &now) != 0) { + wlr_log_errno(WLR_ERROR, "failed to send output present event: " + "failed to read clock"); + return; + } + event->when = &now; + } + + wlr_signal_emit_safe(&output->events.present, event); +} + bool wlr_output_set_gamma(struct wlr_output *output, size_t size, const uint16_t *r, const uint16_t *g, const uint16_t *b) { if (!output->impl->set_gamma) { diff --git a/types/wlr_presentation_time.c b/types/wlr_presentation_time.c new file mode 100644 index 00000000..eeba47b6 --- /dev/null +++ b/types/wlr_presentation_time.c @@ -0,0 +1,223 @@ +#define _POSIX_C_SOURCE 199309L +#include <assert.h> +#include <stdlib.h> +#include <wlr/types/wlr_presentation_time.h> +#include <wlr/types/wlr_surface.h> +#include <wlr/backend.h> +#include "presentation-time-protocol.h" +#include "util/signal.h" + +#define PRESENTATION_VERSION 1 + +static struct wlr_presentation_feedback *presentation_feedback_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &wp_presentation_feedback_interface, NULL)); + return wl_resource_get_user_data(resource); +} + +static void feedback_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_presentation_feedback *feedback = + presentation_feedback_from_resource(resource); + wl_list_remove(&feedback->surface_commit.link); + wl_list_remove(&feedback->surface_destroy.link); + wl_list_remove(&feedback->link); + free(feedback); +} + +// Destroys the feedback +static void feedback_send_presented(struct wlr_presentation_feedback *feedback, + struct wlr_presentation_event *event) { + struct wl_client *client = wl_resource_get_client(feedback->resource); + struct wl_resource *resource; + wl_resource_for_each(resource, &event->output->resources) { + if (wl_resource_get_client(resource) == client) { + wp_presentation_feedback_send_sync_output(feedback->resource, + resource); + } + } + + uint32_t tv_sec_hi = event->tv_sec >> 32; + uint32_t tv_sec_lo = event->tv_sec & 0xFFFFFFFF; + uint32_t seq_hi = event->seq >> 32; + uint32_t seq_lo = event->seq & 0xFFFFFFFF; + wp_presentation_feedback_send_presented(feedback->resource, + tv_sec_hi, tv_sec_lo, event->tv_nsec, event->refresh, + seq_hi, seq_lo, event->flags); + + wl_resource_destroy(feedback->resource); +} + +// Destroys the feedback +static void feedback_send_discarded( + struct wlr_presentation_feedback *feedback) { + wp_presentation_feedback_send_discarded(feedback->resource); + wl_resource_destroy(feedback->resource); +} + +static void feedback_handle_surface_commit(struct wl_listener *listener, + void *data) { + struct wlr_presentation_feedback *feedback = + wl_container_of(listener, feedback, surface_commit); + + if (feedback->committed) { + // The content update has been superseded + feedback_send_discarded(feedback); + } else { + feedback->committed = true; + } +} + +static void feedback_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_presentation_feedback *feedback = + wl_container_of(listener, feedback, surface_destroy); + feedback_send_discarded(feedback); +} + +static const struct wp_presentation_interface presentation_impl; + +static struct wlr_presentation *presentation_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &wp_presentation_interface, + &presentation_impl)); + return wl_resource_get_user_data(resource); +} + +static void presentation_handle_feedback(struct wl_client *client, + struct wl_resource *resource, struct wl_resource *surface_resource, + uint32_t id) { + struct wlr_presentation *presentation = + presentation_from_resource(resource); + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + struct wlr_presentation_feedback *feedback = + calloc(1, sizeof(struct wlr_presentation_feedback)); + if (feedback == NULL) { + wl_client_post_no_memory(client); + return; + } + + uint32_t version = wl_resource_get_version(resource); + feedback->resource = wl_resource_create(client, + &wp_presentation_feedback_interface, version, id); + if (feedback->resource == NULL) { + free(feedback); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(feedback->resource, NULL, feedback, + feedback_handle_resource_destroy); + + feedback->surface = surface; + + feedback->surface_commit.notify = feedback_handle_surface_commit; + wl_signal_add(&surface->events.commit, &feedback->surface_commit); + + feedback->surface_destroy.notify = feedback_handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &feedback->surface_destroy); + + wl_list_insert(&presentation->feedbacks, &feedback->link); +} + +static void presentation_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct wp_presentation_interface presentation_impl = { + .feedback = presentation_handle_feedback, + .destroy = presentation_handle_destroy, +}; + +static void presentation_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void presentation_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_presentation *presentation = data; + + struct wl_resource *resource = wl_resource_create(client, + &wp_presentation_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &presentation_impl, presentation, + presentation_handle_resource_destroy); + wl_list_insert(&presentation->resources, wl_resource_get_link(resource)); + + wp_presentation_send_clock_id(resource, (uint32_t)presentation->clock); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_presentation *presentation = + wl_container_of(listener, presentation, display_destroy); + wlr_presentation_destroy(presentation); +} + +struct wlr_presentation *wlr_presentation_create(struct wl_display *display, + struct wlr_backend *backend) { + struct wlr_presentation *presentation = + calloc(1, sizeof(struct wlr_presentation)); + if (presentation == NULL) { + return NULL; + } + + presentation->global = wl_global_create(display, &wp_presentation_interface, + PRESENTATION_VERSION, presentation, presentation_bind); + if (presentation->global == NULL) { + free(presentation); + return NULL; + } + + presentation->clock = wlr_backend_get_presentation_clock(backend); + + wl_list_init(&presentation->resources); + wl_list_init(&presentation->feedbacks); + wl_signal_init(&presentation->events.destroy); + + presentation->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &presentation->display_destroy); + + return presentation; +} + +void wlr_presentation_destroy(struct wlr_presentation *presentation) { + if (presentation == NULL) { + return; + } + + wlr_signal_emit_safe(&presentation->events.destroy, presentation); + + wl_global_destroy(presentation->global); + + struct wlr_presentation_feedback *feedback, *feedback_tmp; + wl_list_for_each_safe(feedback, feedback_tmp, &presentation->feedbacks, + link) { + wl_resource_destroy(feedback->resource); + } + + struct wl_resource *resource, *resource_tmp; + wl_resource_for_each_safe(resource, resource_tmp, + &presentation->resources) { + wl_resource_destroy(resource); + } + + wl_list_remove(&presentation->display_destroy.link); + free(presentation); +} + +void wlr_presentation_send_surface_presented( + struct wlr_presentation *presentation, struct wlr_surface *surface, + struct wlr_presentation_event *event) { + // TODO: maybe use a hashtable to optimize this function + struct wlr_presentation_feedback *feedback, *feedback_tmp; + wl_list_for_each_safe(feedback, feedback_tmp, + &presentation->feedbacks, link) { + if (feedback->surface == surface && feedback->committed) { + feedback_send_presented(feedback, event); + } + } +} |