summaryrefslogtreecommitdiff
path: root/src/x11/window.c
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-12 17:12:25 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-12 17:12:25 +0300
commitb583befd7faff756ddc8d9cc201ca82f246ed09a (patch)
treeb6c2235bb6a14e951944f1837f579d9e7d24f378 /src/x11/window.c
parenta3c4628ac9a9a9a81cbef9f36a4e15dd217cb0bc (diff)
x11: optimize rendering, event loop, cursors, and multiscreen handling
- Implement MIT-SHM shared memory zero-copy presentation fallback - Install global XSetErrorHandler to suppress fatal BadWindow/BadDrawable crashes - Avoid event starvation by flushing and checking XEventsQueued in dispatch_timeout - Cache standard and hidden X11 cursors across pointer motion - Clamp popups to active monitor bounds via XRandR (XRRGetMonitors) - Fast-path DnD surface lookup via in-process surface tree - Modernize keycode resolution with XKB and ignore Lock/Mod2 mask state
Diffstat (limited to 'src/x11/window.c')
-rw-r--r--src/x11/window.c302
1 files changed, 201 insertions, 101 deletions
diff --git a/src/x11/window.c b/src/x11/window.c
index 12fc954..4b6f6fd 100644
--- a/src/x11/window.c
+++ b/src/x11/window.c
@@ -1,4 +1,5 @@
#define _POSIX_C_SOURCE 200809L
+#define _DEFAULT_SOURCE
#include "common/backend.h"
#include "common/input.h"
#include "common/timing.h"
@@ -10,6 +11,12 @@
#include <X11/cursorfont.h>
#include <X11/keysym.h>
#include <X11/extensions/XInput2.h>
+#include <X11/extensions/XShm.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#if YASL_HAS_XRANDR
+#include <X11/extensions/Xrandr.h>
+#endif
#include <xkbcommon/xkbcommon.h>
#include <assert.h>
@@ -39,6 +46,8 @@ struct x11_surface {
XIC ic;
XImage *image;
uint32_t *pixels;
+ bool shm_active;
+ XShmSegmentInfo shm_info;
struct yasl_vector2i size;
const struct yasl_surface_cbs *cbs;
void *ud;
@@ -92,6 +101,9 @@ struct x11_display {
bool owns_loop;
int xi_opcode;
bool xi2;
+ bool has_shm;
+ bool has_xkb;
+ Cursor cursors[16];
Atom abs_pressure, abs_tilt_x, abs_tilt_y, tool_type, eraser;
struct x11_pointer *pointers;
struct {
@@ -204,79 +216,30 @@ static void drag_drop_hover(struct x11_display *d)
drag_end(d, YASL_DRAG_DROPPED);
}
+static int yasl_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. */
+ return 0;
+}
+
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) {
+ if (s->destroyed || s->window == ignore_icon || s->window == ignore_carried) continue;
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;
- }
+ if (out_pos) *out_pos = (struct yasl_vector2i){tx, ty};
+ return s;
}
}
}
@@ -506,23 +469,76 @@ static bool resize(struct x11_surface *s, struct yasl_vector2i size)
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,
- (unsigned)size.x, (unsigned)size.y, 32, 0);
- uint32_t *pixels = calloc((size_t)size.x * (size_t)size.y, sizeof(*pixels));
- if (!image || !pixels) {
- if (image) XDestroyImage(image);
- free(pixels);
- return false;
+
+ if (s->shm_active) {
+ 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;
+ s->pixels = NULL;
+ s->shm_active = false;
+ } else {
+ if (s->image) XDestroyImage(s->image);
+ free(s->pixels);
+ s->image = NULL;
+ s->pixels = NULL;
+ }
+
+ XImage *image = NULL;
+ uint32_t *pixels = NULL;
+ bool shm_ok = false;
+ if (d->has_shm) {
+ size_t bytes = (size_t)size.x * (size_t)size.y * sizeof(uint32_t);
+ s->shm_info.shmid = shmget(IPC_PRIVATE, bytes, IPC_CREAT | 0600);
+ if (s->shm_info.shmid >= 0) {
+ 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,
+ s->shm_info.shmaddr, &s->shm_info,
+ (unsigned)size.x, (unsigned)size.y);
+ if (image) {
+ shmctl(s->shm_info.shmid, IPC_RMID, NULL);
+ if (XShmAttach(d->native, &s->shm_info)) {
+ pixels = (uint32_t *)s->shm_info.shmaddr;
+ memset(pixels, 0, bytes);
+ shm_ok = true;
+ } else {
+ XDestroyImage(image);
+ image = NULL;
+ shmdt(s->shm_info.shmaddr);
+ s->shm_info.shmaddr = NULL;
+ }
+ } else {
+ shmdt(s->shm_info.shmaddr);
+ s->shm_info.shmaddr = NULL;
+ shmctl(s->shm_info.shmid, IPC_RMID, NULL);
+ }
+ } else {
+ s->shm_info.shmaddr = NULL;
+ shmctl(s->shm_info.shmid, IPC_RMID, NULL);
+ }
+ }
}
- image->data = calloc((size_t)image->bytes_per_line, (size_t)size.y);
- if (!image->data) {
- XDestroyImage(image);
- free(pixels);
- return false;
+ if (!shm_ok) {
+ image = XCreateImage(d->native, DefaultVisual(d->native, d->screen),
+ (unsigned)DefaultDepth(d->native, d->screen), 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) {
+ if (image) XDestroyImage(image);
+ free(pixels);
+ return false;
+ }
+ image->data = calloc((size_t)image->bytes_per_line, (size_t)size.y);
+ if (!image->data) {
+ XDestroyImage(image);
+ free(pixels);
+ return false;
+ }
}
- if (s->image) XDestroyImage(s->image);
- free(s->pixels);
+ s->shm_active = shm_ok;
s->image = image;
s->pixels = pixels;
s->size = size;
@@ -573,8 +589,19 @@ static void surface_destroy(struct yasl_surface *base)
}
cancel_pastes(d, s);
if (s->ic) XDestroyIC(s->ic);
- if (s->image) XDestroyImage(s->image);
- free(s->pixels);
+ if (s->shm_active) {
+ 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;
+ s->pixels = NULL;
+ s->shm_active = false;
+ } else {
+ if (s->image) XDestroyImage(s->image);
+ free(s->pixels);
+ s->image = NULL;
+ s->pixels = NULL;
+ }
if (d->native) {
if (s->gc) XFreeGC(d->native, s->gc);
if (s->window) XDestroyWindow(d->native, s->window);
@@ -718,25 +745,46 @@ static void popup_calculate_position(struct x11_display *d,
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;
+ int mon_x = 0, mon_y = 0;
+ int mon_w = d->native ? DisplayWidth(d->native, d->screen) : 1920;
+ int mon_h = d->native ? DisplayHeight(d->native, d->screen) : 1080;
+
+#if YASL_HAS_XRANDR
+ if (d->native) {
+ int nmonitors = 0;
+ XRRMonitorInfo *monitors = XRRGetMonitors(d->native, RootWindow(d->native, d->screen), True, &nmonitors);
+ if (monitors && nmonitors > 0) {
+ for (int i = 0; i < nmonitors; i++) {
+ if (anchor_x >= monitors[i].x && anchor_x < monitors[i].x + monitors[i].width &&
+ anchor_y >= monitors[i].y && anchor_y < monitors[i].y + monitors[i].height) {
+ mon_x = monitors[i].x;
+ mon_y = monitors[i].y;
+ mon_w = monitors[i].width;
+ mon_h = monitors[i].height;
+ break;
+ }
+ }
+ XRRFreeMonitors(monitors);
+ }
+ }
+#endif
if (spec->beside) {
- if (root_x + spec->size.x > screen_w) {
+ if (root_x + spec->size.x > mon_x + mon_w) {
int flipped_x = anchor_x - spec->size.x + spec->content_offset.x;
- if (flipped_x >= 0) root_x = flipped_x;
+ if (flipped_x >= mon_x) root_x = flipped_x;
}
} else {
- if (root_y + spec->size.y > screen_h) {
+ if (root_y + spec->size.y > mon_y + mon_h) {
int flipped_y = anchor_y - spec->size.y + spec->content_offset.y;
- if (flipped_y >= 0) root_y = flipped_y;
+ if (flipped_y >= mon_y) 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;
+ if (root_x + spec->size.x > mon_x + mon_w) root_x = mon_x + mon_w - spec->size.x;
+ if (root_x < mon_x) root_x = mon_x;
+ if (root_y + spec->size.y > mon_y + mon_h) root_y = mon_y + mon_h - spec->size.y;
+ if (root_y < mon_y) root_y = mon_y;
*out_x = root_x;
*out_y = root_y;
@@ -1003,8 +1051,21 @@ static void key_event(struct x11_surface *s, XKeyEvent *key, bool repeat)
{
struct x11_display *d = display_of(&s->base);
d->mods = modifiers(key->state);
- KeySym sym = XLookupKeysym(key, (key->state & ShiftMask) ? 1 : 0);
- KeySym base = XLookupKeysym(key, 0), shifted = XLookupKeysym(key, 1);
+ unsigned clean_state = key->state & ~(Mod2Mask | LockMask);
+ KeySym sym = NoSymbol, base = NoSymbol, shifted = NoSymbol;
+
+ if (d->has_xkb) {
+ sym = XkbKeycodeToKeysym(d->native, key->keycode, 0, (clean_state & ShiftMask) ? 1 : 0);
+ base = XkbKeycodeToKeysym(d->native, key->keycode, 0, 0);
+ shifted = XkbKeycodeToKeysym(d->native, key->keycode, 0, 1);
+ }
+ if (sym == NoSymbol) {
+ XKeyEvent clean_key = *key;
+ clean_key.state = clean_state;
+ sym = XLookupKeysym(&clean_key, (clean_state & ShiftMask) ? 1 : 0);
+ base = XLookupKeysym(&clean_key, 0);
+ shifted = XLookupKeysym(&clean_key, 1);
+ }
char local[128];
char *text = local;
int len = 0;
@@ -1286,6 +1347,23 @@ static bool present(struct yasl_surface *base, const struct yasl_framebuffer *fb
damage->n < -1 || damage->n > YASL_DAMAGE_MAX) return false;
struct x11_display *d = display_of(base);
XImage *im = s->image;
+ if (s->shm_active) {
+ if (damage->n == -1)
+ XShmPutImage(d->native, s->window, s->gc, im, 0, 0, 0, 0,
+ (unsigned)s->size.x, (unsigned)s->size.y, False);
+ else for (int i = 0; i < damage->n; i++) {
+ struct yasl_rect2i r = damage->rects[i];
+ int64_t right = (int64_t)r.x + r.width, bottom = (int64_t)r.y + r.height;
+ int x = r.x > 0 ? r.x : 0, y = r.y > 0 ? r.y : 0;
+ int w = (int)(right < s->size.x ? right : s->size.x) - x;
+ int h = (int)(bottom < s->size.y ? bottom : s->size.y) - y;
+ if (w > 0 && h > 0)
+ XShmPutImage(d->native, s->window, s->gc, im, x, y, x, y, (unsigned)w, (unsigned)h, False);
+ }
+ s->presented = true;
+ yasl_clock_presented(&s->clock, yasl_loop_now_ns(), false);
+ return true;
+ }
bool direct = im->bits_per_pixel == 32 && im->red_mask == 0xff0000 &&
im->green_mask == 0xff00 && im->blue_mask == 0xff;
int y_start = 0, y_end = s->size.y;
@@ -1363,7 +1441,10 @@ static int ready(int fd, short events, void *ud)
{
(void)fd;
struct yasl_display *base = ud;
- if (events & POLLIN) { dispatch(base); flush(base); }
+ if (events & (POLLIN | POLLERR | POLLHUP)) {
+ dispatch(base);
+ flush(base);
+ }
return 0;
}
@@ -1379,11 +1460,15 @@ static int dispatch_timeout(struct yasl_display *base, int timeout_ms)
}
}
if (d->loop) {
+ XFlush(d->native);
+ if (XEventsQueued(d->native, QueuedAlready) > 0)
+ timeout_ms = 0;
if (!XPending(d->native) && timeout_ms != 0) {
yasl_loop_step(d->loop, timeout_ms);
dispatch(base);
} else {
yasl_loop_step(d->loop, 0);
+ dispatch(base);
}
}
return 0;
@@ -1398,8 +1483,12 @@ static struct yasl_display *display_open(struct yasl_loop *loop,
d->base.callbacks = spec && spec->cbs ? *spec->cbs : (struct yasl_display_cbs){0};
d->native = XOpenDisplay(NULL);
if (!d->native) { free(d); errno = ENODEV; return NULL; }
+ XSetErrorHandler(yasl_x11_error_handler);
d->base.native_handle = d->native;
d->screen = DefaultScreen(d->native);
+ d->has_shm = XShmQueryExtension(d->native);
+ int xkb_opcode, xkb_event, xkb_error, xkb_major = XkbMajorVersion, xkb_minor = XkbMinorVersion;
+ d->has_xkb = XkbQueryExtension(d->native, &xkb_opcode, &xkb_event, &xkb_error, &xkb_major, &xkb_minor);
int xi_event_base, xi_error_base;
int major = 2, minor = 0;
d->xi2 = XQueryExtension(d->native, "XInputExtension", &d->xi_opcode,
@@ -1489,6 +1578,12 @@ static void display_close(struct yasl_display *base)
}
if (d->im) XCloseIM(d->im);
clear_pointers(d);
+ for (size_t i = 0; i < sizeof(d->cursors) / sizeof(d->cursors[0]); i++) {
+ if (d->cursors[i]) {
+ XFreeCursor(d->native, d->cursors[i]);
+ d->cursors[i] = None;
+ }
+ }
XDestroyWindow(d->native, d->selection_window);
XCloseDisplay(d->native);
free(d->selection[0]);
@@ -1591,16 +1686,21 @@ static void cursor(struct yasl_surface *base, enum yasl_cursor cursor)
struct x11_surface *s = (struct x11_surface *)base;
if (s->destroyed || (unsigned)cursor > YASL_CURSOR_HIDDEN) return;
struct x11_display *d = display_of(base);
- Cursor native;
- if (cursor == YASL_CURSOR_HIDDEN) {
- char bits[] = {0};
- Pixmap pixmap = XCreateBitmapFromData(d->native, s->window, bits, 1, 1);
- XColor color = {0};
- native = XCreatePixmapCursor(d->native, pixmap, pixmap, &color, &color, 0, 0);
- XFreePixmap(d->native, pixmap);
- } else native = XCreateFontCursor(d->native, shapes[cursor]);
- XDefineCursor(d->native, s->window, native);
- XFreeCursor(d->native, native);
+ if (!d->native) return;
+
+ if (!d->cursors[cursor]) {
+ if (cursor == YASL_CURSOR_HIDDEN) {
+ char bits[] = {0};
+ Pixmap pixmap = XCreateBitmapFromData(d->native, s->window, bits, 1, 1);
+ XColor color = {0};
+ d->cursors[cursor] = XCreatePixmapCursor(d->native, pixmap, pixmap, &color, &color, 0, 0);
+ XFreePixmap(d->native, pixmap);
+ } else if ((unsigned)cursor < sizeof(shapes) / sizeof(shapes[0])) {
+ d->cursors[cursor] = XCreateFontCursor(d->native, shapes[cursor]);
+ }
+ }
+ if (d->cursors[cursor])
+ XDefineCursor(d->native, s->window, d->cursors[cursor]);
}
static bool place(struct yasl_surface *base, struct yasl_vector2i at)