summaryrefslogtreecommitdiff
path: root/src/x11
diff options
context:
space:
mode:
Diffstat (limited to 'src/x11')
-rw-r--r--src/x11/window.c727
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,