summaryrefslogtreecommitdiff
path: root/src/x11
diff options
context:
space:
mode:
Diffstat (limited to 'src/x11')
-rw-r--r--src/x11/meson.build1
-rw-r--r--src/x11/window.c723
2 files changed, 724 insertions, 0 deletions
diff --git a/src/x11/meson.build b/src/x11/meson.build
new file mode 100644
index 0000000..e1a38f0
--- /dev/null
+++ b/src/x11/meson.build
@@ -0,0 +1 @@
+sources += files('window.c')
diff --git a/src/x11/window.c b/src/x11/window.c
new file mode 100644
index 0000000..b80bfe0
--- /dev/null
+++ b/src/x11/window.c
@@ -0,0 +1,723 @@
+#define _POSIX_C_SOURCE 200809L
+#include "common/backend.h"
+
+#include <X11/Xatom.h>
+#include <X11/XKBlib.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/cursorfont.h>
+#include <X11/keysym.h>
+#include <xkbcommon/xkbcommon.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define INPUT_CAP 256
+#define TEXT_LIMIT (16 * 1024 * 1024)
+
+struct x11_surface {
+ struct yasl_surface base;
+ struct x11_surface *next;
+ struct x11_surface *dead_next;
+ Window window;
+ GC gc;
+ XIC ic;
+ XImage *image;
+ uint32_t *pixels;
+ struct yasl_vector2i size;
+ const struct yasl_surface_cbs *cbs;
+ void *ud;
+ struct yasl_input_event events[INPUT_CAP];
+ char *event_text[INPUT_CAP];
+ char *last_text;
+ unsigned head, count;
+ struct yasl_frame frame;
+ struct yasl_damage_list damage;
+ struct yasl_frame_clock clock;
+ uint32_t state;
+ bool want_draw, drawing, presented, destroyed;
+};
+
+struct x11_display {
+ struct yasl_display base;
+ Display *native;
+ int screen;
+ struct yasl_loop *loop;
+ int watch;
+ struct x11_surface *surfaces, *dead;
+ int depth;
+ XIM im;
+ Window selection_window;
+ Atom protocols, delete_window, utf8, clipboard, targets, paste_property;
+ Atom net_name, net_state, fullscreen, maximized_h, maximized_v;
+ char *selection[2];
+ struct x11_surface *paste_target;
+ uint32_t mods;
+ char *app_id;
+};
+
+static struct x11_display *display_of(const struct yasl_surface *s)
+{
+ return (struct x11_display *)s->display;
+}
+
+static void leave(struct x11_display *d)
+{
+ if (--d->depth) return;
+ while (d->dead) {
+ struct x11_surface *s = d->dead;
+ d->dead = s->dead_next;
+ free(s);
+ }
+}
+
+static void push(struct x11_surface *s, struct yasl_input_event event,
+ const char *text, size_t len)
+{
+ if (s->destroyed || s->count == INPUT_CAP) return;
+ unsigned slot = (s->head + s->count) % INPUT_CAP;
+ if (text) {
+ char *copy = malloc(len + 1);
+ if (!copy) return;
+ memcpy(copy, text, len);
+ copy[len] = 0;
+ s->event_text[slot] = copy;
+ if (event.kind == YASL_INPUT_TEXT) {
+ event.text.text = copy;
+ event.text.len = len;
+ } else {
+ event.paste.text = copy;
+ event.paste.len = len;
+ }
+ }
+ event.surface = &s->base;
+ s->events[slot] = event;
+ s->count++;
+}
+
+static uint32_t modifiers(unsigned state)
+{
+ return ((state & ShiftMask) ? YASL_MOD_SHIFT : 0) |
+ ((state & ControlMask) ? YASL_MOD_CTRL : 0) |
+ ((state & Mod1Mask) ? YASL_MOD_ALT : 0) |
+ ((state & Mod4Mask) ? YASL_MOD_SUPER : 0) |
+ ((state & LockMask) ? YASL_MOD_CAPS : 0);
+}
+
+static bool resize(struct x11_surface *s, struct yasl_vector2i size)
+{
+ if (size.x <= 0 || size.y <= 0 || size.x > 32767 || size.y > 32767 ||
+ (size_t)size.x > SIZE_MAX / sizeof(uint32_t) / (size_t)size.y)
+ return false;
+ if (s->image && size.x == s->size.x && size.y == s->size.y) return true;
+ struct x11_display *d = display_of(&s->base);
+ uint64_t start = yasl_loop_now_ns();
+ XImage *image = XCreateImage(d->native, DefaultVisual(d->native, d->screen),
+ (unsigned)DefaultDepth(d->native, d->screen), ZPixmap, 0, NULL,
+ (unsigned)size.x, (unsigned)size.y, 32, 0);
+ uint32_t *pixels = calloc((size_t)size.x * (size_t)size.y, sizeof(*pixels));
+ if (!image || !pixels) {
+ if (image) XDestroyImage(image);
+ free(pixels);
+ return false;
+ }
+ image->data = calloc((size_t)image->bytes_per_line, (size_t)size.y);
+ if (!image->data) {
+ XDestroyImage(image);
+ free(pixels);
+ return false;
+ }
+ if (s->image) XDestroyImage(s->image);
+ free(s->pixels);
+ s->image = image;
+ s->pixels = pixels;
+ s->size = size;
+ s->want_draw = true;
+ yasl_clock_note_resize(&s->clock, yasl_loop_now_ns() - start);
+ return true;
+}
+
+static void surface_destroy(struct yasl_surface *base)
+{
+ struct x11_surface *s = (struct x11_surface *)base;
+ if (s->destroyed) return;
+ struct x11_display *d = display_of(base);
+ s->destroyed = true;
+ for (struct x11_surface **p = &d->surfaces; *p; p = &(*p)->next) {
+ if (*p == s) { *p = s->next; break; }
+ }
+ if (d->paste_target == s) d->paste_target = NULL;
+ if (s->ic) XDestroyIC(s->ic);
+ if (s->image) XDestroyImage(s->image);
+ free(s->pixels);
+ if (s->gc) XFreeGC(d->native, s->gc);
+ if (s->window) XDestroyWindow(d->native, s->window);
+ for (unsigned i = 0; i < INPUT_CAP; i++) free(s->event_text[i]);
+ free(s->last_text);
+ if (d->depth) {
+ s->dead_next = d->dead;
+ d->dead = s;
+ } else free(s);
+}
+
+static void set_callbacks(struct yasl_surface *base,
+ const struct yasl_surface_cbs *cbs, void *ud)
+{
+ struct x11_surface *s = (struct x11_surface *)base;
+ if (s->destroyed) return;
+ s->cbs = cbs;
+ s->ud = ud;
+}
+
+static void set_title(struct yasl_surface *base, const char *title)
+{
+ struct x11_surface *s = (struct x11_surface *)base;
+ if (s->destroyed || !title) return;
+ struct x11_display *d = display_of(base);
+ XStoreName(d->native, s->window, title);
+ XChangeProperty(d->native, s->window, d->net_name, d->utf8, 8,
+ PropModeReplace, (const unsigned char *)title, (int)strlen(title));
+}
+
+static void net_message(struct x11_surface *s, Atom type,
+ long a, long b, long c, long e, long f)
+{
+ struct x11_display *d = display_of(&s->base);
+ XEvent event = {0};
+ event.xclient = (XClientMessageEvent){
+ .type = ClientMessage, .display = d->native, .window = s->window,
+ .message_type = type, .format = 32, .data.l = {a, b, c, e, f},
+ };
+ XSendEvent(d->native, RootWindow(d->native, d->screen), False,
+ SubstructureRedirectMask | SubstructureNotifyMask, &event);
+}
+
+static struct yasl_surface *create_toplevel(struct yasl_display *base,
+ const struct yasl_surface_spec *spec)
+{
+ if (!spec) return NULL;
+ struct x11_display *d = (struct x11_display *)base;
+ struct x11_surface *s = calloc(1, sizeof(*s));
+ if (!s) return NULL;
+ s->base.display = base;
+ s->clock.refresh_ns = YASL_REFRESH_DEFAULT_NS;
+ if (!resize(s, spec->size)) { free(s); return NULL; }
+ s->window = XCreateSimpleWindow(d->native, RootWindow(d->native, d->screen),
+ 0, 0, (unsigned)spec->size.x, (unsigned)spec->size.y, 0, 0, 0);
+ s->gc = XCreateGC(d->native, s->window, 0, NULL);
+ XSelectInput(d->native, s->window, ExposureMask | StructureNotifyMask |
+ FocusChangeMask | KeyPressMask | KeyReleaseMask | ButtonPressMask |
+ ButtonReleaseMask | PointerMotionMask | LeaveWindowMask | PropertyChangeMask);
+ XSetWMProtocols(d->native, s->window, &d->delete_window, 1);
+ XClassHint hint = { .res_name = d->app_id, .res_class = d->app_id };
+ XSetClassHint(d->native, s->window, &hint);
+ if (d->im) {
+ s->ic = XCreateIC(d->im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
+ XNClientWindow, s->window, XNFocusWindow, s->window, NULL);
+ }
+ s->next = d->surfaces;
+ d->surfaces = s;
+ set_title(&s->base, spec->title ? spec->title : "YASL");
+ if (spec->fullscreen) {
+ XChangeProperty(d->native, s->window, d->net_state, XA_ATOM, 32,
+ PropModeReplace, (unsigned char *)&d->fullscreen, 1);
+ s->state |= YASL_STATE_FULLSCREEN;
+ }
+ XMapWindow(d->native, s->window);
+ XFlush(d->native);
+ return &s->base;
+}
+
+static void update_state(struct x11_surface *s)
+{
+ struct x11_display *d = display_of(&s->base);
+ Atom actual;
+ int format;
+ unsigned long count, left;
+ unsigned char *data = NULL;
+ uint32_t state = s->state & YASL_STATE_ACTIVATED;
+ if (XGetWindowProperty(d->native, s->window, d->net_state, 0, 64, False,
+ XA_ATOM, &actual, &format, &count, &left, &data) == Success && format == 32) {
+ Atom *atoms = (Atom *)data;
+ bool h = false, v = false;
+ for (unsigned long i = 0; i < count; i++) {
+ if (atoms[i] == d->fullscreen) state |= YASL_STATE_FULLSCREEN;
+ if (atoms[i] == d->maximized_h) h = true;
+ if (atoms[i] == d->maximized_v) v = true;
+ }
+ if (h && v) state |= YASL_STATE_MAXIMIZED;
+ }
+ if (data) XFree(data);
+ if (state == s->state) return;
+ s->state = state;
+ if (s->cbs && s->cbs->state) s->cbs->state(&s->base, state, s->ud);
+}
+
+static void selection_request(struct x11_display *d, XSelectionRequestEvent *request)
+{
+ XEvent reply = {0};
+ reply.xselection = (XSelectionEvent){
+ .type = SelectionNotify, .display = d->native,
+ .requestor = request->requestor, .selection = request->selection,
+ .target = request->target, .time = request->time,
+ };
+ Atom property = request->property ? request->property : request->target;
+ int slot = request->selection == XA_PRIMARY ? YASL_PRIMARY : YASL_CLIPBOARD;
+ const char *text = d->selection[slot];
+ if (text && request->target == d->targets) {
+ Atom targets[] = {d->targets, d->utf8, XA_STRING};
+ XChangeProperty(d->native, request->requestor, property, XA_ATOM, 32,
+ PropModeReplace, (unsigned char *)targets, 3);
+ reply.xselection.property = property;
+ } else if (text && (request->target == d->utf8 || request->target == XA_STRING)) {
+ size_t len = strlen(text);
+ long max = XMaxRequestSize(d->native) * 4 - 128;
+ if (len <= (size_t)max) {
+ XChangeProperty(d->native, request->requestor, property, request->target,
+ 8, PropModeReplace, (const unsigned char *)text, (int)len);
+ reply.xselection.property = property;
+ }
+ }
+ XSendEvent(d->native, request->requestor, False, 0, &reply);
+}
+
+static void selection_notify(struct x11_display *d, XSelectionEvent *event)
+{
+ struct x11_surface *s = d->paste_target;
+ d->paste_target = NULL;
+ if (!s || event->property == None) return;
+ Atom actual;
+ int format;
+ unsigned long count, left;
+ unsigned char *data = NULL;
+ if (XGetWindowProperty(d->native, d->selection_window, event->property,
+ 0, TEXT_LIMIT / 4, True, AnyPropertyType,
+ &actual, &format, &count, &left, &data) == Success &&
+ (actual == d->utf8 || actual == XA_STRING) && format == 8 && !left) {
+ push(s, (struct yasl_input_event){.kind = YASL_INPUT_PASTE},
+ (const char *)data, count);
+ }
+ if (data) XFree(data);
+}
+
+static void key_event(struct x11_surface *s, XKeyEvent *key, bool repeat)
+{
+ struct x11_display *d = display_of(&s->base);
+ d->mods = modifiers(key->state);
+ KeySym sym = XLookupKeysym(key, (key->state & ShiftMask) ? 1 : 0);
+ KeySym base = XLookupKeysym(key, 0), shifted = XLookupKeysym(key, 1);
+ char local[128];
+ char *text = local;
+ int len = 0;
+ if (key->type == KeyPress) {
+ if (s->ic) {
+ Status status;
+ len = Xutf8LookupString(s->ic, key, local, sizeof(local), &sym, &status);
+ if (status == XBufferOverflow && len > 0 && len < TEXT_LIMIT) {
+ text = malloc((size_t)len + 1);
+ if (text) len = Xutf8LookupString(s->ic, key, text, len + 1, &sym, &status);
+ else len = 0;
+ }
+ if (status != XLookupChars && status != XLookupBoth) len = 0;
+ } else len = XLookupString(key, local, sizeof(local), &sym, NULL);
+ }
+ uint32_t cp = xkb_keysym_to_utf32((xkb_keysym_t)base);
+ if (!cp) cp = xkb_keysym_to_utf32((xkb_keysym_t)sym);
+ push(s, (struct yasl_input_event){
+ .kind = YASL_INPUT_KEY,
+ .key = {.key = (uint32_t)sym, .codepoint = cp,
+ .base = key->keycode >= 8 ? key->keycode - 8 : 0,
+ .keypad = sym >= XK_KP_Space && sym <= XK_KP_Equal,
+ .shifted = xkb_keysym_to_utf32((xkb_keysym_t)shifted),
+ .text = xkb_keysym_to_utf32((xkb_keysym_t)sym), .mods = d->mods,
+ .action = key->type == KeyRelease ? YASL_KEY_RELEASE :
+ repeat ? YASL_KEY_REPEAT : YASL_KEY_PRESS},
+ }, NULL, 0);
+ if (len > 0 && text)
+ push(s, (struct yasl_input_event){.kind = YASL_INPUT_TEXT}, text, (size_t)len);
+ if (text != local) free(text);
+}
+
+static void dispatch(struct yasl_display *base)
+{
+ struct x11_display *d = (struct x11_display *)base;
+ d->depth++;
+ while (XPending(d->native)) {
+ XEvent event;
+ XNextEvent(d->native, &event);
+ if (XFilterEvent(&event, None)) continue;
+ if (event.type == SelectionRequest) {
+ selection_request(d, &event.xselectionrequest);
+ continue;
+ }
+ if (event.type == SelectionNotify) {
+ selection_notify(d, &event.xselection);
+ continue;
+ }
+ struct x11_surface *s = d->surfaces;
+ while (s && s->window != event.xany.window) s = s->next;
+ if (!s) continue;
+ switch (event.type) {
+ case Expose: s->want_draw = true; break;
+ case ConfigureNotify: {
+ struct yasl_vector2i size = {event.xconfigure.width, event.xconfigure.height};
+ if (size.x == s->size.x && size.y == s->size.y) break;
+ if (resize(s, size) && s->cbs && s->cbs->configure)
+ s->cbs->configure(&s->base, (struct yasl_geometry){size, YASL_SCALE_UNIT}, s->ud);
+ break;
+ }
+ case ClientMessage:
+ if (event.xclient.message_type == d->protocols &&
+ (Atom)event.xclient.data.l[0] == d->delete_window && s->cbs && s->cbs->close)
+ s->cbs->close(&s->base, s->ud);
+ break;
+ case FocusIn: case FocusOut: {
+ bool focused = event.type == FocusIn;
+ if (s->ic) { if (focused) XSetICFocus(s->ic); else XUnsetICFocus(s->ic); }
+ if (focused) s->state |= YASL_STATE_ACTIVATED;
+ else s->state &= ~YASL_STATE_ACTIVATED;
+ push(s, (struct yasl_input_event){
+ .kind = YASL_INPUT_FOCUS, .focus.focused = focused}, NULL, 0);
+ if (s->cbs && s->cbs->state) s->cbs->state(&s->base, s->state, s->ud);
+ break;
+ }
+ case PropertyNotify:
+ if (event.xproperty.atom == d->net_state) update_state(s);
+ break;
+ case KeyPress: case KeyRelease: {
+ bool repeat = false;
+ if (event.type == KeyRelease && XPending(d->native)) {
+ XEvent next;
+ XPeekEvent(d->native, &next);
+ if (next.type == KeyPress && next.xkey.window == event.xkey.window &&
+ next.xkey.keycode == event.xkey.keycode && next.xkey.time == event.xkey.time) {
+ XNextEvent(d->native, &event);
+ repeat = true;
+ }
+ }
+ key_event(s, &event.xkey, repeat);
+ break;
+ }
+ case MotionNotify: case LeaveNotify: case ButtonPress: case ButtonRelease: {
+ struct yasl_mouse_event mouse = {.device = YASL_PTR_DEV_MOUSE};
+ unsigned state;
+ if (event.type == MotionNotify) {
+ mouse.pos = (struct yasl_vector2i){event.xmotion.x, event.xmotion.y};
+ mouse.kind = YASL_PTR_MOTION;
+ state = event.xmotion.state;
+ } else if (event.type == LeaveNotify) {
+ mouse.pos = (struct yasl_vector2i){event.xcrossing.x, event.xcrossing.y};
+ mouse.kind = YASL_PTR_LEAVE;
+ state = event.xcrossing.state;
+ } else {
+ mouse.pos = (struct yasl_vector2i){event.xbutton.x, event.xbutton.y};
+ state = event.xbutton.state;
+ unsigned button = event.xbutton.button;
+ if (button >= 4 && button <= 7) {
+ if (event.type == ButtonRelease) break;
+ mouse.kind = YASL_PTR_SCROLL;
+ mouse.scroll_lines = (button == 4 || button == 6) ? -1 : 1;
+ } else {
+ mouse.kind = event.type == ButtonPress ? YASL_PTR_PRESS : YASL_PTR_RELEASE;
+ mouse.button = button == 1 ? 0x110 : button == 2 ? 0x112 :
+ button == 3 ? 0x111 : (int)button + 0x10b;
+ }
+ }
+ d->mods = mouse.mods = modifiers(state);
+ push(s, (struct yasl_input_event){.kind = YASL_INPUT_MOUSE, .mouse = mouse}, NULL, 0);
+ break;
+ }
+ }
+ }
+ leave(d);
+}
+
+static unsigned long component(uint32_t color, unsigned long mask)
+{
+ if (!mask) return 0;
+ unsigned shift = 0;
+ while (!(mask & 1)) { mask >>= 1; shift++; }
+ return (((unsigned long)color * mask + 127) / 255) << shift;
+}
+
+static bool present(struct yasl_surface *base, const struct yasl_framebuffer *fb,
+ const struct yasl_damage_list *damage)
+{
+ struct x11_surface *s = (struct x11_surface *)base;
+ if (s->destroyed || !s->drawing || s->presented || !fb || !damage ||
+ fb->pixels != s->pixels || fb->size.x != s->size.x || fb->size.y != s->size.y ||
+ damage->n < -1 || damage->n > YASL_DAMAGE_MAX) return false;
+ struct x11_display *d = display_of(base);
+ XImage *im = s->image;
+ bool direct = im->bits_per_pixel == 32 && im->red_mask == 0xff0000 &&
+ im->green_mask == 0xff00 && im->blue_mask == 0xff;
+ for (int y = 0; y < s->size.y; y++) {
+ const uint32_t *row = s->pixels + (size_t)y * s->size.x;
+ if (direct) memcpy(im->data + (size_t)y * im->bytes_per_line, row, (size_t)s->size.x * 4);
+ else for (int x = 0; x < s->size.x; x++) {
+ uint32_t p = row[x];
+ XPutPixel(im, x, y, component((p >> 16) & 255, im->red_mask) |
+ component((p >> 8) & 255, im->green_mask) | component(p & 255, im->blue_mask));
+ }
+ }
+ if (damage->n == -1)
+ XPutImage(d->native, s->window, s->gc, im, 0, 0, 0, 0,
+ (unsigned)s->size.x, (unsigned)s->size.y);
+ else for (int i = 0; i < damage->n; i++) {
+ struct yasl_rect2i r = damage->rects[i];
+ int64_t right = (int64_t)r.x + r.width, bottom = (int64_t)r.y + r.height;
+ int x = r.x > 0 ? r.x : 0, y = r.y > 0 ? r.y : 0;
+ int w = (int)(right < s->size.x ? right : s->size.x) - x;
+ int h = (int)(bottom < s->size.y ? bottom : s->size.y) - y;
+ if (w > 0 && h > 0)
+ XPutImage(d->native, s->window, s->gc, im, x, y, x, y, (unsigned)w, (unsigned)h);
+ }
+ s->presented = true;
+ yasl_clock_presented(&s->clock, yasl_loop_now_ns(), false);
+ return true;
+}
+
+static void flush(struct yasl_display *base)
+{
+ struct x11_display *d = (struct x11_display *)base;
+ d->depth++;
+ for (struct x11_surface *s = d->surfaces, *next; s; s = next) {
+ next = s->next;
+ if (s->destroyed || !s->want_draw || !s->cbs || !s->cbs->draw || s->drawing) continue;
+ s->want_draw = false;
+ s->drawing = true;
+ s->presented = false;
+ s->damage = (struct yasl_damage_list){0};
+ s->frame = (struct yasl_frame){
+ .surface = &s->base,
+ .target = {.pixels = s->pixels, .size = s->size, .damage = &s->damage},
+ };
+ s->cbs->draw(&s->base, &s->frame, s->ud);
+ s->drawing = false;
+ }
+ XFlush(d->native);
+ leave(d);
+}
+
+static int ready(int fd, short events, void *ud)
+{
+ (void)fd;
+ struct yasl_display *base = ud;
+ if (events & POLLIN) { dispatch(base); flush(base); }
+ return 0;
+}
+
+static struct yasl_display *display_open(struct yasl_loop *loop,
+ const struct yasl_display_spec *spec)
+{
+ struct x11_display *d = calloc(1, sizeof(*d));
+ if (!d) return NULL;
+ d->base.ops = &yasl_x11_backend;
+ d->native = XOpenDisplay(NULL);
+ if (!d->native) { free(d); errno = ENODEV; return NULL; }
+ d->screen = DefaultScreen(d->native);
+ d->loop = loop;
+ d->app_id = strdup(spec && spec->app_id ? spec->app_id : "YASL");
+ if (!d->app_id) { XCloseDisplay(d->native); free(d); return NULL; }
+ d->im = XOpenIM(d->native, NULL, NULL, NULL);
+ d->selection_window = XCreateSimpleWindow(d->native,
+ RootWindow(d->native, d->screen), 0, 0, 1, 1, 0, 0, 0);
+#define ATOM(field, name) d->field = XInternAtom(d->native, name, False)
+ ATOM(protocols, "WM_PROTOCOLS");
+ ATOM(delete_window, "WM_DELETE_WINDOW");
+ ATOM(utf8, "UTF8_STRING");
+ ATOM(clipboard, "CLIPBOARD");
+ ATOM(targets, "TARGETS");
+ ATOM(paste_property, "YASL_SELECTION");
+ ATOM(net_name, "_NET_WM_NAME");
+ ATOM(net_state, "_NET_WM_STATE");
+ ATOM(fullscreen, "_NET_WM_STATE_FULLSCREEN");
+ ATOM(maximized_h, "_NET_WM_STATE_MAXIMIZED_HORZ");
+ ATOM(maximized_v, "_NET_WM_STATE_MAXIMIZED_VERT");
+#undef ATOM
+ if (loop) {
+ d->watch = yasl_loop_add_fd(loop, ConnectionNumber(d->native), POLLIN, ready, &d->base);
+ if (d->watch < 0) {
+ if (d->im) XCloseIM(d->im);
+ XDestroyWindow(d->native, d->selection_window);
+ XCloseDisplay(d->native);
+ free(d->app_id);
+ free(d);
+ return NULL;
+ }
+ }
+ return &d->base;
+}
+
+static void display_close(struct yasl_display *base)
+{
+ struct x11_display *d = (struct x11_display *)base;
+ assert(!d->surfaces && "destroy every surface before its display");
+ if (d->loop) yasl_loop_del_fd(d->loop, d->watch);
+ if (d->im) XCloseIM(d->im);
+ XDestroyWindow(d->native, d->selection_window);
+ XCloseDisplay(d->native);
+ free(d->selection[0]);
+ free(d->selection[1]);
+ free(d->app_id);
+ free(d);
+}
+
+static uint32_t caps(const struct yasl_display *d)
+{
+ (void)d;
+ return YASL_CAP_PLACE_WINDOW | YASL_CAP_PRIMARY_SELECTION;
+}
+
+static void copy(struct yasl_display *base, enum yasl_selection sel, const char *text)
+{
+ if (!text || (sel != YASL_CLIPBOARD && sel != YASL_PRIMARY)) return;
+ struct x11_display *d = (struct x11_display *)base;
+ char *value = strdup(text);
+ if (!value) return;
+ free(d->selection[sel]);
+ d->selection[sel] = value;
+ XSetSelectionOwner(d->native, sel == YASL_PRIMARY ? XA_PRIMARY : d->clipboard,
+ d->selection_window, CurrentTime);
+ XFlush(d->native);
+}
+
+static void paste(struct yasl_display *base, enum yasl_selection sel, struct yasl_surface *target)
+{
+ if (sel != YASL_CLIPBOARD && sel != YASL_PRIMARY) return;
+ struct x11_display *d = (struct x11_display *)base;
+ struct x11_surface *s = (struct x11_surface *)target;
+ Atom selection = sel == YASL_PRIMARY ? XA_PRIMARY : d->clipboard;
+ if (XGetSelectionOwner(d->native, selection) == d->selection_window) {
+ const char *text = d->selection[sel];
+ if (text) push(s, (struct yasl_input_event){.kind = YASL_INPUT_PASTE}, text, strlen(text));
+ } else {
+ d->paste_target = s;
+ XConvertSelection(d->native, selection, d->utf8, d->paste_property,
+ d->selection_window, CurrentTime);
+ XFlush(d->native);
+ }
+}
+
+static bool input_poll(struct yasl_surface *base, struct yasl_input_event *event)
+{
+ struct x11_surface *s = (struct x11_surface *)base;
+ if (!event || s->destroyed || !s->count) return false;
+ free(s->last_text);
+ s->last_text = s->event_text[s->head];
+ s->event_text[s->head] = NULL;
+ *event = s->events[s->head];
+ s->head = (s->head + 1) % INPUT_CAP;
+ s->count--;
+ return true;
+}
+
+static void schedule_redraw(struct yasl_surface *base)
+{
+ struct x11_surface *s = (struct x11_surface *)base;
+ if (!s->destroyed) s->want_draw = true;
+}
+
+static struct yasl_display *surface_display(const struct yasl_surface *s) { return s->display; }
+static struct yasl_vector2i size(const struct yasl_surface *s) { return ((const struct x11_surface *)s)->size; }
+static uint32_t scale(const struct yasl_surface *s) { (void)s; return YASL_SCALE_UNIT; }
+static uint32_t state(const struct yasl_surface *s) { return ((const struct x11_surface *)s)->state; }
+static int mods(const struct yasl_display *d) { return (int)((const struct x11_display *)d)->mods; }
+static void *native_display(struct yasl_display *d) { return ((struct x11_display *)d)->native; }
+static void *native_surface(struct yasl_surface *s) { return (void *)(uintptr_t)((struct x11_surface *)s)->window; }
+
+static struct yasl_timing timing(const struct yasl_surface *base)
+{
+ const struct x11_surface *s = (const struct x11_surface *)base;
+ struct yasl_timing t = {.refresh_ns = s->clock.refresh_ns,
+ .last_present_ns = s->clock.last_present_ns,
+ .next_present_ns = yasl_clock_next_present_ns(&s->clock)};
+ yasl_clock_resize_stats(&s->clock, &t.resize);
+ return t;
+}
+
+static uint64_t frame_time(struct yasl_surface *s, uint64_t now)
+{
+ return yasl_clock_frame_timestamp_ns(&((struct x11_surface *)s)->clock, now);
+}
+
+static void cursor(struct yasl_surface *base, enum yasl_cursor cursor)
+{
+ static const unsigned shapes[] = {XC_left_ptr, XC_xterm, XC_hand2, XC_crosshair,
+ XC_fleur, XC_bottom_right_corner, XC_bottom_left_corner,
+ XC_sb_h_double_arrow, XC_sb_v_double_arrow};
+ struct x11_surface *s = (struct x11_surface *)base;
+ if (s->destroyed || (unsigned)cursor > YASL_CURSOR_NONE) return;
+ struct x11_display *d = display_of(base);
+ Cursor native;
+ if (cursor == YASL_CURSOR_NONE) {
+ char bits[] = {0};
+ Pixmap pixmap = XCreateBitmapFromData(d->native, s->window, bits, 1, 1);
+ XColor color = {0};
+ native = XCreatePixmapCursor(d->native, pixmap, pixmap, &color, &color, 0, 0);
+ XFreePixmap(d->native, pixmap);
+ } else native = XCreateFontCursor(d->native, shapes[cursor]);
+ XDefineCursor(d->native, s->window, native);
+ XFreeCursor(d->native, native);
+}
+
+static bool place(struct yasl_surface *base, struct yasl_vector2i at)
+{
+ struct x11_surface *s = (struct x11_surface *)base;
+ struct x11_display *d = display_of(base);
+ Window root, child;
+ int root_x, root_y, x, y;
+ unsigned mask;
+ if (s->destroyed || !XQueryPointer(d->native, s->window,
+ &root, &child, &root_x, &root_y, &x, &y, &mask)) return false;
+ XMoveWindow(d->native, s->window, root_x - at.x, root_y - at.y);
+ return true;
+}
+
+static bool interactive_move(struct yasl_surface *base)
+{
+ struct x11_surface *s = (struct x11_surface *)base;
+ struct x11_display *d = display_of(base);
+ Window root, child;
+ int rx, ry, x, y;
+ unsigned mask;
+ if (s->destroyed || !XQueryPointer(d->native, s->window,
+ &root, &child, &rx, &ry, &x, &y, &mask)) return false;
+ XUngrabPointer(d->native, CurrentTime);
+ net_message(s, XInternAtom(d->native, "_NET_WM_MOVERESIZE", False), rx, ry, 8, 1, 1);
+ return true;
+}
+
+const struct yasl_backend_ops yasl_x11_backend = {
+ .type = YASL_BACKEND_X11,
+ .display_open = display_open,
+ .display_close = display_close,
+ .display_caps = caps,
+ .display_dispatch = dispatch,
+ .display_flush = flush,
+ .display_modifiers = mods,
+ .display_copy = copy,
+ .display_paste = paste,
+ .surface_create_toplevel = create_toplevel,
+ .surface_destroy = surface_destroy,
+ .surface_display = surface_display,
+ .surface_set_callbacks = set_callbacks,
+ .surface_set_title = set_title,
+ .surface_schedule_redraw = schedule_redraw,
+ .surface_present = present,
+ .surface_input_poll = input_poll,
+ .surface_size = size,
+ .surface_scale_120 = scale,
+ .surface_state = state,
+ .surface_timing = timing,
+ .surface_frame_time_ns = frame_time,
+ .surface_set_cursor = cursor,
+ .surface_place_under_pointer = place,
+ .surface_start_interactive_move = interactive_move,
+ .display_get_native_handle = native_display,
+ .surface_get_native_handle = native_surface,
+};