diff options
Diffstat (limited to 'src/x11')
| -rw-r--r-- | src/x11/window.c | 727 |
1 files changed, 697 insertions, 30 deletions
diff --git a/src/x11/window.c b/src/x11/window.c index 5e3dba9..5e1b612 100644 --- a/src/x11/window.c +++ b/src/x11/window.c @@ -48,6 +48,9 @@ struct x11_surface { 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; }; struct paste_request { @@ -80,6 +83,8 @@ struct x11_display { Window selection_window; Atom protocols, delete_window, utf8, clipboard, targets, paste_property; 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; @@ -96,7 +101,27 @@ struct x11_display { 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) @@ -107,6 +132,7 @@ static struct x11_display *display_of(const struct yasl_surface *s) 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) { @@ -126,6 +152,9 @@ static void push(struct x11_surface *s, struct yasl_input_event event, 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; @@ -135,6 +164,125 @@ static void push(struct x11_surface *s, struct yasl_input_event event, 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 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 child = None; + int dx, dy; + if (!XTranslateCoordinates(d->native, RootWindow(d->native, d->screen), + RootWindow(d->native, d->screen), + rx, ry, &dx, &dy, &child) || !child) + return NULL; + + Window ignore_icon = d->drag.icon_window; + Window ignore_carried = d->drag.carried ? d->drag.carried->window : None; + + if (child == ignore_icon || child == ignore_carried) { + Window root, parent, *children = NULL; + unsigned int nchildren = 0; + child = None; + if (XQueryTree(d->native, RootWindow(d->native, d->screen), + &root, &parent, &children, &nchildren) && children) { + for (int i = (int)nchildren - 1; i >= 0; i--) { + Window w = children[i]; + if (w == ignore_icon || w == ignore_carried) continue; + for (struct x11_surface *s = d->surfaces; s; s = s->next) { + 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 (s->window == w) { + child = w; + break; + } + Window cur = s->window; + while (cur && cur != RootWindow(d->native, d->screen)) { + if (cur == w) { child = w; break; } + Window cr, cp, *cc = NULL; + unsigned int ncc = 0; + if (!XQueryTree(d->native, cur, &cr, &cp, &cc, &ncc)) break; + if (cc) XFree(cc); + cur = cp; + } + if (child) break; + } + } + } + if (child) break; + } + XFree(children); + } + } + + for (struct x11_surface *s = d->surfaces; s; s = s->next) { + 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 (s->window == child) { + if (out_pos) *out_pos = (struct yasl_vector2i){tx, ty}; + return s; + } + Window cur = s->window; + while (cur && cur != RootWindow(d->native, d->screen)) { + if (cur == child) { + if (out_pos) *out_pos = (struct yasl_vector2i){tx, ty}; + return s; + } + Window cr, cp, *cc = NULL; + unsigned int ncc = 0; + if (!XQueryTree(d->native, cur, &cr, &cp, &cc, &ncc)) break; + if (cc) XFree(cc); + cur = cp; + } + } + } + } + return NULL; +} + static uint32_t modifiers(unsigned state) { return ((state & ShiftMask) ? YASL_MOD_SHIFT : 0) | @@ -178,6 +326,7 @@ 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; @@ -286,6 +435,8 @@ static void xi_event(struct x11_display *d, XGenericEventCookie *cookie) (((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); } @@ -296,13 +447,37 @@ static void xi_event(struct x11_display *d, XGenericEventCookie *cookie) cookie->evtype != XI_ButtonRelease) return; Window window = crossing ? ((XIEnterEvent *)cookie->data)->event : ((XIDeviceEvent *)cookie->data)->event; - if (cookie->evtype == XI_Motion && d->drag.active && d->drag.carried) - place(&d->drag.carried->base, d->drag.carry_offset); - else if (cookie->evtype == XI_ButtonRelease && d->drag.active) - drag_end(d, YASL_DRAG_DROPPED); struct x11_surface *s = d->surfaces; while (s && s->window != window) s = s->next; - if (!s) return; + 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; @@ -325,6 +500,11 @@ static bool resize(struct x11_surface *s, struct yasl_vector2i size) 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(); XImage *image = XCreateImage(d->native, DefaultVisual(d->native, d->screen), (unsigned)DefaultDepth(d->native, d->screen), ZPixmap, 0, NULL, @@ -373,8 +553,18 @@ 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 && (d->drag.origin == s || d->drag.carried == s)) - drag_end(d, YASL_DRAG_CANCELLED); + 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; } @@ -383,8 +573,10 @@ static void surface_destroy(struct yasl_surface *base) 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); + 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; @@ -442,7 +634,7 @@ static struct yasl_surface *create_toplevel(struct yasl_display *base, XSelectInput(d->native, s->window, ExposureMask | StructureNotifyMask | FocusChangeMask | KeyPressMask | KeyReleaseMask | PropertyChangeMask | (d->xi2 ? 0 : ButtonPressMask | ButtonReleaseMask | - PointerMotionMask | LeaveWindowMask)); + PointerMotionMask | LeaveWindowMask | EnterWindowMask)); if (d->xi2) { unsigned char bits[XIMaskLen(XI_LASTEVENT)] = {0}; XISetMask(bits, XI_Motion); @@ -456,6 +648,9 @@ static struct yasl_surface *create_toplevel(struct yasl_display *base, 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); @@ -473,6 +668,161 @@ static struct yasl_surface *create_toplevel(struct yasl_display *base, 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 screen_w = d->native ? DisplayWidth(d->native, d->screen) : 1920; + int screen_h = d->native ? DisplayHeight(d->native, d->screen) : 1080; + + if (spec->beside) { + if (root_x + spec->size.x > screen_w) { + int flipped_x = anchor_x - spec->size.x + spec->content_offset.x; + if (flipped_x >= 0) root_x = flipped_x; + } + } else { + if (root_y + spec->size.y > screen_h) { + int flipped_y = anchor_y - spec->size.y + spec->content_offset.y; + if (flipped_y >= 0) root_y = flipped_y; + } + } + + if (root_x + spec->size.x > screen_w) root_x = screen_w - spec->size.x; + if (root_x < 0) root_x = 0; + if (root_y + spec->size.y > screen_h) root_y = screen_h - spec->size.y; + if (root_y < 0) root_y = 0; + + *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->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); @@ -526,6 +876,92 @@ static void selection_request(struct x11_display *d, XSelectionRequestEvent *req 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; @@ -593,6 +1029,119 @@ static void key_event(struct x11_surface *s, XKeyEvent *key, bool repeat) 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; @@ -612,6 +1161,10 @@ static void dispatch(struct yasl_display *base) 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; } @@ -628,9 +1181,7 @@ static void dispatch(struct yasl_display *base) 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); + handle_client_message(d, s, &event.xclient); break; case FocusIn: case FocusOut: { bool focused = event.type == FocusIn; @@ -659,19 +1210,41 @@ static void dispatch(struct yasl_display *base) key_event(s, &event.xkey, repeat); break; } - case MotionNotify: case LeaveNotify: case ButtonPress: case ButtonRelease: { + 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 && d->drag.carried) - place(&d->drag.carried->base, d->drag.carry_offset); + 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; @@ -684,7 +1257,7 @@ static void dispatch(struct yasl_display *base) mouse.kind = event.type == ButtonPress ? YASL_PTR_PRESS : YASL_PTR_RELEASE; mouse.button = pointer_button(button); if (event.type == ButtonRelease && d->drag.active) - drag_end(d, YASL_DRAG_DROPPED); + drag_drop_hover(d); } } d->mods = mouse.mods = modifiers(state); @@ -696,14 +1269,6 @@ static void dispatch(struct yasl_display *base) leave(d); } -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 bool present(struct yasl_surface *base, const struct yasl_framebuffer *fb, const struct yasl_damage_list *damage) { @@ -851,6 +1416,17 @@ static struct yasl_display *display_open(struct yasl_loop *loop, 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); @@ -873,6 +1449,7 @@ static void display_close(struct yasl_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); @@ -891,7 +1468,7 @@ static void display_close(struct yasl_display *base) static uint32_t caps(const struct yasl_display *d) { - return YASL_CAP_DRAG | YASL_CAP_DRAG_CARRY | YASL_CAP_PLACE_WINDOW | + return YASL_CAP_POPUPS | YASL_CAP_DRAG | YASL_CAP_DRAG_CARRY | YASL_CAP_PLACE_WINDOW | YASL_CAP_PRIMARY_SELECTION | (((const struct x11_display *)d)->xi2 ? YASL_CAP_TABLET : 0); } @@ -1002,7 +1579,7 @@ static bool place(struct yasl_surface *base, struct yasl_vector2i at) Window root, child; int root_x, root_y, x, y; unsigned mask; - if (s->destroyed || !XQueryPointer(d->native, s->window, + 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; @@ -1013,9 +1590,31 @@ 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); } @@ -1024,15 +1623,73 @@ 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) { - (void)image; 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; } @@ -1046,9 +1703,17 @@ 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 place(base, offset); + return d->native ? place(base, offset) : true; } static bool interactive_move(struct yasl_surface *base) @@ -1058,7 +1723,7 @@ static bool interactive_move(struct yasl_surface *base) Window root, child; int rx, ry, x, y; unsigned mask; - if (s->destroyed || !XQueryPointer(d->native, s->window, + 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); @@ -1078,6 +1743,8 @@ const struct yasl_backend_ops yasl_x11_backend = { .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, |
