From 2408acfac3acd22dc207d41daed82294de2f3275 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Wed, 14 Oct 2026 14:20:10 +0300 Subject: X11: survive server loss, chain X errors, fix popups and pointer requests - Losing the X server queues DISPLAY_LOST instead of exiting. A process-wide IO error handler returns for YASL displays, so Xlib calls the per-display exit handler and returns; later waits fail with ENOTCONN, and dispatch and flush make no Xlib calls on a lost display. - Errors on other displays chain to the previous handler, restored when the last YASL display closes. - MIT-SHM attach is synced; a refused attach detaches and falls back to XPutImage. Shared images are used only for 32-bit TrueColor 0xAARRGGBB. - Undecorated windows set MWM_HINTS_DECORATIONS and leave functions alone. - Fixed-size windows keep their min/max hints in step with set_size. - Swap interval is stored per surface and applied when it becomes current; gl_make_current(NULL) without EGL succeeds as a no-op. - XInitThreads runs before the first connection. - Move, resize, window menu, drag start and place-under-pointer need a held pointer button (ESTALE). Move and resize also need _NET_WM_MOVERESIZE on the root (ENOTSUP) and send the held button. - _GTK_SHOW_WINDOW_MENU carries the XI2 client pointer and root coordinates. - Popups always present through CPU and grab the pointer (XI2 device grab when available); a press outside every YASL window closes the topmost popup. - Drag start while active returns EBUSY; drag carry without a drag EINVAL. Co-Authored-By: Claude Haiku 5.5 --- src/platform/x11/window.c | 371 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 321 insertions(+), 50 deletions(-) (limited to 'src') diff --git a/src/platform/x11/window.c b/src/platform/x11/window.c index e87f682..630eb91 100644 --- a/src/platform/x11/window.c +++ b/src/platform/x11/window.c @@ -62,6 +62,7 @@ struct x11_surface { EGLContext gl_context; EGLSurface gl_surface; EGLConfig gl_config; + int swap_interval; /* Applied whenever this surface becomes current. */ #endif XImage *image; uint32_t *pixels; @@ -139,6 +140,7 @@ struct x11_pointer { struct x11_display { struct yasl_private_display base; + struct x11_display *next_open; Display *native; int screen; struct yasl_private_loop *loop; @@ -170,6 +172,8 @@ struct x11_display { bool has_shm; bool has_xkb; bool lost; + bool popup_grab; /* The pointer is grabbed for the topmost popup. */ + int popup_device; /* XI2 device holding that grab. */ int xkb_event; enum yasl_key keys[256]; Cursor cursors[16]; @@ -297,15 +301,82 @@ static void drag_drop_hover(struct x11_display *d) drag_end(d, YASL_PRIVATE_DRAG_DROPPED); } +/* Xlib's error and IO handlers are process-wide. Open YASL displays are linked + * here so the handlers can recognize their own errors and pass others on. */ +static struct x11_display *open_displays; +static XErrorHandler previous_error_handler; +static XIOErrorHandler previous_io_handler; +static Display *shm_probe; /* Set while an MIT-SHM attach is awaiting its result. */ +static bool shm_probe_failed; +static int yasl_private_x11_error_handler(Display *native, XErrorEvent *event); +static int yasl_private_x11_io_error_handler(Display *native); + +static struct x11_display *display_for_native(Display *native) +{ + for (struct x11_display *d = open_displays; d; d = d->next_open) + if (d->native == native) return d; + return NULL; +} + +static void display_register(struct x11_display *d) +{ + if (!open_displays) { + previous_error_handler = XSetErrorHandler(yasl_private_x11_error_handler); + previous_io_handler = XSetIOErrorHandler(yasl_private_x11_io_error_handler); + } + d->next_open = open_displays; + open_displays = d; +} + +static void display_unregister(struct x11_display *d) +{ + for (struct x11_display **p = &open_displays; *p; p = &(*p)->next_open) { + if (*p != d) continue; + *p = d->next_open; + break; + } + d->next_open = NULL; + if (open_displays) return; + XSetErrorHandler(previous_error_handler); + XSetIOErrorHandler(previous_io_handler); + previous_error_handler = NULL; + previous_io_handler = NULL; +} + +/* Asynchronous errors (BadWindow, BadDrawable, BadMatch) on YASL displays are + * suppressed during close, destruction races and speculative queries. */ static int yasl_private_x11_error_handler(Display *native, XErrorEvent *event) { - (void)native; - (void)event; - /* Suppress asynchronous errors (BadWindow, BadDrawable, BadMatch) - * during window close, destruction races, or speculative property queries. */ + if (!display_for_native(event->display)) + return previous_error_handler ? previous_error_handler(native, event) : 0; + if (event->display == shm_probe) shm_probe_failed = true; return 0; } +/* Marks the display lost once and queues DISPLAY_LOST. The poll watch is removed + * by ready(), on the thread that owns the loop, because this can run on a GL thread. */ +static void display_lost(struct x11_display *d) +{ + if (d->lost) return; + d->lost = true; + atomic_store(&d->base.offline, true); + yasl_private_display_notify_event(&d->base, YASL_PRIVATE_EVENT_DISPLAY_LOST); +} + +/* Xlib's default IO handler prints and exits before consulting the exit handler. + * Returning here makes Xlib call the exit handler below, which returns to the caller. */ +static int yasl_private_x11_io_error_handler(Display *native) +{ + if (display_for_native(native)) return 0; + return previous_io_handler ? previous_io_handler(native) : 0; +} + +static void yasl_private_x11_io_exit_handler(Display *native, void *user_data) +{ + (void)native; + display_lost(user_data); +} + static struct x11_surface *surface_at_root_pos(struct x11_display *d, int rx, int ry, struct yasl_private_vector2i *out_pos) { if (!d->native) return NULL; @@ -327,6 +398,106 @@ static struct x11_surface *surface_at_root_pos(struct x11_display *d, int rx, in return NULL; } +/* Popups are prepended to the list, so the first live one is the topmost. */ +static struct x11_surface *topmost_popup(struct x11_display *d) +{ + for (struct x11_surface *p = d->surfaces; p; p = p->next) + if (p->is_popup && !p->destroyed) return p; + return NULL; +} + +#define POPUP_GRAB_MASK (ButtonPressMask | ButtonReleaseMask | PointerMotionMask | \ + EnterWindowMask | LeaveWindowMask) + +/* XI2 clients get their presses through an XI2 device grab; a core grab does not + * deliver XI2 events to the grabbing client. */ +static bool popup_grab_begin(struct x11_display *d, Window window) +{ + if (!d->xi2) + return XGrabPointer(d->native, window, True, POPUP_GRAB_MASK, GrabModeAsync, GrabModeAsync, + None, None, CurrentTime) == GrabSuccess; + int device = 0; + if (!XIGetClientPointer(d->native, window, &device)) return false; + unsigned char bits[XIMaskLen(XI_LASTEVENT)] = {0}; + XISetMask(bits, XI_ButtonPress); + XISetMask(bits, XI_ButtonRelease); + XISetMask(bits, XI_Motion); + XISetMask(bits, XI_Enter); + XISetMask(bits, XI_Leave); + XIEventMask mask = {device, sizeof(bits), bits}; + if (XIGrabDevice(d->native, device, window, CurrentTime, None, GrabModeAsync, GrabModeAsync, + True, &mask) != GrabSuccess) + return false; + d->popup_device = device; + return true; +} + +static void popup_grab_end(struct x11_display *d) +{ + if (d->xi2) XIUngrabDevice(d->native, d->popup_device, CurrentTime); + else XUngrabPointer(d->native, CurrentTime); +} + +/* Grabs the pointer for the topmost popup, so presses outside YASL windows reach it. */ +static void popup_grab_update(struct x11_display *d) +{ + struct x11_surface *top = topmost_popup(d); + if (!d->native || d->lost) return; + if (d->popup_grab) popup_grab_end(d); + d->popup_grab = top && popup_grab_begin(d, top->window); + XFlush(d->native); +} + +static void popup_dismiss_outside(struct x11_display *d, int root_x, int root_y) +{ + if (!d->popup_grab || surface_at_root_pos(d, root_x, root_y, NULL)) return; + struct x11_surface *top = topmost_popup(d); + if (top) yasl_private_surface_notify_close(&top->base); +} + +/* The lowest pointer button held now, 1 to 5, or 0. Also returns the root position. */ +static unsigned held_button(struct x11_display *d, Window window, int *root_x, int *root_y) +{ + Window root, child; + int rx, ry, x, y; + unsigned mask = 0; + if (!d->native || !XQueryPointer(d->native, window, &root, &child, &rx, &ry, &x, &y, &mask)) + return 0; + if (root_x) *root_x = rx; + if (root_y) *root_y = ry; + for (unsigned b = 1; b <= 5; b++) + if (mask & (Button1Mask << (b - 1))) return b; + return 0; +} + +/* Whether the window manager lists an EWMH atom in _NET_SUPPORTED on the root. */ +static bool wm_supports(struct x11_display *d, const char *name) +{ + Atom want = XInternAtom(d->native, name, False); + Atom list = XInternAtom(d->native, "_NET_SUPPORTED", True); + if (!list) return false; + Window root = RootWindow(d->native, d->screen); + for (long offset = 0;;) { + Atom actual; + int format; + unsigned long count, left; + unsigned char *data = NULL; + if (XGetWindowProperty(d->native, root, list, offset, 1024, False, XA_ATOM, + &actual, &format, &count, &left, &data) != Success || actual != XA_ATOM || format != 32) { + if (data) XFree(data); + return false; + } + Atom *atoms = (Atom *)data; + bool found = false; + for (unsigned long i = 0; data && i < count && !found; i++) + found = atoms[i] == want; + if (data) XFree(data); + if (found) return true; + if (!left) return false; + offset += (long)count; + } +} + static uint32_t modifiers(unsigned state) { return ((state & ShiftMask) ? YASL_PRIVATE_MOD_SHIFT : 0) | @@ -657,6 +828,11 @@ static void xi_event(struct x11_display *d, XGenericEventCookie *cookie) ((XIDeviceEvent *)cookie->data)->event; struct x11_surface *s = d->surfaces; while (s && s->window != window) s = s->next; + if (cookie->evtype == XI_ButtonPress && d->popup_grab) { + const XIDeviceEvent *press = cookie->data; + if (press->detail < 4 || press->detail > 7) /* Wheel clicks are not presses. */ + popup_dismiss_outside(d, (int)round(press->root_x), (int)round(press->root_y)); + } if (cookie->evtype == XI_Motion && d->drag.active) { const XIDeviceEvent *mev = cookie->data; if (d->drag.carried) @@ -709,6 +885,32 @@ static void xi_event(struct x11_display *d, XGenericEventCookie *cookie) } } +/* A shared image is written in place, so only 32-bit TrueColor 0xAARRGGBB layouts + * qualify; other visuals take the converting XPutImage path. */ +static bool shm_format_direct(const XImage *image, const Visual *visual) +{ + return visual->class == TrueColor && image->bits_per_pixel == 32 && + image->red_mask == 0xff0000 && image->green_mask == 0xff00 && image->blue_mask == 0xff; +} + +/* XShmAttach only queues the request, so the server's refusal arrives later as an + * error. Sync around the attach to catch it here; on failure, detach and stop using SHM. + * The sync after the detach consumes its error while the probe is disarmed. */ +static bool shm_attach(struct x11_display *d, XShmSegmentInfo *info) +{ + XSync(d->native, False); + shm_probe = d->native; + shm_probe_failed = false; + bool queued = XShmAttach(d->native, info); + XSync(d->native, False); + shm_probe = NULL; + if (queued && !shm_probe_failed) return true; + XShmDetach(d->native, info); + XSync(d->native, False); + d->has_shm = false; + return false; +} + static bool resize(struct x11_surface *s, struct yasl_private_vector2i size) { if (size.x <= 0 || size.y <= 0 || size.x > 32767 || size.y > 32767 || @@ -760,7 +962,8 @@ static bool resize(struct x11_surface *s, struct yasl_private_vector2i size) (unsigned)size.x, (unsigned)size.y); if (image) { shmctl(s->shm_info.shmid, IPC_RMID, NULL); - if (XShmAttach(d->native, &s->shm_info)) { + if (shm_format_direct(image, s->visual) && + shm_attach(d, &s->shm_info)) { pixels = (uint32_t *)s->shm_info.shmaddr; memset(pixels, 0, bytes); shm_ok = true; @@ -823,7 +1026,7 @@ static void cancel_pastes(struct x11_display *d, struct x11_surface *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); + if (!d->lost) XDestroyWindow(d->native, r->window); free(r->buf); free(r); } else { @@ -894,6 +1097,7 @@ static void surface_destroy(struct yasl_surface *base) for (struct x11_surface **p = &d->surfaces; *p; p = &(*p)->next) { if (*p == s) { *p = s->next; break; } } + if (s->is_popup) popup_grab_update(d); cancel_pastes(d, s); free(s->ime_preedit); s->ime_preedit = NULL; @@ -903,7 +1107,7 @@ static void surface_destroy(struct yasl_surface *base) if (s->gl_surface && d->egl_ready) eglDestroySurface(d->egl, s->gl_surface); #endif if (s->shm_active) { - XShmDetach(d->native, &s->shm_info); + if (!d->lost) XShmDetach(d->native, &s->shm_info); if (s->image) XDestroyImage(s->image); if (s->shm_info.shmaddr) shmdt(s->shm_info.shmaddr); s->image = NULL; @@ -915,7 +1119,7 @@ static void surface_destroy(struct yasl_surface *base) s->image = NULL; s->pixels = NULL; } - if (d->native) { + if (d->native && !d->lost) { if (s->gc) XFreeGC(d->native, s->gc); if (s->window) XDestroyWindow(d->native, s->window); if (s->colormap) XFreeColormap(d->native, s->colormap); @@ -961,6 +1165,15 @@ static bool net_message(struct x11_surface *s, Atom type, SubstructureRedirectMask | SubstructureNotifyMask, &event) != 0; } +/* A fixed-size window tells the window manager that its size is exact. */ +static void set_fixed_size_hints(struct x11_display *d, Window window, struct yasl_private_vector2i size) +{ + XSizeHints size_hints = {.flags = PMinSize | PMaxSize, + .min_width = size.x, .max_width = size.x, + .min_height = size.y, .max_height = size.y}; + XSetWMNormalHints(d->native, window, &size_hints); +} + static bool surface_set_size(struct yasl_surface *base, struct yasl_private_vector2i size) { struct x11_surface *s = (struct x11_surface *)base; @@ -975,6 +1188,8 @@ static bool surface_set_size(struct yasl_surface *base, struct yasl_private_vect } if (!d->native || !s->window) { errno = ENODEV; return false; } XResizeWindow(d->native, s->window, (unsigned)px.x, (unsigned)px.y); + if (s->base.creation_flags & YASL_PRIVATE_SURFACE_FIXED_SIZE) + set_fixed_size_hints(d, s->window, px); XFlush(d->native); return true; } @@ -1253,18 +1468,15 @@ static struct yasl_surface *create_toplevel_visual(struct yasl_private_display * } XSetWMProtocols(d->native, s->window, &d->delete_window, 1); if (spec->flags & YASL_PRIVATE_SURFACE_UNDECORATED) { + /* MWM_HINTS_DECORATIONS: only the decorations field is set, so the + * functions stay enabled. */ struct { unsigned long flags, functions, decorations; long input_mode; unsigned long status; } hints = { - .flags = 1, .decorations = 0, + .flags = 2, .decorations = 0, }; XChangeProperty(d->native, s->window, d->motif_hints, d->motif_hints, 32, PropModeReplace, (unsigned char *)&hints, 5); } - if (spec->flags & YASL_PRIVATE_SURFACE_FIXED_SIZE) { - XSizeHints size_hints = {.flags = PMinSize | PMaxSize, - .min_width = px.x, .max_width = px.x, - .min_height = px.y, .max_height = px.y}; - XSetWMNormalHints(d->native, s->window, &size_hints); - } + if (spec->flags & YASL_PRIVATE_SURFACE_FIXED_SIZE) set_fixed_size_hints(d, s->window, px); XClassHint hint = { .res_name = d->app_id, .res_class = d->app_id }; XSetClassHint(d->native, s->window, &hint); Atom xdnd_version = 5; @@ -1373,6 +1585,7 @@ static struct yasl_surface *create_gl_surface(struct yasl_private_display *base, if (!base_surface) return NULL; struct x11_surface *s = (struct x11_surface *)base_surface; s->gl_config = config; + s->swap_interval = 1; /* EGL's default for window surfaces. */ EGLint surface_attrs[] = {EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR, EGL_NONE}; s->gl_surface = eglCreateWindowSurface(d->egl, config, (EGLNativeWindowType)s->window, options.srgb ? surface_attrs : NULL); @@ -1425,14 +1638,17 @@ static struct yasl_surface *create_gl_surface(struct yasl_private_display *base, static bool gl_make_current(struct yasl_private_display *base, struct yasl_surface *surface) { struct x11_display *d = (struct x11_display *)base; + if (!surface) /* Without EGL there is no context to detach. */ + return !d->egl_ready || eglMakeCurrent(d->egl, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) == EGL_TRUE; if (!d->egl_ready) { errno = ENOTSUP; return false; } - if (!surface) return eglMakeCurrent(d->egl, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); struct x11_surface *s = (struct x11_surface *)surface; if (surface->display != base || s->base.presenter != YASL_PRIVATE_PRESENTER_GL || !s->gl_context) { errno = EINVAL; return false; } - return eglMakeCurrent(d->egl, s->gl_surface, s->gl_surface, s->gl_context) == EGL_TRUE; + if (eglMakeCurrent(d->egl, s->gl_surface, s->gl_surface, s->gl_context) != EGL_TRUE) return false; + eglSwapInterval(d->egl, s->swap_interval); + return true; } static yasl_private_gl_proc gl_get_proc_address(struct yasl_private_display *base, const char *name) @@ -1448,7 +1664,12 @@ static bool gl_swap_interval(struct yasl_private_display *base, struct yasl_surf errno = EINVAL; return false; } - return d->egl_ready && eglSwapInterval(d->egl, interval) == EGL_TRUE; + struct x11_surface *s = (struct x11_surface *)surface; + s->swap_interval = interval; + /* EGL applies the interval to the calling thread's draw surface, so apply it now + * only when this surface is current here; otherwise gl_make_current applies it. */ + if (eglGetCurrentSurface(EGL_DRAW) != s->gl_surface) return true; + return eglSwapInterval(d->egl, interval) == EGL_TRUE; } static bool gl_swap_buffers(struct yasl_private_display *base, struct yasl_surface *surface) @@ -1665,8 +1886,7 @@ static struct yasl_surface *surface_create_popup(struct yasl_surface *parent_bas if (!s) return NULL; s->base.display = parent_base->display; 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.presenter = YASL_PRIVATE_PRESENTER_CPU; s->base.scale_120 = d->scale_120; s->parent = parent; s->is_popup = true; @@ -1736,6 +1956,7 @@ static struct yasl_surface *surface_create_popup(struct yasl_surface *parent_bas } s->next = d->surfaces; d->surfaces = s; + if (d->native) popup_grab_update(d); return &s->base; } @@ -2448,8 +2669,9 @@ static void handle_client_message(struct x11_display *d, struct x11_surface *s, static void dispatch(struct yasl_private_display *base) { struct x11_display *d = (struct x11_display *)base; + if (d->lost) return; d->depth++; - while (XPending(d->native)) { + while (!d->lost && XPending(d->native)) { XEvent event; XNextEvent(d->native, &event); if (event.type == GenericEvent && d->xi2 && @@ -2543,6 +2765,8 @@ static void dispatch(struct yasl_private_display *base) break; } case MotionNotify: case LeaveNotify: case EnterNotify: case ButtonPress: case ButtonRelease: { + if (event.type == ButtonPress && !d->xi2 && (event.xbutton.button < 4 || event.xbutton.button > 7)) + popup_dismiss_outside(d, event.xbutton.x_root, event.xbutton.y_root); struct yasl_private_event ev = { .pointer = {.device = YASL_PRIVATE_PTR_DEV_MOUSE}, }; @@ -2615,6 +2839,7 @@ static bool present(struct yasl_surface *base, const struct yasl_private_framebu fb->pixels != s->pixels || fb->size.x != s->size.x || fb->size.y != s->size.y || damage->n < -1 || damage->n > YASL_PRIVATE_DAMAGE_MAX) return false; struct x11_display *d = (struct x11_display *)base->display; + if (d->lost) { errno = ENOTCONN; return false; } XImage *im = s->image; if (s->shm_active) { if (damage->n == -1) @@ -2686,6 +2911,7 @@ static bool present(struct yasl_surface *base, const struct yasl_private_framebu static void flush(struct yasl_private_display *base) { struct x11_display *d = (struct x11_display *)base; + if (d->lost) return; d->depth++; for (struct x11_surface *s = d->surfaces, *next; s; s = next) { next = s->next; @@ -2708,7 +2934,7 @@ static void flush(struct yasl_private_display *base) yasl_private_surface_draw(&s->base, &s->frame); if (!s->destroyed) s->drawing = false; } - XFlush(d->native); + if (!d->lost) XFlush(d->native); leave(d); } @@ -2717,13 +2943,14 @@ static int ready(int fd, short events, void *ud) (void)fd; struct yasl_private_display *base = ud; struct x11_display *d = (struct x11_display *)base; - if ((events & (POLLERR | POLLHUP)) && !d->lost) { - d->lost = true; - base->offline = true; - yasl_private_display_notify_event(base, YASL_PRIVATE_EVENT_DISPLAY_LOST); + if (events & (POLLERR | POLLHUP)) display_lost(d); + if (d->lost) { + /* A hung-up socket stays readable; stop watching it so the loop does not spin. */ + if (d->watch > 0) yasl_private_loop_del_fd(d->loop, d->watch); + d->watch = 0; return 0; } - if ((events & POLLIN) && !d->lost) { + if (events & POLLIN) { dispatch(base); flush(base); } @@ -2733,8 +2960,11 @@ static int ready(int fd, short events, void *ud) static int dispatch_timeout(struct yasl_private_display *base, int timeout_ms) { struct x11_display *d = (struct x11_display *)base; - if (unlikely(!d || !d->native || d->lost)) return -1; + if (unlikely(!d || !d->native)) { errno = ENODEV; return -1; } + /* The DISPLAY_LOST event is delivered by the wait that found the loss. */ + if (d->lost) { errno = ENOTCONN; return -1; } dispatch(base); + if (d->lost) return 0; for (struct x11_surface *s = d->surfaces; s; s = s->next) { if (s->want_draw || s->input.count || s->input.reset_pending) { timeout_ms = 0; @@ -2743,6 +2973,7 @@ static int dispatch_timeout(struct yasl_private_display *base, int timeout_ms) } if (d->loop) { XFlush(d->native); + if (d->lost) return 0; if (XEventsQueued(d->native, QueuedAlready) > 0) timeout_ms = 0; if (!XPending(d->native) && timeout_ms != 0) { @@ -2762,9 +2993,16 @@ static struct yasl_private_display *display_open(struct yasl_private_loop *loop, struct x11_display *d = calloc(1, sizeof(*d)); if (!d) return NULL; d->base.ops = &yasl_private_x11_backend; + /* GL rendering threads call Xlib, so it must be thread-safe before the first connection. */ + static bool threads_ready; + if (!threads_ready) { + if (!XInitThreads()) { free(d); errno = ENODEV; return NULL; } + threads_ready = true; + } d->native = XOpenDisplay(NULL); if (!d->native) { free(d); errno = ENODEV; return NULL; } - XSetErrorHandler(yasl_private_x11_error_handler); + XSetIOErrorExitHandler(d->native, yasl_private_x11_io_exit_handler, d); + display_register(d); d->base.native_handle = d->native; d->screen = DefaultScreen(d->native); d->scale_120 = scale_from_xft_dpi(d->native); @@ -2820,6 +3058,7 @@ static struct yasl_private_display *display_open(struct yasl_private_loop *loop, if (d->owns_loop) yasl_private_loop_destroy(d->loop); free(d->app_id); XCloseDisplay(d->native); + display_unregister(d); free(d); return NULL; } @@ -2861,6 +3100,7 @@ static struct yasl_private_display *display_open(struct yasl_private_loop *loop, if (d->im) XCloseIM(d->im); XDestroyWindow(d->native, d->selection_window); XCloseDisplay(d->native); + display_unregister(d); free(d->app_id); if (d->owns_loop) yasl_private_loop_destroy(d->loop); free(d); @@ -2891,12 +3131,15 @@ static void display_close(struct yasl_private_display *base) #endif for (size_t i = 0; i < sizeof(d->cursors) / sizeof(d->cursors[0]); i++) { if (d->cursors[i]) { - XFreeCursor(d->native, d->cursors[i]); + if (!d->lost) XFreeCursor(d->native, d->cursors[i]); d->cursors[i] = None; } } - XDestroyWindow(d->native, d->selection_window); + if (!d->lost) XDestroyWindow(d->native, d->selection_window); + /* Still registered during the close: a late IO error must reach our handlers, + * because the default handler would exit. */ XCloseDisplay(d->native); + display_unregister(d); free(d->selection[0]); free(d->selection[1]); free(d->app_id); @@ -3199,8 +3442,13 @@ static bool drag_start(struct yasl_private_display *base, struct yasl_surface *o const struct yasl_private_drag_listener *l, void *ud) { struct x11_display *d = (struct x11_display *)base; - if (!d || !origin || d->drag.active || !l) + if (!d || !origin || !l) { errno = EINVAL; return false; } + if (d->drag.active) { errno = EBUSY; return false; } + /* A drag starts from a press. Without a connection there is no pointer state to read. */ + if (d->native && !held_button(d, ((struct x11_surface *)origin)->window, NULL, NULL)) { + errno = ESTALE; return false; + } d->drag.active = true; d->drag.origin = (struct x11_surface *)origin; d->drag.carried = NULL; @@ -3276,8 +3524,7 @@ static void drag_cancel(struct yasl_private_display *base) static bool drag_carry(struct yasl_surface *base, struct yasl_private_vector2i offset) { struct x11_display *d = (struct x11_display *)base->display; - if (!d || !d->drag.active) - return false; + if (!d || !d->drag.active) { errno = EINVAL; return false; } if (d->drag.icon_window && d->native) { XDestroyWindow(d->native, d->drag.icon_window); d->drag.icon_window = None; @@ -3291,17 +3538,26 @@ static bool drag_carry(struct yasl_surface *base, struct yasl_private_vector2i o return d->native ? place(base, offset) : true; } +/* Shared checks for _NET_WM_MOVERESIZE: a held button, and a window manager that + * supports the request. Returns the held button, or 0 with errno set. */ +static unsigned moveresize_button(struct x11_display *d, struct x11_surface *s, int *rx, int *ry) +{ + unsigned button = held_button(d, s->window, rx, ry); + if (!button) errno = ESTALE; + else if (!wm_supports(d, "_NET_WM_MOVERESIZE")) { errno = ENOTSUP; button = 0; } + return button; +} + static bool interactive_move(struct yasl_surface *base) { struct x11_surface *s = (struct x11_surface *)base; struct x11_display *d = (struct x11_display *)base->display; - Window root, child; - int rx, ry, x, y; - unsigned mask; - if (s->destroyed || !d->native || !XQueryPointer(d->native, s->window, - &root, &child, &rx, &ry, &x, &y, &mask)) return false; + if (s->destroyed || !d->native) { errno = s->destroyed ? EINVAL : ENODEV; return false; } + int rx, ry; + unsigned button = moveresize_button(d, s, &rx, &ry); + if (!button) return false; XUngrabPointer(d->native, CurrentTime); - net_message(s, XInternAtom(d->native, "_NET_WM_MOVERESIZE", False), rx, ry, 8, 1, 1); + net_message(s, XInternAtom(d->native, "_NET_WM_MOVERESIZE", False), rx, ry, 8, button, 1); return true; } @@ -3309,11 +3565,7 @@ static bool interactive_resize(struct yasl_surface *base, enum yasl_private_resi { struct x11_surface *s = (struct x11_surface *)base; struct x11_display *d = (struct x11_display *)base->display; - Window root, child; - int rx, ry, x, y; - unsigned mask; - if (s->destroyed || !d->native || !XQueryPointer(d->native, s->window, - &root, &child, &rx, &ry, &x, &y, &mask)) return false; + if (s->destroyed || !d->native) { errno = s->destroyed ? EINVAL : ENODEV; return false; } int dir; switch (edge) { @@ -3325,11 +3577,14 @@ static bool interactive_resize(struct yasl_surface *base, enum yasl_private_resi case YASL_PRIVATE_EDGE_BOTTOM: dir = 5; break; case YASL_PRIVATE_EDGE_BOTTOM_LEFT: dir = 6; break; case YASL_PRIVATE_EDGE_LEFT: dir = 7; break; - default: return false; + default: errno = EINVAL; return false; } + int rx, ry; + unsigned button = moveresize_button(d, s, &rx, &ry); + if (!button) return false; XUngrabPointer(d->native, CurrentTime); - net_message(s, XInternAtom(d->native, "_NET_WM_MOVERESIZE", False), rx, ry, dir, 1, 1); + net_message(s, XInternAtom(d->native, "_NET_WM_MOVERESIZE", False), rx, ry, dir, button, 1); return true; } @@ -3337,12 +3592,28 @@ static bool show_window_menu(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 || !d->native) return false; + if (s->destroyed || !d->native) { errno = s->destroyed ? EINVAL : ENODEV; return false; } + int rx, ry; + if (!held_button(d, s->window, &rx, &ry)) { errno = ESTALE; return false; } + int device = 0; + if (!d->xi2 || !XIGetClientPointer(d->native, s->window, &device)) { errno = ENOTSUP; return false; } + /* The menu position is relative to the window; the message wants root coordinates. */ + Window child; + int root_x, root_y; + XTranslateCoordinates(d->native, s->window, RootWindow(d->native, d->screen), + pos.x, pos.y, &root_x, &root_y, &child); + XUngrabPointer(d->native, CurrentTime); Atom atom = XInternAtom(d->native, "_GTK_SHOW_WINDOW_MENU", False); - net_message(s, atom, s->window, pos.x, pos.y, 0, 0); + net_message(s, atom, device, root_x, root_y, 0, 0); return true; } +/* Only a press can move a window under the pointer here; see the ESTALE rule. */ +static bool place_under_pointer(struct yasl_surface *base, struct yasl_private_vector2i at) +{ + return place(base, at); +} + static bool surface_update_text_input(struct yasl_surface *base) { struct x11_surface *s = (struct x11_surface *)base; @@ -3423,7 +3694,7 @@ const struct yasl_private_backend_ops yasl_private_x11_backend = { .surface_frame_time_ns = frame_time, .surface_set_cursor = cursor, .surface_drag_carry = drag_carry, - .surface_place_under_pointer = place, + .surface_place_under_pointer = place_under_pointer, .surface_bell = bell, .surface_read_pixels = read_pixels, .display_double_click_ms = double_click_ms, -- cgit v1.2.3