diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-13 13:44:13 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-13 13:44:13 +0300 |
| commit | c059e3aa7f93da470f052a5cd8ee0d8dc969e679 (patch) | |
| tree | 63ffa378287ed2c606eda2e940501b9edc3d2b8b /src/x11/window.c | |
| parent | 87d0bc50d53dc216efb2b04495f090616318bc5c (diff) | |
rename and simplification
Diffstat (limited to 'src/x11/window.c')
| -rw-r--r-- | src/x11/window.c | 2063 |
1 files changed, 0 insertions, 2063 deletions
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, -}; |
