diff options
Diffstat (limited to 'src/platform/x11')
| -rw-r--r-- | src/platform/x11/window.c | 483 |
1 files changed, 427 insertions, 56 deletions
diff --git a/src/platform/x11/window.c b/src/platform/x11/window.c index 43e2109..e87f682 100644 --- a/src/platform/x11/window.c +++ b/src/platform/x11/window.c @@ -55,6 +55,8 @@ struct x11_surface { Window window; GC gc; Colormap colormap; + Visual *visual; + int visual_depth; XIC ic; #if YASL_HAS_EGL EGLContext gl_context; @@ -91,6 +93,24 @@ struct paste_request { Atom selection; uint64_t id; yasl_private_timer_id timeout; + /* INCR receive: chunks accumulate in buf until a zero-length chunk arrives. */ + bool incr; + Atom incr_type; + char *buf; + size_t buf_len; +}; + +/* An INCR transfer this backend sends to a requestor. data is a private copy, so + * clipboard changes do not affect a transfer in progress. */ +struct incr_send { + struct incr_send *next; + struct x11_display *display; + Window requestor; + Atom property, target; + char *data; + size_t length, offset; + bool finishing; + yasl_private_timer_id timeout; }; struct tablet_axis { @@ -125,9 +145,18 @@ struct x11_display { int watch; struct x11_surface *surfaces, *dead; int depth; + /* 32-bit TrueColor visual for CPU windows, when the server has one. */ + Visual *argb_visual; + int argb_depth; + /* Device scale numerator from Xft.dpi, with 120 representing 1x. */ + uint32_t scale_120; XIM im; Window selection_window; + /* Server time of the latest key or button event, and the time each selection was taken. */ + Time last_time, selection_time[2]; + struct incr_send *incr_sends; Atom protocols, delete_window, utf8, clipboard, targets, paste_property, incr; + Atom timestamp, multiple, pair; Atom net_name, net_state, fullscreen, maximized_h, maximized_v, motif_hints; 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; @@ -210,6 +239,24 @@ static void push(struct x11_surface *s, struct yasl_private_event event, input_queue_push(&s->input, &event); } +static void note_time(struct x11_display *d, Time time) +{ + if (time != CurrentTime) d->last_time = time; +} + +/* Xft.dpi of 96 is 1x. The scale is clamped to 1x through 4x; unset is 1x. */ +static uint32_t scale_from_xft_dpi(Display *native) +{ + const char *value = XGetDefault(native, "Xft", "dpi"); + char *end = NULL; + double dpi = value ? strtod(value, &end) : 0; + if (!value || end == value || !isfinite(dpi) || dpi <= 0) return YASL_PRIVATE_SCALE_UNIT; + double scale = dpi / 96.0 * YASL_PRIVATE_SCALE_UNIT; + if (scale < YASL_PRIVATE_SCALE_UNIT) scale = YASL_PRIVATE_SCALE_UNIT; + if (scale > 4 * YASL_PRIVATE_SCALE_UNIT) scale = 4 * YASL_PRIVATE_SCALE_UNIT; + return (uint32_t)lround(scale); +} + static unsigned long component(uint32_t color, unsigned long mask) { if (unlikely(!mask)) return 0; @@ -581,6 +628,8 @@ static void xi_pointer_event(struct x11_surface *s, const XIDeviceEvent *ev) static void xi_event(struct x11_display *d, XGenericEventCookie *cookie) { + if (cookie->evtype == XI_ButtonPress || cookie->evtype == XI_ButtonRelease) + note_time(d, ((const XIDeviceEvent *)cookie->data)->time); if (cookie->evtype == XI_HierarchyChanged || cookie->evtype == XI_DeviceChanged) { if (cookie->evtype == XI_DeviceChanged && ((XIDeviceChangedEvent *)cookie->data)->reason == XISlaveSwitch) return; @@ -705,8 +754,8 @@ static bool resize(struct x11_surface *s, struct yasl_private_vector2i size) s->shm_info.shmaddr = (char *)shmat(s->shm_info.shmid, NULL, 0); if (s->shm_info.shmaddr != (char *)-1) { s->shm_info.readOnly = False; - image = XShmCreateImage(d->native, DefaultVisual(d->native, d->screen), - (unsigned)DefaultDepth(d->native, d->screen), ZPixmap, + image = XShmCreateImage(d->native, s->visual, + (unsigned)s->visual_depth, ZPixmap, s->shm_info.shmaddr, &s->shm_info, (unsigned)size.x, (unsigned)size.y); if (image) { @@ -733,8 +782,8 @@ static bool resize(struct x11_surface *s, struct yasl_private_vector2i size) } } if (!shm_ok) { - image = XCreateImage(d->native, DefaultVisual(d->native, d->screen), - (unsigned)DefaultDepth(d->native, d->screen), ZPixmap, 0, NULL, + image = XCreateImage(d->native, s->visual, + (unsigned)s->visual_depth, ZPixmap, 0, NULL, (unsigned)size.x, (unsigned)size.y, 32, 0); pixels = calloc((size_t)size.x * (size_t)size.y, sizeof(*pixels)); if (!image || !pixels) { @@ -754,7 +803,7 @@ static bool resize(struct x11_surface *s, struct yasl_private_vector2i size) s->pixels = pixels; s->size = size; s->base.size = size; - s->base.scale_120 = YASL_PRIVATE_SCALE_UNIT; + s->base.scale_120 = d->scale_120; s->want_draw = true; yasl_private_clock_note_resize(&s->clock, yasl_private_loop_now_ns() - start); return true; @@ -764,16 +813,18 @@ static void cancel_pastes(struct x11_display *d, struct x11_surface *target) { for (struct paste_request **p = &d->pastes; *p;) { struct paste_request *r = *p; - if (target && r->target == target) { + if (target && r->target == target && !r->incr) { /* Keep the requestor window alive until the selection owner replies. * Some INCR owners write to it after the original surface is gone. */ r->target = NULL; r->id = 0; p = &r->next; - } else if (!target) { + } else if (!target || r->target == target) { + /* An INCR transfer in progress is abandoned; its owner times out. */ *p = r->next; if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); XDestroyWindow(d->native, r->window); + free(r->buf); free(r); } else { p = &r->next; @@ -795,10 +846,33 @@ static uint64_t paste_timeout(void *ud, yasl_private_timer_id id, uint64_t inter XDestroyWindow(d->native, r->window); if (s && !s->destroyed) yasl_private_surface_complete_paste(&s->base, r->id, YASL_PRIVATE_TRANSFER_FAILED, NULL, 0); + free(r->buf); free(r); return 0; } +/* Restart the stall timeout; an INCR transfer gets a fresh allowance per chunk. */ +static void paste_rearm(struct paste_request *r) +{ + struct x11_display *d = r->display; + if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); + r->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, paste_timeout, r); +} + +/* Unlink a paste request at *link and report its outcome to the surface, if alive. */ +static void paste_complete(struct x11_display *d, struct paste_request **link, + enum yasl_private_transfer_status status, const char *text, size_t len) +{ + struct paste_request *r = *link; + *link = r->next; + if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); + XDestroyWindow(d->native, r->window); + if (r->target && !r->target->destroyed) + yasl_private_surface_complete_paste(&r->target->base, r->id, status, text, len); + free(r->buf); + free(r); +} + static void surface_destroy(struct yasl_surface *base) { struct x11_surface *s = (struct x11_surface *)base; @@ -891,13 +965,16 @@ static bool surface_set_size(struct yasl_surface *base, struct yasl_private_vect { struct x11_surface *s = (struct x11_surface *)base; struct x11_display *d = (struct x11_display *)base->display; - if (s->destroyed || s->is_popup || size.x <= 0 || size.y <= 0 || - size.x > 32767 || size.y > 32767) { + /* The request is logical; the window is sized in device pixels. */ + struct yasl_private_vector2i px = {yasl_private_scale_to_device(size.x, s->base.scale_120), + yasl_private_scale_to_device(size.y, s->base.scale_120)}; + if (s->destroyed || s->is_popup || px.x <= 0 || px.y <= 0 || + px.x > 32767 || px.y > 32767) { errno = EINVAL; return false; } if (!d->native || !s->window) { errno = ENODEV; return false; } - XResizeWindow(d->native, s->window, (unsigned)size.x, (unsigned)size.y); + XResizeWindow(d->native, s->window, (unsigned)px.x, (unsigned)px.y); XFlush(d->native); return true; } @@ -1137,13 +1214,18 @@ static struct yasl_surface *create_toplevel_visual(struct yasl_private_display * s->base.creation_flags = spec->flags | (spec->external_gpu ? 0u : YASL_PRIVATE_SURFACE_CPU); s->base.presenter = presenter; s->clock.refresh_ns = YASL_PRIVATE_REFRESH_DEFAULT_NS; - if (!resize(s, spec->size)) { free(s); return NULL; } + /* Logical creation sizes become device pixels. */ + struct yasl_private_vector2i px = {yasl_private_scale_to_device(spec->size.x, d->scale_120), + yasl_private_scale_to_device(spec->size.y, d->scale_120)}; if (!visual) visual = DefaultVisual(d->native, d->screen); if (!depth) depth = DefaultDepth(d->native, d->screen); + s->visual = visual; + s->visual_depth = depth; + if (!resize(s, px)) { free(s); return NULL; } s->colormap = XCreateColormap(d->native, RootWindow(d->native, d->screen), visual, AllocNone); XSetWindowAttributes attrs = {.colormap = s->colormap, .border_pixel = 0, .background_pixel = 0}; s->window = XCreateWindow(d->native, RootWindow(d->native, d->screen), 0, 0, - (unsigned)spec->size.x, (unsigned)spec->size.y, 0, depth, InputOutput, + (unsigned)px.x, (unsigned)px.y, 0, depth, InputOutput, visual, CWBorderPixel | CWBackPixel | CWColormap, &attrs); if (!s->window) { XFreeColormap(d->native, s->colormap); @@ -1153,7 +1235,7 @@ static struct yasl_surface *create_toplevel_visual(struct yasl_private_display * } s->base.native_handle = (void *)(uintptr_t)s->window; s->base.size = s->size; - s->base.scale_120 = YASL_PRIVATE_SCALE_UNIT; + s->base.scale_120 = d->scale_120; s->gc = XCreateGC(d->native, s->window, 0, NULL); XSelectInput(d->native, s->window, ExposureMask | StructureNotifyMask | FocusChangeMask | KeyPressMask | KeyReleaseMask | PropertyChangeMask | @@ -1179,8 +1261,8 @@ static struct yasl_surface *create_toplevel_visual(struct yasl_private_display * } if (spec->flags & YASL_PRIVATE_SURFACE_FIXED_SIZE) { XSizeHints size_hints = {.flags = PMinSize | PMaxSize, - .min_width = spec->size.x, .max_width = spec->size.x, - .min_height = spec->size.y, .max_height = spec->size.y}; + .min_width = px.x, .max_width = px.x, + .min_height = px.y, .max_height = px.y}; XSetWMNormalHints(d->native, s->window, &size_hints); } XClassHint hint = { .res_name = d->app_id, .res_class = d->app_id }; @@ -1207,7 +1289,11 @@ static struct yasl_surface *create_toplevel(struct yasl_private_display *base, { if (!spec) return NULL; if (graphics_device) { errno = ENOTSUP; return NULL; } - return create_toplevel_visual(base, spec, NULL, 0, + struct x11_display *d = (struct x11_display *)base; + /* CPU windows keep alpha when the server has a 32-bit visual. */ + Visual *visual = !spec->external_gpu && d->argb_visual ? d->argb_visual : NULL; + int depth = visual ? d->argb_depth : 0; + return create_toplevel_visual(base, spec, visual, depth, spec->external_gpu ? YASL_PRIVATE_PRESENTER_NONE : YASL_PRIVATE_PRESENTER_CPU); } @@ -1581,10 +1667,19 @@ static struct yasl_surface *surface_create_popup(struct yasl_surface *parent_bas s->base.creation_flags = parent_base->creation_flags; s->base.presenter = (parent_base->creation_flags & YASL_PRIVATE_SURFACE_CPU) ? YASL_PRIVATE_PRESENTER_CPU : YASL_PRIVATE_PRESENTER_NONE; + s->base.scale_120 = d->scale_120; s->parent = parent; s->is_popup = true; s->content_offset = spec->content_offset; s->clock.refresh_ns = parent->clock.refresh_ns; + bool argb = s->base.presenter == YASL_PRIVATE_PRESENTER_CPU && d->argb_visual; + if (argb) { + s->visual = d->argb_visual; + s->visual_depth = d->argb_depth; + } else if (d->native) { + s->visual = DefaultVisual(d->native, d->screen); + s->visual_depth = DefaultDepth(d->native, d->screen); + } int root_x = 0, root_y = 0; struct yasl_private_vector2i actual_size; popup_calculate_position(d, parent, spec, &root_x, &root_y, &actual_size); @@ -1595,17 +1690,28 @@ static struct yasl_surface *surface_create_popup(struct yasl_surface *parent_bas .override_redirect = True, .save_under = True, }; + unsigned long mask = CWOverrideRedirect | CWSaveUnder; + Visual *visual = CopyFromParent; + int depth = CopyFromParent; + if (argb) { + s->colormap = XCreateColormap(d->native, RootWindow(d->native, d->screen), + s->visual, AllocNone); + attrs.colormap = s->colormap; + attrs.border_pixel = 0; + attrs.background_pixel = 0; + mask |= CWColormap | CWBorderPixel | CWBackPixel; + visual = s->visual; + depth = s->visual_depth; + } s->window = XCreateWindow(d->native, RootWindow(d->native, d->screen), root_x, root_y, (unsigned)actual_size.x, (unsigned)actual_size.y, - 0, CopyFromParent, InputOutput, CopyFromParent, - CWOverrideRedirect | CWSaveUnder, &attrs); + 0, depth, InputOutput, visual, mask, &attrs); if (!s->window) { surface_destroy(&s->base); return NULL; } s->base.native_handle = (void *)(uintptr_t)s->window; s->base.size = s->size; - s->base.scale_120 = YASL_PRIVATE_SCALE_UNIT; s->gc = XCreateGC(d->native, s->window, 0, NULL); XSelectInput(d->native, s->window, ExposureMask | StructureNotifyMask | FocusChangeMask | KeyPressMask | KeyReleaseMask | PropertyChangeMask | @@ -1654,7 +1760,7 @@ static bool surface_popup_reposition(struct yasl_surface *popup_base, } if (changed_size) yasl_private_surface_notify_configure(&s->base, - (struct yasl_private_geometry){actual_size, YASL_PRIVATE_SCALE_UNIT}); + (struct yasl_private_geometry){actual_size, s->base.scale_120}); schedule_redraw(&s->base); return true; } @@ -1684,36 +1790,221 @@ static void update_state(struct x11_surface *s) yasl_private_surface_notify_state(&s->base, state); } +/* Largest payload written in one property; bigger ones go out as INCR chunks. */ +static size_t incr_chunk_limit(const struct x11_display *d) +{ + return (size_t)XMaxRequestSize(d->native) * 4 - 128; +} + +enum convert_status { CONVERT_REFUSED, CONVERT_WRITTEN, CONVERT_INCR }; + +/* Unlink and free an INCR transfer. Cancel its timer unless the timer itself is calling. */ +static void incr_send_retire(struct x11_display *d, struct incr_send *st, bool cancel_timer) +{ + for (struct incr_send **p = &d->incr_sends; *p; p = &(*p)->next) { + if (*p != st) continue; + *p = st->next; + break; + } + if (cancel_timer && st->timeout) yasl_private_loop_del_timer(d->loop, st->timeout); + free(st->data); + free(st); +} + +static uint64_t incr_send_timeout(void *ud, yasl_private_timer_id id, uint64_t interval_ns) +{ + (void)id; + (void)interval_ns; + struct incr_send *st = ud; + incr_send_retire(st->display, st, false); + return 0; +} + +/* Restart the stall timeout; each chunk gets a fresh allowance. */ +static void incr_send_arm(struct incr_send *st) +{ + struct x11_display *d = st->display; + if (st->timeout) yasl_private_loop_del_timer(d->loop, st->timeout); + st->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, incr_send_timeout, st); +} + +/* Start an INCR send: publish the total size, then wait for the requestor to + * delete the property before each chunk. On failure data stays with the caller. */ +static bool incr_send_start(struct x11_display *d, Window requestor, Atom property, + Atom target, char *data, size_t length) +{ + struct incr_send *st = calloc(1, sizeof(*st)); + if (!st) return false; + st->display = d; + st->requestor = requestor; + st->property = property; + st->target = target; + st->data = data; + st->length = length; + st->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, incr_send_timeout, st); + if (!st->timeout) { + free(st); + return false; + } + /* The requestor's property deletions arrive as PropertyNotify events. */ + XSelectInput(d->native, requestor, PropertyChangeMask); + unsigned long total = length; + XChangeProperty(d->native, requestor, property, d->incr, 32, PropModeReplace, + (const unsigned char *)&total, 1); + st->next = d->incr_sends; + d->incr_sends = st; + return true; +} + +/* Each requestor deletion asks for the next chunk. After the last data chunk, a + * zero-length chunk ends the transfer, and its deletion finishes it. */ +static void incr_send_step(struct incr_send *st) +{ + struct x11_display *d = st->display; + if (st->finishing) { + incr_send_retire(d, st, true); + return; + } + if (st->offset >= st->length) { + XChangeProperty(d->native, st->requestor, st->property, st->target, 8, + PropModeReplace, (const unsigned char *)"", 0); + st->finishing = true; + } else { + size_t n = st->length - st->offset; + if (n > incr_chunk_limit(d)) n = incr_chunk_limit(d); + XChangeProperty(d->native, st->requestor, st->property, st->target, 8, + PropModeReplace, (const unsigned char *)st->data + st->offset, (int)n); + st->offset += n; + } + incr_send_arm(st); + XFlush(d->native); +} + +/* Returns true when a deletion belongs to an INCR transfer. */ +static bool incr_send_property_deleted(struct x11_display *d, const XPropertyEvent *ev) +{ + for (struct incr_send *st = d->incr_sends; st; st = st->next) { + if (st->requestor != ev->window || st->property != ev->atom) continue; + incr_send_step(st); + return true; + } + return false; +} + +static void incr_cancel_all(struct x11_display *d) +{ + while (d->incr_sends) incr_send_retire(d, d->incr_sends, true); +} + +/* Write the selection's value for target to property on requestor. allow_incr is + * false inside MULTIPLE, where a payload that needs INCR is refused. */ +static enum convert_status convert_selection(struct x11_display *d, int slot, Atom target, + Window requestor, Atom property, bool allow_incr) +{ + const char *text = d->selection[slot]; + if (!text) return CONVERT_REFUSED; + if (target == d->targets) { + size_t latin1_len = 0; + char *latin1 = yasl_private_utf8_to_latin1(text, &latin1_len); + Atom list[5]; + int count = 0; + list[count++] = d->targets; + list[count++] = d->utf8; + if (latin1) list[count++] = XA_STRING; + list[count++] = d->timestamp; + list[count++] = d->multiple; + free(latin1); + XChangeProperty(d->native, requestor, property, XA_ATOM, 32, PropModeReplace, + (const unsigned char *)list, count); + return CONVERT_WRITTEN; + } + if (target == d->timestamp) { + Time when = d->selection_time[slot]; + XChangeProperty(d->native, requestor, property, XA_INTEGER, 32, PropModeReplace, + (const unsigned char *)&when, 1); + return CONVERT_WRITTEN; + } + if (target == d->multiple) { + Atom actual = None; + int format = 0; + unsigned long count = 0, left = 0; + unsigned char *pairs = NULL; + if (XGetWindowProperty(d->native, requestor, property, 0, 0x10000, False, d->pair, + &actual, &format, &count, &left, &pairs) != Success || !pairs || + actual != d->pair || format != 32 || count < 2 || (count & 1) || left) { + if (pairs) XFree(pairs); + return CONVERT_REFUSED; + } + Atom *entries = (Atom *)pairs; + for (unsigned long i = 0; i + 1 < count; i += 2) { + Atom sub_target = entries[i]; + Atom sub_property = entries[i + 1]; + bool ok = sub_target != d->multiple && sub_property != None && sub_property != property && + convert_selection(d, slot, sub_target, requestor, sub_property, false) == CONVERT_WRITTEN; + if (!ok) entries[i + 1] = None; + } + XChangeProperty(d->native, requestor, property, d->pair, 32, PropModeReplace, + pairs, (int)count); + XFree(pairs); + return CONVERT_WRITTEN; + } + if (target != d->utf8 && target != XA_STRING) return CONVERT_REFUSED; + const char *payload = text; + size_t len = strlen(text); + char *data = NULL; + if (target == XA_STRING) { + data = yasl_private_utf8_to_latin1(text, &len); + if (!data) return CONVERT_REFUSED; + payload = data; + } + if (len <= incr_chunk_limit(d)) { + XChangeProperty(d->native, requestor, property, target, 8, PropModeReplace, + (const unsigned char *)payload, (int)len); + free(data); + return CONVERT_WRITTEN; + } + if (!allow_incr) { + free(data); + return CONVERT_REFUSED; + } + if (!data) { + data = malloc(len); + if (!data) return CONVERT_REFUSED; + memcpy(data, text, len); + } + if (incr_send_start(d, requestor, property, target, data, len)) return CONVERT_INCR; + free(data); + return CONVERT_REFUSED; +} + static void selection_request(struct x11_display *d, XSelectionRequestEvent *request) { + int slot = request->selection == XA_PRIMARY ? YASL_PRIVATE_PRIMARY : + request->selection == d->clipboard ? YASL_PRIVATE_CLIPBOARD : -1; + /* Obsolete requestors pass property None and expect the target name. */ + Atom property = request->property ? request->property : request->target; + enum convert_status status = slot < 0 ? CONVERT_REFUSED : + convert_selection(d, slot, request->target, request->requestor, property, true); XEvent reply = {0}; reply.xselection = (XSelectionEvent){ .type = SelectionNotify, .display = d->native, .requestor = request->requestor, .selection = request->selection, .target = request->target, .time = request->time, + .property = status == CONVERT_REFUSED ? None : property, }; - Atom property = request->property ? request->property : request->target; - int slot = request->selection == XA_PRIMARY ? YASL_PRIVATE_PRIMARY : YASL_PRIVATE_CLIPBOARD; - const char *text = d->selection[slot]; - size_t latin1_len = 0; - char *latin1 = text ? yasl_private_utf8_to_latin1(text, &latin1_len) : NULL; - if (text && request->target == d->targets) { - Atom targets[] = {d->targets, d->utf8, XA_STRING}; - XChangeProperty(d->native, request->requestor, property, XA_ATOM, 32, - PropModeReplace, (unsigned char *)targets, latin1 ? 3 : 2); - reply.xselection.property = property; - } else if (text && (request->target == d->utf8 || request->target == XA_STRING)) { - const char *payload = request->target == XA_STRING ? latin1 : text; - size_t len = request->target == XA_STRING ? latin1_len : strlen(text); - long max = XMaxRequestSize(d->native) * 4 - 128; - if (payload && len <= (size_t)max) { - XChangeProperty(d->native, request->requestor, property, request->target, - 8, PropModeReplace, (const unsigned char *)payload, (int)len); - reply.xselection.property = property; - } - } - free(latin1); XSendEvent(d->native, request->requestor, False, 0, &reply); + XFlush(d->native); +} + +/* Another client took a selection this display owned. */ +static void selection_clear(struct x11_display *d, const XSelectionClearEvent *event) +{ + if (event->window != d->selection_window) return; + int slot = event->selection == XA_PRIMARY ? YASL_PRIVATE_PRIMARY : + event->selection == d->clipboard ? YASL_PRIVATE_CLIPBOARD : -1; + if (slot < 0) return; + free(d->selection[slot]); + d->selection[slot] = NULL; } static char *parse_file_uris(const char *data, size_t len, size_t *out_len) @@ -1830,9 +2121,7 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event) free(r); return; } - /* Probe without deleting first. Deleting an INCR marker acknowledges it - * and invites the owner to write more properties; this backend rejects - * streaming, so leave it intact and retain a tombstone until timeout. */ + /* Probe without deleting first; the type tells an INCR marker apart. */ Atom probe_type = None; int probe_format = 0; unsigned long probe_count = 0, probe_left = 0; @@ -1842,10 +2131,12 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event) &probe_count, &probe_left, &probe_data); if (probe_data) XFree(probe_data); if (probe_result == Success && probe_type == d->incr) { - yasl_private_surface_complete_paste(&s->base, r->id, - YASL_PRIVATE_TRANSFER_FAILED, NULL, 0); - r->target = NULL; - r->id = 0; + /* Deleting the marker tells the owner to send the first chunk. The + * request stays queued until a zero-length chunk ends the transfer. */ + XDeleteProperty(d->native, r->window, event->property); + r->incr = true; + paste_rearm(r); + XFlush(d->native); return; } *p = r->next; @@ -1868,11 +2159,6 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event) if (data) { XFree(data); data = NULL; } break; } - if (actual == d->incr) { - /* Unsupported INCR streaming transfer */ - if (data) { XFree(data); data = NULL; } - break; - } if ((actual != d->utf8 && actual != XA_STRING) || format != 8) { if (data) { XFree(data); data = NULL; } break; @@ -1914,6 +2200,63 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event) free(r); } +/* Consume a PropertyNotify on an INCR paste window. Returns false for other windows. */ +static bool paste_incr_property(struct x11_display *d, const XPropertyEvent *ev) +{ + struct paste_request **link = &d->pastes; + while (*link && !((*link)->incr && (*link)->window == ev->window)) link = &(*link)->next; + if (!*link) return false; + if (ev->state != PropertyNewValue || ev->atom != d->paste_property) return true; + struct paste_request *r = *link; + Atom actual = None; + int format = 0; + unsigned long count = 0, left = 0; + unsigned char *data = NULL; + /* Reading and deleting the chunk lets the owner write the next one. */ + int result = XGetWindowProperty(d->native, r->window, ev->atom, 0, TEXT_LIMIT / 4, True, + AnyPropertyType, &actual, &format, &count, &left, &data); + bool valid = result == Success && actual != None && format == 8 && left == 0 && + (actual == d->utf8 || actual == XA_STRING) && count <= TEXT_LIMIT - r->buf_len; + if (!valid) { + if (data) XFree(data); + paste_complete(d, link, YASL_PRIVATE_TRANSFER_FAILED, NULL, 0); + return true; + } + if (count == 0) { + /* A zero-length chunk ends the transfer. */ + if (data) XFree(data); + if (r->buf && r->incr_type == XA_STRING) { + size_t utf8_len = 0; + char *utf8 = yasl_private_utf8_from_latin1(r->buf, r->buf_len, &utf8_len); + free(r->buf); + r->buf = utf8; + r->buf_len = utf8_len; + if (!utf8) { + paste_complete(d, link, YASL_PRIVATE_TRANSFER_FAILED, NULL, 0); + return true; + } + } + bool text_ok = !r->buf || yasl_private_utf8_validate(r->buf, r->buf_len); + if (text_ok) paste_complete(d, link, YASL_PRIVATE_TRANSFER_OK, r->buf ? r->buf : "", r->buf_len); + else paste_complete(d, link, YASL_PRIVATE_TRANSFER_FAILED, NULL, 0); + return true; + } + if (!r->incr_type) r->incr_type = actual; + char *grown = realloc(r->buf, r->buf_len + count + 1); + if (!grown) { + XFree(data); + paste_complete(d, link, YASL_PRIVATE_TRANSFER_FAILED, NULL, 0); + return true; + } + r->buf = grown; + memcpy(r->buf + r->buf_len, data, count); + r->buf_len += count; + r->buf[r->buf_len] = '\0'; + XFree(data); + paste_rearm(r); + return true; +} + static void refresh_keyboard(struct x11_display *d) { memset(d->keys, 0, sizeof(d->keys)); @@ -2126,11 +2469,24 @@ static void dispatch(struct yasl_private_display *base) refresh_keyboard(d); continue; } + switch (event.type) { + case KeyPress: case KeyRelease: note_time(d, event.xkey.time); break; + case ButtonPress: case ButtonRelease: note_time(d, event.xbutton.time); break; + } if (XFilterEvent(&event, None)) continue; if (event.type == SelectionRequest) { selection_request(d, &event.xselectionrequest); continue; } + if (event.type == SelectionClear) { + selection_clear(d, &event.xselectionclear); + continue; + } + if (event.type == PropertyNotify) { + if (event.xproperty.state == PropertyDelete && incr_send_property_deleted(d, &event.xproperty)) + continue; + if (paste_incr_property(d, &event.xproperty)) continue; + } if (event.type == SelectionNotify) { if (event.xselection.selection == d->xdnd_selection) { xdnd_selection_notify(d, &event.xselection); @@ -2149,7 +2505,7 @@ static void dispatch(struct yasl_private_display *base) if (size.x == s->size.x && size.y == s->size.y) break; if (resize(s, size)) yasl_private_surface_notify_configure(&s->base, - (struct yasl_private_geometry){size, YASL_PRIVATE_SCALE_UNIT}); + (struct yasl_private_geometry){size, s->base.scale_120}); break; } case ClientMessage: @@ -2411,6 +2767,12 @@ static struct yasl_private_display *display_open(struct yasl_private_loop *loop, XSetErrorHandler(yasl_private_x11_error_handler); d->base.native_handle = d->native; d->screen = DefaultScreen(d->native); + d->scale_120 = scale_from_xft_dpi(d->native); + XVisualInfo argb; + if (XMatchVisualInfo(d->native, d->screen, 32, TrueColor, &argb)) { + d->argb_visual = argb.visual; + d->argb_depth = argb.depth; + } d->has_shm = XShmQueryExtension(d->native); int xkb_opcode = 0, xkb_event = 0, xkb_error = 0; int xkb_major = XkbMajorVersion, xkb_minor = XkbMinorVersion; @@ -2472,6 +2834,9 @@ static struct yasl_private_display *display_open(struct yasl_private_loop *loop, ATOM(targets, "TARGETS"); ATOM(paste_property, "YASL_PRIVATE_SELECTION"); ATOM(incr, "INCR"); + ATOM(timestamp, "TIMESTAMP"); + ATOM(multiple, "MULTIPLE"); + ATOM(pair, "ATOM_PAIR"); ATOM(net_name, "_NET_WM_NAME"); ATOM(net_state, "_NET_WM_STATE"); ATOM(motif_hints, "_MOTIF_WM_HINTS"); @@ -2511,6 +2876,7 @@ static void display_close(struct yasl_private_display *base) assert(!d->surfaces && "destroy every surface before its display"); drag_cancel(base); cancel_pastes(d, NULL); + incr_cancel_all(d); d->pending_drop.active = false; if (d->loop && d->watch > 0) yasl_private_loop_del_fd(d->loop, d->watch); if (d->owns_loop && d->loop) { @@ -2555,8 +2921,10 @@ static bool copy_text(struct yasl_private_display *base, enum yasl_private_selec if (!value) { errno = ENOMEM; return false; } free(d->selection[sel]); d->selection[sel] = value; + /* Take ownership at the latest key or button time; TIMESTAMP reports it. */ + d->selection_time[sel] = d->last_time; XSetSelectionOwner(d->native, sel == YASL_PRIVATE_PRIMARY ? XA_PRIMARY : d->clipboard, - d->selection_window, CurrentTime); + d->selection_window, d->last_time); XFlush(d->native); return true; } @@ -2590,6 +2958,8 @@ static bool request_paste(struct yasl_private_display *base, enum yasl_private_s r->window = XCreateSimpleWindow(d->native, RootWindow(d->native, d->screen), 0, 0, 1, 1, 0, 0, 0); if (!r->window) { free(r); errno = EIO; return false; } + /* INCR chunks arrive as PropertyNotify events on this window. */ + XSelectInput(d->native, r->window, PropertyChangeMask); r->display = d; r->target = s; r->selection = selection; @@ -2631,7 +3001,8 @@ static void schedule_redraw(struct yasl_surface *base) static struct yasl_private_display *surface_display(const struct yasl_surface *s) { return s->display; } static struct yasl_private_geometry geometry(const struct yasl_surface *s) { - return (struct yasl_private_geometry){((const struct x11_surface *)s)->size, YASL_PRIVATE_SCALE_UNIT}; + const struct x11_surface *x = (const struct x11_surface *)s; + return (struct yasl_private_geometry){x->size, x->base.scale_120}; } static int mods(const struct yasl_private_display *d) { return (int)((const struct x11_display *)d)->mods; } static void *native_display(struct yasl_private_display *d) { return ((struct x11_display *)d)->native; } |
