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