aboutsummaryrefslogtreecommitdiff
path: root/backend
diff options
context:
space:
mode:
Diffstat (limited to 'backend')
-rw-r--r--backend/backend.c291
-rw-r--r--backend/drm/atomic.c273
-rw-r--r--backend/drm/backend.c211
-rw-r--r--backend/drm/drm.c1418
-rw-r--r--backend/drm/legacy.c83
-rw-r--r--backend/drm/properties.c151
-rw-r--r--backend/drm/renderer.c264
-rw-r--r--backend/drm/util.c348
-rw-r--r--backend/headless/backend.c132
-rw-r--r--backend/headless/input_device.c99
-rw-r--r--backend/headless/output.c159
-rw-r--r--backend/libinput/backend.c204
-rw-r--r--backend/libinput/events.c294
-rw-r--r--backend/libinput/keyboard.c83
-rw-r--r--backend/libinput/pointer.c136
-rw-r--r--backend/libinput/switch.c55
-rw-r--r--backend/libinput/tablet_pad.c183
-rw-r--r--backend/libinput/tablet_tool.c371
-rw-r--r--backend/libinput/touch.c97
-rw-r--r--backend/meson.build61
-rw-r--r--backend/multi/backend.c231
-rw-r--r--backend/session/direct-freebsd.c268
-rw-r--r--backend/session/direct-ipc.c269
-rw-r--r--backend/session/direct.c278
-rw-r--r--backend/session/logind.c562
-rw-r--r--backend/session/session.c359
-rw-r--r--backend/wayland/backend.c279
-rw-r--r--backend/wayland/output.c367
-rw-r--r--backend/wayland/wl_seat.c434
-rw-r--r--backend/x11/backend.c314
-rw-r--r--backend/x11/input_device.c244
-rw-r--r--backend/x11/meson.build35
-rw-r--r--backend/x11/output.c236
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, &registry_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;
+}