diff options
Diffstat (limited to 'backend')
33 files changed, 8789 insertions, 0 deletions
diff --git a/backend/backend.c b/backend/backend.c new file mode 100644 index 00000000..b369a135 --- /dev/null +++ b/backend/backend.c @@ -0,0 +1,291 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <errno.h> +#include <libinput.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/backend/drm.h> +#include <wlr/backend/headless.h> +#include <wlr/backend/interface.h> +#include <wlr/backend/libinput.h> +#include <wlr/backend/multi.h> +#include <wlr/backend/session.h> +#include <wlr/backend/wayland.h> +#include <wlr/config.h> +#include <wlr/util/log.h> +#include "backend/multi.h" + +#if WLR_HAS_X11_BACKEND +#include <wlr/backend/x11.h> +#endif + +void wlr_backend_init(struct wlr_backend *backend, + const struct wlr_backend_impl *impl) { + assert(backend); + backend->impl = impl; + wl_signal_init(&backend->events.destroy); + wl_signal_init(&backend->events.new_input); + wl_signal_init(&backend->events.new_output); +} + +bool wlr_backend_start(struct wlr_backend *backend) { + if (backend->impl->start) { + return backend->impl->start(backend); + } + return true; +} + +void wlr_backend_destroy(struct wlr_backend *backend) { + if (!backend) { + return; + } + + if (backend->impl && backend->impl->destroy) { + backend->impl->destroy(backend); + } else { + free(backend); + } +} + +struct wlr_renderer *wlr_backend_get_renderer(struct wlr_backend *backend) { + if (backend->impl->get_renderer) { + return backend->impl->get_renderer(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) { + return 1; + } + + char *end; + int outputs = (int)strtol(outputs_str, &end, 10); + if (*end || outputs < 0) { + wlr_log(WLR_ERROR, "%s specified with invalid integer, ignoring", name); + return 1; + } + + return outputs; +} + +static struct wlr_backend *attempt_wl_backend(struct wl_display *display, + wlr_renderer_create_func_t create_renderer_func) { + struct wlr_backend *backend = wlr_wl_backend_create(display, NULL, create_renderer_func); + if (backend == NULL) { + return NULL; + } + + size_t outputs = parse_outputs_env("WLR_WL_OUTPUTS"); + for (size_t i = 0; i < outputs; ++i) { + wlr_wl_output_create(backend); + } + + return backend; +} + +#if WLR_HAS_X11_BACKEND +static struct wlr_backend *attempt_x11_backend(struct wl_display *display, + const char *x11_display, wlr_renderer_create_func_t create_renderer_func) { + struct wlr_backend *backend = wlr_x11_backend_create(display, x11_display, create_renderer_func); + if (backend == NULL) { + return NULL; + } + + size_t outputs = parse_outputs_env("WLR_X11_OUTPUTS"); + for (size_t i = 0; i < outputs; ++i) { + wlr_x11_output_create(backend); + } + + return backend; +} +#endif + +static struct wlr_backend *attempt_headless_backend( + struct wl_display *display, wlr_renderer_create_func_t create_renderer_func) { + struct wlr_backend *backend = wlr_headless_backend_create(display, create_renderer_func); + if (backend == NULL) { + return NULL; + } + + size_t outputs = parse_outputs_env("WLR_HEADLESS_OUTPUTS"); + for (size_t i = 0; i < outputs; ++i) { + wlr_headless_add_output(backend, 1280, 720); + } + + return backend; +} + +static struct wlr_backend *attempt_drm_backend(struct wl_display *display, + struct wlr_backend *backend, struct wlr_session *session, + wlr_renderer_create_func_t create_renderer_func) { + int gpus[8]; + size_t num_gpus = wlr_session_find_gpus(session, 8, gpus); + struct wlr_backend *primary_drm = NULL; + wlr_log(WLR_INFO, "Found %zu GPUs", num_gpus); + + for (size_t i = 0; i < num_gpus; ++i) { + struct wlr_backend *drm = wlr_drm_backend_create(display, session, + gpus[i], primary_drm, create_renderer_func); + if (!drm) { + wlr_log(WLR_ERROR, "Failed to open DRM device %d", gpus[i]); + continue; + } + + if (!primary_drm) { + primary_drm = drm; + } + + wlr_multi_backend_add(backend, drm); + } + + return primary_drm; +} + +static struct wlr_backend *attempt_backend_by_name(struct wl_display *display, + struct wlr_backend *backend, struct wlr_session **session, + const char *name, wlr_renderer_create_func_t create_renderer_func) { + if (strcmp(name, "wayland") == 0) { + return attempt_wl_backend(display, create_renderer_func); +#if WLR_HAS_X11_BACKEND + } else if (strcmp(name, "x11") == 0) { + return attempt_x11_backend(display, NULL, create_renderer_func); +#endif + } else if (strcmp(name, "headless") == 0) { + return attempt_headless_backend(display, create_renderer_func); + } else if (strcmp(name, "drm") == 0 || strcmp(name, "libinput") == 0) { + // DRM and libinput need a session + if (!*session) { + *session = wlr_session_create(display); + if (!*session) { + wlr_log(WLR_ERROR, "failed to start a session"); + return NULL; + } + } + + if (strcmp(name, "libinput") == 0) { + return wlr_libinput_backend_create(display, *session); + } else { + return attempt_drm_backend(display, backend, *session, create_renderer_func); + } + } + + wlr_log(WLR_ERROR, "unrecognized backend '%s'", name); + return NULL; +} + +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; + } + + char *names = getenv("WLR_BACKENDS"); + if (names) { + names = strdup(names); + if (names == NULL) { + wlr_log(WLR_ERROR, "allocation failed"); + wlr_backend_destroy(backend); + return NULL; + } + + char *saveptr; + char *name = strtok_r(names, ",", &saveptr); + while (name != NULL) { + 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_session_destroy(multi->session); + wlr_backend_destroy(backend); + free(names); + return NULL; + } + + if (!wlr_multi_backend_add(backend, subbackend)) { + wlr_log(WLR_ERROR, "failed to add backend '%s'", name); + wlr_session_destroy(multi->session); + wlr_backend_destroy(backend); + free(names); + return NULL; + } + + name = strtok_r(NULL, ",", &saveptr); + } + + free(names); + return backend; + } + + if (getenv("WAYLAND_DISPLAY") || getenv("_WAYLAND_DISPLAY") || + getenv("WAYLAND_SOCKET")) { + struct wlr_backend *wl_backend = attempt_wl_backend(display, + create_renderer_func); + if (wl_backend) { + wlr_multi_backend_add(backend, wl_backend); + return backend; + } + } + +#if WLR_HAS_X11_BACKEND + const char *x11_display = getenv("DISPLAY"); + if (x11_display) { + struct wlr_backend *x11_backend = + attempt_x11_backend(display, x11_display, create_renderer_func); + if (x11_backend) { + wlr_multi_backend_add(backend, x11_backend); + return backend; + } + } +#endif + + // Attempt DRM+libinput + 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, + multi->session); + if (!libinput) { + wlr_log(WLR_ERROR, "Failed to start libinput backend"); + wlr_session_destroy(multi->session); + wlr_backend_destroy(backend); + return NULL; + } + wlr_multi_backend_add(backend, libinput); + + 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_session_destroy(multi->session); + wlr_backend_destroy(backend); + return NULL; + } + + return backend; +} diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c new file mode 100644 index 00000000..fc649d68 --- /dev/null +++ b/backend/drm/atomic.c @@ -0,0 +1,273 @@ +#include <gbm.h> +#include <stdlib.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include "backend/drm/drm.h" +#include "backend/drm/iface.h" +#include "backend/drm/util.h" + +struct atomic { + drmModeAtomicReq *req; + int cursor; + bool failed; +}; + +static void atomic_begin(struct wlr_drm_crtc *crtc, struct atomic *atom) { + if (!crtc->atomic) { + crtc->atomic = drmModeAtomicAlloc(); + if (!crtc->atomic) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + atom->failed = true; + return; + } + } + + atom->req = crtc->atomic; + atom->cursor = drmModeAtomicGetCursor(atom->req); + atom->failed = false; +} + +static bool atomic_end(int drm_fd, struct atomic *atom) { + if (atom->failed) { + return false; + } + + uint32_t flags = DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_NONBLOCK; + if (drmModeAtomicCommit(drm_fd, atom->req, flags, NULL)) { + wlr_log_errno(WLR_ERROR, "Atomic test failed"); + drmModeAtomicSetCursor(atom->req, atom->cursor); + return false; + } + + return true; +} + +static bool atomic_commit(int drm_fd, struct atomic *atom, + struct wlr_drm_connector *conn, uint32_t flags, bool modeset) { + if (atom->failed) { + return false; + } + + int ret = drmModeAtomicCommit(drm_fd, atom->req, flags, conn); + if (ret) { + wlr_log_errno(WLR_ERROR, "%s: Atomic commit failed (%s)", + conn->output.name, modeset ? "modeset" : "pageflip"); + + // Try to commit without new changes + drmModeAtomicSetCursor(atom->req, atom->cursor); + if (drmModeAtomicCommit(drm_fd, atom->req, flags, conn)) { + wlr_log_errno(WLR_ERROR, + "%s: Atomic commit without new changes failed (%s)", + conn->output.name, modeset ? "modeset" : "pageflip"); + } + } + + drmModeAtomicSetCursor(atom->req, 0); + + return !ret; +} + +static inline void atomic_add(struct atomic *atom, uint32_t id, uint32_t prop, uint64_t val) { + if (!atom->failed && drmModeAtomicAddProperty(atom->req, id, prop, val) < 0) { + wlr_log_errno(WLR_ERROR, "Failed to add atomic DRM property"); + atom->failed = true; + } +} + +static void set_plane_props(struct atomic *atom, struct wlr_drm_plane *plane, + uint32_t crtc_id, uint32_t fb_id, bool set_crtc_xy) { + uint32_t id = plane->id; + const union wlr_drm_plane_props *props = &plane->props; + + // 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)plane->surf.width << 16); + atomic_add(atom, id, props->src_h, (uint64_t)plane->surf.height << 16); + atomic_add(atom, id, props->crtc_w, plane->surf.width); + atomic_add(atom, id, props->crtc_h, plane->surf.height); + atomic_add(atom, id, props->fb_id, fb_id); + atomic_add(atom, id, props->crtc_id, crtc_id); + if (set_crtc_xy) { + atomic_add(atom, id, props->crtc_x, 0); + atomic_add(atom, id, props->crtc_y, 0); + } +} + +static bool atomic_crtc_pageflip(struct wlr_drm_backend *drm, + struct wlr_drm_connector *conn, + struct wlr_drm_crtc *crtc, + uint32_t fb_id, drmModeModeInfo *mode) { + if (mode != NULL) { + if (crtc->mode_id != 0) { + drmModeDestroyPropertyBlob(drm->fd, crtc->mode_id); + } + + if (drmModeCreatePropertyBlob(drm->fd, mode, sizeof(*mode), + &crtc->mode_id)) { + wlr_log_errno(WLR_ERROR, "Unable to create property blob"); + return false; + } + } + + uint32_t flags = DRM_MODE_PAGE_FLIP_EVENT; + if (mode != NULL) { + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } else { + flags |= DRM_MODE_ATOMIC_NONBLOCK; + } + + struct atomic atom; + atomic_begin(crtc, &atom); + atomic_add(&atom, conn->id, conn->props.crtc_id, crtc->id); + if (mode != NULL && conn->props.link_status != 0) { + atomic_add(&atom, conn->id, conn->props.link_status, + DRM_MODE_LINK_STATUS_GOOD); + } + atomic_add(&atom, crtc->id, crtc->props.mode_id, crtc->mode_id); + atomic_add(&atom, crtc->id, crtc->props.active, 1); + set_plane_props(&atom, crtc->primary, crtc->id, fb_id, true); + return atomic_commit(drm->fd, &atom, conn, flags, mode); +} + +static bool atomic_conn_enable(struct wlr_drm_backend *drm, + struct wlr_drm_connector *conn, bool enable) { + struct wlr_drm_crtc *crtc = conn->crtc; + if (crtc == NULL) { + return !enable; + } + + struct atomic atom; + atomic_begin(crtc, &atom); + atomic_add(&atom, crtc->id, crtc->props.active, enable); + if (enable) { + atomic_add(&atom, conn->id, conn->props.crtc_id, crtc->id); + atomic_add(&atom, crtc->id, crtc->props.mode_id, crtc->mode_id); + } else { + atomic_add(&atom, conn->id, conn->props.crtc_id, 0); + atomic_add(&atom, crtc->id, crtc->props.mode_id, 0); + } + return atomic_commit(drm->fd, &atom, conn, DRM_MODE_ATOMIC_ALLOW_MODESET, + true); +} + +bool legacy_crtc_set_cursor(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, struct gbm_bo *bo); + +static bool atomic_crtc_set_cursor(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, struct gbm_bo *bo) { + if (!crtc || !crtc->cursor) { + return true; + } + + struct wlr_drm_plane *plane = crtc->cursor; + // We can't use atomic operations on fake planes + if (plane->id == 0) { + return legacy_crtc_set_cursor(drm, crtc, bo); + } + + struct atomic atom; + + atomic_begin(crtc, &atom); + + if (bo) { + set_plane_props(&atom, plane, crtc->id, get_fb_for_bo(bo), false); + } else { + atomic_add(&atom, plane->id, plane->props.fb_id, 0); + atomic_add(&atom, plane->id, plane->props.crtc_id, 0); + } + + return atomic_end(drm->fd, &atom); +} + +bool legacy_crtc_move_cursor(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, int x, int y); + +static bool atomic_crtc_move_cursor(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, int x, int y) { + if (!crtc || !crtc->cursor) { + return true; + } + + struct wlr_drm_plane *plane = crtc->cursor; + // We can't use atomic operations on fake planes + if (plane->id == 0) { + return legacy_crtc_move_cursor(drm, crtc, x, y); + } + + struct atomic atom; + + atomic_begin(crtc, &atom); + atomic_add(&atom, plane->id, plane->props.crtc_x, x); + atomic_add(&atom, plane->id, plane->props.crtc_y, y); + return atomic_end(drm->fd, &atom); +} + +static bool atomic_crtc_set_gamma(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, size_t size, + uint16_t *r, uint16_t *g, uint16_t *b) { + // Fallback to legacy gamma interface when gamma properties are not available + // (can happen on older Intel GPUs that support gamma but not degamma). + // TEMP: This is broken on AMDGPU. Provide a fallback to legacy until they + // get it fixed. Ref https://bugs.freedesktop.org/show_bug.cgi?id=107459 + const char *no_atomic_str = getenv("WLR_DRM_NO_ATOMIC_GAMMA"); + bool no_atomic = no_atomic_str != NULL && strcmp(no_atomic_str, "1") == 0; + if (crtc->props.gamma_lut == 0 || no_atomic) { + return legacy_iface.crtc_set_gamma(drm, crtc, size, r, g, b); + } + + struct drm_color_lut *gamma = malloc(size * sizeof(struct drm_color_lut)); + if (gamma == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate gamma table"); + return false; + } + + for (size_t i = 0; i < size; i++) { + gamma[i].red = r[i]; + gamma[i].green = g[i]; + gamma[i].blue = b[i]; + } + + if (crtc->gamma_lut != 0) { + drmModeDestroyPropertyBlob(drm->fd, crtc->gamma_lut); + } + + if (drmModeCreatePropertyBlob(drm->fd, gamma, + size * sizeof(struct drm_color_lut), &crtc->gamma_lut)) { + free(gamma); + wlr_log_errno(WLR_ERROR, "Unable to create property blob"); + return false; + } + free(gamma); + + struct atomic atom; + atomic_begin(crtc, &atom); + atomic_add(&atom, crtc->id, crtc->props.gamma_lut, crtc->gamma_lut); + return atomic_end(drm->fd, &atom); +} + +static size_t atomic_crtc_get_gamma_size(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc) { + if (crtc->props.gamma_lut_size == 0) { + return legacy_iface.crtc_get_gamma_size(drm, crtc); + } + + uint64_t gamma_lut_size; + if (!get_drm_prop(drm->fd, crtc->id, crtc->props.gamma_lut_size, + &gamma_lut_size)) { + wlr_log(WLR_ERROR, "Unable to get gamma lut size"); + return 0; + } + + return (size_t)gamma_lut_size; +} + +const struct wlr_drm_interface atomic_iface = { + .conn_enable = atomic_conn_enable, + .crtc_pageflip = atomic_crtc_pageflip, + .crtc_set_cursor = atomic_crtc_set_cursor, + .crtc_move_cursor = atomic_crtc_move_cursor, + .crtc_set_gamma = atomic_crtc_set_gamma, + .crtc_get_gamma_size = atomic_crtc_get_gamma_size, +}; diff --git a/backend/drm/backend.c b/backend/drm/backend.c new file mode 100644 index 00000000..a9082077 --- /dev/null +++ b/backend/drm/backend.c @@ -0,0 +1,211 @@ +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/backend/interface.h> +#include <wlr/backend/session.h> +#include <wlr/interfaces/wlr_output.h> +#include <wlr/render/egl.h> +#include <wlr/types/wlr_list.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include "backend/drm/drm.h" +#include "util/signal.h" + +struct wlr_drm_backend *get_drm_backend_from_backend( + struct wlr_backend *wlr_backend) { + assert(wlr_backend_is_drm(wlr_backend)); + return (struct wlr_drm_backend *)wlr_backend; +} + +static bool backend_start(struct wlr_backend *backend) { + struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); + scan_drm_connectors(drm); + return true; +} + +static void backend_destroy(struct wlr_backend *backend) { + if (!backend) { + return; + } + + struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); + + restore_drm_outputs(drm); + + struct wlr_drm_connector *conn, *next; + wl_list_for_each_safe(conn, next, &drm->outputs, link) { + wlr_output_destroy(&conn->output); + } + + wlr_signal_emit_safe(&backend->events.destroy, backend); + + wl_list_remove(&drm->display_destroy.link); + wl_list_remove(&drm->session_signal.link); + wl_list_remove(&drm->drm_invalidated.link); + + finish_drm_resources(drm); + finish_drm_renderer(&drm->renderer); + wlr_session_close_file(drm->session, drm->fd); + wl_event_source_remove(drm->drm_event); + free(drm); +} + +static struct wlr_renderer *backend_get_renderer( + struct wlr_backend *backend) { + struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); + + if (drm->parent) { + return drm->parent->renderer.wlr_rend; + } else { + return drm->renderer.wlr_rend; + } +} + +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) { + return b->impl == &backend_impl; +} + +static void session_signal(struct wl_listener *listener, void *data) { + struct wlr_drm_backend *drm = + wl_container_of(listener, drm, session_signal); + struct wlr_session *session = data; + + if (session->active) { + wlr_log(WLR_INFO, "DRM fd resumed"); + scan_drm_connectors(drm); + + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link){ + if (conn->output.enabled) { + wlr_output_set_mode(&conn->output, conn->output.current_mode); + } else { + enable_drm_connector(&conn->output, false); + } + + if (!conn->crtc) { + continue; + } + + struct wlr_drm_plane *plane = conn->crtc->cursor; + drm->iface->crtc_set_cursor(drm, conn->crtc, + (plane && plane->cursor_enabled) ? plane->cursor_bo : NULL); + drm->iface->crtc_move_cursor(drm, conn->crtc, conn->cursor_x, + conn->cursor_y); + + if (conn->crtc->gamma_table != NULL) { + size_t size = conn->crtc->gamma_table_size; + uint16_t *r = conn->crtc->gamma_table; + uint16_t *g = conn->crtc->gamma_table + size; + uint16_t *b = conn->crtc->gamma_table + 2 * size; + drm->iface->crtc_set_gamma(drm, conn->crtc, size, r, g, b); + } else { + set_drm_connector_gamma(&conn->output, 0, NULL, NULL, NULL); + } + } + } else { + wlr_log(WLR_INFO, "DRM fd paused"); + } +} + +static void drm_invalidated(struct wl_listener *listener, void *data) { + struct wlr_drm_backend *drm = + wl_container_of(listener, drm, drm_invalidated); + + char *name = drmGetDeviceNameFromFd2(drm->fd); + wlr_log(WLR_DEBUG, "%s invalidated", name); + free(name); + + scan_drm_connectors(drm); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_drm_backend *drm = + wl_container_of(listener, drm, display_destroy); + backend_destroy(&drm->backend); +} + +struct wlr_backend *wlr_drm_backend_create(struct wl_display *display, + struct wlr_session *session, int gpu_fd, struct wlr_backend *parent, + wlr_renderer_create_func_t create_renderer_func) { + assert(display && session && gpu_fd >= 0); + assert(!parent || wlr_backend_is_drm(parent)); + + char *name = drmGetDeviceNameFromFd2(gpu_fd); + drmVersion *version = drmGetVersion(gpu_fd); + wlr_log(WLR_INFO, "Initializing DRM backend for %s (%s)", name, version->name); + free(name); + drmFreeVersion(version); + + struct wlr_drm_backend *drm = calloc(1, sizeof(struct wlr_drm_backend)); + if (!drm) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + wlr_backend_init(&drm->backend, &backend_impl); + + drm->session = session; + wl_list_init(&drm->outputs); + + drm->fd = gpu_fd; + if (parent != NULL) { + drm->parent = get_drm_backend_from_backend(parent); + } + + drm->drm_invalidated.notify = drm_invalidated; + wlr_session_signal_add(session, gpu_fd, &drm->drm_invalidated); + + drm->display = display; + struct wl_event_loop *event_loop = wl_display_get_event_loop(display); + + drm->drm_event = wl_event_loop_add_fd(event_loop, drm->fd, + WL_EVENT_READABLE, handle_drm_event, NULL); + if (!drm->drm_event) { + wlr_log(WLR_ERROR, "Failed to create DRM event source"); + goto error_fd; + } + + drm->session_signal.notify = session_signal; + wl_signal_add(&session->session_signal, &drm->session_signal); + + if (!check_drm_features(drm)) { + goto error_event; + } + + if (!init_drm_resources(drm)) { + goto error_event; + } + + if (!init_drm_renderer(drm, &drm->renderer, create_renderer_func)) { + wlr_log(WLR_ERROR, "Failed to initialize renderer"); + goto error_event; + } + + drm->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &drm->display_destroy); + + return &drm->backend; + +error_event: + wl_list_remove(&drm->session_signal.link); + wl_event_source_remove(drm->drm_event); +error_fd: + wlr_session_close_file(drm->session, drm->fd); + free(drm); + return NULL; +} diff --git a/backend/drm/drm.c b/backend/drm/drm.c new file mode 100644 index 00000000..735b7c29 --- /dev/null +++ b/backend/drm/drm.c @@ -0,0 +1,1418 @@ +#define _POSIX_C_SOURCE 200112L +#include <assert.h> +#include <drm_mode.h> +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <errno.h> +#include <gbm.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <wayland-server.h> +#include <wayland-util.h> +#include <wlr/backend/interface.h> +#include <wlr/interfaces/wlr_output.h> +#include <wlr/render/gles2.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include "backend/drm/drm.h" +#include "backend/drm/iface.h" +#include "backend/drm/util.h" +#include "util/signal.h" + +bool check_drm_features(struct wlr_drm_backend *drm) { + uint64_t cap; + if (drm->parent) { + if (drmGetCap(drm->fd, DRM_CAP_PRIME, &cap) || + !(cap & DRM_PRIME_CAP_IMPORT)) { + wlr_log(WLR_ERROR, + "PRIME import not supported on secondary GPU"); + return false; + } + + if (drmGetCap(drm->parent->fd, DRM_CAP_PRIME, &cap) || + !(cap & DRM_PRIME_CAP_EXPORT)) { + wlr_log(WLR_ERROR, + "PRIME export not supported on primary GPU"); + return false; + } + } + + if (drmSetClientCap(drm->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) { + wlr_log(WLR_ERROR, "DRM universal planes unsupported"); + return false; + } + + 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"); + 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"); + 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; +} + +static int cmp_plane(const void *arg1, const void *arg2) { + const struct wlr_drm_plane *a = arg1; + const struct wlr_drm_plane *b = arg2; + + return (int)a->type - (int)b->type; +} + +static bool init_planes(struct wlr_drm_backend *drm) { + drmModePlaneRes *plane_res = drmModeGetPlaneResources(drm->fd); + if (!plane_res) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM plane resources"); + return false; + } + + wlr_log(WLR_INFO, "Found %"PRIu32" DRM planes", plane_res->count_planes); + + if (plane_res->count_planes == 0) { + drmModeFreePlaneResources(plane_res); + return true; + } + + drm->num_planes = plane_res->count_planes; + drm->planes = calloc(drm->num_planes, sizeof(*drm->planes)); + if (!drm->planes) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto error_res; + } + + for (size_t i = 0; i < drm->num_planes; ++i) { + struct wlr_drm_plane *p = &drm->planes[i]; + + drmModePlane *plane = drmModeGetPlane(drm->fd, plane_res->planes[i]); + if (!plane) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM plane"); + goto error_planes; + } + + p->id = plane->plane_id; + p->possible_crtcs = plane->possible_crtcs; + uint64_t type; + + if (!get_drm_plane_props(drm->fd, p->id, &p->props) || + !get_drm_prop(drm->fd, p->id, p->props.type, &type)) { + drmModeFreePlane(plane); + goto error_planes; + } + + p->type = type; + drm->num_type_planes[type]++; + + drmModeFreePlane(plane); + } + + wlr_log(WLR_INFO, "(%zu overlay, %zu primary, %zu cursor)", + drm->num_overlay_planes, + drm->num_primary_planes, + drm->num_cursor_planes); + + qsort(drm->planes, drm->num_planes, sizeof(*drm->planes), cmp_plane); + + drm->overlay_planes = drm->planes; + drm->primary_planes = drm->overlay_planes + + drm->num_overlay_planes; + drm->cursor_planes = drm->primary_planes + + drm->num_primary_planes; + + drmModeFreePlaneResources(plane_res); + return true; + +error_planes: + free(drm->planes); +error_res: + drmModeFreePlaneResources(plane_res); + return false; +} + +bool init_drm_resources(struct wlr_drm_backend *drm) { + drmModeRes *res = drmModeGetResources(drm->fd); + if (!res) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM resources"); + return false; + } + + 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"); + goto error_res; + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + struct wlr_drm_crtc *crtc = &drm->crtcs[i]; + crtc->id = res->crtcs[i]; + crtc->legacy_crtc = drmModeGetCrtc(drm->fd, crtc->id); + get_drm_crtc_props(drm->fd, crtc->id, &crtc->props); + } + + if (!init_planes(drm)) { + goto error_crtcs; + } + + drmModeFreeResources(res); + + return true; + +error_crtcs: + free(drm->crtcs); +error_res: + drmModeFreeResources(res); + return false; +} + +void finish_drm_resources(struct wlr_drm_backend *drm) { + if (!drm) { + return; + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + struct wlr_drm_crtc *crtc = &drm->crtcs[i]; + drmModeAtomicFree(crtc->atomic); + drmModeFreeCrtc(crtc->legacy_crtc); + if (crtc->mode_id) { + drmModeDestroyPropertyBlob(drm->fd, crtc->mode_id); + } + if (crtc->gamma_lut) { + drmModeDestroyPropertyBlob(drm->fd, crtc->gamma_lut); + } + free(crtc->gamma_table); + } + for (size_t i = 0; i < drm->num_planes; ++i) { + struct wlr_drm_plane *plane = &drm->planes[i]; + if (plane->cursor_bo) { + gbm_bo_destroy(plane->cursor_bo); + } + } + + free(drm->crtcs); + free(drm->planes); +} + +static struct wlr_drm_connector *get_drm_connector_from_output( + struct wlr_output *wlr_output) { + assert(wlr_output_is_drm(wlr_output)); + return (struct wlr_drm_connector *)wlr_output; +} + +static bool drm_connector_make_current(struct wlr_output *output, + int *buffer_age) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + return make_drm_surface_current(&conn->crtc->primary->surf, buffer_age); +} + +static bool drm_connector_swap_buffers(struct wlr_output *output, + pixman_region32_t *damage) { + 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 false; + } + + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc) { + return false; + } + struct wlr_drm_plane *plane = crtc->primary; + + struct gbm_bo *bo = swap_drm_surface_buffers(&plane->surf, damage); + if (drm->parent) { + bo = copy_drm_surface_mgpu(&plane->mgpu_surf, bo); + } + 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 false; + } + + if (!drm->iface->crtc_pageflip(drm, conn, crtc, fb_id, NULL)) { + return false; + } + + conn->pageflip_pending = true; + wlr_output_update_enabled(output, true); + return true; +} + +static void fill_empty_gamma_table(size_t size, + uint16_t *r, uint16_t *g, uint16_t *b) { + for (uint32_t i = 0; i < size; ++i) { + uint16_t val = (uint32_t)0xffff * i / (size - 1); + r[i] = g[i] = b[i] = val; + } +} + +static size_t drm_connector_get_gamma_size(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 (conn->crtc) { + return drm->iface->crtc_get_gamma_size(drm, conn->crtc); + } + + return 0; +} + +bool set_drm_connector_gamma(struct wlr_output *output, size_t size, + const uint16_t *r, const uint16_t *g, const uint16_t *b) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + + if (!conn->crtc) { + return false; + } + + bool reset = false; + if (size == 0) { + reset = true; + size = drm_connector_get_gamma_size(output); + if (size == 0) { + return false; + } + } + + uint16_t *gamma_table = malloc(3 * size * sizeof(uint16_t)); + if (gamma_table == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate gamma table"); + return false; + } + uint16_t *_r = gamma_table; + uint16_t *_g = gamma_table + size; + uint16_t *_b = gamma_table + 2 * size; + + if (reset) { + fill_empty_gamma_table(size, _r, _g, _b); + } else { + memcpy(_r, r, size * sizeof(uint16_t)); + memcpy(_g, g, size * sizeof(uint16_t)); + memcpy(_b, b, size * sizeof(uint16_t)); + } + + bool ok = drm->iface->crtc_set_gamma(drm, conn->crtc, size, _r, _g, _b); + if (ok) { + wlr_output_update_needs_swap(output); + + free(conn->crtc->gamma_table); + conn->crtc->gamma_table = gamma_table; + conn->crtc->gamma_table_size = size; + } else { + free(gamma_table); + } + return ok; +} + +static bool drm_connector_export_dmabuf(struct wlr_output *output, + struct wlr_dmabuf_attributes *attribs) { + 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 false; + } + + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc) { + return false; + } + struct wlr_drm_plane *plane = crtc->primary; + struct wlr_drm_surface *surf = &plane->surf; + + return export_drm_bo(surf->back, attribs); +} + +static void drm_connector_start_renderer(struct wlr_drm_connector *conn) { + if (conn->state != WLR_DRM_CONN_CONNECTED) { + return; + } + + wlr_log(WLR_DEBUG, "Starting renderer on output '%s'", conn->output.name); + + struct wlr_drm_backend *drm = + get_drm_backend_from_backend(conn->output.backend); + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc) { + return; + } + struct wlr_drm_plane *plane = crtc->primary; + + struct gbm_bo *bo = get_drm_surface_front( + drm->parent ? &plane->mgpu_surf : &plane->surf); + uint32_t fb_id = get_fb_for_bo(bo); + + struct wlr_drm_mode *mode = (struct wlr_drm_mode *)conn->output.current_mode; + if (drm->iface->crtc_pageflip(drm, conn, crtc, fb_id, &mode->drm_mode)) { + conn->pageflip_pending = true; + wlr_output_update_enabled(&conn->output, true); + } else { + wl_event_source_timer_update(conn->retry_pageflip, + 1000000.0f / conn->output.current_mode->refresh); + } +} + +static bool drm_connector_set_mode(struct wlr_output *output, + struct wlr_output_mode *mode); + +static void realloc_crtcs(struct wlr_drm_backend *drm, bool *changed_outputs); + +static void attempt_enable_needs_modeset(struct wlr_drm_backend *drm) { + // Try to modeset any output that has a desired mode and a CRTC (ie. was + // lacking a CRTC on last modeset) + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link) { + if (conn->state == WLR_DRM_CONN_NEEDS_MODESET && + conn->crtc != NULL && conn->desired_mode != NULL && + conn->desired_enabled) { + drm_connector_set_mode(&conn->output, conn->desired_mode); + } + } +} + +bool enable_drm_connector(struct wlr_output *output, bool enable) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + if (conn->state != WLR_DRM_CONN_CONNECTED + && conn->state != WLR_DRM_CONN_NEEDS_MODESET) { + return false; + } + + conn->desired_enabled = enable; + + if (enable && conn->crtc == NULL) { + // Maybe we can steal a CRTC from a disabled output + realloc_crtcs(drm, NULL); + } + + bool ok = drm->iface->conn_enable(drm, conn, enable); + if (!ok) { + return false; + } + + if (enable) { + drm_connector_start_renderer(conn); + } else { + realloc_crtcs(drm, NULL); + + attempt_enable_needs_modeset(drm); + } + + wlr_output_update_enabled(&conn->output, enable); + return true; +} + +static ssize_t connector_index_from_crtc(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc) { + size_t i = 0; + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link) { + if (conn->crtc == crtc) { + return i; + } + ++i; + } + return -1; +} + +static void realloc_planes(struct wlr_drm_backend *drm, const uint32_t *crtc_in, + bool *changed_outputs) { + wlr_log(WLR_DEBUG, "Reallocating planes"); + + // overlay, primary, cursor + for (size_t type = 0; type < 3; ++type) { + if (drm->num_type_planes[type] == 0) { + continue; + } + + 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; + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (crtc_in[i] == UNMATCHED) { + crtc[i] = SKIP; + } else if (drm->crtcs[i].planes[type]) { + crtc[i] = drm->crtcs[i].planes[type] + - drm->type_planes[type]; + } else { + crtc[i] = UNMATCHED; + } + } + + match_obj(drm->num_type_planes[type], possible, + drm->num_crtcs, crtc, crtc_res); + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (crtc_res[i] == UNMATCHED || crtc_res[i] == SKIP) { + continue; + } + + struct wlr_drm_crtc *c = &drm->crtcs[i]; + struct wlr_drm_plane **old = &c->planes[type]; + struct wlr_drm_plane *new = &drm->type_planes[type][crtc_res[i]]; + + if (*old != new) { + wlr_log(WLR_DEBUG, + "Assigning plane %d -> %d (type %zu) to CRTC %d", + *old ? (int)(*old)->id : -1, + new ? (int)new->id : -1, + type, + c->id); + + ssize_t conn_idx = connector_index_from_crtc(drm, c); + if (conn_idx >= 0) { + changed_outputs[conn_idx] = true; + } + if (*old) { + finish_drm_surface(&(*old)->surf); + } + finish_drm_surface(&new->surf); + *old = new; + } + } + } +} + +static void drm_connector_cleanup(struct wlr_drm_connector *conn); + +static bool drm_connector_set_mode(struct wlr_output *output, + struct wlr_output_mode *mode) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + if (conn->crtc == NULL) { + // Maybe we can steal a CRTC from a disabled output + realloc_crtcs(drm, NULL); + } + if (conn->crtc == NULL) { + wlr_log(WLR_ERROR, "Cannot modeset '%s': no CRTC for this connector", + conn->output.name); + // Save the desired mode for later, when we'll get a proper CRTC + conn->desired_mode = mode; + return false; + } + + wlr_log(WLR_INFO, "Modesetting '%s' with '%ux%u@%u mHz'", + conn->output.name, mode->width, mode->height, mode->refresh); + + if (!init_drm_plane_surfaces(conn->crtc->primary, drm, + mode->width, mode->height, GBM_FORMAT_XRGB8888)) { + wlr_log(WLR_ERROR, "Failed to initialize renderer for plane"); + return false; + } + + conn->state = WLR_DRM_CONN_CONNECTED; + conn->desired_mode = NULL; + wlr_output_update_mode(&conn->output, mode); + wlr_output_update_enabled(&conn->output, true); + conn->desired_enabled = true; + + drm_connector_start_renderer(conn); + + // When switching VTs, the mode is not updated but the buffers become + // invalid, so we need to manually damage the output here + wlr_output_damage_whole(&conn->output); + + return true; +} + +bool wlr_drm_connector_add_mode(struct wlr_output *output, + const drmModeModeInfo *modeinfo) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + + if (modeinfo->type != DRM_MODE_TYPE_USERDEF) { + return false; + } + + struct wlr_output_mode *wlr_mode; + wl_list_for_each(wlr_mode, &conn->output.modes, link) { + struct wlr_drm_mode *mode = (struct wlr_drm_mode *)wlr_mode; + if (memcmp(&mode->drm_mode, modeinfo, sizeof(*modeinfo)) == 0) { + return true; + } + } + + struct wlr_drm_mode *mode = calloc(1, sizeof(*mode)); + if (!mode) { + return false; + } + memcpy(&mode->drm_mode, modeinfo, sizeof(*modeinfo)); + + mode->wlr_mode.width = mode->drm_mode.hdisplay; + mode->wlr_mode.height = mode->drm_mode.vdisplay; + mode->wlr_mode.refresh = calculate_refresh_rate(modeinfo); + + wlr_log(WLR_INFO, "Registered custom mode " + "%"PRId32"x%"PRId32"@%"PRId32, + mode->wlr_mode.width, mode->wlr_mode.height, + mode->wlr_mode.refresh); + wl_list_insert(&conn->output.modes, &mode->wlr_mode.link); + return true; +} + +static void drm_connector_transform(struct wlr_output *output, + enum wl_output_transform transform) { + output->transform = transform; +} + +static bool drm_connector_set_cursor(struct wlr_output *output, + struct wlr_texture *texture, int32_t scale, + enum wl_output_transform transform, + int32_t hotspot_x, int32_t hotspot_y, bool update_texture) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc) { + return false; + } + + struct wlr_drm_plane *plane = crtc->cursor; + if (!plane) { + // We don't have a real cursor plane, so we make a fake one + plane = calloc(1, sizeof(*plane)); + if (!plane) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return false; + } + crtc->cursor = plane; + } + + if (!plane->surf.gbm) { + int ret; + uint64_t w, h; + ret = drmGetCap(drm->fd, DRM_CAP_CURSOR_WIDTH, &w); + w = ret ? 64 : w; + ret = drmGetCap(drm->fd, DRM_CAP_CURSOR_HEIGHT, &h); + h = ret ? 64 : h; + + struct wlr_drm_renderer *renderer = + drm->parent ? &drm->parent->renderer : &drm->renderer; + + if (!init_drm_surface(&plane->surf, renderer, w, h, + GBM_FORMAT_ARGB8888, 0)) { + wlr_log(WLR_ERROR, "Cannot allocate cursor resources"); + return false; + } + + plane->cursor_bo = gbm_bo_create(drm->renderer.gbm, w, h, + GBM_FORMAT_ARGB8888, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE); + if (!plane->cursor_bo) { + wlr_log_errno(WLR_ERROR, "Failed to create cursor bo"); + return false; + } + } + + wlr_matrix_projection(plane->matrix, plane->surf.width, + plane->surf.height, output->transform); + + struct wlr_box hotspot = { .x = hotspot_x, .y = hotspot_y }; + wlr_box_transform(&hotspot, &hotspot, + wlr_output_transform_invert(output->transform), + plane->surf.width, plane->surf.height); + + if (plane->cursor_hotspot_x != hotspot.x || + plane->cursor_hotspot_y != hotspot.y) { + // Update cursor hotspot + conn->cursor_x -= hotspot.x - plane->cursor_hotspot_x; + conn->cursor_y -= hotspot.y - plane->cursor_hotspot_y; + plane->cursor_hotspot_x = hotspot.x; + plane->cursor_hotspot_y = hotspot.y; + + if (!drm->iface->crtc_move_cursor(drm, conn->crtc, conn->cursor_x, + conn->cursor_y)) { + return false; + } + + wlr_output_update_needs_swap(output); + } + + if (!update_texture) { + // Don't update cursor image + return true; + } + + plane->cursor_enabled = false; + if (texture != NULL) { + int width, height; + wlr_texture_get_size(texture, &width, &height); + width = width * output->scale / scale; + height = height * output->scale / scale; + + if (width > (int)plane->surf.width || height > (int)plane->surf.height) { + wlr_log(WLR_ERROR, "Cursor too large (max %dx%d)", + (int)plane->surf.width, (int)plane->surf.height); + return false; + } + + uint32_t bo_width = gbm_bo_get_width(plane->cursor_bo); + uint32_t bo_height = gbm_bo_get_height(plane->cursor_bo); + + uint32_t bo_stride; + void *bo_data; + if (!gbm_bo_map(plane->cursor_bo, 0, 0, bo_width, bo_height, + GBM_BO_TRANSFER_WRITE, &bo_stride, &bo_data)) { + wlr_log_errno(WLR_ERROR, "Unable to map buffer"); + return false; + } + + make_drm_surface_current(&plane->surf, NULL); + + struct wlr_renderer *rend = plane->surf.renderer->wlr_rend; + + struct wlr_box cursor_box = { .width = width, .height = height }; + + float matrix[9]; + wlr_matrix_project_box(matrix, &cursor_box, transform, 0, plane->matrix); + + wlr_renderer_begin(rend, plane->surf.width, plane->surf.height); + wlr_renderer_clear(rend, (float[]){ 0.0, 0.0, 0.0, 0.0 }); + wlr_render_texture_with_matrix(rend, texture, matrix, 1.0); + wlr_renderer_end(rend); + + wlr_renderer_read_pixels(rend, WL_SHM_FORMAT_ARGB8888, NULL, bo_stride, + plane->surf.width, plane->surf.height, 0, 0, 0, 0, bo_data); + + swap_drm_surface_buffers(&plane->surf, NULL); + + gbm_bo_unmap(plane->cursor_bo, bo_data); + + plane->cursor_enabled = true; + } + + if (!drm->session->active) { + return true; // will be committed when session is resumed + } + + struct gbm_bo *bo = plane->cursor_enabled ? plane->cursor_bo : NULL; + bool ok = drm->iface->crtc_set_cursor(drm, crtc, bo); + if (ok) { + wlr_output_update_needs_swap(output); + } + return ok; +} + +static bool drm_connector_move_cursor(struct wlr_output *output, + int x, int y) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + if (!conn->crtc) { + return false; + } + struct wlr_drm_plane *plane = conn->crtc->cursor; + + struct wlr_box box = { .x = x, .y = y }; + + int width, height; + wlr_output_transformed_resolution(output, &width, &height); + + enum wl_output_transform transform = + wlr_output_transform_invert(output->transform); + wlr_box_transform(&box, &box, transform, width, height); + + if (plane != NULL) { + box.x -= plane->cursor_hotspot_x; + box.y -= plane->cursor_hotspot_y; + } + + conn->cursor_x = box.x; + conn->cursor_y = box.y; + + if (!drm->session->active) { + return true; // will be committed when session is resumed + } + + bool ok = drm->iface->crtc_move_cursor(drm, conn->crtc, box.x, box.y); + if (ok) { + wlr_output_update_needs_swap(output); + } + return ok; +} + +static bool 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 false; + } + + // 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 false; + } + 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 true; + } + if (drm->parent) { + bo = copy_drm_surface_mgpu(&plane->mgpu_surf, bo); + } + 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 true; + } + + if (!drm->iface->crtc_pageflip(drm, conn, crtc, fb_id, NULL)) { + return false; + } + + conn->pageflip_pending = true; + wlr_output_update_enabled(output, true); + return 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); + drmModeFreeCrtc(conn->old_crtc); + wl_event_source_remove(conn->retry_pageflip); + wl_list_remove(&conn->link); + free(conn); +} + +static const struct wlr_output_impl output_impl = { + .enable = enable_drm_connector, + .set_mode = drm_connector_set_mode, + .transform = drm_connector_transform, + .set_cursor = drm_connector_set_cursor, + .move_cursor = drm_connector_move_cursor, + .destroy = drm_connector_destroy, + .make_current = drm_connector_make_current, + .swap_buffers = drm_connector_swap_buffers, + .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) { + return output->impl == &output_impl; +} + +static int retry_pageflip(void *data) { + struct wlr_drm_connector *conn = data; + wlr_log(WLR_INFO, "%s: Retrying pageflip", conn->output.name); + drm_connector_start_renderer(conn); + return 0; +} + +static const int32_t subpixel_map[] = { + [DRM_MODE_SUBPIXEL_UNKNOWN] = WL_OUTPUT_SUBPIXEL_UNKNOWN, + [DRM_MODE_SUBPIXEL_HORIZONTAL_RGB] = WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB, + [DRM_MODE_SUBPIXEL_HORIZONTAL_BGR] = WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR, + [DRM_MODE_SUBPIXEL_VERTICAL_RGB] = WL_OUTPUT_SUBPIXEL_VERTICAL_RGB, + [DRM_MODE_SUBPIXEL_VERTICAL_BGR] = WL_OUTPUT_SUBPIXEL_VERTICAL_BGR, + [DRM_MODE_SUBPIXEL_NONE] = WL_OUTPUT_SUBPIXEL_NONE, +}; + +static void dealloc_crtc(struct wlr_drm_connector *conn) { + struct wlr_drm_backend *drm = + get_drm_backend_from_backend(conn->output.backend); + if (conn->crtc == NULL) { + return; + } + + wlr_log(WLR_DEBUG, "De-allocating CRTC %zu for output '%s'", + conn->crtc - drm->crtcs, conn->output.name); + + set_drm_connector_gamma(&conn->output, 0, NULL, NULL, NULL); + + for (size_t type = 0; type < 3; ++type) { + struct wlr_drm_plane *plane = conn->crtc->planes[type]; + if (plane == NULL) { + continue; + } + + finish_drm_surface(&plane->surf); + conn->crtc->planes[type] = NULL; + } + + drm->iface->conn_enable(drm, conn, false); + + conn->crtc = NULL; +} + +static void realloc_crtcs(struct wlr_drm_backend *drm, bool *changed_outputs) { + size_t num_outputs = wl_list_length(&drm->outputs); + + if (changed_outputs == NULL) { + changed_outputs = calloc(num_outputs, sizeof(bool)); + if (changed_outputs == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + return; + } + } + + wlr_log(WLR_DEBUG, "Reallocating CRTCs"); + + uint32_t crtc[drm->num_crtcs + 1]; + for (size_t i = 0; i < drm->num_crtcs; ++i) { + crtc[i] = UNMATCHED; + } + + struct wlr_drm_connector *connectors[num_outputs + 1]; + + uint32_t possible_crtc[num_outputs + 1]; + memset(possible_crtc, 0, sizeof(possible_crtc)); + + wlr_log(WLR_DEBUG, "State before reallocation:"); + ssize_t i = -1; + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link) { + i++; + connectors[i] = conn; + + wlr_log(WLR_DEBUG, " '%s' crtc=%d state=%d desired_enabled=%d", + conn->output.name, + conn->crtc ? (int)(conn->crtc - drm->crtcs) : -1, + conn->state, conn->desired_enabled); + + if (conn->crtc) { + crtc[conn->crtc - drm->crtcs] = i; + } + + // Only search CRTCs for user-enabled outputs (that are already + // connected or in need of a modeset) + if ((conn->state == WLR_DRM_CONN_CONNECTED || + conn->state == WLR_DRM_CONN_NEEDS_MODESET) && + conn->desired_enabled) { + possible_crtc[i] = conn->possible_crtc; + } + } + + uint32_t crtc_res[drm->num_crtcs + 1]; + match_obj(wl_list_length(&drm->outputs), possible_crtc, + drm->num_crtcs, crtc, crtc_res); + + bool matched[num_outputs + 1]; + memset(matched, false, sizeof(matched)); + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (crtc_res[i] != UNMATCHED) { + matched[crtc_res[i]] = true; + } + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + // We don't want any of the current monitors to be deactivated + if (crtc[i] != UNMATCHED && !matched[crtc[i]] && + connectors[crtc[i]]->desired_enabled) { + wlr_log(WLR_DEBUG, "Could not match a CRTC for connected output %d", + crtc[i]); + return; + } + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (crtc_res[i] == crtc[i]) { + continue; + } + + // De-allocate this CRTC on previous output + if (crtc[i] != UNMATCHED) { + changed_outputs[crtc[i]] = true; + dealloc_crtc(connectors[crtc[i]]); + } + + // Assign this CRTC to next output + if (crtc_res[i] != UNMATCHED) { + changed_outputs[crtc_res[i]] = true; + + struct wlr_drm_connector *conn = connectors[crtc_res[i]]; + dealloc_crtc(conn); + conn->crtc = &drm->crtcs[i]; + + wlr_log(WLR_DEBUG, "Assigning CRTC %zu to output %d -> %d '%s'", + i, crtc[i], crtc_res[i], conn->output.name); + } + } + + wlr_log(WLR_DEBUG, "State after reallocation:"); + wl_list_for_each(conn, &drm->outputs, link) { + wlr_log(WLR_DEBUG, " '%s' crtc=%d state=%d desired_enabled=%d", + conn->output.name, + conn->crtc ? (int)(conn->crtc - drm->crtcs) : -1, + conn->state, conn->desired_enabled); + } + + realloc_planes(drm, crtc_res, changed_outputs); + + // We need to reinitialize any plane that has changed + i = -1; + wl_list_for_each(conn, &drm->outputs, link) { + i++; + struct wlr_output_mode *mode = conn->output.current_mode; + + if (conn->state != WLR_DRM_CONN_CONNECTED || !changed_outputs[i] + || conn->crtc == NULL) { + continue; + } + + if (!init_drm_plane_surfaces(conn->crtc->primary, drm, + mode->width, mode->height, GBM_FORMAT_XRGB8888)) { + wlr_log(WLR_ERROR, "Failed to initialize renderer for plane"); + drm_connector_cleanup(conn); + break; + } + + drm_connector_start_renderer(conn); + + wlr_output_damage_whole(&conn->output); + } +} + +static uint32_t get_possible_crtcs(int fd, drmModeRes *res, + drmModeConnector *conn, bool is_mst) { + uint32_t ret = 0; + + for (int i = 0; i < conn->count_encoders; ++i) { + drmModeEncoder *enc = drmModeGetEncoder(fd, conn->encoders[i]); + if (!enc) { + continue; + } + + ret |= enc->possible_crtcs; + + drmModeFreeEncoder(enc); + } + + // Sometimes DP MST connectors report no encoders, so we'll loop though + // all of the encoders of the MST type instead. + // TODO: See if there is a better solution. + + if (!is_mst || ret) { + return ret; + } + + for (int i = 0; i < res->count_encoders; ++i) { + drmModeEncoder *enc = drmModeGetEncoder(fd, res->encoders[i]); + if (!enc) { + continue; + } + + if (enc->encoder_type == DRM_MODE_ENCODER_DPMST) { + ret |= enc->possible_crtcs; + } + + drmModeFreeEncoder(enc); + } + + return ret; +} + +void scan_drm_connectors(struct wlr_drm_backend *drm) { + wlr_log(WLR_INFO, "Scanning DRM connectors"); + + drmModeRes *res = drmModeGetResources(drm->fd); + if (!res) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM resources"); + return; + } + + size_t seen_len = wl_list_length(&drm->outputs); + // +1 so length can never be 0, which is undefined behaviour. + // Last element isn't used. + bool seen[seen_len + 1]; + memset(seen, false, sizeof(seen)); + size_t new_outputs_len = 0; + struct wlr_drm_connector *new_outputs[res->count_connectors + 1]; + + for (int i = 0; i < res->count_connectors; ++i) { + drmModeConnector *drm_conn = drmModeGetConnector(drm->fd, + res->connectors[i]); + if (!drm_conn) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM connector"); + continue; + } + drmModeEncoder *curr_enc = drmModeGetEncoder(drm->fd, + drm_conn->encoder_id); + + ssize_t index = -1; + struct wlr_drm_connector *c, *wlr_conn = NULL; + wl_list_for_each(c, &drm->outputs, link) { + index++; + if (c->id == drm_conn->connector_id) { + wlr_conn = c; + break; + } + } + + if (!wlr_conn) { + wlr_conn = calloc(1, sizeof(*wlr_conn)); + if (!wlr_conn) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + drmModeFreeEncoder(curr_enc); + drmModeFreeConnector(drm_conn); + continue; + } + wlr_output_init(&wlr_conn->output, &drm->backend, &output_impl, + drm->display); + + struct wl_event_loop *ev = wl_display_get_event_loop(drm->display); + wlr_conn->retry_pageflip = wl_event_loop_add_timer(ev, retry_pageflip, + wlr_conn); + + wlr_conn->state = WLR_DRM_CONN_DISCONNECTED; + wlr_conn->id = drm_conn->connector_id; + + snprintf(wlr_conn->output.name, sizeof(wlr_conn->output.name), + "%s-%"PRIu32, conn_get_name(drm_conn->connector_type), + drm_conn->connector_type_id); + + if (curr_enc) { + wlr_conn->old_crtc = drmModeGetCrtc(drm->fd, curr_enc->crtc_id); + } + + wl_list_insert(drm->outputs.prev, &wlr_conn->link); + wlr_log(WLR_INFO, "Found connector '%s'", wlr_conn->output.name); + } else { + seen[index] = true; + } + + if (curr_enc) { + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (drm->crtcs[i].id == curr_enc->crtc_id) { + wlr_conn->crtc = &drm->crtcs[i]; + break; + } + } + } else { + wlr_conn->crtc = NULL; + } + + // This can only happen *after* hotplug, since we haven't read the + // connector properties yet + if (wlr_conn->props.link_status != 0) { + uint64_t link_status; + if (!get_drm_prop(drm->fd, wlr_conn->id, + wlr_conn->props.link_status, &link_status)) { + wlr_log(WLR_ERROR, "Failed to get link status for '%s'", + wlr_conn->output.name); + continue; + } + + if (link_status == DRM_MODE_LINK_STATUS_BAD) { + // We need to reload our list of modes and force a modeset + wlr_log(WLR_INFO, "Bad link for '%s'", wlr_conn->output.name); + drm_connector_cleanup(wlr_conn); + } + } + + if (wlr_conn->state == WLR_DRM_CONN_DISCONNECTED && + drm_conn->connection == DRM_MODE_CONNECTED) { + wlr_log(WLR_INFO, "'%s' connected", wlr_conn->output.name); + wlr_log(WLR_DEBUG, "Current CRTC: %d", + wlr_conn->crtc ? (int)wlr_conn->crtc->id : -1); + + wlr_conn->output.phys_width = drm_conn->mmWidth; + wlr_conn->output.phys_height = drm_conn->mmHeight; + wlr_log(WLR_INFO, "Physical size: %"PRId32"x%"PRId32, + wlr_conn->output.phys_width, wlr_conn->output.phys_height); + wlr_conn->output.subpixel = subpixel_map[drm_conn->subpixel]; + + get_drm_connector_props(drm->fd, wlr_conn->id, &wlr_conn->props); + + 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->output, edid_len, edid); + free(edid); + + wlr_log(WLR_INFO, "Detected modes:"); + + for (int i = 0; i < drm_conn->count_modes; ++i) { + struct wlr_drm_mode *mode = calloc(1, sizeof(*mode)); + if (!mode) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + continue; + } + + if (drm_conn->modes[i].flags & DRM_MODE_FLAG_INTERLACE) { + free(mode); + continue; + } + + mode->drm_mode = drm_conn->modes[i]; + mode->wlr_mode.width = mode->drm_mode.hdisplay; + mode->wlr_mode.height = mode->drm_mode.vdisplay; + mode->wlr_mode.refresh = calculate_refresh_rate(&mode->drm_mode); + + wlr_log(WLR_INFO, " %"PRId32"x%"PRId32"@%"PRId32, + mode->wlr_mode.width, mode->wlr_mode.height, + mode->wlr_mode.refresh); + + wl_list_insert(&wlr_conn->output.modes, &mode->wlr_mode.link); + } + + wlr_conn->possible_crtc = get_possible_crtcs(drm->fd, res, drm_conn, + wlr_conn->props.path != 0); + if (wlr_conn->possible_crtc == 0) { + wlr_log(WLR_ERROR, "No CRTC possible for connector '%s'", + wlr_conn->output.name); + } + + wlr_output_update_enabled(&wlr_conn->output, wlr_conn->crtc != NULL); + wlr_conn->desired_enabled = true; + + wlr_conn->state = WLR_DRM_CONN_NEEDS_MODESET; + new_outputs[new_outputs_len++] = wlr_conn; + } else if ((wlr_conn->state == WLR_DRM_CONN_CONNECTED || + wlr_conn->state == WLR_DRM_CONN_NEEDS_MODESET) && + drm_conn->connection != DRM_MODE_CONNECTED) { + wlr_log(WLR_INFO, "'%s' disconnected", wlr_conn->output.name); + + drm_connector_cleanup(wlr_conn); + } + + drmModeFreeEncoder(curr_enc); + drmModeFreeConnector(drm_conn); + } + + drmModeFreeResources(res); + + // Iterate in reverse order because we'll remove items from the list and + // still want indices to remain correct. + struct wlr_drm_connector *conn, *tmp_conn; + size_t index = wl_list_length(&drm->outputs); + wl_list_for_each_reverse_safe(conn, tmp_conn, &drm->outputs, link) { + index--; + if (index >= seen_len || seen[index]) { + continue; + } + + wlr_log(WLR_INFO, "'%s' disappeared", conn->output.name); + drm_connector_cleanup(conn); + + if (conn->pageflip_pending) { + conn->state = WLR_DRM_CONN_DISAPPEARED; + } else { + wlr_output_destroy(&conn->output); + } + } + + 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]; + + ssize_t pos = -1; + struct wlr_drm_connector *c; + wl_list_for_each(c, &drm->outputs, link) { + ++pos; + if (c == conn) { + break; + } + } + assert(pos >= 0); + + changed_outputs[pos] = true; + } + + realloc_crtcs(drm, changed_outputs); + + for (size_t i = 0; i < new_outputs_len; ++i) { + struct wlr_drm_connector *conn = new_outputs[i]; + + wlr_log(WLR_INFO, "Requesting modeset for '%s'", + conn->output.name); + wlr_signal_emit_safe(&drm->backend.events.new_output, + &conn->output); + } + + 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 *data) { + struct wlr_drm_connector *conn = data; + struct wlr_drm_backend *drm = + get_drm_backend_from_backend(conn->output.backend); + + conn->pageflip_pending = false; + + if (conn->state == WLR_DRM_CONN_DISAPPEARED) { + wlr_output_destroy(&conn->output); + return; + } + + if (conn->state != WLR_DRM_CONN_CONNECTED || conn->crtc == NULL) { + return; + } + + post_drm_surface(&conn->crtc->primary->surf); + if (drm->parent) { + 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); + } +} + +int handle_drm_event(int fd, uint32_t mask, void *data) { + drmEventContext event = { + .version = 2, + .page_flip_handler = page_flip_handler, + }; + + drmHandleEvent(fd, &event); + return 1; +} + +void restore_drm_outputs(struct wlr_drm_backend *drm) { + uint64_t to_close = (1L << wl_list_length(&drm->outputs)) - 1; + + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link) { + if (conn->state == WLR_DRM_CONN_CONNECTED) { + conn->state = WLR_DRM_CONN_CLEANUP; + } + } + + time_t timeout = time(NULL) + 5; + + while (to_close && time(NULL) < timeout) { + handle_drm_event(drm->fd, 0, NULL); + size_t i = 0; + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link) { + if (conn->state != WLR_DRM_CONN_CLEANUP || !conn->pageflip_pending) { + to_close &= ~(1 << i); + } + i++; + } + } + + if (to_close) { + wlr_log(WLR_ERROR, "Timed out stopping output renderers"); + } + + wl_list_for_each(conn, &drm->outputs, link) { + drmModeCrtc *crtc = conn->old_crtc; + if (!crtc) { + continue; + } + + drmModeSetCrtc(drm->fd, crtc->crtc_id, crtc->buffer_id, crtc->x, crtc->y, + &conn->id, 1, &crtc->mode); + } +} + +static void drm_connector_cleanup(struct wlr_drm_connector *conn) { + if (!conn) { + return; + } + + switch (conn->state) { + case WLR_DRM_CONN_CONNECTED: + case WLR_DRM_CONN_CLEANUP:; + struct wlr_drm_crtc *crtc = conn->crtc; + if (crtc != NULL) { + for (int i = 0; i < 3; ++i) { + if (!crtc->planes[i]) { + continue; + } + + finish_drm_surface(&crtc->planes[i]->surf); + finish_drm_surface(&crtc->planes[i]->mgpu_surf); + if (crtc->planes[i]->id == 0) { + free(crtc->planes[i]); + crtc->planes[i] = NULL; + } + } + } + + conn->output.current_mode = NULL; + conn->desired_mode = NULL; + struct wlr_drm_mode *mode, *tmp; + wl_list_for_each_safe(mode, tmp, &conn->output.modes, wlr_mode.link) { + wl_list_remove(&mode->wlr_mode.link); + free(mode); + } + + conn->output.enabled = false; + conn->output.width = conn->output.height = conn->output.refresh = 0; + + memset(&conn->output.make, 0, sizeof(conn->output.make)); + memset(&conn->output.model, 0, sizeof(conn->output.model)); + memset(&conn->output.serial, 0, sizeof(conn->output.serial)); + + if (conn->output.idle_frame != NULL) { + wl_event_source_remove(conn->output.idle_frame); + conn->output.idle_frame = NULL; + } + conn->output.needs_swap = false; + conn->output.frame_pending = false; + + /* Fallthrough */ + case WLR_DRM_CONN_NEEDS_MODESET: + wlr_log(WLR_INFO, "Emitting destruction signal for '%s'", + conn->output.name); + dealloc_crtc(conn); + conn->possible_crtc = 0; + conn->desired_mode = NULL; + wlr_signal_emit_safe(&conn->output.events.destroy, &conn->output); + break; + case WLR_DRM_CONN_DISCONNECTED: + break; + case WLR_DRM_CONN_DISAPPEARED: + return; // don't change state + } + + conn->state = WLR_DRM_CONN_DISCONNECTED; +} diff --git a/backend/drm/legacy.c b/backend/drm/legacy.c new file mode 100644 index 00000000..182c7a95 --- /dev/null +++ b/backend/drm/legacy.c @@ -0,0 +1,83 @@ +#include <gbm.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include "backend/drm/drm.h" +#include "backend/drm/iface.h" +#include "backend/drm/util.h" + +static bool legacy_crtc_pageflip(struct wlr_drm_backend *drm, + struct wlr_drm_connector *conn, struct wlr_drm_crtc *crtc, + uint32_t fb_id, drmModeModeInfo *mode) { + if (mode) { + if (drmModeSetCrtc(drm->fd, crtc->id, fb_id, 0, 0, + &conn->id, 1, mode)) { + wlr_log_errno(WLR_ERROR, "%s: Failed to set CRTC", conn->output.name); + return false; + } + } + + if (drmModePageFlip(drm->fd, crtc->id, fb_id, DRM_MODE_PAGE_FLIP_EVENT, conn)) { + wlr_log_errno(WLR_ERROR, "%s: Failed to page flip", conn->output.name); + return false; + } + + return true; +} + +static bool legacy_conn_enable(struct wlr_drm_backend *drm, + struct wlr_drm_connector *conn, bool enable) { + int ret = drmModeConnectorSetProperty(drm->fd, conn->id, conn->props.dpms, + enable ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF); + return ret >= 0; +} + +bool legacy_crtc_set_cursor(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, struct gbm_bo *bo) { + if (!crtc || !crtc->cursor) { + return true; + } + + if (!bo) { + if (drmModeSetCursor(drm->fd, crtc->id, 0, 0, 0)) { + wlr_log_errno(WLR_DEBUG, "Failed to clear hardware cursor"); + return false; + } + return true; + } + + struct wlr_drm_plane *plane = crtc->cursor; + + if (drmModeSetCursor(drm->fd, crtc->id, gbm_bo_get_handle(bo).u32, + plane->surf.width, plane->surf.height)) { + wlr_log_errno(WLR_DEBUG, "Failed to set hardware cursor"); + return false; + } + + return true; +} + +bool legacy_crtc_move_cursor(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, int x, int y) { + return !drmModeMoveCursor(drm->fd, crtc->id, x, y); +} + +bool legacy_crtc_set_gamma(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, size_t size, + uint16_t *r, uint16_t *g, uint16_t *b) { + return !drmModeCrtcSetGamma(drm->fd, crtc->id, (uint32_t)size, r, g, b); +} + +size_t legacy_crtc_get_gamma_size(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc) { + return (size_t)crtc->legacy_crtc->gamma_size; +} + +const struct wlr_drm_interface legacy_iface = { + .conn_enable = legacy_conn_enable, + .crtc_pageflip = legacy_crtc_pageflip, + .crtc_set_cursor = legacy_crtc_set_cursor, + .crtc_move_cursor = legacy_crtc_move_cursor, + .crtc_set_gamma = legacy_crtc_set_gamma, + .crtc_get_gamma_size = legacy_crtc_get_gamma_size, +}; diff --git a/backend/drm/properties.c b/backend/drm/properties.c new file mode 100644 index 00000000..5541d1be --- /dev/null +++ b/backend/drm/properties.c @@ -0,0 +1,151 @@ +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include "backend/drm/properties.h" + +/* + * Creates a mapping between property names and an array index where to store + * the ids. The prop_info arrays must be sorted by name, as bsearch is used to + * search them. + */ +struct prop_info { + const char *name; + size_t index; +}; + +static const struct prop_info connector_info[] = { +#define INDEX(name) (offsetof(union wlr_drm_connector_props, name) / sizeof(uint32_t)) + { "CRTC_ID", INDEX(crtc_id) }, + { "DPMS", INDEX(dpms) }, + { "EDID", INDEX(edid) }, + { "PATH", INDEX(path) }, + { "link-status", INDEX(link_status) }, +#undef INDEX +}; + +static const struct prop_info crtc_info[] = { +#define INDEX(name) (offsetof(union wlr_drm_crtc_props, name) / sizeof(uint32_t)) + { "ACTIVE", INDEX(active) }, + { "GAMMA_LUT", INDEX(gamma_lut) }, + { "GAMMA_LUT_SIZE", INDEX(gamma_lut_size) }, + { "MODE_ID", INDEX(mode_id) }, + { "rotation", INDEX(rotation) }, + { "scaling mode", INDEX(scaling_mode) }, +#undef INDEX +}; + +static const struct prop_info plane_info[] = { +#define INDEX(name) (offsetof(union wlr_drm_plane_props, name) / sizeof(uint32_t)) + { "CRTC_H", INDEX(crtc_h) }, + { "CRTC_ID", INDEX(crtc_id) }, + { "CRTC_W", INDEX(crtc_w) }, + { "CRTC_X", INDEX(crtc_x) }, + { "CRTC_Y", INDEX(crtc_y) }, + { "FB_ID", INDEX(fb_id) }, + { "SRC_H", INDEX(src_h) }, + { "SRC_W", INDEX(src_w) }, + { "SRC_X", INDEX(src_x) }, + { "SRC_Y", INDEX(src_y) }, + { "type", INDEX(type) }, +#undef INDEX +}; + +static int cmp_prop_info(const void *arg1, const void *arg2) { + const char *key = arg1; + const struct prop_info *elem = arg2; + + return strcmp(key, elem->name); +} + +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"); + 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"); + continue; + } + + const struct prop_info *p = + bsearch(prop->name, info, info_len, sizeof(info[0]), cmp_prop_info); + if (p) { + result[p->index] = prop->prop_id; + } + + drmModeFreeProperty(prop); + } + + drmModeFreeObjectProperties(props); + return true; +} + +bool get_drm_connector_props(int fd, uint32_t id, + union wlr_drm_connector_props *out) { + return scan_properties(fd, id, DRM_MODE_OBJECT_CONNECTOR, out->props, + connector_info, sizeof(connector_info) / sizeof(connector_info[0])); +} + +bool get_drm_crtc_props(int fd, uint32_t id, union wlr_drm_crtc_props *out) { + return scan_properties(fd, id, DRM_MODE_OBJECT_CRTC, out->props, + crtc_info, sizeof(crtc_info) / sizeof(crtc_info[0])); +} + +bool get_drm_plane_props(int fd, uint32_t id, union wlr_drm_plane_props *out) { + return scan_properties(fd, id, DRM_MODE_OBJECT_PLANE, out->props, + plane_info, sizeof(plane_info) / sizeof(plane_info[0])); +} + +bool get_drm_prop(int fd, uint32_t obj, uint32_t prop, uint64_t *ret) { + drmModeObjectProperties *props = + drmModeObjectGetProperties(fd, obj, DRM_MODE_OBJECT_ANY); + if (!props) { + return false; + } + + bool found = false; + + for (uint32_t i = 0; i < props->count_props; ++i) { + if (props->props[i] == prop) { + *ret = props->prop_values[i]; + found = true; + break; + } + } + + drmModeFreeObjectProperties(props); + return found; +} + +void *get_drm_prop_blob(int fd, uint32_t obj, uint32_t prop, size_t *ret_len) { + uint64_t blob_id; + if (!get_drm_prop(fd, obj, prop, &blob_id)) { + return NULL; + } + + drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(fd, blob_id); + if (!blob) { + return NULL; + } + + void *ptr = malloc(blob->length); + if (!ptr) { + drmModeFreePropertyBlob(blob); + return NULL; + } + + memcpy(ptr, blob->data, blob->length); + *ret_len = blob->length; + + drmModeFreePropertyBlob(blob); + return ptr; +} diff --git a/backend/drm/renderer.c b/backend/drm/renderer.c new file mode 100644 index 00000000..70b1bcbe --- /dev/null +++ b/backend/drm/renderer.c @@ -0,0 +1,264 @@ +#include <assert.h> +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <gbm.h> +#include <stdbool.h> +#include <stdlib.h> +#include <unistd.h> +#include <wayland-util.h> +#include <wlr/render/egl.h> +#include <wlr/render/gles2.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/util/log.h> +#include "backend/drm/drm.h" +#include "glapi.h" + +#ifndef DRM_FORMAT_MOD_LINEAR +#define DRM_FORMAT_MOD_LINEAR 0 +#endif + +bool init_drm_renderer(struct wlr_drm_backend *drm, + struct wlr_drm_renderer *renderer, wlr_renderer_create_func_t create_renderer_func) { + renderer->gbm = gbm_create_device(drm->fd); + if (!renderer->gbm) { + wlr_log(WLR_ERROR, "Failed to create GBM device"); + return false; + } + + if (!create_renderer_func) { + create_renderer_func = wlr_renderer_autocreate; + } + + static EGLint config_attribs[] = { + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_ALPHA_SIZE, 1, + EGL_NONE, + }; + + renderer->wlr_rend = create_renderer_func(&renderer->egl, + EGL_PLATFORM_GBM_MESA, renderer->gbm, + config_attribs, GBM_FORMAT_ARGB8888); + + if (!renderer->wlr_rend) { + wlr_log(WLR_ERROR, "Failed to create EGL/WLR renderer"); + goto error_gbm; + } + + renderer->fd = drm->fd; + return true; + +error_gbm: + gbm_device_destroy(renderer->gbm); + return false; +} + +void finish_drm_renderer(struct wlr_drm_renderer *renderer) { + if (!renderer) { + return; + } + + wlr_renderer_destroy(renderer->wlr_rend); + wlr_egl_finish(&renderer->egl); + gbm_device_destroy(renderer->gbm); +} + +bool init_drm_surface(struct wlr_drm_surface *surf, + struct wlr_drm_renderer *renderer, uint32_t width, uint32_t height, + uint32_t format, uint32_t flags) { + if (surf->width == width && surf->height == height) { + return true; + } + + surf->renderer = renderer; + surf->width = width; + surf->height = height; + + if (surf->gbm) { + if (surf->front) { + gbm_surface_release_buffer(surf->gbm, surf->front); + surf->front = NULL; + } + if (surf->back) { + gbm_surface_release_buffer(surf->gbm, surf->back); + surf->back = NULL; + } + gbm_surface_destroy(surf->gbm); + } + wlr_egl_destroy_surface(&surf->renderer->egl, surf->egl); + + surf->gbm = gbm_surface_create(renderer->gbm, width, height, + format, GBM_BO_USE_RENDERING | flags); + if (!surf->gbm) { + wlr_log_errno(WLR_ERROR, "Failed to create GBM surface"); + goto error_zero; + } + + surf->egl = wlr_egl_create_surface(&renderer->egl, surf->gbm); + if (surf->egl == EGL_NO_SURFACE) { + wlr_log(WLR_ERROR, "Failed to create EGL surface"); + goto error_gbm; + } + + return true; + +error_gbm: + gbm_surface_destroy(surf->gbm); +error_zero: + memset(surf, 0, sizeof(*surf)); + return false; +} + +void finish_drm_surface(struct wlr_drm_surface *surf) { + if (!surf || !surf->renderer) { + return; + } + + if (surf->front) { + gbm_surface_release_buffer(surf->gbm, surf->front); + } + if (surf->back) { + gbm_surface_release_buffer(surf->gbm, surf->back); + } + + wlr_egl_destroy_surface(&surf->renderer->egl, surf->egl); + if (surf->gbm) { + gbm_surface_destroy(surf->gbm); + } + + memset(surf, 0, sizeof(*surf)); +} + +bool make_drm_surface_current(struct wlr_drm_surface *surf, + int *buffer_damage) { + return wlr_egl_make_current(&surf->renderer->egl, surf->egl, buffer_damage); +} + +struct gbm_bo *swap_drm_surface_buffers(struct wlr_drm_surface *surf, + pixman_region32_t *damage) { + if (surf->front) { + gbm_surface_release_buffer(surf->gbm, surf->front); + } + + wlr_egl_swap_buffers(&surf->renderer->egl, surf->egl, damage); + + surf->front = surf->back; + surf->back = gbm_surface_lock_front_buffer(surf->gbm); + return surf->back; +} + +struct gbm_bo *get_drm_surface_front(struct wlr_drm_surface *surf) { + if (surf->front) { + return surf->front; + } + + make_drm_surface_current(surf, NULL); + struct wlr_renderer *renderer = surf->renderer->wlr_rend; + wlr_renderer_begin(renderer, surf->width, surf->height); + wlr_renderer_clear(renderer, (float[]){ 0.0, 0.0, 0.0, 1.0 }); + wlr_renderer_end(renderer); + return swap_drm_surface_buffers(surf, NULL); +} + +void post_drm_surface(struct wlr_drm_surface *surf) { + if (surf->front) { + gbm_surface_release_buffer(surf->gbm, surf->front); + surf->front = NULL; + } +} + +bool export_drm_bo(struct gbm_bo *bo, struct wlr_dmabuf_attributes *attribs) { + memset(attribs, 0, sizeof(struct wlr_dmabuf_attributes)); + + attribs->n_planes = gbm_bo_get_plane_count(bo); + if (attribs->n_planes > WLR_DMABUF_MAX_PLANES) { + return false; + } + + attribs->width = gbm_bo_get_width(bo); + attribs->height = gbm_bo_get_height(bo); + attribs->format = gbm_bo_get_format(bo); + attribs->modifier = gbm_bo_get_modifier(bo); + + for (int i = 0; i < attribs->n_planes; ++i) { + attribs->offset[i] = gbm_bo_get_offset(bo, i); + attribs->stride[i] = gbm_bo_get_stride_for_plane(bo, i); + attribs->fd[i] = gbm_bo_get_fd(bo); + if (attribs->fd[i] < 0) { + for (int j = 0; j < i; ++j) { + close(attribs->fd[j]); + } + return false; + } + } + + return true; +} + +static void free_tex(struct gbm_bo *bo, void *data) { + struct wlr_texture *tex = data; + wlr_texture_destroy(tex); +} + +static struct wlr_texture *get_tex_for_bo(struct wlr_drm_renderer *renderer, + struct gbm_bo *bo) { + struct wlr_texture *tex = gbm_bo_get_user_data(bo); + if (tex) { + return tex; + } + + struct wlr_dmabuf_attributes attribs; + if (!export_drm_bo(bo, &attribs)) { + return NULL; + } + + tex = wlr_texture_from_dmabuf(renderer->wlr_rend, &attribs); + if (tex) { + gbm_bo_set_user_data(bo, tex, free_tex); + } + + return tex; +} + +struct gbm_bo *copy_drm_surface_mgpu(struct wlr_drm_surface *dest, + struct gbm_bo *src) { + make_drm_surface_current(dest, NULL); + + struct wlr_texture *tex = get_tex_for_bo(dest->renderer, src); + assert(tex); + + float mat[9]; + wlr_matrix_projection(mat, 1, 1, WL_OUTPUT_TRANSFORM_NORMAL); + + struct wlr_renderer *renderer = dest->renderer->wlr_rend; + wlr_renderer_begin(renderer, dest->width, dest->height); + wlr_renderer_clear(renderer, (float[]){ 0.0, 0.0, 0.0, 1.0 }); + wlr_render_texture_with_matrix(renderer, tex, mat, 1.0f); + wlr_renderer_end(renderer); + + return swap_drm_surface_buffers(dest, NULL); +} + +bool init_drm_plane_surfaces(struct wlr_drm_plane *plane, + struct wlr_drm_backend *drm, int32_t width, uint32_t height, + uint32_t format) { + if (!drm->parent) { + return init_drm_surface(&plane->surf, &drm->renderer, width, height, + format, GBM_BO_USE_SCANOUT); + } + + if (!init_drm_surface(&plane->surf, &drm->parent->renderer, + width, height, format, GBM_BO_USE_LINEAR)) { + return false; + } + + if (!init_drm_surface(&plane->mgpu_surf, &drm->renderer, + width, height, format, GBM_BO_USE_SCANOUT)) { + finish_drm_surface(&plane->surf); + return false; + } + + return true; +} diff --git a/backend/drm/util.c b/backend/drm/util.c new file mode 100644 index 00000000..6f2dd5be --- /dev/null +++ b/backend/drm/util.c @@ -0,0 +1,348 @@ +#include <drm_mode.h> +#include <drm.h> +#include <gbm.h> +#include <stdio.h> +#include <string.h> +#include <wlr/util/log.h> +#include "backend/drm/util.h" + +int32_t calculate_refresh_rate(const drmModeModeInfo *mode) { + int32_t refresh = (mode->clock * 1000000LL / mode->htotal + + mode->vtotal / 2) / mode->vtotal; + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) { + refresh *= 2; + } + + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) { + refresh /= 2; + } + + if (mode->vscan > 1) { + refresh /= mode->vscan; + } + + return refresh; +} + +// Constructed from http://edid.tv/manufacturer +static const char *get_manufacturer(uint16_t id) { +#define ID(a, b, c) ((a & 0x1f) << 10) | ((b & 0x1f) << 5) | (c & 0x1f) + switch (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"; + case ID('M', 'E', 'G'): return "Abeam Tech Ltd"; + case ID('M', 'E', 'I'): return "Panasonic Industry Company"; + 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"; + case ID('O', 'V', 'R'): return "Oculus VR, Inc."; + case ID('P', 'H', 'L'): return "Philips Consumer Electronics Company"; + 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"; + default: return "Unknown"; + } +#undef ID +} + +/* See https://en.wikipedia.org/wiki/Extended_Display_Identification_Data for layout of EDID data. + * We don't parse the EDID properly. We just expect to receive valid data. + */ +void parse_edid(struct wlr_output *restrict output, size_t len, const uint8_t *data) { + if (!data || len < 128) { + snprintf(output->make, sizeof(output->make), "<Unknown>"); + snprintf(output->model, sizeof(output->model), "<Unknown>"); + return; + } + + uint16_t id = (data[8] << 8) | data[9]; + snprintf(output->make, sizeof(output->make), "%s", get_manufacturer(id)); + + uint16_t model = data[10] | (data[11] << 8); + snprintf(output->model, sizeof(output->model), "0x%04X", model); + + uint32_t serial = data[12] | (data[13] << 8) | (data[14] << 8) | (data[15] << 8); + snprintf(output->serial, sizeof(output->serial), "0x%08X", serial); + + for (size_t i = 72; i <= 108; i += 18) { + uint16_t flag = (data[i] << 8) | data[i + 1]; + if (flag == 0 && data[i + 3] == 0xFC) { + sprintf(output->model, "%.13s", &data[i + 5]); + + // Monitor names are terminated by newline if they're too short + char *nl = strchr(output->model, '\n'); + if (nl) { + *nl = '\0'; + } + } else if (flag == 0 && data[i + 3] == 0xFF) { + sprintf(output->serial, "%.13s", &data[i + 5]); + + // Monitor serial numbers are terminated by newline if they're too + // short + char *nl = strchr(output->serial, '\n'); + if (nl) { + *nl = '\0'; + } + } + } +} + +const char *conn_get_name(uint32_t type_id) { + switch (type_id) { + case DRM_MODE_CONNECTOR_Unknown: return "Unknown"; + case DRM_MODE_CONNECTOR_VGA: return "VGA"; + case DRM_MODE_CONNECTOR_DVII: return "DVI-I"; + case DRM_MODE_CONNECTOR_DVID: return "DVI-D"; + case DRM_MODE_CONNECTOR_DVIA: return "DVI-A"; + case DRM_MODE_CONNECTOR_Composite: return "Composite"; + case DRM_MODE_CONNECTOR_SVIDEO: return "SVIDEO"; + case DRM_MODE_CONNECTOR_LVDS: return "LVDS"; + case DRM_MODE_CONNECTOR_Component: return "Component"; + case DRM_MODE_CONNECTOR_9PinDIN: return "DIN"; + case DRM_MODE_CONNECTOR_DisplayPort: return "DP"; + case DRM_MODE_CONNECTOR_HDMIA: return "HDMI-A"; + case DRM_MODE_CONNECTOR_HDMIB: return "HDMI-B"; + case DRM_MODE_CONNECTOR_TV: return "TV"; + case DRM_MODE_CONNECTOR_eDP: return "eDP"; + case DRM_MODE_CONNECTOR_VIRTUAL: return "Virtual"; + case DRM_MODE_CONNECTOR_DSI: return "DSI"; +#ifdef DRM_MODE_CONNECTOR_DPI + case DRM_MODE_CONNECTOR_DPI: return "DPI"; +#endif + default: return "Unknown"; + } +} + +static void free_fb(struct gbm_bo *bo, void *data) { + uint32_t id = (uintptr_t)data; + + if (id) { + struct gbm_device *gbm = gbm_bo_get_device(bo); + drmModeRmFB(gbm_device_get_fd(gbm), id); + } +} + +uint32_t get_fb_for_bo(struct gbm_bo *bo) { + uint32_t id = (uintptr_t)gbm_bo_get_user_data(bo); + if (id) { + return id; + } + + struct gbm_device *gbm = gbm_bo_get_device(bo); + + int fd = gbm_device_get_fd(gbm); + uint32_t width = gbm_bo_get_width(bo); + uint32_t height = gbm_bo_get_height(bo); + uint32_t handles[4] = {gbm_bo_get_handle(bo).u32}; + uint32_t pitches[4] = {gbm_bo_get_stride(bo)}; + uint32_t offsets[4] = {gbm_bo_get_offset(bo, 0)}; + uint32_t format = gbm_bo_get_format(bo); + + if (drmModeAddFB2(fd, width, height, format, handles, pitches, offsets, &id, 0)) { + wlr_log_errno(WLR_ERROR, "Unable to add DRM framebuffer"); + } + + gbm_bo_set_user_data(bo, (void *)(uintptr_t)id, free_fb); + + return id; +} + +static inline bool is_taken(size_t n, const uint32_t arr[static n], uint32_t key) { + for (size_t i = 0; i < n; ++i) { + if (arr[i] == key) { + return true; + } + } + return false; +} + +/* + * Store all of the non-recursive state in a struct, so we aren't literally + * passing 12 arguments to a function. + */ +struct match_state { + const size_t num_objs; + const uint32_t *restrict objs; + const size_t num_res; + size_t score; + size_t replaced; + uint32_t *restrict res; + uint32_t *restrict best; + const uint32_t *restrict orig; + bool exit_early; +}; + +/* + * skips: The number of SKIP elements encountered so far. + * score: The number of resources we've matched so far. + * replaced: The number of changes from the original solution. + * i: The index of the current element. + * + * 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) { + // Finished + if (i >= st->num_res) { + 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); + + st->exit_early = (st->score == st->num_res - skips + || st->score == st->num_objs) + && st->replaced == 0; + + return true; + } else { + return false; + } + } + + 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)) { + has_best = true; + } + } + if (st->exit_early) { + return true; + } + + if (st->orig[i] != UNMATCHED) { + ++replaced; + } + + for (size_t candidate = 0; candidate < st->num_objs; ++candidate) { + // We tried this earlier + if (candidate == st->orig[i]) { + continue; + } + + // Not compatible + if (!(st->objs[candidate] & (1 << i))) { + continue; + } + + // Already taken + if (is_taken(i, 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)) { + has_best = true; + } + + if (st->exit_early) { + return true; + } + } + + if (has_best) { + return true; + } + + // Maybe this resource can't be matched + st->res[i] = UNMATCHED; + return match_obj_(st, skips, score, replaced, i + 1); +} + +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) { + solution[i] = UNMATCHED; + } + + struct match_state st = { + .num_objs = num_objs, + .num_res = num_res, + .score = 0, + .replaced = SIZE_MAX, + .objs = objs, + .res = solution, + .best = out, + .orig = res, + .exit_early = false, + }; + + match_obj_(&st, 0, 0, 0, 0); + return st.score; +} diff --git a/backend/headless/backend.c b/backend/headless/backend.c new file mode 100644 index 00000000..c0fc6022 --- /dev/null +++ b/backend/headless/backend.c @@ -0,0 +1,132 @@ +#include <assert.h> +#include <stdlib.h> +#include <wlr/interfaces/wlr_input_device.h> +#include <wlr/interfaces/wlr_output.h> +#include <wlr/render/egl.h> +#include <wlr/render/gles2.h> +#include <wlr/util/log.h> +#include "backend/headless.h" +#include "glapi.h" +#include "util/signal.h" + +struct wlr_headless_backend *headless_backend_from_backend( + struct wlr_backend *wlr_backend) { + assert(wlr_backend_is_headless(wlr_backend)); + return (struct wlr_headless_backend *)wlr_backend; +} + +static bool backend_start(struct wlr_backend *wlr_backend) { + struct wlr_headless_backend *backend = + headless_backend_from_backend(wlr_backend); + wlr_log(WLR_INFO, "Starting headless backend"); + + struct wlr_headless_output *output; + wl_list_for_each(output, &backend->outputs, link) { + wl_event_source_timer_update(output->frame_timer, output->frame_delay); + wlr_output_update_enabled(&output->wlr_output, true); + wlr_signal_emit_safe(&backend->backend.events.new_output, + &output->wlr_output); + } + + struct wlr_headless_input_device *input_device; + wl_list_for_each(input_device, &backend->input_devices, + wlr_input_device.link) { + wlr_signal_emit_safe(&backend->backend.events.new_input, + &input_device->wlr_input_device); + } + + backend->started = true; + return true; +} + +static void backend_destroy(struct wlr_backend *wlr_backend) { + struct wlr_headless_backend *backend = + headless_backend_from_backend(wlr_backend); + if (!wlr_backend) { + return; + } + + wl_list_remove(&backend->display_destroy.link); + + struct wlr_headless_output *output, *output_tmp; + wl_list_for_each_safe(output, output_tmp, &backend->outputs, link) { + wlr_output_destroy(&output->wlr_output); + } + + struct wlr_headless_input_device *input_device, *input_device_tmp; + wl_list_for_each_safe(input_device, input_device_tmp, + &backend->input_devices, wlr_input_device.link) { + wlr_input_device_destroy(&input_device->wlr_input_device); + } + + wlr_signal_emit_safe(&wlr_backend->events.destroy, backend); + + wlr_renderer_destroy(backend->renderer); + wlr_egl_finish(&backend->egl); + free(backend); +} + +static struct wlr_renderer *backend_get_renderer( + struct wlr_backend *wlr_backend) { + struct wlr_headless_backend *backend = + headless_backend_from_backend(wlr_backend); + return backend->renderer; +} + +static const struct wlr_backend_impl backend_impl = { + .start = backend_start, + .destroy = backend_destroy, + .get_renderer = backend_get_renderer, +}; + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_headless_backend *backend = + wl_container_of(listener, backend, display_destroy); + backend_destroy(&backend->backend); +} + +struct wlr_backend *wlr_headless_backend_create(struct wl_display *display, + wlr_renderer_create_func_t create_renderer_func) { + wlr_log(WLR_INFO, "Creating headless backend"); + + struct wlr_headless_backend *backend = + calloc(1, sizeof(struct wlr_headless_backend)); + if (!backend) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_headless_backend"); + return NULL; + } + wlr_backend_init(&backend->backend, &backend_impl); + backend->display = display; + wl_list_init(&backend->outputs); + wl_list_init(&backend->input_devices); + + static const EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, + EGL_ALPHA_SIZE, 0, + EGL_BLUE_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_RED_SIZE, 1, + EGL_NONE, + }; + + if (!create_renderer_func) { + create_renderer_func = wlr_renderer_autocreate; + } + + backend->renderer = create_renderer_func(&backend->egl, + EGL_PLATFORM_SURFACELESS_MESA, NULL, (EGLint*)config_attribs, 0); + if (!backend->renderer) { + wlr_log(WLR_ERROR, "Failed to create renderer"); + free(backend); + return NULL; + } + + backend->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &backend->display_destroy); + + return &backend->backend; +} + +bool wlr_backend_is_headless(struct wlr_backend *backend) { + return backend->impl == &backend_impl; +} diff --git a/backend/headless/input_device.c b/backend/headless/input_device.c new file mode 100644 index 00000000..827c6ee4 --- /dev/null +++ b/backend/headless/input_device.c @@ -0,0 +1,99 @@ +#include <assert.h> +#include <stdlib.h> +#include <wlr/interfaces/wlr_input_device.h> +#include <wlr/interfaces/wlr_keyboard.h> +#include <wlr/interfaces/wlr_pointer.h> +#include <wlr/interfaces/wlr_tablet_pad.h> +#include <wlr/interfaces/wlr_tablet_tool.h> +#include <wlr/interfaces/wlr_touch.h> +#include <wlr/interfaces/wlr_switch.h> +#include <wlr/util/log.h> +#include "backend/headless.h" +#include "util/signal.h" + +static const struct wlr_input_device_impl input_device_impl = { 0 }; + +bool wlr_input_device_is_headless(struct wlr_input_device *wlr_dev) { + return wlr_dev->impl == &input_device_impl; +} + +struct wlr_input_device *wlr_headless_add_input_device( + struct wlr_backend *wlr_backend, enum wlr_input_device_type type) { + struct wlr_headless_backend *backend = + headless_backend_from_backend(wlr_backend); + + struct wlr_headless_input_device *device = + calloc(1, sizeof(struct wlr_headless_input_device)); + if (device == NULL) { + return NULL; + } + device->backend = backend; + + int vendor = 0; + int product = 0; + const char *name = "headless"; + struct wlr_input_device *wlr_device = &device->wlr_input_device; + wlr_input_device_init(wlr_device, type, &input_device_impl, name, vendor, + product); + + switch (type) { + case WLR_INPUT_DEVICE_KEYBOARD: + wlr_device->keyboard = calloc(1, sizeof(struct wlr_keyboard)); + if (wlr_device->keyboard == NULL) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_keyboard"); + goto error; + } + wlr_keyboard_init(wlr_device->keyboard, NULL); + break; + case WLR_INPUT_DEVICE_POINTER: + wlr_device->pointer = calloc(1, sizeof(struct wlr_pointer)); + if (wlr_device->pointer == NULL) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_pointer"); + goto error; + } + wlr_pointer_init(wlr_device->pointer, NULL); + break; + case WLR_INPUT_DEVICE_TOUCH: + wlr_device->touch = calloc(1, sizeof(struct wlr_touch)); + if (wlr_device->touch == NULL) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_touch"); + goto error; + } + wlr_touch_init(wlr_device->touch, NULL); + break; + case WLR_INPUT_DEVICE_TABLET_TOOL: + wlr_device->tablet = calloc(1, sizeof(struct wlr_tablet)); + if (wlr_device->tablet == NULL) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_tablet"); + goto error; + } + wlr_tablet_init(wlr_device->tablet, NULL); + break; + case WLR_INPUT_DEVICE_TABLET_PAD: + wlr_device->tablet_pad = calloc(1, sizeof(struct wlr_tablet_pad)); + if (wlr_device->tablet_pad == NULL) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_tablet_pad"); + goto error; + } + wlr_tablet_pad_init(wlr_device->tablet_pad, NULL); + break; + case WLR_INPUT_DEVICE_SWITCH: + wlr_device->lid_switch = calloc(1, sizeof(struct wlr_switch)); + if (wlr_device->lid_switch == NULL) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_switch"); + goto error; + } + wlr_switch_init(wlr_device->lid_switch, NULL); + } + + wl_list_insert(&backend->input_devices, &wlr_device->link); + + if (backend->started) { + wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_device); + } + + return wlr_device; +error: + free(device); + return NULL; +} diff --git a/backend/headless/output.c b/backend/headless/output.c new file mode 100644 index 00000000..3cb35dce --- /dev/null +++ b/backend/headless/output.c @@ -0,0 +1,159 @@ +#include <assert.h> +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <stdlib.h> +#include <wlr/interfaces/wlr_output.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/util/log.h> +#include "backend/headless.h" +#include "util/signal.h" + +static struct wlr_headless_output *headless_output_from_output( + struct wlr_output *wlr_output) { + assert(wlr_output_is_headless(wlr_output)); + return (struct wlr_headless_output *)wlr_output; +} + +static EGLSurface egl_create_surface(struct wlr_egl *egl, unsigned int width, + unsigned int height) { + EGLint attribs[] = {EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE}; + + EGLSurface surf = eglCreatePbufferSurface(egl->display, egl->config, attribs); + if (surf == EGL_NO_SURFACE) { + wlr_log(WLR_ERROR, "Failed to create EGL surface"); + return EGL_NO_SURFACE; + } + return surf; +} + +static bool output_set_custom_mode(struct wlr_output *wlr_output, int32_t width, + int32_t height, int32_t refresh) { + struct wlr_headless_output *output = + headless_output_from_output(wlr_output); + struct wlr_headless_backend *backend = output->backend; + + if (refresh <= 0) { + refresh = HEADLESS_DEFAULT_REFRESH; + } + + wlr_egl_destroy_surface(&backend->egl, output->egl_surface); + + output->egl_surface = egl_create_surface(&backend->egl, width, height); + if (output->egl_surface == EGL_NO_SURFACE) { + wlr_log(WLR_ERROR, "Failed to recreate EGL surface"); + wlr_output_destroy(wlr_output); + return false; + } + + output->frame_delay = 1000000 / refresh; + + wlr_output_update_custom_mode(&output->wlr_output, width, height, refresh); + return true; +} + +static void output_transform(struct wlr_output *wlr_output, + enum wl_output_transform transform) { + struct wlr_headless_output *output = + headless_output_from_output(wlr_output); + output->wlr_output.transform = transform; +} + +static bool output_make_current(struct wlr_output *wlr_output, int *buffer_age) { + struct wlr_headless_output *output = + headless_output_from_output(wlr_output); + return wlr_egl_make_current(&output->backend->egl, output->egl_surface, + buffer_age); +} + +static bool output_swap_buffers(struct wlr_output *wlr_output, + pixman_region32_t *damage) { + // 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) { + struct wlr_headless_output *output = + headless_output_from_output(wlr_output); + + wl_list_remove(&output->link); + + wl_event_source_remove(output->frame_timer); + + wlr_egl_destroy_surface(&output->backend->egl, output->egl_surface); + free(output); +} + +static const struct wlr_output_impl output_impl = { + .set_custom_mode = output_set_custom_mode, + .transform = output_transform, + .destroy = output_destroy, + .make_current = output_make_current, + .swap_buffers = output_swap_buffers, +}; + +bool wlr_output_is_headless(struct wlr_output *wlr_output) { + return wlr_output->impl == &output_impl; +} + +static int signal_frame(void *data) { + struct wlr_headless_output *output = data; + wlr_output_send_frame(&output->wlr_output); + wl_event_source_timer_update(output->frame_timer, output->frame_delay); + return 0; +} + +struct wlr_output *wlr_headless_add_output(struct wlr_backend *wlr_backend, + unsigned int width, unsigned int height) { + struct wlr_headless_backend *backend = + headless_backend_from_backend(wlr_backend); + + struct wlr_headless_output *output = + calloc(1, sizeof(struct wlr_headless_output)); + if (output == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_headless_output"); + return NULL; + } + output->backend = backend; + wlr_output_init(&output->wlr_output, &backend->backend, &output_impl, + backend->display); + struct wlr_output *wlr_output = &output->wlr_output; + + output->egl_surface = egl_create_surface(&backend->egl, width, height); + if (output->egl_surface == EGL_NO_SURFACE) { + wlr_log(WLR_ERROR, "Failed to create EGL surface"); + goto error; + } + + output_set_custom_mode(wlr_output, width, height, 0); + strncpy(wlr_output->make, "headless", sizeof(wlr_output->make)); + strncpy(wlr_output->model, "headless", sizeof(wlr_output->model)); + snprintf(wlr_output->name, sizeof(wlr_output->name), "HEADLESS-%d", + wl_list_length(&backend->outputs) + 1); + + if (!wlr_egl_make_current(&output->backend->egl, output->egl_surface, + NULL)) { + goto error; + } + + wlr_renderer_begin(backend->renderer, wlr_output->width, wlr_output->height); + wlr_renderer_clear(backend->renderer, (float[]){ 1.0, 1.0, 1.0, 1.0 }); + wlr_renderer_end(backend->renderer); + + struct wl_event_loop *ev = wl_display_get_event_loop(backend->display); + output->frame_timer = wl_event_loop_add_timer(ev, signal_frame, output); + + wl_list_insert(&backend->outputs, &output->link); + + if (backend->started) { + wl_event_source_timer_update(output->frame_timer, output->frame_delay); + wlr_output_update_enabled(wlr_output, true); + wlr_signal_emit_safe(&backend->backend.events.new_output, wlr_output); + } + + return wlr_output; + +error: + wlr_output_destroy(&output->wlr_output); + return NULL; +} diff --git a/backend/libinput/backend.c b/backend/libinput/backend.c new file mode 100644 index 00000000..8106af00 --- /dev/null +++ b/backend/libinput/backend.c @@ -0,0 +1,204 @@ +#include <assert.h> +#include <libinput.h> +#include <stdlib.h> +#include <wlr/backend/interface.h> +#include <wlr/backend/session.h> +#include <wlr/util/log.h> +#include "backend/libinput.h" +#include "util/signal.h" + +static struct wlr_libinput_backend *get_libinput_backend_from_backend( + struct wlr_backend *wlr_backend) { + assert(wlr_backend_is_libinput(wlr_backend)); + return (struct wlr_libinput_backend *)wlr_backend; +} + +static int libinput_open_restricted(const char *path, + int flags, void *_backend) { + struct wlr_libinput_backend *backend = _backend; + return wlr_session_open_file(backend->session, path); +} + +static void libinput_close_restricted(int fd, void *_backend) { + struct wlr_libinput_backend *backend = _backend; + wlr_session_close_file(backend->session, fd); +} + +static const struct libinput_interface libinput_impl = { + .open_restricted = libinput_open_restricted, + .close_restricted = libinput_close_restricted +}; + +static int handle_libinput_readable(int fd, uint32_t mask, void *_backend) { + struct wlr_libinput_backend *backend = _backend; + if (libinput_dispatch(backend->libinput_context) != 0) { + wlr_log(WLR_ERROR, "Failed to dispatch libinput"); + // TODO: some kind of abort? + return 0; + } + struct libinput_event *event; + while ((event = libinput_get_event(backend->libinput_context))) { + handle_libinput_event(backend, event); + libinput_event_destroy(event); + } + return 0; +} + +static void log_libinput(struct libinput *libinput_context, + enum libinput_log_priority priority, const char *fmt, va_list args) { + _wlr_vlog(WLR_ERROR, fmt, args); +} + +static bool backend_start(struct wlr_backend *wlr_backend) { + struct wlr_libinput_backend *backend = + get_libinput_backend_from_backend(wlr_backend); + wlr_log(WLR_DEBUG, "Initializing libinput"); + + backend->libinput_context = libinput_udev_create_context(&libinput_impl, + backend, backend->session->udev); + if (!backend->libinput_context) { + wlr_log(WLR_ERROR, "Failed to create libinput context"); + return false; + } + + if (libinput_udev_assign_seat(backend->libinput_context, + backend->session->seat) != 0) { + wlr_log(WLR_ERROR, "Failed to assign libinput seat"); + return false; + } + + // TODO: More sophisticated logging + libinput_log_set_handler(backend->libinput_context, log_libinput); + libinput_log_set_priority(backend->libinput_context, LIBINPUT_LOG_PRIORITY_ERROR); + + int libinput_fd = libinput_get_fd(backend->libinput_context); + char *no_devs = getenv("WLR_LIBINPUT_NO_DEVICES"); + if (no_devs) { + if (strcmp(no_devs, "1") != 0) { + no_devs = NULL; + } + } + if (!no_devs && backend->wlr_device_lists.length == 0) { + handle_libinput_readable(libinput_fd, WL_EVENT_READABLE, backend); + if (backend->wlr_device_lists.length == 0) { + wlr_log(WLR_ERROR, "libinput initialization failed, no input devices"); + wlr_log(WLR_ERROR, "Set WLR_LIBINPUT_NO_DEVICES=1 to suppress this check"); + return false; + } + } + + struct wl_event_loop *event_loop = + wl_display_get_event_loop(backend->display); + if (backend->input_event) { + wl_event_source_remove(backend->input_event); + } + backend->input_event = wl_event_loop_add_fd(event_loop, libinput_fd, + WL_EVENT_READABLE, handle_libinput_readable, backend); + if (!backend->input_event) { + wlr_log(WLR_ERROR, "Failed to create input event on event loop"); + return false; + } + wlr_log(WLR_DEBUG, "libinput successfully initialized"); + return true; +} + +static void backend_destroy(struct wlr_backend *wlr_backend) { + if (!wlr_backend) { + return; + } + struct wlr_libinput_backend *backend = + get_libinput_backend_from_backend(wlr_backend); + + for (size_t i = 0; i < backend->wlr_device_lists.length; i++) { + struct wl_list *wlr_devices = backend->wlr_device_lists.items[i]; + struct wlr_input_device *wlr_dev, *next; + wl_list_for_each_safe(wlr_dev, next, wlr_devices, link) { + wlr_input_device_destroy(wlr_dev); + } + free(wlr_devices); + } + + wlr_signal_emit_safe(&wlr_backend->events.destroy, wlr_backend); + + wl_list_remove(&backend->display_destroy.link); + wl_list_remove(&backend->session_signal.link); + + wlr_list_finish(&backend->wlr_device_lists); + if (backend->input_event) { + wl_event_source_remove(backend->input_event); + } + libinput_unref(backend->libinput_context); + free(backend); +} + +static const struct wlr_backend_impl backend_impl = { + .start = backend_start, + .destroy = backend_destroy, +}; + +bool wlr_backend_is_libinput(struct wlr_backend *b) { + return b->impl == &backend_impl; +} + +static void session_signal(struct wl_listener *listener, void *data) { + struct wlr_libinput_backend *backend = + wl_container_of(listener, backend, session_signal); + struct wlr_session *session = data; + + if (!backend->libinput_context) { + return; + } + + if (session->active) { + libinput_resume(backend->libinput_context); + } else { + libinput_suspend(backend->libinput_context); + } +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_libinput_backend *backend = + wl_container_of(listener, backend, display_destroy); + backend_destroy(&backend->backend); +} + +struct wlr_backend *wlr_libinput_backend_create(struct wl_display *display, + struct wlr_session *session) { + struct wlr_libinput_backend *backend = + calloc(1, sizeof(struct wlr_libinput_backend)); + if (!backend) { + wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno)); + return NULL; + } + wlr_backend_init(&backend->backend, &backend_impl); + + if (!wlr_list_init(&backend->wlr_device_lists)) { + wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno)); + goto error_backend; + } + + backend->session = session; + backend->display = display; + + backend->session_signal.notify = session_signal; + wl_signal_add(&session->session_signal, &backend->session_signal); + + backend->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &backend->display_destroy); + + return &backend->backend; +error_backend: + free(backend); + return NULL; +} + +struct libinput_device *wlr_libinput_get_device_handle( + struct wlr_input_device *wlr_dev) { + struct wlr_libinput_input_device *dev = + (struct wlr_libinput_input_device *)wlr_dev; + return dev->handle; +} + +uint32_t usec_to_msec(uint64_t usec) { + return (uint32_t)(usec / 1000); +} diff --git a/backend/libinput/events.c b/backend/libinput/events.c new file mode 100644 index 00000000..a7a6c114 --- /dev/null +++ b/backend/libinput/events.c @@ -0,0 +1,294 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <libinput.h> +#include <stdlib.h> +#include <wayland-util.h> +#include <wlr/backend/session.h> +#include <wlr/interfaces/wlr_input_device.h> +#include <wlr/util/log.h> +#include "backend/libinput.h" +#include "util/signal.h" + +struct wlr_libinput_input_device *get_libinput_device_from_device( + struct wlr_input_device *wlr_dev) { + assert(wlr_input_device_is_libinput(wlr_dev)); + return (struct wlr_libinput_input_device *)wlr_dev; +} + +struct wlr_input_device *get_appropriate_device( + enum wlr_input_device_type desired_type, + struct libinput_device *libinput_dev) { + struct wl_list *wlr_devices = libinput_device_get_user_data(libinput_dev); + if (!wlr_devices) { + return NULL; + } + struct wlr_input_device *dev; + wl_list_for_each(dev, wlr_devices, link) { + if (dev->type == desired_type) { + return dev; + } + } + return NULL; +} + +static void input_device_destroy(struct wlr_input_device *wlr_dev) { + struct wlr_libinput_input_device *dev = + get_libinput_device_from_device(wlr_dev); + libinput_device_unref(dev->handle); + wl_list_remove(&dev->wlr_input_device.link); + free(dev); +} + +static const struct wlr_input_device_impl input_device_impl = { + .destroy = input_device_destroy, +}; + +static struct wlr_input_device *allocate_device( + struct wlr_libinput_backend *backend, + struct libinput_device *libinput_dev, struct wl_list *wlr_devices, + enum wlr_input_device_type type) { + int vendor = libinput_device_get_id_vendor(libinput_dev); + int product = libinput_device_get_id_product(libinput_dev); + const char *name = libinput_device_get_name(libinput_dev); + struct wlr_libinput_input_device *dev = + calloc(1, sizeof(struct wlr_libinput_input_device)); + if (dev == NULL) { + return NULL; + } + struct wlr_input_device *wlr_dev = &dev->wlr_input_device; + libinput_device_get_size(libinput_dev, + &wlr_dev->width_mm, &wlr_dev->height_mm); + const char *output_name = libinput_device_get_output_name(libinput_dev); + if (output_name != NULL) { + wlr_dev->output_name = strdup(output_name); + } + wl_list_insert(wlr_devices, &wlr_dev->link); + dev->handle = libinput_dev; + libinput_device_ref(libinput_dev); + wlr_input_device_init(wlr_dev, type, &input_device_impl, + name, vendor, product); + return wlr_dev; +} + +bool wlr_input_device_is_libinput(struct wlr_input_device *wlr_dev) { + return wlr_dev->impl == &input_device_impl; +} + +static void handle_device_added(struct wlr_libinput_backend *backend, + struct libinput_device *libinput_dev) { + /* + * Note: the wlr API exposes only devices with a single capability, because + * that meshes better with how Wayland does things and is a bit simpler. + * However, libinput devices often have multiple capabilities - in such + * cases we have to create several devices. + */ + int vendor = libinput_device_get_id_vendor(libinput_dev); + int product = libinput_device_get_id_product(libinput_dev); + const char *name = libinput_device_get_name(libinput_dev); + struct wl_list *wlr_devices = calloc(1, sizeof(struct wl_list)); + if (!wlr_devices) { + wlr_log(WLR_ERROR, "Allocation failed"); + return; + } + wl_list_init(wlr_devices); + wlr_log(WLR_DEBUG, "Added %s [%d:%d]", name, vendor, product); + + if (libinput_device_has_capability( + libinput_dev, LIBINPUT_DEVICE_CAP_KEYBOARD)) { + struct wlr_input_device *wlr_dev = allocate_device(backend, + libinput_dev, wlr_devices, WLR_INPUT_DEVICE_KEYBOARD); + if (!wlr_dev) { + goto fail; + } + wlr_dev->keyboard = create_libinput_keyboard(libinput_dev); + if (!wlr_dev->keyboard) { + free(wlr_dev); + goto fail; + } + wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev); + } + if (libinput_device_has_capability( + libinput_dev, LIBINPUT_DEVICE_CAP_POINTER)) { + struct wlr_input_device *wlr_dev = allocate_device(backend, + libinput_dev, wlr_devices, WLR_INPUT_DEVICE_POINTER); + if (!wlr_dev) { + goto fail; + } + wlr_dev->pointer = create_libinput_pointer(libinput_dev); + if (!wlr_dev->pointer) { + free(wlr_dev); + goto fail; + } + wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev); + } + if (libinput_device_has_capability( + libinput_dev, LIBINPUT_DEVICE_CAP_TOUCH)) { + struct wlr_input_device *wlr_dev = allocate_device(backend, + libinput_dev, wlr_devices, WLR_INPUT_DEVICE_TOUCH); + if (!wlr_dev) { + goto fail; + } + wlr_dev->touch = create_libinput_touch(libinput_dev); + if (!wlr_dev->touch) { + free(wlr_dev); + goto fail; + } + wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev); + } + if (libinput_device_has_capability(libinput_dev, + LIBINPUT_DEVICE_CAP_TABLET_TOOL)) { + struct wlr_input_device *wlr_dev = allocate_device(backend, + libinput_dev, wlr_devices, WLR_INPUT_DEVICE_TABLET_TOOL); + if (!wlr_dev) { + goto fail; + } + wlr_dev->tablet = create_libinput_tablet(libinput_dev); + if (!wlr_dev->tablet) { + free(wlr_dev); + goto fail; + } + wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev); + } + if (libinput_device_has_capability( + libinput_dev, LIBINPUT_DEVICE_CAP_TABLET_PAD)) { + struct wlr_input_device *wlr_dev = allocate_device(backend, + libinput_dev, wlr_devices, WLR_INPUT_DEVICE_TABLET_PAD); + if (!wlr_dev) { + goto fail; + } + wlr_dev->tablet_pad = create_libinput_tablet_pad(libinput_dev); + if (!wlr_dev->tablet_pad) { + free(wlr_dev); + goto fail; + } + wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev); + } + if (libinput_device_has_capability( + libinput_dev, LIBINPUT_DEVICE_CAP_GESTURE)) { + // TODO + } + if (libinput_device_has_capability( + libinput_dev, LIBINPUT_DEVICE_CAP_SWITCH)) { + struct wlr_input_device *wlr_dev = allocate_device(backend, + libinput_dev, wlr_devices, WLR_INPUT_DEVICE_SWITCH); + if (!wlr_dev) { + goto fail; + } + wlr_dev->lid_switch = create_libinput_switch(libinput_dev); + if (!wlr_dev->lid_switch) { + free(wlr_dev); + goto fail; + } + wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev); + } + + if (!wl_list_empty(wlr_devices)) { + libinput_device_set_user_data(libinput_dev, wlr_devices); + wlr_list_push(&backend->wlr_device_lists, wlr_devices); + } else { + free(wlr_devices); + } + return; + +fail: + wlr_log(WLR_ERROR, "Could not allocate new device"); + struct wlr_input_device *dev, *tmp_dev; + wl_list_for_each_safe(dev, tmp_dev, wlr_devices, link) { + free(dev); + } + free(wlr_devices); +} + +static void handle_device_removed(struct wlr_libinput_backend *backend, + struct libinput_device *libinput_dev) { + struct wl_list *wlr_devices = libinput_device_get_user_data(libinput_dev); + int vendor = libinput_device_get_id_vendor(libinput_dev); + int product = libinput_device_get_id_product(libinput_dev); + const char *name = libinput_device_get_name(libinput_dev); + wlr_log(WLR_DEBUG, "Removing %s [%d:%d]", name, vendor, product); + if (!wlr_devices) { + return; + } + struct wlr_input_device *dev, *tmp_dev; + wl_list_for_each_safe(dev, tmp_dev, wlr_devices, link) { + wlr_input_device_destroy(dev); + } + for (size_t i = 0; i < backend->wlr_device_lists.length; i++) { + if (backend->wlr_device_lists.items[i] == wlr_devices) { + wlr_list_del(&backend->wlr_device_lists, i); + break; + } + } + free(wlr_devices); +} + +void handle_libinput_event(struct wlr_libinput_backend *backend, + struct libinput_event *event) { + struct libinput_device *libinput_dev = libinput_event_get_device(event); + enum libinput_event_type event_type = libinput_event_get_type(event); + switch (event_type) { + case LIBINPUT_EVENT_DEVICE_ADDED: + handle_device_added(backend, libinput_dev); + break; + case LIBINPUT_EVENT_DEVICE_REMOVED: + handle_device_removed(backend, libinput_dev); + break; + case LIBINPUT_EVENT_KEYBOARD_KEY: + handle_keyboard_key(event, libinput_dev); + break; + case LIBINPUT_EVENT_POINTER_MOTION: + handle_pointer_motion(event, libinput_dev); + break; + case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: + handle_pointer_motion_abs(event, libinput_dev); + break; + case LIBINPUT_EVENT_POINTER_BUTTON: + handle_pointer_button(event, libinput_dev); + break; + case LIBINPUT_EVENT_POINTER_AXIS: + handle_pointer_axis(event, libinput_dev); + break; + case LIBINPUT_EVENT_TOUCH_DOWN: + handle_touch_down(event, libinput_dev); + break; + case LIBINPUT_EVENT_TOUCH_UP: + handle_touch_up(event, libinput_dev); + break; + case LIBINPUT_EVENT_TOUCH_MOTION: + handle_touch_motion(event, libinput_dev); + break; + case LIBINPUT_EVENT_TOUCH_CANCEL: + handle_touch_cancel(event, libinput_dev); + break; + case LIBINPUT_EVENT_TOUCH_FRAME: + // no-op (at least for now) + break; + case LIBINPUT_EVENT_TABLET_TOOL_AXIS: + handle_tablet_tool_axis(event, libinput_dev); + break; + case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: + handle_tablet_tool_proximity(event, libinput_dev); + break; + case LIBINPUT_EVENT_TABLET_TOOL_TIP: + handle_tablet_tool_tip(event, libinput_dev); + break; + case LIBINPUT_EVENT_TABLET_TOOL_BUTTON: + handle_tablet_tool_button(event, libinput_dev); + break; + case LIBINPUT_EVENT_TABLET_PAD_BUTTON: + handle_tablet_pad_button(event, libinput_dev); + break; + case LIBINPUT_EVENT_TABLET_PAD_RING: + handle_tablet_pad_ring(event, libinput_dev); + break; + case LIBINPUT_EVENT_TABLET_PAD_STRIP: + handle_tablet_pad_strip(event, libinput_dev); + break; + case LIBINPUT_EVENT_SWITCH_TOGGLE: + handle_switch_toggle(event, libinput_dev); + break; + default: + wlr_log(WLR_DEBUG, "Unknown libinput event %d", event_type); + break; + } +} diff --git a/backend/libinput/keyboard.c b/backend/libinput/keyboard.c new file mode 100644 index 00000000..93605e77 --- /dev/null +++ b/backend/libinput/keyboard.c @@ -0,0 +1,83 @@ +#include <assert.h> +#include <libinput.h> +#include <stdlib.h> +#include <wlr/backend/session.h> +#include <wlr/interfaces/wlr_keyboard.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/util/log.h> +#include "backend/libinput.h" + +struct wlr_libinput_keyboard { + struct wlr_keyboard wlr_keyboard; + struct libinput_device *libinput_dev; +}; + +static const struct wlr_keyboard_impl impl; + +static struct wlr_libinput_keyboard *get_libinput_keyboard_from_keyboard( + struct wlr_keyboard *wlr_kb) { + assert(wlr_kb->impl == &impl); + return (struct wlr_libinput_keyboard *)wlr_kb; +} + +static void keyboard_set_leds(struct wlr_keyboard *wlr_kb, uint32_t leds) { + struct wlr_libinput_keyboard *kb = + get_libinput_keyboard_from_keyboard(wlr_kb); + libinput_device_led_update(kb->libinput_dev, leds); +} + +static void keyboard_destroy(struct wlr_keyboard *wlr_kb) { + struct wlr_libinput_keyboard *kb = + get_libinput_keyboard_from_keyboard(wlr_kb); + libinput_device_unref(kb->libinput_dev); + free(kb); +} + +static const struct wlr_keyboard_impl impl = { + .destroy = keyboard_destroy, + .led_update = keyboard_set_leds +}; + +struct wlr_keyboard *create_libinput_keyboard( + struct libinput_device *libinput_dev) { + struct wlr_libinput_keyboard *kb = + calloc(1, sizeof(struct wlr_libinput_keyboard)); + if (kb == NULL) { + return NULL; + } + kb->libinput_dev = libinput_dev; + libinput_device_ref(libinput_dev); + libinput_device_led_update(libinput_dev, 0); + struct wlr_keyboard *wlr_kb = &kb->wlr_keyboard; + wlr_keyboard_init(wlr_kb, &impl); + return wlr_kb; +} + +void handle_keyboard_key(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_KEYBOARD, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, + "Got a keyboard event for a device with no keyboards?"); + return; + } + struct libinput_event_keyboard *kbevent = + libinput_event_get_keyboard_event(event); + struct wlr_event_keyboard_key wlr_event = { 0 }; + wlr_event.time_msec = + usec_to_msec(libinput_event_keyboard_get_time_usec(kbevent)); + wlr_event.keycode = libinput_event_keyboard_get_key(kbevent); + enum libinput_key_state state = + libinput_event_keyboard_get_key_state(kbevent); + switch (state) { + case LIBINPUT_KEY_STATE_RELEASED: + wlr_event.state = WLR_KEY_RELEASED; + break; + case LIBINPUT_KEY_STATE_PRESSED: + wlr_event.state = WLR_KEY_PRESSED; + break; + } + wlr_event.update_state = true; + wlr_keyboard_notify_key(wlr_dev->keyboard, &wlr_event); +} diff --git a/backend/libinput/pointer.c b/backend/libinput/pointer.c new file mode 100644 index 00000000..fb85cddd --- /dev/null +++ b/backend/libinput/pointer.c @@ -0,0 +1,136 @@ +#include <assert.h> +#include <libinput.h> +#include <stdlib.h> +#include <wlr/backend/session.h> +#include <wlr/interfaces/wlr_pointer.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/util/log.h> +#include "backend/libinput.h" +#include "util/signal.h" + +struct wlr_pointer *create_libinput_pointer( + struct libinput_device *libinput_dev) { + assert(libinput_dev); + struct wlr_pointer *wlr_pointer = calloc(1, sizeof(struct wlr_pointer)); + if (!wlr_pointer) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_pointer"); + return NULL; + } + wlr_pointer_init(wlr_pointer, NULL); + return wlr_pointer; +} + +void handle_pointer_motion(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_POINTER, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, "Got a pointer event for a device with no pointers?"); + return; + } + struct libinput_event_pointer *pevent = + libinput_event_get_pointer_event(event); + struct wlr_event_pointer_motion wlr_event = { 0 }; + wlr_event.device = wlr_dev; + wlr_event.time_msec = + usec_to_msec(libinput_event_pointer_get_time_usec(pevent)); + wlr_event.delta_x = libinput_event_pointer_get_dx(pevent); + wlr_event.delta_y = libinput_event_pointer_get_dy(pevent); + wlr_signal_emit_safe(&wlr_dev->pointer->events.motion, &wlr_event); +} + +void handle_pointer_motion_abs(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_POINTER, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, "Got a pointer event for a device with no pointers?"); + return; + } + struct libinput_event_pointer *pevent = + libinput_event_get_pointer_event(event); + struct wlr_event_pointer_motion_absolute wlr_event = { 0 }; + wlr_event.device = wlr_dev; + wlr_event.time_msec = + usec_to_msec(libinput_event_pointer_get_time_usec(pevent)); + wlr_event.x = libinput_event_pointer_get_absolute_x_transformed(pevent, 1); + wlr_event.y = libinput_event_pointer_get_absolute_y_transformed(pevent, 1); + wlr_signal_emit_safe(&wlr_dev->pointer->events.motion_absolute, &wlr_event); +} + +void handle_pointer_button(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_POINTER, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, "Got a pointer event for a device with no pointers?"); + return; + } + struct libinput_event_pointer *pevent = + libinput_event_get_pointer_event(event); + struct wlr_event_pointer_button wlr_event = { 0 }; + wlr_event.device = wlr_dev; + wlr_event.time_msec = + usec_to_msec(libinput_event_pointer_get_time_usec(pevent)); + wlr_event.button = libinput_event_pointer_get_button(pevent); + switch (libinput_event_pointer_get_button_state(pevent)) { + case LIBINPUT_BUTTON_STATE_PRESSED: + wlr_event.state = WLR_BUTTON_PRESSED; + break; + case LIBINPUT_BUTTON_STATE_RELEASED: + wlr_event.state = WLR_BUTTON_RELEASED; + break; + } + wlr_signal_emit_safe(&wlr_dev->pointer->events.button, &wlr_event); +} + +void handle_pointer_axis(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_POINTER, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, "Got a pointer event for a device with no pointers?"); + return; + } + struct libinput_event_pointer *pevent = + libinput_event_get_pointer_event(event); + struct wlr_event_pointer_axis wlr_event = { 0 }; + wlr_event.device = wlr_dev; + wlr_event.time_msec = + usec_to_msec(libinput_event_pointer_get_time_usec(pevent)); + switch (libinput_event_pointer_get_axis_source(pevent)) { + case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL: + wlr_event.source = WLR_AXIS_SOURCE_WHEEL; + break; + case LIBINPUT_POINTER_AXIS_SOURCE_FINGER: + wlr_event.source = WLR_AXIS_SOURCE_FINGER; + break; + case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS: + wlr_event.source = WLR_AXIS_SOURCE_CONTINUOUS; + break; + case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT: + wlr_event.source = WLR_AXIS_SOURCE_WHEEL_TILT; + break; + } + enum libinput_pointer_axis axies[] = { + LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, + LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL, + }; + for (size_t i = 0; i < sizeof(axies) / sizeof(axies[0]); ++i) { + if (libinput_event_pointer_has_axis(pevent, axies[i])) { + switch (axies[i]) { + case LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL: + wlr_event.orientation = WLR_AXIS_ORIENTATION_VERTICAL; + break; + case LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL: + wlr_event.orientation = WLR_AXIS_ORIENTATION_HORIZONTAL; + break; + } + wlr_event.delta = + libinput_event_pointer_get_axis_value(pevent, axies[i]); + wlr_event.delta_discrete = + libinput_event_pointer_get_axis_value_discrete(pevent, axies[i]); + wlr_signal_emit_safe(&wlr_dev->pointer->events.axis, &wlr_event); + } + } +} diff --git a/backend/libinput/switch.c b/backend/libinput/switch.c new file mode 100644 index 00000000..393460b0 --- /dev/null +++ b/backend/libinput/switch.c @@ -0,0 +1,55 @@ +#include <assert.h> +#include <libinput.h> +#include <stdlib.h> +#include <wlr/backend/session.h> +#include <wlr/interfaces/wlr_switch.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/util/log.h> +#include "backend/libinput.h" +#include "util/signal.h" + +struct wlr_switch *create_libinput_switch( + struct libinput_device *libinput_dev) { + assert(libinput_dev); + struct wlr_switch *wlr_switch = calloc(1, sizeof(struct wlr_switch)); + if (!wlr_switch) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_switch"); + return NULL; + } + wlr_switch_init(wlr_switch, NULL); + wlr_log(WLR_DEBUG, "Created switch for device %s", libinput_device_get_name(libinput_dev)); + return wlr_switch; +} + +void handle_switch_toggle(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_SWITCH, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, "Got a switch event for a device with no switch?"); + return; + } + struct libinput_event_switch *sevent = + libinput_event_get_switch_event (event); + struct wlr_event_switch_toggle wlr_event = { 0 }; + wlr_event.device = wlr_dev; + switch (libinput_event_switch_get_switch(sevent)) { + case LIBINPUT_SWITCH_LID: + wlr_event.switch_type = WLR_SWITCH_TYPE_LID; + break; + case LIBINPUT_SWITCH_TABLET_MODE: + wlr_event.switch_type = WLR_SWITCH_TYPE_TABLET_MODE; + break; + } + switch (libinput_event_switch_get_switch_state(sevent)) { + case LIBINPUT_SWITCH_STATE_OFF: + wlr_event.switch_state = WLR_SWITCH_STATE_OFF; + break; + case LIBINPUT_SWITCH_STATE_ON: + wlr_event.switch_state = WLR_SWITCH_STATE_ON; + break; + } + wlr_event.time_msec = + usec_to_msec(libinput_event_switch_get_time_usec(sevent)); + wlr_signal_emit_safe(&wlr_dev->lid_switch->events.toggle, &wlr_event); +} diff --git a/backend/libinput/tablet_pad.c b/backend/libinput/tablet_pad.c new file mode 100644 index 00000000..b053b9a0 --- /dev/null +++ b/backend/libinput/tablet_pad.c @@ -0,0 +1,183 @@ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#include <assert.h> +#include <string.h> +#include <libinput.h> +#include <stdlib.h> +#include <wlr/backend/session.h> +#include <wlr/interfaces/wlr_tablet_pad.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/util/log.h> +#include "backend/libinput.h" +#include "util/signal.h" + +// FIXME: Decide on how to alloc/count here +static void add_pad_group_from_libinput(struct wlr_tablet_pad *pad, + struct libinput_device *device, unsigned int index) { + struct libinput_tablet_pad_mode_group *li_group = + libinput_device_tablet_pad_get_mode_group(device, index); + struct wlr_tablet_pad_group *group = + calloc(1, sizeof(struct wlr_tablet_pad_group)); + if (!group) { + return; + } + + for (size_t i = 0; i < pad->ring_count; ++i) { + if (libinput_tablet_pad_mode_group_has_ring(li_group, i)) { + ++group->ring_count; + } + } + group->rings = calloc(sizeof(unsigned int), group->ring_count); + size_t ring = 0; + for (size_t i = 0; i < pad->ring_count; ++i) { + if (libinput_tablet_pad_mode_group_has_ring(li_group, i)) { + group->rings[ring++] = i; + } + } + + for (size_t i = 0; i < pad->strip_count; ++i) { + if (libinput_tablet_pad_mode_group_has_strip(li_group, i)) { + ++group->strip_count; + } + } + group->strips = calloc(sizeof(unsigned int), group->strip_count); + size_t strip = 0; + for (size_t i = 0; i < pad->strip_count; ++i) { + if (libinput_tablet_pad_mode_group_has_strip(li_group, i)) { + group->strips[strip++] = i; + } + } + + for (size_t i = 0; i < pad->button_count; ++i) { + if (libinput_tablet_pad_mode_group_has_button(li_group, i)) { + ++group->button_count; + } + } + group->buttons = calloc(sizeof(unsigned int), group->button_count); + size_t button = 0; + for (size_t i = 0; i < pad->button_count; ++i) { + if (libinput_tablet_pad_mode_group_has_button(li_group, i)) { + group->buttons[button++] = i; + } + } + + group->mode_count = libinput_tablet_pad_mode_group_get_num_modes(li_group); + wl_list_insert(&pad->groups, &group->link); +} + +struct wlr_tablet_pad *create_libinput_tablet_pad( + struct libinput_device *libinput_dev) { + assert(libinput_dev); + struct wlr_tablet_pad *wlr_tablet_pad = + calloc(1, sizeof(struct wlr_tablet_pad)); + if (!wlr_tablet_pad) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_tablet_pad"); + return NULL; + } + + wlr_tablet_pad->button_count = + libinput_device_tablet_pad_get_num_buttons(libinput_dev); + wlr_tablet_pad->ring_count = + libinput_device_tablet_pad_get_num_rings(libinput_dev); + wlr_tablet_pad->strip_count = + libinput_device_tablet_pad_get_num_strips(libinput_dev); + + wlr_list_init(&wlr_tablet_pad->paths); + struct udev_device *udev = libinput_device_get_udev_device(libinput_dev); + wlr_list_push(&wlr_tablet_pad->paths, strdup(udev_device_get_syspath(udev))); + + wl_list_init(&wlr_tablet_pad->groups); + int groups = libinput_device_tablet_pad_get_num_mode_groups(libinput_dev); + for (int i = 0; i < groups; ++i) { + add_pad_group_from_libinput(wlr_tablet_pad, libinput_dev, i); + } + + wlr_tablet_pad_init(wlr_tablet_pad, NULL); + return wlr_tablet_pad; +} + +void handle_tablet_pad_button(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TABLET_PAD, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, + "Got a tablet pad event for a device with no tablet pad?"); + return; + } + struct libinput_event_tablet_pad *pevent = + libinput_event_get_tablet_pad_event(event); + struct wlr_event_tablet_pad_button wlr_event = { 0 }; + wlr_event.time_msec = + usec_to_msec(libinput_event_tablet_pad_get_time_usec(pevent)); + wlr_event.button = libinput_event_tablet_pad_get_button_number(pevent); + wlr_event.mode = libinput_event_tablet_pad_get_mode(pevent); + wlr_event.group = libinput_tablet_pad_mode_group_get_index( + libinput_event_tablet_pad_get_mode_group(pevent)); + switch (libinput_event_tablet_pad_get_button_state(pevent)) { + case LIBINPUT_BUTTON_STATE_PRESSED: + wlr_event.state = WLR_BUTTON_PRESSED; + break; + case LIBINPUT_BUTTON_STATE_RELEASED: + wlr_event.state = WLR_BUTTON_RELEASED; + break; + } + wlr_signal_emit_safe(&wlr_dev->tablet_pad->events.button, &wlr_event); +} + +void handle_tablet_pad_ring(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TABLET_PAD, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, + "Got a tablet pad event for a device with no tablet pad?"); + return; + } + struct libinput_event_tablet_pad *pevent = + libinput_event_get_tablet_pad_event(event); + struct wlr_event_tablet_pad_ring wlr_event = { 0 }; + wlr_event.time_msec = + usec_to_msec(libinput_event_tablet_pad_get_time_usec(pevent)); + wlr_event.ring = libinput_event_tablet_pad_get_ring_number(pevent); + wlr_event.position = libinput_event_tablet_pad_get_ring_position(pevent); + wlr_event.mode = libinput_event_tablet_pad_get_mode(pevent); + switch (libinput_event_tablet_pad_get_ring_source(pevent)) { + case LIBINPUT_TABLET_PAD_RING_SOURCE_UNKNOWN: + wlr_event.source = WLR_TABLET_PAD_RING_SOURCE_UNKNOWN; + break; + case LIBINPUT_TABLET_PAD_RING_SOURCE_FINGER: + wlr_event.source = WLR_TABLET_PAD_RING_SOURCE_FINGER; + break; + } + wlr_signal_emit_safe(&wlr_dev->tablet_pad->events.ring, &wlr_event); +} + +void handle_tablet_pad_strip(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TABLET_PAD, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, + "Got a tablet pad event for a device with no tablet pad?"); + return; + } + struct libinput_event_tablet_pad *pevent = + libinput_event_get_tablet_pad_event(event); + struct wlr_event_tablet_pad_strip wlr_event = { 0 }; + wlr_event.time_msec = + usec_to_msec(libinput_event_tablet_pad_get_time_usec(pevent)); + wlr_event.strip = libinput_event_tablet_pad_get_strip_number(pevent); + wlr_event.position = libinput_event_tablet_pad_get_strip_position(pevent); + wlr_event.mode = libinput_event_tablet_pad_get_mode(pevent); + switch (libinput_event_tablet_pad_get_strip_source(pevent)) { + case LIBINPUT_TABLET_PAD_STRIP_SOURCE_UNKNOWN: + wlr_event.source = WLR_TABLET_PAD_STRIP_SOURCE_UNKNOWN; + break; + case LIBINPUT_TABLET_PAD_STRIP_SOURCE_FINGER: + wlr_event.source = WLR_TABLET_PAD_STRIP_SOURCE_FINGER; + break; + } + wlr_signal_emit_safe(&wlr_dev->tablet_pad->events.strip, &wlr_event); +} diff --git a/backend/libinput/tablet_tool.c b/backend/libinput/tablet_tool.c new file mode 100644 index 00000000..4e87b700 --- /dev/null +++ b/backend/libinput/tablet_tool.c @@ -0,0 +1,371 @@ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#include <string.h> +#include <assert.h> +#include <libinput.h> +#include <stdlib.h> +#include <wayland-util.h> +#include <wlr/backend/session.h> +#include <wlr/interfaces/wlr_tablet_tool.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/util/log.h> +#include "backend/libinput.h" +#include "util/signal.h" + +static struct wlr_tablet_impl tablet_impl; + +static bool tablet_is_libinput(struct wlr_tablet *tablet) { + return tablet->impl == &tablet_impl; +} + +struct wlr_libinput_tablet_tool { + struct wlr_tablet_tool wlr_tool; + + struct libinput_tablet_tool *libinput_tool; + + bool unique; + // Refcount for destroy + release + size_t pad_refs; +}; + +// TODO: Maybe this should be a wlr_list? Do we keep it, or want to get rid of +// it? +struct tablet_tool_list_elem { + struct wl_list link; + + struct wlr_libinput_tablet_tool *tool; +}; + +struct wlr_libinput_tablet { + struct wlr_tablet wlr_tablet; + + struct wl_list tools; // tablet_tool_list_elem::link +}; + +static void destroy_tool(struct wlr_libinput_tablet_tool *tool) { + wlr_signal_emit_safe(&tool->wlr_tool.events.destroy, &tool->wlr_tool); + libinput_tablet_tool_ref(tool->libinput_tool); + libinput_tablet_tool_set_user_data(tool->libinput_tool, NULL); + free(tool); +} + + +static void destroy_tablet(struct wlr_tablet *wlr_tablet) { + assert(tablet_is_libinput(wlr_tablet)); + struct wlr_libinput_tablet *tablet = + wl_container_of(wlr_tablet, tablet, wlr_tablet); + + struct tablet_tool_list_elem *pos; + struct tablet_tool_list_elem *tmp; + wl_list_for_each_safe(pos, tmp, &tablet->tools, link) { + struct wlr_libinput_tablet_tool *tool = pos->tool; + wl_list_remove(&pos->link); + free(pos); + + if (--tool->pad_refs == 0) { + destroy_tool(tool); + } + } + + free(tablet); +} + +static struct wlr_tablet_impl tablet_impl = { + .destroy = destroy_tablet, +}; + +struct wlr_tablet *create_libinput_tablet( + struct libinput_device *libinput_dev) { + assert(libinput_dev); + struct wlr_libinput_tablet *libinput_tablet = + calloc(1, sizeof(struct wlr_libinput_tablet)); + if (!libinput_tablet) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_tablet_tool"); + return NULL; + } + struct wlr_tablet *wlr_tablet = &libinput_tablet->wlr_tablet; + + wlr_list_init(&wlr_tablet->paths); + struct udev_device *udev = libinput_device_get_udev_device(libinput_dev); + wlr_list_push(&wlr_tablet->paths, strdup(udev_device_get_syspath(udev))); + wlr_tablet->name = strdup(libinput_device_get_name(libinput_dev)); + wl_list_init(&libinput_tablet->tools); + + wlr_tablet_init(wlr_tablet, &tablet_impl); + return wlr_tablet; +} + +static enum wlr_tablet_tool_type wlr_type_from_libinput_type( + enum libinput_tablet_tool_type value) { + switch (value) { + case LIBINPUT_TABLET_TOOL_TYPE_PEN: + return WLR_TABLET_TOOL_TYPE_PEN; + case LIBINPUT_TABLET_TOOL_TYPE_ERASER: + return WLR_TABLET_TOOL_TYPE_ERASER; + case LIBINPUT_TABLET_TOOL_TYPE_BRUSH: + return WLR_TABLET_TOOL_TYPE_BRUSH; + case LIBINPUT_TABLET_TOOL_TYPE_PENCIL: + return WLR_TABLET_TOOL_TYPE_PENCIL; + case LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH: + return WLR_TABLET_TOOL_TYPE_AIRBRUSH; + case LIBINPUT_TABLET_TOOL_TYPE_MOUSE: + return WLR_TABLET_TOOL_TYPE_MOUSE; + case LIBINPUT_TABLET_TOOL_TYPE_LENS: + return WLR_TABLET_TOOL_TYPE_LENS; + } + + assert(false && "UNREACHABLE"); +} + +static struct wlr_libinput_tablet_tool *get_wlr_tablet_tool( + struct libinput_tablet_tool *tool) { + struct wlr_libinput_tablet_tool *ret = + libinput_tablet_tool_get_user_data(tool); + + if (ret) { + return ret; + } + + ret = calloc(1, sizeof(struct wlr_libinput_tablet_tool)); + if (!ret) { + return NULL; + } + + ret->libinput_tool = libinput_tablet_tool_ref(tool); + ret->wlr_tool.pressure = libinput_tablet_tool_has_pressure(tool); + ret->wlr_tool.distance = libinput_tablet_tool_has_distance(tool); + ret->wlr_tool.tilt = libinput_tablet_tool_has_tilt(tool); + ret->wlr_tool.rotation = libinput_tablet_tool_has_rotation(tool); + ret->wlr_tool.slider = libinput_tablet_tool_has_slider(tool); + ret->wlr_tool.wheel = libinput_tablet_tool_has_wheel(tool); + + ret->wlr_tool.hardware_serial = libinput_tablet_tool_get_serial(tool); + ret->wlr_tool.hardware_wacom = libinput_tablet_tool_get_tool_id(tool); + ret->wlr_tool.type = wlr_type_from_libinput_type( + libinput_tablet_tool_get_type(tool)); + + ret->unique = libinput_tablet_tool_is_unique(tool); + + wl_signal_init(&ret->wlr_tool.events.destroy); + + libinput_tablet_tool_set_user_data(tool, ret); + return ret; +} + +static void ensure_tool_reference(struct wlr_libinput_tablet_tool *tool, + struct wlr_tablet *wlr_dev) { + assert(tablet_is_libinput(wlr_dev)); + struct wlr_libinput_tablet *tablet = + wl_container_of(wlr_dev, tablet, wlr_tablet); + + struct tablet_tool_list_elem *pos; + wl_list_for_each(pos, &tablet->tools, link) { + if (pos->tool == tool) { // We already have a ref + // XXX: We *could* optimize the tool to the front of + // the list here, since we will probably get the next + // couple of events from the same tool. + // BUT the list should always be rather short (probably + // single digit amount of tools) so it might be more + // work than it saves + return; + } + } + + struct tablet_tool_list_elem *new = + calloc(1, sizeof(struct tablet_tool_list_elem)); + if (!new) { + wlr_log(WLR_ERROR, "Failed to allocate memory for tracking tablet tool"); + return; + } + + new->tool = tool; + wl_list_insert(&tablet->tools, &new->link); + ++tool->pad_refs; +} + +void handle_tablet_tool_axis(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TABLET_TOOL, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, + "Got a tablet tool event for a device with no tablet tools?"); + return; + } + struct libinput_event_tablet_tool *tevent = + libinput_event_get_tablet_tool_event(event); + struct wlr_event_tablet_tool_axis wlr_event = { 0 }; + struct wlr_libinput_tablet_tool *tool = get_wlr_tablet_tool( + libinput_event_tablet_tool_get_tool(tevent)); + ensure_tool_reference(tool, wlr_dev->tablet); + + wlr_event.device = wlr_dev; + wlr_event.tool = &tool->wlr_tool; + wlr_event.time_msec = + usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)); + if (libinput_event_tablet_tool_x_has_changed(tevent)) { + wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_X; + wlr_event.x = libinput_event_tablet_tool_get_x_transformed(tevent, 1); + wlr_event.dx = libinput_event_tablet_tool_get_dx(tevent); + } + if (libinput_event_tablet_tool_y_has_changed(tevent)) { + wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_Y; + wlr_event.y = libinput_event_tablet_tool_get_y_transformed(tevent, 1); + wlr_event.dy = libinput_event_tablet_tool_get_dy(tevent); + } + if (libinput_event_tablet_tool_pressure_has_changed(tevent)) { + wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_PRESSURE; + wlr_event.pressure = libinput_event_tablet_tool_get_pressure(tevent); + } + if (libinput_event_tablet_tool_distance_has_changed(tevent)) { + wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_DISTANCE; + wlr_event.distance = libinput_event_tablet_tool_get_distance(tevent); + } + if (libinput_event_tablet_tool_tilt_x_has_changed(tevent)) { + wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_TILT_X; + wlr_event.tilt_x = libinput_event_tablet_tool_get_tilt_x(tevent); + } + if (libinput_event_tablet_tool_tilt_y_has_changed(tevent)) { + wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_TILT_Y; + wlr_event.tilt_y = libinput_event_tablet_tool_get_tilt_y(tevent); + } + if (libinput_event_tablet_tool_rotation_has_changed(tevent)) { + wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_ROTATION; + wlr_event.rotation = libinput_event_tablet_tool_get_rotation(tevent); + } + if (libinput_event_tablet_tool_slider_has_changed(tevent)) { + wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_SLIDER; + wlr_event.slider = libinput_event_tablet_tool_get_slider_position(tevent); + } + if (libinput_event_tablet_tool_wheel_has_changed(tevent)) { + wlr_event.updated_axes |= WLR_TABLET_TOOL_AXIS_WHEEL; + wlr_event.wheel_delta = libinput_event_tablet_tool_get_wheel_delta(tevent); + } + wlr_signal_emit_safe(&wlr_dev->tablet->events.axis, &wlr_event); +} + +void handle_tablet_tool_proximity(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TABLET_TOOL, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, + "Got a tablet tool event for a device with no tablet tools?"); + return; + } + struct libinput_event_tablet_tool *tevent = + libinput_event_get_tablet_tool_event(event); + struct wlr_event_tablet_tool_proximity wlr_event = { 0 }; + struct wlr_libinput_tablet_tool *tool = get_wlr_tablet_tool( + libinput_event_tablet_tool_get_tool(tevent)); + ensure_tool_reference(tool, wlr_dev->tablet); + + wlr_event.tool = &tool->wlr_tool; + wlr_event.device = wlr_dev; + wlr_event.time_msec = + usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)); + switch (libinput_event_tablet_tool_get_proximity_state(tevent)) { + case LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT: + wlr_event.state = WLR_TABLET_TOOL_PROXIMITY_OUT; + break; + case LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN: + wlr_event.state = WLR_TABLET_TOOL_PROXIMITY_IN; + break; + } + wlr_signal_emit_safe(&wlr_dev->tablet->events.proximity, &wlr_event); + + if (libinput_event_tablet_tool_get_proximity_state(tevent) == + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN) { + handle_tablet_tool_axis(event, libinput_dev); + } + + // If the tool is not unique, libinput will not find it again after the + // proximity out, so we should destroy it + if (!tool->unique && + libinput_event_tablet_tool_get_proximity_state(tevent) == + LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT) { + // The tool isn't unique, it can't be on multiple tablets + assert(tool->pad_refs == 1); + assert(tablet_is_libinput(wlr_dev->tablet)); + struct wlr_libinput_tablet *tablet = + wl_container_of(wlr_dev->tablet, tablet, wlr_tablet); + struct tablet_tool_list_elem *pos; + struct tablet_tool_list_elem *tmp; + + wl_list_for_each_safe(pos, tmp, &tablet->tools, link) { + if (pos->tool == tool) { + wl_list_remove(&pos->link); + free(pos); + break; + } + } + + destroy_tool(tool); + } +} + +void handle_tablet_tool_tip(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TABLET_TOOL, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, + "Got a tablet tool event for a device with no tablet tools?"); + return; + } + handle_tablet_tool_axis(event, libinput_dev); + struct libinput_event_tablet_tool *tevent = + libinput_event_get_tablet_tool_event(event); + struct wlr_event_tablet_tool_tip wlr_event = { 0 }; + struct wlr_libinput_tablet_tool *tool = get_wlr_tablet_tool( + libinput_event_tablet_tool_get_tool(tevent)); + ensure_tool_reference(tool, wlr_dev->tablet); + + wlr_event.device = wlr_dev; + wlr_event.tool = &tool->wlr_tool; + wlr_event.time_msec = + usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)); + switch (libinput_event_tablet_tool_get_tip_state(tevent)) { + case LIBINPUT_TABLET_TOOL_TIP_UP: + wlr_event.state = WLR_TABLET_TOOL_TIP_UP; + break; + case LIBINPUT_TABLET_TOOL_TIP_DOWN: + wlr_event.state = WLR_TABLET_TOOL_TIP_DOWN; + break; + } + wlr_signal_emit_safe(&wlr_dev->tablet->events.tip, &wlr_event); +} + +void handle_tablet_tool_button(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TABLET_TOOL, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, + "Got a tablet tool event for a device with no tablet tools?"); + return; + } + handle_tablet_tool_axis(event, libinput_dev); + struct libinput_event_tablet_tool *tevent = + libinput_event_get_tablet_tool_event(event); + struct wlr_event_tablet_tool_button wlr_event = { 0 }; + struct wlr_libinput_tablet_tool *tool = get_wlr_tablet_tool( + libinput_event_tablet_tool_get_tool(tevent)); + ensure_tool_reference(tool, wlr_dev->tablet); + + wlr_event.device = wlr_dev; + wlr_event.tool = &tool->wlr_tool; + wlr_event.time_msec = + usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)); + wlr_event.button = libinput_event_tablet_tool_get_button(tevent); + switch (libinput_event_tablet_tool_get_button_state(tevent)) { + case LIBINPUT_BUTTON_STATE_RELEASED: + wlr_event.state = WLR_BUTTON_RELEASED; + break; + case LIBINPUT_BUTTON_STATE_PRESSED: + wlr_event.state = WLR_BUTTON_PRESSED; + break; + } + wlr_signal_emit_safe(&wlr_dev->tablet->events.button, &wlr_event); +} diff --git a/backend/libinput/touch.c b/backend/libinput/touch.c new file mode 100644 index 00000000..cb9b0e36 --- /dev/null +++ b/backend/libinput/touch.c @@ -0,0 +1,97 @@ +#include <assert.h> +#include <libinput.h> +#include <stdlib.h> +#include <wlr/backend/session.h> +#include <wlr/interfaces/wlr_touch.h> +#include <wlr/types/wlr_input_device.h> +#include <wlr/util/log.h> +#include "backend/libinput.h" +#include "util/signal.h" + +struct wlr_touch *create_libinput_touch( + struct libinput_device *libinput_dev) { + assert(libinput_dev); + struct wlr_touch *wlr_touch = calloc(1, sizeof(struct wlr_touch)); + if (!wlr_touch) { + wlr_log(WLR_ERROR, "Unable to allocate wlr_touch"); + return NULL; + } + wlr_touch_init(wlr_touch, NULL); + return wlr_touch; +} + +void handle_touch_down(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TOUCH, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, "Got a touch event for a device with no touch?"); + return; + } + struct libinput_event_touch *tevent = + libinput_event_get_touch_event(event); + struct wlr_event_touch_down wlr_event = { 0 }; + wlr_event.device = wlr_dev; + wlr_event.time_msec = + usec_to_msec(libinput_event_touch_get_time_usec(tevent)); + wlr_event.touch_id = libinput_event_touch_get_slot(tevent); + wlr_event.x = libinput_event_touch_get_x_transformed(tevent, 1); + wlr_event.y = libinput_event_touch_get_y_transformed(tevent, 1); + wlr_signal_emit_safe(&wlr_dev->touch->events.down, &wlr_event); +} + +void handle_touch_up(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TOUCH, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, "Got a touch event for a device with no touch?"); + return; + } + struct libinput_event_touch *tevent = + libinput_event_get_touch_event(event); + struct wlr_event_touch_up wlr_event = { 0 }; + wlr_event.device = wlr_dev; + wlr_event.time_msec = + usec_to_msec(libinput_event_touch_get_time_usec(tevent)); + wlr_event.touch_id = libinput_event_touch_get_slot(tevent); + wlr_signal_emit_safe(&wlr_dev->touch->events.up, &wlr_event); +} + +void handle_touch_motion(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TOUCH, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, "Got a touch event for a device with no touch?"); + return; + } + struct libinput_event_touch *tevent = + libinput_event_get_touch_event(event); + struct wlr_event_touch_motion wlr_event = { 0 }; + wlr_event.device = wlr_dev; + wlr_event.time_msec = + usec_to_msec(libinput_event_touch_get_time_usec(tevent)); + wlr_event.touch_id = libinput_event_touch_get_slot(tevent); + wlr_event.x = libinput_event_touch_get_x_transformed(tevent, 1); + wlr_event.y = libinput_event_touch_get_y_transformed(tevent, 1); + wlr_signal_emit_safe(&wlr_dev->touch->events.motion, &wlr_event); +} + +void handle_touch_cancel(struct libinput_event *event, + struct libinput_device *libinput_dev) { + struct wlr_input_device *wlr_dev = + get_appropriate_device(WLR_INPUT_DEVICE_TOUCH, libinput_dev); + if (!wlr_dev) { + wlr_log(WLR_DEBUG, "Got a touch event for a device with no touch?"); + return; + } + struct libinput_event_touch *tevent = + libinput_event_get_touch_event(event); + struct wlr_event_touch_cancel wlr_event = { 0 }; + wlr_event.device = wlr_dev; + wlr_event.time_msec = + usec_to_msec(libinput_event_touch_get_time_usec(tevent)); + wlr_event.touch_id = libinput_event_touch_get_slot(tevent); + wlr_signal_emit_safe(&wlr_dev->touch->events.cancel, &wlr_event); +} diff --git a/backend/meson.build b/backend/meson.build new file mode 100644 index 00000000..bf9b4f83 --- /dev/null +++ b/backend/meson.build @@ -0,0 +1,61 @@ +backend_parts = [] +backend_files = files( + 'backend.c', + 'drm/atomic.c', + 'drm/backend.c', + 'drm/drm.c', + 'drm/legacy.c', + 'drm/properties.c', + 'drm/renderer.c', + 'drm/util.c', + 'headless/backend.c', + 'headless/input_device.c', + 'headless/output.c', + 'libinput/backend.c', + 'libinput/events.c', + 'libinput/keyboard.c', + 'libinput/pointer.c', + 'libinput/switch.c', + 'libinput/tablet_pad.c', + 'libinput/tablet_tool.c', + 'libinput/touch.c', + 'multi/backend.c', + 'session/direct-ipc.c', + 'session/session.c', + 'wayland/backend.c', + 'wayland/output.c', + 'wayland/wl_seat.c', +) + +backend_deps = [ + drm, + egl, + gbm, + libinput, + pixman, + xkbcommon, + wayland_server, + wlr_protos, + wlr_render, +] + +if host_machine.system().startswith('freebsd') + backend_files += files('session/direct-freebsd.c') +else + backend_files += files('session/direct.c') +endif + +if logind.found() + backend_files += files('session/logind.c') + backend_deps += logind +endif + +subdir('x11') + +lib_wlr_backend = static_library( + 'wlr_backend', + backend_files, + include_directories: wlr_inc, + link_whole: backend_parts, + dependencies: backend_deps, +) diff --git a/backend/multi/backend.c b/backend/multi/backend.c new file mode 100644 index 00000000..50851109 --- /dev/null +++ b/backend/multi/backend.c @@ -0,0 +1,231 @@ +#define _POSIX_C_SOURCE 200112L +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> +#include <time.h> +#include <wlr/backend/interface.h> +#include <wlr/backend/session.h> +#include <wlr/util/log.h> +#include "backend/multi.h" +#include "util/signal.h" + +struct subbackend_state { + struct wlr_backend *backend; + struct wlr_backend *container; + struct wl_listener new_input; + struct wl_listener new_output; + struct wl_listener destroy; + struct wl_list link; +}; + +static struct wlr_multi_backend *multi_backend_from_backend( + struct wlr_backend *wlr_backend) { + assert(wlr_backend_is_multi(wlr_backend)); + return (struct wlr_multi_backend *)wlr_backend; +} + +static bool multi_backend_start(struct wlr_backend *wlr_backend) { + struct wlr_multi_backend *backend = multi_backend_from_backend(wlr_backend); + struct subbackend_state *sub; + wl_list_for_each(sub, &backend->backends, link) { + if (!wlr_backend_start(sub->backend)) { + wlr_log(WLR_ERROR, "Failed to initialize backend."); + return false; + } + } + return true; +} + +static void subbackend_state_destroy(struct subbackend_state *sub) { + wl_list_remove(&sub->new_input.link); + wl_list_remove(&sub->new_output.link); + wl_list_remove(&sub->destroy.link); + wl_list_remove(&sub->link); + free(sub); +} + +static void multi_backend_destroy(struct wlr_backend *wlr_backend) { + struct wlr_multi_backend *backend = multi_backend_from_backend(wlr_backend); + + wl_list_remove(&backend->display_destroy.link); + + struct subbackend_state *sub, *next; + wl_list_for_each_safe(sub, next, &backend->backends, link) { + wlr_backend_destroy(sub->backend); + } + + // Destroy this backend only after removing all sub-backends + wlr_signal_emit_safe(&wlr_backend->events.destroy, backend); + free(backend); +} + +static struct wlr_renderer *multi_backend_get_renderer( + 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) { + struct wlr_renderer *rend = wlr_backend_get_renderer(sub->backend); + if (rend != NULL) { + return rend; + } + } + 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) { + struct wlr_multi_backend *backend = + wl_container_of(listener, backend, display_destroy); + multi_backend_destroy((struct wlr_backend*)backend); +} + +struct wlr_backend *wlr_multi_backend_create(struct wl_display *display) { + struct wlr_multi_backend *backend = + calloc(1, sizeof(struct wlr_multi_backend)); + if (!backend) { + wlr_log(WLR_ERROR, "Backend allocation failed"); + return NULL; + } + + wl_list_init(&backend->backends); + wlr_backend_init(&backend->backend, &backend_impl); + + wl_signal_init(&backend->events.backend_add); + wl_signal_init(&backend->events.backend_remove); + + backend->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &backend->display_destroy); + + return &backend->backend; +} + +bool wlr_backend_is_multi(struct wlr_backend *b) { + return b->impl == &backend_impl; +} + +static void new_input_reemit(struct wl_listener *listener, void *data) { + struct subbackend_state *state = wl_container_of(listener, + state, new_input); + wlr_signal_emit_safe(&state->container->events.new_input, data); +} + +static void new_output_reemit(struct wl_listener *listener, void *data) { + struct subbackend_state *state = wl_container_of(listener, + state, new_output); + wlr_signal_emit_safe(&state->container->events.new_output, data); +} + +static void handle_subbackend_destroy(struct wl_listener *listener, + void *data) { + struct subbackend_state *state = wl_container_of(listener, state, destroy); + subbackend_state_destroy(state); +} + +static struct subbackend_state *multi_backend_get_subbackend(struct wlr_multi_backend *multi, + struct wlr_backend *backend) { + struct subbackend_state *sub = NULL; + wl_list_for_each(sub, &multi->backends, link) { + if (sub->backend == backend) { + return sub; + } + } + return NULL; +} + +bool wlr_multi_backend_add(struct wlr_backend *_multi, + struct wlr_backend *backend) { + struct wlr_multi_backend *multi = multi_backend_from_backend(_multi); + + if (multi_backend_get_subbackend(multi, backend)) { + // already added + return true; + } + + struct wlr_renderer *multi_renderer = + multi_backend_get_renderer(&multi->backend); + struct wlr_renderer *backend_renderer = wlr_backend_get_renderer(backend); + if (multi_renderer != NULL && backend_renderer != NULL && multi_renderer != backend_renderer) { + wlr_log(WLR_ERROR, "Could not add backend: multiple renderers at the " + "same time aren't supported"); + return false; + } + + struct subbackend_state *sub = calloc(1, sizeof(struct subbackend_state)); + if (sub == NULL) { + wlr_log(WLR_ERROR, "Could not add backend: allocation failed"); + return false; + } + wl_list_insert(&multi->backends, &sub->link); + + sub->backend = backend; + sub->container = &multi->backend; + + wl_signal_add(&backend->events.destroy, &sub->destroy); + sub->destroy.notify = handle_subbackend_destroy; + + wl_signal_add(&backend->events.new_input, &sub->new_input); + sub->new_input.notify = new_input_reemit; + + wl_signal_add(&backend->events.new_output, &sub->new_output); + sub->new_output.notify = new_output_reemit; + + wlr_signal_emit_safe(&multi->events.backend_add, backend); + return true; +} + +void wlr_multi_backend_remove(struct wlr_backend *_multi, + struct wlr_backend *backend) { + struct wlr_multi_backend *multi = multi_backend_from_backend(_multi); + + struct subbackend_state *sub = + multi_backend_get_subbackend(multi, backend); + + if (sub) { + wlr_signal_emit_safe(&multi->events.backend_remove, backend); + subbackend_state_destroy(sub); + } +} + +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; + return wl_list_length(&backend->backends) < 1; +} + +void wlr_multi_for_each_backend(struct wlr_backend *_backend, + void (*callback)(struct wlr_backend *backend, void *data), void *data) { + assert(wlr_backend_is_multi(_backend)); + struct wlr_multi_backend *backend = (struct wlr_multi_backend *)_backend; + struct subbackend_state *sub; + wl_list_for_each(sub, &backend->backends, link) { + callback(sub->backend, data); + } +} diff --git a/backend/session/direct-freebsd.c b/backend/session/direct-freebsd.c new file mode 100644 index 00000000..342d0d4e --- /dev/null +++ b/backend/session/direct-freebsd.c @@ -0,0 +1,268 @@ +#include <assert.h> +#include <dev/evdev/input.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/consio.h> +#include <sys/ioctl.h> +#include <sys/kbio.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <termios.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/backend/session/interface.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include "backend/session/direct-ipc.h" +#include "util/signal.h" + +const struct session_impl session_direct; + +struct direct_session { + struct wlr_session base; + int tty_fd; + int old_tty; + int old_kbmode; + int sock; + pid_t child; + + struct wl_event_source *vt_source; +}; + +static struct direct_session *direct_session_from_session( + struct wlr_session *base) { + assert(base->impl == &session_direct); + return (struct direct_session *)base; +} + +static int direct_session_open(struct wlr_session *base, const char *path) { + struct direct_session *session = direct_session_from_session(base); + + int fd = direct_ipc_open(session->sock, path); + if (fd < 0) { + wlr_log(WLR_ERROR, "Failed to open %s: %s%s", path, strerror(-fd), + fd == -EINVAL ? "; is another display server running?" : ""); + return fd; + } + + return fd; +} + +static void direct_session_close(struct wlr_session *base, int fd) { + struct direct_session *session = direct_session_from_session(base); + + int ev; + struct drm_version dv = {0}; + if (ioctl(fd, DRM_IOCTL_VERSION, &dv) == 0) { + direct_ipc_dropmaster(session->sock, fd); + } else if (ioctl(fd, EVIOCGVERSION, &ev) == 0) { + ioctl(fd, EVIOCREVOKE, 0); + } + + close(fd); +} + +static bool direct_change_vt(struct wlr_session *base, unsigned vt) { + struct direct_session *session = direct_session_from_session(base); + + // Only seat0 has VTs associated with it + if (strcmp(session->base.seat, "seat0") != 0) { + return true; + } + + return ioctl(session->tty_fd, VT_ACTIVATE, (int)vt) == 0; +} + +static void direct_session_destroy(struct wlr_session *base) { + struct direct_session *session = direct_session_from_session(base); + + if (strcmp(session->base.seat, "seat0") == 0) { + struct vt_mode mode = { + .mode = VT_AUTO, + }; + + errno = 0; + + ioctl(session->tty_fd, KDSKBMODE, session->old_kbmode); + ioctl(session->tty_fd, KDSETMODE, KD_TEXT); + ioctl(session->tty_fd, VT_SETMODE, &mode); + + ioctl(session->tty_fd, VT_ACTIVATE, session->old_tty); + + if (errno) { + wlr_log(WLR_ERROR, "Failed to restore tty"); + } + + wl_event_source_remove(session->vt_source); + close(session->tty_fd); + } + + direct_ipc_finish(session->sock, session->child); + close(session->sock); + + free(session); +} + +static int vt_handler(int signo, void *data) { + struct direct_session *session = data; + struct drm_version dv = {0}; + struct wlr_device *dev; + + if (session->base.active) { + session->base.active = false; + wlr_signal_emit_safe(&session->base.session_signal, session); + + wl_list_for_each(dev, &session->base.devices, link) { + if (ioctl(dev->fd, DRM_IOCTL_VERSION, &dv) == 0) { + direct_ipc_dropmaster(session->sock, dev->fd); + } + } + + ioctl(session->tty_fd, VT_RELDISP, 1); + } else { + ioctl(session->tty_fd, VT_RELDISP, VT_ACKACQ); + + wl_list_for_each(dev, &session->base.devices, link) { + if (ioctl(dev->fd, DRM_IOCTL_VERSION, &dv) == 0) { + direct_ipc_setmaster(session->sock, dev->fd); + } + } + + session->base.active = true; + wlr_signal_emit_safe(&session->base.session_signal, session); + } + + return 1; +} + +static bool setup_tty(struct direct_session *session, struct wl_display *display) { + int fd = -1, tty = -1, tty0_fd = -1, old_tty = 1; + if ((tty0_fd = open("/dev/ttyv0", O_RDWR | O_CLOEXEC)) < 0) { + wlr_log_errno(WLR_ERROR, "Could not open /dev/ttyv0 to find a free vt"); + goto error; + } + if (ioctl(tty0_fd, VT_GETACTIVE, &old_tty) != 0) { + wlr_log_errno(WLR_ERROR, "Could not get active vt"); + goto error; + } + if (ioctl(tty0_fd, VT_OPENQRY, &tty) != 0) { + wlr_log_errno(WLR_ERROR, "Could not find a free vt"); + goto error; + } + close(tty0_fd); + char tty_path[64]; + snprintf(tty_path, sizeof(tty_path), "/dev/ttyv%d", tty - 1); + wlr_log(WLR_INFO, "Using tty %s", tty_path); + fd = open(tty_path, O_RDWR | O_NOCTTY | O_CLOEXEC); + + if (fd == -1) { + wlr_log_errno(WLR_ERROR, "Cannot open tty"); + return false; + } + + ioctl(fd, VT_ACTIVATE, tty); + ioctl(fd, VT_WAITACTIVE, tty); + + int old_kbmode; + if (ioctl(fd, KDGKBMODE, &old_kbmode)) { + wlr_log_errno(WLR_ERROR, "Failed to read tty %d keyboard mode", tty); + goto error; + } + + if (ioctl(fd, KDSKBMODE, K_CODE)) { + wlr_log_errno(WLR_ERROR, + "Failed to set keyboard mode K_CODE on tty %d", tty); + goto error; + } + + if (ioctl(fd, KDSETMODE, KD_GRAPHICS)) { + wlr_log_errno(WLR_ERROR, "Failed to set graphics mode on tty %d", tty); + goto error; + } + + struct vt_mode mode = { + .mode = VT_PROCESS, + .relsig = SIGUSR2, + .acqsig = SIGUSR2, + .frsig = SIGIO, // has to be set + }; + + if (ioctl(fd, VT_SETMODE, &mode) < 0) { + wlr_log(WLR_ERROR, "Failed to take control of tty %d", tty); + goto error; + } + + struct wl_event_loop *loop = wl_display_get_event_loop(display); + session->vt_source = wl_event_loop_add_signal(loop, SIGUSR2, + vt_handler, session); + if (!session->vt_source) { + goto error; + } + + session->base.vtnr = tty; + session->tty_fd = fd; + session->old_tty = old_tty; + session->old_kbmode = old_kbmode; + + return true; + +error: + // In case we could not get the last active one, drop back to tty 1, + // better than hanging in a useless blank console. Otherwise activate the + // last active. + ioctl(fd, VT_ACTIVATE, old_tty); + close(fd); + return false; +} + +static struct wlr_session *direct_session_create(struct wl_display *disp) { + struct direct_session *session = calloc(1, sizeof(*session)); + if (!session) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + + session->sock = direct_ipc_init(&session->child); + if (session->sock == -1) { + goto error_session; + } + + const char *seat = getenv("XDG_SEAT"); + if (!seat) { + seat = "seat0"; + } + + if (strcmp(seat, "seat0") == 0) { + if (!setup_tty(session, disp)) { + goto error_ipc; + } + } else { + session->base.vtnr = 0; + session->tty_fd = -1; + } + + wlr_log(WLR_INFO, "Successfully loaded direct session"); + + snprintf(session->base.seat, sizeof(session->base.seat), "%s", seat); + session->base.impl = &session_direct; + return &session->base; + +error_ipc: + direct_ipc_finish(session->sock, session->child); + close(session->sock); +error_session: + free(session); + return NULL; +} + +const struct session_impl session_direct = { + .create = direct_session_create, + .destroy = direct_session_destroy, + .open = direct_session_open, + .close = direct_session_close, + .change_vt = direct_change_vt, +}; diff --git a/backend/session/direct-ipc.c b/backend/session/direct-ipc.c new file mode 100644 index 00000000..2b9634da --- /dev/null +++ b/backend/session/direct-ipc.c @@ -0,0 +1,269 @@ +#define _POSIX_C_SOURCE 200809L +#ifdef __FreeBSD__ +#define __BSD_VISIBLE 1 +#include <dev/evdev/input.h> +#endif +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <wlr/config.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#ifdef __linux__ +#include <sys/sysmacros.h> +#include <linux/major.h> +#endif +#include "backend/session/direct-ipc.h" + +enum { DRM_MAJOR = 226 }; + +#if WLR_HAS_LIBCAP +#include <sys/capability.h> + +static bool have_permissions(void) { + cap_t cap = cap_get_proc(); + cap_flag_value_t val; + + if (!cap || cap_get_flag(cap, CAP_SYS_ADMIN, CAP_PERMITTED, &val) || val != CAP_SET) { + wlr_log(WLR_ERROR, "Do not have CAP_SYS_ADMIN; cannot become DRM master"); + cap_free(cap); + return false; + } + + cap_free(cap); + return true; +} +#else +static bool have_permissions(void) { +#ifdef __linux__ + if (geteuid() != 0) { + wlr_log(WLR_ERROR, "Do not have root privileges; cannot become DRM master"); + return false; + } +#endif + return true; +} +#endif + +static void send_msg(int sock, int fd, void *buf, size_t buf_len) { + char control[CMSG_SPACE(sizeof(fd))] = {0}; + struct iovec iovec = { .iov_base = buf, .iov_len = buf_len }; + struct msghdr msghdr = {0}; + + if (buf) { + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + } + + if (fd >= 0) { + msghdr.msg_control = &control; + msghdr.msg_controllen = sizeof(control); + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msghdr); + *cmsg = (struct cmsghdr) { + .cmsg_level = SOL_SOCKET, + .cmsg_type = SCM_RIGHTS, + .cmsg_len = CMSG_LEN(sizeof(fd)), + }; + memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd)); + } + + ssize_t ret; + do { + ret = sendmsg(sock, &msghdr, 0); + } while (ret < 0 && errno == EINTR); +} + +static ssize_t recv_msg(int sock, int *fd_out, void *buf, size_t buf_len) { + char control[CMSG_SPACE(sizeof(*fd_out))] = {0}; + struct iovec iovec = { .iov_base = buf, .iov_len = buf_len }; + struct msghdr msghdr = {0}; + + if (buf) { + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + } + + if (fd_out) { + msghdr.msg_control = &control; + msghdr.msg_controllen = sizeof(control); + } + + ssize_t ret; + do { + ret = recvmsg(sock, &msghdr, MSG_CMSG_CLOEXEC); + } while (ret < 0 && errno == EINTR); + + if (fd_out) { + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msghdr); + if (cmsg) { + memcpy(fd_out, CMSG_DATA(cmsg), sizeof(*fd_out)); + } else { + *fd_out = -1; + } + } + + return ret; +} + +enum msg_type { + MSG_OPEN, + MSG_SETMASTER, + MSG_DROPMASTER, + MSG_END, +}; + +struct msg { + enum msg_type type; + char path[256]; +}; + +static void communicate(int sock) { + struct msg msg; + int drm_fd = -1; + bool running = true; + + while (running && recv_msg(sock, &drm_fd, &msg, sizeof(msg)) > 0) { + switch (msg.type) { + case MSG_OPEN: + errno = 0; + + // These are the same flags that logind opens files with + int fd = open(msg.path, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + int ret = errno; + if (fd == -1) { + goto error; + } + +#ifndef __FreeBSD__ + struct stat st; + if (fstat(fd, &st) < 0) { + ret = errno; + goto error; + } + + uint32_t maj = major(st.st_rdev); + if (maj != INPUT_MAJOR && maj != DRM_MAJOR) { + ret = ENOTSUP; + goto error; + } + + if (maj == DRM_MAJOR && drmSetMaster(fd)) { + ret = errno; + } +#else + int ev; + struct drm_version dv = {0}; + if (ioctl(fd, EVIOCGVERSION, &ev) == -1 && + ioctl(fd, DRM_IOCTL_VERSION, &dv) == -1) { + ret = ENOTSUP; + goto error; + } + + if (dv.version_major != 0 && drmSetMaster(fd)) { + ret = errno; + } +#endif + +error: + send_msg(sock, ret ? -1 : fd, &ret, sizeof(ret)); + if (fd >= 0) { + close(fd); + } + + break; + + case MSG_SETMASTER: + drmSetMaster(drm_fd); + close(drm_fd); + send_msg(sock, -1, NULL, 0); + break; + + case MSG_DROPMASTER: + drmDropMaster(drm_fd); + close(drm_fd); + send_msg(sock, -1, NULL, 0); + break; + + case MSG_END: + running = false; + send_msg(sock, -1, NULL, 0); + break; + } + } + + close(sock); +} + +int direct_ipc_open(int sock, const char *path) { + struct msg msg = { .type = MSG_OPEN }; + snprintf(msg.path, sizeof(msg.path), "%s", path); + + send_msg(sock, -1, &msg, sizeof(msg)); + + int fd, err, ret; + int retry = 0; + do { + ret = recv_msg(sock, &fd, &err, sizeof(err)); + } while (ret == 0 && retry++ < 3); + + return err ? -err : fd; +} + +void direct_ipc_setmaster(int sock, int fd) { + struct msg msg = { .type = MSG_SETMASTER }; + + send_msg(sock, fd, &msg, sizeof(msg)); + recv_msg(sock, NULL, NULL, 0); +} + +void direct_ipc_dropmaster(int sock, int fd) { + struct msg msg = { .type = MSG_DROPMASTER }; + + send_msg(sock, fd, &msg, sizeof(msg)); + recv_msg(sock, NULL, NULL, 0); +} + +void direct_ipc_finish(int sock, pid_t pid) { + struct msg msg = { .type = MSG_END }; + + send_msg(sock, -1, &msg, sizeof(msg)); + recv_msg(sock, NULL, NULL, 0); + + waitpid(pid, NULL, 0); +} + +int direct_ipc_init(pid_t *pid_out) { + if (!have_permissions()) { + return -1; + } + + int sock[2]; + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sock) < 0) { + wlr_log_errno(WLR_ERROR, "Failed to create socket pair"); + return -1; + } + + pid_t pid = fork(); + if (pid < 0) { + wlr_log_errno(WLR_ERROR, "Fork failed"); + close(sock[0]); + close(sock[1]); + return -1; + } else if (pid == 0) { + close(sock[0]); + communicate(sock[1]); + _Exit(0); + } + + close(sock[1]); + *pid_out = pid; + return sock[0]; +} diff --git a/backend/session/direct.c b/backend/session/direct.c new file mode 100644 index 00000000..0912cd58 --- /dev/null +++ b/backend/session/direct.c @@ -0,0 +1,278 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/input.h> +#include <linux/kd.h> +#include <linux/major.h> +#include <linux/vt.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/backend/session/interface.h> +#include <wlr/util/log.h> +#include "backend/session/direct-ipc.h" +#include "util/signal.h" + +enum { DRM_MAJOR = 226 }; + +const struct session_impl session_direct; + +struct direct_session { + struct wlr_session base; + int tty_fd; + int old_kbmode; + int sock; + pid_t child; + + struct wl_event_source *vt_source; +}; + +static struct direct_session *direct_session_from_session( + struct wlr_session *base) { + assert(base->impl == &session_direct); + return (struct direct_session *)base; +} + +static int direct_session_open(struct wlr_session *base, const char *path) { + struct direct_session *session = direct_session_from_session(base); + + int fd = direct_ipc_open(session->sock, path); + if (fd < 0) { + wlr_log(WLR_ERROR, "Failed to open %s: %s%s", path, strerror(-fd), + fd == -EINVAL ? "; is another display server running?" : ""); + return fd; + } + + struct stat st; + if (fstat(fd, &st) < 0) { + close(fd); + return -errno; + } + + if (major(st.st_rdev) == DRM_MAJOR) { + direct_ipc_setmaster(session->sock, fd); + } + + return fd; +} + +static void direct_session_close(struct wlr_session *base, int fd) { + struct direct_session *session = direct_session_from_session(base); + + struct stat st; + if (fstat(fd, &st) < 0) { + wlr_log_errno(WLR_ERROR, "Stat failed"); + close(fd); + return; + } + + if (major(st.st_rdev) == DRM_MAJOR) { + direct_ipc_dropmaster(session->sock, fd); + } else if (major(st.st_rdev) == INPUT_MAJOR) { + ioctl(fd, EVIOCREVOKE, 0); + } + + close(fd); +} + +static bool direct_change_vt(struct wlr_session *base, unsigned vt) { + struct direct_session *session = direct_session_from_session(base); + + // Only seat0 has VTs associated with it + if (strcmp(session->base.seat, "seat0") != 0) { + return true; + } + + return ioctl(session->tty_fd, VT_ACTIVATE, (int)vt) == 0; +} + +static void direct_session_destroy(struct wlr_session *base) { + struct direct_session *session = direct_session_from_session(base); + + if (strcmp(session->base.seat, "seat0") == 0) { + struct vt_mode mode = { + .mode = VT_AUTO, + }; + errno = 0; + + ioctl(session->tty_fd, KDSKBMODE, session->old_kbmode); + ioctl(session->tty_fd, KDSETMODE, KD_TEXT); + ioctl(session->tty_fd, VT_SETMODE, &mode); + + if (errno) { + wlr_log(WLR_ERROR, "Failed to restore tty"); + } + + wl_event_source_remove(session->vt_source); + close(session->tty_fd); + } + + direct_ipc_finish(session->sock, session->child); + close(session->sock); + + free(session); +} + +static int vt_handler(int signo, void *data) { + struct direct_session *session = data; + + if (session->base.active) { + session->base.active = false; + wlr_signal_emit_safe(&session->base.session_signal, session); + + struct wlr_device *dev; + wl_list_for_each(dev, &session->base.devices, link) { + if (major(dev->dev) == DRM_MAJOR) { + direct_ipc_dropmaster(session->sock, + dev->fd); + } + } + + ioctl(session->tty_fd, VT_RELDISP, 1); + } else { + ioctl(session->tty_fd, VT_RELDISP, VT_ACKACQ); + + struct wlr_device *dev; + wl_list_for_each(dev, &session->base.devices, link) { + if (major(dev->dev) == DRM_MAJOR) { + direct_ipc_setmaster(session->sock, + dev->fd); + } + } + + session->base.active = true; + wlr_signal_emit_safe(&session->base.session_signal, session); + } + + return 1; +} + +static bool setup_tty(struct direct_session *session, struct wl_display *display) { + int fd = open("/dev/tty", O_RDWR); + if (fd == -1) { + wlr_log_errno(WLR_ERROR, "Cannot open /dev/tty"); + return false; + } + + struct vt_stat vt_stat; + if (ioctl(fd, VT_GETSTATE, &vt_stat)) { + wlr_log_errno(WLR_ERROR, "Could not get current tty number"); + goto error; + } + + int tty = vt_stat.v_active; + int ret, kd_mode, old_kbmode; + + ret = ioctl(fd, KDGETMODE, &kd_mode); + if (ret) { + wlr_log_errno(WLR_ERROR, "Failed to get tty mode"); + goto error; + } + + if (kd_mode != KD_TEXT) { + wlr_log(WLR_ERROR, + "tty already in graphics mode; is another display server running?"); + goto error; + } + + ioctl(fd, VT_ACTIVATE, tty); + ioctl(fd, VT_WAITACTIVE, tty); + + if (ioctl(fd, KDGKBMODE, &old_kbmode)) { + wlr_log_errno(WLR_ERROR, "Failed to read keyboard mode"); + goto error; + } + + if (ioctl(fd, KDSKBMODE, K_OFF)) { + wlr_log_errno(WLR_ERROR, "Failed to set keyboard mode"); + goto error; + } + + if (ioctl(fd, KDSETMODE, KD_GRAPHICS)) { + wlr_log_errno(WLR_ERROR, "Failed to set graphics mode on tty"); + goto error; + } + + struct vt_mode mode = { + .mode = VT_PROCESS, + .relsig = SIGUSR2, + .acqsig = SIGUSR2, + }; + + if (ioctl(fd, VT_SETMODE, &mode) < 0) { + wlr_log(WLR_ERROR, "Failed to take control of tty"); + goto error; + } + + struct wl_event_loop *loop = wl_display_get_event_loop(display); + session->vt_source = wl_event_loop_add_signal(loop, SIGUSR2, + vt_handler, session); + if (!session->vt_source) { + goto error; + } + + session->base.vtnr = tty; + session->tty_fd = fd; + session->old_kbmode = old_kbmode; + + return true; + +error: + close(fd); + return false; +} + +static struct wlr_session *direct_session_create(struct wl_display *disp) { + struct direct_session *session = calloc(1, sizeof(*session)); + if (!session) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + + session->sock = direct_ipc_init(&session->child); + if (session->sock == -1) { + goto error_session; + } + + const char *seat = getenv("XDG_SEAT"); + if (!seat) { + seat = "seat0"; + } + + if (strcmp(seat, "seat0") == 0) { + if (!setup_tty(session, disp)) { + goto error_ipc; + } + } else { + session->base.vtnr = 0; + session->tty_fd = -1; + } + + snprintf(session->base.seat, sizeof(session->base.seat), "%s", seat); + session->base.impl = &session_direct; + + wlr_log(WLR_INFO, "Successfully loaded direct session"); + return &session->base; + +error_ipc: + direct_ipc_finish(session->sock, session->child); + close(session->sock); +error_session: + free(session); + return NULL; +} + +const struct session_impl session_direct = { + .create = direct_session_create, + .destroy = direct_session_destroy, + .open = direct_session_open, + .close = direct_session_close, + .change_vt = direct_change_vt, +}; diff --git a/backend/session/logind.c b/backend/session/logind.c new file mode 100644 index 00000000..9a1383ce --- /dev/null +++ b/backend/session/logind.c @@ -0,0 +1,562 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/backend/session/interface.h> +#include <wlr/config.h> +#include <wlr/util/log.h> +#include "util/signal.h" + +#if WLR_HAS_SYSTEMD + #include <systemd/sd-bus.h> + #include <systemd/sd-login.h> +#elif WLR_HAS_ELOGIND + #include <elogind/sd-bus.h> + #include <elogind/sd-login.h> +#endif + +enum { DRM_MAJOR = 226 }; + +const struct session_impl session_logind; + +struct logind_session { + struct wlr_session base; + + sd_bus *bus; + struct wl_event_source *event; + + 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( + struct wlr_session *base) { + assert(base->impl == &session_logind); + return (struct logind_session *)base; +} + +static int logind_take_device(struct wlr_session *base, const char *path) { + struct logind_session *session = logind_session_from_session(base); + + int fd = -1; + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + struct stat st; + if (stat(path, &st) < 0) { + wlr_log(WLR_ERROR, "Failed to stat '%s'", 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)); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to take device '%s': %s", path, + error.message); + goto out; + } + + int paused = 0; + ret = sd_bus_message_read(msg, "hb", &fd, &paused); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse D-Bus response for '%s': %s", + path, strerror(-ret)); + goto out; + } + + // The original fd seems to be closed when the message is freed + // so we just clone it. + fd = fcntl(fd, F_DUPFD_CLOEXEC, 0); + if (fd < 0) { + wlr_log(WLR_ERROR, "Failed to clone file descriptor for '%s': %s", + path, strerror(errno)); + goto out; + } + +out: + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return fd; +} + +static void logind_release_device(struct wlr_session *base, int fd) { + struct logind_session *session = logind_session_from_session(base); + + struct stat st; + if (fstat(fd, &st) < 0) { + wlr_log(WLR_ERROR, "Failed to stat device '%d': %s", fd, + strerror(errno)); + return; + } + + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + int ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + session->path, "org.freedesktop.login1.Session", "ReleaseDevice", + &error, &msg, "uu", major(st.st_rdev), minor(st.st_rdev)); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to release device '%d': %s", fd, + error.message); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + close(fd); +} + +static bool logind_change_vt(struct wlr_session *base, unsigned vt) { + struct logind_session *session = logind_session_from_session(base); + + // Only seat0 has VTs associated with it + if (strcmp(session->base.seat, "seat0") != 0) { + return true; + } + + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + "/org/freedesktop/login1/seat/self", "org.freedesktop.login1.Seat", "SwitchTo", + &error, &msg, "u", (uint32_t)vt); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to change to vt '%d'", vt); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return ret >= 0; +} + +static bool find_session_path(struct logind_session *session) { + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + "/org/freedesktop/login1", "org.freedesktop.login1.Manager", + "GetSession", &error, &msg, "s", session->id); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get session path: %s", error.message); + goto out; + } + + const char *path; + ret = sd_bus_message_read(msg, "o", &path); + if (ret < 0) { + wlr_log(WLR_ERROR, "Could not parse session path: %s", error.message); + goto out; + } + session->path = strdup(path); + +out: + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + + return ret >= 0; +} + +static bool session_activate(struct logind_session *session) { + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + session->path, "org.freedesktop.login1.Session", "Activate", + &error, &msg, ""); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to activate session: %s", error.message); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return ret >= 0; +} + +static bool take_control(struct logind_session *session) { + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + session->path, "org.freedesktop.login1.Session", "TakeControl", + &error, &msg, "b", false); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to take control of session: %s", + error.message); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return ret >= 0; +} + +static void release_control(struct logind_session *session) { + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + session->path, "org.freedesktop.login1.Session", "ReleaseControl", + &error, &msg, ""); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to release control of session: %s", + error.message); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); +} + +static void logind_session_destroy(struct wlr_session *base) { + struct logind_session *session = logind_session_from_session(base); + + release_control(session); + + wl_event_source_remove(session->event); + sd_bus_unref(session->bus); + free(session->id); + free(session->path); + free(session); +} + +static int session_removed(sd_bus_message *msg, void *userdata, + sd_bus_error *ret_error) { + wlr_log(WLR_INFO, "SessionRemoved signal received"); + return 0; +} + +static struct wlr_device *find_device(struct wlr_session *session, + dev_t devnum) { + struct wlr_device *dev; + + wl_list_for_each(dev, &session->devices, link) { + if (dev->dev == devnum) { + return dev; + } + } + + wlr_log(WLR_ERROR, "Tried to use dev_t %lu not opened by session", + (unsigned long)devnum); + assert(0); +} + +static int pause_device(sd_bus_message *msg, void *userdata, + sd_bus_error *ret_error) { + struct logind_session *session = userdata; + int ret; + + uint32_t major, minor; + const char *type; + ret = sd_bus_message_read(msg, "uus", &major, &minor, &type); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse D-Bus response for PauseDevice: %s", + strerror(-ret)); + goto error; + } + + if (major == DRM_MAJOR) { + assert(session->has_drm); + session->base.active = false; + wlr_signal_emit_safe(&session->base.session_signal, session); + } + + if (strcmp(type, "pause") == 0) { + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + session->path, "org.freedesktop.login1.Session", "PauseDeviceComplete", + ret_error, &msg, "uu", major, minor); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to send PauseDeviceComplete signal: %s", + strerror(-ret)); + } + } + +error: + return 0; +} + +static int resume_device(sd_bus_message *msg, void *userdata, sd_bus_error *ret_error) { + struct logind_session *session = userdata; + int ret; + + int fd; + uint32_t major, minor; + ret = sd_bus_message_read(msg, "uuh", &major, &minor, &fd); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse D-Bus response for ResumeDevice: %s", + strerror(-ret)); + goto error; + } + + if (major == DRM_MAJOR) { + struct wlr_device *dev = find_device(&session->base, makedev(major, minor)); + dup2(fd, dev->fd); + + if (!session->base.active) { + session->base.active = true; + wlr_signal_emit_safe(&session->base.session_signal, session); + } + } + +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", + error.message); + 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; + + char str[256]; + const char *fmt = "type='signal'," + "sender='org.freedesktop.login1'," + "interface='org.freedesktop.login1.%s'," + "member='%s'," + "path='%s'"; + + snprintf(str, sizeof(str), fmt, "Manager", "SessionRemoved", + "/org/freedesktop/login1"); + ret = sd_bus_add_match(session->bus, NULL, str, session_removed, session); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); + return false; + } + + snprintf(str, sizeof(str), fmt, "Session", "PauseDevice", session->path); + ret = sd_bus_add_match(session->bus, NULL, str, pause_device, session); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); + return false; + } + + snprintf(str, sizeof(str), fmt, "Session", "ResumeDevice", session->path); + ret = sd_bus_add_match(session->bus, NULL, str, resume_device, session); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to add D-Bus match: %s", strerror(-ret)); + 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; +} + +static int dbus_event(int fd, uint32_t mask, void *data) { + sd_bus *bus = data; + while (sd_bus_process(bus, NULL) > 0) { + // Do nothing. + } + return 1; +} + +static struct wlr_session *logind_session_create(struct wl_display *disp) { + int ret; + struct logind_session *session = calloc(1, sizeof(*session)); + if (!session) { + wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno)); + return NULL; + } + + ret = sd_pid_get_session(getpid(), &session->id); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get session id: %s", strerror(-ret)); + goto error; + } + + char *seat; + ret = sd_session_get_seat(session->id, &seat); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get seat id: %s", strerror(-ret)); + goto error; + } + snprintf(session->base.seat, sizeof(session->base.seat), "%s", seat); + + if (strcmp(seat, "seat0") == 0) { + ret = sd_session_get_vt(session->id, &session->base.vtnr); + if (ret < 0) { + wlr_log(WLR_ERROR, "Session not running in virtual terminal"); + goto error; + } + } + free(seat); + + ret = sd_bus_default_system(&session->bus); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to open D-Bus connection: %s", strerror(-ret)); + goto error; + } + + if (!find_session_path(session)) { + sd_bus_unref(session->bus); + goto error; + } + + if (!add_signal_matches(session)) { + goto error_bus; + } + + struct wl_event_loop *event_loop = wl_display_get_event_loop(disp); + session->event = wl_event_loop_add_fd(event_loop, sd_bus_get_fd(session->bus), + WL_EVENT_READABLE, dbus_event, session->bus); + + if (!session_activate(session)) { + goto error_bus; + } + + if (!take_control(session)) { + goto error_bus; + } + + wlr_log(WLR_INFO, "Successfully loaded logind session"); + + session->base.impl = &session_logind; + return &session->base; + +error_bus: + sd_bus_unref(session->bus); + free(session->path); + +error: + free(session->id); + return NULL; +} + +const struct session_impl session_logind = { + .create = logind_session_create, + .destroy = logind_session_destroy, + .open = logind_take_device, + .close = logind_release_device, + .change_vt = logind_change_vt, +}; diff --git a/backend/session/session.c b/backend/session/session.c new file mode 100644 index 00000000..96cde65c --- /dev/null +++ b/backend/session/session.c @@ -0,0 +1,359 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <libudev.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <wayland-server.h> +#include <wlr/backend/session.h> +#include <wlr/backend/session/interface.h> +#include <wlr/config.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include "util/signal.h" + +extern const struct session_impl session_logind; +extern const struct session_impl session_direct; + +static const struct session_impl *impls[] = { +#if WLR_HAS_SYSTEMD || WLR_HAS_ELOGIND + &session_logind, +#endif + &session_direct, + NULL, +}; + +static int udev_event(int fd, uint32_t mask, void *data) { + struct wlr_session *session = data; + + struct udev_device *udev_dev = udev_monitor_receive_device(session->mon); + if (!udev_dev) { + return 1; + } + + const char *action = udev_device_get_action(udev_dev); + + wlr_log(WLR_DEBUG, "udev event for %s (%s)", + udev_device_get_sysname(udev_dev), action); + + if (!action || strcmp(action, "change") != 0) { + goto out; + } + + dev_t devnum = udev_device_get_devnum(udev_dev); + struct wlr_device *dev; + + wl_list_for_each(dev, &session->devices, link) { + if (dev->dev == devnum) { + wlr_signal_emit_safe(&dev->signal, session); + break; + } + } + +out: + udev_device_unref(udev_dev); + return 1; +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_session *session = + wl_container_of(listener, session, display_destroy); + wlr_session_destroy(session); +} + +struct wlr_session *wlr_session_create(struct wl_display *disp) { + struct wlr_session *session = NULL; + + const char *env_wlr_session = getenv("WLR_SESSION"); + if (env_wlr_session) { + if (!strcmp(env_wlr_session, "logind") || !strcmp(env_wlr_session, "systemd")) { + #if WLR_HAS_SYSTEMD || WLR_HAS_ELOGIND + session = session_logind.create(disp); + #else + wlr_log(WLR_ERROR, "wlroots is not compiled with logind support"); + #endif + } else if (!strcmp(env_wlr_session, "direct")) { + session = session_direct.create(disp); + } else { + wlr_log(WLR_ERROR, "WLR_SESSION has an invalid value: %s", env_wlr_session); + } + } else { + const struct session_impl **iter; + for (iter = impls; !session && *iter; ++iter) { + session = (*iter)->create(disp); + } + } + + if (!session) { + wlr_log(WLR_ERROR, "Failed to load session backend"); + return NULL; + } + + session->active = true; + wl_signal_init(&session->session_signal); + wl_signal_init(&session->events.destroy); + wl_list_init(&session->devices); + + session->udev = udev_new(); + if (!session->udev) { + wlr_log_errno(WLR_ERROR, "Failed to create udev context"); + goto error_session; + } + + session->mon = udev_monitor_new_from_netlink(session->udev, "udev"); + if (!session->mon) { + wlr_log_errno(WLR_ERROR, "Failed to create udev monitor"); + goto error_udev; + } + + udev_monitor_filter_add_match_subsystem_devtype(session->mon, "drm", NULL); + udev_monitor_enable_receiving(session->mon); + + struct wl_event_loop *event_loop = wl_display_get_event_loop(disp); + int fd = udev_monitor_get_fd(session->mon); + + session->udev_event = wl_event_loop_add_fd(event_loop, fd, + WL_EVENT_READABLE, udev_event, session); + if (!session->udev_event) { + wlr_log_errno(WLR_ERROR, "Failed to create udev event source"); + goto error_mon; + } + + session->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(disp, &session->display_destroy); + + return session; + +error_mon: + udev_monitor_unref(session->mon); +error_udev: + udev_unref(session->udev); +error_session: + session->impl->destroy(session); + return NULL; +} + +void wlr_session_destroy(struct wlr_session *session) { + if (!session) { + return; + } + + wlr_signal_emit_safe(&session->events.destroy, session); + wl_list_remove(&session->display_destroy.link); + + wl_event_source_remove(session->udev_event); + udev_monitor_unref(session->mon); + udev_unref(session->udev); + + session->impl->destroy(session); +} + +int wlr_session_open_file(struct wlr_session *session, const char *path) { + int fd = session->impl->open(session, path); + if (fd < 0) { + return fd; + } + + struct wlr_device *dev = malloc(sizeof(*dev)); + if (!dev) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto error; + } + + struct stat st; + if (fstat(fd, &st) < 0) { + wlr_log_errno(WLR_ERROR, "Stat failed"); + goto error; + } + + dev->fd = fd; + dev->dev = st.st_rdev; + wl_signal_init(&dev->signal); + wl_list_insert(&session->devices, &dev->link); + + return fd; + +error: + free(dev); + return fd; +} + +static struct wlr_device *find_device(struct wlr_session *session, int fd) { + struct wlr_device *dev; + + wl_list_for_each(dev, &session->devices, link) { + if (dev->fd == fd) { + return dev; + } + } + + wlr_log(WLR_ERROR, "Tried to use fd %d not opened by session", fd); + assert(0); +} + +void wlr_session_close_file(struct wlr_session *session, int fd) { + struct wlr_device *dev = find_device(session, fd); + + session->impl->close(session, fd); + wl_list_remove(&dev->link); + free(dev); +} + +void wlr_session_signal_add(struct wlr_session *session, int fd, + struct wl_listener *listener) { + struct wlr_device *dev = find_device(session, fd); + + wl_signal_add(&dev->signal, listener); +} + +bool wlr_session_change_vt(struct wlr_session *session, unsigned vt) { + if (!session) { + return false; + } + + return session->impl->change_vt(session, vt); +} + +/* Tests if 'path' is KMS compatible by trying to open it. + * It leaves the open device in *fd_out it it succeeds. + */ +static int open_if_kms(struct wlr_session *restrict session, const char *restrict path) { + int fd; + + if (!path) { + return -1; + } + + fd = wlr_session_open_file(session, path); + if (fd < 0) { + return -1; + } + + drmModeRes *res = drmModeGetResources(fd); + if (!res) { + goto out_fd; + } + + drmModeFreeResources(res); + return fd; + +out_fd: + wlr_session_close_file(session, fd); + return -1; +} + +static size_t explicit_find_gpus(struct wlr_session *session, + size_t ret_len, int ret[static ret_len], const char *str) { + char *gpus = strdup(str); + if (!gpus) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return 0; + } + + size_t i = 0; + char *save; + char *ptr = strtok_r(gpus, ":", &save); + do { + if (i >= ret_len) { + break; + } + + ret[i] = open_if_kms(session, ptr); + if (ret[i] < 0) { + wlr_log(WLR_ERROR, "Unable to open %s as DRM device", ptr); + } else { + ++i; + } + } while ((ptr = strtok_r(NULL, ":", &save))); + + free(gpus); + return i; +} + +/* Tries to find the primary GPU by checking for the "boot_vga" attribute. + * If it's not found, it returns the first valid GPU it finds. + */ +size_t wlr_session_find_gpus(struct wlr_session *session, + size_t ret_len, int *ret) { + const char *explicit = getenv("WLR_DRM_DEVICES"); + if (explicit) { + return explicit_find_gpus(session, ret_len, ret, explicit); + } + +#ifdef __FreeBSD__ + // XXX: libudev-devd does not return any GPUs (yet?) + return explicit_find_gpus(session, ret_len, ret, "/dev/drm/0"); +#else + + struct udev_enumerate *en = udev_enumerate_new(session->udev); + if (!en) { + wlr_log(WLR_ERROR, "Failed to create udev enumeration"); + return -1; + } + + udev_enumerate_add_match_subsystem(en, "drm"); + udev_enumerate_add_match_sysname(en, "card[0-9]*"); + udev_enumerate_scan_devices(en); + + struct udev_list_entry *entry; + size_t i = 0; + + udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(en)) { + if (i == ret_len) { + 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) { + continue; + } + + const char *seat = udev_device_get_property_value(dev, "ID_SEAT"); + if (!seat) { + seat = "seat0"; + } + if (session->seat[0] && strcmp(session->seat, seat) != 0) { + udev_device_unref(dev); + 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); + + if (pci) { + const char *id = udev_device_get_sysattr_value(pci, "boot_vga"); + if (id && strcmp(id, "1") == 0) { + is_boot_vga = true; + } + } + + int fd = open_if_kms(session, udev_device_get_devnode(dev)); + if (fd < 0) { + udev_device_unref(dev); + continue; + } + + udev_device_unref(dev); + + ret[i] = fd; + if (is_boot_vga) { + int tmp = ret[0]; + ret[0] = ret[i]; + ret[i] = tmp; + } + + ++i; + } + + udev_enumerate_unref(en); + + return i; +#endif +} diff --git a/backend/wayland/backend.c b/backend/wayland/backend.c new file mode 100644 index 00000000..df1bf431 --- /dev/null +++ b/backend/wayland/backend.c @@ -0,0 +1,279 @@ +#include <assert.h> +#include <limits.h> +#include <stdint.h> +#include <stdlib.h> + +#include <wlr/config.h> + +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <wayland-server.h> + +#include <wlr/backend/interface.h> +#include <wlr/interfaces/wlr_input_device.h> +#include <wlr/interfaces/wlr_output.h> +#include <wlr/render/egl.h> +#include <wlr/render/gles2.h> +#include <wlr/util/log.h> + +#include "backend/wayland.h" +#include "util/signal.h" +#include "xdg-shell-unstable-v6-client-protocol.h" + +struct wlr_wl_backend *get_wl_backend_from_backend(struct wlr_backend *backend) { + assert(wlr_backend_is_wl(backend)); + return (struct wlr_wl_backend *)backend; +} + +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)) { + wl_display_terminate(wl->local_display); + return 0; + } + + if (mask & WL_EVENT_READABLE) { + return wl_display_dispatch(wl->remote_display); + } + if (mask & WL_EVENT_WRITABLE) { + wl_display_flush(wl->remote_display); + return 0; + } + if (mask == 0) { + int count = wl_display_dispatch_pending(wl->remote_display); + wl_display_flush(wl->remote_display); + return count; + } + + return 0; +} + +static void xdg_shell_handle_ping(void *data, struct zxdg_shell_v6 *shell, + uint32_t serial) { + zxdg_shell_v6_pong(shell, serial); +} + +static const struct zxdg_shell_v6_listener xdg_shell_listener = { + xdg_shell_handle_ping, +}; + +static void registry_global(void *data, struct wl_registry *registry, + uint32_t name, const char *iface, uint32_t version) { + struct wlr_wl_backend *wl = data; + + wlr_log(WLR_DEBUG, "Remote wayland global: %s v%d", iface, version); + + if (strcmp(iface, wl_compositor_interface.name) == 0) { + wl->compositor = wl_registry_bind(registry, name, + &wl_compositor_interface, 4); + + } else if (strcmp(iface, wl_seat_interface.name) == 0) { + wl->seat = wl_registry_bind(registry, name, + &wl_seat_interface, 2); + wl_seat_add_listener(wl->seat, &seat_listener, wl); + + } else if (strcmp(iface, wl_shm_interface.name) == 0) { + wl->shm = wl_registry_bind(registry, name, + &wl_shm_interface, 1); + + } else if (strcmp(iface, zxdg_shell_v6_interface.name) == 0) { + wl->shell = wl_registry_bind(registry, name, + &zxdg_shell_v6_interface, 1); + zxdg_shell_v6_add_listener(wl->shell, &xdg_shell_listener, NULL); + } +} + +static void registry_global_remove(void *data, struct wl_registry *registry, + uint32_t name) { + // TODO +} + +static const struct wl_registry_listener registry_listener = { + .global = registry_global, + .global_remove = registry_global_remove +}; + +/* + * Initializes the wayland backend. Opens a connection to a remote wayland + * compositor and creates surfaces for each output, then registers globals on + * the specified display. + */ +static bool backend_start(struct wlr_backend *backend) { + struct wlr_wl_backend *wl = get_wl_backend_from_backend(backend); + wlr_log(WLR_INFO, "Initializating wayland backend"); + + wl->started = true; + + if (wl->keyboard) { + create_wl_keyboard(wl->keyboard, wl); + } + + for (size_t i = 0; i < wl->requested_outputs; ++i) { + wlr_wl_output_create(&wl->backend); + } + + return true; +} + +static void backend_destroy(struct wlr_backend *backend) { + if (!backend) { + return; + } + + struct wlr_wl_backend *wl = get_wl_backend_from_backend(backend); + + struct wlr_wl_output *output, *tmp_output; + wl_list_for_each_safe(output, tmp_output, &wl->outputs, link) { + wlr_output_destroy(&output->wlr_output); + } + + struct wlr_input_device *input_device, *tmp_input_device; + wl_list_for_each_safe(input_device, tmp_input_device, &wl->devices, link) { + wlr_input_device_destroy(input_device); + } + + wlr_signal_emit_safe(&wl->backend.events.destroy, &wl->backend); + + wl_list_remove(&wl->local_display_destroy.link); + + free(wl->seat_name); + + wl_event_source_remove(wl->remote_display_src); + + wlr_renderer_destroy(wl->renderer); + wlr_egl_finish(&wl->egl); + + if (wl->pointer) { + wl_pointer_destroy(wl->pointer); + } + if (wl->seat) { + wl_seat_destroy(wl->seat); + } + if (wl->shm) { + wl_shm_destroy(wl->shm); + } + zxdg_shell_v6_destroy(wl->shell); + wl_compositor_destroy(wl->compositor); + wl_registry_destroy(wl->registry); + wl_display_disconnect(wl->remote_display); + free(wl); +} + +static struct wlr_renderer *backend_get_renderer(struct wlr_backend *backend) { + struct wlr_wl_backend *wl = get_wl_backend_from_backend(backend); + return wl->renderer; +} + +static struct wlr_backend_impl backend_impl = { + .start = backend_start, + .destroy = backend_destroy, + .get_renderer = backend_get_renderer, +}; + +bool wlr_backend_is_wl(struct wlr_backend *b) { + return b->impl == &backend_impl; +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_wl_backend *wl = + wl_container_of(listener, wl, local_display_destroy); + backend_destroy(&wl->backend); +} + +struct wlr_backend *wlr_wl_backend_create(struct wl_display *display, + const char *remote, wlr_renderer_create_func_t create_renderer_func) { + wlr_log(WLR_INFO, "Creating wayland backend"); + + struct wlr_wl_backend *wl = calloc(1, sizeof(*wl)); + if (!wl) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + + wlr_backend_init(&wl->backend, &backend_impl); + + wl->local_display = display; + wl_list_init(&wl->devices); + wl_list_init(&wl->outputs); + + wl->remote_display = wl_display_connect(remote); + if (!wl->remote_display) { + wlr_log_errno(WLR_ERROR, "Could not connect to remote display"); + goto error_wl; + } + + 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; + } + + wl_registry_add_listener(wl->registry, ®istry_listener, wl); + wl_display_dispatch(wl->remote_display); + wl_display_roundtrip(wl->remote_display); + + if (!wl->compositor) { + wlr_log(WLR_ERROR, + "Remote Wayland compositor does not support wl_compositor"); + goto error_registry; + } + if (!wl->shell) { + wlr_log(WLR_ERROR, + "Remote Wayland compositor does not support zxdg_shell_v6"); + goto error_registry; + } + + struct wl_event_loop *loop = wl_display_get_event_loop(wl->local_display); + int fd = wl_display_get_fd(wl->remote_display); + int events = WL_EVENT_READABLE | WL_EVENT_ERROR | WL_EVENT_HANGUP; + wl->remote_display_src = wl_event_loop_add_fd(loop, fd, events, + dispatch_events, wl); + if (!wl->remote_display_src) { + wlr_log(WLR_ERROR, "Failed to create event source"); + goto error_registry; + } + wl_event_source_check(wl->remote_display_src); + + static EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_ALPHA_SIZE, 1, + EGL_NONE, + }; + + if (!create_renderer_func) { + create_renderer_func = wlr_renderer_autocreate; + } + + wl->renderer = create_renderer_func(&wl->egl, EGL_PLATFORM_WAYLAND_EXT, + wl->remote_display, config_attribs, WL_SHM_FORMAT_ARGB8888); + + if (!wl->renderer) { + wlr_log(WLR_ERROR, "Could not create renderer"); + goto error_event; + } + + wl->local_display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &wl->local_display_destroy); + + return &wl->backend; + +error_event: + wl_event_source_remove(wl->remote_display_src); +error_registry: + if (wl->compositor) { + wl_compositor_destroy(wl->compositor); + } + if (wl->shell) { + zxdg_shell_v6_destroy(wl->shell); + } + wl_registry_destroy(wl->registry); +error_display: + wl_display_disconnect(wl->remote_display); +error_wl: + free(wl); + return NULL; +} diff --git a/backend/wayland/output.c b/backend/wayland/output.c new file mode 100644 index 00000000..89d5b5c9 --- /dev/null +++ b/backend/wayland/output.c @@ -0,0 +1,367 @@ +#include <assert.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <unistd.h> + +#include <wayland-client.h> + +#include <wlr/interfaces/wlr_output.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/util/log.h> + +#include "backend/wayland.h" +#include "util/signal.h" +#include "xdg-shell-unstable-v6-client-protocol.h" + +static struct wlr_wl_output *get_wl_output_from_output( + struct wlr_output *wlr_output) { + assert(wlr_output_is_wl(wlr_output)); + return (struct wlr_wl_output *)wlr_output; +} + +static struct wl_callback_listener frame_listener; + +static void surface_frame_callback(void *data, struct wl_callback *cb, + uint32_t time) { + struct wlr_wl_output *output = data; + assert(output); + wl_callback_destroy(cb); + output->frame_callback = NULL; + + wlr_output_send_frame(&output->wlr_output); +} + +static struct wl_callback_listener frame_listener = { + .done = surface_frame_callback +}; + +static bool output_set_custom_mode(struct wlr_output *wlr_output, + int32_t width, int32_t height, int32_t refresh) { + struct wlr_wl_output *output = get_wl_output_from_output(wlr_output); + wl_egl_window_resize(output->egl_window, width, height, 0, 0); + wlr_output_update_custom_mode(&output->wlr_output, width, height, 0); + return true; +} + +static bool output_make_current(struct wlr_output *wlr_output, + int *buffer_age) { + struct wlr_wl_output *output = + get_wl_output_from_output(wlr_output); + return wlr_egl_make_current(&output->backend->egl, output->egl_surface, + buffer_age); +} + +static bool output_swap_buffers(struct wlr_output *wlr_output, + pixman_region32_t *damage) { + struct wlr_wl_output *output = + get_wl_output_from_output(wlr_output); + + if (output->frame_callback != NULL) { + wlr_log(WLR_ERROR, "Skipping buffer swap"); + return false; + } + + output->frame_callback = wl_surface_frame(output->surface); + wl_callback_add_listener(output->frame_callback, &frame_listener, output); + + 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, + enum wl_output_transform transform) { + struct wlr_wl_output *output = get_wl_output_from_output(wlr_output); + output->wlr_output.transform = transform; +} + +static bool output_set_cursor(struct wlr_output *wlr_output, + struct wlr_texture *texture, int32_t scale, + enum wl_output_transform transform, + int32_t hotspot_x, int32_t hotspot_y, bool update_texture) { + struct wlr_wl_output *output = get_wl_output_from_output(wlr_output); + struct wlr_wl_backend *backend = output->backend; + + struct wlr_box hotspot = { .x = hotspot_x, .y = hotspot_y }; + wlr_box_transform(&hotspot, &hotspot, + wlr_output_transform_invert(wlr_output->transform), + output->cursor.width, output->cursor.height); + + // TODO: use output->wlr_output.transform to transform pixels and hotpot + output->cursor.hotspot_x = hotspot.x; + output->cursor.hotspot_y = hotspot.y; + + if (!update_texture) { + // Update hotspot without changing cursor image + update_wl_output_cursor(output); + return true; + } + + if (output->cursor.surface == NULL) { + output->cursor.surface = + wl_compositor_create_surface(backend->compositor); + } + struct wl_surface *surface = output->cursor.surface; + + if (texture != NULL) { + int width, height; + wlr_texture_get_size(texture, &width, &height); + width = width * wlr_output->scale / scale; + height = height * wlr_output->scale / scale; + + output->cursor.width = width; + output->cursor.height = height; + + if (output->cursor.egl_window == NULL) { + output->cursor.egl_window = + wl_egl_window_create(surface, width, height); + } + wl_egl_window_resize(output->cursor.egl_window, width, height, 0, 0); + + EGLSurface egl_surface = + wlr_egl_create_surface(&backend->egl, output->cursor.egl_window); + + wlr_egl_make_current(&backend->egl, egl_surface, NULL); + + struct wlr_box cursor_box = { + .width = width, + .height = height, + }; + + float projection[9]; + wlr_matrix_projection(projection, width, height, wlr_output->transform); + + float matrix[9]; + wlr_matrix_project_box(matrix, &cursor_box, transform, 0, projection); + + wlr_renderer_begin(backend->renderer, width, height); + wlr_renderer_clear(backend->renderer, (float[]){ 0.0, 0.0, 0.0, 0.0 }); + wlr_render_texture_with_matrix(backend->renderer, texture, matrix, 1.0); + wlr_renderer_end(backend->renderer); + + wlr_egl_swap_buffers(&backend->egl, egl_surface, NULL); + wlr_egl_destroy_surface(&backend->egl, egl_surface); + } else { + wl_surface_attach(surface, NULL, 0, 0); + wl_surface_commit(surface); + } + + update_wl_output_cursor(output); + return true; +} + +static void output_destroy(struct wlr_output *wlr_output) { + struct wlr_wl_output *output = get_wl_output_from_output(wlr_output); + if (output == NULL) { + return; + } + + wl_list_remove(&output->link); + + if (output->cursor.egl_window != NULL) { + wl_egl_window_destroy(output->cursor.egl_window); + } + if (output->cursor.surface) { + wl_surface_destroy(output->cursor.surface); + } + + if (output->frame_callback) { + wl_callback_destroy(output->frame_callback); + } + + wlr_egl_destroy_surface(&output->backend->egl, output->egl_surface); + wl_egl_window_destroy(output->egl_window); + zxdg_toplevel_v6_destroy(output->xdg_toplevel); + zxdg_surface_v6_destroy(output->xdg_surface); + wl_surface_destroy(output->surface); + free(output); +} + +void update_wl_output_cursor(struct wlr_wl_output *output) { + if (output->backend->pointer && output->enter_serial) { + wl_pointer_set_cursor(output->backend->pointer, output->enter_serial, + output->cursor.surface, output->cursor.hotspot_x, + output->cursor.hotspot_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 bool 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 true; + } + + output->frame_callback = wl_surface_frame(output->surface); + wl_callback_add_listener(output->frame_callback, &frame_listener, output); + wl_surface_commit(output->surface); + return true; +} + +static const struct wlr_output_impl output_impl = { + .set_custom_mode = output_set_custom_mode, + .transform = output_transform, + .destroy = output_destroy, + .make_current = output_make_current, + .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) { + return wlr_output->impl == &output_impl; +} + +static void xdg_surface_handle_configure(void *data, struct zxdg_surface_v6 *xdg_surface, + uint32_t serial) { + struct wlr_wl_output *output = data; + assert(output && output->xdg_surface == xdg_surface); + + zxdg_surface_v6_ack_configure(xdg_surface, serial); + + // nothing else? +} + +static struct zxdg_surface_v6_listener xdg_surface_listener = { + .configure = xdg_surface_handle_configure, +}; + +static void xdg_toplevel_handle_configure(void *data, + struct zxdg_toplevel_v6 *xdg_toplevel, + int32_t width, int32_t height, struct wl_array *states) { + struct wlr_wl_output *output = data; + assert(output && output->xdg_toplevel == xdg_toplevel); + + if (width == 0 || height == 0) { + return; + } + // loop over states for maximized etc? + wl_egl_window_resize(output->egl_window, width, height, 0, 0); + wlr_output_update_custom_mode(&output->wlr_output, width, height, 0); +} + +static void xdg_toplevel_handle_close(void *data, + struct zxdg_toplevel_v6 *xdg_toplevel) { + struct wlr_wl_output *output = data; + assert(output && output->xdg_toplevel == xdg_toplevel); + + wlr_output_destroy((struct wlr_output *)output); +} + +static struct zxdg_toplevel_v6_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_handle_configure, + .close = xdg_toplevel_handle_close, +}; + +struct wlr_output *wlr_wl_output_create(struct wlr_backend *wlr_backend) { + struct wlr_wl_backend *backend = get_wl_backend_from_backend(wlr_backend); + if (!backend->started) { + ++backend->requested_outputs; + return NULL; + } + + struct wlr_wl_output *output; + if (!(output = calloc(sizeof(struct wlr_wl_output), 1))) { + wlr_log(WLR_ERROR, "Failed to allocate wlr_wl_output"); + return NULL; + } + wlr_output_init(&output->wlr_output, &backend->backend, &output_impl, + backend->local_display); + struct wlr_output *wlr_output = &output->wlr_output; + + wlr_output_update_custom_mode(wlr_output, 1280, 720, 0); + strncpy(wlr_output->make, "wayland", sizeof(wlr_output->make)); + strncpy(wlr_output->model, "wayland", sizeof(wlr_output->model)); + snprintf(wlr_output->name, sizeof(wlr_output->name), "WL-%d", + wl_list_length(&backend->outputs) + 1); + + output->backend = backend; + + output->surface = wl_compositor_create_surface(backend->compositor); + if (!output->surface) { + wlr_log_errno(WLR_ERROR, "Could not create output surface"); + goto error; + } + wl_surface_set_user_data(output->surface, output); + output->xdg_surface = + zxdg_shell_v6_get_xdg_surface(backend->shell, output->surface); + if (!output->xdg_surface) { + wlr_log_errno(WLR_ERROR, "Could not get xdg surface"); + goto error; + } + output->xdg_toplevel = + zxdg_surface_v6_get_toplevel(output->xdg_surface); + if (!output->xdg_toplevel) { + wlr_log_errno(WLR_ERROR, "Could not get xdg toplevel"); + goto error; + } + + char title[32]; + if (snprintf(title, sizeof(title), "wlroots - %s", wlr_output->name)) { + zxdg_toplevel_v6_set_title(output->xdg_toplevel, title); + } + + zxdg_toplevel_v6_set_app_id(output->xdg_toplevel, "wlroots"); + zxdg_surface_v6_add_listener(output->xdg_surface, + &xdg_surface_listener, output); + zxdg_toplevel_v6_add_listener(output->xdg_toplevel, + &xdg_toplevel_listener, output); + wl_surface_commit(output->surface); + + output->egl_window = wl_egl_window_create(output->surface, + wlr_output->width, wlr_output->height); + output->egl_surface = wlr_egl_create_surface(&backend->egl, + output->egl_window); + + wl_display_roundtrip(output->backend->remote_display); + + // start rendering loop per callbacks by rendering first frame + if (!wlr_egl_make_current(&output->backend->egl, output->egl_surface, + NULL)) { + goto error; + } + + wlr_renderer_begin(backend->renderer, wlr_output->width, wlr_output->height); + wlr_renderer_clear(backend->renderer, (float[]){ 1.0, 1.0, 1.0, 1.0 }); + wlr_renderer_end(backend->renderer); + + output->frame_callback = wl_surface_frame(output->surface); + wl_callback_add_listener(output->frame_callback, &frame_listener, output); + + if (!wlr_egl_swap_buffers(&output->backend->egl, output->egl_surface, + NULL)) { + goto error; + } + + wl_list_insert(&backend->outputs, &output->link); + wlr_output_update_enabled(wlr_output, true); + + wlr_signal_emit_safe(&backend->backend.events.new_output, wlr_output); + + if (backend->pointer != NULL) { + create_wl_pointer(backend->pointer, output); + } + + return wlr_output; + +error: + wlr_output_destroy(&output->wlr_output); + return NULL; +} diff --git a/backend/wayland/wl_seat.c b/backend/wayland/wl_seat.c new file mode 100644 index 00000000..b654197a --- /dev/null +++ b/backend/wayland/wl_seat.c @@ -0,0 +1,434 @@ +#define _POSIX_C_SOURCE 200809L + +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include <wayland-client.h> + +#include <wlr/interfaces/wlr_input_device.h> +#include <wlr/interfaces/wlr_keyboard.h> +#include <wlr/interfaces/wlr_output.h> +#include <wlr/interfaces/wlr_pointer.h> +#include <wlr/interfaces/wlr_touch.h> +#include <wlr/util/log.h> + +#include "backend/wayland.h" +#include "util/signal.h" + +static struct wlr_wl_pointer *output_get_pointer(struct wlr_wl_output *output) { + struct wlr_input_device *wlr_dev; + wl_list_for_each(wlr_dev, &output->backend->devices, link) { + if (wlr_dev->type != WLR_INPUT_DEVICE_POINTER) { + continue; + } + struct wlr_wl_pointer *pointer = pointer_get_wl(wlr_dev->pointer); + if (pointer->output == output) { + return pointer; + } + } + + return NULL; +} + +static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, + wl_fixed_t sy) { + struct wlr_wl_backend *backend = data; + if (surface == NULL) { + return; + } + + struct wlr_wl_output *output = wl_surface_get_user_data(surface); + assert(output); + struct wlr_wl_pointer *pointer = output_get_pointer(output); + + output->enter_serial = serial; + backend->current_pointer = pointer; + update_wl_output_cursor(output); +} + +static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface) { + struct wlr_wl_backend *backend = data; + if (surface == NULL) { + return; + } + + struct wlr_wl_output *output = wl_surface_get_user_data(surface); + assert(output); + output->enter_serial = 0; + + if (backend->current_pointer == NULL || + backend->current_pointer->output != output) { + return; + } + + backend->current_pointer = NULL; +} + +static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, + uint32_t time, wl_fixed_t sx, wl_fixed_t sy) { + struct wlr_wl_backend *backend = data; + struct wlr_wl_pointer *pointer = backend->current_pointer; + if (pointer == NULL) { + return; + } + + struct wlr_output *wlr_output = &pointer->output->wlr_output; + struct wlr_event_pointer_motion_absolute event = { + .device = &pointer->input_device->wlr_input_device, + .time_msec = time, + .x = wl_fixed_to_double(sx) / wlr_output->width, + .y = wl_fixed_to_double(sy) / wlr_output->height, + }; + wlr_signal_emit_safe(&pointer->wlr_pointer.events.motion_absolute, &event); +} + +static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { + struct wlr_wl_backend *backend = data; + struct wlr_wl_pointer *pointer = backend->current_pointer; + if (pointer == NULL) { + return; + } + + struct wlr_event_pointer_button event = { + .device = &pointer->input_device->wlr_input_device, + .button = button, + .state = state, + .time_msec = time, + }; + wlr_signal_emit_safe(&pointer->wlr_pointer.events.button, &event); +} + +static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) { + struct wlr_wl_backend *backend = data; + struct wlr_wl_pointer *pointer = backend->current_pointer; + if (pointer == NULL) { + return; + } + + struct wlr_event_pointer_axis event = { + .device = &pointer->input_device->wlr_input_device, + .delta = wl_fixed_to_double(value), + .delta_discrete = pointer->axis_discrete, + .orientation = axis, + .time_msec = time, + .source = pointer->axis_source, + }; + wlr_signal_emit_safe(&pointer->wlr_pointer.events.axis, &event); + + pointer->axis_discrete = 0; +} + +static void pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) { + // This space is intentionally left blank +} + +static void pointer_handle_axis_source(void *data, + struct wl_pointer *wl_pointer, uint32_t axis_source) { + struct wlr_wl_backend *backend = data; + struct wlr_wl_pointer *pointer = backend->current_pointer; + if (pointer == NULL) { + return; + } + + pointer->axis_source = axis_source; +} + +static void pointer_handle_axis_stop(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis) { + // This space is intentionally left blank +} + +static void pointer_handle_axis_discrete(void *data, + struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) { + struct wlr_wl_backend *backend = data; + struct wlr_wl_pointer *pointer = backend->current_pointer; + if (pointer == NULL) { + return; + } + + pointer->axis_discrete = discrete; +} + +static const struct wl_pointer_listener pointer_listener = { + .enter = pointer_handle_enter, + .leave = pointer_handle_leave, + .motion = pointer_handle_motion, + .button = pointer_handle_button, + .axis = pointer_handle_axis, + .frame = pointer_handle_frame, + .axis_source = pointer_handle_axis_source, + .axis_stop = pointer_handle_axis_stop, + .axis_discrete = pointer_handle_axis_discrete, +}; + +static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int32_t fd, uint32_t size) { + // TODO: set keymap +} + +static uint32_t get_current_time_msec() { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return now.tv_nsec / 1000; +} + +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_input_device *dev = data; + + uint32_t time = get_current_time_msec(); + + uint32_t *keycode_ptr; + wl_array_for_each(keycode_ptr, keys) { + struct wlr_event_keyboard_key event = { + .keycode = *keycode_ptr, + .state = WLR_KEY_PRESSED, + .time_msec = time, + .update_state = false, + }; + wlr_keyboard_notify_key(dev->keyboard, &event); + } +} + +static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, struct wl_surface *surface) { + struct wlr_input_device *dev = data; + + uint32_t time = get_current_time_msec(); + + uint32_t pressed[dev->keyboard->num_keycodes + 1]; + memcpy(pressed, dev->keyboard->keycodes, + dev->keyboard->num_keycodes * sizeof(uint32_t)); + + for (size_t i = 0; i < sizeof(pressed)/sizeof(pressed[0]); ++i) { + uint32_t keycode = pressed[i]; + + struct wlr_event_keyboard_key event = { + .keycode = keycode, + .state = WLR_KEY_RELEASED, + .time_msec = time, + .update_state = false, + }; + wlr_keyboard_notify_key(dev->keyboard, &event); + } +} + +static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { + struct wlr_input_device *dev = data; + assert(dev && dev->keyboard); + + struct wlr_event_keyboard_key wlr_event = { + .keycode = key, + .state = state, + .time_msec = time, + .update_state = false, + }; + wlr_keyboard_notify_key(dev->keyboard, &wlr_event); +} + +static void keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) { + struct wlr_input_device *dev = data; + assert(dev && dev->keyboard); + wlr_keyboard_notify_modifiers(dev->keyboard, mods_depressed, mods_latched, + mods_locked, group); +} + +static void keyboard_handle_repeat_info(void *data, + struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) { + // This space is intentionally left blank +} + +static struct wl_keyboard_listener keyboard_listener = { + .keymap = keyboard_handle_keymap, + .enter = keyboard_handle_enter, + .leave = keyboard_handle_leave, + .key = keyboard_handle_key, + .modifiers = keyboard_handle_modifiers, + .repeat_info = keyboard_handle_repeat_info +}; + +static struct wlr_wl_input_device *get_wl_input_device_from_input_device( + struct wlr_input_device *wlr_dev) { + assert(wlr_input_device_is_wl(wlr_dev)); + return (struct wlr_wl_input_device *)wlr_dev; +} + +static void input_device_destroy(struct wlr_input_device *wlr_dev) { + struct wlr_wl_input_device *dev = + get_wl_input_device_from_input_device(wlr_dev); + if (dev->resource) { + wl_proxy_destroy(dev->resource); + } + wl_list_remove(&dev->wlr_input_device.link); + free(dev); +} + +static struct wlr_input_device_impl input_device_impl = { + .destroy = input_device_destroy, +}; + +bool wlr_input_device_is_wl(struct wlr_input_device *dev) { + return dev->impl == &input_device_impl; +} + +static struct wlr_wl_input_device *create_wl_input_device( + struct wlr_wl_backend *backend, enum wlr_input_device_type type) { + struct wlr_wl_input_device *dev = + calloc(1, sizeof(struct wlr_wl_input_device)); + if (dev == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + dev->backend = backend; + + struct wlr_input_device *wlr_dev = &dev->wlr_input_device; + + unsigned int vendor = 0, product = 0; + const char *name = "wayland"; + wlr_input_device_init(wlr_dev, type, &input_device_impl, name, vendor, + product); + wl_list_insert(&backend->devices, &wlr_dev->link); + return dev; +} + +static struct wlr_pointer_impl pointer_impl; + +struct wlr_wl_pointer *pointer_get_wl(struct wlr_pointer *wlr_pointer) { + assert(wlr_pointer->impl == &pointer_impl); + return (struct wlr_wl_pointer *)wlr_pointer; +} + +static void pointer_destroy(struct wlr_pointer *wlr_pointer) { + struct wlr_wl_pointer *pointer = pointer_get_wl(wlr_pointer); + wl_list_remove(&pointer->output_destroy.link); + free(pointer); +} + +static struct wlr_pointer_impl pointer_impl = { + .destroy = pointer_destroy, +}; + +static void pointer_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_wl_pointer *pointer = + wl_container_of(listener, pointer, output_destroy); + wlr_input_device_destroy(&pointer->input_device->wlr_input_device); +} + +void create_wl_pointer(struct wl_pointer *wl_pointer, struct wlr_wl_output *output) { + struct wlr_wl_backend *backend = output->backend; + + struct wlr_input_device *wlr_dev; + wl_list_for_each(wlr_dev, &output->backend->devices, link) { + if (wlr_dev->type != WLR_INPUT_DEVICE_POINTER) { + continue; + } + struct wlr_wl_pointer *pointer = pointer_get_wl(wlr_dev->pointer); + if (pointer->output == output) { + return; + } + } + + struct wlr_wl_pointer *pointer = calloc(1, sizeof(struct wlr_wl_pointer)); + if (pointer == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + return; + } + pointer->wl_pointer = wl_pointer; + pointer->output = output; + + wl_signal_add(&output->wlr_output.events.destroy, &pointer->output_destroy); + pointer->output_destroy.notify = pointer_handle_output_destroy; + + struct wlr_wl_input_device *dev = + create_wl_input_device(backend, WLR_INPUT_DEVICE_POINTER); + if (dev == NULL) { + free(pointer); + wlr_log(WLR_ERROR, "Allocation failed"); + return; + } + pointer->input_device = dev; + + wlr_dev = &dev->wlr_input_device; + wlr_dev->pointer = &pointer->wlr_pointer; + wlr_dev->output_name = strdup(output->wlr_output.name); + wlr_pointer_init(wlr_dev->pointer, &pointer_impl); + + wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_dev); +} + +void create_wl_keyboard(struct wl_keyboard *wl_keyboard, struct wlr_wl_backend *wl) { + struct wlr_wl_input_device *dev = + create_wl_input_device(wl, WLR_INPUT_DEVICE_KEYBOARD); + if (!dev) { + return; + } + + struct wlr_input_device *wlr_dev = &dev->wlr_input_device; + + wlr_dev->keyboard = calloc(1, sizeof(*wlr_dev->keyboard)); + if (!wlr_dev->keyboard) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + free(dev); + return; + } + wlr_keyboard_init(wlr_dev->keyboard, NULL); + + wl_keyboard_add_listener(wl_keyboard, &keyboard_listener, wlr_dev); + dev->resource = wl_keyboard; + wlr_signal_emit_safe(&wl->backend.events.new_input, wlr_dev); +} + +static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, + enum wl_seat_capability caps) { + struct wlr_wl_backend *backend = data; + assert(backend->seat == wl_seat); + + if ((caps & WL_SEAT_CAPABILITY_POINTER)) { + wlr_log(WLR_DEBUG, "seat %p offered pointer", (void*) wl_seat); + + struct wl_pointer *wl_pointer = wl_seat_get_pointer(wl_seat); + backend->pointer = wl_pointer; + + struct wlr_wl_output *output; + wl_list_for_each(output, &backend->outputs, link) { + create_wl_pointer(wl_pointer, output); + } + + wl_pointer_add_listener(wl_pointer, &pointer_listener, backend); + } + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD)) { + wlr_log(WLR_DEBUG, "seat %p offered keyboard", (void*) wl_seat); + + struct wl_keyboard *wl_keyboard = wl_seat_get_keyboard(wl_seat); + backend->keyboard = wl_keyboard; + + if (backend->started) { + create_wl_keyboard(wl_keyboard, backend); + } + } +} + +static void seat_handle_name(void *data, struct wl_seat *wl_seat, + const char *name) { + struct wlr_wl_backend *backend = data; + assert(backend->seat == wl_seat); + // Do we need to check if seatName was previously set for name change? + free(backend->seat_name); + backend->seat_name = strdup(name); +} + +const struct wl_seat_listener seat_listener = { + .capabilities = seat_handle_capabilities, + .name = seat_handle_name, +}; diff --git a/backend/x11/backend.c b/backend/x11/backend.c new file mode 100644 index 00000000..38715631 --- /dev/null +++ b/backend/x11/backend.c @@ -0,0 +1,314 @@ +#define _POSIX_C_SOURCE 200112L + +#include <assert.h> +#include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + +#include <wlr/config.h> + +#include <X11/Xlib-xcb.h> +#include <wayland-server.h> +#include <xcb/xcb.h> +#include <xcb/xfixes.h> +#include <xcb/xinput.h> + +#include <wlr/backend/interface.h> +#include <wlr/backend/x11.h> +#include <wlr/interfaces/wlr_input_device.h> +#include <wlr/interfaces/wlr_keyboard.h> +#include <wlr/interfaces/wlr_pointer.h> +#include <wlr/render/egl.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/util/log.h> + +#include "backend/x11.h" +#include "util/signal.h" + +struct wlr_x11_output *get_x11_output_from_window_id( + struct wlr_x11_backend *x11, xcb_window_t window) { + struct wlr_x11_output *output; + wl_list_for_each(output, &x11->outputs, link) { + if (output->win == window) { + return output; + } + } + return NULL; +} + +static void handle_x11_event(struct wlr_x11_backend *x11, + xcb_generic_event_t *event) { + switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) { + case XCB_EXPOSE: { + xcb_expose_event_t *ev = (xcb_expose_event_t *)event; + struct wlr_x11_output *output = + get_x11_output_from_window_id(x11, ev->window); + if (output != NULL) { + wlr_output_update_needs_swap(&output->wlr_output); + } + break; + } + case XCB_CONFIGURE_NOTIFY: { + xcb_configure_notify_event_t *ev = + (xcb_configure_notify_event_t *)event; + struct wlr_x11_output *output = + get_x11_output_from_window_id(x11, ev->window); + if (output != NULL) { + handle_x11_configure_notify(output, ev); + } + break; + } + case XCB_CLIENT_MESSAGE: { + xcb_client_message_event_t *ev = (xcb_client_message_event_t *)event; + if (ev->data.data32[0] == x11->atoms.wm_delete_window) { + struct wlr_x11_output *output = + get_x11_output_from_window_id(x11, ev->window); + if (output != NULL) { + wlr_output_destroy(&output->wlr_output); + } + } + break; + } + case XCB_GE_GENERIC: { + xcb_ge_generic_event_t *ev = (xcb_ge_generic_event_t *)event; + if (ev->extension == x11->xinput_opcode) { + handle_x11_xinput_event(x11, ev); + } + } + } +} + +static int x11_event(int fd, uint32_t mask, void *data) { + struct wlr_x11_backend *x11 = data; + + if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { + wl_display_terminate(x11->wl_display); + return 0; + } + + xcb_generic_event_t *e; + while ((e = xcb_poll_for_event(x11->xcb))) { + handle_x11_event(x11, e); + free(e); + } + + return 0; +} + +struct wlr_x11_backend *get_x11_backend_from_backend( + struct wlr_backend *wlr_backend) { + assert(wlr_backend_is_x11(wlr_backend)); + return (struct wlr_x11_backend *)wlr_backend; +} + +static bool backend_start(struct wlr_backend *backend) { + struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend); + x11->started = true; + + wlr_signal_emit_safe(&x11->backend.events.new_input, &x11->keyboard_dev); + + for (size_t i = 0; i < x11->requested_outputs; ++i) { + wlr_x11_output_create(&x11->backend); + } + + return true; +} + +static void backend_destroy(struct wlr_backend *backend) { + if (!backend) { + return; + } + + struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend); + + struct wlr_x11_output *output, *tmp; + wl_list_for_each_safe(output, tmp, &x11->outputs, link) { + wlr_output_destroy(&output->wlr_output); + } + + wlr_input_device_destroy(&x11->keyboard_dev); + + wlr_signal_emit_safe(&backend->events.destroy, backend); + + if (x11->event_source) { + wl_event_source_remove(x11->event_source); + } + wl_list_remove(&x11->display_destroy.link); + + wlr_renderer_destroy(x11->renderer); + wlr_egl_finish(&x11->egl); + + if (x11->xlib_conn) { + XCloseDisplay(x11->xlib_conn); + } + free(x11); +} + +static struct wlr_renderer *backend_get_renderer( + struct wlr_backend *backend) { + struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend); + return x11->renderer; +} + +static const struct wlr_backend_impl backend_impl = { + .start = backend_start, + .destroy = backend_destroy, + .get_renderer = backend_get_renderer, +}; + +bool wlr_backend_is_x11(struct wlr_backend *backend) { + return backend->impl == &backend_impl; +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_x11_backend *x11 = + wl_container_of(listener, x11, display_destroy); + backend_destroy(&x11->backend); +} + +struct wlr_backend *wlr_x11_backend_create(struct wl_display *display, + const char *x11_display, + wlr_renderer_create_func_t create_renderer_func) { + struct wlr_x11_backend *x11 = calloc(1, sizeof(*x11)); + if (!x11) { + return NULL; + } + + wlr_backend_init(&x11->backend, &backend_impl); + x11->wl_display = display; + wl_list_init(&x11->outputs); + + x11->xlib_conn = XOpenDisplay(x11_display); + if (!x11->xlib_conn) { + wlr_log(WLR_ERROR, "Failed to open X connection"); + goto error_x11; + } + + x11->xcb = XGetXCBConnection(x11->xlib_conn); + if (!x11->xcb || xcb_connection_has_error(x11->xcb)) { + wlr_log(WLR_ERROR, "Failed to open xcb connection"); + goto error_display; + } + + XSetEventQueueOwner(x11->xlib_conn, XCBOwnsEventQueue); + + struct { + const char *name; + xcb_intern_atom_cookie_t cookie; + xcb_atom_t *atom; + } atom[] = { + { .name = "WM_PROTOCOLS", .atom = &x11->atoms.wm_protocols }, + { .name = "WM_DELETE_WINDOW", .atom = &x11->atoms.wm_delete_window }, + { .name = "_NET_WM_NAME", .atom = &x11->atoms.net_wm_name }, + { .name = "UTF8_STRING", .atom = &x11->atoms.utf8_string }, + }; + + for (size_t i = 0; i < sizeof(atom) / sizeof(atom[0]); ++i) { + atom[i].cookie = xcb_intern_atom(x11->xcb, + true, strlen(atom[i].name), atom[i].name); + } + + for (size_t i = 0; i < sizeof(atom) / sizeof(atom[0]); ++i) { + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply( + x11->xcb, atom[i].cookie, NULL); + + if (reply) { + *atom[i].atom = reply->atom; + free(reply); + } else { + *atom[i].atom = XCB_ATOM_NONE; + } + } + + const xcb_query_extension_reply_t *ext; + + ext = xcb_get_extension_data(x11->xcb, &xcb_xfixes_id); + if (!ext || !ext->present) { + wlr_log(WLR_ERROR, "X11 does not support Xfixes extension"); + goto error_display; + } + + xcb_xfixes_query_version_cookie_t fixes_cookie = + 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) { + wlr_log(WLR_ERROR, "X11 does not support required Xfixes version"); + free(fixes_reply); + goto error_display; + } + free(fixes_reply); + + ext = xcb_get_extension_data(x11->xcb, &xcb_input_id); + if (!ext || !ext->present) { + wlr_log(WLR_ERROR, "X11 does not support Xinput extension"); + goto error_display; + } + x11->xinput_opcode = ext->major_opcode; + + xcb_input_xi_query_version_cookie_t xi_cookie = + 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) { + wlr_log(WLR_ERROR, "X11 does not support required Xinput version"); + free(xi_reply); + goto error_display; + } + free(xi_reply); + + int fd = xcb_get_file_descriptor(x11->xcb); + struct wl_event_loop *ev = wl_display_get_event_loop(display); + uint32_t events = WL_EVENT_READABLE | WL_EVENT_ERROR | WL_EVENT_HANGUP; + x11->event_source = wl_event_loop_add_fd(ev, fd, events, x11_event, x11); + if (!x11->event_source) { + wlr_log(WLR_ERROR, "Could not create event source"); + goto error_display; + } + wl_event_source_check(x11->event_source); + + x11->screen = xcb_setup_roots_iterator(xcb_get_setup(x11->xcb)).data; + + if (!create_renderer_func) { + create_renderer_func = wlr_renderer_autocreate; + } + + static EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_ALPHA_SIZE, 0, + EGL_NONE, + }; + + x11->renderer = create_renderer_func(&x11->egl, EGL_PLATFORM_X11_KHR, + x11->xlib_conn, config_attribs, x11->screen->root_visual); + + if (x11->renderer == NULL) { + wlr_log(WLR_ERROR, "Failed to create renderer"); + goto error_event; + } + + wlr_input_device_init(&x11->keyboard_dev, WLR_INPUT_DEVICE_KEYBOARD, + &input_device_impl, "X11 keyboard", 0, 0); + wlr_keyboard_init(&x11->keyboard, &keyboard_impl); + x11->keyboard_dev.keyboard = &x11->keyboard; + + x11->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &x11->display_destroy); + + return &x11->backend; + +error_event: + wl_event_source_remove(x11->event_source); +error_display: + XCloseDisplay(x11->xlib_conn); +error_x11: + free(x11); + return NULL; +} diff --git a/backend/x11/input_device.c b/backend/x11/input_device.c new file mode 100644 index 00000000..a50f478a --- /dev/null +++ b/backend/x11/input_device.c @@ -0,0 +1,244 @@ +#include <stdlib.h> + +#include <wlr/config.h> + +#ifdef __linux__ +#include <linux/input-event-codes.h> +#elif __FreeBSD__ +#include <dev/evdev/input-event-codes.h> +#endif + +#include <xcb/xcb.h> +#include <xcb/xfixes.h> +#include <xcb/xinput.h> + +#include <wlr/interfaces/wlr_input_device.h> +#include <wlr/interfaces/wlr_keyboard.h> +#include <wlr/interfaces/wlr_pointer.h> +#include <wlr/util/log.h> + +#include "backend/x11.h" +#include "util/signal.h" + +static void send_key_event(struct wlr_x11_backend *x11, uint32_t key, + enum wlr_key_state st, xcb_timestamp_t time) { + struct wlr_event_keyboard_key ev = { + .time_msec = time, + .keycode = key, + .state = st, + .update_state = true, + }; + wlr_keyboard_notify_key(&x11->keyboard, &ev); +} + +static void send_button_event(struct wlr_x11_output *output, uint32_t key, + enum wlr_button_state st, xcb_timestamp_t time) { + struct wlr_event_pointer_button ev = { + .device = &output->pointer_dev, + .time_msec = time, + .button = key, + .state = st, + }; + wlr_signal_emit_safe(&output->pointer.events.button, &ev); +} + +static void send_axis_event(struct wlr_x11_output *output, int32_t delta, + xcb_timestamp_t time) { + struct wlr_event_pointer_axis ev = { + .device = &output->pointer_dev, + .time_msec = time, + .source = WLR_AXIS_SOURCE_WHEEL, + .orientation = WLR_AXIS_ORIENTATION_VERTICAL, + // 15 is a typical value libinput sends for one scroll + .delta = delta * 15, + .delta_discrete = delta, + }; + wlr_signal_emit_safe(&output->pointer.events.axis, &ev); +} + +static void send_pointer_position_event(struct wlr_x11_output *output, + int16_t x, int16_t y, xcb_timestamp_t time) { + struct wlr_event_pointer_motion_absolute ev = { + .device = &output->pointer_dev, + .time_msec = time, + .x = (double)x / output->wlr_output.width, + .y = (double)y / output->wlr_output.height, + }; + wlr_signal_emit_safe(&output->pointer.events.motion_absolute, &ev); +} + +void handle_x11_xinput_event(struct wlr_x11_backend *x11, + xcb_ge_generic_event_t *event) { + struct wlr_x11_output *output; + + switch (event->event_type) { + case XCB_INPUT_KEY_PRESS: { + xcb_input_key_press_event_t *ev = + (xcb_input_key_press_event_t *)event; + + wlr_keyboard_notify_modifiers(&x11->keyboard, ev->mods.base, + ev->mods.latched, ev->mods.locked, ev->mods.effective); + send_key_event(x11, ev->detail - 8, WLR_KEY_PRESSED, ev->time); + x11->time = ev->time; + break; + } + case XCB_INPUT_KEY_RELEASE: { + xcb_input_key_release_event_t *ev = + (xcb_input_key_release_event_t *)event; + + wlr_keyboard_notify_modifiers(&x11->keyboard, ev->mods.base, + ev->mods.latched, ev->mods.locked, ev->mods.effective); + send_key_event(x11, ev->detail - 8, WLR_KEY_RELEASED, ev->time); + x11->time = ev->time; + break; + } + case XCB_INPUT_BUTTON_PRESS: { + xcb_input_button_press_event_t *ev = + (xcb_input_button_press_event_t *)event; + + output = get_x11_output_from_window_id(x11, ev->event); + if (!output) { + return; + } + + switch (ev->detail) { + case XCB_BUTTON_INDEX_1: + send_button_event(output, BTN_LEFT, WLR_BUTTON_PRESSED, + ev->time); + break; + case XCB_BUTTON_INDEX_2: + send_button_event(output, BTN_MIDDLE, WLR_BUTTON_PRESSED, + ev->time); + break; + case XCB_BUTTON_INDEX_3: + send_button_event(output, BTN_RIGHT, WLR_BUTTON_PRESSED, + ev->time); + break; + case XCB_BUTTON_INDEX_4: + send_axis_event(output, -1, ev->time); + break; + case XCB_BUTTON_INDEX_5: + send_axis_event(output, 1, ev->time); + break; + } + + x11->time = ev->time; + break; + } + case XCB_INPUT_BUTTON_RELEASE: { + xcb_input_button_release_event_t *ev = + (xcb_input_button_release_event_t *)event; + + output = get_x11_output_from_window_id(x11, ev->event); + if (!output) { + return; + } + + switch (ev->detail) { + case XCB_BUTTON_INDEX_1: + send_button_event(output, BTN_LEFT, WLR_BUTTON_RELEASED, + ev->time); + break; + case XCB_BUTTON_INDEX_2: + send_button_event(output, BTN_MIDDLE, WLR_BUTTON_RELEASED, + ev->time); + break; + case XCB_BUTTON_INDEX_3: + send_button_event(output, BTN_RIGHT, WLR_BUTTON_RELEASED, + ev->time); + break; + } + + x11->time = ev->time; + break; + } + case XCB_INPUT_MOTION: { + xcb_input_motion_event_t *ev = (xcb_input_motion_event_t *)event; + + output = get_x11_output_from_window_id(x11, ev->event); + if (!output) { + return; + } + + send_pointer_position_event(output, ev->event_x >> 16, + ev->event_y >> 16, ev->time); + x11->time = ev->time; + break; + } + case XCB_INPUT_ENTER: { + xcb_input_enter_event_t *ev = (xcb_input_enter_event_t *)event; + + output = get_x11_output_from_window_id(x11, ev->event); + if (!output) { + return; + } + + if (!output->cursor_hidden) { + xcb_xfixes_hide_cursor(x11->xcb, output->win); + xcb_flush(x11->xcb); + output->cursor_hidden = true; + } + break; + } + case XCB_INPUT_LEAVE: { + xcb_input_leave_event_t *ev = (xcb_input_leave_event_t *)event; + + output = get_x11_output_from_window_id(x11, ev->event); + if (!output) { + return; + } + + if (output->cursor_hidden) { + xcb_xfixes_show_cursor(x11->xcb, output->win); + xcb_flush(x11->xcb); + output->cursor_hidden = false; + } + break; + } + } +} + +static void input_device_destroy(struct wlr_input_device *wlr_device) { + // Don't free the input device, it's on the stack +} + +const struct wlr_input_device_impl input_device_impl = { + .destroy = input_device_destroy, +}; + +static void keyboard_destroy(struct wlr_keyboard *wlr_keyboard) { + // Don't free the keyboard, it's on the stack +} + +const struct wlr_keyboard_impl keyboard_impl = { + .destroy = keyboard_destroy, +}; + +static void pointer_destroy(struct wlr_pointer *wlr_pointer) { + // Don't free the pointer, it's on the stack +} + +const struct wlr_pointer_impl pointer_impl = { + .destroy = pointer_destroy, +}; + +void update_x11_pointer_position(struct wlr_x11_output *output, + xcb_timestamp_t time) { + struct wlr_x11_backend *x11 = output->x11; + + xcb_query_pointer_cookie_t cookie = + xcb_query_pointer(x11->xcb, output->win); + xcb_query_pointer_reply_t *reply = + xcb_query_pointer_reply(x11->xcb, cookie, NULL); + if (!reply) { + return; + } + + send_pointer_position_event(output, reply->win_x, reply->win_y, time); + + free(reply); +} + +bool wlr_input_device_is_x11(struct wlr_input_device *wlr_dev) { + return wlr_dev->impl == &input_device_impl; +} diff --git a/backend/x11/meson.build b/backend/x11/meson.build new file mode 100644 index 00000000..19e873ab --- /dev/null +++ b/backend/x11/meson.build @@ -0,0 +1,35 @@ +x11_libs = [] +x11_required = [ + 'x11-xcb', + 'xcb', + 'xcb-xinput', + 'xcb-xfixes', +] + +foreach lib : x11_required + dep = dependency(lib, required: get_option('x11-backend')) + if not dep.found() + subdir_done() + endif + + x11_libs += dep +endforeach + +lib_wlr_backend_x11 = static_library( + 'wlr_backend_x11', + files( + 'backend.c', + 'input_device.c', + 'output.c', + ), + include_directories: wlr_inc, + dependencies: [ + wayland_server, + pixman, + xkbcommon, + x11_libs, + ], +) + +backend_parts += lib_wlr_backend_x11 +conf_data.set10('WLR_HAS_X11_BACKEND', true) diff --git a/backend/x11/output.c b/backend/x11/output.c new file mode 100644 index 00000000..6f98c590 --- /dev/null +++ b/backend/x11/output.c @@ -0,0 +1,236 @@ +#define _POSIX_C_SOURCE 200809L + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include <xcb/xcb.h> +#include <xcb/xinput.h> + +#include <wlr/interfaces/wlr_output.h> +#include <wlr/interfaces/wlr_pointer.h> +#include <wlr/util/log.h> + +#include "backend/x11.h" +#include "util/signal.h" + +static int signal_frame(void *data) { + struct wlr_x11_output *output = data; + wlr_output_send_frame(&output->wlr_output); + wl_event_source_timer_update(output->frame_timer, output->frame_delay); + return 0; +} + +static void parse_xcb_setup(struct wlr_output *output, + xcb_connection_t *xcb) { + const xcb_setup_t *xcb_setup = xcb_get_setup(xcb); + + snprintf(output->make, sizeof(output->make), "%.*s", + xcb_setup_vendor_length(xcb_setup), + xcb_setup_vendor(xcb_setup)); + snprintf(output->model, sizeof(output->model), "%"PRIu16".%"PRIu16, + xcb_setup->protocol_major_version, + xcb_setup->protocol_minor_version); +} + +static struct wlr_x11_output *get_x11_output_from_output( + struct wlr_output *wlr_output) { + assert(wlr_output_is_x11(wlr_output)); + return (struct wlr_x11_output *)wlr_output; +} + +static void output_set_refresh(struct wlr_output *wlr_output, int32_t refresh) { + struct wlr_x11_output *output = get_x11_output_from_output(wlr_output); + + if (refresh <= 0) { + refresh = X11_DEFAULT_REFRESH; + } + + wlr_output_update_custom_mode(&output->wlr_output, wlr_output->width, + wlr_output->height, refresh); + + output->frame_delay = 1000000 / refresh; +} + +static bool output_set_custom_mode(struct wlr_output *wlr_output, + int32_t width, int32_t height, int32_t refresh) { + struct wlr_x11_output *output = get_x11_output_from_output(wlr_output); + struct wlr_x11_backend *x11 = output->x11; + + output_set_refresh(&output->wlr_output, refresh); + + const uint32_t values[] = { width, height }; + xcb_void_cookie_t cookie = xcb_configure_window_checked( + x11->xcb, output->win, + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values); + + xcb_generic_error_t *error; + if ((error = xcb_request_check(x11->xcb, cookie))) { + wlr_log(WLR_ERROR, "Could not set window size to %dx%d\n", + width, height); + free(error); + return false; + } + + return true; +} + +static void output_transform(struct wlr_output *wlr_output, + enum wl_output_transform transform) { + struct wlr_x11_output *output = get_x11_output_from_output(wlr_output); + output->wlr_output.transform = transform; +} + +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_input_device_destroy(&output->pointer_dev); + + wl_list_remove(&output->link); + wl_event_source_remove(output->frame_timer); + wlr_egl_destroy_surface(&x11->egl, output->surf); + xcb_destroy_window(x11->xcb, output->win); + xcb_flush(x11->xcb); + free(output); +} + +static bool output_make_current(struct wlr_output *wlr_output, + int *buffer_age) { + struct wlr_x11_output *output = get_x11_output_from_output(wlr_output); + struct wlr_x11_backend *x11 = output->x11; + + return wlr_egl_make_current(&x11->egl, output->surf, buffer_age); +} + +static bool output_swap_buffers(struct wlr_output *wlr_output, + pixman_region32_t *damage) { + struct wlr_x11_output *output = (struct wlr_x11_output *)wlr_output; + struct wlr_x11_backend *x11 = output->x11; + + 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 = { + .set_custom_mode = output_set_custom_mode, + .transform = output_transform, + .destroy = output_destroy, + .make_current = output_make_current, + .swap_buffers = output_swap_buffers, +}; + +struct wlr_output *wlr_x11_output_create(struct wlr_backend *backend) { + struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend); + + if (!x11->started) { + ++x11->requested_outputs; + return NULL; + } + + struct wlr_x11_output *output = calloc(1, sizeof(struct wlr_x11_output)); + if (output == NULL) { + return NULL; + } + output->x11 = x11; + + struct wlr_output *wlr_output = &output->wlr_output; + wlr_output_init(wlr_output, &x11->backend, &output_impl, x11->wl_display); + + wlr_output->width = 1024; + wlr_output->height = 768; + + output_set_refresh(&output->wlr_output, 0); + + snprintf(wlr_output->name, sizeof(wlr_output->name), "X11-%d", + wl_list_length(&x11->outputs) + 1); + parse_xcb_setup(wlr_output, x11->xcb); + + uint32_t mask = XCB_CW_EVENT_MASK; + uint32_t values[] = { + XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY + }; + output->win = xcb_generate_id(x11->xcb); + xcb_create_window(x11->xcb, XCB_COPY_FROM_PARENT, output->win, + x11->screen->root, 0, 0, wlr_output->width, wlr_output->height, 1, + XCB_WINDOW_CLASS_INPUT_OUTPUT, x11->screen->root_visual, mask, values); + + struct { + xcb_input_event_mask_t head; + xcb_input_xi_event_mask_t mask; + } xinput_mask = { + .head = { .deviceid = XCB_INPUT_DEVICE_ALL_MASTER, .mask_len = 1 }, + .mask = XCB_INPUT_XI_EVENT_MASK_KEY_PRESS | + XCB_INPUT_XI_EVENT_MASK_KEY_RELEASE | + XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS | + XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE | + XCB_INPUT_XI_EVENT_MASK_MOTION | + XCB_INPUT_XI_EVENT_MASK_ENTER | + XCB_INPUT_XI_EVENT_MASK_LEAVE, + }; + xcb_input_xi_select_events(x11->xcb, output->win, 1, &xinput_mask.head); + + output->surf = wlr_egl_create_surface(&x11->egl, &output->win); + if (!output->surf) { + wlr_log(WLR_ERROR, "Failed to create EGL surface"); + free(output); + return NULL; + } + + xcb_change_property(x11->xcb, XCB_PROP_MODE_REPLACE, output->win, + x11->atoms.wm_protocols, XCB_ATOM_ATOM, 32, 1, + &x11->atoms.wm_delete_window); + + char title[32]; + if (snprintf(title, sizeof(title), "wlroots - %s", wlr_output->name)) { + xcb_change_property(x11->xcb, XCB_PROP_MODE_REPLACE, output->win, + x11->atoms.net_wm_name, x11->atoms.utf8_string, 8, + strlen(title), title); + } + + xcb_map_window(x11->xcb, output->win); + xcb_flush(x11->xcb); + + struct wl_event_loop *ev = wl_display_get_event_loop(x11->wl_display); + output->frame_timer = wl_event_loop_add_timer(ev, signal_frame, output); + + wl_list_insert(&x11->outputs, &output->link); + + wl_event_source_timer_update(output->frame_timer, output->frame_delay); + wlr_output_update_enabled(wlr_output, true); + + wlr_input_device_init(&output->pointer_dev, WLR_INPUT_DEVICE_POINTER, + &input_device_impl, "X11 pointer", 0, 0); + wlr_pointer_init(&output->pointer, &pointer_impl); + output->pointer_dev.pointer = &output->pointer; + output->pointer_dev.output_name = strdup(wlr_output->name); + + wlr_signal_emit_safe(&x11->backend.events.new_output, wlr_output); + wlr_signal_emit_safe(&x11->backend.events.new_input, &output->pointer_dev); + + return wlr_output; +} + +void handle_x11_configure_notify(struct wlr_x11_output *output, + xcb_configure_notify_event_t *ev) { + // ignore events that set an invalid size: + if (ev->width > 0 && ev->height > 0) { + wlr_output_update_custom_mode(&output->wlr_output, ev->width, + ev->height, output->wlr_output.refresh); + + // Move the pointer to its new location + update_x11_pointer_position(output, output->x11->time); + } else { + wlr_log(WLR_DEBUG, + "Ignoring X11 configure event for height=%d, width=%d", + ev->width, ev->height); + } +} + +bool wlr_output_is_x11(struct wlr_output *wlr_output) { + return wlr_output->impl == &output_impl; +} |