summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-13 15:05:06 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-13 15:05:06 +0300
commit98f0d51157fa2a2b8ccaae54ed4850a88d223d01 (patch)
tree90b03d0930986620d95eb0f45c07b96e1331783c
parent339e1ad7adb8340732ee856d0f6f0d2c7f1455d1 (diff)
Simplify surface state, examples, and library installation
-rw-r--r--ARCHITECTURE_PLAN.md2
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--README.md25
-rw-r--r--docs/api-policy.md13
-rw-r--r--docs/getting-started.md81
-rw-r--r--docs/guide.md26
-rw-r--r--docs/migrating.md15
-rw-r--r--docs/testing.md12
-rw-r--r--examples/hello.c104
-rw-r--r--examples/simple_window.c200
-rw-r--r--examples/tablet.c156
-rw-r--r--include/yasl/app.h6
-rw-r--r--include/yasl/yasl.h5
-rw-r--r--meson.build39
-rw-r--r--src/common/app.c36
-rw-r--r--src/common/app_internal.h3
-rw-r--r--src/common/backend.c4
-rw-r--r--src/common/backend.h3
-rw-r--r--src/common/keyboard.h2
-rw-r--r--src/platform/cocoa/api.h2
-rw-r--r--src/platform/cocoa/keymap.h2
-rw-r--r--src/platform/cocoa/window.m21
-rw-r--r--src/platform/sync.h2
-rw-r--r--src/platform/wayland/api.h4
-rw-r--r--src/platform/wayland/backend.c6
-rw-r--r--src/platform/wayland/buffer.c17
-rw-r--r--src/platform/wayland/clipboard.c2
-rw-r--r--src/platform/wayland/internal.h9
-rw-r--r--src/platform/wayland/keyboard.c1
-rw-r--r--src/platform/wayland/keymap.h2
-rw-r--r--src/platform/wayland/popup.c1
-rw-r--r--src/platform/wayland/shell.c56
-rw-r--r--src/platform/wayland/surface.c2
-rw-r--r--src/platform/x11/keymap.h2
-rw-r--r--src/platform/x11/window.c17
-rw-r--r--tests/benchmark.c1
-rw-r--r--tests/common/test_contracts.c9
-rw-r--r--tests/common/test_hello_example.c20
-rw-r--r--tests/common/test_tablet_example.c20
-rw-r--r--tests/common/test_timers.c1
-rw-r--r--tests/helpers/run_backends.py8
-rw-r--r--tests/meson.build17
-rw-r--r--tests/test_lifecycle.c1
-rw-r--r--tests/wayland/test_app_input.c55
-rw-r--r--tests/wayland/test_state.c75
-rw-r--r--tests/x11/test_state.c77
-rw-r--r--tests/x11/test_tablet_native.c49
47 files changed, 722 insertions, 491 deletions
diff --git a/ARCHITECTURE_PLAN.md b/ARCHITECTURE_PLAN.md
index 5c866e5..f2ec38c 100644
--- a/ARCHITECTURE_PLAN.md
+++ b/ARCHITECTURE_PLAN.md
@@ -1,4 +1,4 @@
-# YASL Modern Architecture Plan (Approach A)
+# YASL Modern Architecture Plan
This document outlines the transition of YASL to **Approach A: Inverted Lifecycle & Unified Event Architecture**, designed specifically to serve both high-performance game development and reactive desktop applications (IDEs and editors like `examples/notepad.c`).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 939f4e4..30432ec 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -40,7 +40,7 @@ configurations checked. Cocoa changes need a macOS desktop build/test; Linux
success alone does not validate them. Keep changes focused and describe any
platforms that were not exercised.
-Document public behavior in the installed header first: argument validity,
+Document public behavior in the public header first: argument validity,
units, ownership/lifetime, return/error behavior, thread requirements, callback
rules, and unsupported results. Update guides/examples when the workflow
changes. Use zero-initialized specs and check fallible operations in examples.
diff --git a/README.md b/README.md
index 5f0d4a6..358ee7c 100644
--- a/README.md
+++ b/README.md
@@ -47,28 +47,27 @@ one with `-Dwayland=enabled` or `-Dx11=enabled`; omit one with `disabled`. Runti
selection chooses the native session and allows fallback, or you can request an
explicit backend with `yasl_display_spec.backend`.
-Include `<yasl/yasl.h>` for the core. The complete [hello.c](examples/hello.c)
-handles creation failures, drawing, dispatch, input draining, and cleanup.
-Its drawing callback fills every pixel before presenting:
+Include `<yasl/app.h>` and pass application callbacks to `yasl_run()`.
+The complete [hello.c](examples/hello.c) creates its window in `init`, handles
+close requests in `event`, and fills every pixel in its frame callback:
```c
-static void draw(struct yasl_surface *surface, struct yasl_frame *frame, void *ud)
+static void draw(void *ud, struct yasl_surface *surface, struct yasl_frame_context *frame)
{
(void)ud;
- size_t count = (size_t)frame->target.size.x * frame->target.size.y;
+ size_t count = (size_t)frame->size.x * frame->size.y;
for (size_t i = 0; i < count; i++)
- frame->target.pixels[i] = 0xff306090;
- struct yasl_damage_list damage = {.n = -1};
- if (!yasl_surface_present(surface, &frame->target, &damage)) {
- /* Arrange application error handling after returning. */
- }
+ frame->cpu.pixels[i] = 0xff306090;
+ if (!yasl_frame_present_cpu(frame, NULL, 0))
+ yasl_surface_destroy(surface);
}
```
Toplevel creation sizes are logical pixels; drawing and input use device pixels.
-Targets are borrowed for one draw callback. Keep durable content in application
-memory, dispatch events, drain input queues, then flush scheduled drawing. These
-rules are explained with examples in the application guide.
+Targets are borrowed for one frame callback. Keep durable content in application
+memory; the runner handles dispatch, input polling, pacing, and cleanup.
+Applications managing their own loop can use `<yasl/yasl.h>` directly. Both
+workflows are explained in the application guide.
## License
diff --git a/docs/api-policy.md b/docs/api-policy.md
index 0c8f040..dd22ca7 100644
--- a/docs/api-policy.md
+++ b/docs/api-policy.md
@@ -1,16 +1,17 @@
# Public API policy
-YASL 0.1 is pre-release. The installed headers are the public API; implementation
+YASL 0.1 is pre-release. The headers under `include/yasl/` are the public API; implementation
headers under `src/` are private even when their symbols appear in a static
-archive. The shared library exports only declarations from installed headers.
-Native backend headers and accessors are available only for built backends.
+archive. The shared library exports only declarations from public headers.
+Native backend accessors are exported only for built backends. Installation
+copies only the shared and static libraries; use matching source-tree headers.
There is currently no stable ABI promise and no versioned shared-library SONAME.
Rebuild consumers against the exact headers/library pair they ship. Source and
binary compatibility can change before 1.0. Do not assume struct layout is stable
across releases, mix headers and binaries from different revisions, or persist
-public structs as a file/network format. The package version is available through
-`pkg-config --modversion yasl`.
+public structs as a file/network format. The release version is declared in
+`meson.build`.
See [migration notes](migrating.md) for window creation, portable keyboard, and
fractional scroll API changes.
@@ -28,7 +29,7 @@ Every public addition or behavior change needs:
- An update to the guide and its backend limits when observable behavior changes.
- A runnable example when the new workflow cannot be understood from an isolated
call, and meaningful regression coverage for ownership or behavior changes.
-- A check that shared exports and installed-package consumers still work.
+- A check that shared exports and consumers of the installed library still work.
Functions documenting errno set it on failure. Other booleans report acceptance
or availability without a guaranteed errno; void setters are best effort.
diff --git a/docs/getting-started.md b/docs/getting-started.md
index d21c9bd..caa25eb 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -39,79 +39,68 @@ CPU pixel targets are presented through the system's Metal device; applications
do not need to create or manage that device themselves.
The complete beginner example is [hello.c](../examples/hello.c). It checks
-construction failures, fills every pixel, presents inside `draw`, drains input,
-responds to quit/close/lost, and destroys resources in order. The
+construction failures, fills every pixel, and presents inside a frame callback.
+`yasl_run()` handles input delivery, display notifications, and cleanup. The
[interactive window](../examples/simple_window.c) adds pointer input; the
[tablet example](../examples/tablet.c) demonstrates retained application content,
pressure, erasers, resizing, and scale changes.
## Link an application
-Install the library and public headers into your chosen prefix:
+Install the shared and static libraries into your chosen prefix. Headers,
+examples, documentation, and pkg-config metadata remain in the source tree:
```sh
-meson setup build --prefix=/usr/local
+meson setup build --prefix=/usr/local --libdir=lib
meson compile -C build
meson install -C build
-cc -std=c23 examples/hello.c -o hello $(pkg-config --cflags --libs yasl)
+cc -std=c23 -Iinclude examples/hello.c -o hello -L/usr/local/lib -Wl,-rpath,/usr/local/lib -lyasl
./hello
```
-The prefix must be writable, and your compiler, pkg-config, and runtime loader
-must search it. For a custom prefix, set `PKG_CONFIG_PATH` to its pkg-config
-directory and configure the loader for its library directory. Static linking
-uses `pkg-config --static --libs yasl` to include backend dependencies.
+The prefix must be writable. Use matching headers from `include/`, and adjust
+the library search path and runtime path for your chosen prefix and `libdir`.
+Static consumers must also link the enabled backends' dependencies.
-A Meson consumer uses the installed dependency:
+A Meson application can instead vendor the source under `subprojects/yasl`:
```meson
project('my-app', 'c', default_options: ['c_std=c23'])
-yasl = dependency('yasl', version: '>=0.1.0')
+yasl = subproject('yasl').get_variable('yasl_dep')
executable('my-app', 'main.c', dependencies: yasl)
```
-YASL also declares `meson.override_dependency('yasl', ...)`, so the same consumer
-can use a vendored Meson subproject through a fallback dependency. Public headers
-can be included from C++11 or newer; the library itself builds as C23.
+The subproject dependency includes headers and backend link dependencies.
+Public headers can be included from C++11 or newer; the library itself builds
+as C23. Only the library artifacts are installed by `meson install`.
## The application cycle
-1. Open a display with `yasl_display_open(NULL, &spec)`. NULL creates a loop owned
- by the display. Supply your own loop only when you need shared loop ownership.
-2. Create a toplevel with `yasl_window_spec`: supply a positive **logical-pixel**
- size, a `yasl_window_cbs` table in `cbs`, and callback userdata in `ud`.
- Zero-initialize every option and callback struct. Callback tables are copied;
- userdata must remain alive. The new window's callbacks start after creation
- returns. Read initial geometry with `yasl_surface_geometry()`.
-3. Schedule the initial redraw. `draw` receives a borrowed target. Fill every
- pixel with premultiplied `0xAARRGGBB`, then present that exact target, once.
-4. Dispatch, drain input for every live surface queue, update application state,
- and flush. Flush runs scheduled drawing and sends native requests. Dispatch
- may also invoke callbacks, including drawing during native live resize.
-5. Destroy popups before parents, surfaces before displays, and displays before
- any supplied loop. NULL destroy/close calls do nothing; freed pointers are
- invalid for every operation.
+1. Include `<yasl/app.h>` and initialize a `yasl_app_desc` with your userdata and
+ `init`, `event`, and `frame` callbacks. Call `yasl_run(&desc)`.
+2. In `init`, create a toplevel with a positive **logical-pixel** size and call
+ `yasl_surface_request_frame(surface)` for the first redraw.
+3. In `event`, handle the union member selected by `event->type`. Return
+ `YASL_APP_QUIT` for a close request; otherwise return `YASL_APP_CONTINUE`.
+4. In `frame`, fill every pixel in `frame->cpu.pixels` with premultiplied
+ `0xAARRGGBB` and present once with `yasl_frame_present_cpu(frame, NULL, 0)`.
+5. Let the runner close remaining surfaces and the display. Release your own
+ resources in the optional `quit` callback or after `yasl_run()` returns.
```c
-while (running) {
- if (yasl_display_dispatch(display, -1) < 0) {
- perror("yasl_display_dispatch");
- break;
- }
- struct yasl_input_event event;
- while (yasl_surface_input_poll(surface, &event)) {
- /* Read the union member selected by event.kind. */
- }
- yasl_display_flush(display);
-}
+struct yasl_app_desc desc = {
+ .app_id = "org.example.app",
+ .userdata = &app,
+ .init = init,
+ .event = event,
+ .frame = draw,
+};
+return yasl_run(&desc);
```
-Zero timeout polls, positive timeout bounds the wait in milliseconds, and a
-negative timeout waits for work. Undrained input makes dispatch return without
-waiting. Use the return value only to detect failure, not to count input events.
-If callbacks destroy surfaces, update your application registry immediately and
-only poll surfaces that are still alive. Arrange display/loop cleanup after the
-cycle ends; destroying them inside a callback is unsupported.
+The default pacing mode sleeps until input, resize, or a requested redraw.
+Use `YASL_PACING_CONTINUOUS` for an animation or game. The core API still supports
+manual dispatch/poll/flush when an application owns its event loop; see the guide.
See the [application guide](guide.md) for drawing, input, popups, timers, and
platform limits, or the comments in the [public header](../include/yasl/yasl.h)
diff --git a/docs/guide.md b/docs/guide.md
index adcacc8..1bd1aef 100644
--- a/docs/guide.md
+++ b/docs/guide.md
@@ -39,6 +39,22 @@ it advertises CSD. Check each result and provide an application fallback.
## Window creation and callbacks
+All examples include `<yasl/app.h>` and call `yasl_run()` with a
+`yasl_app_desc`. Create surfaces in `init`, handle `YASL_EVENT_*` notifications
+in `event`, and render into `frame->cpu.pixels` in `frame`. Present with
+`yasl_frame_present_cpu(frame, NULL, 0)` and return `YASL_APP_QUIT` from an event
+handler to exit. The runner handles dispatch, input polling, resize redraws,
+and remaining surface/display cleanup. Display quit stops the runner; connection
+loss and dispatch failures return an error after cleanup.
+
+Unified pointer button and motion events retain `device` and `pen` axes,
+including erasers and pressure-only motion. Cancel held input or active strokes
+on `YASL_EVENT_INPUT_RESET`, pointer leave, or focus loss. Reset also starts the
+next motion delta from zero. [hello.c](../examples/hello.c) shows the minimal
+lifecycle; [tablet.c](../examples/tablet.c) demonstrates device-aware drawing.
+
+For applications managing their own event loop, use the core API below.
+
Open the display with application-wide options and notifications in
`yasl_display_spec`. Create each toplevel with `yasl_window_spec`, including its
title, logical size, optional `yasl_window_cbs` table in `cbs`, and userdata in
@@ -55,6 +71,14 @@ installs popup callbacks; the setter does not invoke the callbacks.
When using `yasl_run()`, its runner installs callbacks for windows on its display.
Use `yasl_app_desc.event` and `.frame` rather than `yasl_window_spec.cbs` and `.ud`.
+Read window state with `yasl_surface_state()`. Its `YASL_STATE_*` flags can
+coexist, and the `state` callback receives the same current bitmask. Wayland
+also reports `YASL_STATE_TILED` when any window edge is tiled; X11 and Cocoa
+currently leave that flag unset. Unsupported flags are absent. On Wayland, an
+ordinary floating toplevel has fullscreen, maximized, and tiled all absent.
+`YASL_STATE_SUSPENDED` describes repaint suspension rather than minimization;
+there is no portable minimized flag.
+
## Geometry and scaling
| Value | Units |
@@ -301,7 +325,7 @@ signals the loop pipe but is not a portable cross-thread event-posting API.
Portable CPU drawing needs no native headers. Opt into `<yasl/native.h>` and
`<yasl/native/wayland.h>`, `<yasl/native/x11.h>`, or `<yasl/native/cocoa.h>` when
-integrating a platform API. Typed headers and functions are installed only for
+integrating a platform API. Typed native functions are exported only for
built backends. Accessors return borrowed handles and zero/NULL for the wrong
backend. Do not pass freed objects, destroy borrowed handles, independently
dispatch native connections, or change surface roles and buffer ownership.
diff --git a/docs/migrating.md b/docs/migrating.md
index da4e591..395072e 100644
--- a/docs/migrating.md
+++ b/docs/migrating.md
@@ -1,9 +1,22 @@
# Migrating pre-release applications
YASL 0.1 has no stable source or binary compatibility promise. Rebuild all
-consumers against the matching installed headers and library after updating.
+consumers against matching public headers and the library after updating.
See the [application guide](guide.md) for the full window and input workflow.
+## Application callbacks
+
+All examples now use `<yasl/app.h>` and `yasl_run()`. Move window creation to
+`yasl_app_desc.init`, input handling to `.event`, and drawing to `.frame`.
+Replace manual dispatch/poll/flush loops with the runner and request redraws with
+`yasl_surface_request_frame()`. Present the frame context with
+`yasl_frame_present_cpu()`; the runner destroys remaining surfaces on exit.
+
+Unified pointer button and motion events retain `device` and tablet axes.
+Handle `YASL_EVENT_INPUT_RESET` by clearing held input and active strokes, just
+as for the core API's `YASL_INPUT_RESET`. Window state remains a `YASL_STATE_*`
+bitmask; `YASL_STATE_TILED` is reported for tiled Wayland toplevels.
+
## Window creation
Rename `struct yasl_surface_spec` to `struct yasl_window_spec` and
diff --git a/docs/testing.md b/docs/testing.md
index d01b82c..bd3f2f0 100644
--- a/docs/testing.md
+++ b/docs/testing.md
@@ -24,7 +24,7 @@ meson test -C build-sanitize --print-errorlogs
Default tests need no desktop connection. C++ header coverage is built when a
C++ compiler is available. On Linux and macOS, the shared-export test ensures
-only installed API declarations are exported. Test disabled-backend configurations separately with
+only public API declarations are exported. Test disabled-backend configurations separately with
`-Dwayland=disabled` or `-Dx11=disabled`; the combined integration harness needs
both backends.
@@ -94,13 +94,13 @@ serves as the drawing cursor. Mouse drawing uses a fixed width. Press C or
Space to clear and Escape to quit. Resizing and display scale changes preserve
the drawing. `--smoke-test` presents one frame and exits for automated checks.
-[`examples/tablet.c`](../examples/tablet.c) includes only `<yasl/yasl.h>` and the C
-standard library. It uses CPU framebuffers and `yasl_surface_input_poll()` on
-every backend. Tablet events arrive as `YASL_INPUT_MOUSE`, distinguished by
-`mouse.device == YASL_PTR_DEV_PEN` or `YASL_PTR_DEV_ERASER`; `mouse.pen` contains
+[`examples/tablet.c`](../examples/tablet.c) includes `<yasl/app.h>` and the C
+standard library. It uses `yasl_run()`, frame callbacks, and unified input events
+on every backend. Tablet button and motion events retain `device`, which is
+`YASL_PTR_DEV_PEN` or `YASL_PTR_DEV_ERASER`; their `pen` member contains
pressure and signed tilt. Button 0 is tip contact on every backend. Track tip
press/release events: zero pressure alone does not mean release. Hover and side
-buttons do not paint, and focus loss, leave, or `YASL_INPUT_RESET` cancels a stroke.
+buttons do not paint, and focus loss, leave, or `YASL_EVENT_INPUT_RESET` cancels a stroke.
Stationary pressure changes arrive as motion events and update the brush too.
The example uses portable physical keys: C and Space clear, Escape exits.
diff --git a/examples/hello.c b/examples/hello.c
index 0c474c2..4153394 100644
--- a/examples/hello.c
+++ b/examples/hello.c
@@ -1,104 +1,56 @@
/* A complete CPU-rendered window. Build instructions: docs/getting-started.md. */
-#include <yasl/yasl.h>
+#include <yasl/app.h>
#include <stdio.h>
-#include <string.h>
struct app {
- bool running;
bool failed;
- bool smoke_test;
};
-static void quit(void *ud)
+static int init(void *ud, struct yasl_display *display)
{
- ((struct app *)ud)->running = false;
-}
-
-static void lost(void *ud)
-{
- struct app *app = ud;
- app->failed = true;
- app->running = false;
-}
-
-static void close_window(struct yasl_surface *surface, void *ud)
-{
- (void)surface;
- quit(ud);
+ struct yasl_window_spec spec = {.title = "Hello YASL", .size = {640, 480}};
+ struct yasl_surface *surface = yasl_surface_create_toplevel(display, &spec);
+ if (!surface) {
+ perror("yasl_surface_create_toplevel");
+ ((struct app *)ud)->failed = true;
+ return YASL_APP_QUIT;
+ }
+ yasl_surface_request_frame(surface);
+ return YASL_APP_CONTINUE;
}
-static void configure(struct yasl_surface *surface, struct yasl_geometry geometry, void *ud)
+static int event(void *ud, const struct yasl_event *event)
{
- (void)geometry;
(void)ud;
- yasl_surface_schedule_redraw(surface);
+ return event->type == YASL_EVENT_WINDOW_CLOSE_REQUEST ? YASL_APP_QUIT : YASL_APP_CONTINUE;
}
-static void draw(struct yasl_surface *surface, struct yasl_frame *frame, void *ud)
+static void draw(void *ud, struct yasl_surface *surface, struct yasl_frame_context *frame)
{
struct app *app = ud;
- size_t count = (size_t)frame->target.size.x * frame->target.size.y;
+ size_t count = (size_t)frame->size.x * frame->size.y;
for (size_t i = 0; i < count; i++)
- frame->target.pixels[i] = 0xff306090; /* Opaque premultiplied ARGB. */
+ frame->cpu.pixels[i] = 0xff306090; /* Opaque premultiplied ARGB. */
- struct yasl_damage_list damage = {.n = -1};
- if (!yasl_surface_present(surface, &frame->target, &damage)) {
+ if (!yasl_frame_present_cpu(frame, NULL, 0)) {
fprintf(stderr, "Could not present the draw target\n");
app->failed = true;
- app->running = false;
}
- if (app->smoke_test) app->running = false;
+ if (app->failed) yasl_surface_destroy(surface);
}
-int main(int argc, char **argv)
+int main(void)
{
- struct app app = {.running = true};
- if (argc == 2 && strcmp(argv[1], "--smoke-test") == 0)
- app.smoke_test = true;
- else if (argc != 1) {
- fprintf(stderr, "Usage: %s [--smoke-test]\n", argv[0]);
- return 1;
- }
-
- struct yasl_display_cbs display_callbacks = {.quit = quit, .lost = lost};
- struct yasl_display_spec display_spec = {
+ struct app app = {0};
+ struct yasl_app_desc desc = {
.app_id = "org.example.hello",
- .cbs = &display_callbacks,
- .ud = &app,
- };
- struct yasl_display *display = yasl_display_open(NULL, &display_spec);
- if (!display) {
- perror("yasl_display_open");
- return 1;
- }
- struct yasl_window_cbs callbacks = {
- .draw = draw, .configure = configure, .close = close_window,
- };
- struct yasl_window_spec spec = {
- .title = "Hello YASL", .size = {640, 480},
- .cbs = &callbacks, .ud = &app,
+ .userdata = &app,
+ .init = init,
+ .event = event,
+ .frame = draw,
};
- struct yasl_surface *surface = yasl_surface_create_toplevel(display, &spec);
- if (!surface) {
- perror("yasl_surface_create_toplevel");
- yasl_display_close(display);
- return 1;
- }
- yasl_surface_schedule_redraw(surface);
-
- while (app.running) {
- if (yasl_display_dispatch(display, -1) < 0) {
- perror("yasl_display_dispatch");
- app.failed = true;
- break;
- }
- /* Drain even unused input so queued events do not cause a busy loop. */
- struct yasl_input_event event;
- while (yasl_surface_input_poll(surface, &event)) {}
- if (app.running) yasl_display_flush(display);
- }
- yasl_surface_destroy(surface);
- yasl_display_close(display);
- return app.failed ? 1 : 0;
+ int result = yasl_run(&desc);
+ if (result) fprintf(stderr, "Could not run the application\n");
+ return result || app.failed ? 1 : 0;
}
diff --git a/examples/simple_window.c b/examples/simple_window.c
index 797de3b..e9e6545 100644
--- a/examples/simple_window.c
+++ b/examples/simple_window.c
@@ -1,9 +1,8 @@
-#include "yasl/yasl.h"
+#include <yasl/app.h>
#include <stdio.h>
-#include <stdlib.h>
-#include <stdbool.h>
+#include <string.h>
-static bool running = true;
+static bool failed, smoke_test;
static int mouse_x = -1;
static int mouse_y = -1;
static bool mouse_down = false;
@@ -23,12 +22,12 @@ static const char *device_name(enum yasl_pointer_device dev)
}
}
-static void on_draw(struct yasl_surface *s, struct yasl_frame *frame, void *ud)
+static void on_draw(void *ud, struct yasl_surface *s, struct yasl_frame_context *frame)
{
(void)ud;
- int w = frame->target.size.x;
- int h = frame->target.size.y;
- uint32_t *pixels = frame->target.pixels;
+ int w = frame->size.x;
+ int h = frame->size.y;
+ uint32_t *pixels = frame->cpu.pixels;
if (!pixels || w <= 0 || h <= 0)
return;
@@ -81,63 +80,28 @@ static void on_draw(struct yasl_surface *s, struct yasl_frame *frame, void *ud)
}
}
- struct yasl_damage_list dmg = { .n = -1 };
- yasl_surface_present(s, &frame->target, &dmg);
-}
-
-static void on_configure(struct yasl_surface *s, struct yasl_geometry geo, void *ud)
-{
- (void)ud;
- printf("[YASL] Resized to %dx%d (scale: %.2fx)\n",
- geo.size.x, geo.size.y, (double)geo.scale_120 / YASL_SCALE_UNIT);
- yasl_surface_schedule_redraw(s);
-}
-
-static void on_close(struct yasl_surface *s, void *ud)
-{
- (void)s;
- (void)ud;
- printf("[YASL] Window close requested\n");
- running = false;
+ if (!yasl_frame_present_cpu(frame, NULL, 0)) {
+ fprintf(stderr, "Failed to present frame\n");
+ failed = true;
+ }
+ if (failed || smoke_test) yasl_surface_destroy(s);
}
-static void on_quit(void *ud)
+static int on_init(void *ud, struct yasl_display *display)
{
(void)ud;
- running = false;
-}
-
-int main(void)
-{
- struct yasl_display_cbs disp_cbs = { .quit = on_quit };
- struct yasl_display_spec disp_spec = {
- .app_id = "org.yasl.example",
- .cbs = &disp_cbs,
- };
- struct yasl_display *d = yasl_display_open(NULL, &disp_spec);
- if (!d) {
- fprintf(stderr, "Failed to open display\n");
- return 1;
- }
-
- struct yasl_window_cbs window_cbs = {
- .draw = on_draw,
- .configure = on_configure,
- .close = on_close,
- };
struct yasl_window_spec window_spec = {
.title = "YASL Interactive Window (Pen & Touch Enabled)",
.size = { .x = 640, .y = 480 },
- .cbs = &window_cbs,
};
- struct yasl_surface *s = yasl_surface_create_toplevel(d, &window_spec);
+ struct yasl_surface *s = yasl_surface_create_toplevel(display, &window_spec);
if (!s) {
fprintf(stderr, "Failed to create surface\n");
- yasl_display_close(d);
- return 1;
+ failed = true;
+ return YASL_APP_QUIT;
}
- yasl_surface_schedule_redraw(s);
+ yasl_surface_request_frame(s);
printf("=== YASL Demo Running ===\n");
printf(" - Stylus/Pen: full pressure (0..65535) and tilt detection\n");
@@ -145,56 +109,88 @@ int main(void)
printf(" - Space: cycle background themes\n");
printf(" - Escape: exit\n\n");
- while (running) {
- yasl_display_dispatch(d, -1);
-
- struct yasl_input_event ev;
- while (yasl_surface_input_poll(s, &ev)) {
- if (ev.kind == YASL_INPUT_KEY) {
- if (ev.key.action == YASL_KEY_PRESS) {
- if (ev.key.key == YASL_KEY_ESCAPE) { /* Escape */
- running = false;
- } else if (ev.key.key == YASL_KEY_SPACE || ev.key.key == YASL_KEY_ENTER) { /* Space or Return */
- color_theme++;
- yasl_surface_schedule_redraw(s);
- }
- }
- } else if (ev.kind == YASL_INPUT_MOUSE) {
- mouse_x = ev.mouse.pos.x;
- mouse_y = ev.mouse.pos.y;
- pen_pressure = ev.mouse.pen.pressure;
- pen_tilt_x = ev.mouse.pen.tilt_x;
- pen_tilt_y = ev.mouse.pen.tilt_y;
- pointer_device = ev.mouse.device;
-
- if (ev.mouse.kind == YASL_PTR_PRESS) {
- mouse_down = true;
- if (pen_pressure > 0 || pointer_device == YASL_PTR_DEV_PEN) {
- printf("[Pen Down] Pressure: %5u/65535 (%.1f%%) | Tilt: (%+3d°, %+3d°) | Device: %s\n",
- pen_pressure, (pen_pressure * 100.0) / 65535.0,
- pen_tilt_x, pen_tilt_y, device_name(pointer_device));
- }
- } else if (ev.mouse.kind == YASL_PTR_RELEASE) {
- mouse_down = false;
- pen_pressure = 0;
- } else if (ev.mouse.kind == YASL_PTR_MOTION && pen_pressure > 0) {
- printf("[Pen Move] Pressure: %5u/65535 (%.1f%%) | Tilt: (%+3d°, %+3d°) | Device: %s\n",
- pen_pressure, (pen_pressure * 100.0) / 65535.0,
- pen_tilt_x, pen_tilt_y, device_name(pointer_device));
- } else if (ev.mouse.kind == YASL_PTR_LEAVE) {
- mouse_x = -1;
- mouse_y = -1;
- mouse_down = false;
- pen_pressure = 0;
- }
- yasl_surface_schedule_redraw(s);
- }
- }
+ return YASL_APP_CONTINUE;
+}
- yasl_display_flush(d);
+static void pointer_sample(struct yasl_vector2i pos, enum yasl_pointer_device device,
+ struct yasl_pointer_pen pen)
+{
+ mouse_x = pos.x;
+ mouse_y = pos.y;
+ pointer_device = device;
+ pen_pressure = pen.pressure;
+ pen_tilt_x = pen.tilt_x;
+ pen_tilt_y = pen.tilt_y;
+}
+
+static int on_event(void *ud, const struct yasl_event *ev)
+{
+ (void)ud;
+ switch (ev->type) {
+ case YASL_EVENT_WINDOW_CLOSE_REQUEST:
+ return YASL_APP_QUIT;
+ case YASL_EVENT_WINDOW_RESIZE: {
+ uint32_t flags = yasl_surface_state(ev->surface);
+ const char *mode = flags & YASL_STATE_FULLSCREEN ? "fullscreen" :
+ flags & YASL_STATE_MAXIMIZED ? "maximized" :
+ flags & YASL_STATE_TILED ? "tiled" : "windowed";
+ printf("[YASL] Resized to %dx%d (scale: %.2fx, %s)\n", ev->resize.size.x,
+ ev->resize.size.y, (double)ev->resize.scale_120 / YASL_SCALE_UNIT, mode);
+ break;
}
+ case YASL_EVENT_KEY:
+ if (ev->key.action != YASL_KEY_PRESS) return YASL_APP_CONTINUE;
+ if (ev->key.key == YASL_KEY_ESCAPE) return YASL_APP_QUIT;
+ if (ev->key.key == YASL_KEY_SPACE || ev->key.key == YASL_KEY_ENTER)
+ color_theme++;
+ break;
+ case YASL_EVENT_MOUSE_BUTTON:
+ pointer_sample(ev->mouse_button.pos, ev->mouse_button.device, ev->mouse_button.pen);
+ if (ev->mouse_button.button == 0) {
+ mouse_down = ev->mouse_button.pressed;
+ if (!mouse_down) pen_pressure = 0;
+ }
+ break;
+ case YASL_EVENT_MOUSE_MOTION:
+ pointer_sample(ev->mouse_motion.pos, ev->mouse_motion.device, ev->mouse_motion.pen);
+ break;
+ case YASL_EVENT_WINDOW_FOCUS:
+ if (ev->focus.focused) return YASL_APP_CONTINUE;
+ /* Fall through to cancel held input on focus loss. */
+ [[fallthrough]];
+ case YASL_EVENT_INPUT_RESET:
+ case YASL_EVENT_MOUSE_LEAVE:
+ mouse_x = mouse_y = -1;
+ mouse_down = false;
+ pen_pressure = 0;
+ break;
+ default:
+ return YASL_APP_CONTINUE;
+ }
+ if (pen_pressure > 0) {
+ printf("[Pen] Pressure: %5u/65535 (%.1f%%) | Tilt: (%+3d°, %+3d°) | Device: %s\n",
+ pen_pressure, pen_pressure * 100.0 / 65535.0,
+ pen_tilt_x, pen_tilt_y, device_name(pointer_device));
+ }
+ yasl_surface_request_frame(ev->surface);
+ return YASL_APP_CONTINUE;
+}
- yasl_surface_destroy(s);
- yasl_display_close(d);
- return 0;
+int main(int argc, char **argv)
+{
+ if (argc == 2 && strcmp(argv[1], "--smoke-test") == 0)
+ smoke_test = true;
+ else if (argc != 1) {
+ fprintf(stderr, "Usage: %s [--smoke-test]\n", argv[0]);
+ return 1;
+ }
+ struct yasl_app_desc desc = {
+ .app_id = "org.yasl.example",
+ .init = on_init,
+ .event = on_event,
+ .frame = on_draw,
+ };
+ int result = yasl_run(&desc);
+ if (result) perror("yasl_run");
+ return result || failed ? 1 : 0;
}
diff --git a/examples/tablet.c b/examples/tablet.c
index 8a1af7e..f392cdc 100644
--- a/examples/tablet.c
+++ b/examples/tablet.c
@@ -1,5 +1,5 @@
/* Pressure-sensitive drawing using only YASL's portable API. */
-#include <yasl/yasl.h>
+#include <yasl/app.h>
#include <math.h>
#include <stdio.h>
@@ -14,10 +14,15 @@
#define HEADER 104
struct app {
- bool running, smoke_test, failed, tablet_supported;
+ bool smoke_test, failed, tablet_supported;
bool down, visible, previous;
+ struct yasl_surface *surface;
enum yasl_backend backend;
- struct yasl_mouse_event pointer;
+ struct {
+ struct yasl_vector2i pos;
+ enum yasl_pointer_device device;
+ struct yasl_pointer_pen pen;
+ } pointer;
double last_x, last_y, last_radius;
uint32_t *canvas;
struct yasl_geometry geometry;
@@ -135,31 +140,49 @@ static void stroke(struct app *app)
app->previous = true;
}
-static void input(struct app *app, const struct yasl_input_event *event)
+static int input(void *ud, const struct yasl_event *event)
{
- if (event->kind == YASL_INPUT_RESET ||
- (event->kind == YASL_INPUT_FOCUS && !event->focus.focused)) {
+ struct app *app = ud;
+ if (event->type == YASL_EVENT_WINDOW_CLOSE_REQUEST)
+ return YASL_APP_QUIT;
+ if (event->type == YASL_EVENT_WINDOW_RESIZE) {
+ if (!resize_canvas(app, (struct yasl_geometry){event->resize.size, event->resize.scale_120})) {
+ app->failed = true;
+ return YASL_APP_QUIT;
+ }
+ } else if (event->type == YASL_EVENT_INPUT_RESET || event->type == YASL_EVENT_MOUSE_LEAVE ||
+ (event->type == YASL_EVENT_WINDOW_FOCUS && !event->focus.focused)) {
cancel_stroke(app);
- } else if (event->kind == YASL_INPUT_KEY && event->key.action == YASL_KEY_PRESS) {
- if (event->key.key == YASL_KEY_ESCAPE) app->running = false;
+ } else if (event->type == YASL_EVENT_KEY && event->key.action == YASL_KEY_PRESS) {
+ if (event->key.key == YASL_KEY_ESCAPE) return YASL_APP_QUIT;
if (event->key.key == YASL_KEY_C || event->key.key == YASL_KEY_SPACE)
clear_canvas(app);
- } else if (event->kind == YASL_INPUT_MOUSE) {
- const struct yasl_mouse_event *pointer = &event->mouse;
- if (pointer->kind == YASL_PTR_SCROLL) return;
- if (app->pointer.device != pointer->device) cancel_stroke(app);
- app->pointer = *pointer;
- app->visible = pointer->kind != YASL_PTR_LEAVE;
- if (pointer->kind == YASL_PTR_LEAVE) cancel_stroke(app);
- else if (pointer->kind == YASL_PTR_PRESS && pointer->button == 0) {
+ } else if (event->type == YASL_EVENT_MOUSE_BUTTON) {
+ if (app->pointer.device != event->mouse_button.device) cancel_stroke(app);
+ app->pointer.pos = event->mouse_button.pos;
+ app->pointer.device = event->mouse_button.device;
+ app->pointer.pen = event->mouse_button.pen;
+ app->visible = true;
+ if (event->mouse_button.pressed && event->mouse_button.button == 0) {
app->down = true;
app->previous = false;
stroke(app);
- } else if (pointer->kind == YASL_PTR_RELEASE && pointer->button == 0) {
+ } else if (!event->mouse_button.pressed && event->mouse_button.button == 0) {
app->down = app->previous = false;
app->pointer.pen.pressure = 0;
- } else if (pointer->kind == YASL_PTR_MOTION && app->down) stroke(app);
+ }
+ } else if (event->type == YASL_EVENT_MOUSE_MOTION) {
+ if (app->pointer.device != event->mouse_motion.device) cancel_stroke(app);
+ app->pointer.pos = event->mouse_motion.pos;
+ app->pointer.device = event->mouse_motion.device;
+ app->pointer.pen = event->mouse_motion.pen;
+ app->visible = true;
+ if (app->down) stroke(app);
+ } else {
+ return YASL_APP_CONTINUE;
}
+ yasl_surface_request_frame(event->surface);
+ return YASL_APP_CONTINUE;
}
/* Tiny built-in 5x7 font keeps this example independent of a text library. */
@@ -214,18 +237,19 @@ static void text(struct yasl_framebuffer *fb, double dpi, int x, int y,
}
}
-static void draw(struct yasl_surface *surface, struct yasl_frame *frame, void *ud)
+static void draw(void *ud, struct yasl_surface *surface, struct yasl_frame_context *frame)
{
struct app *app = ud;
- struct yasl_geometry geometry = yasl_surface_geometry(surface);
- geometry.size = frame->target.size;
+ struct yasl_geometry geometry = {frame->size, frame->scale_120};
if (!resize_canvas(app, geometry)) {
fprintf(stderr, "Cannot allocate drawing canvas\n");
- app->running = false;
app->failed = true;
+ yasl_surface_destroy(surface);
+ app->surface = NULL;
return;
}
- struct yasl_framebuffer *fb = &frame->target;
+ struct yasl_framebuffer target = {.size = frame->size, .pixels = frame->cpu.pixels};
+ struct yasl_framebuffer *fb = &target;
memcpy(fb->pixels, app->canvas, (size_t)geometry.size.x * geometry.size.y * sizeof(uint32_t));
double dpi = (double)app->geometry.scale_120 / YASL_SCALE_UNIT;
rect(fb, 0, 0, fb->size.x, (int)lround(HEADER * dpi), PANEL);
@@ -260,41 +284,42 @@ static void draw(struct yasl_surface *surface, struct yasl_frame *frame, void *u
for (int i = 0; i <= 64; i++)
rect(fb, (int)lround(cx + tx * i / 64), (int)lround(cy + ty * i / 64), 2, 2, ACCENT);
}
- struct yasl_damage_list damage = {.n = -1};
- if (!yasl_surface_present(surface, fb, &damage)) {
+ if (!yasl_frame_present_cpu(frame, NULL, 0)) {
app->failed = true;
- app->running = false;
}
- if (app->smoke_test) app->running = false;
+ if (app->failed || app->smoke_test) {
+ yasl_surface_destroy(surface);
+ app->surface = NULL;
+ }
}
-static void configure(struct yasl_surface *surface, struct yasl_geometry geometry, void *ud)
+static int init(void *ud, struct yasl_display *display)
{
struct app *app = ud;
- if (!resize_canvas(app, geometry)) {
+ app->backend = yasl_display_backend(display);
+ app->tablet_supported = (yasl_display_caps(display) & YASL_CAP_TABLET) != 0;
+ struct yasl_window_spec spec = {
+ .title = "YASL Tablet - pressure and tilt", .size = {900, 640},
+ };
+ app->surface = yasl_surface_create_toplevel(display, &spec);
+ if (!app->surface || !resize_canvas(app, yasl_surface_geometry(app->surface))) {
+ fprintf(stderr, "Cannot create drawing surface\n");
app->failed = true;
- app->running = false;
+ return YASL_APP_QUIT;
}
- yasl_surface_schedule_redraw(surface);
-}
-
-static void close_window(struct yasl_surface *surface, void *ud)
-{
- (void)surface;
- ((struct app *)ud)->running = false;
-}
-
-static void quit(void *ud) { ((struct app *)ud)->running = false; }
-static void lost(void *ud)
-{
- struct app *app = ud;
- app->failed = true;
- app->running = false;
+ yasl_surface_set_mouse_mode(app->surface, YASL_MOUSE_HIDDEN);
+ yasl_surface_request_frame(app->surface);
+ printf("YASL tablet on %s: pressure changes brush width; eraser removes ink.\n"
+ "Mouse: fixed-width drawing. C/Space: clear. Escape: quit.\n",
+ backend_name(app->backend));
+ if (!app->tablet_supported)
+ printf("This display server does not expose tablet input.\n");
+ return YASL_APP_CONTINUE;
}
int main(int argc, char **argv)
{
- struct app app = {.running = true};
+ struct app app = {0};
enum yasl_backend backend = YASL_BACKEND_AUTO;
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--smoke-test")) app.smoke_test = true;
@@ -312,39 +337,12 @@ int main(int argc, char **argv)
return help ? 0 : 1;
}
}
- struct yasl_display_cbs display_cbs = {.quit = quit, .lost = lost};
- struct yasl_display_spec display_spec = {
- .app_id = "org.yasl.tablet", .backend = backend, .cbs = &display_cbs, .ud = &app,
+ struct yasl_app_desc desc = {
+ .app_id = "org.yasl.tablet", .backend = backend, .userdata = &app,
+ .init = init, .event = input, .frame = draw,
};
- struct yasl_display *display = yasl_display_open(NULL, &display_spec);
- if (!display) { perror("Cannot open display"); return 1; }
- app.backend = yasl_display_backend(display);
- app.tablet_supported = (yasl_display_caps(display) & YASL_CAP_TABLET) != 0;
- struct yasl_window_cbs callbacks = {.draw = draw, .configure = configure, .close = close_window};
- struct yasl_window_spec spec = {
- .title = "YASL Tablet - pressure and tilt", .size = {900, 640},
- .cbs = &callbacks, .ud = &app,
- };
- struct yasl_surface *surface = yasl_surface_create_toplevel(display, &spec);
- if (!surface) { perror("Cannot create surface"); yasl_display_close(display); return 1; }
- yasl_surface_set_cursor(surface, YASL_CURSOR_HIDDEN);
- yasl_surface_schedule_redraw(surface);
- printf("YASL tablet on %s: pressure changes brush width; eraser removes ink.\n"
- "Mouse: fixed-width drawing. C/Space: clear. Escape: quit.\n",
- backend_name(app.backend));
- if (!app.tablet_supported)
- printf("This display server does not expose tablet input.\n");
- while (app.running) {
- if (yasl_display_dispatch(display, -1) < 0) { app.failed = true; break; }
- struct yasl_input_event event;
- while (yasl_surface_input_poll(surface, &event)) {
- input(&app, &event);
- yasl_surface_schedule_redraw(surface);
- }
- yasl_display_flush(display);
- }
+ int result = yasl_run(&desc);
+ if (result) perror("yasl_run");
free(app.canvas);
- yasl_surface_destroy(surface);
- yasl_display_close(display);
- return app.failed ? 1 : 0;
+ return result || app.failed ? 1 : 0;
}
diff --git a/include/yasl/app.h b/include/yasl/app.h
index 27523d5..112140d 100644
--- a/include/yasl/app.h
+++ b/include/yasl/app.h
@@ -70,6 +70,7 @@ typedef enum yasl_event_type {
YASL_EVENT_PASTE,
YASL_EVENT_DROP_FILES,
YASL_EVENT_USER,
+ YASL_EVENT_INPUT_RESET, /**< Input interrupted; clear held keys/buttons and composition. */
} yasl_event_type;
/** Unified event payload delivered to yasl_app_desc.event(). */
@@ -95,6 +96,7 @@ struct yasl_event {
struct {
struct yasl_vector2i pos; /**< Device-pixel position relative to surface. */
+ enum yasl_pointer_device device; /**< Mouse, touch, pen, or eraser. */
int16_t button; /**< 0 primary, 1 middle, 2 secondary. */
bool pressed; /**< true for press, false for release. */
uint32_t mods;
@@ -104,6 +106,7 @@ struct yasl_event {
struct {
struct yasl_vector2i pos; /**< Device-pixel position relative to surface. */
struct yasl_vector2i delta; /**< Relative motion delta (dx, dy). */
+ enum yasl_pointer_device device; /**< Mouse, touch, pen, or eraser. */
uint32_t mods;
struct yasl_pointer_pen pen;
} mouse_motion;
@@ -271,6 +274,7 @@ struct yasl_app_desc {
/**
* Unified event handler called for input, resize, close, scale, and custom events.
* Event payloads and borrowed strings are valid for the duration of the callback.
+ * INPUT_RESET has no payload and requires clearing held application input.
* Return YASL_APP_CONTINUE or YASL_APP_QUIT.
*/
int (*event)(void *ud, const struct yasl_event *ev);
@@ -293,6 +297,8 @@ struct yasl_app_desc {
* Handles the display event loop, live-resizing, pacing, and cleanup.
* @param desc Application callbacks and options.
* @return 0 on orderly exit, or non-zero on error.
+ * Display quit notifications stop the runner; connection loss and dispatch
+ * failures return an error after application cleanup.
*/
YASL_API int yasl_run(const struct yasl_app_desc *desc);
diff --git a/include/yasl/yasl.h b/include/yasl/yasl.h
index e2c1d77..3b3b712 100644
--- a/include/yasl/yasl.h
+++ b/include/yasl/yasl.h
@@ -153,6 +153,7 @@ enum yasl_surface_state : uint32_t {
YASL_STATE_ACTIVATED = 1 << 2, /**< Active window. */
YASL_STATE_SUSPENDED = 1 << 3, /**< Compositor reports suspension. */
YASL_STATE_CSD = 1 << 4, /**< Backend reports client-side decorations. */
+ YASL_STATE_TILED = 1 << 5, /**< At least one edge is tiled; currently reported by Wayland. */
};
/** System cursor shapes. HIDDEN preserves pointer input; NONE is its compatibility alias. */
@@ -448,7 +449,7 @@ struct yasl_window_cbs {
void (*configure)(struct yasl_surface *s, struct yasl_geometry geometry, void *ud);
/** Window/popup close request; application is responsible for destruction. */
void (*close)(struct yasl_surface *s, void *ud);
- /** Reported yasl_surface_state bitmask changed. */
+ /** Reported yasl_surface_state bitmask changed; matches yasl_surface_state(s). */
void (*state)(struct yasl_surface *s, uint32_t state, void *ud);
/** YASL surface-drag target hover, leave, or drop; pos is in device pixels. */
void (*drag)(struct yasl_surface *s, enum yasl_drag_event ev,
@@ -746,6 +747,8 @@ YASL_API struct yasl_geometry yasl_surface_geometry(const struct yasl_surface *s
* @param s Surface, or NULL.
* @return A bitmask of yasl_surface_state values, or zero for NULL.
* Flags are reported state, not requests; unsupported states are absent.
+ * Flags may coexist. Windowed and floating have no separate flags. Suspension
+ * does not imply minimization, and no minimized flag is reported.
*/
YASL_API uint32_t yasl_surface_state(const struct yasl_surface *s);
/* Timing uses monotonic nanoseconds. Check source before treating timestamps
diff --git a/meson.build b/meson.build
index b0ea8e8..8e396d8 100644
--- a/meson.build
+++ b/meson.build
@@ -88,35 +88,6 @@ yasl_lib = both_libraries(
install: true,
)
-install_headers(
- 'include/yasl/yasl.h',
- 'include/yasl/app.h',
- 'include/yasl/timing.h',
- 'include/yasl/keycodes.h',
- 'include/yasl/loop.h',
- 'include/yasl/native.h',
- subdir: 'yasl',
-)
-
-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
-
-pkg = import('pkgconfig')
-pkg.generate(
- yasl_lib.get_shared_lib(),
- name: 'yasl',
- description: 'Yet Another Surface Library - minimal, modern desktop window surface abstraction',
- version: meson.project_version(),
- subdirs: '.',
-)
-
yasl_dep = declare_dependency(
link_with: yasl_lib.get_static_lib(),
include_directories: include_directories('include'),
@@ -131,7 +102,7 @@ hello_example = executable(
link_with: yasl_lib.get_shared_lib(),
)
-executable(
+interactive_example = executable(
'yasl-example',
'examples/simple_window.c',
include_directories: inc,
@@ -158,11 +129,3 @@ game_example = executable(
)
subdir('tests')
-
-doc_dir = get_option('datadir') / 'doc' / 'yasl'
-install_data('README.md', 'CONTRIBUTING.md', 'IDE_INTEGRATION.md', 'Doxyfile', 'LICENSE', install_dir: doc_dir)
-# Preserve source-relative links and keep the reference buildable offline.
-install_subdir('docs', install_dir: doc_dir)
-install_subdir('examples', install_dir: doc_dir)
-install_subdir('include', install_dir: doc_dir)
-install_data('LICENSE', install_dir: get_option('datadir') / 'licenses' / 'yasl')
diff --git a/src/common/app.c b/src/common/app.c
index da9747d..e1148f9 100644
--- a/src/common/app.c
+++ b/src/common/app.c
@@ -35,6 +35,7 @@ struct app_runner {
struct yasl_display *display;
const struct yasl_app_desc *desc;
bool running;
+ bool failed;
bool had_surfaces;
int surface_count;
struct app_surface *surfaces;
@@ -380,6 +381,7 @@ static void app_runner_dispatch_input_events(struct app_runner *ar)
if (ie.mouse.kind == YASL_PTR_PRESS || ie.mouse.kind == YASL_PTR_RELEASE) {
ev.type = YASL_EVENT_MOUSE_BUTTON;
ev.mouse_button.pos = ie.mouse.pos;
+ ev.mouse_button.device = ie.mouse.device;
ev.mouse_button.button = ie.mouse.button;
ev.mouse_button.pressed = (ie.mouse.kind == YASL_PTR_PRESS);
ev.mouse_button.mods = ie.mouse.mods;
@@ -391,6 +393,7 @@ static void app_runner_dispatch_input_events(struct app_runner *ar)
} else if (ie.mouse.kind == YASL_PTR_MOTION) {
ev.type = YASL_EVENT_MOUSE_MOTION;
ev.mouse_motion.pos = ie.mouse.pos;
+ ev.mouse_motion.device = ie.mouse.device;
if (as->has_last_mouse_pos) {
ev.mouse_motion.delta.x = ie.mouse.pos.x - as->last_mouse_pos.x;
ev.mouse_motion.delta.y = ie.mouse.pos.y - as->last_mouse_pos.y;
@@ -439,6 +442,9 @@ static void app_runner_dispatch_input_events(struct app_runner *ar)
break;
case YASL_INPUT_RESET:
as->has_last_mouse_pos = false;
+ ev.type = YASL_EVENT_INPUT_RESET;
+ if (ar->desc->event(ar->desc->userdata, &ev) == YASL_APP_QUIT)
+ ar->running = false;
break;
default:
break;
@@ -447,27 +453,38 @@ static void app_runner_dispatch_input_events(struct app_runner *ar)
}
}
+static void app_runner_quit(void *ud)
+{
+ ((struct app_runner *)ud)->running = false;
+}
+
+static void app_runner_lost(void *ud)
+{
+ struct app_runner *runner = ud;
+ runner->failed = true;
+ runner->running = false;
+}
+
int yasl_run(const struct yasl_app_desc *desc)
{
if (!desc || !desc->init)
return -1;
+ struct app_runner runner = {.desc = desc, .running = true};
+ const struct yasl_display_cbs callbacks = {
+ .quit = app_runner_quit, .lost = app_runner_lost,
+ };
struct yasl_display_spec dspec = {
.app_id = desc->app_id,
.backend = desc->backend,
+ .cbs = &callbacks,
+ .ud = &runner,
};
struct yasl_display *d = yasl_display_open(NULL, &dspec);
if (!d)
return -1;
- struct app_runner runner = {
- .display = d,
- .desc = desc,
- .running = true,
- .had_surfaces = false,
- .surface_count = 0,
- .surfaces = NULL,
- };
+ runner.display = d;
int sync_error = yasl_mutex_init(&runner.user_event_mutex);
if (sync_error) {
yasl_display_close(d);
@@ -503,6 +520,7 @@ int yasl_run(const struct yasl_app_desc *desc)
int res = yasl_display_dispatch(d, timeout_ms);
if (res < 0) {
+ runner.failed = true;
break;
}
@@ -546,5 +564,5 @@ int yasl_run(const struct yasl_app_desc *desc)
yasl_mutex_destroy(&runner.user_event_mutex);
yasl_display_close(d);
- return 0;
+ return runner.failed ? -1 : 0;
}
diff --git a/src/common/app_internal.h b/src/common/app_internal.h
index 64fa34c..143df41 100644
--- a/src/common/app_internal.h
+++ b/src/common/app_internal.h
@@ -1,8 +1,7 @@
#ifndef YASL_APP_INTERNAL_H
#define YASL_APP_INTERNAL_H
-#include "yasl/app.h"
-#include "common/backend.h"
+struct yasl_surface;
void app_runner_register_surface(void *runner, struct yasl_surface *s);
void app_runner_unregister_surface(void *runner, struct yasl_surface *s);
diff --git a/src/common/backend.c b/src/common/backend.c
index be06136..3f2143d 100644
--- a/src/common/backend.c
+++ b/src/common/backend.c
@@ -328,9 +328,7 @@ struct yasl_geometry yasl_surface_geometry(const struct yasl_surface *s)
uint32_t yasl_surface_state(const struct yasl_surface *s)
{
- if (!s || !s->display->ops->surface_state)
- return 0;
- return s->display->ops->surface_state(s);
+ return s ? s->state : 0;
}
struct yasl_timing yasl_surface_timing(const struct yasl_surface *s)
diff --git a/src/common/backend.h b/src/common/backend.h
index 37fa167..17d781e 100644
--- a/src/common/backend.h
+++ b/src/common/backend.h
@@ -22,7 +22,7 @@ struct yasl_surface {
void *native_handle;
struct yasl_vector2i size;
uint32_t scale_120;
- uint32_t state;
+ uint32_t state; /* Authoritative YASL_STATE_* bitmask, maintained by the backend. */
uint32_t bg_color;
struct yasl_window_cbs callbacks;
void *ud;
@@ -72,7 +72,6 @@ struct yasl_backend_ops {
bool (*surface_present)(struct yasl_surface *s, const struct yasl_framebuffer *fb, const struct yasl_damage_list *damage);
bool (*surface_input_poll)(struct yasl_surface *s, struct yasl_input_event *ev);
struct yasl_geometry (*surface_geometry)(const struct yasl_surface *s);
- uint32_t (*surface_state)(const struct yasl_surface *s);
struct yasl_timing (*surface_timing)(const struct yasl_surface *s);
uint64_t (*surface_frame_time_ns)(struct yasl_surface *s, uint64_t now);
void (*surface_set_cursor)(struct yasl_surface *s, enum yasl_cursor cursor);
diff --git a/src/common/keyboard.h b/src/common/keyboard.h
index 77c6380..d8b8bbc 100644
--- a/src/common/keyboard.h
+++ b/src/common/keyboard.h
@@ -1,7 +1,7 @@
#ifndef YASL_COMMON_KEYBOARD_H
#define YASL_COMMON_KEYBOARD_H
-#include "yasl/yasl.h"
+#include <stdint.h>
/* Layout labels omit controls, invalid Unicode, and Cocoa function-key PUA. */
static inline uint32_t yasl_key_label(uint32_t cp)
diff --git a/src/platform/cocoa/api.h b/src/platform/cocoa/api.h
index 89b71f3..0f705dd 100644
--- a/src/platform/cocoa/api.h
+++ b/src/platform/cocoa/api.h
@@ -1,7 +1,7 @@
#ifndef YASL_COCOA_API_H
#define YASL_COCOA_API_H
-#include "common/backend.h"
+#include "yasl/yasl.h"
struct cocoa_display;
struct cocoa_surface;
diff --git a/src/platform/cocoa/keymap.h b/src/platform/cocoa/keymap.h
index 1aba70b..a5394e2 100644
--- a/src/platform/cocoa/keymap.h
+++ b/src/platform/cocoa/keymap.h
@@ -1,7 +1,7 @@
#ifndef YASL_COCOA_KEYMAP_H
#define YASL_COCOA_KEYMAP_H
-#include "yasl/yasl.h"
+#include "yasl/keycodes.h"
enum yasl_key yasl_key_from_cocoa(uint32_t code);
diff --git a/src/platform/cocoa/window.m b/src/platform/cocoa/window.m
index dc6175d..aa9fa48 100644
--- a/src/platform/cocoa/window.m
+++ b/src/platform/cocoa/window.m
@@ -3,6 +3,7 @@
#import <QuartzCore/QuartzCore.h>
#include "platform/cocoa/api.h"
+#include "common/backend.h"
#include "yasl/native/cocoa.h"
#include "common/timing.h"
#include "common/loop.h"
@@ -93,7 +94,6 @@ struct cocoa_surface {
struct input_queue input;
struct yasl_vector2i pointer;
double scroll_x;
- uint32_t state;
uint32_t mods;
uint32_t buttons;
enum yasl_cursor cursor;
@@ -180,13 +180,13 @@ static void set_state(struct cocoa_surface *w, uint32_t bit, bool on)
{
if (!w || w->closed)
return;
- uint32_t next = on ? (w->state | bit) : (w->state & ~bit);
- if (next == w->state)
+ uint32_t next = on ? (w->base.state | bit) : (w->base.state & ~bit);
+ if (next == w->base.state)
return;
- w->state = next;
+ w->base.state = next;
darwin_ctx.depth++;
if (w->cbs->state)
- w->cbs->state(&w->base, w->state, w->ud);
+ w->cbs->state(&w->base, next, w->ud);
darwin_ctx.depth--;
}
@@ -713,7 +713,7 @@ static struct yasl_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> send
if (period > 0.0 && period < 0.25)
w->clock.refresh_ns = period * 1e9;
w->frame_due = true;
- if (!w->want_draw || (w->state & YASL_STATE_SUSPENDED)) {
+ if (!w->want_draw || (w->base.state & YASL_STATE_SUSPENDED)) {
if (++w->idle_frames >= LINK_IDLE_FRAMES)
link.paused = YES;
return;
@@ -1069,7 +1069,7 @@ bool cocoa_surface_present(struct cocoa_surface *w, const struct yasl_framebuffe
static void render_and_commit(struct cocoa_surface *w)
{
if (w->closed || !w->cbs->draw || w->rendering || w->size.x <= 0 || w->size.y <= 0 ||
- (w->state & YASL_STATE_SUSPENDED))
+ (w->base.state & YASL_STATE_SUSPENDED))
return;
size_t count = (size_t)w->size.x * (size_t)w->size.y;
@@ -2042,12 +2042,6 @@ static struct yasl_geometry impl_surface_geometry(const struct yasl_surface *s)
return cocoa_surface_geometry((const struct cocoa_surface *)s);
}
-static uint32_t impl_surface_state(const struct yasl_surface *s)
-{
- const struct cocoa_surface *w = (const struct cocoa_surface *)s;
- return w ? w->state : 0;
-}
-
static struct yasl_timing impl_surface_timing(const struct yasl_surface *s)
{
return cocoa_surface_timing((const struct cocoa_surface *)s);
@@ -2156,7 +2150,6 @@ const struct yasl_backend_ops yasl_cocoa_backend = {
.surface_present = impl_surface_present,
.surface_input_poll = impl_surface_input_poll,
.surface_geometry = impl_surface_geometry,
- .surface_state = impl_surface_state,
.surface_timing = impl_surface_timing,
.surface_frame_time_ns = impl_surface_frame_time_ns,
.surface_set_cursor = impl_surface_set_cursor,
diff --git a/src/platform/sync.h b/src/platform/sync.h
index 1dcf28f..43b4a34 100644
--- a/src/platform/sync.h
+++ b/src/platform/sync.h
@@ -5,6 +5,6 @@
* yasl_mutex_init/destroy/lock/unlock operations. Init returns zero on success
* or an errno value on failure. The other operations require an initialized
* mutex. Native types and calls stay in the selected implementation header. */
-#include "yasl_platform_sync.h"
+#include "yasl_platform_sync.h" // IWYU pragma: export
#endif
diff --git a/src/platform/wayland/api.h b/src/platform/wayland/api.h
index 04a6bf1..bbd20df 100644
--- a/src/platform/wayland/api.h
+++ b/src/platform/wayland/api.h
@@ -1,8 +1,7 @@
#ifndef YASL_WAYLAND_API_H
#define YASL_WAYLAND_API_H
-#include "common/backend.h"
-#include "yasl/native/wayland.h"
+#include "yasl/yasl.h"
struct wayland_display;
struct wayland_surface;
@@ -28,7 +27,6 @@ void wayland_surface_schedule_redraw(struct wayland_surface *s);
bool wayland_surface_present(struct wayland_surface *s, const struct yasl_framebuffer *fb, const struct yasl_damage_list *damage);
bool wayland_surface_input_poll(struct wayland_surface *s, struct yasl_input_event *ev);
struct yasl_geometry wayland_surface_geometry(const struct wayland_surface *s);
-uint32_t wayland_surface_state(const struct wayland_surface *s);
struct yasl_timing wayland_surface_timing(const struct wayland_surface *s);
void wayland_surface_set_cursor(struct wayland_surface *s, enum yasl_cursor cursor);
void wayland_surface_set_background_color(struct wayland_surface *s, uint32_t bg);
diff --git a/src/platform/wayland/backend.c b/src/platform/wayland/backend.c
index 3eada38..cc96934 100644
--- a/src/platform/wayland/backend.c
+++ b/src/platform/wayland/backend.c
@@ -111,11 +111,6 @@ static struct yasl_geometry impl_surface_geometry(const struct yasl_surface *s)
return wayland_surface_geometry((const struct wayland_surface *)s);
}
-static uint32_t impl_surface_state(const struct yasl_surface *s)
-{
- return wayland_surface_state((const struct wayland_surface *)s);
-}
-
static struct yasl_timing impl_surface_timing(const struct yasl_surface *s)
{
return wayland_surface_timing((const struct wayland_surface *)s);
@@ -212,7 +207,6 @@ const struct yasl_backend_ops yasl_wayland_backend = {
.surface_present = impl_surface_present,
.surface_input_poll = impl_surface_input_poll,
.surface_geometry = impl_surface_geometry,
- .surface_state = impl_surface_state,
.surface_timing = impl_surface_timing,
.surface_frame_time_ns = impl_surface_frame_time_ns,
.surface_set_cursor = impl_surface_set_cursor,
diff --git a/src/platform/wayland/buffer.c b/src/platform/wayland/buffer.c
index f4243d1..f5a2c0f 100644
--- a/src/platform/wayland/buffer.c
+++ b/src/platform/wayland/buffer.c
@@ -1,4 +1,6 @@
#include "internal.h"
+#include "common/utf8.h"
+#include <errno.h>
#include <sys/resource.h>
static long minor_faults(void)
@@ -134,7 +136,8 @@ int apply_pending_geometry(struct wayland_surface *w)
w->size_pending = false;
return 0;
}
- bool floating = !w->parent && !w->maximized && !w->fullscreen && !w->tiled;
+ bool floating = !w->parent && !(w->base.state &
+ (YASL_STATE_MAXIMIZED | YASL_STATE_FULLSCREEN | YASL_STATE_TILED));
int free_w = floating && w->floating_width > 0 ? w->floating_width : w->logical_width;
int free_h = floating && w->floating_height > 0 ? w->floating_height : w->logical_height;
int logical_w = !w->parent && w->req_width > 0 ? w->req_width : free_w;
@@ -143,7 +146,7 @@ int apply_pending_geometry(struct wayland_surface *w)
if (logical_w < YASL_MIN_LOGICAL_PX) logical_w = YASL_MIN_LOGICAL_PX;
if (logical_h < YASL_MIN_LOGICAL_PX) logical_h = YASL_MIN_LOGICAL_PX;
}
- if (!w->fullscreen) {
+ if (!(w->base.state & YASL_STATE_FULLSCREEN)) {
if (w->bound_width > 0 && logical_w > w->bound_width)
logical_w = w->bound_width;
if (w->bound_height > 0 && logical_h > w->bound_height)
@@ -539,16 +542,6 @@ void wayland_surface_set_title(struct wayland_surface *w, const char *title)
free(copy);
}
-uint32_t wayland_surface_state(const struct wayland_surface *w)
-{
- if (unlikely(!w)) return 0;
- return ((uint32_t)w->fullscreen * YASL_STATE_FULLSCREEN) |
- ((uint32_t)w->maximized * YASL_STATE_MAXIMIZED) |
- ((uint32_t)w->activated * YASL_STATE_ACTIVATED) |
- ((uint32_t)w->suspended * YASL_STATE_SUSPENDED) |
- ((uint32_t)!w->server_decorated * YASL_STATE_CSD);
-}
-
struct yasl_timing wayland_surface_timing(const struct wayland_surface *w)
{
struct yasl_timing t = {
diff --git a/src/platform/wayland/clipboard.c b/src/platform/wayland/clipboard.c
index 24dee97..353e993 100644
--- a/src/platform/wayland/clipboard.c
+++ b/src/platform/wayland/clipboard.c
@@ -1,6 +1,8 @@
#define _GNU_SOURCE
#include "internal.h"
#include "clipboard.h"
+#include "common/utf8.h"
+#include <errno.h>
#define CLIPBOARD_TEXT_MAX (256u * 1024)
diff --git a/src/platform/wayland/internal.h b/src/platform/wayland/internal.h
index f777674..d619dba 100644
--- a/src/platform/wayland/internal.h
+++ b/src/platform/wayland/internal.h
@@ -4,8 +4,8 @@
#define _GNU_SOURCE
#include "api.h"
+#include "common/backend.h"
#include "common/timing.h"
-#include "common/utf8.h"
#include "clipboard.h"
#include <wayland-client.h>
@@ -24,7 +24,6 @@
#include "protocols/xdg-toplevel-drag-v1-client-protocol.h"
#include "protocols/primary-selection-unstable-v1-client-protocol.h"
-#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
@@ -307,10 +306,8 @@ struct wayland_surface {
uint32_t pending_serial;
/* 1-byte booleans packed tightly */
- bool server_decorated;
bool frame_active;
bool frame_presented;
- bool tiled;
bool size_pending;
bool sample_refresh;
bool has_pending_serial;
@@ -318,10 +315,6 @@ struct wayland_surface {
bool closed;
bool destroyed;
bool opaque;
- bool fullscreen;
- bool maximized;
- bool activated;
- bool suspended;
bool want_draw;
};
diff --git a/src/platform/wayland/keyboard.c b/src/platform/wayland/keyboard.c
index 0913079..ebd0fc7 100644
--- a/src/platform/wayland/keyboard.c
+++ b/src/platform/wayland/keyboard.c
@@ -1,6 +1,7 @@
#include "internal.h"
#include "common/keyboard.h"
#include "keymap.h"
+#include <errno.h>
#define XKB_EVDEV_OFFSET 8
#define KEYMAP_MAX_BYTES (8u * 1024 * 1024)
diff --git a/src/platform/wayland/keymap.h b/src/platform/wayland/keymap.h
index 26a5cd6..c0ff81e 100644
--- a/src/platform/wayland/keymap.h
+++ b/src/platform/wayland/keymap.h
@@ -1,7 +1,7 @@
#ifndef YASL_WAYLAND_KEYMAP_H
#define YASL_WAYLAND_KEYMAP_H
-#include "yasl/yasl.h"
+#include "yasl/keycodes.h"
enum yasl_key yasl_key_from_evdev(uint32_t code);
diff --git a/src/platform/wayland/popup.c b/src/platform/wayland/popup.c
index fd415c9..cb578d5 100644
--- a/src/platform/wayland/popup.c
+++ b/src/platform/wayland/popup.c
@@ -137,6 +137,7 @@ struct wayland_surface *wayland_surface_create_popup(struct wayland_surface *par
if (!w)
return NULL;
w->base.display = &c->base;
+ w->base.state = YASL_STATE_CSD;
w->conn = c;
w->conn_next = c->surfaces;
c->surfaces = w;
diff --git a/src/platform/wayland/shell.c b/src/platform/wayland/shell.c
index 4c1caad..92c89d1 100644
--- a/src/platform/wayland/shell.c
+++ b/src/platform/wayland/shell.c
@@ -265,10 +265,12 @@ static const struct xdg_wm_base_listener wm_base_listener = {
.ping = wm_base_ping,
};
-static void notify_state(struct wayland_surface *w)
+static void set_state(struct wayland_surface *w, uint32_t state)
{
+ if (state == w->base.state) return;
+ w->base.state = state;
if (w->cbs && w->cbs->state)
- w->cbs->state(&w->base, wayland_surface_state(w), w->ud);
+ w->cbs->state(&w->base, state, w->ud);
}
static void toplevel_configure(void *data, struct xdg_toplevel *toplevel,
@@ -277,35 +279,29 @@ static void toplevel_configure(void *data, struct xdg_toplevel *toplevel,
(void)toplevel;
struct wayland_surface *w = data;
- uint32_t state_mask = 0;
+ uint32_t state = w->base.state & ~(YASL_STATE_FULLSCREEN |
+ YASL_STATE_MAXIMIZED | YASL_STATE_ACTIVATED |
+ YASL_STATE_SUSPENDED | YASL_STATE_TILED);
uint32_t *s;
wl_array_for_each(s, states) {
- if (*s < 32)
- state_mask |= (1u << *s);
+ switch (*s) {
+ case XDG_TOPLEVEL_STATE_FULLSCREEN: state |= YASL_STATE_FULLSCREEN; break;
+ case XDG_TOPLEVEL_STATE_MAXIMIZED: state |= YASL_STATE_MAXIMIZED; break;
+ case XDG_TOPLEVEL_STATE_ACTIVATED: state |= YASL_STATE_ACTIVATED; break;
+ case XDG_TOPLEVEL_STATE_SUSPENDED: state |= YASL_STATE_SUSPENDED; break;
+ case XDG_TOPLEVEL_STATE_TILED_LEFT:
+ case XDG_TOPLEVEL_STATE_TILED_RIGHT:
+ case XDG_TOPLEVEL_STATE_TILED_TOP:
+ case XDG_TOPLEVEL_STATE_TILED_BOTTOM:
+ state |= YASL_STATE_TILED;
+ break;
+ default: break;
+ }
}
- bool max = (state_mask & (1u << XDG_TOPLEVEL_STATE_MAXIMIZED)) != 0;
- bool fs = (state_mask & (1u << XDG_TOPLEVEL_STATE_FULLSCREEN)) != 0;
- bool act = (state_mask & (1u << XDG_TOPLEVEL_STATE_ACTIVATED)) != 0;
- bool susp = (state_mask & (1u << XDG_TOPLEVEL_STATE_SUSPENDED)) != 0;
- bool tiled = (state_mask & (
- (1u << XDG_TOPLEVEL_STATE_TILED_LEFT) |
- (1u << XDG_TOPLEVEL_STATE_TILED_RIGHT) |
- (1u << XDG_TOPLEVEL_STATE_TILED_TOP) |
- (1u << XDG_TOPLEVEL_STATE_TILED_BOTTOM))) != 0;
-
- if ((fs ^ w->fullscreen) | (max ^ w->maximized) |
- (act ^ w->activated) | (susp ^ w->suspended)) {
- w->fullscreen = fs;
- w->maximized = max;
- w->activated = act;
- w->suspended = susp;
- notify_state(w);
- }
-
- w->tiled = tiled;
w->req_width = width;
w->req_height = height;
+ set_state(w, state);
}
static void toplevel_close(void *data, struct xdg_toplevel *toplevel)
@@ -363,10 +359,12 @@ static void toplevel_decoration_configure(void *data,
{
(void)toplevel_decor;
struct wayland_surface *w = data;
- bool was_server = w->server_decorated;
- w->server_decorated = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
- if (was_server != w->server_decorated)
- notify_state(w);
+ uint32_t state = w->base.state;
+ if (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE)
+ state &= ~YASL_STATE_CSD;
+ else
+ state |= YASL_STATE_CSD;
+ set_state(w, state);
}
static const struct zxdg_toplevel_decoration_v1_listener toplevel_decoration_listener = {
diff --git a/src/platform/wayland/surface.c b/src/platform/wayland/surface.c
index 7daa679..ad3a24c 100644
--- a/src/platform/wayland/surface.c
+++ b/src/platform/wayland/surface.c
@@ -1,5 +1,6 @@
#include "internal.h"
#include <assert.h>
+#include <errno.h>
static void display_free(struct wayland_display *c);
@@ -335,6 +336,7 @@ struct wayland_surface *wayland_surface_create_toplevel(struct wayland_display *
if (!w) return NULL;
c->depth++;
conn_link(c, w);
+ w->base.state = YASL_STATE_CSD;
wayland_surface_set_callbacks(w, NULL, NULL);
w->cursor = YASL_CURSOR_TEXT;
w->req_width = width;
diff --git a/src/platform/x11/keymap.h b/src/platform/x11/keymap.h
index 2ec4295..8114fb5 100644
--- a/src/platform/x11/keymap.h
+++ b/src/platform/x11/keymap.h
@@ -1,7 +1,7 @@
#ifndef YASL_X11_KEYMAP_H
#define YASL_X11_KEYMAP_H
-#include "yasl/yasl.h"
+#include "yasl/keycodes.h"
enum yasl_key yasl_key_from_xkb_name(const char name[4]);
diff --git a/src/platform/x11/window.c b/src/platform/x11/window.c
index 347b4c2..3bab6e2 100644
--- a/src/platform/x11/window.c
+++ b/src/platform/x11/window.c
@@ -58,7 +58,6 @@ struct x11_surface {
struct yasl_frame frame;
struct yasl_damage_list damage;
struct yasl_frame_clock clock;
- uint32_t state;
bool want_draw, drawing, presented, destroyed;
struct x11_surface *parent;
bool is_popup;
@@ -700,7 +699,7 @@ static struct yasl_surface *create_toplevel(struct yasl_display *base,
if (spec->fullscreen) {
XChangeProperty(d->native, s->window, d->net_state, XA_ATOM, 32,
PropModeReplace, (unsigned char *)&d->fullscreen, 1);
- s->state |= YASL_STATE_FULLSCREEN;
+ s->base.state |= YASL_STATE_FULLSCREEN;
}
XMapWindow(d->native, s->window);
XFlush(d->native);
@@ -893,7 +892,7 @@ static void update_state(struct x11_surface *s)
int format;
unsigned long count, left;
unsigned char *data = NULL;
- uint32_t state = s->state & YASL_STATE_ACTIVATED;
+ uint32_t state = s->base.state & ~(YASL_STATE_FULLSCREEN | YASL_STATE_MAXIMIZED);
if (XGetWindowProperty(d->native, s->window, d->net_state, 0, 64, False,
XA_ATOM, &actual, &format, &count, &left, &data) == Success && format == 32) {
Atom *atoms = (Atom *)data;
@@ -906,8 +905,8 @@ static void update_state(struct x11_surface *s)
if (h && v) state |= YASL_STATE_MAXIMIZED;
}
if (data) XFree(data);
- if (state == s->state) return;
- s->state = state;
+ if (state == s->base.state) return;
+ s->base.state = state;
if (s->cbs && s->cbs->state) s->cbs->state(&s->base, state, s->ud);
}
@@ -1335,11 +1334,11 @@ static void dispatch(struct yasl_display *base)
case FocusIn: case FocusOut: {
bool focused = event.type == FocusIn;
if (s->ic) { if (focused) XSetICFocus(s->ic); else XUnsetICFocus(s->ic); }
- if (focused) s->state |= YASL_STATE_ACTIVATED;
- else s->state &= ~YASL_STATE_ACTIVATED;
+ if (focused) s->base.state |= YASL_STATE_ACTIVATED;
+ else s->base.state &= ~YASL_STATE_ACTIVATED;
push(s, (struct yasl_input_event){
.kind = YASL_INPUT_FOCUS, .focus.focused = focused}, NULL, 0);
- if (s->cbs && s->cbs->state) s->cbs->state(&s->base, s->state, s->ud);
+ if (s->cbs && s->cbs->state) s->cbs->state(&s->base, s->base.state, s->ud);
break;
}
case PropertyNotify:
@@ -1746,7 +1745,6 @@ static struct yasl_geometry geometry(const struct yasl_surface *s)
{
return (struct yasl_geometry){((const struct x11_surface *)s)->size, YASL_SCALE_UNIT};
}
-static uint32_t state(const struct yasl_surface *s) { return ((const struct x11_surface *)s)->state; }
static int mods(const struct yasl_display *d) { return (int)((const struct x11_display *)d)->mods; }
static void *native_display(struct yasl_display *d) { return ((struct x11_display *)d)->native; }
static void *native_surface(struct yasl_surface *s) { return (void *)(uintptr_t)((struct x11_surface *)s)->window; }
@@ -2042,7 +2040,6 @@ const struct yasl_backend_ops yasl_x11_backend = {
.surface_present = present,
.surface_input_poll = input_poll,
.surface_geometry = geometry,
- .surface_state = state,
.surface_timing = timing,
.surface_frame_time_ns = frame_time,
.surface_set_cursor = cursor,
diff --git a/tests/benchmark.c b/tests/benchmark.c
index 8cea45c..7431715 100644
--- a/tests/benchmark.c
+++ b/tests/benchmark.c
@@ -10,7 +10,6 @@
#include "yasl/yasl.h"
#include "yasl/loop.h"
#include "yasl/timing.h"
-#include "yasl/native.h"
#include "common/input.h"
#if YASL_HAS_WAYLAND
#include "yasl/native/wayland.h"
diff --git a/tests/common/test_contracts.c b/tests/common/test_contracts.c
index ac0065c..ee6fe61 100644
--- a/tests/common/test_contracts.c
+++ b/tests/common/test_contracts.c
@@ -25,6 +25,14 @@ static void test_timing_clock(void)
assert(stats.max_us == 500.0);
}
+static void test_surface_state(void)
+{
+ /* Reading reported flags does not require a backend query or display. */
+ struct yasl_surface surface = {.state = YASL_STATE_FULLSCREEN | YASL_STATE_ACTIVATED};
+ assert(!yasl_surface_state(NULL));
+ assert(yasl_surface_state(&surface) == (YASL_STATE_FULLSCREEN | YASL_STATE_ACTIVATED));
+}
+
static void test_queue(void)
{
struct input_queue q = {0};
@@ -326,6 +334,7 @@ static void test_errors(void)
int main(void)
{
test_timing_clock();
+ test_surface_state();
test_queue();
test_validation();
test_native();
diff --git a/tests/common/test_hello_example.c b/tests/common/test_hello_example.c
new file mode 100644
index 0000000..6e0318f
--- /dev/null
+++ b/tests/common/test_hello_example.c
@@ -0,0 +1,20 @@
+/* Exercise hello's real frame callback; automated exit belongs in the test. */
+#define main hello_example_main
+#include "../../examples/hello.c"
+#undef main
+
+static void draw_once(void *ud, struct yasl_surface *surface, struct yasl_frame_context *frame)
+{
+ draw(ud, surface, frame);
+ if (!((struct app *)ud)->failed) yasl_surface_destroy(surface);
+}
+
+int main(void)
+{
+ struct app app = {0};
+ struct yasl_app_desc desc = {
+ .app_id = "org.yasl.test.hello", .userdata = &app,
+ .init = init, .event = event, .frame = draw_once,
+ };
+ return yasl_run(&desc) || app.failed ? 1 : 0;
+}
diff --git a/tests/common/test_tablet_example.c b/tests/common/test_tablet_example.c
index 15dde9c..77e79f6 100644
--- a/tests/common/test_tablet_example.c
+++ b/tests/common/test_tablet_example.c
@@ -6,8 +6,20 @@
static void pointer(struct app *app, enum yasl_pointer_kind kind,
enum yasl_pointer_device device, int button, int x, int y, uint16_t pressure)
{
- struct yasl_input_event event = {.kind = YASL_INPUT_MOUSE, .mouse = {
- .kind = kind, .device = device, .button = button, .pos = {x, y}, .pen.pressure = pressure}};
+ struct yasl_event event = {0};
+ if (kind == YASL_PTR_MOTION) {
+ event.type = YASL_EVENT_MOUSE_MOTION;
+ event.mouse_motion.pos = (struct yasl_vector2i){x, y};
+ event.mouse_motion.device = device;
+ event.mouse_motion.pen.pressure = pressure;
+ } else {
+ event.type = YASL_EVENT_MOUSE_BUTTON;
+ event.mouse_button.pos = (struct yasl_vector2i){x, y};
+ event.mouse_button.device = device;
+ event.mouse_button.button = button;
+ event.mouse_button.pressed = kind == YASL_PTR_PRESS;
+ event.mouse_button.pen.pressure = pressure;
+ }
input(app, &event);
}
@@ -38,13 +50,13 @@ int main(void)
pointer(&app, YASL_PTR_PRESS, YASL_PTR_DEV_ERASER, 0, 80, 160, 65535);
assert(app.canvas[(size_t)160 * app.geometry.size.x + 80] == PAPER &&
app.canvas[(size_t)160 * app.geometry.size.x + 130] == INK);
- struct yasl_input_event reset = {.kind = YASL_INPUT_RESET};
+ struct yasl_event reset = {.type = YASL_EVENT_INPUT_RESET};
input(&app, &reset);
pointer(&app, YASL_PTR_MOTION, YASL_PTR_DEV_ERASER, 0, 130, 160, 65535);
assert(!app.down && app.canvas[(size_t)160 * app.geometry.size.x + 130] == INK);
pointer(&app, YASL_PTR_PRESS, YASL_PTR_DEV_MOUSE, 0, 300, 200, 0);
assert(app.canvas[(size_t)200 * app.geometry.size.x + 300] == INK);
- struct yasl_input_event blur = {.kind = YASL_INPUT_FOCUS, .focus.focused = false};
+ struct yasl_event blur = {.type = YASL_EVENT_WINDOW_FOCUS, .focus.focused = false};
input(&app, &blur);
assert(!app.down);
assert(resize_canvas(&app, (struct yasl_geometry){{800, 600}, 240}));
diff --git a/tests/common/test_timers.c b/tests/common/test_timers.c
index f321b2c..8c92158 100644
--- a/tests/common/test_timers.c
+++ b/tests/common/test_timers.c
@@ -4,7 +4,6 @@
#include <assert.h>
#include <stdio.h>
-#include <stdlib.h>
#include <string.h>
struct test_ctx {
diff --git a/tests/helpers/run_backends.py b/tests/helpers/run_backends.py
index 70c82fb..76fe8ab 100644
--- a/tests/helpers/run_backends.py
+++ b/tests/helpers/run_backends.py
@@ -21,7 +21,7 @@ def wait_for(process, ready):
def main():
- weston, xwayland, test, timers, tablet, hello, input_x11 = sys.argv[1:]
+ weston, xwayland, test, timers, tablet, hello, input_x11, interactive = sys.argv[1:]
servers = []
with tempfile.TemporaryDirectory(prefix="yasl-backends-") as directory:
root = Path(directory)
@@ -87,7 +87,11 @@ def main():
subprocess.run([tablet, "--backend", backend, "--smoke-test"],
env=env, check=True, timeout=8)
print("Test: hello example on", backend, flush=True)
- subprocess.run([hello, "--smoke-test"],
+ subprocess.run([hello],
+ env=env if backend == "wayland" else x_env,
+ check=True, timeout=8)
+ print("Test: interactive example on", backend, flush=True)
+ subprocess.run([interactive, "--smoke-test"],
env=env if backend == "wayland" else x_env,
check=True, timeout=8)
except BaseException:
diff --git a/tests/meson.build b/tests/meson.build
index 26bb5cb..951b30e 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -138,6 +138,10 @@ tablet_example_test = executable('yasl-tablet-example-test', 'common/test_tablet
test('tablet-example', tablet_example_test)
if has_x11
+ state_x11_test = executable('yasl-state-x11-test', 'x11/test_state.c',
+ include_directories: inc, dependencies: yasl_dep,
+ )
+ test('state-x11', state_x11_test)
tablet_x11_test = executable('yasl-tablet-x11-test', 'x11/test_tablet.c',
include_directories: inc, dependencies: yasl_dep,
)
@@ -156,6 +160,10 @@ elif get_option('x11_tablet_tests')
endif
if has_wayland
+ state_wayland_test = executable('yasl-state-wayland-test', 'wayland/test_state.c',
+ include_directories: inc, dependencies: yasl_dep,
+ )
+ test('state-wayland', state_wayland_test)
clipboard_wayland_test = executable('yasl-clipboard-wayland-test', 'wayland/test_clipboard.c',
include_directories: inc, dependencies: yasl_dep,
)
@@ -192,8 +200,12 @@ benchmark_exe = executable(
benchmark('performance', benchmark_exe)
if get_option('integration_tests')
+ hello_example_test = executable('yasl-hello-example-test', 'common/test_hello_example.c',
+ dependencies: shared_dep,
+ )
if has_cocoa
- test('hello-cocoa-window', hello_example,
+ test('hello-cocoa-window', hello_example_test, timeout: 20)
+ test('interactive-cocoa-window', interactive_example,
args: ['--smoke-test'], timeout: 20,
)
native_test = executable('yasl-native-test', 'cocoa/test_native.c',
@@ -235,7 +247,8 @@ if get_option('integration_tests')
xwayland = find_program('Xwayland')
test('backends', python,
args: [files('helpers/run_backends.py'), weston, xwayland,
- backend_test, timer_test, tablet_example, hello_example, input_x11_test],
+ backend_test, timer_test, tablet_example, hello_example_test, input_x11_test,
+ interactive_example],
timeout: 60,
)
endif
diff --git a/tests/test_lifecycle.c b/tests/test_lifecycle.c
index c54abd0..00d1264 100644
--- a/tests/test_lifecycle.c
+++ b/tests/test_lifecycle.c
@@ -1,5 +1,4 @@
#include "yasl/loop.h"
-#include "yasl/timing.h"
#include "yasl/yasl.h"
#include <assert.h>
#include <poll.h>
diff --git a/tests/wayland/test_app_input.c b/tests/wayland/test_app_input.c
index 2cacb5e..c8c5687 100644
--- a/tests/wayland/test_app_input.c
+++ b/tests/wayland/test_app_input.c
@@ -9,6 +9,8 @@
struct test_state {
int keys;
+ int buttons, motions, resets;
+ int end;
};
static void push_key(struct yasl_surface *s)
@@ -23,10 +25,32 @@ static void push_key(struct yasl_surface *s)
static int on_init(void *ud, struct yasl_display *d)
{
- (void)ud;
+ struct test_state *state = ud;
+ if (state->end) {
+ struct wayland_display *display = (struct wayland_display *)d;
+ if (state->end == 1)
+ display->cbs->quit(display->ud);
+ else
+ display->cbs->lost(display->ud);
+ return YASL_APP_CONTINUE;
+ }
struct yasl_surface *s = yasl_surface_create_toplevel(d,
&(struct yasl_window_spec){ .title = "App Input Test", .size = {64, 64} });
assert(s);
+ struct input_queue *queue = &((struct wayland_surface *)s)->input;
+ input_queue_clear(queue);
+ struct yasl_input_event event = {.surface = s, .kind = YASL_INPUT_MOUSE,
+ .mouse = {.kind = YASL_PTR_PRESS, .device = YASL_PTR_DEV_ERASER,
+ .pos = {12, 34}, .pen = {.pressure = 50000, .tilt_x = -20, .tilt_y = 30}}};
+ assert(input_queue_push(queue, &event));
+ event.mouse.kind = YASL_PTR_MOTION;
+ event.mouse.pos = (struct yasl_vector2i){15, 39};
+ assert(input_queue_push(queue, &event));
+ struct yasl_input_event reset = {.surface = s, .kind = YASL_INPUT_RESET};
+ assert(input_queue_push(queue, &reset));
+ event.mouse.device = YASL_PTR_DEV_PEN;
+ event.mouse.pos = (struct yasl_vector2i){100, 200};
+ assert(input_queue_push(queue, &event));
push_key(s);
push_key(s);
return YASL_APP_CONTINUE;
@@ -35,7 +59,26 @@ static int on_init(void *ud, struct yasl_display *d)
static int on_event(void *ud, const struct yasl_event *ev)
{
struct test_state *state = ud;
- if (ev->type == YASL_EVENT_KEY) {
+ if (ev->type == YASL_EVENT_MOUSE_BUTTON) {
+ assert(ev->mouse_button.device == YASL_PTR_DEV_ERASER);
+ assert(ev->mouse_button.pressed && ev->mouse_button.button == 0);
+ assert(ev->mouse_button.pos.x == 12 && ev->mouse_button.pos.y == 34);
+ assert(ev->mouse_button.pen.pressure == 50000);
+ assert(ev->mouse_button.pen.tilt_x == -20 && ev->mouse_button.pen.tilt_y == 30);
+ state->buttons++;
+ } else if (ev->type == YASL_EVENT_MOUSE_MOTION) {
+ if (!state->motions) {
+ assert(ev->mouse_motion.device == YASL_PTR_DEV_ERASER);
+ assert(ev->mouse_motion.delta.x == 3 && ev->mouse_motion.delta.y == 5);
+ } else {
+ assert(ev->mouse_motion.device == YASL_PTR_DEV_PEN);
+ assert(!ev->mouse_motion.delta.x && !ev->mouse_motion.delta.y);
+ }
+ assert(ev->mouse_motion.pen.pressure == 50000);
+ state->motions++;
+ } else if (ev->type == YASL_EVENT_INPUT_RESET) {
+ state->resets++;
+ } else if (ev->type == YASL_EVENT_KEY) {
state->keys++;
yasl_surface_destroy(ev->surface);
}
@@ -56,5 +99,13 @@ int main(void)
});
assert(res == 0);
assert(state.keys == 1);
+ assert(state.buttons == 1 && state.motions == 2 && state.resets == 1);
+ for (int end = 1; end <= 2; end++) {
+ state = (struct test_state){.end = end};
+ res = yasl_run(&(struct yasl_app_desc){
+ .backend = YASL_BACKEND_WAYLAND, .userdata = &state, .init = on_init,
+ });
+ assert(end == 1 ? res == 0 : res < 0);
+ }
return 0;
}
diff --git a/tests/wayland/test_state.c b/tests/wayland/test_state.c
new file mode 100644
index 0000000..2da26e5
--- /dev/null
+++ b/tests/wayland/test_state.c
@@ -0,0 +1,75 @@
+#include "../../src/platform/wayland/shell.c"
+#include <assert.h>
+
+struct notifications {
+ int count;
+ uint32_t state;
+};
+
+static void state_changed(struct yasl_surface *surface, uint32_t state, void *ud)
+{
+ struct notifications *notifications = ud;
+ assert(yasl_surface_state(surface) == state);
+ notifications->count++;
+ notifications->state = state;
+}
+
+static void configure(struct wayland_surface *surface, uint32_t *states, size_t count)
+{
+ struct wl_array array = {.size = count * sizeof(*states), .data = states};
+ toplevel_configure(surface, NULL, 640, 480, &array);
+ assert(surface->req_width == 640 && surface->req_height == 480);
+}
+
+int main(void)
+{
+ struct notifications notifications = {0};
+ struct wayland_surface surface = {.base.state = YASL_STATE_CSD};
+ wayland_surface_set_callbacks(&surface, &(struct yasl_window_cbs){
+ .state = state_changed}, &notifications);
+ assert(yasl_surface_state(&surface.base) == YASL_STATE_CSD);
+
+ uint32_t fullscreen[] = {XDG_TOPLEVEL_STATE_FULLSCREEN,
+ XDG_TOPLEVEL_STATE_ACTIVATED, XDG_TOPLEVEL_STATE_FULLSCREEN, 31, UINT32_MAX};
+ configure(&surface, fullscreen, sizeof(fullscreen) / sizeof(*fullscreen));
+ assert(notifications.count == 1);
+ assert(notifications.state == (YASL_STATE_CSD | YASL_STATE_FULLSCREEN | YASL_STATE_ACTIVATED));
+ configure(&surface, fullscreen, sizeof(fullscreen) / sizeof(*fullscreen));
+ assert(notifications.count == 1);
+
+ /* Decoration changes preserve the other flags and do not notify twice. */
+ toplevel_decoration_configure(&surface, NULL, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
+ assert(notifications.count == 2);
+ assert(notifications.state == (YASL_STATE_FULLSCREEN | YASL_STATE_ACTIVATED));
+ toplevel_decoration_configure(&surface, NULL, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
+ assert(notifications.count == 2);
+
+ uint32_t maximized[] = {XDG_TOPLEVEL_STATE_MAXIMIZED, XDG_TOPLEVEL_STATE_SUSPENDED};
+ configure(&surface, maximized, sizeof(maximized) / sizeof(*maximized));
+ assert(notifications.count == 3);
+ assert(notifications.state == (YASL_STATE_MAXIMIZED | YASL_STATE_SUSPENDED));
+ toplevel_decoration_configure(&surface, NULL, ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE);
+ assert(notifications.count == 4);
+ assert(notifications.state == (YASL_STATE_CSD | YASL_STATE_MAXIMIZED | YASL_STATE_SUSPENDED));
+
+ /* Each tiled edge maps to the same portable flag; changing edges alone
+ * leaves the reported state unchanged. */
+ uint32_t edges[] = {XDG_TOPLEVEL_STATE_TILED_LEFT, XDG_TOPLEVEL_STATE_TILED_RIGHT,
+ XDG_TOPLEVEL_STATE_TILED_TOP, XDG_TOPLEVEL_STATE_TILED_BOTTOM};
+ for (size_t i = 0; i < sizeof(edges) / sizeof(*edges); i++) {
+ configure(&surface, &edges[i], 1);
+ assert(notifications.count == 5);
+ assert(notifications.state == (YASL_STATE_CSD | YASL_STATE_TILED));
+ }
+ configure(&surface, edges, 0);
+ assert(notifications.count == 6);
+ assert(notifications.state == YASL_STATE_CSD);
+
+ /* State still advances when applications have not installed callbacks. */
+ wayland_surface_set_callbacks(&surface, NULL, NULL);
+ configure(&surface, fullscreen, sizeof(fullscreen) / sizeof(*fullscreen));
+ assert(yasl_surface_state(&surface.base) ==
+ (YASL_STATE_CSD | YASL_STATE_FULLSCREEN | YASL_STATE_ACTIVATED));
+ assert(notifications.count == 6);
+ return 0;
+}
diff --git a/tests/x11/test_state.c b/tests/x11/test_state.c
new file mode 100644
index 0000000..abdda95
--- /dev/null
+++ b/tests/x11/test_state.c
@@ -0,0 +1,77 @@
+/* Supply native window-state properties without requiring an X server. */
+#define XGetWindowProperty state_property
+#include "../../src/platform/x11/window.c"
+#undef XGetWindowProperty
+
+static Atom properties[3];
+static unsigned long property_count;
+static int notifications;
+static uint32_t reported_state;
+
+int state_property(Display *display, Window window, Atom property,
+ long offset, long length, Bool delete_property, Atom type,
+ Atom *actual, int *format, unsigned long *count, unsigned long *left,
+ unsigned char **data)
+{
+ assert(display && window == 42 && property == 10);
+ assert(!offset && length >= 3 && !delete_property && type == XA_ATOM);
+ *actual = XA_ATOM;
+ *format = 32;
+ *count = property_count;
+ *left = 0;
+ *data = NULL;
+ if (property_count) {
+ *data = malloc(property_count * sizeof(*properties));
+ assert(*data);
+ memcpy(*data, properties, property_count * sizeof(*properties));
+ }
+ return Success;
+}
+
+static void state_changed(struct yasl_surface *surface, uint32_t state, void *ud)
+{
+ (void)ud;
+ assert(yasl_surface_state(surface) == state);
+ reported_state = state;
+ notifications++;
+}
+
+int main(void)
+{
+ struct x11_display display = {.base.ops = &yasl_x11_backend,
+ .net_state = 10, .fullscreen = 20, .maximized_h = 21, .maximized_v = 22};
+ display.native = (Display *)&display;
+ struct x11_surface surface = {.base = {.display = &display.base,
+ .state = YASL_STATE_ACTIVATED}, .window = 42};
+ set_callbacks(&surface.base, &(struct yasl_window_cbs){.state = state_changed}, NULL);
+
+ properties[0] = display.fullscreen;
+ property_count = 1;
+ update_state(&surface);
+ assert(notifications == 1);
+ assert(reported_state == (YASL_STATE_FULLSCREEN | YASL_STATE_ACTIVATED));
+ update_state(&surface);
+ assert(notifications == 1);
+
+ /* Both maximized axes are required; activation survives property changes. */
+ properties[0] = display.maximized_h;
+ update_state(&surface);
+ assert(notifications == 2 && reported_state == YASL_STATE_ACTIVATED);
+ properties[1] = display.maximized_v;
+ property_count = 2;
+ update_state(&surface);
+ assert(notifications == 3);
+ assert(reported_state == (YASL_STATE_MAXIMIZED | YASL_STATE_ACTIVATED));
+ properties[2] = display.fullscreen;
+ property_count = 3;
+ update_state(&surface);
+ assert(notifications == 4);
+ assert(reported_state == (YASL_STATE_FULLSCREEN | YASL_STATE_MAXIMIZED | YASL_STATE_ACTIVATED));
+
+ property_count = 0;
+ update_state(&surface);
+ assert(notifications == 5 && reported_state == YASL_STATE_ACTIVATED);
+ update_state(&surface);
+ assert(notifications == 5);
+ return 0;
+}
diff --git a/tests/x11/test_tablet_native.c b/tests/x11/test_tablet_native.c
index f12ec7f..078606a 100644
--- a/tests/x11/test_tablet_native.c
+++ b/tests/x11/test_tablet_native.c
@@ -55,6 +55,49 @@ static int connect_tool(const char *path)
return fd;
}
+static void native_draw(struct yasl_surface *surface, struct yasl_frame *frame, void *ud)
+{
+ struct yasl_frame_context context = {
+ .surface = surface, .size = frame->target.size,
+ .scale_120 = yasl_surface_geometry(surface).scale_120,
+ .cpu = {.pixels = frame->target.pixels,
+ .stride_bytes = frame->target.size.x * (int)sizeof(uint32_t)},
+ .internal_handle = frame,
+ };
+ draw(ud, surface, &context);
+}
+
+static void native_configure(struct yasl_surface *surface, struct yasl_geometry geometry, void *ud)
+{
+ input(ud, &(struct yasl_event){.surface = surface, .type = YASL_EVENT_WINDOW_RESIZE,
+ .resize = {.size = geometry.size, .scale_120 = geometry.scale_120}});
+}
+
+static void native_input(struct app *app, const struct yasl_input_event *raw)
+{
+ struct yasl_event event = {.surface = raw->surface};
+ if (raw->kind == YASL_INPUT_RESET) event.type = YASL_EVENT_INPUT_RESET;
+ else if (raw->kind == YASL_INPUT_FOCUS) {
+ event.type = YASL_EVENT_WINDOW_FOCUS;
+ event.focus.focused = raw->focus.focused;
+ } else if (raw->kind == YASL_INPUT_MOUSE) {
+ if (raw->mouse.kind == YASL_PTR_MOTION) {
+ event.type = YASL_EVENT_MOUSE_MOTION;
+ event.mouse_motion.pos = raw->mouse.pos;
+ event.mouse_motion.device = raw->mouse.device;
+ event.mouse_motion.pen = raw->mouse.pen;
+ } else if (raw->mouse.kind == YASL_PTR_PRESS || raw->mouse.kind == YASL_PTR_RELEASE) {
+ event.type = YASL_EVENT_MOUSE_BUTTON;
+ event.mouse_button.pos = raw->mouse.pos;
+ event.mouse_button.device = raw->mouse.device;
+ event.mouse_button.button = raw->mouse.button;
+ event.mouse_button.pressed = raw->mouse.kind == YASL_PTR_PRESS;
+ event.mouse_button.pen = raw->mouse.pen;
+ } else if (raw->mouse.kind == YASL_PTR_LEAVE) event.type = YASL_EVENT_MOUSE_LEAVE;
+ }
+ input(app, &event);
+}
+
static void pump(int fd, struct yasl_display *display, struct yasl_surface *surface, struct app *app)
{
xf86ITEventWaitForSync sync = {{sizeof(sync), XF86IT_EVENT_WAIT_FOR_SYNC}};
@@ -68,7 +111,7 @@ static void pump(int fd, struct yasl_display *display, struct yasl_surface *surf
assert(yasl_display_dispatch(display, 0) == 0);
struct yasl_input_event event;
while (yasl_surface_input_poll(surface, &event)) {
- input(app, &event);
+ native_input(app, &event);
yasl_surface_schedule_redraw(surface);
}
yasl_display_flush(display);
@@ -99,14 +142,14 @@ static void set_eraser_property(Display *display)
int main(int argc, char **argv)
{
assert(argc == 3); /* pen socket, eraser socket */
- struct app app = {.running = true, .tablet_supported = true, .backend = YASL_BACKEND_X11};
+ struct app app = {.tablet_supported = true, .backend = YASL_BACKEND_X11};
struct yasl_display_spec ds = {.backend = YASL_BACKEND_X11};
struct yasl_display *display = yasl_display_open(NULL, &ds);
assert(display && (yasl_display_caps(display) & YASL_CAP_TABLET));
struct yasl_window_spec ss = {.title = "YASL native pressure test", .size = {500, 400}};
struct yasl_surface *surface = yasl_surface_create_toplevel(display, &ss);
assert(surface);
- struct yasl_window_cbs cbs = {.draw = draw, .configure = configure};
+ struct yasl_window_cbs cbs = {.draw = native_draw, .configure = native_configure};
yasl_surface_set_callbacks(surface, &cbs, &app);
yasl_surface_schedule_redraw(surface);
Display *x = yasl_display_get_native_handle(display);