diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-14 13:13:52 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-14 13:13:52 +0300 |
| commit | 1ab3e5a123109704789c2768e11a4f9af515821d (patch) | |
| tree | 31ee03e58093a58100c6eb58681d5d29648a5ddd | |
| parent | d86fa6215d40caa7852dbbd6a8cf4aa8e89dd879 (diff) | |
Redefine public and rendering APIs with explicit contracts
29 files changed, 2263 insertions, 829 deletions
diff --git a/examples/game.c b/examples/game.c index 0be4461..850b39b 100644 --- a/examples/game.c +++ b/examples/game.c @@ -12,10 +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("YASL OpenGL", 800, 600, 0, NULL, NULL); - if (!s || !yasl_gl_make_current(s)) { + 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; - yasl_get_error(&error); + error = strerror(errno); fprintf(stderr, "%s\n", error); yasl_shutdown(); return 1; @@ -27,7 +27,7 @@ int main(int argc, char **argv) yasl_shutdown(); return 1; } - yasl_gl_swap_interval(s, 1); + yasl_gl_set_swap_interval(s, 1); int result = 0, frames = 0; bool online = true; for (bool running = true; running;) { @@ -40,7 +40,7 @@ int main(int argc, char **argv) if (e.type == YASL_EVENT_DISPLAY_LOST) 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) && yasl_gl_swap_interval(s, 1); + online = (yasl_gl_make_current(s) == 0) && (yasl_gl_set_swap_interval(s, 1) == 0); } if (n > 0 || !online || !running) continue; int w, h; @@ -49,7 +49,7 @@ int main(int argc, char **argv) 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_swap_buffers(s)) { result = 1; break; } + if ((yasl_gl_present(s) < 0)) { result = 1; break; } if (smoke && ++frames == 3) break; } yasl_gl_make_current(NULL); diff --git a/examples/hello.c b/examples/hello.c index 9e324a6..054051d 100644 --- a/examples/hello.c +++ b/examples/hello.c @@ -1,26 +1,50 @@ -/* A CPU-rendered window. Build instructions: docs/getting-started.md. */ +/* A CPU-rendered window. */ #include <yasl/yasl.h> #include <yasl/cpu.h> int main(void) { - yasl_surface *surface = yasl_surface_create("Hello YASL", 640, 480, YASL_SURFACE_CPU); - yasl_event event; - while (surface && yasl_wait_event(&event, -1) >= 0 && - event.type != YASL_EVENT_CLOSE && event.type != YASL_EVENT_QUIT) { - if (event.type != YASL_EVENT_REFRESH) continue; - int w, h; - uint32_t *pixels = yasl_cpu_pixels(surface, &w, &h); - for (int y = 0; pixels && y < h; y++) { - for (int x = 0; x < w; x++) { - /* Red grows rightward, green downward, blue fades; opaque premultiplied ARGB. */ - 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); + struct yasl_surface_options options = { + .title = "Hello YASL", + .width = 640, + .height = 480, + .type = YASL_SURFACE_CPU, + }; + yasl_surface *surface = yasl_surface_create(&options); + if (!surface) + return 1; + + int result = 0; + for (;;) { + yasl_event event; + if (yasl_wait_event(&event, -1) < 0) { + result = 1; + break; + } + 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; + 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++) { + /* 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); } } - yasl_cpu_present(surface); + if (yasl_cpu_present(surface, &buffer) < 0) { + result = 1; + break; + } } yasl_shutdown(); - return surface ? 0 : 1; + return result; } diff --git a/examples/notepad.c b/examples/notepad.c index 9134653..ff12038 100644 --- a/examples/notepad.c +++ b/examples/notepad.c @@ -576,7 +576,7 @@ static void editor_copy(struct editor *ed) snprintf(buf, (size_t)MAX_LINES * MAX_LINE_LEN, "%s\n", ed->lines[ed->cursor_row]); } - if (!yasl_clipboard_set(YASL_CLIPBOARD, buf)) + if ((yasl_clipboard_set(YASL_CLIPBOARD, buf) < 0)) editor_set_status(ed, "Clipboard copy failed"); free(buf); editor_set_status(ed, "Copied to clipboard"); @@ -988,7 +988,7 @@ static void prompt_execute(struct editor *ed) static bool editor_init(struct editor *ed) { - ed->surface = yasl_surface_create("Untitled - YASL Notepad", 960, 640, YASL_SURFACE_CPU); + ed->surface = yasl_surface_create(&(struct yasl_surface_options){.title="Untitled - YASL Notepad", .width=960, .height=640, .type=YASL_SURFACE_CPU, .flags=0}); 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); @@ -1483,8 +1483,10 @@ static void on_event(struct editor *ed, const yasl_event *ev) static void editor_render(struct editor *ed) { - int w, h; - uint32_t *pix = yasl_cpu_pixels(ed->surface, &w, &h); + 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; if (!pix) return; double display_scale = yasl_surface_get_scale(ed->surface); @@ -1624,7 +1626,7 @@ static void editor_render(struct editor *ed) ed->is_modified ? 0xfffab387 : status_accent, scale); } - if (!yasl_cpu_present(ed->surface)) { + if ((yasl_cpu_present(ed->surface, &buffer) < 0)) { fprintf(stderr, "Could not present CPU image\n"); ed->failed = true; ed->running = false; @@ -1697,7 +1699,7 @@ int main(int argc, char **argv) } } - if (!yasl_set_app_id("org.yasl.notepad") || !editor_init(&ed)) { + if ((yasl_init(&(struct yasl_options){.app_id="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 cb12db1..4b4b552 100644 --- a/examples/simple_window.c +++ b/examples/simple_window.c @@ -6,7 +6,7 @@ int main(int argc, char **argv) { bool smoke = argc == 2 && !strcmp(argv[1], "--smoke-test"); - yasl_surface *s = yasl_surface_create("YASL input", 640, 480, YASL_SURFACE_CPU); + 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; double px = -100, py = -100, pressure = 0; bool dirty = false; @@ -29,8 +29,10 @@ int main(int argc, char **argv) } if (e.type == YASL_EVENT_REFRESH) dirty = true; if (dirty) { - int w, h; - uint32_t *pixels = yasl_cpu_pixels(s, &w, &h); + 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++) { @@ -39,7 +41,7 @@ int main(int argc, char **argv) pixels[(size_t)y * w + x] = dx * dx + dy * dy < radius * radius ? 0xffffc060u : 0xff203040u; } } - if (!yasl_cpu_present(s)) break; + if ((yasl_cpu_present(s, &buffer) < 0)) break; dirty = false; frames++; } diff --git a/examples/tablet.c b/examples/tablet.c index 176a82b..ce796a4 100644 --- a/examples/tablet.c +++ b/examples/tablet.c @@ -24,10 +24,11 @@ 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) +static uint32_t *canvas(yasl_surface *s, int *w, int *h, int *painted_w, int *painted_h, bool clear, struct yasl_cpu_buffer *buffer) { - uint32_t *pixels = yasl_cpu_pixels(s, w, h); - if (!pixels) return NULL; + if (yasl_cpu_acquire(s, buffer) < 0) return NULL; + *w = buffer->width; *h = buffer->height; + uint32_t *pixels = buffer->pixels; 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; @@ -43,11 +44,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_set_backend(!strcmp(b, "wayland") ? YASL_BACKEND_WAYLAND : - !strcmp(b, "x11") ? YASL_BACKEND_X11 : YASL_BACKEND_COCOA)) return 1; + 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; } else return 1; } - yasl_surface *s = yasl_surface_create("YASL pen canvas — C clears, Escape exits", 800, 600, YASL_SURFACE_CPU); + 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}); if (!s) return 1; int painted_w = 0, painted_h = 0, frames = 0, result = 1; bool contact = false; @@ -87,7 +88,8 @@ int main(int argc, char **argv) if (draw || present) { int w, h; int old_w = painted_w, old_h = painted_h; - uint32_t *pixels = canvas(s, &w, &h, &painted_w, &painted_h, clear); + struct yasl_cpu_buffer buffer; + uint32_t *pixels = canvas(s, &w, &h, &painted_w, &painted_h, clear, &buffer); if (!pixels) break; if (w != old_w || h != old_h) contact = false; if (draw) { @@ -99,7 +101,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)) break; + if ((yasl_cpu_present(s, &buffer) < 0)) break; frames++; } if (e.type == YASL_EVENT_PEN || e.type == YASL_EVENT_POINTER) contact = down; diff --git a/include/yasl/cpu.h b/include/yasl/cpu.h index d18f91f..23e0803 100644 --- a/include/yasl/cpu.h +++ b/include/yasl/cpu.h @@ -1,14 +1,29 @@ -/** @file CPU surfaces: draw into the lent pixel buffer, then present it. */ +/** @file CPU rendering with explicitly acquired frame buffers. */ #ifndef YASL_CPU_H #define YASL_CPU_H #include "yasl/yasl.h" #ifdef __cplusplus extern "C" { #endif -/** width*height premultiplied 0xAARRGGBB pixels, rows tightly packed. Contents - * persist; a resize keeps the overlap and zeroes the rest. Valid until the next wait. */ -YASL_API uint32_t *yasl_cpu_pixels(yasl_surface *surface, int *width, int *height); -YASL_API bool yasl_cpu_present(yasl_surface *surface); +/** Borrowed premultiplied 0xAARRGGBB pixels; stride_bytes describes each row. + * Only pixels may be edited. Preserve the remaining fields for present/discard. + * The buffer expires on successful present/discard, the next wait, or destruction. + * Do not retain or access pixels after expiry. */ +struct yasl_cpu_buffer { + uint32_t *pixels; + int width, height; + size_t stride_bytes; + uint64_t token; +}; +/** One outstanding acquisition per surface (-EBUSY otherwise). Starts with the + * last submitted pixels; resize preserves their overlap and clears new pixels. + * Clears out on failure. Acquisition does not change the visible frame. */ +YASL_API int yasl_cpu_acquire(yasl_surface *surface, struct yasl_cpu_buffer *out); +/** Snapshot this buffer for presentation, then consume its token. Failed calls + * leave a valid acquisition retryable. Stale/consumed tokens return -ESTALE. + * Success means queued, not confirmed scanout. */ +YASL_API int yasl_cpu_present(yasl_surface *surface, const struct yasl_cpu_buffer *buffer); +YASL_API int yasl_cpu_discard(yasl_surface *surface, const struct yasl_cpu_buffer *buffer); #ifdef __cplusplus } #endif diff --git a/include/yasl/desktop.h b/include/yasl/desktop.h index 2c2379e..6dc24e9 100644 --- a/include/yasl/desktop.h +++ b/include/yasl/desktop.h @@ -5,16 +5,20 @@ #ifdef __cplusplus extern "C" { #endif -enum yasl_appearance { YASL_APPEARANCE_DARK=1u, YASL_APPEARANCE_LIGHT=2u, YASL_APPEARANCE_HIGH_CONTRAST=4u }; -YASL_API bool yasl_open_uri(const char *uri); -YASL_API bool yasl_notify(const char *title, const char *body); -YASL_API unsigned yasl_appearance(void); -YASL_API bool yasl_surface_bell(yasl_surface *surface); +enum yasl_appearance { + YASL_APPEARANCE_DARK = 1u, + YASL_APPEARANCE_LIGHT = 2u, + YASL_APPEARANCE_HIGH_CONTRAST = 4u +}; +YASL_API int yasl_open_uri(const char *uri); +YASL_API int yasl_notify(const char *title, const char *body); +YASL_API unsigned yasl_get_appearance(void); +YASL_API int yasl_surface_bell(yasl_surface *surface); /** Full byte length excluding NUL, zero on failure. Buffer unchanged unless cap * exceeds returned length. NULL/zero queries size. These three are thread-safe. */ -YASL_API size_t yasl_executable_path(char *out, size_t capacity); -YASL_API size_t yasl_resource_dir(char *out, size_t capacity); -YASL_API size_t yasl_default_locale(char *out, size_t capacity); +YASL_API size_t yasl_get_executable_path(char *out, size_t capacity); +YASL_API size_t yasl_get_resource_dir(char *out, size_t capacity); +YASL_API size_t yasl_get_default_locale(char *out, size_t capacity); #ifdef __cplusplus } #endif diff --git a/include/yasl/gl.h b/include/yasl/gl.h index 39d2c63..d5603e3 100644 --- a/include/yasl/gl.h +++ b/include/yasl/gl.h @@ -1,27 +1,36 @@ -/** @file Optional EGL/OpenGL/OpenGL ES contexts on Linux. Attribute lists are per-call. */ +/** @file Optional EGL/OpenGL/OpenGL ES contexts on Linux. */ #ifndef YASL_GL_H #define YASL_GL_H #include "yasl/yasl.h" #ifdef __cplusplus extern "C" { #endif -enum yasl_gl_attribute { YASL_GL_END, YASL_GL_MAJOR, YASL_GL_MINOR, YASL_GL_PROFILE, YASL_GL_DEBUG, - YASL_GL_FORWARD_COMPAT, YASL_GL_RED_BITS, YASL_GL_GREEN_BITS, YASL_GL_BLUE_BITS, YASL_GL_ALPHA_BITS, - YASL_GL_DEPTH_BITS, YASL_GL_STENCIL_BITS, YASL_GL_SAMPLES, YASL_GL_SRGB, YASL_GL_API }; enum yasl_gl_profile { YASL_GL_ANY, YASL_GL_CORE, YASL_GL_COMPATIBILITY }; enum yasl_gl_api { YASL_GL_OPENGL, YASL_GL_OPENGL_ES }; typedef void (*yasl_gl_proc)(void); -/** key/value pairs terminated by END, NULL defaults to GL3.2 core RGBA8 D24 S8. - * share is a live GL surface or NULL. CPU flag is invalid. Context is owned by - * the returned surface. Detach it from all rendering threads before destruction. - * Cocoa returns NULL with YASL_ERROR_UNSUPPORTED; use Metal or MoltenVK. */ -YASL_API yasl_surface *yasl_gl_surface_create(const char *title, int width, int height, - unsigned flags, const int *attributes, yasl_surface *share); -/** May run on a rendering thread. NULL detaches this thread's current context. */ -YASL_API bool yasl_gl_make_current(yasl_surface *surface); +struct yasl_gl_options { + int major, minor; + enum yasl_gl_profile profile; + enum yasl_gl_api api; + int red_bits, green_bits, blue_bits, alpha_bits; + int depth_bits, stencil_bits, samples; + bool debug, forward_compat, srgb; + yasl_surface *share; +}; +/** C and C++ initializer: GL 3.2 core, RGBA8, D24, S8. */ +#define YASL_GL_OPTIONS_INIT \ + {3, 2, YASL_GL_CORE, YASL_GL_OPENGL, 8, 8, 8, 8, 24, 8, 0, false, false, false, NULL} +/** NULL gl selects defaults. options->type must be NATIVE. share, when set, + * must be a live GL surface. Detach the owned context from every rendering + * thread before destruction. Cocoa fails with errno=ENOTSUP. */ +YASL_API yasl_surface *yasl_gl_surface_create(const struct yasl_surface_options *options, + const struct yasl_gl_options *gl); +/** These four operations may run on a rendering thread. The application must + * synchronize surface/display destruction. NULL make_current detaches. */ +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 bool yasl_gl_swap_interval(yasl_surface *surface, int interval); -YASL_API bool yasl_gl_swap_buffers(yasl_surface *surface); +YASL_API int yasl_gl_set_swap_interval(yasl_surface *surface, int interval); +YASL_API int yasl_gl_present(yasl_surface *surface); #ifdef __cplusplus } #endif diff --git a/include/yasl/metal.h b/include/yasl/metal.h index 695f80c..8197d80 100644 --- a/include/yasl/metal.h +++ b/include/yasl/metal.h @@ -12,8 +12,9 @@ extern "C" { #endif /** Borrowed until surface destruction. YASL owns view/layer and drawableSize. * No device, drawable acquisition, command submission, or implicit present. - * Returns NULL on unsupported backend, CPU/GL surface, or incompatible presenter. */ -YASL_API CAMetalLayer *yasl_metal_layer(yasl_surface *surface); + * Returns -ENOTSUP on unsupported backends, -EBUSY for a different presenter. + * Clears out on failure. Successful access reserves the surface for Metal. */ +YASL_API int yasl_metal_get_layer(yasl_surface *surface, CAMetalLayer **out); #ifdef __cplusplus } #endif diff --git a/include/yasl/native/cocoa.h b/include/yasl/native/cocoa.h index bd92c98..2d2d432 100644 --- a/include/yasl/native/cocoa.h +++ b/include/yasl/native/cocoa.h @@ -1,3 +1,4 @@ +/** @file Borrowed cocoa handles for an initialized, connected surface. */ #ifndef YASL_NATIVE_COCOA_H #define YASL_NATIVE_COCOA_H #include "yasl/yasl.h" @@ -13,10 +14,16 @@ typedef struct objc_object NSView; #ifdef __cplusplus extern "C" { #endif -/** Borrowed native objects. Use on main thread, never change native ownership. */ -YASL_API NSApplication *yasl_cocoa_application(void); -YASL_API NSWindow *yasl_cocoa_window(const yasl_surface *surface); -YASL_API NSView *yasl_cocoa_view(const yasl_surface *surface); +struct yasl_cocoa_handles { + NSApplication *application; + NSWindow *window; + NSView *view; +}; +/** Owner thread only. Returns -ENOTSUP for another backend and -ENOTCONN + * while disconnected. Clears out on failure. Handles expire on display loss + * or surface destruction; query again after DISPLAY_RESTORED. Never destroy + * borrowed objects or dispatch/close the connection independently. */ +YASL_API int yasl_cocoa_get_native(const yasl_surface *surface, struct yasl_cocoa_handles *out); #ifdef __cplusplus } #endif diff --git a/include/yasl/native/wayland.h b/include/yasl/native/wayland.h index 1b8881e..5c18a80 100644 --- a/include/yasl/native/wayland.h +++ b/include/yasl/native/wayland.h @@ -1,3 +1,4 @@ +/** @file Borrowed wayland handles for an initialized, connected surface. */ #ifndef YASL_NATIVE_WAYLAND_H #define YASL_NATIVE_WAYLAND_H #include "yasl/yasl.h" @@ -6,9 +7,15 @@ struct wl_surface; #ifdef __cplusplus extern "C" { #endif -/** Borrowed handles; never dispatch or close native connections independently. */ -YASL_API struct wl_display *yasl_wayland_display(const yasl_surface *surface); -YASL_API struct wl_surface *yasl_wayland_surface(const yasl_surface *surface); +struct yasl_wayland_handles { + struct wl_display *display; + struct wl_surface *surface; +}; +/** Owner thread only. Returns -ENOTSUP for another backend and -ENOTCONN + * while disconnected. Clears out on failure. Handles expire on display loss + * or surface destruction; query again after DISPLAY_RESTORED. Never destroy + * borrowed objects or dispatch/close the connection independently. */ +YASL_API int yasl_wayland_get_native(const yasl_surface *surface, struct yasl_wayland_handles *out); #ifdef __cplusplus } #endif diff --git a/include/yasl/native/x11.h b/include/yasl/native/x11.h index f8c37ae..6cc85fb 100644 --- a/include/yasl/native/x11.h +++ b/include/yasl/native/x11.h @@ -1,3 +1,4 @@ +/** @file Borrowed x11 handles for an initialized, connected surface. */ #ifndef YASL_NATIVE_X11_H #define YASL_NATIVE_X11_H #include "yasl/yasl.h" @@ -5,9 +6,15 @@ typedef struct _XDisplay Display; #ifdef __cplusplus extern "C" { #endif -/** Borrowed handles; surface/window lifetime and native connection stay in YASL. */ -YASL_API Display *yasl_x11_display(const yasl_surface *surface); -YASL_API uintptr_t yasl_x11_window(const yasl_surface *surface); +struct yasl_x11_handles { + Display *display; + uintptr_t window; +}; +/** Owner thread only. Returns -ENOTSUP for another backend and -ENOTCONN + * while disconnected. Clears out on failure. Handles expire on display loss + * or surface destruction; query again after DISPLAY_RESTORED. Never destroy + * borrowed objects or dispatch/close the connection independently. */ +YASL_API int yasl_x11_get_native(const yasl_surface *surface, struct yasl_x11_handles *out); #ifdef __cplusplus } #endif diff --git a/include/yasl/vulkan.h b/include/yasl/vulkan.h index afa1089..1a1c5be 100644 --- a/include/yasl/vulkan.h +++ b/include/yasl/vulkan.h @@ -1,4 +1,4 @@ -/** @file Vulkan WSI only; application owns all Vulkan objects. */ +/** @file Vulkan WSI; YASL tracks the WSI handle, the application owns rendering. */ #ifndef YASL_VULKAN_H #define YASL_VULKAN_H #include "yasl/yasl.h" @@ -6,13 +6,27 @@ #ifdef __cplusplus extern "C" { #endif -YASL_API bool yasl_vulkan_init(PFN_vkGetInstanceProcAddr loader); -/** Borrowed list through shutdown; merge with app extensions before instance creation. */ -YASL_API const char *const *yasl_vulkan_instance_extensions(const yasl_surface *surface, uint32_t *count); -YASL_API VkInstanceCreateFlags yasl_vulkan_instance_flags(const yasl_surface *surface); -/** Uses injected loader; destroy returned VkSurfaceKHR before YASL surface. */ +YASL_API int yasl_vulkan_init(PFN_vkGetInstanceProcAddr loader); +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. */ YASL_API VkResult yasl_vulkan_create_surface(yasl_surface *surface, VkInstance instance, - const VkAllocationCallbacks *allocator, VkSurfaceKHR *out); + 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. */ +YASL_API int yasl_vulkan_destroy_surface(yasl_surface *surface); #ifdef __cplusplus } #endif diff --git a/include/yasl/yasl.h b/include/yasl/yasl.h index c0ebb75..284039a 100644 --- a/include/yasl/yasl.h +++ b/include/yasl/yasl.h @@ -1,8 +1,11 @@ -/** @file Windows, input and events. The first surface starts the backend; use the - * creating thread (main on Cocoa) for every call except yasl_wake. Coordinates are - * logical units unless named pixel. */ +/** @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. */ #ifndef YASL_H #define YASL_H +#include <errno.h> #include <stdbool.h> #include <stddef.h> #include <stdint.h> @@ -18,56 +21,226 @@ extern "C" { typedef struct yasl_surface yasl_surface; typedef struct yasl_event yasl_event; enum yasl_backend { YASL_BACKEND_AUTO, YASL_BACKEND_WAYLAND, YASL_BACKEND_X11, YASL_BACKEND_COCOA }; -enum yasl_surface_flags { YASL_SURFACE_CPU=1u, YASL_SURFACE_HIDDEN=2u, YASL_SURFACE_UNDECORATED=4u, YASL_SURFACE_FIXED_SIZE=8u }; -enum yasl_error { YASL_ERROR_NONE, YASL_ERROR_INVALID, YASL_ERROR_UNSUPPORTED, YASL_ERROR_NO_MEMORY, - YASL_ERROR_NOT_INITIALIZED, YASL_ERROR_BUSY, YASL_ERROR_WRONG_THREAD, YASL_ERROR_CONTEXT_EXPIRED, YASL_ERROR_IO, YASL_ERROR_DISCONNECTED }; +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_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 }; +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_scroll_phase { YASL_SCROLL_PHASE_UNKNOWN, YASL_SCROLL_BEGIN, YASL_SCROLL_UPDATE, YASL_SCROLL_END }; +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_scroll_phase { + YASL_SCROLL_PHASE_UNKNOWN, + YASL_SCROLL_BEGIN, + YASL_SCROLL_UPDATE, + YASL_SCROLL_END +}; 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 }; -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 }; -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 }; -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 }; -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_DISPLAY_LOST, YASL_EVENT_DISPLAY_RESTORED }; +enum yasl_text_purpose { + YASL_TEXT_NORMAL, + YASL_TEXT_PASSWORD, + YASL_TEXT_NUMBER, + YASL_TEXT_TERMINAL +}; +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 +}; +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 +}; +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 +}; +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_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; }; -struct yasl_key_payload { 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; }; -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; bool momentum; }; -struct yasl_pen_payload { 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; 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_window_payload { + int width, height, pixel_width, pixel_height; + double scale; + unsigned state; +}; +struct yasl_key_payload { + 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; +}; +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; + bool momentum; +}; +struct yasl_pen_payload { + 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; + 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; +}; /** 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. @@ -98,69 +271,107 @@ struct yasl_event { struct yasl_drag_payload drag; }; }; -/** Last error on this thread; success does not clear it. */ -YASL_API enum yasl_error yasl_get_error(const char **description); -YASL_API int yasl_get_native_error(void); -/** Only before the first surface is created. */ -YASL_API bool yasl_set_app_id(const char *app_id); -YASL_API bool yasl_set_backend(enum yasl_backend backend); -/** Flags zero creates a surface for your own Vulkan or Metal renderer. */ -YASL_API yasl_surface *yasl_surface_create(const char *title, int width, int height, unsigned flags); -/** Also destroys its popups. */ -YASL_API void yasl_surface_destroy(yasl_surface *surface); -/** Destroys all surfaces. Release your GPU objects first. */ -YASL_API void yasl_shutdown(void); -/** 1 event, 0 none (timeout or wake), -1 failure. timeout_ms zero polls, negative +/** 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. */ YASL_API int yasl_wait_event(yasl_event *out, int timeout_ms); /** Makes a waiting yasl_wait_event return. Thread-safe. */ YASL_API void yasl_wake(void); +/** Monotonic nanoseconds; thread-safe and independent of initialization. */ YASL_API uint64_t yasl_time_ns(void); -YASL_API enum yasl_backend yasl_surface_backend(const yasl_surface *surface); -YASL_API unsigned yasl_surface_features(const yasl_surface *surface); +/** 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 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_state(const yasl_surface *surface); -YASL_API void *yasl_surface_userdata(const yasl_surface *surface); -YASL_API void yasl_surface_set_userdata(yasl_surface *surface, void *userdata); -YASL_API bool yasl_surface_set_title(yasl_surface *surface, const char *title); +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 bool yasl_surface_request_size(yasl_surface *surface, int width, int height); -YASL_API bool yasl_surface_request_fullscreen(yasl_surface *surface, bool enabled); -YASL_API bool yasl_surface_request_maximized(yasl_surface *surface, bool enabled); -YASL_API bool yasl_surface_request_minimize(yasl_surface *surface); -YASL_API bool yasl_surface_set_cursor(yasl_surface *surface, enum yasl_cursor cursor); -YASL_API bool yasl_surface_set_pointer_mode(yasl_surface *surface, enum yasl_pointer_mode mode); +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 void yasl_surface_set_refresh_callback(yasl_surface *surface, void (*refresh)(yasl_surface *,void *), void *userdata); -YASL_API int yasl_double_click_ms(void); -YASL_API bool yasl_clipboard_set(enum yasl_selection selection, const char *text); +YASL_API int yasl_surface_set_refresh_callback(yasl_surface *surface, + void (*refresh)(yasl_surface *, void *), + void *userdata); +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 bool yasl_surface_set_text_input(yasl_surface *surface, bool active, enum yasl_text_purpose purpose); +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 bool 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 bool yasl_surface_set_ime_caret(yasl_surface *surface, double x, double y, double width, double height); -/** Anchor picks a point on the x/y/anchor rectangle of the parent; gravity is the - * direction the popup grows. Functions taking a trigger need a recent press event. */ -YASL_API yasl_surface *yasl_popup_create(yasl_surface *parent, int width, int height, - double x, double y, double anchor_width, double anchor_height, enum yasl_popup_edge anchor, - enum yasl_popup_edge gravity, unsigned constraints, const yasl_event *trigger); -YASL_API bool yasl_popup_reposition(yasl_surface *popup, int width, int height, - double x, double y, double anchor_width, double anchor_height, enum yasl_popup_edge anchor, - enum yasl_popup_edge gravity, unsigned constraints); -YASL_API bool yasl_surface_start_interactive_move(yasl_surface *surface, const yasl_event *trigger); -YASL_API bool yasl_surface_start_interactive_resize(yasl_surface *surface, enum yasl_resize_edge edge, const yasl_event *trigger); -YASL_API bool yasl_surface_show_window_menu(yasl_surface *surface, double x, double y, const yasl_event *trigger); -YASL_API bool yasl_drag_start(yasl_surface *origin, const yasl_event *trigger, - const uint32_t *pixels, int width, int height, int hotspot_x, int hotspot_y); -YASL_API void yasl_drag_cancel(void); -YASL_API bool yasl_surface_drag_carry(yasl_surface *surface, double x, double y); -YASL_API bool yasl_surface_place_under_pointer(yasl_surface *surface, double x, double y); +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); #ifdef __cplusplus } diff --git a/src/common/backend.h b/src/common/backend.h index 6eefbb3..246a9bc 100644 --- a/src/common/backend.h +++ b/src/common/backend.h @@ -25,9 +25,14 @@ struct yasl_private_display { }; struct yasl_surface { + struct yasl_vulkan_binding *vulkan; 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; /* Window values last reported through public WINDOW events. */ diff --git a/src/common/desktop.c b/src/common/desktop.c index 5045604..a9f72e3 100644 --- a/src/common/desktop.c +++ b/src/common/desktop.c @@ -11,17 +11,20 @@ static bool uri_valid(const char *uri) { unsigned char c = (unsigned char)uri[0]; - if ((unsigned)((c | 32) - 'a') >= 26) return false; + if ((unsigned)((c | 32) - 'a') >= 26) + return false; const char *p = uri + 1; for (; *p != ':'; p++) { c = (unsigned char)*p; bool alpha = (unsigned)((c | 32) - 'a') < 26; bool digit = (unsigned)(c - '0') < 10; - if (!alpha && !digit && c != '+' && c != '-' && c != '.') return false; + if (!alpha && !digit && c != '+' && c != '-' && c != '.') + return false; } for (; *p; p++) { c = (unsigned char)*p; - if (c < 0x20 || c == 0x7f) return false; + if (c < 0x20 || c == 0x7f) + return false; } return true; } @@ -38,13 +41,16 @@ static size_t copy_out(char *out, size_t cap, const char *s, size_t len) bool yasl_private_display_open_uri(struct yasl_private_display *d, const char *uri) { - if (!d || !uri || !uri_valid(uri)) return false; + if (!d || !uri || !uri_valid(uri)) + return false; return desktop_open_uri(uri); } -bool yasl_private_display_notify(struct yasl_private_display *d, const char *title, const char *body) +bool yasl_private_display_notify(struct yasl_private_display *d, const char *title, + const char *body) { - if (!d || !title) return false; + if (!d || !title) + return false; return desktop_notify(d->app_id, title, body ? body : ""); } @@ -55,27 +61,31 @@ uint32_t yasl_private_display_appearance(struct yasl_private_display *d) bool yasl_private_surface_bell(struct yasl_surface *s) { - if (!s || !s->display->ops->surface_bell) return false; + if (!s || !s->display->ops->surface_bell) + return false; return s->display->ops->surface_bell(s); } size_t yasl_private_executable_path(char *out, size_t cap) { - if (!out && cap) return 0; + if (!out && cap) + return 0; char buf[DESKTOP_STR_MAX]; return copy_out(out, cap, buf, desktop_executable_path(buf, sizeof(buf))); } size_t yasl_private_resource_dir(char *out, size_t cap) { - if (!out && cap) return 0; + if (!out && cap) + return 0; char buf[DESKTOP_STR_MAX]; return copy_out(out, cap, buf, desktop_resource_dir(buf, sizeof(buf))); } size_t yasl_private_default_locale(char *out, size_t cap) { - if (!out && cap) return 0; + if (!out && cap) + return 0; char buf[DESKTOP_STR_MAX]; return copy_out(out, cap, buf, desktop_default_locale(buf, sizeof(buf))); } @@ -83,13 +93,69 @@ size_t yasl_private_default_locale(char *out, size_t cap) #include "public_internal.h" #include "yasl/desktop.h" #include <errno.h> -bool yasl_open_uri(const char *uri) -{ if(!uri||!uri_valid(uri))return yasl_public_fail(EINVAL);errno=0;return desktop_open_uri(uri)||yasl_public_fail(errno); } -bool yasl_notify(const char *title,const char *body) -{ if(!yasl_public_owner())return false;errno=0;return yasl_private_display_notify(yasl_public_display(),title,body)||yasl_public_fail(errno); } -unsigned yasl_appearance(void) { return yasl_private_display_appearance(yasl_public_display()); } -bool yasl_surface_bell(yasl_surface *s) -{ if(!yasl_public_surface(s,false))return false;errno=0;return yasl_private_surface_bell(s)||yasl_public_fail(errno); } -size_t yasl_executable_path(char *out,size_t cap) { return yasl_private_executable_path(out,cap); } -size_t yasl_resource_dir(char *out,size_t cap) { return yasl_private_resource_dir(out,cap); } -size_t yasl_default_locale(char *out,size_t cap) { return yasl_private_default_locale(out,cap); } +int yasl_open_uri(const char *uri) +{ + if (!uri || !uri_valid(uri)) + return yasl_public_error(EINVAL); + errno = 0; + return yasl_public_status(desktop_open_uri(uri) || yasl_public_fail(errno)); +} +int yasl_notify(const char *title, const char *body) +{ + if (!yasl_public_owner()) + return -errno; + if (!title) + return yasl_public_error(EINVAL); + errno = 0; + return yasl_public_status(yasl_private_display_notify(yasl_public_display(), title, body) || + yasl_public_fail(errno)); +} +unsigned yasl_get_appearance(void) +{ + return yasl_public_owner() ? yasl_private_display_appearance(yasl_public_display()) : 0; +} +int yasl_surface_bell(yasl_surface *s) +{ + if (!yasl_public_surface(s, false)) + 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) +{ + 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; + } + errno = 0; + size_t n = yasl_private_default_locale(out, cap); + if (!n) + yasl_public_fail(errno); + return n; +} diff --git a/src/common/native.c b/src/common/native.c index 88604c9..1b32094 100644 --- a/src/common/native.c +++ b/src/common/native.c @@ -4,22 +4,28 @@ void *yasl_private_display_get_native_handle(struct yasl_private_display *d) { - if (!d) return NULL; - if (d->native_handle) return d->native_handle; - return d->ops && d->ops->display_get_native_handle ? - d->ops->display_get_native_handle(d) : NULL; + if (!d) + return NULL; + if (d->native_handle) + return d->native_handle; + return d->ops && d->ops->display_get_native_handle ? d->ops->display_get_native_handle(d) + : NULL; } void *yasl_private_surface_get_native_handle(struct yasl_surface *s) { - if (!s) return NULL; - if (s->native_handle) return s->native_handle; - return s->display && s->display->ops && s->display->ops->surface_get_native_handle ? - s->display->ops->surface_get_native_handle(s) : NULL; + if (!s) + return NULL; + if (s->native_handle) + return s->native_handle; + return s->display && s->display->ops && s->display->ops->surface_get_native_handle + ? s->display->ops->surface_get_native_handle(s) + : NULL; } struct yasl_surface *yasl_private_surface_create_native(struct yasl_private_display *d, - const struct yasl_private_window_spec *spec, void *graphics_device) + const struct yasl_private_window_spec *spec, + void *graphics_device) { if (!graphics_device) { errno = EINVAL; @@ -29,10 +35,10 @@ struct yasl_surface *yasl_private_surface_create_native(struct yasl_private_disp } bool yasl_private_surface_get_native_frame(struct yasl_surface *s, - const struct yasl_private_framebuffer *target, struct yasl_private_native_frame *out) + const struct yasl_private_framebuffer *target, + struct yasl_private_native_frame *out) { - if (!s || !target || !out || !target->generation || - target->generation != s->frame_generation || + if (!s || !target || !out || !target->generation || target->generation != s->frame_generation || !s->display->ops->surface_get_native_frame) return false; return s->display->ops->surface_get_native_frame(s, target, out); @@ -40,36 +46,83 @@ 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" -struct wl_display *yasl_wayland_display(const yasl_surface *s) -{ return s && s->display->ops->type==YASL_PRIVATE_BACKEND_WAYLAND ? yasl_private_display_get_native_handle(s->display):NULL; } -struct wl_surface *yasl_wayland_surface(const yasl_surface *s) -{ return s && s->display->ops->type==YASL_PRIVATE_BACKEND_WAYLAND ? yasl_private_surface_get_native_handle((yasl_surface*)s):NULL; } +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" -Display *yasl_x11_display(const yasl_surface *s) -{ return s && s->display->ops->type==YASL_PRIVATE_BACKEND_X11 ? yasl_private_display_get_native_handle(s->display):NULL; } -uintptr_t yasl_x11_window(const yasl_surface *s) -{ return s && s->display->ops->type==YASL_PRIVATE_BACKEND_X11 ? (uintptr_t)yasl_private_surface_get_native_handle((yasl_surface*)s):0; } +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" -NSApplication *yasl_cocoa_application(void) -{ struct yasl_private_display *d=yasl_public_display();return d ? yasl_private_cocoa_display_get_application(d):NULL; } -NSWindow *yasl_cocoa_window(const yasl_surface *s) -{ return yasl_private_cocoa_surface_get_window((yasl_surface*)s); } -NSView *yasl_cocoa_view(const yasl_surface *s) -{ return yasl_private_cocoa_surface_get_view((yasl_surface*)s); } +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 -CAMetalLayer *yasl_metal_layer(yasl_surface *s) +int yasl_metal_get_layer(yasl_surface *s, CAMetalLayer **out) { - if(!yasl_public_surface(s,false))return NULL; - if(s->presenter!=YASL_PRIVATE_PRESENTER_NONE && s->presenter!=YASL_PRIVATE_PRESENTER_METAL) {yasl_public_fail(EBUSY);return NULL;} - if(!s->display->ops->surface_get_metal_layer){yasl_public_fail(ENOTSUP);return NULL;} - CAMetalLayer *layer=s->display->ops->surface_get_metal_layer(s); - if(!layer){yasl_public_fail(ENOTSUP);return NULL;} - s->presenter=YASL_PRIVATE_PRESENTER_METAL;return layer; + if (!out) + return yasl_public_error(EINVAL); + *out = NULL; + if (!yasl_public_surface(s, false)) + return -errno; + if (s->display->offline) + return yasl_public_error(ENOTCONN); + 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); + if (!layer) + return yasl_public_error(ENOTSUP); + s->presenter = YASL_PRIVATE_PRESENTER_METAL; + *out = layer; + return 0; } diff --git a/src/common/public_internal.h b/src/common/public_internal.h index 210b222..95ecb6d 100644 --- a/src/common/public_internal.h +++ b/src/common/public_internal.h @@ -6,7 +6,8 @@ struct yasl_private_display *yasl_public_display(void); bool yasl_public_owner(void); bool yasl_public_surface(yasl_surface *, bool mutation); bool yasl_public_fail(int error); -void yasl_public_set_error(enum yasl_error, int native_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 b47fea7..de9e7b3 100644 --- a/src/common/surface.c +++ b/src/common/surface.c @@ -11,514 +11,1155 @@ #include <string.h> static struct yasl_private_display *display; -static enum yasl_backend requested_backend; -static char *application_id; static pthread_t owner; static pthread_mutex_t lifecycle_mutex = PTHREAD_MUTEX_INITIALIZER; static bool pumping; static unsigned refresh_depth; static char *delivered_text; /* Strings of the last returned event; freed by the next poll/wait. */ -static _Thread_local enum yasl_error last_error; -static _Thread_local int native_error; - -void yasl_public_set_error(enum yasl_error error, int native) -{ last_error=error; native_error=native; } bool yasl_public_fail(int error) { - if (!error) error=EIO; - enum yasl_error code=YASL_ERROR_IO; - if (error==EINVAL || error==EOVERFLOW || error==EILSEQ) code=YASL_ERROR_INVALID; - else if (error==ENOTSUP || error==ENOSYS) code=YASL_ERROR_UNSUPPORTED; - else if (error==ENOMEM) code=YASL_ERROR_NO_MEMORY; - else if (error==EBUSY) code=YASL_ERROR_BUSY; - else if (error==EPERM) code=YASL_ERROR_WRONG_THREAD; - else if (error==ENOTCONN) code=YASL_ERROR_DISCONNECTED; - yasl_public_set_error(code,error); errno=error; return false; -} -enum yasl_error yasl_get_error(const char **description) -{ - static const char *const messages[]={"No error","Invalid argument","Unsupported operation", - "Out of memory","Library not initialized","Operation is busy","Wrong thread", - "Input context expired","Native operation failed","Display disconnected"}; - if(description) *description=messages[last_error]; - return last_error; -} -int yasl_get_native_error(void) { return native_error; } -struct yasl_private_display *yasl_public_display(void) { return display; } + errno = error > 0 ? error : EIO; + return false; +} +int yasl_public_error(int error) +{ + yasl_public_fail(error); + return -errno; +} +int yasl_public_status(bool success) +{ + return success ? 0 : yasl_public_error(errno); +} +struct yasl_private_display *yasl_public_display(void) +{ + return display; +} bool yasl_public_owner(void) { - if(!display) { yasl_public_set_error(YASL_ERROR_NOT_INITIALIZED,0); return false; } - if(!pthread_equal(owner,pthread_self())) return yasl_public_fail(EPERM); + if (!display) + return yasl_public_fail(ENODEV); + if (!pthread_equal(owner, pthread_self())) + return yasl_public_fail(EPERM); return true; } -bool yasl_public_surface(yasl_surface *s,bool mutation) +bool yasl_public_surface(yasl_surface *s, bool mutation) { - if(!yasl_public_owner()) return false; - if(!s || s->display!=display) return yasl_public_fail(EINVAL); - if(mutation && refresh_depth) return yasl_public_fail(EBUSY); + if (!yasl_public_owner()) + return false; + if (!s || s->display != display) + return yasl_public_fail(EINVAL); + if (mutation && refresh_depth) + return yasl_public_fail(EBUSY); return true; } -static bool initialize(void) +int yasl_init(const struct yasl_options *options) { - if(display) return yasl_public_owner(); - struct yasl_private_display_spec spec={.app_id=application_id,.backend=(enum yasl_private_backend)requested_backend}; - struct yasl_private_display *d=yasl_private_display_open(NULL,&spec); - if(!d) return yasl_public_fail(errno); + 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) + return yasl_public_error(EINVAL); + struct yasl_private_display_spec spec = { + .app_id = options->app_id, + .backend = (enum yasl_private_backend)options->backend, + }; + errno = 0; + struct yasl_private_display *d = yasl_private_display_open(NULL, &spec); + if (!d) + return yasl_public_error(errno); pthread_mutex_lock(&lifecycle_mutex); - owner=pthread_self(); display=d; + owner = pthread_self(); + display = d; pthread_mutex_unlock(&lifecycle_mutex); - return true; -} -bool yasl_set_app_id(const char *id) -{ - if(display) return yasl_public_fail(EBUSY); - char *copy=id ? strdup(id) : NULL; - if(id&&!copy) return yasl_public_fail(ENOMEM); - free(application_id); application_id=copy; return true; + return 0; } -bool yasl_set_backend(enum yasl_backend backend) +static bool initialize(void) { - if(display) return yasl_public_fail(EBUSY); - if(backend<YASL_BACKEND_AUTO || backend>YASL_BACKEND_COCOA) return yasl_public_fail(EINVAL); - requested_backend=backend; return true; + return display ? yasl_public_owner() : yasl_init(NULL) == 0; } -static bool creation_valid(int width,int height,unsigned flags) +static bool creation_valid(int width, int height, unsigned flags) { - if(width<=0 || height<=0 || width>32767 || height>32767 || - (unsigned long long)width*(unsigned)height>INT32_MAX/4 || (flags & ~15u)) + 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); - if(pumping || refresh_depth) return yasl_public_fail(EBUSY); + if (pumping || refresh_depth) + return yasl_public_fail(EBUSY); return true; } -static void draw_committed(yasl_surface *s,struct yasl_private_frame *frame,void *ud) +static void draw_committed(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) 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); - 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) -{ - s->creation_flags=flags; s->parent=parent; - if(flags&YASL_SURFACE_CPU) s->presenter=YASL_PRIVATE_PRESENTER_CPU; - struct yasl_private_window_cbs cb={.draw=draw_committed}; - 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_surface *yasl_surface_create(const char *title,int width,int height,unsigned flags) -{ - if(!creation_valid(width,height,flags)) return NULL; - bool fresh=!display; - if(!initialize()) return NULL; - struct yasl_private_window_spec spec={.title=title,.size={width,height}, - .flags=flags,.external_gpu=!(flags&YASL_SURFACE_CPU)}; - yasl_surface *s=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; - } - yasl_public_register_surface(s,flags,NULL); return s; -} -void yasl_surface_destroy(yasl_surface *s) -{ - if(!s) return; - if(!yasl_public_surface(s,true)) return; - for(;;) { - yasl_surface *child=NULL; - for(yasl_surface *p=display->surfaces;p;p=p->event_next) if(p->parent==s) { child=p; break; } - if(!child) break; + 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) + 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); + 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) +{ + s->creation_flags = flags; + s->parent = parent; + if (flags & YASL_PRIVATE_SURFACE_CPU) + s->presenter = YASL_PRIVATE_PRESENTER_CPU; + struct yasl_private_window_cbs cb = {.draw = draw_committed}; + 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_surface *yasl_surface_create(const struct yasl_surface_options *options) +{ + if (!options || (unsigned)options->type > YASL_SURFACE_CPU || (options->flags & ~14u)) { + yasl_public_fail(EINVAL); + return 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)) + return NULL; + bool fresh = !display; + if (!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); + 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; + } + yasl_public_register_surface(s, flags, NULL); + s->userdata = options->userdata; + return s; +} +int yasl_surface_destroy(yasl_surface *s) +{ + if (!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 *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) { + child = p; + break; + } + if (!child) + break; yasl_surface_destroy(child); } - free(s->committed_pixels); s->committed_pixels=NULL; - free(s->text_surrounding); s->text_surrounding=NULL; + free(s->cpu_pixels); + s->cpu_pixels = NULL; + free(s->committed_pixels); + s->committed_pixels = NULL; + free(s->text_surrounding); + s->text_surrounding = NULL; yasl_private_surface_destroy(s); + return 0; } -void yasl_shutdown(void) +int yasl_shutdown(void) { - if(display && !yasl_public_owner()) return; - if(pumping || refresh_depth) { yasl_public_fail(EBUSY); return; } - if(display) { - 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); + 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; - free(application_id); application_id=NULL; requested_backend=YASL_BACKEND_AUTO; + 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); + 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(); } +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_backend(const yasl_surface *s) -{ return s ? (enum yasl_backend)s->display->ops->type : YASL_BACKEND_AUTO; } -unsigned yasl_surface_features(const yasl_surface *s) -{ - if(!s) return 0; - unsigned caps=yasl_private_display_caps(s->display); - if(s->display->ops->surface_create_gl) caps|=YASL_CAP_OPENGL; +{ + 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) +{ + 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; + caps |= YASL_CAP_VULKAN; #endif return caps; } -void yasl_surface_get_framebuffer_size(const yasl_surface *s,int *w,int *h) -{ 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) -{ 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 scale_of(s); } -unsigned yasl_surface_state(const yasl_surface *s) { return s?s->state:0; } -void *yasl_surface_userdata(const yasl_surface *s) { return s?s->userdata:NULL; } -void yasl_surface_set_userdata(yasl_surface *s,void *ud) { if(yasl_public_surface(s,false)) s->userdata=ud; } -bool yasl_surface_set_title(yasl_surface *s,const char *title) -{ - if(!yasl_public_surface(s,false)) return false; - if(!title) return yasl_public_fail(EINVAL); - if(!s->display->ops->surface_set_title) return yasl_public_fail(ENOTSUP); - s->display->ops->surface_set_title(s,title); return true; -} -#define REQUEST(name,priv,args,call) bool name args { if(!yasl_public_surface(s,false)) return false; errno=0; return priv call || yasl_public_fail(errno); } -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)) -bool yasl_surface_set_cursor(yasl_surface *s,enum yasl_cursor cursor) -{ - if(!yasl_public_surface(s,false)) return false; - if(cursor<YASL_CURSOR_ARROW || cursor>YASL_CURSOR_HIDDEN) return yasl_public_fail(EINVAL); - if(!s->display->ops->surface_set_cursor) return yasl_public_fail(ENOTSUP); - yasl_private_surface_set_cursor(s,(enum yasl_private_cursor)cursor); return true; -} -void yasl_surface_set_refresh_callback(yasl_surface *s,void(*fn)(yasl_surface *,void *),void *ud) -{ if(yasl_public_surface(s,false)) { s->refresh_callback=fn; s->refresh_userdata=ud; } } +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; + 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; +} +void *yasl_surface_get_userdata(const yasl_surface *s) +{ + return s && yasl_public_surface((yasl_surface *)s, false) ? s->userdata : NULL; +} +int yasl_surface_set_userdata(yasl_surface *s, void *ud) +{ + if (!yasl_public_surface(s, false)) + return -errno; + s->userdata = ud; + return 0; +} +int yasl_surface_set_title(yasl_surface *s, const char *title) +{ + if (!yasl_public_surface(s, false)) + return -errno; + if (!title) + return yasl_public_error(EINVAL); + if (!s->display->ops->surface_set_title) + return yasl_public_error(ENOTSUP); + 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)); \ + } +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)) +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) + return yasl_public_error(EINVAL); + 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) { - ++refresh_depth; s->refresh_callback(s,s->refresh_userdata); --refresh_depth; + 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) { + ++refresh_depth; + s->refresh_callback(s, s->refresh_userdata); + --refresh_depth; } } -static char *copy_text(const char *data,size_t length) +static char *copy_text(const char *data, size_t length) { - if(length==SIZE_MAX || (!data&&length)) { errno=EINVAL; return NULL; } - char *p=malloc(length+1); if(!p) return NULL; - if(length) memcpy(p,data,length); - p[length]=0; return p; + if (length == SIZE_MAX || (!data && length)) { + errno = EINVAL; + return NULL; + } + char *p = malloc(length + 1); + if (!p) + return NULL; + if (length) + memcpy(p, data, length); + p[length] = 0; + return p; } -static const char *deliver_text(const char *data,size_t length) +static const char *deliver_text(const char *data, size_t length) { - free(delivered_text); delivered_text=copy_text(data,length); return delivered_text; + 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 nothing. */ -static bool window_event(yasl_surface *s,struct yasl_private_vector2i size,uint32_t scale_120,uint32_t state,yasl_event *e) +/* Folds native size, state and focus 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) { - bool resized=size.x!=s->reported_size.x || size.y!=s->reported_size.y || scale_120!=s->reported_scale_120; - if(!resized && state==s->reported_state) return false; - s->reported_size=size; s->reported_scale_120=scale_120; s->reported_state=state; - if(resized) 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; + bool resized = size.x != s->reported_size.x || size.y != s->reported_size.y || + scale_120 != s->reported_scale_120; + if (!resized && state == s->reported_state) + return false; + s->reported_size = size; + s->reported_scale_120 = scale_120; + s->reported_state = state; + if (resized) + 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; return true; } -static bool refresh_ready(yasl_surface *s) { return s->reported_size.x>0 && s->reported_size.y>0; } -static bool convert_event(const struct yasl_private_event *p,yasl_event *e) -{ - *e=(yasl_event){.surface=p->surface,.time_ns=p->time_ns}; - double scale=scale_of(p->surface); - switch(p->type) { - case YASL_PRIVATE_EVENT_WINDOW_CLOSE:e->type=YASL_EVENT_CLOSE;break; - case YASL_PRIVATE_EVENT_DISPLAY_QUIT:e->type=YASL_EVENT_QUIT;break; - case YASL_PRIVATE_EVENT_DISPLAY_REOPEN:e->type=YASL_EVENT_REOPEN;break; - case YASL_PRIVATE_EVENT_DISPLAY_LOST:e->type=YASL_EVENT_DISPLAY_LOST;break; - case YASL_PRIVATE_EVENT_DISPLAY_RESTORED:e->type=YASL_EVENT_DISPLAY_RESTORED;break; +static bool refresh_ready(yasl_surface *s) +{ + return s->reported_size.x > 0 && s->reported_size.y > 0; +} +static bool convert_event(const struct yasl_private_event *p, yasl_event *e) +{ + *e = (yasl_event){.surface = p->surface, .time_ns = p->time_ns}; + double scale = scale_of(p->surface); + switch (p->type) { + case YASL_PRIVATE_EVENT_WINDOW_CLOSE: + e->type = YASL_EVENT_CLOSE; + break; + case YASL_PRIVATE_EVENT_DISPLAY_QUIT: + e->type = YASL_EVENT_QUIT; + break; + case YASL_PRIVATE_EVENT_DISPLAY_REOPEN: + e->type = YASL_EVENT_REOPEN; + break; + case YASL_PRIVATE_EVENT_DISPLAY_LOST: + e->type = YASL_EVENT_DISPLAY_LOST; + break; + case YASL_PRIVATE_EVENT_DISPLAY_RESTORED: + e->type = YASL_EVENT_DISPLAY_RESTORED; + break; case YASL_PRIVATE_EVENT_REFRESH: - if(!p->surface) return false; - if(!refresh_ready(p->surface)) { p->surface->refresh_owed=true; return false; } - p->surface->refresh_owed=false; e->type=YASL_EVENT_REFRESH; break; - case YASL_PRIVATE_EVENT_INPUT_RESET:e->type=YASL_EVENT_INPUT_RESET;break; + if (!p->surface) + return false; + if (!refresh_ready(p->surface)) { + p->surface->refresh_owed = true; + return false; + } + p->surface->refresh_owed = false; + e->type = YASL_EVENT_REFRESH; + break; + 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 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); + yasl_surface *s = p->surface; + if (!s) + 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); } 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); + if (!p->surface) + return false; + return 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); + if (!p->surface) + return false; + return 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->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; 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; + 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; 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->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; 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->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; 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->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; 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->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; 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); + 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->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; - 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->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; + 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; 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->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; 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; - default:return false; + 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; + 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()}; - return 1; - } + 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()}; + return 1; + } struct yasl_private_event p; - while(yasl_private_display_poll_event(display,&p)) { - if(p.surface && ((p.type==YASL_PRIVATE_EVENT_COMPOSITION && p.composition.generation!=p.surface->text_generation) || - (p.type==YASL_PRIVATE_EVENT_TEXT_EDIT && p.text_edit.generation!=p.surface->text_generation))) continue; + while (yasl_private_display_poll_event(display, &p)) { + if (p.surface && ((p.type == YASL_PRIVATE_EVENT_COMPOSITION && + p.composition.generation != p.surface->text_generation) || + (p.type == YASL_PRIVATE_EVENT_TEXT_EDIT && + p.text_edit.generation != p.surface->text_generation))) + continue; yasl_event event; - errno=0; - if(convert_event(&p,&event)) { *out=event;return 1; } - if(errno) { yasl_public_fail(errno);return -1; } + errno = 0; + if (convert_event(&p, &event)) { + *out = event; + return 1; + } + if (errno) { + yasl_public_fail(errno); + return -errno; + } } return 0; } -int yasl_wait_event(yasl_event *out,int timeout) +int yasl_wait_event(yasl_event *out, int timeout) { - if(!out) { yasl_public_fail(EINVAL);return -1; } - *out=(yasl_event){.type=YASL_EVENT_NONE}; - if(!yasl_public_owner()) return -1; - if(pumping || refresh_depth) { yasl_public_fail(EBUSY);return -1; } - free(delivered_text); delivered_text=NULL; - int n=take_event(out); if(n) return n; - pumping=true; + 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=-1; } + n = take_event(out); + if (!n) { + int work = yasl_private_display_dispatch(display, timeout); + if (work < 0) { + yasl_public_fail(errno); + n = -errno; + } yasl_private_display_flush(display); - if(n>=0) n=take_event(out); - } - pumping=false;return n; -} -uint32_t *yasl_cpu_pixels(yasl_surface *s,int *w,int *h) -{ - if(w) *w=0; - if(h) *h=0; - if(!yasl_public_surface(s,false)) return NULL; - if(s->presenter!=YASL_PRIVATE_PRESENTER_CPU) { yasl_public_fail(ENOTSUP); return NULL; } - int width=s->size.x,height=s->size.y; - if(width<=0 || height<=0) { yasl_public_fail(EBUSY); return NULL; } - if(width!=s->committed_width || height!=s->committed_height || !s->committed_pixels) { - uint32_t *next=calloc((size_t)width*height,4); - if(!next) { yasl_public_fail(ENOMEM); return NULL; } - int keep_w=width<s->committed_width?width:s->committed_width,keep_h=height<s->committed_height?height:s->committed_height; - if(s->committed_pixels) for(int y=0;y<keep_h;y++) - memcpy(next+(size_t)y*width,s->committed_pixels+(size_t)y*s->committed_width,(size_t)keep_w*4); - free(s->committed_pixels); s->committed_pixels=next; s->committed_width=width; s->committed_height=height; - } - if(w) *w=width; - if(h) *h=height; - return s->committed_pixels; -} -bool yasl_cpu_present(yasl_surface *s) -{ - if(!yasl_public_surface(s,false)) return false; - if(s->presenter!=YASL_PRIVATE_PRESENTER_CPU) return yasl_public_fail(ENOTSUP); - if(!s->committed_pixels || s->committed_width!=s->size.x || s->committed_height!=s->size.y) return yasl_public_fail(EINVAL); - yasl_private_surface_request_frame(s); return true; -} -int yasl_double_click_ms(void) { return display?yasl_private_display_double_click_ms(display):400; } -bool yasl_clipboard_set(enum yasl_selection sel,const char *text) -{ if(!yasl_public_owner())return false;errno=0;return 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; } + if (n >= 0) + n = take_event(out); + } + pumping = false; + return n; +} +static uint64_t cpu_sequence; +int yasl_cpu_acquire(yasl_surface *s, struct yasl_cpu_buffer *out) +{ + 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) +{ + 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); } -bool yasl_surface_set_text_input(yasl_surface *s,bool active,enum yasl_text_purpose purpose) { - if(!yasl_public_surface(s,false))return false; - if(purpose<YASL_TEXT_NORMAL||purpose>YASL_TEXT_TERMINAL)return yasl_public_fail(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 false; }return true; + 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); } -bool yasl_surface_set_surrounding_text(yasl_surface *s,const char *text,size_t len,size_t begin,size_t end,uint64_t revision) +int yasl_surface_set_text_input(yasl_surface *s, bool active, enum yasl_text_purpose purpose) { - if(!yasl_public_surface(s,false))return false; - if((!text&&len)||begin>end||end>len||len>INT32_MAX || (len&&memchr(text,0,len)))return yasl_public_fail(EINVAL); + 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; +} +int yasl_surface_set_surrounding_text(yasl_surface *s, const char *text, size_t len, size_t begin, + size_t end, uint64_t revision) +{ + 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_fail(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_fail(EILSEQ); - for(size_t j=1;j<n;j++)if(((unsigned char)text[i+j]&0xc0)!=0x80)return yasl_public_fail(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_fail(EILSEQ); - i+=n; + 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; } - char *copy=copy_text(text,len);if(!copy)return yasl_public_fail(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 false;} - free(old);return true; + free(old); + return 0; } -static bool logical_rect(yasl_surface *s,double x,double y,double w,double h,struct yasl_private_rect2i *r) +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); - 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; + 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; } -bool yasl_surface_set_ime_caret(yasl_surface *s,double x,double y,double w,double h) +int yasl_surface_set_ime_caret(yasl_surface *s, double x, double y, double w, double h) { - if(!yasl_public_surface(s,false))return false; + if (!yasl_public_surface(s, false)) + return -errno; struct yasl_private_rect2i r; - if(!logical_rect(s,x,y,w,h,&r))return false; - if(!s->display->ops->surface_set_ime_caret)return yasl_public_fail(ENOTSUP); - yasl_private_surface_set_ime_caret(s,r);return true; + 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) +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))) { - yasl_public_set_error(YASL_ERROR_CONTEXT_EXPIRED,0);return false; - }return true; + 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) +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 || (unsigned)anchor>8 || (unsigned)gravity>8 || (constraints&~63u))return yasl_public_fail(EINVAL); - *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); + 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,int w,int h,double x,double y,double aw,double ah,enum yasl_popup_edge anchor,enum yasl_popup_edge gravity,unsigned constraints,const yasl_event *trigger) +yasl_surface *yasl_popup_create(yasl_surface *parent, const struct yasl_popup_options *options, + const yasl_event *trigger) { - 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);return NULL;} - yasl_public_register_surface(s,parent->creation_flags,parent);return s; + 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); + return NULL; + } + yasl_public_register_surface(s, parent->creation_flags, parent); + return s; } -bool yasl_popup_reposition(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) +int yasl_popup_reposition(yasl_surface *s, const struct yasl_popup_options *options) { - if(!yasl_public_surface(s,false))return false; + 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)) + return -errno; struct yasl_private_popup_spec spec; - if(!s->parent)return yasl_public_fail(EINVAL); - if(!popup_spec(s->parent,w,h,x,y,aw,ah,anchor,gravity,constraints,&spec))return false; - errno=0;return yasl_private_surface_popup_reposition(s,&spec)||yasl_public_fail(errno); -} -bool yasl_surface_start_interactive_move(yasl_surface *s,const yasl_event *e) -{ if(!yasl_public_surface(s,false)||!trigger_valid(s,e))return false;errno=0;return yasl_private_surface_start_interactive_move(s)||yasl_public_fail(errno); } -bool 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 false;errno=0;return yasl_private_surface_start_interactive_resize(s,(enum yasl_private_resize_edge)edge)||yasl_public_fail(errno); } -bool 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 false; - struct yasl_private_rect2i r;if(!logical_rect(s,x,y,0,0,&r))return false;errno=0;return yasl_private_surface_show_window_menu(s,(struct yasl_private_vector2i){r.x,r.y})||yasl_public_fail(errno); } -bool yasl_drag_start(yasl_surface *s,const yasl_event *e,const uint32_t *pixels,int w,int h,int hx,int hy) -{ - if(!yasl_public_surface(s,false)||!trigger_valid(s,e))return false; - struct yasl_private_drag_image image={.pixels=pixels,.size={w,h},.hotspot={hx,hy}}; - errno=0;return yasl_private_display_drag_start(display,s,pixels?&image:NULL,NULL,NULL)||yasl_public_fail(errno); -} -void yasl_drag_cancel(void) { if(yasl_public_owner())yasl_private_display_drag_cancel(display); } -bool yasl_surface_drag_carry(yasl_surface *s,double x,double y) -{ if(!yasl_public_surface(s,false))return false; - struct yasl_private_rect2i r;if(!logical_rect(s,x,y,0,0,&r))return false;errno=0;return yasl_private_surface_drag_carry(s,(struct yasl_private_vector2i){r.x,r.y})||yasl_public_fail(errno); } -bool yasl_surface_place_under_pointer(yasl_surface *s,double x,double y) -{ if(!yasl_public_surface(s,false))return false; - struct yasl_private_rect2i r;if(!logical_rect(s,x,y,0,0,&r))return false;errno=0;return 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); } + 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()) + return -errno; + yasl_private_display_drag_cancel(display); + 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 char *title,int w,int h,unsigned flags,const int *attrs,yasl_surface *share) -{ - if(!creation_valid(w,h,flags)||(flags&YASL_SURFACE_CPU)) { yasl_public_fail(EINVAL);return NULL; } - struct yasl_private_gl_options parsed;if(!yasl_private_gl_parse_options(attrs,&parsed)){yasl_public_fail(errno);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}; - 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);return s; -} -bool yasl_gl_make_current(yasl_surface *s) -{ - if(!display){yasl_public_set_error(YASL_ERROR_NOT_INITIALIZED,0);return false;} - if(s && (s->display!=display||s->presenter!=YASL_PRIVATE_PRESENTER_GL))return yasl_public_fail(EINVAL); - if(!display->ops->gl_make_current)return yasl_public_fail(ENOTSUP); - errno=0;return display->ops->gl_make_current(display,s)||yasl_public_fail(errno); +yasl_surface *yasl_gl_surface_create(const struct yasl_surface_options *options, + const struct yasl_gl_options *gl) +{ + 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; +} +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 (!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)); } yasl_gl_proc yasl_gl_get_proc_address(const char *name) { - if(!display||!name||!display->ops->gl_get_proc_address){yasl_public_fail(ENOTSUP);return NULL;} - yasl_gl_proc p=display->ops->gl_get_proc_address(display,name);if(!p)yasl_public_fail(ENOENT);return p; + 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; + } + yasl_gl_proc p = display->ops->gl_get_proc_address(display, name); + if (!p) + yasl_public_fail(ENOENT); + return p; } -bool yasl_gl_swap_interval(yasl_surface *s,int interval) +int yasl_gl_set_swap_interval(yasl_surface *s, int interval) { - if(!s||s->presenter!=YASL_PRIVATE_PRESENTER_GL||!s->display->ops->gl_swap_interval)return yasl_public_fail(EINVAL); - errno=0;return s->display->ops->gl_swap_interval(s->display,s,interval)||yasl_public_fail(errno); + 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)); } -bool yasl_gl_swap_buffers(yasl_surface *s) +int yasl_gl_present(yasl_surface *s) { - if(!s||s->presenter!=YASL_PRIVATE_PRESENTER_GL||!s->display->ops->gl_swap_buffers)return yasl_public_fail(EINVAL); - errno=0;return s->display->ops->gl_swap_buffers(s->display,s)||yasl_public_fail(errno); + 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)); } diff --git a/src/common/vulkan.c b/src/common/vulkan.c index 62bc3a0..77a4dd4 100644 --- a/src/common/vulkan.c +++ b/src/common/vulkan.c @@ -20,69 +20,202 @@ #endif static PFN_vkGetInstanceProcAddr get_proc; static bool portability; -void yasl_private_vulkan_reset(void) { get_proc=NULL; portability=false; } -bool yasl_vulkan_init(PFN_vkGetInstanceProcAddr loader) +void yasl_private_vulkan_reset(void) { - if(!loader)return yasl_public_fail(EINVAL); - get_proc=loader;portability=false; - PFN_vkEnumerateInstanceExtensionProperties enumerate=(PFN_vkEnumerateInstanceExtensionProperties)get_proc(VK_NULL_HANDLE,"vkEnumerateInstanceExtensionProperties"); - if(!enumerate)return yasl_public_fail(ENOTSUP); - uint32_t count=0;VkResult r=enumerate(NULL,&count,NULL); - if(r!=VK_SUCCESS)return yasl_public_fail(EIO); - VkExtensionProperties *ext=count?calloc(count,sizeof(*ext)):NULL; - if(count&&!ext)return yasl_public_fail(ENOMEM); - r=enumerate(NULL,&count,ext); - if(r==VK_SUCCESS)for(uint32_t i=0;i<count;i++)if(!strcmp(ext[i].extensionName,"VK_KHR_portability_enumeration"))portability=true; - free(ext);return r==VK_SUCCESS||yasl_public_fail(EIO); + get_proc = NULL; + portability = false; } -const char *const *yasl_vulkan_instance_extensions(const yasl_surface *s,uint32_t *count) +struct yasl_vulkan_binding { + VkInstance instance; + VkSurfaceKHR surface; + VkAllocationCallbacks allocator; + bool has_allocator; + PFN_vkDestroySurfaceKHR destroy; +}; +int yasl_vulkan_init(PFN_vkGetInstanceProcAddr loader) { - static const char *wayland[]={"VK_KHR_surface","VK_KHR_wayland_surface"}; - static const char *x11[]={"VK_KHR_surface","VK_KHR_xlib_surface"}; - static const char *metal[]={"VK_KHR_surface","VK_EXT_metal_surface","VK_KHR_portability_enumeration"}; - if(count)*count=0; - if(!s||!count||s->presenter==YASL_PRIVATE_PRESENTER_CPU||s->presenter==YASL_PRIVATE_PRESENTER_GL){yasl_public_fail(EINVAL);return NULL;} - switch(s->display->ops->type) { - case YASL_PRIVATE_BACKEND_WAYLAND:*count=2;return wayland; - case YASL_PRIVATE_BACKEND_X11:*count=2;return x11; - case YASL_PRIVATE_BACKEND_COCOA:*count=portability?3:2;return metal; - default:yasl_public_fail(ENOTSUP);return NULL; + struct yasl_private_display *d = yasl_public_display(); + if (d && !yasl_public_owner()) + return -errno; + 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); + PFN_vkEnumerateInstanceExtensionProperties enumerate = + (PFN_vkEnumerateInstanceExtensionProperties)loader( + VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"); + if (!enumerate) + 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) + 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; + 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; +} +int yasl_vulkan_destroy_surface(yasl_surface *s) +{ + if (!yasl_public_surface(s, true)) + return -errno; + struct yasl_vulkan_binding *binding = s->vulkan; + if (!binding) + return 0; + binding->destroy(binding->instance, binding->surface, + binding->has_allocator ? &binding->allocator : NULL); + free(binding); + s->vulkan = NULL; + s->presenter = YASL_PRIVATE_PRESENTER_NONE; + return 0; } -VkInstanceCreateFlags yasl_vulkan_instance_flags(const yasl_surface *s) -{ return s&&s->display->ops->type==YASL_PRIVATE_BACKEND_COCOA&&portability?VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR:0; } -VkResult yasl_vulkan_create_surface(yasl_surface *s,VkInstance instance,const VkAllocationCallbacks *allocator,VkSurfaceKHR *out) +VkResult yasl_vulkan_create_surface(yasl_surface *s, VkInstance instance, + const VkAllocationCallbacks *allocator, VkSurfaceKHR *out) { - if(out)*out=VK_NULL_HANDLE; - if(!yasl_public_surface(s,false)||!instance||!out||!get_proc){yasl_public_fail(EINVAL);return VK_ERROR_INITIALIZATION_FAILED;} - if(s->display->offline){yasl_public_fail(ENOTCONN);return VK_ERROR_SURFACE_LOST_KHR;} - if(s->presenter!=YASL_PRIVATE_PRESENTER_NONE){yasl_public_fail(EBUSY);return VK_ERROR_NATIVE_WINDOW_IN_USE_KHR;} - VkResult result=VK_ERROR_EXTENSION_NOT_PRESENT; - switch(s->display->ops->type) { + if (out) + *out = VK_NULL_HANDLE; + if (!yasl_public_surface(s, true)) + return VK_ERROR_INITIALIZATION_FAILED; + if (!instance || !out) { + yasl_public_fail(EINVAL); + return VK_ERROR_INITIALIZATION_FAILED; + } + if (!get_proc) { + yasl_public_fail(ENODEV); + return VK_ERROR_INITIALIZATION_FAILED; + } + if (s->display->offline) { + yasl_public_fail(ENOTCONN); + return VK_ERROR_SURFACE_LOST_KHR; + } + if (s->vulkan || s->presenter != YASL_PRIVATE_PRESENTER_NONE) { + yasl_public_fail(EBUSY); + return VK_ERROR_NATIVE_WINDOW_IN_USE_KHR; + } + PFN_vkDestroySurfaceKHR destroy = + (PFN_vkDestroySurfaceKHR)get_proc(instance, "vkDestroySurfaceKHR"); + if (!destroy) { + yasl_public_fail(ENOTSUP); + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + struct yasl_vulkan_binding *binding = calloc(1, sizeof(*binding)); + if (!binding) { + yasl_public_fail(ENOMEM); + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + VkResult result = VK_ERROR_EXTENSION_NOT_PRESENT; + switch (s->display->ops->type) { #if YASL_HAS_WAYLAND case YASL_PRIVATE_BACKEND_WAYLAND: { - PFN_vkCreateWaylandSurfaceKHR create=(PFN_vkCreateWaylandSurfaceKHR)get_proc(instance,"vkCreateWaylandSurfaceKHR"); - if(create) { VkWaylandSurfaceCreateInfoKHR info={.sType=VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, - .display=yasl_wayland_display(s),.surface=yasl_wayland_surface(s)};result=create(instance,&info,allocator,out); }break; + struct yasl_wayland_handles native; + if (yasl_wayland_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}; + result = create(instance, &info, allocator, out); + } + break; } #endif #if YASL_HAS_X11 case YASL_PRIVATE_BACKEND_X11: { - PFN_vkCreateXlibSurfaceKHR create=(PFN_vkCreateXlibSurfaceKHR)get_proc(instance,"vkCreateXlibSurfaceKHR"); - if(create) { VkXlibSurfaceCreateInfoKHR info={.sType=VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, - .dpy=yasl_x11_display(s),.window=(Window)yasl_x11_window(s)};result=create(instance,&info,allocator,out); }break; + struct yasl_x11_handles native; + if (yasl_x11_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}; + result = create(instance, &info, allocator, out); + } + break; } #endif #if YASL_HAS_COCOA case YASL_PRIVATE_BACKEND_COCOA: { - PFN_vkCreateMetalSurfaceEXT create=(PFN_vkCreateMetalSurfaceEXT)get_proc(instance,"vkCreateMetalSurfaceEXT"); - if(create&&s->display->ops->surface_get_metal_layer) { VkMetalSurfaceCreateInfoEXT info={.sType=VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, - .pLayer=s->display->ops->surface_get_metal_layer(s)};result=create(instance,&info,allocator,out); }break; + PFN_vkCreateMetalSurfaceEXT create = + (PFN_vkCreateMetalSurfaceEXT)get_proc(instance, "vkCreateMetalSurfaceEXT"); + if (create && s->display->ops->surface_get_metal_layer) { + VkMetalSurfaceCreateInfoEXT info = { + .sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, + .pLayer = s->display->ops->surface_get_metal_layer(s)}; + result = create(instance, &info, allocator, out); + } + break; } #endif - default:break; + default: + break; + } + if (result == VK_SUCCESS) { + binding->instance = instance; + binding->surface = *out; + binding->destroy = destroy; + binding->has_allocator = allocator != NULL; + if (allocator) + binding->allocator = *allocator; + s->vulkan = binding; + s->presenter = YASL_PRIVATE_PRESENTER_VULKAN; + } else { + free(binding); + yasl_public_fail(result == VK_ERROR_EXTENSION_NOT_PRESENT ? ENOTSUP : EIO); } - if(result==VK_SUCCESS)s->presenter=YASL_PRIVATE_PRESENTER_VULKAN; - else yasl_public_fail(result==VK_ERROR_EXTENSION_NOT_PRESENT?ENOTSUP:EIO); return result; } diff --git a/src/platform/wayland/backend.c b/src/platform/wayland/backend.c index 7ac1c65..6b7758f 100644 --- a/src/platform/wayland/backend.c +++ b/src/platform/wayland/backend.c @@ -434,6 +434,11 @@ static void *impl_surface_get_native_handle(struct yasl_surface *s) const struct yasl_private_backend_ops yasl_private_wayland_backend = { #if YASL_HAS_EGL + .surface_create_gl = impl_surface_create_gl, + .gl_make_current = impl_gl_make_current, + .gl_get_proc_address = impl_gl_get_proc_address, + .gl_swap_interval = impl_gl_swap_interval, + .gl_swap_buffers = impl_gl_swap_buffers, #endif .surface_update_text_input = impl_surface_update_text_input, .type = YASL_PRIVATE_BACKEND_WAYLAND, diff --git a/tests/common/test_gl.c b/tests/common/test_gl.c index cd51575..0e4597b 100644 --- a/tests/common/test_gl.c +++ b/tests/common/test_gl.c @@ -27,9 +27,10 @@ static yasl_surface *cpu; static void fail(const char *message, int line) { const char *description = NULL; - enum yasl_error error = yasl_get_error(&description); + int error = errno; + description = strerror(error); fprintf(stderr, "GL integration failure at line %d: %s (YASL error %d: %s, native error %d)\n", - line, message, error, description ? description : "none", yasl_get_native_error()); + line, message, error, description ? description : "none", errno); (void)yasl_gl_make_current(NULL); if (cpu) yasl_surface_destroy(cpu); if (shared) yasl_surface_destroy(shared); @@ -74,13 +75,13 @@ 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_set_backend(YASL_BACKEND_WAYLAND), "select Wayland backend"); - else if (!strcmp(argv[1], "x11")) REQUIRE(yasl_set_backend(YASL_BACKEND_X11), "select X11 backend"); + 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"); else REQUIRE(false, "backend must be wayland or x11"); - primary = yasl_gl_surface_create("YASL GL test", 320, 240, 0, NULL, NULL); + primary = yasl_gl_surface_create(&(struct yasl_surface_options){.title="YASL GL test", .width=320, .height=240, .type=YASL_SURFACE_NATIVE, .flags=0}, NULL); REQUIRE(primary != NULL, "create default OpenGL 3.2 surface"); - REQUIRE(yasl_gl_make_current(primary), "make first GL context current"); + REQUIRE((yasl_gl_make_current(primary) == 0), "make first GL context current"); get_string_fn get_string = NULL; clear_color_fn clear_color = NULL; @@ -114,22 +115,24 @@ int main(int argc, char **argv) REQUIRE(abs((int)pixel[0] - 51) <= 3 && abs((int)pixel[1] - 102) <= 3 && abs((int)pixel[2] - 204) <= 3 && pixel[3] >= 250, "read rendered pixel from the native GL framebuffer"); - REQUIRE(yasl_gl_swap_interval(primary, 0), "disable swap interval"); - REQUIRE(yasl_gl_swap_buffers(primary), "swap first GL surface"); + REQUIRE((yasl_gl_set_swap_interval(primary, 0) == 0), "disable swap interval"); + REQUIRE((yasl_gl_present(primary) == 0), "swap first GL surface"); unsigned texture = 0; gen_textures(1, &texture); bind_texture(GL_TEXTURE_2D_, texture); REQUIRE(is_texture(texture), "create a shareable GL texture"); - shared = yasl_gl_surface_create("YASL GL shared context", 160, 120, 0, NULL, primary); + 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); REQUIRE(shared != NULL, "create shared GL context"); - REQUIRE(yasl_gl_make_current(shared), "make shared context current"); + REQUIRE((yasl_gl_make_current(shared) == 0), "make shared context current"); REQUIRE(is_texture(texture), "verify context shares GL objects"); delete_textures(1, &texture); - REQUIRE(yasl_gl_make_current(primary), "restore primary GL context"); + REQUIRE((yasl_gl_make_current(primary) == 0), "restore primary GL context"); - REQUIRE(yasl_surface_request_size(primary, 400, 300), "request surface resize"); + 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); @@ -138,18 +141,19 @@ int main(int argc, char **argv) "query resized framebuffer dimensions"); clear_color(0.7f, 0.1f, 0.3f, 1.0f); clear(GL_COLOR_BUFFER_BIT_); - REQUIRE(yasl_gl_swap_buffers(primary), "swap resized GL surface"); + REQUIRE((yasl_gl_present(primary) == 0), "swap resized GL surface"); - const int invalid_attributes[] = {0x7fff, 1, YASL_GL_END}; - REQUIRE(yasl_gl_surface_create("invalid attrs", 100, 100, 0, invalid_attributes, NULL) == NULL, - "reject unknown GL context attributes"); - REQUIRE(yasl_gl_surface_create("invalid CPU GL", 100, 100, YASL_SURFACE_CPU, NULL, NULL) == NULL, + 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, + "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("CPU surface", 100, 100, YASL_SURFACE_CPU); + cpu = yasl_surface_create(&(struct yasl_surface_options){.title="CPU surface", .width=100, .height=100, .type=YASL_SURFACE_CPU, .flags=0}); REQUIRE(cpu != NULL, "create CPU surface for incompatibility check"); - REQUIRE(!yasl_gl_make_current(cpu), "reject CPU surface as a GL context"); + REQUIRE((yasl_gl_make_current(cpu) < 0), "reject CPU surface as a GL context"); - REQUIRE(yasl_gl_make_current(NULL), "detach GL context before destruction"); + REQUIRE((yasl_gl_make_current(NULL) == 0), "detach GL context before destruction"); yasl_surface_destroy(cpu); cpu = NULL; yasl_surface_destroy(shared); shared = NULL; yasl_surface_destroy(primary); primary = NULL; diff --git a/tests/common/test_surface_integration.c b/tests/common/test_surface_integration.c index 7cc938a..1f18f23 100644 --- a/tests/common/test_surface_integration.c +++ b/tests/common/test_surface_integration.c @@ -2,6 +2,16 @@ #include <yasl/yasl.h> #include <yasl/cpu.h> #include <yasl/gl.h> +#include <yasl/metal.h> +#if YASL_HAS_WAYLAND +#include <yasl/native/wayland.h> +#endif +#if YASL_HAS_X11 +#include <yasl/native/x11.h> +#endif +#if YASL_HAS_COCOA +#include <yasl/native/cocoa.h> +#endif #include "common/backend.h" #include <assert.h> #include <pthread.h> @@ -14,95 +24,241 @@ void yasl_test_frame_tick(struct yasl_surface *surface); #endif static void drain(void) { - yasl_event e;int n=0; - while(yasl_wait_event(&e,0)>0)assert(++n<1000); + yasl_event e; + int n = 0; + while (yasl_wait_event(&e, 0) > 0) + assert(++n < 1000); } static void *wake_thread(void *unused) -{ (void)unused;struct timespec delay={0,20000000};nanosleep(&delay,NULL);yasl_wake();return NULL; } +{ + (void)unused; + struct timespec delay = {0, 20000000}; + nanosleep(&delay, NULL); + yasl_wake(); + return NULL; +} static void *wrong_thread(void *data) -{ assert(!yasl_surface_set_title(data,"wrong"));assert(yasl_get_error(NULL)==YASL_ERROR_WRONG_THREAD);return NULL; } +{ + 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 + return NULL; +} static int refreshes; -static void refresh(yasl_surface *s,void *ud) +static void refresh(yasl_surface *s, void *ud) { - (void)ud;refreshes++; - yasl_event e;assert(yasl_wait_event(&e,0)==-1);assert(yasl_get_error(NULL)==YASL_ERROR_BUSY); - yasl_surface_destroy(s);assert(yasl_get_error(NULL)==YASL_ERROR_BUSY); + (void)ud; + refreshes++; + yasl_event e; + assert(yasl_wait_event(&e, 0) == -EBUSY); + assert(errno == EBUSY); + 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); } -int main(int argc,char **argv) +int main(int argc, char **argv) { - assert(argc==2); - enum yasl_backend backend=!strcmp(argv[1],"wayland")?YASL_BACKEND_WAYLAND:!strcmp(argv[1],"x11")?YASL_BACKEND_X11:YASL_BACKEND_COCOA; - assert(yasl_set_backend(backend));assert(yasl_set_app_id("org.yasl.surface-test")); - yasl_surface *s=yasl_surface_create("CPU contract",96,80,YASL_SURFACE_CPU); - if(!s){const char *why;yasl_get_error(&why);fprintf(stderr,"surface: %s native=%d\n",why,yasl_get_native_error());return 1;} - assert(yasl_surface_backend(s)==backend); - if(backend==YASL_BACKEND_COCOA) { - assert(!(yasl_surface_features(s)&YASL_CAP_OPENGL)); - assert(!yasl_gl_surface_create("unsupported GL",64,64,0,NULL,NULL)); - assert(yasl_get_error(NULL)==YASL_ERROR_UNSUPPORTED); - assert(!yasl_gl_make_current(NULL)); - assert(yasl_get_error(NULL)==YASL_ERROR_UNSUPPORTED); + assert(argc == 2); + 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}); + if (!s) { + const char *why; + why = strerror(errno); + fprintf(stderr, "surface: %s native=%d\n", why, 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); + 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(yasl_get_error(NULL)==YASL_ERROR_UNSUPPORTED); + assert(errno == ENOTSUP); } /* The first events report the window size, then ask for a frame. */ - yasl_event e;int fw=0,fh=0;bool window=false; - for(int i=0;i<1000;i++) { - assert(yasl_wait_event(&e,i?20:0)>=0); - if(e.type==YASL_EVENT_WINDOW){window=true;fw=e.window.pixel_width;fh=e.window.pixel_height;} - if(e.type==YASL_EVENT_REFRESH){assert(window&&fw>0&&fh>0&&e.surface==s);break;} + yasl_event e; + int fw = 0, fh = 0; + bool window = false; + for (int i = 0; i < 1000; i++) { + assert(yasl_wait_event(&e, i ? 20 : 0) >= 0); + if (e.type == YASL_EVENT_WINDOW) { + window = true; + fw = e.window.pixel_width; + fh = e.window.pixel_height; + } + if (e.type == YASL_EVENT_REFRESH) { + assert(window && fw > 0 && fh > 0 && e.surface == s); + break; + } } - assert(e.type==YASL_EVENT_REFRESH); - int w,h;yasl_surface_get_framebuffer_size(s,&w,&h);assert(w==fw&&h==fh); - assert(!yasl_set_backend(backend));assert(yasl_get_error(NULL)==YASL_ERROR_BUSY); - int pw,ph;uint32_t *pixels=yasl_cpu_pixels(s,&pw,&ph);assert(pixels&&pw==w&&ph==h); - assert(pixels[0]==0&&pixels[(size_t)w*h-1]==0); - for(size_t i=0;i<(size_t)w*h;i++)pixels[i]=0xff102030u; - pixels[(size_t)3*w+2]=0xffaabbccu; - assert(yasl_cpu_present(s)); - assert(yasl_cpu_pixels(s,&pw,&ph)==pixels&&pixels[0]==0xff102030u&&pixels[(size_t)3*w+2]==0xffaabbccu); + assert(e.type == YASL_EVENT_REFRESH); + 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); + 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); drain(); - 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);pthread_join(thread,NULL); - bool ime=yasl_surface_set_text_input(s,true,YASL_TEXT_NORMAL); - if(!ime)assert(yasl_get_error(NULL)==YASL_ERROR_UNSUPPORTED); - bool snapshot=yasl_surface_set_surrounding_text(s,"a\xc3\xa9z",4,1,3,7); - if(ime)assert(snapshot); - else assert(!snapshot&&yasl_get_error(NULL)==YASL_ERROR_UNSUPPORTED); - assert(!yasl_surface_set_surrounding_text(s,"a\xc3\xa9z",4,2,3,8)); - assert(!yasl_surface_set_surrounding_text(s,"\xc0\xaf",2,0,2,8)); + 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); + 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)); 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));assert(yasl_wait_event(&e,0)==1&&e.type==YASL_EVENT_TEXT); - assert(e.text.length==5&&!strcmp(e.text.data,"owned")); + 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)); + 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_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(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); - yasl_surface_set_refresh_callback(s,refresh,NULL); - assert(yasl_cpu_present(s)); + 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(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); + 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); #if YASL_HAS_COCOA 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); + 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); yasl_surface_destroy(s); - assert(!yasl_set_backend(backend)); - s=yasl_surface_create("shared backend remains",64,64,YASL_SURFACE_CPU);assert(s); - yasl_shutdown();assert(yasl_set_backend(backend)); - s=yasl_surface_create("reinitialized",64,64,YASL_SURFACE_CPU);assert(s);yasl_shutdown(); - puts("public surface lifecycle, pixels, ownership, input, wake and refresh passed");return 0; + assert((yasl_init(&(struct yasl_options){.backend = backend}) < 0)); + s = yasl_surface_create(&(struct yasl_surface_options){.title = "shared backend remains", + .width = 64, + .height = 64, + .type = YASL_SURFACE_CPU, + .flags = 0}); + 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(s); + yasl_shutdown(); + puts("public surface lifecycle, pixels, ownership, input, wake and refresh passed"); + return 0; } diff --git a/tests/common/test_vulkan.c b/tests/common/test_vulkan.c index 12daa22..b12256a 100644 --- a/tests/common/test_vulkan.c +++ b/tests/common/test_vulkan.c @@ -41,29 +41,32 @@ static int run_test(enum yasl_backend backend) VkSemaphore acquired = VK_NULL_HANDLE, rendered = VK_NULL_HANDLE; VkCommandBuffer command = VK_NULL_HANDLE; - if (!yasl_set_backend(backend)) { + if ((yasl_init(&(struct yasl_options){.backend=backend}) < 0)) { fprintf(stderr, "vulkan integration: cannot select requested backend %d (YASL error %d)\n", - backend, yasl_get_error(NULL)); + backend, errno); goto cleanup; } - if (!yasl_vulkan_init(vkGetInstanceProcAddr)) { + if ((yasl_vulkan_init(vkGetInstanceProcAddr) < 0)) { fprintf(stderr, "vulkan integration: loader initialization failed (YASL error %d)\n", - yasl_get_error(NULL)); + errno); goto cleanup; } - surface = yasl_surface_create("yasl Vulkan WSI integration", 256, 192, 0); + surface = yasl_surface_create(&(struct yasl_surface_options){.title="yasl Vulkan WSI integration", .width=256, .height=192, .type=YASL_SURFACE_NATIVE, .flags=0}); if (!surface) { fprintf(stderr, "vulkan integration: surface creation failed (YASL error %d)\n", - yasl_get_error(NULL)); + errno); goto cleanup; } - if (!(yasl_surface_features(surface) & YASL_CAP_VULKAN)) { + if (!(yasl_surface_get_features(surface) & YASL_CAP_VULKAN)) { fprintf(stderr, "vulkan integration: requested backend does not advertise Vulkan WSI\n"); goto cleanup; } uint32_t extension_count = 0; - const char *const *required = yasl_vulkan_instance_extensions(surface, &extension_count); + 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) { fprintf(stderr, "vulkan integration: surface returned no required instance extensions\n"); goto cleanup; @@ -99,7 +102,7 @@ static int run_test(enum yasl_backend backend) VkInstanceCreateInfo instance_info = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &app, - .flags = yasl_vulkan_instance_flags(surface), + .flags = requirements.flags, .enabledExtensionCount = extension_count, .ppEnabledExtensionNames = required, }; @@ -108,6 +111,18 @@ static int run_test(enum yasl_backend backend) vr = yasl_vulkan_create_surface(surface, instance, NULL, &vk_surface); if (vr != VK_SUCCESS) { status = fail_vk("yasl_vulkan_create_surface", vr); goto cleanup; } + 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) { + fprintf(stderr, "vulkan integration: live WSI ownership was not enforced\n"); + goto cleanup; + } + if (yasl_vulkan_destroy_surface(surface) != 0 || yasl_vulkan_destroy_surface(surface) != 0) goto cleanup; + vk_surface = VK_NULL_HANDLE; + vr = yasl_vulkan_create_surface(surface, instance, NULL, &vk_surface); + if (vr != VK_SUCCESS) { status = fail_vk("recreate Vulkan surface", vr); goto cleanup; } + uint32_t physical_count = 0; vr = vkEnumeratePhysicalDevices(instance, &physical_count, NULL); if (vr != VK_SUCCESS || !physical_count) { @@ -365,7 +380,7 @@ cleanup: if (command_pool) vkDestroyCommandPool(device, command_pool, NULL); if (swapchain) vkDestroySwapchainKHR(device, swapchain, NULL); if (device) vkDestroyDevice(device, NULL); - if (vk_surface) vkDestroySurfaceKHR(instance, vk_surface, NULL); + if (vk_surface) yasl_vulkan_destroy_surface(surface); if (instance) vkDestroyInstance(instance, NULL); if (surface) yasl_surface_destroy(surface); yasl_shutdown(); diff --git a/tests/meson.build b/tests/meson.build index 8370423..e34f072 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -8,7 +8,11 @@ foreach kind : ['static', 'shared'] endforeach if add_languages('cpp', native: false, required: false) cpp_api = executable('yasl-cpp-api-test', 'test_api.cpp', dependencies: shared_dep, - cpp_args: ['-pedantic-errors'], override_options: ['cpp_std=c++11']) + cpp_args: ['-pedantic-errors', + '-DYASL_HAS_VULKAN=' + (has_vulkan ? '1' : '0'), + '-DYASL_HAS_WAYLAND=' + (has_wayland ? '1' : '0'), + '-DYASL_HAS_X11=' + (has_x11 ? '1' : '0'), + '-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', diff --git a/tests/test_api.cpp b/tests/test_api.cpp index c0693eb..acfbbb8 100644 --- a/tests/test_api.cpp +++ b/tests/test_api.cpp @@ -19,8 +19,17 @@ int main() { yasl_event e{}; - e.pen.x=1.5;e.pen.pressure=0.25; - assert(e.pen.pressure==0.25); - assert(!yasl_surface_create("invalid",-1,10,0)); - yasl_shutdown();return 0; + e.pen.x = 1.5; + e.pen.pressure = 0.25; + assert(e.pen.pressure == 0.25); + 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_shutdown(); + return 0; } diff --git a/tests/test_lifecycle.c b/tests/test_lifecycle.c index a334fed..08213b0 100644 --- a/tests/test_lifecycle.c +++ b/tests/test_lifecycle.c @@ -1,24 +1,37 @@ #include <yasl/yasl.h> #include <yasl/cpu.h> #include <yasl/gl.h> +#include <yasl/metal.h> #include <yasl/desktop.h> #include <assert.h> int main(void) { - yasl_shutdown(); yasl_wake(); - yasl_event e={.type=YASL_EVENT_QUIT}; - assert(yasl_wait_event(&e,0)==-1 && e.type==YASL_EVENT_NONE); - assert(yasl_get_error(NULL)==YASL_ERROR_NOT_INITIALIZED); - assert(!yasl_set_backend((enum yasl_backend)999)); - assert(yasl_get_error(NULL)==YASL_ERROR_INVALID); - assert(!yasl_surface_create("bad",0,64,0)); - assert(!yasl_surface_create("bad",64,64,16)); - assert(yasl_set_app_id("org.yasl.api")); - assert(yasl_set_backend(YASL_BACKEND_AUTO)); - yasl_surface_destroy(NULL); - assert(yasl_time_ns()>0); - const char *description=NULL;yasl_get_error(&description);assert(description); - int w=9,h=9;yasl_surface_get_size(NULL,&w,&h);assert(w==0&&h==0); - assert(yasl_executable_path(NULL,0)>0); - yasl_shutdown();return 0; + assert(yasl_shutdown() == 0); + yasl_wake(); + 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_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); + 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); + assert(yasl_shutdown() == 0); + return 0; } diff --git a/tests/wayland/test_reconnect.c b/tests/wayland/test_reconnect.c index 0c42043..737afb2 100644 --- a/tests/wayland/test_reconnect.c +++ b/tests/wayland/test_reconnect.c @@ -4,6 +4,7 @@ #include <yasl/yasl.h> #include <yasl/cpu.h> #include <yasl/gl.h> +#include <yasl/native/wayland.h> #if TEST_VULKAN #include <yasl/vulkan.h> #endif @@ -11,7 +12,7 @@ #include <stdlib.h> #include <string.h> -#define CHECK(cond, what) do { if (!(cond)) { const char *why; yasl_get_error(&why); \ +#define CHECK(cond, what) do { if (!(cond)) { const char *why; why = strerror(errno); \ fprintf(stderr, "reconnect: %s failed at line %d (%s)\n", what, __LINE__, why); exit(1); } } while (0) typedef void (*clear_color_fn)(float, float, float, float); @@ -34,13 +35,13 @@ static yasl_event expect(enum yasl_event_type type, yasl_surface *surface, int t static void draw_gl(float red) { - CHECK(yasl_gl_make_current(gl), "binding GL"); + CHECK((yasl_gl_make_current(gl) == 0), "binding GL"); clear_color_fn clear_color = (clear_color_fn)yasl_gl_get_proc_address("glClearColor"); clear_fn clear = (clear_fn)yasl_gl_get_proc_address("glClear"); CHECK(clear_color && clear, "loading GL procedures"); clear_color(red, 0.2f, 0.3f, 1); clear(0x00004000u); - CHECK(yasl_gl_swap_buffers(gl), "swapping GL buffers"); + CHECK((yasl_gl_present(gl) == 0), "swapping GL buffers"); } #if TEST_VULKAN @@ -57,60 +58,72 @@ static VkSurfaceKHR vulkan_surface(void) int main(void) { - CHECK(yasl_set_backend(YASL_BACKEND_WAYLAND), "selecting Wayland"); - cpu = yasl_surface_create("reconnect CPU", 160, 120, YASL_SURFACE_CPU); + 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(cpu, "creating CPU window"); - gl = yasl_gl_surface_create("reconnect GL", 160, 120, 0, NULL, NULL); + gl = yasl_gl_surface_create(&(struct yasl_surface_options){.title="reconnect GL", .width=160, .height=120, .type=YASL_SURFACE_NATIVE, .flags=0}, NULL); CHECK(gl, "creating GL window"); expect(YASL_EVENT_REFRESH, cpu, 5000); int w, h; - uint32_t *pixels = yasl_cpu_pixels(cpu, &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; CHECK(pixels, "getting pixels"); for (size_t i = 0; i < (size_t)w * h; i++) pixels[i] = 0xff336699u; - CHECK(yasl_cpu_present(cpu), "presenting"); + CHECK((yasl_cpu_present(cpu, &buffer) == 0), "presenting"); draw_gl(0.8f); #if TEST_VULKAN - CHECK(yasl_vulkan_init(vkGetInstanceProcAddr), "loading Vulkan"); - vk = yasl_surface_create("reconnect Vulkan", 160, 120, 0); + 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"); - uint32_t count = 0; - const char *const *extensions = yasl_vulkan_instance_extensions(vk, &count); + struct yasl_vulkan_requirements requirements; + CHECK(yasl_vulkan_get_requirements(vk, &requirements) == 0, "Vulkan requirements"); VkInstanceCreateInfo info = {.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, - .enabledExtensionCount = count, .ppEnabledExtensionNames = extensions}; + .flags = requirements.flags, .enabledExtensionCount = requirements.extension_count, + .ppEnabledExtensionNames = requirements.extensions}; CHECK(vkCreateInstance(&info, NULL, &instance) == VK_SUCCESS, "creating Vulkan instance"); - VkSurfaceKHR surface = vulkan_surface(); + CHECK(vulkan_surface() != VK_NULL_HANDLE, "Vulkan handle"); #endif puts("ready"); 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, + "rejecting native handles while offline"); + CHECK(yasl_cpu_acquire(cpu, &buffer) == -ENOTCONN, "rejecting CPU acquisition while offline"); + CHECK(yasl_gl_make_current(gl) == -ENOTCONN, "rejecting GL binding while offline"); #if TEST_VULKAN - vkDestroySurfaceKHR(instance, surface, NULL); + CHECK(yasl_vulkan_destroy_surface(vk) == 0, "destroying Vulkan surface"); #endif - CHECK(!yasl_surface_create("offline", 64, 64, YASL_SURFACE_CPU), "refusing windows while offline"); - CHECK(yasl_get_error(NULL) == YASL_ERROR_DISCONNECTED, "reporting the disconnect"); + 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(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, + "querying restored native handles"); expect(YASL_EVENT_REFRESH, cpu, 5000); int rw, rh; - pixels = yasl_cpu_pixels(cpu, &rw, &rh); + CHECK(yasl_cpu_acquire(cpu, &buffer) == 0, "reacquire CPU buffer"); + rw=buffer.width; rh=buffer.height; pixels=buffer.pixels; 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), "presenting after reconnect"); + CHECK((yasl_cpu_present(cpu, &buffer) == 0), "presenting after reconnect"); draw_gl(0.1f); #if TEST_VULKAN - surface = vulkan_surface(); - vkDestroySurfaceKHR(instance, surface, NULL); + CHECK(vulkan_surface() != VK_NULL_HANDLE, "restored Vulkan handle"); + CHECK(yasl_vulkan_destroy_surface(vk) == 0, "destroying Vulkan surface"); vkDestroyInstance(instance, NULL); #endif /* A second window proves the new connection accepts creation. */ - CHECK(yasl_surface_create("after reconnect", 64, 64, YASL_SURFACE_CPU), "creating a window after reconnect"); + 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"); 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 f4f8014..66cdd93 100644 --- a/tests/x11/test_tablet_native.c +++ b/tests/x11/test_tablet_native.c @@ -71,8 +71,10 @@ static void render_event(yasl_surface *surface, struct canvas_state *state, cons state->pen = event->pen; 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); + &state->painted_width, &state->painted_height, false, &buffer); assert(state->pixels); if (event->type == YASL_EVENT_PEN && state->down && (event->pen.action == YASL_PEN_DOWN || event->pen.action == YASL_PEN_MOVE)) { @@ -81,7 +83,11 @@ static void render_event(yasl_surface *surface, struct canvas_state *state, cons event->pen.x * scale, event->pen.y * scale, (1.5 + 12 * event->pen.pressure) * scale, event->pen.tool == YASL_PEN_ERASER); } - assert(yasl_cpu_present(surface)); + 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); + state->pixels = snapshot; } static void drain(yasl_surface *surface, struct canvas_state *state) @@ -101,10 +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. */ - XSync(yasl_x11_display(surface), False); + struct yasl_x11_handles native; + assert(yasl_x11_get_native(surface, &native) == 0); + XSync(native.display, False); drain(surface, state); /* Public polling submits the CPU frame before the native readback. */ - XSync(yasl_x11_display(surface), False); + XSync(native.display, False); } static void set_eraser_property(Display *display) @@ -132,11 +140,13 @@ int main(int argc, char **argv) { assert(argc == 3); /* pen socket, eraser socket */ struct canvas_state app = {0}; - assert(yasl_set_backend(YASL_BACKEND_X11)); - yasl_surface *surface = yasl_surface_create("YASL native pressure test", 500, 400, YASL_SURFACE_CPU); - assert(surface && (yasl_surface_features(surface) & YASL_CAP_TABLET)); - Display *x = yasl_x11_display(surface); - Window window = (Window)yasl_x11_window(surface); + 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; XMoveWindow(x, window, 0, 0); XSetInputFocus(x, window, RevertToParent, CurrentTime); XSync(x, False); @@ -194,6 +204,7 @@ int main(int argc, char **argv) assert(!app.down && !app.pen.pressure); close(pen_fd); close(eraser_fd); + free(app.pixels); yasl_shutdown(); puts("Native Xorg pen pressure, stationary pressure, release, and eraser drawing passed."); return 0; |
