summaryrefslogtreecommitdiff
path: root/src/platform/x11
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform/x11')
-rw-r--r--src/platform/x11/window.c371
1 files changed, 321 insertions, 50 deletions
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,