From 87279f6903343e621c0446b20d1e7d6a495f1125 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Wed, 14 Oct 2026 13:56:48 +0300 Subject: Simplify the public API into one dense header yasl.h now holds everything except Vulkan: 45 functions instead of 62, and three installed headers instead of ten. - One constructor: yasl_surface_create takes CPU, GL or NATIVE, with zero-initialized GL options meaning 3.2 core, RGBA8, D24S8. - Events carry action, mods, x, y and text at the top level; pen positions are now logical like pointer positions. - yasl_cpu_pixels/yasl_cpu_present draw into one persistent canvas, cutting three full-frame copies per frame. - yasl_surface_get_info, yasl_surface_request, YASL_CURSOR_LOCKED, yasl_surface_set_text_input, yasl_surface_begin_move_resize and yasl_surface_get_native replace groups of narrower calls. - Interactive calls drop the decorative trigger event argument. - yasl_wait_event returns 0 only on timeout or yasl_wake, the refresh callback runs only in native modal loops, focus loss sends INPUT_RESET, and a lost event no longer fails every later wait. - Unsupported operations and bad arguments report ENOTSUP and EINVAL instead of EIO; the keycodes header no longer needs C23. - The shared library gets a versioned SONAME and version macros. Co-Authored-By: Claude Opus 5.5 --- examples/game.c | 44 +- examples/hello.c | 48 +- examples/notepad.c | 111 ++- examples/simple_window.c | 52 +- examples/tablet.c | 37 +- include/yasl/keycodes.h | 4 +- include/yasl/vulkan.h | 21 +- include/yasl/yasl.h | 596 ++++++------ meson.build | 19 +- src/common/backend.c | 71 +- src/common/backend.h | 21 +- src/common/desktop.c | 53 +- src/common/events.c | 2 + src/common/native.c | 106 +-- src/common/public_internal.h | 6 +- src/common/surface.c | 1508 ++++++++++++++----------------- src/common/types.h | 6 +- src/common/vulkan.c | 120 +-- src/platform/cocoa/keymap.h | 1 + src/platform/freedesktop/desktop.c | 1 + src/platform/wayland/keymap.h | 1 + src/platform/wayland/surface.c | 2 + src/platform/x11/keymap.h | 1 + tests/common/test_gl.c | 35 +- tests/common/test_surface_integration.c | 316 ++++--- tests/common/test_vulkan.c | 17 +- tests/meson.build | 9 +- tests/test_api.cpp | 27 +- tests/test_lifecycle.c | 38 +- tests/wayland/test_reconnect.c | 41 +- tests/x11/test_tablet_native.c | 48 +- 31 files changed, 1531 insertions(+), 1831 deletions(-) diff --git a/examples/game.c b/examples/game.c index 850b39b..89b9f3a 100644 --- a/examples/game.c +++ b/examples/game.c @@ -1,8 +1,8 @@ /* GPU example: the application owns context binding, drawing and swap pacing. */ #include -#include -#include +#include #include +#include #include typedef void (*clear_color_fn)(float, float, float, float); @@ -12,11 +12,10 @@ typedef void (*viewport_fn)(int, int, int, int); int main(int argc, char **argv) { bool smoke = argc == 2 && !strcmp(argv[1], "--smoke-test"); - yasl_surface *s = yasl_gl_surface_create(&(struct yasl_surface_options){.title="YASL OpenGL", .width=800, .height=600, .type=YASL_SURFACE_NATIVE, .flags=0}, NULL); - if (!s || (yasl_gl_make_current(s) < 0)) { - const char *error; - error = strerror(errno); - fprintf(stderr, "%s\n", error); + yasl_surface *s = yasl_surface_create(&(struct yasl_surface_options){ + .title = "YASL OpenGL", .width = 800, .height = 600, .type = YASL_SURFACE_GL}); + if (!s || yasl_gl_make_current(s) < 0) { + fprintf(stderr, "%s\n", strerror(errno)); yasl_shutdown(); return 1; } @@ -34,23 +33,34 @@ int main(int argc, char **argv) /* Drain events before each frame; while the display is gone, block until it returns. */ yasl_event e; int n = yasl_wait_event(&e, online ? 0 : -1); - if (n < 0) { result = 1; break; } + if (n < 0) { + result = 1; + break; + } if (e.type == YASL_EVENT_CLOSE || e.type == YASL_EVENT_QUIT || - (e.type == YASL_EVENT_KEY && e.key.key == YASL_KEY_ESCAPE)) running = false; - if (e.type == YASL_EVENT_DISPLAY_LOST) online = false; + (e.type == YASL_EVENT_KEY && e.key.key == YASL_KEY_ESCAPE)) + running = false; + if (e.type == YASL_EVENT_DISPLAY_LOST) { + yasl_gl_make_current(NULL); + online = false; + } if (e.type == YASL_EVENT_DISPLAY_RESTORED) { /* The context is new: bind it again and restore GL state here. */ - online = (yasl_gl_make_current(s) == 0) && (yasl_gl_set_swap_interval(s, 1) == 0); + online = yasl_gl_make_current(s) == 0 && yasl_gl_set_swap_interval(s, 1) == 0; } - if (n > 0 || !online || !running) continue; - int w, h; - yasl_surface_get_framebuffer_size(s, &w, &h); - viewport(0, 0, w, h); + if (n > 0 || !online || !running) + continue; + struct yasl_surface_info info = yasl_surface_get_info(s); + viewport(0, 0, info.pixel_width, info.pixel_height); double t = yasl_time_ns() / 1e9; clear_color((float)(0.3 + 0.2 * sin(t)), 0.2f, 0.4f, 1); clear(0x00004000u); - if ((yasl_gl_present(s) < 0)) { result = 1; break; } - if (smoke && ++frames == 3) break; + if (yasl_gl_present(s) < 0) { + result = 1; + break; + } + if (smoke && ++frames == 3) + break; } yasl_gl_make_current(NULL); yasl_shutdown(); diff --git a/examples/hello.c b/examples/hello.c index 054051d..d17dd39 100644 --- a/examples/hello.c +++ b/examples/hello.c @@ -1,50 +1,30 @@ /* A CPU-rendered window. */ #include -#include int main(void) { - struct yasl_surface_options options = { - .title = "Hello YASL", - .width = 640, - .height = 480, - .type = YASL_SURFACE_CPU, - }; - yasl_surface *surface = yasl_surface_create(&options); + yasl_surface *surface = yasl_surface_create( + &(struct yasl_surface_options){.title = "Hello YASL", .width = 640, .height = 480}); if (!surface) return 1; - - int result = 0; - for (;;) { - yasl_event event; - if (yasl_wait_event(&event, -1) < 0) { - result = 1; - break; - } + yasl_event event; + while (yasl_wait_event(&event, -1) >= 0) { if (event.type == YASL_EVENT_CLOSE || event.type == YASL_EVENT_QUIT) break; if (event.type != YASL_EVENT_REFRESH) continue; - - struct yasl_cpu_buffer buffer; - if (yasl_cpu_acquire(surface, &buffer) < 0) { - result = 1; + int w, h; + uint32_t *pixels = yasl_cpu_pixels(surface, &w, &h); + if (!pixels) break; - } - for (int y = 0; y < buffer.height; y++) { - uint32_t *row = (uint32_t *)((unsigned char *)buffer.pixels + y * buffer.stride_bytes); - for (int x = 0; x < buffer.width; x++) { + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) { /* Opaque premultiplied ARGB: red grows rightward, green downward. */ - uint32_t r = (uint32_t)(x * 255 / (buffer.width > 1 ? buffer.width - 1 : 1)); - uint32_t g = (uint32_t)(y * 255 / (buffer.height > 1 ? buffer.height - 1 : 1)); - row[x] = 0xff000000 | r << 16 | g << 8 | (255 - r); + uint32_t r = (uint32_t)(x * 255 / (w > 1 ? w - 1 : 1)); + uint32_t g = (uint32_t)(y * 255 / (h > 1 ? h - 1 : 1)); + pixels[(size_t)y * w + x] = 0xff000000 | r << 16 | g << 8 | (255 - r); } - } - if (yasl_cpu_present(surface, &buffer) < 0) { - result = 1; - break; - } + yasl_cpu_present(surface); } - yasl_shutdown(); - return result; + return yasl_shutdown() < 0; } diff --git a/examples/notepad.c b/examples/notepad.c index ff12038..d0b38b9 100644 --- a/examples/notepad.c +++ b/examples/notepad.c @@ -19,7 +19,6 @@ #define _DEFAULT_SOURCE #include -#include #include #include @@ -988,10 +987,9 @@ static void prompt_execute(struct editor *ed) static bool editor_init(struct editor *ed) { - ed->surface = yasl_surface_create(&(struct yasl_surface_options){.title="Untitled - YASL Notepad", .width=960, .height=640, .type=YASL_SURFACE_CPU, .flags=0}); + ed->surface = yasl_surface_create(&(struct yasl_surface_options){.title="Untitled - YASL Notepad", .width=960, .height=640}); if (!ed->surface) return false; - ed->surface_scale = yasl_surface_get_scale(ed->surface); - yasl_surface_set_text_input(ed->surface, true, YASL_TEXT_NORMAL); + ed->surface_scale = yasl_surface_get_info(ed->surface).scale; ed->worker_running = true; if (pthread_mutex_init(&ed->worker_mutex, NULL)) return false; @@ -1034,19 +1032,21 @@ static void editor_update_ime(struct editor *ed) ed->ime_snapshot_ready = true; ed->text_revision++; } - if (snapshot_changed) - yasl_surface_set_surrounding_text(ed->surface, ed->ime_snapshot, strlen(ed->ime_snapshot), - ed->ime_begin, ed->ime_end, ed->text_revision); - double scale = yasl_surface_get_scale(ed->surface); + double scale = yasl_surface_get_info(ed->surface).scale; int zoom = (scale >= 2.0 ? 2 : 1) + ed->zoom_offset; if (zoom < 1) zoom = 1; double caret_x = (48.0 * zoom + 8.0 * zoom + ed->cursor_col * 8.0 * zoom) / scale; double caret_y = (24.0 * zoom + (ed->cursor_row - ed->scroll_y) * 16.0 * zoom) / scale; - if (!ed->ime_caret_ready || caret_x != ed->ime_caret_x || caret_y != ed->ime_caret_y) { + if (snapshot_changed || !ed->ime_caret_ready || caret_x != ed->ime_caret_x || + caret_y != ed->ime_caret_y) { ed->ime_caret_x = caret_x; ed->ime_caret_y = caret_y; ed->ime_caret_ready = true; - yasl_surface_set_ime_caret(ed->surface, caret_x, caret_y, 2.0, 16.0); + yasl_surface_set_text_input(ed->surface, &(struct yasl_text_input){ + .text = ed->ime_snapshot, .length = strlen(ed->ime_snapshot), + .selection_begin = ed->ime_begin, .selection_end = ed->ime_end, + .revision = ed->text_revision, .caret_x = caret_x, .caret_y = caret_y, + .caret_width = 2.0, .caret_height = 16.0}); } } @@ -1055,49 +1055,49 @@ static void on_event(struct editor *ed, const yasl_event *ev) switch (ev->type) { case YASL_EVENT_TEXT: if (ed->prompt != PROMPT_NONE) { - for (size_t i = 0; i < ev->text.length && ed->prompt_len < (int)sizeof(ed->prompt_buf) - 1; i++) { - if (ev->text.data[i] >= 32 && ev->text.data[i] <= 126) { - ed->prompt_buf[ed->prompt_len++] = ev->text.data[i]; + for (size_t i = 0; i < ev->length && ed->prompt_len < (int)sizeof(ed->prompt_buf) - 1; i++) { + if (ev->text[i] >= 32 && ev->text[i] <= 126) { + ed->prompt_buf[ed->prompt_len++] = ev->text[i]; ed->prompt_buf[ed->prompt_len] = '\0'; } } } else { - insert_text(ed, ev->text.data, ev->text.length); + insert_text(ed, ev->text, ev->length); } editor_request_render(ed); break; case YASL_EVENT_CLIPBOARD: - if (ev->clipboard.status != YASL_TRANSFER_OK) { - editor_set_status(ed, ev->clipboard.status == YASL_TRANSFER_UNAVAILABLE + if (ev->action != YASL_TRANSFER_OK) { + editor_set_status(ed, ev->action == YASL_TRANSFER_UNAVAILABLE ? "Clipboard has no text" : "Clipboard transfer failed"); editor_request_render(ed); break; } - if (ev->clipboard.data && ev->clipboard.length > 0) { - insert_text(ed, ev->clipboard.data, ev->clipboard.length); + if (ev->text && ev->length > 0) { + insert_text(ed, ev->text, ev->length); editor_request_render(ed); } break; case YASL_EVENT_TEXT_EDIT: - if (ed->prompt == PROMPT_NONE && ev->text_edit.data && - ev->text_edit.revision == ed->text_revision && - ev->text_edit.begin <= ev->text_edit.end && - ev->text_edit.end <= strlen(ed->ime_snapshot)) { - ed->cursor_col = (int)ev->text_edit.begin; + if (ed->prompt == PROMPT_NONE && ev->text && + ev->ime.revision == ed->text_revision && + ev->ime.begin <= ev->ime.end && + ev->ime.end <= strlen(ed->ime_snapshot)) { + ed->cursor_col = (int)ev->ime.begin; ed->sel_anchor_row = ed->cursor_row; - ed->sel_anchor_col = (int)ev->text_edit.begin; - ed->cursor_col = (int)ev->text_edit.end; - ed->has_selection = ev->text_edit.begin != ev->text_edit.end; - insert_text(ed, ev->text_edit.data, ev->text_edit.length); + ed->sel_anchor_col = (int)ev->ime.begin; + ed->cursor_col = (int)ev->ime.end; + ed->has_selection = ev->ime.begin != ev->ime.end; + insert_text(ed, ev->text, ev->length); editor_request_render(ed); } break; case YASL_EVENT_DROP: - if (ev->drop.action == YASL_DROP_DATA) { - const char *cursor = ev->drop.data; + if (ev->action == YASL_DROP_RELEASE) { + const char *cursor = ev->text; char path[512]; size_t length; while ((length = yasl_drop_next_path(&cursor, path, sizeof(path))) > 0) { @@ -1111,9 +1111,9 @@ static void on_event(struct editor *ed, const yasl_event *ev) break; case YASL_EVENT_KEY: - if (ev->key.action == YASL_KEY_PRESS || ev->key.action == YASL_KEY_REPEAT) { - bool ctrl = (ev->key.mods & (YASL_MOD_CTRL | YASL_MOD_SUPER)) != 0; - bool shift = (ev->key.mods & YASL_MOD_SHIFT) != 0; + if (ev->action == YASL_KEY_PRESS || ev->action == YASL_KEY_REPEAT) { + bool ctrl = (ev->mods & (YASL_MOD_CTRL | YASL_MOD_SUPER)) != 0; + bool shift = (ev->mods & YASL_MOD_SHIFT) != 0; /* Interactive Prompt Mode */ if (ed->prompt != PROMPT_NONE) { @@ -1219,7 +1219,7 @@ static void on_event(struct editor *ed, const yasl_event *ev) editor_request_render(ed); return; case YASL_KEY_V: - if (!yasl_clipboard_request(ed->surface, YASL_CLIPBOARD)) + if (yasl_clipboard_request(ed->surface, YASL_CLIPBOARD) < 0) editor_set_status(ed, "Clipboard paste failed"); return; case YASL_KEY_Z: @@ -1341,10 +1341,10 @@ static void on_event(struct editor *ed, const yasl_event *ev) break; case YASL_EVENT_POINTER: { - double device_scale = yasl_surface_get_scale(ed->surface); - int px = (int)lround(ev->pointer.x * device_scale); - int py = (int)lround(ev->pointer.y * device_scale); - if (ev->pointer.action == YASL_POINTER_DOWN && ev->pointer.button == YASL_BUTTON_PRIMARY) { + double device_scale = yasl_surface_get_info(ed->surface).scale; + int px = (int)lround(ev->x * device_scale); + int py = (int)lround(ev->y * device_scale); + if (ev->action == YASL_POINTER_DOWN && ev->pointer.button == YASL_BUTTON_PRIMARY) { int scale = (device_scale >= 2.0 ? 2 : 1) + ed->zoom_offset; if (scale < 1) scale = 1; int char_w = 8 * scale; @@ -1395,11 +1395,11 @@ static void on_event(struct editor *ed, const yasl_event *ev) } ed->caret_visible = true; editor_request_render(ed); - } else if (ev->pointer.action == YASL_POINTER_UP && ev->pointer.button == YASL_BUTTON_PRIMARY) { + } else if (ev->action == YASL_POINTER_UP && ev->pointer.button == YASL_BUTTON_PRIMARY) { ed->mouse_dragging = false; - } else if (ev->pointer.action == YASL_POINTER_LEAVE) { + } else if (ev->action == YASL_POINTER_LEAVE) { ed->mouse_dragging = false; - } else if (ev->pointer.action == YASL_POINTER_MOVE && ed->mouse_dragging) { + } else if (ev->action == YASL_POINTER_MOVE && ed->mouse_dragging) { int scale = (device_scale >= 2.0 ? 2 : 1) + ed->zoom_offset; if (scale < 1) scale = 1; int char_w = 8 * scale; @@ -1427,27 +1427,26 @@ static void on_event(struct editor *ed, const yasl_event *ev) yasl_event pointer = {0}; pointer.type = YASL_EVENT_POINTER; pointer.surface = ev->surface; - pointer.pointer.x = ev->pen.x; - pointer.pointer.y = ev->pen.y; + pointer.x = ev->x; + pointer.y = ev->y; pointer.pointer.button = YASL_BUTTON_PRIMARY; - pointer.pointer.mods = 0; - switch (ev->pen.action) { - case YASL_PEN_DOWN: pointer.pointer.action = YASL_POINTER_DOWN; break; - case YASL_PEN_UP: pointer.pointer.action = YASL_POINTER_UP; break; + switch (ev->action) { + case YASL_PEN_DOWN: pointer.action = YASL_POINTER_DOWN; break; + case YASL_PEN_UP: pointer.action = YASL_POINTER_UP; break; case YASL_PEN_LEAVE: - case YASL_PEN_CANCEL: pointer.pointer.action = YASL_POINTER_LEAVE; break; - default: pointer.pointer.action = YASL_POINTER_MOVE; break; + case YASL_PEN_CANCEL: pointer.action = YASL_POINTER_LEAVE; break; + default: pointer.action = YASL_POINTER_MOVE; break; } on_event(ed, &pointer); break; } case YASL_EVENT_SCROLL: - if (ev->scroll.mods & (YASL_MOD_CTRL | YASL_MOD_SUPER)) { + if (ev->mods & (YASL_MOD_CTRL | YASL_MOD_SUPER)) { /* Ctrl + Wheel = Zoom */ if (ev->scroll.lines_y > 0 && ed->zoom_offset < 2) ed->zoom_offset++; else if (ev->scroll.lines_y < 0 && ed->zoom_offset > -1) ed->zoom_offset--; - } else if (ev->scroll.mods & YASL_MOD_SHIFT) { + } else if (ev->mods & YASL_MOD_SHIFT) { /* Shift + Wheel = Horizontal scroll */ ed->scroll_x -= (int)(ev->scroll.lines_y * 3.0); if (ed->scroll_x < 0) ed->scroll_x = 0; @@ -1483,13 +1482,11 @@ static void on_event(struct editor *ed, const yasl_event *ev) static void editor_render(struct editor *ed) { - struct yasl_cpu_buffer buffer; - int acquired = yasl_cpu_acquire(ed->surface, &buffer); - int w = buffer.width, h = buffer.height; - uint32_t *pix = acquired == 0 ? buffer.pixels : NULL; + int w, h; + uint32_t *pix = yasl_cpu_pixels(ed->surface, &w, &h); if (!pix) return; - double display_scale = yasl_surface_get_scale(ed->surface); + double display_scale = yasl_surface_get_info(ed->surface).scale; int scale = (display_scale >= 2.0 ? 2 : 1) + ed->zoom_offset; if (scale < 1) scale = 1; int char_w = 8 * scale; @@ -1626,7 +1623,7 @@ static void editor_render(struct editor *ed) ed->is_modified ? 0xfffab387 : status_accent, scale); } - if ((yasl_cpu_present(ed->surface, &buffer) < 0)) { + if (yasl_cpu_present(ed->surface) < 0) { fprintf(stderr, "Could not present CPU image\n"); ed->failed = true; ed->running = false; @@ -1699,7 +1696,7 @@ int main(int argc, char **argv) } } - if ((yasl_init(&(struct yasl_options){.app_id="org.yasl.notepad"}) < 0) || !editor_init(&ed)) { + if (yasl_init(YASL_BACKEND_AUTO, "org.yasl.notepad") < 0 || !editor_init(&ed)) { fprintf(stderr, "Could not initialize YASL Notepad\n"); editor_shutdown(&ed); yasl_shutdown(); diff --git a/examples/simple_window.c b/examples/simple_window.c index 4b4b552..f85abd4 100644 --- a/examples/simple_window.c +++ b/examples/simple_window.c @@ -1,51 +1,57 @@ /* Mouse and pen input on a CPU surface. */ #include -#include #include int main(int argc, char **argv) { bool smoke = argc == 2 && !strcmp(argv[1], "--smoke-test"); - yasl_surface *s = yasl_surface_create(&(struct yasl_surface_options){.title="YASL input", .width=640, .height=480, .type=YASL_SURFACE_CPU, .flags=0}); - if (!s) return 1; + yasl_surface *s = yasl_surface_create( + &(struct yasl_surface_options){.title = "YASL input", .width = 640, .height = 480}); + if (!s) + return 1; double px = -100, py = -100, pressure = 0; bool dirty = false; int frames = 0, result = 1; yasl_event e; for (;;) { int n = yasl_wait_event(&e, smoke ? 20 : -1); - if (n < 0) break; + if (n < 0) + break; if (e.type == YASL_EVENT_CLOSE || e.type == YASL_EVENT_QUIT || - (e.type == YASL_EVENT_KEY && e.key.key == YASL_KEY_ESCAPE)) { result = 0; break; } - if (e.type == YASL_EVENT_POINTER) { - px = e.pointer.x; py = e.pointer.y; - pressure = (e.pointer.buttons & YASL_BUTTON_BIT(YASL_BUTTON_PRIMARY)) ? 0.5 : 0; - dirty = true; + (e.type == YASL_EVENT_KEY && e.key.key == YASL_KEY_ESCAPE)) { + result = 0; + break; } - if (e.type == YASL_EVENT_PEN) { - px = e.pen.x; py = e.pen.y; - pressure = (e.pen.axes_valid & YASL_PEN_PRESSURE) ? e.pen.pressure : 0; + if (e.type == YASL_EVENT_POINTER || e.type == YASL_EVENT_PEN) { + px = e.x; + py = e.y; + pressure = e.type == YASL_EVENT_PEN ? ((e.pen.axes & YASL_PEN_PRESSURE) ? e.pen.pressure : 0) + : (e.pointer.buttons & YASL_BUTTON_BIT(YASL_BUTTON_PRIMARY)) ? 0.5 + : 0; dirty = true; } - if (e.type == YASL_EVENT_REFRESH) dirty = true; + if (e.type == YASL_EVENT_REFRESH) + dirty = true; if (dirty) { - struct yasl_cpu_buffer buffer; - int acquired = yasl_cpu_acquire(s, &buffer); - int w = buffer.width, h = buffer.height; - uint32_t *pixels = acquired == 0 ? buffer.pixels : NULL; - if (!pixels) break; - double scale = yasl_surface_get_scale(s), radius = (8 + pressure * 48) * scale; - for (int y = 0; y < h; y++) { + int w, h; + uint32_t *pixels = yasl_cpu_pixels(s, &w, &h); + if (!pixels) + break; + double scale = yasl_surface_get_info(s).scale, radius = (8 + pressure * 48) * scale; + for (int y = 0; y < h; y++) for (int x = 0; x < w; x++) { double dx = x - px * scale, dy = y - py * scale; pixels[(size_t)y * w + x] = dx * dx + dy * dy < radius * radius ? 0xffffc060u : 0xff203040u; } - } - if ((yasl_cpu_present(s, &buffer) < 0)) break; + if (yasl_cpu_present(s) < 0) + break; dirty = false; frames++; } - if (smoke && frames && n == 0) { result = 0; break; } + if (smoke && frames && n == 0) { + result = 0; + break; + } } yasl_shutdown(); return result; diff --git a/examples/tablet.c b/examples/tablet.c index ce796a4..8c62fc0 100644 --- a/examples/tablet.c +++ b/examples/tablet.c @@ -1,6 +1,5 @@ /* Pen canvas: pressure, eraser and coherent strokes drawn straight into the CPU surface. */ #include -#include #include #include @@ -24,11 +23,10 @@ static void stamp(uint32_t *pixels, int w, int h, double x, double y, double rad } /* Paints paper over the area a resize added, or everything when clear is set. */ -static uint32_t *canvas(yasl_surface *s, int *w, int *h, int *painted_w, int *painted_h, bool clear, struct yasl_cpu_buffer *buffer) +static uint32_t *canvas(yasl_surface *s, int *w, int *h, int *painted_w, int *painted_h, bool clear) { - if (yasl_cpu_acquire(s, buffer) < 0) return NULL; - *w = buffer->width; *h = buffer->height; - uint32_t *pixels = buffer->pixels; + uint32_t *pixels = yasl_cpu_pixels(s, w, h); + if (!pixels) return NULL; if (clear) *painted_w = *painted_h = 0; for (int y = 0; y < *h; y++) for (int x = y < *painted_h ? *painted_w : 0; x < *w; x++) pixels[(size_t)y * *w + x] = PAPER; @@ -44,11 +42,11 @@ int main(int argc, char **argv) if (!strcmp(argv[i], "--smoke-test")) smoke = true; else if (!strcmp(argv[i], "--backend") && i + 1 < argc) { const char *b = argv[++i]; - if (!(yasl_init(&(struct yasl_options){.backend=!strcmp(b, "wayland") ? YASL_BACKEND_WAYLAND : - !strcmp(b, "x11") ? YASL_BACKEND_X11 : YASL_BACKEND_COCOA}) == 0)) return 1; + if (yasl_init(!strcmp(b, "wayland") ? YASL_BACKEND_WAYLAND : + !strcmp(b, "x11") ? YASL_BACKEND_X11 : YASL_BACKEND_COCOA, NULL) < 0) return 1; } else return 1; } - yasl_surface *s = yasl_surface_create(&(struct yasl_surface_options){.title="YASL pen canvas — C clears, Escape exits", .width=800, .height=600, .type=YASL_SURFACE_CPU, .flags=0}); + yasl_surface *s = yasl_surface_create(&(struct yasl_surface_options){.title="YASL pen canvas — C clears, Escape exits", .width=800, .height=600}); if (!s) return 1; int painted_w = 0, painted_h = 0, frames = 0, result = 1; bool contact = false; @@ -58,26 +56,26 @@ int main(int argc, char **argv) int n = yasl_wait_event(&e, smoke ? 20 : -1); if (n < 0) break; if (smoke && frames && n == 0) { result = 0; break; } - bool escape = e.type == YASL_EVENT_KEY && e.key.action == YASL_KEY_PRESS && e.key.key == YASL_KEY_ESCAPE; + bool escape = e.type == YASL_EVENT_KEY && e.action == YASL_KEY_PRESS && e.key.key == YASL_KEY_ESCAPE; if (e.type == YASL_EVENT_CLOSE || e.type == YASL_EVENT_QUIT || escape) { result = 0; break; } bool clear = false, draw = false, erase = false, down = false, present = e.type == YASL_EVENT_REFRESH; double x = 0, y = 0, p = 0.5; switch (e.type) { case YASL_EVENT_KEY: - if (e.key.action == YASL_KEY_PRESS && e.key.key == YASL_KEY_C) clear = present = true; + if (e.action == YASL_KEY_PRESS && e.key.key == YASL_KEY_C) clear = present = true; break; case YASL_EVENT_PEN: - x = e.pen.x; y = e.pen.y; - p = (e.pen.axes_valid & YASL_PEN_PRESSURE) ? e.pen.pressure : 0.5; + x = e.x; y = e.y; + p = (e.pen.axes & YASL_PEN_PRESSURE) ? e.pen.pressure : 0.5; erase = e.pen.tool == YASL_PEN_ERASER; down = e.pen.in_contact; - draw = down && (e.pen.action == YASL_PEN_DOWN || e.pen.action == YASL_PEN_MOVE); - if (e.pen.action == YASL_PEN_LEAVE || e.pen.action == YASL_PEN_CANCEL) contact = false; + draw = down && (e.action == YASL_PEN_DOWN || e.action == YASL_PEN_MOVE); + if (e.action == YASL_PEN_LEAVE || e.action == YASL_PEN_CANCEL) contact = false; break; case YASL_EVENT_POINTER: - x = e.pointer.x; y = e.pointer.y; + x = e.x; y = e.y; down = (e.pointer.buttons & YASL_BUTTON_BIT(YASL_BUTTON_PRIMARY)) != 0; - draw = down && (e.pointer.action == YASL_POINTER_DOWN || e.pointer.action == YASL_POINTER_MOVE); + draw = down && (e.action == YASL_POINTER_DOWN || e.action == YASL_POINTER_MOVE); break; case YASL_EVENT_INPUT_RESET: contact = false; @@ -88,12 +86,11 @@ int main(int argc, char **argv) if (draw || present) { int w, h; int old_w = painted_w, old_h = painted_h; - struct yasl_cpu_buffer buffer; - uint32_t *pixels = canvas(s, &w, &h, &painted_w, &painted_h, clear, &buffer); + uint32_t *pixels = canvas(s, &w, &h, &painted_w, &painted_h, clear); if (!pixels) break; if (w != old_w || h != old_h) contact = false; if (draw) { - double scale = yasl_surface_get_scale(s); + double scale = yasl_surface_get_info(s).scale; x *= scale; y *= scale; double radius = (1.5 + 12 * p) * scale, dx = contact ? x - last_x : 0, dy = contact ? y - last_y : 0; int steps = (int)ceil(hypot(dx, dy) / (radius * 0.4)); @@ -101,7 +98,7 @@ int main(int argc, char **argv) for (int i = 1; i <= steps; i++) stamp(pixels, w, h, x - dx + dx * i / steps, y - dy + dy * i / steps, radius, erase); last_x = x; last_y = y; } - if ((yasl_cpu_present(s, &buffer) < 0)) break; + if (yasl_cpu_present(s) < 0) break; frames++; } if (e.type == YASL_EVENT_PEN || e.type == YASL_EVENT_POINTER) contact = down; diff --git a/include/yasl/keycodes.h b/include/yasl/keycodes.h index afa3eed..e14933b 100644 --- a/include/yasl/keycodes.h +++ b/include/yasl/keycodes.h @@ -3,9 +3,7 @@ #ifndef YASL_KEYCODES_H #define YASL_KEYCODES_H -#include - -enum yasl_key : uint32_t { +enum yasl_key { YASL_KEY_UNKNOWN = 0x000, /**< No portable identity; native_code may still identify the key. */ YASL_KEY_SPACE = 0x020, YASL_KEY_APOSTROPHE = 0x027, diff --git a/include/yasl/vulkan.h b/include/yasl/vulkan.h index 1a1c5be..079c866 100644 --- a/include/yasl/vulkan.h +++ b/include/yasl/vulkan.h @@ -1,4 +1,4 @@ -/** @file Vulkan WSI; YASL tracks the WSI handle, the application owns rendering. */ +/** @file Vulkan window surfaces; the application owns devices and swapchains. */ #ifndef YASL_VULKAN_H #define YASL_VULKAN_H #include "yasl/yasl.h" @@ -6,26 +6,21 @@ #ifdef __cplusplus extern "C" { #endif -YASL_API int yasl_vulkan_init(PFN_vkGetInstanceProcAddr loader); +/** Instance extensions and flags YASL needs, borrowed until shutdown. Merge + * them into your VkInstanceCreateInfo. */ struct yasl_vulkan_requirements { const char *const *extensions; uint32_t extension_count; VkInstanceCreateFlags flags; }; -/** Initialize the loader first. Borrowed extensions live through shutdown; - * merge them with application extensions before creating the instance. - * Clears out on failure. */ -YASL_API int yasl_vulkan_get_requirements(const yasl_surface *surface, - struct yasl_vulkan_requirements *out); -/** WSI creation retains VkResult semantics. Uses the injected loader. - * One live VkSurfaceKHR per window; destroy it with yasl_vulkan_destroy_surface - * before destroying the YASL surface. The application owns swapchains/devices. */ +/** Records the loader and reports the requirements, initializing YASL if needed. + * Call before creating the instance. EBUSY while a Vulkan surface exists. */ +YASL_API int yasl_vulkan_init(PFN_vkGetInstanceProcAddr loader, struct yasl_vulkan_requirements *out); +/** One surface per NATIVE window; returns the VkResult, with errno set on failure. */ YASL_API VkResult yasl_vulkan_create_surface(yasl_surface *surface, VkInstance instance, const VkAllocationCallbacks *allocator, VkSurfaceKHR *out); -/** Destroy the WSI surface (including while disconnected), releasing the window - * for another renderer. Destroy dependent swapchains first. Uses the instance - * and allocation callbacks recorded at creation. No live WSI surface is a no-op. */ +/** Destroy swapchains first. Also works after DISPLAY_LOST; no surface is a no-op. */ YASL_API int yasl_vulkan_destroy_surface(yasl_surface *surface); #ifdef __cplusplus } diff --git a/include/yasl/yasl.h b/include/yasl/yasl.h index 284039a..6d2b4f0 100644 --- a/include/yasl/yasl.h +++ b/include/yasl/yasl.h @@ -1,8 +1,12 @@ -/** @file Windows, input and events. - * Commands return 0 on success or a negative errno value on failure. Constructors - * return NULL and set errno on failure; data queries document their sentinel. - * Use the initializing thread (main on Cocoa), except where stated otherwise. - * Coordinates are logical units unless named pixel. */ +/** @file YASL: desktop windows, input and events for application-owned rendering. + * + * Calls return 0 or a negative errno; constructors return NULL and set errno; + * queries name their fallback. Errors: EINVAL bad argument, ENOTSUP not + * supported here, EPERM wrong thread, ENODEV not initialized, ENOTCONN display + * lost, EBUSY not allowed now, ESTALE needs a pointer press in progress. + * Call from the thread that initialized YASL (the main thread on macOS) unless + * a function says it is thread-safe. Positions and sizes are logical units; + * multiply by the scale for pixels. Names with "pixel" are in pixels. */ #ifndef YASL_H #define YASL_H #include @@ -15,364 +19,310 @@ #else #define YASL_API #endif +#define YASL_VERSION_MAJOR 0 +#define YASL_VERSION_MINOR 2 +#define YASL_VERSION_PATCH 0 #ifdef __cplusplus extern "C" { #endif typedef struct yasl_surface yasl_surface; typedef struct yasl_event yasl_event; + +/* Lifecycle */ enum yasl_backend { YASL_BACKEND_AUTO, YASL_BACKEND_WAYLAND, YASL_BACKEND_X11, YASL_BACKEND_COCOA }; -enum yasl_surface_type { YASL_SURFACE_NATIVE, YASL_SURFACE_CPU }; -enum yasl_surface_flags { - YASL_SURFACE_HIDDEN = 2u, - YASL_SURFACE_UNDECORATED = 4u, - YASL_SURFACE_FIXED_SIZE = 8u -}; -enum yasl_selection { YASL_CLIPBOARD, YASL_PRIMARY }; -enum yasl_key_action { YASL_KEY_PRESS, YASL_KEY_RELEASE, YASL_KEY_REPEAT }; -enum yasl_mod { - YASL_MOD_SHIFT = 1u, - YASL_MOD_ALT = 2u, - YASL_MOD_CTRL = 4u, - YASL_MOD_SUPER = 8u, - YASL_MOD_CAPS = 16u -}; -enum yasl_pointer_action { - YASL_POINTER_MOVE, - YASL_POINTER_DOWN, - YASL_POINTER_UP, - YASL_POINTER_LEAVE -}; -enum yasl_pointer_mode { YASL_POINTER_NORMAL, YASL_POINTER_RELATIVE }; -enum yasl_button { - YASL_BUTTON_PRIMARY, - YASL_BUTTON_MIDDLE, - YASL_BUTTON_SECONDARY, - YASL_BUTTON_BACK, - YASL_BUTTON_FORWARD -}; -#define YASL_BUTTON_BIT(button) (1u << (button)) -enum yasl_pen_tool { - YASL_PEN_UNKNOWN, - YASL_PEN_PEN, - YASL_PEN_ERASER, - YASL_PEN_BRUSH, - YASL_PEN_AIRBRUSH, - YASL_PEN_PENCIL -}; -enum yasl_pen_action { - YASL_PEN_ENTER, - YASL_PEN_LEAVE, - YASL_PEN_MOVE, - YASL_PEN_DOWN, - YASL_PEN_UP, - YASL_PEN_BUTTON, - YASL_PEN_CANCEL -}; -enum yasl_pen_axis { - YASL_PEN_PRESSURE = 1u, - YASL_PEN_TILT = 2u, - YASL_PEN_ROTATION = 4u, - YASL_PEN_DISTANCE = 8u, - YASL_PEN_TANGENTIAL = 16u, - YASL_PEN_WHEEL = 32u -}; -enum yasl_scroll_valid { YASL_SCROLL_LINES = 1u, YASL_SCROLL_PIXELS = 2u }; -enum yasl_scroll_source { - YASL_SCROLL_UNKNOWN, - YASL_SCROLL_WHEEL, - YASL_SCROLL_FINGER, - YASL_SCROLL_CONTINUOUS +enum yasl_feature { + YASL_FEATURE_POPUPS = 1, YASL_FEATURE_DRAG = 2, YASL_FEATURE_DRAG_CARRY = 4, + YASL_FEATURE_PLACE_WINDOW = 8, YASL_FEATURE_PRIMARY_SELECTION = 16, YASL_FEATURE_TABLET = 32, + YASL_FEATURE_TEXT_INPUT = 64, YASL_FEATURE_LOCKED_CURSOR = 128, YASL_FEATURE_OPENGL = 256, + YASL_FEATURE_VULKAN = 512 +}; +/** Optional: the first surface initializes with AUTO and no app ID. app_id is a + * reverse-DNS name for window grouping and notifications. EALREADY once + * initialized; a failed initialization may be retried. */ +YASL_API int yasl_init(enum yasl_backend backend, const char *app_id); +/** Destroys every surface. EBUSY while a Vulkan surface exists. */ +YASL_API int yasl_shutdown(void); +/** AUTO and 0 before initialization. */ +YASL_API enum yasl_backend yasl_get_backend(void); +YASL_API unsigned yasl_get_features(void); + +/* Surfaces */ +enum yasl_surface_type { + YASL_SURFACE_CPU, /**< Draw into yasl_cpu_pixels. */ + YASL_SURFACE_GL, /**< OpenGL through EGL; Linux only. */ + YASL_SURFACE_NATIVE /**< Draw with Vulkan, Metal or the native handles. */ +}; +/** Zero selects OpenGL 3.2 core (ES 3.0 with es), RGBA8, 24-bit depth, 8-bit stencil. */ +struct yasl_gl_options { + int major, minor, samples; + bool es, compatibility, debug, srgb, no_depth_stencil; + yasl_surface *share; /**< A GL surface whose objects the new context shares. */ }; -enum yasl_scroll_phase { - YASL_SCROLL_PHASE_UNKNOWN, - YASL_SCROLL_BEGIN, - YASL_SCROLL_UPDATE, - YASL_SCROLL_END +struct yasl_surface_options { + const char *title; + int width, height; /**< Required. */ + enum yasl_surface_type type; + bool undecorated, fixed_size; + void *userdata; + /** Called when the window needs drawing while the system blocks the event + * loop, as during a macOS live resize; draw and present before returning. + * REFRESH events cover every other case. Must not wait, create or destroy. */ + void (*refresh)(yasl_surface *surface); + struct yasl_gl_options gl; }; -enum yasl_transfer_status { YASL_TRANSFER_OK, YASL_TRANSFER_UNAVAILABLE, YASL_TRANSFER_FAILED }; -enum yasl_text_purpose { - YASL_TEXT_NORMAL, - YASL_TEXT_PASSWORD, - YASL_TEXT_NUMBER, - YASL_TEXT_TERMINAL +/** Strings are copied. */ +YASL_API yasl_surface *yasl_surface_create(const struct yasl_surface_options *options); +/** Also destroys its popups. NULL is a no-op. EBUSY while one has a Vulkan surface. */ +YASL_API int yasl_surface_destroy(yasl_surface *surface); + +enum yasl_state { + YASL_STATE_FULLSCREEN = 1, + YASL_STATE_MAXIMIZED = 2, + YASL_STATE_ACTIVATED = 4, + YASL_STATE_SUSPENDED = 8, /**< Hidden from view; skip drawing. */ + YASL_STATE_CSD = 16, /**< No system title bar: draw your own. */ + YASL_STATE_TILED = 32 +}; +struct yasl_surface_info { + int width, height, pixel_width, pixel_height; + double scale; + unsigned state; /**< YASL_STATE_* */ }; +/** Zero sizes and scale 1 for NULL. */ +YASL_API struct yasl_surface_info yasl_surface_get_info(const yasl_surface *surface); +YASL_API void *yasl_surface_get_userdata(const yasl_surface *surface); +YASL_API int yasl_surface_set_userdata(yasl_surface *surface, void *userdata); +YASL_API int yasl_surface_set_title(yasl_surface *surface, const char *title); + +/** Window requests; the outcome arrives as a WINDOW event. RESTORE leaves + * fullscreen and maximized. */ +enum yasl_request { YASL_REQUEST_RESTORE, YASL_REQUEST_FULLSCREEN, YASL_REQUEST_MAXIMIZE, YASL_REQUEST_MINIMIZE }; +YASL_API int yasl_surface_request(yasl_surface *surface, enum yasl_request request); +YASL_API int yasl_surface_request_size(yasl_surface *surface, int width, int height); + enum yasl_cursor { - YASL_CURSOR_ARROW, - YASL_CURSOR_TEXT, - YASL_CURSOR_LINK, - YASL_CURSOR_CROSSHAIR, - YASL_CURSOR_MOVE, - YASL_CURSOR_RESIZE_NWSE, - YASL_CURSOR_RESIZE_NESW, - YASL_CURSOR_RESIZE_EW, - YASL_CURSOR_RESIZE_NS, - YASL_CURSOR_HIDDEN -}; -enum yasl_surface_state { - YASL_STATE_FULLSCREEN = 1u, - YASL_STATE_MAXIMIZED = 2u, - YASL_STATE_ACTIVATED = 4u, - YASL_STATE_SUSPENDED = 8u, - YASL_STATE_CSD = 16u, - YASL_STATE_TILED = 32u -}; -enum yasl_feature { - YASL_CAP_POPUPS = 1u, - YASL_CAP_DRAG = 2u, - YASL_CAP_DRAG_CARRY = 4u, - YASL_CAP_PLACE_WINDOW = 8u, - YASL_CAP_PRIMARY_SELECTION = 16u, - YASL_CAP_CSD = 32u, - YASL_CAP_NATIVE_GRAPHICS = 64u, - YASL_CAP_FRAME_PACING = 128u, - YASL_CAP_TABLET = 256u, - YASL_CAP_OPENGL = 512u, - YASL_CAP_VULKAN = 1024u, - YASL_CAP_TEXT_INPUT = 2048u + YASL_CURSOR_ARROW, YASL_CURSOR_TEXT, YASL_CURSOR_LINK, YASL_CURSOR_CROSSHAIR, YASL_CURSOR_MOVE, + YASL_CURSOR_RESIZE_NWSE, YASL_CURSOR_RESIZE_NESW, YASL_CURSOR_RESIZE_EW, YASL_CURSOR_RESIZE_NS, + YASL_CURSOR_HIDDEN, + /** Hidden and held in place while focused; POINTER events then report + * motion only in dx/dy (FEATURE_LOCKED_CURSOR). */ + YASL_CURSOR_LOCKED }; -enum yasl_popup_edge { - YASL_POPUP_CENTER, - YASL_POPUP_TOP, - YASL_POPUP_BOTTOM, - YASL_POPUP_LEFT, - YASL_POPUP_RIGHT, - YASL_POPUP_TOP_LEFT, - YASL_POPUP_BOTTOM_LEFT, - YASL_POPUP_TOP_RIGHT, - YASL_POPUP_BOTTOM_RIGHT -}; -enum yasl_popup_constraint { - YASL_POPUP_SLIDE_X = 1u, - YASL_POPUP_SLIDE_Y = 2u, - YASL_POPUP_FLIP_X = 4u, - YASL_POPUP_FLIP_Y = 8u, - YASL_POPUP_RESIZE_X = 16u, - YASL_POPUP_RESIZE_Y = 32u +YASL_API int yasl_surface_set_cursor(yasl_surface *surface, enum yasl_cursor cursor); + +enum yasl_text_purpose { YASL_TEXT_NORMAL, YASL_TEXT_PASSWORD, YASL_TEXT_NUMBER, YASL_TEXT_TERMINAL }; +/** The focused text field, for input methods. text is UTF-8 around the caret, + * selection offsets are bytes into it, and revision comes back in COMPOSITION + * and TEXT_EDIT events. The caret rectangle places the candidate window. */ +struct yasl_text_input { + enum yasl_text_purpose purpose; + const char *text; + size_t length, selection_begin, selection_end; + uint64_t revision; + double caret_x, caret_y, caret_width, caret_height; +}; +/** Copied. NULL turns input methods off. */ +YASL_API int yasl_surface_set_text_input(yasl_surface *surface, const struct yasl_text_input *input); + +enum yasl_edge { YASL_EDGE_TOP = 1, YASL_EDGE_BOTTOM = 2, YASL_EDGE_LEFT = 4, YASL_EDGE_RIGHT = 8 }; +/** While a pointer or pen button is held: let the user move the window (edges 0) + * or resize it from one edge or a corner (two adjacent edges). */ +YASL_API int yasl_surface_begin_move_resize(yasl_surface *surface, unsigned edges); +YASL_API int yasl_surface_show_window_menu(yasl_surface *surface, double x, double y); +YASL_API int yasl_surface_bell(yasl_surface *surface); + +/* Popups */ +enum yasl_popup_adjust { + YASL_ADJUST_SLIDE_X = 1, YASL_ADJUST_SLIDE_Y = 2, YASL_ADJUST_FLIP_X = 4, YASL_ADJUST_FLIP_Y = 8, + YASL_ADJUST_RESIZE_X = 16, YASL_ADJUST_RESIZE_Y = 32 +}; +/** Placed against an anchor rectangle in the parent: anchor picks a point on it + * and gravity the direction the popup grows, both as YASL_EDGE_* bits (0 is the + * center). adjust keeps it on screen; 0 flips, then slides. */ +struct yasl_popup_options { + int width, height; + double x, y, anchor_width, anchor_height; + unsigned anchor, gravity, adjust; + void *userdata; }; -enum yasl_resize_edge { - YASL_EDGE_TOP = 1, - YASL_EDGE_BOTTOM = 2, - YASL_EDGE_LEFT = 4, - YASL_EDGE_TOP_LEFT = 5, - YASL_EDGE_BOTTOM_LEFT = 6, - YASL_EDGE_RIGHT = 8, - YASL_EDGE_TOP_RIGHT = 9, - YASL_EDGE_BOTTOM_RIGHT = 10 +/** A CPU surface. Create it while handling the input that opens it; it takes + * the input grab and gets CLOSE when dismissed. */ +YASL_API yasl_surface *yasl_popup_create(yasl_surface *parent, const struct yasl_popup_options *options); +YASL_API int yasl_popup_reposition(yasl_surface *popup, const struct yasl_popup_options *options); + +/* Clipboard, drag and drop */ +enum yasl_selection { YASL_CLIPBOARD, YASL_PRIMARY }; +/** Offers copied UTF-8 text; call while handling input on one of your windows. */ +YASL_API int yasl_clipboard_set(enum yasl_selection selection, const char *text); +/** The text arrives as a CLIPBOARD event on target, in request order. */ +YASL_API int yasl_clipboard_request(yasl_surface *target, enum yasl_selection selection); +/** Premultiplied 0xAARRGGBB pixels, rows tightly packed; copied. */ +struct yasl_drag_image { + const uint32_t *pixels; + int pixel_width, pixel_height, hotspot_x, hotspot_y; }; +/** While a pointer button is held on origin: drag it over YASL surfaces, which + * get DRAG events. image may be NULL. */ +YASL_API int yasl_drag_start(yasl_surface *origin, const struct yasl_drag_image *image); +YASL_API int yasl_drag_cancel(void); +/** During a drag: carry this toplevel with (x, y) under the pointer (FEATURE_DRAG_CARRY). */ +YASL_API int yasl_drag_carry(yasl_surface *surface, double x, double y); +/** Move the toplevel so (x, y) is under the pointer (FEATURE_PLACE_WINDOW). */ +YASL_API int yasl_surface_place_under_pointer(yasl_surface *surface, double x, double y); +/** Next local path in a DROP event's text/uri-list. Returns its length, or 0 at + * the end. If capacity exceeds the length, writes it with a NUL and advances + * cursor; otherwise changes nothing, so retry larger. Thread-safe. */ +YASL_API size_t yasl_drop_next_path(const char **cursor, char *out, size_t capacity); + +/* Events */ enum yasl_event_type { YASL_EVENT_NONE, - YASL_EVENT_CLOSE, - YASL_EVENT_QUIT, - YASL_EVENT_REOPEN, - YASL_EVENT_WINDOW, - YASL_EVENT_REFRESH, - YASL_EVENT_KEY, - YASL_EVENT_TEXT, - YASL_EVENT_POINTER, - YASL_EVENT_SCROLL, - YASL_EVENT_PEN, - YASL_EVENT_INPUT_RESET, - YASL_EVENT_COMPOSITION, - YASL_EVENT_TEXT_EDIT, - YASL_EVENT_CLIPBOARD, - YASL_EVENT_DROP, - YASL_EVENT_DRAG, + YASL_EVENT_CLOSE, /**< The user asked to close the window; destroy it to comply. */ + YASL_EVENT_QUIT, /**< The system asked the app to quit; exit to comply. */ + YASL_EVENT_REOPEN, /**< macOS: the app was activated with no window open. */ + YASL_EVENT_WINDOW, /**< window: size, scale or state changed. */ + YASL_EVENT_REFRESH, /**< Draw now: once sized, after every resize, and on expose. */ + YASL_EVENT_KEY, /**< key; action is a yasl_key_action. */ + YASL_EVENT_TEXT, /**< text: typed text to insert. */ + YASL_EVENT_COMPOSITION, /**< text: input method preedit to show at the caret, + * ime.begin..end its cursor; empty ends it. */ + YASL_EVENT_TEXT_EDIT, /**< text: replaces bytes ime.begin..end of the + * yasl_text_input text with revision ime.revision. */ + YASL_EVENT_POINTER, /**< pointer, x, y; action is a yasl_pointer_action. */ + YASL_EVENT_SCROLL, /**< scroll, x, y. */ + YASL_EVENT_PEN, /**< pen, x, y; action is a yasl_pen_action. */ + YASL_EVENT_INPUT_RESET, /**< Forget held keys, buttons and strokes. */ + YASL_EVENT_CLIPBOARD, /**< text; action is a yasl_transfer. */ + YASL_EVENT_DROP, /**< External data: x, y; action is a yasl_drop_action; + * text on RELEASE is a text/uri-list. */ + YASL_EVENT_DRAG, /**< A yasl_drag_start drag: x, y; action is a yasl_drop_action. */ + /** The display server went away. Before the next wait, destroy Vulkan + * surfaces and detach GL contexts on every thread; popups close. Wayland + * reconnects and sends DISPLAY_RESTORED: recreate them (GL contexts are new). + * Elsewhere loss is final and waits fail with ENOTCONN. */ YASL_EVENT_DISPLAY_LOST, YASL_EVENT_DISPLAY_RESTORED }; -enum yasl_drop_action { YASL_DROP_OVER, YASL_DROP_LEAVE, YASL_DROP_DATA }; -enum yasl_drag_action { YASL_DRAG_OVER, YASL_DRAG_LEAVE, YASL_DRAG_DROP }; -struct yasl_window_payload { - int width, height, pixel_width, pixel_height; - double scale; - unsigned state; +enum yasl_key_action { YASL_KEY_PRESS, YASL_KEY_RELEASE, YASL_KEY_REPEAT }; +enum yasl_mod { YASL_MOD_SHIFT = 1, YASL_MOD_ALT = 2, YASL_MOD_CTRL = 4, YASL_MOD_SUPER = 8, YASL_MOD_CAPS = 16 }; +enum yasl_pointer_action { YASL_POINTER_MOVE, YASL_POINTER_DOWN, YASL_POINTER_UP, YASL_POINTER_LEAVE }; +enum yasl_button { YASL_BUTTON_PRIMARY, YASL_BUTTON_MIDDLE, YASL_BUTTON_SECONDARY, YASL_BUTTON_BACK, YASL_BUTTON_FORWARD }; +#define YASL_BUTTON_BIT(button) (1u << (button)) +enum yasl_pen_action { YASL_PEN_ENTER, YASL_PEN_LEAVE, YASL_PEN_MOVE, YASL_PEN_DOWN, YASL_PEN_UP, YASL_PEN_BUTTON, YASL_PEN_CANCEL }; +enum yasl_pen_tool { YASL_PEN_UNKNOWN, YASL_PEN_PEN, YASL_PEN_ERASER, YASL_PEN_BRUSH, YASL_PEN_AIRBRUSH, YASL_PEN_PENCIL }; +enum yasl_pen_axis { + YASL_PEN_PRESSURE = 1, YASL_PEN_TILT = 2, YASL_PEN_ROTATION = 4, YASL_PEN_DISTANCE = 8, + YASL_PEN_TANGENTIAL = 16, YASL_PEN_WHEEL = 32 }; -struct yasl_key_payload { +enum yasl_scroll_source { YASL_SCROLL_UNKNOWN, YASL_SCROLL_WHEEL, YASL_SCROLL_FINGER, YASL_SCROLL_CONTINUOUS }; +enum yasl_scroll_phase { YASL_SCROLL_PHASE_NONE, YASL_SCROLL_BEGIN, YASL_SCROLL_UPDATE, YASL_SCROLL_END }; +enum yasl_transfer { YASL_TRANSFER_OK, YASL_TRANSFER_UNAVAILABLE, YASL_TRANSFER_FAILED }; +enum yasl_drop_action { YASL_DROP_OVER, YASL_DROP_LEAVE, YASL_DROP_RELEASE }; + +/** Physical key, layout labels (codepoint unshifted, shifted; 0 if none) and the + * native code (evdev, X11 keycode or macOS virtual key). Type with TEXT, not this. */ +struct yasl_key_event { enum yasl_key key; - enum yasl_key_action action; - unsigned mods; uint32_t codepoint, shifted, native_code; }; -struct yasl_text_payload { - const char *data; - size_t length; +/** button changed on DOWN/UP; buttons are held YASL_BUTTON_BIT()s. */ +struct yasl_pointer_event { + double dx, dy; + unsigned button, buttons; }; -struct yasl_pointer_payload { - double x, y, dx, dy; - enum yasl_pointer_action action; - unsigned button, buttons, mods; -}; -struct yasl_scroll_payload { - double x, y, lines_x, lines_y, pixels_x, pixels_y; - unsigned valid, source, phase, mods; +/** Positive right/down; a delta is 0 when the device does not report that unit. */ +struct yasl_scroll_event { + double lines_x, lines_y, pixels_x, pixels_y; + enum yasl_scroll_source source; + enum yasl_scroll_phase phase; bool momentum; }; -struct yasl_pen_payload { +/** pressure and distance 0..1, tangential -1..1, tilt and rotation in degrees. + * axes holds the valid YASL_PEN_* axes; button changed on BUTTON. */ +struct yasl_pen_event { uint64_t device_id, tool_id; - double x, y, pressure, tilt_x, tilt_y, rotation, distance, tangential, wheel_degrees; - unsigned axes_valid, axes_changed, buttons, changed_button, tool, action; + double pressure, tilt_x, tilt_y, rotation, distance, tangential, wheel_degrees; + enum yasl_pen_tool tool; + unsigned axes, buttons, button; bool in_contact; }; -struct yasl_composition_payload { - const char *data; - size_t length; - int begin, end; - bool active; - uint64_t revision, generation; -}; -struct yasl_text_edit_payload { - const char *data; - size_t length, begin, end; - uint64_t revision, generation; -}; -struct yasl_clipboard_payload { - const char *data; - size_t length; - uint64_t request_id; - enum yasl_transfer_status status; -}; -struct yasl_drop_payload { - enum yasl_drop_action action; - double x, y; - const char *data; - size_t length; -}; -struct yasl_drag_payload { - enum yasl_drag_action action; - double x, y; +struct yasl_ime_event { + size_t begin, end; + uint64_t revision; }; - -/** Read the union member named by type. Strings stay valid until the next wait. - * CLOSE: the user closed this window. QUIT: the system asked the app to quit. - * WINDOW: size, scale or state changed. REFRESH: draw now; sent once the window - * has a size, after every resize and when the system needs the contents again. - * INPUT_RESET: forget held keys, buttons and strokes. DROP: external data over - * the window, delivered as DATA. DRAG: another YASL surface dragged over. - * DISPLAY_LOST: the display server went away. Destroy Vulkan surfaces and GL - * objects now; windows stay valid. Wayland reconnects, then sends DISPLAY_RESTORED: - * recreate them (GL contexts are new and must be bound again). Elsewhere loss is - * final and later waits fail. */ struct yasl_event { enum yasl_event_type type; yasl_surface *surface; uint64_t time_ns; + unsigned action, mods; /**< mods: YASL_MOD_* for KEY, POINTER and SCROLL. */ + double x, y; + const char *text; /**< NUL-terminated UTF-8 until the next wait, or NULL. */ + size_t length; union { - struct yasl_window_payload window; - struct yasl_key_payload key; - struct yasl_text_payload text; - struct yasl_pointer_payload pointer; - struct yasl_scroll_payload scroll; - struct yasl_pen_payload pen; - struct yasl_composition_payload composition; - struct yasl_text_edit_payload text_edit; - struct yasl_clipboard_payload clipboard; - /** DATA carries a text/uri-list for yasl_drop_next_path; x/y are set for OVER. */ - struct yasl_drop_payload drop; - struct yasl_drag_payload drag; + struct yasl_surface_info window; + struct yasl_key_event key; + struct yasl_pointer_event pointer; + struct yasl_scroll_event scroll; + struct yasl_pen_event pen; + struct yasl_ime_event ime; }; }; -/** NULL options select the automatic backend and no application ID. */ -struct yasl_options { - enum yasl_backend backend; - const char *app_id; -}; -/** Zero initialization selects a native GPU window. A positive logical size is - * required; title may be NULL. Strings are copied before creation returns. */ -struct yasl_surface_options { - const char *title; - int width, height; - enum yasl_surface_type type; - unsigned flags; - void *userdata; -}; -/** Optional explicit initialization. Creation initializes with defaults when - * needed. Reinitialization returns -EALREADY; failed initialization is retryable. */ -YASL_API int yasl_init(const struct yasl_options *options); -YASL_API yasl_surface *yasl_surface_create(const struct yasl_surface_options *options); -/** Also destroys its popups. NULL is a no-op. Returns -EBUSY while a window - * in the subtree owns a Vulkan surface; destroy that WSI surface first. */ -YASL_API int yasl_surface_destroy(yasl_surface *surface); -/** Destroys all surfaces. Returns -EBUSY without destroying any windows if - * a Vulkan surface remains live. Release your GPU objects first. */ -YASL_API int yasl_shutdown(void); -/** 1 event, 0 none (timeout or wake), negative errno on failure. timeout_ms zero polls, negative - * waits forever. Without an event, out->type is NONE. */ +/** 1 with an event, 0 once timeout_ms passes or after yasl_wake. 0 polls and -1 + * waits forever. On failure out->type is NONE. */ YASL_API int yasl_wait_event(yasl_event *out, int timeout_ms); -/** Makes a waiting yasl_wait_event return. Thread-safe. */ +/** Makes a waiting, or the next, yasl_wait_event return 0 once no event is + * pending. Thread-safe. */ YASL_API void yasl_wake(void); -/** Monotonic nanoseconds; thread-safe and independent of initialization. */ +/** Monotonic nanoseconds, the clock of event times. Thread-safe. */ YASL_API uint64_t yasl_time_ns(void); -/** Queries accept NULL: return AUTO, zero, NULL or scale 1; size outputs are - * optional and become zero. Non-NULL surfaces must belong to the owner thread. */ -YASL_API enum yasl_backend yasl_surface_get_backend(const yasl_surface *surface); -YASL_API unsigned yasl_surface_get_features(const yasl_surface *surface); -YASL_API void yasl_surface_get_size(const yasl_surface *surface, int *width, int *height); -YASL_API void yasl_surface_get_framebuffer_size(const yasl_surface *surface, int *width, - int *height); -YASL_API double yasl_surface_get_scale(const yasl_surface *surface); -YASL_API unsigned yasl_surface_get_state(const yasl_surface *surface); -YASL_API void *yasl_surface_get_userdata(const yasl_surface *surface); -YASL_API int yasl_surface_set_userdata(yasl_surface *surface, void *userdata); -YASL_API int yasl_surface_set_title(yasl_surface *surface, const char *title); -/** Requests; the result arrives as a WINDOW event. */ -YASL_API int yasl_surface_request_size(yasl_surface *surface, int width, int height); -YASL_API int yasl_surface_request_fullscreen(yasl_surface *surface, bool enabled); -YASL_API int yasl_surface_request_maximized(yasl_surface *surface, bool enabled); -YASL_API int yasl_surface_request_minimize(yasl_surface *surface); -YASL_API int yasl_surface_set_cursor(yasl_surface *surface, enum yasl_cursor cursor); -YASL_API int yasl_surface_set_pointer_mode(yasl_surface *surface, enum yasl_pointer_mode mode); -/** Draws during native modal loops such as a live resize. The callback may render - * but must not wait, create or destroy surfaces, or shut down. */ -YASL_API int yasl_surface_set_refresh_callback(yasl_surface *surface, - void (*refresh)(yasl_surface *, void *), - void *userdata); + +/* Rendering */ +/** CPU surfaces: the window's pixel_width * pixel_height premultiplied + * 0xAARRGGBB pixels, rows tightly packed. They persist, and a resize keeps the + * overlap and zeroes the rest. Valid until the next wait. Present to show them; + * the system may also show them when it redraws the window. */ +YASL_API uint32_t *yasl_cpu_pixels(yasl_surface *surface, int *pixel_width, int *pixel_height); +YASL_API int yasl_cpu_present(yasl_surface *surface); + +typedef void (*yasl_gl_proc)(void); +/** These four may run on a rendering thread; synchronize them with destruction. + * NULL detaches the calling thread's context, also after DISPLAY_LOST. */ +YASL_API int yasl_gl_make_current(yasl_surface *surface); +YASL_API yasl_gl_proc yasl_gl_get_proc_address(const char *name); +YASL_API int yasl_gl_set_swap_interval(yasl_surface *surface, int interval); +YASL_API int yasl_gl_present(yasl_surface *surface); + +struct wl_display; +struct wl_surface; +struct _XDisplay; +/** Borrowed handles of the active backend; the rest are zero. They expire with + * the surface or on DISPLAY_LOST. Never destroy, dispatch or close them. */ +struct yasl_native { + struct wl_display *wl_display; + struct wl_surface *wl_surface; + struct _XDisplay *x11_display; + unsigned long x11_window; + void *ns_window, *ns_view; /**< NSWindow *, NSView * */ +}; +YASL_API int yasl_surface_get_native(const yasl_surface *surface, struct yasl_native *out); +/** NATIVE surfaces on macOS: the view's CAMetalLayer, reserving the surface for + * Metal. YASL sizes it; choose the device and format, and present yourself. */ +YASL_API void *yasl_metal_get_layer(yasl_surface *surface); + +/* Desktop */ +enum yasl_appearance { YASL_APPEARANCE_DARK = 1, YASL_APPEARANCE_LIGHT = 2, YASL_APPEARANCE_HIGH_CONTRAST = 4 }; +/** Opens uri with its default application. Thread-safe. */ +YASL_API int yasl_open_uri(const char *uri); +YASL_API int yasl_notify(const char *title, const char *body); +/** These two may block briefly on the session bus. 0 is no preference. */ +YASL_API unsigned yasl_get_appearance(void); +/** Milliseconds; 400 when unknown. */ YASL_API int yasl_get_double_click_ms(void); -YASL_API int yasl_clipboard_set(enum yasl_selection selection, const char *text); -/** Returns a request ID; the text arrives as a CLIPBOARD event. Zero on failure. */ -YASL_API uint64_t yasl_clipboard_request(yasl_surface *target, enum yasl_selection selection); -/** IME support for a text field. */ -YASL_API int yasl_surface_set_text_input(yasl_surface *surface, bool active, - enum yasl_text_purpose purpose); -/** UTF-8; selection offsets are bytes. */ -YASL_API int yasl_surface_set_surrounding_text(yasl_surface *surface, const char *text, - size_t length, size_t selection_begin, - size_t selection_end, uint64_t revision); -YASL_API int yasl_surface_set_ime_caret(yasl_surface *surface, double x, double y, double width, - double height); -/** Anchor selects a point on the parent rectangle; gravity is the direction - * the popup grows. Zero edges mean center. All dimensions are logical units. */ -struct yasl_popup_options { - int width, height; - double x, y, anchor_width, anchor_height; - enum yasl_popup_edge anchor, gravity; - unsigned constraints; -}; -/** Trigger must be a recent pointer/pen press on parent. */ -YASL_API yasl_surface *yasl_popup_create(yasl_surface *parent, - const struct yasl_popup_options *options, - const yasl_event *trigger); -YASL_API int yasl_popup_reposition(yasl_surface *popup, const struct yasl_popup_options *options); -YASL_API int yasl_surface_start_interactive_move(yasl_surface *surface, const yasl_event *trigger); -YASL_API int yasl_surface_start_interactive_resize(yasl_surface *surface, - enum yasl_resize_edge edge, - const yasl_event *trigger); -YASL_API int yasl_surface_show_window_menu(yasl_surface *surface, double x, double y, - const yasl_event *trigger); -/** Optional premultiplied ARGB image in device pixels. Copied by drag_start. */ -struct yasl_drag_image { - const uint32_t *pixels; - int width, height; - int hotspot_x, hotspot_y; -}; -YASL_API int yasl_drag_start(yasl_surface *origin, const yasl_event *trigger, - const struct yasl_drag_image *image); -YASL_API int yasl_drag_cancel(void); -YASL_API int yasl_surface_drag_carry(yasl_surface *surface, double x, double y); -YASL_API int yasl_surface_place_under_pointer(yasl_surface *surface, double x, double y); -/** Parse local file URIs. Returns byte length excluding NUL, zero at end. - * If capacity <= length, leaves cursor/output unchanged for retry. NULL/zero - * queries size. The caller owns the cursor string; this helper is thread-safe. */ -YASL_API size_t yasl_drop_next_path(const char **cursor, char *out, size_t capacity); +/** LOCALE is a POSIX name such as en_US; RESOURCE_DIR is macOS only. */ +enum yasl_system_string { YASL_EXECUTABLE_PATH, YASL_RESOURCE_DIR, YASL_LOCALE }; +/** Returns the length, or 0 with errno. Writes it with a NUL only if capacity + * exceeds the length. Thread-safe. */ +YASL_API size_t yasl_get_system_string(enum yasl_system_string which, char *out, size_t capacity); #ifdef __cplusplus } #endif diff --git a/meson.build b/meson.build index 5f5b26c..b6af117 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project( 'yasl', 'c', meson_version: '>=1.6.0', - version: '0.1.0', + version: '0.2.0', default_options: [ 'c_std=c23', 'warning_level=2', @@ -110,6 +110,9 @@ subdir('src') yasl_lib = both_libraries( 'yasl', sources, + version: meson.project_version(), + # Until 1.0, every minor release may break the ABI. + soversion: '0.2', include_directories: inc, dependencies: deps, gnu_symbol_visibility: 'hidden', @@ -123,22 +126,10 @@ yasl_dep = declare_dependency( ) meson.override_dependency('yasl', yasl_dep) -install_headers( - 'include/yasl/yasl.h', 'include/yasl/keycodes.h', 'include/yasl/cpu.h', - 'include/yasl/gl.h', 'include/yasl/metal.h', 'include/yasl/desktop.h', subdir: 'yasl', -) +install_headers('include/yasl/yasl.h', 'include/yasl/keycodes.h', subdir: 'yasl') if has_vulkan install_headers('include/yasl/vulkan.h', subdir: 'yasl') endif -if has_wayland - install_headers('include/yasl/native/wayland.h', subdir: 'yasl/native') -endif -if has_x11 - install_headers('include/yasl/native/x11.h', subdir: 'yasl/native') -endif -if has_cocoa - install_headers('include/yasl/native/cocoa.h', subdir: 'yasl/native') -endif pkgconfig = import('pkgconfig') pkgconfig.generate(yasl_lib, name: 'yasl', description: 'Desktop surfaces for application-owned rendering', diff --git a/src/common/backend.c b/src/common/backend.c index 939ec71..ad9e218 100644 --- a/src/common/backend.c +++ b/src/common/backend.c @@ -133,16 +133,21 @@ int yasl_private_display_double_click_ms(const struct yasl_private_display *d) bool yasl_private_display_drag_start(struct yasl_private_display *d, struct yasl_surface *origin, const struct yasl_private_drag_image *image, const struct yasl_private_drag_listener *l, void *ud) { - if (!d || !d->ops->display_drag_start) - return 0; - if (!origin || origin->display != d) - return false; + if (!d || !origin || origin->display != d) { errno = EINVAL; return false; } + if (!d->ops->display_drag_start) { errno = ENOTSUP; return false; } if (image && (!image->pixels || image->size.x <= 0 || image->size.y <= 0 || image->size.x > 32767 || image->size.y > 32767 || - (int64_t)image->size.x * image->size.y * 4 > INT32_MAX)) + (int64_t)image->size.x * image->size.y * 4 > INT32_MAX || + image->hotspot.x < 0 || image->hotspot.y < 0 || + image->hotspot.x >= image->size.x || image->hotspot.y >= image->size.y)) { + errno = EINVAL; return false; + } const struct yasl_private_drag_listener empty = {0}; - return d->ops->display_drag_start(d, origin, image, l ? l : &empty, ud); + errno = 0; + bool ok = d->ops->display_drag_start(d, origin, image, l ? l : &empty, ud); + if (!ok && !errno) errno = EIO; + return ok; } void yasl_private_display_drag_cancel(struct yasl_private_display *d) @@ -326,13 +331,18 @@ bool yasl_private_surface_read_pixels(struct yasl_surface *s, uint32_t *out, siz bool yasl_private_surface_set_pointer_mode(struct yasl_surface *s, enum yasl_private_pointer_mode mode) { - if (!s || (mode != YASL_PRIVATE_POINTER_NORMAL && mode != YASL_PRIVATE_POINTER_RELATIVE)) + if (!s || (mode != YASL_PRIVATE_POINTER_NORMAL && mode != YASL_PRIVATE_POINTER_RELATIVE)) { + errno = EINVAL; return false; + } if (mode == s->pointer_mode) return true; const struct yasl_private_backend_ops *ops = s->display ? s->display->ops : NULL; - if (!ops || !ops->surface_set_pointer_mode) - return mode == YASL_PRIVATE_POINTER_NORMAL; + if (!ops || !ops->surface_set_pointer_mode) { + if (mode == YASL_PRIVATE_POINTER_NORMAL) return true; + errno = ENOTSUP; + return false; + } if (!ops->surface_set_pointer_mode(s, mode)) return false; s->pointer_mode = mode; @@ -416,43 +426,48 @@ void yasl_private_surface_set_ime_caret(struct yasl_surface *s, struct yasl_priv s->display->ops->surface_set_ime_caret(s, caret); } +/* Backend operations that may be missing: ENOTSUP when absent, EIO when a backend + * fails without saying why. */ +#define OPTIONAL_OP(s, op, ...) \ + do { \ + if (!(s)) { errno = EINVAL; return false; } \ + if (!(s)->display->ops->op) { errno = ENOTSUP; return false; } \ + errno = 0; \ + bool ok_ = (s)->display->ops->op(__VA_ARGS__); \ + if (!ok_ && !errno) errno = EIO; \ + return ok_; \ + } while (0) + bool yasl_private_surface_start_interactive_move(struct yasl_surface *s) { - if (!s || !s->display->ops->surface_start_interactive_move) - return 0; - return s->display->ops->surface_start_interactive_move(s); + OPTIONAL_OP(s, surface_start_interactive_move, s); } bool yasl_private_surface_start_interactive_resize(struct yasl_surface *s, enum yasl_private_resize_edge edge) { - if (!s || !s->display->ops->surface_start_interactive_resize || - (edge != YASL_PRIVATE_EDGE_TOP && edge != YASL_PRIVATE_EDGE_BOTTOM && - edge != YASL_PRIVATE_EDGE_LEFT && edge != YASL_PRIVATE_EDGE_RIGHT && - edge != YASL_PRIVATE_EDGE_TOP_LEFT && edge != YASL_PRIVATE_EDGE_TOP_RIGHT && - edge != YASL_PRIVATE_EDGE_BOTTOM_LEFT && edge != YASL_PRIVATE_EDGE_BOTTOM_RIGHT)) - return 0; - return s->display->ops->surface_start_interactive_resize(s, edge); + if (edge != YASL_PRIVATE_EDGE_TOP && edge != YASL_PRIVATE_EDGE_BOTTOM && + edge != YASL_PRIVATE_EDGE_LEFT && edge != YASL_PRIVATE_EDGE_RIGHT && + edge != YASL_PRIVATE_EDGE_TOP_LEFT && edge != YASL_PRIVATE_EDGE_TOP_RIGHT && + edge != YASL_PRIVATE_EDGE_BOTTOM_LEFT && edge != YASL_PRIVATE_EDGE_BOTTOM_RIGHT) { + errno = EINVAL; + return false; + } + OPTIONAL_OP(s, surface_start_interactive_resize, s, edge); } bool yasl_private_surface_show_window_menu(struct yasl_surface *s, struct yasl_private_vector2i pos) { - if (!s || !s->display->ops->surface_show_window_menu) - return 0; - return s->display->ops->surface_show_window_menu(s, pos); + OPTIONAL_OP(s, surface_show_window_menu, s, pos); } bool yasl_private_surface_drag_carry(struct yasl_surface *s, struct yasl_private_vector2i offset) { - if (!s || !s->display->ops->surface_drag_carry) - return 0; - return s->display->ops->surface_drag_carry(s, offset); + OPTIONAL_OP(s, surface_drag_carry, s, offset); } bool yasl_private_surface_place_under_pointer(struct yasl_surface *s, struct yasl_private_vector2i at) { - if (!s || !s->display->ops->surface_place_under_pointer) - return 0; - return s->display->ops->surface_place_under_pointer(s, at); + OPTIONAL_OP(s, surface_place_under_pointer, s, at); } void yasl_private_surface_clear_timers(struct yasl_surface *s) diff --git a/src/common/backend.h b/src/common/backend.h index 246a9bc..e26f9d2 100644 --- a/src/common/backend.h +++ b/src/common/backend.h @@ -8,6 +8,7 @@ #include "common/gl_options.h" #include "common/timing.h" +#include #include struct yasl_private_backend_ops; @@ -16,7 +17,12 @@ struct yasl_private_event_state; struct yasl_private_display { const struct yasl_private_backend_ops *ops; void *native_handle; - bool offline; /* Connection lost; native handles are unusable until it is restored. */ + /* Connection lost; native handles are unusable until it is restored. Read by GL + * rendering threads, so it is atomic. */ + atomic_bool offline; + /* Set by the backend while a native modal loop (macOS live resize) blocks the + * event loop; refresh callbacks run only then. */ + bool modal; struct yasl_private_loop *loop; /* Backend's copy of yasl_private_display_spec.app_id, or NULL; for desktop services. */ const char *app_id; @@ -29,19 +35,14 @@ struct yasl_surface { unsigned creation_flags; enum yasl_private_presenter presenter; struct yasl_surface *parent; - uint32_t *cpu_pixels; - int cpu_width, cpu_height; - uint64_t cpu_token; - bool cpu_acquired; - uint32_t *committed_pixels; - int committed_width, committed_height; + /* CPU canvas returned by yasl_cpu_pixels and copied into each drawn frame. */ + uint32_t *canvas; + int canvas_width, canvas_height; /* Window values last reported through public WINDOW events. */ struct yasl_private_vector2i reported_size; uint32_t reported_scale_120, reported_state; bool refresh_owed; - bool refresh_active; - void (*refresh_callback)(struct yasl_surface *, void *); - void *refresh_userdata; + void (*refresh)(struct yasl_surface *); bool text_input_active; unsigned text_purpose; char *text_surrounding; diff --git a/src/common/desktop.c b/src/common/desktop.c index a9f72e3..b6dac7f 100644 --- a/src/common/desktop.c +++ b/src/common/desktop.c @@ -91,7 +91,6 @@ size_t yasl_private_default_locale(char *out, size_t cap) } #include "public_internal.h" -#include "yasl/desktop.h" #include int yasl_open_uri(const char *uri) { @@ -114,48 +113,34 @@ unsigned yasl_get_appearance(void) { return yasl_public_owner() ? yasl_private_display_appearance(yasl_public_display()) : 0; } +int yasl_get_double_click_ms(void) +{ + return yasl_public_display() && yasl_public_owner() + ? yasl_private_display_double_click_ms(yasl_public_display()) + : YASL_PRIVATE_DOUBLE_CLICK_MS_DEFAULT; +} int yasl_surface_bell(yasl_surface *s) { - if (!yasl_public_surface(s, false)) + if (!yasl_public_connected(s)) return -errno; if (!s->display->ops->surface_bell) return yasl_public_error(ENOTSUP); errno = 0; return yasl_public_status(yasl_private_surface_bell(s) || yasl_public_fail(errno)); } -size_t yasl_get_executable_path(char *out, size_t cap) +size_t yasl_get_system_string(enum yasl_system_string which, char *out, size_t cap) { - if (!out && cap) { - yasl_public_fail(EINVAL); - return 0; - } - errno = 0; - size_t n = yasl_private_executable_path(out, cap); - if (!n) - yasl_public_fail(errno); - return n; -} -size_t yasl_get_resource_dir(char *out, size_t cap) -{ - if (!out && cap) { - yasl_public_fail(EINVAL); - return 0; - } - errno = 0; - size_t n = yasl_private_resource_dir(out, cap); - if (!n) - yasl_public_fail(errno); - return n; -} -size_t yasl_get_default_locale(char *out, size_t cap) -{ - if (!out && cap) { - yasl_public_fail(EINVAL); - return 0; - } + if ((!out && cap) || (unsigned)which > YASL_LOCALE) + return yasl_public_fail(EINVAL), 0; + char buf[DESKTOP_STR_MAX]; errno = 0; - size_t n = yasl_private_default_locale(out, cap); + size_t n = which == YASL_EXECUTABLE_PATH ? desktop_executable_path(buf, sizeof(buf)) + : which == YASL_RESOURCE_DIR ? desktop_resource_dir(buf, sizeof(buf)) + : desktop_default_locale(buf, sizeof(buf)); if (!n) - yasl_public_fail(errno); - return n; + return yasl_public_fail(errno ? errno : ENOENT), 0; + /* A POSIX locale name without its encoding or modifier, the same on every platform. */ + if (which == YASL_LOCALE) + n = strcspn(buf, ".@"); + return copy_out(out, cap, buf, n); } diff --git a/src/common/events.c b/src/common/events.c index 9245050..b318d0c 100644 --- a/src/common/events.c +++ b/src/common/events.c @@ -60,11 +60,13 @@ void yasl_private_events_close(struct yasl_private_display *d) free(state); } +/* Reports a lost event once, so one failed allocation does not fail every later dispatch. */ int yasl_private_events_error(struct yasl_private_display *d) { if (!d->events) return 0; yasl_private_mutex_lock(&d->events->mutex); int error = d->events->error; + d->events->error = 0; yasl_private_mutex_unlock(&d->events->mutex); return error; } diff --git a/src/common/native.c b/src/common/native.c index 1b32094..36f7010 100644 --- a/src/common/native.c +++ b/src/common/native.c @@ -45,84 +45,54 @@ bool yasl_private_surface_get_native_frame(struct yasl_surface *s, } #include "public_internal.h" -#include "yasl/metal.h" -#if YASL_HAS_WAYLAND || YASL_HAS_X11 || YASL_HAS_COCOA -static int native_validate(const yasl_surface *s, enum yasl_private_backend backend) -{ - if (!yasl_public_surface((yasl_surface *)s, false)) - return -errno; - if (s->display->ops->type != backend) - return yasl_public_error(ENOTSUP); - if (s->display->offline) - return yasl_public_error(ENOTCONN); - return 0; -} -#endif -#if YASL_HAS_WAYLAND -#include "yasl/native/wayland.h" -int yasl_wayland_get_native(const yasl_surface *s, struct yasl_wayland_handles *out) -{ - if (!out) - return yasl_public_error(EINVAL); - *out = (struct yasl_wayland_handles){0}; - int error = native_validate(s, YASL_PRIVATE_BACKEND_WAYLAND); - if (error) - return error; - out->display = yasl_private_display_get_native_handle(s->display); - out->surface = yasl_private_surface_get_native_handle((yasl_surface *)s); - return 0; -} -#endif -#if YASL_HAS_X11 -#include "yasl/native/x11.h" -int yasl_x11_get_native(const yasl_surface *s, struct yasl_x11_handles *out) -{ - if (!out) - return yasl_public_error(EINVAL); - *out = (struct yasl_x11_handles){0}; - int error = native_validate(s, YASL_PRIVATE_BACKEND_X11); - if (error) - return error; - out->display = yasl_private_display_get_native_handle(s->display); - out->window = (uintptr_t)yasl_private_surface_get_native_handle((yasl_surface *)s); - return 0; -} -#endif #if YASL_HAS_COCOA -#include "yasl/native/cocoa.h" #include "common/private_native_cocoa.h" -int yasl_cocoa_get_native(const yasl_surface *s, struct yasl_cocoa_handles *out) -{ - if (!out) - return yasl_public_error(EINVAL); - *out = (struct yasl_cocoa_handles){0}; - int error = native_validate(s, YASL_PRIVATE_BACKEND_COCOA); - if (error) - return error; - out->application = yasl_private_cocoa_display_get_application(s->display); - out->window = yasl_private_cocoa_surface_get_window((yasl_surface *)s); - out->view = yasl_private_cocoa_surface_get_view((yasl_surface *)s); - return 0; -} #endif -int yasl_metal_get_layer(yasl_surface *s, CAMetalLayer **out) +int yasl_surface_get_native(const yasl_surface *cs, struct yasl_native *out) { + yasl_surface *s = (yasl_surface *)cs; if (!out) return yasl_public_error(EINVAL); - *out = NULL; - if (!yasl_public_surface(s, false)) + *out = (struct yasl_native){0}; + if (!yasl_public_connected(s)) return -errno; - if (s->display->offline) + void *window = yasl_private_surface_get_native_handle(s); + switch (s->display->ops->type) { + case YASL_PRIVATE_BACKEND_WAYLAND: + out->wl_display = yasl_private_display_get_native_handle(s->display); + out->wl_surface = window; + break; + case YASL_PRIVATE_BACKEND_X11: + out->x11_display = yasl_private_display_get_native_handle(s->display); + out->x11_window = (unsigned long)(uintptr_t)window; + break; +#if YASL_HAS_COCOA + case YASL_PRIVATE_BACKEND_COCOA: + out->ns_window = yasl_private_cocoa_surface_get_window(s); + out->ns_view = yasl_private_cocoa_surface_get_view(s); + break; +#endif + default: + return yasl_public_error(ENOTSUP); + } + if (!window) { + *out = (struct yasl_native){0}; return yasl_public_error(ENOTCONN); + } + return 0; +} +void *yasl_metal_get_layer(yasl_surface *s) +{ + if (!yasl_public_connected(s)) + return NULL; if (s->vulkan || (s->presenter != YASL_PRIVATE_PRESENTER_NONE && s->presenter != YASL_PRIVATE_PRESENTER_METAL)) - return yasl_public_error(EBUSY); - if (!s->display->ops->surface_get_metal_layer) - return yasl_public_error(ENOTSUP); - CAMetalLayer *layer = s->display->ops->surface_get_metal_layer(s); + return yasl_public_fail(EBUSY), NULL; + void *layer = s->display->ops->surface_get_metal_layer + ? s->display->ops->surface_get_metal_layer(s) + : NULL; if (!layer) - return yasl_public_error(ENOTSUP); + return yasl_public_fail(ENOTSUP), NULL; s->presenter = YASL_PRIVATE_PRESENTER_METAL; - *out = layer; - return 0; + return layer; } diff --git a/src/common/public_internal.h b/src/common/public_internal.h index 95ecb6d..65be842 100644 --- a/src/common/public_internal.h +++ b/src/common/public_internal.h @@ -3,11 +3,15 @@ #include "yasl/yasl.h" #include "common/backend.h" struct yasl_private_display *yasl_public_display(void); +/* Initializes with defaults when needed; false with errno otherwise. */ +bool yasl_public_initialize(void); bool yasl_public_owner(void); +/* Validates a live surface on the owner thread; mutation also refuses inside a refresh callback. */ bool yasl_public_surface(yasl_surface *, bool mutation); +/* yasl_public_surface plus a connected display, for requests sent to the server. */ +bool yasl_public_connected(yasl_surface *); bool yasl_public_fail(int error); int yasl_public_status(bool success); int yasl_public_error(int error); -void yasl_public_register_surface(yasl_surface *, unsigned flags, yasl_surface *parent); void yasl_private_surface_notify_refresh(struct yasl_surface *); #endif diff --git a/src/common/surface.c b/src/common/surface.c index de9e7b3..b9231ea 100644 --- a/src/common/surface.c +++ b/src/common/surface.c @@ -1,21 +1,22 @@ #define _POSIX_C_SOURCE 200809L #include "public_internal.h" -#include "yasl/cpu.h" -#include "yasl/gl.h" #include "common/utf8.h" #include #include #include #include +#include #include #include static struct yasl_private_display *display; static pthread_t owner; static pthread_mutex_t lifecycle_mutex = PTHREAD_MUTEX_INITIALIZER; +static atomic_bool wake_requested; static bool pumping; static unsigned refresh_depth; -static char *delivered_text; /* Strings of the last returned event; freed by the next poll/wait. */ +static char *delivered_text; /* Text of the last returned event; freed by the next wait. */ + bool yasl_public_fail(int error) { errno = error > 0 ? error : EIO; @@ -52,21 +53,24 @@ bool yasl_public_surface(yasl_surface *s, bool mutation) return yasl_public_fail(EBUSY); return true; } -int yasl_init(const struct yasl_options *options) +bool yasl_public_connected(yasl_surface *s) { - if (display) { - if (!yasl_public_owner()) - return -errno; - return yasl_public_error(EALREADY); - } - struct yasl_options defaults = {0}; - if (!options) - options = &defaults; - if ((unsigned)options->backend > YASL_BACKEND_COCOA) + if (!yasl_public_surface(s, false)) + return false; + return !atomic_load(&display->offline) || yasl_public_fail(ENOTCONN); +} +/* Runs a private call that reports failure through errno. */ +#define STATUS(call) (errno = 0, yasl_public_status((call) || yasl_public_fail(errno))) + +int yasl_init(enum yasl_backend backend, const char *app_id) +{ + if (display) + return yasl_public_owner() ? yasl_public_error(EALREADY) : -errno; + if ((unsigned)backend > YASL_BACKEND_COCOA) return yasl_public_error(EINVAL); struct yasl_private_display_spec spec = { - .app_id = options->app_id, - .backend = (enum yasl_private_backend)options->backend, + .app_id = app_id, + .backend = (enum yasl_private_backend)backend, }; errno = 0; struct yasl_private_display *d = yasl_private_display_open(NULL, &spec); @@ -78,83 +82,214 @@ int yasl_init(const struct yasl_options *options) pthread_mutex_unlock(&lifecycle_mutex); return 0; } -static bool initialize(void) +bool yasl_public_initialize(void) { - return display ? yasl_public_owner() : yasl_init(NULL) == 0; + return display ? yasl_public_owner() : yasl_init(YASL_BACKEND_AUTO, NULL) == 0; } -static bool creation_valid(int width, int height, unsigned flags) +static void close_display(void) { - if (width <= 0 || height <= 0 || width > 32767 || height > 32767 || - (unsigned long long)width * (unsigned)height > INT32_MAX / 4 || (flags & ~15u)) - return yasl_public_fail(EINVAL); + pthread_mutex_lock(&lifecycle_mutex); + struct yasl_private_display *d = display; + display = NULL; + pthread_mutex_unlock(&lifecycle_mutex); + yasl_private_display_close(d); +} +int yasl_shutdown(void) +{ + if (display && !yasl_public_owner()) + return -errno; if (pumping || refresh_depth) - return yasl_public_fail(EBUSY); + return yasl_public_error(EBUSY); + if (display) { + for (yasl_surface *s = display->surfaces; s; s = s->event_next) + if (s->vulkan) + return yasl_public_error(EBUSY); + while (display->surfaces) + yasl_surface_destroy(display->surfaces); + close_display(); + } + free(delivered_text); + delivered_text = NULL; + atomic_store(&wake_requested, false); +#if YASL_HAS_VULKAN + extern void yasl_private_vulkan_reset(void); + yasl_private_vulkan_reset(); +#endif + return 0; +} +enum yasl_backend yasl_get_backend(void) +{ + return display && yasl_public_owner() ? (enum yasl_backend)display->ops->type + : YASL_BACKEND_AUTO; +} +unsigned yasl_get_features(void) +{ + if (!display || !yasl_public_owner()) + return 0; + static const struct { + uint32_t cap; + unsigned feature; + } map[] = { + {YASL_PRIVATE_CAP_POPUPS, YASL_FEATURE_POPUPS}, + {YASL_PRIVATE_CAP_DRAG, YASL_FEATURE_DRAG}, + {YASL_PRIVATE_CAP_DRAG_CARRY, YASL_FEATURE_DRAG_CARRY}, + {YASL_PRIVATE_CAP_PLACE_WINDOW, YASL_FEATURE_PLACE_WINDOW}, + {YASL_PRIVATE_CAP_PRIMARY_SELECTION, YASL_FEATURE_PRIMARY_SELECTION}, + {YASL_PRIVATE_CAP_TABLET, YASL_FEATURE_TABLET}, + {YASL_PRIVATE_CAP_TEXT_INPUT, YASL_FEATURE_TEXT_INPUT}, + {YASL_PRIVATE_CAP_RELATIVE_POINTER, YASL_FEATURE_LOCKED_CURSOR}, + }; + uint32_t caps = yasl_private_display_caps(display); + unsigned features = display->ops->surface_create_gl ? YASL_FEATURE_OPENGL : 0; +#if YASL_HAS_VULKAN + features |= YASL_FEATURE_VULKAN; +#endif + for (size_t i = 0; i < sizeof(map) / sizeof(map[0]); i++) + if (caps & map[i].cap) + features |= map[i].feature; + return features; +} + +/* Surfaces */ +static double scale_of(const yasl_surface *s) +{ + return s && s->scale_120 ? (double)s->scale_120 / 120.0 : 1.0; +} +static int logical(int pixels, double scale) +{ + return (int)lround(pixels / scale); +} +static bool size_valid(int width, int height) +{ + return width > 0 && height > 0 && width <= 32767 && height <= 32767 && + (unsigned long long)width * (unsigned)height <= INT32_MAX / 4; +} +/* Keeps the overlap of the old canvas and zeroes the rest. */ +static bool canvas_fit(yasl_surface *s, int w, int h) +{ + if (s->canvas && s->canvas_width == w && s->canvas_height == h) + return true; + if (w <= 0 || h <= 0) + return yasl_public_fail(EAGAIN); + uint32_t *pixels = calloc((size_t)w * (size_t)h, sizeof(*pixels)); + if (!pixels) + return yasl_public_fail(ENOMEM); + int keep_w = w < s->canvas_width ? w : s->canvas_width; + int keep_h = h < s->canvas_height ? h : s->canvas_height; + for (int y = 0; s->canvas && y < keep_h; y++) + memcpy(pixels + (size_t)y * w, s->canvas + (size_t)y * s->canvas_width, + (size_t)keep_w * sizeof(*pixels)); + free(s->canvas); + s->canvas = pixels; + s->canvas_width = w; + s->canvas_height = h; return true; } -static void draw_committed(yasl_surface *s, struct yasl_private_frame *frame, void *ud) +/* CPU presenter: every drawn frame shows the canvas; before the first present there is none. */ +static void draw_canvas(yasl_surface *s, struct yasl_private_frame *frame, void *ud) { (void)ud; - if (s->refresh_callback && pumping && !refresh_depth) { - ++refresh_depth; - s->refresh_callback(s, s->refresh_userdata); - --refresh_depth; - } - if (s->presenter != YASL_PRIVATE_PRESENTER_CPU || !frame->target.pixels) + if (s->presenter != YASL_PRIVATE_PRESENTER_CPU || !frame->target.pixels || !s->canvas) return; int w = frame->target.size.x, h = frame->target.size.y; - if (w == s->committed_width && h == s->committed_height && s->committed_pixels) - memcpy(frame->target.pixels, s->committed_pixels, (size_t)w * h * 4); - else - memset(frame->target.pixels, 0, (size_t)w * h * 4); + if (!canvas_fit(s, w, h)) + return; + memcpy(frame->target.pixels, s->canvas, (size_t)w * (size_t)h * sizeof(uint32_t)); struct yasl_private_damage_list damage = {.n = -1}; yasl_private_surface_present(s, &frame->target, &damage); } -void yasl_public_register_surface(yasl_surface *s, unsigned flags, yasl_surface *parent) +static void register_surface(yasl_surface *s, unsigned flags, yasl_surface *parent, void *userdata) { s->creation_flags = flags; s->parent = parent; + s->userdata = userdata; if (flags & YASL_PRIVATE_SURFACE_CPU) s->presenter = YASL_PRIVATE_PRESENTER_CPU; - struct yasl_private_window_cbs cb = {.draw = draw_committed}; + struct yasl_private_window_cbs cb = {.draw = draw_canvas}; yasl_private_surface_set_callbacks(s, &cb, NULL); s->reported_state = s->state; - struct yasl_private_geometry geometry = yasl_private_surface_geometry(s); - yasl_private_surface_notify_configure(s, geometry); + yasl_private_surface_notify_configure(s, yasl_private_surface_geometry(s)); +} +/* Translates zero-means-default GL options into the private attribute list. */ +static bool gl_attributes(const struct yasl_gl_options *gl, int attrs[static 31]) +{ + int major = gl->major, minor = gl->minor; + if (!major && !minor) { + major = 3; + minor = gl->es ? 0 : 2; + } + bool profiles = !gl->es && (major > 3 || (major == 3 && minor >= 2)); + int profile = !profiles ? YASL_PRIVATE_GL_ANY + : gl->compatibility ? YASL_PRIVATE_GL_COMPATIBILITY + : YASL_PRIVATE_GL_CORE; + int depth = gl->no_depth_stencil ? 0 : 24, stencil = gl->no_depth_stencil ? 0 : 8; + const int list[] = { + YASL_PRIVATE_GL_MAJOR, major, YASL_PRIVATE_GL_MINOR, minor, + YASL_PRIVATE_GL_PROFILE, profile, + YASL_PRIVATE_GL_API, gl->es ? YASL_PRIVATE_GL_OPENGL_ES : YASL_PRIVATE_GL_OPENGL, + YASL_PRIVATE_GL_RED_BITS, 8, YASL_PRIVATE_GL_GREEN_BITS, 8, + YASL_PRIVATE_GL_BLUE_BITS, 8, YASL_PRIVATE_GL_ALPHA_BITS, 8, + YASL_PRIVATE_GL_DEPTH_BITS, depth, YASL_PRIVATE_GL_STENCIL_BITS, stencil, + YASL_PRIVATE_GL_SAMPLES, gl->samples, YASL_PRIVATE_GL_DEBUG, gl->debug, + YASL_PRIVATE_GL_FORWARD_COMPAT, 0, YASL_PRIVATE_GL_SRGB, gl->srgb, + YASL_PRIVATE_GL_END, + }; + memcpy(attrs, list, sizeof(list)); + struct yasl_private_gl_options parsed; + return yasl_private_gl_parse_options(attrs, &parsed) || yasl_public_fail(EINVAL); } -yasl_surface *yasl_surface_create(const struct yasl_surface_options *options) +static yasl_surface *create_gl(const struct yasl_private_window_spec *spec, const int *attrs, + yasl_surface *share) { - if (!options || (unsigned)options->type > YASL_SURFACE_CPU || (options->flags & ~14u)) { - yasl_public_fail(EINVAL); - return NULL; + if (!display->ops->surface_create_gl) + return yasl_public_fail(ENOTSUP), NULL; + errno = 0; + yasl_surface *s = display->ops->surface_create_gl(display, spec, attrs, share); + if (!s) + return yasl_public_fail(errno), NULL; + if (!yasl_private_events_register(s)) { + int error = errno; + display->ops->surface_destroy(s); + return yasl_public_fail(error), NULL; } - const char *title = options->title; - int width = options->width, height = options->height; - unsigned flags = - options->flags | (options->type == YASL_SURFACE_CPU ? YASL_PRIVATE_SURFACE_CPU : 0u); - if (!creation_valid(width, height, flags)) + s->presenter = YASL_PRIVATE_PRESENTER_GL; + return s; +} +yasl_surface *yasl_surface_create(const struct yasl_surface_options *o) +{ + if (!o || (unsigned)o->type > YASL_SURFACE_NATIVE || !size_valid(o->width, o->height)) + return yasl_public_fail(EINVAL), NULL; + if (pumping || refresh_depth) + return yasl_public_fail(EBUSY), NULL; + unsigned flags = (o->type == YASL_SURFACE_CPU ? YASL_PRIVATE_SURFACE_CPU : 0u) | + (o->undecorated ? YASL_PRIVATE_SURFACE_UNDECORATED : 0u) | + (o->fixed_size ? YASL_PRIVATE_SURFACE_FIXED_SIZE : 0u); + int attrs[31]; + yasl_surface *share = o->type == YASL_SURFACE_GL ? o->gl.share : NULL; + if (o->type == YASL_SURFACE_GL && !gl_attributes(&o->gl, attrs)) return NULL; + if (share && (!display || share->display != display || + share->presenter != YASL_PRIVATE_PRESENTER_GL)) + return yasl_public_fail(EINVAL), NULL; bool fresh = !display; - if (!initialize()) + if (!yasl_public_initialize()) return NULL; - struct yasl_private_window_spec spec = {.title = title, - .size = {width, height}, - .flags = flags, - .external_gpu = !(flags & YASL_PRIVATE_SURFACE_CPU)}; - yasl_surface *s = yasl_private_surface_create_toplevel(display, &spec); + struct yasl_private_window_spec spec = { + .title = o->title, + .size = {o->width, o->height}, + .flags = flags, + .external_gpu = o->type != YASL_SURFACE_CPU, + }; + yasl_surface *s = o->type == YASL_SURFACE_GL ? create_gl(&spec, attrs, share) + : yasl_private_surface_create_toplevel(display, &spec); if (!s) { int error = errno; - if (fresh) { - pthread_mutex_lock(&lifecycle_mutex); - struct yasl_private_display *d = display; - display = NULL; - pthread_mutex_unlock(&lifecycle_mutex); - yasl_private_display_close(d); - } - yasl_public_fail(error); - return NULL; + if (fresh) + close_display(); + return yasl_public_fail(error), NULL; } - yasl_public_register_surface(s, flags, NULL); - s->userdata = options->userdata; + register_surface(s, flags, NULL, o->userdata); + s->refresh = o->refresh; return s; } int yasl_surface_destroy(yasl_surface *s) @@ -163,122 +298,39 @@ int yasl_surface_destroy(yasl_surface *s) return 0; if (!yasl_public_surface(s, true)) return -errno; - for (yasl_surface *p = display->surfaces; p; p = p->event_next) { + for (yasl_surface *p = display->surfaces; p; p = p->event_next) for (yasl_surface *ancestor = p; ancestor; ancestor = ancestor->parent) if (ancestor == s && p->vulkan) return yasl_public_error(EBUSY); - } for (;;) { yasl_surface *child = NULL; - for (yasl_surface *p = display->surfaces; p; p = p->event_next) - if (p->parent == s) { + for (yasl_surface *p = display->surfaces; p && !child; p = p->event_next) + if (p->parent == s) child = p; - break; - } if (!child) break; yasl_surface_destroy(child); } - free(s->cpu_pixels); - s->cpu_pixels = NULL; - free(s->committed_pixels); - s->committed_pixels = NULL; + free(s->canvas); + s->canvas = NULL; free(s->text_surrounding); s->text_surrounding = NULL; yasl_private_surface_destroy(s); return 0; } -int yasl_shutdown(void) -{ - if (display && !yasl_public_owner()) - return -errno; - if (pumping || refresh_depth) { - yasl_public_fail(EBUSY); - return -errno; - } - if (display) { - for (yasl_surface *s = display->surfaces; s; s = s->event_next) - if (s->vulkan) - return yasl_public_error(EBUSY); - while (display->surfaces) - yasl_surface_destroy(display->surfaces); - pthread_mutex_lock(&lifecycle_mutex); - struct yasl_private_display *d = display; - display = NULL; - pthread_mutex_unlock(&lifecycle_mutex); - yasl_private_display_close(d); - } - free(delivered_text); - delivered_text = NULL; -#if YASL_HAS_VULKAN - extern void yasl_private_vulkan_reset(void); - yasl_private_vulkan_reset(); -#endif - return 0; -} -void yasl_wake(void) -{ - pthread_mutex_lock(&lifecycle_mutex); - if (display && display->loop) - yasl_private_loop_kick(display->loop); - pthread_mutex_unlock(&lifecycle_mutex); -} -uint64_t yasl_time_ns(void) -{ - return yasl_private_loop_now_ns(); -} -static double scale_of(const yasl_surface *s) -{ - return s && s->scale_120 ? (double)s->scale_120 / 120.0 : 1.0; -} -static int logical(int pixels, double scale) -{ - return (int)lround(pixels / scale); -} -enum yasl_backend yasl_surface_get_backend(const yasl_surface *s) -{ - return s && yasl_public_surface((yasl_surface *)s, false) - ? (enum yasl_backend)s->display->ops->type - : YASL_BACKEND_AUTO; -} -unsigned yasl_surface_get_features(const yasl_surface *s) +struct yasl_surface_info yasl_surface_get_info(const yasl_surface *s) { if (!s || !yasl_public_surface((yasl_surface *)s, false)) - return 0; - unsigned caps = yasl_private_display_caps(s->display); - if (s->display->ops->surface_create_gl) - caps |= YASL_CAP_OPENGL; -#if YASL_HAS_VULKAN - caps |= YASL_CAP_VULKAN; -#endif - return caps; -} -void yasl_surface_get_framebuffer_size(const yasl_surface *s, int *w, int *h) -{ - if (s && !yasl_public_surface((yasl_surface *)s, false)) - s = NULL; - if (w) - *w = s ? s->size.x : 0; - if (h) - *h = s ? s->size.y : 0; -} -void yasl_surface_get_size(const yasl_surface *s, int *w, int *h) -{ - if (s && !yasl_public_surface((yasl_surface *)s, false)) - s = NULL; + return (struct yasl_surface_info){.scale = 1.0}; double scale = scale_of(s); - if (w) - *w = s ? logical(s->size.x, scale) : 0; - if (h) - *h = s ? logical(s->size.y, scale) : 0; -} -double yasl_surface_get_scale(const yasl_surface *s) -{ - return s && yasl_public_surface((yasl_surface *)s, false) ? scale_of(s) : 1.0; -} -unsigned yasl_surface_get_state(const yasl_surface *s) -{ - return s && yasl_public_surface((yasl_surface *)s, false) ? s->state : 0; + return (struct yasl_surface_info){ + .width = logical(s->size.x, scale), + .height = logical(s->size.y, scale), + .pixel_width = s->size.x, + .pixel_height = s->size.y, + .scale = scale, + .state = s->state, + }; } void *yasl_surface_get_userdata(const yasl_surface *s) { @@ -302,54 +354,69 @@ int yasl_surface_set_title(yasl_surface *s, const char *title) s->display->ops->surface_set_title(s, title); return 0; } -#define REQUEST(name, priv, args, call) \ - int name args \ - { \ - if (!yasl_public_surface(s, false)) \ - return -errno; \ - errno = 0; \ - return yasl_public_status(priv call || yasl_public_fail(errno)); \ +int yasl_surface_request(yasl_surface *s, enum yasl_request request) +{ + if (!yasl_public_connected(s)) + return -errno; + switch (request) { + case YASL_REQUEST_RESTORE: { + uint32_t state = s->state; + errno = 0; + bool ok = !(state & YASL_PRIVATE_STATE_FULLSCREEN) || yasl_private_surface_set_fullscreen(s, false); + if (ok && (state & YASL_PRIVATE_STATE_MAXIMIZED || !(state & YASL_PRIVATE_STATE_FULLSCREEN))) + ok = yasl_private_surface_set_maximized(s, false); + return yasl_public_status(ok || yasl_public_fail(errno)); + } + case YASL_REQUEST_FULLSCREEN: + return STATUS(yasl_private_surface_set_fullscreen(s, true)); + case YASL_REQUEST_MAXIMIZE: + return STATUS(yasl_private_surface_set_maximized(s, true)); + case YASL_REQUEST_MINIMIZE: + return STATUS(yasl_private_surface_minimize(s)); } -REQUEST(yasl_surface_request_size, yasl_private_surface_set_size, (yasl_surface * s, int w, int h), - (s, ((struct yasl_private_vector2i){w, h}))) -REQUEST(yasl_surface_request_fullscreen, yasl_private_surface_set_fullscreen, - (yasl_surface * s, bool v), (s, v)) -REQUEST(yasl_surface_request_maximized, yasl_private_surface_set_maximized, - (yasl_surface * s, bool v), (s, v)) -REQUEST(yasl_surface_request_minimize, yasl_private_surface_minimize, (yasl_surface * s), (s)) -REQUEST(yasl_surface_set_pointer_mode, yasl_private_surface_set_pointer_mode, - (yasl_surface * s, enum yasl_pointer_mode m), (s, (enum yasl_private_pointer_mode)m)) + return yasl_public_error(EINVAL); +} +int yasl_surface_request_size(yasl_surface *s, int w, int h) +{ + if (!yasl_public_connected(s)) + return -errno; + double scale = scale_of(s); + if (!size_valid(w, h) || !size_valid((int)ceil(w * scale), (int)ceil(h * scale))) + return yasl_public_error(EINVAL); + return STATUS(yasl_private_surface_set_size(s, (struct yasl_private_vector2i){w, h})); +} int yasl_surface_set_cursor(yasl_surface *s, enum yasl_cursor cursor) { if (!yasl_public_surface(s, false)) return -errno; - if (cursor < YASL_CURSOR_ARROW || cursor > YASL_CURSOR_HIDDEN) + if ((unsigned)cursor > YASL_CURSOR_LOCKED) return yasl_public_error(EINVAL); + bool locked = cursor == YASL_CURSOR_LOCKED; + if (!yasl_private_surface_set_pointer_mode(s, locked ? YASL_PRIVATE_POINTER_RELATIVE + : YASL_PRIVATE_POINTER_NORMAL)) + return yasl_public_error(errno); + if (locked) + return 0; if (!s->display->ops->surface_set_cursor) return yasl_public_error(ENOTSUP); yasl_private_surface_set_cursor(s, (enum yasl_private_cursor)cursor); return 0; } -int yasl_surface_set_refresh_callback(yasl_surface *s, void (*fn)(yasl_surface *, void *), void *ud) -{ - if (!yasl_public_surface(s, false)) - return -errno; - s->refresh_callback = fn; - s->refresh_userdata = ud; - return 0; -} void yasl_private_surface_notify_refresh(yasl_surface *s) { if (!s || !s->display) return; - struct yasl_private_event ev = {.type = YASL_PRIVATE_EVENT_REFRESH, .surface = s}; - yasl_private_events_push(s->display, s, &ev); - if (s->refresh_callback && pumping && !refresh_depth) { + if (s->refresh && s->display->modal && pumping && !refresh_depth) { ++refresh_depth; - s->refresh_callback(s, s->refresh_userdata); + s->refresh(s); --refresh_depth; + return; } + struct yasl_private_event ev = {.type = YASL_PRIVATE_EVENT_REFRESH, .surface = s}; + yasl_private_events_push(s->display, s, &ev); } + +/* Text input */ static char *copy_text(const char *data, size_t length) { if (length == SIZE_MAX || (!data && length)) { @@ -357,20 +424,226 @@ static char *copy_text(const char *data, size_t length) return NULL; } char *p = malloc(length + 1); - if (!p) + if (!p) { + errno = ENOMEM; return NULL; + } if (length) memcpy(p, data, length); p[length] = 0; return p; } +static bool logical_rect(yasl_surface *s, double x, double y, double w, double h, + struct yasl_private_rect2i *r) +{ + if (!isfinite(x) || !isfinite(y) || !isfinite(w) || !isfinite(h) || w < 0 || h < 0) + return yasl_public_fail(EINVAL); + double scale = scale_of(s); + if (fabs(x) * scale >= INT_MAX || fabs(y) * scale >= INT_MAX || w * scale >= INT_MAX || + h * scale >= INT_MAX) + return yasl_public_fail(EOVERFLOW); + *r = (struct yasl_private_rect2i){ + yasl_private_scale_to_device(x, s->scale_120), yasl_private_scale_to_device(y, s->scale_120), + yasl_private_scale_to_device(w, s->scale_120), yasl_private_scale_to_device(h, s->scale_120)}; + return true; +} +static bool utf8_boundary(const char *text, size_t length, size_t at) +{ + return at >= length || ((unsigned char)text[at] & 0xc0) != 0x80; +} +static bool text_update(yasl_surface *s) +{ + if (!s->display->ops->surface_update_text_input) + return true; + errno = 0; + return s->display->ops->surface_update_text_input(s) || yasl_public_fail(errno); +} +int yasl_surface_set_text_input(yasl_surface *s, const struct yasl_text_input *in) +{ + if (!yasl_public_surface(s, false)) + return -errno; + struct yasl_private_rect2i caret = {0}; + char *copy = NULL; + if (in) { + if ((unsigned)in->purpose > YASL_TEXT_TERMINAL || (!in->text && in->length) || + in->selection_begin > in->selection_end || in->selection_end > in->length || + in->length > INT32_MAX || (in->length && memchr(in->text, 0, in->length))) + return yasl_public_error(EINVAL); + if (!yasl_private_utf8_validate(in->text ? in->text : "", in->length) || + !utf8_boundary(in->text, in->length, in->selection_begin) || + !utf8_boundary(in->text, in->length, in->selection_end)) + return yasl_public_error(EILSEQ); + if (!logical_rect(s, in->caret_x, in->caret_y, in->caret_width, in->caret_height, &caret)) + return -errno; + if (!(copy = copy_text(in->text, in->length))) + return yasl_public_error(errno); + } + struct text_state { + bool active; + unsigned purpose; + char *surrounding; + size_t length, begin, end; + uint64_t revision, generation; + } old = {s->text_input_active, s->text_purpose, s->text_surrounding, s->text_length, + s->text_begin, s->text_end, s->text_revision, s->text_generation}; + bool active = in != NULL; + unsigned purpose = in ? (unsigned)in->purpose : s->text_purpose; + if (active != old.active || purpose != old.purpose) + ++s->text_generation; + s->text_input_active = active; + s->text_purpose = purpose; + s->text_surrounding = copy; + s->text_length = in ? in->length : 0; + s->text_begin = in ? in->selection_begin : 0; + s->text_end = in ? in->selection_end : 0; + s->text_revision = in ? in->revision : old.revision; + if (!text_update(s)) { + int error = errno; + free(copy); + s->text_input_active = old.active; + s->text_purpose = old.purpose; + s->text_surrounding = old.surrounding; + s->text_length = old.length; + s->text_begin = old.begin; + s->text_end = old.end; + s->text_revision = old.revision; + s->text_generation = old.generation; + return yasl_public_error(error); + } + free(old.surrounding); + if (active && s->display->ops->surface_set_ime_caret) + yasl_private_surface_set_ime_caret(s, caret); + return 0; +} + +/* Window management */ +int yasl_surface_begin_move_resize(yasl_surface *s, unsigned edges) +{ + if (!yasl_public_connected(s)) + return -errno; + if ((edges & ~15u) || (edges & 3u) == 3u || (edges & 12u) == 12u) + return yasl_public_error(EINVAL); + if (!edges) + return STATUS(yasl_private_surface_start_interactive_move(s)); + return STATUS(yasl_private_surface_start_interactive_resize(s, (enum yasl_private_resize_edge)edges)); +} +int yasl_surface_show_window_menu(yasl_surface *s, double x, double y) +{ + struct yasl_private_rect2i r; + if (!yasl_public_connected(s) || !logical_rect(s, x, y, 0, 0, &r)) + return -errno; + return STATUS(yasl_private_surface_show_window_menu(s, (struct yasl_private_vector2i){r.x, r.y})); +} + +/* Popups */ +static bool popup_spec(yasl_surface *parent, const struct yasl_popup_options *o, + struct yasl_private_popup_spec *out) +{ + /* YASL_EDGE_* bits to xdg_positioner anchor/gravity values; -1 rejects opposite edges. */ + static const signed char placement[16] = {0, 1, 2, -1, 3, 5, 6, -1, 4, 7, 8, -1, -1, -1, -1, -1}; + if (!o || !size_valid(o->width, o->height) || o->anchor > 15 || o->gravity > 15 || + placement[o->anchor] < 0 || placement[o->gravity] < 0 || (o->adjust & ~63u)) + return yasl_public_fail(EINVAL); + double scale = scale_of(parent); + if (!size_valid((int)ceil(o->width * scale), (int)ceil(o->height * scale))) + return yasl_public_fail(EINVAL); + *out = (struct yasl_private_popup_spec){ + .size = {yasl_private_scale_to_device(o->width, parent->scale_120), + yasl_private_scale_to_device(o->height, parent->scale_120)}, + .anchor_edges = (unsigned)placement[o->anchor], + .gravity = (unsigned)placement[o->gravity], + .constraints = o->adjust ? o->adjust + : YASL_ADJUST_FLIP_X | YASL_ADJUST_FLIP_Y | YASL_ADJUST_SLIDE_X | + YASL_ADJUST_SLIDE_Y, + }; + return logical_rect(parent, o->x, o->y, o->anchor_width, o->anchor_height, &out->anchor); +} +yasl_surface *yasl_popup_create(yasl_surface *parent, const struct yasl_popup_options *o) +{ + struct yasl_private_popup_spec spec; + if (!yasl_public_connected(parent) || !yasl_public_surface(parent, true) || + !popup_spec(parent, o, &spec)) + return NULL; + errno = 0; + yasl_surface *s = yasl_private_surface_create_popup(parent, &spec); + if (!s) + return yasl_public_fail(errno), NULL; + register_surface(s, YASL_PRIVATE_SURFACE_CPU, parent, o->userdata); + return s; +} +int yasl_popup_reposition(yasl_surface *s, const struct yasl_popup_options *o) +{ + struct yasl_private_popup_spec spec; + if (!yasl_public_connected(s)) + return -errno; + if (!s->parent) + return yasl_public_error(EINVAL); + if (!popup_spec(s->parent, o, &spec)) + return -errno; + return STATUS(yasl_private_surface_popup_reposition(s, &spec)); +} + +/* Clipboard, drag and drop */ +int yasl_clipboard_set(enum yasl_selection selection, const char *text) +{ + if (!yasl_public_owner()) + return -errno; + if (atomic_load(&display->offline)) + return yasl_public_error(ENOTCONN); + return STATUS(yasl_private_clipboard_set(display, (enum yasl_private_selection)selection, text)); +} +int yasl_clipboard_request(yasl_surface *s, enum yasl_selection selection) +{ + if (!yasl_public_connected(s)) + return -errno; + return STATUS(yasl_private_clipboard_request(s, (enum yasl_private_selection)selection) != 0); +} +int yasl_drag_start(yasl_surface *s, const struct yasl_drag_image *source) +{ + if (!yasl_public_connected(s)) + return -errno; + struct yasl_private_drag_image image = {0}; + if (source) + image = (struct yasl_private_drag_image){ + .pixels = source->pixels, + .size = {source->pixel_width, source->pixel_height}, + .hotspot = {source->hotspot_x, source->hotspot_y}}; + return STATUS(yasl_private_display_drag_start(display, s, source ? &image : NULL, NULL, NULL)); +} +int yasl_drag_cancel(void) +{ + if (!yasl_public_owner()) + return -errno; + yasl_private_display_drag_cancel(display); + return 0; +} +int yasl_drag_carry(yasl_surface *s, double x, double y) +{ + struct yasl_private_rect2i r; + if (!yasl_public_connected(s) || !logical_rect(s, x, y, 0, 0, &r)) + return -errno; + return STATUS(yasl_private_surface_drag_carry(s, (struct yasl_private_vector2i){r.x, r.y})); +} +int yasl_surface_place_under_pointer(yasl_surface *s, double x, double y) +{ + struct yasl_private_rect2i r; + if (!yasl_public_connected(s) || !logical_rect(s, x, y, 0, 0, &r)) + return -errno; + return STATUS(yasl_private_surface_place_under_pointer(s, (struct yasl_private_vector2i){r.x, r.y})); +} +size_t yasl_drop_next_path(const char **cursor, char *out, size_t cap) +{ + return yasl_private_drop_next_path(cursor, out, cap); +} + +/* Events */ static const char *deliver_text(const char *data, size_t length) { free(delivered_text); delivered_text = copy_text(data, length); return delivered_text; } -/* Folds native size, state and focus changes into one WINDOW snapshot; unchanged values report +/* Folds native size, scale and state changes into one WINDOW snapshot; unchanged values report * nothing. */ static bool window_event(yasl_surface *s, struct yasl_private_vector2i size, uint32_t scale_120, uint32_t state, yasl_event *e) @@ -386,18 +659,31 @@ static bool window_event(yasl_surface *s, struct yasl_private_vector2i size, uin s->refresh_owed = true; double scale = scale_120 ? scale_120 / 120.0 : 1.0; e->type = YASL_EVENT_WINDOW; - e->window.pixel_width = size.x; - e->window.pixel_height = size.y; - e->window.scale = scale; - e->window.width = logical(size.x, scale); - e->window.height = logical(size.y, scale); - e->window.state = state; + e->window = (struct yasl_surface_info){ + .width = logical(size.x, scale), + .height = logical(size.y, scale), + .pixel_width = size.x, + .pixel_height = size.y, + .scale = scale, + .state = state, + }; return true; } static bool refresh_ready(yasl_surface *s) { return s->reported_size.x > 0 && s->reported_size.y > 0; } +static bool with_text(yasl_event *e, const char *data, size_t length) +{ + e->length = length; + e->text = deliver_text(data, length); + return e->text != NULL; +} +/* IME offsets: -1 (unknown) places the cursor at the end of the text. */ +static size_t ime_offset(int offset, size_t length) +{ + return offset < 0 || (size_t)offset > length ? length : (size_t)offset; +} static bool convert_event(const struct yasl_private_event *p, yasl_event *e) { *e = (yasl_event){.surface = p->surface, .time_ns = p->time_ns}; @@ -405,19 +691,19 @@ static bool convert_event(const struct yasl_private_event *p, yasl_event *e) switch (p->type) { case YASL_PRIVATE_EVENT_WINDOW_CLOSE: e->type = YASL_EVENT_CLOSE; - break; + return true; case YASL_PRIVATE_EVENT_DISPLAY_QUIT: e->type = YASL_EVENT_QUIT; - break; + return true; case YASL_PRIVATE_EVENT_DISPLAY_REOPEN: e->type = YASL_EVENT_REOPEN; - break; + return true; case YASL_PRIVATE_EVENT_DISPLAY_LOST: e->type = YASL_EVENT_DISPLAY_LOST; - break; + return true; case YASL_PRIVATE_EVENT_DISPLAY_RESTORED: e->type = YASL_EVENT_DISPLAY_RESTORED; - break; + return true; case YASL_PRIVATE_EVENT_REFRESH: if (!p->surface) return false; @@ -427,159 +713,134 @@ static bool convert_event(const struct yasl_private_event *p, yasl_event *e) } p->surface->refresh_owed = false; e->type = YASL_EVENT_REFRESH; - break; + return true; case YASL_PRIVATE_EVENT_INPUT_RESET: e->type = YASL_EVENT_INPUT_RESET; - break; - case YASL_PRIVATE_EVENT_FOCUS: { - yasl_surface *s = p->surface; - if (!s) + return true; + case YASL_PRIVATE_EVENT_FOCUS: + /* Activation comes from the window state; losing focus releases held input. */ + if (p->focus.focused) return false; - uint32_t state = p->focus.focused ? s->reported_state | YASL_STATE_ACTIVATED - : s->reported_state & ~(uint32_t)YASL_STATE_ACTIVATED; - return window_event(s, s->reported_size, s->reported_scale_120, state, e); - } + e->type = YASL_EVENT_INPUT_RESET; + return true; case YASL_PRIVATE_EVENT_WINDOW_STATE: - if (!p->surface) - return false; - return window_event(p->surface, p->surface->reported_size, p->surface->reported_scale_120, - p->state.flags, e); + return p->surface && window_event(p->surface, p->surface->reported_size, + p->surface->reported_scale_120, p->state.flags, e); case YASL_PRIVATE_EVENT_WINDOW_RESIZE: - if (!p->surface) - return false; - return window_event(p->surface, p->resize.geometry.size, p->resize.geometry.scale_120, - p->surface->reported_state, e); + return p->surface && window_event(p->surface, p->resize.geometry.size, + p->resize.geometry.scale_120, p->surface->reported_state, e); case YASL_PRIVATE_EVENT_KEY: e->type = YASL_EVENT_KEY; - e->key.key = p->key.key; - e->key.action = (enum yasl_key_action)p->key.action; - e->key.mods = p->key.mods; - e->key.codepoint = p->key.codepoint; - e->key.shifted = p->key.shifted; - e->key.native_code = p->key.native_code; - break; + e->action = (unsigned)p->key.action; + e->mods = p->key.mods; + e->key = (struct yasl_key_event){p->key.key, p->key.codepoint, p->key.shifted, + p->key.native_code}; + return true; case YASL_PRIVATE_EVENT_TEXT: e->type = YASL_EVENT_TEXT; - e->text.length = p->text.len; - e->text.data = deliver_text(p->text.data, p->text.len); - return e->text.data != NULL; + return with_text(e, p->text.data, p->text.len); case YASL_PRIVATE_EVENT_DROP: e->type = YASL_EVENT_DROP; - e->drop.action = YASL_DROP_DATA; - e->drop.length = p->text.len; - e->drop.data = deliver_text(p->text.data, p->text.len); - return e->drop.data != NULL; + e->action = YASL_DROP_RELEASE; + return with_text(e, p->text.data, p->text.len); case YASL_PRIVATE_EVENT_PASTE: e->type = YASL_EVENT_CLIPBOARD; - e->clipboard.length = p->paste.len; - e->clipboard.request_id = p->paste.request_id; - e->clipboard.status = (enum yasl_transfer_status)p->paste.status; - e->clipboard.data = deliver_text(p->paste.data, p->paste.len); - return e->clipboard.data != NULL; + e->action = (unsigned)p->paste.status; + return with_text(e, p->paste.data, p->paste.len); case YASL_PRIVATE_EVENT_COMPOSITION: e->type = YASL_EVENT_COMPOSITION; - e->composition.length = p->composition.len; - e->composition.begin = p->composition.begin; - e->composition.end = p->composition.end; - e->composition.active = p->composition.active; - e->composition.revision = p->composition.revision; - e->composition.generation = p->composition.generation; - e->composition.data = deliver_text(p->composition.data, p->composition.len); - return e->composition.data != NULL; + e->ime = (struct yasl_ime_event){ime_offset(p->composition.begin, p->composition.len), + ime_offset(p->composition.end, p->composition.len), + p->composition.revision}; + return with_text(e, p->composition.data, p->composition.len); case YASL_PRIVATE_EVENT_TEXT_EDIT: e->type = YASL_EVENT_TEXT_EDIT; - e->text_edit.length = p->text_edit.len; - e->text_edit.begin = p->text_edit.begin; - e->text_edit.end = p->text_edit.end; - e->text_edit.revision = p->text_edit.revision; - e->text_edit.generation = p->text_edit.generation; - e->text_edit.data = deliver_text(p->text_edit.data, p->text_edit.len); - return e->text_edit.data != NULL; + e->ime = (struct yasl_ime_event){p->text_edit.begin, p->text_edit.end, p->text_edit.revision}; + return with_text(e, p->text_edit.data, p->text_edit.len); case YASL_PRIVATE_EVENT_PEN: e->type = YASL_EVENT_PEN; -#define PEN(field) e->pen.field = p->pen.field - PEN(device_id); - PEN(tool_id); - PEN(x); - PEN(y); - PEN(pressure); - PEN(tilt_x); - PEN(tilt_y); - PEN(rotation); - PEN(distance); - PEN(tangential); - PEN(wheel_degrees); - PEN(axes_valid); - PEN(axes_changed); - PEN(buttons); - PEN(changed_button); - PEN(tool); - PEN(action); - PEN(in_contact); -#undef PEN - break; - case YASL_PRIVATE_EVENT_POINTER_SCROLL: + e->action = p->pen.action; + e->x = p->pen.x / scale; + e->y = p->pen.y / scale; + e->pen = (struct yasl_pen_event){ + .device_id = p->pen.device_id, + .tool_id = p->pen.tool_id, + .pressure = p->pen.pressure, + .tilt_x = p->pen.tilt_x, + .tilt_y = p->pen.tilt_y, + .rotation = p->pen.rotation, + .distance = p->pen.distance, + .tangential = p->pen.tangential, + .wheel_degrees = p->pen.wheel_degrees, + .tool = (enum yasl_pen_tool)p->pen.tool, + .axes = p->pen.axes_valid, + .buttons = p->pen.buttons, + .button = p->pen.changed_button, + .in_contact = p->pen.in_contact, + }; + return true; + case YASL_PRIVATE_EVENT_POINTER_SCROLL: { + unsigned valid = p->pointer.scroll_valid ? p->pointer.scroll_valid : YASL_PRIVATE_SCROLL_LINES; + bool lines = valid & YASL_PRIVATE_SCROLL_LINES, pixels = valid & YASL_PRIVATE_SCROLL_PIXELS; e->type = YASL_EVENT_SCROLL; - e->scroll.x = p->pointer.pos.x / scale; - e->scroll.y = p->pointer.pos.y / scale; - e->scroll.lines_x = p->pointer.scroll.x; - e->scroll.lines_y = p->pointer.scroll.y; - e->scroll.pixels_x = p->pointer.pixel_scroll.x / scale; - e->scroll.pixels_y = p->pointer.pixel_scroll.y / scale; - e->scroll.valid = p->pointer.scroll_valid ? p->pointer.scroll_valid : YASL_SCROLL_LINES; - e->scroll.source = p->pointer.scroll_source; - e->scroll.phase = p->pointer.scroll_phase; - e->scroll.momentum = p->pointer.scroll_momentum; - e->scroll.mods = p->pointer.mods; - break; + e->mods = p->pointer.mods; + e->x = p->pointer.pos.x / scale; + e->y = p->pointer.pos.y / scale; + e->scroll = (struct yasl_scroll_event){ + .lines_x = lines ? p->pointer.scroll.x : 0, + .lines_y = lines ? p->pointer.scroll.y : 0, + .pixels_x = pixels ? p->pointer.pixel_scroll.x / scale : 0, + .pixels_y = pixels ? p->pointer.pixel_scroll.y / scale : 0, + .source = (enum yasl_scroll_source)p->pointer.scroll_source, + .phase = (enum yasl_scroll_phase)p->pointer.scroll_phase, + .momentum = p->pointer.scroll_momentum, + }; + return true; + } case YASL_PRIVATE_EVENT_POINTER_DOWN: case YASL_PRIVATE_EVENT_POINTER_UP: case YASL_PRIVATE_EVENT_POINTER_MOTION: case YASL_PRIVATE_EVENT_POINTER_LEAVE: e->type = YASL_EVENT_POINTER; - e->pointer.x = - (p->pointer.precise_position ? p->pointer.precise_x : p->pointer.pos.x) / scale; - e->pointer.y = - (p->pointer.precise_position ? p->pointer.precise_y : p->pointer.pos.y) / scale; - e->pointer.dx = p->pointer.delta.x / scale; - e->pointer.dy = p->pointer.delta.y / scale; - e->pointer.action = p->type == YASL_PRIVATE_EVENT_POINTER_DOWN ? YASL_POINTER_DOWN - : p->type == YASL_PRIVATE_EVENT_POINTER_UP ? YASL_POINTER_UP - : p->type == YASL_PRIVATE_EVENT_POINTER_LEAVE ? YASL_POINTER_LEAVE - : YASL_POINTER_MOVE; - e->pointer.button = p->pointer.button; - e->pointer.buttons = p->pointer.buttons; - e->pointer.mods = p->pointer.mods; - break; + e->action = p->type == YASL_PRIVATE_EVENT_POINTER_DOWN ? YASL_POINTER_DOWN + : p->type == YASL_PRIVATE_EVENT_POINTER_UP ? YASL_POINTER_UP + : p->type == YASL_PRIVATE_EVENT_POINTER_LEAVE ? YASL_POINTER_LEAVE + : YASL_POINTER_MOVE; + e->mods = p->pointer.mods; + e->x = (p->pointer.precise_position ? p->pointer.precise_x : p->pointer.pos.x) / scale; + e->y = (p->pointer.precise_position ? p->pointer.precise_y : p->pointer.pos.y) / scale; + e->pointer = (struct yasl_pointer_event){p->pointer.delta.x / scale, p->pointer.delta.y / scale, + p->pointer.button, p->pointer.buttons}; + return true; case YASL_PRIVATE_EVENT_DRAG: e->type = YASL_EVENT_DRAG; - e->drag.action = (enum yasl_drag_action)p->drag.action; - e->drag.x = p->drag.pos.x / scale; - e->drag.y = p->drag.pos.y / scale; - break; + e->action = p->drag.action == YASL_PRIVATE_DRAG_OVER ? YASL_DROP_OVER + : p->drag.action == YASL_PRIVATE_DRAG_LEAVE ? YASL_DROP_LEAVE + : YASL_DROP_RELEASE; + e->x = p->drag.pos.x / scale; + e->y = p->drag.pos.y / scale; + return true; case YASL_PRIVATE_EVENT_DROP_HOVER: e->type = YASL_EVENT_DROP; - e->drop.action = - p->drop.action == YASL_PRIVATE_DROP_LEAVE ? YASL_DROP_LEAVE : YASL_DROP_OVER; - e->drop.x = p->drop.pos.x / scale; - e->drop.y = p->drop.pos.y / scale; - break; + e->action = p->drop.action == YASL_PRIVATE_DROP_LEAVE ? YASL_DROP_LEAVE : YASL_DROP_OVER; + e->x = p->drop.pos.x / scale; + e->y = p->drop.pos.y / scale; + return true; default: return false; } - return true; } static int take_event(yasl_event *out) { for (yasl_surface *s = display->surfaces; s; s = s->event_next) if (s->refresh_owed && refresh_ready(s)) { s->refresh_owed = false; - *out = - (yasl_event){.type = YASL_EVENT_REFRESH, .surface = s, .time_ns = yasl_time_ns()}; + *out = (yasl_event){.type = YASL_EVENT_REFRESH, .surface = s, .time_ns = yasl_time_ns()}; return 1; } struct yasl_private_event p; while (yasl_private_display_poll_event(display, &p)) { + /* Input method events from an older text field are stale. */ if (p.surface && ((p.type == YASL_PRIVATE_EVENT_COMPOSITION && p.composition.generation != p.surface->text_generation) || (p.type == YASL_PRIVATE_EVENT_TEXT_EDIT && @@ -591,547 +852,130 @@ static int take_event(yasl_event *out) *out = event; return 1; } - if (errno) { - yasl_public_fail(errno); - return -errno; - } + if (errno) + return yasl_public_error(errno); } return 0; } -int yasl_wait_event(yasl_event *out, int timeout) +/* Internal wakeups, such as a redraw request, keep waiting; only an event, the deadline or + * yasl_wake end the wait. */ +static int pump(yasl_event *out, int timeout) { - if (!out) { - yasl_public_fail(EINVAL); - return -errno; - } - *out = (yasl_event){.type = YASL_EVENT_NONE}; - if (!yasl_public_owner()) - return -errno; - if (pumping || refresh_depth) { - yasl_public_fail(EBUSY); - return -errno; - } - for (yasl_surface *s = display->surfaces; s; s = s->event_next) - s->cpu_acquired = false; - free(delivered_text); - delivered_text = NULL; - int n = take_event(out); - if (n) - return n; - pumping = true; - yasl_private_display_flush(display); - n = take_event(out); - if (!n) { - int work = yasl_private_display_dispatch(display, timeout); - if (work < 0) { - yasl_public_fail(errno); - n = -errno; + uint64_t deadline = timeout > 0 ? yasl_time_ns() + (uint64_t)timeout * 1000000u : 0; + for (;;) { + int n = take_event(out); + if (n) + return n; + yasl_private_display_flush(display); + if ((n = take_event(out))) + return n; + if (atomic_exchange(&wake_requested, false)) + return 0; + int wait = timeout; + if (timeout > 0) { + uint64_t now = yasl_time_ns(); + if (now >= deadline) + return 0; + wait = (int)((deadline - now + 999999u) / 1000000u); } + if (yasl_private_display_dispatch(display, wait) < 0) + return yasl_public_error(errno); yasl_private_display_flush(display); - if (n >= 0) - n = take_event(out); + if ((n = take_event(out)) || timeout == 0) + return n; } - pumping = false; - return n; } -static uint64_t cpu_sequence; -int yasl_cpu_acquire(yasl_surface *s, struct yasl_cpu_buffer *out) +int yasl_wait_event(yasl_event *out, int timeout) { if (!out) return yasl_public_error(EINVAL); - *out = (struct yasl_cpu_buffer){0}; - if (!yasl_public_surface(s, false)) - return -errno; - if (s->presenter != YASL_PRIVATE_PRESENTER_CPU) - return yasl_public_error(ENOTSUP); - if (s->display->offline) - return yasl_public_error(ENOTCONN); - if (s->cpu_acquired) - return yasl_public_error(EBUSY); - int w = s->size.x, h = s->size.y; - if (w <= 0 || h <= 0) - return yasl_public_error(EAGAIN); - if ((size_t)w > SIZE_MAX / sizeof(uint32_t) / (size_t)h) - return yasl_public_error(EOVERFLOW); - if (s->cpu_width != w || s->cpu_height != h || !s->cpu_pixels) { - uint32_t *pixels = malloc((size_t)w * h * sizeof(*pixels)); - if (!pixels) - return yasl_public_error(ENOMEM); - free(s->cpu_pixels); - s->cpu_pixels = pixels; - s->cpu_width = w; - s->cpu_height = h; - } - memset(s->cpu_pixels, 0, (size_t)w * h * sizeof(uint32_t)); - int keep_w = w < s->committed_width ? w : s->committed_width; - int keep_h = h < s->committed_height ? h : s->committed_height; - if (s->committed_pixels) - for (int y = 0; y < keep_h; y++) - memcpy(s->cpu_pixels + (size_t)y * w, - s->committed_pixels + (size_t)y * s->committed_width, - (size_t)keep_w * sizeof(uint32_t)); - if (++cpu_sequence == 0) - ++cpu_sequence; - s->cpu_token = cpu_sequence; - s->cpu_acquired = true; - *out = - (struct yasl_cpu_buffer){s->cpu_pixels, w, h, (size_t)w * sizeof(uint32_t), s->cpu_token}; - return 0; -} -static int cpu_validate(yasl_surface *s, const struct yasl_cpu_buffer *buffer) -{ - if (!yasl_public_surface(s, false)) - return -errno; - if (!buffer) - return yasl_public_error(EINVAL); - if (s->presenter != YASL_PRIVATE_PRESENTER_CPU) - return yasl_public_error(ENOTSUP); - if (!s->cpu_acquired || !buffer->token || buffer->token != s->cpu_token || - s->cpu_width != s->size.x || s->cpu_height != s->size.y) - return yasl_public_error(ESTALE); - if (buffer->pixels != s->cpu_pixels || buffer->width != s->cpu_width || - buffer->height != s->cpu_height || - buffer->stride_bytes != (size_t)s->cpu_width * sizeof(uint32_t)) - return yasl_public_error(EINVAL); - return 0; -} -int yasl_cpu_present(yasl_surface *s, const struct yasl_cpu_buffer *buffer) -{ - int error = cpu_validate(s, buffer); - if (error) - return error; - if (s->display->offline) - return yasl_public_error(ENOTCONN); - size_t bytes = (size_t)s->cpu_width * s->cpu_height * sizeof(uint32_t); - if (s->committed_width != s->cpu_width || s->committed_height != s->cpu_height || - !s->committed_pixels) { - uint32_t *pixels = malloc(bytes); - if (!pixels) - return yasl_public_error(ENOMEM); - free(s->committed_pixels); - s->committed_pixels = pixels; - s->committed_width = s->cpu_width; - s->committed_height = s->cpu_height; - } - memcpy(s->committed_pixels, s->cpu_pixels, bytes); - s->cpu_acquired = false; - yasl_private_surface_request_frame(s); - return 0; -} -int yasl_cpu_discard(yasl_surface *s, const struct yasl_cpu_buffer *buffer) -{ - int error = cpu_validate(s, buffer); - if (error) - return error; - s->cpu_acquired = false; - return 0; -} -int yasl_get_double_click_ms(void) -{ - return display && yasl_public_owner() ? yasl_private_display_double_click_ms(display) : 400; -} -int yasl_clipboard_set(enum yasl_selection sel, const char *text) -{ + *out = (yasl_event){.type = YASL_EVENT_NONE}; if (!yasl_public_owner()) return -errno; - errno = 0; - return yasl_public_status( - yasl_private_clipboard_set(display, (enum yasl_private_selection)sel, text) || - yasl_public_fail(errno)); -} -uint64_t yasl_clipboard_request(yasl_surface *s, enum yasl_selection sel) -{ - if (!yasl_public_surface(s, false)) - return 0; - errno = 0; - uint64_t id = yasl_private_clipboard_request(s, (enum yasl_private_selection)sel); - if (!id) - yasl_public_fail(errno); - return id; -} -static bool text_update(yasl_surface *s) -{ - if (!s->display->ops->surface_update_text_input) - return true; - errno = 0; - return s->display->ops->surface_update_text_input(s) || yasl_public_fail(errno); -} -int yasl_surface_set_text_input(yasl_surface *s, bool active, enum yasl_text_purpose purpose) -{ - if (!yasl_public_surface(s, false)) - return -errno; - if (purpose < YASL_TEXT_NORMAL || purpose > YASL_TEXT_TERMINAL) - return yasl_public_error(EINVAL); - bool old = s->text_input_active; - unsigned old_purpose = s->text_purpose; - uint64_t generation = s->text_generation; - if (old != active || old_purpose != (unsigned)purpose) - ++s->text_generation; - s->text_input_active = active; - s->text_purpose = purpose; - if (!text_update(s)) { - s->text_input_active = old; - s->text_purpose = old_purpose; - s->text_generation = generation; - return -errno; - } - return 0; + if (pumping || refresh_depth) + return yasl_public_error(EBUSY); + free(delivered_text); + delivered_text = NULL; + pumping = true; + int n = pump(out, timeout); + pumping = false; + if (n < 0) + *out = (yasl_event){.type = YASL_EVENT_NONE}; + return n; } -int yasl_surface_set_surrounding_text(yasl_surface *s, const char *text, size_t len, size_t begin, - size_t end, uint64_t revision) +void yasl_wake(void) { - if (!yasl_public_surface(s, false)) - return -errno; - if ((!text && len) || begin > end || end > len || len > INT32_MAX || - (len && memchr(text, 0, len))) - return yasl_public_error(EINVAL); - /* Validate UTF-8 and require selection boundaries between code points. */ - if ((begin < len && ((unsigned char)text[begin] & 0xc0) == 0x80) || - (end < len && ((unsigned char)text[end] & 0xc0) == 0x80)) - return yasl_public_error(EINVAL); - for (size_t i = 0; i < len;) { - unsigned char c = (unsigned char)text[i]; - size_t n = c < 0x80 ? 1 - : c >= 0xc2 && c <= 0xdf ? 2 - : c >= 0xe0 && c <= 0xef ? 3 - : c >= 0xf0 && c <= 0xf4 ? 4 - : 0; - if (!n || i + n > len) - return yasl_public_error(EILSEQ); - for (size_t j = 1; j < n; j++) - if (((unsigned char)text[i + j] & 0xc0) != 0x80) - return yasl_public_error(EILSEQ); - if (n >= 3 && ((c == 0xe0 && (unsigned char)text[i + 1] < 0xa0) || - (c == 0xed && (unsigned char)text[i + 1] >= 0xa0) || - (c == 0xf0 && (unsigned char)text[i + 1] < 0x90) || - (c == 0xf4 && (unsigned char)text[i + 1] >= 0x90))) - return yasl_public_error(EILSEQ); - i += n; - } - char *copy = copy_text(text, len); - if (!copy) - return yasl_public_error(errno); - char *old = s->text_surrounding; - size_t old_len = s->text_length, old_begin = s->text_begin, old_end = s->text_end; - uint64_t old_rev = s->text_revision; - s->text_surrounding = copy; - s->text_length = len; - s->text_begin = begin; - s->text_end = end; - s->text_revision = revision; - if (!text_update(s)) { - free(copy); - s->text_surrounding = old; - s->text_length = old_len; - s->text_begin = old_begin; - s->text_end = old_end; - s->text_revision = old_rev; - return -errno; - } - free(old); - return 0; + atomic_store(&wake_requested, true); + pthread_mutex_lock(&lifecycle_mutex); + if (display && display->loop) + yasl_private_loop_kick(display->loop); + pthread_mutex_unlock(&lifecycle_mutex); } -static bool logical_rect(yasl_surface *s, double x, double y, double w, double h, - struct yasl_private_rect2i *r) +uint64_t yasl_time_ns(void) { - if (!isfinite(x) || !isfinite(y) || !isfinite(w) || !isfinite(h) || w < 0 || h < 0) - return yasl_public_fail(EINVAL); - double scale = scale_of(s); - if (fabs(x) * scale > INT_MAX || fabs(y) * scale > INT_MAX || w * scale > INT_MAX || - h * scale > INT_MAX) - return yasl_public_fail(EOVERFLOW); - r->x = yasl_private_scale_to_device(x, s->scale_120); - r->y = yasl_private_scale_to_device(y, s->scale_120); - r->width = yasl_private_scale_to_device(w, s->scale_120); - r->height = yasl_private_scale_to_device(h, s->scale_120); - return true; + return yasl_private_loop_now_ns(); } -int yasl_surface_set_ime_caret(yasl_surface *s, double x, double y, double w, double h) + +/* CPU rendering */ +uint32_t *yasl_cpu_pixels(yasl_surface *s, int *pw, int *ph) { + if (pw) + *pw = 0; + if (ph) + *ph = 0; if (!yasl_public_surface(s, false)) - return -errno; - struct yasl_private_rect2i r; - if (!logical_rect(s, x, y, w, h, &r)) - return -errno; - if (!s->display->ops->surface_set_ime_caret) - return yasl_public_error(ENOTSUP); - yasl_private_surface_set_ime_caret(s, r); - return 0; -} -static bool trigger_valid(yasl_surface *s, const yasl_event *e) -{ - if (!e || e->surface != s || !e->time_ns || yasl_time_ns() - e->time_ns > 5000000000ull || - !((e->type == YASL_EVENT_POINTER && e->pointer.action == YASL_POINTER_DOWN) || - (e->type == YASL_EVENT_PEN && e->pen.action == YASL_PEN_DOWN))) { - return yasl_public_fail(ESTALE); - } - return true; -} -static bool popup_spec(yasl_surface *s, int w, int h, double x, double y, double aw, double ah, - enum yasl_popup_edge anchor, enum yasl_popup_edge gravity, - unsigned constraints, struct yasl_private_popup_spec *out) -{ - if (w <= 0 || h <= 0 || w > 32767 || h > 32767 || (unsigned)anchor > 8 || - (unsigned)gravity > 8 || (constraints & ~63u)) - return yasl_public_fail(EINVAL); - if (w * scale_of(s) > 32767 || h * scale_of(s) > 32767) - return yasl_public_fail(EOVERFLOW); - *out = (struct yasl_private_popup_spec){.size = {yasl_private_scale_to_device(w, s->scale_120), - yasl_private_scale_to_device(h, s->scale_120)}, - .anchor_edges = anchor, - .gravity = gravity, - .constraints = constraints}; - return logical_rect(s, x, y, aw, ah, &out->anchor); -} -yasl_surface *yasl_popup_create(yasl_surface *parent, const struct yasl_popup_options *options, - const yasl_event *trigger) -{ - if (!options) { - yasl_public_fail(EINVAL); return NULL; - } - int w = options->width, h = options->height; - double x = options->x, y = options->y, aw = options->anchor_width, ah = options->anchor_height; - enum yasl_popup_edge anchor = options->anchor, gravity = options->gravity; - unsigned constraints = options->constraints; - if (!yasl_public_surface(parent, true) || !trigger_valid(parent, trigger)) - return NULL; - struct yasl_private_popup_spec spec; - if (!popup_spec(parent, w, h, x, y, aw, ah, anchor, gravity, constraints, &spec)) - return NULL; - yasl_surface *s = yasl_private_surface_create_popup(parent, &spec); - if (!s) { - yasl_public_fail(errno); + if (s->presenter != YASL_PRIVATE_PRESENTER_CPU) + return yasl_public_fail(ENOTSUP), NULL; + if (!canvas_fit(s, s->size.x, s->size.y)) return NULL; - } - yasl_public_register_surface(s, parent->creation_flags, parent); - return s; + if (pw) + *pw = s->canvas_width; + if (ph) + *ph = s->canvas_height; + return s->canvas; } -int yasl_popup_reposition(yasl_surface *s, const struct yasl_popup_options *options) +int yasl_cpu_present(yasl_surface *s) { - if (!options) - return yasl_public_error(EINVAL); - int w = options->width, h = options->height; - double x = options->x, y = options->y, aw = options->anchor_width, ah = options->anchor_height; - enum yasl_popup_edge anchor = options->anchor, gravity = options->gravity; - unsigned constraints = options->constraints; - if (!yasl_public_surface(s, false)) + if (!yasl_public_connected(s)) return -errno; - struct yasl_private_popup_spec spec; - if (!s->parent) - return yasl_public_error(EINVAL); - if (!popup_spec(s->parent, w, h, x, y, aw, ah, anchor, gravity, constraints, &spec)) - return -errno; - errno = 0; - return yasl_public_status(yasl_private_surface_popup_reposition(s, &spec) || - yasl_public_fail(errno)); -} -int yasl_surface_start_interactive_move(yasl_surface *s, const yasl_event *e) -{ - if (!yasl_public_surface(s, false) || !trigger_valid(s, e)) - return -errno; - errno = 0; - return yasl_public_status(yasl_private_surface_start_interactive_move(s) || - yasl_public_fail(errno)); -} -int yasl_surface_start_interactive_resize(yasl_surface *s, enum yasl_resize_edge edge, - const yasl_event *e) -{ - if (!yasl_public_surface(s, false) || !trigger_valid(s, e)) - return -errno; - errno = 0; - return yasl_public_status( - yasl_private_surface_start_interactive_resize(s, (enum yasl_private_resize_edge)edge) || - yasl_public_fail(errno)); -} -int yasl_surface_show_window_menu(yasl_surface *s, double x, double y, const yasl_event *e) -{ - if (!yasl_public_surface(s, false) || !trigger_valid(s, e)) - return -errno; - struct yasl_private_rect2i r; - if (!logical_rect(s, x, y, 0, 0, &r)) - return -errno; - errno = 0; - return yasl_public_status( - yasl_private_surface_show_window_menu(s, (struct yasl_private_vector2i){r.x, r.y}) || - yasl_public_fail(errno)); -} -int yasl_drag_start(yasl_surface *s, const yasl_event *e, const struct yasl_drag_image *source) -{ - if (!yasl_public_surface(s, false) || !trigger_valid(s, e)) - return -errno; - struct yasl_private_drag_image image = {0}; - if (source) - image = (struct yasl_private_drag_image){.pixels = source->pixels, - .size = {source->width, source->height}, - .hotspot = {source->hotspot_x, source->hotspot_y}}; - errno = 0; - return yasl_public_status( - yasl_private_display_drag_start(display, s, source ? &image : NULL, NULL, NULL) || - yasl_public_fail(errno)); -} -int yasl_drag_cancel(void) -{ - if (!yasl_public_owner()) + if (s->presenter != YASL_PRIVATE_PRESENTER_CPU) + return yasl_public_error(ENOTSUP); + if (!canvas_fit(s, s->size.x, s->size.y)) return -errno; - yasl_private_display_drag_cancel(display); + yasl_private_surface_request_frame(s); return 0; } -int yasl_surface_drag_carry(yasl_surface *s, double x, double y) -{ - if (!yasl_public_surface(s, false)) - return -errno; - struct yasl_private_rect2i r; - if (!logical_rect(s, x, y, 0, 0, &r)) - return -errno; - errno = 0; - return yasl_public_status( - yasl_private_surface_drag_carry(s, (struct yasl_private_vector2i){r.x, r.y}) || - yasl_public_fail(errno)); -} -int yasl_surface_place_under_pointer(yasl_surface *s, double x, double y) -{ - if (!yasl_public_surface(s, false)) - return -errno; - struct yasl_private_rect2i r; - if (!logical_rect(s, x, y, 0, 0, &r)) - return -errno; - errno = 0; - return yasl_public_status( - yasl_private_surface_place_under_pointer(s, (struct yasl_private_vector2i){r.x, r.y}) || - yasl_public_fail(errno)); -} -size_t yasl_drop_next_path(const char **cursor, char *out, size_t cap) -{ - return yasl_private_drop_next_path(cursor, out, cap); -} -yasl_surface *yasl_gl_surface_create(const struct yasl_surface_options *options, - const struct yasl_gl_options *gl) +/* OpenGL; these may run on a rendering thread, so they read only immutable or atomic state. */ +static bool gl_surface(yasl_surface *s) { - if (!options || options->type != YASL_SURFACE_NATIVE || (options->flags & ~14u)) { - yasl_public_fail(EINVAL); - return NULL; - } - const struct yasl_gl_options defaults = YASL_GL_OPTIONS_INIT; - if (!gl) - gl = &defaults; - const int attrs[] = { - YASL_PRIVATE_GL_MAJOR, - gl->major, - YASL_PRIVATE_GL_MINOR, - gl->minor, - YASL_PRIVATE_GL_PROFILE, - gl->profile, - YASL_PRIVATE_GL_API, - gl->api, - YASL_PRIVATE_GL_RED_BITS, - gl->red_bits, - YASL_PRIVATE_GL_GREEN_BITS, - gl->green_bits, - YASL_PRIVATE_GL_BLUE_BITS, - gl->blue_bits, - YASL_PRIVATE_GL_ALPHA_BITS, - gl->alpha_bits, - YASL_PRIVATE_GL_DEPTH_BITS, - gl->depth_bits, - YASL_PRIVATE_GL_STENCIL_BITS, - gl->stencil_bits, - YASL_PRIVATE_GL_SAMPLES, - gl->samples, - YASL_PRIVATE_GL_DEBUG, - gl->debug, - YASL_PRIVATE_GL_FORWARD_COMPAT, - gl->forward_compat, - YASL_PRIVATE_GL_SRGB, - gl->srgb, - YASL_PRIVATE_GL_END, - }; - const char *title = options->title; - int w = options->width, h = options->height; - unsigned flags = options->flags; - yasl_surface *share = gl->share; - if (!creation_valid(w, h, flags)) { - return NULL; - } - struct yasl_private_gl_options parsed; - if (!yasl_private_gl_parse_options(attrs, &parsed)) { - yasl_public_fail(errno); - return NULL; - } - if (share && !display) { - yasl_public_fail(EINVAL); - return NULL; - } - bool fresh = !display; - if (!initialize()) - return NULL; - if (share && (share->display != display || share->presenter != YASL_PRIVATE_PRESENTER_GL)) { - yasl_public_fail(EINVAL); - return NULL; - } - if (!display->ops->surface_create_gl) { - if (fresh) - yasl_shutdown(); - yasl_public_fail(ENOTSUP); - return NULL; - } - struct yasl_private_window_spec spec = { - .title = title, .size = {w, h}, .flags = flags, .external_gpu = true}; - errno = 0; - yasl_surface *s = display->ops->surface_create_gl(display, &spec, attrs, share); - if (!s) { - int error = errno; - if (fresh) - yasl_shutdown(); - yasl_public_fail(error); - return NULL; - } - if (!yasl_private_events_register(s)) { - int error = errno; - display->ops->surface_destroy(s); - if (fresh) - yasl_shutdown(); - yasl_public_fail(error); - return NULL; - } - s->presenter = YASL_PRIVATE_PRESENTER_GL; - yasl_public_register_surface(s, flags, NULL); - s->userdata = options->userdata; - return s; + if (!display) + return yasl_public_fail(ENODEV); + if (!s || s->display != display || s->presenter != YASL_PRIVATE_PRESENTER_GL) + return yasl_public_fail(EINVAL); + return !atomic_load(&display->offline) || yasl_public_fail(ENOTCONN); } int yasl_gl_make_current(yasl_surface *s) { - if (!display) { - return yasl_public_error(ENODEV); - } - if (s && (s->display != display || s->presenter != YASL_PRIVATE_PRESENTER_GL)) - return yasl_public_error(EINVAL); - if (display->offline) - return yasl_public_error(ENOTCONN); + if (!s && (!display || !display->ops->gl_make_current)) + return 0; /* Nothing can be current. */ + if (s && !gl_surface(s)) + return -errno; if (!display->ops->gl_make_current) return yasl_public_error(ENOTSUP); - errno = 0; - return yasl_public_status(display->ops->gl_make_current(display, s) || yasl_public_fail(errno)); + return STATUS(display->ops->gl_make_current(display, s)); } yasl_gl_proc yasl_gl_get_proc_address(const char *name) { - if (!display) { - yasl_public_fail(ENODEV); - return NULL; - } - if (!name || !*name) { - yasl_public_fail(EINVAL); - return NULL; - } - if (display->offline) { - yasl_public_fail(ENOTCONN); - return NULL; - } - if (!display->ops->gl_get_proc_address) { - yasl_public_fail(ENOTSUP); - return NULL; - } + if (!display) + return yasl_public_fail(ENODEV), NULL; + if (!name || !*name) + return yasl_public_fail(EINVAL), NULL; + if (atomic_load(&display->offline)) + return yasl_public_fail(ENOTCONN), NULL; + if (!display->ops->gl_get_proc_address) + return yasl_public_fail(ENOTSUP), NULL; yasl_gl_proc p = display->ops->gl_get_proc_address(display, name); if (!p) yasl_public_fail(ENOENT); @@ -1139,27 +983,17 @@ yasl_gl_proc yasl_gl_get_proc_address(const char *name) } int yasl_gl_set_swap_interval(yasl_surface *s, int interval) { - if (!display) - return yasl_public_error(ENODEV); - if (display->offline) - return yasl_public_error(ENOTCONN); - if (!s || s->display != display || s->presenter != YASL_PRIVATE_PRESENTER_GL || - !s->display->ops->gl_swap_interval) - return yasl_public_error(EINVAL); - errno = 0; - return yasl_public_status(s->display->ops->gl_swap_interval(s->display, s, interval) || - yasl_public_fail(errno)); + if (!gl_surface(s)) + return -errno; + if (!display->ops->gl_swap_interval) + return yasl_public_error(ENOTSUP); + return STATUS(display->ops->gl_swap_interval(display, s, interval)); } int yasl_gl_present(yasl_surface *s) { - if (!display) - return yasl_public_error(ENODEV); - if (display->offline) - return yasl_public_error(ENOTCONN); - if (!s || s->display != display || s->presenter != YASL_PRIVATE_PRESENTER_GL || - !s->display->ops->gl_swap_buffers) - return yasl_public_error(EINVAL); - errno = 0; - return yasl_public_status(s->display->ops->gl_swap_buffers(s->display, s) || - yasl_public_fail(errno)); + if (!gl_surface(s)) + return -errno; + if (!display->ops->gl_swap_buffers) + return yasl_public_error(ENOTSUP); + return STATUS(display->ops->gl_swap_buffers(display, s)); } diff --git a/src/common/types.h b/src/common/types.h index 8b9bbba..3fffd47 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -126,7 +126,8 @@ enum yasl_private_display_cap : uint32_t { YASL_PRIVATE_CAP_FRAME_PACING = 1 << 7, /**< Compositor/display-driven draws. */ /* Backend/protocol supports tablet axes, independent of attached hardware. */ YASL_PRIVATE_CAP_TABLET = 1 << 8, - YASL_PRIVATE_CAP_TEXT_INPUT = 1 << 11, /**< Tablet protocol support, independent of attached hardware. */ + YASL_PRIVATE_CAP_RELATIVE_POINTER = 1 << 9, /**< YASL_PRIVATE_POINTER_RELATIVE is available. */ + YASL_PRIVATE_CAP_TEXT_INPUT = 1 << 11, /**< Input method support. */ }; /** Independent selections. An unsupported selection never aliases the clipboard. */ @@ -378,6 +379,9 @@ struct yasl_private_key_event { uint32_t native_code; }; +/** Bits of yasl_private_pointer_event.scroll_valid. */ +enum yasl_private_scroll_valid { YASL_PRIVATE_SCROLL_LINES = 1u, YASL_PRIVATE_SCROLL_PIXELS = 2u }; + /** Pointer sample shared by all POINTER_* events. Pen and eraser share it with * mouse input. Track contact with DOWN/UP of the primary button, not pressure. * This is not a multi-contact touch API. */ diff --git a/src/common/vulkan.c b/src/common/vulkan.c index 77a4dd4..cdf3e8b 100644 --- a/src/common/vulkan.c +++ b/src/common/vulkan.c @@ -12,12 +12,6 @@ #include #include #include -#if YASL_HAS_WAYLAND -#include "yasl/native/wayland.h" -#endif -#if YASL_HAS_X11 -#include "yasl/native/x11.h" -#endif static PFN_vkGetInstanceProcAddr get_proc; static bool portability; void yasl_private_vulkan_reset(void) @@ -32,17 +26,40 @@ struct yasl_vulkan_binding { bool has_allocator; PFN_vkDestroySurfaceKHR destroy; }; -int yasl_vulkan_init(PFN_vkGetInstanceProcAddr loader) +static bool backend_requirements(struct yasl_vulkan_requirements *out) { - struct yasl_private_display *d = yasl_public_display(); - if (d && !yasl_public_owner()) - return -errno; + static const char *const wayland[] = {"VK_KHR_surface", "VK_KHR_wayland_surface"}; + static const char *const x11[] = {"VK_KHR_surface", "VK_KHR_xlib_surface"}; + static const char *const metal[] = {"VK_KHR_surface", "VK_EXT_metal_surface", + "VK_KHR_portability_enumeration"}; + switch (yasl_public_display()->ops->type) { + case YASL_PRIVATE_BACKEND_WAYLAND: + *out = (struct yasl_vulkan_requirements){wayland, 2, 0}; + return true; + case YASL_PRIVATE_BACKEND_X11: + *out = (struct yasl_vulkan_requirements){x11, 2, 0}; + return true; + case YASL_PRIVATE_BACKEND_COCOA: + *out = (struct yasl_vulkan_requirements){ + metal, portability ? 3 : 2, + portability ? VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR : 0}; + return true; + default: + return yasl_public_fail(ENOTSUP); + } +} +int yasl_vulkan_init(PFN_vkGetInstanceProcAddr loader, struct yasl_vulkan_requirements *out) +{ + if (!out) + return yasl_public_error(EINVAL); + *out = (struct yasl_vulkan_requirements){0}; if (!loader) return yasl_public_error(EINVAL); - if (d) - for (yasl_surface *s = d->surfaces; s; s = s->event_next) - if (s->vulkan) - return yasl_public_error(EBUSY); + if (!yasl_public_initialize()) + return -errno; + for (yasl_surface *s = yasl_public_display()->surfaces; s; s = s->event_next) + if (s->vulkan) + return yasl_public_error(EBUSY); PFN_vkEnumerateInstanceExtensionProperties enumerate = (PFN_vkEnumerateInstanceExtensionProperties)loader( VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"); @@ -50,59 +67,22 @@ int yasl_vulkan_init(PFN_vkGetInstanceProcAddr loader) return yasl_public_error(ENOTSUP); uint32_t count = 0; VkResult result = enumerate(NULL, &count, NULL); - if (result != VK_SUCCESS) - return yasl_public_error(EIO); - VkExtensionProperties *ext = count ? calloc(count, sizeof(*ext)) : NULL; - if (count && !ext) - return yasl_public_error(ENOMEM); - bool next_portability = false; - if (count) + VkExtensionProperties *ext = NULL; + if (result == VK_SUCCESS && count) { + if (!(ext = calloc(count, sizeof(*ext)))) + return yasl_public_error(ENOMEM); result = enumerate(NULL, &count, ext); - if (result == VK_SUCCESS) - for (uint32_t i = 0; i < count; i++) - if (!strcmp(ext[i].extensionName, "VK_KHR_portability_enumeration")) - next_portability = true; + } + bool next_portability = false; + for (uint32_t i = 0; result == VK_SUCCESS && i < count; i++) + if (!strcmp(ext[i].extensionName, "VK_KHR_portability_enumeration")) + next_portability = true; free(ext); if (result != VK_SUCCESS) return yasl_public_error(EIO); get_proc = loader; portability = next_portability; - return 0; -} -int yasl_vulkan_get_requirements(const yasl_surface *s, struct yasl_vulkan_requirements *out) -{ - static const char *const wayland[] = {"VK_KHR_surface", "VK_KHR_wayland_surface"}; - static const char *const x11[] = {"VK_KHR_surface", "VK_KHR_xlib_surface"}; - static const char *const metal[] = {"VK_KHR_surface", "VK_EXT_metal_surface", - "VK_KHR_portability_enumeration"}; - if (!out) - return yasl_public_error(EINVAL); - *out = (struct yasl_vulkan_requirements){0}; - if (!yasl_public_surface((yasl_surface *)s, false)) - return -errno; - if (!get_proc) - return yasl_public_error(ENODEV); - if (s->presenter != YASL_PRIVATE_PRESENTER_NONE && - s->presenter != YASL_PRIVATE_PRESENTER_VULKAN) - return yasl_public_error(EBUSY); - switch (s->display->ops->type) { - case YASL_PRIVATE_BACKEND_WAYLAND: - out->extensions = wayland; - out->extension_count = 2; - break; - case YASL_PRIVATE_BACKEND_X11: - out->extensions = x11; - out->extension_count = 2; - break; - case YASL_PRIVATE_BACKEND_COCOA: - out->extensions = metal; - out->extension_count = portability ? 3 : 2; - out->flags = portability ? VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR : 0; - break; - default: - return yasl_public_error(ENOTSUP); - } - return 0; + return backend_requirements(out) ? 0 : -errno; } int yasl_vulkan_destroy_surface(yasl_surface *s) { @@ -133,7 +113,7 @@ VkResult yasl_vulkan_create_surface(yasl_surface *s, VkInstance instance, yasl_public_fail(ENODEV); return VK_ERROR_INITIALIZATION_FAILED; } - if (s->display->offline) { + if (atomic_load(&s->display->offline)) { yasl_public_fail(ENOTCONN); return VK_ERROR_SURFACE_LOST_KHR; } @@ -156,16 +136,16 @@ VkResult yasl_vulkan_create_surface(yasl_surface *s, VkInstance instance, switch (s->display->ops->type) { #if YASL_HAS_WAYLAND case YASL_PRIVATE_BACKEND_WAYLAND: { - struct yasl_wayland_handles native; - if (yasl_wayland_get_native(s, &native) < 0) + struct yasl_native native; + if (yasl_surface_get_native(s, &native) < 0) break; PFN_vkCreateWaylandSurfaceKHR create = (PFN_vkCreateWaylandSurfaceKHR)get_proc(instance, "vkCreateWaylandSurfaceKHR"); if (create) { VkWaylandSurfaceCreateInfoKHR info = { .sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, - .display = native.display, - .surface = native.surface}; + .display = native.wl_display, + .surface = native.wl_surface}; result = create(instance, &info, allocator, out); } break; @@ -173,16 +153,16 @@ VkResult yasl_vulkan_create_surface(yasl_surface *s, VkInstance instance, #endif #if YASL_HAS_X11 case YASL_PRIVATE_BACKEND_X11: { - struct yasl_x11_handles native; - if (yasl_x11_get_native(s, &native) < 0) + struct yasl_native native; + if (yasl_surface_get_native(s, &native) < 0) break; PFN_vkCreateXlibSurfaceKHR create = (PFN_vkCreateXlibSurfaceKHR)get_proc(instance, "vkCreateXlibSurfaceKHR"); if (create) { VkXlibSurfaceCreateInfoKHR info = {.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, - .dpy = native.display, - .window = (Window)native.window}; + .dpy = native.x11_display, + .window = (Window)native.x11_window}; result = create(instance, &info, allocator, out); } break; diff --git a/src/platform/cocoa/keymap.h b/src/platform/cocoa/keymap.h index 792b10d..bfb9604 100644 --- a/src/platform/cocoa/keymap.h +++ b/src/platform/cocoa/keymap.h @@ -1,6 +1,7 @@ #ifndef YASL_PRIVATE_COCOA_KEYMAP_H #define YASL_PRIVATE_COCOA_KEYMAP_H +#include #include "yasl/keycodes.h" enum yasl_key yasl_private_key_from_cocoa(uint32_t code); diff --git a/src/platform/freedesktop/desktop.c b/src/platform/freedesktop/desktop.c index f90701a..76a7956 100644 --- a/src/platform/freedesktop/desktop.c +++ b/src/platform/freedesktop/desktop.c @@ -136,6 +136,7 @@ size_t desktop_resource_dir(char *buf, size_t cap) { (void)buf; (void)cap; + errno = ENOTSUP; /* Linux applications have no bundle. */ return 0; } diff --git a/src/platform/wayland/keymap.h b/src/platform/wayland/keymap.h index 26b6ed4..2cef68c 100644 --- a/src/platform/wayland/keymap.h +++ b/src/platform/wayland/keymap.h @@ -1,6 +1,7 @@ #ifndef YASL_PRIVATE_WAYLAND_KEYMAP_H #define YASL_PRIVATE_WAYLAND_KEYMAP_H +#include #include "yasl/keycodes.h" enum yasl_key yasl_private_key_from_evdev(uint32_t code); diff --git a/src/platform/wayland/surface.c b/src/platform/wayland/surface.c index 635c905..2b72317 100644 --- a/src/platform/wayland/surface.c +++ b/src/platform/wayland/surface.c @@ -382,6 +382,8 @@ uint32_t wayland_display_caps(const struct wayland_display *c) caps |= YASL_PRIVATE_CAP_DRAG_CARRY; if (c->primary_selection_mgr) caps |= YASL_PRIVATE_CAP_PRIMARY_SELECTION; + if (c->relative_pointer_mgr && c->pointer_constraints) + caps |= YASL_PRIVATE_CAP_RELATIVE_POINTER; return caps; } diff --git a/src/platform/x11/keymap.h b/src/platform/x11/keymap.h index 9bb009d..daf4ce4 100644 --- a/src/platform/x11/keymap.h +++ b/src/platform/x11/keymap.h @@ -1,6 +1,7 @@ #ifndef YASL_PRIVATE_X11_KEYMAP_H #define YASL_PRIVATE_X11_KEYMAP_H +#include #include "yasl/keycodes.h" enum yasl_key yasl_private_key_from_xkb_name(const char name[4]); diff --git a/tests/common/test_gl.c b/tests/common/test_gl.c index 0e4597b..2bf3d8c 100644 --- a/tests/common/test_gl.c +++ b/tests/common/test_gl.c @@ -1,6 +1,6 @@ #define _POSIX_C_SOURCE 200809L -#include +#include #include #include #include @@ -60,7 +60,9 @@ static void wait_for_size(yasl_surface *surface, int width, int height) double deadline = seconds_now() + 8.0; int actual_width = 0, actual_height = 0; while (seconds_now() < deadline) { - yasl_surface_get_size(surface, &actual_width, &actual_height); + struct yasl_surface_info info = yasl_surface_get_info(surface); + actual_width = info.width; + actual_height = info.height; if (actual_width == width && actual_height == height) return; yasl_event event = {0}; int result = yasl_wait_event(&event, 50); @@ -75,11 +77,12 @@ static void wait_for_size(yasl_surface *surface, int width, int height) int main(int argc, char **argv) { REQUIRE(argc == 2, "expected backend argument: wayland or x11"); - if (!strcmp(argv[1], "wayland")) REQUIRE((yasl_init(&(struct yasl_options){.backend=YASL_BACKEND_WAYLAND}) == 0), "select Wayland backend"); - else if (!strcmp(argv[1], "x11")) REQUIRE((yasl_init(&(struct yasl_options){.backend=YASL_BACKEND_X11}) == 0), "select X11 backend"); + if (!strcmp(argv[1], "wayland")) REQUIRE(yasl_init(YASL_BACKEND_WAYLAND, NULL) == 0, "select Wayland backend"); + else if (!strcmp(argv[1], "x11")) REQUIRE(yasl_init(YASL_BACKEND_X11, NULL) == 0, "select X11 backend"); else REQUIRE(false, "backend must be wayland or x11"); - primary = yasl_gl_surface_create(&(struct yasl_surface_options){.title="YASL GL test", .width=320, .height=240, .type=YASL_SURFACE_NATIVE, .flags=0}, NULL); + primary = yasl_surface_create(&(struct yasl_surface_options){.title="YASL GL test", .width=320, .height=240, .type=YASL_SURFACE_GL}); + REQUIRE(yasl_get_features() & YASL_FEATURE_OPENGL, "report OpenGL support"); REQUIRE(primary != NULL, "create default OpenGL 3.2 surface"); REQUIRE((yasl_gl_make_current(primary) == 0), "make first GL context current"); @@ -109,7 +112,8 @@ int main(int argc, char **argv) finish(); unsigned char pixel[4] = {0}; int pixel_width = 0, pixel_height = 0; - yasl_surface_get_framebuffer_size(primary, &pixel_width, &pixel_height); + pixel_width = yasl_surface_get_info(primary).pixel_width; + pixel_height = yasl_surface_get_info(primary).pixel_height; REQUIRE(pixel_width > 0 && pixel_height > 0, "query initial framebuffer size"); read_pixels(pixel_width / 2, pixel_height / 2, 1, 1, GL_RGBA_, GL_UNSIGNED_BYTE_, pixel); REQUIRE(abs((int)pixel[0] - 51) <= 3 && abs((int)pixel[1] - 102) <= 3 && @@ -123,9 +127,7 @@ int main(int argc, char **argv) bind_texture(GL_TEXTURE_2D_, texture); REQUIRE(is_texture(texture), "create a shareable GL texture"); - struct yasl_gl_options sharing = YASL_GL_OPTIONS_INIT; - sharing.share = primary; - shared = yasl_gl_surface_create(&(struct yasl_surface_options){.title="YASL GL shared context", .width=160, .height=120, .type=YASL_SURFACE_NATIVE, .flags=0}, &sharing); + shared = yasl_surface_create(&(struct yasl_surface_options){.title="YASL GL shared context", .width=160, .height=120, .type=YASL_SURFACE_GL, .gl = {.share = primary}}); REQUIRE(shared != NULL, "create shared GL context"); REQUIRE((yasl_gl_make_current(shared) == 0), "make shared context current"); REQUIRE(is_texture(texture), "verify context shares GL objects"); @@ -134,8 +136,9 @@ int main(int argc, char **argv) REQUIRE((yasl_surface_request_size(primary, 400, 300) == 0), "request surface resize"); wait_for_size(primary, 400, 300); - yasl_surface_get_framebuffer_size(primary, &pixel_width, &pixel_height); - double scale = yasl_surface_get_scale(primary); + pixel_width = yasl_surface_get_info(primary).pixel_width; + pixel_height = yasl_surface_get_info(primary).pixel_height; + double scale = yasl_surface_get_info(primary).scale; REQUIRE(abs(pixel_width - (int)lround(400 * scale)) <= 1 && abs(pixel_height - (int)lround(300 * scale)) <= 1, "query resized framebuffer dimensions"); @@ -143,13 +146,11 @@ int main(int argc, char **argv) clear(GL_COLOR_BUFFER_BIT_); REQUIRE((yasl_gl_present(primary) == 0), "swap resized GL surface"); - struct yasl_gl_options invalid_options = YASL_GL_OPTIONS_INIT; - invalid_options.major = -1; - REQUIRE(yasl_gl_surface_create(&(struct yasl_surface_options){.title="invalid attrs", .width=100, .height=100, .type=YASL_SURFACE_NATIVE, .flags=0}, &invalid_options) == NULL, + REQUIRE(yasl_surface_create(&(struct yasl_surface_options){.title="invalid attrs", .width=100, .height=100, .type=YASL_SURFACE_GL, .gl = {.major = -1}}) == NULL && errno == EINVAL, "reject invalid GL context options"); - REQUIRE(yasl_gl_surface_create(&(struct yasl_surface_options){.title="invalid CPU GL", .width=100, .height=100, .type=YASL_SURFACE_CPU, .flags=0}, NULL) == NULL, - "reject CPU flag on GL surface creation"); - cpu = yasl_surface_create(&(struct yasl_surface_options){.title="CPU surface", .width=100, .height=100, .type=YASL_SURFACE_CPU, .flags=0}); + cpu = yasl_surface_create(&(struct yasl_surface_options){.title="CPU surface", .width=100, .height=100}); + REQUIRE(yasl_surface_create(&(struct yasl_surface_options){.title="no CPU sharing", .width=100, .height=100, .type=YASL_SURFACE_GL, .gl = {.share = cpu}}) == NULL && errno == EINVAL, + "reject a non-GL share surface"); REQUIRE(cpu != NULL, "create CPU surface for incompatibility check"); REQUIRE((yasl_gl_make_current(cpu) < 0), "reject CPU surface as a GL context"); diff --git a/tests/common/test_surface_integration.c b/tests/common/test_surface_integration.c index 1f18f23..072a307 100644 --- a/tests/common/test_surface_integration.c +++ b/tests/common/test_surface_integration.c @@ -1,17 +1,5 @@ #define _POSIX_C_SOURCE 200809L #include -#include -#include -#include -#if YASL_HAS_WAYLAND -#include -#endif -#if YASL_HAS_X11 -#include -#endif -#if YASL_HAS_COCOA -#include -#endif #include "common/backend.h" #include #include @@ -40,18 +28,20 @@ static void *wake_thread(void *unused) static void *wrong_thread(void *data) { assert(yasl_surface_set_title(data, "wrong") == -EPERM); - struct yasl_cpu_buffer buffer; - assert(yasl_cpu_acquire(data, &buffer) == -EPERM && !buffer.pixels); -#if YASL_HAS_WAYLAND - struct yasl_wayland_handles native; - assert(yasl_wayland_get_native(data, &native) == -EPERM && !native.display && !native.surface); -#endif + int w = 1, h = 1; + assert(!yasl_cpu_pixels(data, &w, &h) && errno == EPERM && !w && !h); + struct yasl_native native; + assert(yasl_surface_get_native(data, &native) == -EPERM && !native.wl_display && !native.x11_display); return NULL; } +static void push(yasl_surface *s, struct yasl_private_event event) +{ + event.surface = s; + assert(yasl_private_events_push(s->display, s, &event)); +} static int refreshes; -static void refresh(yasl_surface *s, void *ud) +static void refresh(yasl_surface *s) { - (void)ud; refreshes++; yasl_event e; assert(yasl_wait_event(&e, 0) == -EBUSY); @@ -59,7 +49,10 @@ static void refresh(yasl_surface *s, void *ud) assert(yasl_surface_destroy(s) == -EBUSY); struct yasl_surface_options options = {.width = 32, .height = 32}; assert(!yasl_surface_create(&options) && errno == EBUSY); - assert(!yasl_gl_surface_create(&options, NULL) && errno == EBUSY); + options.type = YASL_SURFACE_GL; + assert(!yasl_surface_create(&options) && errno == EBUSY); + int w, h; + assert(yasl_cpu_pixels(s, &w, &h) && yasl_cpu_present(s) == 0); } int main(int argc, char **argv) { @@ -67,53 +60,32 @@ int main(int argc, char **argv) enum yasl_backend backend = !strcmp(argv[1], "wayland") ? YASL_BACKEND_WAYLAND : !strcmp(argv[1], "x11") ? YASL_BACKEND_X11 : YASL_BACKEND_COCOA; - assert(yasl_init( - &(struct yasl_options){.backend = backend, .app_id = "org.yasl.surface-test"}) == 0); - yasl_surface *s = yasl_surface_create(&(struct yasl_surface_options){ - .title = "CPU contract", .width = 96, .height = 80, .type = YASL_SURFACE_CPU, .flags = 0}); + assert(yasl_init(backend, "org.yasl.surface-test") == 0); + assert(yasl_init(backend, NULL) == -EALREADY); + assert(yasl_get_backend() == backend); + unsigned features = yasl_get_features(); + yasl_surface *s = yasl_surface_create( + &(struct yasl_surface_options){.title = "CPU contract", .width = 96, .height = 80}); if (!s) { - const char *why; - why = strerror(errno); - fprintf(stderr, "surface: %s native=%d\n", why, errno); + fprintf(stderr, "surface: %s\n", strerror(errno)); return 1; } - assert(yasl_surface_get_backend(s) == backend); assert(yasl_surface_set_userdata(s, &backend) == 0); assert(yasl_surface_get_userdata(s) == &backend); -#if YASL_HAS_WAYLAND - struct yasl_wayland_handles wl; - int wl_result = yasl_wayland_get_native(s, &wl); - assert(wl_result == (backend == YASL_BACKEND_WAYLAND ? 0 : -ENOTSUP)); - assert(backend == YASL_BACKEND_WAYLAND ? wl.display && wl.surface : !wl.display && !wl.surface); - assert(yasl_wayland_get_native(s, NULL) == -EINVAL); -#endif -#if YASL_HAS_X11 - struct yasl_x11_handles x; - int x_result = yasl_x11_get_native(s, &x); - assert(x_result == (backend == YASL_BACKEND_X11 ? 0 : -ENOTSUP)); - assert(backend == YASL_BACKEND_X11 ? x.display && x.window : !x.display && !x.window); -#endif -#if YASL_HAS_COCOA - struct yasl_cocoa_handles cocoa; - assert(yasl_cocoa_get_native(s, &cocoa) == 0 && cocoa.application && cocoa.window && - cocoa.view); -#endif - CAMetalLayer *layer = NULL; - assert(yasl_metal_get_layer(s, &layer) == -EBUSY && !layer); - assert(yasl_surface_set_ime_caret(s, 1e300, 0, 1, 1) == -EOVERFLOW); + struct yasl_native native; + assert(yasl_surface_get_native(s, &native) == 0); + assert(backend != YASL_BACKEND_WAYLAND || (native.wl_display && native.wl_surface)); + assert(backend != YASL_BACKEND_X11 || (native.x11_display && native.x11_window)); + assert(backend != YASL_BACKEND_COCOA || (native.ns_window && native.ns_view)); + assert(yasl_surface_get_native(s, NULL) == -EINVAL); + assert(!yasl_metal_get_layer(s) && errno == EBUSY); if (backend == YASL_BACKEND_COCOA) { - assert(!(yasl_surface_get_features(s) & YASL_CAP_OPENGL)); - assert(!yasl_gl_surface_create(&(struct yasl_surface_options){.title = "unsupported GL", - .width = 64, - .height = 64, - .type = YASL_SURFACE_NATIVE, - .flags = 0}, - NULL)); - assert(errno == ENOTSUP); - assert((yasl_gl_make_current(NULL) < 0)); - assert(errno == ENOTSUP); - assert(!yasl_gl_get_proc_address("glClear")); + assert(!(features & YASL_FEATURE_OPENGL)); + assert(!yasl_surface_create(&(struct yasl_surface_options){ + .title = "unsupported GL", .width = 64, .height = 64, .type = YASL_SURFACE_GL})); assert(errno == ENOTSUP); + assert(yasl_gl_make_current(NULL) == 0); + assert(!yasl_gl_get_proc_address("glClear") && errno == ENOTSUP); } /* The first events report the window size, then ask for a frame. */ yasl_event e; @@ -132,131 +104,155 @@ int main(int argc, char **argv) } } assert(e.type == YASL_EVENT_REFRESH); + struct yasl_surface_info info = yasl_surface_get_info(s); + assert(info.pixel_width == fw && info.pixel_height == fh && info.scale > 0); + + /* CPU pixels persist between frames and are the window's own buffer. */ int w, h; - yasl_surface_get_framebuffer_size(s, &w, &h); - assert(w == fw && h == fh); - assert((yasl_init(&(struct yasl_options){.backend = backend}) < 0)); - assert(errno == EALREADY); - struct yasl_cpu_buffer buffer, other; - assert(yasl_cpu_acquire(s, &buffer) == 0); - uint32_t *pixels = buffer.pixels; - assert(buffer.width == w && buffer.height == h && buffer.stride_bytes == (size_t)w * 4); + uint32_t *pixels = yasl_cpu_pixels(s, &w, &h); + assert(pixels && w == fw && h == fh); assert(pixels[0] == 0 && pixels[(size_t)w * h - 1] == 0); - assert(yasl_cpu_acquire(s, &other) == -EBUSY && !other.pixels); for (size_t i = 0; i < (size_t)w * h; i++) pixels[i] = 0xff102030u; pixels[(size_t)3 * w + 2] = 0xffaabbccu; - other = buffer; - other.stride_bytes++; - assert(yasl_cpu_present(s, &other) == -EINVAL); - assert(yasl_cpu_present(s, &buffer) == 0); - assert(yasl_cpu_present(s, &buffer) == -ESTALE); - assert(yasl_cpu_acquire(s, &buffer) == 0); - assert(buffer.pixels[0] == 0xff102030u && buffer.pixels[(size_t)3 * w + 2] == 0xffaabbccu); - buffer.pixels[0] = 0xffffffffu; - assert(s->committed_pixels[0] == 0xff102030u); - assert(yasl_cpu_discard(s, &buffer) == 0); - assert(yasl_cpu_discard(s, &buffer) == -ESTALE); - assert(yasl_cpu_acquire(s, &buffer) == 0 && buffer.pixels[0] == 0xff102030u); + assert(yasl_cpu_present(s) == 0); + drain(); + pixels = yasl_cpu_pixels(s, &w, &h); + assert(pixels[0] == 0xff102030u && pixels[(size_t)3 * w + 2] == 0xffaabbccu); + + /* Waiting ends only on an event, the timeout or yasl_wake, never early. */ + drain(); + uint64_t start = yasl_time_ns(); + int n = yasl_wait_event(&e, 200); + assert(n == 1 || yasl_time_ns() - start >= 190000000ull); drain(); - assert(yasl_cpu_present(s, &buffer) == -ESTALE); - yasl_surface *second = yasl_surface_create(&(struct yasl_surface_options){ - .width = 32, .height = 32, .type = YASL_SURFACE_CPU, .userdata = &backend}); - assert(second && yasl_surface_get_userdata(second) == &backend); - assert(yasl_cpu_acquire(s, &buffer) == 0); - assert(yasl_cpu_acquire(second, &other) == 0); - assert(yasl_cpu_present(second, &buffer) == -ESTALE); - assert(yasl_cpu_discard(second, &other) == 0); - assert(yasl_surface_destroy(second) == 0); - assert(yasl_cpu_discard(s, &buffer) == 0); pthread_t thread; - assert(!pthread_create(&thread, NULL, wrong_thread, s)); - pthread_join(thread, NULL); assert(!pthread_create(&thread, NULL, wake_thread, NULL)); - int n = yasl_wait_event(&e, 500); - assert(n >= 0); + start = yasl_time_ns(); + n = yasl_wait_event(&e, 5000); pthread_join(thread, NULL); - bool ime = (yasl_surface_set_text_input(s, true, YASL_TEXT_NORMAL) == 0); - if (!ime) - assert(errno == ENOTSUP); - bool snapshot = (yasl_surface_set_surrounding_text(s, "a\xc3\xa9z", 4, 1, 3, 7) == 0); - if (ime) - assert(snapshot); - else - assert(!snapshot && errno == ENOTSUP); - assert((yasl_surface_set_surrounding_text(s, "a\xc3\xa9z", 4, 2, 3, 8) < 0)); - assert((yasl_surface_set_surrounding_text(s, "\xc0\xaf", 2, 0, 2, 8) < 0)); + assert(n == 1 || (n == 0 && yasl_time_ns() - start < 4000000000ull)); + assert(!pthread_create(&thread, NULL, wrong_thread, s)); + pthread_join(thread, NULL); + + yasl_surface *second = yasl_surface_create( + &(struct yasl_surface_options){.width = 32, .height = 32, .userdata = &backend}); + assert(second && yasl_surface_get_userdata(second) == &backend); + assert(yasl_surface_destroy(second) == 0); + + /* Text input */ + int ime = yasl_surface_set_text_input(s, &(struct yasl_text_input){ + .text = "a\xc3\xa9z", .length = 4, .selection_begin = 1, .selection_end = 3, + .revision = 7, .caret_x = 1, .caret_y = 2, .caret_width = 1, .caret_height = 10}); + assert(ime == 0 || ime == -ENOTSUP); + assert(yasl_surface_set_text_input(s, &(struct yasl_text_input){ + .text = "a\xc3\xa9z", .length = 4, .selection_begin = 2, .selection_end = 3}) == -EILSEQ); + assert(yasl_surface_set_text_input(s, &(struct yasl_text_input){ + .text = "\xc0\xaf", .length = 2}) == -EILSEQ); + assert(yasl_surface_set_text_input(s, &(struct yasl_text_input){ + .text = "ab", .length = 2, .selection_end = 3}) == -EINVAL); + assert(yasl_surface_set_text_input(s, &(struct yasl_text_input){.caret_x = 1e300}) == -EOVERFLOW); + assert(yasl_surface_set_text_input(s, NULL) == 0 || ime == -ENOTSUP); + + /* Arguments are checked before the backend sees them. */ + assert(yasl_surface_begin_move_resize(s, YASL_EDGE_TOP | YASL_EDGE_BOTTOM) == -EINVAL); + assert(yasl_surface_begin_move_resize(s, 16) == -EINVAL); + assert(yasl_surface_request(s, (enum yasl_request)9) == -EINVAL); + assert(yasl_surface_request_size(s, 0, 10) == -EINVAL); + assert(yasl_surface_set_cursor(s, (enum yasl_cursor)99) == -EINVAL); + int locked = yasl_surface_set_cursor(s, YASL_CURSOR_LOCKED); + assert(locked == ((features & YASL_FEATURE_LOCKED_CURSOR) ? 0 : -ENOTSUP)); + assert(yasl_surface_set_cursor(s, YASL_CURSOR_ARROW) == 0); + assert(yasl_drag_start(s, &(struct yasl_drag_image){.pixel_width = 4, .pixel_height = 4}) == -EINVAL); + uint32_t icon[4] = {0}; + assert(yasl_drag_start(s, &(struct yasl_drag_image){ + .pixels = icon, .pixel_width = 2, .pixel_height = 2, .hotspot_x = 2}) == -EINVAL); + assert(!yasl_popup_create(s, &(struct yasl_popup_options){.width = 10, .height = 10, + .anchor = YASL_EDGE_LEFT | YASL_EDGE_RIGHT}) && + errno == EINVAL); + assert(yasl_clipboard_set((enum yasl_selection)5, "x") == -EINVAL); + assert(yasl_clipboard_set(YASL_CLIPBOARD, NULL) == -EINVAL); + assert(yasl_clipboard_set(YASL_CLIPBOARD, "\xff") == -EILSEQ); + assert(yasl_clipboard_set(YASL_PRIMARY, "x") == + ((features & YASL_FEATURE_PRIMARY_SELECTION) ? 0 : -ENOTSUP)); drain(); - struct yasl_private_event pen = {.type = YASL_PRIVATE_EVENT_PEN, - .surface = s, - .pen = {.device_id = 10, - .tool_id = 20, - .x = 12.5, - .y = 9.25, - .pressure = 0.3, - .axes_valid = YASL_PRIVATE_PEN_PRESSURE, - .action = YASL_PRIVATE_PEN_DOWN, - .in_contact = true}}; - assert(yasl_private_events_push(s->display, s, &pen)); - assert(yasl_wait_event(&e, 0) == 1 && e.type == YASL_EVENT_PEN && e.pen.device_id == 10 && - e.pen.pressure == 0.3 && e.pen.in_contact); - struct yasl_private_event text = { - .type = YASL_PRIVATE_EVENT_TEXT, .surface = s, .text = {.data = "owned", .len = 5}}; - assert(yasl_private_events_push(s->display, s, &text)); + + /* Event conversion: positions are logical, text is owned until the next wait. */ + uint32_t real_scale = s->scale_120; + s->scale_120 = 240; + push(s, (struct yasl_private_event){ + .type = YASL_PRIVATE_EVENT_PEN, + .pen = {.device_id = 10, .x = 25, .y = 18.5, .pressure = 0.3, + .axes_valid = YASL_PRIVATE_PEN_PRESSURE, .action = YASL_PRIVATE_PEN_DOWN, + .in_contact = true}}); + assert(yasl_wait_event(&e, 0) == 1 && e.type == YASL_EVENT_PEN && e.action == YASL_PEN_DOWN); + assert(e.x == 12.5 && e.y == 9.25 && e.pen.device_id == 10 && e.pen.pressure == 0.3 && + (e.pen.axes & YASL_PEN_PRESSURE) && e.pen.in_contact); + push(s, (struct yasl_private_event){ + .type = YASL_PRIVATE_EVENT_POINTER_DOWN, + .pointer = {.pos = {20, 10}, .button = YASL_PRIVATE_BUTTON_SECONDARY, + .buttons = 4, .mods = YASL_PRIVATE_MOD_CTRL}}); + assert(yasl_wait_event(&e, 0) == 1 && e.type == YASL_EVENT_POINTER && + e.action == YASL_POINTER_DOWN && e.x == 10 && e.y == 5 && e.mods == YASL_MOD_CTRL && + e.pointer.button == YASL_BUTTON_SECONDARY && e.pointer.buttons == 4); + s->scale_120 = real_scale; + push(s, (struct yasl_private_event){.type = YASL_PRIVATE_EVENT_TEXT, .text = {"owned", 5}}); assert(yasl_wait_event(&e, 0) == 1 && e.type == YASL_EVENT_TEXT); - assert(e.text.length == 5 && !strcmp(e.text.data, "owned")); - /* Unchanged focus folds into no event; a change reports the activated state. */ - unsigned active = yasl_surface_get_state(s) & YASL_STATE_ACTIVATED; - struct yasl_private_event focus = { - .type = YASL_PRIVATE_EVENT_FOCUS, .surface = s, .focus = {.focused = !active}}; - assert(yasl_private_events_push(s->display, s, &focus)); - assert(yasl_wait_event(&e, 0) == 1 && e.type == YASL_EVENT_WINDOW); - assert((e.window.state & YASL_STATE_ACTIVATED) == (active ? 0u : YASL_STATE_ACTIVATED) && - e.window.pixel_width == w); - assert(yasl_private_events_push(s->display, s, &focus)); + assert(e.length == 5 && !strcmp(e.text, "owned")); + push(s, (struct yasl_private_event){ + .type = YASL_PRIVATE_EVENT_PASTE, + .paste = {.data = "", .request_id = 1, .status = YASL_PRIVATE_TRANSFER_UNAVAILABLE}}); + assert(yasl_wait_event(&e, 0) == 1 && e.type == YASL_EVENT_CLIPBOARD && + e.action == YASL_TRANSFER_UNAVAILABLE && e.length == 0); + /* Losing focus releases held input; activation itself comes from the window state. */ + push(s, (struct yasl_private_event){.type = YASL_PRIVATE_EVENT_FOCUS, .focus = {.focused = false}}); + assert(yasl_wait_event(&e, 0) == 1 && e.type == YASL_EVENT_INPUT_RESET); + push(s, (struct yasl_private_event){.type = YASL_PRIVATE_EVENT_FOCUS, .focus = {.focused = true}}); assert(yasl_wait_event(&e, 0) == 0 && e.type == YASL_EVENT_NONE); - struct yasl_private_event hover = {.type = YASL_PRIVATE_EVENT_DROP_HOVER, - .surface = s, - .drop = {YASL_PRIVATE_DROP_LEAVE, {0, 0}}}; - assert(yasl_private_events_push(s->display, s, &hover)); - assert(yasl_wait_event(&e, 0) == 1 && e.type == YASL_EVENT_DROP && - e.drop.action == YASL_DROP_LEAVE); + push(s, (struct yasl_private_event){.type = YASL_PRIVATE_EVENT_DROP_HOVER, + .drop = {YASL_PRIVATE_DROP_LEAVE, {0, 0}}}); + assert(yasl_wait_event(&e, 0) == 1 && e.type == YASL_EVENT_DROP && e.action == YASL_DROP_LEAVE); + + /* A resize keeps the overlap of the pixels and zeroes the rest. */ assert(yasl_surface_request_size(s, 112, 96) == 0); uint64_t resize_deadline = yasl_time_ns() + 3000000000ull; - int logical_w = 0, logical_h = 0; do { assert(yasl_wait_event(&e, 20) >= 0); - yasl_surface_get_size(s, &logical_w, &logical_h); - } while ((logical_w != 112 || logical_h != 96) && yasl_time_ns() < resize_deadline); - assert(logical_w == 112 && logical_h == 96); - assert(yasl_cpu_acquire(s, &buffer) == 0); - assert(buffer.pixels[0] == 0xff102030u); - assert(buffer.pixels[(size_t)3 * buffer.width + 2] == 0xffaabbccu); - assert(buffer.pixels[(size_t)buffer.width * buffer.height - 1] == 0); - assert(yasl_cpu_present(s, &buffer) == 0); - yasl_surface_set_refresh_callback(s, refresh, NULL); - assert(yasl_cpu_acquire(s, &buffer) == 0); - assert(yasl_cpu_present(s, &buffer) == 0); + info = yasl_surface_get_info(s); + } while ((info.width != 112 || info.height != 96) && yasl_time_ns() < resize_deadline); + assert(info.width == 112 && info.height == 96); + pixels = yasl_cpu_pixels(s, &w, &h); + assert(pixels && w == info.pixel_width && h == info.pixel_height); + assert(pixels[0] == 0xff102030u && pixels[(size_t)3 * w + 2] == 0xffaabbccu); + assert(pixels[(size_t)w * h - 1] == 0); + assert(yasl_cpu_present(s) == 0); + + /* The refresh callback is reserved for modal loops; ordinary waits deliver REFRESH. */ + s->refresh = refresh; + for (int i = 0; i < 5; i++) { + assert(yasl_cpu_pixels(s, &w, &h) && yasl_cpu_present(s) == 0); + assert(yasl_wait_event(&e, 20) >= 0); + } + assert(refreshes == 0); #if YASL_HAS_COCOA + s->display->modal = true; yasl_test_frame_tick(s); -#endif uint64_t deadline = yasl_private_loop_now_ns() + 3000000000ull; while (!refreshes && yasl_private_loop_now_ns() < deadline) assert(yasl_wait_event(&e, 20) >= 0); assert(refreshes > 0); - yasl_surface_set_refresh_callback(s, NULL, NULL); + s->display->modal = false; +#endif + s->refresh = NULL; yasl_surface_destroy(s); - assert((yasl_init(&(struct yasl_options){.backend = backend}) < 0)); + assert(yasl_init(backend, NULL) == -EALREADY); s = yasl_surface_create(&(struct yasl_surface_options){.title = "shared backend remains", - .width = 64, - .height = 64, - .type = YASL_SURFACE_CPU, - .flags = 0}); + .width = 64, .height = 64}); assert(s); yasl_shutdown(); - assert((yasl_init(&(struct yasl_options){.backend = backend}) == 0)); - s = yasl_surface_create(&(struct yasl_surface_options){ - .title = "reinitialized", .width = 64, .height = 64, .type = YASL_SURFACE_CPU, .flags = 0}); + assert(yasl_init(backend, NULL) == 0); + s = yasl_surface_create( + &(struct yasl_surface_options){.title = "reinitialized", .width = 64, .height = 64}); assert(s); yasl_shutdown(); puts("public surface lifecycle, pixels, ownership, input, wake and refresh passed"); diff --git a/tests/common/test_vulkan.c b/tests/common/test_vulkan.c index b12256a..4633d55 100644 --- a/tests/common/test_vulkan.c +++ b/tests/common/test_vulkan.c @@ -41,30 +41,29 @@ static int run_test(enum yasl_backend backend) VkSemaphore acquired = VK_NULL_HANDLE, rendered = VK_NULL_HANDLE; VkCommandBuffer command = VK_NULL_HANDLE; - if ((yasl_init(&(struct yasl_options){.backend=backend}) < 0)) { + if (yasl_init(backend, NULL) < 0) { fprintf(stderr, "vulkan integration: cannot select requested backend %d (YASL error %d)\n", backend, errno); goto cleanup; } - if ((yasl_vulkan_init(vkGetInstanceProcAddr) < 0)) { + struct yasl_vulkan_requirements requirements; + if (yasl_vulkan_init(vkGetInstanceProcAddr, &requirements) < 0) { fprintf(stderr, "vulkan integration: loader initialization failed (YASL error %d)\n", errno); goto cleanup; } - surface = yasl_surface_create(&(struct yasl_surface_options){.title="yasl Vulkan WSI integration", .width=256, .height=192, .type=YASL_SURFACE_NATIVE, .flags=0}); + surface = yasl_surface_create(&(struct yasl_surface_options){.title="yasl Vulkan WSI integration", .width=256, .height=192, .type=YASL_SURFACE_NATIVE}); if (!surface) { fprintf(stderr, "vulkan integration: surface creation failed (YASL error %d)\n", errno); goto cleanup; } - if (!(yasl_surface_get_features(surface) & YASL_CAP_VULKAN)) { + if (!(yasl_get_features() & YASL_FEATURE_VULKAN)) { fprintf(stderr, "vulkan integration: requested backend does not advertise Vulkan WSI\n"); goto cleanup; } uint32_t extension_count = 0; - struct yasl_vulkan_requirements requirements; - if (yasl_vulkan_get_requirements(surface, &requirements) < 0) goto cleanup; extension_count = requirements.extension_count; const char *const *required = requirements.extensions; if (!required || !extension_count) { @@ -114,7 +113,7 @@ static int run_test(enum yasl_backend backend) VkSurfaceKHR duplicate = VK_NULL_HANDLE; if (yasl_vulkan_create_surface(surface, instance, NULL, &duplicate) != VK_ERROR_NATIVE_WINDOW_IN_USE_KHR || duplicate != VK_NULL_HANDLE || yasl_surface_destroy(surface) != -EBUSY || - yasl_shutdown() != -EBUSY || yasl_vulkan_init(vkGetInstanceProcAddr) != -EBUSY) { + yasl_shutdown() != -EBUSY || yasl_vulkan_init(vkGetInstanceProcAddr, &requirements) != -EBUSY) { fprintf(stderr, "vulkan integration: live WSI ownership was not enforced\n"); goto cleanup; } @@ -191,8 +190,8 @@ static int run_test(enum yasl_backend backend) VkExtent2D extent = capabilities.currentExtent; if (extent.width == UINT32_MAX) { - int width = 0, height = 0; - yasl_surface_get_framebuffer_size(surface, &width, &height); + int width = yasl_surface_get_info(surface).pixel_width; + int height = yasl_surface_get_info(surface).pixel_height; if (width <= 0 || height <= 0) { fprintf(stderr, "vulkan integration: surface has no drawable framebuffer extent\n"); goto cleanup; diff --git a/tests/meson.build b/tests/meson.build index e34f072..7e1940e 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -15,17 +15,10 @@ if add_languages('cpp', native: false, required: false) '-DYASL_HAS_COCOA=' + (has_cocoa ? '1' : '0')], override_options: ['cpp_std=c++11']) test('cpp-api', cpp_api) endif -headers = files('../include/yasl/yasl.h', '../include/yasl/cpu.h', '../include/yasl/gl.h', - '../include/yasl/metal.h', '../include/yasl/desktop.h') +headers = files('../include/yasl/yasl.h') if has_vulkan headers += files('../include/yasl/vulkan.h') endif -foreach backend : ['wayland', 'x11', 'cocoa'] - enabled = {'wayland': has_wayland, 'x11': has_x11, 'cocoa': has_cocoa}[backend] - if enabled - headers += files('../include/yasl/native/' + backend + '.h') - endif -endforeach nm = find_program('nm') export_args = ['--platform=' + system] if get_option('b_sanitize').contains('address') diff --git a/tests/test_api.cpp b/tests/test_api.cpp index acfbbb8..beec36d 100644 --- a/tests/test_api.cpp +++ b/tests/test_api.cpp @@ -1,35 +1,24 @@ #include -#include -#include -#include -#include #if YASL_HAS_VULKAN #include #endif -#if YASL_HAS_WAYLAND -#include -#endif -#if YASL_HAS_X11 -#include -#endif -#if YASL_HAS_COCOA -#include -#endif #include int main() { yasl_event e{}; - e.pen.x = 1.5; + e.x = 1.5; e.pen.pressure = 0.25; - assert(e.pen.pressure == 0.25); + assert(e.pen.pressure == 0.25 && e.text == nullptr); yasl_surface_options options{}; options.width = -1; options.height = 10; assert(!yasl_surface_create(&options) && errno == EINVAL); - yasl_gl_options gl = YASL_GL_OPTIONS_INIT; - assert(gl.major == 3 && gl.minor == 2 && gl.share == nullptr); - yasl_cpu_buffer buffer{}; - assert(yasl_cpu_acquire(nullptr, &buffer) == -ENODEV); + yasl_gl_options gl{}; + assert(gl.major == 0 && gl.share == nullptr); + int w = 0, h = 0; + assert(!yasl_cpu_pixels(nullptr, &w, &h) && errno == ENODEV); + yasl_native native{}; + assert(yasl_surface_get_native(nullptr, &native) == -ENODEV); yasl_shutdown(); return 0; } diff --git a/tests/test_lifecycle.c b/tests/test_lifecycle.c index 08213b0..41dafe6 100644 --- a/tests/test_lifecycle.c +++ b/tests/test_lifecycle.c @@ -1,9 +1,6 @@ #include -#include -#include -#include -#include #include +#include int main(void) { assert(yasl_shutdown() == 0); @@ -11,27 +8,34 @@ int main(void) yasl_event event = {.type = YASL_EVENT_QUIT}; assert(yasl_wait_event(&event, 0) == -ENODEV && event.type == YASL_EVENT_NONE); assert(yasl_wait_event(NULL, 0) == -EINVAL); - assert(yasl_init(&(struct yasl_options){.backend = (enum yasl_backend)999}) == -EINVAL); + assert(yasl_init((enum yasl_backend)999, NULL) == -EINVAL); + assert(yasl_get_backend() == YASL_BACKEND_AUTO && yasl_get_features() == 0); assert(!yasl_surface_create(NULL) && errno == EINVAL); struct yasl_surface_options options = {.width = 0, .height = 64}; assert(!yasl_surface_create(&options) && errno == EINVAL); options.width = 64; - options.flags = 16; - assert(!yasl_surface_create(&options) && errno == EINVAL); - options.flags = 0; options.type = (enum yasl_surface_type)99; assert(!yasl_surface_create(&options) && errno == EINVAL); - struct yasl_cpu_buffer buffer = {.width = 9}; - assert(yasl_cpu_acquire(NULL, &buffer) == -ENODEV && !buffer.pixels && !buffer.width); - assert(yasl_cpu_acquire(NULL, NULL) == -EINVAL); - CAMetalLayer *layer = NULL; - assert(yasl_metal_get_layer(NULL, &layer) == -ENODEV && !layer); + options.type = YASL_SURFACE_GL; + options.gl.major = 9; + assert(!yasl_surface_create(&options) && errno == EINVAL); + int w = 9, h = 9; + assert(!yasl_cpu_pixels(NULL, &w, &h) && errno == ENODEV && w == 0 && h == 0); + assert(yasl_cpu_present(NULL) == -ENODEV); + assert(!yasl_metal_get_layer(NULL) && errno == ENODEV); + struct yasl_native native = {.x11_window = 7}; + assert(yasl_surface_get_native(NULL, &native) == -ENODEV && native.x11_window == 0); assert(yasl_surface_destroy(NULL) == 0); assert(yasl_time_ns() > 0); - int w = 9, h = 9; - yasl_surface_get_size(NULL, &w, &h); - assert(w == 0 && h == 0); - assert(yasl_get_executable_path(NULL, 0) > 0); + struct yasl_surface_info info = yasl_surface_get_info(NULL); + assert(info.width == 0 && info.pixel_height == 0 && info.scale == 1.0); + assert(yasl_gl_make_current(NULL) == 0); + assert(yasl_get_double_click_ms() == 400); + assert(yasl_get_system_string(YASL_EXECUTABLE_PATH, NULL, 0) > 0); + char locale[64]; + size_t n = yasl_get_system_string(YASL_LOCALE, locale, sizeof(locale)); + assert(n > 0 && n < sizeof(locale) && !strpbrk(locale, ".@")); + assert(yasl_get_system_string((enum yasl_system_string)9, locale, sizeof(locale)) == 0 && errno == EINVAL); assert(yasl_shutdown() == 0); return 0; } diff --git a/tests/wayland/test_reconnect.c b/tests/wayland/test_reconnect.c index 737afb2..891d98b 100644 --- a/tests/wayland/test_reconnect.c +++ b/tests/wayland/test_reconnect.c @@ -2,9 +2,6 @@ * after "ready" and starts a new one after "lost". Windows must survive, keep * their pixels, and accept new GL and Vulkan objects after DISPLAY_RESTORED. */ #include -#include -#include -#include #if TEST_VULKAN #include #endif @@ -58,29 +55,25 @@ static VkSurfaceKHR vulkan_surface(void) int main(void) { - CHECK((yasl_init(&(struct yasl_options){.backend=YASL_BACKEND_WAYLAND}) == 0), "selecting Wayland"); - cpu = yasl_surface_create(&(struct yasl_surface_options){.title="reconnect CPU", .width=160, .height=120, .type=YASL_SURFACE_CPU, .flags=0}); + CHECK(yasl_init(YASL_BACKEND_WAYLAND, NULL) == 0, "selecting Wayland"); + cpu = yasl_surface_create(&(struct yasl_surface_options){.title="reconnect CPU", .width=160, .height=120}); CHECK(cpu, "creating CPU window"); - gl = yasl_gl_surface_create(&(struct yasl_surface_options){.title="reconnect GL", .width=160, .height=120, .type=YASL_SURFACE_NATIVE, .flags=0}, NULL); + gl = yasl_surface_create(&(struct yasl_surface_options){.title="reconnect GL", .width=160, .height=120, .type=YASL_SURFACE_GL}); CHECK(gl, "creating GL window"); expect(YASL_EVENT_REFRESH, cpu, 5000); int w, h; - struct yasl_cpu_buffer buffer; - CHECK(yasl_cpu_acquire(cpu, &buffer) == 0, "acquire CPU buffer"); - w=buffer.width; h=buffer.height; - uint32_t *pixels = buffer.pixels; + uint32_t *pixels = yasl_cpu_pixels(cpu, &w, &h); CHECK(pixels, "getting pixels"); for (size_t i = 0; i < (size_t)w * h; i++) pixels[i] = 0xff336699u; - CHECK((yasl_cpu_present(cpu, &buffer) == 0), "presenting"); + CHECK(yasl_cpu_present(cpu) == 0, "presenting"); draw_gl(0.8f); #if TEST_VULKAN - CHECK((yasl_vulkan_init(vkGetInstanceProcAddr) == 0), "loading Vulkan"); - vk = yasl_surface_create(&(struct yasl_surface_options){.title="reconnect Vulkan", .width=160, .height=120, .type=YASL_SURFACE_NATIVE, .flags=0}); - CHECK(vk, "creating Vulkan window"); struct yasl_vulkan_requirements requirements; - CHECK(yasl_vulkan_get_requirements(vk, &requirements) == 0, "Vulkan requirements"); + CHECK(yasl_vulkan_init(vkGetInstanceProcAddr, &requirements) == 0, "loading Vulkan"); + vk = yasl_surface_create(&(struct yasl_surface_options){.title="reconnect Vulkan", .width=160, .height=120, .type=YASL_SURFACE_NATIVE}); + CHECK(vk, "creating Vulkan window"); VkInstanceCreateInfo info = {.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .flags = requirements.flags, .enabledExtensionCount = requirements.extension_count, .ppEnabledExtensionNames = requirements.extensions}; @@ -92,29 +85,29 @@ int main(void) fflush(stdout); expect(YASL_EVENT_DISPLAY_LOST, NULL, 10000); - struct yasl_wayland_handles native; - CHECK(yasl_wayland_get_native(cpu, &native) == -ENOTCONN && !native.display && !native.surface, + struct yasl_native native; + CHECK(yasl_surface_get_native(cpu, &native) == -ENOTCONN && !native.wl_display && !native.wl_surface, "rejecting native handles while offline"); - CHECK(yasl_cpu_acquire(cpu, &buffer) == -ENOTCONN, "rejecting CPU acquisition while offline"); + CHECK(yasl_cpu_present(cpu) == -ENOTCONN, "rejecting CPU presentation while offline"); CHECK(yasl_gl_make_current(gl) == -ENOTCONN, "rejecting GL binding while offline"); + CHECK(yasl_gl_make_current(NULL) == 0, "detaching GL while offline"); #if TEST_VULKAN CHECK(yasl_vulkan_destroy_surface(vk) == 0, "destroying Vulkan surface"); #endif - CHECK(!yasl_surface_create(&(struct yasl_surface_options){.title="offline", .width=64, .height=64, .type=YASL_SURFACE_CPU, .flags=0}), "refusing windows while offline"); + CHECK(!yasl_surface_create(&(struct yasl_surface_options){.title="offline", .width=64, .height=64}), "refusing windows while offline"); CHECK(errno == ENOTCONN, "reporting the disconnect"); puts("lost"); fflush(stdout); expect(YASL_EVENT_DISPLAY_RESTORED, NULL, 15000); - CHECK(yasl_wayland_get_native(cpu, &native) == 0 && native.display && native.surface, + CHECK(yasl_surface_get_native(cpu, &native) == 0 && native.wl_display && native.wl_surface, "querying restored native handles"); expect(YASL_EVENT_REFRESH, cpu, 5000); int rw, rh; - CHECK(yasl_cpu_acquire(cpu, &buffer) == 0, "reacquire CPU buffer"); - rw=buffer.width; rh=buffer.height; pixels=buffer.pixels; + pixels = yasl_cpu_pixels(cpu, &rw, &rh); CHECK(pixels && rw == w && rh == h, "keeping the window size"); CHECK(pixels[0] == 0xff336699u && pixels[(size_t)w * h - 1] == 0xff336699u, "keeping the pixels"); - CHECK((yasl_cpu_present(cpu, &buffer) == 0), "presenting after reconnect"); + CHECK(yasl_cpu_present(cpu) == 0, "presenting after reconnect"); draw_gl(0.1f); #if TEST_VULKAN CHECK(vulkan_surface() != VK_NULL_HANDLE, "restored Vulkan handle"); @@ -123,7 +116,7 @@ int main(void) #endif /* A second window proves the new connection accepts creation. */ - CHECK(yasl_surface_create(&(struct yasl_surface_options){.title="after reconnect", .width=64, .height=64, .type=YASL_SURFACE_CPU, .flags=0}), "creating a window after reconnect"); + CHECK(yasl_surface_create(&(struct yasl_surface_options){.title="after reconnect", .width=64, .height=64}), "creating a window after reconnect"); yasl_gl_make_current(NULL); yasl_shutdown(); puts("restored"); diff --git a/tests/x11/test_tablet_native.c b/tests/x11/test_tablet_native.c index 66cdd93..bbe39f2 100644 --- a/tests/x11/test_tablet_native.c +++ b/tests/x11/test_tablet_native.c @@ -2,7 +2,6 @@ #define main tablet_example_main #include "../../examples/tablet.c" #undef main -#include #include #include #include @@ -59,7 +58,8 @@ static int connect_tool(const char *path) } struct canvas_state { - struct yasl_pen_payload pen; + struct yasl_pen_event pen; + double x, y; uint32_t *pixels; int width, height, painted_width, painted_height; bool down; @@ -69,24 +69,24 @@ static void render_event(yasl_surface *surface, struct canvas_state *state, cons { if (event->type == YASL_EVENT_PEN) { state->pen = event->pen; + state->x = event->x; + state->y = event->y; state->down = event->pen.in_contact; } - struct yasl_cpu_buffer buffer; free(state->pixels); - state->pixels = canvas(surface, &state->width, &state->height, - &state->painted_width, &state->painted_height, false, &buffer); - assert(state->pixels); + uint32_t *pixels = canvas(surface, &state->width, &state->height, + &state->painted_width, &state->painted_height, false); + assert(pixels); if (event->type == YASL_EVENT_PEN && state->down && - (event->pen.action == YASL_PEN_DOWN || event->pen.action == YASL_PEN_MOVE)) { - double scale = yasl_surface_get_scale(surface); - stamp(state->pixels, state->width, state->height, - event->pen.x * scale, event->pen.y * scale, + (event->action == YASL_PEN_DOWN || event->action == YASL_PEN_MOVE)) { + double scale = yasl_surface_get_info(surface).scale; + stamp(pixels, state->width, state->height, event->x * scale, event->y * scale, (1.5 + 12 * event->pen.pressure) * scale, event->pen.tool == YASL_PEN_ERASER); } uint32_t *snapshot = malloc((size_t)state->width * state->height * sizeof(*snapshot)); assert(snapshot); - memcpy(snapshot, buffer.pixels, (size_t)state->width * state->height * sizeof(*snapshot)); - assert(yasl_cpu_present(surface, &buffer) == 0); + memcpy(snapshot, pixels, (size_t)state->width * state->height * sizeof(*snapshot)); + assert(yasl_cpu_present(surface) == 0); state->pixels = snapshot; } @@ -107,12 +107,12 @@ static void pump(int fd, yasl_surface *surface, struct canvas_state *state) receive_all(fd, &response, sizeof(response)); assert(response.type == XF86IT_RESPONSE_SYNC_FINISHED && response.length == sizeof(response)); /* The driver acknowledgment guarantees XI2 processing has finished. */ - struct yasl_x11_handles native; - assert(yasl_x11_get_native(surface, &native) == 0); - XSync(native.display, False); + struct yasl_native native; + assert(yasl_surface_get_native(surface, &native) == 0); + XSync(native.x11_display, False); drain(surface, state); /* Public polling submits the CPU frame before the native readback. */ - XSync(native.display, False); + XSync(native.x11_display, False); } static void set_eraser_property(Display *display) @@ -140,13 +140,13 @@ int main(int argc, char **argv) { assert(argc == 3); /* pen socket, eraser socket */ struct canvas_state app = {0}; - assert((yasl_init(&(struct yasl_options){.backend=YASL_BACKEND_X11}) == 0)); - yasl_surface *surface = yasl_surface_create(&(struct yasl_surface_options){.title="YASL native pressure test", .width=500, .height=400, .type=YASL_SURFACE_CPU, .flags=0}); - assert(surface && (yasl_surface_get_features(surface) & YASL_CAP_TABLET)); - struct yasl_x11_handles native; - assert(yasl_x11_get_native(surface, &native) == 0); - Display *x = native.display; - Window window = (Window)native.window; + assert(yasl_init(YASL_BACKEND_X11, NULL) == 0); + yasl_surface *surface = yasl_surface_create(&(struct yasl_surface_options){.title="YASL native pressure test", .width=500, .height=400}); + assert(surface && (yasl_get_features() & YASL_FEATURE_TABLET)); + struct yasl_native native; + assert(yasl_surface_get_native(surface, &native) == 0); + Display *x = native.x11_display; + Window window = (Window)native.x11_window; XMoveWindow(x, window, 0, 0); XSetInputFocus(x, window, RevertToParent, CurrentTime); XSync(x, False); @@ -163,7 +163,7 @@ int main(int argc, char **argv) send_all(pen_fd, &motion, sizeof(motion)); pump(pen_fd, surface, &app); assert(!app.down && app.pen.tool == YASL_PEN_PEN); - assert(app.pen.x == 150 && app.pen.y == 200); + assert(app.x == 150 && app.y == 200); assert(app.pixels[(size_t)200 * app.width + 150] == PAPER); xf86ITEventButton button = {.header = {sizeof(button), XF86IT_EVENT_BUTTON}, .is_absolute = 1, .button = 1, .is_press = 1, .valuators = motion.valuators}; -- cgit v1.2.3