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