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.c2063
2 files changed, 0 insertions, 2064 deletions
diff --git a/src/x11/meson.build b/src/x11/meson.build
deleted file mode 100644
index e1a38f0..0000000
--- a/src/x11/meson.build
+++ /dev/null
@@ -1 +0,0 @@
-sources += files('window.c')
diff --git a/src/x11/window.c b/src/x11/window.c
deleted file mode 100644
index c00a449..0000000
--- a/src/x11/window.c
+++ /dev/null
@@ -1,2063 +0,0 @@
-#define _POSIX_C_SOURCE 200809L
-#define _DEFAULT_SOURCE
-#include "common/backend.h"
-#include "common/input.h"
-#include "common/keyboard.h"
-#include "common/utf8.h"
-#include "common/timing.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 <X11/extensions/XInput2.h>
-#include <X11/extensions/XShm.h>
-#include <sys/ipc.h>
-#include <sys/shm.h>
-#if YASL_HAS_XRANDR
-#include <X11/extensions/Xrandr.h>
-#endif
-#include <xkbcommon/xkbcommon.h>
-
-#include <assert.h>
-#include <errno.h>
-#include <limits.h>
-#include <math.h>
-#include <poll.h>
-#include <stdlib.h>
-#include <string.h>
-#include <strings.h>
-
-#define TEXT_LIMIT (16 * 1024 * 1024)
-
-#ifndef likely
-#define likely(x) __builtin_expect(!!(x), 1)
-#endif
-#ifndef unlikely
-#define unlikely(x) __builtin_expect(!!(x), 0)
-#endif
-
-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;
- bool shm_active;
- XShmSegmentInfo shm_info;
- struct yasl_vector2i size;
- const struct yasl_surface_cbs *cbs;
- void *ud;
- struct input_queue input;
- 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_surface *parent;
- bool is_popup;
- struct yasl_vector2i content_offset;
- char *ime_preedit;
- int ime_cursor_begin;
- int ime_cursor_end;
- struct yasl_rect2i ime_caret;
-};
-
-struct paste_request {
- struct paste_request *next;
- struct x11_surface *target;
- Window window;
-};
-
-struct tablet_axis {
- int number;
- double min, max, value;
-};
-
-struct x11_pointer {
- struct x11_pointer *next;
- int id;
- enum yasl_pointer_device device;
- struct tablet_axis pressure, tilt_x, tilt_y;
-};
-
-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, incr;
- Atom net_name, net_state, fullscreen, maximized_h, maximized_v;
- Atom xdnd_aware, xdnd_enter, xdnd_position, xdnd_status, xdnd_leave, xdnd_drop, xdnd_finished;
- Atom xdnd_selection, xdnd_action_copy, xdnd_type_list, uri_list;
- char *selection[2];
- struct paste_request *pastes;
- uint32_t mods;
- char *app_id;
- bool owns_loop;
- int xi_opcode;
- bool xi2;
- bool has_shm;
- bool has_xkb;
- int xkb_event;
- enum yasl_key keys[256];
- Cursor cursors[16];
- Atom abs_pressure, abs_tilt_x, abs_tilt_y, tool_type, eraser;
- struct x11_pointer *pointers;
- struct {
- bool active;
- struct x11_surface *origin;
- struct x11_surface *carried;
- struct yasl_vector2i carry_offset;
- struct yasl_drag_listener listener;
- void *ud;
- struct x11_surface *hover;
- struct yasl_vector2i hover_pos;
- Window icon_window;
- Pixmap icon_pixmap;
- struct yasl_vector2i hotspot;
- } drag;
- struct {
- Window source;
- int version;
- bool accepts;
- bool has_uri_list;
- struct x11_surface *surface;
- struct yasl_vector2i pos;
- Time time;
- } xdnd;
- struct {
- bool active;
- Window source;
- struct x11_surface *surface;
- Time time;
- } pending_drop;
-};
-
-static struct x11_display *display_of(const struct yasl_surface *s)
-{
- return (struct x11_display *)s->display;
-}
-
-static bool place(struct yasl_surface *base, struct yasl_vector2i at);
-static void drag_end(struct x11_display *d, enum yasl_drag_end how);
-static void drag_cancel(struct yasl_display *base);
-static void schedule_redraw(struct yasl_surface *base);
-
-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) return;
- if (text) {
- if (event.kind == YASL_INPUT_TEXT) {
- event.text.text = text;
- event.text.len = len;
- } else if (event.kind == YASL_INPUT_DROP_FILES) {
- event.drop_files.files = text;
- event.drop_files.len = len;
- } else {
- event.paste.text = text;
- event.paste.len = len;
- }
- }
- event.surface = &s->base;
- input_queue_push(&s->input, &event);
-}
-
-static unsigned long component(uint32_t color, unsigned long mask)
-{
- if (unlikely(!mask)) return 0;
- unsigned shift = (unsigned)__builtin_ctzl(mask);
- unsigned long m = mask >> shift;
- return (((unsigned long)color * m + 127) / 255) << shift;
-}
-
-static void drag_set_hover(struct x11_display *d, struct x11_surface *s, struct yasl_vector2i pos)
-{
- if (!d->drag.active) return;
- if (d->drag.hover && d->drag.hover != s) {
- struct x11_surface *old = d->drag.hover;
- struct yasl_vector2i old_pos = d->drag.hover_pos;
- d->drag.hover = NULL;
- if (!old->destroyed && old->cbs && old->cbs->drag)
- old->cbs->drag(&old->base, YASL_DRAG_LEAVE, old_pos, old->ud);
- if (!d->drag.active) return;
- }
- if (s && s->destroyed) return;
- d->drag.hover = s;
- d->drag.hover_pos = pos;
- if (s && s->cbs && s->cbs->drag)
- s->cbs->drag(&s->base, YASL_DRAG_OVER, pos, s->ud);
-}
-
-static void drag_leave_hover(struct x11_display *d, struct x11_surface *s, struct yasl_vector2i pos)
-{
- if (!d->drag.active || d->drag.hover != s) return;
- d->drag.hover = NULL;
- if (s && !s->destroyed && s->cbs && s->cbs->drag)
- s->cbs->drag(&s->base, YASL_DRAG_LEAVE, pos, s->ud);
-}
-
-static void drag_drop_hover(struct x11_display *d)
-{
- if (!d->drag.active) return;
- drag_end(d, YASL_DRAG_DROPPED);
-}
-
-static int yasl_x11_error_handler(Display *native, XErrorEvent *event)
-{
- (void)native;
- (void)event;
- /* Suppress asynchronous errors (BadWindow, BadDrawable, BadMatch)
- * during window close, destruction races, or speculative property queries. */
- return 0;
-}
-
-static struct x11_surface *surface_at_root_pos(struct x11_display *d, int rx, int ry, struct yasl_vector2i *out_pos)
-{
- if (!d->native) return NULL;
- Window ignore_icon = d->drag.icon_window;
- Window ignore_carried = d->drag.carried ? d->drag.carried->window : None;
-
- for (struct x11_surface *s = d->surfaces; s; s = s->next) {
- if (s->destroyed || s->window == ignore_icon || s->window == ignore_carried) continue;
- int tx = 0, ty = 0;
- Window sub = None;
- if (XTranslateCoordinates(d->native, RootWindow(d->native, d->screen),
- s->window, rx, ry, &tx, &ty, &sub)) {
- if (tx >= 0 && ty >= 0 && tx < s->size.x && ty < s->size.y) {
- if (out_pos) *out_pos = (struct yasl_vector2i){tx, ty};
- return s;
- }
- }
- }
- return NULL;
-}
-
-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 void pointer_info(struct x11_display *d, struct x11_pointer *p,
- const XIDeviceInfo *info)
-{
- p->id = info->deviceid;
- p->device = YASL_PTR_DEV_MOUSE;
- p->pressure.number = p->tilt_x.number = p->tilt_y.number = -1;
- bool touch = false;
- for (int i = 0; i < info->num_classes; i++) {
- XIAnyClassInfo *c = info->classes[i];
- if (c->type == XITouchClass) touch = true;
- if (c->type != XIValuatorClass) continue;
- XIValuatorClassInfo *v = (XIValuatorClassInfo *)c;
- if (v->mode != XIModeAbsolute) continue;
- struct tablet_axis *axis = v->label == d->abs_pressure ? &p->pressure :
- v->label == d->abs_tilt_x ? &p->tilt_x :
- v->label == d->abs_tilt_y ? &p->tilt_y : NULL;
- if (axis) *axis = (struct tablet_axis){v->number, v->min, v->max, v->value};
- }
- if (!touch && p->pressure.number >= 0) {
- p->device = YASL_PTR_DEV_PEN;
- /* Xorg's Wacom and libinput drivers expose separate eraser devices.
- * Use the tool property when available, with the device name as a
- * fallback for servers (including Xwayland) without that property. */
- if (info->name) {
- for (const char *s = info->name; *s; s++)
- if (!strncasecmp(s, "eraser", 6)) { p->device = YASL_PTR_DEV_ERASER; break; }
- }
- }
-}
-
-static struct x11_pointer *pointer_for(struct x11_display *d, int id)
-{
- for (struct x11_pointer *p = d->pointers; p; p = p->next)
- if (p->id == id) return p;
- if (!d->native) return NULL;
- int count = 0;
- XIDeviceInfo *info = XIQueryDevice(d->native, id, &count);
- if (!info) return NULL;
- struct x11_pointer *p = count ? calloc(1, sizeof(*p)) : NULL;
- if (p) {
- pointer_info(d, p, info);
- if (p->device == YASL_PTR_DEV_PEN || p->device == YASL_PTR_DEV_ERASER) {
- Atom type;
- int format;
- unsigned long n, after;
- unsigned char *value = NULL;
- if (XIGetProperty(d->native, id, d->tool_type, 0, 1, False,
- XA_ATOM, &type, &format, &n, &after, &value) == Success &&
- type == XA_ATOM && format == 32 && n == 1 && value)
- /* XIGetProperty packs format-32 values as uint32_t, unlike
- * XGetWindowProperty's array of native-sized longs. */
- p->device = *(uint32_t *)value == d->eraser ? YASL_PTR_DEV_ERASER : p->device;
- if (value) XFree(value);
- }
- p->next = d->pointers;
- d->pointers = p;
- }
- XIFreeDeviceInfo(info);
- return p;
-}
-
-static void clear_pointers(struct x11_display *d)
-{
- while (d->pointers) {
- struct x11_pointer *p = d->pointers;
- d->pointers = p->next;
- free(p);
- }
-}
-
-static void update_axes(struct x11_pointer *p, const XIValuatorState *state)
-{
- const double *value = state->values;
- for (int i = 0; i < state->mask_len * 8; i++) {
- if (!XIMaskIsSet(state->mask, i)) continue;
- struct tablet_axis *axes[] = {&p->pressure, &p->tilt_x, &p->tilt_y};
- for (unsigned a = 0; a < sizeof(axes) / sizeof(axes[0]); a++)
- if (axes[a]->number == i && isfinite(*value)) axes[a]->value = *value;
- value++;
- }
-}
-
-static uint16_t pressure_value(const struct tablet_axis *a)
-{
- if (a->number < 0 || !(a->max > a->min) || !isfinite(a->value)) return 0;
- double v = (a->value - a->min) / (a->max - a->min);
- return (uint16_t)lround(fmax(0, fmin(1, v)) * 65535);
-}
-
-static int8_t tilt_value(const struct tablet_axis *a)
-{
- /* Xorg tablet tilt valuators are already in degrees. */
- return a->number < 0 || !isfinite(a->value) ? 0 :
- (int8_t)lround(fmax(-90, fmin(90, a->value)));
-}
-
-static int pointer_button(unsigned button)
-{
- return button == 1 ? 0 : button == 2 ? 1 : button == 3 ? 2 :
- button >= 8 && button <= INT16_MAX ? (int)button - 5 : 3;
-}
-
-static void xi_pointer_event(struct x11_surface *s, const XIDeviceEvent *ev)
-{
- struct x11_display *d = display_of(&s->base);
- struct x11_pointer *p = pointer_for(d, ev->sourceid);
- struct yasl_mouse_event mouse = {
- .pos = {yasl_scale_to_device(ev->event_x, YASL_SCALE_UNIT),
- yasl_scale_to_device(ev->event_y, YASL_SCALE_UNIT)},
- .device = p ? p->device : YASL_PTR_DEV_MOUSE,
- .mods = modifiers(ev->mods.effective),
- .button = 3,
- };
- if (p && (p->device == YASL_PTR_DEV_PEN || p->device == YASL_PTR_DEV_ERASER)) {
- update_axes(p, &ev->valuators);
- if (ev->evtype == XI_ButtonRelease && ev->detail == 1)
- p->pressure.value = p->pressure.min;
- mouse.pen = (struct yasl_pointer_pen){
- pressure_value(&p->pressure), tilt_value(&p->tilt_x), tilt_value(&p->tilt_y)};
- }
- if (ev->evtype == XI_Motion) mouse.kind = YASL_PTR_MOTION;
- else if (ev->detail >= 4 && ev->detail <= 7) {
- if (ev->evtype == XI_ButtonRelease) return;
- mouse.kind = YASL_PTR_SCROLL;
- if (ev->detail <= 5) mouse.scroll.y = ev->detail == 4 ? -1 : 1;
- else mouse.scroll.x = ev->detail == 6 ? -1 : 1;
- } else {
- mouse.kind = ev->evtype == XI_ButtonPress ? YASL_PTR_PRESS : YASL_PTR_RELEASE;
- mouse.button = pointer_button(ev->detail);
- }
- d->mods = mouse.mods;
- push(s, (struct yasl_input_event){.kind = YASL_INPUT_MOUSE, .mouse = mouse}, NULL, 0);
-}
-
-static void xi_event(struct x11_display *d, XGenericEventCookie *cookie)
-{
- if (cookie->evtype == XI_HierarchyChanged || cookie->evtype == XI_DeviceChanged) {
- if (cookie->evtype == XI_DeviceChanged &&
- ((XIDeviceChangedEvent *)cookie->data)->reason == XISlaveSwitch) return;
- clear_pointers(d);
- if (cookie->evtype == XI_HierarchyChanged &&
- (((XIHierarchyEvent *)cookie->data)->flags &
- (XISlaveRemoved | XIDeviceDisabled | XISlaveDetached | XIMasterRemoved))) {
- /* Removal/disable may interrupt a drag before a release arrives. */
- if (d->drag.active)
- drag_end(d, YASL_DRAG_CANCELLED);
- for (struct x11_surface *s = d->surfaces; s; s = s->next)
- push(s, (struct yasl_input_event){.kind = YASL_INPUT_RESET}, NULL, 0);
- }
- return;
- }
- bool crossing = cookie->evtype == XI_Enter || cookie->evtype == XI_Leave;
- if (!crossing && cookie->evtype != XI_Motion && cookie->evtype != XI_ButtonPress &&
- cookie->evtype != XI_ButtonRelease) return;
- Window window = crossing ? ((XIEnterEvent *)cookie->data)->event :
- ((XIDeviceEvent *)cookie->data)->event;
- struct x11_surface *s = d->surfaces;
- while (s && s->window != window) s = s->next;
- if (cookie->evtype == XI_Motion && d->drag.active) {
- const XIDeviceEvent *mev = cookie->data;
- if (d->drag.carried)
- place(&d->drag.carried->base, d->drag.carry_offset);
- else if (d->drag.icon_window && d->native)
- XMoveWindow(d->native, d->drag.icon_window,
- (int)round(mev->root_x) - d->drag.hotspot.x,
- (int)round(mev->root_y) - d->drag.hotspot.y);
- struct yasl_vector2i hpos = {yasl_scale_to_device(mev->event_x, YASL_SCALE_UNIT),
- yasl_scale_to_device(mev->event_y, YASL_SCALE_UNIT)};
- struct x11_surface *hsurf = d->native ?
- surface_at_root_pos(d, (int)round(mev->root_x), (int)round(mev->root_y), &hpos) : s;
- drag_set_hover(d, hsurf, hpos);
- } else if (cookie->evtype == XI_Enter && d->drag.active) {
- const XIEnterEvent *eev = cookie->data;
- struct yasl_vector2i hpos = {yasl_scale_to_device(eev->event_x, YASL_SCALE_UNIT),
- yasl_scale_to_device(eev->event_y, YASL_SCALE_UNIT)};
- struct x11_surface *hsurf = d->native ?
- surface_at_root_pos(d, (int)round(eev->root_x), (int)round(eev->root_y), &hpos) : s;
- drag_set_hover(d, hsurf, hpos);
- } else if (cookie->evtype == XI_Leave && d->drag.active) {
- const XIEnterEvent *lev = cookie->data;
- struct yasl_vector2i lpos = {yasl_scale_to_device(lev->event_x, YASL_SCALE_UNIT),
- yasl_scale_to_device(lev->event_y, YASL_SCALE_UNIT)};
- drag_leave_hover(d, s, lpos);
- } else if (cookie->evtype == XI_ButtonRelease && d->drag.active) {
- drag_drop_hover(d);
- }
- if (!s || s->destroyed) return;
- if (!crossing) xi_pointer_event(s, cookie->data);
- else {
- const XIEnterEvent *ev = cookie->data;
- struct x11_pointer *p = pointer_for(d, ev->sourceid);
- if (p) p->pressure.value = p->pressure.min;
- push(s, (struct yasl_input_event){.kind = YASL_INPUT_MOUSE, .mouse = {
- .pos = {yasl_scale_to_device(ev->event_x, YASL_SCALE_UNIT),
- yasl_scale_to_device(ev->event_y, YASL_SCALE_UNIT)},
- .device = p ? p->device : YASL_PTR_DEV_MOUSE,
- .button = 3, .mods = modifiers(ev->mods.effective),
- .kind = cookie->evtype == XI_Leave ? YASL_PTR_LEAVE : YASL_PTR_MOTION,
- }}, NULL, 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);
- if (!d->native) {
- s->size = size;
- s->want_draw = true;
- return true;
- }
- uint64_t start = yasl_loop_now_ns();
-
- if (s->shm_active) {
- XShmDetach(d->native, &s->shm_info);
- if (s->image) XDestroyImage(s->image);
- if (s->shm_info.shmaddr) shmdt(s->shm_info.shmaddr);
- s->image = NULL;
- s->pixels = NULL;
- s->shm_active = false;
- } else {
- if (s->image) XDestroyImage(s->image);
- free(s->pixels);
- s->image = NULL;
- s->pixels = NULL;
- }
-
- XImage *image = NULL;
- uint32_t *pixels = NULL;
- bool shm_ok = false;
- if (d->has_shm) {
- size_t bytes = (size_t)size.x * (size_t)size.y * sizeof(uint32_t);
- s->shm_info.shmid = shmget(IPC_PRIVATE, bytes, IPC_CREAT | 0600);
- if (s->shm_info.shmid >= 0) {
- s->shm_info.shmaddr = (char *)shmat(s->shm_info.shmid, NULL, 0);
- if (s->shm_info.shmaddr != (char *)-1) {
- s->shm_info.readOnly = False;
- image = XShmCreateImage(d->native, DefaultVisual(d->native, d->screen),
- (unsigned)DefaultDepth(d->native, d->screen), ZPixmap,
- s->shm_info.shmaddr, &s->shm_info,
- (unsigned)size.x, (unsigned)size.y);
- if (image) {
- shmctl(s->shm_info.shmid, IPC_RMID, NULL);
- if (XShmAttach(d->native, &s->shm_info)) {
- pixels = (uint32_t *)s->shm_info.shmaddr;
- memset(pixels, 0, bytes);
- shm_ok = true;
- } else {
- XDestroyImage(image);
- image = NULL;
- shmdt(s->shm_info.shmaddr);
- s->shm_info.shmaddr = NULL;
- }
- } else {
- shmdt(s->shm_info.shmaddr);
- s->shm_info.shmaddr = NULL;
- shmctl(s->shm_info.shmid, IPC_RMID, NULL);
- }
- } else {
- s->shm_info.shmaddr = NULL;
- shmctl(s->shm_info.shmid, IPC_RMID, NULL);
- }
- }
- }
- if (!shm_ok) {
- 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);
- 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;
- }
- }
- s->shm_active = shm_ok;
- s->image = image;
- s->pixels = pixels;
- s->size = size;
- s->base.size = size;
- s->base.scale_120 = YASL_SCALE_UNIT;
- s->want_draw = true;
- yasl_clock_note_resize(&s->clock, yasl_loop_now_ns() - start);
- return true;
-}
-
-static void cancel_pastes(struct x11_display *d, struct x11_surface *target)
-{
- for (struct paste_request **p = &d->pastes; *p;) {
- struct paste_request *r = *p;
- if (!target) {
- *p = r->next;
- XDestroyWindow(d->native, r->window);
- free(r);
- } else {
- /* Keep the requestor alive until SelectionNotify: the owner may
- * still be writing to its property. Never retain a dead target. */
- if (r->target == target) r->target = NULL;
- p = &r->next;
- }
- }
-}
-
-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);
- if (d->drag.active) {
- if (d->drag.origin == s || d->drag.carried == s)
- drag_end(d, YASL_DRAG_CANCELLED);
- else if (d->drag.hover == s)
- d->drag.hover = NULL;
- }
- if (d->xdnd.surface == s)
- d->xdnd.surface = NULL;
- if (d->pending_drop.surface == s) d->pending_drop.surface = NULL;
- for (struct x11_surface *p = d->surfaces; p; p = p->next) {
- if (p->parent == s) p->parent = NULL;
- }
- s->destroyed = true;
- for (struct x11_surface **p = &d->surfaces; *p; p = &(*p)->next) {
- if (*p == s) { *p = s->next; break; }
- }
- cancel_pastes(d, s);
- free(s->ime_preedit);
- s->ime_preedit = NULL;
- if (s->ic) XDestroyIC(s->ic);
- if (s->shm_active) {
- XShmDetach(d->native, &s->shm_info);
- if (s->image) XDestroyImage(s->image);
- if (s->shm_info.shmaddr) shmdt(s->shm_info.shmaddr);
- s->image = NULL;
- s->pixels = NULL;
- s->shm_active = false;
- } else {
- if (s->image) XDestroyImage(s->image);
- free(s->pixels);
- s->image = NULL;
- s->pixels = NULL;
- }
- if (d->native) {
- if (s->gc) XFreeGC(d->native, s->gc);
- if (s->window) XDestroyWindow(d->native, s->window);
- }
- input_queue_clear(&s->input);
- 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->base.callbacks = cbs ? *cbs : (struct yasl_surface_cbs){0};
- s->cbs = &s->base.callbacks;
- 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, void *graphics_device)
-{
- (void)graphics_device;
- 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->base.native_handle = (void *)(uintptr_t)s->window;
- s->base.size = s->size;
- s->base.scale_120 = YASL_SCALE_UNIT;
- s->gc = XCreateGC(d->native, s->window, 0, NULL);
- XSelectInput(d->native, s->window, ExposureMask | StructureNotifyMask |
- FocusChangeMask | KeyPressMask | KeyReleaseMask | PropertyChangeMask |
- (d->xi2 ? 0 : ButtonPressMask | ButtonReleaseMask |
- PointerMotionMask | LeaveWindowMask | EnterWindowMask));
- if (d->xi2) {
- unsigned char bits[XIMaskLen(XI_LASTEVENT)] = {0};
- XISetMask(bits, XI_Motion);
- XISetMask(bits, XI_ButtonPress);
- XISetMask(bits, XI_ButtonRelease);
- XISetMask(bits, XI_Enter);
- XISetMask(bits, XI_Leave);
- XIEventMask mask = {XIAllMasterDevices, sizeof(bits), bits};
- XISelectEvents(d->native, s->window, &mask, 1);
- }
- 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);
- Atom xdnd_version = 5;
- XChangeProperty(d->native, s->window, d->xdnd_aware, XA_ATOM, 32,
- PropModeReplace, (unsigned char *)&xdnd_version, 1);
- 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 bool popup_spec_valid(const struct x11_surface *parent,
- const struct yasl_popup_spec *spec)
-{
- if (!parent || !spec) return false;
- int parent_cx = parent->is_popup ? parent->content_offset.x : 0;
- int parent_cy = parent->is_popup ? parent->content_offset.y : 0;
- int64_t x = (int64_t)spec->anchor.x - parent_cx;
- int64_t y = (int64_t)spec->anchor.y - parent_cy;
- return x >= -32768 && x <= 32767 && y >= -32768 && y <= 32767 &&
- spec->anchor.width >= 0 && spec->anchor.height >= 0 &&
- spec->size.x > 0 && spec->size.y > 0 &&
- spec->size.x <= 32767 && spec->size.y <= 32767 &&
- spec->content_offset.x >= 0 && spec->content_offset.y >= 0 &&
- spec->content_size.x >= 0 && spec->content_size.y >= 0 &&
- (int64_t)spec->content_offset.x + spec->content_size.x <= spec->size.x &&
- (int64_t)spec->content_offset.y + spec->content_size.y <= spec->size.y;
-}
-
-static void popup_calculate_position(struct x11_display *d,
- struct x11_surface *parent,
- const struct yasl_popup_spec *spec,
- int *out_x, int *out_y)
-{
- int parent_rx = 0, parent_ry = 0;
- if (d->native) {
- Window child;
- XTranslateCoordinates(d->native, parent->window,
- RootWindow(d->native, d->screen),
- 0, 0, &parent_rx, &parent_ry, &child);
- }
- int parent_cx = parent->is_popup ? parent->content_offset.x : 0;
- int parent_cy = parent->is_popup ? parent->content_offset.y : 0;
-
- int anchor_x = parent_rx + (spec->anchor.x - parent_cx);
- int anchor_y = parent_ry + (spec->anchor.y - parent_cy);
-
- int root_x, root_y;
- if (spec->beside) {
- root_x = anchor_x + spec->anchor.width - spec->content_offset.x;
- root_y = anchor_y - spec->content_offset.y;
- } else {
- root_x = anchor_x - spec->content_offset.x;
- root_y = anchor_y + spec->anchor.height - spec->content_offset.y;
- }
-
- int mon_x = 0, mon_y = 0;
- int mon_w = d->native ? DisplayWidth(d->native, d->screen) : 1920;
- int mon_h = d->native ? DisplayHeight(d->native, d->screen) : 1080;
-
-#if YASL_HAS_XRANDR
- if (d->native) {
- int nmonitors = 0;
- XRRMonitorInfo *monitors = XRRGetMonitors(d->native, RootWindow(d->native, d->screen), True, &nmonitors);
- if (monitors && nmonitors > 0) {
- for (int i = 0; i < nmonitors; i++) {
- if (anchor_x >= monitors[i].x && anchor_x < monitors[i].x + monitors[i].width &&
- anchor_y >= monitors[i].y && anchor_y < monitors[i].y + monitors[i].height) {
- mon_x = monitors[i].x;
- mon_y = monitors[i].y;
- mon_w = monitors[i].width;
- mon_h = monitors[i].height;
- break;
- }
- }
- XRRFreeMonitors(monitors);
- }
- }
-#endif
-
- if (spec->beside) {
- if (root_x + spec->size.x > mon_x + mon_w) {
- int flipped_x = anchor_x - spec->size.x + spec->content_offset.x;
- if (flipped_x >= mon_x) root_x = flipped_x;
- }
- } else {
- if (root_y + spec->size.y > mon_y + mon_h) {
- int flipped_y = anchor_y - spec->size.y + spec->content_offset.y;
- if (flipped_y >= mon_y) root_y = flipped_y;
- }
- }
-
- if (root_x + spec->size.x > mon_x + mon_w) root_x = mon_x + mon_w - spec->size.x;
- if (root_x < mon_x) root_x = mon_x;
- if (root_y + spec->size.y > mon_y + mon_h) root_y = mon_y + mon_h - spec->size.y;
- if (root_y < mon_y) root_y = mon_y;
-
- *out_x = root_x;
- *out_y = root_y;
-}
-
-static struct yasl_surface *surface_create_popup(struct yasl_surface *parent_base,
- const struct yasl_popup_spec *spec)
-{
- struct x11_surface *parent = (struct x11_surface *)parent_base;
- if (!parent || parent->destroyed || !popup_spec_valid(parent, spec))
- return NULL;
- struct x11_display *d = display_of(parent_base);
- struct x11_surface *s = calloc(1, sizeof(*s));
- if (!s) return NULL;
- s->base.display = parent_base->display;
- s->parent = parent;
- s->is_popup = true;
- s->content_offset = spec->content_offset;
- s->clock.refresh_ns = parent->clock.refresh_ns;
- if (!resize(s, spec->size)) { free(s); return NULL; }
-
- int root_x = 0, root_y = 0;
- popup_calculate_position(d, parent, spec, &root_x, &root_y);
-
- if (d->native) {
- XSetWindowAttributes attrs = {
- .override_redirect = True,
- .save_under = True,
- };
- s->window = XCreateWindow(d->native, RootWindow(d->native, d->screen),
- root_x, root_y, (unsigned)spec->size.x, (unsigned)spec->size.y,
- 0, CopyFromParent, InputOutput, CopyFromParent,
- CWOverrideRedirect | CWSaveUnder, &attrs);
- if (!s->window) {
- surface_destroy(&s->base);
- return NULL;
- }
- s->base.native_handle = (void *)(uintptr_t)s->window;
- s->base.size = s->size;
- s->base.scale_120 = YASL_SCALE_UNIT;
- s->gc = XCreateGC(d->native, s->window, 0, NULL);
- XSelectInput(d->native, s->window, ExposureMask | StructureNotifyMask |
- FocusChangeMask | KeyPressMask | KeyReleaseMask | PropertyChangeMask |
- (d->xi2 ? 0 : ButtonPressMask | ButtonReleaseMask |
- PointerMotionMask | LeaveWindowMask | EnterWindowMask));
- if (d->xi2) {
- unsigned char bits[XIMaskLen(XI_LASTEVENT)] = {0};
- XISetMask(bits, XI_Motion);
- XISetMask(bits, XI_ButtonPress);
- XISetMask(bits, XI_ButtonRelease);
- XISetMask(bits, XI_Enter);
- XISetMask(bits, XI_Leave);
- XIEventMask mask = {XIAllMasterDevices, sizeof(bits), bits};
- XISelectEvents(d->native, s->window, &mask, 1);
- }
- Atom xdnd_version = 5;
- XChangeProperty(d->native, s->window, d->xdnd_aware, XA_ATOM, 32,
- PropModeReplace, (unsigned char *)&xdnd_version, 1);
- if (d->im) {
- s->ic = XCreateIC(d->im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
- XNClientWindow, s->window, XNFocusWindow, s->window, NULL);
- }
- XMapRaised(d->native, s->window);
- XFlush(d->native);
- }
- s->next = d->surfaces;
- d->surfaces = s;
- return &s->base;
-}
-
-static bool surface_popup_reposition(struct yasl_surface *popup_base,
- const struct yasl_popup_spec *spec)
-{
- struct x11_surface *s = (struct x11_surface *)popup_base;
- if (!s || s->destroyed || !s->is_popup || !s->parent || s->parent->destroyed ||
- !popup_spec_valid(s->parent, spec))
- return false;
- struct x11_display *d = display_of(popup_base);
- if (spec->size.x != s->size.x || spec->size.y != s->size.y) {
- if (!resize(s, spec->size))
- return false;
- }
- s->content_offset = spec->content_offset;
- int root_x = 0, root_y = 0;
- popup_calculate_position(d, s->parent, spec, &root_x, &root_y);
- if (d->native && s->window) {
- XMoveResizeWindow(d->native, s->window, root_x, root_y,
- (unsigned)spec->size.x, (unsigned)spec->size.y);
- XFlush(d->native);
- }
- schedule_redraw(&s->base);
- return true;
-}
-
-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];
- size_t latin1_len = 0;
- char *latin1 = text ? yasl_utf8_to_latin1(text, &latin1_len) : NULL;
- 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, latin1 ? 3 : 2);
- reply.xselection.property = property;
- } else if (text && (request->target == d->utf8 || request->target == XA_STRING)) {
- const char *payload = request->target == XA_STRING ? latin1 : text;
- size_t len = request->target == XA_STRING ? latin1_len : strlen(text);
- long max = XMaxRequestSize(d->native) * 4 - 128;
- if (payload && len <= (size_t)max) {
- XChangeProperty(d->native, request->requestor, property, request->target,
- 8, PropModeReplace, (const unsigned char *)payload, (int)len);
- reply.xselection.property = property;
- }
- }
- free(latin1);
- XSendEvent(d->native, request->requestor, False, 0, &reply);
-}
-
-static char *parse_file_uris(const char *data, size_t len, size_t *out_len)
-{
- if (!data || !len) {
- if (out_len) *out_len = 0;
- return NULL;
- }
- char *out = malloc(len * 2 + 2);
- if (!out) {
- if (out_len) *out_len = 0;
- return NULL;
- }
- size_t out_pos = 0;
- size_t i = 0;
- while (i < len) {
- while (i < len && (data[i] == '\r' || data[i] == '\n')) i++;
- if (i >= len) break;
- size_t start = i;
- while (i < len && data[i] != '\r' && data[i] != '\n') i++;
- size_t end = i;
- while (end > start && (data[end - 1] == ' ' || data[end - 1] == '\t' || data[end - 1] == '\r'))
- end--;
- size_t line_len = end - start;
- if (line_len >= 7 && !strncmp(data + start, "file://", 7)) {
- memcpy(out + out_pos, data + start, line_len);
- out_pos += line_len;
- out[out_pos++] = '\r';
- out[out_pos++] = '\n';
- }
- }
- if (!out_pos) {
- free(out);
- if (out_len) *out_len = 0;
- return NULL;
- }
- out[out_pos] = '\0';
- if (out_len) *out_len = out_pos;
- return out;
-}
-
-static void xdnd_selection_notify(struct x11_display *d, XSelectionEvent *event)
-{
- if (!d->pending_drop.active) return;
- Window source = d->pending_drop.source;
- struct x11_surface *s = d->pending_drop.surface;
- d->pending_drop.active = false;
- d->pending_drop.source = None;
- d->pending_drop.surface = NULL;
-
- bool success = false;
- if (s && !s->destroyed && event->property != None) {
- Atom actual;
- int format;
- unsigned long count, left;
- unsigned char *data = NULL;
- if (XGetWindowProperty(d->native, s->window, event->property,
- 0, TEXT_LIMIT / 4, True, AnyPropertyType,
- &actual, &format, &count, &left, &data) == Success && data && count > 0) {
- size_t out_len = 0;
- char *parsed = parse_file_uris((const char *)data, count, &out_len);
- if (parsed && out_len > 0) {
- push(s, (struct yasl_input_event){.kind = YASL_INPUT_DROP_FILES}, parsed, out_len);
- free(parsed);
- success = true;
- }
- }
- if (data) XFree(data);
- }
- if (source) {
- XEvent finished = {0};
- finished.xclient = (XClientMessageEvent){
- .type = ClientMessage,
- .display = d->native,
- .window = source,
- .message_type = d->xdnd_finished,
- .format = 32,
- .data.l = {
- s ? s->window : None,
- success ? 1 : 0,
- success ? d->xdnd_action_copy : None,
- },
- };
- XSendEvent(d->native, source, False, NoEventMask, &finished);
- XFlush(d->native);
- }
-}
-
-static void selection_notify(struct x11_display *d, XSelectionEvent *event)
-{
- struct paste_request **p = &d->pastes;
- while (*p && (*p)->window != event->requestor) p = &(*p)->next;
- if (!*p) return;
- struct paste_request *r = *p;
- *p = r->next;
- struct x11_surface *s = r->target;
- if (!s || event->property == None) {
- XDestroyWindow(d->native, r->window);
- free(r);
- return;
- }
- Atom actual = None;
- int format = 0;
- unsigned long count = 0, left = 0;
- unsigned char *data = NULL;
- char *buf = NULL;
- size_t total = 0;
- unsigned long offset = 0;
- bool complete = false;
-
- /* Read up to TEXT_LIMIT bytes in 64KB chunks to avoid huge single-request limits */
- while (total < TEXT_LIMIT) {
- unsigned long chunk = 16384; /* 64 KB (in 32-bit multiples) */
- if (XGetWindowProperty(d->native, r->window, event->property,
- (long)offset, (long)chunk, True, AnyPropertyType,
- &actual, &format, &count, &left, &data) != Success || !actual) {
- if (data) { XFree(data); data = NULL; }
- break;
- }
- if (actual == d->incr) {
- /* Unsupported INCR streaming transfer */
- if (data) { XFree(data); data = NULL; }
- break;
- }
- if ((actual != d->utf8 && actual != XA_STRING) || format != 8) {
- if (data) { XFree(data); data = NULL; }
- break;
- }
- char *nb = realloc(buf, total + count + 1);
- if (!nb) {
- XFree(data);
- break;
- }
- buf = nb;
- if (count) memcpy(buf + total, data, count);
- total += count;
- buf[total] = '\0';
- offset += count / 4;
- XFree(data);
- data = NULL;
- if (left == 0) { complete = true; break; }
- }
- if (complete && actual == XA_STRING) {
- size_t utf8_len;
- char *utf8 = yasl_utf8_from_latin1(buf, total, &utf8_len);
- free(buf);
- buf = utf8;
- if (buf) total = utf8_len;
- }
- if (complete && buf && yasl_utf8_validate(buf, total))
- push(s, (struct yasl_input_event){.kind = YASL_INPUT_PASTE}, buf, total);
- free(buf);
- XDestroyWindow(d->native, r->window);
- free(r);
-}
-
-static void refresh_keyboard(struct x11_display *d)
-{
- memset(d->keys, 0, sizeof(d->keys));
- if (!d->has_xkb) return;
- XkbDescPtr keyboard = XkbGetKeyboard(d->native, XkbAllComponentsMask, XkbUseCoreKbd);
- if (!keyboard) return;
- if (keyboard->names && keyboard->names->keys) {
- for (unsigned code = keyboard->min_key_code; code <= keyboard->max_key_code; code++)
- d->keys[code] = yasl_key_from_xkb_name(keyboard->names->keys[code].name);
- }
- XkbFreeKeyboard(keyboard, XkbAllComponentsMask, True);
-}
-
-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);
- unsigned clean_state = key->state & ~(Mod2Mask | LockMask);
- KeySym sym = NoSymbol, base = NoSymbol, shifted = NoSymbol;
-
- int group = XkbGroupForCoreState(key->state);
- if (d->has_xkb) {
- sym = XkbKeycodeToKeysym(d->native, key->keycode, group, (clean_state & ShiftMask) ? 1 : 0);
- base = XkbKeycodeToKeysym(d->native, key->keycode, group, 0);
- shifted = XkbKeycodeToKeysym(d->native, key->keycode, group, 1);
- }
- if (sym == NoSymbol) {
- XKeyEvent clean_key = *key;
- clean_key.state = clean_state;
- sym = XLookupKeysym(&clean_key, (clean_state & ShiftMask) ? 1 : 0);
- base = XLookupKeysym(&clean_key, 0);
- shifted = XLookupKeysym(&clean_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 {
- /* XLookupString's bytes are Latin-1. Use its modifier-aware keysym
- * and encode UTF-8 even when no input method is available. */
- XLookupString(key, local, sizeof(local), &sym, NULL);
- len = xkb_keysym_to_utf8((xkb_keysym_t)sym, local, sizeof(local));
- len = len > 0 ? len - 1 : 0; /* xkb includes the terminating NUL. */
- }
- }
- uint32_t cp = xkb_keysym_to_utf32((xkb_keysym_t)base);
- push(s, (struct yasl_input_event){
- .kind = YASL_INPUT_KEY,
- .key = {.key = key->keycode < 256 ? d->keys[key->keycode] : YASL_KEY_UNKNOWN,
- .codepoint = yasl_key_label(cp), .native_code = key->keycode,
- .shifted = yasl_key_label(xkb_keysym_to_utf32((xkb_keysym_t)shifted)),
- .mods = d->mods,
- .action = key->type == KeyRelease ? YASL_KEY_RELEASE :
- repeat ? YASL_KEY_REPEAT : YASL_KEY_PRESS},
- }, NULL, 0);
- if (len > 0 && text && (unsigned char)text[0] >= 0x20 && text[0] != 0x7f &&
- !(d->mods & (YASL_MOD_CTRL | YASL_MOD_SUPER | YASL_MOD_ALT)))
- push(s, (struct yasl_input_event){.kind = YASL_INPUT_TEXT}, text, (size_t)len);
- if (text != local) free(text);
-}
-
-static void handle_client_message(struct x11_display *d, struct x11_surface *s,
- const XClientMessageEvent *ev)
-{
- if (ev->message_type == d->protocols &&
- (Atom)ev->data.l[0] == d->delete_window && s->cbs && s->cbs->close) {
- s->cbs->close(&s->base, s->ud);
- } else if (ev->message_type == d->xdnd_enter) {
- Window source = (Window)ev->data.l[0];
- int version = (int)((unsigned long)ev->data.l[1] >> 24);
- bool more_types = (ev->data.l[1] & 1) != 0;
- bool has_uri = false;
- if (more_types && d->native) {
- Atom actual;
- int format;
- unsigned long count, left;
- unsigned char *prop_data = NULL;
- if (XGetWindowProperty(d->native, source, d->xdnd_type_list,
- 0, 64, False, XA_ATOM, &actual, &format, &count, &left, &prop_data) == Success &&
- actual == XA_ATOM && format == 32 && prop_data) {
- Atom *types = (Atom *)prop_data;
- for (unsigned long i = 0; i < count; i++) {
- if (types[i] == d->uri_list) { has_uri = true; break; }
- }
- }
- if (prop_data) XFree(prop_data);
- } else {
- for (int i = 2; i <= 4; i++) {
- if ((Atom)ev->data.l[i] == d->uri_list) { has_uri = true; break; }
- }
- }
- d->xdnd.source = source;
- d->xdnd.version = version;
- d->xdnd.has_uri_list = has_uri;
- d->xdnd.accepts = has_uri;
- } else if (ev->message_type == d->xdnd_position) {
- Window source = (Window)ev->data.l[0];
- int rx = (int)(int16_t)((ev->data.l[2] >> 16) & 0xFFFF);
- int ry = (int)(int16_t)(ev->data.l[2] & 0xFFFF);
- Time t = (Time)ev->data.l[3];
- int wx = rx, wy = ry;
- if (d->native) {
- Window child;
- XTranslateCoordinates(d->native, RootWindow(d->native, d->screen), s->window,
- rx, ry, &wx, &wy, &child);
- }
- struct yasl_vector2i pos = {wx, wy};
- if (d->xdnd.surface && d->xdnd.surface != s) {
- if (d->xdnd.accepts && d->xdnd.surface->cbs && d->xdnd.surface->cbs->drop)
- d->xdnd.surface->cbs->drop(&d->xdnd.surface->base, YASL_DROP_LEAVE, d->xdnd.pos, d->xdnd.surface->ud);
- }
- d->xdnd.surface = s;
- d->xdnd.pos = pos;
- d->xdnd.time = t;
- if (d->xdnd.accepts && s->cbs && s->cbs->drop)
- s->cbs->drop(&s->base, YASL_DROP_OVER, pos, s->ud);
-
- if (d->native) {
- XEvent reply = {0};
- reply.xclient = (XClientMessageEvent){
- .type = ClientMessage,
- .display = d->native,
- .window = source,
- .message_type = d->xdnd_status,
- .format = 32,
- .data.l = {
- s->window,
- d->xdnd.accepts ? 1 : 0,
- 0,
- 0,
- d->xdnd.accepts ? d->xdnd_action_copy : None,
- },
- };
- XSendEvent(d->native, source, False, NoEventMask, &reply);
- }
- } else if (ev->message_type == d->xdnd_leave) {
- if (d->xdnd.surface == s) {
- if (d->xdnd.accepts && s->cbs && s->cbs->drop)
- s->cbs->drop(&s->base, YASL_DROP_LEAVE, d->xdnd.pos, s->ud);
- }
- d->xdnd = (typeof(d->xdnd)){0};
- } else if (ev->message_type == d->xdnd_drop) {
- Window source = (Window)ev->data.l[0];
- Time drop_time = (Time)ev->data.l[2];
- bool was_accepting = d->xdnd.accepts && d->xdnd.has_uri_list && (d->xdnd.source == source);
- if (d->xdnd.surface == s) {
- if (was_accepting && s->cbs && s->cbs->drop)
- s->cbs->drop(&s->base, YASL_DROP_LEAVE, d->xdnd.pos, s->ud);
- }
- if (was_accepting) {
- d->pending_drop.active = true;
- d->pending_drop.source = source;
- d->pending_drop.surface = s;
- d->pending_drop.time = drop_time;
- if (d->native) {
- XConvertSelection(d->native, d->xdnd_selection, d->uri_list,
- d->paste_property, s->window, drop_time);
- }
- } else if (d->native) {
- XEvent reply = {0};
- reply.xclient = (XClientMessageEvent){
- .type = ClientMessage,
- .display = d->native,
- .window = source,
- .message_type = d->xdnd_finished,
- .format = 32,
- .data.l = { s->window, 0, None },
- };
- XSendEvent(d->native, source, False, NoEventMask, &reply);
- }
- d->xdnd = (typeof(d->xdnd)){0};
- }
-}
-
-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 (event.type == GenericEvent && d->xi2 &&
- event.xcookie.extension == d->xi_opcode && XGetEventData(d->native, &event.xcookie)) {
- xi_event(d, &event.xcookie);
- XFreeEventData(d->native, &event.xcookie);
- continue;
- }
- if (event.type == MappingNotify) {
- XRefreshKeyboardMapping(&event.xmapping);
- refresh_keyboard(d);
- continue;
- }
- if (d->has_xkb && event.type == d->xkb_event) {
- XkbEvent *xkb = (XkbEvent *)&event;
- if (xkb->any.xkb_type == XkbMapNotify) XkbRefreshKeyboardMapping(&xkb->map);
- refresh_keyboard(d);
- continue;
- }
- if (XFilterEvent(&event, None)) continue;
- if (event.type == SelectionRequest) {
- selection_request(d, &event.xselectionrequest);
- continue;
- }
- if (event.type == SelectionNotify) {
- if (event.xselection.selection == d->xdnd_selection) {
- xdnd_selection_notify(d, &event.xselection);
- continue;
- }
- 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:
- handle_client_message(d, s, &event.xclient);
- 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 EnterNotify: 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;
- if (d->drag.active) {
- if (d->drag.carried)
- place(&d->drag.carried->base, d->drag.carry_offset);
- else if (d->drag.icon_window && d->native)
- XMoveWindow(d->native, d->drag.icon_window,
- event.xmotion.x_root - d->drag.hotspot.x,
- event.xmotion.y_root - d->drag.hotspot.y);
- struct yasl_vector2i hpos = mouse.pos;
- struct x11_surface *hsurf = d->native ?
- surface_at_root_pos(d, event.xmotion.x_root, event.xmotion.y_root, &hpos) : s;
- drag_set_hover(d, hsurf, hpos);
- }
- } else if (event.type == EnterNotify) {
- mouse.pos = (struct yasl_vector2i){event.xcrossing.x, event.xcrossing.y};
- mouse.kind = YASL_PTR_MOTION;
- state = event.xcrossing.state;
- if (d->drag.active) {
- struct yasl_vector2i hpos = mouse.pos;
- struct x11_surface *hsurf = d->native ?
- surface_at_root_pos(d, event.xcrossing.x_root, event.xcrossing.y_root, &hpos) : s;
- drag_set_hover(d, hsurf, hpos);
- }
- } 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;
- if (d->drag.active)
- drag_leave_hover(d, s, mouse.pos);
- } 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;
- if (button <= 5) mouse.scroll.y = button == 4 ? -1 : 1;
- else mouse.scroll.x = button == 6 ? -1 : 1;
- } else {
- mouse.kind = event.type == ButtonPress ? YASL_PTR_PRESS : YASL_PTR_RELEASE;
- mouse.button = pointer_button(button);
- if (event.type == ButtonRelease && d->drag.active)
- drag_drop_hover(d);
- }
- }
- d->mods = mouse.mods = modifiers(state);
- push(s, (struct yasl_input_event){.kind = YASL_INPUT_MOUSE, .mouse = mouse}, NULL, 0);
- break;
- }
- }
- }
- leave(d);
-}
-
-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;
- if (s->shm_active) {
- if (damage->n == -1)
- XShmPutImage(d->native, s->window, s->gc, im, 0, 0, 0, 0,
- (unsigned)s->size.x, (unsigned)s->size.y, False);
- 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)
- XShmPutImage(d->native, s->window, s->gc, im, x, y, x, y, (unsigned)w, (unsigned)h, False);
- }
- s->presented = true;
- yasl_clock_presented(&s->clock, yasl_loop_now_ns(), false);
- return true;
- }
- bool direct = im->bits_per_pixel == 32 && im->red_mask == 0xff0000 &&
- im->green_mask == 0xff00 && im->blue_mask == 0xff;
- int y_start = 0, y_end = s->size.y;
- int x_start = 0, x_end = s->size.x;
- if (damage->n >= 0) {
- y_start = s->size.y; y_end = 0;
- x_start = s->size.x; x_end = 0;
- for (int i = 0; i < damage->n; i++) {
- struct yasl_rect2i r = damage->rects[i];
- int y0 = r.y > 0 ? r.y : 0;
- int y1 = (r.y + r.height < s->size.y) ? r.y + r.height : s->size.y;
- int x0 = r.x > 0 ? r.x : 0;
- int x1 = (r.x + r.width < s->size.x) ? r.x + r.width : s->size.x;
- if (y0 < y_start) y_start = y0;
- if (y1 > y_end) y_end = y1;
- if (x0 < x_start) x_start = x0;
- if (x1 > x_end) x_end = x1;
- }
- if (y_start >= y_end || x_start >= x_end) {
- y_start = y_end = x_start = x_end = 0;
- }
- }
- size_t copy_bytes = (size_t)(x_end - x_start) * 4;
- for (int y = y_start; y < y_end; 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 + (size_t)x_start * 4,
- row + x_start, copy_bytes);
- else for (int x = x_start; x < x_end; 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 = {.generation = yasl_frame_begin(&s->base),
- .pixels = s->pixels, .size = s->size},
- };
- 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 | POLLERR | POLLHUP)) {
- dispatch(base);
- flush(base);
- }
- return 0;
-}
-
-static int dispatch_timeout(struct yasl_display *base, int timeout_ms)
-{
- struct x11_display *d = (struct x11_display *)base;
- if (unlikely(!d || !d->native)) return -1;
- dispatch(base);
- for (struct x11_surface *s = d->surfaces; s; s = s->next) {
- if (s->want_draw || s->input.count || s->input.reset_pending) {
- timeout_ms = 0;
- break;
- }
- }
- if (d->loop) {
- XFlush(d->native);
- if (XEventsQueued(d->native, QueuedAlready) > 0)
- timeout_ms = 0;
- if (!XPending(d->native) && timeout_ms != 0) {
- yasl_loop_step(d->loop, timeout_ms);
- dispatch(base);
- } else {
- yasl_loop_step(d->loop, 0);
- dispatch(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->base.callbacks = spec && spec->cbs ? *spec->cbs : (struct yasl_display_cbs){0};
- d->native = XOpenDisplay(NULL);
- if (!d->native) { free(d); errno = ENODEV; return NULL; }
- XSetErrorHandler(yasl_x11_error_handler);
- d->base.native_handle = d->native;
- d->screen = DefaultScreen(d->native);
- d->has_shm = XShmQueryExtension(d->native);
- int xkb_opcode = 0, xkb_event = 0, xkb_error = 0;
- int xkb_major = XkbMajorVersion, xkb_minor = XkbMinorVersion;
- d->has_xkb = XkbQueryExtension(d->native, &xkb_opcode, &xkb_event, &xkb_error, &xkb_major, &xkb_minor);
- d->xkb_event = xkb_event;
- if (d->has_xkb) {
- unsigned events = XkbNewKeyboardNotifyMask | XkbMapNotifyMask | XkbNamesNotifyMask;
- XkbSelectEvents(d->native, XkbUseCoreKbd, events, events);
- refresh_keyboard(d);
- }
- int xi_event_base, xi_error_base;
- int major = 2, minor = 0;
- d->xi2 = XQueryExtension(d->native, "XInputExtension", &d->xi_opcode,
- &xi_event_base, &xi_error_base) &&
- XIQueryVersion(d->native, &major, &minor) == Success && major >= 2;
- if (d->xi2) {
- d->abs_pressure = XInternAtom(d->native, "Abs Pressure", False);
- d->abs_tilt_x = XInternAtom(d->native, "Abs Tilt X", False);
- d->abs_tilt_y = XInternAtom(d->native, "Abs Tilt Y", False);
- d->tool_type = XInternAtom(d->native, "Wacom Tool Type", False);
- d->eraser = XInternAtom(d->native, "ERASER", False);
- unsigned char bits[XIMaskLen(XI_LASTEVENT)] = {0};
- XISetMask(bits, XI_HierarchyChanged);
- XISetMask(bits, XI_DeviceChanged);
- XIEventMask mask = {XIAllDevices, sizeof(bits), bits};
- XISelectEvents(d->native, RootWindow(d->native, d->screen), &mask, 1);
- }
- if (!loop) {
- d->loop = yasl_loop_create();
- d->owns_loop = true;
- } else {
- d->loop = loop;
- }
- d->base.loop = d->loop;
- d->app_id = strdup(spec && spec->app_id ? spec->app_id : "YASL");
- if (!d->loop || !d->app_id) {
- if (d->owns_loop) yasl_loop_destroy(d->loop);
- free(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(incr, "INCR");
- 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");
- ATOM(xdnd_aware, "XdndAware");
- ATOM(xdnd_enter, "XdndEnter");
- ATOM(xdnd_position, "XdndPosition");
- ATOM(xdnd_status, "XdndStatus");
- ATOM(xdnd_leave, "XdndLeave");
- ATOM(xdnd_drop, "XdndDrop");
- ATOM(xdnd_finished, "XdndFinished");
- ATOM(xdnd_selection, "XdndSelection");
- ATOM(xdnd_action_copy, "XdndActionCopy");
- ATOM(xdnd_type_list, "XdndTypeList");
- ATOM(uri_list, "text/uri-list");
-#undef ATOM
- if (d->loop) {
- d->watch = yasl_loop_add_fd(d->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);
- if (d->owns_loop) yasl_loop_destroy(d->loop);
- 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");
- drag_cancel(base);
- cancel_pastes(d, NULL);
- d->pending_drop.active = false;
- if (d->loop && d->watch > 0) yasl_loop_del_fd(d->loop, d->watch);
- if (d->owns_loop && d->loop) {
- yasl_loop_destroy(d->loop);
- d->loop = NULL;
- d->base.loop = NULL;
- }
- if (d->im) XCloseIM(d->im);
- clear_pointers(d);
- for (size_t i = 0; i < sizeof(d->cursors) / sizeof(d->cursors[0]); i++) {
- if (d->cursors[i]) {
- XFreeCursor(d->native, d->cursors[i]);
- d->cursors[i] = None;
- }
- }
- 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)
-{
- return YASL_CAP_POPUPS | YASL_CAP_DRAG | YASL_CAP_DRAG_CARRY | YASL_CAP_PLACE_WINDOW |
- YASL_CAP_PRIMARY_SELECTION | YASL_CAP_CSD |
- (((const struct x11_display *)d)->xi2 ? YASL_CAP_TABLET : 0);
-}
-
-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 {
- struct paste_request *r = calloc(1, sizeof(*r));
- if (!r) return;
- r->window = XCreateSimpleWindow(d->native, RootWindow(d->native, d->screen),
- 0, 0, 1, 1, 0, 0, 0);
- r->target = s;
- r->next = d->pastes;
- d->pastes = r;
- XConvertSelection(d->native, selection, d->utf8, d->paste_property,
- r->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;
- return !s->destroyed && input_queue_pop(&s->input, event);
-}
-
-static void schedule_redraw(struct yasl_surface *base)
-{
- struct x11_surface *s = (struct x11_surface *)base;
- if (!s->destroyed) {
- s->want_draw = true;
- struct x11_display *d = display_of(base);
- if (d && d->loop)
- yasl_loop_kick(d->loop);
- }
-}
-
-static struct yasl_display *surface_display(const struct yasl_surface *s) { return s->display; }
-static struct yasl_geometry geometry(const struct yasl_surface *s)
-{
- return (struct yasl_geometry){((const struct x11_surface *)s)->size, 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),
- .source = s->clock.source};
- 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_HIDDEN) return;
- struct x11_display *d = display_of(base);
- if (!d->native) return;
-
- if (!d->cursors[cursor]) {
- if (cursor == YASL_CURSOR_HIDDEN) {
- char bits[] = {0};
- Pixmap pixmap = XCreateBitmapFromData(d->native, s->window, bits, 1, 1);
- XColor color = {0};
- d->cursors[cursor] = XCreatePixmapCursor(d->native, pixmap, pixmap, &color, &color, 0, 0);
- XFreePixmap(d->native, pixmap);
- } else if ((unsigned)cursor < sizeof(shapes) / sizeof(shapes[0])) {
- d->cursors[cursor] = XCreateFontCursor(d->native, shapes[cursor]);
- }
- }
- if (d->cursors[cursor])
- XDefineCursor(d->native, s->window, d->cursors[cursor]);
-}
-
-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 || !d->native || !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 void drag_end(struct x11_display *d, enum yasl_drag_end how)
-{
- if (!d || !d->drag.active) return;
- struct yasl_drag_listener l = d->drag.listener;
- void *ud = d->drag.ud;
- struct x11_surface *hover = d->drag.hover;
- struct yasl_vector2i hover_pos = d->drag.hover_pos;
-
- d->drag.active = false;
- d->drag.listener = (struct yasl_drag_listener){0};
- d->drag.hover = NULL;
- d->drag.origin = NULL;
- d->drag.carried = NULL;
-
- if (d->drag.icon_window && d->native) {
- XDestroyWindow(d->native, d->drag.icon_window);
- d->drag.icon_window = None;
- }
- if (d->drag.icon_pixmap && d->native) {
- XFreePixmap(d->native, d->drag.icon_pixmap);
- d->drag.icon_pixmap = None;
- }
-
- if (hover && !hover->destroyed && hover->cbs && hover->cbs->drag) {
- if (how == YASL_DRAG_DROPPED)
- hover->cbs->drag(&hover->base, YASL_DRAG_DROP, hover_pos, hover->ud);
- else
- hover->cbs->drag(&hover->base, YASL_DRAG_LEAVE, hover_pos, hover->ud);
- }
-
- if (l.end)
- l.end(ud, how);
-}
-
-static bool drag_start(struct yasl_display *base, struct yasl_surface *origin,
- const struct yasl_drag_image *image,
- const struct yasl_drag_listener *l, void *ud)
-{
- struct x11_display *d = (struct x11_display *)base;
- if (!d || !origin || d->drag.active || !l)
- return false;
- d->drag.active = true;
- d->drag.origin = (struct x11_surface *)origin;
- d->drag.carried = NULL;
- d->drag.hover = NULL;
- d->drag.listener = *l;
- d->drag.ud = ud;
- d->drag.icon_window = None;
- d->drag.icon_pixmap = None;
-
- if (image && image->pixels && image->size.x > 0 && image->size.y > 0) {
- d->drag.hotspot = image->hotspot;
- int rx = 0, ry = 0;
- if (d->native) {
- Window root, child;
- int wx, wy;
- unsigned mask;
- XQueryPointer(d->native, RootWindow(d->native, d->screen),
- &root, &child, &rx, &ry, &wx, &wy, &mask);
- int x = rx - image->hotspot.x;
- int y = ry - image->hotspot.y;
- XSetWindowAttributes attrs = {
- .override_redirect = True,
- .save_under = True,
- };
- d->drag.icon_window = XCreateWindow(d->native, RootWindow(d->native, d->screen),
- x, y, (unsigned)image->size.x, (unsigned)image->size.y, 0,
- CopyFromParent, InputOutput, CopyFromParent,
- CWOverrideRedirect | CWSaveUnder, &attrs);
- if (d->drag.icon_window) {
- GC gc = XCreateGC(d->native, d->drag.icon_window, 0, NULL);
- XImage *im = XCreateImage(d->native, DefaultVisual(d->native, d->screen),
- (unsigned)DefaultDepth(d->native, d->screen), ZPixmap, 0, NULL,
- (unsigned)image->size.x, (unsigned)image->size.y, 32, 0);
- if (gc && im) {
- im->data = malloc((size_t)im->bytes_per_line * (size_t)image->size.y);
- if (im->data) {
- bool direct = im->bits_per_pixel == 32 && im->red_mask == 0xff0000 &&
- im->green_mask == 0xff00 && im->blue_mask == 0xff;
- for (int r = 0; r < image->size.y; r++) {
- const uint32_t *row = image->pixels + (size_t)r * image->size.x;
- if (direct) memcpy(im->data + (size_t)r * im->bytes_per_line, row, (size_t)image->size.x * 4);
- else for (int c = 0; c < image->size.x; c++) {
- uint32_t p = row[c];
- XPutPixel(im, c, r, component((p >> 16) & 255, im->red_mask) |
- component((p >> 8) & 255, im->green_mask) | component(p & 255, im->blue_mask));
- }
- }
- d->drag.icon_pixmap = XCreatePixmap(d->native, RootWindow(d->native, d->screen),
- (unsigned)image->size.x, (unsigned)image->size.y,
- (unsigned)DefaultDepth(d->native, d->screen));
- if (d->drag.icon_pixmap) {
- XPutImage(d->native, d->drag.icon_pixmap, gc, im, 0, 0, 0, 0,
- (unsigned)image->size.x, (unsigned)image->size.y);
- XSetWindowBackgroundPixmap(d->native, d->drag.icon_window, d->drag.icon_pixmap);
- }
- }
- }
- if (im) XDestroyImage(im);
- if (gc) XFreeGC(d->native, gc);
- XMapRaised(d->native, d->drag.icon_window);
- XFlush(d->native);
- }
- }
- }
- return true;
-}
-
-static void drag_cancel(struct yasl_display *base)
-{
- drag_end((struct x11_display *)base, YASL_DRAG_CANCELLED);
-}
-
-static bool drag_carry(struct yasl_surface *base, struct yasl_vector2i offset)
-{
- struct x11_display *d = display_of(base);
- if (!d || !d->drag.active)
- return false;
- if (d->drag.icon_window && d->native) {
- XDestroyWindow(d->native, d->drag.icon_window);
- d->drag.icon_window = None;
- }
- if (d->drag.icon_pixmap && d->native) {
- XFreePixmap(d->native, d->drag.icon_pixmap);
- d->drag.icon_pixmap = None;
- }
- d->drag.carried = (struct x11_surface *)base;
- d->drag.carry_offset = offset;
- return d->native ? place(base, offset) : 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 || !d->native || !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;
-}
-
-static bool interactive_resize(struct yasl_surface *base, enum yasl_resize_edge edge)
-{
- 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 || !d->native || !XQueryPointer(d->native, s->window,
- &root, &child, &rx, &ry, &x, &y, &mask)) return false;
-
- int dir;
- switch (edge) {
- case YASL_EDGE_TOP_LEFT: dir = 0; break;
- case YASL_EDGE_TOP: dir = 1; break;
- case YASL_EDGE_TOP_RIGHT: dir = 2; break;
- case YASL_EDGE_RIGHT: dir = 3; break;
- case YASL_EDGE_BOTTOM_RIGHT: dir = 4; break;
- case YASL_EDGE_BOTTOM: dir = 5; break;
- case YASL_EDGE_BOTTOM_LEFT: dir = 6; break;
- case YASL_EDGE_LEFT: dir = 7; break;
- default: return false;
- }
-
- XUngrabPointer(d->native, CurrentTime);
- net_message(s, XInternAtom(d->native, "_NET_WM_MOVERESIZE", False), rx, ry, dir, 1, 1);
- return true;
-}
-
-static bool show_window_menu(struct yasl_surface *base, struct yasl_vector2i pos)
-{
- struct x11_surface *s = (struct x11_surface *)base;
- struct x11_display *d = display_of(base);
- if (s->destroyed || !d->native) return false;
- Atom atom = XInternAtom(d->native, "_GTK_SHOW_WINDOW_MENU", False);
- net_message(s, atom, s->window, pos.x, pos.y, 0, 0);
- return true;
-}
-
-static void set_ime_caret(struct yasl_surface *base, struct yasl_rect2i caret)
-{
- struct x11_surface *s = (struct x11_surface *)base;
- if (!s) return;
- s->ime_caret = caret;
- if (s->ic) {
- XPoint spot = { (short)caret.x, (short)(caret.y + caret.height) };
- XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL);
- if (preedit_attr) {
- XSetICValues(s->ic, XNPreeditAttributes, preedit_attr, NULL);
- XFree(preedit_attr);
- }
- }
-}
-
-static const char *ime_preedit(const struct yasl_surface *base)
-{
- const struct x11_surface *s = (const struct x11_surface *)base;
- return s ? s->ime_preedit : NULL;
-}
-
-static bool ime_preedit_cursor(const struct yasl_surface *base, int *begin, int *end)
-{
- const struct x11_surface *s = (const struct x11_surface *)base;
- if (!s || !s->ime_preedit || s->ime_cursor_begin < 0)
- return false;
- if (begin) *begin = s->ime_cursor_begin;
- if (end) *end = s->ime_cursor_end;
- 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_timeout = dispatch_timeout,
- .display_flush = flush,
- .display_modifiers = mods,
- .display_copy = copy,
- .display_paste = paste,
- .display_drag_start = drag_start,
- .display_drag_cancel = drag_cancel,
- .surface_create_toplevel = create_toplevel,
- .surface_create_popup = surface_create_popup,
- .surface_popup_reposition = surface_popup_reposition,
- .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_geometry = geometry,
- .surface_state = state,
- .surface_timing = timing,
- .surface_frame_time_ns = frame_time,
- .surface_set_cursor = cursor,
- .surface_drag_carry = drag_carry,
- .surface_place_under_pointer = place,
- .surface_start_interactive_move = interactive_move,
- .surface_start_interactive_resize = interactive_resize,
- .surface_show_window_menu = show_window_menu,
- .surface_set_ime_caret = set_ime_caret,
- .surface_ime_preedit = ime_preedit,
- .surface_ime_preedit_cursor = ime_preedit_cursor,
- .display_get_native_handle = native_display,
- .surface_get_native_handle = native_surface,
-};