diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-13 23:41:31 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-13 23:41:31 +0300 |
| commit | 76073fe5fad8799e5850805f2f090af2cb62c741 (patch) | |
| tree | 9658019babe4760a3634bdab4da0731a10e42e68 | |
| parent | dd1775a9927922c14f8679ccbc4d3ca8d5917e20 (diff) | |
Simplify the public event and CPU API
Applications now need far less ceremony: hello.c is a 20-line loop that
handles REFRESH, CLOSE and QUIT and draws into the surface's own pixels.
- Fold GEOMETRY, STATE and FOCUS into one WINDOW event carrying size,
scale and state; unchanged values send nothing.
- Guarantee REFRESH once a window has a size and after every resize,
never at size zero.
- Merge DROP_HOVER into DROP with over/leave/data actions and give drag
and drop actions enums.
- Keep event strings valid until the next wait and drop
yasl_event_release; empty or failed waits report NONE.
- Replace copy-in presentation with a lent pixel buffer:
yasl_cpu_pixels and yasl_cpu_present.
- Remove yasl_poll_event, yasl_cpu_read_pixels, unused event fields and
comments that restated names.
- Wayland: resize floating windows in request_size instead of reporting
it unsupported, and stop marking new GPU surfaces as Vulkan so
yasl_vulkan_create_surface no longer fails with NATIVE_WINDOW_IN_USE.
Also includes the in-progress public API refactor that was uncommitted
in the tree (removal of the app/loop layer, new surface, GL and Vulkan
entry points).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
130 files changed, 9122 insertions, 8059 deletions
diff --git a/ARCHITECTURE_PLAN.md b/ARCHITECTURE_PLAN.md deleted file mode 100644 index f2ec38c..0000000 --- a/ARCHITECTURE_PLAN.md +++ /dev/null @@ -1,89 +0,0 @@ -# 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`). - ---- - -## 1. Motivation & Core Goals - -### The Problem in YASL 0.1 -1. **Split-Brain Event Model**: Window lifecycle and drawing use callbacks, but input events are queued in a surface ring buffer requiring a manual `dispatch -> poll -> flush` pump. -2. **Platform Live-Resize Quirks**: On macOS (`inLiveResize`) and Wayland (`xdg_surface.configure`), drawing must be strictly synchronized to the OS event to avoid freezing, visual stutter, or black borders. -3. **Game vs. IDE Divergence**: - - Games need continuous, VSync-paced ticks (60/120/144+ Hz) and direct GPU presentation (Metal/Vulkan). - - IDEs need reactive, on-demand redraws (0% CPU when idle), rich text input (IME, UTF-8, autorepeat), multi-window support, and cross-thread communication (LSP/file watchers). -4. **Stack Overhead**: Presenting a CPU frame currently requires passing a 1,044-byte stack struct (`yasl_damage_list`). - ---- - -## 2. Architecture Overview - -``` -┌────────────────────────────────────────────────────────┐ -│ Layer 2: Specialized Helpers (Opt-in) │ -│ - File Descriptor Watcher (LSP, Sockets, Pipes) │ -│ - Cross-Thread Event Dispatcher (yasl_post_event) │ -│ - Mouse Relative Mode (Pointer Lock for 3D/Games) │ -├────────────────────────────────────────────────────────┤ -│ Layer 1: Unified App Runner (include/yasl/app.h) │ -│ - yasl_run(const struct yasl_app_desc *desc) │ -│ - Dual-mode pacing: YASL_PACING_CONTINUOUS vs ON_DEMAND│ -│ - Decoupled frame target (CPU software vs GPU native) │ -├────────────────────────────────────────────────────────┤ -│ Layer 0: Core Native Surfaces & Dispatchers │ -│ - Wayland / X11 / Cocoa display & surface backends │ -└────────────────────────────────────────────────────────┘ -``` - ---- - -## 3. Phased Execution Plan - -### Phase 1: Define Public Interface (`include/yasl/app.h` & modernized types) -1. **Unified Event Pipeline (`yasl_event`)**: - - Merge input and window events into a single enum: - - `YASL_EVENT_KEY`, `YASL_EVENT_TEXT` - - `YASL_EVENT_MOUSE_MOTION`, `YASL_EVENT_MOUSE_BUTTON`, `YASL_EVENT_MOUSE_SCROLL` - - `YASL_EVENT_WINDOW_RESIZE`, `YASL_EVENT_WINDOW_SCALE_CHANGED`, `YASL_EVENT_WINDOW_CLOSE_REQUEST`, `YASL_EVENT_WINDOW_FOCUS` - - `YASL_EVENT_USER` (custom payloads from background threads) - - Add `bool repeat` to `yasl_key_event` to distinguish game keypresses from editor autorepeat. -2. **Dual-Target Frame Context (`yasl_frame_context`)**: - - Provide `ctx->cpu` (software buffer) and `ctx->gpu` (native layer/surface). - - Lightweight presentation: `yasl_frame_present_cpu(ctx, dirty_rects, count)` where `NULL` dirty rects indicate full presentation without a 1 KB stack structure. -3. **Pacing Modes**: - - `YASL_PACING_CONTINUOUS`: Drives `frame()` every display refresh for games. - - `YASL_PACING_ON_DEMAND`: Sleeps at 0% CPU when idle; wakes on events, timers, or `yasl_surface_request_frame()`. -4. **App Descriptor & Runner**: - - Define `struct yasl_app_desc` with `init`, `event`, `frame`, and `quit`. - - Declare `int yasl_run(const struct yasl_app_desc *desc);`. - -### Phase 2: Implement Core Runner & Cross-Thread Messaging (`src/common/app.c`) -1. **The Lifecycle Pump (`yasl_run`)**: - - Initialize display and event loop, invoke `desc->init()`. - - Run the unified event loop: - - Drain native inputs and dispatch clean `const yasl_event *` into `desc->event()`. - - Automatically synchronize macOS live-resize (`inLiveResize`) and Wayland configure passes to call `desc->frame()`. - - Regulate pacing: continuous ticks for games vs. reactive idle sleep for desktop apps. -2. **Cross-Thread Event Posting (`yasl_post_event`)**: - - Implement thread-safe event enqueueing backed by a mutex-protected ringbuffer and loop wake pipe (`yasl_loop_kick`). - - Allows background threads (LSP, compilers, physics, asset loaders) to post `YASL_EVENT_USER` safely to the main thread. - -### Phase 3: Add Key Capabilities for Games & Desktop UI -1. **Relative Mouse Mode (`yasl_surface_set_mouse_mode`)**: - - Implement `YASL_MOUSE_RELATIVE` (pointer lock / virtualized motion deltas) for games and 3D viewports. -2. **Background File Descriptor Watching (`yasl_app_watch_fd`)**: - - Register pipes/sockets (e.g., LSP stdin/stdout) directly with the main loop. - -### Phase 4: Verification, Examples & Testing -1. **`examples/game.c`**: - - Continuous 60/144 FPS game loop, animated graphics, raw WASD input, and relative mouse control. -2. **`examples/notepad.c`**: - - Comprehensive text editor example demonstrating YASL's capabilities: - - Reactive rendering (0% CPU at idle, redrawing only when dirty). - - UTF-8 text insertion, backspace, enter, and cursor navigation with autorepeat. - - Blinking text caret driven by a timer. - - Scale-aware text rendering and dynamic window resizing. - - Background async task (e.g., word count / syntax check / LSP task) posting results via `yasl_post_event`. -3. **Automated Tests**: - - Add test suites in `tests/` verifying `yasl_run`, event propagation, cross-thread posting, and pacing modes. - - Verify all 18 existing backend tests pass without regressions. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 788425e..7e31c97 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,57 +1,27 @@ # Contributing -YASL focuses on desktop surfaces for applications that own their renderer. -Read the [API policy](docs/api-policy.md) and [application guide](docs/guide.md) -before changing public behavior. Keep backend details in native extensions and -make common operations usable through the portable headers. +YASL is a small desktop surface/input library for application-owned loops and +renderers. Read [the guide](docs/guide.md) and [API policy](docs/api-policy.md). +Keep public C APIs direct: opaque surface pointers, plain IDs/numeric arguments, +and one tagged event. Avoid new descriptor/geometry/string/handle helper structs, +application runners, graphics engines, or hidden presentation policy. -Shared API behavior and backend interfaces live in `src/common/`. Native -windowing implementations, key mappings, and typed native accessors live in -`src/platform/<backend>/`; keep their generated protocols with the backend. -OS support shared by multiple backends lives separately in `src/platform/posix/` -and is selected by `src/platform/meson.build`. It currently supplies the event -loop and the private mutex implementation used through `platform/sync.h`. -Linux desktop services shared by Wayland and X11, such as xdg-open and the -D-Bus session bus, live in `src/platform/freedesktop/`; Cocoa keeps its own in -`src/platform/cocoa/desktop.m`. Both implement `src/common/desktop.h`. -Common code should depend on these internal interfaces rather than native OS -headers. Keep public headers in `include/yasl/` and tests in `tests/<backend>/`. +Portable lifetime, event ownership, validation, and rendering bridges live in +src/common. Native implementations live in src/platform/wayland, x11, and cocoa. +Private backend geometry/frame/loop types are implementation details, not installed +API. Linux desktop services live in src/platform/freedesktop; POSIX synchronization +and waiting live in src/platform/posix. Keep native handles typed in extension headers. -For a future Win32 backend, add `src/platform/win32/`, select its event loop and -`yasl_platform_sync.h` in the platform build, and register its backend through -the common dispatch interface. Windows still needs build/dependency selection, -public API/export support, and ports of POSIX-based examples and test harnesses; -the directory structure alone does not provide Windows support. - -For a bug report, include the revision, operating system, selected backend from -`yasl_display_backend()`, capability mask, compiler, build options, reproduction -steps, and a small example if possible. For input issues include the relevant -event fields, layout/device/driver, and whether the issue occurs on another -backend. Report observations separately from expected behavior. - -Build and test with: +Document public units, ownership, errors, thread requirements, callback timing, +and unsupported behavior. Add useful behavior coverage and examples. Run: ```sh -meson setup build meson compile -C build meson test -C build --print-errorlogs ``` -See [testing](docs/testing.md) for isolated Linux integration tests, sanitizer -builds, and native tablet tests. Changes to shared behavior need both backend -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 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. -Build the reference with `doxygen Doxyfile`; warnings are errors. Do not edit -vendored generated protocol files for unrelated changes. - -Use the existing C style and `yasl_`/`YASL_` public names. Run the tests relevant -to the changed behavior, including shared exports and public C/C++ compilation. -For a pull request, explain the observable problem, resulting behavior, and -validation, including any native tests still needed. Contributions use the -project's MIT license. +Changes to portable/native behavior need both Linux backends and installed shared/ +static consumers checked. Use the isolated GPU suite described in [testing](docs/testing.md). +Cocoa changes require a native macOS build/test; disclose platforms not exercised. +Do not edit generated protocol files for unrelated changes. Check exact exports and +public C/C++ compilation. Contributions use the project's MIT license. diff --git a/IDE_INTEGRATION.md b/IDE_INTEGRATION.md deleted file mode 100644 index 67ba96e..0000000 --- a/IDE_INTEGRATION.md +++ /dev/null @@ -1,198 +0,0 @@ -# IDE integration - -YASL includes Zed debugger profiles for stepping from example applications into -the library's display and window creation code. Open the repository root (the -directory containing `meson.build`) as your Zed project. The shared configuration -uses `build/` for the build directory on Linux and macOS. - -## Prerequisites - -Install Zed, a C23 compiler, Meson 1.6 or newer, Ninja, pkg-config, and Python 3. -A C++ compiler enables the optional public-header compatibility test. Install -the backend development dependencies described in -[Getting started](docs/getting-started.md#build-and-run): at least one of Wayland -or X11 on Linux, or the macOS 14+ SDK on macOS. Running windows on macOS also -requires a Metal-capable device. - -[Zed's C support](https://zed.dev/docs/languages/c) includes clangd for code -completion, diagnostics, and navigation, and CodeLLDB for debugging. Allow Zed -to obtain these tools when prompted, or configure your existing installations -in your personal Zed settings. Use a recent clangd with C23 support. - -Make sure `meson`, `ninja`, your compiler, `pkg-config`, and `python3` are on the -PATH visible to Zed's terminal. You can open the project from an initialized -terminal with `zed .` when Zed's CLI is installed. On macOS, if your toolchain -defaults to an older deployment target, launch from a terminal with -`MACOSX_DEPLOYMENT_TARGET=14.0` (or newer) exported before configuring. - -## Start debugging window creation - -1. Save your changes and open `examples/hello.c`. -2. Set gutter breakpoints on the calls to `yasl_display_open()` and - `yasl_surface_create_toplevel()` in `main()`. -3. Open the command palette (`Ctrl+Shift+P` on Linux, `Cmd+Shift+P` on macOS), - run `debugger: start`, and choose **YASL: debug hello**. -4. The debugger prepares the build and pauses on entry. Continue to the display - creation breakpoint, then use **Step Into** to enter YASL's implementation in - `src/common/backend.c`. Continue to the surface creation breakpoint and step - into `yasl_surface_create_toplevel()` and `yasl_create_toplevel()`. -5. Step into `d->ops->surface_create_toplevel(...)` to reach native window - creation. Inspect `spec`, `d`, local variables, and the call stack in Zed's - debug panel. Use **Step Over** for calls you want to execute without entering. - -Every debug profile runs **YASL: build for debugger** before launching. This -task creates or reconfigures `build/` with debug symbols, optimization disabled, -and LTO disabled, then builds the library and examples. It explicitly sets -`buildtype=debug`, `debug=true`, `optimization=0`, and `b_lto=false`, including -when `build/` was previously a release or customized build. Other Meson options, -including backend selection, are preserved. Both the example and YASL have -source-level debug information; the shared-library examples load the library -from the Meson build tree. - -Profiles pause on entry through CodeLLDB's `stopOnEntry` setting, so initialization -does not run before you can inspect the session. To launch directly to your -breakpoints instead, set `"stopOnEntry": false` in the desired profile. See -[CodeLLDB's launch settings](https://github.com/vadimcn/codelldb/blob/master/MANUAL.md#launching-a-new-process). - -Native creation follows the backend selected at runtime: - -| Backend | Window creation implementation | -| --- | --- | -| Wayland | `wayland_surface_create_toplevel()` in `src/platform/wayland/surface.c` | -| X11 | `create_toplevel()` in `src/platform/x11/window.c` | -| Cocoa | `cocoa_surface_create_toplevel()` in `src/platform/cocoa/window.m` | - -You can also put a source breakpoint directly in these functions before -launching. A breakpoint in a backend that is disabled or not selected will not -be hit. In the notepad and game examples, `yasl_run()` opens the display before -calling the example's initialization callback; set a breakpoint in `yasl_run()` -in `src/common/app.c` to follow that path. - -## Configure and build without launching - -1. Open the command palette (`Ctrl+Shift+P` on Linux, `Cmd+Shift+P` on macOS). -2. Run `task: spawn`, then select **YASL: configure debug build**. -3. Run **YASL: build** from the same task picker. -4. Open `examples/hello.c` or a file in `src/` to use completion and navigation. - -The configure task runs: - -```sh -meson setup build --backend=ninja --buildtype=debug -``` - -Meson's Ninja backend writes `build/compile_commands.json`. The root -[.clangd](.clangd) points clangd at that directory, so it uses Meson's actual -include paths, C standard, and enabled-backend definitions. Keep the generated -database in the ignored build directory; no root-level copy or symlink is -needed. The same configuration works with other clangd-based editors. See -[clangd's compilation database setting](https://clangd.llvm.org/config#compilationdatabase). - -If `build/` is already configured, the configure task leaves its existing -options in place. To change an existing build to debug, run this in Zed's -terminal: - -```sh -meson configure build -Dbuildtype=debug -``` - -Save edited files before building, running, or debugging. Use **YASL: -reconfigure** to rerun Meson configuration and refresh its metadata. Meson also -regenerates the build automatically when build definitions change. - -## Tasks and examples - -The project tasks live in [.zed/tasks.json](.zed/tasks.json). Select them with -`task: spawn`; `task: rerun` repeats the last task. See -[Zed's task documentation](https://zed.dev/docs/tasks) for personal keybindings. - -| Task | Behavior | -| --- | --- | -| YASL: configure debug build | Set up `build/` with Ninja and debug symbols. | -| YASL: reconfigure | Reconfigure an existing `build/`, preserving its options. | -| YASL: build | Compile the library, examples, and enabled tests. | -| YASL: build for debugger | Configure symbols and disable optimization/LTO, then build. | -| YASL: test | Build as needed and run enabled tests, printing failure logs. | -| YASL: benchmark | Build as needed and run Meson's registered benchmarks. | -| YASL: run hello | Build and launch `yasl-hello`. | -| YASL: run simple window | Build and launch `yasl-example`. | -| YASL: run tablet | Build and launch `yasl-tablet`. | -| YASL: run notepad | Build and launch `yasl-notepad`. | -| YASL: run game | Build and launch `yasl-game`. | - -Each run task builds its executable before launching and stops if compilation -fails. Run tasks explicitly use `sh` for their compound command. All tasks use -the repository root as their working directory, including when a nested source -file is active. - -Examples need a desktop connection. On Linux, run Zed in your desktop session -so the launched program inherits `WAYLAND_DISPLAY`/`DISPLAY` and -`XDG_RUNTIME_DIR` as appropriate. Most tests can run without a display, but the -currently registered `app-runner` test opens a window even in a default build. -`app-input-destroy` also needs a Wayland compositor when `WAYLAND_DISPLAY` is -set. Run the full suite in a desktop session with the corresponding backend -enabled; additional tests retain their configured requirements. - -## Debugging - -The profiles in [.zed/debug.json](.zed/debug.json) use CodeLLDB. Run -`debugger: start` from the command palette and select an example. Profiles cover -hello, simple window, tablet, notepad, game, and the lifecycle test -(`build/tests/yasl-test`), which needs no display. Each profile builds through -**YASL: build for debugger** and pauses on entry. -See [Zed's debugger documentation](https://zed.dev/docs/debugger). - -To debug another test, copy the lifecycle profile and change its `label` and -`program` to the executable under `build/tests/`. To pass example arguments, -add an `args` array to a debug profile; for example, the tablet profile accepts -`"args": ["--backend", "x11"]` when that backend is enabled. - -## Backend options and additional builds - -Linux backend selection defaults to automatic dependency detection. To require -X11 and disable Wayland in the IDE build, run: - -```sh -meson configure build -Dx11=enabled -Dwayland=disabled -meson compile -C build -``` - -Restore automatic selection with `-Dx11=auto -Dwayland=auto`. Integration tests -are opt-in through `-Dintegration_tests=true`; their server dependencies and -desktop requirements are documented in [Testing](docs/testing.md). - -For sanitizers or a different compiler, prefer a separate build directory: - -```sh -meson setup build-sanitize -Db_sanitize=address,undefined -meson test -C build-sanitize --print-errorlogs -``` - -The checked-in Zed tasks and profiles continue to target `build/`. To use a -different directory throughout the IDE, update the tasks' build arguments and -run commands, debugger program paths, and `.clangd`'s `CompilationDatabase` -together. Ignore additional build directories locally (for example through -`.git/info/exclude`) and keep machine-specific tool paths out of shared files. - -## Troubleshooting - -- **Missing headers or incorrect backend diagnostics:** confirm that - `build/compile_commands.json` exists, build successfully, then run - `editor: restart language server`. clangd analyzes the backend configuration - in that database; disabled native backends may lack their SDK headers. -- **C23 syntax errors in the editor:** update clangd; the compiler used by Meson - and the clangd used by Zed are separate tools. -- **A task cannot find Meson or a compiler:** check `command -v meson ninja cc` - in Zed's terminal and fix the PATH used by Zed and its task shell. -- **No window opens:** check the desktop environment and enabled backends. - Use the lifecycle debug profile to check debugging without a display. -- **`app-runner` or `app-input-destroy` fails at startup:** check that the test - can connect to the inherited desktop session. For a headless check of the - lifecycle test alone, run `meson test -C build basic --print-errorlogs`. -- **Breakpoints do not bind:** use a **YASL: debug ...** profile so the debugger - build task runs, and confirm the executable and library come from `build/`. - Shared-library breakpoints may remain pending until the library loads. Check - that a native backend breakpoint belongs to the enabled, selected backend. -- **Configuration fails on macOS:** check the SDK and deployment target against - [Getting started](docs/getting-started.md#build-and-run). Compiler or SDK - changes may require a fresh build directory. @@ -1,66 +1,45 @@ # YASL — Yet Another Surface Library -YASL is a C desktop surface library for applications that own their renderer. -It creates windows and supplies CPU pixel targets, scale-aware geometry, queued -input, clipboard text, popups, surface drags, and optional frame pacing. -It runs on Wayland, X11, and macOS Cocoa. - -A software-rendered editor, terminal, drawing tool, or custom interface can open -a window and paint pixels without creating an OpenGL context or adopting a -widget toolkit. YASL also exposes tablet pressure/tilt and IME composition. -It does not provide fonts, widgets, audio, or a graphics engine. Native handles -and Cocoa/Metal rendering are separate extensions. - -**Status:** pre-release 0.1; source and ABI compatibility are not yet guaranteed. -Physical keys have portable names, committed text arrives as UTF-8, and scroll -offsets use fractional X/Y line units. Read the [application guide](docs/guide.md) -for platform capabilities and limits. - -## Start here +YASL is a small C desktop surface and input library for applications that own +their event loop and renderer. It supports Wayland, X11, and Cocoa, with CPU +images, optional OpenGL contexts, Vulkan window surfaces, and direct Metal layers. +Pen input includes tool identity, proximity/contact, pressure, tilt, and available +rotation/distance/tangential/wheel axes. It also supplies IME composition/editing, +clipboard text, popups, file drops, and surface drags. + +The first surface initializes a shared backend. Surface operations use ordinary +arguments; there are no public display objects, creation descriptors, frame +objects, point/size/string wrappers, or mandatory application runner. + +```c +yasl_surface *surface = yasl_surface_create("Example", 800, 600, 0); +``` -- [Getting started](docs/getting-started.md): build, install, link, and run a window. -- [Application guide](docs/guide.md): geometry, drawing, input, ownership, and backend limits. -- [Public reference](include/yasl/yasl.h), [app runner](include/yasl/app.h), [loop](include/yasl/loop.h), - [timing](include/yasl/timing.h), and [native extensions](include/yasl/native.h). - Run `doxygen Doxyfile` for a searchable HTML reference in `build-docs/html`. -- [API redesign](docs/api-redesign.md): queue, frame, ownership, and backend contracts. -- [API policy](docs/api-policy.md), [migration notes](docs/migrating.md), and - [contributing](CONTRIBUTING.md). -- [Tests, tablet example, and benchmarks](docs/testing.md). -- [IDE integration](IDE_INTEGRATION.md): step through example and window creation code with Zed's debugger. +Flags zero creates a GPU window without choosing a graphics API. The application +attaches a Vulkan surface or Metal layer, or uses `yasl_gl_surface_create` to create +an OpenGL window/context. `YASL_SURFACE_CPU` gives the window a pixel buffer: draw into +`yasl_cpu_pixels` and call `yasl_cpu_present`. Native handles are available through typed extension headers. -## Build a window +**Status:** pre-release 0.1; source and ABI compatibility are not promised. +Use matching headers and library. Supported operations depend on the backend, +server, and optional dependencies; check results and `yasl_get_error`. -Use a C23 compiler, Meson 1.6 or newer, and Ninja. Linux needs the development -packages for Wayland (`wayland-client`, `wayland-cursor`, `xkbcommon`) or X11 -(`x11`, `xi`, `xkbcommon`, `xext`; optional `xrandr`). Optional `dbus-1` enables -desktop notifications and appearance queries on Linux. Cocoa requires macOS 14 or -newer and the system SDK, with a Metal-capable device for window drawing. +Build with a C23 compiler, Meson 1.6+, and Ninja: ```sh meson setup build meson compile -C build -./build/yasl-notepad +./build/yasl-hello meson test -C build --print-errorlogs ``` -Linux builds automatically include backends with available dependencies. Require -one with `-Dwayland=enabled` or `-Dx11=enabled`; omit one with `disabled`. Runtime -selection chooses the native session and allows fallback, or you can request an -explicit backend with `yasl_display_spec.backend`. - -Include `<yasl/app.h>` and pass application callbacks to `yasl_run()`. -The complete [hello.c](examples/hello.c) describes its window in -`yasl_app_desc.window`, lets the runner quit when it is closed, and fills every -pixel with an RGB gradient in its frame callback. - -Toplevel creation sizes are logical pixels; drawing and input use device pixels. -Both loop styles consume one display event FIFO and receive `yasl_frame`; -`yasl_frame_present()` submits its target. 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. +Linux needs Wayland or X11 development packages; EGL and wayland-egl enable GL, +and the Vulkan SDK enables its typed bridge. Cocoa needs macOS 14+ and its SDK. -## License +- [Getting started](docs/getting-started.md): creation, linking, and rendering. +- [Application guide](docs/guide.md): ownership, events, GPUs, pen, and backend limits. +- [Testing](docs/testing.md): unit tests and isolated GPU/backend tests. +- [API policy](docs/api-policy.md) and [contributing](CONTRIBUTING.md). +- Public headers live in [include/yasl](include/yasl); examples live in [examples](examples). -[MIT](LICENSE). +MIT licensed; see [LICENSE](LICENSE). diff --git a/docs/api-policy.md b/docs/api-policy.md index 3e510f3..f2f4d92 100644 --- a/docs/api-policy.md +++ b/docs/api-policy.md @@ -1,38 +1,18 @@ # Public API policy -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 public headers. -Native backend accessors are exported only for built backends. Installation -copies both libraries, public headers, enabled native headers, and pkg-config metadata. +YASL is pre-release 0.1. Source and ABI compatibility are not guaranteed. Rebuild +consumers against matching installed headers/library; do not serialize public events +or assume their layout is a stable file/network format. -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 release version is declared in -`meson.build`. +Only installed include/yasl headers are public. The surface library exposes an +opaque surface and one tagged event record, with direct C arguments and ordinary +pointer/integer results. Native backend headers, geometry records, callbacks, loops, +and drawing targets under src are private even when a static archive contains them. +The shared library exports exactly the installed public declarations. -See [migration notes](migrating.md) for window creation, portable keyboard, and -fractional scroll API changes. - -API changes should keep ordinary CPU drawing usable with only `<yasl/yasl.h>` -and the C standard library. Prefer an explicit capability and an ordinary -unsupported result over backend names in portable application logic. Native -rendering and handles belong in the extension headers. Avoid adding engine, -widget, renderer, or application policy to the surface layer. - -Every public addition or behavior change needs: - -- A header contract describing arguments, result, units, lifetime, threading, - callback timing, and unsupported behavior where applicable. -- 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 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. -Callbacks and successful returns do not imply confirmed screen presentation. -See `yasl_timing.source` for provenance. Future changes that strengthen error -reporting must preserve these distinctions in documentation. +Do not restore removed runner/frame/display APIs or compatibility aliases. Keep +rendering bridges separate from the portable surface/input core and preserve the +application's ownership of GPU resources and its loop. Public additions need clear +units, ownership, thread/callback timing, errors and unsupported behavior, useful +examples, and behavior coverage. Update the guide and test installed shared/static +consumers and exports. Changes affecting Cocoa need a native macOS build/test. diff --git a/docs/api-redesign.md b/docs/api-redesign.md deleted file mode 100644 index a111a92..0000000 --- a/docs/api-redesign.md +++ /dev/null @@ -1,147 +0,0 @@ -# Public API redesign - -This is the implementation contract for the pre-release API revision. The -portable layer owns event ordering, queued payloads, surface identities, and -frame validation. Backends translate native behavior into that contract. - -## One event stream - -`yasl_display_poll_event(display, &event)` consumes one display-wide FIFO. Input, -window resize/close/state, drag hover/drop, composition, clipboard results, and -display quit/reopen/loss use the same `yasl_event`. Events have an increasing -`sequence` assigned when YASL queues them, and a monotonic `time_ns`. Sequence -orders capture by YASL, not unknowable ordering between OS threads or devices. -The runner consumes this same queue; it does not drain separate queues by window. - -Event strings are copied on enqueue and borrowed until the next successful -display poll or display close, even if the recipient is destroyed. Destroying a -surface removes its undelivered events. A callback may destroy its recipient; -it must stop using that surface. Draws remain synchronous to satisfy native -live-resize rules; the runner drains queued events before invoking a frame. - -Legacy surface polling consumes input from the same queue and discards pending -non-input notifications, which were already delivered through legacy callbacks. -Applications must choose one polling style; discarding prevents idle spin and -unbounded retention in old manual loops. Legacy window -callbacks remain for migration, but applications should choose queue delivery -and a draw callback. Backend notifications must queue before calling a legacy -callback, and must not access a surface after a callback that can destroy it. - -## One frame contract - -Both manual drawing and `yasl_app_desc.frame` receive `struct yasl_frame`. -Its `target` describes CPU pixels (NULL for native rendering), device-pixel size, -and row stride. The frame also carries frame index, scale, and an estimated -animation timestamp. Native drawables are queried through `<yasl/native.h>`; -there is no untyped GPU layer or public internal pointer in the portable frame. - -`yasl_frame_present(frame, rects, count)` submits the exact active frame once. -NULL with zero count means full damage; a non-NULL array with zero count means -no damage. NULL with nonzero count and negative extents fail with EINVAL. -More than the backend rectangle limit falls back to full damage after validating -every rectangle; damage must never be silently lost. Targets expire at callback -return. Pixels must always describe a complete frame. - -`yasl_surface_request_frame` is the normal coalescing redraw operation. -`yasl_surface_set_pacing` returns success/failure and works with either loop -style. Continuous drawing uses native pacing where available and a refresh-based -timer otherwise. On-demand surfaces require explicit invalidation. - -## Identities, posting, and shutdown - -`yasl_surface_id(surface)` returns a nonzero identity unique within its display. -`yasl_display_post(display, &message)` is thread-safe; messages target an identity -(zero means display-wide), never a borrowed surface pointer. The display must -remain alive during posting, and producer threads must stop before display close. -After a successful post the optional release callback runs exactly once on the -display thread, after consumption or cancellation (including surface destruction -and shutdown). A failed post leaves payload ownership with the caller. Posting -to a retired identity fails. The display queue owns only its copy of the message; -the release callback defines payload cleanup. Release may post, but must not -poll, dispatch, or destroy YASL objects. Legacy `yasl_post_event` adapts to -this mechanism, with the caller responsible for its borrowed target lifetime. - -Application callbacks may return CONTINUE, QUIT, or ERROR. ERROR makes -`yasl_run` return failure after cleanup. `yasl_app_exit(display, result)` permits -timers, fd callbacks, and frames to request the same orderly termination. -The optional initial window is available through `yasl_app_window(display)`. -Surface userdata is independent of legacy callback userdata and accessible -through `yasl_surface_set_userdata` / `yasl_surface_userdata`. - -## Window requests and units - -New code spells toplevel creation size `size_logical`; `size` remains an alias -for migration. Framebuffer geometry, pointer coordinates, popup placement, -damage, IME carets, and drag-image coordinates are device pixels as documented -on their structures. Conversion helpers remain explicit. - -`yasl_surface_set_size(surface, logical_size)`, -`yasl_surface_set_fullscreen(surface, enabled)`, -`yasl_surface_set_maximized(surface, enabled)`, and -`yasl_surface_minimize(surface)` return whether the native request was issued. -They set errno on failure: EINVAL for invalid input/non-toplevel surfaces, -ENOTSUP for unsupported requests, or an OS error. Success is acceptance of a -request, not a promise that the window manager honored it. Resulting size/state -is reported through events. No portable absolute window-position setter exists. - -## Clipboard and composition - -`yasl_clipboard_set(display, selection, text)` returns whether a copied UTF-8 -offer was issued, with errno on failure. `yasl_clipboard_request(surface, -selection)` returns a nonzero request ID, or zero with errno on failure. -The backend operation `display_request_paste(d, selection, surface, id)` returns -true only after accepting responsibility for one completion. It completes via -`yasl_surface_complete_paste(surface, id, status, text, len)`; status is -YASL_TRANSFER_OK, UNAVAILABLE, or FAILED. Results use YASL_EVENT_PASTE and its -`.paste` payload (data/len/request_id/status); legacy `.text` aliases data/len. -Surface destruction cancels delivery and native transfers. A backend must -complete errors as well as success, including empty text. Request IDs are -monotonic per display. Legacy paste uses ID zero. External Linux transfers time out after 30 seconds. - -YASL_EVENT_COMPOSITION carries copied UTF-8 preedit text, byte selection offsets -(both -1 if unavailable), and an active flag. Each native change queues a -snapshot, including composition ending; committed text remains YASL_EVENT_TEXT. -The query functions remain migration helpers. - -## Backend boundary - -Common helpers declared in `common/backend.h`: - -* `yasl_surface_notify_configure(s, geometry)` -* `yasl_surface_notify_close(s)` -* `yasl_surface_notify_state(s, state)` -* `yasl_surface_notify_drag(s, event, position_px)` -* `yasl_surface_notify_drop(s, event, position_px)` -* `yasl_surface_notify_composition(s, text, begin, end)` (NULL/empty ends it) -* `yasl_surface_draw(s, frame)` (fills frame metadata and invokes draw) -* `yasl_display_notify_event(d, YASL_EVENT_DISPLAY_QUIT/REOPEN/LOST)` -* `yasl_surface_complete_paste(s, request_id, status, text, length)` - -Backends keep using their existing input_queue; a new `surface_input_queue` -operation exposes that queue to common code for attaching its event publisher. -Backends must replace direct lifecycle/draw/drag callbacks with these helpers, -without weakening their native destruction guards. They implement optional -surface_set_size/fullscreen/maximized/minimize and display_copy_text / -display_request_paste operations. Common wrappers validate arguments and -report unsupported operations uniformly. - -Linux backends are compiled and tested independently and together. Cocoa changes -can be inspected here but require macOS compilation/runtime verification. - -## Validation - -The combined Linux configuration passes all 26 Meson tests, including isolated -Weston/Xwayland integration, runner error/cleanup paths, event ordering, -clipboard outcomes, composition, and destruction during callbacks. Separate -Wayland-only and X11-only builds pass their unit suites and the final public -export, C++11, and event-contract checks. - -AddressSanitizer and UndefinedBehaviorSanitizer pass the common contract tests -and the isolated backend integration suite. Leak detection was disabled because -LeakSanitizer is unavailable in this execution environment. Doxygen builds with -warnings treated as errors. A staged install links both shared and static -consumers using only the installed headers and pkg-config metadata. - -Cocoa code and native regression tests were updated and reviewed, but were not -compiled or executed: macOS validation remains required. Existing X11 INCR and -Wayland arbitrary-size limitations are reported explicitly rather than hidden. diff --git a/docs/getting-started.md b/docs/getting-started.md index d1f284d..bb7f095 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,113 +1,71 @@ # Getting started -YASL gives a C application desktop windows with CPU pixel targets, scale-aware -geometry, queued input, clipboard text, and optional frame pacing. It fits -software-rendered tools such as editors, terminals, drawing applications, and -custom interfaces that already have their own renderer. A window needs neither -an OpenGL context nor a graphics device supplied by the application. +YASL creates desktop surfaces and delivers queued input. You own the event loop, +renderer, graphics device/context use, and application lifetime. -The core headers expose no window system types. Native handles and Metal drawing -are separate opt-in extensions. YASL currently supports Linux (Wayland and X11) -and macOS (Cocoa); it does not provide widgets, font shaping, audio, an OpenGL -context API, or a Vulkan surface API. It is pre-release software; see the -[API policy](api-policy.md) before distributing a library that depends on its ABI. - -## Build and run - -Use a C23 compiler, Meson 1.6 or newer, and Ninja. GCC and Clang can build the library. On Linux install either backend's development dependencies: - -| Backend | pkg-config dependencies | -| --- | --- | -| Wayland | `wayland-client`, `wayland-cursor`, `xkbcommon` | -| X11 | `x11`, `xi`, `xkbcommon`, `xext`; optional `xrandr` | -| Linux desktop services | Optional `dbus-1` for notifications and appearance | -| Cocoa | macOS 14+ SDK with AppKit, Metal, QuartzCore, and UserNotifications; Metal-capable device | +Build with a C23 compiler, Meson 1.6+, and Ninja. Linux backend dependencies are +wayland-client, wayland-cursor, xkbcommon, or x11, xi, xext, xkbcommon (optional +xrandr). Optional egl and wayland-egl enable GL contexts; Vulkan SDK development +files enable the Vulkan bridge. Optional dbus-1 enables Linux desktop services. +Cocoa requires macOS 14+ and the AppKit, Metal, QuartzCore, OpenGL, and +UserNotifications system frameworks. Optional Vulkan SDK enables MoltenVK WSI. ```sh meson setup build meson compile -C build ./build/yasl-hello +./build/yasl-game meson test -C build --print-errorlogs ``` -Each Linux backend defaults to `auto`, meaning build it when its dependencies -are available. Require one with `-Dwayland=enabled` or `-Dx11=enabled`; omit one -with `-Dwayland=disabled` or `-Dx11=disabled`. At least one backend is required. -`-Ddbus` works the same way for the optional D-Bus desktop services. -Cocoa is selected on macOS and requires no extra third-party dependencies. -Its minimum deployment target is macOS 14. Set `MACOSX_DEPLOYMENT_TARGET=14.0` -(or newer) before configuring if your toolchain defaults to an older target. -CPU pixel targets are presented through the system's Metal device; applications -do not need to create or manage that device themselves. +Backend options `-Dwayland=enabled|disabled|auto`, `-Dx11=...`, and `-Ddbus=...` +control dependency selection. At least one window backend must be built. Runtime +AUTO selects the native session and falls back to another built Linux backend. +For an explicit choice call `yasl_set_backend` before creating any surface. -The complete beginner example is [hello.c](../examples/hello.c). It lets -`yasl_run()` create its window from `yasl_app_desc.window`, fills every pixel -with an RGB gradient, 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. +## A window -## Link an application +```c +#include <yasl/yasl.h> + +int main(void) +{ + yasl_surface *s = yasl_surface_create("Example", 800, 600, 0); + if (!s) return 1; + /* Attach a GPU renderer here and draw on each REFRESH. */ + yasl_event event; + while (yasl_wait_event(&event, -1) >= 0 && + event.type != YASL_EVENT_CLOSE && event.type != YASL_EVENT_QUIT) { + } + /* Destroy GPU objects before shutting down. */ + yasl_shutdown(); + return 0; +} +``` -Install the libraries, public headers, and pkg-config metadata into your prefix: +The first creation initializes a shared backend. More windows use that backend; +there is no public display/context object. Closing the final window does not +implicitly terminate the application or reset configuration. `yasl_shutdown` +releases shared resources and restores defaults. + +[hello.c](../examples/hello.c) draws into a CPU surface's pixel buffer on every REFRESH. [game.c](../examples/game.c) creates a GL context, binds it, +loads procedures, draws, and swaps. [tablet.c](../examples/tablet.c) retains a canvas +and draws pressure-sensitive pen/eraser strokes. [notepad.c](../examples/notepad.c) +uses a worker queue, wakeups, clipboard, IME, and its own blink deadline. + +## Link and install ```sh 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) -./hello -``` - -The prefix must be writable. For a custom prefix, add its pkg-config directory -to `PKG_CONFIG_PATH` and its library directory to the runtime loader path. -Use `pkg-config --static --libs yasl` for static consumers so backend dependencies -are included. Installed native accessor headers match the built backends. - -A Meson application can instead vendor the source under `subprojects/yasl`: - -```meson -project('my-app', 'c', default_options: ['c_std=c23']) -yasl = subproject('yasl').get_variable('yasl_dep') -executable('my-app', 'main.c', dependencies: yasl) -``` - -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. The libraries, public headers, and pkg-config metadata are installed together. - -## The application cycle - -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, `YASL_APP_ERROR` for failure, or - `YASL_APP_CONTINUE` to keep running. -4. In `frame`, fill every pixel in `frame->target.pixels` with premultiplied - `0xAARRGGBB` and present once with `yasl_frame_present(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 -struct yasl_app_desc desc = { - .app_id = "org.example.app", - .userdata = &app, - .init = init, - .event = event, - .frame = draw, -}; -return yasl_run(&desc); ``` -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. +Use a writable prefix. For a custom prefix configure PKG_CONFIG_PATH and the +runtime library search path. Static consumers use `pkg-config --static --libs yasl`. +Public headers also compile as C++11. Native headers and the Vulkan header are +installed only when their respective support is built. -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) -for each function's contract. To build the searchable HTML reference, install -Doxygen and run `doxygen Doxyfile` from the repository root. Open -`build-docs/html/index.html`; Doxygen is not needed to build the library. +See the [guide](guide.md) for geometry, event lifetime, GPU ownership, input, and +backend limits. `doxygen Doxyfile` builds the reference in build-docs/html. diff --git a/docs/guide.md b/docs/guide.md index 1b64880..d0db071 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -1,476 +1,141 @@ -# Application guide - -The contracts in [yasl.h](../include/yasl/yasl.h), [loop.h](../include/yasl/loop.h), -[timing.h](../include/yasl/timing.h), [desktop.h](../include/yasl/desktop.h), and -[native.h](../include/yasl/native.h) are the reference for individual calls. All examples here use the display's opening -thread, which must be the main thread on macOS. Userdata stays owned by the -application. Copy borrowed data before retaining it beyond its documented life. - -## Backend selection and capabilities - -Zero-initialize `yasl_display_spec` or pass NULL to select automatically. On -Linux, `WAYLAND_DISPLAY`, `WAYLAND_SOCKET`, or `XDG_SESSION_TYPE=wayland` makes -YASL try Wayland first; otherwise it tries X11 first. It falls back to the other -built backend if opening fails. On macOS it uses Cocoa. Explicit -`YASL_BACKEND_WAYLAND`, `YASL_BACKEND_X11`, or `YASL_BACKEND_COCOA` requests never -fall back. Query the result with `yasl_display_backend()`. - -Use `yasl_display_caps()` for optional behavior. A bit means backend/server -support, not that a device is attached or that the current input state allows -an operation. Always check the operation's return value too. - -| Capability | Meaning | Wayland | X11 | Cocoa | -| --- | --- | --- | --- | --- | -| `YASL_CAP_POPUPS` | Child popup surfaces | Yes, input grab required | Yes | Yes | -| `YASL_CAP_DRAG` | Surface-drag source and target events | Data device v3 | Yes | Yes | -| `YASL_CAP_DRAG_CARRY` | Carry a toplevel with a surface drag | Optional toplevel-drag protocol | Yes | Yes | -| `YASL_CAP_PLACE_WINDOW` | Place a window under the pointer | No | Yes | Yes | -| `YASL_CAP_PRIMARY_SELECTION` | Separate primary text selection | Optional primary-selection protocol | Yes | Named pasteboard emulation | -| `YASL_CAP_CSD` | Backend advertises client decoration support | Yes | No | Yes | -| `YASL_CAP_NATIVE_GRAPHICS` | Application-managed native drawables | No | No | Metal | -| `YASL_CAP_FRAME_PACING` | Display/compositor-driven drawing | Yes | No | Yes | -| `YASL_CAP_TABLET` | Tablet event translation | Optional tablet-v2 protocol | XInput 2 | Yes | - -This table describes the implementation, not universal operating-system -promises. Wayland protocols and XInput availability depend on the running -server. A capability does not guarantee every related action: Cocoa currently -returns false for interactive resize and window-menu requests even though -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`. Set `window` to a `yasl_window_spec` for a single toplevel, -which the runner creates before `init` with its first frame requested; -`yasl_run()` returns an error if creation fails. Create further surfaces in the -optional `init`, handle `YASL_EVENT_*` notifications in `event`, and render into -`frame->target.pixels` in `frame`. Present with `yasl_frame_present(frame, NULL, -0)` and return `YASL_APP_QUIT` from an event handler to exit. Without an `event` -callback, closing any window stops the runner. The runner handles dispatch, queued event delivery, resize redraws, -and remaining surface/display cleanup. Display quit stops the runner; connection -loss and dispatch failures return an error after cleanup. The runner delivers -the same display-wide FIFO consumed by `yasl_display_poll_event()`, including -window state, drag hover, composition, clipboard completion, and display lifecycle -events. `event.sequence` increases in enqueue order across all windows; it is -YASL capture order, not a claim about ordering between native producers. -Return `YASL_APP_ERROR` on application failure, or call -`yasl_app_exit(display, YASL_APP_ERROR)` from a frame, timer, or fd callback. -Cleanup still runs, and `yasl_run()` returns failure. During `init`, -`yasl_app_window(display)` exposes the optional initial window. Application data -can be supplied in `window.userdata` or attached with -`yasl_surface_set_userdata()`; it is independent of callback userdata. - -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, `size_logical`, optional `yasl_window_cbs` table in `cbs`, and userdata in -`ud`. One display can own multiple windows with separate callback tables. - -The window copies its callbacks during creation. Its callbacks cannot run before -creation returns, so the application can store the returned surface first. -Userdata stays borrowed until callbacks are replaced or the surface is destroyed. -Read initial geometry with `yasl_surface_geometry()`; creation does not guarantee -an initial `configure` notification. Request the first redraw once application -state is ready. `yasl_surface_set_callbacks()` replaces callbacks later and also -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`. -Replacing callbacks on a runner-owned surface fails with `EBUSY`. -With a manual loop, install just the draw callback and drain the display FIFO -after dispatch; lifecycle and input share the same event handler logic. - -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 | -| --- | --- | -| Toplevel `yasl_window_spec.size` | Logical pixels | -| `yasl_geometry.size`, framebuffer size | Device pixels | -| Pointer position, caret, window-menu position | Device pixels, relative to the surface | -| Damage rectangles | Device pixels, relative to the draw target | -| All popup fields, including `size` | Device pixels; anchor is relative to the parent | -| Drag image size, hotspot, carry/place offsets | Device pixels | -| `scale_120` | 120 = 1x, 180 = 1.5x, 240 = 2x | - -The origin is the top left, x increases right, and y increases down. Use -`yasl_surface_geometry()` for the current size and scale, and update cached -layout on `configure`. The native system may choose a different size from the -request. X11 currently reports unit scale; automatic output DPI scaling is not -implemented there. - -```c -struct yasl_geometry geometry = yasl_surface_geometry(surface); -int padding_px = yasl_scale_to_device(8.0, geometry.scale_120); -int logical_width = yasl_scale_to_logical(geometry.size.x, geometry.scale_120); -``` - -Scale conversion rounds to the nearest integer, with ties away from zero, -saturates out-of-range finite results, and returns zero for a zero scale. -`yasl_scale_to_device()` also returns zero for non-finite logical coordinates. - -## Drawing and pacing - -`yasl_surface_request_frame()` coalesces requests. `yasl_display_flush()` -services them when the backend can draw. During a native resize, dispatch can -also draw synchronously. Avoid assuming callbacks occur only in flush. - -The CPU target is tightly packed premultiplied `0xAARRGGBB`. Row stride is -`target.size.x * sizeof(uint32_t)`. For example, opaque red is `0xffff0000`, -50% transparent red is approximately `0x80800000`, and transparent black is -`0x00000000`. The numeric format describes a 32-bit value, not a portable byte -sequence. Keep your application's durable content separately: a new target may -contain no previous frame contents. - -```c -static void draw(struct yasl_surface *surface, struct yasl_frame *frame, void *ud) -{ - (void)ud; - size_t count = (size_t)frame->target.size.x * frame->target.size.y; - for (size_t i = 0; i < count; i++) - frame->target.pixels[i] = 0xff306090; - if (!yasl_frame_present(frame, NULL, 0)) { - /* Arrange application error handling after returning. */ - } -} -``` - -Present the exact active frame at most once during its callback. Do not mutate -the target identity or retain frame/pixel pointers after return. NULL rectangles -with count zero mean full damage; a non-NULL array with zero count means no damage. -All supplied extents must be nonnegative. Rectangles are clipped, and more than -`YASL_DAMAGE_MAX` rectangles fall back to full damage after all are validated. -Damage may reduce uploads, but every target must contain complete valid pixels. -Native rendering uses the same frame, with a drawable queried in the extension. - -`yasl_surface_set_pacing()` works in either loop style. ON_DEMAND coalesces -explicit requests and sleeps when idle. CONTINUOUS uses native display signals -where available and a refresh-based timer otherwise, with about 60 Hz as a -fallback estimate. It reports allocation/argument failures instead of silently -ignoring surfaces outside the runner. Suspension can suppress actual drawing. -Legacy `yasl_surface_present()` and `yasl_surface_schedule_redraw()` remain -migration adapters; new code uses the frame presentation and request functions. - -`yasl_surface_read_pixels()` copies the last presented CPU frame, for example -for a screenshot or a debugging tool. Call it outside draw callbacks. A frame -is available from a successful present until the next draw starts, and again -once that draw presents. Call it with no buffer first to learn the extent. The -copy is YASL's frame, not the composited screen, and native rendering has none. - -`yasl_surface_timing()` reports estimates and their provenance. All timestamps -are monotonic nanoseconds. `YASL_TIMING_SUBMISSION` is CPU submission time, -`YASL_TIMING_FRAME_CALLBACK` is a compositor pacing signal, and only -`YASL_TIMING_PRESENTATION` confirms compositor presentation. `UNKNOWN` means no -sample. `refresh_ns` and `next_present_ns` remain estimates. Use -`yasl_surface_frame_time_ns(surface, yasl_loop_now_ns())` as a stable animation -clock; it does not establish actual scanout time. - -## Input, text, and payload lifetime - -Dispatch receives native input; `yasl_display_poll_event()` drains the display -FIFO without waiting. Drain it once for the whole display, including multiple -windows and popup input. `event.surface` identifies the live recipient and -`surface_id` is its stable identity. Destruction cancels undelivered recipient -events. `yasl_surface_poll_event()` remains an input-only migration adapter and -discards pending non-input notifications, which legacy callbacks already -delivered. Do not mix the two polling styles. - -Read the union member selected by `event.type`. `time_ns` is monotonic and -`sequence` orders capture across the display. TEXT and DROP use `.text`, PASTE -uses `.paste`, and COMPOSITION uses `.composition`. Each string is copied when -queued, NUL-terminated, and borrowed until the next successful display/surface -poll or display close. Dispatch, unsuccessful polls, and recipient destruction -do not invalidate the last polled string. Copy it to retain it longer. - -Use `YASL_EVENT_TEXT` for document text insertion, including committed IME text. -It contains UTF-8 and can contain several characters. Key actions are for -commands and held-key state; releases and repeats have separate tags. A text -commit need not correspond to one key event, and a key need not produce text. - -`key.key` is a portable physical identity from -[keycodes.h](../include/yasl/keycodes.h), included by `<yasl/yasl.h>`. Names follow -US keyboard positions: `YASL_KEY_A` identifies the same position on a French -layout even though it types Q. Modifiers do not change this identity. Keypad -keys and left/right modifiers have distinct names. Unknown native keys report -`YASL_KEY_UNKNOWN`; `native_code` preserves their backend-specific identity -(Wayland evdev code, X11 server keycode, or Cocoa virtual keycode). - -`codepoint` and `shifted` are optional Unicode scalar labels for the active -layout without modifiers and with Shift. Zero means no single printable label, -including function keys, unavailable translations, and mappings that produce -multiple characters. Dead keys may have a spacing label or zero. These fields -are useful for displaying shortcuts or -choosing layout-sensitive commands; they are never document text. For example: - -```c -if (event.type == YASL_EVENT_KEY && event.key.action == YASL_KEY_PRESS && - event.key.key == YASL_KEY_ESCAPE) { - running = false; -} -/* Insert event.text.data[0..event.text.len) only for YASL_EVENT_TEXT. */ -``` - -Wayland uses the supplied XKB layout and locale compose rules for ordinary text -and dead keys, with text-input-v3 for IME commits. Compose rules use `LC_ALL`, -`LC_CTYPE`, then `LANG`, falling back to `C.UTF-8`. X11 uses the X input method -when available; Cocoa uses AppKit text services. X11 input methods use the -application's `LC_CTYPE` locale. Initialize it before opening a display when -using locale-specific XIM, for example with `setlocale(LC_CTYPE, "")`; YASL does -not change the process locale. IME availability and system configuration still -determine which compositions work. Physical key events -remain available for commands; input methods may consume keys while composing. - -Pointer events use the `YASL_EVENT_POINTER_*` types and share `event.pointer`, -with `device` distinguishing mouse, touch, pen, and eraser. `POINTER_DOWN` and -`POINTER_UP` carry a `YASL_BUTTON_*` value: `PRIMARY` is the left button, pen tip, -or touch contact. Other types leave `button` meaningless. Every pointer event -also carries `buttons`, a `YASL_BUTTON_BIT()` mask of the buttons held after -that event, tracked from the queue's down and up events. Use it to tell a drag -from a hover. Count clicks against `yasl_display_double_click_ms()`, the -system interval: XSETTINGS on X11, the KDE or GNOME setting through the -Settings portal on Wayland, and the system value on Cocoa. It falls back to -`YASL_DOUBLE_CLICK_MS_DEFAULT` (400 ms). The query can wait briefly on Linux, so -read it when a window gains focus. `YASL_EVENT_INPUT_RESET` clears the mask. On Wayland, -`POINTER_LEAVE` also clears it, because the compositor sends leave during a -press only when it takes the pointer away. X11 and Cocoa keep the press across -leave and still deliver the release. `POINTER_MOTION` reports `delta`, the device-pixel movement since -the previous sample, which restarts from zero after enter, leave, or reset. -`yasl_surface_set_pointer_mode()` with `YASL_POINTER_RELATIVE` hides and locks the -cursor while the surface is focused; motion then carries unbounded `delta` and -`pos` stays at the lock position. Wayland implements it with the -relative-pointer and pointer-constraints protocols, reporting the compositor's -accelerated motion in device pixels. The compositor decides when the lock -engages; until then, and while it is released on focus loss, motion stays -absolute with a computed `delta`. X11, Cocoa, and compositors without those -protocols return false and keep the current mode. Pen -pressure is normalized to 0..65535, signed tilt is in degrees, and missing axes -are zero. Pressure-only changes can produce motion. Zero pressure alone does -not mean release. This interface does not expose simultaneous touch contacts. - -For `YASL_EVENT_POINTER_SCROLL`, read `pointer.scroll.x` and `.y`: fractional line offsets, -positive right and down, after system scroll preferences. Both axes may be -nonzero in one event. Preserve fractions and choose your application's own -pixels per line; one wheel detent normally gives one line. `button` is unused -for scroll. Wayland high-resolution wheel values take precedence over duplicate -discrete/raw descriptions. Smooth Wayland offsets and precise Cocoa offsets -use ten native units per line. X11 currently reports whole wheel steps, so it -does not provide the same smooth-scroll precision as the other backends. - -Cancel held keys, buttons, active strokes, and composition on `YASL_EVENT_INPUT_RESET`, -focus loss, or pointer leave as appropriate to your application. RESET has no -union payload. It is emitted after input-queue allocation failure and certain -device interruptions; ignoring it can leave application input stuck. - -## Clipboard, file drops, and IME - -`yasl_clipboard_set(display, selection, text)` copies a NUL-terminated UTF-8 -offer and reports failure through its return and errno. Wayland may require a -recent input serial. `yasl_clipboard_request(surface, selection)` returns a -nonzero request ID, or zero with errno when rejected. Each accepted request -produces one `YASL_EVENT_PASTE` carrying `.paste.request_id`, `.status`, `.data`, -and `.len`, unless its target/display is destroyed. OK includes empty text; -UNAVAILABLE means no text offer, and FAILED means transfer/decoding failure. -External Linux transfers have a 30-second timeout. X11 does not implement INCR -and reports unsupported large transfers as FAILED. Legacy STRING uses Latin-1 -conversion. Malformed UTF-8 completes as failure. - -Unsupported primary selection reports ENOTSUP and never aliases the clipboard. -Cocoa uses a YASL named pasteboard for primary selection. Legacy copy/paste calls -remain adapters; legacy paste completions carry request ID zero. - -External file drag hover uses `YASL_EVENT_DROP_HOVER`; actual file URLs -arrive as `YASL_EVENT_DROP`. `text.data` is a `text/uri-list` string separated by -CRLF, not a list of native paths. `yasl_drop_next_path()` walks it and decodes -each local `file:` URL into a path. It skips comments, other schemes, remote -hosts, and malformed lines: - -```c -const char *cursor = event.text.data; -char path[4096]; -size_t len; -while ((len = yasl_drop_next_path(&cursor, path, sizeof(path))) != 0) { - if (len >= sizeof(path)) break; /* Too long; grow the buffer instead. */ - /* Open path. */ -} -``` - -Decoded paths are raw bytes and may contain any character the file system -allows, including newlines. Quote them before pasting into a shell. - -Handle `YASL_EVENT_COMPOSITION` for immutable UTF-8 preedit snapshots, byte -selection offsets, and composition ending. Missing selection information uses --1 offsets. Insert only committed `YASL_EVENT_TEXT` into the document. Set the -candidate-window caret with `yasl_surface_set_ime_caret()` in device pixels. -X11 uses XIM preedit callbacks when offered; fallback input methods may provide -no inline composition. The old preedit getters remain migration helpers. - -## Popups and surface drags - -Create popups from a live parent, normally in response to input. All fields in -`yasl_popup_spec` use device pixels. `size` includes transparent margins; -`content_offset` and `content_size` describe the actual content rectangle used -for positioning. A zero content-size axis extends to the corresponding size -edge. `beside = true` prefers the anchor's right edge; false prefers below. -Placement may flip or slide to stay on screen. Wayland requires a recent input -serial to grab a popup, and older protocols may reject repositioning. Destroy -children before parents. The close callback requests application destruction; -it does not automatically free the popup. - -`YASL_EVENT_DRAG` handles a YASL surface drag; `YASL_EVENT_DROP_HOVER` handles -external data hover. Legacy callbacks remain migration adapters. `yasl_display_drag_start()` is intended for window/tab tear-off -workflows, not arbitrary MIME data sources. Keep transferred application state -in your own registry. Start from a recent pointer press, and check the boolean -result even when `YASL_CAP_DRAG` is present. Supply an optional copied image and -completion listener. One active drag is supported per display. A successful -start calls a non-NULL `end` exactly once, including explicit cancel or display -close; a failed start never does. `DROPPED` does not prove that another -application accepted your application object. - -Where `YASL_CAP_DRAG_CARRY` is present, `yasl_surface_drag_carry()` can attach a -toplevel to the active drag. Otherwise an image can represent it. With -`YASL_CAP_PLACE_WINDOW`, `yasl_surface_place_under_pointer()` requests placement -relative to the pointer. These calls may fail due to current input/drag state. -Completion can run synchronously during cancel/close; keep listener userdata -alive until then and do not destroy the display recursively. - -## Timers and file descriptors - -Include `<yasl/loop.h>`. Use `yasl_display_loop(display)` for application timers -so they participate in display dispatch. Intervals are nanoseconds; the -`YASL_MS_TO_NS(ms)` macro converts a nonnegative integer millisecond value. - -```c -static uint64_t tick(void *ud, yasl_timer_id id, uint64_t interval_ns) -{ - (void)id; - yasl_surface_schedule_redraw(ud); - return interval_ns; /* Return zero for a one-shot timer. */ -} -/* Check zero for failure. This timer is canceled with the surface. */ -yasl_timer_id timer = yasl_surface_add_timer(surface, YASL_MS_TO_NS(16), tick, surface); -``` - -A timer callback receives the scheduled interval, not actual elapsed time. A -nonzero return repeats/reschedules; zero retires it. Missed periods coalesce. -Timers can add/delete timers, including themselves. Loop timers end when retired, -deleted, or the loop is destroyed; surface timers also end on surface destruction. -Userdata remains application-owned. Delete surface timers with -`yasl_surface_del_timer()` and ordinary timers with `yasl_loop_del_timer()`. - -File watches use `<poll.h>` masks. `yasl_loop_add_fd()` returns a positive watch -ID or -1. Its callback receives `revents`, including error/hangup flags; its -integer return is ignored. Consume readiness or disable/delete the watch. -The loop never closes application file descriptors: delete the watch before -closing its descriptor. Ready callbacks may add or remove watches. Do not call -`yasl_loop_step()` or `yasl_loop_run()` recursively. - -Every backend's dispatch wakes when a loop descriptor becomes ready. Cocoa -waits for native events and mirrors the loop's descriptors in a kqueue while it -waits. If kqueue rejects a descriptor, Cocoa bounds the wait to 10 ms instead. - -Loop wake and post calls work from any thread. `yasl_loop_kick()` only wakes the loop, and it -is also safe in a signal handler. `yasl_loop_post()` queues a function to run -on the owning thread during a later step or dispatch. Use it to hand results -from worker threads to the loop: - -```c -static void results_ready(void *ud) -{ - worker_drain(ud); /* Runs on the loop thread. */ -} -/* On the worker thread; false means allocation failed. */ -yasl_loop_post(loop, results_ready, worker); -``` - -`yasl_loop_watch_signals()` delivers POSIX signals to a callback on the loop -thread. It installs a process-wide handler for each signal, with `SA_RESTART`, -and restores the previous action on unwatch. YASL leaves thread signal masks -alone: a watched signal must stay unblocked in at least one thread. Only one -loop can watch a given signal at a time. - -## Worker messages and window requests - -Capture `yasl_surface_id(surface)` on the display thread and pass that value to -workers. `yasl_display_post(display, &message)` accepts an identity instead of a -borrowed surface pointer. Zero targets the display. On success, optional -`message.release(payload)` runs exactly once on the display thread after delivery -or cancellation, including shutdown. It may post messages but must not poll, -dispatch, or destroy YASL objects. Failed posting leaves ownership with the caller. -Stop and join producers before closing the display. The same API works with a -manual loop and with the runner; `yasl_post_event()` is a legacy adapter. - -Toplevel requests `yasl_surface_set_size(surface, logical_size)`, -`yasl_surface_set_fullscreen`, `yasl_surface_set_maximized`, and -`yasl_surface_minimize` return acceptance with errno on failure. Confirm actual -geometry/state through queued events. Popup targets are invalid. Wayland cannot -request an arbitrary live size and returns ENOTSUP for that operation; fullscreen, -maximize, and minimize use xdg-shell requests. X11 and Cocoa implement all four. - -## Desktop services - -Include `<yasl/desktop.h>` for services that sit next to windowing. Display and -surface calls use the display thread; path and locale calls work from any -thread. - -| Call | Linux | Cocoa | -| --- | --- | --- | -| `yasl_display_open_uri()` | `xdg-open` from `PATH` | NSWorkspace | -| `yasl_display_notify()` | Notifications service over D-Bus | User notifications; app bundle required | -| `yasl_display_appearance()` | Settings portal over D-Bus, then `GTK_THEME` | Effective appearance and increase-contrast setting | -| `yasl_surface_bell()` | Wayland xdg-system-bell; X11 XKB bell | `NSBeep` | -| `yasl_executable_path()` | `/proc/self/exe` | Executable path, resolved | -| `yasl_resource_dir()` | Always zero | App bundle resources | -| `yasl_default_locale()` | `LC_ALL`, `LC_CTYPE`, `LANG`, then `C.UTF-8` | Current language and region | - -`yasl_display_open_uri()` checks only that the URI has a scheme. Decide which -schemes and hosts your application trusts before calling it, for example -before opening a link from untrusted text. On Linux the opener starts in its -own session with default signal handling and none of the application's -descriptors above stderr. D-Bus services need YASL built with `dbus-1`; without -it, notifications return false and appearance falls back to `GTK_THEME`. -Appearance changes send no notification, so query again when a window gains -focus. - -Functions that fill a buffer return the full length, excluding the NUL. A -result less than the buffer size means the string was written. Otherwise the -buffer is unchanged, so call again with a larger one: - -```c -char path[4096]; -size_t len = yasl_executable_path(path, sizeof(path)); -if (len == 0 || len >= sizeof(path)) { - /* No path, or a longer buffer is needed. */ -} -``` - -## Native interoperability - -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 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. - -Application-managed native drawing currently supports Cocoa/Metal. Use -`yasl_surface_create_native()` with a non-NULL Metal device, which YASL retains. -During draw, query `yasl_surface_get_native_frame()` for a borrowed drawable, -encode and present with your renderer, then call `yasl_frame_present()` to -record submission. The drawable expires on successful present, callback return, -or surface destruction. Do not infer GPU completion from the boolean result. -Other backends return NULL with `errno = ENOTSUP` for native creation; having a -native window handle does not imply this rendering path is supported. +# Surface and input guide + +## Lifetime, threads, and errors + +The first successful `yasl_surface_create` or `yasl_gl_surface_create` initializes +shared native state. Subsequent surfaces share one backend. Optional `yasl_set_app_id` +and `yasl_set_backend` configure the next initialization; they fail after initialization. +Destroying the final surface preserves shared state. `yasl_shutdown` destroys remaining +surfaces, releases the backend, and restores defaults. Release application-owned GPU +objects before their surface or shutdown. Parent destruction destroys popup descendants. + +Creation, window operations, event pumping, and shutdown use the first creation's +thread, which must be the main thread on Cocoa. GL context binding/drawing/swapping +may use a rendering thread; detach its context before destroying the window. The +application synchronizes renderer state/geometry and joins workers before shutdown. +`yasl_wake` is thread-safe and does nothing before initialization or after shutdown. + +Constructors return NULL on failure; fallible mutations return bool. Event reads +return 1 for an event, 0 for empty/timeout/wake, -1 for failure. `yasl_get_error` +returns a stable per-thread error and static description; `yasl_get_native_error` +returns supplementary native diagnostics. Success does not clear a preceding error. +Valid-object size/scale/userdata getters return ordinary values. Destroyed pointers +are invalid. Strings passed to creation and setters are copied; userdata is borrowed. + +## Events and coordinates + +`yasl_poll_event` and `yasl_wait_event` process native events and consume one shared +FIFO. No manual dispatch/flush cycle. A failed/empty read leaves output unchanged. +Wait timeout zero polls, negative waits for work, positive milliseconds bound waiting. +A wake can return zero. Polling before initialization returns zero; waiting reports +NOT_INITIALIZED. Close and quit are requests; the application decides how to respond. +Display loss is a terminal native failure; destroy resources and shut down. + +Read the union member selected by type. Events own their text through +`yasl_event_release`, independently of later polls and source destruction. Release +every successful event exactly once; numeric events need no allocation to release. +Do not duplicate event ownership by shallow copying. Event surface pointers remain +borrowed and become invalid when their surface is destroyed. Undelivered events for +a destroyed surface are canceled. Sequence orders capture by YASL, not OS causality. +Timestamps use monotonic nanoseconds; native_time states whether normalized native +sample timing was available or capture time was used. + +Creation dimensions, pointer/pen coordinates, popup placement, caret, and CSD actions +use logical content units. Fractional positions use doubles. Image dimensions, +byte stride, framebuffer size, and damage use device pixels. Query actual framebuffer +extent for swapchain/viewport sizing; scale alone need not determine integer rounding. +Request functions report acceptance, while geometry/state events report actual results. +There is no portable absolute window-position promise. State's known mask distinguishes +reported false from unavailable state fields. + +## Rendering + +GPU creation (flags zero) does not create a device, context, or software framebuffer. +One presenter attaches to each surface. Incompatible second attachments fail. + +- Vulkan: inject vkGetInstanceProcAddr, query instance extensions and flags through + the live GPU surface, create the instance, then `yasl_vulkan_create_surface`. + The application owns VkInstance/VkDevice/queues/swapchains/VkSurfaceKHR, handles + image acquisition, synchronization and recreation, and presents with Vulkan. +- Metal: `yasl_metal_layer` returns a borrowed CAMetalLayer on Cocoa. YASL owns + the native view/layer and updates its dimensions. The app sets device/pixel format, + obtains drawables, and submits/presents commands. No additional YASL present call. +- GL: `yasl_gl_surface_create` accepts title, dimensions, flags, a per-call int + key/value attribute list terminated by YASL_GL_END, and an optional sharing surface. + NULL attributes default to GL3.2 core, RGBA8, depth24/stencil8. The surface owns + its context. Bind, load procedures, set swap interval, and swap explicitly. + GL API support depends on the native driver; Cocoa rejects OpenGL ES/debug contexts + and supports its native legacy/core profiles. +- CPU: create with YASL_SURFACE_CPU and call `yasl_cpu_present` with premultiplied + numeric 0xAARRGGBB, width/height, byte stride, and optional damage rows containing + x/y/width/height. Extent must match current framebuffer. NULL/zero damage means full; + explicit lists must be nonempty. Initial submission and changed extent require full + images. Later patches update the retained submitted image. Source bytes are copied + before return, so the app may reuse/free them. Native uploads may copy the entire + retained image, but never unsubmitted source changes. `yasl_cpu_read_pixels` reads + that submitted CPU image, not the composited screen. + +GPU creation/presentation uses native requests directly; YASL acknowledges Wayland +configuration before external GPU submission. Native refresh events can be handled +in the ordinary loop. An optional refresh callback permits drawing while the native +window system remains in a modal resize path. It runs only during pumping and never +recursively; it may query geometry and render, but must not pump, create/destroy +surfaces, or shut down. Geometry events may not yet have been consumed, so query +current geometry there. Without a hook, CPU exposes reuse committed pixels and GPU +redraw waits for the application's loop to resume. + +## Keyboard, pen, and scrolling + +Physical YASL_KEY names identify US keyboard positions independent of layout. +Layout labels support shortcuts; committed UTF-8 TEXT is for document insertion. +Keys and text need not correspond one-to-one. Input RESET/focus loss cancels held +application input. Keep pointer and pen streams separate to avoid duplicate strokes. + +PEN events carry connection-local device/tool IDs, tool type, proximity/contact, +button transitions, coherent position and axes. Pressure is 0..1; zero does not end +contact. Tilt uses degrees -90..90; rotation uses clockwise degrees; distance is +normalized, not a physical unit. Axis validity distinguishes unavailable from zero. +Changed masks indicate native updates. Tools/axes depend on hardware and backend; +IDs do not guarantee persistent hardware serials. Pen-generated mouse duplicates +are suppressed where recognized; do not treat tool class as a multi-contact API. + +SCROLL preserves wheel-line and precise-pixel values with validity, source, phase, +and momentum metadata where supplied. Applications choose layout pixels per wheel +line. X11 supplies native wheel/smooth valuator data where available. Missing native +metadata stays explicitly unavailable instead of being inferred. + +## Text input, clipboard, popups, and drags + +Activate text input for a text control with `yasl_surface_set_text_input`. Set purpose, +caret, and copied UTF-8 surrounding text/selection/revision through direct setters. +Snapshot offsets and TEXT_EDIT replacement ranges are UTF-8 bytes; COMPOSITION is +uncommitted preedit. Apply a matching-revision edit once, update your snapshot, and +ignore/rebase stale edits. Activation generation distinguishes controls. Ordinary key +text remains TEXT. Never insert preedit as committed document text. Wayland requires +text-input-v3 for native IME; absence is reported as unsupported. Cocoa translates +native UTF-16 replacement/deletion to snapshot UTF-8 ranges. X11 XIM supplies ordinary +text/preedit but cannot promise surrounding-text replacement support. + +Clipboard set copies UTF-8; read returns a nonzero request ID and later CLIPBOARD +completion with OK, UNAVAILABLE, or FAILED. Empty successful text is distinct from +failure. Destroying the target cancels transfer and delivery. Primary selection stays +independent and reports unsupported where unavailable. Linux external transfers time +out after 30 seconds; X11 currently lacks INCR for large transfers. File drops carry +URI-list text; `yasl_drop_next_path` decodes supported local paths into caller buffers. + +Popup arguments use logical parent anchor coordinates, anchor/gravity directions, +and flip/slide/resize constraint bits. Popup size includes its content. Destroying a +parent retires descendants too. Native grabs, CSD moves/resizes, menus, and surface +drags require a live triggering press event; unsupported/expired input fails explicitly. +Surface drags support application tab/window carry; they are not an arbitrary MIME +source API. Check operation results even when feature bits advertise support. + +## Backend limits + +Wayland support varies by compositor protocols; no portable absolute placement or +minimized-state query is assumed. X11 currently reports unit scale and lacks an +implemented relative-pointer lock. Cocoa requires macOS 14+; native Metal/GL and +Objective-C paths require validation on macOS. EGL/wayland-egl and Vulkan SDK support +are independently optional at build time. Feature bits describe implementation/ +server support, not guaranteed permission or attached hardware. The current work was +compiled and tested on Linux; macOS validation remains a separate required check. diff --git a/docs/migrating.md b/docs/migrating.md deleted file mode 100644 index 912ea4d..0000000 --- a/docs/migrating.md +++ /dev/null @@ -1,132 +0,0 @@ -# Migrating pre-release applications - -YASL 0.1 has no stable source or binary compatibility promise. Rebuild all -consumers against matching public headers and the library after updating. -See the [application guide](guide.md) for the full window and input workflow. - -## Display queue, frames, and application failures - -The runner now consumes the display-wide FIFO directly. Manual loops should -replace per-surface polling with `yasl_display_poll_event(display, &event)`. -Surface polling remains an input-only adapter that discards pending non-input -notifications delivered through legacy callbacks. Do not mix polling styles. Strings from either poll -now survive recipient destruction and expire at the next successful poll on -that display, or display close. Queue allocation failure is reported by dispatch -as an error instead of silently losing a lifecycle notification. - -Replace `struct yasl_frame_context` with `struct yasl_frame`, `frame->cpu.pixels` -with `frame->target.pixels`, and `frame->size` with `frame->target.size`. -`target.stride_bytes` replaces `cpu.stride_bytes`. Native drawing uses -`yasl_surface_get_native_frame(surface, &frame->target, &native)`. -`yasl_frame_present(frame, NULL, 0)` replaces the CPU presentation wrapper. -A non-NULL damage array with count zero now explicitly means no damaged pixels. -Lists larger than the backend limit become full damage instead of truncating. - -`YASL_APP_ERROR` and `yasl_app_exit(display, result)` report application failures -through `yasl_run`. `yasl_app_window(display)` gives init access to desc.window. -Use `yasl_surface_set_userdata` / `yasl_surface_userdata` for per-window state, -or `window.userdata` during creation. `yasl_surface_set_callbacks` now returns a -boolean and rejects runner-owned surfaces with EBUSY. - -Spell toplevel creation sizes `size_logical`; `size` remains an alias. -New window request functions report acceptance with errno. Pacing now works -outside the runner too, and returns a boolean instead of silently ignoring calls. - -Replace clipboard copy/paste with `yasl_clipboard_set` / `yasl_clipboard_request`. -Check the returned request ID and `event.paste.status` on completion. Empty text -is successful; missing data and transfer errors now have explicit outcomes. -Preedit snapshots arrive through `YASL_EVENT_COMPOSITION`; state and hover use -`YASL_EVENT_WINDOW_STATE`, `YASL_EVENT_DRAG`, and `YASL_EVENT_DROP_HOVER`. - -For worker messages, capture a surface ID on the display thread and use -`yasl_display_post` with an optional release callback. Do not also free the -payload in the event handler when release owns cleanup. Stop producer threads -before display shutdown; retired IDs reject posting with ENOENT. - -## Application callbacks - -All examples now use `<yasl/app.h>` and `yasl_run()`. Describe a single window -in `yasl_app_desc.window`, or create windows in `.init`; move 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()`; the runner destroys remaining surfaces on exit. - -Window state remains a `YASL_STATE_*` bitmask; `YASL_STATE_TILED` is reported -for tiled Wayland toplevels. - -## Events - -The core API and the runner now share one `struct yasl_event`, declared in -`<yasl/yasl.h>`. `yasl_display_poll_event()` returns the same events that -`yasl_app_desc.event` receives. Legacy surface polling returns only input types. - -| Previous use | Replacement | -| --- | --- | -| `yasl_surface_input_poll()`, `struct yasl_input_event` | `yasl_surface_poll_event()`, `struct yasl_event` | -| `event.kind`, `YASL_INPUT_*` | `event.type`, `YASL_EVENT_*` | -| `YASL_INPUT_MOUSE` with `mouse.kind` | `YASL_EVENT_POINTER_DOWN`, `_UP`, `_MOTION`, `_SCROLL`, `_LEAVE` with `event.pointer` | -| Runner `YASL_EVENT_MOUSE_BUTTON` with `pressed` | `YASL_EVENT_POINTER_DOWN` / `_UP` | -| Integer `button` 0/1/2 | `YASL_BUTTON_PRIMARY`, `_MIDDLE`, `_SECONDARY`, `_BACK`, `_FORWARD` | -| `paste.text`, `drop_files.files`, `text.text` | `text.data` for `PASTE`, `DROP`, and `TEXT` | -| `YASL_INPUT_DROP_FILES` / `YASL_EVENT_DROP_FILES` | `YASL_EVENT_DROP` | -| `YASL_EVENT_WINDOW_FOCUS` | `YASL_EVENT_FOCUS` | -| `YASL_EVENT_WINDOW_SCALE_CHANGED` | `YASL_EVENT_WINDOW_RESIZE` with `resize.scale_changed` | -| `YASL_EVENT_WINDOW_CLOSE_REQUEST` | `YASL_EVENT_WINDOW_CLOSE` | -| `user.type` | `user.code` | -| `key.repeat` | `key.action == YASL_KEY_REPEAT` | -| `yasl_surface_set_mouse_mode()` | `yasl_surface_set_pointer_mode()` | - -Every event carries a monotonic `time_ns`. Pointer motion carries `delta` on -both paths. Handle `YASL_EVENT_INPUT_RESET` by clearing held input and active -strokes. - -## Window creation - -Rename `struct yasl_surface_spec` to `struct yasl_window_spec` and -`struct yasl_surface_cbs` to `struct yasl_window_cbs`. Surface handles and -functions retain their names. Supply the window's callback table in `spec.cbs` -and its userdata in `spec.ud` when calling `yasl_surface_create_toplevel()` or -`yasl_surface_create_native()`. Callback tables are copied, and userdata is -borrowed. The new window's callbacks cannot run before creation returns; read -initial geometry with `yasl_surface_geometry()` and request the first redraw -after initializing application state. - -`yasl_surface_set_callbacks()` remains available to replace callbacks after -creation and to configure popups. A NULL `spec.cbs` creates a window without -callbacks. Displays owned by `yasl_run()` use the runner's callbacks instead; -provide application notifications through `yasl_app_desc.event` and `.frame`. - -## Keyboard events - -| Previous use | Replacement | -| --- | --- | -| Linux keysym or Cocoa virtual keycode in `key.key` | Portable physical `YASL_KEY_*` identity | -| `key.base` | `key.native_code`, with explicit backend-specific encoding | -| `key.keypad` | Distinct `YASL_KEY_KP_*` identities | -| Advisory `key.text` character | `YASL_EVENT_TEXT` UTF-8 payload for insertion | -| Layout label from a keysym | Optional `key.codepoint` and `key.shifted` Unicode scalars | - -Replace magic numbers and X11 symbols with named keys. For example, a quit -command checks `key.key == YASL_KEY_ESCAPE` on a press. `YASL_KEY_A` means the -US A position on every layout; use layout labels when a command should follow -the printed letter instead. Do not cast key identities to characters. Keypad -Enter and ordinary Enter now have distinct identities without a separate flag. - -Text comes only from `YASL_EVENT_TEXT`. Handle its byte length, copy it when -retaining it after the next successful poll, and allow multiple characters per -commit. Cocoa sends key events through its text input manager; Wayland emits -ordinary XKB text and locale compose results in addition to IME commits. - -## Scroll events - -Replace `scroll_lines`, `scroll_delta`, and scroll button numbers with -`pointer.scroll.x` and `.y` for `YASL_EVENT_POINTER_SCROLL`. These double -values are fractional line offsets, positive right/down on every backend. -Do not infer scroll direction from `button`. One event may include both axes. -Remove backend-specific sign inversions and whole-line casts; multiply the -offset by your application's preferred pixels per line instead. - -X11 still supplies whole wheel steps. Wayland high-resolution wheels and smooth -offsets, and Cocoa precise scrolling, preserve fractions. Device acceleration -and system preferences still affect the native offsets. diff --git a/docs/testing.md b/docs/testing.md index bd3f2f0..ce2082f 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -1,156 +1,36 @@ -# Testing and examples - -Configure isolated Linux display-server integration tests with Weston and -Xwayland installed: - -```sh -meson setup build-test -Dintegration_tests=true -meson test -C build-test --print-errorlogs -``` - -The harness starts and cleans up its own servers. It checks explicit requests, -automatic selection, fallback, both backends in one process, frame lifetimes, -callback copying, clipboard ownership, and timers. It also runs the beginner -and tablet examples on both backends. On macOS the option enables native window, -input ownership, keyboard/text/scroll, cursor, tablet, and example tests; these -require a desktop session. Linux results do not verify Cocoa behavior. - -For memory/undefined-behavior checks use a separate build: - -```sh -meson setup build-sanitize -Db_sanitize=address,undefined -Dintegration_tests=true -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 public API declarations are exported. Test disabled-backend configurations separately with -`-Dwayland=disabled` or `-Dx11=disabled`; the combined integration harness needs -both backends. - -Keyboard tests check physical identities, distinct keypad/modifier keys, layout -labels, Shift/Caps Lock, shortcut text suppression, AltGr, dead-key composition, -repeat, and composition reset. The X11 integration test changes and restores -the isolated server's keymap to verify that labels change while physical keys -retain their identity. The X11 text fallback is also checked without an input -method, including Latin-1, Cyrillic, and non-BMP characters encoded as UTF-8. -Scroll tests cover both axes, fractional Wayland offsets, -duplicate protocol representations, and X11 core/XInput wheel directions. -Cocoa integration tests cover UTF-16 labels, modifier-only events, native text -commit routing, and diagonal precise scrolling. Native IME interaction and -hardware gestures still need testing on the target desktop. -Clipboard tests cover Latin-1 interoperability, empty transfers, and rejection -of malformed UTF-8 on Linux. - -## Cocoa validation - -On macOS 14 or newer, use a logged-in desktop session with a Metal-capable -device. Configure `-Dintegration_tests=true`, compile, and run the full suite. -Cross-compiling with an Apple SDK checks compilation and linking; run -`meson test -C build-macos public-exports` to check the dylib's exports on the -build machine. A cross file should provide an `nm` that can read Mach-O, such -as `llvm-nm`. These checks do not execute the native window and input tests. - -To move an already compiled, unsanitized Cocoa suite to another Mac without -rebuilding it: - -```sh -python3 tests/helpers/package_cocoa_tests.py build-macos cocoa-tests.tar.gz -``` - -On the matching Mac, extract the archive and run: - -```sh -tar -xzf cocoa-tests.tar.gz -python3 yasl-cocoa-tests/run.py -``` - -The bundle includes the dylib, every executable test registered by Meson, and -the license. The build-machine export check is separate. The runner checks file -hashes, enforces each test's timeout, saves output under `logs/`, and writes -`results.json`. A nonzero exit means validation failed. `--list` lists the -bundled tests without running them. Use Python 3.8 or newer and a bundle matching -its architecture; Apple Silicon uses `aarch64`, Intel uses `x86_64`. - -## Tablet drawing example - -Build and run the same C application on Wayland, X11, or Cocoa: +# Tests ```sh meson setup build meson compile -C build -./build/yasl-tablet -./build/yasl-tablet --backend wayland -./build/yasl-tablet --backend x11 -# On macOS: -./build/yasl-tablet --backend cocoa +meson test -C build --print-errorlogs ``` -Draw with the pen tip: pressure controls brush width. Flip to the eraser to -remove ink. The window shows the selected backend, tool, contact state, raw -pressure (0..65535), percentage, and tilt in degrees, with a live pressure bar, -brush outline, and tilt vector. The system cursor is hidden so the brush overlay -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 `<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_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. -Letter names describe US positions, so the clear key follows the C position -when the user selects another layout. +Tests cover public static/shared C and C++11 consumers, exact shared exports, +queue ownership/order/destruction, UTF-8, GL attributes, private native event loops, +clipboard, IME editing, pointer/pen coherence, cursor, and popup positioning. Native +unit fixtures deliberately use private implementation headers; applications must use +installed public headers. -`YASL_CAP_TABLET` indicates backend/protocol support, not whether a tablet is -plugged in or which axes it has. Missing axes are zero. Wayland requires the -compositor's `zwp_tablet_manager_v2`; X11 requires XInput 2 and driver-provided -`Abs Pressure`/`Abs Tilt X`/`Abs Tilt Y` valuators. Cocoa handles both tablet mouse -subtypes and native tablet point/proximity events. X11 identifies erasers by -the Wacom tool property or the driver's device name. Hardware and driver -support determine which readings are available. The example displays a message -when the server has no tablet protocol and still allows mouse drawing. - -The test suite checks synthetic Wayland/XInput samples and the drawing behavior -without hardware, including pressure-only changes, sparse axes, eraser input, -hover, release, reset, and scale changes. Linux integration tests launch the -example on isolated Wayland and X11 servers; macOS integration tests also check -Cocoa tablet event translation. Physical tablet testing is still needed to -validate a particular device/driver combination. - -For an end-to-end X11 pressure test without hardware, install Xorg's `dummy` -video driver and `inputtest` input driver, then run: +For private Linux display servers and actual GPU presentation, install Weston, +Xwayland, EGL/wayland-egl, and Vulkan/Mesa software drivers, then: ```sh -meson configure build -Dx11_tablet_tests=true -meson test -C build tablet-x11-native --print-errorlogs +meson configure build -Dintegration_tests=true +meson compile -C build +meson test -C build --print-errorlogs ``` -This starts an isolated Xorg server with virtual pen and eraser devices, sends -real driver samples, checks YASL's shared events through the example, and verifies -its presented window pixels. It checks stationary pressure changes, release -without valuators, and eraser identification through the driver's tool property. -The test disables automatic physical input/GPU attachment and cleans up its -server. Set `YASL_XORG_MODULE_PATH` to a comma-separated module search path if -the test drivers are outside Xorg's default directories. - -## Benchmarks +The integration harness starts its own headless GL Weston and Xwayland, runs public +surface ownership/CPU damage/wakeup/refresh checks, GL drawing/readback/context-sharing/ +resize, Vulkan swapchain clear/present, and example smoke tests. It selects Mesa +software rendering and Lavapipe when available. It needs permission to create local +Unix sockets. A sandbox blocking those sockets is not a backend test result. -`yasl-benchmark` measures queue, timer, and surface-operation costs. Results -depend on the build, backend, and display server; they do not measure confirmed -screen presentation latency. - -```sh -# Run via meson test runner -meson test -C build --benchmark --verbose - -# Or invoke the executable directly -ninja -C build tests/yasl-benchmark -./build/tests/yasl-benchmark -``` +`-Dx11_tablet_tests=true` additionally uses Xorg dummy/inputtest drivers for pen input +without physical hardware. These are separate from the compositor-free native unit +fixtures. Cocoa integration tests run only on macOS and need the native SDK/display. +Linux results do not validate Cocoa compilation or runtime. +For memory/undefined-behavior checks use a separate build with `-Db_sanitize=address,undefined` +and run the same suites. Keep sanitizer builds isolated from the normal build. diff --git a/examples/game.c b/examples/game.c index 33c2f6e..79beb7c 100644 --- a/examples/game.c +++ b/examples/game.c @@ -1,360 +1,53 @@ -/** - * @file game.c - * A continuous 60/144+ FPS game loop demonstration built on YASL Approach A. - * - * Demonstrates: - * - Continuous display-synchronized ticks (YASL_PACING_CONTINUOUS) - * - 3D software rendering (rotating shaded cube + starfield) - * - WASD continuous player movement & key state tracking - * - Relative pointer mode (YASL_POINTER_RELATIVE) for smooth first-person camera orbit - * - Spacebar toggles pointer lock (Relative vs Normal cursor) - * - Real-time FPS counter and frame latency display - * - Dynamic window resizing with automatic aspect ratio adjustment - */ - -#define _DEFAULT_SOURCE -#include <yasl/app.h> - -#include <math.h> -#include <stdbool.h> -#include <stdint.h> +/* GPU example: the application owns context binding, drawing and swap pacing. */ +#include <yasl/yasl.h> +#include <yasl/gl.h> #include <stdio.h> -#include <stdlib.h> +#include <math.h> #include <string.h> -#define NUM_STARS 256 -#define FOV_SCALE 320.0f - -struct star { - float x, y, z; -}; - -struct vec3 { - float x, y, z; -}; - -struct game_state { - struct yasl_display *display; - struct yasl_surface *surface; - - /* Camera & controls */ - float cam_x, cam_y, cam_z; - float cam_yaw, cam_pitch; - bool key_w, key_a, key_s, key_d; - bool key_q, key_e; - bool relative_mouse; - - /* Simulation objects */ - float cube_rot_x, cube_rot_y; - struct star stars[NUM_STARS]; - - /* FPS & timing stats */ - uint64_t last_stat_time_ns; - int frame_count; - float current_fps; - float avg_frame_time_ms; -}; - -/* 8x8 bitmap font for overlay HUD */ -static const uint8_t font8x8_digits[10][8] = { - {0x3C,0x66,0x6E,0x76,0x66,0x66,0x3C,0x00}, /* 0 */ - {0x18,0x38,0x18,0x18,0x18,0x18,0x7E,0x00}, /* 1 */ - {0x3C,0x66,0x06,0x0C,0x18,0x30,0x7E,0x00}, /* 2 */ - {0x3C,0x66,0x06,0x1C,0x06,0x66,0x3C,0x00}, /* 3 */ - {0x0C,0x1C,0x34,0x64,0x7E,0x04,0x04,0x00}, /* 4 */ - {0x7E,0x60,0x7C,0x06,0x06,0x66,0x3C,0x00}, /* 5 */ - {0x1C,0x30,0x60,0x7C,0x66,0x66,0x3C,0x00}, /* 6 */ - {0x7E,0x06,0x0C,0x18,0x30,0x30,0x30,0x00}, /* 7 */ - {0x3C,0x66,0x66,0x3C,0x66,0x66,0x3C,0x00}, /* 8 */ - {0x3C,0x66,0x66,0x3E,0x06,0x0C,0x38,0x00}, /* 9 */ -}; - -static void draw_digit(uint32_t *pix, int w, int h, int px, int py, int digit, uint32_t col, int s) -{ - if (digit < 0 || digit > 9) return; - const uint8_t *glyph = font8x8_digits[digit]; - for (int y = 0; y < 8; y++) { - uint8_t row = glyph[y]; - for (int x = 0; x < 8; x++) { - if (row & (0x80 >> x)) { - for (int dy = 0; dy < s; dy++) { - int yy = py + y * s + dy; - if (yy < 0 || yy >= h) continue; - for (int dx = 0; dx < s; dx++) { - int xx = px + x * s + dx; - if (xx >= 0 && xx < w) pix[(size_t)yy * w + xx] = col; - } - } - } +typedef void (*clear_color_fn)(float, float, float, float); +typedef void (*clear_fn)(unsigned); +typedef void (*viewport_fn)(int, int, int, int); + +int main(int argc, char **argv) +{ + bool smoke = argc == 2 && !strcmp(argv[1], "--smoke-test"); + yasl_surface *s = yasl_gl_surface_create("YASL OpenGL", 800, 600, 0, NULL, NULL); + if (!s || !yasl_gl_make_current(s)) { + const char *error; + yasl_get_error(&error); + fprintf(stderr, "%s\n", error); + yasl_shutdown(); + return 1; + } + clear_color_fn clear_color = (clear_color_fn)yasl_gl_get_proc_address("glClearColor"); + clear_fn clear = (clear_fn)yasl_gl_get_proc_address("glClear"); + viewport_fn viewport = (viewport_fn)yasl_gl_get_proc_address("glViewport"); + if (!clear_color || !clear || !viewport) { + yasl_shutdown(); + return 1; + } + yasl_gl_swap_interval(s, 1); + int result = 0, frames = 0; + for (bool running = true; running;) { + yasl_event e; + int n; + while ((n = yasl_wait_event(&e, 0)) > 0) { + 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) { result = 1; running = false; } } - } -} - -static void draw_number(uint32_t *pix, int w, int h, int px, int py, int num, uint32_t col, int scale) -{ - char buf[16]; - snprintf(buf, sizeof(buf), "%d", num); - for (int i = 0; buf[i]; i++) { - draw_digit(pix, w, h, px + i * 8 * scale, py, buf[i] - '0', col, scale); - } -} - -static void draw_line(uint32_t *pix, int w, int h, int x0, int y0, int x1, int y1, uint32_t col) -{ - int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; - int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; - int err = dx + dy, e2; - - while (1) { - if (x0 >= 0 && x0 < w && y0 >= 0 && y0 < h) { - pix[(size_t)y0 * w + x0] = col; - } - if (x0 == x1 && y0 == y1) break; - e2 = 2 * err; - if (e2 >= dy) { err += dy; x0 += sx; } - if (e2 <= dx) { err += dx; y0 += sy; } - } -} - -static struct vec3 rotate_vertex(struct vec3 v, float rx, float ry) -{ - /* Rotate around Y */ - float cy = cosf(ry), sy = sinf(ry); - float x1 = v.x * cy + v.z * sy; - float z1 = -v.x * sy + v.z * cy; - - /* Rotate around X */ - float cx = cosf(rx), sx = sinf(rx); - float y2 = v.y * cx - z1 * sx; - float z2 = v.y * sx + z1 * cx; - - return (struct vec3){x1, y2, z2}; -} - -static bool project_point(struct vec3 p, int w, int h, int *out_x, int *out_y) -{ - if (p.z <= 0.2f) return false; - float inv_z = FOV_SCALE / p.z; - *out_x = (int)(w * 0.5f + p.x * inv_z); - *out_y = (int)(h * 0.5f - p.y * inv_z); - return true; -} - -static enum yasl_app_result on_init(void *ud, struct yasl_display *d) -{ - struct game_state *game = ud; - game->display = d; - - struct yasl_window_spec spec = { - .title = "YASL 3D Game Loop (Approach A)", - .size_logical = {800, 600}, - }; - game->surface = yasl_surface_create_toplevel(d, &spec); - if (!game->surface) return YASL_APP_ERROR; - - /* Drive continuous 60/144Hz ticks */ - if (!yasl_surface_set_pacing(game->surface, YASL_PACING_CONTINUOUS)) - return YASL_APP_ERROR; - - game->cam_z = 3.5f; - game->relative_mouse = false; - - /* Initialize stars */ - for (int i = 0; i < NUM_STARS; i++) { - game->stars[i].x = ((float)(rand() % 2000) - 1000.0f) / 100.0f; - game->stars[i].y = ((float)(rand() % 2000) - 1000.0f) / 100.0f; - game->stars[i].z = ((float)(rand() % 1000) + 10.0f) / 50.0f; - } - - game->last_stat_time_ns = yasl_loop_now_ns(); - return YASL_APP_CONTINUE; -} - -static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) -{ - struct game_state *game = ud; - - switch (ev->type) { - case YASL_EVENT_KEY: { - bool down = ev->key.action != YASL_KEY_RELEASE; - - switch (ev->key.key) { - case YASL_KEY_W: game->key_w = down; break; - case YASL_KEY_S: game->key_s = down; break; - case YASL_KEY_A: game->key_a = down; break; - case YASL_KEY_D: game->key_d = down; break; - case YASL_KEY_Q: game->key_q = down; break; - case YASL_KEY_E: game->key_e = down; break; - case YASL_KEY_SPACE: - if (ev->key.action == YASL_KEY_PRESS) { - /* Toggle relative pointer mode; unsupported backends stay normal. */ - game->relative_mouse = !game->relative_mouse && - yasl_surface_set_pointer_mode(game->surface, YASL_POINTER_RELATIVE); - if (!game->relative_mouse) - yasl_surface_set_pointer_mode(game->surface, YASL_POINTER_NORMAL); - } - break; - case YASL_KEY_ESCAPE: - if (ev->key.action == YASL_KEY_PRESS) { - if (game->relative_mouse) { - game->relative_mouse = false; - yasl_surface_set_pointer_mode(game->surface, YASL_POINTER_NORMAL); - } else { - return YASL_APP_QUIT; - } - } - break; - default: - break; - } - break; - } - - case YASL_EVENT_POINTER_MOTION: - if (game->relative_mouse) { - /* Smooth first-person camera orbit */ - game->cam_yaw += (float)ev->pointer.delta.x * 0.005f; - game->cam_pitch += (float)ev->pointer.delta.y * 0.005f; - if (game->cam_pitch > 1.4f) game->cam_pitch = 1.4f; - if (game->cam_pitch < -1.4f) game->cam_pitch = -1.4f; - } - break; - - case YASL_EVENT_POINTER_DOWN: - if (!game->relative_mouse) { - /* Click window to capture cursor */ - game->relative_mouse = - yasl_surface_set_pointer_mode(game->surface, YASL_POINTER_RELATIVE); - } - break; - - case YASL_EVENT_WINDOW_CLOSE: - return YASL_APP_QUIT; - - default: - break; - } - - return YASL_APP_CONTINUE; -} - -static void on_frame(void *ud, struct yasl_surface *s, struct yasl_frame *ctx) -{ - (void)s; - struct game_state *game = ud; - int w = ctx->target.size.x; - int h = ctx->target.size.y; - uint32_t *pix = ctx->target.pixels; - if (!pix || w <= 0 || h <= 0) return; - - /* Update game simulation */ - float speed = 0.04f; - float forward_x = sinf(game->cam_yaw); - float forward_z = cosf(game->cam_yaw); - float right_x = cosf(game->cam_yaw); - float right_z = -sinf(game->cam_yaw); - - if (game->key_w) { game->cam_x -= forward_x * speed; game->cam_z -= forward_z * speed; } - if (game->key_s) { game->cam_x += forward_x * speed; game->cam_z += forward_z * speed; } - if (game->key_a) { game->cam_x -= right_x * speed; game->cam_z -= right_z * speed; } - if (game->key_d) { game->cam_x += right_x * speed; game->cam_z += right_z * speed; } - if (game->key_q) { game->cam_y -= speed; } - if (game->key_e) { game->cam_y += speed; } - - game->cube_rot_x += 0.015f; - game->cube_rot_y += 0.025f; - - /* Clear screen to deep space dark blue */ - memset(pix, 0x08, (size_t)w * h * sizeof(uint32_t)); - - /* 1. Render 3D Starfield */ - for (int i = 0; i < NUM_STARS; i++) { - game->stars[i].z -= 0.05f; - if (game->stars[i].z <= 0.2f) game->stars[i].z += 20.0f; - - struct vec3 sp = {game->stars[i].x - game->cam_x, - game->stars[i].y - game->cam_y, - game->stars[i].z}; - int sx, sy; - if (project_point(sp, w, h, &sx, &sy) && sx >= 0 && sx < w && sy >= 0 && sy < h) { - uint8_t brightness = (uint8_t)(255.0f * (1.0f - sp.z / 20.0f)); - uint32_t star_col = 0xff000000 | (brightness << 16) | (brightness << 8) | brightness; - pix[(size_t)sy * w + sx] = star_col; - } - } - - /* 2. Render 3D Cube */ - static const struct vec3 cube_vertices[8] = { - {-1.0f, -1.0f, -1.0f}, { 1.0f, -1.0f, -1.0f}, - { 1.0f, 1.0f, -1.0f}, {-1.0f, 1.0f, -1.0f}, - {-1.0f, -1.0f, 1.0f}, { 1.0f, -1.0f, 1.0f}, - { 1.0f, 1.0f, 1.0f}, {-1.0f, 1.0f, 1.0f}, - }; - - static const int cube_edges[12][2] = { - {0, 1}, {1, 2}, {2, 3}, {3, 0}, - {4, 5}, {5, 6}, {6, 7}, {7, 4}, - {0, 4}, {1, 5}, {2, 6}, {3, 7}, - }; - - int proj_x[8], proj_y[8]; - bool valid[8]; - - for (int i = 0; i < 8; i++) { - struct vec3 rot = rotate_vertex(cube_vertices[i], game->cube_rot_x, game->cube_rot_y); - rot.x -= game->cam_x; - rot.y -= game->cam_y; - rot.z += game->cam_z; - valid[i] = project_point(rot, w, h, &proj_x[i], &proj_y[i]); - } - - uint32_t cyan = 0xff00ffff; - for (int i = 0; i < 12; i++) { - int u = cube_edges[i][0]; - int v = cube_edges[i][1]; - if (valid[u] && valid[v]) { - draw_line(pix, w, h, proj_x[u], proj_y[u], proj_x[v], proj_y[v], cyan); - } - } - - /* 3. Center crosshair in relative mouse mode */ - if (game->relative_mouse) { - int cx = w / 2, cy = h / 2; - draw_line(pix, w, h, cx - 6, cy, cx + 6, cy, 0xffffffff); - draw_line(pix, w, h, cx, cy - 6, cx, cy + 6, 0xffffffff); - } - - /* 4. FPS counter calculation */ - game->frame_count++; - uint64_t now = yasl_loop_now_ns(); - if (now - game->last_stat_time_ns >= 1000000000ull) { - uint64_t elapsed = now - game->last_stat_time_ns; - game->current_fps = (float)game->frame_count * 1000000000.0f / (float)elapsed; - game->avg_frame_time_ms = 1000.0f / (game->current_fps > 0 ? game->current_fps : 1.0f); - game->frame_count = 0; - game->last_stat_time_ns = now; - } - - /* Draw FPS overlay digits in top-left */ - int scale = ctx->scale_120 >= 240 ? 2 : 1; - draw_number(pix, w, h, 16 * scale, 16 * scale, (int)game->current_fps, 0xff00ff00, scale); - - /* Submit frame */ - if (!yasl_frame_present(ctx, NULL, 0)) - yasl_app_exit(yasl_surface_display(s), YASL_APP_ERROR); -} - -int main(void) -{ - static struct game_state game; - struct yasl_app_desc desc = { - .app_id = "org.yasl.game", - .backend = YASL_BACKEND_AUTO, - .userdata = &game, - .init = on_init, - .event = on_event, - .frame = on_frame, - }; - - return yasl_run(&desc); + if (n < 0) { result = 1; break; } + int w, h; + yasl_surface_get_framebuffer_size(s, &w, &h); + viewport(0, 0, w, h); + double t = yasl_time_ns() / 1e9; + clear_color((float)(0.3 + 0.2 * sin(t)), 0.2f, 0.4f, 1); + clear(0x00004000u); + if (!yasl_gl_swap_buffers(s)) { result = 1; break; } + if (smoke && ++frames == 3) break; + } + yasl_gl_make_current(NULL); + yasl_shutdown(); + return result; } diff --git a/examples/hello.c b/examples/hello.c index 31ab18b..9e324a6 100644 --- a/examples/hello.c +++ b/examples/hello.c @@ -1,38 +1,26 @@ -/* A complete CPU-rendered window. Build instructions: docs/getting-started.md. */ -#include <yasl/app.h> -#include <stdio.h> +/* A CPU-rendered window. Build instructions: docs/getting-started.md. */ +#include <yasl/yasl.h> +#include <yasl/cpu.h> -static bool failed; -static void draw(void *ud, struct yasl_surface *surface, struct yasl_frame *frame) +int main(void) { - (void)ud; - int w = frame->target.size.x, h = frame->target.size.y; - for (int y = 0; y < h; y++) { - uint32_t *row = frame->target.pixels + (size_t)y * (frame->target.stride_bytes / 4); - for (int x = 0; x < w; x++) { - /* Red grows rightward, green downward, blue fades; opaque premultiplied ARGB. */ - uint32_t r = (uint32_t)(x * 255 / (w > 1 ? w - 1 : 1)); - uint32_t g = (uint32_t)(y * 255 / (h > 1 ? h - 1 : 1)); - row[x] = 0xff000000 | r << 16 | g << 8 | (255 - r); + yasl_surface *surface = yasl_surface_create("Hello YASL", 640, 480, YASL_SURFACE_CPU); + yasl_event event; + while (surface && yasl_wait_event(&event, -1) >= 0 && + event.type != YASL_EVENT_CLOSE && event.type != YASL_EVENT_QUIT) { + if (event.type != YASL_EVENT_REFRESH) continue; + int w, h; + uint32_t *pixels = yasl_cpu_pixels(surface, &w, &h); + for (int y = 0; pixels && y < h; y++) { + for (int x = 0; x < w; x++) { + /* Red grows rightward, green downward, blue fades; opaque premultiplied ARGB. */ + uint32_t r = (uint32_t)(x * 255 / (w > 1 ? w - 1 : 1)); + uint32_t g = (uint32_t)(y * 255 / (h > 1 ? h - 1 : 1)); + pixels[(size_t)y * w + x] = 0xff000000 | r << 16 | g << 8 | (255 - r); + } } + yasl_cpu_present(surface); } - - if (!yasl_frame_present(frame, NULL, 0)) { - fprintf(stderr, "Could not present the draw target\n"); - failed = true; - } - if (failed) yasl_app_exit(yasl_surface_display(surface), YASL_APP_ERROR); -} - -int main(void) -{ - struct yasl_window_spec window = {.title = "Hello YASL", .size_logical = {640, 480}}; - struct yasl_app_desc desc = { - .app_id = "org.example.hello", - .window = &window, - .frame = draw, - }; - int result = yasl_run(&desc); - if (result) fprintf(stderr, "Could not run the application\n"); - return result || failed ? 1 : 0; + yasl_shutdown(); + return surface ? 0 : 1; } diff --git a/examples/notepad.c b/examples/notepad.c index 0b331e8..92b9d44 100644 --- a/examples/notepad.c +++ b/examples/notepad.c @@ -1,6 +1,6 @@ /** * @file notepad.c - * A full-featured desktop text editor built on YASL Approach A. + * A desktop text editor built on YASL's public CPU surface API. * * Features: * - New (Ctrl+N), Open (Ctrl+O, CLI arg, drag-and-drop), Save (Ctrl+S), Save As (Ctrl+Shift+S) @@ -14,14 +14,15 @@ * - Zoom In / Out / Reset (Ctrl+=, Ctrl+-, Ctrl+0) * - Mouse selection with click, drag, double-click (word), triple-click (line) * - Unsaved changes confirmation on exit - * - Real-time asynchronous background stats via yasl_display_post() - * - 0% CPU consumption when idle (YASL_PACING_ON_DEMAND) + * - Real-time asynchronous background stats via a worker and yasl_wake() */ #define _DEFAULT_SOURCE -#include <yasl/app.h> +#include <yasl/yasl.h> +#include <yasl/cpu.h> #include <ctype.h> +#include <math.h> #include <pthread.h> #include <stdbool.h> #include <stdint.h> @@ -134,8 +135,6 @@ static const uint8_t font8x8[95][8] = { {0x76,0xDC,0x00,0x00,0x00,0x00,0x00,0x00} /* ~ */ }; -#define MSG_STATS_READY 100 - typedef enum prompt_mode { PROMPT_NONE = 0, PROMPT_OPEN, @@ -160,8 +159,15 @@ struct undo_snapshot { }; struct editor { - struct yasl_display *display; - struct yasl_surface *surface; + yasl_surface *surface; + double surface_scale; + bool dirty, running, failed; + uint64_t text_revision; + char ime_snapshot[MAX_LINE_LEN]; + size_t ime_begin, ime_end; + bool ime_snapshot_ready; + double ime_caret_x, ime_caret_y; + bool ime_caret_ready; /* File tracking */ char file_path[512]; @@ -182,7 +188,7 @@ struct editor { /* Double/Triple click tracking */ uint64_t last_click_time; - struct yasl_vector2i last_click_pos; + double last_click_x, last_click_y; int click_count; /* Viewport & Layout */ @@ -213,8 +219,10 @@ struct editor { struct doc_stats stats; _Atomic bool worker_running; bool worker_started, worker_mutex_ready, worker_cond_ready; - uint64_t worker_surface_id; bool worker_dirty; + char (*worker_lines)[MAX_LINE_LEN]; + int worker_line_count; + uint64_t stats_generation; pthread_t worker_tid; pthread_mutex_t worker_mutex; pthread_cond_t worker_cond; @@ -223,7 +231,13 @@ struct editor { static void editor_set_status(struct editor *ed, const char *msg) { snprintf(ed->status_msg, sizeof(ed->status_msg), "%s", msg); - ed->status_msg_time = yasl_loop_now_ns(); + ed->status_msg_time = yasl_time_ns(); + ed->dirty = true; +} + +static void editor_request_render(struct editor *ed) +{ + if (ed) ed->dirty = true; } static void editor_update_title(struct editor *ed) @@ -316,7 +330,13 @@ static void editor_redo(struct editor *ed) static void editor_notify_worker(struct editor *ed) { + char (*snapshot)[MAX_LINE_LEN] = malloc(sizeof(ed->lines)); + if (!snapshot) return; + memcpy(snapshot, ed->lines, sizeof(ed->lines)); pthread_mutex_lock(&ed->worker_mutex); + free(ed->worker_lines); + ed->worker_lines = snapshot; + ed->worker_line_count = ed->line_count; ed->worker_dirty = true; pthread_cond_signal(&ed->worker_cond); pthread_mutex_unlock(&ed->worker_mutex); @@ -334,15 +354,17 @@ static void *worker_thread(void *arg) pthread_mutex_unlock(&ed->worker_mutex); break; } + char (*lines)[MAX_LINE_LEN] = ed->worker_lines; + int line_count = ed->worker_line_count; + ed->worker_lines = NULL; ed->worker_dirty = false; pthread_mutex_unlock(&ed->worker_mutex); - - usleep(30000); + if (!lines) continue; int word_count = 0; int char_count = 0; - for (int i = 0; i < ed->line_count; i++) { - const char *p = ed->lines[i]; + for (int i = 0; i < line_count; i++) { + const char *p = lines[i]; bool in_word = false; while (*p) { char_count++; @@ -356,31 +378,19 @@ static void *worker_thread(void *arg) } } + free(lines); if (!ed->worker_running) break; - - struct doc_stats *s = malloc(sizeof(*s)); - if (s) { - s->words = word_count; - s->chars = char_count; - if (!yasl_display_post(ed->display, &(struct yasl_message){ - .target_id = ed->worker_surface_id, .code = MSG_STATS_READY, - .payload = s, .size = sizeof(*s), .release = free})) - free(s); - } + pthread_mutex_lock(&ed->worker_mutex); + ed->stats.words = word_count; + ed->stats.chars = char_count; + ed->stats_generation++; + pthread_mutex_unlock(&ed->worker_mutex); + yasl_wake(); } return NULL; } -static uint64_t on_blink_timer(void *ud, yasl_timer_id id, uint64_t interval_ns) -{ - (void)id; - struct editor *ed = ud; - ed->caret_visible = !ed->caret_visible; - yasl_surface_request_frame(ed->surface); - return interval_ns; -} - /* File I/O */ static bool editor_open_file(struct editor *ed, const char *path) { @@ -472,27 +482,6 @@ static void editor_new_file(struct editor *ed) editor_notify_worker(ed); } -static void decode_uri_path(const char *uri, char *out, size_t max_out) -{ - if (strncmp(uri, "file://", 7) == 0) { - uri += 7; - if (strncmp(uri, "localhost/", 10) == 0) { - uri += 9; - } - } - size_t out_len = 0; - while (*uri && *uri != '\r' && *uri != '\n' && out_len < max_out - 1) { - if (*uri == '%' && isxdigit((unsigned char)uri[1]) && isxdigit((unsigned char)uri[2])) { - char hex[3] = {uri[1], uri[2], '\0'}; - out[out_len++] = (char)strtol(hex, NULL, 16); - uri += 3; - } else { - out[out_len++] = *uri++; - } - } - out[out_len] = '\0'; -} - /* Selection helper */ static void get_selection_bounds(const struct editor *ed, int *r0, int *c0, int *r1, int *c1) { @@ -505,7 +494,8 @@ static void get_selection_bounds(const struct editor *ed, int *r0, int *c0, int (ed->sel_anchor_row == ed->cursor_row && ed->sel_anchor_col <= ed->cursor_col)) { *r0 = ed->sel_anchor_row; *c0 = ed->sel_anchor_col; *r1 = ed->cursor_row; *c1 = ed->cursor_col; - } else { + } + else { *r0 = ed->cursor_row; *c0 = ed->cursor_col; *r1 = ed->sel_anchor_row; *c1 = ed->sel_anchor_col; } @@ -535,7 +525,8 @@ static void editor_delete_selection(struct editor *ed) char *line = ed->lines[r0]; int len = (int)strlen(line); memmove(line + c0, line + c1, (size_t)(len - c1 + 1)); - } else { + } + else { char *first = ed->lines[r0]; char *last = ed->lines[r1]; char temp[MAX_LINE_LEN]; @@ -585,7 +576,7 @@ static void editor_copy(struct editor *ed) snprintf(buf, (size_t)MAX_LINES * MAX_LINE_LEN, "%s\n", ed->lines[ed->cursor_row]); } - if (!yasl_clipboard_set(ed->display, YASL_CLIPBOARD, buf)) + if (!yasl_clipboard_set(YASL_CLIPBOARD, buf)) editor_set_status(ed, "Clipboard copy failed"); free(buf); editor_set_status(ed, "Copied to clipboard"); @@ -995,30 +986,19 @@ static void prompt_execute(struct editor *ed) } } -/* YASL App callbacks */ -static enum yasl_app_result on_init(void *ud, struct yasl_display *d) +static bool editor_init(struct editor *ed) { - struct editor *ed = ud; - ed->display = d; - - struct yasl_window_spec spec = { - .title = "Untitled - YASL Notepad", - .size_logical = {960, 640}, - }; - ed->surface = yasl_surface_create_toplevel(d, &spec); - if (!ed->surface) return YASL_APP_ERROR; - ed->worker_surface_id = yasl_surface_id(ed->surface); - - yasl_surface_set_pacing(ed->surface, YASL_PACING_ON_DEMAND); - if (!yasl_surface_add_timer(ed->surface, YASL_MS_TO_NS(500), on_blink_timer, ed)) - return YASL_APP_ERROR; + ed->surface = yasl_surface_create("Untitled - YASL Notepad", 960, 640, YASL_SURFACE_CPU); + 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->worker_running = true; - if (pthread_mutex_init(&ed->worker_mutex, NULL)) return YASL_APP_ERROR; + if (pthread_mutex_init(&ed->worker_mutex, NULL)) return false; ed->worker_mutex_ready = true; - if (pthread_cond_init(&ed->worker_cond, NULL)) return YASL_APP_ERROR; + if (pthread_cond_init(&ed->worker_cond, NULL)) return false; ed->worker_cond_ready = true; - if (pthread_create(&ed->worker_tid, NULL, worker_thread, ed)) return YASL_APP_ERROR; + if (pthread_create(&ed->worker_tid, NULL, worker_thread, ed)) return false; ed->worker_started = true; if (ed->has_file) { @@ -1028,49 +1008,104 @@ static enum yasl_app_result on_init(void *ud, struct yasl_display *d) } editor_notify_worker(ed); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + ed->running = true; + return true; } -static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) +static void editor_update_ime(struct editor *ed) { - struct editor *ed = ud; + if (!ed->surface || ed->line_count <= 0) return; + const char *line = ed->lines[ed->cursor_row]; + size_t length = strlen(line); + size_t begin = (size_t)ed->cursor_col, end = begin; + if (ed->has_selection && ed->sel_anchor_row == ed->cursor_row) { + begin = (size_t)(ed->sel_anchor_col < ed->cursor_col ? ed->sel_anchor_col : ed->cursor_col); + end = (size_t)(ed->sel_anchor_col > ed->cursor_col ? ed->sel_anchor_col : ed->cursor_col); + } + if (begin > length) begin = length; + if (end > length) end = length; + bool snapshot_changed = !ed->ime_snapshot_ready || strcmp(ed->ime_snapshot, line) || + begin != ed->ime_begin || end != ed->ime_end; + if (snapshot_changed) { + snprintf(ed->ime_snapshot, sizeof(ed->ime_snapshot), "%s", line); + ed->ime_begin = begin; + ed->ime_end = end; + 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); + 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) { + 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); + } +} +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.len && ed->prompt_len < (int)sizeof(ed->prompt_buf) - 1; i++) { + 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]; ed->prompt_buf[ed->prompt_len] = '\0'; } } } else { - insert_text(ed, ev->text.data, ev->text.len); + insert_text(ed, ev->text.data, ev->text.length); } - yasl_surface_request_frame(ed->surface); + editor_request_render(ed); break; - case YASL_EVENT_PASTE: - if (ev->paste.status != YASL_TRANSFER_OK) { - editor_set_status(ed, ev->paste.status == YASL_TRANSFER_UNAVAILABLE + case YASL_EVENT_CLIPBOARD: + if (ev->clipboard.status != YASL_TRANSFER_OK) { + editor_set_status(ed, ev->clipboard.status == YASL_TRANSFER_UNAVAILABLE ? "Clipboard has no text" : "Clipboard transfer failed"); - yasl_surface_request_frame(ed->surface); + editor_request_render(ed); break; } - if (ev->text.data && ev->text.len > 0) { - insert_text(ed, ev->text.data, ev->text.len); - yasl_surface_request_frame(ed->surface); + if (ev->clipboard.data && ev->clipboard.length > 0) { + insert_text(ed, ev->clipboard.data, ev->clipboard.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; + 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); + editor_request_render(ed); } break; case YASL_EVENT_DROP: - if (ev->text.data && ev->text.len > 0) { + if (ev->drop.action == YASL_DROP_DATA) { + const char *cursor = ev->drop.data; char path[512]; - decode_uri_path(ev->text.data, path, sizeof(path)); - if (path[0]) { - editor_open_file(ed, path); - yasl_surface_request_frame(ed->surface); + size_t length; + while ((length = yasl_drop_next_path(&cursor, path, sizeof(path))) > 0) { + if (length < sizeof(path)) { + editor_open_file(ed, path); + editor_request_render(ed); + break; + } } } break; @@ -1089,30 +1124,30 @@ static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) } else if (ev->key.key == YASL_KEY_BACKSPACE && ed->prompt_len > 0) { ed->prompt_buf[--ed->prompt_len] = '\0'; } - yasl_surface_request_frame(ed->surface); + editor_request_render(ed); break; } /* Function Keys */ if (ev->key.key == YASL_KEY_F3) { editor_find_next(ed, ed->search_query, !shift); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; } else if (ev->key.key == YASL_KEY_F5) { insert_date_time(ed); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; } /* Shortcuts */ if (ctrl) { switch (ev->key.key) { case YASL_KEY_Q: - return YASL_APP_QUIT; + ed->running = false; return; case YASL_KEY_N: editor_new_file(ed); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_S: if (shift || !ed->has_file) { ed->prompt = PROMPT_SAVE; @@ -1121,89 +1156,89 @@ static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) } else { editor_save_file(ed, ed->file_path); } - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_O: ed->prompt = PROMPT_OPEN; ed->prompt_len = 0; ed->prompt_buf[0] = '\0'; - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_F: ed->prompt = PROMPT_FIND; ed->prompt_len = 0; ed->prompt_buf[0] = '\0'; - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_H: ed->prompt = PROMPT_REPLACE_FIND; ed->prompt_len = 0; ed->prompt_buf[0] = '\0'; - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_G: ed->prompt = PROMPT_GOTO; ed->prompt_len = 0; ed->prompt_buf[0] = '\0'; - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_W: ed->word_wrap = !ed->word_wrap; editor_set_status(ed, ed->word_wrap ? "Word Wrap: ON" : "Word Wrap: OFF"); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_EQUAL: /* Zoom In */ if (ed->zoom_offset < 2) ed->zoom_offset++; editor_set_status(ed, "Zoom In"); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_MINUS: /* Zoom Out */ if (ed->zoom_offset > -1) ed->zoom_offset--; editor_set_status(ed, "Zoom Out"); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_0: /* Zoom Reset */ ed->zoom_offset = 0; editor_set_status(ed, "Zoom Reset"); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_A: ed->sel_anchor_row = 0; ed->sel_anchor_col = 0; ed->cursor_row = ed->line_count - 1; ed->cursor_col = (int)strlen(ed->lines[ed->line_count - 1]); ed->has_selection = true; - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_C: editor_copy(ed); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_X: editor_cut(ed); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_V: if (!yasl_clipboard_request(ed->surface, YASL_CLIPBOARD)) editor_set_status(ed, "Clipboard paste failed"); - return YASL_APP_CONTINUE; + return; case YASL_KEY_Z: if (shift) editor_redo(ed); else editor_undo(ed); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_Y: editor_redo(ed); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_BACKSPACE: handle_backspace(ed, true); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_DELETE: handle_delete(ed, true); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; default: break; } @@ -1212,7 +1247,7 @@ static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) /* Tab key */ if (ev->key.key == YASL_KEY_TAB) { handle_tab(ed, shift); - yasl_surface_request_frame(ed->surface); + editor_request_render(ed); break; } @@ -1301,38 +1336,40 @@ static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) break; } ed->caret_visible = true; - yasl_surface_request_frame(ed->surface); + editor_request_render(ed); } break; - case YASL_EVENT_POINTER_DOWN: { - if (ev->pointer.button == YASL_BUTTON_PRIMARY) { - struct yasl_geometry geom = yasl_surface_geometry(ed->surface); - int scale = (geom.scale_120 >= 240 ? 2 : 1) + ed->zoom_offset; + 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) { + int scale = (device_scale >= 2.0 ? 2 : 1) + ed->zoom_offset; if (scale < 1) scale = 1; int char_w = 8 * scale; int line_h = 16 * scale; int gutter_w = 48 * scale; int header_h = 24 * scale; - int row = (ev->pointer.pos.y - header_h) / line_h + ed->scroll_y; - int col = (ev->pointer.pos.x - gutter_w) / char_w + (ed->word_wrap ? 0 : ed->scroll_x); + int row = (py - header_h) / line_h + ed->scroll_y; + int col = (px - gutter_w) / char_w + (ed->word_wrap ? 0 : ed->scroll_x); if (row < 0) row = 0; if (row >= ed->line_count) row = ed->line_count - 1; int len = (int)strlen(ed->lines[row]); if (col < 0) col = 0; if (col > len) col = len; - uint64_t now = yasl_loop_now_ns(); + uint64_t now = yasl_time_ns(); if (now - ed->last_click_time < 350000000ull && - abs(ev->pointer.pos.x - ed->last_click_pos.x) < 8 && - abs(ev->pointer.pos.y - ed->last_click_pos.y) < 8) { + abs(px - (int)ed->last_click_x) < 8 && abs(py - (int)ed->last_click_y) < 8) { ed->click_count++; } else { ed->click_count = 1; } ed->last_click_time = now; - ed->last_click_pos = ev->pointer.pos; + ed->last_click_x = px; + ed->last_click_y = py; if (ed->click_count == 2) { /* Double click: select word */ @@ -1357,29 +1394,21 @@ static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) ed->mouse_dragging = true; } ed->caret_visible = true; - yasl_surface_request_frame(ed->surface); - } - break; - } - - case YASL_EVENT_POINTER_UP: - if (ev->pointer.button == YASL_BUTTON_PRIMARY) { + editor_request_render(ed); + } else if (ev->pointer.action == YASL_POINTER_UP && ev->pointer.button == YASL_BUTTON_PRIMARY) { ed->mouse_dragging = false; - } - break; - - case YASL_EVENT_POINTER_MOTION: - if (ed->mouse_dragging) { - struct yasl_geometry geom = yasl_surface_geometry(ed->surface); - int scale = (geom.scale_120 >= 240 ? 2 : 1) + ed->zoom_offset; + } else if (ev->pointer.action == YASL_POINTER_LEAVE) { + ed->mouse_dragging = false; + } else if (ev->pointer.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; int line_h = 16 * scale; int gutter_w = 48 * scale; int header_h = 24 * scale; - int row = (ev->pointer.pos.y - header_h) / line_h + ed->scroll_y; - int col = (ev->pointer.pos.x - gutter_w) / char_w + (ed->word_wrap ? 0 : ed->scroll_x); + int row = (py - header_h) / line_h + ed->scroll_y; + int col = (px - gutter_w) / char_w + (ed->word_wrap ? 0 : ed->scroll_x); if (row < 0) row = 0; if (row >= ed->line_count) row = ed->line_count - 1; int len = (int)strlen(ed->lines[row]); @@ -1389,56 +1418,81 @@ static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) ed->cursor_row = row; ed->cursor_col = col; ed->has_selection = (ed->cursor_row != ed->sel_anchor_row || ed->cursor_col != ed->sel_anchor_col); - yasl_surface_request_frame(ed->surface); + editor_request_render(ed); + } + break; + } + + case YASL_EVENT_PEN: { + 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.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; + case YASL_PEN_LEAVE: + case YASL_PEN_CANCEL: pointer.pointer.action = YASL_POINTER_LEAVE; break; + default: pointer.pointer.action = YASL_POINTER_MOVE; break; } + on_event(ed, &pointer); break; + } - case YASL_EVENT_POINTER_SCROLL: - if (ev->pointer.mods & (YASL_MOD_CTRL | YASL_MOD_SUPER)) { + case YASL_EVENT_SCROLL: + if (ev->scroll.mods & (YASL_MOD_CTRL | YASL_MOD_SUPER)) { /* Ctrl + Wheel = Zoom */ - if (ev->pointer.scroll.y > 0 && ed->zoom_offset < 2) ed->zoom_offset++; - else if (ev->pointer.scroll.y < 0 && ed->zoom_offset > -1) ed->zoom_offset--; - } else if (ev->pointer.mods & YASL_MOD_SHIFT) { + 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) { /* Shift + Wheel = Horizontal scroll */ - ed->scroll_x -= (int)(ev->pointer.scroll.y * 3.0); + ed->scroll_x -= (int)(ev->scroll.lines_y * 3.0); if (ed->scroll_x < 0) ed->scroll_x = 0; } else { /* Normal vertical scroll */ - ed->scroll_y -= (int)(ev->pointer.scroll.y * 3.0); + ed->scroll_y -= (int)(ev->scroll.lines_y * 3.0); if (ed->scroll_y < 0) ed->scroll_y = 0; if (ed->scroll_y >= ed->line_count) ed->scroll_y = ed->line_count - 1; } - yasl_surface_request_frame(ed->surface); + editor_request_render(ed); break; - case YASL_EVENT_USER: - if (ev->user.code == MSG_STATS_READY && ev->user.payload) { - struct doc_stats *s = (struct doc_stats *)ev->user.payload; - ed->stats = *s; - yasl_surface_request_frame(ed->surface); - } - break; + case YASL_EVENT_CLOSE: + ed->running = false; return; + case YASL_EVENT_QUIT: + ed->running = false; return; + case YASL_EVENT_DISPLAY_LOST: + ed->failed = true; + ed->running = false; + return; - case YASL_EVENT_WINDOW_CLOSE: - return YASL_APP_QUIT; + case YASL_EVENT_WINDOW: + ed->surface_scale = ev->window.scale; + if (ev->window.state & YASL_STATE_ACTIVATED) ed->caret_visible = true; + editor_request_render(ed); + break; + case YASL_EVENT_REFRESH: + editor_request_render(ed); + break; default: break; } - return YASL_APP_CONTINUE; + return; } -static void on_frame(void *ud, struct yasl_surface *s, struct yasl_frame *ctx) +static void editor_render(struct editor *ed) { - (void)s; - struct editor *ed = ud; - int w = ctx->target.size.x; - int h = ctx->target.size.y; - uint32_t *pix = ctx->target.pixels; - if (!pix || w <= 0 || h <= 0) return; - - int scale = (ctx->scale_120 >= 240 ? 2 : 1) + ed->zoom_offset; + 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); + int scale = (display_scale >= 2.0 ? 2 : 1) + ed->zoom_offset; if (scale < 1) scale = 1; int char_w = 8 * scale; int line_h = 16 * scale; @@ -1554,11 +1608,14 @@ static void on_frame(void *ud, struct yasl_surface *s, struct yasl_frame *ctx) fill_rect(pix, w, h, 0, status_y, w, status_h, bg_status); char status_left[160]; - if (ed->status_msg[0] && (yasl_loop_now_ns() - ed->status_msg_time < 3000000000ull)) { + if (ed->status_msg[0] && (yasl_time_ns() - ed->status_msg_time < 3000000000ull)) { snprintf(status_left, sizeof(status_left), "%s", ed->status_msg); } else { + pthread_mutex_lock(&ed->worker_mutex); + struct doc_stats stats = ed->stats; + pthread_mutex_unlock(&ed->worker_mutex); snprintf(status_left, sizeof(status_left), "Ln %d, Col %d Lines: %d Words: %d Chars: %d", - ed->cursor_row + 1, ed->cursor_col + 1, ed->line_count, ed->stats.words, ed->stats.chars); + ed->cursor_row + 1, ed->cursor_col + 1, ed->line_count, stats.words, stats.chars); } draw_string(pix, w, h, 12 * scale, status_y + 6 * scale, status_left, text_status, scale); @@ -1571,14 +1628,17 @@ static void on_frame(void *ud, struct yasl_surface *s, struct yasl_frame *ctx) ed->is_modified ? 0xfffab387 : status_accent, scale); } - if (!yasl_frame_present(ctx, NULL, 0)) - yasl_app_exit(yasl_surface_display(s), YASL_APP_ERROR); + if (!yasl_cpu_present(ed->surface)) { + fprintf(stderr, "Could not present CPU image\n"); + ed->failed = true; + ed->running = false; + return; + } + ed->dirty = false; } -static void on_quit(void *ud, struct yasl_display *d) +static void editor_shutdown(struct editor *ed) { - (void)d; - struct editor *ed = ud; ed->worker_running = false; if (ed->worker_started) { pthread_mutex_lock(&ed->worker_mutex); @@ -1586,6 +1646,7 @@ static void on_quit(void *ud, struct yasl_display *d) pthread_mutex_unlock(&ed->worker_mutex); pthread_join(ed->worker_tid, NULL); } + free(ed->worker_lines); if (ed->worker_cond_ready) pthread_cond_destroy(&ed->worker_cond); if (ed->worker_mutex_ready) pthread_mutex_destroy(&ed->worker_mutex); } @@ -1593,15 +1654,19 @@ static void on_quit(void *ud, struct yasl_display *d) int main(int argc, char **argv) { static struct editor ed; + bool smoke_test = false; - /* Check command-line file argument */ - if (argc > 1) { - snprintf(ed.file_path, sizeof(ed.file_path), "%s", argv[1]); - ed.has_file = true; - } else { + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--smoke-test")) smoke_test = true; + else if (!ed.has_file) { + snprintf(ed.file_path, sizeof(ed.file_path), "%s", argv[i]); + ed.has_file = true; + } + } + if (!ed.has_file) { const char *initial_content[] = { "// Welcome to YASL Notepad!", - "// A full-featured desktop text editor built on YASL Approach A.", + "// A full-featured desktop text editor built with YASL.", "", "#include <stdio.h>", "", @@ -1636,15 +1701,68 @@ int main(int argc, char **argv) } } - struct yasl_app_desc desc = { - .app_id = "org.yasl.notepad", - .backend = YASL_BACKEND_AUTO, - .userdata = &ed, - .init = on_init, - .event = on_event, - .frame = on_frame, - .quit = on_quit, - }; - - return yasl_run(&desc); + if (!yasl_set_app_id("org.yasl.notepad") || !editor_init(&ed)) { + fprintf(stderr, "Could not initialize YASL Notepad\n"); + editor_shutdown(&ed); + yasl_shutdown(); + return 1; + } + + editor_update_ime(&ed); + editor_render(&ed); + if (smoke_test) { + editor_shutdown(&ed); + yasl_surface_destroy(ed.surface); + yasl_shutdown(); + return ed.failed ? 1 : 0; + } + + uint64_t next_blink = yasl_time_ns() + 500000000ull; + uint64_t seen_stats = 0; + int exit_status = 0; + while (ed.running) { + uint64_t now = yasl_time_ns(); + int timeout_ms = next_blink > now ? (int)((next_blink - now + 999999ull) / 1000000ull) : 0; + yasl_event ev; + int result = yasl_wait_event(&ev, timeout_ms); + if (result > 0) { + on_event(&ed, &ev); + } else if (result < 0) { + fprintf(stderr, "YASL event wait failed\n"); + exit_status = 1; + break; + } + int queued = 0; + while (ed.running && (queued = yasl_wait_event(&ev, 0)) > 0) + on_event(&ed, &ev); + if (queued < 0) { + fprintf(stderr, "YASL event poll failed\n"); + exit_status = 1; + break; + } + + now = yasl_time_ns(); + if (now >= next_blink) { + ed.caret_visible = !ed.caret_visible; + ed.dirty = true; + next_blink = now + 500000000ull; + } + pthread_mutex_lock(&ed.worker_mutex); + uint64_t stats_generation = ed.stats_generation; + pthread_mutex_unlock(&ed.worker_mutex); + if (stats_generation != seen_stats) { + seen_stats = stats_generation; + ed.dirty = true; + } + editor_update_ime(&ed); + if (ed.dirty) { + editor_render(&ed); + editor_update_ime(&ed); + } + } + + editor_shutdown(&ed); + yasl_surface_destroy(ed.surface); + yasl_shutdown(); + return exit_status || ed.failed ? 1 : 0; } diff --git a/examples/simple_window.c b/examples/simple_window.c index 5a8b314..114bfbf 100644 --- a/examples/simple_window.c +++ b/examples/simple_window.c @@ -1,198 +1,50 @@ -#include <yasl/app.h> -#include <stdio.h> +/* Mouse and pen input on a CPU surface. */ +#include <yasl/yasl.h> +#include <yasl/cpu.h> #include <string.h> -static bool failed, smoke_test; -static int mouse_x = -1; -static int mouse_y = -1; -static bool mouse_down = false; -static int color_theme = 0; -static uint16_t pen_pressure = 0; -static int8_t pen_tilt_x = 0; -static int8_t pen_tilt_y = 0; -static enum yasl_pointer_device pointer_device = YASL_PTR_DEV_MOUSE; - -static const char *device_name(enum yasl_pointer_device dev) -{ - switch (dev) { - case YASL_PTR_DEV_PEN: return "Pen/Stylus"; - case YASL_PTR_DEV_ERASER: return "Eraser"; - case YASL_PTR_DEV_TOUCH: return "Touch"; - default: return "Mouse"; - } -} - -static void on_draw(void *ud, struct yasl_surface *s, struct yasl_frame *frame) +int main(int argc, char **argv) { - (void)ud; - int w = frame->target.size.x; - int h = frame->target.size.y; - uint32_t *pixels = frame->target.pixels; - if (!pixels || w <= 0 || h <= 0) - return; - - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - uint8_t r, g, b; - uint8_t u = (uint8_t)((x * 255) / (w > 0 ? w : 1)); - uint8_t v = (uint8_t)((y * 255) / (h > 0 ? h : 1)); - - switch (color_theme % 3) { - case 0: - r = u; - g = v; - b = 160; - break; - case 1: - r = 40; - g = u; - b = v; - break; - default: - r = v; - g = 60; - b = u; - break; - } - - /* Draw interactive pointer / stylus indicator */ - if (mouse_x >= 0 && mouse_y >= 0) { - int dx = x - mouse_x; - int dy = y - mouse_y; - int dist_sq = dx * dx + dy * dy; - - /* Pressure-sensitive radius: scales dynamically from 8px to 64px */ - int radius; - if (pen_pressure > 0) { - radius = 8 + (int)(((uint32_t)pen_pressure * 56) / 65535); - } else { - radius = mouse_down ? 36 : 18; - } - - if (dist_sq < radius * radius) { - r = (uint8_t)(255 - r / 2); - g = (uint8_t)(255 - g / 2); - b = (uint8_t)(255 - b / 2); + bool smoke = argc == 2 && !strcmp(argv[1], "--smoke-test"); + yasl_surface *s = yasl_surface_create("YASL input", 640, 480, YASL_SURFACE_CPU); + 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 || e.type == YASL_EVENT_DISPLAY_LOST) 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; + } + 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; + dirty = true; + } + if (e.type == YASL_EVENT_REFRESH) dirty = true; + if (dirty) { + int w, h; + uint32_t *pixels = yasl_cpu_pixels(s, &w, &h); + if (!pixels) break; + double scale = yasl_surface_get_scale(s), 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; } } - - pixels[y * w + x] = (255u << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; - } - } - - if (!yasl_frame_present(frame, NULL, 0)) { - fprintf(stderr, "Failed to present frame\n"); - failed = true; - } - if (failed) yasl_app_exit(yasl_surface_display(s), YASL_APP_ERROR); - else if (smoke_test) yasl_surface_destroy(s); -} - -static enum yasl_app_result on_init(void *ud, struct yasl_display *display) -{ - (void)ud; - struct yasl_window_spec window_spec = { - .title = "YASL Interactive Window (Pen & Touch Enabled)", - .size_logical = { .x = 640, .y = 480 }, - }; - struct yasl_surface *s = yasl_surface_create_toplevel(display, &window_spec); - if (!s) { - fprintf(stderr, "Failed to create surface\n"); - failed = true; - return YASL_APP_ERROR; - } - - yasl_surface_request_frame(s); - - printf("=== YASL Demo Running ===\n"); - printf(" - Stylus/Pen: full pressure (0..65535) and tilt detection\n"); - printf(" - Mouse/Touch: interactive click/motion highlights\n"); - printf(" - Space: cycle background themes\n"); - printf(" - Escape: exit\n\n"); - - return YASL_APP_CONTINUE; -} - -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 enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) -{ - (void)ud; - switch (ev->type) { - case YASL_EVENT_WINDOW_CLOSE: - 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.geometry.size.x, - ev->resize.geometry.size.y, (double)ev->resize.geometry.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_POINTER_DOWN: - case YASL_EVENT_POINTER_UP: - case YASL_EVENT_POINTER_MOTION: - pointer_sample(ev->pointer.pos, ev->pointer.device, ev->pointer.pen); - if (ev->type == YASL_EVENT_POINTER_DOWN && ev->pointer.button == YASL_BUTTON_PRIMARY) { - mouse_down = true; - } else if (ev->type == YASL_EVENT_POINTER_UP && ev->pointer.button == YASL_BUTTON_PRIMARY) { - mouse_down = false; - pen_pressure = 0; + if (!yasl_cpu_present(s)) break; + dirty = false; + frames++; } - break; - case YASL_EVENT_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_POINTER_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; -} - -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; + if (smoke && frames && n == 0) { result = 0; break; } } - 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; + yasl_shutdown(); + return result; } diff --git a/examples/tablet.c b/examples/tablet.c index ba1a94f..dad4b30 100644 --- a/examples/tablet.c +++ b/examples/tablet.c @@ -1,346 +1,109 @@ -/* Pressure-sensitive drawing using only YASL's portable API. */ -#include <yasl/app.h> - -#include <math.h> -#include <stdio.h> -#include <stdlib.h> +/* 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> -#define PAPER 0xfff5f2eau -#define INK 0xff315fadu -#define PANEL 0xff172338u -#define TEXT 0xffe5edf8u -#define ACCENT 0xff59d5b6u -#define HEADER 104 - -struct app { - bool smoke_test, failed, tablet_supported; - bool down, visible, previous; - struct yasl_surface *surface; - enum yasl_backend backend; - 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; -}; - -static const char *backend_name(enum yasl_backend backend) -{ - switch (backend) { - case YASL_BACKEND_WAYLAND: return "WAYLAND"; - case YASL_BACKEND_X11: return "X11"; - case YASL_BACKEND_COCOA: return "COCOA"; - default: return "AUTO"; - } -} - -static const char *device_name(enum yasl_pointer_device device) -{ - switch (device) { - case YASL_PTR_DEV_PEN: return "PEN"; - case YASL_PTR_DEV_ERASER: return "ERASER"; - case YASL_PTR_DEV_TOUCH: return "TOUCH"; - default: return "MOUSE"; - } -} - -static void cancel_stroke(struct app *app) -{ - app->down = app->previous = app->visible = false; - app->pointer.pen = (struct yasl_pointer_pen){0}; -} - -static bool resize_canvas(struct app *app, struct yasl_geometry geometry) -{ - int w = geometry.size.x, h = geometry.size.y; - if (w <= 0 || h <= 0 || !geometry.scale_120 || - (size_t)w > SIZE_MAX / sizeof(uint32_t) / (size_t)h) return false; - if (app->canvas && w == app->geometry.size.x && h == app->geometry.size.y && - geometry.scale_120 == app->geometry.scale_120) return true; - uint32_t *pixels = malloc((size_t)w * h * sizeof(*pixels)); - if (!pixels) return false; - double ratio = (double)app->geometry.scale_120 / geometry.scale_120; - for (int y = 0; y < h; y++) { - int old_y = (int)(y * ratio); - for (int x = 0; x < w; x++) { - int old_x = (int)(x * ratio); - pixels[(size_t)y * w + x] = app->canvas && - old_x < app->geometry.size.x && old_y < app->geometry.size.y ? - app->canvas[(size_t)old_y * app->geometry.size.x + old_x] : PAPER; - } - } - free(app->canvas); - app->canvas = pixels; - app->geometry = geometry; - cancel_stroke(app); - return true; -} - -static void clear_canvas(struct app *app) -{ - for (size_t i = 0, n = (size_t)app->geometry.size.x * app->geometry.size.y; i < n; i++) - app->canvas[i] = PAPER; - app->previous = false; -} - -static double brush_radius(const struct app *app) -{ - return ((double)app->geometry.scale_120 / YASL_SCALE_UNIT) * - ((app->pointer.device == YASL_PTR_DEV_PEN || - app->pointer.device == YASL_PTR_DEV_ERASER) ? - 2 + 24 * ((double)app->pointer.pen.pressure / 65535) : 6); -} - -static void stamp(struct app *app, double cx, double cy, double radius) -{ - int w = app->geometry.size.x, h = app->geometry.size.y; - int top = yasl_scale_to_device(HEADER, app->geometry.scale_120); - int x0 = (int)fmax(0, fmin(w, floor(cx - radius))); - int y0 = (int)fmax(top, fmin(h, floor(cy - radius))); - int x1 = (int)fmax(0, fmin(w, ceil(cx + radius))); - int y1 = (int)fmax(0, fmin(h, ceil(cy + radius))); - uint32_t color = app->pointer.device == YASL_PTR_DEV_ERASER ? PAPER : INK; - for (int y = y0; y < y1; y++) - for (int x = x0; x < x1; x++) { - double dx = x + 0.5 - cx, dy = y + 0.5 - cy; - if (dx * dx + dy * dy <= radius * radius) - app->canvas[(size_t)y * w + x] = color; - } -} - -static void stroke(struct app *app) -{ - double x = app->pointer.pos.x, y = app->pointer.pos.y; - double radius = brush_radius(app); - /* Leaving the canvas breaks the line so re-entry cannot bridge the UI. */ - if (x < 0 || x >= app->geometry.size.x || - y < HEADER * ((double)app->geometry.scale_120 / YASL_SCALE_UNIT) || - y >= app->geometry.size.y) { - app->previous = false; - return; - } - if (app->previous) { - double dx = x - app->last_x, dy = y - app->last_y; - int steps = (int)ceil(hypot(dx, dy)); - for (int i = 1; i <= steps; i++) { - double t = (double)i / steps; - stamp(app, app->last_x + dx * t, app->last_y + dy * t, - app->last_radius + (radius - app->last_radius) * t); - } - } - /* Also stamp pressure-only motion, even when position has not changed. */ - stamp(app, x, y, radius); - app->last_x = x; - app->last_y = y; - app->last_radius = radius; - app->previous = true; -} - -static enum yasl_app_result input(void *ud, const struct yasl_event *event) -{ - struct app *app = ud; - if (event->type == YASL_EVENT_WINDOW_CLOSE) - return YASL_APP_QUIT; - if (event->type == YASL_EVENT_WINDOW_RESIZE) { - if (!resize_canvas(app, event->resize.geometry)) { - app->failed = true; - return YASL_APP_ERROR; - } - } else if (event->type == YASL_EVENT_INPUT_RESET || event->type == YASL_EVENT_POINTER_LEAVE || - (event->type == YASL_EVENT_FOCUS && !event->focus.focused)) { - cancel_stroke(app); - } 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->type == YASL_EVENT_POINTER_DOWN || event->type == YASL_EVENT_POINTER_UP || - event->type == YASL_EVENT_POINTER_MOTION) { - if (app->pointer.device != event->pointer.device) cancel_stroke(app); - app->pointer.pos = event->pointer.pos; - app->pointer.device = event->pointer.device; - app->pointer.pen = event->pointer.pen; - app->visible = true; - if (event->type == YASL_EVENT_POINTER_DOWN && event->pointer.button == YASL_BUTTON_PRIMARY) { - app->down = true; - app->previous = false; - stroke(app); - } else if (event->type == YASL_EVENT_POINTER_UP && event->pointer.button == YASL_BUTTON_PRIMARY) { - app->down = app->previous = false; - app->pointer.pen.pressure = 0; - } else if (event->type == YASL_EVENT_POINTER_MOTION) { - 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. */ -static const uint8_t glyphs[128][7] = { - ['A']={14,17,17,31,17,17,17}, ['B']={30,17,17,30,17,17,30}, - ['C']={14,17,16,16,16,17,14}, ['D']={30,17,17,17,17,17,30}, - ['E']={31,16,16,30,16,16,31}, ['F']={31,16,16,30,16,16,16}, - ['G']={14,17,16,23,17,17,14}, ['H']={17,17,17,31,17,17,17}, - ['I']={14,4,4,4,4,4,14}, ['J']={7,2,2,2,2,18,12}, - ['K']={17,18,20,24,20,18,17}, ['L']={16,16,16,16,16,16,31}, - ['M']={17,27,21,21,17,17,17}, ['N']={17,25,21,19,17,17,17}, - ['O']={14,17,17,17,17,17,14}, ['P']={30,17,17,30,16,16,16}, - ['Q']={14,17,17,17,21,18,13}, ['R']={30,17,17,30,20,18,17}, - ['S']={15,16,16,14,1,1,30}, ['T']={31,4,4,4,4,4,4}, - ['U']={17,17,17,17,17,17,14}, ['V']={17,17,17,17,17,10,4}, - ['W']={17,17,17,21,21,21,10}, ['X']={17,17,10,4,10,17,17}, - ['Y']={17,17,10,4,4,4,4}, ['Z']={31,1,2,4,8,16,31}, - ['0']={14,17,19,21,25,17,14}, ['1']={4,12,4,4,4,4,14}, - ['2']={14,17,1,2,4,8,31}, ['3']={30,1,1,14,1,1,30}, - ['4']={2,6,10,18,31,2,2}, ['5']={31,16,16,30,1,1,30}, - ['6']={14,16,16,30,17,17,14}, ['7']={31,1,2,4,8,8,8}, - ['8']={14,17,17,14,17,17,14}, ['9']={14,17,17,15,1,1,14}, - ['-']={0,0,0,31,0,0,0}, ['+']={0,4,4,31,4,4,0}, - ['/']={1,1,2,4,8,16,16}, [':']={0,4,4,0,4,4,0}, - ['.']={0,0,0,0,0,4,4}, ['%']={17,2,4,4,8,16,17}, -}; - -static void rect(struct yasl_framebuffer *fb, int x, int y, int w, int h, uint32_t color) -{ - int right = x + w < fb->size.x ? x + w : fb->size.x; - int bottom = y + h < fb->size.y ? y + h : fb->size.y; - for (int j = y > 0 ? y : 0; j < bottom; j++) - for (int i = x > 0 ? x : 0; i < right; i++) - fb->pixels[(size_t)j * fb->size.x + i] = color; -} - -static void text(struct yasl_framebuffer *fb, double dpi, int x, int y, - const char *str, uint32_t color) -{ - for (; *str; str++, x += 12) { - unsigned ch = (unsigned char)*str; - if (ch >= 128) continue; - for (int row = 0; row < 7; row++) - for (int col = 0; col < 5; col++) { - if (!(glyphs[ch][row] & (1 << (4 - col)))) continue; - int left = (int)lround((x + col * 2) * dpi); - int top = (int)lround((y + row * 2) * dpi); - rect(fb, left, top, - (int)lround((x + col * 2 + 2) * dpi) - left, - (int)lround((y + row * 2 + 2) * dpi) - top, color); - } - } -} +#define PAPER 0xfff0eee8u +#define INK 0xff202830u -static void draw(void *ud, struct yasl_surface *surface, struct yasl_frame *frame) +static void stamp(uint32_t *pixels, int w, int h, double x, double y, double radius, bool erase) { - struct app *app = ud; - struct yasl_geometry geometry = {frame->target.size, frame->scale_120}; - if (!resize_canvas(app, geometry)) { - fprintf(stderr, "Cannot allocate drawing canvas\n"); - app->failed = true; - yasl_app_exit(yasl_surface_display(surface), YASL_APP_ERROR); - yasl_surface_destroy(surface); - app->surface = NULL; - return; - } - struct yasl_framebuffer target = {.size = frame->target.size, .pixels = frame->target.pixels}; - struct yasl_framebuffer *fb = ⌖ - 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); - char line[128]; - snprintf(line, sizeof(line), "YASL TABLET / %s / %s / %s", backend_name(app->backend), - device_name(app->pointer.device), app->down ? "CONTACT" : "HOVER"); - text(fb, dpi, 16, 12, line, TEXT); - if (app->tablet_supported) - snprintf(line, sizeof(line), "PRESSURE %5u / 65535 %5.1f%%", app->pointer.pen.pressure, - app->pointer.pen.pressure * 100.0 / 65535); - else snprintf(line, sizeof(line), "TABLET PROTOCOL UNAVAILABLE / MOUSE DRAWING"); - text(fb, dpi, 16, 36, line, ACCENT); - int bar_width = (int)lround(220 * dpi); - rect(fb, (int)(16 * dpi), (int)(58 * dpi), bar_width, (int)(6 * dpi), 0xff34455eu); - rect(fb, (int)(16 * dpi), (int)(58 * dpi), - (int)((uint32_t)app->pointer.pen.pressure * (uint64_t)bar_width / 65535), (int)(6 * dpi), ACCENT); - snprintf(line, sizeof(line), "TILT X %+d / Y %+d", app->pointer.pen.tilt_x, app->pointer.pen.tilt_y); - text(fb, dpi, 260, 54, line, TEXT); - text(fb, dpi, 16, 82, "DRAW WITH TIP / FLIP TO ERASE / C CLEAR / ESC QUIT", TEXT); - if (app->visible && app->pointer.pos.x >= 0 && app->pointer.pos.x < fb->size.x && - app->pointer.pos.y >= HEADER * dpi && app->pointer.pos.y < fb->size.y) { - double radius = brush_radius(app); - double cx = app->pointer.pos.x, cy = app->pointer.pos.y; - /* Brush outline and tilt vector are overlays, never stored in canvas. */ - for (int i = 0; i < 180; i++) { - double angle = i * (6.283185307179586 / 180); - rect(fb, (int)lround(cx + cos(angle) * radius), - (int)lround(cy + sin(angle) * radius), 1, 1, PANEL); + int left = (int)floor(x - radius), top = (int)floor(y - radius); + int right = (int)ceil(x + radius), bottom = (int)ceil(y + radius); + if (left < 0) left = 0; + if (top < 0) top = 0; + if (right > w) right = w; + if (bottom > h) bottom = h; + for (int row = top; row < bottom; row++) { + for (int col = left; col < right; col++) { + double dx = col - x, dy = row - y; + if (dx * dx + dy * dy <= radius * radius) pixels[(size_t)row * w + col] = erase ? PAPER : INK; } - double tx = app->pointer.pen.tilt_x * dpi / 2; - double ty = app->pointer.pen.tilt_y * dpi / 2; - for (int i = 0; i <= 64; i++) - rect(fb, (int)lround(cx + tx * i / 64), (int)lround(cy + ty * i / 64), 2, 2, ACCENT); - } - if (!yasl_frame_present(frame, NULL, 0)) { - app->failed = true; - } - if (app->failed) yasl_app_exit(yasl_surface_display(surface), YASL_APP_ERROR); - if (app->failed || app->smoke_test) { - yasl_surface_destroy(surface); - app->surface = NULL; } } -static enum yasl_app_result init(void *ud, struct yasl_display *display) +/* 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 app *app = ud; - 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_logical = {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; - return YASL_APP_ERROR; - } - yasl_surface_set_cursor(app->surface, YASL_CURSOR_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; + 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; + *painted_w = *w; + *painted_h = *h; + return pixels; } int main(int argc, char **argv) { - struct app app = {0}; - enum yasl_backend backend = YASL_BACKEND_AUTO; + bool smoke = false; for (int i = 1; i < argc; i++) { - if (!strcmp(argv[i], "--smoke-test")) app.smoke_test = true; + if (!strcmp(argv[i], "--smoke-test")) smoke = true; else if (!strcmp(argv[i], "--backend") && i + 1 < argc) { - const char *name = argv[++i]; - if (!strcmp(name, "auto")) backend = YASL_BACKEND_AUTO; - else if (!strcmp(name, "wayland")) backend = YASL_BACKEND_WAYLAND; - else if (!strcmp(name, "x11")) backend = YASL_BACKEND_X11; - else if (!strcmp(name, "cocoa")) backend = YASL_BACKEND_COCOA; - else { fprintf(stderr, "Unknown backend: %s\n", name); return 1; } - } else { - bool help = !strcmp(argv[i], "--help"); - fprintf(help ? stdout : stderr, - "Usage: %s [--backend auto|wayland|x11|cocoa] [--smoke-test]\n", argv[0]); - return help ? 0 : 1; + const char *b = argv[++i]; + if (!yasl_set_backend(!strcmp(b, "wayland") ? YASL_BACKEND_WAYLAND : + !strcmp(b, "x11") ? YASL_BACKEND_X11 : YASL_BACKEND_COCOA)) return 1; + } else return 1; + } + yasl_surface *s = yasl_surface_create("YASL pen canvas — C clears, Escape exits", 800, 600, YASL_SURFACE_CPU); + if (!s) return 1; + int painted_w = 0, painted_h = 0, frames = 0, result = 1; + bool contact = false; + double last_x = 0, last_y = 0; + yasl_event e; + for (;;) { + int n = yasl_wait_event(&e, smoke ? 20 : -1); + if (n < 0 || e.type == YASL_EVENT_DISPLAY_LOST) 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; + 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; + 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; + 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; + break; + case YASL_EVENT_POINTER: + x = e.pointer.x; y = e.pointer.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); + break; + case YASL_EVENT_INPUT_RESET: + contact = false; + break; + default: + break; + } + if (draw || present) { + int w, h; + int old_w = painted_w, old_h = painted_h; + uint32_t *pixels = canvas(s, &w, &h, &painted_w, &painted_h, clear); + if (!pixels) break; + if (w != old_w || h != old_h) contact = false; + if (draw) { + double scale = yasl_surface_get_scale(s); + 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)); + if (steps < 1) steps = 1; + for (int i = 1; i <= steps; i++) stamp(pixels, w, h, x - dx + dx * i / steps, y - dy + dy * i / steps, radius, erase); + last_x = x; last_y = y; + } + if (!yasl_cpu_present(s)) break; + frames++; } + if (e.type == YASL_EVENT_PEN || e.type == YASL_EVENT_POINTER) contact = down; } - struct yasl_app_desc desc = { - .app_id = "org.yasl.tablet", .backend = backend, .userdata = &app, - .init = init, .event = input, .frame = draw, - }; - int result = yasl_run(&desc); - if (result) perror("yasl_run"); - free(app.canvas); - return result || app.failed ? 1 : 0; + yasl_shutdown(); + return result; } diff --git a/include/yasl/app.h b/include/yasl/app.h deleted file mode 100644 index 78e62c4..0000000 --- a/include/yasl/app.h +++ /dev/null @@ -1,147 +0,0 @@ -/** - * @file - * Unified application runner, event pipeline, and presentation context. - * - * Implements the inverted lifecycle architecture for modern cross-platform - * applications (games, IDEs, and desktop tools). Instead of managing manual - * dispatch/poll/flush pumps, the application provides event and frame callbacks - * to yasl_run(), which drives platform runloops, display pacing, and live resizing. - */ -#ifndef YASL_APP_H -#define YASL_APP_H - -#include "yasl/yasl.h" -#include "yasl/loop.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** Application callback return status. */ -typedef enum yasl_app_result { - YASL_APP_CONTINUE = 0, /**< Continue running the application. */ - YASL_APP_QUIT, /**< Request graceful termination of yasl_run(). */ - YASL_APP_ERROR, /**< Request orderly cleanup and a failing run result. */ -} yasl_app_result; - -/** Migration adapter; prefer yasl_frame_present() from yasl.h. */ -YASL_API bool yasl_frame_present_cpu(struct yasl_frame *frame, - const struct yasl_rect2i *dirty_rects, int count); - -/** - * Safely post a custom user event to the main event loop from ANY thread. - * Wakes the display loop and delivers YASL_EVENT_USER to the event callback. - * @param d Display. - * @param s Optional target surface, or NULL. - * @param code Application-defined message code (yasl_user_event.code). - * @param payload Arbitrary pointer passed through to the event. - * @param size Optional payload size. - * @return true if queued; false if memory failed or d is not run by yasl_run(). - * Migration adapter: keep s alive through the call, and stop producers before - * shutdown. Prefer yasl_display_post() with a captured ID and release callback - * in either loop style; legacy payloads have no cancellation cleanup. - */ -YASL_API bool yasl_post_event(struct yasl_display *d, struct yasl_surface *s, - uint32_t code, void *payload, size_t size); - -/** - * Watch an open file descriptor or socket for poll-style readiness in the main loop. - * @param d Display. - * @param fd Open file descriptor (e.g. LSP stdout pipe or socket). - * @param poll_events Poll bitmask (e.g. POLLIN, POLLOUT). - * @param cb Readiness callback run on the main thread. - * @param ud Userdata. - * @return Positive watch ID, or -1 on failure. - */ -YASL_API int yasl_app_watch_fd(struct yasl_display *d, int fd, short poll_events, - int (*cb)(int fd, short revents, void *ud), void *ud); - -/** - * Unregister a file watch. - * @param d Display. - * @param watch_id Watch ID returned by yasl_app_watch_fd(). - */ -YASL_API void yasl_app_unwatch_fd(struct yasl_display *d, int watch_id); - -/** - * Schedule a repeating or one-shot timer on the display's event loop. - * @param d Display. - * @param interval_ns Delay in monotonic nanoseconds. - * @param cb Callback returning next interval in nanoseconds, or 0 to stop. - * @param ud Userdata. - * @return Positive timer ID, or 0 on failure. - */ -YASL_API yasl_timer_id yasl_app_add_timer(struct yasl_display *d, uint64_t interval_ns, - yasl_timer_cb cb, void *ud); - -/** - * Cancel an active timer. - * @param d Display. - * @param id Timer ID returned by yasl_app_add_timer(). - */ -YASL_API void yasl_app_del_timer(struct yasl_display *d, yasl_timer_id id); - -/** Application callbacks descriptor passed to yasl_run(). */ -struct yasl_app_desc { - /** Application identifier, e.g. "org.example.app". */ - const char *app_id; - /** Desired backend; YASL_BACKEND_AUTO chooses native with fallback. */ - enum yasl_backend backend; - /** User data passed to all callbacks. */ - void *userdata; - /** - * Optional toplevel created before init(), with its first frame requested. - * Borrowed for the call; spec cbs and ud are ignored as for any runner window. - * yasl_run() returns an error without calling init() if creation fails. - */ - const struct yasl_window_spec *window; - - /** - * Optional; called once after display and event machinery initialization. - * Use this to create further windows and initialize application resources. - * Return YASL_APP_CONTINUE, YASL_APP_QUIT, or YASL_APP_ERROR. - */ - enum yasl_app_result (*init)(void *ud, struct yasl_display *d); - - /** - * Unified event handler for input, window and user events, in display-wide enqueue order. - * 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, YASL_APP_QUIT, or YASL_APP_ERROR. Optional; without it, a - * WINDOW_CLOSE request on any window stops yasl_run(). - */ - enum yasl_app_result (*event)(void *ud, const struct yasl_event *ev); - - /** - * Invoked when a surface needs to present a frame. - * Synchronized automatically with display pacing and live window resizing. - */ - void (*frame)(void *ud, struct yasl_surface *s, struct yasl_frame *frame); - - /** - * Called right before the display is closed and yasl_run() exits. - * Destroy remaining surfaces and release application resources here. - */ - void (*quit)(void *ud, struct yasl_display *d); -}; - -/** Request orderly exit from any display-thread callback; ERROR returns failure. */ -YASL_API void yasl_app_exit(struct yasl_display *d, enum yasl_app_result result); -/** Borrow the optional desc.window surface during init or later; NULL if absent/destroyed. */ -YASL_API struct yasl_surface *yasl_app_window(struct yasl_display *d); - -/** - * Run the application with the inverted lifecycle runner. - * 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); - -#ifdef __cplusplus -} -#endif - -#endif /* YASL_APP_H */ diff --git a/include/yasl/cpu.h b/include/yasl/cpu.h new file mode 100644 index 0000000..d18f91f --- /dev/null +++ b/include/yasl/cpu.h @@ -0,0 +1,15 @@ +/** @file CPU surfaces: draw into the lent pixel buffer, then present it. */ +#ifndef YASL_CPU_H +#define YASL_CPU_H +#include "yasl/yasl.h" +#ifdef __cplusplus +extern "C" { +#endif +/** width*height premultiplied 0xAARRGGBB pixels, rows tightly packed. Contents + * persist; a resize keeps the overlap and zeroes the rest. Valid until the next wait. */ +YASL_API uint32_t *yasl_cpu_pixels(yasl_surface *surface, int *width, int *height); +YASL_API bool yasl_cpu_present(yasl_surface *surface); +#ifdef __cplusplus +} +#endif +#endif diff --git a/include/yasl/desktop.h b/include/yasl/desktop.h index 3e72a50..2c2379e 100644 --- a/include/yasl/desktop.h +++ b/include/yasl/desktop.h @@ -1,113 +1,21 @@ -/** - * @file - * Desktop services next to windowing: opening URIs, notifications, the bell, - * appearance preferences, and process paths. - * - * Display and surface functions use the display's opening thread (the main - * thread on Cocoa). Path and locale functions may be called from any thread. - * On Linux, notifications and appearance use the D-Bus session bus when YASL - * is built with libdbus-1; without it they report unsupported. - * - * Functions that fill a buffer return the full string length in bytes, - * excluding the NUL, or zero on failure. A result less than cap means out - * holds the NUL-terminated string. Otherwise out is unchanged; call again with - * a buffer of at least the result plus one. - */ +/** @file Optional desktop services; window operations use the owning thread. */ #ifndef YASL_DESKTOP_H #define YASL_DESKTOP_H - #include "yasl/yasl.h" - #ifdef __cplusplus extern "C" { #endif - -/** Appearance preference flags returned by yasl_display_appearance(). */ -enum yasl_appearance : uint32_t { - YASL_APPEARANCE_DARK = 1 << 0, /**< The user prefers dark colors. */ - YASL_APPEARANCE_LIGHT = 1 << 1, /**< The user prefers light colors. */ - YASL_APPEARANCE_HIGH_CONTRAST = 1 << 2, /**< The user asks for higher contrast. */ -}; - -/** - * Open a URI with the user's default handler. - * @param d Display, or NULL. - * @param uri NUL-terminated absolute URI with a scheme, such as https: or file:. - * @return true if the request was handed to the system; false for NULL - * arguments, a missing scheme, or a failed launch. - * YASL checks only the URI syntax. The application decides which schemes and - * hosts it trusts. Success does not confirm that a handler opened the URI. - * Linux runs xdg-open from PATH in a new session, with default signal - * dispositions, an empty signal mask, stdin on /dev/null, and no descriptors - * above stderr. It forks a short-lived helper first, which raises SIGCHLD; - * YASL reaps it unless the application's reaper does. Cocoa uses NSWorkspace. - */ -YASL_API bool yasl_display_open_uri(struct yasl_display *d, const char *uri); -/** - * Show a desktop notification. - * @param d Display, or NULL. - * @param title NUL-terminated UTF-8 summary. - * @param body NUL-terminated UTF-8 body, or NULL for none. - * @return true if the request was sent; false for NULL arguments or when no - * notification service is available. - * Linux sends org.freedesktop.Notifications.Notify with the display's app_id - * as the application name and desktop-entry hint. Cocoa uses the user - * notification center, which needs an app bundle with a bundle identifier, - * and asks for permission on first use. Success does not confirm that the - * notification was shown. - */ -YASL_API bool yasl_display_notify(struct yasl_display *d, const char *title, const char *body); -/** - * Read the user's appearance preferences. - * @param d Display, or NULL. - * @return A yasl_appearance bitmask, or zero for NULL, no preference, or no - * way to read it. - * Linux reads color-scheme and contrast from the XDG Settings portal, which - * waits briefly for a reply, then falls back to the GTK_THEME variable. Cocoa - * reads the effective appearance and the increase-contrast setting. The - * result is not cached and no change notification is sent; query again when - * the window is focused. - */ -YASL_API uint32_t yasl_display_appearance(struct yasl_display *d); -/** - * Ring the system bell for a surface. - * @param s Surface, or NULL. - * @return true if the request was sent; false for NULL or when unsupported. - * The system decides whether the bell is audible, visual, or muted. Wayland - * needs the xdg-system-bell protocol; X11 uses the XKB bell for the window - * and Cocoa uses NSBeep. - */ -YASL_API bool yasl_surface_bell(struct yasl_surface *s); -/** - * Get the absolute path of the running executable, with symbolic links resolved. - * @param out Output buffer, or NULL when cap is zero. - * @param cap Size of out in bytes. - * @return Path length, or zero on failure. See the buffer rule in this file. - */ -YASL_API size_t yasl_executable_path(char *out, size_t cap); -/** - * Get the resource directory of the app bundle that contains the executable. - * @param out Output buffer, or NULL when cap is zero. - * @param cap Size of out in bytes. - * @return Path length, or zero when the executable is not in an app bundle. - * Only Cocoa has app bundles; other platforms return zero. - */ -YASL_API size_t yasl_resource_dir(char *out, size_t cap); -/** - * Get the user's preferred locale as a POSIX locale name, such as "en_US.UTF-8". - * @param out Output buffer, or NULL when cap is zero. - * @param cap Size of out in bytes. - * @return Name length, or zero on failure. - * Linux returns the first nonempty LC_ALL, LC_CTYPE, or LANG, and "C.UTF-8" - * when none is set. Cocoa builds the name from the current locale's language - * and region with a ".UTF-8" codeset, because apps started from Finder have no - * locale variables. Use the result to set LANG for child processes; YASL does - * not change the process locale. - */ -YASL_API size_t yasl_default_locale(char *out, size_t cap); - +enum yasl_appearance { YASL_APPEARANCE_DARK=1u, YASL_APPEARANCE_LIGHT=2u, YASL_APPEARANCE_HIGH_CONTRAST=4u }; +YASL_API bool yasl_open_uri(const char *uri); +YASL_API bool yasl_notify(const char *title, const char *body); +YASL_API unsigned yasl_appearance(void); +YASL_API bool yasl_surface_bell(yasl_surface *surface); +/** Full byte length excluding NUL, zero on failure. Buffer unchanged unless cap + * exceeds returned length. NULL/zero queries size. These three are thread-safe. */ +YASL_API size_t yasl_executable_path(char *out, size_t capacity); +YASL_API size_t yasl_resource_dir(char *out, size_t capacity); +YASL_API size_t yasl_default_locale(char *out, size_t capacity); #ifdef __cplusplus } #endif - -#endif /* YASL_DESKTOP_H */ +#endif diff --git a/include/yasl/gl.h b/include/yasl/gl.h new file mode 100644 index 0000000..66a245a --- /dev/null +++ b/include/yasl/gl.h @@ -0,0 +1,27 @@ +/** @file Optional OpenGL/OpenGL ES contexts. Attribute lists are per-call. */ +#ifndef YASL_GL_H +#define YASL_GL_H +#include "yasl/yasl.h" +#ifdef __cplusplus +extern "C" { +#endif +enum yasl_gl_attribute { YASL_GL_END, YASL_GL_MAJOR, YASL_GL_MINOR, YASL_GL_PROFILE, YASL_GL_DEBUG, + YASL_GL_FORWARD_COMPAT, YASL_GL_RED_BITS, YASL_GL_GREEN_BITS, YASL_GL_BLUE_BITS, YASL_GL_ALPHA_BITS, + YASL_GL_DEPTH_BITS, YASL_GL_STENCIL_BITS, YASL_GL_SAMPLES, YASL_GL_SRGB, YASL_GL_API }; +enum yasl_gl_profile { YASL_GL_ANY, YASL_GL_CORE, YASL_GL_COMPATIBILITY }; +enum yasl_gl_api { YASL_GL_OPENGL, YASL_GL_OPENGL_ES }; +typedef void (*yasl_gl_proc)(void); +/** key/value pairs terminated by END, NULL defaults to GL3.2 core RGBA8 D24 S8. + * share is a live GL surface or NULL. CPU flag is invalid. Context is owned by + * the returned surface. Detach it from all rendering threads before destruction. */ +YASL_API yasl_surface *yasl_gl_surface_create(const char *title, int width, int height, + unsigned flags, const int *attributes, yasl_surface *share); +/** May run on a rendering thread. NULL detaches this thread's current context. */ +YASL_API bool yasl_gl_make_current(yasl_surface *surface); +YASL_API yasl_gl_proc yasl_gl_get_proc_address(const char *name); +YASL_API bool yasl_gl_swap_interval(yasl_surface *surface, int interval); +YASL_API bool yasl_gl_swap_buffers(yasl_surface *surface); +#ifdef __cplusplus +} +#endif +#endif diff --git a/include/yasl/keycodes.h b/include/yasl/keycodes.h index d34aeae..afa3eed 100644 --- a/include/yasl/keycodes.h +++ b/include/yasl/keycodes.h @@ -1,149 +1,142 @@ -/** - * @file - * Portable physical keyboard identities, included by yasl/yasl.h. - * Letter/punctuation names describe positions on a US keyboard, regardless of - * active layout or modifiers. Use YASL_EVENT_TEXT for text and codepoint/shifted - * in yasl_key_event for layout labels. These values are neither Unicode nor - * native scancodes; do not derive a character from a key enumerator. - */ +/** @file Physical keys, named by their position on a US keyboard whatever the + * layout. Use TEXT events for typed text and key.codepoint for layout labels. */ #ifndef YASL_KEYCODES_H #define YASL_KEYCODES_H #include <stdint.h> -/** Physical keys. Unrecognized native keys report YASL_KEY_UNKNOWN. */ enum yasl_key : uint32_t { YASL_KEY_UNKNOWN = 0x000, /**< No portable identity; native_code may still identify the key. */ - YASL_KEY_SPACE = 0x020, /**< Space. */ - YASL_KEY_APOSTROPHE = 0x027, /**< Apostrophe. */ - YASL_KEY_COMMA = 0x02c, /**< Comma. */ - YASL_KEY_MINUS = 0x02d, /**< Minus. */ - YASL_KEY_PERIOD = 0x02e, /**< Period. */ - YASL_KEY_SLASH = 0x02f, /**< Slash. */ - YASL_KEY_0 = 0x030, /**< Physical 0 key. */ - YASL_KEY_1 = 0x031, /**< Physical 1 key. */ - YASL_KEY_2 = 0x032, /**< Physical 2 key. */ - YASL_KEY_3 = 0x033, /**< Physical 3 key. */ - YASL_KEY_4 = 0x034, /**< Physical 4 key. */ - YASL_KEY_5 = 0x035, /**< Physical 5 key. */ - YASL_KEY_6 = 0x036, /**< Physical 6 key. */ - YASL_KEY_7 = 0x037, /**< Physical 7 key. */ - YASL_KEY_8 = 0x038, /**< Physical 8 key. */ - YASL_KEY_9 = 0x039, /**< Physical 9 key. */ - YASL_KEY_SEMICOLON = 0x03b, /**< Semicolon. */ - YASL_KEY_EQUAL = 0x03d, /**< Equal. */ - YASL_KEY_A = 0x041, /**< Physical US A position; independent of layout and modifiers. */ - YASL_KEY_B = 0x042, /**< Physical US B position; independent of layout and modifiers. */ - YASL_KEY_C = 0x043, /**< Physical US C position; independent of layout and modifiers. */ - YASL_KEY_D = 0x044, /**< Physical US D position; independent of layout and modifiers. */ - YASL_KEY_E = 0x045, /**< Physical US E position; independent of layout and modifiers. */ - YASL_KEY_F = 0x046, /**< Physical US F position; independent of layout and modifiers. */ - YASL_KEY_G = 0x047, /**< Physical US G position; independent of layout and modifiers. */ - YASL_KEY_H = 0x048, /**< Physical US H position; independent of layout and modifiers. */ - YASL_KEY_I = 0x049, /**< Physical US I position; independent of layout and modifiers. */ - YASL_KEY_J = 0x04a, /**< Physical US J position; independent of layout and modifiers. */ - YASL_KEY_K = 0x04b, /**< Physical US K position; independent of layout and modifiers. */ - YASL_KEY_L = 0x04c, /**< Physical US L position; independent of layout and modifiers. */ - YASL_KEY_M = 0x04d, /**< Physical US M position; independent of layout and modifiers. */ - YASL_KEY_N = 0x04e, /**< Physical US N position; independent of layout and modifiers. */ - YASL_KEY_O = 0x04f, /**< Physical US O position; independent of layout and modifiers. */ - YASL_KEY_P = 0x050, /**< Physical US P position; independent of layout and modifiers. */ - YASL_KEY_Q = 0x051, /**< Physical US Q position; independent of layout and modifiers. */ - YASL_KEY_R = 0x052, /**< Physical US R position; independent of layout and modifiers. */ - YASL_KEY_S = 0x053, /**< Physical US S position; independent of layout and modifiers. */ - YASL_KEY_T = 0x054, /**< Physical US T position; independent of layout and modifiers. */ - YASL_KEY_U = 0x055, /**< Physical US U position; independent of layout and modifiers. */ - YASL_KEY_V = 0x056, /**< Physical US V position; independent of layout and modifiers. */ - YASL_KEY_W = 0x057, /**< Physical US W position; independent of layout and modifiers. */ - YASL_KEY_X = 0x058, /**< Physical US X position; independent of layout and modifiers. */ - YASL_KEY_Y = 0x059, /**< Physical US Y position; independent of layout and modifiers. */ - YASL_KEY_Z = 0x05a, /**< Physical US Z position; independent of layout and modifiers. */ - YASL_KEY_LEFT_BRACKET = 0x05b, /**< Left Bracket. */ - YASL_KEY_BACKSLASH = 0x05c, /**< Backslash. */ - YASL_KEY_RIGHT_BRACKET = 0x05d, /**< Right Bracket. */ - YASL_KEY_GRAVE = 0x060, /**< Grave. */ - YASL_KEY_ESCAPE = 0x100, /**< Escape. */ - YASL_KEY_ENTER = 0x101, /**< Enter. */ - YASL_KEY_TAB = 0x102, /**< Tab. */ - YASL_KEY_BACKSPACE = 0x103, /**< Backspace. */ - YASL_KEY_INSERT = 0x104, /**< Insert. */ - YASL_KEY_DELETE = 0x105, /**< Delete. */ - YASL_KEY_RIGHT = 0x106, /**< Right. */ - YASL_KEY_LEFT = 0x107, /**< Left. */ - YASL_KEY_DOWN = 0x108, /**< Down. */ - YASL_KEY_UP = 0x109, /**< Up. */ - YASL_KEY_PAGE_UP = 0x10a, /**< Page Up. */ - YASL_KEY_PAGE_DOWN = 0x10b, /**< Page Down. */ - YASL_KEY_HOME = 0x10c, /**< Home. */ - YASL_KEY_END = 0x10d, /**< End. */ - YASL_KEY_CAPS_LOCK = 0x10e, /**< Caps Lock. */ - YASL_KEY_SCROLL_LOCK = 0x10f, /**< Scroll Lock. */ - YASL_KEY_NUM_LOCK = 0x110, /**< Num Lock. */ - YASL_KEY_PRINT_SCREEN = 0x111, /**< Print Screen. */ - YASL_KEY_PAUSE = 0x112, /**< Pause. */ - YASL_KEY_ISO_BACKSLASH = 0x113, /**< Iso Backslash. */ - YASL_KEY_YEN = 0x114, /**< Yen. */ - YASL_KEY_RO = 0x115, /**< Ro. */ - YASL_KEY_KANA = 0x116, /**< Kana. */ - YASL_KEY_EISU = 0x117, /**< Eisu. */ - YASL_KEY_CONVERT = 0x118, /**< Convert. */ - YASL_KEY_NONCONVERT = 0x119, /**< Nonconvert. */ - YASL_KEY_F1 = 0x120, /**< Function key F1. */ - YASL_KEY_F2 = 0x121, /**< Function key F2. */ - YASL_KEY_F3 = 0x122, /**< Function key F3. */ - YASL_KEY_F4 = 0x123, /**< Function key F4. */ - YASL_KEY_F5 = 0x124, /**< Function key F5. */ - YASL_KEY_F6 = 0x125, /**< Function key F6. */ - YASL_KEY_F7 = 0x126, /**< Function key F7. */ - YASL_KEY_F8 = 0x127, /**< Function key F8. */ - YASL_KEY_F9 = 0x128, /**< Function key F9. */ - YASL_KEY_F10 = 0x129, /**< Function key F10. */ - YASL_KEY_F11 = 0x12a, /**< Function key F11. */ - YASL_KEY_F12 = 0x12b, /**< Function key F12. */ - YASL_KEY_F13 = 0x12c, /**< Function key F13. */ - YASL_KEY_F14 = 0x12d, /**< Function key F14. */ - YASL_KEY_F15 = 0x12e, /**< Function key F15. */ - YASL_KEY_F16 = 0x12f, /**< Function key F16. */ - YASL_KEY_F17 = 0x130, /**< Function key F17. */ - YASL_KEY_F18 = 0x131, /**< Function key F18. */ - YASL_KEY_F19 = 0x132, /**< Function key F19. */ - YASL_KEY_F20 = 0x133, /**< Function key F20. */ - YASL_KEY_F21 = 0x134, /**< Function key F21. */ - YASL_KEY_F22 = 0x135, /**< Function key F22. */ - YASL_KEY_F23 = 0x136, /**< Function key F23. */ - YASL_KEY_F24 = 0x137, /**< Function key F24. */ - YASL_KEY_F25 = 0x138, /**< Function key F25. */ - YASL_KEY_KP_0 = 0x140, /**< Numeric keypad 0. */ - YASL_KEY_KP_1 = 0x141, /**< Numeric keypad 1. */ - YASL_KEY_KP_2 = 0x142, /**< Numeric keypad 2. */ - YASL_KEY_KP_3 = 0x143, /**< Numeric keypad 3. */ - YASL_KEY_KP_4 = 0x144, /**< Numeric keypad 4. */ - YASL_KEY_KP_5 = 0x145, /**< Numeric keypad 5. */ - YASL_KEY_KP_6 = 0x146, /**< Numeric keypad 6. */ - YASL_KEY_KP_7 = 0x147, /**< Numeric keypad 7. */ - YASL_KEY_KP_8 = 0x148, /**< Numeric keypad 8. */ - YASL_KEY_KP_9 = 0x149, /**< Numeric keypad 9. */ - YASL_KEY_KP_DECIMAL = 0x14a, /**< Numeric keypad decimal. */ - YASL_KEY_KP_DIVIDE = 0x14b, /**< Numeric keypad divide. */ - YASL_KEY_KP_MULTIPLY = 0x14c, /**< Numeric keypad multiply. */ - YASL_KEY_KP_SUBTRACT = 0x14d, /**< Numeric keypad subtract. */ - YASL_KEY_KP_ADD = 0x14e, /**< Numeric keypad add. */ - YASL_KEY_KP_ENTER = 0x14f, /**< Numeric keypad enter. */ - YASL_KEY_KP_EQUAL = 0x150, /**< Numeric keypad equal. */ - YASL_KEY_KP_COMMA = 0x151, /**< Numeric keypad comma. */ - YASL_KEY_KP_CLEAR = 0x152, /**< Numeric keypad clear. */ - YASL_KEY_LEFT_SHIFT = 0x160, /**< Left Shift. */ - YASL_KEY_LEFT_CONTROL = 0x161, /**< Left Control. */ - YASL_KEY_LEFT_ALT = 0x162, /**< Left Alt. */ - YASL_KEY_LEFT_SUPER = 0x163, /**< Left Super. */ - YASL_KEY_RIGHT_SHIFT = 0x164, /**< Right Shift. */ - YASL_KEY_RIGHT_CONTROL = 0x165, /**< Right Control. */ - YASL_KEY_RIGHT_ALT = 0x166, /**< Right Alt. */ - YASL_KEY_RIGHT_SUPER = 0x167, /**< Right Super. */ - YASL_KEY_MENU = 0x168, /**< Menu. */ - YASL_KEY_VOLUME_UP = 0x169, /**< Volume Up. */ - YASL_KEY_VOLUME_DOWN = 0x16a, /**< Volume Down. */ - YASL_KEY_MUTE = 0x16b, /**< Mute. */ + YASL_KEY_SPACE = 0x020, + YASL_KEY_APOSTROPHE = 0x027, + YASL_KEY_COMMA = 0x02c, + YASL_KEY_MINUS = 0x02d, + YASL_KEY_PERIOD = 0x02e, + YASL_KEY_SLASH = 0x02f, + YASL_KEY_0 = 0x030, + YASL_KEY_1 = 0x031, + YASL_KEY_2 = 0x032, + YASL_KEY_3 = 0x033, + YASL_KEY_4 = 0x034, + YASL_KEY_5 = 0x035, + YASL_KEY_6 = 0x036, + YASL_KEY_7 = 0x037, + YASL_KEY_8 = 0x038, + YASL_KEY_9 = 0x039, + YASL_KEY_SEMICOLON = 0x03b, + YASL_KEY_EQUAL = 0x03d, + YASL_KEY_A = 0x041, + YASL_KEY_B = 0x042, + YASL_KEY_C = 0x043, + YASL_KEY_D = 0x044, + YASL_KEY_E = 0x045, + YASL_KEY_F = 0x046, + YASL_KEY_G = 0x047, + YASL_KEY_H = 0x048, + YASL_KEY_I = 0x049, + YASL_KEY_J = 0x04a, + YASL_KEY_K = 0x04b, + YASL_KEY_L = 0x04c, + YASL_KEY_M = 0x04d, + YASL_KEY_N = 0x04e, + YASL_KEY_O = 0x04f, + YASL_KEY_P = 0x050, + YASL_KEY_Q = 0x051, + YASL_KEY_R = 0x052, + YASL_KEY_S = 0x053, + YASL_KEY_T = 0x054, + YASL_KEY_U = 0x055, + YASL_KEY_V = 0x056, + YASL_KEY_W = 0x057, + YASL_KEY_X = 0x058, + YASL_KEY_Y = 0x059, + YASL_KEY_Z = 0x05a, + YASL_KEY_LEFT_BRACKET = 0x05b, + YASL_KEY_BACKSLASH = 0x05c, + YASL_KEY_RIGHT_BRACKET = 0x05d, + YASL_KEY_GRAVE = 0x060, + YASL_KEY_ESCAPE = 0x100, + YASL_KEY_ENTER = 0x101, + YASL_KEY_TAB = 0x102, + YASL_KEY_BACKSPACE = 0x103, + YASL_KEY_INSERT = 0x104, + YASL_KEY_DELETE = 0x105, + YASL_KEY_RIGHT = 0x106, + YASL_KEY_LEFT = 0x107, + YASL_KEY_DOWN = 0x108, + YASL_KEY_UP = 0x109, + YASL_KEY_PAGE_UP = 0x10a, + YASL_KEY_PAGE_DOWN = 0x10b, + YASL_KEY_HOME = 0x10c, + YASL_KEY_END = 0x10d, + YASL_KEY_CAPS_LOCK = 0x10e, + YASL_KEY_SCROLL_LOCK = 0x10f, + YASL_KEY_NUM_LOCK = 0x110, + YASL_KEY_PRINT_SCREEN = 0x111, + YASL_KEY_PAUSE = 0x112, + YASL_KEY_ISO_BACKSLASH = 0x113, + YASL_KEY_YEN = 0x114, + YASL_KEY_RO = 0x115, + YASL_KEY_KANA = 0x116, + YASL_KEY_EISU = 0x117, + YASL_KEY_CONVERT = 0x118, + YASL_KEY_NONCONVERT = 0x119, + YASL_KEY_F1 = 0x120, + YASL_KEY_F2 = 0x121, + YASL_KEY_F3 = 0x122, + YASL_KEY_F4 = 0x123, + YASL_KEY_F5 = 0x124, + YASL_KEY_F6 = 0x125, + YASL_KEY_F7 = 0x126, + YASL_KEY_F8 = 0x127, + YASL_KEY_F9 = 0x128, + YASL_KEY_F10 = 0x129, + YASL_KEY_F11 = 0x12a, + YASL_KEY_F12 = 0x12b, + YASL_KEY_F13 = 0x12c, + YASL_KEY_F14 = 0x12d, + YASL_KEY_F15 = 0x12e, + YASL_KEY_F16 = 0x12f, + YASL_KEY_F17 = 0x130, + YASL_KEY_F18 = 0x131, + YASL_KEY_F19 = 0x132, + YASL_KEY_F20 = 0x133, + YASL_KEY_F21 = 0x134, + YASL_KEY_F22 = 0x135, + YASL_KEY_F23 = 0x136, + YASL_KEY_F24 = 0x137, + YASL_KEY_F25 = 0x138, + YASL_KEY_KP_0 = 0x140, + YASL_KEY_KP_1 = 0x141, + YASL_KEY_KP_2 = 0x142, + YASL_KEY_KP_3 = 0x143, + YASL_KEY_KP_4 = 0x144, + YASL_KEY_KP_5 = 0x145, + YASL_KEY_KP_6 = 0x146, + YASL_KEY_KP_7 = 0x147, + YASL_KEY_KP_8 = 0x148, + YASL_KEY_KP_9 = 0x149, + YASL_KEY_KP_DECIMAL = 0x14a, + YASL_KEY_KP_DIVIDE = 0x14b, + YASL_KEY_KP_MULTIPLY = 0x14c, + YASL_KEY_KP_SUBTRACT = 0x14d, + YASL_KEY_KP_ADD = 0x14e, + YASL_KEY_KP_ENTER = 0x14f, + YASL_KEY_KP_EQUAL = 0x150, + YASL_KEY_KP_COMMA = 0x151, + YASL_KEY_KP_CLEAR = 0x152, + YASL_KEY_LEFT_SHIFT = 0x160, + YASL_KEY_LEFT_CONTROL = 0x161, + YASL_KEY_LEFT_ALT = 0x162, + YASL_KEY_LEFT_SUPER = 0x163, + YASL_KEY_RIGHT_SHIFT = 0x164, + YASL_KEY_RIGHT_CONTROL = 0x165, + YASL_KEY_RIGHT_ALT = 0x166, + YASL_KEY_RIGHT_SUPER = 0x167, + YASL_KEY_MENU = 0x168, + YASL_KEY_VOLUME_UP = 0x169, + YASL_KEY_VOLUME_DOWN = 0x16a, + YASL_KEY_MUTE = 0x16b, }; #endif /* YASL_KEYCODES_H */ diff --git a/include/yasl/metal.h b/include/yasl/metal.h new file mode 100644 index 0000000..695f80c --- /dev/null +++ b/include/yasl/metal.h @@ -0,0 +1,20 @@ +/** @file Direct Metal layer; application configures its device/format and presents. */ +#ifndef YASL_METAL_H +#define YASL_METAL_H +#include "yasl/yasl.h" +#ifdef __OBJC__ +@class CAMetalLayer; +#else +typedef struct objc_object CAMetalLayer; +#endif +#ifdef __cplusplus +extern "C" { +#endif +/** Borrowed until surface destruction. YASL owns view/layer and drawableSize. + * No device, drawable acquisition, command submission, or implicit present. + * Returns NULL on unsupported backend, CPU/GL surface, or incompatible presenter. */ +YASL_API CAMetalLayer *yasl_metal_layer(yasl_surface *surface); +#ifdef __cplusplus +} +#endif +#endif diff --git a/include/yasl/native/cocoa.h b/include/yasl/native/cocoa.h index 9b221cf..bd92c98 100644 --- a/include/yasl/native/cocoa.h +++ b/include/yasl/native/cocoa.h @@ -1,55 +1,23 @@ -/** - * @file - * Typed borrowed Cocoa handles; available on macOS. Use on the main thread. - */ #ifndef YASL_NATIVE_COCOA_H #define YASL_NATIVE_COCOA_H - #include "yasl/yasl.h" - #ifdef __OBJC__ @class NSApplication; @class NSWindow; @class NSView; #else -typedef struct objc_object NSApplication; /**< Opaque Cocoa application in plain C/C++. */ -typedef struct objc_object NSWindow; /**< Opaque Cocoa window in plain C/C++. */ -typedef struct objc_object NSView; /**< Opaque Cocoa view in plain C/C++. */ +typedef struct objc_object NSApplication; +typedef struct objc_object NSWindow; +typedef struct objc_object NSView; #endif - #ifdef __cplusplus extern "C" { #endif - -/* Return borrowed Cocoa handles (expire on display/surface destruction). - * Return NULL / nil if display/surface is not a Cocoa object or NULL. */ -/** - * Get the borrowed NSApplication for an object of the matching backend. - * @param d Live display, or NULL. - * @return The handle, or zero/NULL for NULL or a different backend. - * Expires on display destruction. Available only when this backend is built. - * Never pass a pointer to a destroyed object. - */ -YASL_API NSApplication *yasl_cocoa_display_get_application(struct yasl_display *d); -/** - * Get the borrowed NSWindow for an object of the matching backend. - * @param s Live surface, or NULL. - * @return The handle, or zero/NULL for NULL or a different backend. - * Expires on surface destruction. Available only when this backend is built. - * Never pass a pointer to a destroyed object. - */ -YASL_API NSWindow *yasl_cocoa_surface_get_window(struct yasl_surface *s); -/** - * Get the borrowed NSView for an object of the matching backend. - * @param s Live surface, or NULL. - * @return The handle, or zero/NULL for NULL or a different backend. - * Expires on surface destruction. Available only when this backend is built. - * Never pass a pointer to a destroyed object. - */ -YASL_API NSView *yasl_cocoa_surface_get_view(struct yasl_surface *s); - +/** Borrowed native objects. Use on main thread, never change native ownership. */ +YASL_API NSApplication *yasl_cocoa_application(void); +YASL_API NSWindow *yasl_cocoa_window(const yasl_surface *surface); +YASL_API NSView *yasl_cocoa_view(const yasl_surface *surface); #ifdef __cplusplus } #endif - -#endif /* YASL_NATIVE_COCOA_H */ +#endif diff --git a/include/yasl/native/wayland.h b/include/yasl/native/wayland.h index b11e3b8..1b8881e 100644 --- a/include/yasl/native/wayland.h +++ b/include/yasl/native/wayland.h @@ -1,40 +1,15 @@ -/** - * @file - * Typed borrowed Wayland handles; available when built with Wayland support. - */ #ifndef YASL_NATIVE_WAYLAND_H #define YASL_NATIVE_WAYLAND_H - #include "yasl/yasl.h" - +struct wl_display; +struct wl_surface; #ifdef __cplusplus extern "C" { #endif - -struct wl_display; -struct wl_surface; - -/* Return borrowed Wayland handles (expire on display/surface destruction). - * Return NULL if display/surface is not a Wayland object or NULL. */ -/** - * Get the borrowed Wayland connection for an object of the matching backend. - * @param d Live display, or NULL. - * @return The handle, or zero/NULL for NULL or a different backend. - * Expires on display destruction. Available only when this backend is built. - * Never pass a pointer to a destroyed object. - */ -YASL_API struct wl_display *yasl_wayland_display_get_wl_display(struct yasl_display *d); -/** - * Get the borrowed Wayland surface for an object of the matching backend. - * @param s Live surface, or NULL. - * @return The handle, or zero/NULL for NULL or a different backend. - * Expires on surface destruction. Available only when this backend is built. - * Never pass a pointer to a destroyed object. - */ -YASL_API struct wl_surface *yasl_wayland_surface_get_wl_surface(struct yasl_surface *s); - +/** Borrowed handles; never dispatch or close native connections independently. */ +YASL_API struct wl_display *yasl_wayland_display(const yasl_surface *surface); +YASL_API struct wl_surface *yasl_wayland_surface(const yasl_surface *surface); #ifdef __cplusplus } #endif - -#endif /* YASL_NATIVE_WAYLAND_H */ +#endif diff --git a/include/yasl/native/x11.h b/include/yasl/native/x11.h index b719b4b..f8c37ae 100644 --- a/include/yasl/native/x11.h +++ b/include/yasl/native/x11.h @@ -1,40 +1,14 @@ -/** - * @file - * Typed borrowed X11 handles; available when built with X11 support. - */ #ifndef YASL_NATIVE_X11_H #define YASL_NATIVE_X11_H - #include "yasl/yasl.h" -#include <stdint.h> - +typedef struct _XDisplay Display; #ifdef __cplusplus extern "C" { #endif - -typedef struct _XDisplay Display; /**< Opaque Xlib connection, compatible with Xlib.h. */ - -/* Return borrowed X11 handles (expire on display/surface destruction). - * Return NULL / 0 if display/surface is not an X11 object or NULL. */ -/** - * Get the borrowed X11 Display for an object of the matching backend. - * @param d Live display, or NULL. - * @return The handle, or zero/NULL for NULL or a different backend. - * Expires on display destruction. Available only when this backend is built. - * Never pass a pointer to a destroyed object. - */ -YASL_API Display *yasl_x11_display_get_display(struct yasl_display *d); -/** - * Get the borrowed X11 Window ID for an object of the matching backend. - * @param s Live surface, or NULL. - * @return The handle, or zero/NULL for NULL or a different backend. - * Expires on surface destruction. Available only when this backend is built. - * Never pass a pointer to a destroyed object. - */ -YASL_API uintptr_t yasl_x11_surface_get_window(struct yasl_surface *s); - +/** Borrowed handles; surface/window lifetime and native connection stay in YASL. */ +YASL_API Display *yasl_x11_display(const yasl_surface *surface); +YASL_API uintptr_t yasl_x11_window(const yasl_surface *surface); #ifdef __cplusplus } #endif - -#endif /* YASL_NATIVE_X11_H */ +#endif diff --git a/include/yasl/vulkan.h b/include/yasl/vulkan.h new file mode 100644 index 0000000..afa1089 --- /dev/null +++ b/include/yasl/vulkan.h @@ -0,0 +1,19 @@ +/** @file Vulkan WSI only; application owns all Vulkan objects. */ +#ifndef YASL_VULKAN_H +#define YASL_VULKAN_H +#include "yasl/yasl.h" +#include <vulkan/vulkan.h> +#ifdef __cplusplus +extern "C" { +#endif +YASL_API bool yasl_vulkan_init(PFN_vkGetInstanceProcAddr loader); +/** Borrowed list through shutdown; merge with app extensions before instance creation. */ +YASL_API const char *const *yasl_vulkan_instance_extensions(const yasl_surface *surface, uint32_t *count); +YASL_API VkInstanceCreateFlags yasl_vulkan_instance_flags(const yasl_surface *surface); +/** Uses injected loader; destroy returned VkSurfaceKHR before YASL surface. */ +YASL_API VkResult yasl_vulkan_create_surface(yasl_surface *surface, VkInstance instance, + const VkAllocationCallbacks *allocator, VkSurfaceKHR *out); +#ifdef __cplusplus +} +#endif +#endif diff --git a/include/yasl/yasl.h b/include/yasl/yasl.h index 889f70a..350339b 100644 --- a/include/yasl/yasl.h +++ b/include/yasl/yasl.h @@ -1,1089 +1,152 @@ -/** - * @file - * Portable desktop surfaces, CPU drawing, input, and display dispatch. - * - * All calls and callbacks use the display's opening thread (the main thread on - * Cocoa), unless explicitly documented otherwise. Userdata is borrowed; callback - * tables and creation specs are copied. Do not recursively dispatch/flush or - * destroy a display/loop while dispatch or a callback is active. Destroy child - * popups before parents, surfaces before displays, displays before borrowed loops. - * Surface destruction from its own callback is supported; stop using it immediately. - * - * Zero-initialize option and callback structs before setting fields. Pointers to - * destroyed objects are invalid, even for getters and destroy functions. NULL - * handling is documented per function; it does not make freed pointers safe. - * Only functions explicitly documenting errno guarantee a diagnostic there. - * Other boolean results report success/availability; void setters are best effort. - * New window requests, clipboard operations, frame presentation, and posting - * document errno on failure. Event strings belong to the display queue. - * - * Toplevel creation sizes use logical pixels. Geometry, framebuffers, pointer - * positions, damage, IME carets, popup specs, and drag offsets use device pixels. - * Coordinates have their origin at the upper left; x grows right and y grows down. - * Scale is an integer ratio with YASL_SCALE_UNIT (120) representing 1x. - * See the application guide for the display-wide dispatch/poll/flush cycle - * and backend limits. Legacy surface polling is an input-only filtered adapter. - */ +/** @file Windows, input and events. The first surface starts the backend; use the + * creating thread (main on Cocoa) for every call except yasl_wake. Coordinates are + * logical units unless named pixel. */ #ifndef YASL_H #define YASL_H - #include <stdbool.h> #include <stddef.h> #include <stdint.h> -#include <limits.h> -#include <float.h> -#include <math.h> - -#include "yasl/timing.h" #include "yasl/keycodes.h" - +#if defined(__GNUC__) +#define YASL_API __attribute__((visibility("default"))) +#else +#define YASL_API +#endif #ifdef __cplusplus extern "C" { #endif - -/** Public shared-library symbol visibility annotation. */ -#if defined(_WIN32) || defined(__CYGWIN__) - #if defined(YASL_BUILD_SHARED) - #define YASL_API __declspec(dllexport) - #elif defined(YASL_SHARED) - #define YASL_API __declspec(dllimport) - #else - #define YASL_API - #endif -#elif defined(__GNUC__) && __GNUC__ >= 4 - #define YASL_API __attribute__((visibility("default"))) -#else - #define YASL_API -#endif - -struct yasl_display; -struct yasl_surface; -struct yasl_loop; - -/* Geometry */ -/** A two-dimensional integer size, position, or offset; units depend on the caller. */ -struct yasl_vector2i { - /** Horizontal coordinate or width. */ - int x; - /** Vertical coordinate or height. */ - int y; -}; - -/** Two-dimensional fractional offset; units depend on the containing structure. */ -struct yasl_vector2d { - /** Horizontal component. */ - double x; - /** Vertical component. */ - double y; -}; - -/** Integer rectangle; origin is upper left and extents must be nonnegative. */ -struct yasl_rect2i { - /** Left coordinate. */ - int x; - /** Top coordinate. */ - int y; - /** Nonnegative horizontal extent. */ - int width; - /** Nonnegative vertical extent. */ - int height; -}; - -/** Scale numerator representing 1x. */ -#define YASL_SCALE_UNIT 120 -/** Backend minimum logical dimension used when constraining toplevel sizes. */ -#define YASL_MIN_LOGICAL_PX 64 - -/* Nearest integer, ties away from zero. Zero scale returns zero; finite - * out-of-range results saturate. Non-finite logical coordinates return zero. */ -/** Convert a logical coordinate to device pixels, rounding ties away from zero. - * @param logical Logical coordinate; non-finite values return zero. - * @param scale_120 Scale numerator; zero returns zero. - * @return Rounded device coordinate, saturated to the range of int. - */ -static inline int yasl_scale_to_device(double logical, uint32_t scale_120) -{ - if (!scale_120 || !(logical >= -DBL_MAX && logical <= DBL_MAX)) return 0; - double px = (scale_120 == YASL_SCALE_UNIT) ? logical : (logical * ((double)scale_120 / YASL_SCALE_UNIT)); - if (px >= INT_MAX) return INT_MAX; - if (px <= INT_MIN) return INT_MIN; - return (int)(px + copysign(0.5, px)); -} - -/** Convert a device coordinate to logical pixels, rounding ties away from zero. - * @param device Device coordinate. - * @param scale_120 Scale numerator; zero returns zero. - * @return Rounded logical coordinate, saturated to the range of int. - */ -static inline int yasl_scale_to_logical(int device, uint32_t scale_120) -{ - if (!scale_120) return 0; - if (scale_120 == YASL_SCALE_UNIT) return device; - int64_t px = (int64_t)device * YASL_SCALE_UNIT; - int64_t half = (int64_t)(scale_120 / 2); - int64_t bias = (px < 0) ? -half : half; - px = (px + bias) / scale_120; - if (px > INT_MAX) return INT_MAX; - if (px < INT_MIN) return INT_MIN; - return (int)px; -} - -/* Capabilities */ -/** Optional backend/server features, returned as a bitmask by yasl_display_caps(). */ -enum yasl_display_cap : uint32_t { - YASL_CAP_POPUPS = 1 << 0, /**< Popup creation and placement. */ - YASL_CAP_DRAG = 1 << 1, /**< Surface-drag source and target support. */ - YASL_CAP_DRAG_CARRY = 1 << 2, /**< Carry a toplevel during a drag. */ - YASL_CAP_PLACE_WINDOW = 1 << 3, /**< Place a window relative to the pointer. */ - YASL_CAP_PRIMARY_SELECTION = 1 << 4, /**< Separate primary selection. */ - YASL_CAP_CSD = 1 << 5, /**< Backend advertises client decoration support; check each action result. */ - YASL_CAP_NATIVE_GRAPHICS = 1 << 6, /**< Application-managed native rendering. */ - YASL_CAP_FRAME_PACING = 1 << 7, /**< Compositor/display-driven draws. */ - /* Backend/protocol supports tablet axes, independent of attached hardware. */ - YASL_CAP_TABLET = 1 << 8, /**< Tablet protocol support, independent of attached hardware. */ -}; - -/** Independent selections. An unsupported selection never aliases the clipboard. */ -enum yasl_selection : uint8_t { - YASL_CLIPBOARD, /**< Explicit copy/paste selection. */ - YASL_PRIMARY, /**< Independent primary selection, if supported. */ -}; - -/** Reported surface state flags; absent flags may also mean unsupported state. */ -enum yasl_surface_state : uint32_t { - YASL_STATE_FULLSCREEN = 1 << 0, /**< Fullscreen. */ - YASL_STATE_MAXIMIZED = 1 << 1, /**< Maximized. */ - 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. */ -enum yasl_cursor { - YASL_CURSOR_ARROW, /**< Default arrow. */ - YASL_CURSOR_TEXT, /**< Text insertion. */ - YASL_CURSOR_LINK, /**< Link/pointer hand. */ - YASL_CURSOR_CROSSHAIR, /**< Crosshair. */ - YASL_CURSOR_MOVE, /**< Move in any direction. */ - YASL_CURSOR_RESIZE_NWSE, /**< Northwest/southeast resize. */ - YASL_CURSOR_RESIZE_NESW, /**< Northeast/southwest resize. */ - YASL_CURSOR_RESIZE_EW, /**< Horizontal resize. */ - YASL_CURSOR_RESIZE_NS, /**< Vertical resize. */ - YASL_CURSOR_HIDDEN, /**< Hidden system cursor; pointer input continues. */ - YASL_CURSOR_NONE = YASL_CURSOR_HIDDEN, /**< Compatibility alias for HIDDEN. */ -}; - -/** Valid interactive resize edges; combinations are restricted to the named values. */ -enum yasl_resize_edge : uint32_t { - YASL_EDGE_NONE = 0, /**< No resize edge; rejected by interactive resize. */ - YASL_EDGE_TOP = 1, /**< Top edge. */ - YASL_EDGE_BOTTOM = 2, /**< Bottom edge. */ - YASL_EDGE_LEFT = 4, /**< Left edge. */ - YASL_EDGE_TOP_LEFT = 5, /**< Top-left corner. */ - YASL_EDGE_BOTTOM_LEFT = 6, /**< Bottom-left corner. */ - YASL_EDGE_RIGHT = 8, /**< Right edge. */ - YASL_EDGE_TOP_RIGHT = 9, /**< Top-right corner. */ - YASL_EDGE_BOTTOM_RIGHT = 10, /**< Bottom-right corner. */ -}; - -/* Damage & Framebuffer - * CPU pixels are tightly packed premultiplied ARGB8888 (0xAARRGGBB). - * Size, pointer positions, IME caret and damage use device pixels; surface - * toplevel creation sizes use logical pixels. Damage is a hint: backends may upload - * the whole frame. Every pixel must contain valid content on each draw. - */ -/** Maximum number of damage rectangles in one presentation. */ -#define YASL_DAMAGE_MAX 64 - -/** Damage hints for one presentation; every target still needs complete valid pixels. */ -struct yasl_damage_list { - /** -1: full target; 0: no damaged rectangles; otherwise number of rects. */ - int n; - /** Reserved hint; callers may leave zero. Core presentation uses rects. */ - struct yasl_rect2i bounds; - /** First n rectangles, in device pixels; ignored for n == -1. */ - struct yasl_rect2i rects[YASL_DAMAGE_MAX]; -}; - -/** Borrowed draw target, valid only for the current draw callback. Never free pixels. */ -struct yasl_framebuffer { - /** Opaque draw identity. Preserve it and all target fields when copying. */ - uint64_t generation; - /** Writable premultiplied ARGB8888, row stride size.x * sizeof(uint32_t); may be NULL for native rendering. */ - uint32_t *pixels; - /** Target extent in device pixels. */ - struct yasl_vector2i size; - /** Row stride in bytes, populated for the active draw callback. */ - int stride_bytes; -}; - -/** One draw invocation. Do not retain its pointer after the callback returns. */ -struct yasl_frame { - /** Surface being drawn. */ - struct yasl_surface *surface; - /** Target to initialize and present before returning. */ - struct yasl_framebuffer target; - /** Zero-based index of this surface draw. */ - uint64_t frame_index; - /** Estimated animation time; not confirmed presentation. */ - uint64_t target_time_ns; - /** Device scale numerator, with 120 representing 1x. */ - uint32_t scale_120; -}; - -/** Coalesced redraw policy, valid with either loop style. */ -typedef enum yasl_pacing_mode { - YASL_PACING_ON_DEMAND = 0, /**< Draw only when invalidated or exposed. */ - YASL_PACING_CONTINUOUS, /**< Draw continuously using native pacing or a timer. */ -} yasl_pacing_mode; - -/* Drag and Drop */ -/** Target notifications for YASL surface drags, separate from external file drops. */ -enum yasl_drag_event { - YASL_DRAG_OVER, /**< Surface drag entered or moved over the target. */ - YASL_DRAG_LEAVE, /**< Surface drag left the target. */ - YASL_DRAG_DROP, /**< Surface drag dropped on the target. */ -}; - -/** External text/file drag hover notifications; completed data arrives through input. */ -enum yasl_drop_event { - YASL_DROP_OVER, /**< External data drag entered or moved over the target. */ - YASL_DROP_LEAVE, /**< External data drag left the target. */ -}; - -/** Source-side drag completion; DROPPED does not prove application-level acceptance. */ -enum yasl_drag_end { - YASL_DRAG_DROPPED, /**< Drag ended in a drop; not an application acceptance receipt. */ - YASL_DRAG_CANCELLED, /**< Drag canceled, including display close. */ -}; - -/** Copied drag icon in tightly packed premultiplied 0xAARRGGBB device pixels. */ -struct yasl_drag_image { - /** Copied pixel array; required when an image is supplied. */ - const uint32_t *pixels; - /** Positive width and height in device pixels. */ - struct yasl_vector2i size; - /** Pointer position within the image, in device pixels. */ - struct yasl_vector2i hotspot; -}; - -/** Copied drag completion listener; userdata remains owned by the application. */ -struct yasl_drag_listener { - /** Called exactly once after a successful start, possibly synchronously on cancel/close. */ - void (*end)(void *ud, enum yasl_drag_end how); -}; - -/** Configured device-pixel content size and the corresponding display scale. */ -struct yasl_geometry { - /** Current content extent in device pixels. */ - struct yasl_vector2i size; - /** Positive scale numerator; 120 means 1x and 180 means 1.5x. */ - uint32_t scale_120; -}; - -/* Input Model */ -/** Keyboard press, release, or autorepeat; releases must not insert text. */ -enum yasl_key_action { - YASL_KEY_PRESS, /**< Key pressed. */ - YASL_KEY_RELEASE, /**< Key released. */ - YASL_KEY_REPEAT, /**< Key autorepeat. */ -}; - -/** Modifier bitmask. SUPER is Command on macOS. */ -enum yasl_mod : uint32_t { - YASL_MOD_SHIFT = 1 << 0, /**< Shift modifier. */ - YASL_MOD_ALT = 1 << 1, /**< Alt/Option modifier. */ - YASL_MOD_CTRL = 1 << 2, /**< Control modifier. */ - YASL_MOD_SUPER = 1 << 3, /**< Super/Command modifier. */ - YASL_MOD_CAPS = 1 << 4, /**< Caps Lock modifier. */ -}; - -/** Pointer source class; capability support does not imply an attached device. */ -enum yasl_pointer_device { - YASL_PTR_DEV_MOUSE, /**< Mouse or trackpad pointer. */ - YASL_PTR_DEV_TOUCH, /**< Touch translated into pointer input. */ - YASL_PTR_DEV_PEN, /**< Tablet stylus. */ - YASL_PTR_DEV_ERASER, /**< Tablet eraser tool. */ -}; - -/** Pointer button; meaningful only for POINTER_DOWN and POINTER_UP. */ -enum yasl_pointer_button { - YASL_BUTTON_PRIMARY = 0, /**< Left button, pen tip, or touch contact. */ - YASL_BUTTON_MIDDLE = 1, /**< Middle button / wheel click. */ - YASL_BUTTON_SECONDARY = 2, /**< Right button or pen barrel button. */ - YASL_BUTTON_BACK = 3, /**< Side "back" button. */ - YASL_BUTTON_FORWARD = 4, /**< Side "forward" button. */ -}; - -/** Bit for a yasl_pointer_button in yasl_pointer_event.buttons. */ +typedef struct yasl_surface yasl_surface; +typedef struct yasl_event yasl_event; +enum yasl_backend { YASL_BACKEND_AUTO, YASL_BACKEND_WAYLAND, YASL_BACKEND_X11, YASL_BACKEND_COCOA }; +enum yasl_surface_flags { YASL_SURFACE_CPU=1u, YASL_SURFACE_HIDDEN=2u, YASL_SURFACE_UNDECORATED=4u, YASL_SURFACE_FIXED_SIZE=8u }; +enum yasl_error { YASL_ERROR_NONE, YASL_ERROR_INVALID, YASL_ERROR_UNSUPPORTED, YASL_ERROR_NO_MEMORY, + YASL_ERROR_NOT_INITIALIZED, YASL_ERROR_BUSY, YASL_ERROR_WRONG_THREAD, YASL_ERROR_CONTEXT_EXPIRED, YASL_ERROR_IO }; +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)) - -/** Pointer capture mode for a surface. */ -enum yasl_pointer_mode { - YASL_POINTER_NORMAL = 0, /**< Absolute positions; cursor per yasl_surface_set_cursor(). */ - /** Cursor hidden and locked in place while the surface has focus. POINTER_MOTION - * reports unbounded relative motion in delta; pos stays at the lock position. - * The lock is released on focus loss and re-acquired on focus gain. */ - YASL_POINTER_RELATIVE, -}; - -/** Normalized tablet axes; zero for unavailable axes and non-tablet devices. */ -struct yasl_pointer_pen { - /** 0..65535 normalized to device range. Zero alone does not end contact. */ - uint16_t pressure; - /** Degrees in -90..90; positive right. */ - int8_t tilt_x; - /** Degrees in -90..90; positive down. */ - int8_t tilt_y; -}; - -/** Event tag. Each tag lists the union member it selects. */ -enum yasl_event_type { - YASL_EVENT_NONE = 0, - - /* Input: produced by backends, returned by yasl_surface_poll_event(). */ - YASL_EVENT_KEY, /**< .key */ - YASL_EVENT_TEXT, /**< .text — committed UTF-8; insert this, not key codepoints. */ - YASL_EVENT_POINTER_DOWN, /**< .pointer — button/tip pressed; button valid. */ - YASL_EVENT_POINTER_UP, /**< .pointer — button/tip released; button valid. */ - YASL_EVENT_POINTER_MOTION, /**< .pointer — position, relative motion, or pen axes changed. */ - YASL_EVENT_POINTER_SCROLL, /**< .pointer — scroll valid. */ - YASL_EVENT_POINTER_LEAVE, /**< .pointer — last known pos/device; cancel hover state. */ - YASL_EVENT_FOCUS, /**< .focus — keyboard focus changed; cancel held state on loss. */ - YASL_EVENT_PASTE, /**< .text — UTF-8 from yasl_display_paste(). */ - YASL_EVENT_DROP, /**< .text — dropped text/uri-list: CRLF-separated file URLs, not paths. */ - YASL_EVENT_INPUT_RESET, /**< No payload. Input stream interrupted; clear held keys, buttons, strokes, IME. */ - - /* Window and application: part of the display FIFO, as well as runner delivery. - * Legacy window callbacks also receive matching native notifications. */ - YASL_EVENT_WINDOW_RESIZE, /**< .resize — size and/or scale changed; reallocate and redraw. */ - YASL_EVENT_WINDOW_CLOSE, /**< No payload. User asked to close the window. */ - YASL_EVENT_USER, /**< .user — posted application message. */ - YASL_EVENT_WINDOW_STATE, /**< .state — reported state changed. */ - YASL_EVENT_DRAG, /**< .drag — surface-drag target notification. */ - YASL_EVENT_DROP_HOVER, /**< .drop — external drag hover/leave. */ - YASL_EVENT_COMPOSITION, /**< .composition — preedit snapshot, including ending. */ - YASL_EVENT_DISPLAY_QUIT, /**< No payload; system requested application quit. */ - YASL_EVENT_DISPLAY_REOPEN, /**< No payload; system requested activation. */ - YASL_EVENT_DISPLAY_LOST, /**< No payload; display connection failed. */ -}; - -/** Physical keyboard transition with optional layout labels. - * key is independent of modifiers/layout and stable between press and release. - * codepoint and shifted are optional single Unicode scalars for the current - * layout at levels zero/one, not text input. Zero means unavailable, a control - * key, or a layout mapping that produces multiple characters. Insert committed - * YASL_EVENT_TEXT into documents instead. */ -struct yasl_key_event { - /** Portable physical key; YASL_KEY_UNKNOWN when unmapped. */ - enum yasl_key key; - /** Press, release, or autorepeat. */ - enum yasl_key_action action; - /** yasl_mod bitmask at this event. */ - uint32_t mods; - /** Optional unshifted layout Unicode scalar; zero if unavailable. */ - uint32_t codepoint; - /** Optional shifted layout Unicode scalar; zero if unavailable. */ - uint32_t shifted; - /** Native code: Wayland evdev, X11 server keycode, Cocoa virtual keycode. */ - uint32_t native_code; -}; - -/** 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. */ -struct yasl_pointer_event { - /** Surface-relative device-pixel position. Frozen while YASL_POINTER_RELATIVE is locked. */ - struct yasl_vector2i pos; - /** MOTION: device-pixel movement since the previous sample on this surface. - * Unbounded under YASL_POINTER_RELATIVE; zero after enter, leave, or reset. */ - struct yasl_vector2d delta; - /** SCROLL: fractional line offsets after system preferences; positive right/down. */ - struct yasl_vector2d scroll; - /** DOWN/UP: which button changed. */ - enum yasl_pointer_button button; - /** YASL_BUTTON_BIT() mask of buttons held after this event, on every - * POINTER_* type. Tracked from this queue's DOWN and UP events for all - * devices; INPUT_RESET clears it. On Wayland LEAVE clears it too, since the - * compositor sends leave during a press only when it takes the pointer. - * X11 and Cocoa keep sending motion and the release after LEAVE. */ - uint32_t buttons; - /** Mouse, touch, pen, or eraser. */ - enum yasl_pointer_device device; - /** yasl_mod bitmask at this event. */ - uint32_t mods; - /** Current tablet axes, including on pressure-only motion; missing axes are zero. */ - struct yasl_pointer_pen pen; -}; - -/** Borrowed NUL-terminated UTF-8, shared by TEXT, PASTE and DROP. */ -struct yasl_text_event { - /** Borrowed NUL-terminated bytes; see yasl_surface_poll_event() for lifetime. */ - const char *data; - /** Length in bytes excluding the terminating NUL. */ - size_t len; -}; - -/** Clipboard transfer outcome. */ -enum yasl_transfer_status { - YASL_TRANSFER_OK = 0, /**< Text received successfully, including empty text. */ - YASL_TRANSFER_UNAVAILABLE, /**< No text selection is available. */ - YASL_TRANSFER_FAILED, /**< Transfer or decoding failed. */ -}; - -/** Clipboard completion; data/len alias the legacy text payload. */ -struct yasl_paste_event { - const char *data; /**< Borrowed NUL-terminated UTF-8; empty on failure. */ - size_t len; /**< Byte length excluding NUL. */ - uint64_t request_id; /**< Zero for a legacy paste request. */ - enum yasl_transfer_status status; /**< Completion outcome. */ -}; - -/** Borrowed UTF-8 preedit snapshot, valid until the next successful display poll. */ -struct yasl_composition_event { - const char *data; /**< Borrowed NUL-terminated UTF-8 snapshot. */ - size_t len; /**< Byte length excluding NUL. */ - int begin; /**< UTF-8 byte offset, or -1 if unavailable. */ - int end; /**< UTF-8 byte offset, or -1 if unavailable. */ - bool active; /**< Whether composition text is present. */ -}; - -/** Reported window-state change. */ -struct yasl_state_event { uint32_t flags; /**< Current YASL_STATE_* flags. */ }; -/** Surface-drag target notification; position is in device pixels. */ -struct yasl_drag_target_event { - enum yasl_drag_event action; /**< Hover, leave, or drop. */ - struct yasl_vector2i pos; /**< Surface-relative device pixels. */ -}; -/** External drag hover notification; position is in device pixels. */ -struct yasl_drop_hover_event { - enum yasl_drop_event action; /**< Hover or leave. */ - struct yasl_vector2i pos; /**< Surface-relative device pixels. */ -}; - -/** Keyboard focus transition. */ -struct yasl_focus_event { - /** true on focus gain, false on loss. */ - bool focused; -}; - -/** New configured geometry for a surface. */ -struct yasl_resize_event { - /** Content size in device pixels and display scale. */ - struct yasl_geometry geometry; - /** true when scale_120 differs from the previous resize of this surface. */ - bool scale_changed; -}; - -/** Application message posted with yasl_post_event(). */ -struct yasl_user_event { - /** Application-defined message code. */ - uint32_t code; - /** Pointer passed through unchanged; ownership stays with the application. */ - void *payload; - /** Optional payload size, passed through unchanged. */ - size_t size; -}; - -/** One tagged event; read only the union member selected by type. */ +enum yasl_pen_tool { YASL_PEN_UNKNOWN, YASL_PEN_PEN, YASL_PEN_ERASER, YASL_PEN_BRUSH, YASL_PEN_AIRBRUSH, YASL_PEN_PENCIL }; +enum yasl_pen_action { YASL_PEN_ENTER, YASL_PEN_LEAVE, YASL_PEN_MOVE, YASL_PEN_DOWN, YASL_PEN_UP, YASL_PEN_BUTTON, YASL_PEN_CANCEL }; +enum yasl_pen_axis { YASL_PEN_PRESSURE=1u, YASL_PEN_TILT=2u, YASL_PEN_ROTATION=4u, YASL_PEN_DISTANCE=8u, YASL_PEN_TANGENTIAL=16u, YASL_PEN_WHEEL=32u }; +enum yasl_scroll_valid { YASL_SCROLL_LINES=1u, YASL_SCROLL_PIXELS=2u }; +enum yasl_scroll_source { YASL_SCROLL_UNKNOWN, YASL_SCROLL_WHEEL, YASL_SCROLL_FINGER, YASL_SCROLL_CONTINUOUS }; +enum yasl_scroll_phase { YASL_SCROLL_PHASE_UNKNOWN, YASL_SCROLL_BEGIN, YASL_SCROLL_UPDATE, YASL_SCROLL_END }; +enum yasl_transfer_status { YASL_TRANSFER_OK, YASL_TRANSFER_UNAVAILABLE, YASL_TRANSFER_FAILED }; +enum yasl_text_purpose { YASL_TEXT_NORMAL, YASL_TEXT_PASSWORD, YASL_TEXT_NUMBER, YASL_TEXT_TERMINAL }; +enum yasl_cursor { YASL_CURSOR_ARROW, YASL_CURSOR_TEXT, YASL_CURSOR_LINK, YASL_CURSOR_CROSSHAIR, YASL_CURSOR_MOVE, + YASL_CURSOR_RESIZE_NWSE, YASL_CURSOR_RESIZE_NESW, YASL_CURSOR_RESIZE_EW, YASL_CURSOR_RESIZE_NS, YASL_CURSOR_HIDDEN }; +enum yasl_surface_state { YASL_STATE_FULLSCREEN=1u, YASL_STATE_MAXIMIZED=2u, YASL_STATE_ACTIVATED=4u, + YASL_STATE_SUSPENDED=8u, YASL_STATE_CSD=16u, YASL_STATE_TILED=32u }; +enum yasl_feature { YASL_CAP_POPUPS=1u, YASL_CAP_DRAG=2u, YASL_CAP_DRAG_CARRY=4u, YASL_CAP_PLACE_WINDOW=8u, + YASL_CAP_PRIMARY_SELECTION=16u, YASL_CAP_CSD=32u, YASL_CAP_NATIVE_GRAPHICS=64u, YASL_CAP_FRAME_PACING=128u, + YASL_CAP_TABLET=256u, YASL_CAP_OPENGL=512u, YASL_CAP_VULKAN=1024u, YASL_CAP_TEXT_INPUT=2048u }; +enum yasl_popup_edge { YASL_POPUP_CENTER, YASL_POPUP_TOP, YASL_POPUP_BOTTOM, YASL_POPUP_LEFT, + YASL_POPUP_RIGHT, YASL_POPUP_TOP_LEFT, YASL_POPUP_BOTTOM_LEFT, YASL_POPUP_TOP_RIGHT, YASL_POPUP_BOTTOM_RIGHT }; +enum yasl_popup_constraint { YASL_POPUP_SLIDE_X=1u, YASL_POPUP_SLIDE_Y=2u, YASL_POPUP_FLIP_X=4u, + YASL_POPUP_FLIP_Y=8u, YASL_POPUP_RESIZE_X=16u, YASL_POPUP_RESIZE_Y=32u }; +enum yasl_resize_edge { YASL_EDGE_TOP=1, YASL_EDGE_BOTTOM=2, YASL_EDGE_LEFT=4, YASL_EDGE_TOP_LEFT=5, + YASL_EDGE_BOTTOM_LEFT=6, YASL_EDGE_RIGHT=8, YASL_EDGE_TOP_RIGHT=9, YASL_EDGE_BOTTOM_RIGHT=10 }; +enum yasl_event_type { YASL_EVENT_NONE, YASL_EVENT_CLOSE, YASL_EVENT_QUIT, YASL_EVENT_REOPEN, YASL_EVENT_WINDOW, + YASL_EVENT_REFRESH, YASL_EVENT_KEY, YASL_EVENT_TEXT, YASL_EVENT_POINTER, YASL_EVENT_SCROLL, YASL_EVENT_PEN, + YASL_EVENT_INPUT_RESET, YASL_EVENT_COMPOSITION, YASL_EVENT_TEXT_EDIT, YASL_EVENT_CLIPBOARD, YASL_EVENT_DROP, + YASL_EVENT_DRAG, YASL_EVENT_DISPLAY_LOST }; +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 }; +/** 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. */ struct yasl_event { - /** Selects the valid union member. */ enum yasl_event_type type; - /** Recipient surface, or NULL for display-wide events. Can differ from the - * polled surface for routed popup input. */ - struct yasl_surface *surface; - /** Monotonic timestamp in nanoseconds, same clock as yasl_loop_now_ns(). */ + yasl_surface *surface; uint64_t time_ns; - /** Increasing display-wide enqueue order; zero for standalone private queues. */ - uint64_t sequence; - /** Stable recipient identity; zero for display-wide events. */ - uint64_t surface_id; - union { - struct yasl_key_event key; /**< KEY */ - struct yasl_pointer_event pointer; /**< POINTER_* */ - struct yasl_text_event text; /**< TEXT, legacy PASTE, DROP */ - struct yasl_paste_event paste; /**< PASTE */ - struct yasl_composition_event composition; /**< COMPOSITION */ - struct yasl_state_event state; /**< WINDOW_STATE */ - struct yasl_drag_target_event drag; /**< DRAG */ - struct yasl_drop_hover_event drop; /**< DROP_HOVER */ - struct yasl_focus_event focus; /**< FOCUS */ - struct yasl_resize_event resize; /**< WINDOW_RESIZE */ - struct yasl_user_event user; /**< USER */ - }; -}; - -/* Ownership and threading - * All calls and callbacks run on the display's opening thread (main thread on - * Cocoa). Displays borrow supplied loops; destroy surfaces before displays, - * popups before parents, and displays before their supplied loop. - * Specs, strings, callback tables and drag listeners are copied during calls. - * Userdata is borrowed until callbacks are removed or the object/drag ends. - * Surface destruction is allowed from its callbacks; display/loop destruction - * during dispatch or callbacks is not supported. - */ - -/* Callbacks */ -/** Optional display notifications. NULL callback members are ignored. */ -struct yasl_display_cbs { - /** Application quit request; arrange orderly shutdown after dispatch returns. */ - void (*quit)(void *ud); - /** Application reopen/activation request, currently Cocoa. */ - void (*reopen)(void *ud); - /** Display connection lost; arrange cleanup after dispatch returns. */ - void (*lost)(void *ud); -}; - -/** Optional window/popup notifications, copied from yasl_window_spec.cbs or by -yasl_surface_set_callbacks(). -Callbacks may destroy their surface. They must then return without further use -of the surface or draw target. NULL members are ignored. */ -struct yasl_window_cbs { - /** Initialize the entire target and optionally present. Requested through schedule_redraw/flush. */ - void (*draw)(struct yasl_surface *s, struct yasl_frame *frame, void *ud); - /** Configured size/scale changed; reallocate application content and request a redraw. */ - 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; 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, - struct yasl_vector2i pos, void *ud); - /** External data-drag hover or leave; pos is in device pixels. */ - void (*drop)(struct yasl_surface *s, enum yasl_drop_event ev, - struct yasl_vector2i pos, void *ud); -}; - -/* Display Lifecycle */ -/** Runtime backend choice. AUTO selects a native backend and allows fallback. */ -enum yasl_backend { - YASL_BACKEND_AUTO = 0, /**< Choose native backend with fallback. */ - YASL_BACKEND_WAYLAND, /**< Require Wayland. */ - YASL_BACKEND_X11, /**< Require X11. */ - YASL_BACKEND_COCOA, /**< Require Cocoa. */ -}; - -/** Display options. Zero-initialize for automatic backend selection. */ -struct yasl_display_spec { - /** Copied application identifier, conventionally a reverse-domain UTF-8 string; NULL uses backend defaults. */ - const char *app_id; - /** Copied optional display callbacks; NULL disables them. */ - const struct yasl_display_cbs *cbs; - /** Borrowed display callback userdata. */ - void *ud; - - /** Zero/AUTO chooses native backend; explicit choices never fall back. */ - enum yasl_backend backend; -}; - -/* Return NULL and set errno if the requested backend cannot open. */ -/** - * Open a desktop connection and its event machinery. - * @param loop Borrowed loop, or NULL to create a display-owned loop. - * @param spec Copied options, or NULL for automatic backend selection and defaults. - * @return An owned display, or NULL with errno set. EINVAL means an invalid backend; - * ENOSYS means the requested backend was not built; ENODEV is the fallback when - * opening fails without a more specific platform error. Explicit choices never - * fall back. Close with yasl_display_close() after destroying its surfaces. - */ -YASL_API struct yasl_display *yasl_display_open(struct yasl_loop *loop, - const struct yasl_display_spec *spec); -/** - * Read the selected backend. - * @param d Display, or NULL. - * @return The actual backend, or YASL_BACKEND_AUTO for NULL. - */ -YASL_API enum yasl_backend yasl_display_backend(const struct yasl_display *d); -/** - * Close a display and release its owned loop, if any. - * @param d Display to close, or NULL for no action. - * Destroy all surfaces first. Cancels an active drag and may invoke its end - * listener. Must not be called during dispatch or from a callback. - */ -YASL_API void yasl_display_close(struct yasl_display *d); -/** - * Query currently available optional features. - * @param d Display, or NULL. - * @return A bitmask of yasl_display_cap values, or zero for NULL. - * Capabilities describe backend/server support, not attached input hardware or - * permission to perform an operation at any arbitrary moment. - */ -YASL_API uint32_t yasl_display_caps(const struct yasl_display *d); -/* Process events, waiting at most timeout_ms: zero polls, negative waits. - * Pending input or redraw work returns immediately. Returns -1 on failure. */ -/** - * Receive events and run ready loop sources. - * @param d Display to dispatch. - * @param timeout_ms Maximum wait in milliseconds: zero polls, negative waits indefinitely. - * @return A nonnegative backend-specific work count on success, or -1 with errno - * set on failure (EINVAL for NULL, EIO if no specific backend error is available). - * Do not interpret the count as the number of queued input events. Pending input - * or runnable redraw work prevents waiting. Drain input and call - * yasl_display_flush() after updating application state. Do not dispatch recursively. - */ -YASL_API int yasl_display_dispatch(struct yasl_display *d, int timeout_ms); -/** - * Draw scheduled surfaces and send pending display requests. - * @param d Display, or NULL for no action. - * May synchronously invoke draw and other callbacks. A paced surface may wait - * for its next display/compositor signal. This is not a GPU completion wait. - */ -YASL_API void yasl_display_flush(struct yasl_display *d); -/** - * Read the latest modifier state known to the backend. - * @param d Display, or NULL. - * @return A bitmask of yasl_mod values, or zero for NULL. - * For an individual event use its own mods field instead. - */ -YASL_API int yasl_display_modifiers(const struct yasl_display *d); -/** Double-click interval used when the system reports none. */ -#define YASL_DOUBLE_CLICK_MS_DEFAULT 400 -/** - * Read the system's double-click interval. - * @param d Display, or NULL. - * @return Maximum milliseconds between the clicks of a double click, or - * YASL_DOUBLE_CLICK_MS_DEFAULT when d is NULL or the system reports none. - * X11 reads the XSETTINGS Net/DoubleClickTime value. Wayland, and X11 without - * XSETTINGS, read the KDE or GNOME setting through the XDG Settings portal, - * which needs D-Bus and can wait briefly. Cocoa reads the system interval. - * The user can change it at any time; read it again when a window gains focus - * rather than on every click. - */ -YASL_API int yasl_display_double_click_ms(const struct yasl_display *d); -/* Unsupported/invalid selections are no-ops, never clipboard aliases. */ -/** - * Offer a copy of a NUL-terminated UTF-8 string as a selection. - * @param d Display, or NULL for no action. - * @param sel YASL_CLIPBOARD or YASL_PRIMARY. - * @param text Text copied during this call; NULL does nothing. - * Unsupported selections do nothing. The void return does not confirm that the - * system accepted the offer; Wayland may require a recent input serial. - */ -YASL_API void yasl_display_copy(struct yasl_display *d, enum yasl_selection sel, const char *text); -/** - * Request text delivery to a particular surface. - * @param d Display that owns target. - * @param sel YASL_CLIPBOARD or YASL_PRIMARY. - * @param target Destination surface; NULL or a surface on another display does nothing. - * Results arrive as YASL_EVENT_PASTE, possibly during a later dispatch. No result - * is promised for unavailable data or a failed transfer. Destroying target cancels - * delivery. Unsupported selections do nothing. - */ -YASL_API void yasl_display_paste(struct yasl_display *d, enum yasl_selection sel, struct yasl_surface *target); - -/* A successful drag invokes the copied listener's non-NULL end once, including - * cancel and display close. Failed starts do not invoke it. */ -/** - * Start a surface drag from a recent pointer press. - * @param d Display that owns origin. - * @param origin Origin surface. - * @param image Optional copied premultiplied ARGB8888 drag image, or NULL. - * @param l Optional copied completion listener, or NULL. - * @param ud Borrowed listener userdata until completion. - * @return true if started; false if unsupported, invalid, or not currently permitted. - * A successful start invokes a non-NULL end callback exactly once, including - * cancellation or display close. Failed starts do not invoke it. This API carries - * surfaces, not arbitrary MIME payloads; use application state to identify the item. - */ -YASL_API bool yasl_display_drag_start(struct yasl_display *d, struct yasl_surface *origin, - const struct yasl_drag_image *image, - const struct yasl_drag_listener *l, void *ud); -/** - * Cancel the active surface drag, if any. - * @param d Display, or NULL for no action. - * May invoke the end listener synchronously. - */ -YASL_API void yasl_display_drag_cancel(struct yasl_display *d); - -/* Surface Lifecycle */ -/** Toplevel options. Zero-initialize, then supply a positive logical-pixel size. */ -struct yasl_window_spec { - /** Copied UTF-8 title; NULL uses backend defaults. */ - const char *title; - /** Requested logical-pixel width and height; both must be positive. */ union { - struct yasl_vector2i size_logical; /**< Requested logical-pixel extent. */ - struct yasl_vector2i size; /**< Migration alias for size_logical. */ + struct { int width,height,pixel_width,pixel_height; double scale; unsigned state; } window; + struct { enum yasl_key key; enum yasl_key_action action; unsigned mods; + uint32_t codepoint, shifted, native_code; } key; + struct { const char *data; size_t length; } text; + struct { double x,y,dx,dy; enum yasl_pointer_action action; unsigned button,buttons,mods; } pointer; + struct { double x,y,lines_x,lines_y,pixels_x,pixels_y; unsigned valid,source,phase,mods; bool momentum; } scroll; + struct { uint64_t device_id,tool_id; double x,y,pressure,tilt_x,tilt_y,rotation,distance,tangential,wheel_degrees; + unsigned axes_valid,axes_changed,buttons,changed_button,tool,action; bool in_contact; } pen; + struct { const char *data; size_t length; int begin,end; bool active; uint64_t revision,generation; } composition; + struct { const char *data; size_t length,begin,end; uint64_t revision,generation; } text_edit; + struct { const char *data; size_t length; uint64_t request_id; enum yasl_transfer_status status; } clipboard; + /** DATA carries a text/uri-list for yasl_drop_next_path; x/y are set for OVER. */ + struct { enum yasl_drop_action action; double x,y; const char *data; size_t length; } drop; + struct { enum yasl_drag_action action; double x,y; } drag; }; - /** Request initial fullscreen; actual state is reported asynchronously. */ - bool fullscreen; - /** Permit unpaced presentation where supported; a hint, not a guarantee. */ - bool tearing; - /** Copied optional window callbacks; NULL disables them. */ - const struct yasl_window_cbs *cbs; - /** Borrowed window callback userdata; ignored by the runner. */ - void *ud; - /** Borrowed application data, independent of callbacks and available in either loop style. */ - void *userdata; }; - -/** Popup options, entirely in device pixels, including size. -The content rectangle excludes transparent margins/shadows used for placement. -An axis of content_size equal to zero extends to that axis of size. The offset -and resolved content size must fit in size. Anchor extents must be nonnegative. -Dimensions must be positive, at most 32767, and fit an INT32_MAX-byte buffer; -backends may impose further limits after scaling or resolving placement. */ -struct yasl_popup_spec { - /** Full popup framebuffer extent in device pixels, including transparent margins. */ - struct yasl_vector2i size; - /** Rectangle in parent framebuffer coordinates, in device pixels. */ - struct yasl_rect2i anchor; - /** true prefers right of anchor; false prefers below. Placement may flip/slide. */ - bool beside; - /** Top-left content offset within the popup, in device pixels. */ - struct yasl_vector2i content_offset; - /** Content extent; zero per axis extends to the corresponding size edge. */ - struct yasl_vector2i content_size; -}; - -/** - * Create a resizable desktop window with CPU drawing targets. - * @param d Owning display. - * @param spec Required copied options; size is in logical pixels. - * @return An owned surface, or NULL with errno set. EINVAL means invalid options, - * including a nonpositive dimension, a dimension above 32767, or a buffer above - * INT32_MAX bytes. ENODEV is the fallback backend failure. Actual configured - * size may differ from the request. The callback table in spec->cbs is copied; - * spec->ud stays borrowed until callbacks are replaced or the surface is destroyed. - * This window's callbacks cannot run before creation returns. Use - * yasl_surface_geometry() for initial geometry; an initial configure callback - * is not guaranteed. Schedule an initial redraw after setting up application state. - * On a display owned by yasl_run(), the runner installs its own callbacks instead; - * use yasl_app_desc.event and yasl_app_desc.frame for application notifications. - */ -YASL_API struct yasl_surface *yasl_surface_create_toplevel(struct yasl_display *d, - const struct yasl_window_spec *spec); -/** - * Create an owned popup attached to a parent surface. - * @param parent Borrowed parent; destroy the popup before the parent. - * @param spec Required copied options, entirely in device pixels. - * @return A popup, or NULL with errno set: EINVAL for invalid options, ENOTSUP - * for missing popup support, or a backend error (ENODEV if none was supplied). - * Wayland requires a recent input serial for its popup grab. The backend may - * flip or slide placement to fit the screen. See yasl_popup_spec. - */ -YASL_API struct yasl_surface *yasl_surface_create_popup(struct yasl_surface *parent, - const struct yasl_popup_spec *spec); -/** - * Update a popup's geometry and placement. - * @param popup An existing popup. - * @param spec Required copied replacement options, in device pixels. - * @return true on success; false with errno set: EINVAL for invalid options, - * ENOTSUP for missing popup support, or a backend error (ENODEV if unspecified). - * Older Wayland popup protocol versions can reject repositioning. - */ -YASL_API bool yasl_surface_popup_reposition(struct yasl_surface *popup, - const struct yasl_popup_spec *spec); -/** - * Destroy a surface and cancel its surface timers and pending input. - * @param s Surface, or NULL for no action. - * Destroy child popups first. May be called from a surface callback; return from - * that callback without further use of the surface or its draw target. Every - * pointer or native handle obtained from this surface becomes invalid. - */ -YASL_API void yasl_surface_destroy(struct yasl_surface *s); -/** - * Get the display that owns a surface. - * @param s Surface, or NULL. - * @return A borrowed display, or NULL. - */ -YASL_API struct yasl_display *yasl_surface_display(const struct yasl_surface *s); - -/** - * Replace the surface's callback table and userdata. - * @param s Surface. - * @param cbs Table copied by value; NULL removes all callbacks. NULL members are ignored. - * @param ud Borrowed userdata passed to the installed callbacks. - * @return true if replaced; false with EINVAL for NULL, EBUSY on a runner-owned surface. - * This call only replaces callbacks; it does not invoke them. - * Use yasl_surface_geometry() to read current geometry; do not rely on receiving - * an initial configure callback when registering. - */ -YASL_API bool yasl_surface_set_callbacks(struct yasl_surface *s, - const struct yasl_window_cbs *cbs, void *ud); -/** - * Replace the window title with copied UTF-8 text. - * @param s Surface. - * @param title NUL-terminated UTF-8 title. Pass an empty string to clear it. - * Backends may truncate the title. - */ -YASL_API void yasl_surface_set_title(struct yasl_surface *s, const char *title); -/** - * Request a future draw callback, coalescing repeated requests. - * @param s Surface, or NULL for no action. - * Call yasl_display_flush() to service the request. Schedule again from draw to - * animate. Without YASL_CAP_FRAME_PACING, throttle animation with a timer. - */ -YASL_API void yasl_surface_schedule_redraw(struct yasl_surface *s); -/* Present exactly the target supplied to the current draw callback, at most - * once. Targets expire when that callback returns or the surface is destroyed. - * damage is required: n must be -1 or 0..YASL_DAMAGE_MAX. Rectangles must have - * nonnegative extents; they are clipped to the target. Invalid input returns - * false without consuming the frame. See yasl/native.h for native rendering. */ -/** - * Submit the current draw target at most once. - * @param s Surface currently being drawn. - * @param fb Exact target from the current draw callback, including its generation. - * @param damage Required damage hint: n is -1 for all pixels or 0..YASL_DAMAGE_MAX. - * @return true if accepted; false for invalid, stale, already presented, or failed targets. - * Rectangles must have nonnegative extents and are clipped to the target. Invalid - * input does not consume the frame. All pixels must be initialized even with - * partial damage. The backend may upload all pixels. Success records submission, - * not confirmed scanout. The target expires when draw returns or s is destroyed. - */ -YASL_API bool yasl_surface_present(struct yasl_surface *s, const struct yasl_framebuffer *fb, - const struct yasl_damage_list *damage); -/** - * Migration adapter: consume one input event for a surface from the display FIFO. - * Popup input can belong to the root queue; inspect ev->surface. Returns false - * for empty/invalid arguments, leaving ev unchanged. Strings remain borrowed - * until the next successful display or surface poll, or display close. Pending - * recipient events are canceled on destruction. Prefer yasl_display_poll_event() - * to receive window, composition, and display events as well as input. This - * input-only adapter discards pending non-input events across the display - * (legacy callbacks already delivered lifecycle changes). Do not mix - * the two polling styles in one application. Use the display thread. - */ -YASL_API bool yasl_surface_poll_event(struct yasl_surface *s, struct yasl_event *ev); -/** - * Decode the next local file path from a YASL_EVENT_DROP payload. - * @param cursor Required in/out position in the NUL-terminated list. Start it - * at event.text.data; a NULL *cursor returns zero. - * @param out Output for the NUL-terminated path, or NULL when cap is zero. - * @param cap Size of out in bytes. - * @return Path length in bytes, excluding the NUL, or zero when no paths remain. - * When the result is less than cap, out holds the path and *cursor moves past - * it. Otherwise out and *cursor are unchanged; call again with a buffer of at - * least the result plus one. - * Accepts file:///path, file:/path, and file://host/path when host is - * localhost or this machine's name. Percent escapes decode to raw bytes, and - * a '?' or '#' ends the path. Skips blank lines, comments, other schemes, - * other hosts, and lines with a malformed escape or an escaped NUL. May be - * called from any thread. - */ -YASL_API size_t yasl_drop_next_path(const char **cursor, char *out, size_t cap); -/** - * Copy the last frame presented from the surface's CPU target. - * @param s Surface. - * @param out Output for size->x * size->y premultiplied ARGB8888 pixels with - * tightly packed rows, or NULL when cap is zero. - * @param cap Capacity of out in pixels. - * @param size Required output: the frame extent in device pixels, or zero when - * no frame is available. - * @return true if copied. false leaves out unchanged; when *size is nonzero, - * out was too small, so allocate size->x * size->y pixels and call again. - * A frame is available from a successful yasl_surface_present() until the - * next draw callback starts, and again once that draw presents. After a draw - * that does not present or a resize that replaces the target, no frame may be - * available; native rendering never has one. Call outside draw callbacks. This copies YASL's frame, not - * the composited screen: decorations, other windows, and color conversion are - * not included. - */ -YASL_API bool yasl_surface_read_pixels(struct yasl_surface *s, uint32_t *out, size_t cap, - struct yasl_vector2i *size); -/** - * Select absolute or relative (locked) pointer input for a surface. - * @param s Surface. - * @param mode YASL_POINTER_NORMAL or YASL_POINTER_RELATIVE. - * @return true if the mode is supported; false leaves the current mode. - * RELATIVE hides the cursor and locks it while the surface is focused; - * POINTER_MOTION then carries unbounded delta. Returning to NORMAL restores - * the cursor selected with yasl_surface_set_cursor(). RELATIVE is supported on - * Wayland compositors with the relative-pointer and pointer-constraints - * protocols; X11 and Cocoa return false. - */ -YASL_API bool yasl_surface_set_pointer_mode(struct yasl_surface *s, enum yasl_pointer_mode mode); - -/** - * Consume one event from the display-wide FIFO without waiting. - * Returns false for an empty queue or invalid arguments, leaving ev unchanged. - * Strings remain valid until the next successful poll or display close. - * Surface destruction cancels undelivered events, not the last polled payload. - * Use the display thread. Do not mix display and surface polling in one app. - */ -YASL_API bool yasl_display_poll_event(struct yasl_display *d, struct yasl_event *ev); - -/** Stable nonzero identity within a display; zero for NULL. Use on the display thread. */ -YASL_API uint64_t yasl_surface_id(const struct yasl_surface *s); -/** Store borrowed application data independently of callback userdata. */ -YASL_API void yasl_surface_set_userdata(struct yasl_surface *s, void *userdata); -/** Read borrowed application data, or NULL for NULL; use on the display thread. */ -YASL_API void *yasl_surface_userdata(const struct yasl_surface *s); - -/** Thread-safe message descriptor. Zero target_id means display-wide. */ -struct yasl_message { - uint64_t target_id; /**< Surface identity, or zero for the display. */ - uint32_t code; /**< Application-defined message code. */ - void *payload; /**< Borrowed until release; passed through unchanged. */ - size_t size; /**< Application-defined payload length. */ - /** Optional cleanup on display thread after delivery/cancellation, exactly once. - * May post messages, but must not dispatch, poll, or destroy YASL objects. */ - void (*release)(void *payload); -}; -/** - * Copy and enqueue a message from any thread; the display must stay alive. - * Stop producer threads before closing the display. On success release runs - * once after consumption or cancellation. On failure ownership stays with the - * caller; errno is EINVAL, ENOENT (retired identity), ENOMEM, or EOVERFLOW. - */ -YASL_API bool yasl_display_post(struct yasl_display *d, const struct yasl_message *message); - -/** - * Present the active frame once. NULL/0 means full damage; non-NULL/0 means none. - * Validate all rectangles; excess rectangles fall back to full damage. - * Returns false with EINVAL for invalid/stale targets, EALREADY for a consumed - * frame, or errno from the backend (EIO if unspecified). - * Frame and pixels expire at callback return; use the display thread. - */ -YASL_API bool yasl_frame_present(struct yasl_frame *frame, - const struct yasl_rect2i *rects, size_t count); -/** Coalesce a redraw request; use on the display thread. NULL is ignored. */ -YASL_API void yasl_surface_request_frame(struct yasl_surface *s); -/** Set drawing policy for either loop style. Returns false with errno on failure. */ -YASL_API bool yasl_surface_set_pacing(struct yasl_surface *s, yasl_pacing_mode mode); - -/** Request a positive logical-pixel size. Success means issued, not configured. */ -YASL_API bool yasl_surface_set_size(struct yasl_surface *s, struct yasl_vector2i size_logical); -/** Request fullscreen state; returns acceptance, not confirmation. */ -YASL_API bool yasl_surface_set_fullscreen(struct yasl_surface *s, bool enabled); -/** Request maximized state; returns acceptance, not confirmation. */ -YASL_API bool yasl_surface_set_maximized(struct yasl_surface *s, bool enabled); -/** Request minimization. Window requests set errno on failure; display thread only. */ -YASL_API bool yasl_surface_minimize(struct yasl_surface *s); - -/** Offer copied UTF-8 text. Returns false with errno if invalid/unsupported/failed. */ -YASL_API bool yasl_clipboard_set(struct yasl_display *d, enum yasl_selection selection, - const char *text); -/** - * Request text; returns a nonzero ID or zero with errno. One PASTE completion - * follows each accepted request unless its surface/display is destroyed. - * Inspect paste.status even when data is empty. Use the display thread. - */ -YASL_API uint64_t yasl_clipboard_request(struct yasl_surface *s, enum yasl_selection selection); - -/* Current device-pixel size and scale, matching the configure callback. */ -/** - * Read current device-pixel size and scale together. - * @param s Surface, or NULL. - * @return Geometry matching configure callbacks; NULL gives zero size and unit scale. - */ -YASL_API struct yasl_geometry yasl_surface_geometry(const struct yasl_surface *s); -/** - * Read current window state. - * @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 - * as presentation confirmation. refresh/next_present are estimates. Without - * YASL_CAP_FRAME_PACING, redraws run on flush without display synchronization. - * Tearing may bypass pacing even when the capability is present. */ -/** - * Read frame timing estimates and their provenance. - * @param s Surface, or NULL. - * @return Current timing, or a zero-initialized result for NULL. - * Timestamps use monotonic nanoseconds. Inspect source before treating a timestamp - * as confirmation of presentation. Refresh and next-present values are estimates. - */ -YASL_API struct yasl_timing yasl_surface_timing(const struct yasl_surface *s); -/** - * Get a nondecreasing animation timestamp aligned to estimated refresh. - * @param s Surface, or NULL. - * @param now Current monotonic nanoseconds, usually from yasl_loop_now_ns(). - * @return Animation timestamp, or zero for NULL. Before timing is known, returns now. - * Use this to advance animations; it is not presentation feedback. - */ -YASL_API uint64_t yasl_surface_frame_time_ns(struct yasl_surface *s, uint64_t now); -/* Set the mouse and tablet cursor for this surface, including future entries. - * YASL_CURSOR_HIDDEN stays hidden until another cursor is selected. */ -/** - * Select the surface's system mouse and tablet cursor. - * @param s Surface. - * @param cursor Cursor shape; YASL_CURSOR_HIDDEN hides the cursor without disabling input. - * The choice persists across motion and re-entry until another shape is selected. - */ -YASL_API void yasl_surface_set_cursor(struct yasl_surface *s, enum yasl_cursor cursor); -/** - * Set a backend window background hint. - * @param s Surface. - * @param bg Packed 0xAARRGGBB color; backends may ignore alpha or the entire hint. - * Does not initialize framebuffer pixels or replace drawing. - */ -YASL_API void yasl_surface_set_background_color(struct yasl_surface *s, uint32_t bg); - -/* IME Support: preedit is borrowed until the next dispatch or destruction. */ -/** - * Read the current uncommitted UTF-8 composition text. - * @param s Surface, or NULL. - * @return Borrowed NUL-terminated text, or NULL if absent/unsupported. - * Copy to retain across the next dispatch or surface destruction. Composition - * changes can schedule a redraw. Insert committed YASL_EVENT_TEXT into documents, - * not preedit text. - */ -YASL_API const char *yasl_surface_ime_preedit(const struct yasl_surface *s); -/** - * Read the cursor/selection range in the preedit string. - * @param s Surface. - * @param begin Required output UTF-8 byte offset for the range start. - * @param end Required output UTF-8 byte offset for the range end. - * @return true if available; false without changing outputs otherwise. - * Currently available on Wayland; Cocoa preedit text has no range result. - */ -YASL_API bool yasl_surface_ime_preedit_cursor(const struct yasl_surface *s, int *begin, int *end); -/** - * Place the composition candidate window near the application caret. - * @param s Surface. - * @param caret Caret rectangle relative to the surface, in device pixels. - * Update after caret motion or scale changes. Unsupported backends ignore it. - */ -YASL_API void yasl_surface_set_ime_caret(struct yasl_surface *s, struct yasl_rect2i caret); - -/* Client-Side Decorations (CSD) Actions */ -/** - * Ask the window system to begin an interactive move. - * @param s Toplevel surface, normally during handling of a pointer press. - * @return true if the request was issued; false if unsupported or not permitted. - * A recent input serial may be required. Success does not confirm a final position. - */ -YASL_API bool yasl_surface_start_interactive_move(struct yasl_surface *s); -/** - * Ask the window system to begin an interactive resize. - * @param s Toplevel surface, normally during handling of a pointer press. - * @param edge One nonzero yasl_resize_edge enumerator. - * @return true if issued; false for an invalid edge, missing support, or rejected request. - */ -YASL_API bool yasl_surface_start_interactive_resize(struct yasl_surface *s, enum yasl_resize_edge edge); -/** - * Ask the window system to display its window management menu. - * @param s Toplevel surface. - * @param pos Surface-relative position in device pixels. - * @return true if issued; false if unsupported or not currently permitted. - */ -YASL_API bool yasl_surface_show_window_menu(struct yasl_surface *s, struct yasl_vector2i pos); - -/* Drag Support */ -/** - * Attach a toplevel surface to the current surface drag. - * @param s Surface to carry; check YASL_CAP_DRAG_CARRY first. - * @param offset Device-pixel offset of the pointer within the carried surface. - * @return true if accepted; false if unsupported or no compatible drag is active. - */ -YASL_API bool yasl_surface_drag_carry(struct yasl_surface *s, struct yasl_vector2i offset); -/** - * Place a toplevel surface relative to the current pointer. - * @param s Surface; check YASL_CAP_PLACE_WINDOW first. - * @param at Device-pixel position within the surface to place under the pointer. - * @return true if issued; false if unsupported or unavailable. - */ -YASL_API bool yasl_surface_place_under_pointer(struct yasl_surface *s, struct yasl_vector2i at); - +/** Last error on this thread; success does not clear it. */ +YASL_API enum yasl_error yasl_get_error(const char **description); +YASL_API int yasl_get_native_error(void); +/** Only before the first surface is created. */ +YASL_API bool yasl_set_app_id(const char *app_id); +YASL_API bool yasl_set_backend(enum yasl_backend backend); +/** Flags zero creates a surface for your own Vulkan or Metal renderer. */ +YASL_API yasl_surface *yasl_surface_create(const char *title, int width, int height, unsigned flags); +/** Also destroys its popups. */ +YASL_API void yasl_surface_destroy(yasl_surface *surface); +/** Destroys all surfaces. Release your GPU objects first. */ +YASL_API void yasl_shutdown(void); +/** 1 event, 0 none (timeout or wake), -1 failure. timeout_ms zero polls, negative + * waits forever. Without an event, out->type is NONE. */ +YASL_API int yasl_wait_event(yasl_event *out, int timeout_ms); +/** Makes a waiting yasl_wait_event return. Thread-safe. */ +YASL_API void yasl_wake(void); +YASL_API uint64_t yasl_time_ns(void); +YASL_API enum yasl_backend yasl_surface_backend(const yasl_surface *surface); +YASL_API unsigned yasl_surface_features(const yasl_surface *surface); +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_state(const yasl_surface *surface); +YASL_API void *yasl_surface_userdata(const yasl_surface *surface); +YASL_API void yasl_surface_set_userdata(yasl_surface *surface, void *userdata); +YASL_API bool yasl_surface_set_title(yasl_surface *surface, const char *title); +/** Requests; the result arrives as a WINDOW event. */ +YASL_API bool yasl_surface_request_size(yasl_surface *surface, int width, int height); +YASL_API bool yasl_surface_request_fullscreen(yasl_surface *surface, bool enabled); +YASL_API bool yasl_surface_request_maximized(yasl_surface *surface, bool enabled); +YASL_API bool yasl_surface_request_minimize(yasl_surface *surface); +YASL_API bool yasl_surface_set_cursor(yasl_surface *surface, enum yasl_cursor cursor); +YASL_API bool yasl_surface_set_pointer_mode(yasl_surface *surface, enum yasl_pointer_mode mode); +/** Draws during native modal loops such as a live resize. The callback may render + * but must not wait, create or destroy surfaces, or shut down. */ +YASL_API void yasl_surface_set_refresh_callback(yasl_surface *surface, void (*refresh)(yasl_surface *,void *), void *userdata); +YASL_API int yasl_double_click_ms(void); +YASL_API bool yasl_clipboard_set(enum yasl_selection selection, const char *text); +/** Returns a request ID; the text arrives as a CLIPBOARD event. Zero on failure. */ +YASL_API uint64_t yasl_clipboard_request(yasl_surface *target, enum yasl_selection selection); +/** IME support for a text field. */ +YASL_API bool yasl_surface_set_text_input(yasl_surface *surface, bool active, enum yasl_text_purpose purpose); +/** UTF-8; selection offsets are bytes. */ +YASL_API bool yasl_surface_set_surrounding_text(yasl_surface *surface, const char *text, size_t length, + size_t selection_begin, size_t selection_end, uint64_t revision); +YASL_API bool yasl_surface_set_ime_caret(yasl_surface *surface, double x, double y, double width, double height); +/** Anchor picks a point on the x/y/anchor rectangle of the parent; gravity is the + * direction the popup grows. Functions taking a trigger need a recent press event. */ +YASL_API yasl_surface *yasl_popup_create(yasl_surface *parent, int width, int height, + double x, double y, double anchor_width, double anchor_height, enum yasl_popup_edge anchor, + enum yasl_popup_edge gravity, unsigned constraints, const yasl_event *trigger); +YASL_API bool yasl_popup_reposition(yasl_surface *popup, int width, int height, + double x, double y, double anchor_width, double anchor_height, enum yasl_popup_edge anchor, + enum yasl_popup_edge gravity, unsigned constraints); +YASL_API bool yasl_surface_start_interactive_move(yasl_surface *surface, const yasl_event *trigger); +YASL_API bool yasl_surface_start_interactive_resize(yasl_surface *surface, enum yasl_resize_edge edge, const yasl_event *trigger); +YASL_API bool yasl_surface_show_window_menu(yasl_surface *surface, double x, double y, const yasl_event *trigger); +YASL_API bool yasl_drag_start(yasl_surface *origin, const yasl_event *trigger, + const uint32_t *pixels, int width, int height, int hotspot_x, int hotspot_y); +YASL_API void yasl_drag_cancel(void); +YASL_API bool yasl_surface_drag_carry(yasl_surface *surface, double x, double y); +YASL_API bool yasl_surface_place_under_pointer(yasl_surface *surface, double x, double y); +YASL_API size_t yasl_drop_next_path(const char **cursor, char *out, size_t capacity); #ifdef __cplusplus } #endif - -#endif /* YASL_H */ +#endif diff --git a/meson.build b/meson.build index 9a59016..4d7d7ce 100644 --- a/meson.build +++ b/meson.build @@ -21,6 +21,8 @@ sources = [] has_wayland = false has_x11 = false has_dbus = false +has_egl = false +has_vulkan = false has_cocoa = system == 'darwin' if system == 'linux' @@ -46,6 +48,17 @@ if system == 'linux' if has_dbus deps += [dbus] endif + egl_dep = dependency('egl', required: false) + wayland_egl_dep = dependency('wayland-egl', required: false) + has_egl = egl_dep.found() and (not has_wayland or wayland_egl_dep.found()) + if has_egl + deps += egl_dep + if has_wayland + deps += wayland_egl_dep + endif + endif + vulkan_dep = dependency('vulkan', required: false) + has_vulkan = vulkan_dep.found() m_dep = cc.find_library('m', required: false) rt_dep = cc.find_library('rt', required: false) deps += [m_dep, rt_dep] @@ -63,17 +76,24 @@ elif system == 'darwin' error('Cocoa requires macOS 14 or newer. Set MACOSX_DEPLOYMENT_TARGET=14.0 or a newer target before configuring.') endif deps += [ - dependency('appleframeworks', modules: ['AppKit', 'Metal', 'QuartzCore', 'UserNotifications']), + dependency('appleframeworks', modules: ['AppKit', 'Metal', 'QuartzCore', 'UserNotifications', 'OpenGL']), ] else error('YASL supports Linux and macOS.') endif +if has_cocoa + vulkan_dep = dependency('vulkan', required: false) + has_vulkan = vulkan_dep.found() +endif + if not has_wayland and not has_x11 and not has_cocoa error('Enable at least one window backend.') endif add_project_arguments( + '-DYASL_HAS_EGL=' + (has_egl ? '1' : '0'), + '-DYASL_HAS_VULKAN=' + (has_vulkan ? '1' : '0'), '-DYASL_HAS_WAYLAND=' + (has_wayland ? '1' : '0'), '-DYASL_HAS_X11=' + (has_x11 ? '1' : '0'), '-DYASL_HAS_COCOA=' + (has_cocoa ? '1' : '0'), @@ -103,10 +123,12 @@ yasl_dep = declare_dependency( meson.override_dependency('yasl', yasl_dep) install_headers( - 'include/yasl/yasl.h', 'include/yasl/app.h', 'include/yasl/loop.h', - 'include/yasl/timing.h', 'include/yasl/keycodes.h', 'include/yasl/native.h', - 'include/yasl/desktop.h', subdir: 'yasl', + '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', ) +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 diff --git a/src/common/app.c b/src/common/app.c deleted file mode 100644 index 63cf2ce..0000000 --- a/src/common/app.c +++ /dev/null @@ -1,153 +0,0 @@ -#include "yasl/app.h" -#include "common/app_internal.h" -#include "common/backend.h" - -#include <errno.h> - -struct app_runner { - struct yasl_display *display; - const struct yasl_app_desc *desc; - uint64_t initial_window; - bool running, failed, had_surfaces; - int error; -}; - -void yasl_app_exit(struct yasl_display *d, enum yasl_app_result result) -{ - if (!d || !d->app_runner || result == YASL_APP_CONTINUE) return; - struct app_runner *runner = d->app_runner; - runner->running = false; - if (result == YASL_APP_ERROR) { runner->failed = true; runner->error = EIO; } -} - -struct yasl_surface *yasl_app_window(struct yasl_display *d) -{ - if (!d || !d->app_runner) return NULL; - struct app_runner *runner = d->app_runner; - return runner->initial_window ? yasl_events_find(d, runner->initial_window) : NULL; -} - -static void dispatch_events(struct app_runner *runner) -{ - struct yasl_event event; - while (runner->running && yasl_display_poll_event(runner->display, &event)) { - if (runner->desc->event) - yasl_app_exit(runner->display, runner->desc->event(runner->desc->userdata, &event)); - else if (event.type == YASL_EVENT_WINDOW_CLOSE) - yasl_app_exit(runner->display, YASL_APP_QUIT); - if (event.type == YASL_EVENT_DISPLAY_QUIT) - yasl_app_exit(runner->display, YASL_APP_QUIT); - if (event.type == YASL_EVENT_DISPLAY_LOST) - yasl_app_exit(runner->display, YASL_APP_ERROR); - } -} - -static void draw(struct yasl_surface *s, struct yasl_frame *frame, void *ud) -{ - struct app_runner *runner = ud; - uint64_t id = s->id; - dispatch_events(runner); - /* Draining events can destroy the surface whose native draw is active. */ - struct yasl_surface *live = yasl_events_find(runner->display, id); - if (live && runner->running && runner->desc->frame) - runner->desc->frame(runner->desc->userdata, live, frame); -} - -void app_runner_register_surface(void *data, struct yasl_surface *s) -{ - struct app_runner *runner = data; - struct yasl_window_cbs callbacks = {.draw = draw}; - yasl_surface_set_callbacks(s, &callbacks, runner); - s->app_surface = runner; - runner->had_surfaces = true; -} - -void app_runner_unregister_surface(void *data, struct yasl_surface *s) -{ - (void)data; - s->app_surface = NULL; -} - -bool yasl_frame_present_cpu(struct yasl_frame *frame, const struct yasl_rect2i *rects, int count) -{ - /* Preserve the migration adapter's old NULL/nonpositive full-damage rule. */ - return yasl_frame_present(frame, count <= 0 ? NULL : rects, count <= 0 ? 0 : (size_t)count); -} - -bool yasl_post_event(struct yasl_display *d, struct yasl_surface *s, - uint32_t code, void *payload, size_t size) -{ - if (!d || !d->app_runner) { errno = EINVAL; return false; } - /* Legacy callers must keep s alive through this call. New code passes an ID. */ - return yasl_display_post(d, &(struct yasl_message){ - .target_id = s ? yasl_surface_id(s) : 0, .code = code, .payload = payload, .size = size, - }); -} - -int yasl_app_watch_fd(struct yasl_display *d, int fd, short events, - int (*cb)(int, short, void *), void *ud) -{ - if (!d || !d->loop) { errno = EINVAL; return -1; } - return yasl_loop_add_fd(d->loop, fd, events, cb, ud); -} -void yasl_app_unwatch_fd(struct yasl_display *d, int id) -{ - if (d && d->loop) yasl_loop_del_fd(d->loop, id); -} -yasl_timer_id yasl_app_add_timer(struct yasl_display *d, uint64_t interval, yasl_timer_cb cb, void *ud) -{ - if (!d || !d->loop) return 0; - return yasl_loop_add_timer(d->loop, interval, cb, ud); -} -void yasl_app_del_timer(struct yasl_display *d, yasl_timer_id id) -{ - if (d && d->loop) yasl_loop_del_timer(d->loop, id); -} - -int yasl_run(const struct yasl_app_desc *desc) -{ - if (!desc) { errno = EINVAL; return -1; } - struct yasl_display *d = yasl_display_open(NULL, &(struct yasl_display_spec){ - .app_id = desc->app_id, .backend = desc->backend, - }); - if (!d) return -1; - struct app_runner runner = {.display = d, .desc = desc, .running = true}; - d->app_runner = &runner; - if (desc->window) { - struct yasl_surface *s = yasl_surface_create_toplevel(d, desc->window); - if (!s) { - int error = errno; - d->app_runner = NULL; - yasl_display_close(d); - errno = error; - return -1; - } - runner.initial_window = s->id; - yasl_surface_request_frame(s); - } - if (desc->init) yasl_app_exit(d, desc->init(desc->userdata, d)); - while (runner.running) { - dispatch_events(&runner); - if (!runner.running || (runner.had_surfaces && !d->surfaces)) break; - if (yasl_display_dispatch(d, -1) < 0) { - int dispatch_error = errno; - dispatch_events(&runner); - runner.failed = true; - runner.error = dispatch_error; - break; - } - dispatch_events(&runner); - if (runner.running) yasl_display_flush(d); - } - int queue_error = yasl_events_error(d); - if (queue_error) { runner.failed = true; runner.error = queue_error; } - int error = runner.error; - if (desc->quit) desc->quit(desc->userdata, d); - /* Backend registries create children after parents: reverse creation order - * ensures child popups are destroyed before their parent. */ - while (d->surfaces) yasl_surface_destroy(d->surfaces); - d->app_runner = NULL; - yasl_display_close(d); - if (runner.failed) errno = error; - return runner.failed ? -1 : 0; -} diff --git a/src/common/app_internal.h b/src/common/app_internal.h deleted file mode 100644 index 143df41..0000000 --- a/src/common/app_internal.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef YASL_APP_INTERNAL_H -#define YASL_APP_INTERNAL_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); - -#endif diff --git a/src/common/backend.c b/src/common/backend.c index a81e1e7..8d20f67 100644 --- a/src/common/backend.c +++ b/src/common/backend.c @@ -1,42 +1,41 @@ #include "backend.h" #include "loop.h" -#include "common/app_internal.h" #include "common/utf8.h" #include <errno.h> #include <stdlib.h> #include <string.h> -static const struct yasl_backend_ops *backend_ops(enum yasl_backend backend) +static const struct yasl_private_backend_ops *backend_ops(enum yasl_private_backend backend) { switch (backend) { #if YASL_HAS_WAYLAND - case YASL_BACKEND_WAYLAND: return &yasl_wayland_backend; + case YASL_PRIVATE_BACKEND_WAYLAND: return &yasl_private_wayland_backend; #endif #if YASL_HAS_X11 - case YASL_BACKEND_X11: return &yasl_x11_backend; + case YASL_PRIVATE_BACKEND_X11: return &yasl_private_x11_backend; #endif #if YASL_HAS_COCOA - case YASL_BACKEND_COCOA: return &yasl_cocoa_backend; + case YASL_PRIVATE_BACKEND_COCOA: return &yasl_private_cocoa_backend; #endif default: return NULL; } } -static struct yasl_display *open_backend(enum yasl_backend backend, - struct yasl_loop *loop, const struct yasl_display_spec *spec) +static struct yasl_private_display *open_backend(enum yasl_private_backend backend, + struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec) { - const struct yasl_backend_ops *ops = backend_ops(backend); + const struct yasl_private_backend_ops *ops = backend_ops(backend); if (!ops) { errno = ENOSYS; return NULL; } errno = 0; - struct yasl_display *d = ops->display_open(loop, spec); + struct yasl_private_display *d = ops->display_open(loop, spec); if (!d && !errno) errno = ENODEV; if (d) { d->callback_ud = spec ? spec->ud : NULL; - if (!yasl_events_open(d)) { + if (!yasl_private_events_open(d)) { int error = errno; ops->display_close(d); errno = error; @@ -46,52 +45,52 @@ static struct yasl_display *open_backend(enum yasl_backend backend, return d; } -struct yasl_display *yasl_display_open(struct yasl_loop *loop, - const struct yasl_display_spec *spec) +struct yasl_private_display *yasl_private_display_open(struct yasl_private_loop *loop, + const struct yasl_private_display_spec *spec) { - enum yasl_backend backend = spec ? spec->backend : YASL_BACKEND_AUTO; - if (backend < YASL_BACKEND_AUTO || backend > YASL_BACKEND_COCOA) { + enum yasl_private_backend backend = spec ? spec->backend : YASL_PRIVATE_BACKEND_AUTO; + if (backend < YASL_PRIVATE_BACKEND_AUTO || backend > YASL_PRIVATE_BACKEND_COCOA) { errno = EINVAL; return NULL; } - if (backend != YASL_BACKEND_AUTO) + if (backend != YASL_PRIVATE_BACKEND_AUTO) return open_backend(backend, loop, spec); #if YASL_HAS_COCOA - return open_backend(YASL_BACKEND_COCOA, loop, spec); + return open_backend(YASL_PRIVATE_BACKEND_COCOA, loop, spec); #else const char *session = getenv("XDG_SESSION_TYPE"); bool wayland = getenv("WAYLAND_DISPLAY") || getenv("WAYLAND_SOCKET") || (session && strcmp(session, "wayland") == 0); - enum yasl_backend first = wayland ? YASL_BACKEND_WAYLAND : YASL_BACKEND_X11; - enum yasl_backend second = wayland ? YASL_BACKEND_X11 : YASL_BACKEND_WAYLAND; - struct yasl_display *d = NULL; + enum yasl_private_backend first = wayland ? YASL_PRIVATE_BACKEND_WAYLAND : YASL_PRIVATE_BACKEND_X11; + enum yasl_private_backend second = wayland ? YASL_PRIVATE_BACKEND_X11 : YASL_PRIVATE_BACKEND_WAYLAND; + struct yasl_private_display *d = NULL; if (backend_ops(first)) d = open_backend(first, loop, spec); if (!d && backend_ops(second)) d = open_backend(second, loop, spec); return d; #endif } -enum yasl_backend yasl_display_backend(const struct yasl_display *d) +enum yasl_private_backend yasl_private_display_backend(const struct yasl_private_display *d) { - return d ? d->ops->type : YASL_BACKEND_AUTO; + return d ? d->ops->type : YASL_PRIVATE_BACKEND_AUTO; } -void yasl_display_close(struct yasl_display *d) +void yasl_private_display_close(struct yasl_private_display *d) { if (!d || !d->ops->display_close) return; - yasl_events_close(d); + yasl_private_events_close(d); d->ops->display_close(d); } -uint32_t yasl_display_caps(const struct yasl_display *d) +uint32_t yasl_private_display_caps(const struct yasl_private_display *d) { if (!d || !d->ops->display_caps) return 0; return d->ops->display_caps(d); } -int yasl_display_dispatch(struct yasl_display *d, int timeout_ms) +int yasl_private_display_dispatch(struct yasl_private_display *d, int timeout_ms) { if (!d) { errno = EINVAL; @@ -101,59 +100,59 @@ int yasl_display_dispatch(struct yasl_display *d, int timeout_ms) errno = ENOTSUP; return -1; } - int queue_error = yasl_events_error(d); + int queue_error = yasl_private_events_error(d); if (queue_error) { errno = queue_error; return -1; } - if (yasl_events_pending(d)) timeout_ms = 0; + if (yasl_private_events_pending(d)) timeout_ms = 0; errno = 0; int result = d->ops->display_dispatch_timeout(d, timeout_ms); - queue_error = yasl_events_error(d); + queue_error = yasl_private_events_error(d); if (queue_error) { errno = queue_error; return -1; } if (result < 0 && !errno) errno = EIO; return result; } -void yasl_display_flush(struct yasl_display *d) +void yasl_private_display_flush(struct yasl_private_display *d) { if (!d || !d->ops->display_flush) return; d->ops->display_flush(d); } -int yasl_display_modifiers(const struct yasl_display *d) +int yasl_private_display_modifiers(const struct yasl_private_display *d) { if (!d || !d->ops->display_modifiers) return 0; return d->ops->display_modifiers(d); } -int yasl_display_double_click_ms(const struct yasl_display *d) +int yasl_private_display_double_click_ms(const struct yasl_private_display *d) { int ms = d && d->ops->display_double_click_ms ? d->ops->display_double_click_ms(d) : 0; /* Reject nonsense from misconfigured settings as well as no answer. */ - return ms > 0 && ms <= 10000 ? ms : YASL_DOUBLE_CLICK_MS_DEFAULT; + return ms > 0 && ms <= 10000 ? ms : YASL_PRIVATE_DOUBLE_CLICK_MS_DEFAULT; } -void yasl_display_copy(struct yasl_display *d, enum yasl_selection sel, const char *text) +void yasl_private_display_copy(struct yasl_private_display *d, enum yasl_private_selection sel, const char *text) { if (!d || !d->ops->display_copy || !text || - (sel != YASL_CLIPBOARD && sel != YASL_PRIMARY) || - (sel == YASL_PRIMARY && !(yasl_display_caps(d) & YASL_CAP_PRIMARY_SELECTION))) + (sel != YASL_PRIVATE_CLIPBOARD && sel != YASL_PRIVATE_PRIMARY) || + (sel == YASL_PRIVATE_PRIMARY && !(yasl_private_display_caps(d) & YASL_PRIVATE_CAP_PRIMARY_SELECTION))) return; d->ops->display_copy(d, sel, text); } -void yasl_display_paste(struct yasl_display *d, enum yasl_selection sel, struct yasl_surface *target) +void yasl_private_display_paste(struct yasl_private_display *d, enum yasl_private_selection sel, struct yasl_surface *target) { if (!d || !d->ops->display_paste || - (sel != YASL_CLIPBOARD && sel != YASL_PRIMARY) || - (sel == YASL_PRIMARY && !(yasl_display_caps(d) & YASL_CAP_PRIMARY_SELECTION))) + (sel != YASL_PRIVATE_CLIPBOARD && sel != YASL_PRIVATE_PRIMARY) || + (sel == YASL_PRIVATE_PRIMARY && !(yasl_private_display_caps(d) & YASL_PRIVATE_CAP_PRIMARY_SELECTION))) return; if (!target || target->display != d) return; d->ops->display_paste(d, sel, target); } -bool yasl_display_drag_start(struct yasl_display *d, struct yasl_surface *origin, const struct yasl_drag_image *image, const struct yasl_drag_listener *l, void *ud) +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; @@ -163,19 +162,19 @@ bool yasl_display_drag_start(struct yasl_display *d, struct yasl_surface *origin image->size.x > 32767 || image->size.y > 32767 || (int64_t)image->size.x * image->size.y * 4 > INT32_MAX)) return false; - const struct yasl_drag_listener empty = {0}; + const struct yasl_private_drag_listener empty = {0}; return d->ops->display_drag_start(d, origin, image, l ? l : &empty, ud); } -void yasl_display_drag_cancel(struct yasl_display *d) +void yasl_private_display_drag_cancel(struct yasl_private_display *d) { if (!d || !d->ops->display_drag_cancel) return; d->ops->display_drag_cancel(d); } -struct yasl_surface *yasl_create_toplevel(struct yasl_display *d, - const struct yasl_window_spec *spec, void *graphics_device) +struct yasl_surface *yasl_private_create_toplevel(struct yasl_private_display *d, + const struct yasl_private_window_spec *spec, void *graphics_device) { if (!d || !d->ops->surface_create_toplevel || !spec || spec->size.x <= 0 || spec->size.y <= 0 || @@ -184,14 +183,14 @@ struct yasl_surface *yasl_create_toplevel(struct yasl_display *d, errno = EINVAL; return NULL; } - if (graphics_device && !(yasl_display_caps(d) & YASL_CAP_NATIVE_GRAPHICS)) { + if (graphics_device && !(yasl_private_display_caps(d) & YASL_PRIVATE_CAP_NATIVE_GRAPHICS)) { errno = ENOTSUP; return NULL; } errno = 0; struct yasl_surface *s = d->ops->surface_create_toplevel(d, spec, graphics_device); if (!s && !errno) errno = ENODEV; - if (s && !yasl_events_register(s)) { + if (s && !yasl_private_events_register(s)) { int error = errno; d->ops->surface_destroy(s); errno = error; @@ -201,21 +200,18 @@ struct yasl_surface *yasl_create_toplevel(struct yasl_display *d, s->userdata = spec->userdata; /* Native creation may dispatch events. Attach this window's callbacks * only after it finishes, so callers can store the returned surface. */ - if (d->app_runner) - app_runner_register_surface(d->app_runner, s); - else - yasl_surface_set_callbacks(s, spec->cbs, spec->ud); + yasl_private_surface_set_callbacks(s, spec->cbs, spec->ud); } return s; } -struct yasl_surface *yasl_surface_create_toplevel(struct yasl_display *d, - const struct yasl_window_spec *spec) +struct yasl_surface *yasl_private_surface_create_toplevel(struct yasl_private_display *d, + const struct yasl_private_window_spec *spec) { - return yasl_create_toplevel(d, spec, NULL); + return yasl_private_create_toplevel(d, spec, NULL); } -static bool popup_spec_valid(const struct yasl_popup_spec *spec) +static bool popup_spec_valid(const struct yasl_private_popup_spec *spec) { return spec && spec->size.x > 0 && spec->size.y > 0 && spec->size.x <= 32767 && spec->size.y <= 32767 && @@ -227,13 +223,13 @@ static bool popup_spec_valid(const struct yasl_popup_spec *spec) (int64_t)spec->content_offset.y + spec->content_size.y <= spec->size.y; } -struct yasl_surface *yasl_surface_create_popup(struct yasl_surface *parent, const struct yasl_popup_spec *spec) +struct yasl_surface *yasl_private_surface_create_popup(struct yasl_surface *parent, const struct yasl_private_popup_spec *spec) { if (!parent || !popup_spec_valid(spec)) { errno = EINVAL; return NULL; } - if (!(yasl_display_caps(parent->display) & YASL_CAP_POPUPS) || + if (!(yasl_private_display_caps(parent->display) & YASL_PRIVATE_CAP_POPUPS) || !parent->display->ops->surface_create_popup) { errno = ENOTSUP; return NULL; @@ -243,25 +239,23 @@ struct yasl_surface *yasl_surface_create_popup(struct yasl_surface *parent, cons if (!s && !errno) errno = ENODEV; if (s) { s->popup = true; - if (!yasl_events_register(s)) { + if (!yasl_private_events_register(s)) { int error = errno; parent->display->ops->surface_destroy(s); errno = error; return NULL; } - if (parent->display->app_runner) - app_runner_register_surface(parent->display->app_runner, s); } return s; } -bool yasl_surface_popup_reposition(struct yasl_surface *popup, const struct yasl_popup_spec *spec) +bool yasl_private_surface_popup_reposition(struct yasl_surface *popup, const struct yasl_private_popup_spec *spec) { if (!popup || !popup_spec_valid(spec)) { errno = EINVAL; return false; } - if (!(yasl_display_caps(popup->display) & YASL_CAP_POPUPS) || + if (!(yasl_private_display_caps(popup->display) & YASL_PRIVATE_CAP_POPUPS) || !popup->display->ops->surface_popup_reposition) { errno = ENOTSUP; return false; @@ -272,68 +266,61 @@ bool yasl_surface_popup_reposition(struct yasl_surface *popup, const struct yasl return result; } -void yasl_surface_destroy(struct yasl_surface *s) +void yasl_private_surface_destroy(struct yasl_surface *s) { if (!s || !s->display->ops->surface_destroy) return; - if (s->app_surface && s->display->app_runner) { - app_runner_unregister_surface(s->display->app_runner, s); - } - yasl_events_unregister(s); - yasl_surface_clear_timers(s); + yasl_private_events_unregister(s); + yasl_private_surface_clear_timers(s); input_queue_clear(&s->input); s->display->ops->surface_destroy(s); } -struct yasl_display *yasl_surface_display(const struct yasl_surface *s) +struct yasl_private_display *yasl_private_surface_display(const struct yasl_surface *s) { return s ? s->display : NULL; } -bool yasl_surface_set_callbacks(struct yasl_surface *s, const struct yasl_window_cbs *cbs, void *ud) +bool yasl_private_surface_set_callbacks(struct yasl_surface *s, const struct yasl_private_window_cbs *cbs, void *ud) { if (!s) { errno = EINVAL; return false; } - if (s->display && s->display->app_runner && s->app_surface) { - errno = EBUSY; - return false; - } - s->callbacks = cbs ? *cbs : (struct yasl_window_cbs){0}; + s->callbacks = cbs ? *cbs : (struct yasl_private_window_cbs){0}; s->ud = ud; if (s->display && s->display->ops && s->display->ops->surface_set_callbacks) s->display->ops->surface_set_callbacks(s, cbs, ud); return true; } -void yasl_surface_set_title(struct yasl_surface *s, const char *title) +void yasl_private_surface_set_title(struct yasl_surface *s, const char *title) { if (!s || !s->display->ops->surface_set_title) return; s->display->ops->surface_set_title(s, title); } -void yasl_surface_schedule_redraw(struct yasl_surface *s) +void yasl_private_surface_schedule_redraw(struct yasl_surface *s) { if (!s || !s->display->ops->surface_schedule_redraw) return; s->display->ops->surface_schedule_redraw(s); } -bool yasl_surface_present(struct yasl_surface *s, const struct yasl_framebuffer *fb, const struct yasl_damage_list *damage) +bool yasl_private_surface_present(struct yasl_surface *s, const struct yasl_private_framebuffer *fb, const struct yasl_private_damage_list *damage) { if (!s || !s->display->ops->surface_present || !fb || !damage || !fb->generation || fb->generation != s->frame_generation || fb->size.x <= 0 || fb->size.y <= 0 || - damage->n < -1 || damage->n > YASL_DAMAGE_MAX) + damage->n < -1 || damage->n > YASL_PRIVATE_DAMAGE_MAX) return false; - struct yasl_damage_list clipped; + struct yasl_private_damage_list clipped; clipped.n = damage->n; if (damage->n < 0) { clipped.bounds = damage->bounds; } else { clipped.n = 0; - clipped.bounds = (struct yasl_rect2i){0}; + clipped.bounds = (struct yasl_private_rect2i){0}; for (int i = 0; i < damage->n; i++) { - struct yasl_rect2i r = damage->rects[i]; + struct yasl_private_rect2i r = damage->rects[i]; if (r.width < 0 || r.height < 0) return false; int64_t right = (int64_t)r.x + r.width; int64_t bottom = (int64_t)r.y + r.height; @@ -341,7 +328,7 @@ bool yasl_surface_present(struct yasl_surface *s, const struct yasl_framebuffer if (right > fb->size.x) right = fb->size.x; if (bottom > fb->size.y) bottom = fb->size.y; if (right > x && bottom > y) - clipped.rects[clipped.n++] = (struct yasl_rect2i){ + clipped.rects[clipped.n++] = (struct yasl_private_rect2i){ (int)x, (int)y, (int)(right - x), (int)(bottom - y), }; } @@ -349,164 +336,164 @@ bool yasl_surface_present(struct yasl_surface *s, const struct yasl_framebuffer return s->display->ops->surface_present(s, fb, &clipped); } -bool yasl_surface_poll_event(struct yasl_surface *s, struct yasl_event *ev) +bool yasl_private_surface_poll_event(struct yasl_surface *s, struct yasl_private_event *ev) { if (!s || !ev) return false; if (s->display && s->display->events) - return yasl_events_poll_surface(s, ev); + return yasl_private_events_poll_surface(s, ev); if (s->display && s->display->ops && s->display->ops->surface_poll_event) return s->display->ops->surface_poll_event(s, ev); return input_queue_pop(&s->input, ev); } -bool yasl_surface_read_pixels(struct yasl_surface *s, uint32_t *out, size_t cap, - struct yasl_vector2i *size) +bool yasl_private_surface_read_pixels(struct yasl_surface *s, uint32_t *out, size_t cap, + struct yasl_private_vector2i *size) { if (!size) return false; - *size = (struct yasl_vector2i){0}; + *size = (struct yasl_private_vector2i){0}; if (!s || (!out && cap) || !s->display->ops->surface_read_pixels) return false; return s->display->ops->surface_read_pixels(s, out, cap, size); } -bool yasl_surface_set_pointer_mode(struct yasl_surface *s, enum yasl_pointer_mode mode) +bool yasl_private_surface_set_pointer_mode(struct yasl_surface *s, enum yasl_private_pointer_mode mode) { - if (!s || (mode != YASL_POINTER_NORMAL && mode != YASL_POINTER_RELATIVE)) + if (!s || (mode != YASL_PRIVATE_POINTER_NORMAL && mode != YASL_PRIVATE_POINTER_RELATIVE)) return false; if (mode == s->pointer_mode) return true; - const struct yasl_backend_ops *ops = s->display ? s->display->ops : NULL; + const struct yasl_private_backend_ops *ops = s->display ? s->display->ops : NULL; if (!ops || !ops->surface_set_pointer_mode) - return mode == YASL_POINTER_NORMAL; + return mode == YASL_PRIVATE_POINTER_NORMAL; if (!ops->surface_set_pointer_mode(s, mode)) return false; s->pointer_mode = mode; return true; } -struct yasl_geometry yasl_surface_geometry(const struct yasl_surface *s) +struct yasl_private_geometry yasl_private_surface_geometry(const struct yasl_surface *s) { if (!s) - return (struct yasl_geometry){.scale_120 = YASL_SCALE_UNIT}; + return (struct yasl_private_geometry){.scale_120 = YASL_PRIVATE_SCALE_UNIT}; if (s->scale_120) - return (struct yasl_geometry){s->size, s->scale_120}; + return (struct yasl_private_geometry){s->size, s->scale_120}; if (s->display && s->display->ops && s->display->ops->surface_geometry) return s->display->ops->surface_geometry(s); - return (struct yasl_geometry){s->size, YASL_SCALE_UNIT}; + return (struct yasl_private_geometry){s->size, YASL_PRIVATE_SCALE_UNIT}; } -uint32_t yasl_surface_state(const struct yasl_surface *s) +uint32_t yasl_private_surface_state(const struct yasl_surface *s) { return s ? s->state : 0; } -struct yasl_timing yasl_surface_timing(const struct yasl_surface *s) +struct yasl_private_timing yasl_private_surface_timing(const struct yasl_surface *s) { if (!s) - return (struct yasl_timing){0}; + return (struct yasl_private_timing){0}; if (s->display && s->display->ops && s->display->ops->surface_timing) return s->display->ops->surface_timing(s); - struct yasl_timing t = { + struct yasl_private_timing t = { .refresh_ns = s->clock.refresh_ns, .last_present_ns = s->clock.last_present_ns, - .next_present_ns = yasl_clock_next_present_ns(&s->clock), + .next_present_ns = yasl_private_clock_next_present_ns(&s->clock), .frames_skipped = s->frames_skipped, .source = s->clock.source, }; - yasl_clock_resize_stats(&s->clock, &t.resize); + yasl_private_clock_resize_stats(&s->clock, &t.resize); return t; } -uint64_t yasl_surface_frame_time_ns(struct yasl_surface *s, uint64_t now) +uint64_t yasl_private_surface_frame_time_ns(struct yasl_surface *s, uint64_t now) { if (!s) return 0; if (s->display && s->display->ops && s->display->ops->surface_frame_time_ns) return s->display->ops->surface_frame_time_ns(s, now); - return yasl_clock_frame_timestamp_ns(&s->clock, now); + return yasl_private_clock_frame_timestamp_ns(&s->clock, now); } -void yasl_surface_set_cursor(struct yasl_surface *s, enum yasl_cursor cursor) +void yasl_private_surface_set_cursor(struct yasl_surface *s, enum yasl_private_cursor cursor) { if (!s || !s->display->ops->surface_set_cursor) return; s->display->ops->surface_set_cursor(s, cursor); } -void yasl_surface_set_background_color(struct yasl_surface *s, uint32_t bg) +void yasl_private_surface_set_background_color(struct yasl_surface *s, uint32_t bg) { if (!s || !s->display->ops->surface_set_background_color) return; s->display->ops->surface_set_background_color(s, bg); } -const char *yasl_surface_ime_preedit(const struct yasl_surface *s) +const char *yasl_private_surface_ime_preedit(const struct yasl_surface *s) { if (!s || !s->display->ops->surface_ime_preedit) return 0; return s->display->ops->surface_ime_preedit(s); } -bool yasl_surface_ime_preedit_cursor(const struct yasl_surface *s, int *begin, int *end) +bool yasl_private_surface_ime_preedit_cursor(const struct yasl_surface *s, int *begin, int *end) { if (!s || !begin || !end || !s->display->ops->surface_ime_preedit_cursor) return 0; return s->display->ops->surface_ime_preedit_cursor(s, begin, end); } -void yasl_surface_set_ime_caret(struct yasl_surface *s, struct yasl_rect2i caret) +void yasl_private_surface_set_ime_caret(struct yasl_surface *s, struct yasl_private_rect2i caret) { if (!s || !s->display->ops->surface_set_ime_caret) return; s->display->ops->surface_set_ime_caret(s, caret); } -bool yasl_surface_start_interactive_move(struct yasl_surface *s) +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); } -bool yasl_surface_start_interactive_resize(struct yasl_surface *s, enum yasl_resize_edge edge) +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_EDGE_TOP && edge != YASL_EDGE_BOTTOM && - edge != YASL_EDGE_LEFT && edge != YASL_EDGE_RIGHT && - edge != YASL_EDGE_TOP_LEFT && edge != YASL_EDGE_TOP_RIGHT && - edge != YASL_EDGE_BOTTOM_LEFT && edge != YASL_EDGE_BOTTOM_RIGHT)) + (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); } -bool yasl_surface_show_window_menu(struct yasl_surface *s, struct yasl_vector2i pos) +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); } -bool yasl_surface_drag_carry(struct yasl_surface *s, struct yasl_vector2i offset) +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); } -bool yasl_surface_place_under_pointer(struct yasl_surface *s, struct yasl_vector2i at) +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); } -void yasl_surface_clear_timers(struct yasl_surface *s) +void yasl_private_surface_clear_timers(struct yasl_surface *s) { if (!s || !s->timers) return; - struct yasl_loop *loop = s->display ? s->display->loop : NULL; + struct yasl_private_loop *loop = s->display ? s->display->loop : NULL; for (int i = 0; i < s->timer_count; i++) { if (loop && s->timers[i]) { - yasl_loop_del_timer(loop, s->timers[i]); + yasl_private_loop_del_timer(loop, s->timers[i]); } } free(s->timers); @@ -515,13 +502,13 @@ void yasl_surface_clear_timers(struct yasl_surface *s) s->timer_capacity = 0; } -yasl_timer_id yasl_surface_add_timer(struct yasl_surface *s, uint64_t interval_ns, - yasl_timer_cb cb, void *ud) +yasl_private_timer_id yasl_private_surface_add_timer(struct yasl_surface *s, uint64_t interval_ns, + yasl_private_timer_cb cb, void *ud) { if (!s || !s->display || !s->display->loop || !cb || interval_ns == 0) return 0; - yasl_timer_id id = yasl_loop_add_timer(s->display->loop, interval_ns, cb, ud); + yasl_private_timer_id id = yasl_private_loop_add_timer(s->display->loop, interval_ns, cb, ud); if (!id) return 0; /* A one-shot timer retires itself in the loop. Drop retired ids before @@ -529,15 +516,15 @@ yasl_timer_id yasl_surface_add_timer(struct yasl_surface *s, uint64_t interval_n if (s->timer_count >= s->timer_capacity) { int kept = 0; for (int i = 0; i < s->timer_count; i++) - if (yasl_loop_timer_armed(s->display->loop, s->timers[i])) + if (yasl_private_loop_timer_armed(s->display->loop, s->timers[i])) s->timers[kept++] = s->timers[i]; s->timer_count = kept; } if (s->timer_count >= s->timer_capacity) { int new_cap = s->timer_capacity ? s->timer_capacity * 2 : 4; - yasl_timer_id *nt = realloc(s->timers, (size_t)new_cap * sizeof(*nt)); + yasl_private_timer_id *nt = realloc(s->timers, (size_t)new_cap * sizeof(*nt)); if (!nt) { - yasl_loop_del_timer(s->display->loop, id); + yasl_private_loop_del_timer(s->display->loop, id); return 0; } s->timers = nt; @@ -547,24 +534,24 @@ yasl_timer_id yasl_surface_add_timer(struct yasl_surface *s, uint64_t interval_n return id; } -bool yasl_surface_del_timer(struct yasl_surface *s, yasl_timer_id id) +bool yasl_private_surface_del_timer(struct yasl_surface *s, yasl_private_timer_id id) { if (!s || id == 0) return false; for (int i = 0; i < s->timer_count; i++) { if (s->timers[i] == id) { s->timers[i] = s->timers[--s->timer_count]; - return s->display && yasl_loop_del_timer(s->display->loop, id); + return s->display && yasl_private_loop_del_timer(s->display->loop, id); } } return false; } -struct yasl_loop *yasl_display_loop(struct yasl_display *d) +struct yasl_private_loop *yasl_private_display_loop(struct yasl_private_display *d) { return d ? d->loop : NULL; } -bool yasl_frame_present(struct yasl_frame *frame, const struct yasl_rect2i *rects, size_t count) +bool yasl_private_frame_present(struct yasl_private_frame *frame, const struct yasl_private_rect2i *rects, size_t count) { if (!frame || !frame->surface || (!rects && count)) { errno = EINVAL; return false; } struct yasl_surface *s = frame->surface; @@ -573,81 +560,81 @@ bool yasl_frame_present(struct yasl_frame *frame, const struct yasl_rect2i *rect for (size_t i = 0; i < count; i++) { if (rects[i].width < 0 || rects[i].height < 0) { errno = EINVAL; return false; } } - struct yasl_damage_list damage = {0}; - if (!rects || count > YASL_DAMAGE_MAX) damage.n = -1; + struct yasl_private_damage_list damage = {0}; + if (!rects || count > YASL_PRIVATE_DAMAGE_MAX) damage.n = -1; else { damage.n = (int)count; for (size_t i = 0; i < count; i++) damage.rects[i] = rects[i]; } errno = 0; - bool ok = yasl_surface_present(s, &frame->target, &damage); + bool ok = yasl_private_surface_present(s, &frame->target, &damage); if (ok) s->frame_presented = true; if (!ok && !errno) errno = EIO; return ok; } -void yasl_surface_request_frame(struct yasl_surface *s) +void yasl_private_surface_request_frame(struct yasl_surface *s) { if (!s) return; - yasl_surface_schedule_redraw(s); - if (s->display->loop) yasl_loop_kick(s->display->loop); + yasl_private_surface_schedule_redraw(s); + if (s->display->loop) yasl_private_loop_kick(s->display->loop); } -static uint64_t pacing_tick(void *ud, yasl_timer_id id, uint64_t interval) +static uint64_t pacing_tick(void *ud, yasl_private_timer_id id, uint64_t interval) { (void)id; (void)interval; struct yasl_surface *s = ud; - yasl_surface_request_frame(s); - double refresh = yasl_surface_timing(s).refresh_ns; + yasl_private_surface_request_frame(s); + double refresh = yasl_private_surface_timing(s).refresh_ns; return refresh >= 1000000.0 && refresh <= 1000000000.0 ? (uint64_t)refresh : 16666667; } -bool yasl_surface_set_pacing(struct yasl_surface *s, yasl_pacing_mode mode) +bool yasl_private_surface_set_pacing(struct yasl_surface *s, yasl_private_pacing_mode mode) { - if (!s || (mode != YASL_PACING_ON_DEMAND && mode != YASL_PACING_CONTINUOUS)) { + if (!s || (mode != YASL_PRIVATE_PACING_ON_DEMAND && mode != YASL_PRIVATE_PACING_CONTINUOUS)) { errno = EINVAL; return false; } if (s->pacing_timer) { - yasl_surface_del_timer(s, s->pacing_timer); + yasl_private_surface_del_timer(s, s->pacing_timer); s->pacing_timer = 0; } - if (mode == YASL_PACING_CONTINUOUS && !(yasl_display_caps(s->display) & YASL_CAP_FRAME_PACING)) { - s->pacing_timer = yasl_surface_add_timer(s, 16666667, pacing_tick, s); + if (mode == YASL_PRIVATE_PACING_CONTINUOUS && !(yasl_private_display_caps(s->display) & YASL_PRIVATE_CAP_FRAME_PACING)) { + s->pacing_timer = yasl_private_surface_add_timer(s, 16666667, pacing_tick, s); if (!s->pacing_timer) { - s->pacing = YASL_PACING_ON_DEMAND; + s->pacing = YASL_PRIVATE_PACING_ON_DEMAND; errno = ENOMEM; return false; } } s->pacing = mode; - if (mode == YASL_PACING_CONTINUOUS) yasl_surface_request_frame(s); + if (mode == YASL_PRIVATE_PACING_CONTINUOUS) yasl_private_surface_request_frame(s); return true; } -void yasl_surface_draw(struct yasl_surface *s, struct yasl_frame *frame) +void yasl_private_surface_draw(struct yasl_surface *s, struct yasl_private_frame *frame) { if (!s || !frame || !s->callbacks.draw) return; - struct yasl_display *d = s->display; + struct yasl_private_display *d = s->display; uint64_t id = s->id; frame->surface = s; frame->target.stride_bytes = frame->target.size.x * (int)sizeof(uint32_t); frame->scale_120 = s->scale_120; frame->frame_index = s->draw_index++; - frame->target_time_ns = yasl_surface_frame_time_ns(s, yasl_loop_now_ns()); + frame->target_time_ns = yasl_private_surface_frame_time_ns(s, yasl_private_loop_now_ns()); s->drawing = true; s->frame_presented = false; s->active_frame = frame; s->callbacks.draw(s, frame, s->ud); /* The callback may destroy s. Lookup by identity before accessing it. */ - struct yasl_surface *live = id ? yasl_events_find(d, id) : NULL; + struct yasl_surface *live = id ? yasl_private_events_find(d, id) : NULL; if (live) { live->drawing = false; live->active_frame = NULL; - if (live->pacing == YASL_PACING_CONTINUOUS && - (yasl_display_caps(d) & YASL_CAP_FRAME_PACING)) - yasl_surface_schedule_redraw(live); + if (live->pacing == YASL_PRIVATE_PACING_CONTINUOUS && + (yasl_private_display_caps(d) & YASL_PRIVATE_CAP_FRAME_PACING)) + yasl_private_surface_schedule_redraw(live); } } @@ -657,7 +644,7 @@ static bool window_valid(struct yasl_surface *s) return true; } -bool yasl_surface_set_size(struct yasl_surface *s, struct yasl_vector2i size) +bool yasl_private_surface_set_size(struct yasl_surface *s, struct yasl_private_vector2i size) { if (!window_valid(s)) return false; if (size.x <= 0 || size.y <= 0 || size.x > 32767 || size.y > 32767 || @@ -669,7 +656,7 @@ bool yasl_surface_set_size(struct yasl_surface *s, struct yasl_vector2i size) return ok; } -bool yasl_surface_set_fullscreen(struct yasl_surface *s, bool enabled) +bool yasl_private_surface_set_fullscreen(struct yasl_surface *s, bool enabled) { if (!window_valid(s)) return false; if (!s->display->ops->surface_set_fullscreen) { errno = ENOTSUP; return false; } @@ -679,7 +666,7 @@ bool yasl_surface_set_fullscreen(struct yasl_surface *s, bool enabled) return ok; } -bool yasl_surface_set_maximized(struct yasl_surface *s, bool enabled) +bool yasl_private_surface_set_maximized(struct yasl_surface *s, bool enabled) { if (!window_valid(s)) return false; if (!s->display->ops->surface_set_maximized) { errno = ENOTSUP; return false; } @@ -689,7 +676,7 @@ bool yasl_surface_set_maximized(struct yasl_surface *s, bool enabled) return ok; } -bool yasl_surface_minimize(struct yasl_surface *s) +bool yasl_private_surface_minimize(struct yasl_surface *s) { if (!window_valid(s)) return false; if (!s->display->ops->surface_minimize) { errno = ENOTSUP; return false; } @@ -699,22 +686,22 @@ bool yasl_surface_minimize(struct yasl_surface *s) return ok; } -static bool selection_valid(struct yasl_display *d, enum yasl_selection selection) +static bool selection_valid(struct yasl_private_display *d, enum yasl_private_selection selection) { - if (!d || (selection != YASL_CLIPBOARD && selection != YASL_PRIMARY)) { + if (!d || (selection != YASL_PRIVATE_CLIPBOARD && selection != YASL_PRIVATE_PRIMARY)) { errno = EINVAL; return false; } - if (selection == YASL_PRIMARY && !(yasl_display_caps(d) & YASL_CAP_PRIMARY_SELECTION)) { + if (selection == YASL_PRIVATE_PRIMARY && !(yasl_private_display_caps(d) & YASL_PRIVATE_CAP_PRIMARY_SELECTION)) { errno = ENOTSUP; return false; } return true; } -bool yasl_clipboard_set(struct yasl_display *d, enum yasl_selection selection, const char *text) +bool yasl_private_clipboard_set(struct yasl_private_display *d, enum yasl_private_selection selection, const char *text) { if (!selection_valid(d, selection)) return false; if (!text) { errno = EINVAL; return false; } - if (!yasl_utf8_validate(text, strlen(text))) { errno = EILSEQ; return false; } + if (!yasl_private_utf8_validate(text, strlen(text))) { errno = EILSEQ; return false; } if (!d->ops->display_copy_text) { errno = ENOTSUP; return false; } errno = 0; bool ok = d->ops->display_copy_text(d, selection, text); @@ -722,13 +709,13 @@ bool yasl_clipboard_set(struct yasl_display *d, enum yasl_selection selection, c return ok; } -uint64_t yasl_clipboard_request(struct yasl_surface *s, enum yasl_selection selection) +uint64_t yasl_private_clipboard_request(struct yasl_surface *s, enum yasl_private_selection selection) { if (!s) { errno = EINVAL; return 0; } - struct yasl_display *d = s->display; + struct yasl_private_display *d = s->display; if (!selection_valid(d, selection)) return 0; if (!d->ops->display_request_paste) { errno = ENOTSUP; return 0; } - uint64_t id = yasl_events_request_id(d); + uint64_t id = yasl_private_events_request_id(d); if (!id) return 0; errno = 0; if (!d->ops->display_request_paste(d, selection, s, id)) { diff --git a/src/common/backend.h b/src/common/backend.h index 876a4ca..e3dc6de 100644 --- a/src/common/backend.h +++ b/src/common/backend.h @@ -1,48 +1,64 @@ -#ifndef YASL_BACKEND_H -#define YASL_BACKEND_H +#ifndef YASL_PRIVATE_BACKEND_H +#define YASL_PRIVATE_BACKEND_H -#include "yasl/yasl.h" -#include "yasl/loop.h" -#include "yasl/native.h" +#include "common/types.h" +#include "common/private_loop.h" +#include "common/private_native.h" #include "common/input.h" +#include "common/gl_options.h" #include "common/timing.h" #include <string.h> -struct yasl_backend_ops; -struct yasl_event_state; +struct yasl_private_backend_ops; +struct yasl_private_event_state; -struct yasl_display { - const struct yasl_backend_ops *ops; +struct yasl_private_display { + const struct yasl_private_backend_ops *ops; void *native_handle; - struct yasl_display_cbs callbacks; - struct yasl_loop *loop; - void *app_runner; - /* Backend's copy of yasl_display_spec.app_id, or NULL; for desktop services. */ + struct yasl_private_display_cbs callbacks; + struct yasl_private_loop *loop; + /* Backend's copy of yasl_private_display_spec.app_id, or NULL; for desktop services. */ const char *app_id; void *callback_ud; - struct yasl_event_state *events; + struct yasl_private_event_state *events; struct yasl_surface *surfaces; }; struct yasl_surface { - struct yasl_display *display; + unsigned creation_flags; + enum yasl_private_presenter presenter; + struct yasl_surface *parent; + uint32_t *committed_pixels; + int committed_width, committed_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; + bool text_input_active; + unsigned text_purpose; + char *text_surrounding; + size_t text_length, text_begin, text_end; + uint64_t text_revision, text_generation; + struct yasl_private_display *display; void *native_handle; - struct yasl_vector2i size; + struct yasl_private_vector2i size; uint32_t scale_120; - uint32_t state; /* Authoritative YASL_STATE_* bitmask, maintained by the backend. */ + uint32_t state; /* Authoritative YASL_PRIVATE_STATE_* bitmask, maintained by the backend. */ uint32_t bg_color; - struct yasl_window_cbs callbacks; + struct yasl_private_window_cbs callbacks; void *ud; uint64_t frame_generation; struct input_queue input; - enum yasl_pointer_mode pointer_mode; - struct yasl_frame_clock clock; + enum yasl_private_pointer_mode pointer_mode; + struct yasl_private_frame_clock clock; uint64_t frames_skipped; - yasl_timer_id *timers; + yasl_private_timer_id *timers; int timer_count; int timer_capacity; - void *app_surface; struct yasl_surface *event_next; uint64_t id; void *userdata; @@ -51,40 +67,41 @@ struct yasl_surface { bool frame_presented; uint32_t reported_scale; uint64_t draw_index; - struct yasl_frame *active_frame; - yasl_pacing_mode pacing; - yasl_timer_id pacing_timer; + struct yasl_private_frame *active_frame; + yasl_private_pacing_mode pacing; + yasl_private_timer_id pacing_timer; }; -bool yasl_events_open(struct yasl_display *d); -void yasl_events_close(struct yasl_display *d); -int yasl_events_error(struct yasl_display *d); -bool yasl_events_pending(struct yasl_display *d); -struct yasl_surface *yasl_events_find(struct yasl_display *d, uint64_t id); -bool yasl_events_register(struct yasl_surface *s); -void yasl_events_unregister(struct yasl_surface *s); -bool yasl_events_push(struct yasl_display *d, struct yasl_surface *queue_owner, - const struct yasl_event *event); -bool yasl_events_poll_surface(struct yasl_surface *s, struct yasl_event *event); -void yasl_surface_notify_configure(struct yasl_surface *s, struct yasl_geometry geometry); -void yasl_surface_notify_close(struct yasl_surface *s); -void yasl_surface_notify_state(struct yasl_surface *s, uint32_t state); -void yasl_surface_notify_drag(struct yasl_surface *s, enum yasl_drag_event ev, struct yasl_vector2i pos); -void yasl_surface_notify_drop(struct yasl_surface *s, enum yasl_drop_event ev, struct yasl_vector2i pos); -void yasl_surface_notify_composition(struct yasl_surface *s, const char *text, int begin, int end); -void yasl_surface_draw(struct yasl_surface *s, struct yasl_frame *frame); -void yasl_display_notify_event(struct yasl_display *d, enum yasl_event_type type); -void yasl_surface_complete_paste(struct yasl_surface *s, uint64_t request_id, - enum yasl_transfer_status status, const char *text, size_t len); -uint64_t yasl_events_request_id(struct yasl_display *d); +bool yasl_private_events_open(struct yasl_private_display *d); +void yasl_private_events_close(struct yasl_private_display *d); +int yasl_private_events_error(struct yasl_private_display *d); +bool yasl_private_events_pending(struct yasl_private_display *d); +struct yasl_surface *yasl_private_events_find(struct yasl_private_display *d, uint64_t id); +bool yasl_private_events_register(struct yasl_surface *s); +void yasl_private_events_unregister(struct yasl_surface *s); +bool yasl_private_events_push(struct yasl_private_display *d, struct yasl_surface *queue_owner, + const struct yasl_private_event *event); +bool yasl_private_events_poll_surface(struct yasl_surface *s, struct yasl_private_event *event); +void yasl_private_surface_notify_configure(struct yasl_surface *s, struct yasl_private_geometry geometry); +void yasl_private_surface_notify_refresh(struct yasl_surface *s); +void yasl_private_surface_notify_close(struct yasl_surface *s); +void yasl_private_surface_notify_state(struct yasl_surface *s, uint32_t state); +void yasl_private_surface_notify_drag(struct yasl_surface *s, enum yasl_private_drag_event ev, struct yasl_private_vector2i pos); +void yasl_private_surface_notify_drop(struct yasl_surface *s, enum yasl_private_drop_event ev, struct yasl_private_vector2i pos); +void yasl_private_surface_notify_composition(struct yasl_surface *s, const char *text, int begin, int end); +void yasl_private_surface_draw(struct yasl_surface *s, struct yasl_private_frame *frame); +void yasl_private_display_notify_event(struct yasl_private_display *d, enum yasl_private_event_type type); +void yasl_private_surface_complete_paste(struct yasl_surface *s, uint64_t request_id, + enum yasl_private_transfer_status status, const char *text, size_t len); +uint64_t yasl_private_events_request_id(struct yasl_private_display *d); -void yasl_surface_clear_timers(struct yasl_surface *s); -struct yasl_surface *yasl_create_toplevel(struct yasl_display *d, - const struct yasl_window_spec *spec, void *graphics_device); +void yasl_private_surface_clear_timers(struct yasl_surface *s); +struct yasl_surface *yasl_private_create_toplevel(struct yasl_private_display *d, + const struct yasl_private_window_spec *spec, void *graphics_device); /* Report a presented CPU frame's extent, and copy it when out can hold it. */ -static inline bool yasl_copy_frame(const uint32_t *pixels, struct yasl_vector2i extent, - uint32_t *out, size_t cap, struct yasl_vector2i *size) +static inline bool yasl_private_copy_frame(const uint32_t *pixels, struct yasl_private_vector2i extent, + uint32_t *out, size_t cap, struct yasl_private_vector2i *size) { *size = extent; size_t count = (size_t)extent.x * (size_t)extent.y; @@ -93,83 +110,92 @@ static inline bool yasl_copy_frame(const uint32_t *pixels, struct yasl_vector2i return true; } -static inline uint64_t yasl_frame_begin(struct yasl_surface *s) +static inline uint64_t yasl_private_frame_begin(struct yasl_surface *s) { if (++s->frame_generation == 0) ++s->frame_generation; return s->frame_generation; } -struct yasl_backend_ops { - enum yasl_backend type; - struct yasl_display *(*display_open)(struct yasl_loop *loop, const struct yasl_display_spec *spec); - void (*display_close)(struct yasl_display *d); - uint32_t (*display_caps)(const struct yasl_display *d); - int (*display_dispatch_timeout)(struct yasl_display *d, int timeout_ms); - void (*display_flush)(struct yasl_display *d); - int (*display_modifiers)(const struct yasl_display *d); +typedef void (*yasl_private_gl_proc)(void); +struct yasl_private_backend_ops { + struct yasl_surface *(*surface_create_gl)(struct yasl_private_display *, const struct yasl_private_window_spec *, const int *, struct yasl_surface *share); + bool (*gl_make_current)(struct yasl_private_display *, struct yasl_surface *); + yasl_private_gl_proc (*gl_get_proc_address)(struct yasl_private_display *, const char *); + bool (*gl_swap_interval)(struct yasl_private_display *, struct yasl_surface *, int); + bool (*gl_swap_buffers)(struct yasl_private_display *, struct yasl_surface *); + void *(*surface_get_metal_layer)(struct yasl_surface *); + bool (*surface_update_text_input)(struct yasl_surface *); + + enum yasl_private_backend type; + struct yasl_private_display *(*display_open)(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec); + void (*display_close)(struct yasl_private_display *d); + uint32_t (*display_caps)(const struct yasl_private_display *d); + int (*display_dispatch_timeout)(struct yasl_private_display *d, int timeout_ms); + void (*display_flush)(struct yasl_private_display *d); + int (*display_modifiers)(const struct yasl_private_display *d); /* Optional; returns zero or less when the system reports no interval. */ - int (*display_double_click_ms)(const struct yasl_display *d); - void (*display_copy)(struct yasl_display *d, enum yasl_selection sel, const char *text); - void (*display_paste)(struct yasl_display *d, enum yasl_selection sel, struct yasl_surface *target); - bool (*display_copy_text)(struct yasl_display *d, enum yasl_selection sel, const char *text); - bool (*display_request_paste)(struct yasl_display *d, enum yasl_selection sel, + int (*display_double_click_ms)(const struct yasl_private_display *d); + void (*display_copy)(struct yasl_private_display *d, enum yasl_private_selection sel, const char *text); + void (*display_paste)(struct yasl_private_display *d, enum yasl_private_selection sel, struct yasl_surface *target); + bool (*display_copy_text)(struct yasl_private_display *d, enum yasl_private_selection sel, const char *text); + bool (*display_request_paste)(struct yasl_private_display *d, enum yasl_private_selection sel, struct yasl_surface *target, uint64_t request_id); - bool (*display_drag_start)(struct yasl_display *d, struct yasl_surface *origin, const struct yasl_drag_image *image, const struct yasl_drag_listener *l, void *ud); - void (*display_drag_cancel)(struct yasl_display *d); + bool (*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); + void (*display_drag_cancel)(struct yasl_private_display *d); /* Create with callbacks disabled; common code installs them after return. */ - struct yasl_surface *(*surface_create_toplevel)(struct yasl_display *d, - const struct yasl_window_spec *spec, void *graphics_device); - struct yasl_surface *(*surface_create_popup)(struct yasl_surface *parent, const struct yasl_popup_spec *spec); - bool (*surface_popup_reposition)(struct yasl_surface *popup, const struct yasl_popup_spec *spec); + struct yasl_surface *(*surface_create_toplevel)(struct yasl_private_display *d, + const struct yasl_private_window_spec *spec, void *graphics_device); + struct yasl_surface *(*surface_create_popup)(struct yasl_surface *parent, const struct yasl_private_popup_spec *spec); + bool (*surface_popup_reposition)(struct yasl_surface *popup, const struct yasl_private_popup_spec *spec); void (*surface_destroy)(struct yasl_surface *s); struct input_queue *(*surface_input_queue)(struct yasl_surface *s); - bool (*surface_set_size)(struct yasl_surface *s, struct yasl_vector2i size_logical); + bool (*surface_set_size)(struct yasl_surface *s, struct yasl_private_vector2i size_logical); bool (*surface_set_fullscreen)(struct yasl_surface *s, bool enabled); bool (*surface_set_maximized)(struct yasl_surface *s, bool enabled); bool (*surface_minimize)(struct yasl_surface *s); - struct yasl_display *(*surface_display)(const struct yasl_surface *s); + struct yasl_private_display *(*surface_display)(const struct yasl_surface *s); /* Copy only; must not invoke application callbacks. */ - void (*surface_set_callbacks)(struct yasl_surface *s, const struct yasl_window_cbs *cbs, void *ud); + void (*surface_set_callbacks)(struct yasl_surface *s, const struct yasl_private_window_cbs *cbs, void *ud); void (*surface_set_title)(struct yasl_surface *s, const char *title); void (*surface_schedule_redraw)(struct yasl_surface *s); - bool (*surface_present)(struct yasl_surface *s, const struct yasl_framebuffer *fb, const struct yasl_damage_list *damage); + bool (*surface_present)(struct yasl_surface *s, const struct yasl_private_framebuffer *fb, const struct yasl_private_damage_list *damage); /* Optional; NULL means the common input queue is drained. */ - bool (*surface_poll_event)(struct yasl_surface *s, struct yasl_event *ev); - /* Optional; NULL means only YASL_POINTER_NORMAL is supported. Backends that + bool (*surface_poll_event)(struct yasl_surface *s, struct yasl_private_event *ev); + /* Optional; NULL means only YASL_PRIVATE_POINTER_NORMAL is supported. Backends that * implement RELATIVE push POINTER_MOTION with delta set and pos frozen. */ - bool (*surface_set_pointer_mode)(struct yasl_surface *s, enum yasl_pointer_mode mode); - struct yasl_geometry (*surface_geometry)(const struct yasl_surface *s); - struct yasl_timing (*surface_timing)(const struct yasl_surface *s); + bool (*surface_set_pointer_mode)(struct yasl_surface *s, enum yasl_private_pointer_mode mode); + struct yasl_private_geometry (*surface_geometry)(const struct yasl_surface *s); + struct yasl_private_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); + void (*surface_set_cursor)(struct yasl_surface *s, enum yasl_private_cursor cursor); void (*surface_set_background_color)(struct yasl_surface *s, uint32_t bg); const char *(*surface_ime_preedit)(const struct yasl_surface *s); bool (*surface_ime_preedit_cursor)(const struct yasl_surface *s, int *begin, int *end); - void (*surface_set_ime_caret)(struct yasl_surface *s, struct yasl_rect2i caret); + void (*surface_set_ime_caret)(struct yasl_surface *s, struct yasl_private_rect2i caret); bool (*surface_start_interactive_move)(struct yasl_surface *s); - bool (*surface_start_interactive_resize)(struct yasl_surface *s, enum yasl_resize_edge edge); - bool (*surface_show_window_menu)(struct yasl_surface *s, struct yasl_vector2i pos); - bool (*surface_drag_carry)(struct yasl_surface *s, struct yasl_vector2i offset); - bool (*surface_place_under_pointer)(struct yasl_surface *s, struct yasl_vector2i at); + bool (*surface_start_interactive_resize)(struct yasl_surface *s, enum yasl_private_resize_edge edge); + bool (*surface_show_window_menu)(struct yasl_surface *s, struct yasl_private_vector2i pos); + bool (*surface_drag_carry)(struct yasl_surface *s, struct yasl_private_vector2i offset); + bool (*surface_place_under_pointer)(struct yasl_surface *s, struct yasl_private_vector2i at); /* Optional; NULL means the bell is unsupported. */ bool (*surface_bell)(struct yasl_surface *s); /* Optional; NULL means no readback. Fills size only for an available frame. */ bool (*surface_read_pixels)(struct yasl_surface *s, uint32_t *out, size_t cap, - struct yasl_vector2i *size); - void *(*display_get_native_handle)(struct yasl_display *d); + struct yasl_private_vector2i *size); + void *(*display_get_native_handle)(struct yasl_private_display *d); void *(*surface_get_native_handle)(struct yasl_surface *s); bool (*surface_get_native_frame)(struct yasl_surface *s, - const struct yasl_framebuffer *target, struct yasl_native_frame *out); + const struct yasl_private_framebuffer *target, struct yasl_private_native_frame *out); }; #if YASL_HAS_WAYLAND -extern const struct yasl_backend_ops yasl_wayland_backend; +extern const struct yasl_private_backend_ops yasl_private_wayland_backend; #endif #if YASL_HAS_X11 -extern const struct yasl_backend_ops yasl_x11_backend; +extern const struct yasl_private_backend_ops yasl_private_x11_backend; #endif #if YASL_HAS_COCOA -extern const struct yasl_backend_ops yasl_cocoa_backend; +extern const struct yasl_private_backend_ops yasl_private_cocoa_backend; #endif #endif diff --git a/src/common/desktop.c b/src/common/desktop.c index 23c62d7..5045604 100644 --- a/src/common/desktop.c +++ b/src/common/desktop.c @@ -1,4 +1,4 @@ -#include "yasl/desktop.h" +#include "common/private_desktop.h" #include "common/backend.h" #include "common/desktop.h" @@ -36,46 +36,60 @@ static size_t copy_out(char *out, size_t cap, const char *s, size_t len) return len; } -bool yasl_display_open_uri(struct yasl_display *d, const char *uri) +bool yasl_private_display_open_uri(struct yasl_private_display *d, const char *uri) { if (!d || !uri || !uri_valid(uri)) return false; return desktop_open_uri(uri); } -bool yasl_display_notify(struct yasl_display *d, const char *title, const char *body) +bool yasl_private_display_notify(struct yasl_private_display *d, const char *title, const char *body) { if (!d || !title) return false; return desktop_notify(d->app_id, title, body ? body : ""); } -uint32_t yasl_display_appearance(struct yasl_display *d) +uint32_t yasl_private_display_appearance(struct yasl_private_display *d) { return d ? desktop_appearance() : 0; } -bool yasl_surface_bell(struct yasl_surface *s) +bool yasl_private_surface_bell(struct yasl_surface *s) { if (!s || !s->display->ops->surface_bell) return false; return s->display->ops->surface_bell(s); } -size_t yasl_executable_path(char *out, size_t cap) +size_t yasl_private_executable_path(char *out, size_t cap) { if (!out && cap) return 0; char buf[DESKTOP_STR_MAX]; return copy_out(out, cap, buf, desktop_executable_path(buf, sizeof(buf))); } -size_t yasl_resource_dir(char *out, size_t cap) +size_t yasl_private_resource_dir(char *out, size_t cap) { if (!out && cap) return 0; char buf[DESKTOP_STR_MAX]; return copy_out(out, cap, buf, desktop_resource_dir(buf, sizeof(buf))); } -size_t yasl_default_locale(char *out, size_t cap) +size_t yasl_private_default_locale(char *out, size_t cap) { if (!out && cap) return 0; char buf[DESKTOP_STR_MAX]; return copy_out(out, cap, buf, desktop_default_locale(buf, sizeof(buf))); } + +#include "public_internal.h" +#include "yasl/desktop.h" +#include <errno.h> +bool yasl_open_uri(const char *uri) +{ if(!uri||!uri_valid(uri))return yasl_public_fail(EINVAL);errno=0;return desktop_open_uri(uri)||yasl_public_fail(errno); } +bool yasl_notify(const char *title,const char *body) +{ if(!yasl_public_owner())return false;errno=0;return yasl_private_display_notify(yasl_public_display(),title,body)||yasl_public_fail(errno); } +unsigned yasl_appearance(void) { return yasl_private_display_appearance(yasl_public_display()); } +bool yasl_surface_bell(yasl_surface *s) +{ if(!yasl_public_surface(s,false))return false;errno=0;return yasl_private_surface_bell(s)||yasl_public_fail(errno); } +size_t yasl_executable_path(char *out,size_t cap) { return yasl_private_executable_path(out,cap); } +size_t yasl_resource_dir(char *out,size_t cap) { return yasl_private_resource_dir(out,cap); } +size_t yasl_default_locale(char *out,size_t cap) { return yasl_private_default_locale(out,cap); } diff --git a/src/common/desktop.h b/src/common/desktop.h index 9af5952..68763b2 100644 --- a/src/common/desktop.h +++ b/src/common/desktop.h @@ -1,5 +1,5 @@ -#ifndef YASL_COMMON_DESKTOP_H -#define YASL_COMMON_DESKTOP_H +#ifndef YASL_PRIVATE_COMMON_DESKTOP_H +#define YASL_PRIVATE_COMMON_DESKTOP_H #include <stdbool.h> #include <stddef.h> diff --git a/src/common/events.c b/src/common/events.c index a9bbcaf..ba78d77 100644 --- a/src/common/events.c +++ b/src/common/events.c @@ -6,15 +6,15 @@ #include <string.h> struct event_node { - struct yasl_event event; + struct yasl_private_event event; uint64_t queue_id; char *text; void (*release)(void *); struct event_node *next; }; -struct yasl_event_state { - yasl_mutex mutex; +struct yasl_private_event_state { + yasl_private_mutex mutex; struct event_node *head, *tail, *last; uint64_t sequence, identity, request; int error; @@ -37,67 +37,67 @@ static void dispose_list(struct event_node *nodes) } } -bool yasl_events_open(struct yasl_display *d) +bool yasl_private_events_open(struct yasl_private_display *d) { - struct yasl_event_state *state = calloc(1, sizeof(*state)); + struct yasl_private_event_state *state = calloc(1, sizeof(*state)); if (!state) { errno = ENOMEM; return false; } - int error = yasl_mutex_init(&state->mutex); + int error = yasl_private_mutex_init(&state->mutex); if (error) { free(state); errno = error; return false; } d->events = state; return true; } -void yasl_events_close(struct yasl_display *d) +void yasl_private_events_close(struct yasl_private_display *d) { - struct yasl_event_state *state = d->events; + struct yasl_private_event_state *state = d->events; if (!state) return; /* Producers must already have stopped. Detach before callbacks so cleanup * can make harmless failed posts without touching a dying queue. */ d->events = NULL; dispose_list(state->head); dispose(state->last); - yasl_mutex_destroy(&state->mutex); + yasl_private_mutex_destroy(&state->mutex); free(state); } -int yasl_events_error(struct yasl_display *d) +int yasl_private_events_error(struct yasl_private_display *d) { if (!d->events) return 0; - yasl_mutex_lock(&d->events->mutex); + yasl_private_mutex_lock(&d->events->mutex); int error = d->events->error; - yasl_mutex_unlock(&d->events->mutex); + yasl_private_mutex_unlock(&d->events->mutex); return error; } -bool yasl_events_pending(struct yasl_display *d) +bool yasl_private_events_pending(struct yasl_private_display *d) { if (!d->events) return false; - yasl_mutex_lock(&d->events->mutex); + yasl_private_mutex_lock(&d->events->mutex); bool pending = d->events->head != NULL; - yasl_mutex_unlock(&d->events->mutex); + yasl_private_mutex_unlock(&d->events->mutex); return pending; } -struct yasl_surface *yasl_events_find(struct yasl_display *d, uint64_t id) +struct yasl_surface *yasl_private_events_find(struct yasl_private_display *d, uint64_t id) { for (struct yasl_surface *s = d->surfaces; s; s = s->event_next) if (s->id == id) return s; return NULL; } -static bool publish_input(const struct yasl_event *event, void *ud) +static bool publish_input(const struct yasl_private_event *event, void *ud) { struct yasl_surface *owner = ud; - return yasl_events_push(owner->display, owner, event); + return yasl_private_events_push(owner->display, owner, event); } -bool yasl_events_register(struct yasl_surface *s) +bool yasl_private_events_register(struct yasl_surface *s) { - struct yasl_display *d = s->display; + struct yasl_private_display *d = s->display; if (!d->events) return true; /* Internal test doubles need no queue. */ - yasl_mutex_lock(&d->events->mutex); + yasl_private_mutex_lock(&d->events->mutex); if (d->events->identity == UINT64_MAX) { - yasl_mutex_unlock(&d->events->mutex); + yasl_private_mutex_unlock(&d->events->mutex); errno = EOVERFLOW; return false; } @@ -105,26 +105,26 @@ bool yasl_events_register(struct yasl_surface *s) s->id = ++d->events->identity; s->event_next = d->surfaces; d->surfaces = s; - yasl_mutex_unlock(&d->events->mutex); + yasl_private_mutex_unlock(&d->events->mutex); struct input_queue *q = d->ops->surface_input_queue ? d->ops->surface_input_queue(s) : &s->input; if (q) { /* Preserve input generated during native creation. */ - struct yasl_event event; + struct yasl_private_event event; while (input_queue_pop(q, &event)) - yasl_events_push(d, s, &event); + yasl_private_events_push(d, s, &event); q->publish = publish_input; q->publish_ud = s; } return true; } -void yasl_events_unregister(struct yasl_surface *s) +void yasl_private_events_unregister(struct yasl_surface *s) { - struct yasl_display *d = s->display; + struct yasl_private_display *d = s->display; if (!d->events) return; struct event_node *discarded = NULL; - yasl_mutex_lock(&d->events->mutex); + yasl_private_mutex_lock(&d->events->mutex); struct yasl_surface **surface = &d->surfaces; while (*surface && *surface != s) surface = &(*surface)->event_next; if (*surface) *surface = s->event_next; @@ -142,30 +142,30 @@ void yasl_events_unregister(struct yasl_surface *s) } } s->event_next = NULL; - yasl_mutex_unlock(&d->events->mutex); + yasl_private_mutex_unlock(&d->events->mutex); dispose_list(discarded); } -static bool append(struct yasl_display *d, struct event_node *node) +static bool append(struct yasl_private_display *d, struct event_node *node) { - struct yasl_event_state *state = d->events; + struct yasl_private_event_state *state = d->events; if (state->sequence == UINT64_MAX) { errno = EOVERFLOW; return false; } node->event.sequence = ++state->sequence; - if (!node->event.time_ns) node->event.time_ns = yasl_loop_now_ns(); + if (!node->event.time_ns) node->event.time_ns = yasl_private_loop_now_ns(); if (state->tail) state->tail->next = node; else state->head = node; state->tail = node; return true; } -static bool string_event(enum yasl_event_type type) +static bool string_event(enum yasl_private_event_type type) { - return type == YASL_EVENT_TEXT || type == YASL_EVENT_PASTE || - type == YASL_EVENT_DROP || type == YASL_EVENT_COMPOSITION; + return type == YASL_PRIVATE_EVENT_TEXT || type == YASL_PRIVATE_EVENT_PASTE || + type == YASL_PRIVATE_EVENT_DROP || type == YASL_PRIVATE_EVENT_COMPOSITION || type == YASL_PRIVATE_EVENT_TEXT_EDIT; } -bool yasl_events_push(struct yasl_display *d, struct yasl_surface *queue_owner, - const struct yasl_event *event) +bool yasl_private_events_push(struct yasl_private_display *d, struct yasl_surface *queue_owner, + const struct yasl_private_event *event) { if (!d || !d->events || (event->surface && !event->surface->id)) return true; struct event_node *node = calloc(1, sizeof(*node)); @@ -182,56 +182,57 @@ bool yasl_events_push(struct yasl_display *d, struct yasl_surface *queue_owner, if (!node->text) { free(node); goto failed; } if (event->text.len) memcpy(node->text, event->text.data, event->text.len); node->text[event->text.len] = '\0'; - if (event->type == YASL_EVENT_PASTE) node->event.paste.data = node->text; - else if (event->type == YASL_EVENT_COMPOSITION) node->event.composition.data = node->text; + if (event->type == YASL_PRIVATE_EVENT_PASTE) node->event.paste.data = node->text; + else if (event->type == YASL_PRIVATE_EVENT_TEXT_EDIT) node->event.text_edit.data = node->text; + else if (event->type == YASL_PRIVATE_EVENT_COMPOSITION) node->event.composition.data = node->text; else node->event.text.data = node->text; } - yasl_mutex_lock(&d->events->mutex); - if ((node->event.surface_id && !yasl_events_find(d, node->event.surface_id)) || - (node->queue_id && !yasl_events_find(d, node->queue_id))) { - yasl_mutex_unlock(&d->events->mutex); + yasl_private_mutex_lock(&d->events->mutex); + if ((node->event.surface_id && !yasl_private_events_find(d, node->event.surface_id)) || + (node->queue_id && !yasl_private_events_find(d, node->queue_id))) { + yasl_private_mutex_unlock(&d->events->mutex); dispose(node); return true; /* A native teardown notification for a canceled recipient. */ } bool ok = append(d, node); if (!ok) d->events->error = errno; - yasl_mutex_unlock(&d->events->mutex); + yasl_private_mutex_unlock(&d->events->mutex); if (!ok) dispose(node); return ok; failed: - yasl_mutex_lock(&d->events->mutex); + yasl_private_mutex_lock(&d->events->mutex); d->events->error = ENOMEM; - yasl_mutex_unlock(&d->events->mutex); + yasl_private_mutex_unlock(&d->events->mutex); errno = ENOMEM; return false; } -bool yasl_display_post(struct yasl_display *d, const struct yasl_message *message) +bool yasl_private_display_post(struct yasl_private_display *d, const struct yasl_private_message *message) { if (!d || !d->events || !message) { errno = EINVAL; return false; } struct event_node *node = calloc(1, sizeof(*node)); if (!node) { errno = ENOMEM; return false; } - node->event = (struct yasl_event){ - .type = YASL_EVENT_USER, .surface_id = message->target_id, + node->event = (struct yasl_private_event){ + .type = YASL_PRIVATE_EVENT_USER, .surface_id = message->target_id, .user = {.code = message->code, .payload = message->payload, .size = message->size}, }; node->queue_id = message->target_id; node->release = message->release; - yasl_mutex_lock(&d->events->mutex); - bool ok = !message->target_id || yasl_events_find(d, message->target_id); + yasl_private_mutex_lock(&d->events->mutex); + bool ok = !message->target_id || yasl_private_events_find(d, message->target_id); if (!ok) errno = ENOENT; else ok = append(d, node); - yasl_mutex_unlock(&d->events->mutex); + yasl_private_mutex_unlock(&d->events->mutex); if (!ok) { free(node); return false; } - if (d->loop) yasl_loop_kick(d->loop); + if (d->loop) yasl_private_loop_kick(d->loop); return true; } -static bool poll_event(struct yasl_display *d, uint64_t queue_id, struct yasl_event *event) +static bool poll_event(struct yasl_private_display *d, uint64_t queue_id, struct yasl_private_event *event) { if (!d || !d->events || !event) return false; - struct yasl_event_state *state = d->events; - yasl_mutex_lock(&state->mutex); + struct yasl_private_event_state *state = d->events; + yasl_private_mutex_lock(&state->mutex); struct event_node *discarded = NULL; struct event_node **entry = &state->head; struct event_node *previous = NULL; @@ -241,8 +242,8 @@ static bool poll_event(struct yasl_display *d, uint64_t queue_id, struct yasl_ev * Mixing this adapter with display polling is deliberately unsupported. */ while (*entry) { struct event_node *candidate = *entry; - if (candidate->event.type < YASL_EVENT_KEY || - candidate->event.type > YASL_EVENT_INPUT_RESET) { + if (candidate->event.type < YASL_PRIVATE_EVENT_KEY || + candidate->event.type > YASL_PRIVATE_EVENT_INPUT_RESET) { *entry = candidate->next; if (state->tail == candidate) state->tail = previous; candidate->next = discarded; @@ -261,7 +262,7 @@ static bool poll_event(struct yasl_display *d, uint64_t queue_id, struct yasl_ev } struct event_node *node = *entry; if (!node) { - yasl_mutex_unlock(&state->mutex); + yasl_private_mutex_unlock(&state->mutex); dispose_list(discarded); return false; } @@ -269,104 +270,105 @@ static bool poll_event(struct yasl_display *d, uint64_t queue_id, struct yasl_ev if (state->tail == node) state->tail = previous; node->next = NULL; if (node->event.surface_id) - node->event.surface = yasl_events_find(d, node->event.surface_id); + node->event.surface = yasl_private_events_find(d, node->event.surface_id); struct event_node *last = state->last; state->last = node; *event = node->event; - yasl_mutex_unlock(&state->mutex); + yasl_private_mutex_unlock(&state->mutex); dispose(last); dispose_list(discarded); return true; } -bool yasl_display_poll_event(struct yasl_display *d, struct yasl_event *event) +bool yasl_private_display_poll_event(struct yasl_private_display *d, struct yasl_private_event *event) { return poll_event(d, 0, event); } -bool yasl_events_poll_surface(struct yasl_surface *s, struct yasl_event *event) +bool yasl_private_events_poll_surface(struct yasl_surface *s, struct yasl_private_event *event) { return s && s->id ? poll_event(s->display, s->id, event) : false; } -uint64_t yasl_events_request_id(struct yasl_display *d) +uint64_t yasl_private_events_request_id(struct yasl_private_display *d) { if (!d->events || d->events->request == UINT64_MAX) { errno = EOVERFLOW; return 0; } return ++d->events->request; } -uint64_t yasl_surface_id(const struct yasl_surface *s) { return s ? s->id : 0; } -void yasl_surface_set_userdata(struct yasl_surface *s, void *ud) { if (s) s->userdata = ud; } -void *yasl_surface_userdata(const struct yasl_surface *s) { return s ? s->userdata : NULL; } +uint64_t yasl_private_surface_id(const struct yasl_surface *s) { return s ? s->id : 0; } +void yasl_private_surface_set_userdata(struct yasl_surface *s, void *ud) { if (s) s->userdata = ud; } +void *yasl_private_surface_userdata(const struct yasl_surface *s) { return s ? s->userdata : NULL; } -void yasl_surface_notify_configure(struct yasl_surface *s, struct yasl_geometry geometry) +void yasl_private_surface_notify_configure(struct yasl_surface *s, struct yasl_private_geometry geometry) { uint32_t previous_scale = s->reported_scale; s->reported_scale = geometry.scale_120; s->size = geometry.size; s->scale_120 = geometry.scale_120; - struct yasl_event event = {.type = YASL_EVENT_WINDOW_RESIZE, .surface = s, + struct yasl_private_event event = {.type = YASL_PRIVATE_EVENT_WINDOW_RESIZE, .surface = s, .resize = {.geometry = geometry, .scale_changed = previous_scale != geometry.scale_120}}; - yasl_events_push(s->display, s, &event); - if (s->display) yasl_surface_request_frame(s); + yasl_private_events_push(s->display, s, &event); + if (s->display) yasl_private_surface_request_frame(s); if (s->callbacks.configure) s->callbacks.configure(s, geometry, s->ud); } -void yasl_surface_notify_close(struct yasl_surface *s) +void yasl_private_surface_notify_close(struct yasl_surface *s) { - struct yasl_event event = {.type = YASL_EVENT_WINDOW_CLOSE, .surface = s}; - yasl_events_push(s->display, s, &event); + struct yasl_private_event event = {.type = YASL_PRIVATE_EVENT_WINDOW_CLOSE, .surface = s}; + yasl_private_events_push(s->display, s, &event); if (s->callbacks.close) s->callbacks.close(s, s->ud); } -void yasl_surface_notify_state(struct yasl_surface *s, uint32_t flags) +void yasl_private_surface_notify_state(struct yasl_surface *s, uint32_t flags) { s->state = flags; - struct yasl_event event = {.type = YASL_EVENT_WINDOW_STATE, .surface = s, .state.flags = flags}; - yasl_events_push(s->display, s, &event); + struct yasl_private_event event = {.type = YASL_PRIVATE_EVENT_WINDOW_STATE, .surface = s, .state.flags = flags}; + yasl_private_events_push(s->display, s, &event); if (s->callbacks.state) s->callbacks.state(s, flags, s->ud); } -void yasl_surface_notify_drag(struct yasl_surface *s, enum yasl_drag_event action, struct yasl_vector2i pos) +void yasl_private_surface_notify_drag(struct yasl_surface *s, enum yasl_private_drag_event action, struct yasl_private_vector2i pos) { - struct yasl_event event = {.type = YASL_EVENT_DRAG, .surface = s, .drag = {action, pos}}; - yasl_events_push(s->display, s, &event); + struct yasl_private_event event = {.type = YASL_PRIVATE_EVENT_DRAG, .surface = s, .drag = {action, pos}}; + yasl_private_events_push(s->display, s, &event); if (s->callbacks.drag) s->callbacks.drag(s, action, pos, s->ud); } -void yasl_surface_notify_drop(struct yasl_surface *s, enum yasl_drop_event action, struct yasl_vector2i pos) +void yasl_private_surface_notify_drop(struct yasl_surface *s, enum yasl_private_drop_event action, struct yasl_private_vector2i pos) { - struct yasl_event event = {.type = YASL_EVENT_DROP_HOVER, .surface = s, .drop = {action, pos}}; - yasl_events_push(s->display, s, &event); + struct yasl_private_event event = {.type = YASL_PRIVATE_EVENT_DROP_HOVER, .surface = s, .drop = {action, pos}}; + yasl_private_events_push(s->display, s, &event); if (s->callbacks.drop) s->callbacks.drop(s, action, pos, s->ud); } -void yasl_surface_notify_composition(struct yasl_surface *s, const char *text, int begin, int end) +void yasl_private_surface_notify_composition(struct yasl_surface *s, const char *text, int begin, int end) { - struct yasl_event event = {.type = YASL_EVENT_COMPOSITION, .surface = s, + struct yasl_private_event event = {.type = YASL_PRIVATE_EVENT_COMPOSITION, .surface = s, .composition = {.data = text ? text : "", .len = text ? strlen(text) : 0, - .begin = begin, .end = end, .active = text && text[0]}}; - yasl_events_push(s->display, s, &event); + .begin = begin, .end = end, .active = text && text[0], + .revision = s->text_revision, .generation = s->text_generation}}; + yasl_private_events_push(s->display, s, &event); } -void yasl_surface_complete_paste(struct yasl_surface *s, uint64_t id, - enum yasl_transfer_status status, const char *text, size_t len) +void yasl_private_surface_complete_paste(struct yasl_surface *s, uint64_t id, + enum yasl_private_transfer_status status, const char *text, size_t len) { - struct yasl_event event = {.type = YASL_EVENT_PASTE, .surface = s, + struct yasl_private_event event = {.type = YASL_PRIVATE_EVENT_PASTE, .surface = s, .paste = {.data = text ? text : "", .len = text ? len : 0, .request_id = id, .status = status}}; if (!s->display->events) { struct input_queue *q = s->display->ops->surface_input_queue ? s->display->ops->surface_input_queue(s) : &s->input; input_queue_push(q, &event); - } else yasl_events_push(s->display, s, &event); + } else yasl_private_events_push(s->display, s, &event); } -void yasl_display_notify_event(struct yasl_display *d, enum yasl_event_type type) +void yasl_private_display_notify_event(struct yasl_private_display *d, enum yasl_private_event_type type) { - struct yasl_event event = {.type = type}; - yasl_events_push(d, NULL, &event); - void (*callback)(void *) = type == YASL_EVENT_DISPLAY_QUIT ? d->callbacks.quit - : type == YASL_EVENT_DISPLAY_REOPEN ? d->callbacks.reopen - : type == YASL_EVENT_DISPLAY_LOST ? d->callbacks.lost : NULL; + struct yasl_private_event event = {.type = type}; + yasl_private_events_push(d, NULL, &event); + void (*callback)(void *) = type == YASL_PRIVATE_EVENT_DISPLAY_QUIT ? d->callbacks.quit + : type == YASL_PRIVATE_EVENT_DISPLAY_REOPEN ? d->callbacks.reopen + : type == YASL_PRIVATE_EVENT_DISPLAY_LOST ? d->callbacks.lost : NULL; if (callback) callback(d->callback_ud); } diff --git a/src/common/gl_options.c b/src/common/gl_options.c new file mode 100644 index 0000000..ec8f98a --- /dev/null +++ b/src/common/gl_options.c @@ -0,0 +1,38 @@ +#include "gl_options.h" +#include <errno.h> +#include <stddef.h> +bool yasl_private_gl_parse_options(const int *attrs, struct yasl_private_gl_options *out) +{ + if (!out) { errno = EINVAL; return false; } + struct yasl_private_gl_options o = {.major=3, .minor=2, .profile=YASL_PRIVATE_GL_CORE, + .red_bits=8, .green_bits=8, .blue_bits=8, .alpha_bits=8, .depth_bits=24, .stencil_bits=8}; + if (attrs) for (size_t i=0;;i+=2) { + if (i >= 128) { errno=EINVAL; return false; } + int key=attrs[i]; + if (key==YASL_PRIVATE_GL_END) break; + int v=attrs[i+1]; + switch(key) { + case YASL_PRIVATE_GL_MAJOR: o.major=v; break; + case YASL_PRIVATE_GL_MINOR: o.minor=v; break; + case YASL_PRIVATE_GL_PROFILE: o.profile=v; break; + case YASL_PRIVATE_GL_DEBUG: o.debug=v; break; + case YASL_PRIVATE_GL_FORWARD_COMPAT: o.forward_compat=v; break; + case YASL_PRIVATE_GL_RED_BITS: o.red_bits=v; break; + case YASL_PRIVATE_GL_GREEN_BITS: o.green_bits=v; break; + case YASL_PRIVATE_GL_BLUE_BITS: o.blue_bits=v; break; + case YASL_PRIVATE_GL_ALPHA_BITS: o.alpha_bits=v; break; + case YASL_PRIVATE_GL_DEPTH_BITS: o.depth_bits=v; break; + case YASL_PRIVATE_GL_STENCIL_BITS: o.stencil_bits=v; break; + case YASL_PRIVATE_GL_SAMPLES: o.samples=v; break; + case YASL_PRIVATE_GL_SRGB: o.srgb=v; break; + case YASL_PRIVATE_GL_API: o.api=v; break; + default: errno=EINVAL; return false; + } + } + if (o.major<1 || o.major>4 || o.minor<0 || o.minor>9 || o.profile<0 || o.profile>2 || + o.api<0 || o.api>1 || (unsigned)o.debug>1 || (unsigned)o.forward_compat>1 || + (unsigned)o.srgb>1 || (unsigned)o.red_bits>64 || (unsigned)o.green_bits>64 || + (unsigned)o.blue_bits>64 || (unsigned)o.alpha_bits>64 || (unsigned)o.depth_bits>64 || + (unsigned)o.stencil_bits>64 || (unsigned)o.samples>64) { errno=EINVAL; return false; } + *out=o; return true; +} diff --git a/src/common/gl_options.h b/src/common/gl_options.h new file mode 100644 index 0000000..eae38bb --- /dev/null +++ b/src/common/gl_options.h @@ -0,0 +1,20 @@ +#ifndef YASL_PRIVATE_GL_OPTIONS_H +#define YASL_PRIVATE_GL_OPTIONS_H +#include <stdbool.h> +/* Attribute values are shared with the public GL bridge; not installed. */ +enum { + YASL_PRIVATE_GL_END = 0, YASL_PRIVATE_GL_MAJOR = 1, YASL_PRIVATE_GL_MINOR, + YASL_PRIVATE_GL_PROFILE, YASL_PRIVATE_GL_DEBUG, YASL_PRIVATE_GL_FORWARD_COMPAT, + YASL_PRIVATE_GL_RED_BITS, YASL_PRIVATE_GL_GREEN_BITS, YASL_PRIVATE_GL_BLUE_BITS, + YASL_PRIVATE_GL_ALPHA_BITS, YASL_PRIVATE_GL_DEPTH_BITS, YASL_PRIVATE_GL_STENCIL_BITS, + YASL_PRIVATE_GL_SAMPLES, YASL_PRIVATE_GL_SRGB, YASL_PRIVATE_GL_API +}; +enum { YASL_PRIVATE_GL_ANY = 0, YASL_PRIVATE_GL_CORE = 1, YASL_PRIVATE_GL_COMPATIBILITY = 2 }; +enum { YASL_PRIVATE_GL_OPENGL = 0, YASL_PRIVATE_GL_OPENGL_ES = 1 }; +struct yasl_private_gl_options { + int major, minor, profile, debug, forward_compat; + int red_bits, green_bits, blue_bits, alpha_bits, depth_bits, stencil_bits; + int samples, srgb, api; +}; +bool yasl_private_gl_parse_options(const int *, struct yasl_private_gl_options *); +#endif diff --git a/src/common/input.c b/src/common/input.c index 511cd0a..338ed2d 100644 --- a/src/common/input.c +++ b/src/common/input.c @@ -1,29 +1,29 @@ #include "input.h" -#include "yasl/loop.h" +#include "common/private_loop.h" #include <stdlib.h> #include <string.h> -static bool has_text(const struct yasl_event *ev) +static bool has_text(const struct yasl_private_event *ev) { - return ev->type == YASL_EVENT_TEXT || ev->type == YASL_EVENT_PASTE || - ev->type == YASL_EVENT_DROP; + return ev->type == YASL_PRIVATE_EVENT_TEXT || ev->type == YASL_PRIVATE_EVENT_PASTE || + ev->type == YASL_PRIVATE_EVENT_DROP || ev->type == YASL_PRIVATE_EVENT_TEXT_EDIT; } static void discard_pending(struct input_queue *q) { for (size_t i = 0; i < q->count; i++) { - struct yasl_event *ev = &q->events[(q->head + i) & (q->capacity - 1)]; + struct yasl_private_event *ev = &q->events[(q->head + i) & (q->capacity - 1)]; if (has_text(ev)) free((void *)ev->text.data); } q->head = q->count = 0; } -bool input_queue_push(struct input_queue *q, const struct yasl_event *ev) +bool input_queue_push(struct input_queue *q, const struct yasl_private_event *ev) { - struct yasl_event copy = *ev; + struct yasl_private_event copy = *ev; char *owned = NULL; - if (!copy.time_ns) copy.time_ns = yasl_loop_now_ns(); + if (!copy.time_ns) copy.time_ns = yasl_private_loop_now_ns(); if (has_text(ev) && !q->publish) { size_t len = ev->text.len; if ((!ev->text.data && len) || len == SIZE_MAX) goto failed; @@ -31,42 +31,43 @@ bool input_queue_push(struct input_queue *q, const struct yasl_event *ev) if (!owned) goto failed; if (len) memcpy(owned, ev->text.data, len); owned[len] = '\0'; - if (ev->type == YASL_EVENT_PASTE) copy.paste.data = owned; + if (ev->type == YASL_PRIVATE_EVENT_PASTE) copy.paste.data = owned; + else if (ev->type == YASL_PRIVATE_EVENT_TEXT_EDIT) copy.text_edit.data = owned; else copy.text.data = owned; } uint32_t buttons = q->buttons; switch (ev->type) { - case YASL_EVENT_POINTER_MOTION: + case YASL_PRIVATE_EVENT_POINTER_MOTION: if (!q->relative) { copy.pointer.delta = q->has_last_pos - ? (struct yasl_vector2d){ev->pointer.pos.x - q->last_pos.x, + ? (struct yasl_private_vector2d){ev->pointer.pos.x - q->last_pos.x, ev->pointer.pos.y - q->last_pos.y} - : (struct yasl_vector2d){0, 0}; + : (struct yasl_private_vector2d){0, 0}; } q->last_pos = ev->pointer.pos; q->has_last_pos = true; break; - case YASL_EVENT_POINTER_DOWN: - case YASL_EVENT_POINTER_UP: + case YASL_PRIVATE_EVENT_POINTER_DOWN: + case YASL_PRIVATE_EVENT_POINTER_UP: if ((unsigned)ev->pointer.button < 32) { - uint32_t bit = YASL_BUTTON_BIT(ev->pointer.button); - buttons = ev->type == YASL_EVENT_POINTER_DOWN ? buttons | bit : buttons & ~bit; + uint32_t bit = YASL_PRIVATE_BUTTON_BIT(ev->pointer.button); + buttons = ev->type == YASL_PRIVATE_EVENT_POINTER_DOWN ? buttons | bit : buttons & ~bit; } q->last_pos = ev->pointer.pos; q->has_last_pos = true; break; - case YASL_EVENT_POINTER_LEAVE: + case YASL_PRIVATE_EVENT_POINTER_LEAVE: if (q->leave_ends_press) buttons = 0; q->has_last_pos = false; break; - case YASL_EVENT_INPUT_RESET: + case YASL_PRIVATE_EVENT_INPUT_RESET: buttons = 0; q->has_last_pos = false; break; default: break; } - if (ev->type >= YASL_EVENT_POINTER_DOWN && ev->type <= YASL_EVENT_POINTER_LEAVE) + if (ev->type >= YASL_PRIVATE_EVENT_POINTER_DOWN && ev->type <= YASL_PRIVATE_EVENT_POINTER_LEAVE) copy.pointer.buttons = buttons; if (q->publish) { q->buttons = buttons; @@ -76,7 +77,7 @@ bool input_queue_push(struct input_queue *q, const struct yasl_event *ev) size_t capacity = q->capacity ? q->capacity * 2 : 32; if (capacity < q->capacity || capacity > SIZE_MAX / sizeof(*q->events)) goto failed; - struct yasl_event *events = malloc(capacity * sizeof(*events)); + struct yasl_private_event *events = malloc(capacity * sizeof(*events)); if (!events) goto failed; if (q->events && q->count > 0) { size_t first = q->capacity - q->head; @@ -107,7 +108,7 @@ failed: return false; } -bool input_queue_pop(struct input_queue *q, struct yasl_event *ev) +bool input_queue_pop(struct input_queue *q, struct yasl_private_event *ev) { if (!ev || (!q->count && !q->reset_pending)) return false; if (q->last_text) { @@ -115,9 +116,9 @@ bool input_queue_pop(struct input_queue *q, struct yasl_event *ev) q->last_text = NULL; } if (q->reset_pending) { - *ev = (struct yasl_event){ - .type = YASL_EVENT_INPUT_RESET, .surface = q->reset_surface, - .time_ns = yasl_loop_now_ns(), + *ev = (struct yasl_private_event){ + .type = YASL_PRIVATE_EVENT_INPUT_RESET, .surface = q->reset_surface, + .time_ns = yasl_private_loop_now_ns(), }; q->reset_pending = false; return true; @@ -132,7 +133,7 @@ bool input_queue_pop(struct input_queue *q, struct yasl_event *ev) void input_queue_clear(struct input_queue *q) { - bool (*publish)(const struct yasl_event *, void *) = q->publish; + bool (*publish)(const struct yasl_private_event *, void *) = q->publish; void *publish_ud = q->publish_ud; discard_pending(q); free(q->last_text); diff --git a/src/common/input.h b/src/common/input.h index 8980c55..d97ad17 100644 --- a/src/common/input.h +++ b/src/common/input.h @@ -1,27 +1,27 @@ -#ifndef YASL_INPUT_H -#define YASL_INPUT_H +#ifndef YASL_PRIVATE_INPUT_H +#define YASL_PRIVATE_INPUT_H -#include "yasl/yasl.h" +#include "common/types.h" /* Each queued string and the last successfully polled string are owned here. */ struct input_queue { - struct yasl_event *events; + struct yasl_private_event *events; size_t head, count, capacity; char *last_text; /* Last pointer position, for computing absolute-mode motion deltas. */ - struct yasl_vector2i last_pos; + struct yasl_private_vector2i last_pos; bool has_last_pos; - /* Set by backends while YASL_POINTER_RELATIVE is active: keep pushed deltas. */ + /* Set by backends while YASL_PRIVATE_POINTER_RELATIVE is active: keep pushed deltas. */ bool relative; struct yasl_surface *reset_surface; bool reset_pending; - /* YASL_BUTTON_BIT() mask from pushed DOWN/UP events. */ + /* YASL_PRIVATE_BUTTON_BIT() mask from pushed DOWN/UP events. */ uint32_t buttons; /* Set by backends whose pointer leave ends any press: no release follows. */ bool leave_ends_press; /* Installed by common code for display-wide publication. Standalone queues * retain their normal storage/pop semantics when publish is NULL. */ - bool (*publish)(const struct yasl_event *ev, void *ud); + bool (*publish)(const struct yasl_private_event *ev, void *ud); void *publish_ud; }; @@ -29,8 +29,8 @@ struct input_queue { * POINTER_MOTION delta is computed from the previous position; otherwise the * backend-supplied delta is kept. Leave and reset clear the position history. * Pointer events get the held-button mask; reset clears it. */ -bool input_queue_push(struct input_queue *q, const struct yasl_event *ev); -bool input_queue_pop(struct input_queue *q, struct yasl_event *ev); +bool input_queue_push(struct input_queue *q, const struct yasl_private_event *ev); +bool input_queue_pop(struct input_queue *q, struct yasl_private_event *ev); void input_queue_clear(struct input_queue *q); #endif diff --git a/src/common/keyboard.h b/src/common/keyboard.h index d8b8bbc..ce144fb 100644 --- a/src/common/keyboard.h +++ b/src/common/keyboard.h @@ -1,10 +1,10 @@ -#ifndef YASL_COMMON_KEYBOARD_H -#define YASL_COMMON_KEYBOARD_H +#ifndef YASL_PRIVATE_COMMON_KEYBOARD_H +#define YASL_PRIVATE_COMMON_KEYBOARD_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) +static inline uint32_t yasl_private_key_label(uint32_t cp) { return cp >= 0x20 && cp != 0x7f && cp <= 0x10ffff && !(cp >= 0x80 && cp <= 0x9f) && !(cp >= 0xd800 && cp <= 0xdfff) && diff --git a/src/common/loop.h b/src/common/loop.h index 60c5b64..3a1c542 100644 --- a/src/common/loop.h +++ b/src/common/loop.h @@ -1,15 +1,15 @@ -#ifndef YASL_COMMON_LOOP_H -#define YASL_COMMON_LOOP_H +#ifndef YASL_PRIVATE_COMMON_LOOP_H +#define YASL_PRIVATE_COMMON_LOOP_H -#include "yasl/loop.h" +#include "common/private_loop.h" struct pollfd; -int yasl_loop_next_timeout_ms(const struct yasl_loop *l); +int yasl_private_loop_next_timeout_ms(const struct yasl_private_loop *l); /* The loop's current poll set, valid until the next watch change. Backends * that wait on native events use it to wake on loop readiness. */ -int yasl_loop_poll_set(const struct yasl_loop *l, const struct pollfd **fds); +int yasl_private_loop_poll_set(const struct yasl_private_loop *l, const struct pollfd **fds); /* Whether id is armed: added, not deleted, and not retired by its callback. */ -bool yasl_loop_timer_armed(const struct yasl_loop *l, yasl_timer_id id); +bool yasl_private_loop_timer_armed(const struct yasl_private_loop *l, yasl_private_timer_id id); -#endif /* YASL_COMMON_LOOP_H */ +#endif /* YASL_PRIVATE_COMMON_LOOP_H */ diff --git a/src/common/meson.build b/src/common/meson.build index 1577132..819e4a8 100644 --- a/src/common/meson.build +++ b/src/common/meson.build @@ -1 +1,5 @@ -sources += files('app.c', 'backend.c', 'desktop.c', 'events.c', 'input.c', 'native.c', 'timing.c', 'uri.c', 'utf8.c') +sources += files('backend.c', 'desktop.c', 'events.c', 'input.c', 'native.c', 'timing.c', 'uri.c', 'utf8.c', 'surface.c', 'gl_options.c') +if has_vulkan + sources += files('vulkan.c') + deps += vulkan_dep +endif diff --git a/src/common/native.c b/src/common/native.c index 42b065c..88604c9 100644 --- a/src/common/native.c +++ b/src/common/native.c @@ -2,7 +2,7 @@ #include <errno.h> -void *yasl_display_get_native_handle(struct yasl_display *d) +void *yasl_private_display_get_native_handle(struct yasl_private_display *d) { if (!d) return NULL; if (d->native_handle) return d->native_handle; @@ -10,7 +10,7 @@ void *yasl_display_get_native_handle(struct yasl_display *d) d->ops->display_get_native_handle(d) : NULL; } -void *yasl_surface_get_native_handle(struct yasl_surface *s) +void *yasl_private_surface_get_native_handle(struct yasl_surface *s) { if (!s) return NULL; if (s->native_handle) return s->native_handle; @@ -18,18 +18,18 @@ void *yasl_surface_get_native_handle(struct yasl_surface *s) s->display->ops->surface_get_native_handle(s) : NULL; } -struct yasl_surface *yasl_surface_create_native(struct yasl_display *d, - const struct yasl_window_spec *spec, void *graphics_device) +struct yasl_surface *yasl_private_surface_create_native(struct yasl_private_display *d, + const struct yasl_private_window_spec *spec, void *graphics_device) { if (!graphics_device) { errno = EINVAL; return NULL; } - return yasl_create_toplevel(d, spec, graphics_device); + return yasl_private_create_toplevel(d, spec, graphics_device); } -bool yasl_surface_get_native_frame(struct yasl_surface *s, - const struct yasl_framebuffer *target, struct yasl_native_frame *out) +bool yasl_private_surface_get_native_frame(struct yasl_surface *s, + const struct yasl_private_framebuffer *target, struct yasl_private_native_frame *out) { if (!s || !target || !out || !target->generation || target->generation != s->frame_generation || @@ -37,3 +37,39 @@ bool yasl_surface_get_native_frame(struct yasl_surface *s, return false; return s->display->ops->surface_get_native_frame(s, target, out); } + +#include "public_internal.h" +#include "yasl/metal.h" +#if YASL_HAS_WAYLAND +#include "yasl/native/wayland.h" +struct wl_display *yasl_wayland_display(const yasl_surface *s) +{ return s && s->display->ops->type==YASL_PRIVATE_BACKEND_WAYLAND ? yasl_private_display_get_native_handle(s->display):NULL; } +struct wl_surface *yasl_wayland_surface(const yasl_surface *s) +{ return s && s->display->ops->type==YASL_PRIVATE_BACKEND_WAYLAND ? yasl_private_surface_get_native_handle((yasl_surface*)s):NULL; } +#endif +#if YASL_HAS_X11 +#include "yasl/native/x11.h" +Display *yasl_x11_display(const yasl_surface *s) +{ return s && s->display->ops->type==YASL_PRIVATE_BACKEND_X11 ? yasl_private_display_get_native_handle(s->display):NULL; } +uintptr_t yasl_x11_window(const yasl_surface *s) +{ return s && s->display->ops->type==YASL_PRIVATE_BACKEND_X11 ? (uintptr_t)yasl_private_surface_get_native_handle((yasl_surface*)s):0; } +#endif +#if YASL_HAS_COCOA +#include "yasl/native/cocoa.h" +#include "common/private_native_cocoa.h" +NSApplication *yasl_cocoa_application(void) +{ struct yasl_private_display *d=yasl_public_display();return d ? yasl_private_cocoa_display_get_application(d):NULL; } +NSWindow *yasl_cocoa_window(const yasl_surface *s) +{ return yasl_private_cocoa_surface_get_window((yasl_surface*)s); } +NSView *yasl_cocoa_view(const yasl_surface *s) +{ return yasl_private_cocoa_surface_get_view((yasl_surface*)s); } +#endif +CAMetalLayer *yasl_metal_layer(yasl_surface *s) +{ + if(!yasl_public_surface(s,false))return NULL; + if(s->presenter!=YASL_PRIVATE_PRESENTER_NONE && s->presenter!=YASL_PRIVATE_PRESENTER_METAL) {yasl_public_fail(EBUSY);return NULL;} + if(!s->display->ops->surface_get_metal_layer){yasl_public_fail(ENOTSUP);return NULL;} + CAMetalLayer *layer=s->display->ops->surface_get_metal_layer(s); + if(!layer){yasl_public_fail(ENOTSUP);return NULL;} + s->presenter=YASL_PRIVATE_PRESENTER_METAL;return layer; +} diff --git a/src/common/private_desktop.h b/src/common/private_desktop.h new file mode 100644 index 0000000..b408585 --- /dev/null +++ b/src/common/private_desktop.h @@ -0,0 +1,113 @@ +/** + * @file + * Desktop services next to windowing: opening URIs, notifications, the bell, + * appearance preferences, and process paths. + * + * Display and surface functions use the display's opening thread (the main + * thread on Cocoa). Path and locale functions may be called from any thread. + * On Linux, notifications and appearance use the D-Bus session bus when YASL + * is built with libdbus-1; without it they report unsupported. + * + * Functions that fill a buffer return the full string length in bytes, + * excluding the NUL, or zero on failure. A result less than cap means out + * holds the NUL-terminated string. Otherwise out is unchanged; call again with + * a buffer of at least the result plus one. + */ +#ifndef YASL_PRIVATE_DESKTOP_H +#define YASL_PRIVATE_DESKTOP_H + +#include "common/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Appearance preference flags returned by yasl_private_display_appearance(). */ +enum yasl_private_appearance : uint32_t { + YASL_PRIVATE_APPEARANCE_DARK = 1 << 0, /**< The user prefers dark colors. */ + YASL_PRIVATE_APPEARANCE_LIGHT = 1 << 1, /**< The user prefers light colors. */ + YASL_PRIVATE_APPEARANCE_HIGH_CONTRAST = 1 << 2, /**< The user asks for higher contrast. */ +}; + +/** + * Open a URI with the user's default handler. + * @param d Display, or NULL. + * @param uri NUL-terminated absolute URI with a scheme, such as https: or file:. + * @return true if the request was handed to the system; false for NULL + * arguments, a missing scheme, or a failed launch. + * YASL checks only the URI syntax. The application decides which schemes and + * hosts it trusts. Success does not confirm that a handler opened the URI. + * Linux runs xdg-open from PATH in a new session, with default signal + * dispositions, an empty signal mask, stdin on /dev/null, and no descriptors + * above stderr. It forks a short-lived helper first, which raises SIGCHLD; + * YASL reaps it unless the application's reaper does. Cocoa uses NSWorkspace. + */ +bool yasl_private_display_open_uri(struct yasl_private_display *d, const char *uri); +/** + * Show a desktop notification. + * @param d Display, or NULL. + * @param title NUL-terminated UTF-8 summary. + * @param body NUL-terminated UTF-8 body, or NULL for none. + * @return true if the request was sent; false for NULL arguments or when no + * notification service is available. + * Linux sends org.freedesktop.Notifications.Notify with the display's app_id + * as the application name and desktop-entry hint. Cocoa uses the user + * notification center, which needs an app bundle with a bundle identifier, + * and asks for permission on first use. Success does not confirm that the + * notification was shown. + */ +bool yasl_private_display_notify(struct yasl_private_display *d, const char *title, const char *body); +/** + * Read the user's appearance preferences. + * @param d Display, or NULL. + * @return A yasl_private_appearance bitmask, or zero for NULL, no preference, or no + * way to read it. + * Linux reads color-scheme and contrast from the XDG Settings portal, which + * waits briefly for a reply, then falls back to the GTK_THEME variable. Cocoa + * reads the effective appearance and the increase-contrast setting. The + * result is not cached and no change notification is sent; query again when + * the window is focused. + */ +uint32_t yasl_private_display_appearance(struct yasl_private_display *d); +/** + * Ring the system bell for a surface. + * @param s Surface, or NULL. + * @return true if the request was sent; false for NULL or when unsupported. + * The system decides whether the bell is audible, visual, or muted. Wayland + * needs the xdg-system-bell protocol; X11 uses the XKB bell for the window + * and Cocoa uses NSBeep. + */ +bool yasl_private_surface_bell(struct yasl_surface *s); +/** + * Get the absolute path of the running executable, with symbolic links resolved. + * @param out Output buffer, or NULL when cap is zero. + * @param cap Size of out in bytes. + * @return Path length, or zero on failure. See the buffer rule in this file. + */ +size_t yasl_private_executable_path(char *out, size_t cap); +/** + * Get the resource directory of the app bundle that contains the executable. + * @param out Output buffer, or NULL when cap is zero. + * @param cap Size of out in bytes. + * @return Path length, or zero when the executable is not in an app bundle. + * Only Cocoa has app bundles; other platforms return zero. + */ +size_t yasl_private_resource_dir(char *out, size_t cap); +/** + * Get the user's preferred locale as a POSIX locale name, such as "en_US.UTF-8". + * @param out Output buffer, or NULL when cap is zero. + * @param cap Size of out in bytes. + * @return Name length, or zero on failure. + * Linux returns the first nonempty LC_ALL, LC_CTYPE, or LANG, and "C.UTF-8" + * when none is set. Cocoa builds the name from the current locale's language + * and region with a ".UTF-8" codeset, because apps started from Finder have no + * locale variables. Use the result to set LANG for child processes; YASL does + * not change the process locale. + */ +size_t yasl_private_default_locale(char *out, size_t cap); + +#ifdef __cplusplus +} +#endif + +#endif /* YASL_PRIVATE_DESKTOP_H */ diff --git a/include/yasl/loop.h b/src/common/private_loop.h index a8e9974..432bbc3 100644 --- a/include/yasl/loop.h +++ b/src/common/private_loop.h @@ -3,17 +3,17 @@ * Optional event loop, file watches, and timers. * * Use the loop's owning thread for all calls and callbacks, except - * yasl_loop_now_ns(), yasl_loop_kick(), and yasl_loop_post(), which any thread + * yasl_private_loop_now_ns(), yasl_private_loop_kick(), and yasl_private_loop_post(), which any thread * may call. A display borrows a supplied loop. For window applications, - * drive yasl_display_dispatch(), drain input, then yasl_display_flush(); the + * drive yasl_private_display_dispatch(), drain input, then yasl_private_display_flush(); the * standalone loop_run() function does not replace this cycle. Timer intervals are * nanoseconds; timeout_ms arguments are milliseconds. Do not recursively step/run * or destroy a loop from a callback. */ -#ifndef YASL_LOOP_H -#define YASL_LOOP_H +#ifndef YASL_PRIVATE_LOOP_H +#define YASL_PRIVATE_LOOP_H -#include "yasl/yasl.h" +#include "common/types.h" #ifdef __cplusplus extern "C" { @@ -24,11 +24,11 @@ extern "C" { * Calls and callbacks use the loop's owning thread. */ /** * Create an owned standalone file-descriptor and timer loop. - * @return A loop to destroy with yasl_loop_destroy(), or NULL on allocation failure. - * Use on one owning thread. Passing it to yasl_display_open() only lends it to + * @return A loop to destroy with yasl_private_loop_destroy(), or NULL on allocation failure. + * Use on one owning thread. Passing it to yasl_private_display_open() only lends it to * the display; the caller retains ownership. */ -YASL_API struct yasl_loop *yasl_loop_create(void); +struct yasl_private_loop *yasl_private_loop_create(void); /** * Release a loop, its watches, its timers, and its signal watch. * @param l Loop, or NULL for no action. @@ -36,15 +36,15 @@ YASL_API struct yasl_loop *yasl_loop_create(void); * or an active step/run. Watched file descriptors are not closed. Posted * functions that have not run are dropped; no thread may still post or kick. */ -YASL_API void yasl_loop_destroy(struct yasl_loop *l); +void yasl_private_loop_destroy(struct yasl_private_loop *l); /** - * Step a standalone loop until yasl_loop_quit() is called. + * Step a standalone loop until yasl_private_loop_quit() is called. * @param l Loop. * @return Zero after quit, or -1 on failure. * For a window application, use a dispatch, input-poll, flush cycle instead; * loop_run alone does not perform that full cycle on every backend. */ -YASL_API int yasl_loop_run(struct yasl_loop *l); +int yasl_private_loop_run(struct yasl_private_loop *l); /** * Wait for file-descriptor readiness or a timer deadline and run callbacks. * @param l Loop. @@ -53,24 +53,24 @@ YASL_API int yasl_loop_run(struct yasl_loop *l); * Timer deadlines shorten the wait. Callbacks can add/delete sources, including * themselves. Do not recursively step/run this loop. */ -YASL_API int yasl_loop_step(struct yasl_loop *l, int timeout_ms); +int yasl_private_loop_step(struct yasl_private_loop *l, int timeout_ms); /** - * Ask the currently running yasl_loop_run() to return after this step. + * Ask the currently running yasl_private_loop_run() to return after this step. * @param l Loop, or NULL for no action. * Call on the owning thread, usually from a callback. */ -YASL_API void yasl_loop_quit(struct yasl_loop *l); +void yasl_private_loop_quit(struct yasl_private_loop *l); /** * Wake a loop that is waiting, without running any callback. * @param l Loop, or NULL for no action. * May be called from any thread and from a signal handler. The loop must stay - * alive until the call returns. A waiting yasl_loop_step(), yasl_loop_run(), or - * yasl_display_dispatch() on this loop returns early; a loop that is not + * alive until the call returns. A waiting yasl_private_loop_step(), yasl_private_loop_run(), or + * yasl_private_display_dispatch() on this loop returns early; a loop that is not * waiting returns early from its next wait instead. Kicks coalesce. This does - * not schedule a redraw or enqueue an event; use yasl_loop_post() to run code + * not schedule a redraw or enqueue an event; use yasl_private_loop_post() to run code * on the owning thread. */ -YASL_API void yasl_loop_kick(struct yasl_loop *l); +void yasl_private_loop_kick(struct yasl_private_loop *l); /** * Run a function on the loop's owning thread. * @param l Loop. @@ -79,13 +79,13 @@ YASL_API void yasl_loop_kick(struct yasl_loop *l); * @return true if queued; false for NULL arguments, a loop without a wake * pipe, or allocation failure. * May be called from any thread, but not from a signal handler. Wakes the loop - * like yasl_loop_kick(). Functions run in posting order, during a - * yasl_loop_step() or display dispatch on the owning thread. They may post + * like yasl_private_loop_kick(). Functions run in posting order, during a + * yasl_private_loop_step() or display dispatch on the owning thread. They may post * again; a function posted while posts are running waits for a later step. - * Stop posting before yasl_loop_destroy(), which drops functions that have + * Stop posting before yasl_private_loop_destroy(), which drops functions that have * not run. */ -YASL_API bool yasl_loop_post(struct yasl_loop *l, void (*fn)(void *ud), void *ud); +bool yasl_private_loop_post(struct yasl_private_loop *l, void (*fn)(void *ud), void *ud); /** * Deliver POSIX signals to a callback on the loop's owning thread. * @param l Loop. @@ -104,30 +104,30 @@ YASL_API bool yasl_loop_post(struct yasl_loop *l, void (*fn)(void *ud), void *ud * signals reset to their default action across exec, so spawned programs * do not inherit the handler. Repeated deliveries of one signal before the * callback runs coalesce into one callback. SIGCHLD reports stopped and - * continued children as well as exits. Callbacks run during yasl_loop_step() + * continued children as well as exits. Callbacks run during yasl_private_loop_step() * or display dispatch, and may remove the watch. */ -YASL_API bool yasl_loop_watch_signals(struct yasl_loop *l, const int *signals, int n, +bool yasl_private_loop_watch_signals(struct yasl_private_loop *l, const int *signals, int n, void (*cb)(void *ud, int signo), void *ud); /** * Stop watching signals and restore their previous actions. * @param l Loop, or NULL for no action. * Does nothing if the loop watches no signals. Signals that arrived but have - * not reached the callback are dropped. yasl_loop_destroy() also unwatches. + * not reached the callback are dropped. yasl_private_loop_destroy() also unwatches. */ -YASL_API void yasl_loop_unwatch_signals(struct yasl_loop *l); +void yasl_private_loop_unwatch_signals(struct yasl_private_loop *l); /** * Read the monotonic clock used by YASL timers and timing results. * @return Nanoseconds from CLOCK_MONOTONIC, unrelated to wall-clock time. * May be called from any thread. */ -YASL_API uint64_t yasl_loop_now_ns(void); +uint64_t yasl_private_loop_now_ns(void); /** * Get the loop used for a display's file watches and application timers. * @param d Display, or NULL. * @return Borrowed loop, or NULL. Do not destroy a display-owned loop yourself. */ -YASL_API struct yasl_loop *yasl_display_loop(struct yasl_display *d); +struct yasl_private_loop *yasl_private_display_loop(struct yasl_private_display *d); /** * Register a borrowed file descriptor for poll-style readiness. * @param l Loop. @@ -139,15 +139,15 @@ YASL_API struct yasl_loop *yasl_display_loop(struct yasl_display *d); * Delete the watch before closing fd. The callback must consume readiness or * change/remove its watch to avoid repeated immediate wakeups. */ -YASL_API int yasl_loop_add_fd(struct yasl_loop *l, int fd, short events, +int yasl_private_loop_add_fd(struct yasl_private_loop *l, int fd, short events, int (*fn)(int fd, short revents, void *ud), void *ud); /** * Remove a file watch without closing its descriptor. * @param l Loop. - * @param id Watch ID returned by yasl_loop_add_fd(). + * @param id Watch ID returned by yasl_private_loop_add_fd(). * @return Zero if removed, or -1 for an invalid/missing watch. */ -YASL_API int yasl_loop_del_fd(struct yasl_loop *l, int id); +int yasl_private_loop_del_fd(struct yasl_private_loop *l, int id); /** * Replace a watch's poll event mask. * @param l Loop. @@ -155,19 +155,19 @@ YASL_API int yasl_loop_del_fd(struct yasl_loop *l, int id); * @param events New <poll.h> mask; zero disables requested readiness (errors may still arrive). * @return Zero if updated, or -1 for an invalid/missing watch. */ -YASL_API int yasl_loop_set_fd_events(struct yasl_loop *l, int id, short events); +int yasl_private_loop_set_fd_events(struct yasl_private_loop *l, int id, short events); /* Timers use nanoseconds. Return the next interval to repeat/reschedule, or zero * to remove the timer. A zero ID indicates failure. Loop timers live until they * stop, are deleted, or the loop is destroyed. Surface timers are also canceled * when their surface is destroyed. */ /** Timer identity scoped to its loop. Zero is invalid; do not reuse a retired ID. */ -typedef uint32_t yasl_timer_id; +typedef uint32_t yasl_private_timer_id; /** Timer callback. interval_ns is the last scheduled delay; return the next delay or zero to stop. */ -typedef uint64_t (*yasl_timer_cb)(void *ud, yasl_timer_id id, uint64_t interval_ns); +typedef uint64_t (*yasl_private_timer_cb)(void *ud, yasl_private_timer_id id, uint64_t interval_ns); /** Convert a nonnegative integer millisecond value to nanoseconds; avoid overflow. */ -#define YASL_MS_TO_NS(ms) ((uint64_t)(ms) * 1000000ull) +#define YASL_PRIVATE_MS_TO_NS(ms) ((uint64_t)(ms) * 1000000ull) /** * Schedule a timer on a loop. @@ -179,36 +179,36 @@ typedef uint64_t (*yasl_timer_cb)(void *ud, yasl_timer_id id, uint64_t interval_ * Repeating timers coalesce missed deadlines; do not use them to count elapsed * wall time. The callback receives the last scheduled interval, not elapsed time. */ -YASL_API yasl_timer_id yasl_loop_add_timer(struct yasl_loop *l, uint64_t interval_ns, - yasl_timer_cb cb, void *ud); +yasl_private_timer_id yasl_private_loop_add_timer(struct yasl_private_loop *l, uint64_t interval_ns, + yasl_private_timer_cb cb, void *ud); /** * Cancel a loop timer, including from its own callback. * @param l Loop. * @param id Timer ID. * @return true if an armed timer was removed, false if invalid or already retired. */ -YASL_API bool yasl_loop_del_timer(struct yasl_loop *l, yasl_timer_id id); +bool yasl_private_loop_del_timer(struct yasl_private_loop *l, yasl_private_timer_id id); /** * Schedule a timer canceled automatically on surface destruction. * @param s Surface whose display loop will run the timer. * @param interval_ns Nonzero initial delay in nanoseconds. - * @param cb Required timer callback, with the same rules as yasl_loop_add_timer(). + * @param cb Required timer callback, with the same rules as yasl_private_loop_add_timer(). * @param ud Borrowed userdata until the timer ends or the surface is destroyed. * @return Nonzero timer ID, or zero on failure. - * The surface does not own userdata. Cancel with yasl_surface_del_timer(). + * The surface does not own userdata. Cancel with yasl_private_surface_del_timer(). */ -YASL_API yasl_timer_id yasl_surface_add_timer(struct yasl_surface *s, uint64_t interval_ns, - yasl_timer_cb cb, void *ud); +yasl_private_timer_id yasl_private_surface_add_timer(struct yasl_surface *s, uint64_t interval_ns, + yasl_private_timer_cb cb, void *ud); /** - * Cancel a timer registered through yasl_surface_add_timer(). + * Cancel a timer registered through yasl_private_surface_add_timer(). * @param s Owning surface. * @param id Timer ID. * @return true if still armed and removed; false if unknown or already retired. */ -YASL_API bool yasl_surface_del_timer(struct yasl_surface *s, yasl_timer_id id); +bool yasl_private_surface_del_timer(struct yasl_surface *s, yasl_private_timer_id id); #ifdef __cplusplus } #endif -#endif /* YASL_LOOP_H */ +#endif /* YASL_PRIVATE_LOOP_H */ diff --git a/include/yasl/native.h b/src/common/private_native.h index 64bc161..d3d9aae 100644 --- a/include/yasl/native.h +++ b/src/common/private_native.h @@ -7,17 +7,17 @@ * backend accessors live in the yasl/native headers and are installed only for built * backends. Application-managed drawing currently supports Cocoa/Metal only. */ -#ifndef YASL_NATIVE_H -#define YASL_NATIVE_H +#ifndef YASL_PRIVATE_NATIVE_H +#define YASL_PRIVATE_NATIVE_H -#include "yasl/yasl.h" +#include "common/types.h" #ifdef __cplusplus extern "C" { #endif /* Optional platform interoperability. All handles are borrowed and expire on - * object destruction. Check yasl_display_backend() before casting: + * object destruction. Check yasl_private_display_backend() before casting: * * display surface * Wayland struct wl_display * struct wl_surface * @@ -27,11 +27,11 @@ extern "C" { /** * Get a borrowed, backend-specific display handle. * @param d Display, or NULL. - * @return Native handle, or NULL. Inspect yasl_display_backend() before casting. + * @return Native handle, or NULL. Inspect yasl_private_display_backend() before casting. * Prefer a typed accessor from yasl/native/wayland.h, x11.h, or cocoa.h. * The handle expires on display close; do not close or dispatch it independently. */ -YASL_API void *yasl_display_get_native_handle(struct yasl_display *d); +void *yasl_private_display_get_native_handle(struct yasl_private_display *d); /** * Get a borrowed, backend-specific surface handle. * @param s Surface, or NULL. @@ -39,28 +39,28 @@ YASL_API void *yasl_display_get_native_handle(struct yasl_display *d); * Prefer typed accessors. The handle expires on surface destruction. Do not * change native ownership, event masks, buffer attachment, or surface roles. */ -YASL_API void *yasl_surface_get_native_handle(struct yasl_surface *s); +void *yasl_private_surface_get_native_handle(struct yasl_surface *s); /* Create a surface for application-managed native rendering. graphics_device * must be non-NULL; currently an id<MTLDevice> on Cocoa, retained by the surface. * Other backends return NULL with errno=ENOTSUP. CPU rendering uses the ordinary - * yasl_surface_create_toplevel() and requires no native device. */ + * yasl_private_surface_create_toplevel() and requires no native device. */ /** * Create a toplevel for application-managed native rendering. - * @param d Display with YASL_CAP_NATIVE_GRAPHICS. + * @param d Display with YASL_PRIVATE_CAP_NATIVE_GRAPHICS. * @param spec Copied window options, with logical-pixel size and optional callbacks. * @param graphics_device Required native graphics device; currently id<MTLDevice> on Cocoa. * @return Owned surface, or NULL with errno set: EINVAL for invalid arguments, * ENOTSUP for an unsupported backend, or a backend error. * Cocoa retains the device. Query the drawable during draw, submit/present it, - * then call yasl_surface_present() to record submission. - * Callback ownership and timing follow yasl_surface_create_toplevel(). + * then call yasl_private_surface_present() to record submission. + * Callback ownership and timing follow yasl_private_surface_create_toplevel(). */ -YASL_API struct yasl_surface *yasl_surface_create_native(struct yasl_display *d, - const struct yasl_window_spec *spec, void *graphics_device); +struct yasl_surface *yasl_private_surface_create_native(struct yasl_private_display *d, + const struct yasl_private_window_spec *spec, void *graphics_device); /** Borrowed native drawable for the current draw target, currently Cocoa/Metal. */ -struct yasl_native_frame { +struct yasl_private_native_frame { /** Borrowed id<CAMetalDrawable> on Cocoa. */ void *drawable; /** Cocoa CAMetalLayer presentsWithTransaction setting for this frame. */ @@ -70,7 +70,7 @@ struct yasl_native_frame { /* Query the current draw target's native drawable. Returns false for unsupported * backends, stale targets, or outside the draw callback. The result expires on * successful present, callback return, or surface destruction. Native renderers - * submit/present the drawable, then call yasl_surface_present() to record it. */ + * submit/present the drawable, then call yasl_private_surface_present() to record it. */ /** * Get the current draw target's native drawable. * @param s Surface currently being drawn. @@ -80,11 +80,11 @@ struct yasl_native_frame { * stale targets, or calls outside draw. The drawable is borrowed and expires on * successful present, callback return, or surface destruction. Currently Cocoa only. */ -YASL_API bool yasl_surface_get_native_frame(struct yasl_surface *s, - const struct yasl_framebuffer *target, struct yasl_native_frame *out); +bool yasl_private_surface_get_native_frame(struct yasl_surface *s, + const struct yasl_private_framebuffer *target, struct yasl_private_native_frame *out); #ifdef __cplusplus } #endif -#endif /* YASL_NATIVE_H */ +#endif /* YASL_PRIVATE_NATIVE_H */ diff --git a/src/common/private_native_cocoa.h b/src/common/private_native_cocoa.h new file mode 100644 index 0000000..2eecd6e --- /dev/null +++ b/src/common/private_native_cocoa.h @@ -0,0 +1,55 @@ +/** + * @file + * Typed borrowed Cocoa handles; available on macOS. Use on the main thread. + */ +#ifndef YASL_PRIVATE_NATIVE_COCOA_H +#define YASL_PRIVATE_NATIVE_COCOA_H + +#include "common/types.h" + +#ifdef __OBJC__ +@class NSApplication; +@class NSWindow; +@class NSView; +#else +typedef struct objc_object NSApplication; /**< Opaque Cocoa application in plain C/C++. */ +typedef struct objc_object NSWindow; /**< Opaque Cocoa window in plain C/C++. */ +typedef struct objc_object NSView; /**< Opaque Cocoa view in plain C/C++. */ +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Return borrowed Cocoa handles (expire on display/surface destruction). + * Return NULL / nil if display/surface is not a Cocoa object or NULL. */ +/** + * Get the borrowed NSApplication for an object of the matching backend. + * @param d Live display, or NULL. + * @return The handle, or zero/NULL for NULL or a different backend. + * Expires on display destruction. Available only when this backend is built. + * Never pass a pointer to a destroyed object. + */ +NSApplication *yasl_private_cocoa_display_get_application(struct yasl_private_display *d); +/** + * Get the borrowed NSWindow for an object of the matching backend. + * @param s Live surface, or NULL. + * @return The handle, or zero/NULL for NULL or a different backend. + * Expires on surface destruction. Available only when this backend is built. + * Never pass a pointer to a destroyed object. + */ +NSWindow *yasl_private_cocoa_surface_get_window(struct yasl_surface *s); +/** + * Get the borrowed NSView for an object of the matching backend. + * @param s Live surface, or NULL. + * @return The handle, or zero/NULL for NULL or a different backend. + * Expires on surface destruction. Available only when this backend is built. + * Never pass a pointer to a destroyed object. + */ +NSView *yasl_private_cocoa_surface_get_view(struct yasl_surface *s); + +#ifdef __cplusplus +} +#endif + +#endif /* YASL_PRIVATE_NATIVE_COCOA_H */ diff --git a/src/common/private_native_wayland.h b/src/common/private_native_wayland.h new file mode 100644 index 0000000..5033f7f --- /dev/null +++ b/src/common/private_native_wayland.h @@ -0,0 +1,40 @@ +/** + * @file + * Typed borrowed Wayland handles; available when built with Wayland support. + */ +#ifndef YASL_PRIVATE_NATIVE_WAYLAND_H +#define YASL_PRIVATE_NATIVE_WAYLAND_H + +#include "common/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wl_display; +struct wl_surface; + +/* Return borrowed Wayland handles (expire on display/surface destruction). + * Return NULL if display/surface is not a Wayland object or NULL. */ +/** + * Get the borrowed Wayland connection for an object of the matching backend. + * @param d Live display, or NULL. + * @return The handle, or zero/NULL for NULL or a different backend. + * Expires on display destruction. Available only when this backend is built. + * Never pass a pointer to a destroyed object. + */ +struct wl_display *yasl_private_wayland_display_get_wl_display(struct yasl_private_display *d); +/** + * Get the borrowed Wayland surface for an object of the matching backend. + * @param s Live surface, or NULL. + * @return The handle, or zero/NULL for NULL or a different backend. + * Expires on surface destruction. Available only when this backend is built. + * Never pass a pointer to a destroyed object. + */ +struct wl_surface *yasl_private_wayland_surface_get_wl_surface(struct yasl_surface *s); + +#ifdef __cplusplus +} +#endif + +#endif /* YASL_PRIVATE_NATIVE_WAYLAND_H */ diff --git a/src/common/private_native_x11.h b/src/common/private_native_x11.h new file mode 100644 index 0000000..d1930bd --- /dev/null +++ b/src/common/private_native_x11.h @@ -0,0 +1,40 @@ +/** + * @file + * Typed borrowed X11 handles; available when built with X11 support. + */ +#ifndef YASL_PRIVATE_NATIVE_X11_H +#define YASL_PRIVATE_NATIVE_X11_H + +#include "common/types.h" +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _XDisplay Display; /**< Opaque Xlib connection, compatible with Xlib.h. */ + +/* Return borrowed X11 handles (expire on display/surface destruction). + * Return NULL / 0 if display/surface is not an X11 object or NULL. */ +/** + * Get the borrowed X11 Display for an object of the matching backend. + * @param d Live display, or NULL. + * @return The handle, or zero/NULL for NULL or a different backend. + * Expires on display destruction. Available only when this backend is built. + * Never pass a pointer to a destroyed object. + */ +Display *yasl_private_x11_display_get_display(struct yasl_private_display *d); +/** + * Get the borrowed X11 Window ID for an object of the matching backend. + * @param s Live surface, or NULL. + * @return The handle, or zero/NULL for NULL or a different backend. + * Expires on surface destruction. Available only when this backend is built. + * Never pass a pointer to a destroyed object. + */ +uintptr_t yasl_private_x11_surface_get_window(struct yasl_surface *s); + +#ifdef __cplusplus +} +#endif + +#endif /* YASL_PRIVATE_NATIVE_X11_H */ diff --git a/include/yasl/timing.h b/src/common/private_timing.h index 7e4af9e..308765c 100644 --- a/include/yasl/timing.h +++ b/src/common/private_timing.h @@ -3,8 +3,8 @@ * Timing provenance, refresh estimates, and buffer-resize statistics. * All timestamps are monotonic nanoseconds, not wall-clock dates. */ -#ifndef YASL_TIMING_H -#define YASL_TIMING_H +#ifndef YASL_PRIVATE_TIMING_H +#define YASL_PRIVATE_TIMING_H #include <stdint.h> @@ -13,7 +13,7 @@ extern "C" { #endif /** Cumulative backend buffer-resize measurements; unavailable counters are zero. */ -struct yasl_resize_stats { +struct yasl_private_resize_stats { /** Number of measured buffer resizes. */ uint64_t count; /** Mean measured resize duration in microseconds. */ @@ -26,15 +26,15 @@ struct yasl_resize_stats { /* Describes last_present_ns, not merely availability of an extension. */ /** Provenance of last_present_ns; only PRESENTATION confirms compositor presentation. */ -enum yasl_timing_source { - YASL_TIMING_UNKNOWN = 0, /**< No timing sample yet. */ - YASL_TIMING_SUBMISSION, /**< CPU submission time, not scanout confirmation. */ - YASL_TIMING_FRAME_CALLBACK, /**< Compositor pacing callback, not scanout confirmation. */ - YASL_TIMING_PRESENTATION, /**< Compositor-confirmed presentation. */ +enum yasl_private_timing_source { + YASL_PRIVATE_TIMING_UNKNOWN = 0, /**< No timing sample yet. */ + YASL_PRIVATE_TIMING_SUBMISSION, /**< CPU submission time, not scanout confirmation. */ + YASL_PRIVATE_TIMING_FRAME_CALLBACK, /**< Compositor pacing callback, not scanout confirmation. */ + YASL_PRIVATE_TIMING_PRESENTATION, /**< Compositor-confirmed presentation. */ }; /** Snapshot of estimates and timing provenance. Zero timestamps mean no sample yet. */ -struct yasl_timing { +struct yasl_private_timing { /** Estimated refresh interval in nanoseconds; zero if unavailable. */ double refresh_ns; /** Last timestamp, whose meaning is specified by source. */ @@ -44,13 +44,13 @@ struct yasl_timing { /** Backend skipped-frame counter; not comparable across backends. */ uint64_t frames_skipped; /** Provenance of last_present_ns. */ - enum yasl_timing_source source; + enum yasl_private_timing_source source; /** Cumulative backend resize statistics. */ - struct yasl_resize_stats resize; + struct yasl_private_resize_stats resize; }; #ifdef __cplusplus } #endif -#endif /* YASL_TIMING_H */ +#endif /* YASL_PRIVATE_TIMING_H */ diff --git a/src/common/public_internal.h b/src/common/public_internal.h new file mode 100644 index 0000000..210b222 --- /dev/null +++ b/src/common/public_internal.h @@ -0,0 +1,12 @@ +#ifndef YASL_PUBLIC_INTERNAL_H +#define YASL_PUBLIC_INTERNAL_H +#include "yasl/yasl.h" +#include "common/backend.h" +struct yasl_private_display *yasl_public_display(void); +bool yasl_public_owner(void); +bool yasl_public_surface(yasl_surface *, bool mutation); +bool yasl_public_fail(int error); +void yasl_public_set_error(enum yasl_error, int native_error); +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 new file mode 100644 index 0000000..897b95a --- /dev/null +++ b/src/common/surface.c @@ -0,0 +1,522 @@ +#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 <stdlib.h> +#include <string.h> + +static struct yasl_private_display *display; +static enum yasl_backend requested_backend; +static char *application_id; +static pthread_t owner; +static pthread_mutex_t lifecycle_mutex = PTHREAD_MUTEX_INITIALIZER; +static bool pumping; +static unsigned refresh_depth; +static char *delivered_text; /* Strings of the last returned event; freed by the next poll/wait. */ +static _Thread_local enum yasl_error last_error; +static _Thread_local int native_error; + +void yasl_public_set_error(enum yasl_error error, int native) +{ last_error=error; native_error=native; } +bool yasl_public_fail(int error) +{ + if (!error) error=EIO; + enum yasl_error code=YASL_ERROR_IO; + if (error==EINVAL || error==EOVERFLOW || error==EILSEQ) code=YASL_ERROR_INVALID; + else if (error==ENOTSUP || error==ENOSYS) code=YASL_ERROR_UNSUPPORTED; + else if (error==ENOMEM) code=YASL_ERROR_NO_MEMORY; + else if (error==EBUSY) code=YASL_ERROR_BUSY; + else if (error==EPERM) code=YASL_ERROR_WRONG_THREAD; + yasl_public_set_error(code,error); errno=error; return false; +} +enum yasl_error yasl_get_error(const char **description) +{ + static const char *const messages[]={"No error","Invalid argument","Unsupported operation", + "Out of memory","Library not initialized","Operation is busy","Wrong thread", + "Input context expired","Native operation failed"}; + if(description) *description=messages[last_error]; + return last_error; +} +int yasl_get_native_error(void) { return native_error; } +struct yasl_private_display *yasl_public_display(void) { return display; } +bool yasl_public_owner(void) +{ + if(!display) { yasl_public_set_error(YASL_ERROR_NOT_INITIALIZED,0); return false; } + if(!pthread_equal(owner,pthread_self())) return yasl_public_fail(EPERM); + return true; +} +bool yasl_public_surface(yasl_surface *s,bool mutation) +{ + if(!yasl_public_owner()) return false; + if(!s || s->display!=display) return yasl_public_fail(EINVAL); + if(mutation && refresh_depth) return yasl_public_fail(EBUSY); + return true; +} +static bool initialize(void) +{ + if(display) return yasl_public_owner(); + struct yasl_private_display_spec spec={.app_id=application_id,.backend=(enum yasl_private_backend)requested_backend}; + struct yasl_private_display *d=yasl_private_display_open(NULL,&spec); + if(!d) return yasl_public_fail(errno); + pthread_mutex_lock(&lifecycle_mutex); + owner=pthread_self(); display=d; + pthread_mutex_unlock(&lifecycle_mutex); + return true; +} +bool yasl_set_app_id(const char *id) +{ + if(display) return yasl_public_fail(EBUSY); + char *copy=id ? strdup(id) : NULL; + if(id&&!copy) return yasl_public_fail(ENOMEM); + free(application_id); application_id=copy; return true; +} +bool yasl_set_backend(enum yasl_backend backend) +{ + if(display) return yasl_public_fail(EBUSY); + if(backend<YASL_BACKEND_AUTO || backend>YASL_BACKEND_COCOA) return yasl_public_fail(EINVAL); + requested_backend=backend; return true; +} +static bool creation_valid(int width,int height,unsigned flags) +{ + if(width<=0 || height<=0 || width>32767 || height>32767 || + (unsigned long long)width*(unsigned)height>INT32_MAX/4 || (flags & ~15u)) + return yasl_public_fail(EINVAL); + if(pumping || refresh_depth) return yasl_public_fail(EBUSY); + return true; +} +static void draw_committed(yasl_surface *s,struct yasl_private_frame *frame,void *ud) +{ + (void)ud; + if(s->refresh_callback && pumping && !refresh_depth) { + ++refresh_depth; s->refresh_callback(s,s->refresh_userdata); --refresh_depth; + } + if(s->presenter!=YASL_PRIVATE_PRESENTER_CPU || !frame->target.pixels) return; + int w=frame->target.size.x,h=frame->target.size.y; + if(w==s->committed_width && h==s->committed_height && s->committed_pixels) + memcpy(frame->target.pixels,s->committed_pixels,(size_t)w*h*4); + else memset(frame->target.pixels,0,(size_t)w*h*4); + struct yasl_private_damage_list damage={.n=-1}; + yasl_private_surface_present(s,&frame->target,&damage); +} +void yasl_public_register_surface(yasl_surface *s,unsigned flags,yasl_surface *parent) +{ + s->creation_flags=flags; s->parent=parent; + if(flags&YASL_SURFACE_CPU) s->presenter=YASL_PRIVATE_PRESENTER_CPU; + struct yasl_private_window_cbs cb={.draw=draw_committed}; + yasl_private_surface_set_callbacks(s,&cb,NULL); + s->reported_state=s->state; + struct yasl_private_geometry geometry=yasl_private_surface_geometry(s); + yasl_private_surface_notify_configure(s,geometry); +} +yasl_surface *yasl_surface_create(const char *title,int width,int height,unsigned flags) +{ + if(!creation_valid(width,height,flags)) return NULL; + bool fresh=!display; + if(!initialize()) return NULL; + struct yasl_private_window_spec spec={.title=title,.size_logical={width,height}, + .flags=flags,.external_gpu=!(flags&YASL_SURFACE_CPU)}; + yasl_surface *s=yasl_private_surface_create_toplevel(display,&spec); + if(!s) { + int error=errno; + if(fresh) { pthread_mutex_lock(&lifecycle_mutex); struct yasl_private_display *d=display; display=NULL; + pthread_mutex_unlock(&lifecycle_mutex); yasl_private_display_close(d); } + yasl_public_fail(error); return NULL; + } + yasl_public_register_surface(s,flags,NULL); return s; +} +void yasl_surface_destroy(yasl_surface *s) +{ + if(!s) return; + if(!yasl_public_surface(s,true)) return; + for(;;) { + yasl_surface *child=NULL; + for(yasl_surface *p=display->surfaces;p;p=p->event_next) if(p->parent==s) { child=p; break; } + if(!child) break; + yasl_surface_destroy(child); + } + free(s->committed_pixels); s->committed_pixels=NULL; + free(s->text_surrounding); s->text_surrounding=NULL; + yasl_private_surface_destroy(s); +} +void yasl_shutdown(void) +{ + if(display && !yasl_public_owner()) return; + if(pumping || refresh_depth) { yasl_public_fail(EBUSY); return; } + if(display) { + while(display->surfaces) yasl_surface_destroy(display->surfaces); + pthread_mutex_lock(&lifecycle_mutex); struct yasl_private_display *d=display; display=NULL; + pthread_mutex_unlock(&lifecycle_mutex); yasl_private_display_close(d); + } + free(delivered_text); delivered_text=NULL; + free(application_id); application_id=NULL; requested_backend=YASL_BACKEND_AUTO; +#if YASL_HAS_VULKAN + extern void yasl_private_vulkan_reset(void); + yasl_private_vulkan_reset(); +#endif +} +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_backend(const yasl_surface *s) +{ return s ? (enum yasl_backend)s->display->ops->type : YASL_BACKEND_AUTO; } +unsigned yasl_surface_features(const yasl_surface *s) +{ + if(!s) return 0; + unsigned caps=yasl_private_display_caps(s->display); + if(s->display->ops->surface_create_gl) caps|=YASL_CAP_OPENGL; +#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(w) *w=s?s->size.x:0; if(h) *h=s?s->size.y:0; } +void yasl_surface_get_size(const yasl_surface *s,int *w,int *h) +{ double scale=scale_of(s); if(w) *w=s?logical(s->size.x,scale):0; if(h) *h=s?logical(s->size.y,scale):0; } +double yasl_surface_get_scale(const yasl_surface *s) { return scale_of(s); } +unsigned yasl_surface_state(const yasl_surface *s) { return s?s->state:0; } +void *yasl_surface_userdata(const yasl_surface *s) { return s?s->userdata:NULL; } +void yasl_surface_set_userdata(yasl_surface *s,void *ud) { if(yasl_public_surface(s,false)) s->userdata=ud; } +bool yasl_surface_set_title(yasl_surface *s,const char *title) +{ + if(!yasl_public_surface(s,false)) return false; + if(!title) return yasl_public_fail(EINVAL); + if(!s->display->ops->surface_set_title) return yasl_public_fail(ENOTSUP); + s->display->ops->surface_set_title(s,title); return true; +} +#define REQUEST(name,priv,args,call) bool name args { if(!yasl_public_surface(s,false)) return false; errno=0; return priv call || yasl_public_fail(errno); } +REQUEST(yasl_surface_request_size,yasl_private_surface_set_size,(yasl_surface *s,int w,int h),(s,((struct yasl_private_vector2i){w,h}))) +REQUEST(yasl_surface_request_fullscreen,yasl_private_surface_set_fullscreen,(yasl_surface *s,bool v),(s,v)) +REQUEST(yasl_surface_request_maximized,yasl_private_surface_set_maximized,(yasl_surface *s,bool v),(s,v)) +REQUEST(yasl_surface_request_minimize,yasl_private_surface_minimize,(yasl_surface *s),(s)) +REQUEST(yasl_surface_set_pointer_mode,yasl_private_surface_set_pointer_mode,(yasl_surface *s,enum yasl_pointer_mode m),(s,(enum yasl_private_pointer_mode)m)) +bool yasl_surface_set_cursor(yasl_surface *s,enum yasl_cursor cursor) +{ + if(!yasl_public_surface(s,false)) return false; + if(cursor<YASL_CURSOR_ARROW || cursor>YASL_CURSOR_HIDDEN) return yasl_public_fail(EINVAL); + if(!s->display->ops->surface_set_cursor) return yasl_public_fail(ENOTSUP); + yasl_private_surface_set_cursor(s,(enum yasl_private_cursor)cursor); return true; +} +void yasl_surface_set_refresh_callback(yasl_surface *s,void(*fn)(yasl_surface *,void *),void *ud) +{ if(yasl_public_surface(s,false)) { s->refresh_callback=fn; s->refresh_userdata=ud; } } +void yasl_private_surface_notify_refresh(yasl_surface *s) +{ + if(!s || !s->display) return; + struct yasl_private_event ev={.type=YASL_PRIVATE_EVENT_REFRESH,.surface=s}; + yasl_private_events_push(s->display,s,&ev); + if(s->refresh_callback && pumping && !refresh_depth) { + ++refresh_depth; s->refresh_callback(s,s->refresh_userdata); --refresh_depth; + } +} +static char *copy_text(const char *data,size_t length) +{ + if(length==SIZE_MAX || (!data&&length)) { errno=EINVAL; return NULL; } + char *p=malloc(length+1); if(!p) return NULL; + if(length) memcpy(p,data,length); + p[length]=0; return p; +} +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 nothing. */ +static bool window_event(yasl_surface *s,struct yasl_private_vector2i size,uint32_t scale_120,uint32_t state,yasl_event *e) +{ + bool resized=size.x!=s->reported_size.x || size.y!=s->reported_size.y || scale_120!=s->reported_scale_120; + if(!resized && state==s->reported_state) return false; + s->reported_size=size; s->reported_scale_120=scale_120; s->reported_state=state; + if(resized) s->refresh_owed=true; + double scale=scale_120 ? scale_120/120.0 : 1.0; + e->type=YASL_EVENT_WINDOW; e->window.pixel_width=size.x; e->window.pixel_height=size.y; e->window.scale=scale; + e->window.width=logical(size.x,scale); e->window.height=logical(size.y,scale); + e->window.state=state; + return true; +} +static bool refresh_ready(yasl_surface *s) { return s->reported_size.x>0 && s->reported_size.y>0; } +static bool convert_event(const struct yasl_private_event *p,yasl_event *e) +{ + *e=(yasl_event){.surface=p->surface,.time_ns=p->time_ns}; + double scale=scale_of(p->surface); + switch(p->type) { + case YASL_PRIVATE_EVENT_WINDOW_CLOSE:e->type=YASL_EVENT_CLOSE;break; + case YASL_PRIVATE_EVENT_DISPLAY_QUIT:e->type=YASL_EVENT_QUIT;break; + case YASL_PRIVATE_EVENT_DISPLAY_REOPEN:e->type=YASL_EVENT_REOPEN;break; + case YASL_PRIVATE_EVENT_DISPLAY_LOST:e->type=YASL_EVENT_DISPLAY_LOST;break; + case YASL_PRIVATE_EVENT_REFRESH: + if(!p->surface) return false; + if(!refresh_ready(p->surface)) { p->surface->refresh_owed=true; return false; } + p->surface->refresh_owed=false; e->type=YASL_EVENT_REFRESH; break; + case YASL_PRIVATE_EVENT_INPUT_RESET:e->type=YASL_EVENT_INPUT_RESET;break; + case YASL_PRIVATE_EVENT_FOCUS: { + yasl_surface *s=p->surface; if(!s) return false; + uint32_t state=p->focus.focused ? s->reported_state|YASL_STATE_ACTIVATED : s->reported_state&~(uint32_t)YASL_STATE_ACTIVATED; + return window_event(s,s->reported_size,s->reported_scale_120,state,e); + } + 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); + 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); + 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; + 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; + 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; + 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; + 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; + 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; + 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->type=YASL_EVENT_SCROLL;e->scroll.x=p->pointer.pos.x/scale;e->scroll.y=p->pointer.pos.y/scale; + e->scroll.lines_x=p->pointer.scroll.x;e->scroll.lines_y=p->pointer.scroll.y; + e->scroll.pixels_x=p->pointer.pixel_scroll.x/scale;e->scroll.pixels_y=p->pointer.pixel_scroll.y/scale; + e->scroll.valid=p->pointer.scroll_valid?p->pointer.scroll_valid:YASL_SCROLL_LINES; + e->scroll.source=p->pointer.scroll_source;e->scroll.phase=p->pointer.scroll_phase;e->scroll.momentum=p->pointer.scroll_momentum;e->scroll.mods=p->pointer.mods;break; + case YASL_PRIVATE_EVENT_POINTER_DOWN:case YASL_PRIVATE_EVENT_POINTER_UP:case YASL_PRIVATE_EVENT_POINTER_MOTION:case YASL_PRIVATE_EVENT_POINTER_LEAVE: + e->type=YASL_EVENT_POINTER;e->pointer.x=(p->pointer.precise_position?p->pointer.precise_x:p->pointer.pos.x)/scale; + e->pointer.y=(p->pointer.precise_position?p->pointer.precise_y:p->pointer.pos.y)/scale; + e->pointer.dx=p->pointer.delta.x/scale;e->pointer.dy=p->pointer.delta.y/scale; + e->pointer.action=p->type==YASL_PRIVATE_EVENT_POINTER_DOWN?YASL_POINTER_DOWN:p->type==YASL_PRIVATE_EVENT_POINTER_UP?YASL_POINTER_UP: + p->type==YASL_PRIVATE_EVENT_POINTER_LEAVE?YASL_POINTER_LEAVE:YASL_POINTER_MOVE; + e->pointer.button=p->pointer.button;e->pointer.buttons=p->pointer.buttons;e->pointer.mods=p->pointer.mods;break; + case YASL_PRIVATE_EVENT_DRAG: + e->type=YASL_EVENT_DRAG;e->drag.action=(enum yasl_drag_action)p->drag.action; + e->drag.x=p->drag.pos.x/scale;e->drag.y=p->drag.pos.y/scale;break; + case YASL_PRIVATE_EVENT_DROP_HOVER: + e->type=YASL_EVENT_DROP;e->drop.action=p->drop.action==YASL_PRIVATE_DROP_LEAVE?YASL_DROP_LEAVE:YASL_DROP_OVER; + e->drop.x=p->drop.pos.x/scale;e->drop.y=p->drop.pos.y/scale;break; + default:return false; + } + return true; +} +static int take_event(yasl_event *out) +{ + for(yasl_surface *s=display->surfaces;s;s=s->event_next) if(s->refresh_owed && refresh_ready(s)) { + s->refresh_owed=false; + *out=(yasl_event){.type=YASL_EVENT_REFRESH,.surface=s,.time_ns=yasl_time_ns()}; + return 1; + } + struct yasl_private_event p; + while(yasl_private_display_poll_event(display,&p)) { + if(p.surface && ((p.type==YASL_PRIVATE_EVENT_COMPOSITION && p.composition.generation!=p.surface->text_generation) || + (p.type==YASL_PRIVATE_EVENT_TEXT_EDIT && p.text_edit.generation!=p.surface->text_generation))) continue; + yasl_event event; + errno=0; + if(convert_event(&p,&event)) { *out=event;return 1; } + if(errno) { yasl_public_fail(errno);return -1; } + } + return 0; +} +int yasl_wait_event(yasl_event *out,int timeout) +{ + if(!out) { yasl_public_fail(EINVAL);return -1; } + *out=(yasl_event){.type=YASL_EVENT_NONE}; + if(!yasl_public_owner()) return -1; + if(pumping || refresh_depth) { yasl_public_fail(EBUSY);return -1; } + free(delivered_text); delivered_text=NULL; + int n=take_event(out); if(n) return n; + pumping=true; + yasl_private_display_flush(display); + n=take_event(out); + if(!n) { + int work=yasl_private_display_dispatch(display,timeout); + if(work<0) { yasl_public_fail(errno);n=-1; } + yasl_private_display_flush(display); + if(n>=0) n=take_event(out); + } + pumping=false;return n; +} +uint32_t *yasl_cpu_pixels(yasl_surface *s,int *w,int *h) +{ + if(w) *w=0; + if(h) *h=0; + if(!yasl_public_surface(s,false)) return NULL; + if(s->presenter!=YASL_PRIVATE_PRESENTER_CPU) { yasl_public_fail(ENOTSUP); return NULL; } + int width=s->size.x,height=s->size.y; + if(width<=0 || height<=0) { yasl_public_fail(EBUSY); return NULL; } + if(width!=s->committed_width || height!=s->committed_height || !s->committed_pixels) { + uint32_t *next=calloc((size_t)width*height,4); + if(!next) { yasl_public_fail(ENOMEM); return NULL; } + int keep_w=width<s->committed_width?width:s->committed_width,keep_h=height<s->committed_height?height:s->committed_height; + if(s->committed_pixels) for(int y=0;y<keep_h;y++) + memcpy(next+(size_t)y*width,s->committed_pixels+(size_t)y*s->committed_width,(size_t)keep_w*4); + free(s->committed_pixels); s->committed_pixels=next; s->committed_width=width; s->committed_height=height; + } + if(w) *w=width; + if(h) *h=height; + return s->committed_pixels; +} +bool yasl_cpu_present(yasl_surface *s) +{ + if(!yasl_public_surface(s,false)) return false; + if(s->presenter!=YASL_PRIVATE_PRESENTER_CPU) return yasl_public_fail(ENOTSUP); + if(!s->committed_pixels || s->committed_width!=s->size.x || s->committed_height!=s->size.y) return yasl_public_fail(EINVAL); + yasl_private_surface_request_frame(s); return true; +} +int yasl_double_click_ms(void) { return display?yasl_private_display_double_click_ms(display):400; } +bool yasl_clipboard_set(enum yasl_selection sel,const char *text) +{ if(!yasl_public_owner())return false;errno=0;return yasl_private_clipboard_set(display,(enum yasl_private_selection)sel,text)||yasl_public_fail(errno); } +uint64_t yasl_clipboard_request(yasl_surface *s,enum yasl_selection sel) +{ if(!yasl_public_surface(s,false))return 0;errno=0;uint64_t id=yasl_private_clipboard_request(s,(enum yasl_private_selection)sel);if(!id)yasl_public_fail(errno);return id; } +static bool text_update(yasl_surface *s) +{ if(!s->display->ops->surface_update_text_input)return true;errno=0;return s->display->ops->surface_update_text_input(s)||yasl_public_fail(errno); } +bool yasl_surface_set_text_input(yasl_surface *s,bool active,enum yasl_text_purpose purpose) +{ + if(!yasl_public_surface(s,false))return false; + if(purpose<YASL_TEXT_NORMAL||purpose>YASL_TEXT_TERMINAL)return yasl_public_fail(EINVAL); + bool old=s->text_input_active;unsigned old_purpose=s->text_purpose;uint64_t generation=s->text_generation; + if(old!=active || old_purpose!=(unsigned)purpose)++s->text_generation; + s->text_input_active=active;s->text_purpose=purpose; + if(!text_update(s)) { s->text_input_active=old;s->text_purpose=old_purpose;s->text_generation=generation;return false; }return true; +} +bool yasl_surface_set_surrounding_text(yasl_surface *s,const char *text,size_t len,size_t begin,size_t end,uint64_t revision) +{ + if(!yasl_public_surface(s,false))return false; + if((!text&&len)||begin>end||end>len||len>INT32_MAX || (len&&memchr(text,0,len)))return yasl_public_fail(EINVAL); + /* Validate UTF-8 and require selection boundaries between code points. */ + if((begin<len&&((unsigned char)text[begin]&0xc0)==0x80)||(end<len&&((unsigned char)text[end]&0xc0)==0x80))return yasl_public_fail(EINVAL); + for(size_t i=0;i<len;) { unsigned char c=(unsigned char)text[i];size_t n=c<0x80?1:c>=0xc2&&c<=0xdf?2:c>=0xe0&&c<=0xef?3:c>=0xf0&&c<=0xf4?4:0; + if(!n||i+n>len)return yasl_public_fail(EILSEQ); + for(size_t j=1;j<n;j++)if(((unsigned char)text[i+j]&0xc0)!=0x80)return yasl_public_fail(EILSEQ); + if(n>=3 && ((c==0xe0&&(unsigned char)text[i+1]<0xa0)||(c==0xed&&(unsigned char)text[i+1]>=0xa0)|| + (c==0xf0&&(unsigned char)text[i+1]<0x90)||(c==0xf4&&(unsigned char)text[i+1]>=0x90)))return yasl_public_fail(EILSEQ); + i+=n; + } + char *copy=copy_text(text,len);if(!copy)return yasl_public_fail(errno); + char *old=s->text_surrounding;size_t old_len=s->text_length,old_begin=s->text_begin,old_end=s->text_end;uint64_t old_rev=s->text_revision; + s->text_surrounding=copy;s->text_length=len;s->text_begin=begin;s->text_end=end;s->text_revision=revision; + if(!text_update(s)){free(copy);s->text_surrounding=old;s->text_length=old_len;s->text_begin=old_begin;s->text_end=old_end;s->text_revision=old_rev;return false;} + free(old);return true; +} +static bool logical_rect(yasl_surface *s,double x,double y,double w,double h,struct yasl_private_rect2i *r) +{ + if(!isfinite(x)||!isfinite(y)||!isfinite(w)||!isfinite(h)||w<0||h<0)return yasl_public_fail(EINVAL); + r->x=yasl_private_scale_to_device(x,s->scale_120);r->y=yasl_private_scale_to_device(y,s->scale_120); + r->width=yasl_private_scale_to_device(w,s->scale_120);r->height=yasl_private_scale_to_device(h,s->scale_120);return true; +} +bool yasl_surface_set_ime_caret(yasl_surface *s,double x,double y,double w,double h) +{ + if(!yasl_public_surface(s,false))return false; + struct yasl_private_rect2i r; + if(!logical_rect(s,x,y,w,h,&r))return false; + if(!s->display->ops->surface_set_ime_caret)return yasl_public_fail(ENOTSUP); + yasl_private_surface_set_ime_caret(s,r);return true; +} +static bool trigger_valid(yasl_surface *s,const yasl_event *e) +{ + if(!e||e->surface!=s||!e->time_ns||yasl_time_ns()-e->time_ns>5000000000ull || + !((e->type==YASL_EVENT_POINTER&&e->pointer.action==YASL_POINTER_DOWN)||(e->type==YASL_EVENT_PEN&&e->pen.action==YASL_PEN_DOWN))) { + yasl_public_set_error(YASL_ERROR_CONTEXT_EXPIRED,0);return false; + }return true; +} +static bool popup_spec(yasl_surface *s,int w,int h,double x,double y,double aw,double ah,enum yasl_popup_edge anchor,enum yasl_popup_edge gravity,unsigned constraints,struct yasl_private_popup_spec *out) +{ + if(w<=0||h<=0 || (unsigned)anchor>8 || (unsigned)gravity>8 || (constraints&~63u))return yasl_public_fail(EINVAL); + *out=(struct yasl_private_popup_spec){.size={yasl_private_scale_to_device(w,s->scale_120),yasl_private_scale_to_device(h,s->scale_120)},.anchor_edges=anchor,.gravity=gravity,.constraints=constraints,.positioner_explicit=true}; + return logical_rect(s,x,y,aw,ah,&out->anchor); +} +yasl_surface *yasl_popup_create(yasl_surface *parent,int w,int h,double x,double y,double aw,double ah,enum yasl_popup_edge anchor,enum yasl_popup_edge gravity,unsigned constraints,const yasl_event *trigger) +{ + if(!yasl_public_surface(parent,true)||!trigger_valid(parent,trigger))return NULL; + struct yasl_private_popup_spec spec;if(!popup_spec(parent,w,h,x,y,aw,ah,anchor,gravity,constraints,&spec))return NULL; + yasl_surface *s=yasl_private_surface_create_popup(parent,&spec);if(!s){yasl_public_fail(errno);return NULL;} + yasl_public_register_surface(s,parent->creation_flags,parent);return s; +} +bool yasl_popup_reposition(yasl_surface *s,int w,int h,double x,double y,double aw,double ah,enum yasl_popup_edge anchor,enum yasl_popup_edge gravity,unsigned constraints) +{ + if(!yasl_public_surface(s,false))return false; + struct yasl_private_popup_spec spec; + if(!s->parent)return yasl_public_fail(EINVAL); + if(!popup_spec(s->parent,w,h,x,y,aw,ah,anchor,gravity,constraints,&spec))return false; + errno=0;return yasl_private_surface_popup_reposition(s,&spec)||yasl_public_fail(errno); +} +bool yasl_surface_start_interactive_move(yasl_surface *s,const yasl_event *e) +{ if(!yasl_public_surface(s,false)||!trigger_valid(s,e))return false;errno=0;return yasl_private_surface_start_interactive_move(s)||yasl_public_fail(errno); } +bool yasl_surface_start_interactive_resize(yasl_surface *s,enum yasl_resize_edge edge,const yasl_event *e) +{ if(!yasl_public_surface(s,false)||!trigger_valid(s,e))return false;errno=0;return yasl_private_surface_start_interactive_resize(s,(enum yasl_private_resize_edge)edge)||yasl_public_fail(errno); } +bool yasl_surface_show_window_menu(yasl_surface *s,double x,double y,const yasl_event *e) +{ if(!yasl_public_surface(s,false)||!trigger_valid(s,e))return false; + struct yasl_private_rect2i r;if(!logical_rect(s,x,y,0,0,&r))return false;errno=0;return yasl_private_surface_show_window_menu(s,(struct yasl_private_vector2i){r.x,r.y})||yasl_public_fail(errno); } +bool yasl_drag_start(yasl_surface *s,const yasl_event *e,const uint32_t *pixels,int w,int h,int hx,int hy) +{ + if(!yasl_public_surface(s,false)||!trigger_valid(s,e))return false; + struct yasl_private_drag_image image={.pixels=pixels,.size={w,h},.hotspot={hx,hy}}; + errno=0;return yasl_private_display_drag_start(display,s,pixels?&image:NULL,NULL,NULL)||yasl_public_fail(errno); +} +void yasl_drag_cancel(void) { if(yasl_public_owner())yasl_private_display_drag_cancel(display); } +bool yasl_surface_drag_carry(yasl_surface *s,double x,double y) +{ if(!yasl_public_surface(s,false))return false; + struct yasl_private_rect2i r;if(!logical_rect(s,x,y,0,0,&r))return false;errno=0;return yasl_private_surface_drag_carry(s,(struct yasl_private_vector2i){r.x,r.y})||yasl_public_fail(errno); } +bool yasl_surface_place_under_pointer(yasl_surface *s,double x,double y) +{ if(!yasl_public_surface(s,false))return false; + struct yasl_private_rect2i r;if(!logical_rect(s,x,y,0,0,&r))return false;errno=0;return yasl_private_surface_place_under_pointer(s,(struct yasl_private_vector2i){r.x,r.y})||yasl_public_fail(errno); } +size_t yasl_drop_next_path(const char **cursor,char *out,size_t cap) +{ return yasl_private_drop_next_path(cursor,out,cap); } + +yasl_surface *yasl_gl_surface_create(const char *title,int w,int h,unsigned flags,const int *attrs,yasl_surface *share) +{ + if(!creation_valid(w,h,flags)||(flags&YASL_SURFACE_CPU)) { yasl_public_fail(EINVAL);return NULL; } + struct yasl_private_gl_options parsed;if(!yasl_private_gl_parse_options(attrs,&parsed)){yasl_public_fail(errno);return NULL;} + bool fresh=!display;if(!initialize())return NULL; + if(share && (share->display!=display || share->presenter!=YASL_PRIVATE_PRESENTER_GL)) {yasl_public_fail(EINVAL);return NULL;} + if(!display->ops->surface_create_gl) {if(fresh)yasl_shutdown();yasl_public_fail(ENOTSUP);return NULL;} + struct yasl_private_window_spec spec={.title=title,.size_logical={w,h},.flags=flags,.external_gpu=true}; + yasl_surface *s=display->ops->surface_create_gl(display,&spec,attrs,share); + if(!s){int error=errno;if(fresh)yasl_shutdown();yasl_public_fail(error);return NULL;} + if(!yasl_private_events_register(s)){int error=errno;display->ops->surface_destroy(s);if(fresh)yasl_shutdown();yasl_public_fail(error);return NULL;} + s->presenter=YASL_PRIVATE_PRESENTER_GL;yasl_public_register_surface(s,flags,NULL);return s; +} +bool yasl_gl_make_current(yasl_surface *s) +{ + if(!display){yasl_public_set_error(YASL_ERROR_NOT_INITIALIZED,0);return false;} + if(s && (s->display!=display||s->presenter!=YASL_PRIVATE_PRESENTER_GL))return yasl_public_fail(EINVAL); + if(!display->ops->gl_make_current)return yasl_public_fail(ENOTSUP); + errno=0;return display->ops->gl_make_current(display,s)||yasl_public_fail(errno); +} +yasl_gl_proc yasl_gl_get_proc_address(const char *name) +{ + if(!display||!name||!display->ops->gl_get_proc_address){yasl_public_fail(ENOTSUP);return NULL;} + yasl_gl_proc p=display->ops->gl_get_proc_address(display,name);if(!p)yasl_public_fail(ENOENT);return p; +} +bool yasl_gl_swap_interval(yasl_surface *s,int interval) +{ + if(!s||s->presenter!=YASL_PRIVATE_PRESENTER_GL||!s->display->ops->gl_swap_interval)return yasl_public_fail(EINVAL); + errno=0;return s->display->ops->gl_swap_interval(s->display,s,interval)||yasl_public_fail(errno); +} +bool yasl_gl_swap_buffers(yasl_surface *s) +{ + if(!s||s->presenter!=YASL_PRIVATE_PRESENTER_GL||!s->display->ops->gl_swap_buffers)return yasl_public_fail(EINVAL); + errno=0;return s->display->ops->gl_swap_buffers(s->display,s)||yasl_public_fail(errno); +} diff --git a/src/common/timing.c b/src/common/timing.c index 7a91327..69c25cf 100644 --- a/src/common/timing.c +++ b/src/common/timing.c @@ -1,6 +1,6 @@ #include "timing.h" -uint64_t yasl_clock_extend_ms(uint32_t ms, uint64_t now_ns) +uint64_t yasl_private_clock_extend_ms(uint32_t ms, uint64_t now_ns) { uint64_t now_ms = now_ns / 1000000; int64_t ahead = (int32_t)(ms - (uint32_t)now_ms); @@ -9,7 +9,7 @@ uint64_t yasl_clock_extend_ms(uint32_t ms, uint64_t now_ns) return (uint64_t)((int64_t)now_ms + ahead) * 1000000ull; } -void yasl_clock_presented(struct yasl_frame_clock *c, uint64_t now, bool sample) +void yasl_private_clock_presented(struct yasl_private_frame_clock *c, uint64_t now, bool sample) { if (c->last_present_ns) { double dt = (double)(now - c->last_present_ns); @@ -17,10 +17,10 @@ void yasl_clock_presented(struct yasl_frame_clock *c, uint64_t now, bool sample) c->refresh_ns += 0.125 * (dt - c->refresh_ns); } c->last_present_ns = now; - c->source = YASL_TIMING_SUBMISSION; + c->source = YASL_PRIVATE_TIMING_SUBMISSION; } -void yasl_clock_note_resize(struct yasl_frame_clock *c, uint64_t dt_ns) +void yasl_private_clock_note_resize(struct yasl_private_frame_clock *c, uint64_t dt_ns) { c->resize_count++; c->resize_sum_ns += dt_ns; @@ -28,8 +28,8 @@ void yasl_clock_note_resize(struct yasl_frame_clock *c, uint64_t dt_ns) c->resize_max_ns = dt_ns; } -void yasl_clock_resize_stats(const struct yasl_frame_clock *c, - struct yasl_resize_stats *out) +void yasl_private_clock_resize_stats(const struct yasl_private_frame_clock *c, + struct yasl_private_resize_stats *out) { out->count = c->resize_count; out->avg_us = c->resize_count @@ -39,13 +39,13 @@ void yasl_clock_resize_stats(const struct yasl_frame_clock *c, out->minflt = c->resize_minflt; } -uint64_t yasl_clock_next_present_ns(const struct yasl_frame_clock *c) +uint64_t yasl_private_clock_next_present_ns(const struct yasl_private_frame_clock *c) { uint64_t next = c->last_present_ns + (uint64_t)c->refresh_ns; return (uint64_t)-(c->last_present_ns != 0) & next; } -uint64_t yasl_clock_frame_timestamp_ns(struct yasl_frame_clock *c, uint64_t now) +uint64_t yasl_private_clock_frame_timestamp_ns(struct yasl_private_frame_clock *c, uint64_t now) { if (!c->last_present_ns || c->refresh_ns <= 0.0) { c->frame_time_ns = now; diff --git a/src/common/timing.h b/src/common/timing.h index a3c3c88..a0c6ef1 100644 --- a/src/common/timing.h +++ b/src/common/timing.h @@ -1,12 +1,12 @@ -#ifndef YASL_COMMON_TIMING_H -#define YASL_COMMON_TIMING_H +#ifndef YASL_PRIVATE_COMMON_TIMING_H +#define YASL_PRIVATE_COMMON_TIMING_H -#include "yasl/timing.h" +#include "common/private_timing.h" #include <stdbool.h> -#define YASL_REFRESH_DEFAULT_NS 16666666.0 +#define YASL_PRIVATE_REFRESH_DEFAULT_NS 16666666.0 -struct yasl_frame_clock { +struct yasl_private_frame_clock { uint64_t last_present_ns; uint64_t resize_count; uint64_t resize_sum_ns; @@ -14,15 +14,15 @@ struct yasl_frame_clock { long resize_minflt; uint64_t frame_time_ns; double refresh_ns; - enum yasl_timing_source source; + enum yasl_private_timing_source source; }; -uint64_t yasl_clock_extend_ms(uint32_t ms, uint64_t now_ns); -void yasl_clock_presented(struct yasl_frame_clock *c, uint64_t now, bool sample); -void yasl_clock_note_resize(struct yasl_frame_clock *c, uint64_t dt_ns); -void yasl_clock_resize_stats(const struct yasl_frame_clock *c, - struct yasl_resize_stats *out); -uint64_t yasl_clock_next_present_ns(const struct yasl_frame_clock *c); -uint64_t yasl_clock_frame_timestamp_ns(struct yasl_frame_clock *c, uint64_t now); +uint64_t yasl_private_clock_extend_ms(uint32_t ms, uint64_t now_ns); +void yasl_private_clock_presented(struct yasl_private_frame_clock *c, uint64_t now, bool sample); +void yasl_private_clock_note_resize(struct yasl_private_frame_clock *c, uint64_t dt_ns); +void yasl_private_clock_resize_stats(const struct yasl_private_frame_clock *c, + struct yasl_private_resize_stats *out); +uint64_t yasl_private_clock_next_present_ns(const struct yasl_private_frame_clock *c); +uint64_t yasl_private_clock_frame_timestamp_ns(struct yasl_private_frame_clock *c, uint64_t now); -#endif /* YASL_COMMON_TIMING_H */ +#endif /* YASL_PRIVATE_COMMON_TIMING_H */ diff --git a/src/common/types.h b/src/common/types.h new file mode 100644 index 0000000..91f8a47 --- /dev/null +++ b/src/common/types.h @@ -0,0 +1,1101 @@ +/** + * @file + * Portable desktop surfaces, CPU drawing, input, and display dispatch. + * + * All calls and callbacks use the display's opening thread (the main thread on + * Cocoa), unless explicitly documented otherwise. Userdata is borrowed; callback + * tables and creation specs are copied. Do not recursively dispatch/flush or + * destroy a display/loop while dispatch or a callback is active. Destroy child + * popups before parents, surfaces before displays, displays before borrowed loops. + * Surface destruction from its own callback is supported; stop using it immediately. + * + * Zero-initialize option and callback structs before setting fields. Pointers to + * destroyed objects are invalid, even for getters and destroy functions. NULL + * handling is documented per function; it does not make freed pointers safe. + * Only functions explicitly documenting errno guarantee a diagnostic there. + * Other boolean results report success/availability; void setters are best effort. + * New window requests, clipboard operations, frame presentation, and posting + * document errno on failure. Event strings belong to the display queue. + * + * Toplevel creation sizes use logical pixels. Geometry, framebuffers, pointer + * positions, damage, IME carets, popup specs, and drag offsets use device pixels. + * Coordinates have their origin at the upper left; x grows right and y grows down. + * Scale is an integer ratio with YASL_PRIVATE_SCALE_UNIT (120) representing 1x. + * See the application guide for the display-wide dispatch/poll/flush cycle + * and backend limits. Legacy surface polling is an input-only filtered adapter. + */ +#ifndef YASL_PRIVATE_H +#define YASL_PRIVATE_H + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <limits.h> +#include <float.h> +#include <math.h> + +#include "common/private_timing.h" +#include "yasl/yasl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct yasl_private_display; +struct yasl_surface; +struct yasl_private_loop; + +/* Geometry */ +/** A two-dimensional integer size, position, or offset; units depend on the caller. */ +struct yasl_private_vector2i { + /** Horizontal coordinate or width. */ + int x; + /** Vertical coordinate or height. */ + int y; +}; + +/** Two-dimensional fractional offset; units depend on the containing structure. */ +struct yasl_private_vector2d { + /** Horizontal component. */ + double x; + /** Vertical component. */ + double y; +}; + +/** Integer rectangle; origin is upper left and extents must be nonnegative. */ +struct yasl_private_rect2i { + /** Left coordinate. */ + int x; + /** Top coordinate. */ + int y; + /** Nonnegative horizontal extent. */ + int width; + /** Nonnegative vertical extent. */ + int height; +}; + +/** Scale numerator representing 1x. */ +#define YASL_PRIVATE_SCALE_UNIT 120 +/** Backend minimum logical dimension used when constraining toplevel sizes. */ +#define YASL_PRIVATE_MIN_LOGICAL_PX 64 + +/* Nearest integer, ties away from zero. Zero scale returns zero; finite + * out-of-range results saturate. Non-finite logical coordinates return zero. */ +/** Convert a logical coordinate to device pixels, rounding ties away from zero. + * @param logical Logical coordinate; non-finite values return zero. + * @param scale_120 Scale numerator; zero returns zero. + * @return Rounded device coordinate, saturated to the range of int. + */ +static inline int yasl_private_scale_to_device(double logical, uint32_t scale_120) +{ + if (!scale_120 || !(logical >= -DBL_MAX && logical <= DBL_MAX)) return 0; + double px = (scale_120 == YASL_PRIVATE_SCALE_UNIT) ? logical : (logical * ((double)scale_120 / YASL_PRIVATE_SCALE_UNIT)); + if (px >= INT_MAX) return INT_MAX; + if (px <= INT_MIN) return INT_MIN; + return (int)(px + copysign(0.5, px)); +} + +/** Convert a device coordinate to logical pixels, rounding ties away from zero. + * @param device Device coordinate. + * @param scale_120 Scale numerator; zero returns zero. + * @return Rounded logical coordinate, saturated to the range of int. + */ +static inline int yasl_private_scale_to_logical(int device, uint32_t scale_120) +{ + if (!scale_120) return 0; + if (scale_120 == YASL_PRIVATE_SCALE_UNIT) return device; + int64_t px = (int64_t)device * YASL_PRIVATE_SCALE_UNIT; + int64_t half = (int64_t)(scale_120 / 2); + int64_t bias = (px < 0) ? -half : half; + px = (px + bias) / scale_120; + if (px > INT_MAX) return INT_MAX; + if (px < INT_MIN) return INT_MIN; + return (int)px; +} + +/* Capabilities */ +/** Optional backend/server features, returned as a bitmask by yasl_private_display_caps(). */ +enum yasl_private_display_cap : uint32_t { + YASL_PRIVATE_CAP_POPUPS = 1 << 0, /**< Popup creation and placement. */ + YASL_PRIVATE_CAP_DRAG = 1 << 1, /**< Surface-drag source and target support. */ + YASL_PRIVATE_CAP_DRAG_CARRY = 1 << 2, /**< Carry a toplevel during a drag. */ + YASL_PRIVATE_CAP_PLACE_WINDOW = 1 << 3, /**< Place a window relative to the pointer. */ + YASL_PRIVATE_CAP_PRIMARY_SELECTION = 1 << 4, /**< Separate primary selection. */ + YASL_PRIVATE_CAP_CSD = 1 << 5, /**< Backend advertises client decoration support; check each action result. */ + YASL_PRIVATE_CAP_NATIVE_GRAPHICS = 1 << 6, /**< Application-managed native rendering. */ + 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. */ +}; + +/** Independent selections. An unsupported selection never aliases the clipboard. */ +enum yasl_private_selection : uint8_t { + YASL_PRIVATE_CLIPBOARD, /**< Explicit copy/paste selection. */ + YASL_PRIVATE_PRIMARY, /**< Independent primary selection, if supported. */ +}; + +/** Reported surface state flags; absent flags may also mean unsupported state. */ +enum yasl_private_surface_state : uint32_t { + YASL_PRIVATE_STATE_FULLSCREEN = 1 << 0, /**< Fullscreen. */ + YASL_PRIVATE_STATE_MAXIMIZED = 1 << 1, /**< Maximized. */ + YASL_PRIVATE_STATE_ACTIVATED = 1 << 2, /**< Active window. */ + YASL_PRIVATE_STATE_SUSPENDED = 1 << 3, /**< Compositor reports suspension. */ + YASL_PRIVATE_STATE_CSD = 1 << 4, /**< Backend reports client-side decorations. */ + YASL_PRIVATE_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. */ +enum yasl_private_cursor { + YASL_PRIVATE_CURSOR_ARROW, /**< Default arrow. */ + YASL_PRIVATE_CURSOR_TEXT, /**< Text insertion. */ + YASL_PRIVATE_CURSOR_LINK, /**< Link/pointer hand. */ + YASL_PRIVATE_CURSOR_CROSSHAIR, /**< Crosshair. */ + YASL_PRIVATE_CURSOR_MOVE, /**< Move in any direction. */ + YASL_PRIVATE_CURSOR_RESIZE_NWSE, /**< Northwest/southeast resize. */ + YASL_PRIVATE_CURSOR_RESIZE_NESW, /**< Northeast/southwest resize. */ + YASL_PRIVATE_CURSOR_RESIZE_EW, /**< Horizontal resize. */ + YASL_PRIVATE_CURSOR_RESIZE_NS, /**< Vertical resize. */ + YASL_PRIVATE_CURSOR_HIDDEN, /**< Hidden system cursor; pointer input continues. */ + YASL_PRIVATE_CURSOR_NONE = YASL_PRIVATE_CURSOR_HIDDEN, /**< Compatibility alias for HIDDEN. */ +}; + +/** Valid interactive resize edges; combinations are restricted to the named values. */ +enum yasl_private_resize_edge : uint32_t { + YASL_PRIVATE_EDGE_NONE = 0, /**< No resize edge; rejected by interactive resize. */ + YASL_PRIVATE_EDGE_TOP = 1, /**< Top edge. */ + YASL_PRIVATE_EDGE_BOTTOM = 2, /**< Bottom edge. */ + YASL_PRIVATE_EDGE_LEFT = 4, /**< Left edge. */ + YASL_PRIVATE_EDGE_TOP_LEFT = 5, /**< Top-left corner. */ + YASL_PRIVATE_EDGE_BOTTOM_LEFT = 6, /**< Bottom-left corner. */ + YASL_PRIVATE_EDGE_RIGHT = 8, /**< Right edge. */ + YASL_PRIVATE_EDGE_TOP_RIGHT = 9, /**< Top-right corner. */ + YASL_PRIVATE_EDGE_BOTTOM_RIGHT = 10, /**< Bottom-right corner. */ +}; + +/* Damage & Framebuffer + * CPU pixels are tightly packed premultiplied ARGB8888 (0xAARRGGBB). + * Size, pointer positions, IME caret and damage use device pixels; surface + * toplevel creation sizes use logical pixels. Damage is a hint: backends may upload + * the whole frame. Every pixel must contain valid content on each draw. + */ +/** Maximum number of damage rectangles in one presentation. */ +#define YASL_PRIVATE_DAMAGE_MAX 64 + +/** Damage hints for one presentation; every target still needs complete valid pixels. */ +struct yasl_private_damage_list { + /** -1: full target; 0: no damaged rectangles; otherwise number of rects. */ + int n; + /** Reserved hint; callers may leave zero. Core presentation uses rects. */ + struct yasl_private_rect2i bounds; + /** First n rectangles, in device pixels; ignored for n == -1. */ + struct yasl_private_rect2i rects[YASL_PRIVATE_DAMAGE_MAX]; +}; + +/** Borrowed draw target, valid only for the current draw callback. Never free pixels. */ +struct yasl_private_framebuffer { + /** Opaque draw identity. Preserve it and all target fields when copying. */ + uint64_t generation; + /** Writable premultiplied ARGB8888, row stride size.x * sizeof(uint32_t); may be NULL for native rendering. */ + uint32_t *pixels; + /** Target extent in device pixels. */ + struct yasl_private_vector2i size; + /** Row stride in bytes, populated for the active draw callback. */ + int stride_bytes; +}; + +/** One draw invocation. Do not retain its pointer after the callback returns. */ +struct yasl_private_frame { + /** Surface being drawn. */ + struct yasl_surface *surface; + /** Target to initialize and present before returning. */ + struct yasl_private_framebuffer target; + /** Zero-based index of this surface draw. */ + uint64_t frame_index; + /** Estimated animation time; not confirmed presentation. */ + uint64_t target_time_ns; + /** Device scale numerator, with 120 representing 1x. */ + uint32_t scale_120; +}; + +/** Coalesced redraw policy, valid with either loop style. */ +typedef enum yasl_private_pacing_mode { + YASL_PRIVATE_PACING_ON_DEMAND = 0, /**< Draw only when invalidated or exposed. */ + YASL_PRIVATE_PACING_CONTINUOUS, /**< Draw continuously using native pacing or a timer. */ +} yasl_private_pacing_mode; + +/* Drag and Drop */ +/** Target notifications for YASL surface drags, separate from external file drops. */ +enum yasl_private_drag_event { + YASL_PRIVATE_DRAG_OVER, /**< Surface drag entered or moved over the target. */ + YASL_PRIVATE_DRAG_LEAVE, /**< Surface drag left the target. */ + YASL_PRIVATE_DRAG_DROP, /**< Surface drag dropped on the target. */ +}; + +/** External text/file drag hover notifications; completed data arrives through input. */ +enum yasl_private_drop_event { + YASL_PRIVATE_DROP_OVER, /**< External data drag entered or moved over the target. */ + YASL_PRIVATE_DROP_LEAVE, /**< External data drag left the target. */ +}; + +/** Source-side drag completion; DROPPED does not prove application-level acceptance. */ +enum yasl_private_drag_end { + YASL_PRIVATE_DRAG_DROPPED, /**< Drag ended in a drop; not an application acceptance receipt. */ + YASL_PRIVATE_DRAG_CANCELLED, /**< Drag canceled, including display close. */ +}; + +/** Copied drag icon in tightly packed premultiplied 0xAARRGGBB device pixels. */ +struct yasl_private_drag_image { + /** Copied pixel array; required when an image is supplied. */ + const uint32_t *pixels; + /** Positive width and height in device pixels. */ + struct yasl_private_vector2i size; + /** Pointer position within the image, in device pixels. */ + struct yasl_private_vector2i hotspot; +}; + +/** Copied drag completion listener; userdata remains owned by the application. */ +struct yasl_private_drag_listener { + /** Called exactly once after a successful start, possibly synchronously on cancel/close. */ + void (*end)(void *ud, enum yasl_private_drag_end how); +}; + +/** Configured device-pixel content size and the corresponding display scale. */ +struct yasl_private_geometry { + /** Current content extent in device pixels. */ + struct yasl_private_vector2i size; + /** Positive scale numerator; 120 means 1x and 180 means 1.5x. */ + uint32_t scale_120; +}; + +/* Input Model */ +/** Keyboard press, release, or autorepeat; releases must not insert text. */ +enum yasl_private_key_action { + YASL_PRIVATE_KEY_PRESS, /**< Key pressed. */ + YASL_PRIVATE_KEY_RELEASE, /**< Key released. */ + YASL_PRIVATE_KEY_REPEAT, /**< Key autorepeat. */ +}; + +/** Modifier bitmask. SUPER is Command on macOS. */ +enum yasl_private_mod : uint32_t { + YASL_PRIVATE_MOD_SHIFT = 1 << 0, /**< Shift modifier. */ + YASL_PRIVATE_MOD_ALT = 1 << 1, /**< Alt/Option modifier. */ + YASL_PRIVATE_MOD_CTRL = 1 << 2, /**< Control modifier. */ + YASL_PRIVATE_MOD_SUPER = 1 << 3, /**< Super/Command modifier. */ + YASL_PRIVATE_MOD_CAPS = 1 << 4, /**< Caps Lock modifier. */ +}; + +/** Pointer source class; capability support does not imply an attached device. */ +enum yasl_private_pointer_device { + YASL_PRIVATE_PTR_DEV_MOUSE, /**< Mouse or trackpad pointer. */ + YASL_PRIVATE_PTR_DEV_TOUCH, /**< Touch translated into pointer input. */ + YASL_PRIVATE_PTR_DEV_PEN, /**< Tablet stylus. */ + YASL_PRIVATE_PTR_DEV_ERASER, /**< Tablet eraser tool. */ +}; + +/** Pointer button; meaningful only for POINTER_DOWN and POINTER_UP. */ +enum yasl_private_pointer_button { + YASL_PRIVATE_BUTTON_PRIMARY = 0, /**< Left button, pen tip, or touch contact. */ + YASL_PRIVATE_BUTTON_MIDDLE = 1, /**< Middle button / wheel click. */ + YASL_PRIVATE_BUTTON_SECONDARY = 2, /**< Right button or pen barrel button. */ + YASL_PRIVATE_BUTTON_BACK = 3, /**< Side "back" button. */ + YASL_PRIVATE_BUTTON_FORWARD = 4, /**< Side "forward" button. */ +}; + +/** Bit for a yasl_private_pointer_button in yasl_private_pointer_event.buttons. */ +#define YASL_PRIVATE_BUTTON_BIT(button) (1u << (button)) + +/** Pointer capture mode for a surface. */ +enum yasl_private_pointer_mode { + YASL_PRIVATE_POINTER_NORMAL = 0, /**< Absolute positions; cursor per yasl_private_surface_set_cursor(). */ + /** Cursor hidden and locked in place while the surface has focus. POINTER_MOTION + * reports unbounded relative motion in delta; pos stays at the lock position. + * The lock is released on focus loss and re-acquired on focus gain. */ + YASL_PRIVATE_POINTER_RELATIVE, +}; + +/** Normalized tablet axes; zero for unavailable axes and non-tablet devices. */ +struct yasl_private_pointer_pen { + /** 0..65535 normalized to device range. Zero alone does not end contact. */ + uint16_t pressure; + /** Degrees in -90..90; positive right. */ + int8_t tilt_x; + /** Degrees in -90..90; positive down. */ + int8_t tilt_y; +}; + +/** Event tag. Each tag lists the union member it selects. */ +enum yasl_private_event_type { + YASL_PRIVATE_EVENT_NONE = 0, + + /* Input: produced by backends, returned by yasl_private_surface_poll_event(). */ + YASL_PRIVATE_EVENT_KEY, /**< .key */ + YASL_PRIVATE_EVENT_TEXT, /**< .text — committed UTF-8; insert this, not key codepoints. */ + YASL_PRIVATE_EVENT_POINTER_DOWN, /**< .pointer — button/tip pressed; button valid. */ + YASL_PRIVATE_EVENT_POINTER_UP, /**< .pointer — button/tip released; button valid. */ + YASL_PRIVATE_EVENT_POINTER_MOTION, /**< .pointer — position, relative motion, or pen axes changed. */ + YASL_PRIVATE_EVENT_POINTER_SCROLL, /**< .pointer — scroll valid. */ + YASL_PRIVATE_EVENT_POINTER_LEAVE, /**< .pointer — last known pos/device; cancel hover state. */ + YASL_PRIVATE_EVENT_FOCUS, /**< .focus — keyboard focus changed; cancel held state on loss. */ + YASL_PRIVATE_EVENT_PASTE, /**< .text — UTF-8 from yasl_private_display_paste(). */ + YASL_PRIVATE_EVENT_DROP, /**< .text — dropped text/uri-list: CRLF-separated file URLs, not paths. */ + YASL_PRIVATE_EVENT_INPUT_RESET, /**< No payload. Input stream interrupted; clear held keys, buttons, strokes, IME. */ + + /* Window and application: part of the display FIFO, as well as runner delivery. + * Legacy window callbacks also receive matching native notifications. */ + YASL_PRIVATE_EVENT_WINDOW_RESIZE, /**< .resize — size and/or scale changed; reallocate and redraw. */ + YASL_PRIVATE_EVENT_WINDOW_CLOSE, /**< No payload. User asked to close the window. */ + YASL_PRIVATE_EVENT_USER, /**< .user — posted application message. */ + YASL_PRIVATE_EVENT_WINDOW_STATE, /**< .state — reported state changed. */ + YASL_PRIVATE_EVENT_DRAG, /**< .drag — surface-drag target notification. */ + YASL_PRIVATE_EVENT_DROP_HOVER, /**< .drop — external drag hover/leave. */ + YASL_PRIVATE_EVENT_COMPOSITION, /**< .composition — preedit snapshot, including ending. */ + YASL_PRIVATE_EVENT_DISPLAY_QUIT, /**< No payload; system requested application quit. */ + YASL_PRIVATE_EVENT_DISPLAY_REOPEN, /**< No payload; system requested activation. */ + YASL_PRIVATE_EVENT_DISPLAY_LOST, + YASL_PRIVATE_EVENT_PEN, + YASL_PRIVATE_EVENT_TEXT_EDIT, + YASL_PRIVATE_EVENT_REFRESH, /**< No payload; display connection failed. */ +}; + +/** Physical keyboard transition with optional layout labels. + * key is independent of modifiers/layout and stable between press and release. + * codepoint and shifted are optional single Unicode scalars for the current + * layout at levels zero/one, not text input. Zero means unavailable, a control + * key, or a layout mapping that produces multiple characters. Insert committed + * YASL_PRIVATE_EVENT_TEXT into documents instead. */ +struct yasl_private_key_event { + /** Portable physical key; YASL_KEY_UNKNOWN when unmapped. */ + enum yasl_key key; + /** Press, release, or autorepeat. */ + enum yasl_key_action action; + /** yasl_private_mod bitmask at this event. */ + uint32_t mods; + /** Optional unshifted layout Unicode scalar; zero if unavailable. */ + uint32_t codepoint; + /** Optional shifted layout Unicode scalar; zero if unavailable. */ + uint32_t shifted; + /** Native code: Wayland evdev, X11 server keycode, Cocoa virtual keycode. */ + uint32_t native_code; +}; + +/** 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. */ +struct yasl_private_pointer_event { + struct yasl_private_vector2d pixel_scroll; + unsigned scroll_valid, scroll_source, scroll_phase; + bool scroll_momentum; + double precise_x, precise_y; + bool precise_position; + /** Surface-relative device-pixel position. Frozen while YASL_PRIVATE_POINTER_RELATIVE is locked. */ + struct yasl_private_vector2i pos; + /** MOTION: device-pixel movement since the previous sample on this surface. + * Unbounded under YASL_PRIVATE_POINTER_RELATIVE; zero after enter, leave, or reset. */ + struct yasl_private_vector2d delta; + /** SCROLL: fractional line offsets after system preferences; positive right/down. */ + struct yasl_private_vector2d scroll; + /** DOWN/UP: which button changed. */ + enum yasl_private_pointer_button button; + /** YASL_PRIVATE_BUTTON_BIT() mask of buttons held after this event, on every + * POINTER_* type. Tracked from this queue's DOWN and UP events for all + * devices; INPUT_RESET clears it. On Wayland LEAVE clears it too, since the + * compositor sends leave during a press only when it takes the pointer. + * X11 and Cocoa keep sending motion and the release after LEAVE. */ + uint32_t buttons; + /** Mouse, touch, pen, or eraser. */ + enum yasl_private_pointer_device device; + /** yasl_private_mod bitmask at this event. */ + uint32_t mods; + /** Current tablet axes, including on pressure-only motion; missing axes are zero. */ + struct yasl_private_pointer_pen pen; +}; + +/** Borrowed NUL-terminated UTF-8, shared by TEXT, PASTE and DROP. */ +struct yasl_private_text_event { + /** Borrowed NUL-terminated bytes; see yasl_private_surface_poll_event() for lifetime. */ + const char *data; + /** Length in bytes excluding the terminating NUL. */ + size_t len; +}; + +/** Clipboard transfer outcome. */ +enum yasl_private_transfer_status { + YASL_PRIVATE_TRANSFER_OK = 0, /**< Text received successfully, including empty text. */ + YASL_PRIVATE_TRANSFER_UNAVAILABLE, /**< No text selection is available. */ + YASL_PRIVATE_TRANSFER_FAILED, /**< Transfer or decoding failed. */ +}; + +/** Clipboard completion; data/len alias the legacy text payload. */ +struct yasl_private_paste_event { + const char *data; /**< Borrowed NUL-terminated UTF-8; empty on failure. */ + size_t len; /**< Byte length excluding NUL. */ + uint64_t request_id; /**< Zero for a legacy paste request. */ + enum yasl_private_transfer_status status; /**< Completion outcome. */ +}; + +/** Borrowed UTF-8 preedit snapshot, valid until the next successful display poll. */ +struct yasl_private_composition_event { + const char *data; /**< Borrowed NUL-terminated UTF-8 snapshot. */ + size_t len; /**< Byte length excluding NUL. */ + int begin; /**< UTF-8 byte offset, or -1 if unavailable. */ + int end; /**< UTF-8 byte offset, or -1 if unavailable. */ + bool active; /**< Whether composition text is present. */ + uint64_t revision, generation; +}; + +/** Reported window-state change. */ +struct yasl_private_state_event { uint32_t flags; /**< Current YASL_PRIVATE_STATE_* flags. */ }; +/** Surface-drag target notification; position is in device pixels. */ +struct yasl_private_drag_target_event { + enum yasl_private_drag_event action; /**< Hover, leave, or drop. */ + struct yasl_private_vector2i pos; /**< Surface-relative device pixels. */ +}; +/** External drag hover notification; position is in device pixels. */ +struct yasl_private_drop_hover_event { + enum yasl_private_drop_event action; /**< Hover or leave. */ + struct yasl_private_vector2i pos; /**< Surface-relative device pixels. */ +}; + +/** Keyboard focus transition. */ +struct yasl_private_focus_event { + /** true on focus gain, false on loss. */ + bool focused; +}; + +/** New configured geometry for a surface. */ +struct yasl_private_resize_event { + /** Content size in device pixels and display scale. */ + struct yasl_private_geometry geometry; + /** true when scale_120 differs from the previous resize of this surface. */ + bool scale_changed; +}; + +/** Application message posted with yasl_private_post_event(). */ +struct yasl_private_user_event { + /** Application-defined message code. */ + uint32_t code; + /** Pointer passed through unchanged; ownership stays with the application. */ + void *payload; + /** Optional payload size, passed through unchanged. */ + size_t size; +}; + +/** One tagged event; read only the union member selected by type. */ +enum yasl_private_surface_flags { YASL_PRIVATE_SURFACE_CPU = 1u, YASL_PRIVATE_SURFACE_HIDDEN = 2u, YASL_PRIVATE_SURFACE_UNDECORATED = 4u, YASL_PRIVATE_SURFACE_FIXED_SIZE = 8u }; +enum yasl_private_pen_action { YASL_PRIVATE_PEN_ENTER, YASL_PRIVATE_PEN_LEAVE, YASL_PRIVATE_PEN_MOVE, YASL_PRIVATE_PEN_DOWN, YASL_PRIVATE_PEN_UP, YASL_PRIVATE_PEN_BUTTON, YASL_PRIVATE_PEN_CANCEL }; +enum yasl_private_pen_axis { YASL_PRIVATE_PEN_PRESSURE = 1u, YASL_PRIVATE_PEN_TILT = 2u, YASL_PRIVATE_PEN_ROTATION = 4u, YASL_PRIVATE_PEN_DISTANCE = 8u, YASL_PRIVATE_PEN_TANGENTIAL = 16u, YASL_PRIVATE_PEN_WHEEL = 32u }; +enum yasl_private_presenter { YASL_PRIVATE_PRESENTER_NONE, YASL_PRIVATE_PRESENTER_CPU, YASL_PRIVATE_PRESENTER_GL, YASL_PRIVATE_PRESENTER_VULKAN, YASL_PRIVATE_PRESENTER_METAL }; +struct yasl_private_pen_sample { + uint64_t device_id, tool_id; + double x, y, pressure, tilt_x, tilt_y, rotation, distance, tangential, wheel_degrees; + uint32_t axes_valid, axes_changed, buttons, changed_button; + bool in_contact; + unsigned tool, action; +}; +struct yasl_private_event { + /** Selects the valid union member. */ + enum yasl_private_event_type type; + /** Recipient surface, or NULL for display-wide events. Can differ from the + * polled surface for routed popup input. */ + struct yasl_surface *surface; + /** Monotonic timestamp in nanoseconds, same clock as yasl_private_loop_now_ns(). */ + uint64_t time_ns; + /** Increasing display-wide enqueue order; zero for standalone private queues. */ + uint64_t sequence; + /** Stable recipient identity; zero for display-wide events. */ + uint64_t surface_id; + union { + struct yasl_private_pen_sample pen; + struct { const char *data; size_t len; uint64_t revision, generation; size_t begin, end; } text_edit; + struct yasl_private_key_event key; /**< KEY */ + struct yasl_private_pointer_event pointer; /**< POINTER_* */ + struct yasl_private_text_event text; /**< TEXT, legacy PASTE, DROP */ + struct yasl_private_paste_event paste; /**< PASTE */ + struct yasl_private_composition_event composition; /**< COMPOSITION */ + struct yasl_private_state_event state; /**< WINDOW_STATE */ + struct yasl_private_drag_target_event drag; /**< DRAG */ + struct yasl_private_drop_hover_event drop; /**< DROP_HOVER */ + struct yasl_private_focus_event focus; /**< FOCUS */ + struct yasl_private_resize_event resize; /**< WINDOW_RESIZE */ + struct yasl_private_user_event user; /**< USER */ + }; +}; + +/* Ownership and threading + * All calls and callbacks run on the display's opening thread (main thread on + * Cocoa). Displays borrow supplied loops; destroy surfaces before displays, + * popups before parents, and displays before their supplied loop. + * Specs, strings, callback tables and drag listeners are copied during calls. + * Userdata is borrowed until callbacks are removed or the object/drag ends. + * Surface destruction is allowed from its callbacks; display/loop destruction + * during dispatch or callbacks is not supported. + */ + +/* Callbacks */ +/** Optional display notifications. NULL callback members are ignored. */ +struct yasl_private_display_cbs { + /** Application quit request; arrange orderly shutdown after dispatch returns. */ + void (*quit)(void *ud); + /** Application reopen/activation request, currently Cocoa. */ + void (*reopen)(void *ud); + /** Display connection lost; arrange cleanup after dispatch returns. */ + void (*lost)(void *ud); +}; + +/** Optional window/popup notifications, copied from yasl_private_window_spec.cbs or by +yasl_private_surface_set_callbacks(). +Callbacks may destroy their surface. They must then return without further use +of the surface or draw target. NULL members are ignored. */ +struct yasl_private_window_cbs { + /** Initialize the entire target and optionally present. Requested through schedule_redraw/flush. */ + void (*draw)(struct yasl_surface *s, struct yasl_private_frame *frame, void *ud); + /** Configured size/scale changed; reallocate application content and request a redraw. */ + void (*configure)(struct yasl_surface *s, struct yasl_private_geometry geometry, void *ud); + /** Window/popup close request; application is responsible for destruction. */ + void (*close)(struct yasl_surface *s, void *ud); + /** Reported yasl_private_surface_state bitmask changed; matches yasl_private_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_private_drag_event ev, + struct yasl_private_vector2i pos, void *ud); + /** External data-drag hover or leave; pos is in device pixels. */ + void (*drop)(struct yasl_surface *s, enum yasl_private_drop_event ev, + struct yasl_private_vector2i pos, void *ud); +}; + +/* Display Lifecycle */ +/** Runtime backend choice. AUTO selects a native backend and allows fallback. */ +enum yasl_private_backend { + YASL_PRIVATE_BACKEND_AUTO = 0, /**< Choose native backend with fallback. */ + YASL_PRIVATE_BACKEND_WAYLAND, /**< Require Wayland. */ + YASL_PRIVATE_BACKEND_X11, /**< Require X11. */ + YASL_PRIVATE_BACKEND_COCOA, /**< Require Cocoa. */ +}; + +/** Display options. Zero-initialize for automatic backend selection. */ +struct yasl_private_display_spec { + /** Copied application identifier, conventionally a reverse-domain UTF-8 string; NULL uses backend defaults. */ + const char *app_id; + /** Copied optional display callbacks; NULL disables them. */ + const struct yasl_private_display_cbs *cbs; + /** Borrowed display callback userdata. */ + void *ud; + + /** Zero/AUTO chooses native backend; explicit choices never fall back. */ + enum yasl_private_backend backend; +}; + +/* Return NULL and set errno if the requested backend cannot open. */ +/** + * Open a desktop connection and its event machinery. + * @param loop Borrowed loop, or NULL to create a display-owned loop. + * @param spec Copied options, or NULL for automatic backend selection and defaults. + * @return An owned display, or NULL with errno set. EINVAL means an invalid backend; + * ENOSYS means the requested backend was not built; ENODEV is the fallback when + * opening fails without a more specific platform error. Explicit choices never + * fall back. Close with yasl_private_display_close() after destroying its surfaces. + */ +struct yasl_private_display *yasl_private_display_open(struct yasl_private_loop *loop, + const struct yasl_private_display_spec *spec); +/** + * Read the selected backend. + * @param d Display, or NULL. + * @return The actual backend, or YASL_PRIVATE_BACKEND_AUTO for NULL. + */ +enum yasl_private_backend yasl_private_display_backend(const struct yasl_private_display *d); +/** + * Close a display and release its owned loop, if any. + * @param d Display to close, or NULL for no action. + * Destroy all surfaces first. Cancels an active drag and may invoke its end + * listener. Must not be called during dispatch or from a callback. + */ +void yasl_private_display_close(struct yasl_private_display *d); +/** + * Query currently available optional features. + * @param d Display, or NULL. + * @return A bitmask of yasl_private_display_cap values, or zero for NULL. + * Capabilities describe backend/server support, not attached input hardware or + * permission to perform an operation at any arbitrary moment. + */ +uint32_t yasl_private_display_caps(const struct yasl_private_display *d); +/* Process events, waiting at most timeout_ms: zero polls, negative waits. + * Pending input or redraw work returns immediately. Returns -1 on failure. */ +/** + * Receive events and run ready loop sources. + * @param d Display to dispatch. + * @param timeout_ms Maximum wait in milliseconds: zero polls, negative waits indefinitely. + * @return A nonnegative backend-specific work count on success, or -1 with errno + * set on failure (EINVAL for NULL, EIO if no specific backend error is available). + * Do not interpret the count as the number of queued input events. Pending input + * or runnable redraw work prevents waiting. Drain input and call + * yasl_private_display_flush() after updating application state. Do not dispatch recursively. + */ +int yasl_private_display_dispatch(struct yasl_private_display *d, int timeout_ms); +/** + * Draw scheduled surfaces and send pending display requests. + * @param d Display, or NULL for no action. + * May synchronously invoke draw and other callbacks. A paced surface may wait + * for its next display/compositor signal. This is not a GPU completion wait. + */ +void yasl_private_display_flush(struct yasl_private_display *d); +/** + * Read the latest modifier state known to the backend. + * @param d Display, or NULL. + * @return A bitmask of yasl_private_mod values, or zero for NULL. + * For an individual event use its own mods field instead. + */ +int yasl_private_display_modifiers(const struct yasl_private_display *d); +/** Double-click interval used when the system reports none. */ +#define YASL_PRIVATE_DOUBLE_CLICK_MS_DEFAULT 400 +/** + * Read the system's double-click interval. + * @param d Display, or NULL. + * @return Maximum milliseconds between the clicks of a double click, or + * YASL_PRIVATE_DOUBLE_CLICK_MS_DEFAULT when d is NULL or the system reports none. + * X11 reads the XSETTINGS Net/DoubleClickTime value. Wayland, and X11 without + * XSETTINGS, read the KDE or GNOME setting through the XDG Settings portal, + * which needs D-Bus and can wait briefly. Cocoa reads the system interval. + * The user can change it at any time; read it again when a window gains focus + * rather than on every click. + */ +int yasl_private_display_double_click_ms(const struct yasl_private_display *d); +/* Unsupported/invalid selections are no-ops, never clipboard aliases. */ +/** + * Offer a copy of a NUL-terminated UTF-8 string as a selection. + * @param d Display, or NULL for no action. + * @param sel YASL_PRIVATE_CLIPBOARD or YASL_PRIVATE_PRIMARY. + * @param text Text copied during this call; NULL does nothing. + * Unsupported selections do nothing. The void return does not confirm that the + * system accepted the offer; Wayland may require a recent input serial. + */ +void yasl_private_display_copy(struct yasl_private_display *d, enum yasl_private_selection sel, const char *text); +/** + * Request text delivery to a particular surface. + * @param d Display that owns target. + * @param sel YASL_PRIVATE_CLIPBOARD or YASL_PRIVATE_PRIMARY. + * @param target Destination surface; NULL or a surface on another display does nothing. + * Results arrive as YASL_PRIVATE_EVENT_PASTE, possibly during a later dispatch. No result + * is promised for unavailable data or a failed transfer. Destroying target cancels + * delivery. Unsupported selections do nothing. + */ +void yasl_private_display_paste(struct yasl_private_display *d, enum yasl_private_selection sel, struct yasl_surface *target); + +/* A successful drag invokes the copied listener's non-NULL end once, including + * cancel and display close. Failed starts do not invoke it. */ +/** + * Start a surface drag from a recent pointer press. + * @param d Display that owns origin. + * @param origin Origin surface. + * @param image Optional copied premultiplied ARGB8888 drag image, or NULL. + * @param l Optional copied completion listener, or NULL. + * @param ud Borrowed listener userdata until completion. + * @return true if started; false if unsupported, invalid, or not currently permitted. + * A successful start invokes a non-NULL end callback exactly once, including + * cancellation or display close. Failed starts do not invoke it. This API carries + * surfaces, not arbitrary MIME payloads; use application state to identify the item. + */ +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); +/** + * Cancel the active surface drag, if any. + * @param d Display, or NULL for no action. + * May invoke the end listener synchronously. + */ +void yasl_private_display_drag_cancel(struct yasl_private_display *d); + +/* Surface Lifecycle */ +/** Toplevel options. Zero-initialize, then supply a positive logical-pixel size. */ +struct yasl_private_window_spec { + unsigned flags; + bool external_gpu; + /** Copied UTF-8 title; NULL uses backend defaults. */ + const char *title; + /** Requested logical-pixel width and height; both must be positive. */ + union { + struct yasl_private_vector2i size_logical; /**< Requested logical-pixel extent. */ + struct yasl_private_vector2i size; /**< Migration alias for size_logical. */ + }; + /** Request initial fullscreen; actual state is reported asynchronously. */ + bool fullscreen; + /** Permit unpaced presentation where supported; a hint, not a guarantee. */ + bool tearing; + /** Copied optional window callbacks; NULL disables them. */ + const struct yasl_private_window_cbs *cbs; + /** Borrowed window callback userdata; ignored by the runner. */ + void *ud; + /** Borrowed application data, independent of callbacks and available in either loop style. */ + void *userdata; +}; + +/** Popup options, entirely in device pixels, including size. +The content rectangle excludes transparent margins/shadows used for placement. +An axis of content_size equal to zero extends to that axis of size. The offset +and resolved content size must fit in size. Anchor extents must be nonnegative. +Dimensions must be positive, at most 32767, and fit an INT32_MAX-byte buffer; +backends may impose further limits after scaling or resolving placement. */ +struct yasl_private_popup_spec { + unsigned anchor_edges, gravity, constraints; + bool positioner_explicit; + /** Full popup framebuffer extent in device pixels, including transparent margins. */ + struct yasl_private_vector2i size; + /** Rectangle in parent framebuffer coordinates, in device pixels. */ + struct yasl_private_rect2i anchor; + /** true prefers right of anchor; false prefers below. Placement may flip/slide. */ + bool beside; + /** Top-left content offset within the popup, in device pixels. */ + struct yasl_private_vector2i content_offset; + /** Content extent; zero per axis extends to the corresponding size edge. */ + struct yasl_private_vector2i content_size; +}; + +/** + * Create a resizable desktop window with CPU drawing targets. + * @param d Owning display. + * @param spec Required copied options; size is in logical pixels. + * @return An owned surface, or NULL with errno set. EINVAL means invalid options, + * including a nonpositive dimension, a dimension above 32767, or a buffer above + * INT32_MAX bytes. ENODEV is the fallback backend failure. Actual configured + * size may differ from the request. The callback table in spec->cbs is copied; + * spec->ud stays borrowed until callbacks are replaced or the surface is destroyed. + * This window's callbacks cannot run before creation returns. Use + * yasl_private_surface_geometry() for initial geometry; an initial configure callback + * is not guaranteed. Schedule an initial redraw after setting up application state. + * On a display owned by yasl_private_run(), the runner installs its own callbacks instead; + * use yasl_private_app_desc.event and yasl_private_app_desc.frame for application notifications. + */ +struct yasl_surface *yasl_private_surface_create_toplevel(struct yasl_private_display *d, + const struct yasl_private_window_spec *spec); +/** + * Create an owned popup attached to a parent surface. + * @param parent Borrowed parent; destroy the popup before the parent. + * @param spec Required copied options, entirely in device pixels. + * @return A popup, or NULL with errno set: EINVAL for invalid options, ENOTSUP + * for missing popup support, or a backend error (ENODEV if none was supplied). + * Wayland requires a recent input serial for its popup grab. The backend may + * flip or slide placement to fit the screen. See yasl_private_popup_spec. + */ +struct yasl_surface *yasl_private_surface_create_popup(struct yasl_surface *parent, + const struct yasl_private_popup_spec *spec); +/** + * Update a popup's geometry and placement. + * @param popup An existing popup. + * @param spec Required copied replacement options, in device pixels. + * @return true on success; false with errno set: EINVAL for invalid options, + * ENOTSUP for missing popup support, or a backend error (ENODEV if unspecified). + * Older Wayland popup protocol versions can reject repositioning. + */ +bool yasl_private_surface_popup_reposition(struct yasl_surface *popup, + const struct yasl_private_popup_spec *spec); +/** + * Destroy a surface and cancel its surface timers and pending input. + * @param s Surface, or NULL for no action. + * Destroy child popups first. May be called from a surface callback; return from + * that callback without further use of the surface or its draw target. Every + * pointer or native handle obtained from this surface becomes invalid. + */ +void yasl_private_surface_destroy(struct yasl_surface *s); +/** + * Get the display that owns a surface. + * @param s Surface, or NULL. + * @return A borrowed display, or NULL. + */ +struct yasl_private_display *yasl_private_surface_display(const struct yasl_surface *s); + +/** + * Replace the surface's callback table and userdata. + * @param s Surface. + * @param cbs Table copied by value; NULL removes all callbacks. NULL members are ignored. + * @param ud Borrowed userdata passed to the installed callbacks. + * @return true if replaced; false with EINVAL for NULL, EBUSY on a runner-owned surface. + * This call only replaces callbacks; it does not invoke them. + * Use yasl_private_surface_geometry() to read current geometry; do not rely on receiving + * an initial configure callback when registering. + */ +bool yasl_private_surface_set_callbacks(struct yasl_surface *s, + const struct yasl_private_window_cbs *cbs, void *ud); +/** + * Replace the window title with copied UTF-8 text. + * @param s Surface. + * @param title NUL-terminated UTF-8 title. Pass an empty string to clear it. + * Backends may truncate the title. + */ +void yasl_private_surface_set_title(struct yasl_surface *s, const char *title); +/** + * Request a future draw callback, coalescing repeated requests. + * @param s Surface, or NULL for no action. + * Call yasl_private_display_flush() to service the request. Schedule again from draw to + * animate. Without YASL_PRIVATE_CAP_FRAME_PACING, throttle animation with a timer. + */ +void yasl_private_surface_schedule_redraw(struct yasl_surface *s); +/* Present exactly the target supplied to the current draw callback, at most + * once. Targets expire when that callback returns or the surface is destroyed. + * damage is required: n must be -1 or 0..YASL_PRIVATE_DAMAGE_MAX. Rectangles must have + * nonnegative extents; they are clipped to the target. Invalid input returns + * false without consuming the frame. See yasl/native.h for native rendering. */ +/** + * Submit the current draw target at most once. + * @param s Surface currently being drawn. + * @param fb Exact target from the current draw callback, including its generation. + * @param damage Required damage hint: n is -1 for all pixels or 0..YASL_PRIVATE_DAMAGE_MAX. + * @return true if accepted; false for invalid, stale, already presented, or failed targets. + * Rectangles must have nonnegative extents and are clipped to the target. Invalid + * input does not consume the frame. All pixels must be initialized even with + * partial damage. The backend may upload all pixels. Success records submission, + * not confirmed scanout. The target expires when draw returns or s is destroyed. + */ +bool yasl_private_surface_present(struct yasl_surface *s, const struct yasl_private_framebuffer *fb, + const struct yasl_private_damage_list *damage); +/** + * Migration adapter: consume one input event for a surface from the display FIFO. + * Popup input can belong to the root queue; inspect ev->surface. Returns false + * for empty/invalid arguments, leaving ev unchanged. Strings remain borrowed + * until the next successful display or surface poll, or display close. Pending + * recipient events are canceled on destruction. Prefer yasl_private_display_poll_event() + * to receive window, composition, and display events as well as input. This + * input-only adapter discards pending non-input events across the display + * (legacy callbacks already delivered lifecycle changes). Do not mix + * the two polling styles in one application. Use the display thread. + */ +bool yasl_private_surface_poll_event(struct yasl_surface *s, struct yasl_private_event *ev); +/** + * Decode the next local file path from a YASL_PRIVATE_EVENT_DROP payload. + * @param cursor Required in/out position in the NUL-terminated list. Start it + * at event.text.data; a NULL *cursor returns zero. + * @param out Output for the NUL-terminated path, or NULL when cap is zero. + * @param cap Size of out in bytes. + * @return Path length in bytes, excluding the NUL, or zero when no paths remain. + * When the result is less than cap, out holds the path and *cursor moves past + * it. Otherwise out and *cursor are unchanged; call again with a buffer of at + * least the result plus one. + * Accepts file:///path, file:/path, and file://host/path when host is + * localhost or this machine's name. Percent escapes decode to raw bytes, and + * a '?' or '#' ends the path. Skips blank lines, comments, other schemes, + * other hosts, and lines with a malformed escape or an escaped NUL. May be + * called from any thread. + */ +size_t yasl_private_drop_next_path(const char **cursor, char *out, size_t cap); +/** + * Copy the last frame presented from the surface's CPU target. + * @param s Surface. + * @param out Output for size->x * size->y premultiplied ARGB8888 pixels with + * tightly packed rows, or NULL when cap is zero. + * @param cap Capacity of out in pixels. + * @param size Required output: the frame extent in device pixels, or zero when + * no frame is available. + * @return true if copied. false leaves out unchanged; when *size is nonzero, + * out was too small, so allocate size->x * size->y pixels and call again. + * A frame is available from a successful yasl_private_surface_present() until the + * next draw callback starts, and again once that draw presents. After a draw + * that does not present or a resize that replaces the target, no frame may be + * available; native rendering never has one. Call outside draw callbacks. This copies YASL's frame, not + * the composited screen: decorations, other windows, and color conversion are + * not included. + */ +bool yasl_private_surface_read_pixels(struct yasl_surface *s, uint32_t *out, size_t cap, + struct yasl_private_vector2i *size); +/** + * Select absolute or relative (locked) pointer input for a surface. + * @param s Surface. + * @param mode YASL_PRIVATE_POINTER_NORMAL or YASL_PRIVATE_POINTER_RELATIVE. + * @return true if the mode is supported; false leaves the current mode. + * RELATIVE hides the cursor and locks it while the surface is focused; + * POINTER_MOTION then carries unbounded delta. Returning to NORMAL restores + * the cursor selected with yasl_private_surface_set_cursor(). RELATIVE is supported on + * Wayland compositors with the relative-pointer and pointer-constraints + * protocols; X11 and Cocoa return false. + */ +bool yasl_private_surface_set_pointer_mode(struct yasl_surface *s, enum yasl_private_pointer_mode mode); + +/** + * Consume one event from the display-wide FIFO without waiting. + * Returns false for an empty queue or invalid arguments, leaving ev unchanged. + * Strings remain valid until the next successful poll or display close. + * Surface destruction cancels undelivered events, not the last polled payload. + * Use the display thread. Do not mix display and surface polling in one app. + */ +bool yasl_private_display_poll_event(struct yasl_private_display *d, struct yasl_private_event *ev); + +/** Stable nonzero identity within a display; zero for NULL. Use on the display thread. */ +uint64_t yasl_private_surface_id(const struct yasl_surface *s); +/** Store borrowed application data independently of callback userdata. */ +void yasl_private_surface_set_userdata(struct yasl_surface *s, void *userdata); +/** Read borrowed application data, or NULL for NULL; use on the display thread. */ +void *yasl_private_surface_userdata(const struct yasl_surface *s); + +/** Thread-safe message descriptor. Zero target_id means display-wide. */ +struct yasl_private_message { + uint64_t target_id; /**< Surface identity, or zero for the display. */ + uint32_t code; /**< Application-defined message code. */ + void *payload; /**< Borrowed until release; passed through unchanged. */ + size_t size; /**< Application-defined payload length. */ + /** Optional cleanup on display thread after delivery/cancellation, exactly once. + * May post messages, but must not dispatch, poll, or destroy YASL objects. */ + void (*release)(void *payload); +}; +/** + * Copy and enqueue a message from any thread; the display must stay alive. + * Stop producer threads before closing the display. On success release runs + * once after consumption or cancellation. On failure ownership stays with the + * caller; errno is EINVAL, ENOENT (retired identity), ENOMEM, or EOVERFLOW. + */ +bool yasl_private_display_post(struct yasl_private_display *d, const struct yasl_private_message *message); + +/** + * Present the active frame once. NULL/0 means full damage; non-NULL/0 means none. + * Validate all rectangles; excess rectangles fall back to full damage. + * Returns false with EINVAL for invalid/stale targets, EALREADY for a consumed + * frame, or errno from the backend (EIO if unspecified). + * Frame and pixels expire at callback return; use the display thread. + */ +bool yasl_private_frame_present(struct yasl_private_frame *frame, + const struct yasl_private_rect2i *rects, size_t count); +/** Coalesce a redraw request; use on the display thread. NULL is ignored. */ +void yasl_private_surface_request_frame(struct yasl_surface *s); +/** Set drawing policy for either loop style. Returns false with errno on failure. */ +bool yasl_private_surface_set_pacing(struct yasl_surface *s, yasl_private_pacing_mode mode); + +/** Request a positive logical-pixel size. Success means issued, not configured. */ +bool yasl_private_surface_set_size(struct yasl_surface *s, struct yasl_private_vector2i size_logical); +/** Request fullscreen state; returns acceptance, not confirmation. */ +bool yasl_private_surface_set_fullscreen(struct yasl_surface *s, bool enabled); +/** Request maximized state; returns acceptance, not confirmation. */ +bool yasl_private_surface_set_maximized(struct yasl_surface *s, bool enabled); +/** Request minimization. Window requests set errno on failure; display thread only. */ +bool yasl_private_surface_minimize(struct yasl_surface *s); + +/** Offer copied UTF-8 text. Returns false with errno if invalid/unsupported/failed. */ +bool yasl_private_clipboard_set(struct yasl_private_display *d, enum yasl_private_selection selection, + const char *text); +/** + * Request text; returns a nonzero ID or zero with errno. One PASTE completion + * follows each accepted request unless its surface/display is destroyed. + * Inspect paste.status even when data is empty. Use the display thread. + */ +uint64_t yasl_private_clipboard_request(struct yasl_surface *s, enum yasl_private_selection selection); + +/* Current device-pixel size and scale, matching the configure callback. */ +/** + * Read current device-pixel size and scale together. + * @param s Surface, or NULL. + * @return Geometry matching configure callbacks; NULL gives zero size and unit scale. + */ +struct yasl_private_geometry yasl_private_surface_geometry(const struct yasl_surface *s); +/** + * Read current window state. + * @param s Surface, or NULL. + * @return A bitmask of yasl_private_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. + */ +uint32_t yasl_private_surface_state(const struct yasl_surface *s); +/* Timing uses monotonic nanoseconds. Check source before treating timestamps + * as presentation confirmation. refresh/next_present are estimates. Without + * YASL_PRIVATE_CAP_FRAME_PACING, redraws run on flush without display synchronization. + * Tearing may bypass pacing even when the capability is present. */ +/** + * Read frame timing estimates and their provenance. + * @param s Surface, or NULL. + * @return Current timing, or a zero-initialized result for NULL. + * Timestamps use monotonic nanoseconds. Inspect source before treating a timestamp + * as confirmation of presentation. Refresh and next-present values are estimates. + */ +struct yasl_private_timing yasl_private_surface_timing(const struct yasl_surface *s); +/** + * Get a nondecreasing animation timestamp aligned to estimated refresh. + * @param s Surface, or NULL. + * @param now Current monotonic nanoseconds, usually from yasl_private_loop_now_ns(). + * @return Animation timestamp, or zero for NULL. Before timing is known, returns now. + * Use this to advance animations; it is not presentation feedback. + */ +uint64_t yasl_private_surface_frame_time_ns(struct yasl_surface *s, uint64_t now); +/* Set the mouse and tablet cursor for this surface, including future entries. + * YASL_PRIVATE_CURSOR_HIDDEN stays hidden until another cursor is selected. */ +/** + * Select the surface's system mouse and tablet cursor. + * @param s Surface. + * @param cursor Cursor shape; YASL_PRIVATE_CURSOR_HIDDEN hides the cursor without disabling input. + * The choice persists across motion and re-entry until another shape is selected. + */ +void yasl_private_surface_set_cursor(struct yasl_surface *s, enum yasl_private_cursor cursor); +/** + * Set a backend window background hint. + * @param s Surface. + * @param bg Packed 0xAARRGGBB color; backends may ignore alpha or the entire hint. + * Does not initialize framebuffer pixels or replace drawing. + */ +void yasl_private_surface_set_background_color(struct yasl_surface *s, uint32_t bg); + +/* IME Support: preedit is borrowed until the next dispatch or destruction. */ +/** + * Read the current uncommitted UTF-8 composition text. + * @param s Surface, or NULL. + * @return Borrowed NUL-terminated text, or NULL if absent/unsupported. + * Copy to retain across the next dispatch or surface destruction. Composition + * changes can schedule a redraw. Insert committed YASL_PRIVATE_EVENT_TEXT into documents, + * not preedit text. + */ +const char *yasl_private_surface_ime_preedit(const struct yasl_surface *s); +/** + * Read the cursor/selection range in the preedit string. + * @param s Surface. + * @param begin Required output UTF-8 byte offset for the range start. + * @param end Required output UTF-8 byte offset for the range end. + * @return true if available; false without changing outputs otherwise. + * Currently available on Wayland; Cocoa preedit text has no range result. + */ +bool yasl_private_surface_ime_preedit_cursor(const struct yasl_surface *s, int *begin, int *end); +/** + * Place the composition candidate window near the application caret. + * @param s Surface. + * @param caret Caret rectangle relative to the surface, in device pixels. + * Update after caret motion or scale changes. Unsupported backends ignore it. + */ +void yasl_private_surface_set_ime_caret(struct yasl_surface *s, struct yasl_private_rect2i caret); + +/* Client-Side Decorations (CSD) Actions */ +/** + * Ask the window system to begin an interactive move. + * @param s Toplevel surface, normally during handling of a pointer press. + * @return true if the request was issued; false if unsupported or not permitted. + * A recent input serial may be required. Success does not confirm a final position. + */ +bool yasl_private_surface_start_interactive_move(struct yasl_surface *s); +/** + * Ask the window system to begin an interactive resize. + * @param s Toplevel surface, normally during handling of a pointer press. + * @param edge One nonzero yasl_private_resize_edge enumerator. + * @return true if issued; false for an invalid edge, missing support, or rejected request. + */ +bool yasl_private_surface_start_interactive_resize(struct yasl_surface *s, enum yasl_private_resize_edge edge); +/** + * Ask the window system to display its window management menu. + * @param s Toplevel surface. + * @param pos Surface-relative position in device pixels. + * @return true if issued; false if unsupported or not currently permitted. + */ +bool yasl_private_surface_show_window_menu(struct yasl_surface *s, struct yasl_private_vector2i pos); + +/* Drag Support */ +/** + * Attach a toplevel surface to the current surface drag. + * @param s Surface to carry; check YASL_PRIVATE_CAP_DRAG_CARRY first. + * @param offset Device-pixel offset of the pointer within the carried surface. + * @return true if accepted; false if unsupported or no compatible drag is active. + */ +bool yasl_private_surface_drag_carry(struct yasl_surface *s, struct yasl_private_vector2i offset); +/** + * Place a toplevel surface relative to the current pointer. + * @param s Surface; check YASL_PRIVATE_CAP_PLACE_WINDOW first. + * @param at Device-pixel position within the surface to place under the pointer. + * @return true if issued; false if unsupported or unavailable. + */ +bool yasl_private_surface_place_under_pointer(struct yasl_surface *s, struct yasl_private_vector2i at); + +#ifdef __cplusplus +} +#endif + +#endif /* YASL_PRIVATE_H */ diff --git a/src/common/uri.c b/src/common/uri.c index d836943..cc539a1 100644 --- a/src/common/uri.c +++ b/src/common/uri.c @@ -1,5 +1,5 @@ #define _POSIX_C_SOURCE 200809L -#include "yasl/yasl.h" +#include "common/types.h" #include <string.h> #include <strings.h> @@ -55,7 +55,7 @@ static size_t decode_path(const char *line, size_t len, char *out) return n; } -size_t yasl_drop_next_path(const char **cursor, char *out, size_t cap) +size_t yasl_private_drop_next_path(const char **cursor, char *out, size_t cap) { if (!cursor || !*cursor || (!out && cap)) return 0; const char *p = *cursor; diff --git a/src/common/utf8.c b/src/common/utf8.c index 78a5d8e..2fb737a 100644 --- a/src/common/utf8.c +++ b/src/common/utf8.c @@ -11,7 +11,7 @@ #define unlikely(x) __builtin_expect(!!(x), 0) #endif -bool yasl_utf8_validate(const char *str, size_t len) +bool yasl_private_utf8_validate(const char *str, size_t len) { if (!str) return len == 0; const unsigned char *p = (const unsigned char *)str; @@ -54,7 +54,7 @@ bool yasl_utf8_validate(const char *str, size_t len) return true; } -char *yasl_utf8_from_latin1(const char *str, size_t len, size_t *out_len) +char *yasl_private_utf8_from_latin1(const char *str, size_t len, size_t *out_len) { if (len > (SIZE_MAX - 1) / 2) return NULL; char *out = malloc(len * 2 + 1); @@ -73,7 +73,7 @@ char *yasl_utf8_from_latin1(const char *str, size_t len, size_t *out_len) return out; } -char *yasl_utf8_to_latin1(const char *str, size_t *out_len) +char *yasl_private_utf8_to_latin1(const char *str, size_t *out_len) { char *out = malloc(strlen(str) + 1); if (!out) return NULL; @@ -94,11 +94,11 @@ char *yasl_utf8_to_latin1(const char *str, size_t *out_len) return out; } -char *yasl_utf8_sanitize_dup(const char *str) +char *yasl_private_utf8_sanitize_dup(const char *str) { if (unlikely(!str)) return NULL; size_t len = strlen(str); - if (likely(yasl_utf8_validate(str, len))) { + if (likely(yasl_private_utf8_validate(str, len))) { char *out = malloc(len + 1); if (unlikely(!out)) return NULL; memcpy(out, str, len + 1); diff --git a/src/common/utf8.h b/src/common/utf8.h index f31c99d..5ffdb15 100644 --- a/src/common/utf8.h +++ b/src/common/utf8.h @@ -1,14 +1,14 @@ -#ifndef YASL_COMMON_UTF8_H -#define YASL_COMMON_UTF8_H +#ifndef YASL_PRIVATE_COMMON_UTF8_H +#define YASL_PRIVATE_COMMON_UTF8_H #include <stdbool.h> #include <stddef.h> -bool yasl_utf8_validate(const char *str, size_t len); -char *yasl_utf8_sanitize_dup(const char *str); +bool yasl_private_utf8_validate(const char *str, size_t len); +char *yasl_private_utf8_sanitize_dup(const char *str); /* Owned conversions for X11 STRING interoperability. to_latin1 rejects text * outside Latin-1; byte lengths exclude the allocated NUL terminator. */ -char *yasl_utf8_from_latin1(const char *str, size_t len, size_t *out_len); -char *yasl_utf8_to_latin1(const char *str, size_t *out_len); +char *yasl_private_utf8_from_latin1(const char *str, size_t len, size_t *out_len); +char *yasl_private_utf8_to_latin1(const char *str, size_t *out_len); #endif diff --git a/src/common/vulkan.c b/src/common/vulkan.c new file mode 100644 index 0000000..1bca415 --- /dev/null +++ b/src/common/vulkan.c @@ -0,0 +1,87 @@ +#if YASL_HAS_WAYLAND +#define VK_USE_PLATFORM_WAYLAND_KHR +#endif +#if YASL_HAS_X11 +#define VK_USE_PLATFORM_XLIB_KHR +#endif +#if YASL_HAS_COCOA +#define VK_USE_PLATFORM_METAL_EXT +#endif +#include "yasl/vulkan.h" +#include "public_internal.h" +#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) { get_proc=NULL; portability=false; } +bool yasl_vulkan_init(PFN_vkGetInstanceProcAddr loader) +{ + if(!loader)return yasl_public_fail(EINVAL); + get_proc=loader;portability=false; + PFN_vkEnumerateInstanceExtensionProperties enumerate=(PFN_vkEnumerateInstanceExtensionProperties)get_proc(VK_NULL_HANDLE,"vkEnumerateInstanceExtensionProperties"); + if(!enumerate)return yasl_public_fail(ENOTSUP); + uint32_t count=0;VkResult r=enumerate(NULL,&count,NULL); + if(r!=VK_SUCCESS)return yasl_public_fail(EIO); + VkExtensionProperties *ext=count?calloc(count,sizeof(*ext)):NULL; + if(count&&!ext)return yasl_public_fail(ENOMEM); + r=enumerate(NULL,&count,ext); + if(r==VK_SUCCESS)for(uint32_t i=0;i<count;i++)if(!strcmp(ext[i].extensionName,"VK_KHR_portability_enumeration"))portability=true; + free(ext);return r==VK_SUCCESS||yasl_public_fail(EIO); +} +const char *const *yasl_vulkan_instance_extensions(const yasl_surface *s,uint32_t *count) +{ + static const char *wayland[]={"VK_KHR_surface","VK_KHR_wayland_surface"}; + static const char *x11[]={"VK_KHR_surface","VK_KHR_xlib_surface"}; + static const char *metal[]={"VK_KHR_surface","VK_EXT_metal_surface","VK_KHR_portability_enumeration"}; + if(count)*count=0; + if(!s||!count||s->presenter==YASL_PRIVATE_PRESENTER_CPU||s->presenter==YASL_PRIVATE_PRESENTER_GL){yasl_public_fail(EINVAL);return NULL;} + switch(s->display->ops->type) { + case YASL_PRIVATE_BACKEND_WAYLAND:*count=2;return wayland; + case YASL_PRIVATE_BACKEND_X11:*count=2;return x11; + case YASL_PRIVATE_BACKEND_COCOA:*count=portability?3:2;return metal; + default:yasl_public_fail(ENOTSUP);return NULL; + } +} +VkInstanceCreateFlags yasl_vulkan_instance_flags(const yasl_surface *s) +{ return s&&s->display->ops->type==YASL_PRIVATE_BACKEND_COCOA&&portability?VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR:0; } +VkResult yasl_vulkan_create_surface(yasl_surface *s,VkInstance instance,const VkAllocationCallbacks *allocator,VkSurfaceKHR *out) +{ + if(out)*out=VK_NULL_HANDLE; + if(!yasl_public_surface(s,false)||!instance||!out||!get_proc){yasl_public_fail(EINVAL);return VK_ERROR_INITIALIZATION_FAILED;} + if(s->presenter!=YASL_PRIVATE_PRESENTER_NONE){yasl_public_fail(EBUSY);return VK_ERROR_NATIVE_WINDOW_IN_USE_KHR;} + VkResult result=VK_ERROR_EXTENSION_NOT_PRESENT; + switch(s->display->ops->type) { +#if YASL_HAS_WAYLAND + case YASL_PRIVATE_BACKEND_WAYLAND: { + PFN_vkCreateWaylandSurfaceKHR create=(PFN_vkCreateWaylandSurfaceKHR)get_proc(instance,"vkCreateWaylandSurfaceKHR"); + if(create) { VkWaylandSurfaceCreateInfoKHR info={.sType=VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, + .display=yasl_wayland_display(s),.surface=yasl_wayland_surface(s)};result=create(instance,&info,allocator,out); }break; + } +#endif +#if YASL_HAS_X11 + case YASL_PRIVATE_BACKEND_X11: { + PFN_vkCreateXlibSurfaceKHR create=(PFN_vkCreateXlibSurfaceKHR)get_proc(instance,"vkCreateXlibSurfaceKHR"); + if(create) { VkXlibSurfaceCreateInfoKHR info={.sType=VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, + .dpy=yasl_x11_display(s),.window=(Window)yasl_x11_window(s)};result=create(instance,&info,allocator,out); }break; + } +#endif +#if YASL_HAS_COCOA + case YASL_PRIVATE_BACKEND_COCOA: { + PFN_vkCreateMetalSurfaceEXT create=(PFN_vkCreateMetalSurfaceEXT)get_proc(instance,"vkCreateMetalSurfaceEXT"); + if(create&&s->display->ops->surface_get_metal_layer) { VkMetalSurfaceCreateInfoEXT info={.sType=VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, + .pLayer=s->display->ops->surface_get_metal_layer(s)};result=create(instance,&info,allocator,out); }break; + } +#endif + default:break; + } + if(result==VK_SUCCESS)s->presenter=YASL_PRIVATE_PRESENTER_VULKAN; + else yasl_public_fail(result==VK_ERROR_EXTENSION_NOT_PRESENT?ENOTSUP:EIO); + return result; +} diff --git a/src/platform/cocoa/api.h b/src/platform/cocoa/api.h index eaacc01..4a22410 100644 --- a/src/platform/cocoa/api.h +++ b/src/platform/cocoa/api.h @@ -1,40 +1,40 @@ -#ifndef YASL_COCOA_API_H -#define YASL_COCOA_API_H +#ifndef YASL_PRIVATE_COCOA_API_H +#define YASL_PRIVATE_COCOA_API_H -#include "yasl/yasl.h" +#include "common/types.h" struct cocoa_display; struct cocoa_surface; -struct cocoa_display *cocoa_display_open(struct yasl_loop *loop, const struct yasl_display_spec *spec); +struct cocoa_display *cocoa_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec); void cocoa_display_close(struct cocoa_display *d); uint32_t cocoa_display_caps(const struct cocoa_display *d); int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms); void cocoa_display_flush(struct cocoa_display *d); int cocoa_display_modifiers(const struct cocoa_display *d); -void cocoa_display_copy(struct cocoa_display *d, enum yasl_selection sel, const char *text); -void cocoa_display_paste(struct cocoa_display *d, enum yasl_selection sel, struct cocoa_surface *target); -bool cocoa_display_drag_start(struct cocoa_display *d, struct cocoa_surface *origin, const struct yasl_drag_image *image, const struct yasl_drag_listener *l, void *ud); +void cocoa_display_copy(struct cocoa_display *d, enum yasl_private_selection sel, const char *text); +void cocoa_display_paste(struct cocoa_display *d, enum yasl_private_selection sel, struct cocoa_surface *target); +bool cocoa_display_drag_start(struct cocoa_display *d, struct cocoa_surface *origin, const struct yasl_private_drag_image *image, const struct yasl_private_drag_listener *l, void *ud); struct cocoa_surface *cocoa_surface_create_toplevel(struct cocoa_display *d, - const struct yasl_window_spec *spec, void *graphics_device); -struct cocoa_surface *cocoa_surface_create_popup(struct cocoa_surface *parent, const struct yasl_popup_spec *spec); -bool cocoa_surface_popup_reposition(struct cocoa_surface *popup, const struct yasl_popup_spec *spec); + const struct yasl_private_window_spec *spec, void *graphics_device); +struct cocoa_surface *cocoa_surface_create_popup(struct cocoa_surface *parent, const struct yasl_private_popup_spec *spec); +bool cocoa_surface_popup_reposition(struct cocoa_surface *popup, const struct yasl_private_popup_spec *spec); void cocoa_surface_destroy(struct cocoa_surface *s); -void cocoa_surface_set_callbacks(struct cocoa_surface *s, const struct yasl_window_cbs *cbs, void *ud); +void cocoa_surface_set_callbacks(struct cocoa_surface *s, const struct yasl_private_window_cbs *cbs, void *ud); void cocoa_surface_set_title(struct cocoa_surface *s, const char *title); void cocoa_surface_schedule_redraw(struct cocoa_surface *s); -bool cocoa_surface_present(struct cocoa_surface *s, const struct yasl_framebuffer *fb, const struct yasl_damage_list *damage); -bool cocoa_surface_poll_event(struct cocoa_surface *s, struct yasl_event *ev); -struct yasl_geometry cocoa_surface_geometry(const struct cocoa_surface *s); -struct yasl_timing cocoa_surface_timing(const struct cocoa_surface *s); -void cocoa_surface_set_cursor(struct cocoa_surface *s, enum yasl_cursor cursor); +bool cocoa_surface_present(struct cocoa_surface *s, const struct yasl_private_framebuffer *fb, const struct yasl_private_damage_list *damage); +bool cocoa_surface_poll_event(struct cocoa_surface *s, struct yasl_private_event *ev); +struct yasl_private_geometry cocoa_surface_geometry(const struct cocoa_surface *s); +struct yasl_private_timing cocoa_surface_timing(const struct cocoa_surface *s); +void cocoa_surface_set_cursor(struct cocoa_surface *s, enum yasl_private_cursor cursor); void cocoa_surface_set_background_color(struct cocoa_surface *s, uint32_t bg); bool cocoa_surface_ime_preedit_cursor(const struct cocoa_surface *s, int *begin, int *end); -void cocoa_surface_set_ime_caret(struct cocoa_surface *s, struct yasl_rect2i caret); +void cocoa_surface_set_ime_caret(struct cocoa_surface *s, struct yasl_private_rect2i caret); bool cocoa_surface_start_interactive_move(struct cocoa_surface *s); -bool cocoa_surface_start_interactive_resize(struct cocoa_surface *s, enum yasl_resize_edge edge); -bool cocoa_surface_show_window_menu(struct cocoa_surface *s, struct yasl_vector2i pos); -bool cocoa_surface_drag_carry(struct cocoa_surface *s, struct yasl_vector2i offset); -bool cocoa_surface_place_under_pointer(struct cocoa_surface *s, struct yasl_vector2i at); +bool cocoa_surface_start_interactive_resize(struct cocoa_surface *s, enum yasl_private_resize_edge edge); +bool cocoa_surface_show_window_menu(struct cocoa_surface *s, struct yasl_private_vector2i pos); +bool cocoa_surface_drag_carry(struct cocoa_surface *s, struct yasl_private_vector2i offset); +bool cocoa_surface_place_under_pointer(struct cocoa_surface *s, struct yasl_private_vector2i at); #endif diff --git a/src/platform/cocoa/desktop.m b/src/platform/cocoa/desktop.m index 526cbb9..76aa551 100644 --- a/src/platform/cocoa/desktop.m +++ b/src/platform/cocoa/desktop.m @@ -2,7 +2,7 @@ #import <UserNotifications/UserNotifications.h> #include "common/desktop.h" -#include "yasl/desktop.h" +#include "common/private_desktop.h" #include <CoreFoundation/CoreFoundation.h> #include <limits.h> @@ -94,11 +94,11 @@ uint32_t desktop_appearance(void) NSAppearanceName name = [NSApp.effectiveAppearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]]; if ([name isEqualToString:NSAppearanceNameDarkAqua]) - flags |= YASL_APPEARANCE_DARK; + flags |= YASL_PRIVATE_APPEARANCE_DARK; else if (name) - flags |= YASL_APPEARANCE_LIGHT; + flags |= YASL_PRIVATE_APPEARANCE_LIGHT; if ([NSWorkspace sharedWorkspace].accessibilityDisplayShouldIncreaseContrast) - flags |= YASL_APPEARANCE_HIGH_CONTRAST; + flags |= YASL_PRIVATE_APPEARANCE_HIGH_CONTRAST; return flags; } } diff --git a/src/platform/cocoa/keymap.c b/src/platform/cocoa/keymap.c index b81c364..c43837c 100644 --- a/src/platform/cocoa/keymap.c +++ b/src/platform/cocoa/keymap.c @@ -1,7 +1,7 @@ #include "keymap.h" #include "keycodes.h" -enum yasl_key yasl_key_from_cocoa(uint32_t code) +enum yasl_key yasl_private_key_from_cocoa(uint32_t code) { switch (code) { case kVK_ANSI_A: return YASL_KEY_A; diff --git a/src/platform/cocoa/keymap.h b/src/platform/cocoa/keymap.h index a5394e2..792b10d 100644 --- a/src/platform/cocoa/keymap.h +++ b/src/platform/cocoa/keymap.h @@ -1,8 +1,8 @@ -#ifndef YASL_COCOA_KEYMAP_H -#define YASL_COCOA_KEYMAP_H +#ifndef YASL_PRIVATE_COCOA_KEYMAP_H +#define YASL_PRIVATE_COCOA_KEYMAP_H #include "yasl/keycodes.h" -enum yasl_key yasl_key_from_cocoa(uint32_t code); +enum yasl_key yasl_private_key_from_cocoa(uint32_t code); #endif diff --git a/src/platform/cocoa/window.m b/src/platform/cocoa/window.m index 60b20b0..7be78fd 100644 --- a/src/platform/cocoa/window.m +++ b/src/platform/cocoa/window.m @@ -1,10 +1,12 @@ #import <AppKit/AppKit.h> #import <Metal/Metal.h> #import <QuartzCore/QuartzCore.h> +#import <OpenGL/OpenGL.h> #include "platform/cocoa/api.h" #include "common/backend.h" -#include "yasl/native/cocoa.h" +#include "common/public_internal.h" +#include "common/private_native_cocoa.h" #include "common/timing.h" #include "common/loop.h" #include "platform/cocoa/keycodes.h" @@ -18,8 +20,11 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <dlfcn.h> #include <sys/event.h> #include <unistd.h> +#include <objc/message.h> +#include <objc/runtime.h> #define LINK_IDLE_FRAMES 8 /* Application-defined event subtype that only wakes a waiting dispatch. */ @@ -63,6 +68,7 @@ struct cocoa_surface { YaslWindowController *controller; YaslView *view; CAMetalLayer *layer; + NSOpenGLContext *gl_context; id<MTLCommandQueue> software_queue; id<MTLBuffer> software_buffer; NSUInteger software_buffer_capacity; @@ -70,16 +76,19 @@ struct cocoa_surface { struct cocoa_surface *parent; bool is_popup; - struct yasl_vector2i content_offset; + struct yasl_private_vector2i content_offset; - const struct yasl_window_cbs *cbs; + const struct yasl_private_window_cbs *cbs; void *ud; uint64_t frames_skipped; char *preedit; - struct yasl_rect2i ime_caret; + size_t preedit_begin, preedit_end; + NSUInteger marked_utf16_location; + uint64_t preedit_revision, preedit_generation; + struct yasl_private_rect2i ime_caret; - struct yasl_vector2i size; + struct yasl_private_vector2i size; uint32_t scale_120; uint32_t bg_color; uint32_t *pixels; @@ -87,11 +96,11 @@ struct cocoa_surface { id<CAMetalDrawable> drawable; bool transaction; - struct yasl_frame frame; - struct yasl_damage_list damage; + struct yasl_private_frame frame; + struct yasl_private_damage_list damage; bool did_present; /* Extent of the frame in pixels, valid while did_present is set. */ - struct yasl_vector2i shown_size; + struct yasl_private_vector2i shown_size; unsigned idle_frames; bool want_draw; bool frame_due; @@ -102,27 +111,29 @@ struct cocoa_surface { struct cocoa_surface *dead_next; struct input_queue input; - struct yasl_vector2i pointer; + struct yasl_private_vector2i pointer; double scroll_x; uint32_t mods; uint32_t buttons; - enum yasl_cursor cursor; - enum yasl_pointer_device tablet_device; + enum yasl_private_cursor cursor; + enum yasl_private_pointer_device tablet_device; bool tablet_down; + uint64_t pen_device_id, pen_tool_id; + uint32_t pen_buttons; char title[160]; - struct yasl_frame_clock clock; + struct yasl_private_frame_clock clock; }; struct cocoa_display { - struct yasl_display base; + struct yasl_private_display base; struct cocoa_surface **windows; int count; int capacity; - const struct yasl_display_cbs *cbs; + const struct yasl_private_display_cbs *cbs; void *ud; char *app_id; - struct yasl_loop *loop; + struct yasl_private_loop *loop; id key_up_monitor; NSCursor *hidden_cursor; YaslAppDelegate *delegate; @@ -132,15 +143,15 @@ struct cocoa_display { bool active; struct cocoa_surface *origin; struct cocoa_surface *carried; - struct yasl_vector2i carry_offset; + struct yasl_private_vector2i carry_offset; struct cocoa_surface *hover; - struct yasl_vector2i hover_pos; - struct yasl_drag_listener listener; + struct yasl_private_vector2i hover_pos; + struct yasl_private_drag_listener listener; void *ud; NSImage *image; NSWindow *image_window; - struct yasl_vector2i image_size; - struct yasl_vector2i image_hotspot; + struct yasl_private_vector2i image_size; + struct yasl_private_vector2i image_hotspot; } drag; int depth; bool open; @@ -151,16 +162,16 @@ struct cocoa_display { static struct cocoa_display darwin_ctx; -static void drag_end(struct cocoa_display *d, enum yasl_drag_end how) +static void drag_end(struct cocoa_display *d, enum yasl_private_drag_end how) { if (!d || !d->drag.active) return; - struct yasl_drag_listener l = d->drag.listener; + struct yasl_private_drag_listener l = d->drag.listener; void *ud = d->drag.ud; struct cocoa_surface *hover = d->drag.hover; - struct yasl_vector2i pos = d->drag.hover_pos; + struct yasl_private_vector2i pos = d->drag.hover_pos; d->drag.active = false; - d->drag.listener = (struct yasl_drag_listener){0}; + d->drag.listener = (struct yasl_private_drag_listener){0}; d->drag.hover = NULL; d->drag.origin = NULL; d->drag.carried = NULL; @@ -175,10 +186,10 @@ static void drag_end(struct cocoa_display *d, enum yasl_drag_end how) if (hover && !hover->destroyed) { darwin_ctx.depth++; - if (how == YASL_DRAG_DROPPED) - yasl_surface_notify_drag(&hover->base, YASL_DRAG_DROP, pos); + if (how == YASL_PRIVATE_DRAG_DROPPED) + yasl_private_surface_notify_drag(&hover->base, YASL_PRIVATE_DRAG_DROP, pos); else - yasl_surface_notify_drag(&hover->base, YASL_DRAG_LEAVE, pos); + yasl_private_surface_notify_drag(&hover->base, YASL_PRIVATE_DRAG_LEAVE, pos); darwin_ctx.depth--; } if (l.end) @@ -187,6 +198,7 @@ static void drag_end(struct cocoa_display *d, enum yasl_drag_end how) static void render_and_commit(struct cocoa_surface *w); static void reap_dead(struct cocoa_display *d); +static void push_pen(struct cocoa_surface *w, NSEvent *event, unsigned action, bool contact); static void set_state(struct cocoa_surface *w, uint32_t bit, bool on) { @@ -197,21 +209,25 @@ static void set_state(struct cocoa_surface *w, uint32_t bit, bool on) return; w->base.state = next; darwin_ctx.depth++; - yasl_surface_notify_state(&w->base, next); + yasl_private_surface_notify_state(&w->base, next); darwin_ctx.depth--; } static void check_pointer_reset(struct cocoa_surface *w) { if (!w || w->closed) return; - if (w->buttons != 0 || w->tablet_down) { - struct yasl_event ev = { - .type = YASL_EVENT_INPUT_RESET, + if (w->buttons != 0 || w->tablet_down || w->tablet_device != YASL_PRIVATE_PTR_DEV_MOUSE) { + if (w->tablet_device != YASL_PRIVATE_PTR_DEV_MOUSE) + push_pen(w, nil, YASL_PRIVATE_PEN_CANCEL, false); + struct yasl_private_event ev = { + .type = YASL_PRIVATE_EVENT_INPUT_RESET, .surface = &w->base, }; input_queue_push(&w->input, &ev); w->buttons = 0; w->tablet_down = false; + w->tablet_device = YASL_PRIVATE_PTR_DEV_MOUSE; + w->pen_device_id = w->pen_tool_id = 0; } } @@ -221,14 +237,14 @@ static void push_focus(struct cocoa_surface *w, bool focused) return; if (!focused) check_pointer_reset(w); - struct yasl_event ev = { - .type = YASL_EVENT_FOCUS, + struct yasl_private_event ev = { + .type = YASL_PRIVATE_EVENT_FOCUS, .surface = &w->base, .focus = { .focused = focused }, }; input_queue_push(&w->input, &ev); darwin_ctx.depth++; - set_state(w, YASL_STATE_ACTIVATED, focused); + set_state(w, YASL_PRIVATE_STATE_ACTIVATED, focused); if (!w->destroyed && !w->closed) cocoa_surface_schedule_redraw(w); darwin_ctx.depth--; @@ -236,18 +252,18 @@ static void push_focus(struct cocoa_surface *w, bool focused) static void sync_size(struct cocoa_surface *w, bool force) { - if (!w || w->destroyed || !w->layer || w->closed) + if (!w || w->destroyed || w->closed || !w->view || !w->window) return; - uint64_t start = yasl_loop_now_ns(); + uint64_t start = yasl_private_loop_now_ns(); NSRect bounds = w->view.bounds; CGFloat scale = w->window.backingScaleFactor; if (scale < 1.0) scale = 1.0; - struct yasl_vector2i size = { + struct yasl_private_vector2i size = { .x = (int)lround(bounds.size.width * scale), .y = (int)lround(bounds.size.height * scale), }; - uint32_t scale_120 = (uint32_t)lround(scale * YASL_SCALE_UNIT); + uint32_t scale_120 = (uint32_t)lround(scale * YASL_PRIVATE_SCALE_UNIT); bool rescaled = force || scale_120 != w->scale_120; bool resized = force || (size.x != w->size.x || size.y != w->size.y); if (!rescaled && !resized) @@ -255,15 +271,21 @@ static void sync_size(struct cocoa_surface *w, bool force) darwin_ctx.depth++; w->size = size; w->scale_120 = scale_120; - w->layer.contentsScale = scale; - w->layer.drawableSize = CGSizeMake(size.x, size.y); + if (w->layer) { + w->layer.contentsScale = scale; + w->layer.drawableSize = CGSizeMake(size.x, size.y); + } + if (w->gl_context) + [w->gl_context update]; /* Common configure notification already requests a frame before invoking * the legacy callback. Finish local bookkeeping before that callback can * destroy this surface. */ - yasl_clock_note_resize(&w->clock, yasl_loop_now_ns() - start); - yasl_surface_notify_configure(&w->base, (struct yasl_geometry){ + yasl_private_clock_note_resize(&w->clock, yasl_private_loop_now_ns() - start); + yasl_private_surface_notify_configure(&w->base, (struct yasl_private_geometry){ .size = size, .scale_120 = scale_120, }); + if (w->base.presenter != YASL_PRIVATE_PRESENTER_CPU) + yasl_private_surface_notify_refresh(&w->base); if (!w->destroyed && !w->closed && w->view.inLiveResize) render_and_commit(w); darwin_ctx.depth--; @@ -272,11 +294,11 @@ static void sync_size(struct cocoa_surface *w, bool force) static unsigned cocoa_mods(NSEventModifierFlags m) { unsigned mods = 0; - if (m & NSEventModifierFlagShift) mods |= YASL_MOD_SHIFT; - if (m & NSEventModifierFlagOption) mods |= YASL_MOD_ALT; - if (m & NSEventModifierFlagControl) mods |= YASL_MOD_CTRL; - if (m & NSEventModifierFlagCommand) mods |= YASL_MOD_SUPER; - if (m & NSEventModifierFlagCapsLock) mods |= YASL_MOD_CAPS; + if (m & NSEventModifierFlagShift) mods |= YASL_PRIVATE_MOD_SHIFT; + if (m & NSEventModifierFlagOption) mods |= YASL_PRIVATE_MOD_ALT; + if (m & NSEventModifierFlagControl) mods |= YASL_PRIVATE_MOD_CTRL; + if (m & NSEventModifierFlagCommand) mods |= YASL_PRIVATE_MOD_SUPER; + if (m & NSEventModifierFlagCapsLock) mods |= YASL_PRIVATE_MOD_CAPS; return mods; } @@ -287,19 +309,19 @@ static uint32_t cocoa_key_label(NSString *characters) if (!characters.length) return 0; unichar first = [characters characterAtIndex:0]; if (characters.length == 1) - return yasl_key_label(first); + return yasl_private_key_label(first); if (characters.length == 2 && first >= 0xd800 && first <= 0xdbff) { unichar second = [characters characterAtIndex:1]; if (second >= 0xdc00 && second <= 0xdfff) - return yasl_key_label(0x10000 + ((first - 0xd800) << 10) + second - 0xdc00); + return yasl_private_key_label(0x10000 + ((first - 0xd800) << 10) + second - 0xdc00); } return 0; } -static struct yasl_key_event cocoa_key_event(NSEvent *ev, enum yasl_key_action action) +static struct yasl_private_key_event cocoa_key_event(NSEvent *ev, enum yasl_key_action action) { - struct yasl_key_event k = { - .key = yasl_key_from_cocoa(ev.keyCode), + struct yasl_private_key_event k = { + .key = yasl_private_key_from_cocoa(ev.keyCode), .action = action, .mods = cocoa_mods(ev.modifierFlags), .native_code = ev.keyCode, @@ -315,27 +337,15 @@ static struct yasl_key_event cocoa_key_event(NSEvent *ev, enum yasl_key_action a static void on_key_event(struct cocoa_surface *w, NSEvent *ev, enum yasl_key_action action) { w->mods = cocoa_mods(ev.modifierFlags); - struct yasl_key_event k = cocoa_key_event(ev, action); - struct yasl_event input_ev = { - .type = YASL_EVENT_KEY, + struct yasl_private_key_event k = cocoa_key_event(ev, action); + struct yasl_private_event input_ev = { + .type = YASL_PRIVATE_EVENT_KEY, .surface = &w->base, .key = k, }; input_queue_push(&w->input, &input_ev); } -static void on_text_input(struct cocoa_surface *w, const char *text, size_t len) -{ - if (len) { - struct yasl_event ev = { - .type = YASL_EVENT_TEXT, - .surface = &w->base, - .text = { .data = text, .len = len }, - }; - input_queue_push(&w->input, &ev); - } -} - static bool modifier_down(NSEvent *ev) { NSEventModifierFlags f = ev.modifierFlags; @@ -356,22 +366,22 @@ static bool modifier_down(NSEvent *ev) static int cocoa_button(NSInteger number) { switch (number) { - case 0: return YASL_BUTTON_PRIMARY; - case 1: return YASL_BUTTON_SECONDARY; - case 2: return YASL_BUTTON_MIDDLE; - case 3: return YASL_BUTTON_BACK; - case 4: return YASL_BUTTON_FORWARD; + case 0: return YASL_PRIVATE_BUTTON_PRIMARY; + case 1: return YASL_PRIVATE_BUTTON_SECONDARY; + case 2: return YASL_PRIVATE_BUTTON_MIDDLE; + case 3: return YASL_PRIVATE_BUTTON_BACK; + case 4: return YASL_PRIVATE_BUTTON_FORWARD; default: return -1; } } -static struct yasl_vector2i event_pos(struct cocoa_surface *w, NSEvent *ev) +static struct yasl_private_vector2i event_pos(struct cocoa_surface *w, NSEvent *ev) { NSPoint p = [w->view convertPoint:ev.locationInWindow fromView:nil]; CGFloat scale = w->window.backingScaleFactor; if (scale < 1.0) scale = 1.0; - return (struct yasl_vector2i){ + return (struct yasl_private_vector2i){ .x = (int)floor(p.x * scale), .y = (int)floor((w->view.bounds.size.height - p.y) * scale), }; @@ -389,7 +399,7 @@ static void update_drag_hover(struct cocoa_display *d, NSPoint screenPoint) NSInteger winNum = [NSWindow windowNumberAtPoint:screenPoint belowWindowWithWindowNumber:excludeNum]; struct cocoa_surface *target = NULL; - struct yasl_vector2i pos = {0}; + struct yasl_private_vector2i pos = {0}; if (winNum > 0) { NSWindow *nswin = [NSApp windowWithWindowNumber:winNum]; @@ -429,11 +439,11 @@ static void update_drag_hover(struct cocoa_display *d, NSPoint screenPoint) if (target != d->drag.hover) { if (d->drag.hover) { struct cocoa_surface *old = d->drag.hover; - struct yasl_vector2i old_pos = d->drag.hover_pos; + struct yasl_private_vector2i old_pos = d->drag.hover_pos; d->drag.hover = NULL; if (!old->destroyed) { darwin_ctx.depth++; - yasl_surface_notify_drag(&old->base, YASL_DRAG_LEAVE, old_pos); + yasl_private_surface_notify_drag(&old->base, YASL_PRIVATE_DRAG_LEAVE, old_pos); darwin_ctx.depth--; } if (!d->drag.active) return; @@ -443,23 +453,117 @@ static void update_drag_hover(struct cocoa_display *d, NSPoint screenPoint) d->drag.hover_pos = pos; if (target && !target->destroyed) { darwin_ctx.depth++; - yasl_surface_notify_drag(&target->base, YASL_DRAG_OVER, pos); + yasl_private_surface_notify_drag(&target->base, YASL_PRIVATE_DRAG_OVER, pos); darwin_ctx.depth--; } } else if (target && !target->destroyed) { d->drag.hover_pos = pos; darwin_ctx.depth++; - yasl_surface_notify_drag(&target->base, YASL_DRAG_OVER, pos); + yasl_private_surface_notify_drag(&target->base, YASL_PRIVATE_DRAG_OVER, pos); darwin_ctx.depth--; } } -static void push_pointer(struct cocoa_surface *w, NSEvent *ev, enum yasl_event_type type, +static uint64_t event_integer(NSEvent *event, const char *name) +{ + SEL selector = sel_registerName(name); + if (![event respondsToSelector:selector]) return 0; + return (uint64_t)((NSInteger (*)(id, SEL))objc_msgSend)(event, selector); +} + +static double event_double(NSEvent *event, const char *name) +{ + SEL selector = sel_registerName(name); + if (![event respondsToSelector:selector]) return 0; + return ((double (*)(id, SEL))objc_msgSend)(event, selector); +} + +static void push_pen(struct cocoa_surface *w, NSEvent *event, unsigned action, bool contact) +{ + if (!w || w->closed || w->tablet_device == YASL_PRIVATE_PTR_DEV_MOUSE) return; + struct yasl_private_pen_sample pen = { + .device_id = w->pen_device_id, + .tool_id = w->pen_tool_id, + .x = w->pointer.x, + .y = w->pointer.y, + .tool = w->tablet_device == YASL_PRIVATE_PTR_DEV_ERASER ? 2u : 1u, + .action = action, + .in_contact = contact, + }; + if (event) { + if (w->view && [event respondsToSelector:@selector(locationInWindow)]) { + NSPoint point = [w->view convertPoint:event.locationInWindow fromView:nil]; + CGFloat scale = w->window.backingScaleFactor; + if (scale < 1.0) scale = 1.0; + pen.x = point.x * scale; + pen.y = (w->view.bounds.size.height - point.y) * scale; + } + if (event.buttonMask & NSEventButtonMaskPenTip) + pen.buttons |= YASL_PRIVATE_BUTTON_BIT(YASL_PRIVATE_BUTTON_PRIMARY); + if (event.buttonMask & NSEventButtonMaskPenLowerSide) + pen.buttons |= YASL_PRIVATE_BUTTON_BIT(YASL_PRIVATE_BUTTON_SECONDARY); + if (event.buttonMask & NSEventButtonMaskPenUpperSide) + pen.buttons |= YASL_PRIVATE_BUTTON_BIT(YASL_PRIVATE_BUTTON_MIDDLE); + if (event.type == NSEventTypeTabletPoint || event.subtype == NSEventSubtypeTabletPoint) { + pen.pressure = fmax(0.0, fmin(1.0, event.pressure)); + pen.axes_valid |= YASL_PRIVATE_PEN_PRESSURE; + pen.axes_changed |= YASL_PRIVATE_PEN_PRESSURE; + NSPoint tilt = event.tilt; + pen.tilt_x = fmax(-90.0, fmin(90.0, tilt.x * 90.0)); + pen.tilt_y = fmax(-90.0, fmin(90.0, -tilt.y * 90.0)); + pen.axes_valid |= YASL_PRIVATE_PEN_TILT; + pen.axes_changed |= YASL_PRIVATE_PEN_TILT; + if ([event respondsToSelector:@selector(rotation)]) { + pen.rotation = event_double(event, "rotation"); + pen.axes_valid |= YASL_PRIVATE_PEN_ROTATION; + pen.axes_changed |= YASL_PRIVATE_PEN_ROTATION; + } + if ([event respondsToSelector:@selector(tangentialPressure)]) { + pen.tangential = event_double(event, "tangentialPressure"); + pen.axes_valid |= YASL_PRIVATE_PEN_TANGENTIAL; + pen.axes_changed |= YASL_PRIVATE_PEN_TANGENTIAL; + } + } + uint64_t device = event_integer(event, "pointingDeviceID"); + uint64_t tool = event_integer(event, "pointingDeviceSerialNumber"); + if (device) pen.device_id = device; + if (tool) pen.tool_id = tool; + } + if (action == YASL_PRIVATE_PEN_DOWN) { + pen.buttons |= YASL_PRIVATE_BUTTON_BIT(YASL_PRIVATE_BUTTON_PRIMARY); + pen.changed_button = YASL_PRIVATE_BUTTON_BIT(YASL_PRIVATE_BUTTON_PRIMARY); + } else if (action == YASL_PRIVATE_PEN_UP) { + pen.buttons &= ~YASL_PRIVATE_BUTTON_BIT(YASL_PRIVATE_BUTTON_PRIMARY); + pen.changed_button = YASL_PRIVATE_BUTTON_BIT(YASL_PRIVATE_BUTTON_PRIMARY); + } + if (action == YASL_PRIVATE_PEN_LEAVE || action == YASL_PRIVATE_PEN_CANCEL) { + pen.buttons = 0; + pen.in_contact = false; + pen.pressure = 0; + w->pen_buttons = 0; + } else { + uint32_t changed = pen.buttons ^ w->pen_buttons; + if (action == YASL_PRIVATE_PEN_MOVE && changed) { + action = YASL_PRIVATE_PEN_BUTTON; + pen.changed_button = changed & (~changed + 1u); + } + w->pen_buttons = pen.buttons; + } + pen.action = action; + struct yasl_private_event ev = { + .type = YASL_PRIVATE_EVENT_PEN, + .surface = &w->base, + .pen = pen, + }; + input_queue_push(&w->input, &ev); +} + +static void push_pointer(struct cocoa_surface *w, NSEvent *ev, enum yasl_private_event_type type, int button) { if (!w || w->closed) return; - if ((type == YASL_EVENT_POINTER_DOWN || type == YASL_EVENT_POINTER_UP) && + if ((type == YASL_PRIVATE_EVENT_POINTER_DOWN || type == YASL_PRIVATE_EVENT_POINTER_UP) && (button < 0 || button > 4)) return; bool proximity = ev.type == NSEventTypeTabletProximity || @@ -467,26 +571,30 @@ static void push_pointer(struct cocoa_surface *w, NSEvent *ev, enum yasl_event_t if (proximity) { if (ev.isEnteringProximity) { w->tablet_device = ev.pointingDeviceType == NSPointingDeviceTypeEraser ? - YASL_PTR_DEV_ERASER : ev.pointingDeviceType == NSPointingDeviceTypePen ? - YASL_PTR_DEV_PEN : YASL_PTR_DEV_MOUSE; + YASL_PRIVATE_PTR_DEV_ERASER : ev.pointingDeviceType == NSPointingDeviceTypePen ? + YASL_PRIVATE_PTR_DEV_PEN : YASL_PRIVATE_PTR_DEV_MOUSE; + w->pen_device_id = event_integer(ev, "pointingDeviceID"); + w->pen_tool_id = event_integer(ev, "pointingDeviceSerialNumber"); + if (w->tablet_device != YASL_PRIVATE_PTR_DEV_MOUSE) + push_pen(w, ev, YASL_PRIVATE_PEN_ENTER, false); return; } - type = YASL_EVENT_POINTER_LEAVE; + type = YASL_PRIVATE_EVENT_POINTER_LEAVE; } w->mods = cocoa_mods(ev.modifierFlags); - if (type != YASL_EVENT_POINTER_LEAVE) + if (type != YASL_PRIVATE_EVENT_POINTER_LEAVE) w->pointer = event_pos(w, ev); - enum yasl_pointer_device device = YASL_PTR_DEV_MOUSE; - struct yasl_pointer_pen pen = {0}; + enum yasl_private_pointer_device device = YASL_PRIVATE_PTR_DEV_MOUSE; + struct yasl_private_pointer_pen pen = {0}; - if (type == YASL_EVENT_POINTER_LEAVE && w->tablet_device != YASL_PTR_DEV_MOUSE) { + if (type == YASL_PRIVATE_EVENT_POINTER_LEAVE && w->tablet_device != YASL_PRIVATE_PTR_DEV_MOUSE) { device = w->tablet_device; w->tablet_down = false; } else if (ev.type == NSEventTypeTabletPoint || ev.subtype == NSEventSubtypeTabletPoint) { /* pointingDeviceType belongs to proximity events. Remember that * event's tool so erasers and zero-pressure hover stay classified. */ - device = w->tablet_device == YASL_PTR_DEV_ERASER ? YASL_PTR_DEV_ERASER : YASL_PTR_DEV_PEN; + device = w->tablet_device == YASL_PRIVATE_PTR_DEV_ERASER ? YASL_PRIVATE_PTR_DEV_ERASER : YASL_PRIVATE_PTR_DEV_PEN; float press = (float)ev.pressure; if (press > 1.0f) press = 1.0f; if (press > 0.0f) @@ -495,32 +603,40 @@ static void push_pointer(struct cocoa_surface *w, NSEvent *ev, enum yasl_event_t NSPoint tilt = ev.tilt; pen.tilt_x = (int8_t)lround(fmax(-1, fmin(1, tilt.x)) * 90.0); pen.tilt_y = (int8_t)lround(fmax(-1, fmin(1, -tilt.y)) * 90.0); - if (button == 0 && type == YASL_EVENT_POINTER_DOWN) w->tablet_down = true; - if (button == 0 && type == YASL_EVENT_POINTER_UP) { + if (button == 0 && type == YASL_PRIVATE_EVENT_POINTER_DOWN) w->tablet_down = true; + if (button == 0 && type == YASL_PRIVATE_EVENT_POINTER_UP) { w->tablet_down = false; pen.pressure = 0; } } - if (type == YASL_EVENT_POINTER_DOWN && button >= 0 && button < 32) + if (type == YASL_PRIVATE_EVENT_POINTER_DOWN && button >= 0 && button < 32) w->buttons |= (1u << button); - else if (type == YASL_EVENT_POINTER_UP && button >= 0 && button < 32) + else if (type == YASL_PRIVATE_EVENT_POINTER_UP && button >= 0 && button < 32) w->buttons &= ~(1u << button); - struct yasl_event input_ev = { + struct yasl_private_event input_ev = { .type = type, .surface = &w->base, .pointer = { .pos = w->pointer, - .button = (type == YASL_EVENT_POINTER_DOWN || type == YASL_EVENT_POINTER_UP) ? - (enum yasl_pointer_button)button : YASL_BUTTON_PRIMARY, + .button = (type == YASL_PRIVATE_EVENT_POINTER_DOWN || type == YASL_PRIVATE_EVENT_POINTER_UP) ? + (enum yasl_private_pointer_button)button : YASL_PRIVATE_BUTTON_PRIMARY, .device = device, .mods = w->mods, .pen = pen, }, }; input_queue_push(&w->input, &input_ev); - if (type == YASL_EVENT_POINTER_MOTION && darwin_ctx.drag.active) { + if (type == YASL_PRIVATE_EVENT_POINTER_LEAVE && device != YASL_PRIVATE_PTR_DEV_MOUSE) { + push_pen(w, ev, YASL_PRIVATE_PEN_LEAVE, false); + } else if (ev.type == NSEventTypeTabletPoint || ev.subtype == NSEventSubtypeTabletPoint) { + unsigned action = button == YASL_PRIVATE_BUTTON_PRIMARY && type == YASL_PRIVATE_EVENT_POINTER_DOWN ? YASL_PRIVATE_PEN_DOWN : + button == YASL_PRIVATE_BUTTON_PRIMARY && type == YASL_PRIVATE_EVENT_POINTER_UP ? YASL_PRIVATE_PEN_UP : + YASL_PRIVATE_PEN_MOVE; + push_pen(w, ev, action, w->tablet_down); + } + if (type == YASL_PRIVATE_EVENT_POINTER_MOTION && darwin_ctx.drag.active) { if (darwin_ctx.drag.carried) { cocoa_surface_place_under_pointer(darwin_ctx.drag.carried, darwin_ctx.drag.carry_offset); } else if (darwin_ctx.drag.image_window) { @@ -534,23 +650,24 @@ static void push_pointer(struct cocoa_surface *w, NSEvent *ev, enum yasl_event_t mouse.y - pt_h + darwin_ctx.drag.image_hotspot.y / scale)]; } update_drag_hover(&darwin_ctx, [NSEvent mouseLocation]); - } else if (type == YASL_EVENT_POINTER_UP && darwin_ctx.drag.active) { + } else if (type == YASL_PRIVATE_EVENT_POINTER_UP && darwin_ctx.drag.active) { update_drag_hover(&darwin_ctx, [NSEvent mouseLocation]); - drag_end(&darwin_ctx, YASL_DRAG_DROPPED); + drag_end(&darwin_ctx, YASL_PRIVATE_DRAG_DROPPED); } if (proximity) { - w->tablet_device = YASL_PTR_DEV_MOUSE; + w->tablet_device = YASL_PRIVATE_PTR_DEV_MOUSE; w->tablet_down = false; + w->pen_device_id = w->pen_tool_id = 0; } } -static NSCursor *cocoa_cursor(enum yasl_cursor cursor) +static NSCursor *cocoa_cursor(enum yasl_private_cursor cursor) { switch (cursor) { - case YASL_CURSOR_TEXT: return [NSCursor IBeamCursor]; - case YASL_CURSOR_LINK: return [NSCursor pointingHandCursor]; - case YASL_CURSOR_CROSSHAIR: return [NSCursor crosshairCursor]; - case YASL_CURSOR_HIDDEN: + case YASL_PRIVATE_CURSOR_TEXT: return [NSCursor IBeamCursor]; + case YASL_PRIVATE_CURSOR_LINK: return [NSCursor pointingHandCursor]; + case YASL_PRIVATE_CURSOR_CROSSHAIR: return [NSCursor crosshairCursor]; + case YASL_PRIVATE_CURSOR_HIDDEN: if (!darwin_ctx.hidden_cursor) { NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize(1, 1)]; [image lockFocus]; @@ -561,7 +678,7 @@ static NSCursor *cocoa_cursor(enum yasl_cursor cursor) [image release]; } return darwin_ctx.hidden_cursor; - case YASL_CURSOR_ARROW: + case YASL_PRIVATE_CURSOR_ARROW: default: return [NSCursor arrowCursor]; } } @@ -570,14 +687,14 @@ static NSCursor *cocoa_cursor(enum yasl_cursor cursor) - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { (void)sender; - yasl_display_notify_event(&darwin_ctx.base, YASL_EVENT_DISPLAY_QUIT); + yasl_private_display_notify_event(&darwin_ctx.base, YASL_PRIVATE_EVENT_DISPLAY_QUIT); return NSTerminateCancel; } - (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)visible { (void)sender; if (!visible) - yasl_display_notify_event(&darwin_ctx.base, YASL_EVENT_DISPLAY_REOPEN); + yasl_private_display_notify_event(&darwin_ctx.base, YASL_PRIVATE_EVENT_DISPLAY_REOPEN); return YES; } - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender @@ -587,13 +704,13 @@ static NSCursor *cocoa_cursor(enum yasl_cursor cursor) } @end -static struct yasl_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> sender) +static struct yasl_private_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> sender) { NSPoint p = [view convertPoint:[sender draggingLocation] fromView:nil]; NSWindow *win = view.window; CGFloat scale = win ? win.backingScaleFactor : 1.0; if (scale < 1.0) scale = 1.0; - return (struct yasl_vector2i){ + return (struct yasl_private_vector2i){ .x = (int)floor(p.x * scale), .y = (int)floor((view.bounds.size.height - p.y) * scale), }; @@ -619,9 +736,9 @@ static struct yasl_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> send if ([pb canReadItemWithDataConformingToTypes:@[NSPasteboardTypeFileURL]]) { struct cocoa_surface *w = self.surface; if (w && !w->closed) { - struct yasl_vector2i pos = dragging_pos(self, sender); + struct yasl_private_vector2i pos = dragging_pos(self, sender); darwin_ctx.depth++; - yasl_surface_notify_drop(&w->base, YASL_DROP_OVER, pos); + yasl_private_surface_notify_drop(&w->base, YASL_PRIVATE_DROP_OVER, pos); darwin_ctx.depth--; } return NSDragOperationCopy; @@ -640,9 +757,9 @@ static struct yasl_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> send if ([pb canReadItemWithDataConformingToTypes:@[NSPasteboardTypeFileURL]]) { struct cocoa_surface *w = self.surface; if (w && !w->closed) { - struct yasl_vector2i pos = dragging_pos(self, sender); + struct yasl_private_vector2i pos = dragging_pos(self, sender); darwin_ctx.depth++; - yasl_surface_notify_drop(&w->base, YASL_DROP_LEAVE, pos); + yasl_private_surface_notify_drop(&w->base, YASL_PRIVATE_DROP_LEAVE, pos); darwin_ctx.depth--; } } @@ -652,7 +769,7 @@ static struct yasl_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> send { struct cocoa_surface *w = self.surface; if (!w) return NO; - struct yasl_vector2i pos = dragging_pos(self, sender); + struct yasl_private_vector2i pos = dragging_pos(self, sender); BOOL ok = NO; NSPasteboard *pb = [sender draggingPasteboard]; @@ -666,8 +783,8 @@ static struct yasl_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> send } const char *utf8 = result.UTF8String; if (utf8 && strlen(utf8)) { - struct yasl_event ev = { - .type = YASL_EVENT_DROP, + struct yasl_private_event ev = { + .type = YASL_PRIVATE_EVENT_DROP, .surface = &w->base, .text = { .data = utf8, .len = strlen(utf8) }, }; @@ -678,7 +795,7 @@ static struct yasl_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> send } if (!w->destroyed) { darwin_ctx.depth++; - yasl_surface_notify_drop(&w->base, YASL_DROP_LEAVE, pos); + yasl_private_surface_notify_drop(&w->base, YASL_PRIVATE_DROP_LEAVE, pos); darwin_ctx.depth--; } return ok; @@ -704,6 +821,13 @@ static struct yasl_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> send - (BOOL)canBecomeKeyView { return YES; } - (BOOL)wantsUpdateLayer { return YES; } +- (void)updateLayer +{ + struct cocoa_surface *w = self.surface; + if (w && !w->closed && w->base.presenter != YASL_PRIVATE_PRESENTER_CPU) + yasl_private_surface_notify_refresh(&w->base); +} + - (void)setFrameSize:(NSSize)size { [super setFrameSize:size]; @@ -743,14 +867,14 @@ 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->base.state & YASL_STATE_SUSPENDED)) { + if (!w->want_draw || (w->base.state & YASL_PRIVATE_STATE_SUSPENDED)) { if (++w->idle_frames >= LINK_IDLE_FRAMES) link.paused = YES; return; } w->idle_frames = 0; if (darwin_ctx.loop) - yasl_loop_kick(darwin_ctx.loop); + yasl_private_loop_kick(darwin_ctx.loop); } - (void)updateTrackingAreas @@ -773,7 +897,7 @@ static struct yasl_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> send { (void)event; struct cocoa_surface *w = self.surface; - [cocoa_cursor(w ? w->cursor : YASL_CURSOR_ARROW) set]; + [cocoa_cursor(w ? w->cursor : YASL_PRIVATE_CURSOR_ARROW) set]; } - (void)keyDown:(NSEvent *)event @@ -802,44 +926,44 @@ static struct yasl_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> send - (void)mouseDown:(NSEvent *)event { [self.window makeFirstResponder:self]; - push_pointer(self.surface, event, YASL_EVENT_POINTER_DOWN, cocoa_button(event.buttonNumber)); + push_pointer(self.surface, event, YASL_PRIVATE_EVENT_POINTER_DOWN, cocoa_button(event.buttonNumber)); } - (void)rightMouseDown:(NSEvent *)event { [self mouseDown:event]; } - (void)otherMouseDown:(NSEvent *)event { [self mouseDown:event]; } - (void)mouseUp:(NSEvent *)event { - push_pointer(self.surface, event, YASL_EVENT_POINTER_UP, cocoa_button(event.buttonNumber)); + push_pointer(self.surface, event, YASL_PRIVATE_EVENT_POINTER_UP, cocoa_button(event.buttonNumber)); } - (void)rightMouseUp:(NSEvent *)event { [self mouseUp:event]; } - (void)otherMouseUp:(NSEvent *)event { [self mouseUp:event]; } - (void)mouseMoved:(NSEvent *)event { - push_pointer(self.surface, event, YASL_EVENT_POINTER_MOTION, 0); + push_pointer(self.surface, event, YASL_PRIVATE_EVENT_POINTER_MOTION, 0); } - (void)mouseDragged:(NSEvent *)event { - push_pointer(self.surface, event, YASL_EVENT_POINTER_MOTION, 0); + push_pointer(self.surface, event, YASL_PRIVATE_EVENT_POINTER_MOTION, 0); } - (void)rightMouseDragged:(NSEvent *)event { - push_pointer(self.surface, event, YASL_EVENT_POINTER_MOTION, 0); + push_pointer(self.surface, event, YASL_PRIVATE_EVENT_POINTER_MOTION, 0); } - (void)otherMouseDragged:(NSEvent *)event { - push_pointer(self.surface, event, YASL_EVENT_POINTER_MOTION, 0); + push_pointer(self.surface, event, YASL_PRIVATE_EVENT_POINTER_MOTION, 0); } - (void)mouseExited:(NSEvent *)event { - push_pointer(self.surface, event, YASL_EVENT_POINTER_LEAVE, 0); + push_pointer(self.surface, event, YASL_PRIVATE_EVENT_POINTER_LEAVE, 0); } - (void)tabletProximity:(NSEvent *)event { - push_pointer(self.surface, event, YASL_EVENT_POINTER_MOTION, 0); + push_pointer(self.surface, event, YASL_PRIVATE_EVENT_POINTER_MOTION, 0); } - (void)tabletPoint:(NSEvent *)event @@ -847,8 +971,8 @@ static struct yasl_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> send struct cocoa_surface *w = self.surface; if (!w) return; bool down = (event.buttonMask & NSEventButtonMaskPenTip) != 0; - enum yasl_event_type type = down == w->tablet_down ? YASL_EVENT_POINTER_MOTION : - down ? YASL_EVENT_POINTER_DOWN : YASL_EVENT_POINTER_UP; + enum yasl_private_event_type type = down == w->tablet_down ? YASL_PRIVATE_EVENT_POINTER_MOTION : + down ? YASL_PRIVATE_EVENT_POINTER_DOWN : YASL_PRIVATE_EVENT_POINTER_UP; push_pointer(w, event, type, 0); } @@ -864,8 +988,8 @@ static struct yasl_vector2i dragging_pos(YaslView *view, id<NSDraggingInfo> send if (!isfinite(dx) || !isfinite(dy) || (dx == 0 && dy == 0)) return; w->mods = cocoa_mods(event.modifierFlags); w->pointer = event_pos(w, event); - struct yasl_event input_ev = { - .type = YASL_EVENT_POINTER_SCROLL, + struct yasl_private_event input_ev = { + .type = YASL_PRIVATE_EVENT_POINTER_SCROLL, .surface = &w->base, .pointer = { .pos = w->pointer, @@ -882,34 +1006,131 @@ static NSString *plain_string(id string) ? [(NSAttributedString *)string string] : (NSString *)string; } +static bool text_utf16_to_utf8(NSString *text, NSUInteger index, size_t *byte_offset) +{ + if (!text || index > text.length) return false; + const char *prefix = [[text substringToIndex:index] UTF8String]; + if (!prefix) return false; + *byte_offset = strlen(prefix); + return true; +} + +static bool text_utf8_to_utf16(const char *text, size_t byte_offset, NSUInteger *index) +{ + size_t len = text ? strlen(text) : 0; + if (byte_offset > len) return false; + NSString *prefix = [[NSString alloc] initWithBytes:text length:byte_offset + encoding:NSUTF8StringEncoding]; + if (!prefix) return false; + *index = prefix.length; + [prefix release]; + return true; +} + +static bool text_edit_range(struct cocoa_surface *w, NSRange range, bool use_marked, + size_t *begin, size_t *end) +{ + if (range.location == NSNotFound) { + if (use_marked && w->preedit) { + if (w->preedit_revision != w->base.text_revision || + w->preedit_generation != w->base.text_generation) + return false; + *begin = w->preedit_begin; + *end = w->preedit_end; + } else { + *begin = w->base.text_begin; + *end = w->base.text_end; + } + return *begin <= *end && *end <= w->base.text_length; + } + NSString *surrounding = [NSString stringWithUTF8String:w->base.text_surrounding ?: ""]; + size_t a, b; + if (!surrounding || range.location > surrounding.length || + range.length > surrounding.length - range.location || + !text_utf16_to_utf8(surrounding, range.location, &a) || + !text_utf16_to_utf8(surrounding, range.location + range.length, &b)) + return false; + *begin = a; + *end = b; + return true; +} + +static void push_text_edit(struct cocoa_surface *w, const char *text, size_t len, + size_t begin, size_t end) +{ + if (!w->base.text_input_active || begin > end || end > w->base.text_length) return; + struct yasl_private_event ev = { + .type = YASL_PRIVATE_EVENT_TEXT_EDIT, + .surface = &w->base, + .text_edit = { + .data = text ?: "", .len = len, + .revision = w->base.text_revision, + .generation = w->base.text_generation, + .begin = begin, .end = end, + }, + }; + input_queue_push(&w->input, &ev); +} + - (void)insertText:(id)string replacementRange:(NSRange)replacementRange { - (void)replacementRange; struct cocoa_surface *w = self.surface; - if (!w || w->destroyed || w->closed) return; + if (!w || w->destroyed || w->closed || !w->base.text_input_active) return; const char *utf8 = plain_string(string).UTF8String; size_t len = utf8 ? strlen(utf8) : 0; + size_t begin, end; + if (!text_edit_range(w, replacementRange, true, &begin, &end)) return; bool had_preedit = w->preedit != NULL; + darwin_ctx.depth++; + push_text_edit(w, utf8, len, begin, end); free(w->preedit); w->preedit = NULL; - darwin_ctx.depth++; - on_text_input(w, utf8, len); + w->preedit_begin = w->preedit_end = 0; if (had_preedit) - yasl_surface_notify_composition(&w->base, NULL, -1, -1); + yasl_private_surface_notify_composition(&w->base, NULL, -1, -1); if (!w->destroyed && !w->closed) cocoa_surface_schedule_redraw(w); darwin_ctx.depth--; } -- (void)doCommandBySelector:(SEL)selector { (void)selector; } +- (void)doCommandBySelector:(SEL)selector +{ + struct cocoa_surface *w = self.surface; + if (!w || w->destroyed || w->closed || !w->base.text_input_active) return; + NSString *surrounding = [NSString stringWithUTF8String:w->base.text_surrounding ?: ""]; + NSUInteger begin, end; + if (!sel_isEqual(selector, @selector(deleteBackward)) && + !sel_isEqual(selector, @selector(deleteForward))) return; + if (!text_utf8_to_utf16(w->base.text_surrounding ?: "", w->base.text_begin, &begin) || + !text_utf8_to_utf16(w->base.text_surrounding ?: "", w->base.text_end, &end)) return; + if (begin == end && sel_isEqual(selector, @selector(deleteBackward)) && begin > 0) { + NSRange composed = [surrounding rangeOfComposedCharacterSequenceAtIndex:begin - 1]; + begin = composed.location; + } else if (begin == end && sel_isEqual(selector, @selector(deleteForward)) && end < surrounding.length) { + NSRange composed = [surrounding rangeOfComposedCharacterSequenceAtIndex:end]; + end = NSMaxRange(composed); + } + size_t byte_begin, byte_end; + if (text_utf16_to_utf8(surrounding, begin, &byte_begin) && + text_utf16_to_utf8(surrounding, end, &byte_end) && byte_begin != byte_end) + push_text_edit(w, "", 0, byte_begin, byte_end); +} - (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange { - (void)replacementRange; struct cocoa_surface *w = self.surface; - if (!w || w->destroyed || w->closed) return; + if (!w || w->destroyed || w->closed || !w->base.text_input_active) return; NSString *str = plain_string(string); + size_t range_begin, range_end; + if (!text_edit_range(w, replacementRange, true, &range_begin, &range_end)) return; + w->preedit_begin = range_begin; + w->preedit_end = range_end; + w->preedit_revision = w->base.text_revision; + w->preedit_generation = w->base.text_generation; + if (!text_utf8_to_utf16(w->base.text_surrounding ?: "", range_begin, + &w->marked_utf16_location)) + w->marked_utf16_location = 0; const char *utf8 = str.length ? str.UTF8String : NULL; free(w->preedit); w->preedit = utf8 ? strdup(utf8) : NULL; @@ -926,7 +1147,7 @@ static NSString *plain_string(id string) } } darwin_ctx.depth++; - yasl_surface_notify_composition(&w->base, w->preedit, begin, end); + yasl_private_surface_notify_composition(&w->base, w->preedit, begin, end); if (!w->destroyed && !w->closed) cocoa_surface_schedule_redraw(w); darwin_ctx.depth--; @@ -939,19 +1160,28 @@ static NSString *plain_string(id string) free(w->preedit); w->preedit = NULL; darwin_ctx.depth++; - yasl_surface_notify_composition(&w->base, NULL, -1, -1); + yasl_private_surface_notify_composition(&w->base, NULL, -1, -1); if (!w->destroyed && !w->closed) cocoa_surface_schedule_redraw(w); darwin_ctx.depth--; } -- (NSRange)selectedRange { return NSMakeRange(NSNotFound, 0); } +- (NSRange)selectedRange +{ + struct cocoa_surface *w = self.surface; + if (!w || !w->base.text_input_active) return NSMakeRange(NSNotFound, 0); + NSUInteger begin, end; + if (!text_utf8_to_utf16(w->base.text_surrounding ?: "", w->base.text_begin, &begin) || + !text_utf8_to_utf16(w->base.text_surrounding ?: "", w->base.text_end, &end)) + return NSMakeRange(NSNotFound, 0); + return NSMakeRange(begin, end - begin); +} - (NSRange)markedRange { struct cocoa_surface *w = self.surface; if (!w || !w->preedit) return NSMakeRange(NSNotFound, 0); - return NSMakeRange(0, [NSString stringWithUTF8String:w->preedit].length); + return NSMakeRange(w->marked_utf16_location, [NSString stringWithUTF8String:w->preedit].length); } - (BOOL)hasMarkedText { return self.surface && self.surface->preedit != NULL; } - (nullable NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range @@ -1012,7 +1242,7 @@ static NSString *plain_string(id string) if (!w) return; bool visible = (self.window.occlusionState & NSWindowOcclusionStateVisible) != 0; darwin_ctx.depth++; - set_state(w, YASL_STATE_SUSPENDED, !visible); + set_state(w, YASL_PRIVATE_STATE_SUSPENDED, !visible); if (!w->destroyed && !w->closed) { if (visible) cocoa_surface_schedule_redraw(w); @@ -1042,8 +1272,8 @@ static NSString *plain_string(id string) return; darwin_ctx.depth++; bool fullscreen = (self.window.styleMask & NSWindowStyleMaskFullScreen) != 0; - set_state(w, YASL_STATE_FULLSCREEN, fullscreen); - set_state(w, YASL_STATE_MAXIMIZED, !fullscreen && self.window.isZoomed); + set_state(w, YASL_PRIVATE_STATE_FULLSCREEN, fullscreen); + set_state(w, YASL_PRIVATE_STATE_MAXIMIZED, !fullscreen && self.window.isZoomed); sync_size(w, false); darwin_ctx.depth--; } @@ -1075,16 +1305,16 @@ static NSString *plain_string(id string) darwin_ctx.depth++; w->closed = true; [w->link invalidate]; - yasl_surface_notify_close(&w->base); + yasl_private_surface_notify_close(&w->base); darwin_ctx.depth--; } @end -bool cocoa_surface_present(struct cocoa_surface *w, const struct yasl_framebuffer *fb, - const struct yasl_damage_list *damage) +bool cocoa_surface_present(struct cocoa_surface *w, const struct yasl_private_framebuffer *fb, + const struct yasl_private_damage_list *damage) { - if (!w || w->closed || !w->rendering || w->did_present || !fb || !damage || + if (!w || w->base.presenter != YASL_PRIVATE_PRESENTER_CPU || w->closed || !w->rendering || w->did_present || !fb || !damage || fb->pixels != w->frame.target.pixels || fb->size.x != w->size.x || fb->size.y != w->size.y || !w->drawable) return false; @@ -1116,7 +1346,7 @@ bool cocoa_surface_present(struct cocoa_surface *w, const struct yasl_framebuffe [command presentDrawable:drawable]; [command commit]; } - yasl_clock_presented(&w->clock, yasl_loop_now_ns(), false); + yasl_private_clock_presented(&w->clock, yasl_private_loop_now_ns(), false); w->did_present = true; w->shown_size = w->size; return true; @@ -1124,8 +1354,8 @@ bool cocoa_surface_present(struct cocoa_surface *w, const struct yasl_framebuffe static void render_and_commit(struct cocoa_surface *w) { - if (w->destroyed || w->closed || !w->cbs->draw || w->rendering || w->size.x <= 0 || w->size.y <= 0 || - (w->base.state & YASL_STATE_SUSPENDED)) + if (w->base.presenter != YASL_PRIVATE_PRESENTER_CPU || w->destroyed || w->closed || !w->cbs->draw || w->rendering || w->size.x <= 0 || w->size.y <= 0 || + (w->base.state & YASL_PRIVATE_STATE_SUSPENDED)) return; size_t count = (size_t)w->size.x * (size_t)w->size.y; @@ -1143,17 +1373,17 @@ static void render_and_commit(struct cocoa_surface *w) id<CAMetalDrawable> drawable = [w->layer nextDrawable]; w->drawable = drawable; w->transaction = w->layer.presentsWithTransaction; - w->damage = (struct yasl_damage_list){0}; - w->frame = (struct yasl_frame){ + w->damage = (struct yasl_private_damage_list){0}; + w->frame = (struct yasl_private_frame){ .surface = &w->base, .target = { - .generation = yasl_frame_begin(&w->base), + .generation = yasl_private_frame_begin(&w->base), .pixels = w->software_queue ? w->pixels : NULL, .size = w->size, }, }; if (drawable) - yasl_surface_draw(&w->base, &w->frame); + yasl_private_surface_draw(&w->base, &w->frame); if (!w->destroyed && !w->closed && !w->did_present) { w->want_draw = true; w->frames_skipped++; @@ -1191,7 +1421,7 @@ static void loop_ready(CFFileDescriptorRef fd, CFOptionFlags flags, void *info) /* A kqueue mirroring the loop's poll set, or -1. complete is false when some * descriptor could not be registered. */ -static int loop_kqueue(struct yasl_loop *loop, bool *complete) +static int loop_kqueue(struct yasl_private_loop *loop, bool *complete) { *complete = true; int kq = kqueue(); @@ -1200,7 +1430,7 @@ static int loop_kqueue(struct yasl_loop *loop, bool *complete) return -1; } const struct pollfd *fds; - int n = yasl_loop_poll_set(loop, &fds); + int n = yasl_private_loop_poll_set(loop, &fds); for (int i = 0; i < n; i++) { struct kevent change; if (fds[i].events & POLLIN) { @@ -1238,7 +1468,7 @@ int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms) int count = 0; @try { if (d->loop) { - int timer_to = yasl_loop_next_timeout_ms(d->loop); + int timer_to = yasl_private_loop_next_timeout_ms(d->loop); if (timer_to >= 0 && (timeout_ms < 0 || timer_to < timeout_ms)) { timeout_ms = timer_to; } @@ -1303,7 +1533,7 @@ int cocoa_display_dispatch_timeout(struct cocoa_display *d, int timeout_ms) } if (d->loop) { - yasl_loop_step(d->loop, 0); + yasl_private_loop_step(d->loop, 0); } } @finally { d->depth--; @@ -1331,6 +1561,10 @@ void cocoa_display_flush(struct cocoa_display *d) void cocoa_surface_schedule_redraw(struct cocoa_surface *w) { if (!w || w->closed) return; + if (w->base.presenter != YASL_PRIVATE_PRESENTER_CPU) { + yasl_private_surface_notify_refresh(&w->base); + return; + } w->want_draw = true; w->idle_frames = 0; if (w->link.paused) { @@ -1338,7 +1572,7 @@ void cocoa_surface_schedule_redraw(struct cocoa_surface *w) w->link.paused = NO; } if (darwin_ctx.loop) - yasl_loop_kick(darwin_ctx.loop); + yasl_private_loop_kick(darwin_ctx.loop); } static void app_init(const char *app_name) @@ -1390,17 +1624,17 @@ static void place_window(NSWindow *window) darwin_ctx.cascade = [window cascadeTopLeftFromPoint:darwin_ctx.cascade]; } -struct cocoa_display *cocoa_display_open(struct yasl_loop *loop, const struct yasl_display_spec *spec) +struct cocoa_display *cocoa_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec) { assert([NSThread isMainThread]); if (darwin_ctx.open) return NULL; const char *name = spec && spec->app_id ? spec->app_id : "YASL"; @autoreleasepool { app_init(name); } - darwin_ctx.base.ops = &yasl_cocoa_backend; + darwin_ctx.base.ops = &yasl_private_cocoa_backend; darwin_ctx.base.native_handle = (__bridge void *)[NSApplication sharedApplication]; darwin_ctx.open = true; if (!loop) { - darwin_ctx.loop = yasl_loop_create(); + darwin_ctx.loop = yasl_private_loop_create(); darwin_ctx.owns_loop = true; } else { darwin_ctx.loop = loop; @@ -1408,7 +1642,7 @@ struct cocoa_display *cocoa_display_open(struct yasl_loop *loop, const struct ya } darwin_ctx.base.loop = darwin_ctx.loop; if (spec) { - darwin_ctx.base.callbacks = spec->cbs ? *spec->cbs : (struct yasl_display_cbs){0}; + darwin_ctx.base.callbacks = spec->cbs ? *spec->cbs : (struct yasl_private_display_cbs){0}; darwin_ctx.cbs = &darwin_ctx.base.callbacks; darwin_ctx.ud = spec->ud; darwin_ctx.base.callback_ud = spec->ud; @@ -1421,7 +1655,7 @@ struct cocoa_display *cocoa_display_open(struct yasl_loop *loop, const struct ya void cocoa_display_close(struct cocoa_display *d) { if (!d) return; - drag_end(d, YASL_DRAG_CANCELLED); + drag_end(d, YASL_PRIVATE_DRAG_CANCELLED); assert(!d->count && "destroy every surface before its display"); reap_dead(d); if (d->key_up_monitor) { @@ -1429,7 +1663,7 @@ void cocoa_display_close(struct cocoa_display *d) d->key_up_monitor = nil; } if (d->owns_loop && d->loop) { - yasl_loop_destroy(d->loop); + yasl_private_loop_destroy(d->loop); d->loop = NULL; d->base.loop = NULL; } @@ -1444,14 +1678,15 @@ void cocoa_display_close(struct cocoa_display *d) uint32_t cocoa_display_caps(const struct cocoa_display *d) { - return d ? (YASL_CAP_POPUPS | YASL_CAP_DRAG | YASL_CAP_DRAG_CARRY | - YASL_CAP_PLACE_WINDOW | YASL_CAP_PRIMARY_SELECTION | - YASL_CAP_CSD | YASL_CAP_NATIVE_GRAPHICS | - YASL_CAP_FRAME_PACING | YASL_CAP_TABLET) : 0; + return d ? (YASL_PRIVATE_CAP_POPUPS | YASL_PRIVATE_CAP_DRAG | YASL_PRIVATE_CAP_DRAG_CARRY | + YASL_PRIVATE_CAP_PLACE_WINDOW | YASL_PRIVATE_CAP_PRIMARY_SELECTION | + YASL_PRIVATE_CAP_CSD | YASL_PRIVATE_CAP_NATIVE_GRAPHICS | + YASL_PRIVATE_CAP_FRAME_PACING | YASL_PRIVATE_CAP_TABLET | + YASL_PRIVATE_CAP_TEXT_INPUT) : 0; } struct cocoa_surface *cocoa_surface_create_toplevel(struct cocoa_display *d, - const struct yasl_window_spec *spec, void *graphics_device) + const struct yasl_private_window_spec *spec, void *graphics_device) { assert([NSThread isMainThread]); if (!d || !d->open || !spec) @@ -1468,15 +1703,24 @@ struct cocoa_surface *cocoa_surface_create_toplevel(struct cocoa_display *d, struct cocoa_surface *w = calloc(1, sizeof(*w)); if (!w) return NULL; w->base.display = &d->base; + w->base.creation_flags = spec->flags; + bool external_gpu = spec->external_gpu || graphics_device != NULL; + /* Leave an external-GPU surface unclaimed until the public bridge + * selects Metal or Vulkan. Both bridges request the layer while the + * presenter is still NONE. */ + w->base.presenter = external_gpu ? YASL_PRIVATE_PRESENTER_NONE : YASL_PRIVATE_PRESENTER_CPU; cocoa_surface_set_callbacks(w, NULL, NULL); - w->clock.refresh_ns = YASL_REFRESH_DEFAULT_NS; + w->clock.refresh_ns = YASL_PRIVATE_REFRESH_DEFAULT_NS; const char *t = spec->title ? spec->title : "YASL"; strncpy(w->title, t, sizeof(w->title) - 1); int win_w = spec->size.x > 0 ? spec->size.x : 800; int win_h = spec->size.y > 0 ? spec->size.y : 480; - NSWindowStyleMask style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | - NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; + NSWindowStyleMask style = (spec->flags & YASL_PRIVATE_SURFACE_UNDECORATED) ? + NSWindowStyleMaskBorderless : NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | + NSWindowStyleMaskMiniaturizable; + if (!(spec->flags & (YASL_PRIVATE_SURFACE_UNDECORATED | YASL_PRIVATE_SURFACE_FIXED_SIZE))) + style |= NSWindowStyleMaskResizable; w->window = [[YaslWindow alloc] initWithContentRect:NSMakeRect(0, 0, win_w, win_h) styleMask:style backing:NSBackingStoreBuffered @@ -1499,7 +1743,7 @@ struct cocoa_surface *cocoa_surface_create_toplevel(struct cocoa_display *d, w->layer = (CAMetalLayer *)w->view.layer; if (graphics_device) { w->layer.device = (id<MTLDevice>)graphics_device; - } else { + } else if (!external_gpu) { id<MTLDevice> device = MTLCreateSystemDefaultDevice(); w->layer.device = device; w->software_queue = [device newCommandQueue]; @@ -1526,13 +1770,14 @@ struct cocoa_surface *cocoa_surface_create_toplevel(struct cocoa_display *d, w->view.surface = w; darwin_ctx.windows[darwin_ctx.count++] = w; - [w->window makeKeyAndOrderFront:nil]; - [w->window makeFirstResponder:w->view]; - [NSApp activate]; + if (!(spec->flags & YASL_PRIVATE_SURFACE_HIDDEN)) { + [w->window makeKeyAndOrderFront:nil]; + [w->window makeFirstResponder:w->view]; + } sync_size(w, true); if (spec->tearing) { - w->layer.displaySyncEnabled = NO; + if (w->layer) w->layer.displaySyncEnabled = NO; w->unpaced = true; } if (spec->fullscreen) @@ -1544,6 +1789,7 @@ struct cocoa_surface *cocoa_surface_create_toplevel(struct cocoa_display *d, static void surface_free(struct cocoa_surface *w) { [w->link release]; + [w->gl_context release]; [w->software_buffer release]; [w->software_queue release]; [w->view release]; @@ -1570,7 +1816,7 @@ void cocoa_surface_destroy(struct cocoa_surface *w) if (!w || w->destroyed) return; if (darwin_ctx.drag.active) { if (darwin_ctx.drag.origin == w || darwin_ctx.drag.carried == w) - drag_end(&darwin_ctx, YASL_DRAG_CANCELLED); + drag_end(&darwin_ctx, YASL_PRIVATE_DRAG_CANCELLED); else if (darwin_ctx.drag.hover == w) darwin_ctx.drag.hover = NULL; } @@ -1589,7 +1835,7 @@ void cocoa_surface_destroy(struct cocoa_surface *w) w->tablet_down = false; darwin_ctx.depth++; bool native_closed = w->closed; - static const struct yasl_window_cbs no_callbacks = {0}; + static const struct yasl_private_window_cbs no_callbacks = {0}; w->destroyed = true; w->closed = true; w->cbs = &no_callbacks; @@ -1619,7 +1865,7 @@ void cocoa_surface_destroy(struct cocoa_surface *w) } darwin_ctx.depth--; if (darwin_ctx.loop) - yasl_loop_kick(darwin_ctx.loop); + yasl_private_loop_kick(darwin_ctx.loop); } void cocoa_surface_set_title(struct cocoa_surface *w, const char *title) @@ -1629,7 +1875,7 @@ void cocoa_surface_set_title(struct cocoa_surface *w, const char *title) w->window.title = [NSString stringWithUTF8String:w->title] ?: @"YASL"; } -static bool impl_surface_set_size(struct yasl_surface *surface, struct yasl_vector2i size) +static bool impl_surface_set_size(struct yasl_surface *surface, struct yasl_private_vector2i size) { struct cocoa_surface *w = (struct cocoa_surface *)surface; if (!w || w->closed || w->is_popup || size.x <= 0 || size.y <= 0) { @@ -1693,16 +1939,16 @@ void cocoa_surface_set_background_color(struct cocoa_surface *w, uint32_t bg) dark ? NSAppearanceNameDarkAqua : NSAppearanceNameAqua]; } -void cocoa_surface_set_cursor(struct cocoa_surface *w, enum yasl_cursor cursor) +void cocoa_surface_set_cursor(struct cocoa_surface *w, enum yasl_private_cursor cursor) { - if (!w || w->closed || cursor == w->cursor || (unsigned)cursor > YASL_CURSOR_HIDDEN) return; + if (!w || w->closed || cursor == w->cursor || (unsigned)cursor > YASL_PRIVATE_CURSOR_HIDDEN) return; w->cursor = cursor; NSPoint p = [w->view convertPoint:w->window.mouseLocationOutsideOfEventStream fromView:nil]; if (w->window.isKeyWindow && NSPointInRect(p, w->view.bounds)) [cocoa_cursor(cursor) set]; } -bool cocoa_surface_place_under_pointer(struct cocoa_surface *w, struct yasl_vector2i at) +bool cocoa_surface_place_under_pointer(struct cocoa_surface *w, struct yasl_private_vector2i at) { if (!w || w->closed || !w->window) return false; CGFloat scale = w->window.backingScaleFactor; @@ -1716,7 +1962,7 @@ bool cocoa_surface_place_under_pointer(struct cocoa_surface *w, struct yasl_vect return true; } -void cocoa_surface_set_ime_caret(struct cocoa_surface *w, struct yasl_rect2i caret) +void cocoa_surface_set_ime_caret(struct cocoa_surface *w, struct yasl_private_rect2i caret) { if (w) w->ime_caret = caret; } @@ -1727,26 +1973,26 @@ bool cocoa_surface_ime_preedit_cursor(const struct cocoa_surface *w, int *begin, return false; } -static NSPasteboard *pasteboard_for_selection(enum yasl_selection sel) +static NSPasteboard *pasteboard_for_selection(enum yasl_private_selection sel) { switch (sel) { - case YASL_CLIPBOARD: + case YASL_PRIVATE_CLIPBOARD: return [NSPasteboard generalPasteboard]; - case YASL_PRIMARY: + case YASL_PRIVATE_PRIMARY: return [NSPasteboard pasteboardWithName:@"dev.wsap.yasl.primary"]; default: return nil; } } -static bool cocoa_display_copy_text(struct cocoa_display *d, enum yasl_selection sel, const char *text); +static bool cocoa_display_copy_text(struct cocoa_display *d, enum yasl_private_selection sel, const char *text); -void cocoa_display_copy(struct cocoa_display *d, enum yasl_selection sel, const char *text) +void cocoa_display_copy(struct cocoa_display *d, enum yasl_private_selection sel, const char *text) { (void)cocoa_display_copy_text(d, sel, text); } -static bool cocoa_display_copy_text(struct cocoa_display *d, enum yasl_selection sel, const char *text) +static bool cocoa_display_copy_text(struct cocoa_display *d, enum yasl_private_selection sel, const char *text) { assert([NSThread isMainThread]); if (!d || !text) { errno = EINVAL; return false; } @@ -1763,7 +2009,7 @@ static bool cocoa_display_copy_text(struct cocoa_display *d, enum yasl_selection return true; } -static bool cocoa_display_request_paste(struct cocoa_display *d, enum yasl_selection sel, +static bool cocoa_display_request_paste(struct cocoa_display *d, enum yasl_private_selection sel, struct cocoa_surface *target, uint64_t id) { assert([NSThread isMainThread]); @@ -1776,15 +2022,15 @@ static bool cocoa_display_request_paste(struct cocoa_display *d, enum yasl_selec if (!pb) { errno = EINVAL; return false; } NSString *s = [pb stringForType:NSPasteboardTypeString]; const char *utf8 = s ? s.UTF8String : NULL; - enum yasl_transfer_status status = !s ? YASL_TRANSFER_UNAVAILABLE : - utf8 ? YASL_TRANSFER_OK : YASL_TRANSFER_FAILED; - yasl_surface_complete_paste(&target->base, id, status, + enum yasl_private_transfer_status status = !s ? YASL_PRIVATE_TRANSFER_UNAVAILABLE : + utf8 ? YASL_PRIVATE_TRANSFER_OK : YASL_PRIVATE_TRANSFER_FAILED; + yasl_private_surface_complete_paste(&target->base, id, status, utf8 ? utf8 : NULL, utf8 ? strlen(utf8) : 0); errno = 0; return true; } -void cocoa_display_paste(struct cocoa_display *d, enum yasl_selection sel, struct cocoa_surface *target) +void cocoa_display_paste(struct cocoa_display *d, enum yasl_private_selection sel, struct cocoa_surface *target) { (void)cocoa_display_request_paste(d, sel, target, 0); } @@ -1795,18 +2041,20 @@ int cocoa_display_modifiers(const struct cocoa_display *d) return (int)cocoa_mods(NSEvent.modifierFlags); } -struct yasl_geometry cocoa_surface_geometry(const struct cocoa_surface *w) +struct yasl_private_geometry cocoa_surface_geometry(const struct cocoa_surface *w) { - return (struct yasl_geometry){ - .size = w ? w->size : (struct yasl_vector2i){0}, - .scale_120 = w ? w->scale_120 : YASL_SCALE_UNIT, + return (struct yasl_private_geometry){ + .size = w ? w->size : (struct yasl_private_vector2i){0}, + .scale_120 = w ? w->scale_120 : YASL_PRIVATE_SCALE_UNIT, }; } static bool popup_spec_valid(const struct cocoa_surface *parent, - const struct yasl_popup_spec *spec) + const struct yasl_private_popup_spec *spec) { if (!parent || !spec) return false; + if (spec->positioner_explicit && (spec->anchor_edges > 8 || spec->gravity > 8 || + (spec->constraints & ~63u))) return false; int parent_cx = parent->is_popup ? parent->content_offset.x : 0; int parent_cy = parent->is_popup ? parent->content_offset.y : 0; int64_t x = (int64_t)spec->anchor.x - parent_cx; @@ -1821,7 +2069,7 @@ static bool popup_spec_valid(const struct cocoa_surface *parent, (int64_t)spec->content_offset.y + spec->content_size.y <= spec->size.y; } -static NSRect popup_calc_frame(struct cocoa_surface *parent, const struct yasl_popup_spec *spec) +static NSRect popup_calc_frame_legacy(struct cocoa_surface *parent, const struct yasl_private_popup_spec *spec) { CGFloat scale = parent->window ? parent->window.backingScaleFactor : 1.0; if (scale < 1.0) scale = 1.0; @@ -1885,8 +2133,153 @@ static NSRect popup_calc_frame(struct cocoa_surface *parent, const struct yasl_p return frame; } +enum { + POPUP_EDGE_TOP = 1, + POPUP_EDGE_BOTTOM = 2, + POPUP_EDGE_LEFT = 3, + POPUP_EDGE_RIGHT = 4, + POPUP_EDGE_TOP_LEFT = 5, + POPUP_EDGE_BOTTOM_LEFT = 6, + POPUP_EDGE_TOP_RIGHT = 7, + POPUP_EDGE_BOTTOM_RIGHT = 8, + POPUP_CONSTRAINT_SLIDE_X = 1u << 0, + POPUP_CONSTRAINT_SLIDE_Y = 1u << 1, + POPUP_CONSTRAINT_FLIP_X = 1u << 2, + POPUP_CONSTRAINT_FLIP_Y = 1u << 3, + POPUP_CONSTRAINT_RESIZE_X = 1u << 4, + POPUP_CONSTRAINT_RESIZE_Y = 1u << 5, +}; + +static bool popup_edge_has_left(unsigned edge) +{ return edge == POPUP_EDGE_LEFT || edge == POPUP_EDGE_TOP_LEFT || edge == POPUP_EDGE_BOTTOM_LEFT; } +static bool popup_edge_has_right(unsigned edge) +{ return edge == POPUP_EDGE_RIGHT || edge == POPUP_EDGE_TOP_RIGHT || edge == POPUP_EDGE_BOTTOM_RIGHT; } +static bool popup_edge_has_top(unsigned edge) +{ return edge == POPUP_EDGE_TOP || edge == POPUP_EDGE_TOP_LEFT || edge == POPUP_EDGE_TOP_RIGHT; } +static bool popup_edge_has_bottom(unsigned edge) +{ return edge == POPUP_EDGE_BOTTOM || edge == POPUP_EDGE_BOTTOM_LEFT || edge == POPUP_EDGE_BOTTOM_RIGHT; } + +static unsigned popup_flip_x_edge(unsigned edge) +{ + switch (edge) { + case POPUP_EDGE_LEFT: return POPUP_EDGE_RIGHT; + case POPUP_EDGE_RIGHT: return POPUP_EDGE_LEFT; + case POPUP_EDGE_TOP_LEFT: return POPUP_EDGE_TOP_RIGHT; + case POPUP_EDGE_BOTTOM_LEFT: return POPUP_EDGE_BOTTOM_RIGHT; + case POPUP_EDGE_TOP_RIGHT: return POPUP_EDGE_TOP_LEFT; + case POPUP_EDGE_BOTTOM_RIGHT: return POPUP_EDGE_BOTTOM_LEFT; + default: return edge; + } +} + +static unsigned popup_flip_y_edge(unsigned edge) +{ + switch (edge) { + case POPUP_EDGE_TOP: return POPUP_EDGE_BOTTOM; + case POPUP_EDGE_BOTTOM: return POPUP_EDGE_TOP; + case POPUP_EDGE_TOP_LEFT: return POPUP_EDGE_BOTTOM_LEFT; + case POPUP_EDGE_TOP_RIGHT: return POPUP_EDGE_BOTTOM_RIGHT; + case POPUP_EDGE_BOTTOM_LEFT: return POPUP_EDGE_TOP_LEFT; + case POPUP_EDGE_BOTTOM_RIGHT: return POPUP_EDGE_TOP_RIGHT; + default: return edge; + } +} + +static CGFloat popup_axis_overflow(CGFloat min, CGFloat max, CGFloat visible_min, CGFloat visible_max) +{ + return MAX(0, visible_min - min) + MAX(0, max - visible_max); +} + +static NSRect popup_frame_explicit(struct cocoa_surface *parent, + const struct yasl_private_popup_spec *spec, unsigned anchor, unsigned gravity) +{ + CGFloat scale = parent->window.backingScaleFactor; + if (scale < 1.0) scale = 1.0; + CGFloat parent_x = (parent->is_popup ? parent->content_offset.x : 0) / scale; + CGFloat parent_y = (parent->is_popup ? parent->content_offset.y : 0) / scale; + CGFloat ax = spec->anchor.x / scale - parent_x; + CGFloat ay = spec->anchor.y / scale - parent_y; + CGFloat aw = spec->anchor.width / scale; + CGFloat ah = spec->anchor.height / scale; + CGFloat anchor_x = popup_edge_has_left(anchor) ? ax : + popup_edge_has_right(anchor) ? ax + aw : ax + aw * 0.5; + CGFloat anchor_y = popup_edge_has_top(anchor) ? ay : + popup_edge_has_bottom(anchor) ? ay + ah : ay + ah * 0.5; + CGFloat offset_x = spec->content_offset.x / scale; + CGFloat offset_y = spec->content_offset.y / scale; + CGFloat content_w = (spec->content_size.x ? spec->content_size.x : spec->size.x - spec->content_offset.x) / scale; + CGFloat content_h = (spec->content_size.y ? spec->content_size.y : spec->size.y - spec->content_offset.y) / scale; + CGFloat content_left = popup_edge_has_right(gravity) ? anchor_x : + popup_edge_has_left(gravity) ? anchor_x - content_w : anchor_x - content_w * 0.5; + CGFloat content_top = popup_edge_has_bottom(gravity) ? anchor_y : + popup_edge_has_top(gravity) ? anchor_y - content_h : anchor_y - content_h * 0.5; + CGFloat top_left_x = content_left - offset_x; + CGFloat top_left_y = content_top - offset_y; + + CGFloat view_h = parent->view.bounds.size.height; + NSPoint in_view = NSMakePoint(top_left_x, view_h - top_left_y); + NSPoint in_window = [parent->view convertPoint:in_view toView:nil]; + NSPoint screen_top_left = [parent->window convertPointToScreen:in_window]; + CGFloat width = spec->size.x / scale; + CGFloat height = spec->size.y / scale; + return NSMakeRect(screen_top_left.x, screen_top_left.y - height, width, height); +} + +static NSRect popup_calc_frame(struct cocoa_surface *parent, const struct yasl_private_popup_spec *spec) +{ + if (!spec->positioner_explicit) + return popup_calc_frame_legacy(parent, spec); + + NSRect frame = popup_frame_explicit(parent, spec, spec->anchor_edges, spec->gravity); + NSScreen *screen = parent->window.screen ?: [NSScreen mainScreen]; + if (!screen) return frame; + NSRect visible = screen.visibleFrame; + unsigned constraints = spec->constraints; + + if (constraints & POPUP_CONSTRAINT_FLIP_X) { + NSRect flipped = popup_frame_explicit(parent, spec, + popup_flip_x_edge(spec->anchor_edges), popup_flip_x_edge(spec->gravity)); + if (popup_axis_overflow(flipped.origin.x, NSMaxX(flipped), NSMinX(visible), NSMaxX(visible)) < + popup_axis_overflow(frame.origin.x, NSMaxX(frame), NSMinX(visible), NSMaxX(visible))) + frame = NSMakeRect(flipped.origin.x, frame.origin.y, flipped.size.width, frame.size.height); + } + if (constraints & POPUP_CONSTRAINT_FLIP_Y) { + NSRect flipped = popup_frame_explicit(parent, spec, + popup_flip_y_edge(spec->anchor_edges), popup_flip_y_edge(spec->gravity)); + if (popup_axis_overflow(flipped.origin.y, NSMaxY(flipped), NSMinY(visible), NSMaxY(visible)) < + popup_axis_overflow(frame.origin.y, NSMaxY(frame), NSMinY(visible), NSMaxY(visible))) + frame = NSMakeRect(frame.origin.x, flipped.origin.y, frame.size.width, flipped.size.height); + } + + if (constraints & POPUP_CONSTRAINT_SLIDE_X) { + if (NSMinX(frame) < NSMinX(visible)) frame.origin.x = NSMinX(visible); + if (NSMaxX(frame) > NSMaxX(visible)) frame.origin.x = NSMaxX(visible) - frame.size.width; + } + if (constraints & POPUP_CONSTRAINT_SLIDE_Y) { + if (NSMinY(frame) < NSMinY(visible)) frame.origin.y = NSMinY(visible); + if (NSMaxY(frame) > NSMaxY(visible)) frame.origin.y = NSMaxY(visible) - frame.size.height; + } + if (constraints & POPUP_CONSTRAINT_RESIZE_X) { + if (NSMinX(frame) < NSMinX(visible)) { + frame.size.width = MAX(0, frame.size.width - (NSMinX(visible) - NSMinX(frame))); + frame.origin.x = NSMinX(visible); + } + if (NSMaxX(frame) > NSMaxX(visible)) + frame.size.width = MAX(0, NSMaxX(visible) - frame.origin.x); + } + if (constraints & POPUP_CONSTRAINT_RESIZE_Y) { + if (NSMinY(frame) < NSMinY(visible)) { + frame.size.height = MAX(0, frame.size.height - (NSMinY(visible) - NSMinY(frame))); + frame.origin.y = NSMinY(visible); + } + if (NSMaxY(frame) > NSMaxY(visible)) + frame.size.height = MAX(0, NSMaxY(visible) - frame.origin.y); + } + return frame; +} + struct cocoa_surface *cocoa_surface_create_popup(struct cocoa_surface *parent, - const struct yasl_popup_spec *spec) + const struct yasl_private_popup_spec *spec) { assert([NSThread isMainThread]); if (!parent || parent->closed || !parent->window || !parent->view || @@ -1905,8 +2298,10 @@ struct cocoa_surface *cocoa_surface_create_popup(struct cocoa_surface *parent, struct cocoa_surface *w = calloc(1, sizeof(*w)); if (!w) return NULL; w->base.display = parent->base.display; + w->base.presenter = parent->base.presenter; + w->base.creation_flags = parent->base.creation_flags; cocoa_surface_set_callbacks(w, NULL, NULL); - w->clock.refresh_ns = parent->clock.refresh_ns ? parent->clock.refresh_ns : YASL_REFRESH_DEFAULT_NS; + w->clock.refresh_ns = parent->clock.refresh_ns ? parent->clock.refresh_ns : YASL_PRIVATE_REFRESH_DEFAULT_NS; w->parent = parent; w->is_popup = true; w->content_offset = spec->content_offset; @@ -1968,7 +2363,7 @@ struct cocoa_surface *cocoa_surface_create_popup(struct cocoa_surface *parent, } bool cocoa_surface_popup_reposition(struct cocoa_surface *popup, - const struct yasl_popup_spec *spec) + const struct yasl_private_popup_spec *spec) { if (!popup || popup->closed || !popup->is_popup || !popup->parent || !popup->parent->window || !popup->parent->view || @@ -2020,8 +2415,8 @@ static NSImage *image_from_pixels(const uint32_t *pixels, int width, int height) } bool cocoa_display_drag_start(struct cocoa_display *d, struct cocoa_surface *origin, - const struct yasl_drag_image *image, - const struct yasl_drag_listener *l, void *ud) + const struct yasl_private_drag_image *image, + const struct yasl_private_drag_listener *l, void *ud) { if (!d || !origin || d->drag.active || !l) return false; @@ -2029,7 +2424,7 @@ bool cocoa_display_drag_start(struct cocoa_display *d, struct cocoa_surface *ori d->drag.origin = origin; d->drag.carried = NULL; d->drag.hover = NULL; - d->drag.hover_pos = (struct yasl_vector2i){0}; + d->drag.hover_pos = (struct yasl_private_vector2i){0}; d->drag.listener = *l; d->drag.ud = ud; [d->drag.image release]; @@ -2074,7 +2469,7 @@ bool cocoa_display_drag_start(struct cocoa_display *d, struct cocoa_surface *ori return true; } -bool cocoa_surface_drag_carry(struct cocoa_surface *w, struct yasl_vector2i offset) +bool cocoa_surface_drag_carry(struct cocoa_surface *w, struct yasl_private_vector2i offset) { if (!w || w->closed || !darwin_ctx.drag.active) return false; @@ -2088,33 +2483,33 @@ bool cocoa_surface_drag_carry(struct cocoa_surface *w, struct yasl_vector2i offs return cocoa_surface_place_under_pointer(w, offset); } -bool cocoa_surface_start_interactive_resize(struct cocoa_surface *s, enum yasl_resize_edge edge) +bool cocoa_surface_start_interactive_resize(struct cocoa_surface *s, enum yasl_private_resize_edge edge) { (void)s; (void)edge; return false; } -bool cocoa_surface_show_window_menu(struct cocoa_surface *s, struct yasl_vector2i pos) +bool cocoa_surface_show_window_menu(struct cocoa_surface *s, struct yasl_private_vector2i pos) { (void)s; (void)pos; return false; } void cocoa_surface_set_callbacks(struct cocoa_surface *w, - const struct yasl_window_cbs *cbs, void *ud) + const struct yasl_private_window_cbs *cbs, void *ud) { if (!w || w->closed) return; - w->base.callbacks = cbs ? *cbs : (struct yasl_window_cbs){0}; + w->base.callbacks = cbs ? *cbs : (struct yasl_private_window_cbs){0}; w->base.ud = ud; w->cbs = &w->base.callbacks; w->ud = ud; } -bool cocoa_surface_poll_event(struct cocoa_surface *w, struct yasl_event *ev) +bool cocoa_surface_poll_event(struct cocoa_surface *w, struct yasl_private_event *ev) { if (!w || !ev || !input_queue_pop(&w->input, ev)) return false; - if (ev->type == YASL_EVENT_INPUT_RESET) { + if (ev->type == YASL_PRIVATE_EVENT_INPUT_RESET) { w->buttons = 0; w->tablet_down = false; w->mods = 0; @@ -2126,103 +2521,103 @@ bool cocoa_surface_poll_event(struct cocoa_surface *w, struct yasl_event *ev) return true; } -struct yasl_timing cocoa_surface_timing(const struct cocoa_surface *w) +struct yasl_private_timing cocoa_surface_timing(const struct cocoa_surface *w) { - struct yasl_timing timing = {0}; + struct yasl_private_timing timing = {0}; if (w) { timing.refresh_ns = w->clock.refresh_ns; timing.last_present_ns = w->clock.last_present_ns; - timing.next_present_ns = yasl_clock_next_present_ns(&w->clock); + timing.next_present_ns = yasl_private_clock_next_present_ns(&w->clock); timing.frames_skipped = w->frames_skipped; timing.source = w->clock.source; - yasl_clock_resize_stats(&w->clock, &timing.resize); + yasl_private_clock_resize_stats(&w->clock, &timing.resize); } return timing; } -static struct yasl_display *impl_display_open(struct yasl_loop *loop, const struct yasl_display_spec *spec) +static struct yasl_private_display *impl_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec) { struct cocoa_display *result = cocoa_display_open(loop, spec); return result ? &result->base : NULL; } -static void impl_display_close(struct yasl_display *d) +static void impl_display_close(struct yasl_private_display *d) { cocoa_display_close((struct cocoa_display *)d); } -static uint32_t impl_display_caps(const struct yasl_display *d) +static uint32_t impl_display_caps(const struct yasl_private_display *d) { return cocoa_display_caps((const struct cocoa_display *)d); } -static int impl_display_dispatch_timeout(struct yasl_display *d, int timeout_ms) +static int impl_display_dispatch_timeout(struct yasl_private_display *d, int timeout_ms) { return cocoa_display_dispatch_timeout((struct cocoa_display *)d, timeout_ms); } -static void impl_display_flush(struct yasl_display *d) +static void impl_display_flush(struct yasl_private_display *d) { cocoa_display_flush((struct cocoa_display *)d); } -static int impl_display_double_click_ms(const struct yasl_display *d) +static int impl_display_double_click_ms(const struct yasl_private_display *d) { (void)d; return (int)lround([NSEvent doubleClickInterval] * 1000.0); } -static int impl_display_modifiers(const struct yasl_display *d) +static int impl_display_modifiers(const struct yasl_private_display *d) { return cocoa_display_modifiers((const struct cocoa_display *)d); } -static void impl_display_copy(struct yasl_display *d, enum yasl_selection sel, const char *text) +static void impl_display_copy(struct yasl_private_display *d, enum yasl_private_selection sel, const char *text) { cocoa_display_copy((struct cocoa_display *)d, sel, text); } -static void impl_display_paste(struct yasl_display *d, enum yasl_selection sel, struct yasl_surface *target) +static void impl_display_paste(struct yasl_private_display *d, enum yasl_private_selection sel, struct yasl_surface *target) { cocoa_display_paste((struct cocoa_display *)d, sel, (struct cocoa_surface *)target); } -static bool impl_display_copy_text(struct yasl_display *d, enum yasl_selection sel, const char *text) +static bool impl_display_copy_text(struct yasl_private_display *d, enum yasl_private_selection sel, const char *text) { return cocoa_display_copy_text((struct cocoa_display *)d, sel, text); } -static bool impl_display_request_paste(struct yasl_display *d, enum yasl_selection sel, +static bool impl_display_request_paste(struct yasl_private_display *d, enum yasl_private_selection sel, struct yasl_surface *target, uint64_t request_id) { return cocoa_display_request_paste((struct cocoa_display *)d, sel, (struct cocoa_surface *)target, request_id); } -static bool impl_display_drag_start(struct yasl_display *d, struct yasl_surface *origin, const struct yasl_drag_image *image, const struct yasl_drag_listener *l, void *ud) +static bool impl_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) { return cocoa_display_drag_start((struct cocoa_display *)d, (struct cocoa_surface *)origin, image, l, ud); } -static void impl_display_drag_cancel(struct yasl_display *d) +static void impl_display_drag_cancel(struct yasl_private_display *d) { - drag_end((struct cocoa_display *)d, YASL_DRAG_CANCELLED); + drag_end((struct cocoa_display *)d, YASL_PRIVATE_DRAG_CANCELLED); } -static struct yasl_surface *impl_surface_create_toplevel(struct yasl_display *d, - const struct yasl_window_spec *spec, void *graphics_device) +static struct yasl_surface *impl_surface_create_toplevel(struct yasl_private_display *d, + const struct yasl_private_window_spec *spec, void *graphics_device) { struct cocoa_surface *result = cocoa_surface_create_toplevel((struct cocoa_display *)d, spec, graphics_device); return result ? &result->base : NULL; } -static struct yasl_surface *impl_surface_create_popup(struct yasl_surface *parent, const struct yasl_popup_spec *spec) +static struct yasl_surface *impl_surface_create_popup(struct yasl_surface *parent, const struct yasl_private_popup_spec *spec) { struct cocoa_surface *result = cocoa_surface_create_popup((struct cocoa_surface *)parent, spec); return result ? &result->base : NULL; } -static bool impl_surface_popup_reposition(struct yasl_surface *popup, const struct yasl_popup_spec *spec) +static bool impl_surface_popup_reposition(struct yasl_surface *popup, const struct yasl_private_popup_spec *spec) { return cocoa_surface_popup_reposition((struct cocoa_surface *)popup, spec); } @@ -2238,12 +2633,12 @@ static struct input_queue *impl_surface_input_queue(struct yasl_surface *s) return w ? &w->input : NULL; } -static struct yasl_display *impl_surface_display(const struct yasl_surface *s) +static struct yasl_private_display *impl_surface_display(const struct yasl_surface *s) { return s ? &darwin_ctx.base : NULL; } -static void impl_surface_set_callbacks(struct yasl_surface *s, const struct yasl_window_cbs *cbs, void *ud) +static void impl_surface_set_callbacks(struct yasl_surface *s, const struct yasl_private_window_cbs *cbs, void *ud) { cocoa_surface_set_callbacks((struct cocoa_surface *)s, cbs, ud); } @@ -2258,17 +2653,17 @@ static void impl_surface_schedule_redraw(struct yasl_surface *s) cocoa_surface_schedule_redraw((struct cocoa_surface *)s); } -static bool impl_surface_present(struct yasl_surface *s, const struct yasl_framebuffer *fb, const struct yasl_damage_list *damage) +static bool impl_surface_present(struct yasl_surface *s, const struct yasl_private_framebuffer *fb, const struct yasl_private_damage_list *damage) { return cocoa_surface_present((struct cocoa_surface *)s, fb, damage); } -static struct yasl_geometry impl_surface_geometry(const struct yasl_surface *s) +static struct yasl_private_geometry impl_surface_geometry(const struct yasl_surface *s) { return cocoa_surface_geometry((const struct cocoa_surface *)s); } -static struct yasl_timing impl_surface_timing(const struct yasl_surface *s) +static struct yasl_private_timing impl_surface_timing(const struct yasl_surface *s) { return cocoa_surface_timing((const struct cocoa_surface *)s); } @@ -2276,10 +2671,10 @@ static struct yasl_timing impl_surface_timing(const struct yasl_surface *s) static uint64_t impl_surface_frame_time_ns(struct yasl_surface *s, uint64_t now) { struct cocoa_surface *w = (struct cocoa_surface *)s; - return w ? yasl_clock_frame_timestamp_ns(&w->clock, now) : now; + return w ? yasl_private_clock_frame_timestamp_ns(&w->clock, now) : now; } -static void impl_surface_set_cursor(struct yasl_surface *s, enum yasl_cursor cursor) +static void impl_surface_set_cursor(struct yasl_surface *s, enum yasl_private_cursor cursor) { cocoa_surface_set_cursor((struct cocoa_surface *)s, cursor); } @@ -2300,7 +2695,7 @@ static bool impl_surface_ime_preedit_cursor(const struct yasl_surface *s, int *b return cocoa_surface_ime_preedit_cursor((const struct cocoa_surface *)s, begin, end); } -static void impl_surface_set_ime_caret(struct yasl_surface *s, struct yasl_rect2i caret) +static void impl_surface_set_ime_caret(struct yasl_surface *s, struct yasl_private_rect2i caret) { cocoa_surface_set_ime_caret((struct cocoa_surface *)s, caret); } @@ -2310,35 +2705,165 @@ static bool impl_surface_start_interactive_move(struct yasl_surface *s) return cocoa_surface_start_interactive_move((struct cocoa_surface *)s); } -static bool impl_surface_start_interactive_resize(struct yasl_surface *s, enum yasl_resize_edge edge) +static bool impl_surface_start_interactive_resize(struct yasl_surface *s, enum yasl_private_resize_edge edge) { return cocoa_surface_start_interactive_resize((struct cocoa_surface *)s, edge); } -static bool impl_surface_show_window_menu(struct yasl_surface *s, struct yasl_vector2i pos) +static bool impl_surface_show_window_menu(struct yasl_surface *s, struct yasl_private_vector2i pos) { return cocoa_surface_show_window_menu((struct cocoa_surface *)s, pos); } -static bool impl_surface_drag_carry(struct yasl_surface *s, struct yasl_vector2i offset) +static bool impl_surface_drag_carry(struct yasl_surface *s, struct yasl_private_vector2i offset) { return cocoa_surface_drag_carry((struct cocoa_surface *)s, offset); } -static bool impl_surface_place_under_pointer(struct yasl_surface *s, struct yasl_vector2i at) +static bool impl_surface_place_under_pointer(struct yasl_surface *s, struct yasl_private_vector2i at) { return cocoa_surface_place_under_pointer((struct cocoa_surface *)s, at); } +static struct yasl_surface *impl_surface_create_gl(struct yasl_private_display *display, + const struct yasl_private_window_spec *spec, const int *attrs, struct yasl_surface *share) +{ + struct yasl_private_gl_options options; + if (!spec || !yasl_private_gl_parse_options(attrs, &options) || + options.api != YASL_PRIVATE_GL_OPENGL || options.debug || (share && + (share->display != display || share->presenter != YASL_PRIVATE_PRESENTER_GL))) + return NULL; + int profile = options.profile; + if (profile == YASL_PRIVATE_GL_ANY) + profile = options.major >= 3 ? YASL_PRIVATE_GL_CORE : YASL_PRIVATE_GL_COMPATIBILITY; + if (options.forward_compat && profile != YASL_PRIVATE_GL_CORE) + return NULL; + if ((profile == YASL_PRIVATE_GL_CORE && + (options.major != 3 || options.minor != 2)) || + (profile == YASL_PRIVATE_GL_COMPATIBILITY && + (options.major > 2 || (options.major == 2 && options.minor > 1)))) + return NULL; + + struct yasl_private_window_spec gl_spec = *spec; + gl_spec.external_gpu = true; + struct cocoa_surface *w = cocoa_surface_create_toplevel((struct cocoa_display *)display, &gl_spec, NULL); + if (!w) return NULL; + + NSOpenGLPixelFormatAttribute pixel_attrs[32]; + NSUInteger n = 0; + pixel_attrs[n++] = NSOpenGLPFAAccelerated; + pixel_attrs[n++] = NSOpenGLPFADoubleBuffer; + pixel_attrs[n++] = NSOpenGLPFAColorSize; + pixel_attrs[n++] = options.red_bits + options.green_bits + options.blue_bits + options.alpha_bits; + pixel_attrs[n++] = NSOpenGLPFAAlphaSize; + pixel_attrs[n++] = options.alpha_bits; + pixel_attrs[n++] = NSOpenGLPFADepthSize; + pixel_attrs[n++] = options.depth_bits; + pixel_attrs[n++] = NSOpenGLPFAStencilSize; + pixel_attrs[n++] = options.stencil_bits; + if (options.samples > 0) { + pixel_attrs[n++] = NSOpenGLPFASampleBuffers; + pixel_attrs[n++] = 1; + pixel_attrs[n++] = NSOpenGLPFASamples; + pixel_attrs[n++] = options.samples; + } + if (options.srgb) pixel_attrs[n++] = NSOpenGLPFASRGB; + if (profile == YASL_PRIVATE_GL_CORE) { + pixel_attrs[n++] = NSOpenGLPFAOpenGLProfile; + pixel_attrs[n++] = NSOpenGLProfileVersion3_2Core; + } else if (profile == YASL_PRIVATE_GL_COMPATIBILITY) { + pixel_attrs[n++] = NSOpenGLPFAOpenGLProfile; + pixel_attrs[n++] = NSOpenGLProfileVersionLegacy; + } + pixel_attrs[n++] = 0; + + NSOpenGLPixelFormat *format = [[NSOpenGLPixelFormat alloc] initWithAttributes:pixel_attrs]; + NSOpenGLContext *shared = share ? ((struct cocoa_surface *)share)->gl_context : nil; + if (format) + w->gl_context = [[NSOpenGLContext alloc] initWithFormat:format shareContext:shared]; + [format release]; + if (!w->gl_context) { + cocoa_surface_destroy(w); + reap_dead((struct cocoa_display *)display); + return NULL; + } + [w->view setLayer:[CALayer layer]]; + w->layer = nil; + [w->gl_context setView:w->view]; + w->base.presenter = YASL_PRIVATE_PRESENTER_GL; + return &w->base; +} + +static bool impl_gl_make_current(struct yasl_private_display *display, struct yasl_surface *surface) +{ + (void)display; + if (!surface) { + [NSOpenGLContext clearCurrentContext]; + return true; + } + struct cocoa_surface *w = (struct cocoa_surface *)surface; + if (w->base.presenter != YASL_PRIVATE_PRESENTER_GL || !w->gl_context || w->closed) + return false; + [w->gl_context makeCurrentContext]; + return [NSOpenGLContext currentContext] == w->gl_context; +} + +static yasl_private_gl_proc impl_gl_get_proc_address(struct yasl_private_display *display, const char *name) +{ + (void)display; + return name ? (yasl_private_gl_proc)dlsym(RTLD_DEFAULT, name) : NULL; +} + +static bool impl_gl_swap_interval(struct yasl_private_display *display, struct yasl_surface *surface, int interval) +{ + (void)display; + struct cocoa_surface *w = (struct cocoa_surface *)surface; + if (!w || w->base.presenter != YASL_PRIVATE_PRESENTER_GL || !w->gl_context || w->closed) + return false; + [w->gl_context setValues:&interval forParameter:NSOpenGLCPSwapInterval]; + return true; +} + +static bool impl_gl_swap_buffers(struct yasl_private_display *display, struct yasl_surface *surface) +{ + (void)display; + struct cocoa_surface *w = (struct cocoa_surface *)surface; + if (!w || w->base.presenter != YASL_PRIVATE_PRESENTER_GL || !w->gl_context || w->closed) + return false; + [w->gl_context flushBuffer]; + yasl_private_clock_presented(&w->clock, yasl_private_loop_now_ns(), false); + return true; +} + +static void *impl_surface_get_metal_layer(struct yasl_surface *surface) +{ + struct cocoa_surface *w = (struct cocoa_surface *)surface; + return w && (w->base.presenter == YASL_PRIVATE_PRESENTER_NONE || + w->base.presenter == YASL_PRIVATE_PRESENTER_METAL) ? (void *)w->layer : NULL; +} + +static bool impl_surface_update_text_input(struct yasl_surface *surface) +{ + struct cocoa_surface *w = (struct cocoa_surface *)surface; + if (!w || w->closed || w->destroyed || !w->view) return false; + if (w->base.text_input_active) { + [w->window makeFirstResponder:w->view]; + } else { + [w->view.inputContext discardMarkedText]; + if (w->preedit) [w->view unmarkText]; + } + return true; +} + static bool impl_surface_read_pixels(struct yasl_surface *s, uint32_t *out, size_t cap, - struct yasl_vector2i *size) + struct yasl_private_vector2i *size) { struct cocoa_surface *w = (struct cocoa_surface *)s; /* did_present stays set from the last draw until the next one starts, and * pixels are reallocated only when a draw starts. */ if (w->closed || w->rendering || !w->did_present || !w->software_queue || !w->pixels) return false; - return yasl_copy_frame(w->pixels, w->shown_size, out, cap, size); + return yasl_private_copy_frame(w->pixels, w->shown_size, out, cap, size); } static bool impl_surface_bell(struct yasl_surface *s) @@ -2349,7 +2874,7 @@ static bool impl_surface_bell(struct yasl_surface *s) return true; } -static void *impl_display_get_native_handle(struct yasl_display *d) +static void *impl_display_get_native_handle(struct yasl_private_display *d) { return d ? (void *)NSApp : NULL; } @@ -2361,19 +2886,26 @@ static void *impl_surface_get_native_handle(struct yasl_surface *s) } static bool impl_surface_get_native_frame(struct yasl_surface *s, - const struct yasl_framebuffer *target, struct yasl_native_frame *out) + const struct yasl_private_framebuffer *target, struct yasl_private_native_frame *out) { struct cocoa_surface *w = (struct cocoa_surface *)s; if (w->closed || !w->rendering || w->did_present || !w->drawable || target->pixels != w->frame.target.pixels || target->size.x != w->size.x || target->size.y != w->size.y) return false; - *out = (struct yasl_native_frame){(void *)w->drawable, w->transaction}; + *out = (struct yasl_private_native_frame){(void *)w->drawable, w->transaction}; return true; } -const struct yasl_backend_ops yasl_cocoa_backend = { - .type = YASL_BACKEND_COCOA, +const struct yasl_private_backend_ops yasl_private_cocoa_backend = { + .type = YASL_PRIVATE_BACKEND_COCOA, + .surface_create_gl = impl_surface_create_gl, + .gl_make_current = impl_gl_make_current, + .gl_get_proc_address = impl_gl_get_proc_address, + .gl_swap_interval = impl_gl_swap_interval, + .gl_swap_buffers = impl_gl_swap_buffers, + .surface_get_metal_layer = impl_surface_get_metal_layer, + .surface_update_text_input = impl_surface_update_text_input, .display_open = impl_display_open, .display_close = impl_display_close, .display_caps = impl_display_caps, @@ -2421,20 +2953,20 @@ const struct yasl_backend_ops yasl_cocoa_backend = { .surface_get_native_frame = impl_surface_get_native_frame, }; -NSApplication *yasl_cocoa_display_get_application(struct yasl_display *d) +NSApplication *yasl_private_cocoa_display_get_application(struct yasl_private_display *d) { - return (d && yasl_display_backend(d) == YASL_BACKEND_COCOA) ? - (NSApplication *)yasl_display_get_native_handle(d) : nil; + return (d && yasl_private_display_backend(d) == YASL_PRIVATE_BACKEND_COCOA) ? + (NSApplication *)yasl_private_display_get_native_handle(d) : nil; } -NSWindow *yasl_cocoa_surface_get_window(struct yasl_surface *s) +NSWindow *yasl_private_cocoa_surface_get_window(struct yasl_surface *s) { - return (s && s->display && yasl_display_backend(s->display) == YASL_BACKEND_COCOA) ? - (NSWindow *)yasl_surface_get_native_handle(s) : nil; + return (s && s->display && yasl_private_display_backend(s->display) == YASL_PRIVATE_BACKEND_COCOA) ? + (NSWindow *)yasl_private_surface_get_native_handle(s) : nil; } -NSView *yasl_cocoa_surface_get_view(struct yasl_surface *s) +NSView *yasl_private_cocoa_surface_get_view(struct yasl_surface *s) { - NSWindow *w = yasl_cocoa_surface_get_window(s); + NSWindow *w = yasl_private_cocoa_surface_get_window(s); return w ? w.contentView : nil; } diff --git a/src/platform/freedesktop/dbus.h b/src/platform/freedesktop/dbus.h index fd0aeb1..beda62b 100644 --- a/src/platform/freedesktop/dbus.h +++ b/src/platform/freedesktop/dbus.h @@ -1,5 +1,5 @@ -#ifndef YASL_FREEDESKTOP_DBUS_H -#define YASL_FREEDESKTOP_DBUS_H +#ifndef YASL_PRIVATE_FREEDESKTOP_DBUS_H +#define YASL_PRIVATE_FREEDESKTOP_DBUS_H #include <stdbool.h> #include <stdint.h> diff --git a/src/platform/freedesktop/desktop.c b/src/platform/freedesktop/desktop.c index a8b9bc7..f90701a 100644 --- a/src/platform/freedesktop/desktop.c +++ b/src/platform/freedesktop/desktop.c @@ -1,7 +1,7 @@ #define _GNU_SOURCE #include "common/desktop.h" #include "platform/freedesktop/dbus.h" -#include "yasl/desktop.h" +#include "common/private_desktop.h" #include <errno.h> #include <fcntl.h> @@ -105,21 +105,21 @@ uint32_t desktop_appearance(void) int64_t value; if (portal_read_int("org.freedesktop.appearance", "color-scheme", &value)) { portal = true; - if (value == 1) flags |= YASL_APPEARANCE_DARK; - else if (value == 2) flags |= YASL_APPEARANCE_LIGHT; + if (value == 1) flags |= YASL_PRIVATE_APPEARANCE_DARK; + else if (value == 2) flags |= YASL_PRIVATE_APPEARANCE_LIGHT; } if (portal_read_int("org.freedesktop.appearance", "contrast", &value)) { portal = true; - if (value == 1) flags |= YASL_APPEARANCE_HIGH_CONTRAST; + if (value == 1) flags |= YASL_PRIVATE_APPEARANCE_HIGH_CONTRAST; } if (portal) return flags; /* GTK_THEME is "Name" or "Name:variant", as in "Adwaita:dark". */ const char *theme = getenv("GTK_THEME"); if (!theme) return 0; - if (strstr(theme, "HighContrast")) flags |= YASL_APPEARANCE_HIGH_CONTRAST; + if (strstr(theme, "HighContrast")) flags |= YASL_PRIVATE_APPEARANCE_HIGH_CONTRAST; if (strstr(theme, ":dark") || strstr(theme, "HighContrastInverse")) - flags |= YASL_APPEARANCE_DARK; + flags |= YASL_PRIVATE_APPEARANCE_DARK; return flags; } diff --git a/src/platform/posix/loop.c b/src/platform/posix/loop.c index 3c3d730..49132f8 100644 --- a/src/platform/posix/loop.c +++ b/src/platform/posix/loop.c @@ -14,35 +14,35 @@ #include <time.h> #include <unistd.h> -#define YASL_LOOP_INIT_CAP 32 +#define YASL_PRIVATE_LOOP_INIT_CAP 32 -typedef int (*yasl_loop_fd_fn)(int fd, short revents, void *ud); +typedef int (*yasl_private_loop_fd_fn)(int fd, short revents, void *ud); -struct yasl_fd_source { - yasl_loop_fd_fn fn; +struct yasl_private_fd_source { + yasl_private_loop_fd_fn fn; void *ud; int id; int fd; short events; }; -struct yasl_timer_source { - yasl_timer_cb fn; +struct yasl_private_timer_source { + yasl_private_timer_cb fn; void *ud; uint64_t interval_ns; uint64_t deadline_ns; - yasl_timer_id id; + yasl_private_timer_id id; bool deleted; }; -struct yasl_post { +struct yasl_private_post { void (*fn)(void *ud); void *ud; - struct yasl_post *next; + struct yasl_private_post *next; }; /* Signals this loop watches; the previous actions are restored on unwatch. */ -struct yasl_signal_watch { +struct yasl_private_signal_watch { void (*cb)(void *ud, int signo); void *ud; int count; @@ -50,8 +50,8 @@ struct yasl_signal_watch { struct sigaction old[NSIG]; }; -struct yasl_loop { - struct yasl_fd_source *fds; +struct yasl_private_loop { + struct yasl_private_fd_source *fds; struct pollfd *pollfds; int count; int capacity; @@ -61,17 +61,17 @@ struct yasl_loop { int wake_wfd; int wake_watch; - struct yasl_timer_source *timers; + struct yasl_private_timer_source *timers; int timer_count; int timer_capacity; uint32_t next_timer_id; int timer_firing_depth; - yasl_mutex post_mutex; - struct yasl_post *post_head; - struct yasl_post *post_tail; + yasl_private_mutex post_mutex; + struct yasl_private_post *post_head; + struct yasl_private_post *post_tail; - struct yasl_signal_watch *signals; + struct yasl_private_signal_watch *signals; }; /* Handlers run on any thread, so they only touch lock-free atomics: they mark @@ -79,34 +79,34 @@ struct yasl_loop { * stored as fd + 1 so that the zero initializer means no pipe. */ static atomic_int signal_wake_fd[NSIG]; static atomic_bool signal_pending[NSIG]; -static struct yasl_loop *signal_owner[NSIG]; +static struct yasl_private_loop *signal_owner[NSIG]; -uint64_t yasl_loop_now_ns(void) +uint64_t yasl_private_loop_now_ns(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; } -static void run_posts(struct yasl_loop *l) +static void run_posts(struct yasl_private_loop *l) { - yasl_mutex_lock(&l->post_mutex); - struct yasl_post *p = l->post_head; + yasl_private_mutex_lock(&l->post_mutex); + struct yasl_private_post *p = l->post_head; l->post_head = l->post_tail = NULL; - yasl_mutex_unlock(&l->post_mutex); + yasl_private_mutex_unlock(&l->post_mutex); while (p) { - struct yasl_post *next = p->next; + struct yasl_private_post *next = p->next; p->fn(p->ud); free(p); p = next; } } -static void run_signals(struct yasl_loop *l) +static void run_signals(struct yasl_private_loop *l) { /* A callback may unwatch, so re-check the watch before each delivery. */ for (int signo = 1; signo < NSIG; signo++) { - struct yasl_signal_watch *w = l->signals; + struct yasl_private_signal_watch *w = l->signals; if (!w) return; if (signal_owner[signo] != l || !atomic_exchange(&signal_pending[signo], false)) continue; @@ -120,18 +120,18 @@ static int loop_wake_cb(int fd, short revents, void *ud) char buf[128]; ssize_t n; while ((n = read(fd, buf, sizeof(buf))) > 0 || (n < 0 && errno == EINTR)) {} - struct yasl_loop *l = ud; + struct yasl_private_loop *l = ud; run_posts(l); run_signals(l); return 0; } -struct yasl_loop *yasl_loop_create(void) +struct yasl_private_loop *yasl_private_loop_create(void) { - struct yasl_loop *l = calloc(1, sizeof(*l)); + struct yasl_private_loop *l = calloc(1, sizeof(*l)); if (!l) return NULL; - l->capacity = YASL_LOOP_INIT_CAP; + l->capacity = YASL_PRIVATE_LOOP_INIT_CAP; l->fds = malloc((size_t)l->capacity * sizeof(*l->fds)); l->pollfds = malloc((size_t)l->capacity * sizeof(*l->pollfds)); if (!l->fds || !l->pollfds) { @@ -141,7 +141,7 @@ struct yasl_loop *yasl_loop_create(void) return NULL; } - if (yasl_mutex_init(&l->post_mutex) != 0) { + if (yasl_private_mutex_init(&l->post_mutex) != 0) { free(l->fds); free(l->pollfds); free(l); @@ -173,23 +173,23 @@ struct yasl_loop *yasl_loop_create(void) #endif if (l->wake_rfd >= 0) { - l->wake_watch = yasl_loop_add_fd(l, l->wake_rfd, POLLIN, loop_wake_cb, l); + l->wake_watch = yasl_private_loop_add_fd(l, l->wake_rfd, POLLIN, loop_wake_cb, l); } return l; } -void yasl_loop_destroy(struct yasl_loop *l) +void yasl_private_loop_destroy(struct yasl_private_loop *l) { if (!l) return; - yasl_loop_unwatch_signals(l); - for (struct yasl_post *p = l->post_head; p;) { - struct yasl_post *next = p->next; + yasl_private_loop_unwatch_signals(l); + for (struct yasl_private_post *p = l->post_head; p;) { + struct yasl_private_post *next = p->next; free(p); p = next; } - yasl_mutex_destroy(&l->post_mutex); + yasl_private_mutex_destroy(&l->post_mutex); if (l->wake_watch > 0) - yasl_loop_del_fd(l, l->wake_watch); + yasl_private_loop_del_fd(l, l->wake_watch); if (l->wake_rfd >= 0) close(l->wake_rfd); if (l->wake_wfd >= 0) @@ -200,15 +200,15 @@ void yasl_loop_destroy(struct yasl_loop *l) free(l); } -int yasl_loop_add_fd(struct yasl_loop *l, int fd, short events, +int yasl_private_loop_add_fd(struct yasl_private_loop *l, int fd, short events, int (*fn)(int fd, short revents, void *ud), void *ud) { if (!l || fd < 0) return -1; if (l->count >= l->capacity) { - int new_cap = l->capacity ? l->capacity * 2 : YASL_LOOP_INIT_CAP; - struct yasl_fd_source *new_fds = realloc(l->fds, (size_t)new_cap * sizeof(*new_fds)); + int new_cap = l->capacity ? l->capacity * 2 : YASL_PRIVATE_LOOP_INIT_CAP; + struct yasl_private_fd_source *new_fds = realloc(l->fds, (size_t)new_cap * sizeof(*new_fds)); if (!new_fds) return -1; l->fds = new_fds; @@ -221,7 +221,7 @@ int yasl_loop_add_fd(struct yasl_loop *l, int fd, short events, int id = l->next_id++; if (l->next_id <= 0) l->next_id = 1; - l->fds[l->count] = (struct yasl_fd_source){ + l->fds[l->count] = (struct yasl_private_fd_source){ .fn = fn, .ud = ud, .id = id, .fd = fd, .events = events, }; l->pollfds[l->count] = (struct pollfd){ @@ -231,7 +231,7 @@ int yasl_loop_add_fd(struct yasl_loop *l, int fd, short events, return id; } -int yasl_loop_del_fd(struct yasl_loop *l, int id) +int yasl_private_loop_del_fd(struct yasl_private_loop *l, int id) { if (!l || id <= 0) return -1; for (int i = 0; i < l->count; i++) { @@ -245,7 +245,7 @@ int yasl_loop_del_fd(struct yasl_loop *l, int id) return -1; } -int yasl_loop_set_fd_events(struct yasl_loop *l, int id, short events) +int yasl_private_loop_set_fd_events(struct yasl_private_loop *l, int id, short events) { if (!l || id <= 0) return -1; for (int i = 0; i < l->count; i++) { @@ -270,25 +270,25 @@ static void wake_write(int fd) errno = saved; } -void yasl_loop_kick(struct yasl_loop *l) +void yasl_private_loop_kick(struct yasl_private_loop *l) { if (!l || l->wake_wfd < 0) return; wake_write(l->wake_wfd); } -bool yasl_loop_post(struct yasl_loop *l, void (*fn)(void *ud), void *ud) +bool yasl_private_loop_post(struct yasl_private_loop *l, void (*fn)(void *ud), void *ud) { if (!l || !fn || l->wake_wfd < 0) return false; - struct yasl_post *p = malloc(sizeof(*p)); + struct yasl_private_post *p = malloc(sizeof(*p)); if (!p) return false; - *p = (struct yasl_post){.fn = fn, .ud = ud}; - yasl_mutex_lock(&l->post_mutex); + *p = (struct yasl_private_post){.fn = fn, .ud = ud}; + yasl_private_mutex_lock(&l->post_mutex); if (l->post_tail) l->post_tail->next = p; else l->post_head = p; l->post_tail = p; - yasl_mutex_unlock(&l->post_mutex); + yasl_private_mutex_unlock(&l->post_mutex); wake_write(l->wake_wfd); return true; } @@ -305,7 +305,7 @@ static bool signal_valid(int signo) return signo > 0 && signo < NSIG && signo != SIGKILL && signo != SIGSTOP; } -bool yasl_loop_watch_signals(struct yasl_loop *l, const int *signals, int n, +bool yasl_private_loop_watch_signals(struct yasl_private_loop *l, const int *signals, int n, void (*cb)(void *ud, int signo), void *ud) { if (!l || !signals || n <= 0 || !cb || l->wake_wfd < 0) { @@ -324,14 +324,14 @@ bool yasl_loop_watch_signals(struct yasl_loop *l, const int *signals, int n, } wanted[signals[i]] = true; } - struct yasl_signal_watch *w = calloc(1, sizeof(*w)); + struct yasl_private_signal_watch *w = calloc(1, sizeof(*w)); if (!w) return false; w->cb = cb; w->ud = ud; /* Keep the old watch's saved actions for signals that stay watched, so * that unwatch restores what the application had before any watch. */ - struct yasl_signal_watch *old = l->signals; + struct yasl_private_signal_watch *old = l->signals; struct sigaction sa = {.sa_handler = signal_handler, .sa_flags = SA_RESTART}; sigemptyset(&sa.sa_mask); for (int signo = 1; signo < NSIG; signo++) { @@ -380,10 +380,10 @@ bool yasl_loop_watch_signals(struct yasl_loop *l, const int *signals, int n, return true; } -void yasl_loop_unwatch_signals(struct yasl_loop *l) +void yasl_private_loop_unwatch_signals(struct yasl_private_loop *l) { if (!l || !l->signals) return; - struct yasl_signal_watch *w = l->signals; + struct yasl_private_signal_watch *w = l->signals; l->signals = NULL; for (int i = 0; i < w->count; i++) { int signo = w->signals[i]; @@ -395,19 +395,19 @@ void yasl_loop_unwatch_signals(struct yasl_loop *l) free(w); } -static void timer_cleanup_deleted(struct yasl_loop *l) +static void timer_cleanup_deleted(struct yasl_private_loop *l) { if (!l || l->timer_firing_depth > 0) return; int dest = 0; for (int src = 0; src < l->timer_count; src++) { - struct yasl_timer_source t = l->timers[src]; + struct yasl_private_timer_source t = l->timers[src]; l->timers[dest] = t; dest += !t.deleted; } l->timer_count = dest; } -bool yasl_loop_timer_armed(const struct yasl_loop *l, yasl_timer_id id) +bool yasl_private_loop_timer_armed(const struct yasl_private_loop *l, yasl_private_timer_id id) { if (!l || id == 0) return false; for (int i = 0; i < l->timer_count; i++) @@ -416,7 +416,7 @@ bool yasl_loop_timer_armed(const struct yasl_loop *l, yasl_timer_id id) return false; } -int yasl_loop_poll_set(const struct yasl_loop *l, const struct pollfd **fds) +int yasl_private_loop_poll_set(const struct yasl_private_loop *l, const struct pollfd **fds) { if (!l) { *fds = NULL; @@ -426,10 +426,10 @@ int yasl_loop_poll_set(const struct yasl_loop *l, const struct pollfd **fds) return l->count; } -int yasl_loop_next_timeout_ms(const struct yasl_loop *l) +int yasl_private_loop_next_timeout_ms(const struct yasl_private_loop *l) { if (!l || l->timer_count <= 0) return -1; - uint64_t now = yasl_loop_now_ns(); + uint64_t now = yasl_private_loop_now_ns(); uint64_t min_deadline = UINT64_MAX; bool has_active = false; for (int i = 0; i < l->timer_count; i++) { @@ -446,14 +446,14 @@ int yasl_loop_next_timeout_ms(const struct yasl_loop *l) return (ms > (uint64_t)INT_MAX) ? INT_MAX : (int)ms; } -yasl_timer_id yasl_loop_add_timer(struct yasl_loop *l, uint64_t interval_ns, - yasl_timer_cb cb, void *ud) +yasl_private_timer_id yasl_private_loop_add_timer(struct yasl_private_loop *l, uint64_t interval_ns, + yasl_private_timer_cb cb, void *ud) { if (!l || !cb || interval_ns == 0) return 0; if (l->timer_count >= l->timer_capacity) { int new_cap = l->timer_capacity ? l->timer_capacity * 2 : 16; - struct yasl_timer_source *new_timers = realloc(l->timers, (size_t)new_cap * sizeof(*new_timers)); + struct yasl_private_timer_source *new_timers = realloc(l->timers, (size_t)new_cap * sizeof(*new_timers)); if (!new_timers) return 0; l->timers = new_timers; l->timer_capacity = new_cap; @@ -462,8 +462,8 @@ yasl_timer_id yasl_loop_add_timer(struct yasl_loop *l, uint64_t interval_ns, uint32_t id = l->next_timer_id++; if (l->next_timer_id == 0) l->next_timer_id = 1; - uint64_t now = yasl_loop_now_ns(); - l->timers[l->timer_count++] = (struct yasl_timer_source){ + uint64_t now = yasl_private_loop_now_ns(); + l->timers[l->timer_count++] = (struct yasl_private_timer_source){ .fn = cb, .ud = ud, .id = id, @@ -472,11 +472,11 @@ yasl_timer_id yasl_loop_add_timer(struct yasl_loop *l, uint64_t interval_ns, .deleted = false, }; - yasl_loop_kick(l); + yasl_private_loop_kick(l); return id; } -bool yasl_loop_del_timer(struct yasl_loop *l, yasl_timer_id id) +bool yasl_private_loop_del_timer(struct yasl_private_loop *l, yasl_private_timer_id id) { if (!l || id == 0) return false; for (int i = l->timer_count - 1; i >= 0; i--) { @@ -496,11 +496,11 @@ bool yasl_loop_del_timer(struct yasl_loop *l, yasl_timer_id id) return false; } -int yasl_loop_step(struct yasl_loop *l, int timeout_ms) +int yasl_private_loop_step(struct yasl_private_loop *l, int timeout_ms) { if (!l) return -1; - int timer_to = yasl_loop_next_timeout_ms(l); + int timer_to = yasl_private_loop_next_timeout_ms(l); int effective_timeout = timeout_ms; if (timer_to >= 0) { if (effective_timeout < 0 || timer_to < effective_timeout) @@ -528,9 +528,9 @@ int yasl_loop_step(struct yasl_loop *l, int timeout_ms) if (l->count > 0) { int count = l->count; - struct yasl_fd_source sstack[32]; + struct yasl_private_fd_source sstack[32]; struct pollfd pstack[32]; - struct yasl_fd_source *scopy = (count <= 32) ? sstack : malloc((size_t)count * sizeof(*scopy)); + struct yasl_private_fd_source *scopy = (count <= 32) ? sstack : malloc((size_t)count * sizeof(*scopy)); struct pollfd *pcopy = (count <= 32) ? pstack : malloc((size_t)count * sizeof(*pcopy)); if (!scopy || !pcopy) { if (scopy && scopy != sstack) free(scopy); @@ -570,7 +570,7 @@ int yasl_loop_step(struct yasl_loop *l, int timeout_ms) } if (l->timer_count > 0) { - uint64_t now = yasl_loop_now_ns(); + uint64_t now = yasl_private_loop_now_ns(); l->timer_firing_depth++; int initial_count = l->timer_count; for (int i = 0; i < initial_count; i++) { @@ -578,10 +578,10 @@ int yasl_loop_step(struct yasl_loop *l, int timeout_ms) if (l->timers[i].deleted || l->timers[i].deadline_ns > now) continue; - yasl_timer_id tid = l->timers[i].id; + yasl_private_timer_id tid = l->timers[i].id; uint64_t interval = l->timers[i].interval_ns; void *ud = l->timers[i].ud; - struct yasl_timer_source timer_src = l->timers[i]; + struct yasl_private_timer_source timer_src = l->timers[i]; uint64_t next_interval = timer_src.fn(ud, tid, interval); handled++; @@ -623,18 +623,18 @@ int yasl_loop_step(struct yasl_loop *l, int timeout_ms) return handled; } -int yasl_loop_run(struct yasl_loop *l) +int yasl_private_loop_run(struct yasl_private_loop *l) { if (!l) return -1; l->running = true; while (l->running) { - if (yasl_loop_step(l, -1) < 0) + if (yasl_private_loop_step(l, -1) < 0) return -1; } return 0; } -void yasl_loop_quit(struct yasl_loop *l) +void yasl_private_loop_quit(struct yasl_private_loop *l) { if (l) l->running = false; } diff --git a/src/platform/posix/yasl_platform_sync.h b/src/platform/posix/yasl_platform_sync.h index f94a25d..972eef2 100644 --- a/src/platform/posix/yasl_platform_sync.h +++ b/src/platform/posix/yasl_platform_sync.h @@ -1,26 +1,26 @@ -#ifndef YASL_POSIX_SYNC_H -#define YASL_POSIX_SYNC_H +#ifndef YASL_PRIVATE_POSIX_SYNC_H +#define YASL_PRIVATE_POSIX_SYNC_H #include <pthread.h> -typedef pthread_mutex_t yasl_mutex; +typedef pthread_mutex_t yasl_private_mutex; -static inline int yasl_mutex_init(yasl_mutex *mutex) +static inline int yasl_private_mutex_init(yasl_private_mutex *mutex) { return pthread_mutex_init(mutex, NULL); } -static inline void yasl_mutex_destroy(yasl_mutex *mutex) +static inline void yasl_private_mutex_destroy(yasl_private_mutex *mutex) { pthread_mutex_destroy(mutex); } -static inline void yasl_mutex_lock(yasl_mutex *mutex) +static inline void yasl_private_mutex_lock(yasl_private_mutex *mutex) { pthread_mutex_lock(mutex); } -static inline void yasl_mutex_unlock(yasl_mutex *mutex) +static inline void yasl_private_mutex_unlock(yasl_private_mutex *mutex) { pthread_mutex_unlock(mutex); } diff --git a/src/platform/sync.h b/src/platform/sync.h index 43b4a34..444ff03 100644 --- a/src/platform/sync.h +++ b/src/platform/sync.h @@ -1,8 +1,8 @@ -#ifndef YASL_PLATFORM_SYNC_H -#define YASL_PLATFORM_SYNC_H +#ifndef YASL_PRIVATE_PLATFORM_SYNC_H +#define YASL_PRIVATE_PLATFORM_SYNC_H -/* The OS support directory selected by Meson provides yasl_mutex and the - * yasl_mutex_init/destroy/lock/unlock operations. Init returns zero on success +/* The OS support directory selected by Meson provides yasl_private_mutex and the + * yasl_private_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" // IWYU pragma: export diff --git a/src/platform/wayland/api.h b/src/platform/wayland/api.h index 0f935b2..9670af1 100644 --- a/src/platform/wayland/api.h +++ b/src/platform/wayland/api.h @@ -1,48 +1,48 @@ -#ifndef YASL_WAYLAND_API_H -#define YASL_WAYLAND_API_H +#ifndef YASL_PRIVATE_WAYLAND_API_H +#define YASL_PRIVATE_WAYLAND_API_H -#include "yasl/yasl.h" +#include "common/types.h" struct wayland_display; struct wayland_surface; -struct wayland_display *wayland_display_open(struct yasl_loop *loop, const struct yasl_display_spec *spec); +struct wayland_display *wayland_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec); void wayland_display_close(struct wayland_display *d); uint32_t wayland_display_caps(const struct wayland_display *d); int wayland_display_dispatch_timeout(struct wayland_display *d, int timeout_ms); void wayland_display_flush(struct wayland_display *d); int wayland_display_modifiers(const struct wayland_display *d); -void wayland_display_copy(struct wayland_display *d, enum yasl_selection sel, const char *text); -void wayland_display_paste(struct wayland_display *d, enum yasl_selection sel, struct wayland_surface *target); -bool wayland_display_copy_text(struct wayland_display *d, enum yasl_selection sel, const char *text); -bool wayland_display_request_paste(struct wayland_display *d, enum yasl_selection sel, +void wayland_display_copy(struct wayland_display *d, enum yasl_private_selection sel, const char *text); +void wayland_display_paste(struct wayland_display *d, enum yasl_private_selection sel, struct wayland_surface *target); +bool wayland_display_copy_text(struct wayland_display *d, enum yasl_private_selection sel, const char *text); +bool wayland_display_request_paste(struct wayland_display *d, enum yasl_private_selection sel, struct wayland_surface *target, uint64_t request_id); -bool wayland_display_drag_start(struct wayland_display *d, struct wayland_surface *origin, const struct yasl_drag_image *image, const struct yasl_drag_listener *l, void *ud); +bool wayland_display_drag_start(struct wayland_display *d, struct wayland_surface *origin, const struct yasl_private_drag_image *image, const struct yasl_private_drag_listener *l, void *ud); void wayland_display_drag_cancel(struct wayland_display *d); struct wayland_surface *wayland_surface_create_toplevel(struct wayland_display *d, - const struct yasl_window_spec *spec, void *graphics_device); -struct wayland_surface *wayland_surface_create_popup(struct wayland_surface *parent, const struct yasl_popup_spec *spec); -bool wayland_surface_popup_reposition(struct wayland_surface *popup, const struct yasl_popup_spec *spec); + const struct yasl_private_window_spec *spec, void *graphics_device); +struct wayland_surface *wayland_surface_create_popup(struct wayland_surface *parent, const struct yasl_private_popup_spec *spec); +bool wayland_surface_popup_reposition(struct wayland_surface *popup, const struct yasl_private_popup_spec *spec); void wayland_surface_destroy(struct wayland_surface *s); -void wayland_surface_set_callbacks(struct wayland_surface *s, const struct yasl_window_cbs *cbs, void *ud); +void wayland_surface_set_callbacks(struct wayland_surface *s, const struct yasl_private_window_cbs *cbs, void *ud); void wayland_surface_set_title(struct wayland_surface *s, const char *title); 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_poll_event(struct wayland_surface *s, struct yasl_event *ev); -struct yasl_geometry wayland_surface_geometry(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); -bool wayland_surface_set_pointer_mode(struct wayland_surface *s, enum yasl_pointer_mode mode); +bool wayland_surface_present(struct wayland_surface *s, const struct yasl_private_framebuffer *fb, const struct yasl_private_damage_list *damage); +bool wayland_surface_poll_event(struct wayland_surface *s, struct yasl_private_event *ev); +struct yasl_private_geometry wayland_surface_geometry(const struct wayland_surface *s); +struct yasl_private_timing wayland_surface_timing(const struct wayland_surface *s); +void wayland_surface_set_cursor(struct wayland_surface *s, enum yasl_private_cursor cursor); +bool wayland_surface_set_pointer_mode(struct wayland_surface *s, enum yasl_private_pointer_mode mode); void wayland_surface_set_background_color(struct wayland_surface *s, uint32_t bg); bool wayland_surface_ime_preedit_cursor(const struct wayland_surface *s, int *begin, int *end); -void wayland_surface_set_ime_caret(struct wayland_surface *s, struct yasl_rect2i caret); +void wayland_surface_set_ime_caret(struct wayland_surface *s, struct yasl_private_rect2i caret); bool wayland_surface_start_interactive_move(struct wayland_surface *s); -bool wayland_surface_start_interactive_resize(struct wayland_surface *s, enum yasl_resize_edge edge); -bool wayland_surface_show_window_menu(struct wayland_surface *s, struct yasl_vector2i pos); -bool wayland_surface_drag_carry(struct wayland_surface *s, struct yasl_vector2i offset); -bool wayland_surface_place_under_pointer(struct wayland_surface *s, struct yasl_vector2i at); +bool wayland_surface_start_interactive_resize(struct wayland_surface *s, enum yasl_private_resize_edge edge); +bool wayland_surface_show_window_menu(struct wayland_surface *s, struct yasl_private_vector2i pos); +bool wayland_surface_drag_carry(struct wayland_surface *s, struct yasl_private_vector2i offset); +bool wayland_surface_place_under_pointer(struct wayland_surface *s, struct yasl_private_vector2i at); bool wayland_surface_bell(struct wayland_surface *s); bool wayland_surface_read_pixels(struct wayland_surface *s, uint32_t *out, size_t cap, - struct yasl_vector2i *size); + struct yasl_private_vector2i *size); #endif diff --git a/src/platform/wayland/backend.c b/src/platform/wayland/backend.c index 7026a8c..02aae59 100644 --- a/src/platform/wayland/backend.c +++ b/src/platform/wayland/backend.c @@ -2,50 +2,50 @@ #include "platform/freedesktop/dbus.h" #include <errno.h> -static struct yasl_display *impl_display_open(struct yasl_loop *loop, const struct yasl_display_spec *spec) +static struct yasl_private_display *impl_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec) { struct wayland_display *result = wayland_display_open(loop, spec); return result ? &result->base : NULL; } -static void impl_display_close(struct yasl_display *d) +static void impl_display_close(struct yasl_private_display *d) { wayland_display_close((struct wayland_display *)d); } -static uint32_t impl_display_caps(const struct yasl_display *d) +static uint32_t impl_display_caps(const struct yasl_private_display *d) { return wayland_display_caps((const struct wayland_display *)d); } -static int impl_display_dispatch_timeout(struct yasl_display *d, int timeout_ms) +static int impl_display_dispatch_timeout(struct yasl_private_display *d, int timeout_ms) { return wayland_display_dispatch_timeout((struct wayland_display *)d, timeout_ms); } -static void impl_display_flush(struct yasl_display *d) +static void impl_display_flush(struct yasl_private_display *d) { wayland_display_flush((struct wayland_display *)d); } -static int impl_display_modifiers(const struct yasl_display *d) +static int impl_display_modifiers(const struct yasl_private_display *d) { return wayland_display_modifiers((const struct wayland_display *)d); } -static int impl_display_double_click_ms(const struct yasl_display *d) +static int impl_display_double_click_ms(const struct yasl_private_display *d) { /* Wayland has no protocol for it; desktops publish it as a setting. */ (void)d; return portal_double_click_ms(); } -static void impl_display_copy(struct yasl_display *d, enum yasl_selection sel, const char *text) +static void impl_display_copy(struct yasl_private_display *d, enum yasl_private_selection sel, const char *text) { wayland_display_copy((struct wayland_display *)d, sel, text); } -static bool impl_display_copy_text(struct yasl_display *d, enum yasl_selection sel, +static bool impl_display_copy_text(struct yasl_private_display *d, enum yasl_private_selection sel, const char *text) { bool ok = wayland_display_copy_text((struct wayland_display *)d, sel, text); @@ -53,12 +53,12 @@ static bool impl_display_copy_text(struct yasl_display *d, enum yasl_selection s return ok; } -static void impl_display_paste(struct yasl_display *d, enum yasl_selection sel, struct yasl_surface *target) +static void impl_display_paste(struct yasl_private_display *d, enum yasl_private_selection sel, struct yasl_surface *target) { wayland_display_paste((struct wayland_display *)d, sel, (struct wayland_surface *)target); } -static bool impl_display_request_paste(struct yasl_display *d, enum yasl_selection sel, +static bool impl_display_request_paste(struct yasl_private_display *d, enum yasl_private_selection sel, struct yasl_surface *target, uint64_t request_id) { bool ok = wayland_display_request_paste((struct wayland_display *)d, sel, @@ -67,30 +67,171 @@ static bool impl_display_request_paste(struct yasl_display *d, enum yasl_selecti return ok; } -static bool impl_display_drag_start(struct yasl_display *d, struct yasl_surface *origin, const struct yasl_drag_image *image, const struct yasl_drag_listener *l, void *ud) +static bool impl_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) { return wayland_display_drag_start((struct wayland_display *)d, (struct wayland_surface *)origin, image, l, ud); } -static void impl_display_drag_cancel(struct yasl_display *d) +static void impl_display_drag_cancel(struct yasl_private_display *d) { wayland_display_drag_cancel((struct wayland_display *)d); } -static struct yasl_surface *impl_surface_create_toplevel(struct yasl_display *d, - const struct yasl_window_spec *spec, void *graphics_device) +static struct yasl_surface *impl_surface_create_toplevel(struct yasl_private_display *d, + const struct yasl_private_window_spec *spec, void *graphics_device) { struct wayland_surface *result = wayland_surface_create_toplevel((struct wayland_display *)d, spec, graphics_device); return result ? &result->base : NULL; } -static struct yasl_surface *impl_surface_create_popup(struct yasl_surface *parent, const struct yasl_popup_spec *spec) +static bool impl_surface_update_text_input(struct yasl_surface *s) +{ + return wayland_surface_update_text_input((struct wayland_surface *)s); +} + +#if YASL_HAS_EGL +static bool egl_configure(struct wayland_display *d, const struct yasl_private_gl_options *o, EGLConfig *config) +{ + if (!d->egl_initialized) return false; + EGLint renderable = o->api == YASL_PRIVATE_GL_OPENGL_ES + ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_BIT; +#ifdef EGL_OPENGL_ES_BIT + if (o->api == YASL_PRIVATE_GL_OPENGL_ES && o->major < 2) + renderable = EGL_OPENGL_ES_BIT; +#endif +#ifdef EGL_OPENGL_ES3_BIT_KHR + if (o->api == YASL_PRIVATE_GL_OPENGL_ES && o->major >= 3) + renderable = EGL_OPENGL_ES3_BIT_KHR; +#endif + EGLint attrs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, renderable, + EGL_RED_SIZE, o->red_bits, + EGL_GREEN_SIZE, o->green_bits, + EGL_BLUE_SIZE, o->blue_bits, + EGL_ALPHA_SIZE, o->alpha_bits, + EGL_DEPTH_SIZE, o->depth_bits, + EGL_STENCIL_SIZE, o->stencil_bits, + EGL_SAMPLE_BUFFERS, o->samples > 0 ? 1 : 0, + EGL_SAMPLES, o->samples, + EGL_NONE, + }; + EGLint api = o->api == YASL_PRIVATE_GL_OPENGL_ES ? EGL_OPENGL_ES_API : EGL_OPENGL_API; + if (d->egl_api && d->egl_api != api) return false; + EGLint count = 0; + if (!eglChooseConfig(d->egl_display, attrs, config, 1, &count) || count < 1) + return false; + d->egl_api = api; + return eglBindAPI(api) == EGL_TRUE; +} + +static struct yasl_surface *impl_surface_create_gl(struct yasl_private_display *display, + const struct yasl_private_window_spec *spec, const int *attributes, struct yasl_surface *share) +{ + struct wayland_display *d = (struct wayland_display *)display; + struct yasl_private_gl_options o; + if (!spec || !yasl_private_gl_parse_options(attributes, &o) || !d || !d->egl_initialized) + return NULL; + struct yasl_private_window_spec gl_spec = *spec; + gl_spec.external_gpu = true; + gl_spec.flags &= ~YASL_PRIVATE_SURFACE_CPU; + struct wayland_surface *w = wayland_surface_create_toplevel(d, &gl_spec, NULL); + if (!w) return NULL; + if (!egl_configure(d, &o, &w->egl_config)) goto fail; + EGLContext share_context = share && share->display == display + ? ((struct wayland_surface *)share)->egl_context : EGL_NO_CONTEXT; + if (share && share_context == EGL_NO_CONTEXT) goto fail; + w->egl_window = wl_egl_window_create(w->surface, w->width, w->height); + if (!w->egl_window) goto fail; + EGLint context_attrs[12]; + int n = 0; + if (o.api == YASL_PRIVATE_GL_OPENGL_ES) { + context_attrs[n++] = EGL_CONTEXT_CLIENT_VERSION; + context_attrs[n++] = o.major; + } else { + context_attrs[n++] = EGL_CONTEXT_MAJOR_VERSION_KHR; + context_attrs[n++] = o.major; + context_attrs[n++] = EGL_CONTEXT_MINOR_VERSION_KHR; + context_attrs[n++] = o.minor; + if (o.profile != YASL_PRIVATE_GL_ANY) { + context_attrs[n++] = EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR; + context_attrs[n++] = o.profile == YASL_PRIVATE_GL_CORE + ? EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR + : EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR; + } + EGLint flags = (o.debug ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0) | + (o.forward_compat ? EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR : 0); + if (flags) { + context_attrs[n++] = EGL_CONTEXT_FLAGS_KHR; + context_attrs[n++] = flags; + } + } + context_attrs[n] = EGL_NONE; + w->egl_context = eglCreateContext(d->egl_display, w->egl_config, share_context, context_attrs); + if (w->egl_context == EGL_NO_CONTEXT) goto fail; + EGLint surface_attrs[] = { +#ifdef EGL_GL_COLORSPACE_KHR + EGL_GL_COLORSPACE_KHR, o.srgb ? EGL_GL_COLORSPACE_SRGB_KHR : EGL_GL_COLORSPACE_LINEAR_KHR, +#endif + EGL_NONE, + }; + w->egl_surface = eglCreateWindowSurface(d->egl_display, w->egl_config, + (EGLNativeWindowType)w->egl_window, surface_attrs); + if (w->egl_surface == EGL_NO_SURFACE) { w->egl_surface = 0; goto fail; } + w->base.presenter = YASL_PRIVATE_PRESENTER_GL; + return &w->base; +fail: + wayland_surface_destroy(w); + return NULL; +} + +static bool impl_gl_make_current(struct yasl_private_display *display, struct yasl_surface *surface) +{ + struct wayland_display *d = (struct wayland_display *)display; + struct wayland_surface *w = (struct wayland_surface *)surface; + if (!d) return false; + if (!w) + return eglMakeCurrent(d->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) == EGL_TRUE; + return w->conn == d && w->base.presenter == YASL_PRIVATE_PRESENTER_GL && + eglMakeCurrent(d->egl_display, w->egl_surface, w->egl_surface, w->egl_context) == EGL_TRUE; +} + +static yasl_private_gl_proc impl_gl_get_proc_address(struct yasl_private_display *display, const char *name) +{ + (void)display; + return name ? (yasl_private_gl_proc)eglGetProcAddress(name) : NULL; +} + +static bool impl_gl_swap_interval(struct yasl_private_display *display, struct yasl_surface *surface, int interval) +{ + struct wayland_display *d = (struct wayland_display *)display; + struct wayland_surface *w = (struct wayland_surface *)surface; + return d && w && w->conn == d && w->base.presenter == YASL_PRIVATE_PRESENTER_GL && + impl_gl_make_current(display, surface) && eglSwapInterval(d->egl_display, interval) == EGL_TRUE; +} + +static bool impl_gl_swap_buffers(struct yasl_private_display *display, struct yasl_surface *surface) +{ + struct wayland_display *d = (struct wayland_display *)display; + struct wayland_surface *w = (struct wayland_surface *)surface; + if (!d || !w || w->conn != d || w->closed || !w->configured || + w->base.presenter != YASL_PRIVATE_PRESENTER_GL) + return false; + if (!impl_gl_make_current(display, surface)) return false; + wayland_surface_arm_frame(w); + surface_request_presentation_feedback(w); + if (!eglSwapBuffers(d->egl_display, w->egl_surface)) return false; + return true; +} +#endif + +static struct yasl_surface *impl_surface_create_popup(struct yasl_surface *parent, const struct yasl_private_popup_spec *spec) { struct wayland_surface *result = wayland_surface_create_popup((struct wayland_surface *)parent, spec); return result ? &result->base : NULL; } -static bool impl_surface_popup_reposition(struct yasl_surface *popup, const struct yasl_popup_spec *spec) +static bool impl_surface_popup_reposition(struct yasl_surface *popup, const struct yasl_private_popup_spec *spec) { return wayland_surface_popup_reposition((struct wayland_surface *)popup, spec); } @@ -106,7 +247,7 @@ static struct input_queue *impl_surface_input_queue(struct yasl_surface *s) return w ? &w->input : NULL; } -static bool impl_surface_set_size(struct yasl_surface *s, struct yasl_vector2i size) +static bool impl_surface_set_size(struct yasl_surface *s, struct yasl_private_vector2i size) { struct wayland_surface *w = (struct wayland_surface *)s; if (!w || w->closed || w->destroyed || !w->toplevel || @@ -114,8 +255,19 @@ static bool impl_surface_set_size(struct yasl_surface *s, struct yasl_vector2i s errno = EINVAL; return false; } - errno = ENOTSUP; - return false; + /* Floating windows choose their own size; constrained states keep it for later. */ + w->floating_width = size.x; + w->floating_height = size.y; + if (w->base.state & (YASL_PRIVATE_STATE_MAXIMIZED | YASL_PRIVATE_STATE_FULLSCREEN | YASL_PRIVATE_STATE_TILED)) + return true; + w->req_width = w->req_height = 0; + w->size_pending = true; + if (w->base.presenter != YASL_PRIVATE_PRESENTER_CPU && w->configured && apply_pending_geometry(w) < 0) { + errno = EIO; + return false; + } + wayland_surface_schedule_redraw(w); + return true; } static bool impl_surface_set_fullscreen(struct yasl_surface *s, bool enabled) @@ -153,14 +305,14 @@ static bool impl_surface_minimize(struct yasl_surface *s) return true; } -static struct yasl_display *impl_surface_display(const struct yasl_surface *s) +static struct yasl_private_display *impl_surface_display(const struct yasl_surface *s) { const struct wayland_surface *w = (const struct wayland_surface *)s; struct wayland_display *result = w ? w->conn : NULL; return result ? &result->base : NULL; } -static void impl_surface_set_callbacks(struct yasl_surface *s, const struct yasl_window_cbs *cbs, void *ud) +static void impl_surface_set_callbacks(struct yasl_surface *s, const struct yasl_private_window_cbs *cbs, void *ud) { wayland_surface_set_callbacks((struct wayland_surface *)s, cbs, ud); } @@ -175,37 +327,37 @@ static void impl_surface_schedule_redraw(struct yasl_surface *s) wayland_surface_schedule_redraw((struct wayland_surface *)s); } -static bool impl_surface_present(struct yasl_surface *s, const struct yasl_framebuffer *fb, const struct yasl_damage_list *damage) +static bool impl_surface_present(struct yasl_surface *s, const struct yasl_private_framebuffer *fb, const struct yasl_private_damage_list *damage) { return wayland_surface_present((struct wayland_surface *)s, fb, damage); } -static bool impl_surface_poll_event(struct yasl_surface *s, struct yasl_event *ev) +static bool impl_surface_poll_event(struct yasl_surface *s, struct yasl_private_event *ev) { return wayland_surface_poll_event((struct wayland_surface *)s, ev); } -static struct yasl_geometry impl_surface_geometry(const struct yasl_surface *s) +static struct yasl_private_geometry impl_surface_geometry(const struct yasl_surface *s) { return wayland_surface_geometry((const struct wayland_surface *)s); } -static struct yasl_timing impl_surface_timing(const struct yasl_surface *s) +static struct yasl_private_timing impl_surface_timing(const struct yasl_surface *s) { return wayland_surface_timing((const struct wayland_surface *)s); } static uint64_t impl_surface_frame_time_ns(struct yasl_surface *s, uint64_t now) { - return yasl_clock_frame_timestamp_ns(&((struct wayland_surface *)s)->clock, now); + return yasl_private_clock_frame_timestamp_ns(&((struct wayland_surface *)s)->clock, now); } -static void impl_surface_set_cursor(struct yasl_surface *s, enum yasl_cursor cursor) +static void impl_surface_set_cursor(struct yasl_surface *s, enum yasl_private_cursor cursor) { wayland_surface_set_cursor((struct wayland_surface *)s, cursor); } -static bool impl_surface_set_pointer_mode(struct yasl_surface *s, enum yasl_pointer_mode mode) +static bool impl_surface_set_pointer_mode(struct yasl_surface *s, enum yasl_private_pointer_mode mode) { return wayland_surface_set_pointer_mode((struct wayland_surface *)s, mode); } @@ -226,7 +378,7 @@ static bool impl_surface_ime_preedit_cursor(const struct yasl_surface *s, int *b return wayland_surface_ime_preedit_cursor((const struct wayland_surface *)s, begin, end); } -static void impl_surface_set_ime_caret(struct yasl_surface *s, struct yasl_rect2i caret) +static void impl_surface_set_ime_caret(struct yasl_surface *s, struct yasl_private_rect2i caret) { wayland_surface_set_ime_caret((struct wayland_surface *)s, caret); } @@ -236,28 +388,28 @@ static bool impl_surface_start_interactive_move(struct yasl_surface *s) return wayland_surface_start_interactive_move((struct wayland_surface *)s); } -static bool impl_surface_start_interactive_resize(struct yasl_surface *s, enum yasl_resize_edge edge) +static bool impl_surface_start_interactive_resize(struct yasl_surface *s, enum yasl_private_resize_edge edge) { return wayland_surface_start_interactive_resize((struct wayland_surface *)s, edge); } -static bool impl_surface_show_window_menu(struct yasl_surface *s, struct yasl_vector2i pos) +static bool impl_surface_show_window_menu(struct yasl_surface *s, struct yasl_private_vector2i pos) { return wayland_surface_show_window_menu((struct wayland_surface *)s, pos); } -static bool impl_surface_drag_carry(struct yasl_surface *s, struct yasl_vector2i offset) +static bool impl_surface_drag_carry(struct yasl_surface *s, struct yasl_private_vector2i offset) { return wayland_surface_drag_carry((struct wayland_surface *)s, offset); } -static bool impl_surface_place_under_pointer(struct yasl_surface *s, struct yasl_vector2i at) +static bool impl_surface_place_under_pointer(struct yasl_surface *s, struct yasl_private_vector2i at) { return wayland_surface_place_under_pointer((struct wayland_surface *)s, at); } static bool impl_surface_read_pixels(struct yasl_surface *s, uint32_t *out, size_t cap, - struct yasl_vector2i *size) + struct yasl_private_vector2i *size) { return wayland_surface_read_pixels((struct wayland_surface *)s, out, cap, size); } @@ -267,7 +419,7 @@ static bool impl_surface_bell(struct yasl_surface *s) return wayland_surface_bell((struct wayland_surface *)s); } -static void *impl_display_get_native_handle(struct yasl_display *d) +static void *impl_display_get_native_handle(struct yasl_private_display *d) { struct wayland_display *w = (struct wayland_display *)d; return w ? w->wl : NULL; @@ -279,8 +431,16 @@ static void *impl_surface_get_native_handle(struct yasl_surface *s) return w ? w->surface : NULL; } -const struct yasl_backend_ops yasl_wayland_backend = { - .type = YASL_BACKEND_WAYLAND, +const struct yasl_private_backend_ops yasl_private_wayland_backend = { +#if YASL_HAS_EGL + .surface_create_gl = impl_surface_create_gl, + .gl_make_current = impl_gl_make_current, + .gl_get_proc_address = impl_gl_get_proc_address, + .gl_swap_interval = impl_gl_swap_interval, + .gl_swap_buffers = impl_gl_swap_buffers, +#endif + .surface_update_text_input = impl_surface_update_text_input, + .type = YASL_PRIVATE_BACKEND_WAYLAND, .display_open = impl_display_open, .display_close = impl_display_close, .display_caps = impl_display_caps, diff --git a/src/platform/wayland/buffer.c b/src/platform/wayland/buffer.c index 874cc76..bbc055d 100644 --- a/src/platform/wayland/buffer.c +++ b/src/platform/wayland/buffer.c @@ -24,7 +24,7 @@ static const struct wl_buffer_listener buffer_listener; int device_px(int logical, uint32_t scale_120) { - int64_t px = ((int64_t)logical * scale_120 + YASL_SCALE_UNIT / 2) / YASL_SCALE_UNIT; + int64_t px = ((int64_t)logical * scale_120 + YASL_PRIVATE_SCALE_UNIT / 2) / YASL_PRIVATE_SCALE_UNIT; return px > 0 && px <= INT_MAX ? (int)px : -1; } @@ -58,7 +58,7 @@ int buffer_reserve(struct wayland_surface *w, struct buffer *b, size_t needed) if (b->busy || needed > INT32_MAX) return -1; size_t capacity = b->capacity <= INT32_MAX / 2 ? b->capacity * 2 : INT32_MAX; if (capacity < needed) capacity = needed; - int fd = memfd_create("yasl_surface_buffer", MFD_CLOEXEC | MFD_ALLOW_SEALING); + int fd = memfd_create("yasl_private_surface_buffer", MFD_CLOEXEC | MFD_ALLOW_SEALING); if (fd < 0) return -1; if (!fd_truncate(fd, capacity) || fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_SEAL) < 0) { @@ -89,7 +89,7 @@ int buffer_prepare(struct wayland_surface *w, struct buffer *b, int width, int h { if (b->proxy && !b->busy && b->width == width && b->height == height) return 0; long faults_before = minor_faults(); - uint64_t t0 = yasl_loop_now_ns(); + uint64_t t0 = yasl_private_loop_now_ns(); size_t bytes; if (!buffer_size(width, height, &bytes) || buffer_reserve(w, b, bytes) < 0) return -1; @@ -104,7 +104,7 @@ int buffer_prepare(struct wayland_surface *w, struct buffer *b, int width, int h b->busy = false; b->presented = false; wl_buffer_add_listener(proxy, listener, b); - yasl_clock_note_resize(&w->clock, yasl_loop_now_ns() - t0); + yasl_private_clock_note_resize(&w->clock, yasl_private_loop_now_ns() - t0); long faults = minor_faults() - faults_before; if (faults > 0) w->clock.resize_minflt += faults; return 0; @@ -114,10 +114,10 @@ static void publish_surface_scale(struct wayland_surface *w) { if (w->viewport) { wp_viewport_set_destination(w->viewport, w->logical_width, w->logical_height); - } else if (w->scale_120 % YASL_SCALE_UNIT == 0 && + } else if (w->scale_120 % YASL_PRIVATE_SCALE_UNIT == 0 && wl_proxy_get_version((struct wl_proxy *)w->surface) >= WL_SURFACE_SET_BUFFER_SCALE_SINCE_VERSION) { - wl_surface_set_buffer_scale(w->surface, (int32_t)(w->scale_120 / YASL_SCALE_UNIT)); + wl_surface_set_buffer_scale(w->surface, (int32_t)(w->scale_120 / YASL_PRIVATE_SCALE_UNIT)); } } @@ -137,16 +137,16 @@ int apply_pending_geometry(struct wayland_surface *w) return 0; } bool floating = !w->parent && !(w->base.state & - (YASL_STATE_MAXIMIZED | YASL_STATE_FULLSCREEN | YASL_STATE_TILED)); + (YASL_PRIVATE_STATE_MAXIMIZED | YASL_PRIVATE_STATE_FULLSCREEN | YASL_PRIVATE_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; int logical_h = !w->parent && w->req_height > 0 ? w->req_height : free_h; if (!w->parent) { - 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 (logical_w < YASL_PRIVATE_MIN_LOGICAL_PX) logical_w = YASL_PRIVATE_MIN_LOGICAL_PX; + if (logical_h < YASL_PRIVATE_MIN_LOGICAL_PX) logical_h = YASL_PRIVATE_MIN_LOGICAL_PX; } - if (!(w->base.state & YASL_STATE_FULLSCREEN)) { + if (!(w->base.state & YASL_PRIVATE_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) @@ -155,7 +155,9 @@ int apply_pending_geometry(struct wayland_surface *w) int width = device_px(logical_w, w->pending_scale_120); int height = device_px(logical_h, w->pending_scale_120); size_t bytes; - if (!buffer_size(width, height, &bytes)) { + if (width <= 0 || height <= 0 || + (w->base.presenter == YASL_PRIVATE_PRESENTER_CPU && + !buffer_size(width, height, &bytes))) { return -1; } bool rescaled = w->scale_120 != w->pending_scale_120; @@ -169,13 +171,17 @@ int apply_pending_geometry(struct wayland_surface *w) return 0; w->scale_120 = w->pending_scale_120; - w->scale_factor = (double)w->scale_120 / YASL_SCALE_UNIT; + w->scale_factor = (double)w->scale_120 / YASL_PRIVATE_SCALE_UNIT; w->width = width; w->height = height; - w->base.size = (struct yasl_vector2i){width, height}; + w->base.size = (struct yasl_private_vector2i){width, height}; w->base.scale_120 = w->scale_120; w->logical_width = logical_w; w->logical_height = logical_h; +#if YASL_HAS_EGL + if (w->egl_window && (resized || rescaled)) + wl_egl_window_resize(w->egl_window, width, height, 0, 0); +#endif surface_update_opaque_region(w); if (resized || rescaled) { for (int i = 0; i < NBUF; i++) @@ -183,7 +189,7 @@ int apply_pending_geometry(struct wayland_surface *w) } publish_surface_scale(w); if (resized || rescaled) { - yasl_surface_notify_configure(&w->base, (struct yasl_geometry){ + yasl_private_surface_notify_configure(&w->base, (struct yasl_private_geometry){ .size = { .x = width, .y = height }, .scale_120 = w->scale_120, }); if (w->closed || w->destroyed) @@ -225,7 +231,7 @@ void surface_follow_outputs(struct wayland_surface *w) w->clock.refresh_ns = refresh; if (need_scale) - surface_request_scale(w, (uint32_t)max_scale * YASL_SCALE_UNIT); + surface_request_scale(w, (uint32_t)max_scale * YASL_PRIVATE_SCALE_UNIT); } static void surface_enter(void *data, struct wl_surface *surface, struct wl_output *output) @@ -250,7 +256,7 @@ static void surface_preferred_scale(void *data, struct wl_surface *surface, int3 struct wayland_surface *w = data; if (w->frac_scale || w->parent || factor <= 0) return; - surface_request_scale(w, (uint32_t)factor * YASL_SCALE_UNIT); + surface_request_scale(w, (uint32_t)factor * YASL_PRIVATE_SCALE_UNIT); } static void surface_preferred_transform(void *data, struct wl_surface *surface, @@ -285,13 +291,15 @@ static void frame_done(void *data, struct wl_callback *cb, uint32_t ms) wl_callback_destroy(cb); w->frame_cb = NULL; - uint64_t now = yasl_loop_now_ns(); + uint64_t now = yasl_private_loop_now_ns(); if (!w->conn->pres_clock_known) { - yasl_clock_presented(&w->clock, yasl_clock_extend_ms(ms, now), w->sample_refresh); - w->clock.source = YASL_TIMING_FRAME_CALLBACK; + yasl_private_clock_presented(&w->clock, yasl_private_clock_extend_ms(ms, now), w->sample_refresh); + w->clock.source = YASL_PRIVATE_TIMING_FRAME_CALLBACK; } - if (w->want_draw) + if (w->base.presenter != YASL_PRIVATE_PRESENTER_CPU) + yasl_private_surface_notify_refresh(&w->base); + else if (w->want_draw) wayland_surface_schedule_redraw(w); } @@ -299,14 +307,14 @@ static const struct wl_callback_listener frame_listener = { .done = frame_done, }; -static void arm_frame(struct wayland_surface *w) +void wayland_surface_arm_frame(struct wayland_surface *w) { if (!w->configured || w->closed || w->frame_cb) return; w->frame_cb = wl_surface_frame(w->surface); if (!w->frame_cb) { surface_fail(w); return; } w->sample_refresh = w->clock.last_present_ns && - yasl_loop_now_ns() - w->clock.last_present_ns < + yasl_private_loop_now_ns() - w->clock.last_present_ns < (uint64_t)(w->clock.refresh_ns / 2); if (wl_callback_add_listener(w->frame_cb, &frame_listener, w) < 0) { wl_callback_destroy(w->frame_cb); @@ -365,7 +373,7 @@ static void presented(void *data, struct wp_presentation_feedback *fb, (((uint64_t)tv_sec_hi << 32) | tv_sec_lo) * 1000000000ull + tv_nsec); if (refresh) w->clock.refresh_ns = refresh; w->clock.last_present_ns = ns; - w->clock.source = YASL_TIMING_PRESENTATION; + w->clock.source = YASL_PRIVATE_TIMING_PRESENTATION; } (void)fb; feedback_destroy(feedback); @@ -419,7 +427,7 @@ static void commit_buffer(struct wayland_surface *w, int idx) { wl_surface_attach(w->surface, w->buffers[idx].proxy, 0, 0); - const struct yasl_damage_list *d = &w->damage; + const struct yasl_private_damage_list *d = &w->damage; if (d->n < 0 || !w->buffers[idx].presented) wl_surface_damage_buffer(w->surface, 0, 0, w->width, w->height); else @@ -438,8 +446,8 @@ static void commit_buffer(struct wayland_surface *w, int idx) wp_fifo_v1_set_barrier(w->fifo); } if (w->commit_timer && w->conn->pres_clock_known) { - uint64_t now = yasl_loop_now_ns(); - uint64_t next = yasl_clock_next_present_ns(&w->clock); + uint64_t now = yasl_private_loop_now_ns(); + uint64_t next = yasl_private_clock_next_present_ns(&w->clock); bool soon = next > now && next - now < (uint64_t)(w->clock.refresh_ns * 1.5); next = conn_mono_to_pres(w->conn, next); if (soon) @@ -458,10 +466,11 @@ static void commit_buffer(struct wayland_surface *w, int idx) w->buffers[idx].presented = true; } -bool wayland_surface_present(struct wayland_surface *w, const struct yasl_framebuffer *fb, - const struct yasl_damage_list *damage) +bool wayland_surface_present(struct wayland_surface *w, const struct yasl_private_framebuffer *fb, + const struct yasl_private_damage_list *damage) { - if (!w || w->closed || !w->frame_active || w->frame_presented || !fb || + if (!w || w->base.presenter != YASL_PRIVATE_PRESENTER_CPU || + w->closed || !w->frame_active || w->frame_presented || !fb || !damage || fb->pixels != w->frame.target.pixels || fb->size.x != w->width || fb->size.y != w->height) return false; @@ -470,7 +479,7 @@ bool wayland_surface_present(struct wayland_surface *w, const struct yasl_frameb w->damage.bounds = damage->bounds; if (damage->n > 0) memcpy(w->damage.rects, damage->rects, (size_t)damage->n * sizeof(damage->rects[0])); - arm_frame(w); + wayland_surface_arm_frame(w); if (w->closed) return false; commit_buffer(w, w->cur); @@ -481,17 +490,17 @@ bool wayland_surface_present(struct wayland_surface *w, const struct yasl_frameb } bool wayland_surface_read_pixels(struct wayland_surface *w, uint32_t *out, size_t cap, - struct yasl_vector2i *size) + struct yasl_private_vector2i *size) { if (!w || w->closed || w->frame_active || !w->has_shown) return false; const struct buffer *b = &w->buffers[w->shown]; if (!b->pixels) return false; - return yasl_copy_frame(b->pixels, (struct yasl_vector2i){b->width, b->height}, out, cap, size); + return yasl_private_copy_frame(b->pixels, (struct yasl_private_vector2i){b->width, b->height}, out, cap, size); } void surface_draw_frame(struct wayland_surface *w) { - if (w->closed || !w->configured) + if (w->closed || !w->configured || w->base.presenter != YASL_PRIVATE_PRESENTER_CPU) return; if (apply_pending_geometry(w) < 0) { surface_fail(w); @@ -517,18 +526,18 @@ void surface_draw_frame(struct wayland_surface *w) if (w->parent) memset(w->buffers[idx].pixels, 0, (size_t)w->width * (size_t)w->height * sizeof(uint32_t)); - w->damage = (struct yasl_damage_list){0}; - w->frame = (struct yasl_frame){ + w->damage = (struct yasl_private_damage_list){0}; + w->frame = (struct yasl_private_frame){ .surface = &w->base, .target = { - .generation = yasl_frame_begin(&w->base), + .generation = yasl_private_frame_begin(&w->base), .pixels = w->buffers[idx].pixels, .size = { .x = w->width, .y = w->height }, }, }; w->frame_active = true; w->frame_presented = false; - yasl_surface_draw(&w->base, &w->frame); + yasl_private_surface_draw(&w->base, &w->frame); if (w->closed || w->destroyed) return; w->frame_active = false; @@ -555,21 +564,21 @@ int surface_init_scaling(struct wayland_surface *w) void wayland_surface_set_title(struct wayland_surface *w, const char *title) { if (!w || !w->toplevel || !title) return; - char *copy = yasl_utf8_sanitize_dup(title); + char *copy = yasl_private_utf8_sanitize_dup(title); if (!copy) return; xdg_toplevel_set_title(w->toplevel, copy); free(copy); } -struct yasl_timing wayland_surface_timing(const struct wayland_surface *w) +struct yasl_private_timing wayland_surface_timing(const struct wayland_surface *w) { - struct yasl_timing t = { + struct yasl_private_timing t = { .refresh_ns = w->clock.refresh_ns, .last_present_ns = w->clock.last_present_ns, - .next_present_ns = yasl_clock_next_present_ns(&w->clock), + .next_present_ns = yasl_private_clock_next_present_ns(&w->clock), .frames_skipped = w->frames_skipped, .source = w->clock.source, }; - yasl_clock_resize_stats(&w->clock, &t.resize); + yasl_private_clock_resize_stats(&w->clock, &t.resize); return t; } diff --git a/src/platform/wayland/clipboard.c b/src/platform/wayland/clipboard.c index 8ca9b93..cf39b6c 100644 --- a/src/platform/wayland/clipboard.c +++ b/src/platform/wayland/clipboard.c @@ -25,7 +25,7 @@ struct transfer { struct offer *drop; struct wl_surface *drop_surface; int fd, id; - yasl_timer_id timeout; + yasl_private_timer_id timeout; char *text; size_t len, offset, capacity; uint64_t request_id; @@ -48,7 +48,7 @@ struct clipboard { struct offer *drag_offer; struct primary_offer *primary_selection; struct primary_offer *primary_pending_offers; - struct yasl_loop *loop; + struct yasl_private_loop *loop; struct transfer *transfers; char *text; char *primary_text; @@ -70,7 +70,7 @@ const char *clipboard_drag_mime(void) return mime; } -static void drag_event(struct clipboard *c, enum yasl_drag_event ev, +static void drag_event(struct clipboard *c, enum yasl_private_drag_event ev, wl_fixed_t x, wl_fixed_t y) { if (c->drag) @@ -78,7 +78,7 @@ static void drag_event(struct clipboard *c, enum yasl_drag_event ev, c->drag_ud); } -static void drop_event(struct clipboard *c, enum yasl_drop_event ev) +static void drop_event(struct clipboard *c, enum yasl_private_drop_event ev) { if (c->drop && c->drag_surface) c->drop(ev, c->drag_surface, wl_fixed_to_double(c->drag_x), @@ -89,7 +89,7 @@ static void free_offer(struct offer *o); static void transfer_end(struct transfer *t, bool complete); -static uint64_t transfer_timeout(void *ud, yasl_timer_id id, uint64_t interval) +static uint64_t transfer_timeout(void *ud, yasl_private_timer_id id, uint64_t interval) { (void)interval; struct transfer *t = ud; @@ -106,9 +106,9 @@ static void transfer_end(struct transfer *t, bool complete) struct transfer **p = &owner->transfers; while (*p && *p != t) p = &(*p)->next; if (*p) *p = t->next; - yasl_loop_del_fd(owner->loop, t->id); + yasl_private_loop_del_fd(owner->loop, t->id); if (t->timeout) - yasl_loop_del_timer(owner->loop, t->timeout); + yasl_private_loop_del_timer(owner->loop, t->timeout); close(t->fd); if (t->drop) { if (complete && wl_data_offer_get_version(t->drop->proxy) >= @@ -124,7 +124,7 @@ static void transfer_end(struct transfer *t, bool complete) struct wl_surface *surface = t->drop_surface; uint64_t request_id = t->request_id; bool uri_list = t->uri_list; - enum yasl_transfer_status status = complete ? YASL_TRANSFER_OK : YASL_TRANSFER_FAILED; + enum yasl_private_transfer_status status = complete ? YASL_PRIVATE_TRANSFER_OK : YASL_PRIVATE_TRANSFER_FAILED; const char *text = complete ? (t->text ? t->text : "") : NULL; size_t len = complete ? t->len : 0; if (notify) @@ -156,7 +156,7 @@ static int transfer_io(int fd, short events, void *ud) if (n > 0) { t->len += (size_t)n; } else if (n == 0) { - bool valid = yasl_utf8_validate(t->text, t->len); + bool valid = yasl_private_utf8_validate(t->text, t->len); transfer_end(t, valid); } else if (errno != EAGAIN && errno != EINTR) transfer_end(t, false); } @@ -176,12 +176,12 @@ static struct transfer *start_transfer_text(struct clipboard *c, int fd, bool se if (!t->text) { close(fd); free(t); return NULL; } t->len = strlen(t->text); } - t->id = yasl_loop_add_fd(c->loop, fd, sending ? POLLOUT : POLLIN, transfer_io, t); + t->id = yasl_private_loop_add_fd(c->loop, fd, sending ? POLLOUT : POLLIN, transfer_io, t); if (t->id < 0) { close(fd); free(t->text); free(t); return NULL; } - t->timeout = yasl_loop_add_timer(c->loop, CLIPBOARD_TRANSFER_TIMEOUT_NS, + t->timeout = yasl_private_loop_add_timer(c->loop, CLIPBOARD_TRANSFER_TIMEOUT_NS, transfer_timeout, t); if (!t->timeout) { - yasl_loop_del_fd(c->loop, t->id); + yasl_private_loop_del_fd(c->loop, t->id); close(fd); free(t->text); free(t); @@ -311,9 +311,9 @@ static void drag_offer_enter(void *ud, struct wl_data_device *device, uint32_t s c->drag_y = y; c->drag_surface = surface; if (o && o->tab) - drag_event(c, YASL_DRAG_OVER, x, y); + drag_event(c, YASL_PRIVATE_DRAG_OVER, x, y); else if (o && !o->tab && (o->uri_list || o->mime)) - drop_event(c, YASL_DROP_OVER); + drop_event(c, YASL_PRIVATE_DROP_OVER); } } static void drag_offer_clear(void *ud, struct wl_data_device *device) @@ -321,10 +321,10 @@ static void drag_offer_clear(void *ud, struct wl_data_device *device) (void)device; struct clipboard *c = ud; if (c->drag_offer && c->drag_offer->tab) - drag_event(c, YASL_DRAG_LEAVE, c->drag_x, c->drag_y); + drag_event(c, YASL_PRIVATE_DRAG_LEAVE, c->drag_x, c->drag_y); else if (c->drag_offer && !c->drag_offer->tab && (c->drag_offer->uri_list || c->drag_offer->mime)) - drop_event(c, YASL_DROP_LEAVE); + drop_event(c, YASL_PRIVATE_DROP_LEAVE); free_offer(c->drag_offer); c->drag_offer = NULL; c->drag_surface = NULL; @@ -336,10 +336,10 @@ static void drag_motion(void *ud, struct wl_data_device *device, uint32_t time, c->drag_x = x; c->drag_y = y; if (c->drag_offer && c->drag_offer->tab) - drag_event(c, YASL_DRAG_OVER, x, y); + drag_event(c, YASL_PRIVATE_DRAG_OVER, x, y); else if (c->drag_offer && !c->drag_offer->tab && (c->drag_offer->uri_list || c->drag_offer->mime)) - drop_event(c, YASL_DROP_OVER); + drop_event(c, YASL_PRIVATE_DROP_OVER); } static void drag_drop(void *ud, struct wl_data_device *device) { @@ -350,11 +350,11 @@ static void drag_drop(void *ud, struct wl_data_device *device) c->drag_offer = NULL; bool v3 = wl_data_offer_get_version(o->proxy) >= 3; if (o->tab) { - drag_event(c, YASL_DRAG_DROP, c->drag_x, c->drag_y); + drag_event(c, YASL_PRIVATE_DRAG_DROP, c->drag_x, c->drag_y); if (v3) wl_data_offer_finish(o->proxy); } else if (o && !o->tab && (o->uri_list || o->mime)) { - drop_event(c, YASL_DROP_LEAVE); + drop_event(c, YASL_PRIVATE_DROP_LEAVE); int fds[2]; if (pipe2(fds, O_CLOEXEC) == 0) { wl_data_offer_receive(o->proxy, o->uri_list ? "text/uri-list" : o->mime, fds[1]); @@ -522,7 +522,7 @@ struct wl_data_device *clipboard_data_device(const struct clipboard *c) } struct clipboard *clipboard_create(struct wl_display *display, struct wl_data_device_manager *manager, - struct wl_seat *seat, struct yasl_loop *loop, + struct wl_seat *seat, struct yasl_private_loop *loop, clipboard_receive_fn receive, void *ud) { if (!manager || !seat) return NULL; @@ -603,7 +603,7 @@ bool clipboard_copy(struct clipboard *c, const char *text, uint32_t serial) void clipboard_paste(struct clipboard *c, struct wl_surface *target) { if (c && target) - (void)clipboard_request_paste(c, YASL_CLIPBOARD, target, 0); + (void)clipboard_request_paste(c, YASL_PRIVATE_CLIPBOARD, target, 0); } bool clipboard_copy_primary(struct clipboard *c, const char *text, uint32_t serial) @@ -630,24 +630,24 @@ bool clipboard_copy_primary(struct clipboard *c, const char *text, uint32_t seri void clipboard_paste_primary(struct clipboard *c, struct wl_surface *target) { if (c && target) - (void)clipboard_request_paste(c, YASL_PRIMARY, target, 0); + (void)clipboard_request_paste(c, YASL_PRIVATE_PRIMARY, target, 0); } -bool clipboard_request_paste(struct clipboard *c, enum yasl_selection selection, +bool clipboard_request_paste(struct clipboard *c, enum yasl_private_selection selection, struct wl_surface *target, uint64_t request_id) { if (!c || !target || !c->receive) return false; int fds[2]; - if (selection == YASL_PRIMARY) { + if (selection == YASL_PRIVATE_PRIMARY) { if (!c->primary_device || !c->primary_selection || !c->primary_selection->mime) { c->receive(NULL, 0, false, target, request_id, - YASL_TRANSFER_UNAVAILABLE, c->ud); + YASL_PRIVATE_TRANSFER_UNAVAILABLE, c->ud); return true; } if (pipe2(fds, O_CLOEXEC) < 0) { c->receive(NULL, 0, false, target, request_id, - YASL_TRANSFER_FAILED, c->ud); + YASL_PRIVATE_TRANSFER_FAILED, c->ud); return true; } zwp_primary_selection_offer_v1_receive(c->primary_selection->proxy, @@ -656,12 +656,12 @@ bool clipboard_request_paste(struct clipboard *c, enum yasl_selection selection, } else { if (!c->device || !c->selection || !c->selection->mime) { c->receive(NULL, 0, false, target, request_id, - YASL_TRANSFER_UNAVAILABLE, c->ud); + YASL_PRIVATE_TRANSFER_UNAVAILABLE, c->ud); return true; } if (pipe2(fds, O_CLOEXEC) < 0) { c->receive(NULL, 0, false, target, request_id, - YASL_TRANSFER_FAILED, c->ud); + YASL_PRIVATE_TRANSFER_FAILED, c->ud); return true; } wl_data_offer_receive(c->selection->proxy, c->selection->mime, fds[1]); @@ -670,7 +670,7 @@ bool clipboard_request_paste(struct clipboard *c, enum yasl_selection selection, struct transfer *transfer = start_transfer_paste(c, fds[0], target, request_id); if (!transfer) { c->receive(NULL, 0, false, target, request_id, - YASL_TRANSFER_FAILED, c->ud); + YASL_PRIVATE_TRANSFER_FAILED, c->ud); return true; } if (wl_display_flush(c->display) < 0 && errno != EAGAIN) diff --git a/src/platform/wayland/clipboard.h b/src/platform/wayland/clipboard.h index 133cc14..d03df17 100644 --- a/src/platform/wayland/clipboard.h +++ b/src/platform/wayland/clipboard.h @@ -1,10 +1,10 @@ -#ifndef YASL_WAYLAND_CLIPBOARD_H -#define YASL_WAYLAND_CLIPBOARD_H +#ifndef YASL_PRIVATE_WAYLAND_CLIPBOARD_H +#define YASL_PRIVATE_WAYLAND_CLIPBOARD_H #include <stdint.h> #include <stddef.h> #include <stdbool.h> -#include "yasl/yasl.h" +#include "common/types.h" struct wl_display; struct wl_data_device_manager; @@ -16,27 +16,27 @@ struct zwp_primary_selection_device_manager_v1; typedef void (*clipboard_receive_fn)(const char *, size_t, bool uri_list, struct wl_surface *on, uint64_t request_id, - enum yasl_transfer_status, void *); + enum yasl_private_transfer_status, void *); struct clipboard *clipboard_create(struct wl_display *, struct wl_data_device_manager *, - struct wl_seat *, struct yasl_loop *, + struct wl_seat *, struct yasl_private_loop *, clipboard_receive_fn, void *); void clipboard_destroy(struct clipboard *); bool clipboard_set_seat(struct clipboard *, struct wl_seat *); void clipboard_set_primary_manager(struct clipboard *, struct zwp_primary_selection_device_manager_v1 *); bool clipboard_copy(struct clipboard *, const char *, uint32_t); void clipboard_paste(struct clipboard *, struct wl_surface *target); -bool clipboard_request_paste(struct clipboard *, enum yasl_selection, +bool clipboard_request_paste(struct clipboard *, enum yasl_private_selection, struct wl_surface *target, uint64_t request_id); void clipboard_forget_surface(struct clipboard *, struct wl_surface *); bool clipboard_copy_primary(struct clipboard *, const char *, uint32_t); void clipboard_paste_primary(struct clipboard *, struct wl_surface *target); -typedef void (*clipboard_drag_fn)(enum yasl_drag_event, struct wl_surface *on, +typedef void (*clipboard_drag_fn)(enum yasl_private_drag_event, struct wl_surface *on, double x, double y, void *); void clipboard_set_drag_handler(struct clipboard *, clipboard_drag_fn, void *); -typedef void (*clipboard_drop_fn)(enum yasl_drop_event, struct wl_surface *on, +typedef void (*clipboard_drop_fn)(enum yasl_private_drop_event, struct wl_surface *on, double x, double y, void *); void clipboard_set_drop_handler(struct clipboard *, clipboard_drop_fn, void *); const char *clipboard_drag_mime(void); diff --git a/src/platform/wayland/drag.c b/src/platform/wayland/drag.c index 1f4a78b..07e31b5 100644 --- a/src/platform/wayland/drag.c +++ b/src/platform/wayland/drag.c @@ -11,13 +11,13 @@ static void icon_clear(struct drag_icon *icon) *icon = (struct drag_icon){0}; } -static bool icon_create(struct wayland_display *c, const struct yasl_drag_image *image) +static bool icon_create(struct wayland_display *c, const struct yasl_private_drag_image *image) { struct drag_icon *icon = &c->drag.icon; size_t bytes; if (!image->pixels || !buffer_size(image->size.x, image->size.y, &bytes)) return false; - int fd = memfd_create("yasl_drag_icon", MFD_CLOEXEC | MFD_ALLOW_SEALING); + int fd = memfd_create("yasl_private_drag_icon", MFD_CLOEXEC | MFD_ALLOW_SEALING); if (fd < 0) return false; void *mapping = MAP_FAILED; @@ -48,7 +48,7 @@ static bool icon_create(struct wayland_display *c, const struct yasl_drag_image return true; } -static void icon_show(struct wayland_display *c, const struct yasl_drag_image *image) +static void icon_show(struct wayland_display *c, const struct yasl_private_drag_image *image) { struct drag_icon *icon = &c->drag.icon; uint32_t scale = c->drag.scale_120; @@ -56,18 +56,18 @@ static void icon_show(struct wayland_display *c, const struct yasl_drag_image *i icon->viewport = wp_viewporter_get_viewport(c->viewporter, icon->surface); if (icon->viewport) wp_viewport_set_destination(icon->viewport, - yasl_scale_to_logical(image->size.x, scale), - yasl_scale_to_logical(image->size.y, scale)); - } else if (scale % YASL_SCALE_UNIT == 0 && + yasl_private_scale_to_logical(image->size.x, scale), + yasl_private_scale_to_logical(image->size.y, scale)); + } else if (scale % YASL_PRIVATE_SCALE_UNIT == 0 && wl_proxy_get_version((struct wl_proxy *)icon->surface) >= WL_SURFACE_SET_BUFFER_SCALE_SINCE_VERSION) { - wl_surface_set_buffer_scale(icon->surface, (int32_t)(scale / YASL_SCALE_UNIT)); + wl_surface_set_buffer_scale(icon->surface, (int32_t)(scale / YASL_PRIVATE_SCALE_UNIT)); } if (wl_proxy_get_version((struct wl_proxy *)icon->surface) >= WL_SURFACE_OFFSET_SINCE_VERSION) wl_surface_offset(icon->surface, - -yasl_scale_to_logical(image->hotspot.x, scale), - -yasl_scale_to_logical(image->hotspot.y, scale)); + -yasl_private_scale_to_logical(image->hotspot.x, scale), + -yasl_private_scale_to_logical(image->hotspot.y, scale)); wl_surface_attach(icon->surface, icon->buffer, 0, 0); wl_surface_damage_buffer(icon->surface, 0, 0, image->size.x, image->size.y); wl_surface_commit(icon->surface); @@ -88,9 +88,9 @@ static void drag_clear(struct wayland_display *c, bool ended) *d = (struct conn_drag){0}; } -static void drag_finish(struct wayland_display *c, enum yasl_drag_end how, bool ended) +static void drag_finish(struct wayland_display *c, enum yasl_private_drag_end how, bool ended) { - struct yasl_drag_listener l = c->drag.listener; + struct yasl_private_drag_listener l = c->drag.listener; void *ud = c->drag.ud; drag_clear(c, ended); if (l.end) @@ -100,7 +100,7 @@ static void drag_finish(struct wayland_display *c, enum yasl_drag_end how, bool void conn_drag_end(struct wayland_display *c) { if (c && c->drag.source) - drag_finish(c, YASL_DRAG_CANCELLED, false); + drag_finish(c, YASL_PRIVATE_DRAG_CANCELLED, false); } static void source_target(void *ud, struct wl_data_source *s, const char *mime) { (void)ud; (void)s; (void)mime; } @@ -112,7 +112,7 @@ static void source_cancelled(void *ud, struct wl_data_source *s) (void)s; struct wayland_display *c = ud; bool released = c->drag.dropped; - drag_finish(c, released ? YASL_DRAG_DROPPED : YASL_DRAG_CANCELLED, true); + drag_finish(c, released ? YASL_PRIVATE_DRAG_DROPPED : YASL_PRIVATE_DRAG_CANCELLED, true); } static void source_dropped(void *ud, struct wl_data_source *s) @@ -124,7 +124,7 @@ static void source_dropped(void *ud, struct wl_data_source *s) static void source_finished(void *ud, struct wl_data_source *s) { (void)s; - drag_finish(ud, YASL_DRAG_DROPPED, true); + drag_finish(ud, YASL_PRIVATE_DRAG_DROPPED, true); } static const struct wl_data_source_listener source_listener = { @@ -150,8 +150,8 @@ static struct seat *drag_seat(struct wayland_display *c, struct wayland_surface } bool wayland_display_drag_start(struct wayland_display *c, struct wayland_surface *origin, - const struct yasl_drag_image *image, - const struct yasl_drag_listener *l, void *ud) + const struct yasl_private_drag_image *image, + const struct yasl_private_drag_listener *l, void *ud) { if (!c || !origin || origin->conn != c || !l || c->drag.source) return false; @@ -190,39 +190,39 @@ bool wayland_display_drag_start(struct wayland_display *c, struct wayland_surfac void wayland_display_drag_cancel(struct wayland_display *c) { if (c && c->drag.source) - drag_finish(c, YASL_DRAG_CANCELLED, false); + drag_finish(c, YASL_PRIVATE_DRAG_CANCELLED, false); } -bool wayland_surface_drag_carry(struct wayland_surface *w, struct yasl_vector2i offset) +bool wayland_surface_drag_carry(struct wayland_surface *w, struct yasl_private_vector2i offset) { struct wayland_display *c = w ? w->conn : NULL; if (!c || !c->drag.source || !c->drag.toplevel || !w->toplevel) return false; uint32_t scale = c->drag.scale_120; xdg_toplevel_drag_v1_attach(c->drag.toplevel, w->toplevel, - yasl_scale_to_logical(offset.x, scale), - yasl_scale_to_logical(offset.y, scale)); + yasl_private_scale_to_logical(offset.x, scale), + yasl_private_scale_to_logical(offset.y, scale)); c->drag.carrying = true; return true; } -bool wayland_surface_place_under_pointer(struct wayland_surface *w, struct yasl_vector2i at) +bool wayland_surface_place_under_pointer(struct wayland_surface *w, struct yasl_private_vector2i at) { (void)w; (void)at; return false; } -static struct yasl_vector2i drag_device_pos(const struct wayland_surface *w, double x, double y) +static struct yasl_private_vector2i drag_device_pos(const struct wayland_surface *w, double x, double y) { double dx = x * w->scale_factor, dy = y * w->scale_factor; - return (struct yasl_vector2i){ + return (struct yasl_private_vector2i){ .x = (int)(dx < 0 ? dx - 0.5 : dx + 0.5), .y = (int)(dy < 0 ? dy - 0.5 : dy + 0.5), }; } -void conn_drop_target(struct wayland_display *c, enum yasl_drop_event ev, +void conn_drop_target(struct wayland_display *c, enum yasl_private_drop_event ev, struct wl_surface *on, double x, double y) { (void)c; @@ -230,10 +230,10 @@ void conn_drop_target(struct wayland_display *c, enum yasl_drop_event ev, (struct wayland_surface *)wl_surface_get_user_data(on) : NULL; if (!w || w->parent || w->closed) return; - yasl_surface_notify_drop(&w->base, ev, drag_device_pos(w, x, y)); + yasl_private_surface_notify_drop(&w->base, ev, drag_device_pos(w, x, y)); } -void conn_drag_target(struct wayland_display *c, enum yasl_drag_event ev, +void conn_drag_target(struct wayland_display *c, enum yasl_private_drag_event ev, struct wl_surface *on, double x, double y) { (void)c; @@ -241,5 +241,5 @@ void conn_drag_target(struct wayland_display *c, enum yasl_drag_event ev, (struct wayland_surface *)wl_surface_get_user_data(on) : NULL; if (!w || w->parent || w->closed) return; - yasl_surface_notify_drag(&w->base, ev, drag_device_pos(w, x, y)); + yasl_private_surface_notify_drag(&w->base, ev, drag_device_pos(w, x, y)); } diff --git a/src/platform/wayland/ime.c b/src/platform/wayland/ime.c index 4a091d8..62e5936 100644 --- a/src/platform/wayland/ime.c +++ b/src/platform/wayland/ime.c @@ -1,5 +1,7 @@ #include "internal.h" +#include <errno.h> + #define IME_TEXT_MAX (256u * 1024) static void show_preedit(struct wayland_surface *w, char *text, int begin, int end) @@ -12,7 +14,7 @@ static void show_preedit(struct wayland_surface *w, char *text, int begin, int e w->ime_shown_begin = begin; w->ime_shown_end = end; if (changed) { - yasl_surface_notify_composition(&w->base, w->ime_shown, + yasl_private_surface_notify_composition(&w->base, w->ime_shown, w->ime_shown_begin, w->ime_shown_end); wayland_surface_schedule_redraw(w); } @@ -24,21 +26,63 @@ void ime_reset(struct wayland_surface *w) w->ime_preedit = NULL; free(w->ime_commit); w->ime_commit = NULL; + w->ime_delete_pending = false; show_preedit(w, NULL, -1, -1); } void ime_send_caret(struct wayland_surface *w) { + wayland_surface_update_text_input(w); +} + +bool wayland_surface_update_text_input(struct wayland_surface *w) +{ + if (!w || !w->conn) { + errno = EINVAL; + return false; + } + if (!w->conn->text_input_mgr) { + errno = ENOTSUP; + return false; + } struct seat *seat = w->ime_seat; - if (!seat || !seat->text_input) - return; - uint32_t scale = w->scale_120 ? w->scale_120 : YASL_SCALE_UNIT; + if (!seat || !seat->text_input || seat->ime_focus != w) + return true; + if (!w->base.text_input_active) { + if (seat->ime_enabled) { + zwp_text_input_v3_disable(seat->text_input); + zwp_text_input_v3_commit(seat->text_input); + seat->ime_enabled = false; + } + ime_reset(w); + return true; + } + if (!seat->ime_enabled) { + zwp_text_input_v3_enable(seat->text_input); + seat->ime_enabled = true; + } + unsigned purpose = w->base.text_purpose; + if (purpose > ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL) + purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL; + zwp_text_input_v3_set_content_type(seat->text_input, + ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE, purpose); + const char *surrounding = w->base.text_surrounding ? w->base.text_surrounding : ""; + size_t length = w->base.text_surrounding ? w->base.text_length : 0; + size_t cursor = w->base.text_surrounding ? w->base.text_end : 0; + size_t anchor = w->base.text_surrounding ? w->base.text_begin : 0; + if (cursor > length) cursor = length; + if (anchor > length) anchor = length; + if (length <= INT32_MAX && cursor <= INT32_MAX && anchor <= INT32_MAX) + zwp_text_input_v3_set_surrounding_text(seat->text_input, surrounding, + (int32_t)cursor, (int32_t)anchor); + uint32_t scale = w->scale_120 ? w->scale_120 : YASL_PRIVATE_SCALE_UNIT; zwp_text_input_v3_set_cursor_rectangle(seat->text_input, - yasl_scale_to_logical(w->ime_caret.x, scale), - yasl_scale_to_logical(w->ime_caret.y, scale), - yasl_scale_to_logical(w->ime_caret.width, scale), - yasl_scale_to_logical(w->ime_caret.height, scale)); + yasl_private_scale_to_logical(w->ime_caret.x, scale), + yasl_private_scale_to_logical(w->ime_caret.y, scale), + yasl_private_scale_to_logical(w->ime_caret.width, scale), + yasl_private_scale_to_logical(w->ime_caret.height, scale)); zwp_text_input_v3_commit(seat->text_input); + return true; } bool wayland_surface_ime_preedit_cursor(const struct wayland_surface *w, int *begin, int *end) @@ -50,7 +94,7 @@ bool wayland_surface_ime_preedit_cursor(const struct wayland_surface *w, int *be return true; } -void wayland_surface_set_ime_caret(struct wayland_surface *w, struct yasl_rect2i caret) +void wayland_surface_set_ime_caret(struct wayland_surface *w, struct yasl_private_rect2i caret) { if (!w || (w->ime_caret.x == caret.x && w->ime_caret.y == caret.y && w->ime_caret.width == caret.width && w->ime_caret.height == caret.height)) @@ -62,17 +106,14 @@ void wayland_surface_set_ime_caret(struct wayland_surface *w, struct yasl_rect2i static void text_input_enter(void *data, struct zwp_text_input_v3 *text_input, struct wl_surface *surface) { + (void)text_input; struct seat *s = data; struct wayland_surface *w = surface_root(surface ? (struct wayland_surface *)wl_surface_get_user_data(surface) : NULL); s->ime_focus = w; if (!w) return; - zwp_text_input_v3_enable(text_input); - zwp_text_input_v3_set_content_type(text_input, - ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE, - ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL); w->ime_seat = s; - ime_send_caret(w); + wayland_surface_update_text_input(w); } static void text_input_leave(void *data, struct zwp_text_input_v3 *text_input, @@ -83,8 +124,11 @@ static void text_input_leave(void *data, struct zwp_text_input_v3 *text_input, struct wayland_surface *w = s->ime_focus; s->ime_focus = NULL; if (!w) return; - zwp_text_input_v3_disable(text_input); - zwp_text_input_v3_commit(text_input); + if (s->ime_enabled) { + zwp_text_input_v3_disable(text_input); + zwp_text_input_v3_commit(text_input); + s->ime_enabled = false; + } if (w->ime_seat == s) w->ime_seat = NULL; ime_reset(w); @@ -110,7 +154,7 @@ static void text_input_preedit_string(void *data, { (void)text_input; struct wayland_surface *w = ((struct seat *)data)->ime_focus; - if (!w) return; + if (!w || !w->base.text_input_active) return; keyboard_text_reset((struct seat *)data); free(w->ime_preedit); w->ime_preedit = text ? copy_text(text) : NULL; @@ -126,7 +170,7 @@ static void text_input_commit_string(void *data, { (void)text_input; struct wayland_surface *w = ((struct seat *)data)->ime_focus; - if (!w) return; + if (!w || !w->base.text_input_active) return; keyboard_text_reset((struct seat *)data); free(w->ime_commit); w->ime_commit = (text && text[0]) ? copy_text(text) : NULL; @@ -136,7 +180,31 @@ static void text_input_delete_surrounding_text(void *data, struct zwp_text_input_v3 *text_input, uint32_t before_length, uint32_t after_length) { - (void)data; (void)text_input; (void)before_length; (void)after_length; + (void)text_input; + struct seat *seat = data; + struct wayland_surface *w = seat->ime_focus; + if (!w || !w->base.text_input_active || !w->base.text_surrounding) + return; + size_t length = w->base.text_length; + size_t cursor = w->base.text_end <= length ? w->base.text_end : length; + while (cursor && cursor < length && + (((unsigned char)w->base.text_surrounding[cursor] & 0xc0) == 0x80)) + cursor--; + size_t begin = before_length > cursor ? 0 : cursor - before_length; + size_t end = after_length > length - cursor ? length : cursor + after_length; + while (begin && begin < length && + (((unsigned char)w->base.text_surrounding[begin] & 0xc0) == 0x80)) + begin--; + while (end < length && (((unsigned char)w->base.text_surrounding[end] & 0xc0) == 0x80)) + end++; + /* text_input_v3 batches deletion and commit into one `done` group. Keep + * the replacement range paired with that group's committed text and + * enqueue both together below, preserving snapshot metadata. */ + w->ime_delete_pending = true; + w->ime_delete_begin = begin; + w->ime_delete_end = end; + w->ime_delete_revision = w->base.text_revision; + w->ime_delete_generation = w->base.text_generation; } static void text_input_done(void *data, struct zwp_text_input_v3 *text_input, @@ -144,17 +212,46 @@ static void text_input_done(void *data, struct zwp_text_input_v3 *text_input, { (void)text_input; (void)serial; struct wayland_surface *w = ((struct seat *)data)->ime_focus; - if (!w) return; - if (w->ime_commit) { - struct yasl_event ev = { - .type = YASL_EVENT_TEXT, + if (!w || !w->base.text_input_active) return; + bool has_snapshot = w->base.text_surrounding != NULL && + w->base.text_length <= INT32_MAX; + bool has_commit = w->ime_commit && w->ime_commit[0]; + bool edit = w->ime_delete_pending || (has_commit && has_snapshot); + if (edit) { + size_t begin = w->ime_delete_pending ? w->ime_delete_begin : w->base.text_begin; + size_t end = w->ime_delete_pending ? w->ime_delete_end : w->base.text_end; + if (begin > w->base.text_length) begin = w->base.text_length; + if (end > w->base.text_length) end = w->base.text_length; + if (end < begin) end = begin; + struct yasl_private_event ev = { + .type = YASL_PRIVATE_EVENT_TEXT_EDIT, + .surface = &w->base, + .text_edit = { + .data = has_commit ? w->ime_commit : "", + .len = has_commit ? strlen(w->ime_commit) : 0, + .revision = w->ime_delete_revision, + .generation = w->ime_delete_generation, + .begin = begin, + .end = end, + }, + }; + if (!w->ime_delete_pending) { + ev.text_edit.revision = w->base.text_revision; + ev.text_edit.generation = w->base.text_generation; + } + input_queue_push(&w->input, &ev); + w->ime_delete_pending = false; + } + if (w->ime_commit && !edit) { + struct yasl_private_event ev = { + .type = YASL_PRIVATE_EVENT_TEXT, .surface = &w->base, .text = { .data = w->ime_commit, .len = strlen(w->ime_commit) }, }; input_queue_push(&w->input, &ev); - free(w->ime_commit); - w->ime_commit = NULL; } + free(w->ime_commit); + w->ime_commit = NULL; char *preedit = w->ime_preedit && w->ime_preedit[0] ? w->ime_preedit : NULL; if (!preedit) free(w->ime_preedit); diff --git a/src/platform/wayland/internal.h b/src/platform/wayland/internal.h index 34d4133..a479e2b 100644 --- a/src/platform/wayland/internal.h +++ b/src/platform/wayland/internal.h @@ -1,5 +1,5 @@ -#ifndef YASL_WAYLAND_INTERNAL_H -#define YASL_WAYLAND_INTERNAL_H +#ifndef YASL_PRIVATE_WAYLAND_INTERNAL_H +#define YASL_PRIVATE_WAYLAND_INTERNAL_H #define _GNU_SOURCE @@ -10,6 +10,11 @@ #include <wayland-client.h> #include <wayland-cursor.h> +#if YASL_HAS_EGL +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <wayland-egl.h> +#endif #include "protocols/xdg-shell-client-protocol.h" #include "protocols/xdg-decoration-client-protocol.h" #include "protocols/cursor-shape-v1-client-protocol.h" @@ -73,7 +78,7 @@ struct wl_conn_output { struct pending_press { struct wayland_surface *surface; - struct yasl_vector2i pos; + struct yasl_private_vector2i pos; int button; bool release; }; @@ -105,7 +110,7 @@ struct conn_drag { struct wl_data_source *source; struct xdg_toplevel_drag_v1 *toplevel; struct drag_icon icon; - struct yasl_drag_listener listener; + struct yasl_private_drag_listener listener; void *ud; struct seat *seat; uint32_t scale_120; @@ -160,15 +165,20 @@ struct seat { uint64_t pointer_buttons; uint64_t tool_buttons; double axis[2], axis_value120[2]; + double axis_pixels[2]; int64_t axis_discrete[2]; + uint32_t axis_source; + bool scroll_gesture, axis_stop_pending; + unsigned axis_phase; + bool ime_enabled; bool axis_has_value120[2], axis_has_discrete[2]; double touch_scroll_y; /* Embedded structs */ xkb_mod_mask_t input_mod_masks[8]; - struct yasl_vector2i pointer_pos; - struct yasl_vector2i ptr_left_pos; - struct yasl_vector2i tool_pos; + struct yasl_private_vector2i pointer_pos; + struct yasl_private_vector2i ptr_left_pos; + struct yasl_private_vector2i tool_pos; struct touch_point touch_points[SEAT_TOUCH_POINTS]; struct press_queue ptr_presses; struct press_queue tool_presses; @@ -183,7 +193,7 @@ struct seat { uint32_t press_serial; uint32_t pointer_serial; int cursor_shape; - struct yasl_pointer_pen pen; + struct yasl_private_pointer_pen pen; int tool_wheel; /* 1-byte booleans grouped tightly */ @@ -199,9 +209,14 @@ struct seat { #include "common/input.h" struct wayland_display { - struct yasl_display base; + struct yasl_private_display base; /* 8-byte pointers & 64-bit masks */ struct wl_display *wl; +#if YASL_HAS_EGL + EGLDisplay egl_display; + EGLint egl_api; + bool egl_initialized; +#endif struct wl_registry *registry; struct wl_compositor *compositor; struct wl_shm *shm; @@ -228,8 +243,8 @@ struct wayland_display { struct wp_fifo_manager_v1 *fifo_mgr; struct wp_commit_timing_manager_v1 *commit_timing_mgr; struct xkb_context *xkb_ctx; - struct yasl_loop *loop; - const struct yasl_display_cbs *cbs; + struct yasl_private_loop *loop; + const struct yasl_private_display_cbs *cbs; void *ud; char *app_id; struct xdg_toplevel_drag_manager_v1 *toplevel_drag_mgr; @@ -243,6 +258,7 @@ struct wayland_display { clockid_t pres_clock; int watch; int depth; + uint64_t next_tool_id; /* 1-byte booleans */ bool owns_loop; @@ -266,6 +282,12 @@ struct wayland_surface { struct wayland_surface *dead_next; struct wayland_surface *parent; struct wl_surface *surface; +#if YASL_HAS_EGL + struct wl_egl_window *egl_window; + EGLSurface egl_surface; + EGLContext egl_context; + EGLConfig egl_config; +#endif struct xdg_surface *xdg_surface; struct xdg_toplevel *toplevel; struct xdg_popup *popup; @@ -276,6 +298,8 @@ struct wayland_surface { char *ime_commit; char *ime_shown; struct seat *ime_seat; + size_t ime_delete_begin, ime_delete_end; + uint64_t ime_delete_revision, ime_delete_generation; struct wp_tearing_control_v1 *tearing_control; struct latency_feedback *latency_feedbacks; struct latency_feedback latency_pool[8]; @@ -285,24 +309,24 @@ struct wayland_surface { /* Persistent lock held while relative; lock_seat owns the locked wl_pointer. */ struct zwp_locked_pointer_v1 *pointer_lock; struct seat *lock_seat; - const struct yasl_window_cbs *cbs; + const struct yasl_private_window_cbs *cbs; void *ud; uint64_t frames_skipped; uint64_t outputs; double scale_factor; /* Sub-structures */ - struct yasl_rect2i ime_caret; - struct yasl_rect2i content; - struct yasl_frame_clock clock; + struct yasl_private_rect2i ime_caret; + struct yasl_private_rect2i content; + struct yasl_private_frame_clock clock; struct buffer buffers[NBUF]; - struct yasl_frame frame; - struct yasl_damage_list damage; + struct yasl_private_frame frame; + struct yasl_private_damage_list damage; int ime_cursor_begin; struct input_queue input; /* 4-byte integers */ - enum yasl_cursor cursor; + enum yasl_private_cursor cursor; int ime_cursor_end; int ime_shown_begin, ime_shown_end; int floating_width, floating_height; @@ -331,6 +355,7 @@ struct wayland_surface { bool want_draw; bool relative; bool pointer_locked; + bool ime_delete_pending; }; @@ -343,7 +368,7 @@ int buffer_prepare(struct wayland_surface *w, struct buffer *b, int width, int const struct wl_buffer_listener *listener); int apply_pending_geometry(struct wayland_surface *w); -static inline void wl_damage_list(struct wl_surface *s, const struct yasl_damage_list *d) +static inline void wl_damage_list(struct wl_surface *s, const struct yasl_private_damage_list *d) { if (d->n < 0) { wl_surface_damage_buffer(s, d->bounds.x, d->bounds.y, @@ -365,6 +390,7 @@ void surface_follow_outputs(struct wayland_surface *w); uint64_t conn_pres_to_mono(const struct wayland_display *c, uint64_t ns); uint64_t conn_mono_to_pres(const struct wayland_display *c, uint64_t ns); void surface_draw_frame(struct wayland_surface *w); +void wayland_surface_arm_frame(struct wayland_surface *w); void surface_fail(struct wayland_surface *w); void display_leave(struct wayland_display *c); @@ -376,9 +402,9 @@ static inline struct wayland_surface *surface_root(struct wayland_surface *w) } void conn_drag_end(struct wayland_display *c); -void conn_drop_target(struct wayland_display *c, enum yasl_drop_event ev, +void conn_drop_target(struct wayland_display *c, enum yasl_private_drop_event ev, struct wl_surface *on, double x, double y); -void conn_drag_target(struct wayland_display *c, enum yasl_drag_event ev, +void conn_drag_target(struct wayland_display *c, enum yasl_private_drag_event ev, struct wl_surface *on, double x, double y); void seat_bind(struct wayland_display *c, struct wl_registry *registry, @@ -386,6 +412,7 @@ void seat_bind(struct wayland_display *c, struct wl_registry *registry, bool seat_remove(struct wayland_display *c, uint32_t name); void seat_destroy(struct seat *s); void seat_note_input(struct seat *s, uint32_t serial); +void seat_ensure_text_input(struct seat *s); void seat_forget_surface(struct seat *s, struct wayland_surface *w); void seat_clipboard_ensure(struct seat *s); int display_input_init(struct wayland_display *c); @@ -409,6 +436,7 @@ extern const struct zwp_text_input_v3_listener text_input_listener; void ime_reset(struct wayland_surface *w); void keyboard_text_reset(struct seat *s); void ime_send_caret(struct wayland_surface *w); +bool wayland_surface_update_text_input(struct wayland_surface *w); int surface_init_shell(struct wayland_surface *w, const char *title); extern const struct xdg_surface_listener shell_surface_listener; diff --git a/src/platform/wayland/keyboard.c b/src/platform/wayland/keyboard.c index 7f8274d..6f9dec9 100644 --- a/src/platform/wayland/keyboard.c +++ b/src/platform/wayland/keyboard.c @@ -133,8 +133,8 @@ bool conn_keyboard_on(const struct wayland_display *c, const struct wayland_surf static void push_focus(struct wayland_surface *w, bool focused) { - struct yasl_event ev = { - .type = YASL_EVENT_FOCUS, + struct yasl_private_event ev = { + .type = YASL_PRIVATE_EVENT_FOCUS, .surface = &w->base, .focus = { .focused = focused }, }; @@ -218,18 +218,18 @@ static uint32_t key_codepoint_at_level(struct seat *s, uint32_t key, xkb_layout_ return n == 1 ? xkb_keysym_to_utf32(syms[0]) : 0; } -static struct yasl_key_event xkb_key_event(struct seat *s, uint32_t key, +static struct yasl_private_key_event xkb_key_event(struct seat *s, uint32_t key, enum yasl_key_action action) { uint32_t code = key + XKB_EVDEV_OFFSET; xkb_layout_index_t layout = xkb_state_key_get_layout(s->xkb_state, code); - struct yasl_key_event k = { - .key = yasl_key_from_evdev(key), + struct yasl_private_key_event k = { + .key = yasl_private_key_from_evdev(key), .action = action, .mods = input_modifiers(s), - .codepoint = yasl_key_label(key_codepoint_at_level(s, key, layout, 0)), - .shifted = yasl_key_label(key_codepoint_at_level(s, key, layout, 1)), + .codepoint = yasl_private_key_label(key_codepoint_at_level(s, key, layout, 0)), + .shifted = yasl_private_key_label(key_codepoint_at_level(s, key, layout, 1)), .native_code = key, }; return k; @@ -239,8 +239,8 @@ static void keyboard_text(struct seat *s, struct wayland_surface *w, uint32_t ke { uint32_t code = key + XKB_EVDEV_OFFSET; unsigned mods = input_modifiers(s); - if ((mods & (YASL_MOD_CTRL | YASL_MOD_SUPER)) || - ((mods & YASL_MOD_ALT) && + if ((mods & (YASL_PRIVATE_MOD_CTRL | YASL_PRIVATE_MOD_SUPER)) || + ((mods & YASL_PRIVATE_MOD_ALT) && !xkb_state_mod_index_is_consumed(s->xkb_state, code, s->mod_alt))) { keyboard_text_reset(s); return; @@ -278,13 +278,13 @@ static void keyboard_text(struct seat *s, struct wayland_surface *w, uint32_t ke else xkb_state_key_get_utf8(s->xkb_state, code, text, (size_t)len + 1); /* Editing commands belong to key events, not text insertion. */ if ((unsigned char)text[0] >= 0x20 && text[0] != 0x7f) { - struct yasl_event ev = {.type = YASL_EVENT_TEXT, + struct yasl_private_event ev = {.type = YASL_PRIVATE_EVENT_TEXT, .surface = &w->base, .text = {.data = text, .len = (size_t)len}}; input_queue_push(&w->input, &ev); } if (text != local) free(text); } else { - struct yasl_event ev = {.type = YASL_EVENT_INPUT_RESET, .surface = &w->base}; + struct yasl_private_event ev = {.type = YASL_PRIVATE_EVENT_INPUT_RESET, .surface = &w->base}; input_queue_push(&w->input, &ev); } } @@ -296,9 +296,9 @@ static bool keyboard_dispatch_key(struct seat *s, struct wayland_surface *w, uin { if (!s->xkb_state || !w->cbs) return false; - struct yasl_key_event k = xkb_key_event(s, key, action); - struct yasl_event ev = { - .type = YASL_EVENT_KEY, + struct yasl_private_key_event k = xkb_key_event(s, key, action); + struct yasl_private_event ev = { + .type = YASL_PRIVATE_EVENT_KEY, .surface = &w->base, .key = k, }; @@ -329,7 +329,7 @@ static void keyboard_key(void *data, struct wl_keyboard *keyboard, if (keyboard_dispatch_key(s, w, key, YASL_KEY_PRESS) && repeatable) { s->repeat_key = key; s->repeat_held = true; - s->repeat_started_ns = yasl_loop_now_ns(); + s->repeat_started_ns = yasl_private_loop_now_ns(); keyboard_repeat_sync(s); } } @@ -389,7 +389,7 @@ int keyboard_repeat_init(struct seat *s) if (s->repeat_fd >= 0) return 0; s->repeat_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); if (s->repeat_fd < 0) return -1; - s->repeat_watch = yasl_loop_add_fd(s->conn->loop, s->repeat_fd, POLLIN, + s->repeat_watch = yasl_private_loop_add_fd(s->conn->loop, s->repeat_fd, POLLIN, keyboard_repeat_ready, s); if (s->repeat_watch <= 0) { s->repeat_watch = 0; @@ -406,18 +406,18 @@ int seat_modifiers(const struct seat *s) if (unlikely(!s || !s->xkb_state)) return 0; return ((s->mod_shift != XKB_MOD_INVALID && xkb_state_mod_index_is_active(s->xkb_state, s->mod_shift, - XKB_STATE_MODS_EFFECTIVE) > 0) * YASL_MOD_SHIFT) | + XKB_STATE_MODS_EFFECTIVE) > 0) * YASL_PRIVATE_MOD_SHIFT) | ((s->mod_alt != XKB_MOD_INVALID && xkb_state_mod_index_is_active(s->xkb_state, s->mod_alt, - XKB_STATE_MODS_EFFECTIVE) > 0) * YASL_MOD_ALT) | + XKB_STATE_MODS_EFFECTIVE) > 0) * YASL_PRIVATE_MOD_ALT) | ((s->mod_ctrl != XKB_MOD_INVALID && xkb_state_mod_index_is_active(s->xkb_state, s->mod_ctrl, - XKB_STATE_MODS_EFFECTIVE) > 0) * YASL_MOD_CTRL) | + XKB_STATE_MODS_EFFECTIVE) > 0) * YASL_PRIVATE_MOD_CTRL) | ((s->mod_super != XKB_MOD_INVALID && xkb_state_mod_index_is_active(s->xkb_state, s->mod_super, - XKB_STATE_MODS_EFFECTIVE) > 0) * YASL_MOD_SUPER) | + XKB_STATE_MODS_EFFECTIVE) > 0) * YASL_PRIVATE_MOD_SUPER) | ((xkb_state_mod_name_is_active(s->xkb_state, XKB_MOD_NAME_CAPS, - XKB_STATE_MODS_EFFECTIVE) > 0) * YASL_MOD_CAPS); + XKB_STATE_MODS_EFFECTIVE) > 0) * YASL_PRIVATE_MOD_CAPS); } int wayland_display_modifiers(const struct wayland_display *c) @@ -425,12 +425,12 @@ int wayland_display_modifiers(const struct wayland_display *c) return c ? seat_modifiers(c->active_seat ? c->active_seat : c->seats) : 0; } -void wayland_display_copy(struct wayland_display *c, enum yasl_selection sel, const char *text) +void wayland_display_copy(struct wayland_display *c, enum yasl_private_selection sel, const char *text) { (void)wayland_display_copy_text(c, sel, text); } -bool wayland_display_copy_text(struct wayland_display *c, enum yasl_selection sel, +bool wayland_display_copy_text(struct wayland_display *c, enum yasl_private_selection sel, const char *text) { struct seat *s = c ? (c->active_seat ? c->active_seat : c->seats) : NULL; @@ -447,28 +447,28 @@ bool wayland_display_copy_text(struct wayland_display *c, enum yasl_selection se return false; } errno = 0; - bool ok = sel == YASL_PRIMARY ? + bool ok = sel == YASL_PRIVATE_PRIMARY ? clipboard_copy_primary(s->clipboard, text, s->input_serial) : clipboard_copy(s->clipboard, text, s->input_serial); return ok; } -void wayland_display_paste(struct wayland_display *c, enum yasl_selection sel, struct wayland_surface *target) +void wayland_display_paste(struct wayland_display *c, enum yasl_private_selection sel, struct wayland_surface *target) { struct seat *s = c ? (c->active_seat ? c->active_seat : c->seats) : NULL; if (!target || target->closed || target->destroyed) return; if (!s || !s->clipboard) { - yasl_surface_complete_paste(&target->base, 0, - YASL_TRANSFER_UNAVAILABLE, NULL, 0); + yasl_private_surface_complete_paste(&target->base, 0, + YASL_PRIVATE_TRANSFER_UNAVAILABLE, NULL, 0); return; } - if (sel == YASL_PRIMARY) + if (sel == YASL_PRIVATE_PRIMARY) clipboard_paste_primary(s->clipboard, target->surface); else clipboard_paste(s->clipboard, target->surface); } -bool wayland_display_request_paste(struct wayland_display *c, enum yasl_selection sel, +bool wayland_display_request_paste(struct wayland_display *c, enum yasl_private_selection sel, struct wayland_surface *target, uint64_t request_id) { struct seat *s = c ? (c->active_seat ? c->active_seat : c->seats) : NULL; @@ -486,7 +486,7 @@ bool wayland_display_request_paste(struct wayland_display *c, enum yasl_selectio return ok; } -bool wayland_surface_poll_event(struct wayland_surface *w, struct yasl_event *ev) +bool wayland_surface_poll_event(struct wayland_surface *w, struct yasl_private_event *ev) { return w && ev && input_queue_pop(&w->input, ev); } diff --git a/src/platform/wayland/keymap.c b/src/platform/wayland/keymap.c index d626f30..6c3b4dd 100644 --- a/src/platform/wayland/keymap.c +++ b/src/platform/wayland/keymap.c @@ -1,7 +1,7 @@ #include "keymap.h" /* Numeric Linux evdev codes; no platform headers are needed by this table. */ -enum yasl_key yasl_key_from_evdev(uint32_t code) +enum yasl_key yasl_private_key_from_evdev(uint32_t code) { switch (code) { case 1: return YASL_KEY_ESCAPE; /* KEY_ESC */ diff --git a/src/platform/wayland/keymap.h b/src/platform/wayland/keymap.h index c0ff81e..26b6ed4 100644 --- a/src/platform/wayland/keymap.h +++ b/src/platform/wayland/keymap.h @@ -1,8 +1,8 @@ -#ifndef YASL_WAYLAND_KEYMAP_H -#define YASL_WAYLAND_KEYMAP_H +#ifndef YASL_PRIVATE_WAYLAND_KEYMAP_H +#define YASL_PRIVATE_WAYLAND_KEYMAP_H #include "yasl/keycodes.h" -enum yasl_key yasl_key_from_evdev(uint32_t code); +enum yasl_key yasl_private_key_from_evdev(uint32_t code); #endif diff --git a/src/platform/wayland/native.c b/src/platform/wayland/native.c index 55ce445..54ca184 100644 --- a/src/platform/wayland/native.c +++ b/src/platform/wayland/native.c @@ -1,14 +1,14 @@ #include "common/backend.h" -#include "yasl/native/wayland.h" +#include "common/private_native_wayland.h" -struct wl_display *yasl_wayland_display_get_wl_display(struct yasl_display *d) +struct wl_display *yasl_private_wayland_display_get_wl_display(struct yasl_private_display *d) { - return (d && yasl_display_backend(d) == YASL_BACKEND_WAYLAND) ? - (struct wl_display *)yasl_display_get_native_handle(d) : NULL; + return (d && yasl_private_display_backend(d) == YASL_PRIVATE_BACKEND_WAYLAND) ? + (struct wl_display *)yasl_private_display_get_native_handle(d) : NULL; } -struct wl_surface *yasl_wayland_surface_get_wl_surface(struct yasl_surface *s) +struct wl_surface *yasl_private_wayland_surface_get_wl_surface(struct yasl_surface *s) { - return (s && s->display && yasl_display_backend(s->display) == YASL_BACKEND_WAYLAND) ? - (struct wl_surface *)yasl_surface_get_native_handle(s) : NULL; + return (s && s->display && yasl_private_display_backend(s->display) == YASL_PRIVATE_BACKEND_WAYLAND) ? + (struct wl_surface *)yasl_private_surface_get_native_handle(s) : NULL; } diff --git a/src/platform/wayland/pointer.c b/src/platform/wayland/pointer.c index da666da..5f486ae 100644 --- a/src/platform/wayland/pointer.c +++ b/src/platform/wayland/pointer.c @@ -12,12 +12,22 @@ struct tablet_tool { struct zwp_tablet_tool_v2 *proxy; struct wp_cursor_shape_device_v1 *shape_dev; uint32_t proximity_serial; + uint64_t device_id, tool_id; + uint32_t axes_valid, axes_changed, capability_mask; + double x, y; + double pressure, tilt_x, tilt_y, rotation, distance, tangential, wheel_degrees; + unsigned changed_button; + unsigned pending_actions[8], pending_buttons[8], pending_action_count; + bool pending_contacts[8]; + unsigned kind; + bool in_contact, pen_in_proximity; bool eraser; }; struct tablet_device { struct tablet_device *next; struct zwp_tablet_v2 *proxy; + uint64_t id; }; struct tablet_pad { @@ -25,10 +35,10 @@ struct tablet_pad { struct zwp_tablet_pad_v2 *proxy; }; -static struct yasl_vector2i device_from_fixed(const struct wayland_surface *w, +static struct yasl_private_vector2i device_from_fixed(const struct wayland_surface *w, wl_fixed_t x, wl_fixed_t y) { - return (struct yasl_vector2i){ + return (struct yasl_private_vector2i){ .x = (int)lrint(wl_fixed_to_double(x) * w->scale_factor), .y = (int)lrint(wl_fixed_to_double(y) * w->scale_factor), }; @@ -41,10 +51,10 @@ void seat_note_input(struct seat *s, uint32_t serial) } static void push_pointer(struct seat *s, struct wayland_surface *w, - enum yasl_event_type type, struct yasl_pointer_event ev) + enum yasl_private_event_type type, struct yasl_private_pointer_event ev) { ev.mods = (uint32_t)seat_modifiers(s); - struct yasl_event input_ev = { + struct yasl_private_event input_ev = { .type = type, .surface = &w->base, .pointer = ev, @@ -52,13 +62,15 @@ static void push_pointer(struct seat *s, struct wayland_surface *w, input_queue_push(&surface_root(w)->input, &input_ev); } -static void report_wheel(struct seat *s, struct wayland_surface *w, - struct yasl_vector2i pos, double x, double y) +static unsigned scroll_source_of(uint32_t source) { - if ((x == 0 && y == 0) || !isfinite(x) || !isfinite(y)) return; - push_pointer(s, w, YASL_EVENT_POINTER_SCROLL, (struct yasl_pointer_event){ - .pos = pos, .scroll = {x, y}, - }); + switch (source) { + case WL_POINTER_AXIS_SOURCE_WHEEL: return 1; + case WL_POINTER_AXIS_SOURCE_WHEEL_TILT: return 1; + case WL_POINTER_AXIS_SOURCE_FINGER: return 2; + case WL_POINTER_AXIS_SOURCE_CONTINUOUS: return 3; + default: return 0; + } } static bool cursor_theme_for(struct wayland_display *c, int scale) @@ -90,10 +102,10 @@ static bool seat_cursor_surface(struct seat *s) } static void set_cursor_theme_fallback(struct seat *s, struct wayland_surface *w, - enum yasl_cursor shape) + enum yasl_private_cursor shape) { struct wayland_display *c = s->conn; - int scale = (int)((w->scale_120 + YASL_SCALE_UNIT - 1) / YASL_SCALE_UNIT); + int scale = (int)((w->scale_120 + YASL_PRIVATE_SCALE_UNIT - 1) / YASL_PRIVATE_SCALE_UNIT); if (scale < 1) scale = 1; if (!seat_cursor_surface(s) || !cursor_theme_for(c, scale)) return; static const char *names[][2] = { @@ -107,7 +119,7 @@ static void set_cursor_theme_fallback(struct seat *s, struct wayland_surface *w, {"ew-resize", "ew-resize"}, {"ns-resize", "ns-resize"}, }; - int idx = (unsigned)shape < YASL_CURSOR_HIDDEN ? (int)shape : 0; + int idx = (unsigned)shape < YASL_PRIVATE_CURSOR_HIDDEN ? (int)shape : 0; struct wl_cursor *cursor = wl_cursor_theme_get_cursor(c->cursor_theme, names[idx][0]); if (!cursor) cursor = wl_cursor_theme_get_cursor(c->cursor_theme, names[idx][1]); if (!cursor || !cursor->image_count) return; @@ -117,11 +129,11 @@ static void set_cursor_theme_fallback(struct seat *s, struct wayland_surface *w, int theme_scale = c->cursor_theme_scale; if (s->cursor_viewport) { - uint32_t scale_120 = (uint32_t)theme_scale * YASL_SCALE_UNIT; + uint32_t scale_120 = (uint32_t)theme_scale * YASL_PRIVATE_SCALE_UNIT; wp_viewport_set_destination(s->cursor_viewport, - (int)(((int64_t)(int)img->width * YASL_SCALE_UNIT + scale_120 - 1) / + (int)(((int64_t)(int)img->width * YASL_PRIVATE_SCALE_UNIT + scale_120 - 1) / scale_120), - (int)(((int64_t)(int)img->height * YASL_SCALE_UNIT + scale_120 - 1) / + (int)(((int64_t)(int)img->height * YASL_PRIVATE_SCALE_UNIT + scale_120 - 1) / scale_120)); } else { if (img->width % (uint32_t)theme_scale || img->height % (uint32_t)theme_scale) @@ -137,17 +149,17 @@ static void set_cursor_theme_fallback(struct seat *s, struct wayland_surface *w, wl_surface_commit(s->cursor_surface); } -static uint32_t cursor_shape_of(enum yasl_cursor shape) +static uint32_t cursor_shape_of(enum yasl_private_cursor shape) { switch (shape) { - case YASL_CURSOR_TEXT: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT; - case YASL_CURSOR_LINK: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER; - case YASL_CURSOR_CROSSHAIR: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR; - case YASL_CURSOR_MOVE: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE; - case YASL_CURSOR_RESIZE_EW: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE; - case YASL_CURSOR_RESIZE_NS: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE; - case YASL_CURSOR_RESIZE_NWSE: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE; - case YASL_CURSOR_RESIZE_NESW: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE; + case YASL_PRIVATE_CURSOR_TEXT: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT; + case YASL_PRIVATE_CURSOR_LINK: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER; + case YASL_PRIVATE_CURSOR_CROSSHAIR: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR; + case YASL_PRIVATE_CURSOR_MOVE: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE; + case YASL_PRIVATE_CURSOR_RESIZE_EW: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE; + case YASL_PRIVATE_CURSOR_RESIZE_NS: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE; + case YASL_PRIVATE_CURSOR_RESIZE_NWSE: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE; + case YASL_PRIVATE_CURSOR_RESIZE_NESW: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE; default: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT; } } @@ -155,11 +167,11 @@ static uint32_t cursor_shape_of(enum yasl_cursor shape) /* Relative mode hides the pointer but keeps w->cursor for the return to NORMAL. */ static void seat_set_cursor(struct seat *s, struct wayland_surface *w) { - enum yasl_cursor shape = w->relative ? YASL_CURSOR_HIDDEN : w->cursor; + enum yasl_private_cursor shape = w->relative ? YASL_PRIVATE_CURSOR_HIDDEN : w->cursor; if (!s->pointer || !s->pointer_serial || s->pointer_focus != w || s->cursor_shape == (int)shape) return; - if (shape == YASL_CURSOR_HIDDEN) + if (shape == YASL_PRIVATE_CURSOR_HIDDEN) wl_pointer_set_cursor(s->pointer, s->pointer_serial, NULL, 0, 0); else if (s->cursor_shape_dev) wp_cursor_shape_device_v1_set_shape(s->cursor_shape_dev, @@ -169,20 +181,20 @@ static void seat_set_cursor(struct seat *s, struct wayland_surface *w) s->cursor_shape = shape; } -static void tool_set_cursor(struct tablet_tool *t, enum yasl_cursor shape) +static void tool_set_cursor(struct tablet_tool *t, enum yasl_private_cursor shape) { if (!t->proximity_serial) return; - if (shape == YASL_CURSOR_HIDDEN) + if (shape == YASL_PRIVATE_CURSOR_HIDDEN) zwp_tablet_tool_v2_set_cursor(t->proxy, t->proximity_serial, NULL, 0, 0); else if (t->shape_dev) wp_cursor_shape_device_v1_set_shape(t->shape_dev, t->proximity_serial, cursor_shape_of(shape)); } -void wayland_surface_set_cursor(struct wayland_surface *w, enum yasl_cursor shape) +void wayland_surface_set_cursor(struct wayland_surface *w, enum yasl_private_cursor shape) { - if (!w || !w->conn || w->destroyed || (unsigned)shape > YASL_CURSOR_HIDDEN) return; + if (!w || !w->conn || w->destroyed || (unsigned)shape > YASL_PRIVATE_CURSOR_HIDDEN) return; w->cursor = shape; for (struct seat *s = w->conn->seats; s; s = s->next) { seat_set_cursor(s, w); @@ -193,7 +205,7 @@ void wayland_surface_set_cursor(struct wayland_surface *w, enum yasl_cursor shap static void press_queue_flush(struct seat *s, struct press_queue *q, struct wayland_surface *only, uint64_t *buttons, - struct yasl_pointer_event like) + struct yasl_private_pointer_event like) { int kept = 0; int count = q->n; @@ -206,10 +218,10 @@ static void press_queue_flush(struct seat *s, struct press_queue *q, if (!p.surface) continue; uint64_t mask = 1ull << p.button; *buttons = (*buttons & ~mask) | ((uint64_t)(!p.release) << p.button); - struct yasl_pointer_event ev = like; + struct yasl_private_pointer_event ev = like; ev.pos = p.pos; - ev.button = (enum yasl_pointer_button)p.button; - enum yasl_event_type type = p.release ? YASL_EVENT_POINTER_UP : YASL_EVENT_POINTER_DOWN; + ev.button = (enum yasl_private_pointer_button)p.button; + enum yasl_private_event_type type = p.release ? YASL_PRIVATE_EVENT_POINTER_UP : YASL_PRIVATE_EVENT_POINTER_DOWN; push_pointer(s, p.surface, type, ev); } q->head = 0; @@ -217,7 +229,7 @@ static void press_queue_flush(struct seat *s, struct press_queue *q, } static void press_queue_push(struct press_queue *q, struct wayland_surface *w, - struct yasl_vector2i pos, int button, bool release) + struct yasl_private_vector2i pos, int button, bool release) { if (q->n == 8) { q->head = (q->head + 1) & 7; @@ -244,10 +256,15 @@ void seat_pointer_reset(struct seat *s) s->pointer_serial = 0; s->motion_pending = false; memset(s->axis, 0, sizeof(s->axis)); + memset(s->axis_pixels, 0, sizeof(s->axis_pixels)); memset(s->axis_value120, 0, sizeof(s->axis_value120)); memset(s->axis_discrete, 0, sizeof(s->axis_discrete)); memset(s->axis_has_value120, 0, sizeof(s->axis_has_value120)); memset(s->axis_has_discrete, 0, sizeof(s->axis_has_discrete)); + s->axis_stop_pending = false; + s->scroll_gesture = false; + s->axis_phase = 0; + s->axis_source = WL_POINTER_AXIS_SOURCE_WHEEL; s->cursor_shape = -1; } @@ -263,7 +280,7 @@ static void flush_pending_motion(struct seat *s, struct wayland_surface *w) { if (!s->motion_pending) return; s->motion_pending = false; - push_pointer(s, w, YASL_EVENT_POINTER_MOTION, (struct yasl_pointer_event){ + push_pointer(s, w, YASL_PRIVATE_EVENT_POINTER_MOTION, (struct yasl_private_pointer_event){ .pos = s->pointer_pos, }); } @@ -356,6 +373,13 @@ static void pointer_axis(void *data, struct wl_pointer *pointer, double v = wl_fixed_to_double(value); if (likely(axis < 2)) { s->axis[axis] += v; + s->axis_pixels[axis] += v; + if (s->axis_source == WL_POINTER_AXIS_SOURCE_FINGER || + s->axis_source == WL_POINTER_AXIS_SOURCE_CONTINUOUS) { + if (!s->scroll_gesture) s->axis_phase = 1u; + else if (s->axis_phase != 1u) s->axis_phase = 2u; + s->scroll_gesture = true; + } } pointer_event_done(s); } @@ -363,13 +387,14 @@ static void pointer_axis(void *data, struct wl_pointer *pointer, static void pointer_axis_source(void *data, struct wl_pointer *pointer, uint32_t axis_source) { (void)pointer; - (void)data; (void)axis_source; + ((struct seat *)data)->axis_source = axis_source; } static void pointer_axis_stop(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis) { /* A stop ends a gesture, not the offsets already received in this frame. */ - (void)data; (void)pointer; (void)time; (void)axis; + (void)pointer; (void)time; (void)axis; + ((struct seat *)data)->axis_stop_pending = true; } static void pointer_axis_discrete(void *data, struct wl_pointer *pointer, uint32_t axis, int32_t discrete) @@ -405,7 +430,28 @@ static void pointer_frame_scroll(struct seat *s, struct wayland_surface *w) s->axis_discrete[axis] = 0; s->axis_has_value120[axis] = s->axis_has_discrete[axis] = false; } - report_wheel(s, w, s->pointer_pos, lines[1], lines[0]); + bool stopped = s->axis_stop_pending; + unsigned source = scroll_source_of(s->axis_source); + unsigned phase = source == 2 || source == 3 + ? (stopped ? 3u : s->axis_phase) : 0u; + bool has_pixels = (source == 2 || source == 3) && + (s->axis_pixels[0] != 0 || s->axis_pixels[1] != 0); + if (lines[0] != 0 || lines[1] != 0 || stopped) { + push_pointer(s, w, YASL_PRIVATE_EVENT_POINTER_SCROLL, + (struct yasl_private_pointer_event){ + .pos = s->pointer_pos, + .scroll = {lines[1], lines[0]}, + .pixel_scroll = {s->axis_pixels[1] * w->scale_factor, + s->axis_pixels[0] * w->scale_factor}, + .scroll_valid = 1u | (has_pixels ? 2u : 0u), + .scroll_source = source, + .scroll_phase = phase, + }); + } + memset(s->axis_pixels, 0, sizeof(s->axis_pixels)); + s->axis_stop_pending = false; + if (stopped) s->scroll_gesture = false; + s->axis_phase = 0; } static void pointer_frame(void *data, struct wl_pointer *pointer) @@ -416,14 +462,14 @@ static void pointer_frame(void *data, struct wl_pointer *pointer) if (left) { s->ptr_left = NULL; if (s->ptr_left_motion) - push_pointer(s, left, YASL_EVENT_POINTER_MOTION, (struct yasl_pointer_event){ + push_pointer(s, left, YASL_PRIVATE_EVENT_POINTER_MOTION, (struct yasl_private_pointer_event){ .pos = s->ptr_left_pos, }); - press_queue_flush(s, &s->ptr_presses, left, &s->pointer_buttons, (struct yasl_pointer_event){0}); - push_pointer(s, left, YASL_EVENT_POINTER_LEAVE, (struct yasl_pointer_event){ + press_queue_flush(s, &s->ptr_presses, left, &s->pointer_buttons, (struct yasl_private_pointer_event){0}); + push_pointer(s, left, YASL_PRIVATE_EVENT_POINTER_LEAVE, (struct yasl_private_pointer_event){ .pos = s->ptr_left_pos, }); - struct yasl_vector2i pos = s->pointer_pos; + struct yasl_private_vector2i pos = s->pointer_pos; bool motion = s->motion_pending; uint32_t serial = s->pointer_focus ? s->pointer_serial : 0; int shape = s->pointer_focus ? s->cursor_shape : -1; @@ -440,7 +486,7 @@ static void pointer_frame(void *data, struct wl_pointer *pointer) } flush_pending_motion(s, w); - press_queue_flush(s, &s->ptr_presses, NULL, &s->pointer_buttons, (struct yasl_pointer_event){0}); + press_queue_flush(s, &s->ptr_presses, NULL, &s->pointer_buttons, (struct yasl_private_pointer_event){0}); pointer_frame_scroll(s, w); } @@ -460,7 +506,7 @@ static void relative_motion(void *data, struct zwp_relative_pointer_v1 *pointer, /* Unlocked movement arrives as wl_pointer.motion; the queue derives its delta. */ if (!w || !w->pointer_locked || w->lock_seat != s) return; flush_pending_motion(s, w); - push_pointer(s, w, YASL_EVENT_POINTER_MOTION, (struct yasl_pointer_event){ + push_pointer(s, w, YASL_PRIVATE_EVENT_POINTER_MOTION, (struct yasl_private_pointer_event){ .pos = s->pointer_pos, .delta = {wl_fixed_to_double(dx) * w->scale_factor, wl_fixed_to_double(dy) * w->scale_factor}, @@ -525,12 +571,12 @@ static void seat_lock_surfaces(struct seat *s) surface_lock_pointer(w, s); } -bool wayland_surface_set_pointer_mode(struct wayland_surface *w, enum yasl_pointer_mode mode) +bool wayland_surface_set_pointer_mode(struct wayland_surface *w, enum yasl_private_pointer_mode mode) { if (!w || !w->conn || w->destroyed) return false; struct wayland_display *c = w->conn; - bool relative = mode == YASL_POINTER_RELATIVE; + bool relative = mode == YASL_PRIVATE_POINTER_RELATIVE; if (relative && (!c->relative_pointer_mgr || !c->pointer_constraints)) return false; w->relative = relative; @@ -572,10 +618,10 @@ static struct touch_point *touch_find(struct seat *s, int32_t id) return NULL; } -static struct yasl_vector2i touch_pos(const struct touch_point *p) +static struct yasl_private_vector2i touch_pos(const struct touch_point *p) { double scale = p->surface->scale_factor; - return (struct yasl_vector2i){ + return (struct yasl_private_vector2i){ .x = (int)lrint(p->x * scale), .y = (int)lrint(p->y * scale), }; } @@ -583,11 +629,11 @@ static struct yasl_vector2i touch_pos(const struct touch_point *p) static void touch_report(struct seat *s, struct touch_point *p, int button, - enum yasl_event_type type) + enum yasl_private_event_type type) { - push_pointer(s, p->surface, type, (struct yasl_pointer_event){ - .pos = touch_pos(p), .button = (enum yasl_pointer_button)button, - .device = YASL_PTR_DEV_TOUCH, + push_pointer(s, p->surface, type, (struct yasl_private_pointer_event){ + .pos = touch_pos(p), .button = (enum yasl_private_pointer_button)button, + .device = YASL_PRIVATE_PTR_DEV_TOUCH, }); } @@ -644,7 +690,7 @@ static void touch_frame(void *data, struct wl_touch *touch) if (p->down && !s->touch_primary && !s->touch_scrolling) { s->touch_primary = p; s->touch_pressed = true; - touch_report(s, p, 0, YASL_EVENT_POINTER_DOWN); + touch_report(s, p, 0, YASL_PRIVATE_EVENT_POINTER_DOWN); } if (!p->up) { active_n++; @@ -657,7 +703,7 @@ static void touch_frame(void *data, struct wl_touch *touch) struct touch_point *primary = s->touch_primary; if (primary && !s->touch_scrolling && active_n >= 2) { if (s->touch_pressed) - touch_report(s, primary, 0, YASL_EVENT_POINTER_UP); + touch_report(s, primary, 0, YASL_PRIVATE_EVENT_POINTER_UP); s->touch_pressed = false; s->touch_scrolling = true; s->touch_scroll_y = centroid_n ? centroid_sum / centroid_n : 0; @@ -667,14 +713,14 @@ static void touch_frame(void *data, struct wl_touch *touch) s->touch_scroll_y = y; if (dy != 0.0) { double lines = dy / SCROLL_PX_PER_LINE; - push_pointer(s, primary->surface, YASL_EVENT_POINTER_SCROLL, (struct yasl_pointer_event){ + push_pointer(s, primary->surface, YASL_PRIVATE_EVENT_POINTER_SCROLL, (struct yasl_private_pointer_event){ .pos = touch_pos(primary), .scroll = {0, -lines}, - .device = YASL_PTR_DEV_TOUCH, + .device = YASL_PRIVATE_PTR_DEV_TOUCH, }); } } else if (primary && primary->moved && s->touch_pressed) { - touch_report(s, primary, 0, YASL_EVENT_POINTER_MOTION); + touch_report(s, primary, 0, YASL_PRIVATE_EVENT_POINTER_MOTION); } for (int i = 0; i < SEAT_TOUCH_POINTS; i++) { struct touch_point *p = &s->touch_points[i]; @@ -683,7 +729,7 @@ static void touch_frame(void *data, struct wl_touch *touch) active_n--; if (p == s->touch_primary) { if (s->touch_pressed) - touch_report(s, p, 0, YASL_EVENT_POINTER_UP); + touch_report(s, p, 0, YASL_PRIVATE_EVENT_POINTER_UP); s->touch_pressed = false; s->touch_primary = NULL; } @@ -701,7 +747,7 @@ static void touch_cancel(void *data, struct wl_touch *touch) (void)touch; struct seat *s = data; if (s->touch_primary && s->touch_pressed) - touch_report(s, s->touch_primary, 0, YASL_EVENT_POINTER_UP); + touch_report(s, s->touch_primary, 0, YASL_PRIVATE_EVENT_POINTER_UP); memset(s->touch_points, 0, sizeof(s->touch_points)); s->touch_primary = NULL; s->touch_pressed = false; @@ -719,29 +765,100 @@ static const struct wl_touch_listener touch_listener = { .frame = touch_frame, .cancel = touch_cancel, }; -static struct yasl_pointer_event tool_event(const struct seat *s) +static struct yasl_private_pointer_event tool_event(const struct seat *s) { - return (struct yasl_pointer_event){ - .device = s->tool && s->tool->eraser ? YASL_PTR_DEV_ERASER : YASL_PTR_DEV_PEN, + return (struct yasl_private_pointer_event){ + .device = s->tool && s->tool->eraser ? YASL_PRIVATE_PTR_DEV_ERASER : YASL_PRIVATE_PTR_DEV_PEN, .pen = s->pen, }; } +static void push_pen_sample(struct tablet_tool *tool, struct wayland_surface *w, + unsigned action, uint32_t changed) +{ + struct seat *s = tool->seat; + if (!w) return; + uint64_t buttons = s->tool_buttons; + for (int i = 0; i < s->tool_presses.n; i++) { + const struct pending_press *press = &s->tool_presses.ev[(s->tool_presses.head + i) & 7]; + if (press->surface && press->button >= 0 && press->button < 32) { + uint64_t bit = 1ull << press->button; + buttons = (buttons & ~bit) | ((uint64_t)!press->release << press->button); + } + } + if (action == YASL_PRIVATE_PEN_LEAVE || action == YASL_PRIVATE_PEN_CANCEL) + buttons = 0; + struct yasl_private_event ev = { + .type = YASL_PRIVATE_EVENT_PEN, + .surface = &w->base, + .pen = { + .device_id = tool->device_id, + .tool_id = tool->tool_id, + .x = tool->x, + .y = tool->y, + .pressure = tool->pressure, + .tilt_x = tool->tilt_x, + .tilt_y = tool->tilt_y, + .rotation = tool->rotation, + .distance = tool->distance, + .tangential = tool->tangential, + .wheel_degrees = tool->wheel_degrees, + .axes_valid = tool->axes_valid, + .axes_changed = changed, + .buttons = (uint32_t)buttons, + .changed_button = tool->changed_button, + .in_contact = tool->in_contact, + .tool = tool->kind, + .action = action, + }, + }; + input_queue_push(&surface_root(w)->input, &ev); +} + +static void pen_action_push(struct tablet_tool *tool, unsigned action, unsigned button) +{ + if (tool->pending_action_count == 8) { + memmove(tool->pending_actions, tool->pending_actions + 1, + 7 * sizeof(tool->pending_actions[0])); + memmove(tool->pending_buttons, tool->pending_buttons + 1, + 7 * sizeof(tool->pending_buttons[0])); + memmove(tool->pending_contacts, tool->pending_contacts + 1, + 7 * sizeof(tool->pending_contacts[0])); + tool->pending_action_count--; + } + unsigned i = tool->pending_action_count++; + tool->pending_actions[i] = action; + tool->pending_buttons[i] = button; + tool->pending_contacts[i] = tool->in_contact; +} + static void tablet_tool_proximity_in(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface) { - (void)tool; (void)tablet; + (void)tool; struct tablet_tool *t = data; struct seat *s = t->seat; struct wayland_surface *w = surface ? (struct wayland_surface *)wl_surface_get_user_data(surface) : NULL; s->tablet_focus = w; s->tool = t; + t->device_id = 0; + for (struct tablet_device *device = s->tablet_devices; device; device = device->next) + if (device->proxy == tablet) { t->device_id = device->id; break; } + t->pen_in_proximity = true; + t->in_contact = false; + t->pending_action_count = 0; + pen_action_push(t, YASL_PRIVATE_PEN_ENTER, 0); + t->axes_changed = 0; + t->pressure = t->tilt_x = t->tilt_y = t->rotation = t->distance = 0; + t->tangential = t->wheel_degrees = 0; s->tool_buttons = 0; - s->pen = (struct yasl_pointer_pen){0}; + s->pen = (struct yasl_private_pointer_pen){0}; t->proximity_serial = serial; if (!w) return; + t->x = s->tool_pos.x; + t->y = s->tool_pos.y; if (s->conn->cursor_shape_mgr && !t->shape_dev) t->shape_dev = wp_cursor_shape_manager_v1_get_tablet_tool_v2( s->conn->cursor_shape_mgr, t->proxy); @@ -756,11 +873,14 @@ static void tablet_tool_proximity_out(void *data, struct zwp_tablet_tool_v2 *too struct seat *s = t->seat; struct wayland_surface *w = s->tablet_focus; if (w) { + t->in_contact = false; + t->pressure = 0; + push_pen_sample(t, w, YASL_PRIVATE_PEN_LEAVE, 0); press_queue_flush(s, &s->tool_presses, NULL, &s->tool_buttons, tool_event(s)); s->pen.pressure = 0; - struct yasl_pointer_event ev = tool_event(s); + struct yasl_private_pointer_event ev = tool_event(s); ev.pos = s->tool_pos; - push_pointer(s, w, YASL_EVENT_POINTER_LEAVE, ev); + push_pointer(s, w, YASL_PRIVATE_EVENT_POINTER_LEAVE, ev); } s->tool_presses.n = 0; s->tablet_focus = NULL; @@ -769,28 +889,38 @@ static void tablet_tool_proximity_out(void *data, struct zwp_tablet_tool_v2 *too s->tool_wheel = 0; s->tool_buttons = 0; t->proximity_serial = 0; + t->pen_in_proximity = false; + t->in_contact = false; } static void tablet_tool_down(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial) { (void)tool; - struct seat *s = ((struct tablet_tool *)data)->seat; + struct tablet_tool *t = data; + struct seat *s = t->seat; struct wayland_surface *w = s->tablet_focus; if (!w) return; seat_note_input(s, serial); s->press_serial = serial; s->press_surface = w; press_queue_push(&s->tool_presses, w, s->tool_pos, 0, false); + t->in_contact = true; + pen_action_push(t, YASL_PRIVATE_PEN_DOWN, 0); } static void tablet_tool_up(void *data, struct zwp_tablet_tool_v2 *tool) { (void)tool; struct seat *s = ((struct tablet_tool *)data)->seat; + struct tablet_tool *t = data; struct wayland_surface *w = s->tablet_focus; if (!w) return; s->pen.pressure = 0; press_queue_push(&s->tool_presses, w, s->tool_pos, 0, true); + t->pressure = 0; + t->in_contact = false; + pen_action_push(t, YASL_PRIVATE_PEN_UP, 0); + t->axes_changed |= YASL_PRIVATE_PEN_PRESSURE; } static void tablet_tool_motion(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t x, wl_fixed_t y) @@ -800,6 +930,9 @@ static void tablet_tool_motion(void *data, struct zwp_tablet_tool_v2 *tool, wl_f struct wayland_surface *w = s->tablet_focus; if (!w) return; s->tool_pos = device_from_fixed(w, x, y); + struct tablet_tool *owned = data; + owned->x = wl_fixed_to_double(x) * w->scale_factor; + owned->y = wl_fixed_to_double(y) * w->scale_factor; s->tool_motion = true; } @@ -807,7 +940,8 @@ static void tablet_tool_button(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial, uint32_t button, uint32_t state) { (void)tool; - struct seat *s = ((struct tablet_tool *)data)->seat; + struct tablet_tool *t = data; + struct seat *s = t->seat; struct wayland_surface *w = s->tablet_focus; if (!w) return; seat_note_input(s, serial); @@ -820,13 +954,18 @@ static void tablet_tool_button(void *data, struct zwp_tablet_tool_v2 *tool, } bool release = state == ZWP_TABLET_TOOL_V2_BUTTON_STATE_RELEASED; press_queue_push(&s->tool_presses, w, s->tool_pos, b, release); + pen_action_push(t, YASL_PRIVATE_PEN_BUTTON, (unsigned)b); } static void tool_pressure(void *d, struct zwp_tablet_tool_v2 *t, uint32_t v) { (void)t; struct seat *s = ((struct tablet_tool *)d)->seat; + struct tablet_tool *owned = d; s->pen.pressure = v > 65535 ? 65535 : (uint16_t)v; + owned->pressure = (double)(v > 65535 ? 65535 : v) / 65535.0; + owned->axes_valid |= YASL_PRIVATE_PEN_PRESSURE; + owned->axes_changed |= YASL_PRIVATE_PEN_PRESSURE; s->tool_motion = true; } @@ -840,40 +979,68 @@ static void tool_tilt(void *d, struct zwp_tablet_tool_v2 *t, wl_fixed_t x, wl_fi { (void)t; struct seat *s = ((struct tablet_tool *)d)->seat; + struct tablet_tool *owned = d; s->pen.tilt_x = tilt_degrees(x); s->pen.tilt_y = tilt_degrees(y); + owned->tilt_x = wl_fixed_to_double(x); + owned->tilt_y = wl_fixed_to_double(y); + owned->axes_valid |= YASL_PRIVATE_PEN_TILT; + owned->axes_changed |= YASL_PRIVATE_PEN_TILT; s->tool_motion = true; } static void tool_wheel(void *d, struct zwp_tablet_tool_v2 *t, wl_fixed_t deg, int32_t clicks) { - (void)t; (void)deg; - ((struct tablet_tool *)d)->seat->tool_wheel += clicks; + (void)t; + struct tablet_tool *owned = d; + owned->seat->tool_wheel += clicks; + owned->wheel_degrees += wl_fixed_to_double(deg); + owned->axes_valid |= YASL_PRIVATE_PEN_WHEEL; + owned->axes_changed |= YASL_PRIVATE_PEN_WHEEL; } static void tool_frame(void *d, struct zwp_tablet_tool_v2 *t, uint32_t time) { (void)t; (void)time; struct seat *s = ((struct tablet_tool *)d)->seat; + struct tablet_tool *owned = d; struct wayland_surface *w = s->tablet_focus; if (!w) { s->tool_presses.n = 0; s->tool_wheel = 0; return; } - struct yasl_pointer_event like = tool_event(s); + struct yasl_private_pointer_event like = tool_event(s); + if (owned->pending_action_count) { + unsigned actions = owned->pending_action_count; + bool contact = owned->in_contact; + for (unsigned i = 0; i < actions; i++) { + owned->changed_button = owned->pending_buttons[i]; + owned->in_contact = owned->pending_contacts[i]; + push_pen_sample(owned, w, owned->pending_actions[i], + i + 1 == actions ? owned->axes_changed : 0); + } + owned->in_contact = contact; + owned->pending_action_count = 0; + owned->axes_changed = 0; + owned->changed_button = 0; + } else if (s->tool_motion || owned->axes_changed) { + push_pen_sample(owned, w, YASL_PRIVATE_PEN_MOVE, owned->axes_changed); + owned->axes_changed = 0; + owned->changed_button = 0; + } if (s->tool_motion) { - struct yasl_pointer_event ev = like; + struct yasl_private_pointer_event ev = like; ev.pos = s->tool_pos; - push_pointer(s, w, YASL_EVENT_POINTER_MOTION, ev); + push_pointer(s, w, YASL_PRIVATE_EVENT_POINTER_MOTION, ev); s->tool_motion = false; } press_queue_flush(s, &s->tool_presses, NULL, &s->tool_buttons, like); if (s->tool_wheel) { - struct yasl_pointer_event ev = like; + struct yasl_private_pointer_event ev = like; ev.pos = s->tool_pos; ev.scroll.y = s->tool_wheel; - push_pointer(s, w, YASL_EVENT_POINTER_SCROLL, ev); + push_pointer(s, w, YASL_PRIVATE_EVENT_POINTER_SCROLL, ev); s->tool_wheel = 0; } } @@ -899,16 +1066,43 @@ static void tablet_tool_removed(void *data, struct zwp_tablet_tool_v2 *tool) static void tool_type(void *d, struct zwp_tablet_tool_v2 *t, uint32_t type) { (void)t; - ((struct tablet_tool *)d)->eraser = type == ZWP_TABLET_TOOL_V2_TYPE_ERASER; + struct tablet_tool *tool = d; + tool->eraser = type == ZWP_TABLET_TOOL_V2_TYPE_ERASER; + switch (type) { + case ZWP_TABLET_TOOL_V2_TYPE_PEN: tool->kind = 1; break; + case ZWP_TABLET_TOOL_V2_TYPE_ERASER: tool->kind = 2; break; + case ZWP_TABLET_TOOL_V2_TYPE_BRUSH: tool->kind = 3; break; + case ZWP_TABLET_TOOL_V2_TYPE_AIRBRUSH: tool->kind = 4; break; + case ZWP_TABLET_TOOL_V2_TYPE_PENCIL: tool->kind = 5; break; + default: tool->kind = 0; break; + } } -static void tool_hardware_serial(void *d, struct zwp_tablet_tool_v2 *t, uint32_t hi, uint32_t lo) { (void)d; (void)t; (void)hi; (void)lo; } +static void tool_hardware_serial(void *d, struct zwp_tablet_tool_v2 *t, uint32_t hi, uint32_t lo) +{ (void)t; struct tablet_tool *tool = d; uint64_t id = ((uint64_t)hi << 32) | lo; if (id) tool->tool_id = id; } static void tool_hardware_id_wacom(void *d, struct zwp_tablet_tool_v2 *t, uint32_t hi, uint32_t lo) { (void)d; (void)t; (void)hi; (void)lo; } -static void tool_capability(void *d, struct zwp_tablet_tool_v2 *t, uint32_t cap) { (void)d; (void)t; (void)cap; } +static void tool_capability(void *d, struct zwp_tablet_tool_v2 *t, uint32_t cap) +{ + (void)t; + struct tablet_tool *tool = d; + tool->capability_mask |= 1u << cap; + switch (cap) { + case ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE: tool->axes_valid |= YASL_PRIVATE_PEN_PRESSURE; break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_TILT: tool->axes_valid |= YASL_PRIVATE_PEN_TILT; break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_ROTATION: tool->axes_valid |= YASL_PRIVATE_PEN_ROTATION; break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_DISTANCE: tool->axes_valid |= YASL_PRIVATE_PEN_DISTANCE; break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_SLIDER: tool->axes_valid |= YASL_PRIVATE_PEN_TANGENTIAL; break; + case ZWP_TABLET_TOOL_V2_CAPABILITY_WHEEL: tool->axes_valid |= YASL_PRIVATE_PEN_WHEEL; break; + default: break; + } +} static void tool_done(void *d, struct zwp_tablet_tool_v2 *t) { (void)d; (void)t; } -static void tool_distance(void *d, struct zwp_tablet_tool_v2 *t, uint32_t v) { (void)d; (void)t; (void)v; } -static void tool_rotation(void *d, struct zwp_tablet_tool_v2 *t, wl_fixed_t deg) { (void)d; (void)t; (void)deg; } -static void tool_slider(void *d, struct zwp_tablet_tool_v2 *t, int32_t pos) { (void)d; (void)t; (void)pos; } +static void tool_distance(void *d, struct zwp_tablet_tool_v2 *t, uint32_t v) +{ (void)t; struct tablet_tool *tool = d; tool->distance = (double)(v > 65535 ? 65535 : v) / 65535.0; tool->axes_valid |= YASL_PRIVATE_PEN_DISTANCE; tool->axes_changed |= YASL_PRIVATE_PEN_DISTANCE; } +static void tool_rotation(void *d, struct zwp_tablet_tool_v2 *t, wl_fixed_t deg) +{ (void)t; struct tablet_tool *tool = d; tool->rotation = wl_fixed_to_double(deg); tool->axes_valid |= YASL_PRIVATE_PEN_ROTATION; tool->axes_changed |= YASL_PRIVATE_PEN_ROTATION; } +static void tool_slider(void *d, struct zwp_tablet_tool_v2 *t, int32_t pos) +{ (void)t; struct tablet_tool *tool = d; tool->tangential = pos / 65535.0; if (tool->tangential < -1) tool->tangential = -1; if (tool->tangential > 1) tool->tangential = 1; tool->axes_valid |= YASL_PRIVATE_PEN_TANGENTIAL; tool->axes_changed |= YASL_PRIVATE_PEN_TANGENTIAL; } static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = { .type = tool_type, .hardware_serial = tool_hardware_serial, @@ -997,6 +1191,7 @@ static void tablet_seat_tablet_added(void *data, struct zwp_tablet_seat_v2 *tabl struct tablet_device *owned = calloc(1, sizeof(*owned)); if (!owned) { zwp_tablet_v2_destroy(tablet); return; } owned->proxy = tablet; + owned->id = ++s->conn->next_tool_id; owned->next = s->tablet_devices; s->tablet_devices = owned; zwp_tablet_v2_add_listener(tablet, &tablet_listener, s); @@ -1010,6 +1205,7 @@ static void tablet_seat_tool_added(void *data, struct zwp_tablet_seat_v2 *tablet if (!owned) { zwp_tablet_tool_v2_destroy(tool); return; } owned->proxy = tool; owned->seat = s; + owned->tool_id = ++s->conn->next_tool_id; owned->next = s->tablet_tools; s->tablet_tools = owned; zwp_tablet_tool_v2_add_listener(tool, &tablet_tool_listener, owned); @@ -1035,7 +1231,7 @@ static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = { static void clipboard_received(const char *text, size_t len, bool uri_list, struct wl_surface *on, uint64_t request_id, - enum yasl_transfer_status status, void *ud) + enum yasl_private_transfer_status status, void *ud) { (void)ud; struct wayland_surface *w = on ? @@ -1043,13 +1239,13 @@ static void clipboard_received(const char *text, size_t len, bool uri_list, if (uri_list) w = surface_root(w); if (!w || w->closed || w->destroyed) return; if (!uri_list) { - yasl_surface_complete_paste(&w->base, request_id, status, text, len); + yasl_private_surface_complete_paste(&w->base, request_id, status, text, len); return; } - if (status != YASL_TRANSFER_OK) return; - struct yasl_event ev = { + if (status != YASL_PRIVATE_TRANSFER_OK) return; + struct yasl_private_event ev = { .surface = &w->base, - .type = uri_list ? YASL_EVENT_DROP : YASL_EVENT_PASTE, + .type = uri_list ? YASL_PRIVATE_EVENT_DROP : YASL_PRIVATE_EVENT_PASTE, .text = { .data = text, .len = len, @@ -1058,14 +1254,14 @@ static void clipboard_received(const char *text, size_t len, bool uri_list, input_queue_push(&w->input, &ev); } -static void clipboard_drag(enum yasl_drag_event ev, struct wl_surface *on, +static void clipboard_drag(enum yasl_private_drag_event ev, struct wl_surface *on, double x, double y, void *ud) { struct seat *s = ud; conn_drag_target(s->conn, ev, on, x, y); } -static void clipboard_drop_hover(enum yasl_drop_event ev, struct wl_surface *on, +static void clipboard_drop_hover(enum yasl_private_drop_event ev, struct wl_surface *on, double x, double y, void *ud) { struct seat *s = ud; @@ -1152,6 +1348,16 @@ static void seat_drop_touch(struct seat *s) } } +void seat_ensure_text_input(struct seat *s) +{ + if (!s || !s->conn->text_input_mgr || s->text_input || !s->proxy) + return; + s->text_input = zwp_text_input_manager_v3_get_text_input( + s->conn->text_input_mgr, s->proxy); + if (s->text_input) + zwp_text_input_v3_add_listener(s->text_input, &text_input_listener, s); +} + static void seat_capabilities(void *data, struct wl_seat *proxy, uint32_t caps) { struct seat *s = data; @@ -1209,11 +1415,7 @@ static void seat_capabilities(void *data, struct wl_seat *proxy, uint32_t caps) zwp_tablet_seat_v2_add_listener(s->tablet_seat, &tablet_seat_listener, s); } - if (c->text_input_mgr && !s->text_input) { - s->text_input = zwp_text_input_manager_v3_get_text_input(c->text_input_mgr, proxy); - if (s->text_input) - zwp_text_input_v3_add_listener(s->text_input, &text_input_listener, s); - } + seat_ensure_text_input(s); seat_clipboard_ensure(s); } @@ -1297,8 +1499,8 @@ void seat_destroy(struct seat *s) seat_drop_keyboard(s); struct wayland_surface *left = s->focus_leaving; if (left && !conn_keyboard_on(c, left, NULL)) { - struct yasl_event ev = { - .type = YASL_EVENT_FOCUS, + struct yasl_private_event ev = { + .type = YASL_PRIVATE_EVENT_FOCUS, .surface = &left->base, .focus = { .focused = false }, }; @@ -1306,7 +1508,7 @@ void seat_destroy(struct seat *s) } seat_drop_touch(s); clipboard_destroy(s->clipboard); - if (s->repeat_watch > 0) yasl_loop_del_fd(c->loop, s->repeat_watch); + if (s->repeat_watch > 0) yasl_private_loop_del_fd(c->loop, s->repeat_watch); if (s->repeat_fd >= 0) close(s->repeat_fd); if (s->cursor_viewport) wp_viewport_destroy(s->cursor_viewport); if (s->cursor_surface) wl_surface_destroy(s->cursor_surface); diff --git a/src/platform/wayland/popup.c b/src/platform/wayland/popup.c index 44c7206..c563eb4 100644 --- a/src/platform/wayland/popup.c +++ b/src/platform/wayland/popup.c @@ -24,9 +24,19 @@ static const struct xdg_popup_listener popup_listener = { }; static bool popup_spec_valid(const struct wayland_surface *parent, - const struct yasl_popup_spec *spec) + const struct yasl_private_popup_spec *spec) { if (!parent || !spec) return false; + if (spec->positioner_explicit && + (spec->anchor_edges > XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT || + spec->gravity > XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT || + (spec->constraints & ~(unsigned)(XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y | + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y | + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y)))) + return false; int64_t x = (int64_t)spec->anchor.x - parent->content.x; int64_t y = (int64_t)spec->anchor.y - parent->content.y; return x >= INT_MIN && x <= INT_MAX && y >= INT_MIN && y <= INT_MAX && @@ -37,29 +47,29 @@ static bool popup_spec_valid(const struct wayland_surface *parent, (int64_t)spec->content_offset.y + spec->content_size.y <= spec->size.y; } -static bool popup_apply_spec(struct wayland_surface *w, const struct yasl_popup_spec *spec) +static bool popup_apply_spec(struct wayland_surface *w, const struct yasl_private_popup_spec *spec) { uint32_t scale = w->parent->scale_120; - struct yasl_vector2i logical = { - .x = (int)(((int64_t)spec->size.x * YASL_SCALE_UNIT + scale - 1) / scale), - .y = (int)(((int64_t)spec->size.y * YASL_SCALE_UNIT + scale - 1) / scale), + struct yasl_private_vector2i logical = { + .x = (int)(((int64_t)spec->size.x * YASL_PRIVATE_SCALE_UNIT + scale - 1) / scale), + .y = (int)(((int64_t)spec->size.y * YASL_PRIVATE_SCALE_UNIT + scale - 1) / scale), }; int width = device_px(logical.x, scale); int height = device_px(logical.y, scale); size_t bytes; if (logical.x <= 0 || logical.y <= 0 || !buffer_size(width, height, &bytes)) return false; - struct yasl_vector2i content = spec->content_size; + struct yasl_private_vector2i content = spec->content_size; if (!content.x) content.x = spec->size.x - spec->content_offset.x; if (!content.y) content.y = spec->size.y - spec->content_offset.y; bool rescaled = scale != w->scale_120; w->scale_120 = w->pending_scale_120 = scale; - w->scale_factor = (double)scale / YASL_SCALE_UNIT; + w->scale_factor = (double)scale / YASL_PRIVATE_SCALE_UNIT; w->width = width; w->height = height; w->logical_width = logical.x; w->logical_height = logical.y; - w->content = (struct yasl_rect2i){ + w->content = (struct yasl_private_rect2i){ .x = spec->content_offset.x, .y = spec->content_offset.y, .width = content.x, .height = content.y }; @@ -67,20 +77,20 @@ static bool popup_apply_spec(struct wayland_surface *w, const struct yasl_popup_ w->buffers[i].presented = false; if (w->viewport) wp_viewport_set_destination(w->viewport, logical.x, logical.y); - else if ((rescaled || !w->configured) && scale % YASL_SCALE_UNIT == 0 && + else if ((rescaled || !w->configured) && scale % YASL_PRIVATE_SCALE_UNIT == 0 && wl_proxy_get_version((struct wl_proxy *)w->surface) >= WL_SURFACE_SET_BUFFER_SCALE_SINCE_VERSION) - wl_surface_set_buffer_scale(w->surface, (int32_t)(scale / YASL_SCALE_UNIT)); + wl_surface_set_buffer_scale(w->surface, (int32_t)(scale / YASL_PRIVATE_SCALE_UNIT)); return true; } static void popup_set_geometry(struct wayland_surface *w) { uint32_t scale = w->scale_120; - int gx = yasl_scale_to_logical(w->content.x, scale); - int gy = yasl_scale_to_logical(w->content.y, scale); - int gw = yasl_scale_to_logical(w->content.width, scale); - int gh = yasl_scale_to_logical(w->content.height, scale); + int gx = yasl_private_scale_to_logical(w->content.x, scale); + int gy = yasl_private_scale_to_logical(w->content.y, scale); + int gw = yasl_private_scale_to_logical(w->content.width, scale); + int gh = yasl_private_scale_to_logical(w->content.height, scale); if (gw < 1) gw = 1; if (gh < 1) gh = 1; if (gx + gw > w->logical_width) @@ -91,41 +101,45 @@ static void popup_set_geometry(struct wayland_surface *w) } static struct xdg_positioner *popup_positioner(struct wayland_surface *w, - const struct yasl_popup_spec *spec) + const struct yasl_private_popup_spec *spec) { struct wayland_surface *parent = w->parent; struct xdg_positioner *pos = xdg_wm_base_create_positioner(w->conn->wm_base); if (!pos) return NULL; uint32_t scale = parent->scale_120; - int gw = yasl_scale_to_logical(w->content.width, scale); - int gh = yasl_scale_to_logical(w->content.height, scale); + int gw = yasl_private_scale_to_logical(w->content.width, scale); + int gh = yasl_private_scale_to_logical(w->content.height, scale); xdg_positioner_set_size(pos, gw > 0 ? gw : 1, gh > 0 ? gh : 1); - struct yasl_vector2i at = { + struct yasl_private_vector2i at = { .x = spec->anchor.x - parent->content.x, .y = spec->anchor.y - parent->content.y, }; - int aw = yasl_scale_to_logical(spec->anchor.width, scale); - int ah = yasl_scale_to_logical(spec->anchor.height, scale); + int aw = yasl_private_scale_to_logical(spec->anchor.width, scale); + int ah = yasl_private_scale_to_logical(spec->anchor.height, scale); xdg_positioner_set_anchor_rect(pos, - yasl_scale_to_logical(at.x, scale), - yasl_scale_to_logical(at.y, scale), + yasl_private_scale_to_logical(at.x, scale), + yasl_private_scale_to_logical(at.y, scale), aw > 0 ? aw : 1, ah > 0 ? ah : 1); - xdg_positioner_set_anchor(pos, spec->beside ? XDG_POSITIONER_ANCHOR_TOP_RIGHT - : XDG_POSITIONER_ANCHOR_TOP_LEFT); - xdg_positioner_set_gravity(pos, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT); - xdg_positioner_set_constraint_adjustment(pos, + uint32_t anchor = spec->positioner_explicit ? spec->anchor_edges : + (spec->beside ? XDG_POSITIONER_ANCHOR_TOP_RIGHT : XDG_POSITIONER_ANCHOR_TOP_LEFT); + uint32_t gravity = spec->positioner_explicit ? spec->gravity : + XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT; + uint32_t constraints = spec->positioner_explicit ? spec->constraints : XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y); + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y; + xdg_positioner_set_anchor(pos, anchor); + xdg_positioner_set_gravity(pos, gravity); + xdg_positioner_set_constraint_adjustment(pos, constraints); if (xdg_positioner_get_version(pos) >= XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION) xdg_positioner_set_reactive(pos); return pos; } struct wayland_surface *wayland_surface_create_popup(struct wayland_surface *parent, - const struct yasl_popup_spec *spec) + const struct yasl_private_popup_spec *spec) { struct wayland_display *c = parent ? parent->conn : NULL; struct seat *seat = c ? (c->active_seat ? c->active_seat : c->seats) : NULL; @@ -137,7 +151,8 @@ 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->base.state = YASL_PRIVATE_STATE_CSD; + w->base.presenter = YASL_PRIVATE_PRESENTER_CPU; w->input.leave_ends_press = true; w->conn = c; w->conn_next = c->surfaces; @@ -150,7 +165,7 @@ struct wayland_surface *wayland_surface_create_popup(struct wayland_surface *par if (!w->surface) goto err; w->base.native_handle = w->surface; - w->base.size = (struct yasl_vector2i){spec->size.x, spec->size.y}; + w->base.size = (struct yasl_private_vector2i){spec->size.x, spec->size.y}; w->base.scale_120 = parent->scale_120; wl_surface_set_user_data(w->surface, w); if (surface_init_scaling(w) < 0) @@ -180,13 +195,13 @@ err: return NULL; } -bool wayland_surface_popup_reposition(struct wayland_surface *w, const struct yasl_popup_spec *spec) +bool wayland_surface_popup_reposition(struct wayland_surface *w, const struct yasl_private_popup_spec *spec) { if (!w || !w->popup || w->closed || !w->conn || w->conn->lost || !popup_spec_valid(w->parent, spec) || xdg_popup_get_version(w->popup) < XDG_POPUP_REPOSITION_SINCE_VERSION) return false; - struct yasl_popup_spec old = { + struct yasl_private_popup_spec old = { .size = { .x = w->width, .y = w->height }, .content_offset = { .x = w->content.x, .y = w->content.y }, .content_size = { .x = w->content.width, .y = w->content.height }, diff --git a/src/platform/wayland/shell.c b/src/platform/wayland/shell.c index fc2ce2e..c2578e7 100644 --- a/src/platform/wayland/shell.c +++ b/src/platform/wayland/shell.c @@ -173,7 +173,7 @@ uint32_t conn_output_scale_120(const struct wayland_display *c, uint64_t outputs for (const struct wl_conn_output *o = c->outputs; o; o = o->next) if ((o->mask & outputs) && o->scale > scale) scale = o->scale; - return (uint32_t)scale * YASL_SCALE_UNIT; + return (uint32_t)scale * YASL_PRIVATE_SCALE_UNIT; } static void registry_global(void *data, struct wl_registry *registry, @@ -227,6 +227,8 @@ static void registry_global(void *data, struct wl_registry *registry, } else if (strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0) { c->text_input_mgr = wl_registry_bind(registry, name, &zwp_text_input_manager_v3_interface, 1); + for (struct seat *seat = c->seats; seat; seat = seat->next) + seat_ensure_text_input(seat); } else if (strcmp(interface, wp_tearing_control_manager_v1_interface.name) == 0) { c->tearing_mgr = wl_registry_bind(registry, name, &wp_tearing_control_manager_v1_interface, 1); @@ -277,7 +279,7 @@ static void set_state(struct wayland_surface *w, uint32_t state) { if (state == w->base.state) return; w->base.state = state; - yasl_surface_notify_state(&w->base, state); + yasl_private_surface_notify_state(&w->base, state); } static void toplevel_configure(void *data, struct xdg_toplevel *toplevel, @@ -286,21 +288,21 @@ static void toplevel_configure(void *data, struct xdg_toplevel *toplevel, (void)toplevel; struct wayland_surface *w = data; - uint32_t state = w->base.state & ~(YASL_STATE_FULLSCREEN | - YASL_STATE_MAXIMIZED | YASL_STATE_ACTIVATED | - YASL_STATE_SUSPENDED | YASL_STATE_TILED); + uint32_t state = w->base.state & ~(YASL_PRIVATE_STATE_FULLSCREEN | + YASL_PRIVATE_STATE_MAXIMIZED | YASL_PRIVATE_STATE_ACTIVATED | + YASL_PRIVATE_STATE_SUSPENDED | YASL_PRIVATE_STATE_TILED); uint32_t *s; wl_array_for_each(s, states) { 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_FULLSCREEN: state |= YASL_PRIVATE_STATE_FULLSCREEN; break; + case XDG_TOPLEVEL_STATE_MAXIMIZED: state |= YASL_PRIVATE_STATE_MAXIMIZED; break; + case XDG_TOPLEVEL_STATE_ACTIVATED: state |= YASL_PRIVATE_STATE_ACTIVATED; break; + case XDG_TOPLEVEL_STATE_SUSPENDED: state |= YASL_PRIVATE_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; + state |= YASL_PRIVATE_STATE_TILED; break; default: break; } @@ -316,7 +318,7 @@ static void toplevel_close(void *data, struct xdg_toplevel *toplevel) (void)toplevel; struct wayland_surface *w = data; if (w && !w->closed) - yasl_surface_notify_close(&w->base); + yasl_private_surface_notify_close(&w->base); } static void toplevel_configure_bounds(void *data, struct xdg_toplevel *toplevel, @@ -349,11 +351,21 @@ static const struct xdg_toplevel_listener toplevel_listener = { static void xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) { struct wayland_surface *w = data; - (void)xdg_surface; w->pending_serial = serial; w->has_pending_serial = true; w->size_pending = true; w->configured = true; + /* CPU surfaces acknowledge with their SHM commit. External presenters + * commit through EGL/Vulkan, so apply the configure and acknowledge it + * before renderer code can submit that first native buffer. */ + if (w->base.presenter != YASL_PRIVATE_PRESENTER_CPU) { + xdg_surface_ack_configure(xdg_surface, serial); + w->has_pending_serial = false; + if (apply_pending_geometry(w) < 0) { + surface_fail(w); + return; + } + } wayland_surface_schedule_redraw(w); } @@ -368,9 +380,9 @@ static void toplevel_decoration_configure(void *data, struct wayland_surface *w = data; uint32_t state = w->base.state; if (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE) - state &= ~YASL_STATE_CSD; + state &= ~YASL_PRIVATE_STATE_CSD; else - state |= YASL_STATE_CSD; + state |= YASL_PRIVATE_STATE_CSD; set_state(w, state); } @@ -387,11 +399,11 @@ int surface_init_shell(struct wayland_surface *w, const char *title) if (!w->toplevel) return -1; xdg_toplevel_add_listener(w->toplevel, &toplevel_listener, w); - wayland_surface_set_title(w, title ? title : "yasl_app"); + wayland_surface_set_title(w, title ? title : "yasl_private_app"); - const char *app_id = w->conn->app_id ? w->conn->app_id : "yasl_app"; + const char *app_id = w->conn->app_id ? w->conn->app_id : "yasl_private_app"; xdg_toplevel_set_app_id(w->toplevel, app_id); - xdg_toplevel_set_min_size(w->toplevel, YASL_MIN_LOGICAL_PX, YASL_MIN_LOGICAL_PX); + xdg_toplevel_set_min_size(w->toplevel, YASL_PRIVATE_MIN_LOGICAL_PX, YASL_PRIVATE_MIN_LOGICAL_PX); if (w->conn->decor_mgr) { w->toplevel_decor = zxdg_decoration_manager_v1_get_toplevel_decoration( @@ -400,7 +412,9 @@ int surface_init_shell(struct wayland_surface *w, const char *title) zxdg_toplevel_decoration_v1_add_listener(w->toplevel_decor, &toplevel_decoration_listener, w); zxdg_toplevel_decoration_v1_set_mode(w->toplevel_decor, - ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + (w->base.creation_flags & YASL_PRIVATE_SURFACE_UNDECORATED) + ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE + : ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); } return 0; } diff --git a/src/platform/wayland/surface.c b/src/platform/wayland/surface.c index 2e85e2b..31096f4 100644 --- a/src/platform/wayland/surface.c +++ b/src/platform/wayland/surface.c @@ -52,7 +52,7 @@ static void display_lose(struct wayland_display *c) surface_fail(w); } display_leave(c); - yasl_display_notify_event(&c->base, YASL_EVENT_DISPLAY_LOST); + yasl_private_display_notify_event(&c->base, YASL_PRIVATE_EVENT_DISPLAY_LOST); } void surface_fail(struct wayland_surface *w) @@ -60,7 +60,7 @@ void surface_fail(struct wayland_surface *w) if (w->closed) return; w->closed = true; - yasl_surface_notify_close(&w->base); + yasl_private_surface_notify_close(&w->base); } static int display_ready(int fd, short revents, void *ud) @@ -80,7 +80,7 @@ static int display_ready(int fd, short revents, void *ud) if (revents & POLLOUT) { if (wl_display_flush(c->wl) >= 0) { if (c->loop && c->watch > 0) - yasl_loop_set_fd_events(c->loop, c->watch, POLLIN); + yasl_private_loop_set_fd_events(c->loop, c->watch, POLLIN); } else if (errno != EAGAIN) { display_lose(c); } @@ -113,7 +113,7 @@ static bool display_arm(struct wayland_display *c) events |= POLLOUT; } if (c->loop && c->watch > 0) - yasl_loop_set_fd_events(c->loop, c->watch, events); + yasl_private_loop_set_fd_events(c->loop, c->watch, events); return had_pending; } @@ -154,7 +154,7 @@ int wayland_display_dispatch_timeout(struct wayland_display *c, int timeout_ms) int n = 0; if (c->loop) { - n = yasl_loop_step(c->loop, timeout_ms); + n = yasl_private_loop_step(c->loop, timeout_ms); } if (c->reading) { @@ -190,7 +190,7 @@ void wayland_display_flush(struct wayland_display *c) static void display_free(struct wayland_display *c) { conn_drag_end(c); - if (c->loop && c->watch > 0) yasl_loop_del_fd(c->loop, c->watch); + if (c->loop && c->watch > 0) yasl_private_loop_del_fd(c->loop, c->watch); if (c->wl) display_cancel_read(c); while (c->seats) seat_destroy(c->seats); @@ -217,9 +217,12 @@ static void display_free(struct wayland_display *c) if (c->shm) wl_shm_destroy(c->shm); if (c->compositor) wl_compositor_destroy(c->compositor); if (c->registry) wl_registry_destroy(c->registry); +#if YASL_HAS_EGL + if (c->egl_initialized) eglTerminate(c->egl_display); +#endif if (c->wl) wl_display_disconnect(c->wl); if (c->owns_loop && c->loop) { - yasl_loop_destroy(c->loop); + yasl_private_loop_destroy(c->loop); c->loop = NULL; c->base.loop = NULL; } @@ -227,13 +230,13 @@ static void display_free(struct wayland_display *c) free(c); } -struct wayland_display *wayland_display_open(struct yasl_loop *loop, const struct yasl_display_spec *spec) +struct wayland_display *wayland_display_open(struct yasl_private_loop *loop, const struct yasl_private_display_spec *spec) { struct wayland_display *c = calloc(1, sizeof(*c)); if (!c) return NULL; - c->base.ops = &yasl_wayland_backend; + c->base.ops = &yasl_private_wayland_backend; if (!loop) { - c->loop = yasl_loop_create(); + c->loop = yasl_private_loop_create(); c->owns_loop = true; } else { c->loop = loop; @@ -241,7 +244,7 @@ struct wayland_display *wayland_display_open(struct yasl_loop *loop, const struc c->base.loop = c->loop; if (!c->loop) goto err; if (spec) { - c->base.callbacks = spec->cbs ? *spec->cbs : (struct yasl_display_cbs){0}; + c->base.callbacks = spec->cbs ? *spec->cbs : (struct yasl_private_display_cbs){0}; c->cbs = &c->base.callbacks; c->ud = spec->ud; if (spec->app_id) c->app_id = strdup(spec->app_id); @@ -251,6 +254,13 @@ struct wayland_display *wayland_display_open(struct yasl_loop *loop, const struc c->wl = c->xkb_ctx ? wl_display_connect(NULL) : NULL; if (!c->wl) goto err; c->base.native_handle = c->wl; +#if YASL_HAS_EGL + c->egl_display = eglGetDisplay((EGLNativeDisplayType)c->wl); + if (c->egl_display != EGL_NO_DISPLAY) { + EGLint major = 0, minor = 0; + c->egl_initialized = eglInitialize(c->egl_display, &major, &minor) == EGL_TRUE; + } +#endif c->registry = wl_display_get_registry(c->wl); if (!c->registry) goto err; @@ -260,7 +270,7 @@ struct wayland_display *wayland_display_open(struct yasl_loop *loop, const struc surface_init_cursor(c); if (c->loop) { - c->watch = yasl_loop_add_fd(c->loop, wl_display_get_fd(c->wl), POLLIN, display_ready, c); + c->watch = yasl_private_loop_add_fd(c->loop, wl_display_get_fd(c->wl), POLLIN, display_ready, c); if (c->watch <= 0) { c->watch = 0; goto err; } } if (display_input_init(c) < 0) @@ -282,16 +292,18 @@ void wayland_display_close(struct wayland_display *c) uint32_t wayland_display_caps(const struct wayland_display *c) { if (!c) return 0; - uint32_t caps = YASL_CAP_POPUPS | YASL_CAP_CSD | YASL_CAP_FRAME_PACING; - if (c->tablet_mgr) caps |= YASL_CAP_TABLET; + uint32_t caps = YASL_PRIVATE_CAP_POPUPS | YASL_PRIVATE_CAP_CSD | + YASL_PRIVATE_CAP_FRAME_PACING | YASL_PRIVATE_CAP_NATIVE_GRAPHICS; + if (c->tablet_mgr) caps |= YASL_PRIVATE_CAP_TABLET; + if (c->text_input_mgr) caps |= YASL_PRIVATE_CAP_TEXT_INPUT; struct seat *s = c->active_seat ? c->active_seat : c->seats; const struct wl_data_device *device = s ? clipboard_data_device(s->clipboard) : NULL; if (device && wl_data_device_get_version((struct wl_data_device *)device) >= 3) - caps |= YASL_CAP_DRAG; + caps |= YASL_PRIVATE_CAP_DRAG; if (c->toplevel_drag_mgr) - caps |= YASL_CAP_DRAG_CARRY; + caps |= YASL_PRIVATE_CAP_DRAG_CARRY; if (c->primary_selection_mgr) - caps |= YASL_CAP_PRIMARY_SELECTION; + caps |= YASL_PRIVATE_CAP_PRIMARY_SELECTION; return caps; } @@ -321,15 +333,18 @@ static void conn_unlink(struct wayland_surface *w) } -static bool spec_valid(const struct yasl_window_spec *spec) +static bool spec_valid(const struct yasl_private_window_spec *spec) { size_t bytes; - return spec && spec->size.x > 0 && spec->size.y > 0 && - buffer_size(spec->size.x, spec->size.y, &bytes); + if (!spec || spec->size.x <= 0 || spec->size.y <= 0) + return false; + return spec->external_gpu && !(spec->flags & YASL_PRIVATE_SURFACE_CPU) + ? spec->size.x <= INT32_MAX && spec->size.y <= INT32_MAX + : buffer_size(spec->size.x, spec->size.y, &bytes); } struct wayland_surface *wayland_surface_create_toplevel(struct wayland_display *c, - const struct yasl_window_spec *spec, void *graphics_device) + const struct yasl_private_window_spec *spec, void *graphics_device) { (void)graphics_device; if (!c || c->lost || !spec_valid(spec)) @@ -340,28 +355,35 @@ 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; + w->base.state = YASL_PRIVATE_STATE_CSD; + w->base.creation_flags = spec->flags; + w->base.presenter = (spec->external_gpu && !(spec->flags & YASL_PRIVATE_SURFACE_CPU)) + ? YASL_PRIVATE_PRESENTER_NONE : YASL_PRIVATE_PRESENTER_CPU; wayland_surface_set_callbacks(w, NULL, NULL); - w->cursor = YASL_CURSOR_TEXT; + w->cursor = YASL_PRIVATE_CURSOR_TEXT; w->req_width = width; w->req_height = height; w->logical_width = width; w->logical_height = height; - w->scale_120 = YASL_SCALE_UNIT; - w->pending_scale_120 = YASL_SCALE_UNIT; + w->scale_120 = YASL_PRIVATE_SCALE_UNIT; + w->pending_scale_120 = YASL_PRIVATE_SCALE_UNIT; w->scale_factor = 1.0; w->size_pending = true; w->clock.refresh_ns = c->outputs && c->outputs->refresh_ns - ? c->outputs->refresh_ns : YASL_REFRESH_DEFAULT_NS; + ? c->outputs->refresh_ns : YASL_PRIVATE_REFRESH_DEFAULT_NS; w->surface = wl_compositor_create_surface(c->compositor); if (!w->surface) goto err; w->base.native_handle = w->surface; - w->base.size = (struct yasl_vector2i){width, height}; - w->base.scale_120 = YASL_SCALE_UNIT; + w->base.size = (struct yasl_private_vector2i){width, height}; + w->base.scale_120 = YASL_PRIVATE_SCALE_UNIT; wl_surface_set_user_data(w->surface, w); if (surface_init_scaling(w) < 0) goto err; if (surface_init_shell(w, spec->title) < 0) goto err; + if (spec->flags & YASL_PRIVATE_SURFACE_FIXED_SIZE) { + xdg_toplevel_set_min_size(w->toplevel, width, height); + xdg_toplevel_set_max_size(w->toplevel, width, height); + } w->opaque = spec->fullscreen; if (spec->fullscreen) xdg_toplevel_set_fullscreen(w->toplevel, NULL); @@ -389,10 +411,10 @@ err: return NULL; } -void wayland_surface_set_callbacks(struct wayland_surface *w, const struct yasl_window_cbs *cbs, void *ud) +void wayland_surface_set_callbacks(struct wayland_surface *w, const struct yasl_private_window_cbs *cbs, void *ud) { if (w && !w->closed) { - w->base.callbacks = cbs ? *cbs : (struct yasl_window_cbs){0}; + w->base.callbacks = cbs ? *cbs : (struct yasl_private_window_cbs){0}; w->base.ud = ud; w->cbs = &w->base.callbacks; w->ud = ud; @@ -410,6 +432,15 @@ void wayland_surface_destroy(struct wayland_surface *w) free(w->ime_commit); free(w->ime_shown); if (w->tearing_control) wp_tearing_control_v1_destroy(w->tearing_control); +#if YASL_HAS_EGL + if (w->egl_context) { + if (eglGetCurrentContext() == w->egl_context) + eglMakeCurrent(w->conn->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(w->conn->egl_display, w->egl_context); + } + if (w->egl_surface) eglDestroySurface(w->conn->egl_display, w->egl_surface); + if (w->egl_window) wl_egl_window_destroy(w->egl_window); +#endif if (w->fifo) wp_fifo_v1_destroy(w->fifo); if (w->commit_timer) wp_commit_timer_v1_destroy(w->commit_timer); surface_feedback_destroy(w); @@ -427,7 +458,7 @@ void wayland_surface_destroy(struct wayland_surface *w) } struct wayland_display *c = w->conn; conn_unlink(w); - static const struct yasl_window_cbs no_callbacks = {0}; + static const struct yasl_private_window_cbs no_callbacks = {0}; *w = (struct wayland_surface){ .base = w->base, .conn_next = w->conn_next, @@ -446,16 +477,20 @@ void wayland_surface_destroy(struct wayland_surface *w) void wayland_surface_schedule_redraw(struct wayland_surface *w) { if (!w || w->closed) return; + if (w->base.presenter != YASL_PRIVATE_PRESENTER_CPU) { + yasl_private_surface_notify_refresh(&w->base); + return; + } w->want_draw = true; if (w->conn && w->conn->loop) - yasl_loop_kick(w->conn->loop); + yasl_private_loop_kick(w->conn->loop); } -struct yasl_geometry wayland_surface_geometry(const struct wayland_surface *w) +struct yasl_private_geometry wayland_surface_geometry(const struct wayland_surface *w) { - return (struct yasl_geometry){ - .size = w ? (struct yasl_vector2i){w->width, w->height} : (struct yasl_vector2i){0}, - .scale_120 = w ? w->scale_120 : YASL_SCALE_UNIT, + return (struct yasl_private_geometry){ + .size = w ? (struct yasl_private_vector2i){w->width, w->height} : (struct yasl_private_vector2i){0}, + .scale_120 = w ? w->scale_120 : YASL_PRIVATE_SCALE_UNIT, }; } @@ -473,23 +508,23 @@ bool wayland_surface_start_interactive_move(struct wayland_surface *s) return true; } -bool wayland_surface_start_interactive_resize(struct wayland_surface *s, enum yasl_resize_edge edge) +bool wayland_surface_start_interactive_resize(struct wayland_surface *s, enum yasl_private_resize_edge edge) { - if (!s || !s->toplevel || !s->conn || edge == YASL_EDGE_NONE) return false; + if (!s || !s->toplevel || !s->conn || edge == YASL_PRIVATE_EDGE_NONE) return false; struct seat *seat = s->conn->active_seat ? s->conn->active_seat : s->conn->seats; if (!seat || !seat->proxy || !seat->press_serial) return false; xdg_toplevel_resize(s->toplevel, seat->proxy, seat->press_serial, (uint32_t)edge); return true; } -bool wayland_surface_show_window_menu(struct wayland_surface *s, struct yasl_vector2i pos) +bool wayland_surface_show_window_menu(struct wayland_surface *s, struct yasl_private_vector2i pos) { if (!s || !s->toplevel || !s->conn) return false; struct seat *seat = s->conn->active_seat ? s->conn->active_seat : s->conn->seats; if (!seat || !seat->proxy || !seat->press_serial) return false; - uint32_t scale = s->scale_120 ? s->scale_120 : YASL_SCALE_UNIT; + uint32_t scale = s->scale_120 ? s->scale_120 : YASL_PRIVATE_SCALE_UNIT; xdg_toplevel_show_window_menu(s->toplevel, seat->proxy, seat->press_serial, - yasl_scale_to_logical(pos.x, scale), - yasl_scale_to_logical(pos.y, scale)); + yasl_private_scale_to_logical(pos.x, scale), + yasl_private_scale_to_logical(pos.y, scale)); return true; } diff --git a/src/platform/x11/keymap.c b/src/platform/x11/keymap.c index 3c566b3..35178dd 100644 --- a/src/platform/x11/keymap.c +++ b/src/platform/x11/keymap.c @@ -2,7 +2,7 @@ #include <string.h> /* XKB standard physical key names, independent of server numeric keycodes. */ -enum yasl_key yasl_key_from_xkb_name(const char name[4]) +enum yasl_key yasl_private_key_from_xkb_name(const char name[4]) { static const struct { char name[4]; enum yasl_key key; } keys[] = { {{'T', 'L', 'D', 'E'}, YASL_KEY_GRAVE}, diff --git a/src/platform/x11/keymap.h b/src/platform/x11/keymap.h index 8114fb5..9bb009d 100644 --- a/src/platform/x11/keymap.h +++ b/src/platform/x11/keymap.h @@ -1,8 +1,8 @@ -#ifndef YASL_X11_KEYMAP_H -#define YASL_X11_KEYMAP_H +#ifndef YASL_PRIVATE_X11_KEYMAP_H +#define YASL_PRIVATE_X11_KEYMAP_H #include "yasl/keycodes.h" -enum yasl_key yasl_key_from_xkb_name(const char name[4]); +enum yasl_key yasl_private_key_from_xkb_name(const char name[4]); #endif diff --git a/src/platform/x11/native.c b/src/platform/x11/native.c index 249adec..fffe171 100644 --- a/src/platform/x11/native.c +++ b/src/platform/x11/native.c @@ -1,14 +1,14 @@ #include "common/backend.h" -#include "yasl/native/x11.h" +#include "common/private_native_x11.h" -Display *yasl_x11_display_get_display(struct yasl_display *d) +Display *yasl_private_x11_display_get_display(struct yasl_private_display *d) { - return (d && yasl_display_backend(d) == YASL_BACKEND_X11) ? - (Display *)yasl_display_get_native_handle(d) : NULL; + return (d && yasl_private_display_backend(d) == YASL_PRIVATE_BACKEND_X11) ? + (Display *)yasl_private_display_get_native_handle(d) : NULL; } -uintptr_t yasl_x11_surface_get_window(struct yasl_surface *s) +uintptr_t yasl_private_x11_surface_get_window(struct yasl_surface *s) { - return (s && s->display && yasl_display_backend(s->display) == YASL_BACKEND_X11) ? - (uintptr_t)yasl_surface_get_native_handle(s) : 0; + return (s && s->display && yasl_private_display_backend(s->display) == YASL_PRIVATE_BACKEND_X11) ? + (uintptr_t)yasl_private_surface_get_native_handle(s) : 0; } diff --git a/src/platform/x11/window.c b/src/platform/x11/window.c index 004d56c..8129dd5 100644 --- a/src/platform/x11/window.c +++ b/src/platform/x11/window.c @@ -16,6 +16,10 @@ #include <X11/keysym.h> #include <X11/extensions/XInput2.h> #include <X11/extensions/XShm.h> +#if YASL_HAS_EGL +#include <EGL/egl.h> +#include <EGL/eglext.h> +#endif #include <sys/ipc.h> #include <sys/shm.h> #if YASL_HAS_XRANDR @@ -35,7 +39,7 @@ #include <wchar.h> #define TEXT_LIMIT (16 * 1024 * 1024) -#define PASTE_TIMEOUT_NS YASL_MS_TO_NS(30000) +#define PASTE_TIMEOUT_NS YASL_PRIVATE_MS_TO_NS(30000) #ifndef likely #define likely(x) __builtin_expect(!!(x), 1) @@ -50,26 +54,33 @@ struct x11_surface { struct x11_surface *dead_next; Window window; GC gc; + Colormap colormap; XIC ic; +#if YASL_HAS_EGL + EGLContext gl_context; + EGLSurface gl_surface; + EGLConfig gl_config; +#endif XImage *image; uint32_t *pixels; bool shm_active; XShmSegmentInfo shm_info; - struct yasl_vector2i size; - const struct yasl_window_cbs *cbs; + struct yasl_private_vector2i size; + const struct yasl_private_window_cbs *cbs; void *ud; struct input_queue input; - struct yasl_frame frame; - struct yasl_damage_list damage; - struct yasl_frame_clock clock; + struct yasl_private_frame frame; + struct yasl_private_damage_list damage; + struct yasl_private_frame_clock clock; bool want_draw, drawing, presented, destroyed; + bool focused; struct x11_surface *parent; bool is_popup; - struct yasl_vector2i content_offset; + struct yasl_private_vector2i content_offset; char *ime_preedit; int ime_cursor_begin; int ime_cursor_end; - struct yasl_rect2i ime_caret; + struct yasl_private_rect2i ime_caret; }; struct paste_request { @@ -79,33 +90,45 @@ struct paste_request { Window window; Atom selection; uint64_t id; - yasl_timer_id timeout; + yasl_private_timer_id timeout; }; struct tablet_axis { int number; double min, max, value; + bool valid; +}; + +struct x11_scroll_axis { + int number; + double increment, value; + bool valid, initialized; + int type; }; struct x11_pointer { struct x11_pointer *next; int id; - enum yasl_pointer_device device; - struct tablet_axis pressure, tilt_x, tilt_y; + enum yasl_private_pointer_device device; + unsigned tool; + struct tablet_axis pressure, tilt_x, tilt_y, rotation, distance, tangential, wheel; + struct x11_scroll_axis scroll_x, scroll_y; + uint32_t buttons; + bool finger_scroll; }; struct x11_display { - struct yasl_display base; + struct yasl_private_display base; Display *native; int screen; - struct yasl_loop *loop; + struct yasl_private_loop *loop; int watch; struct x11_surface *surfaces, *dead; int depth; XIM im; Window selection_window; Atom protocols, delete_window, utf8, clipboard, targets, paste_property, incr; - Atom net_name, net_state, fullscreen, maximized_h, maximized_v; + Atom net_name, net_state, fullscreen, maximized_h, maximized_v, motif_hints; Atom xdnd_aware, xdnd_enter, xdnd_position, xdnd_status, xdnd_leave, xdnd_drop, xdnd_finished; Atom xdnd_selection, xdnd_action_copy, xdnd_type_list, uri_list; char *selection[2]; @@ -121,20 +144,26 @@ struct x11_display { int xkb_event; enum yasl_key keys[256]; Cursor cursors[16]; - Atom abs_pressure, abs_tilt_x, abs_tilt_y, tool_type, eraser; + Atom abs_pressure, abs_tilt_x, abs_tilt_y, abs_rotation, abs_distance, abs_tangential, abs_wheel; + Atom tool_type, eraser; + Atom brush, airbrush, pencil; struct x11_pointer *pointers; +#if YASL_HAS_EGL + EGLDisplay egl; + bool egl_ready; +#endif struct { bool active; struct x11_surface *origin; struct x11_surface *carried; - struct yasl_vector2i carry_offset; - struct yasl_drag_listener listener; + struct yasl_private_vector2i carry_offset; + struct yasl_private_drag_listener listener; void *ud; struct x11_surface *hover; - struct yasl_vector2i hover_pos; + struct yasl_private_vector2i hover_pos; Window icon_window; Pixmap icon_pixmap; - struct yasl_vector2i hotspot; + struct yasl_private_vector2i hotspot; } drag; struct { Window source; @@ -142,7 +171,7 @@ struct x11_display { bool accepts; bool has_uri_list; struct x11_surface *surface; - struct yasl_vector2i pos; + struct yasl_private_vector2i pos; Time time; } xdnd; struct { @@ -153,11 +182,11 @@ struct x11_display { } pending_drop; }; -static bool place(struct yasl_surface *base, struct yasl_vector2i at); -static void drag_end(struct x11_display *d, enum yasl_drag_end how); -static void drag_cancel(struct yasl_display *base); +static bool place(struct yasl_surface *base, struct yasl_private_vector2i at); +static void drag_end(struct x11_display *d, enum yasl_private_drag_end how); +static void drag_cancel(struct yasl_private_display *base); static void schedule_redraw(struct yasl_surface *base); -static uint64_t paste_timeout(void *ud, yasl_timer_id id, uint64_t interval_ns); +static uint64_t paste_timeout(void *ud, yasl_private_timer_id id, uint64_t interval_ns); static void leave(struct x11_display *d) { @@ -169,7 +198,7 @@ static void leave(struct x11_display *d) } } -static void push(struct x11_surface *s, struct yasl_event event, +static void push(struct x11_surface *s, struct yasl_private_event event, const char *text, size_t len) { if (s->destroyed) return; @@ -189,39 +218,39 @@ static unsigned long component(uint32_t color, unsigned long mask) return (((unsigned long)color * m + 127) / 255) << shift; } -static void drag_set_hover(struct x11_display *d, struct x11_surface *s, struct yasl_vector2i pos) +static void drag_set_hover(struct x11_display *d, struct x11_surface *s, struct yasl_private_vector2i pos) { if (!d->drag.active) return; if (d->drag.hover && d->drag.hover != s) { struct x11_surface *old = d->drag.hover; - struct yasl_vector2i old_pos = d->drag.hover_pos; + struct yasl_private_vector2i old_pos = d->drag.hover_pos; d->drag.hover = NULL; if (!old->destroyed) - yasl_surface_notify_drag(&old->base, YASL_DRAG_LEAVE, old_pos); + yasl_private_surface_notify_drag(&old->base, YASL_PRIVATE_DRAG_LEAVE, old_pos); if (!d->drag.active || (s && s->destroyed)) return; } if (s && s->destroyed) return; d->drag.hover = s; d->drag.hover_pos = pos; if (s && !s->destroyed) - yasl_surface_notify_drag(&s->base, YASL_DRAG_OVER, pos); + yasl_private_surface_notify_drag(&s->base, YASL_PRIVATE_DRAG_OVER, pos); } -static void drag_leave_hover(struct x11_display *d, struct x11_surface *s, struct yasl_vector2i pos) +static void drag_leave_hover(struct x11_display *d, struct x11_surface *s, struct yasl_private_vector2i pos) { if (!d->drag.active || d->drag.hover != s) return; d->drag.hover = NULL; if (s && !s->destroyed) - yasl_surface_notify_drag(&s->base, YASL_DRAG_LEAVE, pos); + yasl_private_surface_notify_drag(&s->base, YASL_PRIVATE_DRAG_LEAVE, pos); } static void drag_drop_hover(struct x11_display *d) { if (!d->drag.active) return; - drag_end(d, YASL_DRAG_DROPPED); + drag_end(d, YASL_PRIVATE_DRAG_DROPPED); } -static int yasl_x11_error_handler(Display *native, XErrorEvent *event) +static int yasl_private_x11_error_handler(Display *native, XErrorEvent *event) { (void)native; (void)event; @@ -230,7 +259,7 @@ static int yasl_x11_error_handler(Display *native, XErrorEvent *event) return 0; } -static struct x11_surface *surface_at_root_pos(struct x11_display *d, int rx, int ry, struct yasl_vector2i *out_pos) +static struct x11_surface *surface_at_root_pos(struct x11_display *d, int rx, int ry, struct yasl_private_vector2i *out_pos) { if (!d->native) return NULL; Window ignore_icon = d->drag.icon_window; @@ -243,7 +272,7 @@ static struct x11_surface *surface_at_root_pos(struct x11_display *d, int rx, in if (XTranslateCoordinates(d->native, RootWindow(d->native, d->screen), s->window, rx, ry, &tx, &ty, &sub)) { if (tx >= 0 && ty >= 0 && tx < s->size.x && ty < s->size.y) { - if (out_pos) *out_pos = (struct yasl_vector2i){tx, ty}; + if (out_pos) *out_pos = (struct yasl_private_vector2i){tx, ty}; return s; } } @@ -253,19 +282,31 @@ static struct x11_surface *surface_at_root_pos(struct x11_display *d, int rx, in static uint32_t modifiers(unsigned state) { - return ((state & ShiftMask) ? YASL_MOD_SHIFT : 0) | - ((state & ControlMask) ? YASL_MOD_CTRL : 0) | - ((state & Mod1Mask) ? YASL_MOD_ALT : 0) | - ((state & Mod4Mask) ? YASL_MOD_SUPER : 0) | - ((state & LockMask) ? YASL_MOD_CAPS : 0); + return ((state & ShiftMask) ? YASL_PRIVATE_MOD_SHIFT : 0) | + ((state & ControlMask) ? YASL_PRIVATE_MOD_CTRL : 0) | + ((state & Mod1Mask) ? YASL_PRIVATE_MOD_ALT : 0) | + ((state & Mod4Mask) ? YASL_PRIVATE_MOD_SUPER : 0) | + ((state & LockMask) ? YASL_PRIVATE_MOD_CAPS : 0); } static void pointer_info(struct x11_display *d, struct x11_pointer *p, const XIDeviceInfo *info) { p->id = info->deviceid; - p->device = YASL_PTR_DEV_MOUSE; + p->device = YASL_PRIVATE_PTR_DEV_MOUSE; + p->tool = 0; + p->buttons = 0; p->pressure.number = p->tilt_x.number = p->tilt_y.number = -1; + p->rotation.number = p->distance.number = p->tangential.number = p->wheel.number = -1; + p->scroll_x.number = p->scroll_y.number = -1; + if (info->name) { + for (const char *s = info->name; *s; s++) { + if (!strncasecmp(s, "touchpad", 8) || !strncasecmp(s, "trackpad", 8)) { + p->finger_scroll = true; + break; + } + } + } bool touch = false; for (int i = 0; i < info->num_classes; i++) { XIAnyClassInfo *c = info->classes[i]; @@ -275,17 +316,42 @@ static void pointer_info(struct x11_display *d, struct x11_pointer *p, if (v->mode != XIModeAbsolute) continue; struct tablet_axis *axis = v->label == d->abs_pressure ? &p->pressure : v->label == d->abs_tilt_x ? &p->tilt_x : - v->label == d->abs_tilt_y ? &p->tilt_y : NULL; - if (axis) *axis = (struct tablet_axis){v->number, v->min, v->max, v->value}; + v->label == d->abs_tilt_y ? &p->tilt_y : + v->label == d->abs_rotation ? &p->rotation : + v->label == d->abs_distance ? &p->distance : + v->label == d->abs_tangential ? &p->tangential : + v->label == d->abs_wheel ? &p->wheel : NULL; + if (axis) *axis = (struct tablet_axis){v->number, v->min, v->max, v->value, true}; + } + for (int i = 0; i < info->num_classes; i++) { + XIAnyClassInfo *c = info->classes[i]; + if (c->type != XIScrollClass) continue; + XIScrollClassInfo *scroll = (XIScrollClassInfo *)c; + struct x11_scroll_axis *axis = scroll->scroll_type == XIScrollTypeVertical ? + &p->scroll_y : scroll->scroll_type == XIScrollTypeHorizontal ? &p->scroll_x : NULL; + if (!axis) continue; + *axis = (struct x11_scroll_axis){.number = scroll->number, + .increment = scroll->increment, .type = scroll->scroll_type}; + for (int j = 0; j < info->num_classes; j++) { + XIAnyClassInfo *vclass = info->classes[j]; + if (vclass->type != XIValuatorClass) continue; + XIValuatorClassInfo *v = (XIValuatorClassInfo *)vclass; + if (v->number == scroll->number) { + axis->value = v->value; + axis->initialized = axis->valid = true; + break; + } + } } if (!touch && p->pressure.number >= 0) { - p->device = YASL_PTR_DEV_PEN; + p->device = YASL_PRIVATE_PTR_DEV_PEN; + p->tool = 1; /* Xorg's Wacom and libinput drivers expose separate eraser devices. * Use the tool property when available, with the device name as a * fallback for servers (including Xwayland) without that property. */ if (info->name) { for (const char *s = info->name; *s; s++) - if (!strncasecmp(s, "eraser", 6)) { p->device = YASL_PTR_DEV_ERASER; break; } + if (!strncasecmp(s, "eraser", 6)) { p->device = YASL_PRIVATE_PTR_DEV_ERASER; break; } } } } @@ -301,17 +367,21 @@ static struct x11_pointer *pointer_for(struct x11_display *d, int id) struct x11_pointer *p = count ? calloc(1, sizeof(*p)) : NULL; if (p) { pointer_info(d, p, info); - if (p->device == YASL_PTR_DEV_PEN || p->device == YASL_PTR_DEV_ERASER) { + if (p->device == YASL_PRIVATE_PTR_DEV_PEN || p->device == YASL_PRIVATE_PTR_DEV_ERASER) { Atom type; int format; unsigned long n, after; unsigned char *value = NULL; if (XIGetProperty(d->native, id, d->tool_type, 0, 1, False, XA_ATOM, &type, &format, &n, &after, &value) == Success && - type == XA_ATOM && format == 32 && n == 1 && value) + type == XA_ATOM && format == 32 && n == 1 && value) { /* XIGetProperty packs format-32 values as uint32_t, unlike * XGetWindowProperty's array of native-sized longs. */ - p->device = *(uint32_t *)value == d->eraser ? YASL_PTR_DEV_ERASER : p->device; + if (*(uint32_t *)value == d->eraser) p->device = YASL_PRIVATE_PTR_DEV_ERASER; + else if (d->brush && *(uint32_t *)value == d->brush) p->tool = 3; + else if (d->airbrush && *(uint32_t *)value == d->airbrush) p->tool = 4; + else if (d->pencil && *(uint32_t *)value == d->pencil) p->tool = 5; + } if (value) XFree(value); } p->next = d->pointers; @@ -330,21 +400,61 @@ static void clear_pointers(struct x11_display *d) } } -static void update_axes(struct x11_pointer *p, const XIValuatorState *state) +static uint32_t update_axes(struct x11_pointer *p, const XIValuatorState *state) { + uint32_t changed = 0; const double *value = state->values; for (int i = 0; i < state->mask_len * 8; i++) { if (!XIMaskIsSet(state->mask, i)) continue; - struct tablet_axis *axes[] = {&p->pressure, &p->tilt_x, &p->tilt_y}; + struct tablet_axis *axes[] = {&p->pressure, &p->tilt_x, &p->tilt_y, &p->rotation, + &p->distance, &p->tangential, &p->wheel}; + const uint32_t bits[] = {YASL_PRIVATE_PEN_PRESSURE, YASL_PRIVATE_PEN_TILT, + YASL_PRIVATE_PEN_TILT, YASL_PRIVATE_PEN_ROTATION, YASL_PRIVATE_PEN_DISTANCE, + YASL_PRIVATE_PEN_TANGENTIAL, YASL_PRIVATE_PEN_WHEEL}; for (unsigned a = 0; a < sizeof(axes) / sizeof(axes[0]); a++) - if (axes[a]->number == i && isfinite(*value)) axes[a]->value = *value; + if (axes[a]->number == i && isfinite(*value)) { + axes[a]->value = *value; + axes[a]->valid = true; + changed |= bits[a]; + } + value++; + } + return changed; +} + +static void update_scroll(struct x11_pointer *p, const XIValuatorState *state, + struct yasl_private_pointer_event *out) +{ + const double *value = state->values; + for (int i = 0; i < state->mask_len * 8; i++) { + if (!XIMaskIsSet(state->mask, i)) continue; + struct x11_scroll_axis *axes[] = {&p->scroll_x, &p->scroll_y}; + for (unsigned a = 0; a < 2; a++) { + struct x11_scroll_axis *axis = axes[a]; + if (axis->number != i || !isfinite(*value)) continue; + if (axis->initialized) { + double delta = *value - axis->value; + if (axis->increment != 0 && isfinite(delta)) { + double lines = delta / axis->increment; + if (a == 0) out->scroll.x += lines; + else out->scroll.y += lines; + if (lines != 0) { + out->scroll_valid |= 1u; + out->scroll_source = p->finger_scroll ? 2u : 1u; + out->scroll_phase = 2u; + } + } + } + axis->value = *value; + axis->initialized = true; + } value++; } } static uint16_t pressure_value(const struct tablet_axis *a) { - if (a->number < 0 || !(a->max > a->min) || !isfinite(a->value)) return 0; + if (a->number < 0 || !(a->max > a->min) || !a->valid || !isfinite(a->value)) return 0; double v = (a->value - a->min) / (a->max - a->min); return (uint16_t)lround(fmax(0, fmin(1, v)) * 65535); } @@ -352,50 +462,121 @@ static uint16_t pressure_value(const struct tablet_axis *a) static int8_t tilt_value(const struct tablet_axis *a) { /* Xorg tablet tilt valuators are already in degrees. */ - return a->number < 0 || !isfinite(a->value) ? 0 : + return a->number < 0 || !a->valid || !isfinite(a->value) ? 0 : (int8_t)lround(fmax(-90, fmin(90, a->value))); } -static enum yasl_pointer_button pointer_button(unsigned button) +static enum yasl_private_pointer_button pointer_button(unsigned button) +{ + return button == 1 ? YASL_PRIVATE_BUTTON_PRIMARY : + button == 2 ? YASL_PRIVATE_BUTTON_MIDDLE : + button == 3 ? YASL_PRIVATE_BUTTON_SECONDARY : + button >= 8 && button <= INT16_MAX ? (enum yasl_private_pointer_button)(button - 5) : + YASL_PRIVATE_BUTTON_BACK; +} + +static uint32_t pen_axes_valid(const struct x11_pointer *p) { - return button == 1 ? YASL_BUTTON_PRIMARY : - button == 2 ? YASL_BUTTON_MIDDLE : - button == 3 ? YASL_BUTTON_SECONDARY : - button >= 8 && button <= INT16_MAX ? (enum yasl_pointer_button)(button - 5) : - YASL_BUTTON_BACK; + return (p->pressure.valid ? YASL_PRIVATE_PEN_PRESSURE : 0) | + (p->tilt_x.valid || p->tilt_y.valid ? YASL_PRIVATE_PEN_TILT : 0) | + (p->rotation.valid ? YASL_PRIVATE_PEN_ROTATION : 0) | + (p->distance.valid ? YASL_PRIVATE_PEN_DISTANCE : 0) | + (p->tangential.valid ? YASL_PRIVATE_PEN_TANGENTIAL : 0) | + (p->wheel.valid ? YASL_PRIVATE_PEN_WHEEL : 0); +} + +static double axis_normalized(const struct tablet_axis *axis) +{ + return axis->valid && axis->max > axis->min && isfinite(axis->value) ? + fmax(0, fmin(1, (axis->value - axis->min) / (axis->max - axis->min))) : 0; +} + +static void push_pen_sample(struct x11_surface *s, struct x11_pointer *p, + unsigned action, double x, double y, uint32_t changed, unsigned changed_button) +{ + if (!p || (p->device != YASL_PRIVATE_PTR_DEV_PEN && p->device != YASL_PRIVATE_PTR_DEV_ERASER)) return; + push(s, (struct yasl_private_event){.type = YASL_PRIVATE_EVENT_PEN, + .pen = { + .device_id = (uint64_t)(unsigned)p->id, + .tool_id = 0, /* XI2 exposes source IDs, but not a stable per-tool serial. */ + .x = x, .y = y, + .pressure = p->pressure.valid && p->pressure.max > p->pressure.min ? + fmax(0, fmin(1, (p->pressure.value - p->pressure.min) / + (p->pressure.max - p->pressure.min))) : 0, + .tilt_x = p->tilt_x.valid ? fmax(-90, fmin(90, p->tilt_x.value)) : 0, + .tilt_y = p->tilt_y.valid ? fmax(-90, fmin(90, p->tilt_y.value)) : 0, + .rotation = p->rotation.valid ? p->rotation.value : 0, + .distance = axis_normalized(&p->distance), + .tangential = p->tangential.valid && p->tangential.max > p->tangential.min ? + fmax(-1, fmin(1, 2 * axis_normalized(&p->tangential) - 1)) : 0, + .wheel_degrees = p->wheel.valid ? p->wheel.value : 0, + .axes_valid = pen_axes_valid(p), .axes_changed = changed, + .buttons = p->buttons, .changed_button = changed_button, + .in_contact = (p->buttons & YASL_PRIVATE_BUTTON_BIT(YASL_PRIVATE_BUTTON_PRIMARY)) != 0, + .tool = p->device == YASL_PRIVATE_PTR_DEV_ERASER ? 2u : p->tool, + .action = action, + }}, NULL, 0); } static void xi_pointer_event(struct x11_surface *s, const XIDeviceEvent *ev) { struct x11_display *d = (struct x11_display *)s->base.display; struct x11_pointer *p = pointer_for(d, ev->sourceid); - struct yasl_event event = { + struct yasl_private_event event = { .pointer = { - .pos = {yasl_scale_to_device(ev->event_x, YASL_SCALE_UNIT), - yasl_scale_to_device(ev->event_y, YASL_SCALE_UNIT)}, - .device = p ? p->device : YASL_PTR_DEV_MOUSE, + .pos = {yasl_private_scale_to_device(ev->event_x, YASL_PRIVATE_SCALE_UNIT), + yasl_private_scale_to_device(ev->event_y, YASL_PRIVATE_SCALE_UNIT)}, + .device = p ? p->device : YASL_PRIVATE_PTR_DEV_MOUSE, .mods = modifiers(ev->mods.effective), }, }; - if (p && (p->device == YASL_PTR_DEV_PEN || p->device == YASL_PTR_DEV_ERASER)) { - update_axes(p, &ev->valuators); - if (ev->evtype == XI_ButtonRelease && ev->detail == 1) + uint32_t pen_changed = 0; + if (p && (p->device == YASL_PRIVATE_PTR_DEV_PEN || p->device == YASL_PRIVATE_PTR_DEV_ERASER)) { + pen_changed = update_axes(p, &ev->valuators); + if (ev->evtype == XI_ButtonRelease && ev->detail == 1) { p->pressure.value = p->pressure.min; - event.pointer.pen = (struct yasl_pointer_pen){ + pen_changed |= p->pressure.valid ? YASL_PRIVATE_PEN_PRESSURE : 0; + } + if (ev->evtype == XI_ButtonPress || ev->evtype == XI_ButtonRelease) { + enum yasl_private_pointer_button button = pointer_button(ev->detail); + uint32_t mask = YASL_PRIVATE_BUTTON_BIT(button); + if (ev->evtype == XI_ButtonPress) p->buttons |= mask; + else p->buttons &= ~mask; + } + event.pointer.pen = (struct yasl_private_pointer_pen){ pressure_value(&p->pressure), tilt_value(&p->tilt_x), tilt_value(&p->tilt_y)}; } - if (ev->evtype == XI_Motion) event.type = YASL_EVENT_POINTER_MOTION; + if (p) update_scroll(p, &ev->valuators, &event.pointer); + if (ev->evtype == XI_Motion) event.type = YASL_PRIVATE_EVENT_POINTER_MOTION; else if (ev->detail >= 4 && ev->detail <= 7) { if (ev->evtype == XI_ButtonRelease) return; - event.type = YASL_EVENT_POINTER_SCROLL; + event.type = YASL_PRIVATE_EVENT_POINTER_SCROLL; if (ev->detail <= 5) event.pointer.scroll.y = ev->detail == 4 ? -1 : 1; else event.pointer.scroll.x = ev->detail == 6 ? -1 : 1; + event.pointer.scroll_valid = 1u; + event.pointer.scroll_source = p && p->finger_scroll ? 2u : 1u; + event.pointer.scroll_phase = 2u; } else { - event.type = ev->evtype == XI_ButtonPress ? YASL_EVENT_POINTER_DOWN : YASL_EVENT_POINTER_UP; + event.type = ev->evtype == XI_ButtonPress ? YASL_PRIVATE_EVENT_POINTER_DOWN : YASL_PRIVATE_EVENT_POINTER_UP; event.pointer.button = pointer_button(ev->detail); } d->mods = event.pointer.mods; + if (p && (p->device == YASL_PRIVATE_PTR_DEV_PEN || p->device == YASL_PRIVATE_PTR_DEV_ERASER) && + ev->detail != 4 && ev->detail != 5 && ev->detail != 6 && ev->detail != 7) { + unsigned action = ev->evtype == XI_Motion ? YASL_PRIVATE_PEN_MOVE : + ev->evtype == XI_ButtonPress ? (ev->detail == 1 ? YASL_PRIVATE_PEN_DOWN : YASL_PRIVATE_PEN_BUTTON) : + (ev->detail == 1 ? YASL_PRIVATE_PEN_UP : YASL_PRIVATE_PEN_BUTTON); + push_pen_sample(s, p, action, ev->event_x, ev->event_y, pen_changed, + ev->evtype == XI_Motion ? 0u : (unsigned)ev->detail); + } push(s, event, NULL, 0); + if (event.type == YASL_PRIVATE_EVENT_POINTER_MOTION && event.pointer.scroll_valid) { + event.type = YASL_PRIVATE_EVENT_POINTER_SCROLL; + event.pointer.pos = (struct yasl_private_vector2i){ + yasl_private_scale_to_device(ev->event_x, YASL_PRIVATE_SCALE_UNIT), + yasl_private_scale_to_device(ev->event_y, YASL_PRIVATE_SCALE_UNIT)}; + push(s, event, NULL, 0); + } } static void xi_event(struct x11_display *d, XGenericEventCookie *cookie) @@ -403,15 +584,20 @@ static void xi_event(struct x11_display *d, XGenericEventCookie *cookie) if (cookie->evtype == XI_HierarchyChanged || cookie->evtype == XI_DeviceChanged) { if (cookie->evtype == XI_DeviceChanged && ((XIDeviceChangedEvent *)cookie->data)->reason == XISlaveSwitch) return; + for (struct x11_pointer *p = d->pointers; p; p = p->next) { + if (!p->buttons) continue; + for (struct x11_surface *s = d->surfaces; s; s = s->next) + push_pen_sample(s, p, YASL_PRIVATE_PEN_CANCEL, 0, 0, 0, 0); + } clear_pointers(d); if (cookie->evtype == XI_HierarchyChanged && (((XIHierarchyEvent *)cookie->data)->flags & (XISlaveRemoved | XIDeviceDisabled | XISlaveDetached | XIMasterRemoved))) { /* Removal/disable may interrupt a drag before a release arrives. */ if (d->drag.active) - drag_end(d, YASL_DRAG_CANCELLED); + drag_end(d, YASL_PRIVATE_DRAG_CANCELLED); for (struct x11_surface *s = d->surfaces; s; s = s->next) - push(s, (struct yasl_event){.type = YASL_EVENT_INPUT_RESET}, NULL, 0); + push(s, (struct yasl_private_event){.type = YASL_PRIVATE_EVENT_INPUT_RESET}, NULL, 0); } return; } @@ -430,22 +616,22 @@ static void xi_event(struct x11_display *d, XGenericEventCookie *cookie) XMoveWindow(d->native, d->drag.icon_window, (int)round(mev->root_x) - d->drag.hotspot.x, (int)round(mev->root_y) - d->drag.hotspot.y); - struct yasl_vector2i hpos = {yasl_scale_to_device(mev->event_x, YASL_SCALE_UNIT), - yasl_scale_to_device(mev->event_y, YASL_SCALE_UNIT)}; + struct yasl_private_vector2i hpos = {yasl_private_scale_to_device(mev->event_x, YASL_PRIVATE_SCALE_UNIT), + yasl_private_scale_to_device(mev->event_y, YASL_PRIVATE_SCALE_UNIT)}; struct x11_surface *hsurf = d->native ? surface_at_root_pos(d, (int)round(mev->root_x), (int)round(mev->root_y), &hpos) : s; drag_set_hover(d, hsurf, hpos); } else if (cookie->evtype == XI_Enter && d->drag.active) { const XIEnterEvent *eev = cookie->data; - struct yasl_vector2i hpos = {yasl_scale_to_device(eev->event_x, YASL_SCALE_UNIT), - yasl_scale_to_device(eev->event_y, YASL_SCALE_UNIT)}; + struct yasl_private_vector2i hpos = {yasl_private_scale_to_device(eev->event_x, YASL_PRIVATE_SCALE_UNIT), + yasl_private_scale_to_device(eev->event_y, YASL_PRIVATE_SCALE_UNIT)}; struct x11_surface *hsurf = d->native ? surface_at_root_pos(d, (int)round(eev->root_x), (int)round(eev->root_y), &hpos) : s; drag_set_hover(d, hsurf, hpos); } else if (cookie->evtype == XI_Leave && d->drag.active) { const XIEnterEvent *lev = cookie->data; - struct yasl_vector2i lpos = {yasl_scale_to_device(lev->event_x, YASL_SCALE_UNIT), - yasl_scale_to_device(lev->event_y, YASL_SCALE_UNIT)}; + struct yasl_private_vector2i lpos = {yasl_private_scale_to_device(lev->event_x, YASL_PRIVATE_SCALE_UNIT), + yasl_private_scale_to_device(lev->event_y, YASL_PRIVATE_SCALE_UNIT)}; drag_leave_hover(d, s, lpos); } else if (cookie->evtype == XI_ButtonRelease && d->drag.active) { drag_drop_hover(d); @@ -455,24 +641,35 @@ static void xi_event(struct x11_display *d, XGenericEventCookie *cookie) else { const XIEnterEvent *ev = cookie->data; struct x11_pointer *p = pointer_for(d, ev->sourceid); - if (p) p->pressure.value = p->pressure.min; - push(s, (struct yasl_event){ - .type = cookie->evtype == XI_Leave ? YASL_EVENT_POINTER_LEAVE : YASL_EVENT_POINTER_MOTION, + if (p && cookie->evtype == XI_Leave) { + p->pressure.value = p->pressure.min; + if (p->pressure.valid) push_pen_sample(s, p, YASL_PRIVATE_PEN_LEAVE, + ev->event_x, ev->event_y, YASL_PRIVATE_PEN_PRESSURE, 0); + } else if (p && (p->device == YASL_PRIVATE_PTR_DEV_PEN || p->device == YASL_PRIVATE_PTR_DEV_ERASER)) { + push_pen_sample(s, p, YASL_PRIVATE_PEN_ENTER, ev->event_x, ev->event_y, 0, 0); + } + push(s, (struct yasl_private_event){ + .type = cookie->evtype == XI_Leave ? YASL_PRIVATE_EVENT_POINTER_LEAVE : YASL_PRIVATE_EVENT_POINTER_MOTION, .pointer = { - .pos = {yasl_scale_to_device(ev->event_x, YASL_SCALE_UNIT), - yasl_scale_to_device(ev->event_y, YASL_SCALE_UNIT)}, - .device = p ? p->device : YASL_PTR_DEV_MOUSE, + .pos = {yasl_private_scale_to_device(ev->event_x, YASL_PRIVATE_SCALE_UNIT), + yasl_private_scale_to_device(ev->event_y, YASL_PRIVATE_SCALE_UNIT)}, + .device = p ? p->device : YASL_PRIVATE_PTR_DEV_MOUSE, .mods = modifiers(ev->mods.effective), }, }, NULL, 0); } } -static bool resize(struct x11_surface *s, struct yasl_vector2i size) +static bool resize(struct x11_surface *s, struct yasl_private_vector2i size) { if (size.x <= 0 || size.y <= 0 || size.x > 32767 || size.y > 32767 || (size_t)size.x > SIZE_MAX / sizeof(uint32_t) / (size_t)size.y) return false; + if (s->base.presenter != YASL_PRIVATE_PRESENTER_CPU) { + s->size = size; + s->base.size = size; + return true; + } if (s->image && size.x == s->size.x && size.y == s->size.y) return true; struct x11_display *d = (struct x11_display *)s->base.display; if (!d->native) { @@ -480,7 +677,7 @@ static bool resize(struct x11_surface *s, struct yasl_vector2i size) s->want_draw = true; return true; } - uint64_t start = yasl_loop_now_ns(); + uint64_t start = yasl_private_loop_now_ns(); /* The presented frame goes with the old pixels. */ s->presented = false; @@ -557,9 +754,9 @@ static bool resize(struct x11_surface *s, struct yasl_vector2i size) s->pixels = pixels; s->size = size; s->base.size = size; - s->base.scale_120 = YASL_SCALE_UNIT; + s->base.scale_120 = YASL_PRIVATE_SCALE_UNIT; s->want_draw = true; - yasl_clock_note_resize(&s->clock, yasl_loop_now_ns() - start); + yasl_private_clock_note_resize(&s->clock, yasl_private_loop_now_ns() - start); return true; } @@ -575,7 +772,7 @@ static void cancel_pastes(struct x11_display *d, struct x11_surface *target) p = &r->next; } else if (!target) { *p = r->next; - if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); + if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); XDestroyWindow(d->native, r->window); free(r); } else { @@ -584,7 +781,7 @@ static void cancel_pastes(struct x11_display *d, struct x11_surface *target) } } -static uint64_t paste_timeout(void *ud, yasl_timer_id id, uint64_t interval_ns) +static uint64_t paste_timeout(void *ud, yasl_private_timer_id id, uint64_t interval_ns) { (void)id; (void)interval_ns; @@ -597,7 +794,7 @@ static uint64_t paste_timeout(void *ud, yasl_timer_id id, uint64_t interval_ns) r->timeout = 0; /* The loop retires this one-shot timer after the callback. */ XDestroyWindow(d->native, r->window); if (s && !s->destroyed) - yasl_surface_complete_paste(&s->base, r->id, YASL_TRANSFER_FAILED, NULL, 0); + yasl_private_surface_complete_paste(&s->base, r->id, YASL_PRIVATE_TRANSFER_FAILED, NULL, 0); free(r); return 0; } @@ -609,7 +806,7 @@ static void surface_destroy(struct yasl_surface *base) struct x11_display *d = (struct x11_display *)base->display; if (d->drag.active) { if (d->drag.origin == s || d->drag.carried == s) - drag_end(d, YASL_DRAG_CANCELLED); + drag_end(d, YASL_PRIVATE_DRAG_CANCELLED); else if (d->drag.hover == s) d->drag.hover = NULL; } @@ -627,6 +824,10 @@ static void surface_destroy(struct yasl_surface *base) free(s->ime_preedit); s->ime_preedit = NULL; if (s->ic) XDestroyIC(s->ic); +#if YASL_HAS_EGL + if (s->gl_context && d->egl_ready) eglDestroyContext(d->egl, s->gl_context); + if (s->gl_surface && d->egl_ready) eglDestroySurface(d->egl, s->gl_surface); +#endif if (s->shm_active) { XShmDetach(d->native, &s->shm_info); if (s->image) XDestroyImage(s->image); @@ -643,6 +844,7 @@ static void surface_destroy(struct yasl_surface *base) if (d->native) { if (s->gc) XFreeGC(d->native, s->gc); if (s->window) XDestroyWindow(d->native, s->window); + if (s->colormap) XFreeColormap(d->native, s->colormap); } input_queue_clear(&s->input); if (d->depth) { @@ -652,11 +854,11 @@ static void surface_destroy(struct yasl_surface *base) } static void set_callbacks(struct yasl_surface *base, - const struct yasl_window_cbs *cbs, void *ud) + const struct yasl_private_window_cbs *cbs, void *ud) { struct x11_surface *s = (struct x11_surface *)base; if (s->destroyed) return; - s->base.callbacks = cbs ? *cbs : (struct yasl_window_cbs){0}; + s->base.callbacks = cbs ? *cbs : (struct yasl_private_window_cbs){0}; s->cbs = &s->base.callbacks; s->base.ud = ud; s->ud = ud; @@ -685,7 +887,7 @@ static bool net_message(struct x11_surface *s, Atom type, SubstructureRedirectMask | SubstructureNotifyMask, &event) != 0; } -static bool surface_set_size(struct yasl_surface *base, struct yasl_vector2i size) +static bool surface_set_size(struct yasl_surface *base, struct yasl_private_vector2i size) { struct x11_surface *s = (struct x11_surface *)base; struct x11_display *d = (struct x11_display *)base->display; @@ -817,7 +1019,7 @@ static char *xim_text_dup(const XIMText *text, size_t *len) if (converted != text->length) { free(out); return NULL; } } out[used] = '\0'; - if (!yasl_utf8_validate(out, used)) { free(out); return NULL; } + if (!yasl_private_utf8_validate(out, used)) { free(out); return NULL; } *len = used; return out; } @@ -831,7 +1033,7 @@ static void xim_preedit_done(XIM im, XPointer client_data, XPointer call_data) free(s->ime_preedit); s->ime_preedit = NULL; s->ime_cursor_begin = s->ime_cursor_end = -1; - yasl_surface_notify_composition(&s->base, NULL, -1, -1); + yasl_private_surface_notify_composition(&s->base, NULL, -1, -1); } static int xim_preedit_start(XIM im, XPointer client_data, XPointer call_data) @@ -880,7 +1082,7 @@ static void xim_preedit_draw(XIM im, XPointer client_data, XPointer call_data) int caret = draw->caret < 0 ? -1 : (int)utf8_offset_for_chars( s->ime_preedit ? s->ime_preedit : "", new_len, draw->caret); s->ime_cursor_begin = s->ime_cursor_end = caret; - yasl_surface_notify_composition(&s->base, s->ime_preedit, caret, caret); + yasl_private_surface_notify_composition(&s->base, s->ime_preedit, caret, caret); } static void xim_preedit_caret(XIM im, XPointer client_data, XPointer call_data) @@ -892,7 +1094,7 @@ static void xim_preedit_caret(XIM im, XPointer client_data, XPointer call_data) int offset = caret->position < 0 ? -1 : (int)utf8_offset_for_chars( s->ime_preedit, strlen(s->ime_preedit), caret->position); s->ime_cursor_begin = s->ime_cursor_end = offset; - yasl_surface_notify_composition(&s->base, s->ime_preedit, offset, offset); + yasl_private_surface_notify_composition(&s->base, s->ime_preedit, offset, offset); } static XIC create_ic(struct x11_display *d, struct x11_surface *s) @@ -923,22 +1125,35 @@ static XIC create_ic(struct x11_display *d, struct x11_surface *s) return ic; } -static struct yasl_surface *create_toplevel(struct yasl_display *base, - const struct yasl_window_spec *spec, void *graphics_device) +static struct yasl_surface *create_toplevel_visual(struct yasl_private_display *base, + const struct yasl_private_window_spec *spec, Visual *visual, int depth, + enum yasl_private_presenter presenter) { - (void)graphics_device; if (!spec) return NULL; struct x11_display *d = (struct x11_display *)base; struct x11_surface *s = calloc(1, sizeof(*s)); if (!s) return NULL; s->base.display = base; - s->clock.refresh_ns = YASL_REFRESH_DEFAULT_NS; + s->base.creation_flags = spec->flags | (spec->external_gpu ? 0u : YASL_PRIVATE_SURFACE_CPU); + s->base.presenter = presenter; + s->clock.refresh_ns = YASL_PRIVATE_REFRESH_DEFAULT_NS; if (!resize(s, spec->size)) { free(s); return NULL; } - s->window = XCreateSimpleWindow(d->native, RootWindow(d->native, d->screen), - 0, 0, (unsigned)spec->size.x, (unsigned)spec->size.y, 0, 0, 0); + if (!visual) visual = DefaultVisual(d->native, d->screen); + if (!depth) depth = DefaultDepth(d->native, d->screen); + s->colormap = XCreateColormap(d->native, RootWindow(d->native, d->screen), visual, AllocNone); + XSetWindowAttributes attrs = {.colormap = s->colormap, .border_pixel = 0, .background_pixel = 0}; + s->window = XCreateWindow(d->native, RootWindow(d->native, d->screen), 0, 0, + (unsigned)spec->size.x, (unsigned)spec->size.y, 0, depth, InputOutput, + visual, CWBorderPixel | CWBackPixel | CWColormap, &attrs); + if (!s->window) { + XFreeColormap(d->native, s->colormap); + free(s); + errno = ENODEV; + return NULL; + } s->base.native_handle = (void *)(uintptr_t)s->window; s->base.size = s->size; - s->base.scale_120 = YASL_SCALE_UNIT; + s->base.scale_120 = YASL_PRIVATE_SCALE_UNIT; s->gc = XCreateGC(d->native, s->window, 0, NULL); XSelectInput(d->native, s->window, ExposureMask | StructureNotifyMask | FocusChangeMask | KeyPressMask | KeyReleaseMask | PropertyChangeMask | @@ -955,6 +1170,19 @@ static struct yasl_surface *create_toplevel(struct yasl_display *base, XISelectEvents(d->native, s->window, &mask, 1); } XSetWMProtocols(d->native, s->window, &d->delete_window, 1); + if (spec->flags & YASL_PRIVATE_SURFACE_UNDECORATED) { + struct { unsigned long flags, functions, decorations; long input_mode; unsigned long status; } hints = { + .flags = 1, .decorations = 0, + }; + XChangeProperty(d->native, s->window, d->motif_hints, d->motif_hints, 32, + PropModeReplace, (unsigned char *)&hints, 5); + } + if (spec->flags & YASL_PRIVATE_SURFACE_FIXED_SIZE) { + XSizeHints size_hints = {.flags = PMinSize | PMaxSize, + .min_width = spec->size.x, .max_width = spec->size.x, + .min_height = spec->size.y, .max_height = spec->size.y}; + XSetWMNormalHints(d->native, s->window, &size_hints); + } XClassHint hint = { .res_name = d->app_id, .res_class = d->app_id }; XSetClassHint(d->native, s->window, &hint); Atom xdnd_version = 5; @@ -967,17 +1195,197 @@ 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->base.state |= YASL_STATE_FULLSCREEN; + s->base.state |= YASL_PRIVATE_STATE_FULLSCREEN; } - XMapWindow(d->native, s->window); + if (!(spec->flags & YASL_PRIVATE_SURFACE_HIDDEN)) XMapWindow(d->native, s->window); XFlush(d->native); return &s->base; } +static struct yasl_surface *create_toplevel(struct yasl_private_display *base, + const struct yasl_private_window_spec *spec, void *graphics_device) +{ + if (!spec) return NULL; + if (graphics_device) { errno = ENOTSUP; return NULL; } + return create_toplevel_visual(base, spec, NULL, 0, + spec->external_gpu ? YASL_PRIVATE_PRESENTER_NONE : YASL_PRIVATE_PRESENTER_CPU); +} + +#if YASL_HAS_EGL +static bool egl_open(struct x11_display *d) +{ + if (d->egl_ready) return true; + d->egl = eglGetDisplay((EGLNativeDisplayType)d->native); + EGLint major = 0, minor = 0; + if (d->egl == EGL_NO_DISPLAY || !eglInitialize(d->egl, &major, &minor)) { + errno = ENOTSUP; + return false; + } + d->egl_ready = true; + return true; +} + +static struct yasl_surface *create_gl_surface(struct yasl_private_display *base, + const struct yasl_private_window_spec *spec, const int *attrs, + struct yasl_surface *share) +{ + if (!spec) { errno = EINVAL; return NULL; } + struct x11_display *d = (struct x11_display *)base; + struct yasl_private_gl_options options; + if (!yasl_private_gl_parse_options(attrs, &options)) { errno = EINVAL; return NULL; } + if (!egl_open(d)) return NULL; + if (options.srgb) { + const char *extensions = eglQueryString(d->egl, EGL_EXTENSIONS); + if (!extensions || !strstr(extensions, "EGL_KHR_gl_colorspace")) { + errno = ENOTSUP; + return NULL; + } + } + + EGLint renderable = options.api == YASL_PRIVATE_GL_OPENGL_ES ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_BIT; + EGLint config_attrs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, renderable, + EGL_RED_SIZE, options.red_bits, + EGL_GREEN_SIZE, options.green_bits, + EGL_BLUE_SIZE, options.blue_bits, + EGL_ALPHA_SIZE, options.alpha_bits, + EGL_DEPTH_SIZE, options.depth_bits, + EGL_STENCIL_SIZE, options.stencil_bits, + EGL_SAMPLE_BUFFERS, options.samples > 0 ? 1 : 0, + EGL_SAMPLES, options.samples, + EGL_NONE, + }; + EGLConfig config = NULL; + EGLint count = 0; + if (!eglChooseConfig(d->egl, config_attrs, &config, 1, &count) || !count) { + errno = ENOTSUP; + return NULL; + } + EGLint visual_id = 0, depth = 0; + if (!eglGetConfigAttrib(d->egl, config, EGL_NATIVE_VISUAL_ID, &visual_id) || + !eglGetConfigAttrib(d->egl, config, EGL_BUFFER_SIZE, &depth)) { + errno = ENODEV; + return NULL; + } + XVisualInfo visual_query = {.visualid = (VisualID)visual_id}; + int visual_count = 0; + XVisualInfo *visual_info = XGetVisualInfo(d->native, VisualIDMask, &visual_query, &visual_count); + if (!visual_info || !visual_count) { + if (visual_info) XFree(visual_info); + errno = ENODEV; + return NULL; + } + Visual *visual = visual_info[0].visual; + depth = visual_info[0].depth; + struct yasl_private_window_spec gl_spec = *spec; + gl_spec.external_gpu = true; + gl_spec.flags &= ~YASL_PRIVATE_SURFACE_CPU; + struct yasl_surface *base_surface = create_toplevel_visual(base, &gl_spec, visual, depth, + YASL_PRIVATE_PRESENTER_GL); + XFree(visual_info); + if (!base_surface) return NULL; + struct x11_surface *s = (struct x11_surface *)base_surface; + s->gl_config = config; + EGLint surface_attrs[] = {EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR, EGL_NONE}; + s->gl_surface = eglCreateWindowSurface(d->egl, config, + (EGLNativeWindowType)s->window, options.srgb ? surface_attrs : NULL); + if (s->gl_surface == EGL_NO_SURFACE) { + surface_destroy(base_surface); + errno = ENODEV; + return NULL; + } + if (options.api == YASL_PRIVATE_GL_OPENGL) { + if (!eglBindAPI(EGL_OPENGL_API)) { surface_destroy(base_surface); errno = ENOTSUP; return NULL; } + } else if (!eglBindAPI(EGL_OPENGL_ES_API)) { + surface_destroy(base_surface); errno = ENOTSUP; return NULL; + } + EGLContext share_context = EGL_NO_CONTEXT; + if (share) { + struct x11_surface *shared = (struct x11_surface *)share; + if (share->display != base || shared->base.presenter != YASL_PRIVATE_PRESENTER_GL || + shared->gl_context == EGL_NO_CONTEXT) { + surface_destroy(base_surface); errno = EINVAL; return NULL; + } + share_context = shared->gl_context; + } + EGLint context_attrs[12]; + EGLint n = 0; + context_attrs[n++] = EGL_CONTEXT_MAJOR_VERSION; + context_attrs[n++] = options.major; + context_attrs[n++] = EGL_CONTEXT_MINOR_VERSION; + context_attrs[n++] = options.minor; + if (options.api == YASL_PRIVATE_GL_OPENGL && options.profile != YASL_PRIVATE_GL_ANY) { + context_attrs[n++] = EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR; + context_attrs[n++] = options.profile == YASL_PRIVATE_GL_CORE ? + EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR : EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR; + } + EGLint context_flags = (options.debug ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0) | + (options.forward_compat ? EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR : 0); + if (context_flags) { + context_attrs[n++] = EGL_CONTEXT_FLAGS_KHR; + context_attrs[n++] = context_flags; + } + context_attrs[n] = EGL_NONE; + s->gl_context = eglCreateContext(d->egl, config, share_context, context_attrs); + if (s->gl_context == EGL_NO_CONTEXT) { + surface_destroy(base_surface); + errno = ENOTSUP; + return NULL; + } + return base_surface; +} + +static bool gl_make_current(struct yasl_private_display *base, struct yasl_surface *surface) +{ + struct x11_display *d = (struct x11_display *)base; + if (!d->egl_ready) { errno = ENOTSUP; return false; } + if (!surface) return eglMakeCurrent(d->egl, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + struct x11_surface *s = (struct x11_surface *)surface; + if (surface->display != base || s->base.presenter != YASL_PRIVATE_PRESENTER_GL || !s->gl_context) { + errno = EINVAL; + return false; + } + return eglMakeCurrent(d->egl, s->gl_surface, s->gl_surface, s->gl_context) == EGL_TRUE; +} + +static yasl_private_gl_proc gl_get_proc_address(struct yasl_private_display *base, const char *name) +{ + (void)base; + return name ? (yasl_private_gl_proc)eglGetProcAddress(name) : NULL; +} + +static bool gl_swap_interval(struct yasl_private_display *base, struct yasl_surface *surface, int interval) +{ + struct x11_display *d = (struct x11_display *)base; + if (!surface || surface->display != base || ((struct x11_surface *)surface)->base.presenter != YASL_PRIVATE_PRESENTER_GL) { + errno = EINVAL; + return false; + } + return d->egl_ready && eglSwapInterval(d->egl, interval) == EGL_TRUE; +} + +static bool gl_swap_buffers(struct yasl_private_display *base, struct yasl_surface *surface) +{ + struct x11_display *d = (struct x11_display *)base; + struct x11_surface *s = (struct x11_surface *)surface; + if (!surface || surface->display != base || s->base.presenter != YASL_PRIVATE_PRESENTER_GL || !d->egl_ready) { + errno = EINVAL; + return false; + } + if (!eglSwapBuffers(d->egl, s->gl_surface)) return false; + s->presented = true; + yasl_private_clock_presented(&s->clock, yasl_private_loop_now_ns(), false); + return true; +} +#endif + static bool popup_spec_valid(const struct x11_surface *parent, - const struct yasl_popup_spec *spec) + const struct yasl_private_popup_spec *spec) { if (!parent || !spec) return false; + if (spec->positioner_explicit && (spec->anchor_edges > 8 || spec->gravity > 8 || + (spec->constraints & ~63u))) return false; int parent_cx = parent->is_popup ? parent->content_offset.x : 0; int parent_cy = parent->is_popup ? parent->content_offset.y : 0; int64_t x = (int64_t)spec->anchor.x - parent_cx; @@ -992,10 +1400,87 @@ static bool popup_spec_valid(const struct x11_surface *parent, (int64_t)spec->content_offset.y + spec->content_size.y <= spec->size.y; } +enum { X11_ANCHOR_NONE, X11_ANCHOR_TOP, X11_ANCHOR_BOTTOM, X11_ANCHOR_LEFT, + X11_ANCHOR_RIGHT, X11_ANCHOR_TOP_LEFT, X11_ANCHOR_BOTTOM_LEFT, + X11_ANCHOR_TOP_RIGHT, X11_ANCHOR_BOTTOM_RIGHT }; +enum { X11_CONSTRAINT_SLIDE_X = 1u, X11_CONSTRAINT_SLIDE_Y = 2u, + X11_CONSTRAINT_FLIP_X = 4u, X11_CONSTRAINT_FLIP_Y = 8u, + X11_CONSTRAINT_RESIZE_X = 16u, X11_CONSTRAINT_RESIZE_Y = 32u }; + +static void popup_anchor_point(const struct yasl_private_popup_spec *spec, int *x, int *y) +{ + switch (spec->anchor_edges) { + case X11_ANCHOR_TOP: *x = spec->anchor.x + spec->anchor.width / 2; *y = spec->anchor.y; break; + case X11_ANCHOR_BOTTOM: *x = spec->anchor.x + spec->anchor.width / 2; *y = spec->anchor.y + spec->anchor.height; break; + case X11_ANCHOR_LEFT: *x = spec->anchor.x; *y = spec->anchor.y + spec->anchor.height / 2; break; + case X11_ANCHOR_RIGHT: *x = spec->anchor.x + spec->anchor.width; *y = spec->anchor.y + spec->anchor.height / 2; break; + case X11_ANCHOR_TOP_LEFT: *x = spec->anchor.x; *y = spec->anchor.y; break; + case X11_ANCHOR_BOTTOM_LEFT: *x = spec->anchor.x; *y = spec->anchor.y + spec->anchor.height; break; + case X11_ANCHOR_TOP_RIGHT: *x = spec->anchor.x + spec->anchor.width; *y = spec->anchor.y; break; + case X11_ANCHOR_BOTTOM_RIGHT: *x = spec->anchor.x + spec->anchor.width; *y = spec->anchor.y + spec->anchor.height; break; + default: *x = spec->anchor.x + spec->anchor.width / 2; *y = spec->anchor.y + spec->anchor.height / 2; break; + } +} + +static unsigned popup_flip_x(unsigned gravity) +{ + switch (gravity) { + case X11_ANCHOR_LEFT: return X11_ANCHOR_RIGHT; + case X11_ANCHOR_RIGHT: return X11_ANCHOR_LEFT; + case X11_ANCHOR_TOP_LEFT: return X11_ANCHOR_TOP_RIGHT; + case X11_ANCHOR_BOTTOM_LEFT: return X11_ANCHOR_BOTTOM_RIGHT; + case X11_ANCHOR_TOP_RIGHT: return X11_ANCHOR_TOP_LEFT; + case X11_ANCHOR_BOTTOM_RIGHT: return X11_ANCHOR_BOTTOM_LEFT; + default: return gravity; + } +} + +static unsigned popup_flip_y(unsigned gravity) +{ + switch (gravity) { + case X11_ANCHOR_TOP: return X11_ANCHOR_BOTTOM; + case X11_ANCHOR_BOTTOM: return X11_ANCHOR_TOP; + case X11_ANCHOR_TOP_LEFT: return X11_ANCHOR_BOTTOM_LEFT; + case X11_ANCHOR_TOP_RIGHT: return X11_ANCHOR_BOTTOM_RIGHT; + case X11_ANCHOR_BOTTOM_LEFT: return X11_ANCHOR_TOP_LEFT; + case X11_ANCHOR_BOTTOM_RIGHT: return X11_ANCHOR_TOP_RIGHT; + default: return gravity; + } +} + +static void popup_position_at(const struct yasl_private_popup_spec *spec, int ax, int ay, + unsigned gravity, struct yasl_private_vector2i size, int *x, int *y) +{ + int content_w = spec->content_size.x ? spec->content_size.x : size.x - spec->content_offset.x; + int content_h = spec->content_size.y ? spec->content_size.y : size.y - spec->content_offset.y; + switch (gravity) { + case X11_ANCHOR_LEFT: case X11_ANCHOR_TOP_LEFT: case X11_ANCHOR_BOTTOM_LEFT: + *x = ax - content_w - spec->content_offset.x; break; + case X11_ANCHOR_RIGHT: case X11_ANCHOR_TOP_RIGHT: case X11_ANCHOR_BOTTOM_RIGHT: + *x = ax - spec->content_offset.x; break; + default: *x = ax - content_w / 2 - spec->content_offset.x; break; + } + switch (gravity) { + case X11_ANCHOR_TOP: case X11_ANCHOR_TOP_LEFT: case X11_ANCHOR_TOP_RIGHT: + *y = ay - content_h - spec->content_offset.y; break; + case X11_ANCHOR_BOTTOM: case X11_ANCHOR_BOTTOM_LEFT: case X11_ANCHOR_BOTTOM_RIGHT: + *y = ay - spec->content_offset.y; break; + default: *y = ay - content_h / 2 - spec->content_offset.y; break; + } +} + +static int popup_overflow(int pos, int extent, int low, int high) +{ + int64_t end = (int64_t)pos + extent; + int64_t overflow = (pos < low ? (int64_t)low - pos : 0) + (end > high ? end - high : 0); + return overflow > INT_MAX ? INT_MAX : (int)overflow; +} + static void popup_calculate_position(struct x11_display *d, struct x11_surface *parent, - const struct yasl_popup_spec *spec, - int *out_x, int *out_y) + const struct yasl_private_popup_spec *spec, + int *out_x, int *out_y, + struct yasl_private_vector2i *out_size) { int parent_rx = 0, parent_ry = 0; if (d->native) { @@ -1007,16 +1492,23 @@ static void popup_calculate_position(struct x11_display *d, int parent_cx = parent->is_popup ? parent->content_offset.x : 0; int parent_cy = parent->is_popup ? parent->content_offset.y : 0; - int anchor_x = parent_rx + (spec->anchor.x - parent_cx); - int anchor_y = parent_ry + (spec->anchor.y - parent_cy); - - int root_x, root_y; - if (spec->beside) { - root_x = anchor_x + spec->anchor.width - spec->content_offset.x; - root_y = anchor_y - spec->content_offset.y; + struct yasl_private_vector2i size = spec->size; + int anchor_x, anchor_y, root_x, root_y; + if (spec->positioner_explicit) { + popup_anchor_point(spec, &anchor_x, &anchor_y); + anchor_x += parent_rx - parent_cx; + anchor_y += parent_ry - parent_cy; + popup_position_at(spec, anchor_x, anchor_y, spec->gravity, size, &root_x, &root_y); } else { - root_x = anchor_x - spec->content_offset.x; - root_y = anchor_y + spec->anchor.height - spec->content_offset.y; + anchor_x = parent_rx + (spec->anchor.x - parent_cx); + anchor_y = parent_ry + (spec->anchor.y - parent_cy); + if (spec->beside) { + root_x = anchor_x + spec->anchor.width - spec->content_offset.x; + root_y = anchor_y - spec->content_offset.y; + } else { + root_x = anchor_x - spec->content_offset.x; + root_y = anchor_y + spec->anchor.height - spec->content_offset.y; + } } int mon_x = 0, mon_y = 0; @@ -1043,29 +1535,69 @@ static void popup_calculate_position(struct x11_display *d, } #endif - if (spec->beside) { - if (root_x + spec->size.x > mon_x + mon_w) { - int flipped_x = anchor_x - spec->size.x + spec->content_offset.x; - if (flipped_x >= mon_x) root_x = flipped_x; + if (spec->positioner_explicit) { + if (spec->constraints & X11_CONSTRAINT_FLIP_X) { + int flip_x, flip_y; + /* Reflect the selected anchor edge across the anchor rectangle center. */ + int anchor_origin_x = parent_rx - parent_cx + spec->anchor.x; + int flipped_anchor_x = 2 * anchor_origin_x + spec->anchor.width - anchor_x; + popup_position_at(spec, flipped_anchor_x, anchor_y, popup_flip_x(spec->gravity), size, &flip_x, &flip_y); + if (popup_overflow(flip_x, size.x, mon_x, mon_x + mon_w) < + popup_overflow(root_x, size.x, mon_x, mon_x + mon_w)) root_x = flip_x; + } + if (spec->constraints & X11_CONSTRAINT_FLIP_Y) { + int flip_x, flip_y; + int anchor_origin_y = parent_ry - parent_cy + spec->anchor.y; + int flipped_anchor_y = 2 * anchor_origin_y + spec->anchor.height - anchor_y; + popup_position_at(spec, anchor_x, flipped_anchor_y, popup_flip_y(spec->gravity), size, &flip_x, &flip_y); + if (popup_overflow(flip_y, size.y, mon_y, mon_y + mon_h) < + popup_overflow(root_y, size.y, mon_y, mon_y + mon_h)) root_y = flip_y; + } + if (spec->constraints & X11_CONSTRAINT_SLIDE_X) { + if (root_x < mon_x) root_x = mon_x; + if (root_x + size.x > mon_x + mon_w) root_x = mon_x + mon_w - size.x; + } + if (spec->constraints & X11_CONSTRAINT_SLIDE_Y) { + if (root_y < mon_y) root_y = mon_y; + if (root_y + size.y > mon_y + mon_h) root_y = mon_y + mon_h - size.y; + } + if ((spec->constraints & X11_CONSTRAINT_RESIZE_X) && + popup_overflow(root_x, size.x, mon_x, mon_x + mon_w)) { + if (root_x >= mon_x + mon_w) root_x = mon_x + mon_w - 1; + if (root_x < mon_x) { size.x -= mon_x - root_x; root_x = mon_x; } + if (root_x + size.x > mon_x + mon_w) size.x = mon_x + mon_w - root_x; + if (size.x < 1) size.x = 1; + } + if ((spec->constraints & X11_CONSTRAINT_RESIZE_Y) && + popup_overflow(root_y, size.y, mon_y, mon_y + mon_h)) { + if (root_y >= mon_y + mon_h) root_y = mon_y + mon_h - 1; + if (root_y < mon_y) { size.y -= mon_y - root_y; root_y = mon_y; } + if (root_y + size.y > mon_y + mon_h) size.y = mon_y + mon_h - root_y; + if (size.y < 1) size.y = 1; } } else { - if (root_y + spec->size.y > mon_y + mon_h) { - int flipped_y = anchor_y - spec->size.y + spec->content_offset.y; + if (spec->beside) { + if (root_x + size.x > mon_x + mon_w) { + int flipped_x = anchor_x - size.x + spec->content_offset.x; + if (flipped_x >= mon_x) root_x = flipped_x; + } + } else if (root_y + size.y > mon_y + mon_h) { + int flipped_y = anchor_y - size.y + spec->content_offset.y; if (flipped_y >= mon_y) root_y = flipped_y; } + if (root_x + size.x > mon_x + mon_w) root_x = mon_x + mon_w - size.x; + if (root_x < mon_x) root_x = mon_x; + if (root_y + size.y > mon_y + mon_h) root_y = mon_y + mon_h - size.y; + if (root_y < mon_y) root_y = mon_y; } - if (root_x + spec->size.x > mon_x + mon_w) root_x = mon_x + mon_w - spec->size.x; - if (root_x < mon_x) root_x = mon_x; - if (root_y + spec->size.y > mon_y + mon_h) root_y = mon_y + mon_h - spec->size.y; - if (root_y < mon_y) root_y = mon_y; - *out_x = root_x; *out_y = root_y; + if (out_size) *out_size = size; } static struct yasl_surface *surface_create_popup(struct yasl_surface *parent_base, - const struct yasl_popup_spec *spec) + const struct yasl_private_popup_spec *spec) { struct x11_surface *parent = (struct x11_surface *)parent_base; if (!parent || parent->destroyed || !popup_spec_valid(parent, spec)) @@ -1074,14 +1606,17 @@ static struct yasl_surface *surface_create_popup(struct yasl_surface *parent_bas struct x11_surface *s = calloc(1, sizeof(*s)); if (!s) return NULL; s->base.display = parent_base->display; + s->base.creation_flags = parent_base->creation_flags; + s->base.presenter = (parent_base->creation_flags & YASL_PRIVATE_SURFACE_CPU) ? + YASL_PRIVATE_PRESENTER_CPU : YASL_PRIVATE_PRESENTER_NONE; s->parent = parent; s->is_popup = true; s->content_offset = spec->content_offset; s->clock.refresh_ns = parent->clock.refresh_ns; - if (!resize(s, spec->size)) { free(s); return NULL; } - int root_x = 0, root_y = 0; - popup_calculate_position(d, parent, spec, &root_x, &root_y); + struct yasl_private_vector2i actual_size; + popup_calculate_position(d, parent, spec, &root_x, &root_y, &actual_size); + if (!resize(s, actual_size)) { free(s); return NULL; } if (d->native) { XSetWindowAttributes attrs = { @@ -1089,7 +1624,7 @@ static struct yasl_surface *surface_create_popup(struct yasl_surface *parent_bas .save_under = True, }; s->window = XCreateWindow(d->native, RootWindow(d->native, d->screen), - root_x, root_y, (unsigned)spec->size.x, (unsigned)spec->size.y, + root_x, root_y, (unsigned)actual_size.x, (unsigned)actual_size.y, 0, CopyFromParent, InputOutput, CopyFromParent, CWOverrideRedirect | CWSaveUnder, &attrs); if (!s->window) { @@ -1098,7 +1633,7 @@ static struct yasl_surface *surface_create_popup(struct yasl_surface *parent_bas } s->base.native_handle = (void *)(uintptr_t)s->window; s->base.size = s->size; - s->base.scale_120 = YASL_SCALE_UNIT; + s->base.scale_120 = YASL_PRIVATE_SCALE_UNIT; s->gc = XCreateGC(d->native, s->window, 0, NULL); XSelectInput(d->native, s->window, ExposureMask | StructureNotifyMask | FocusChangeMask | KeyPressMask | KeyReleaseMask | PropertyChangeMask | @@ -1127,25 +1662,27 @@ static struct yasl_surface *surface_create_popup(struct yasl_surface *parent_bas } static bool surface_popup_reposition(struct yasl_surface *popup_base, - const struct yasl_popup_spec *spec) + const struct yasl_private_popup_spec *spec) { struct x11_surface *s = (struct x11_surface *)popup_base; if (!s || s->destroyed || !s->is_popup || !s->parent || s->parent->destroyed || !popup_spec_valid(s->parent, spec)) return false; struct x11_display *d = (struct x11_display *)popup_base->display; - if (spec->size.x != s->size.x || spec->size.y != s->size.y) { - if (!resize(s, spec->size)) - return false; - } s->content_offset = spec->content_offset; int root_x = 0, root_y = 0; - popup_calculate_position(d, s->parent, spec, &root_x, &root_y); + struct yasl_private_vector2i actual_size; + popup_calculate_position(d, s->parent, spec, &root_x, &root_y, &actual_size); + bool changed_size = actual_size.x != s->size.x || actual_size.y != s->size.y; + if (changed_size && !resize(s, actual_size)) return false; if (d->native && s->window) { XMoveResizeWindow(d->native, s->window, root_x, root_y, - (unsigned)spec->size.x, (unsigned)spec->size.y); + (unsigned)actual_size.x, (unsigned)actual_size.y); XFlush(d->native); } + if (changed_size) + yasl_private_surface_notify_configure(&s->base, + (struct yasl_private_geometry){actual_size, YASL_PRIVATE_SCALE_UNIT}); schedule_redraw(&s->base); return true; } @@ -1157,22 +1694,22 @@ static void update_state(struct x11_surface *s) int format; unsigned long count, left; unsigned char *data = NULL; - uint32_t state = s->base.state & ~(YASL_STATE_FULLSCREEN | YASL_STATE_MAXIMIZED); + uint32_t state = s->base.state & ~(YASL_PRIVATE_STATE_FULLSCREEN | YASL_PRIVATE_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; bool h = false, v = false; for (unsigned long i = 0; i < count; i++) { - if (atoms[i] == d->fullscreen) state |= YASL_STATE_FULLSCREEN; + if (atoms[i] == d->fullscreen) state |= YASL_PRIVATE_STATE_FULLSCREEN; if (atoms[i] == d->maximized_h) h = true; if (atoms[i] == d->maximized_v) v = true; } - if (h && v) state |= YASL_STATE_MAXIMIZED; + if (h && v) state |= YASL_PRIVATE_STATE_MAXIMIZED; } if (data) XFree(data); if (state == s->base.state) return; s->base.state = state; - yasl_surface_notify_state(&s->base, state); + yasl_private_surface_notify_state(&s->base, state); } static void selection_request(struct x11_display *d, XSelectionRequestEvent *request) @@ -1184,10 +1721,10 @@ static void selection_request(struct x11_display *d, XSelectionRequestEvent *req .target = request->target, .time = request->time, }; Atom property = request->property ? request->property : request->target; - int slot = request->selection == XA_PRIMARY ? YASL_PRIMARY : YASL_CLIPBOARD; + int slot = request->selection == XA_PRIMARY ? YASL_PRIVATE_PRIMARY : YASL_PRIVATE_CLIPBOARD; const char *text = d->selection[slot]; size_t latin1_len = 0; - char *latin1 = text ? yasl_utf8_to_latin1(text, &latin1_len) : NULL; + char *latin1 = text ? yasl_private_utf8_to_latin1(text, &latin1_len) : NULL; if (text && request->target == d->targets) { Atom targets[] = {d->targets, d->utf8, XA_STRING}; XChangeProperty(d->native, request->requestor, property, XA_ATOM, 32, @@ -1267,7 +1804,7 @@ static void xdnd_selection_notify(struct x11_display *d, XSelectionEvent *event) size_t out_len = 0; char *parsed = parse_file_uris((const char *)data, count, &out_len); if (parsed && out_len > 0) { - push(s, (struct yasl_event){.type = YASL_EVENT_DROP}, parsed, out_len); + push(s, (struct yasl_private_event){.type = YASL_PRIVATE_EVENT_DROP}, parsed, out_len); free(parsed); success = true; } @@ -1304,20 +1841,20 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event) /* Surface destruction detached this accepted request. The reply means * the owner has finished writing the initial property; retire safely. */ *p = r->next; - if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); + if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); XDestroyWindow(d->native, r->window); free(r); return; } if (event->property == None) { *p = r->next; - if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); - enum yasl_transfer_status status = + if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); + enum yasl_private_transfer_status status = XGetSelectionOwner(d->native, r->selection) == None - ? YASL_TRANSFER_UNAVAILABLE : YASL_TRANSFER_FAILED; + ? YASL_PRIVATE_TRANSFER_UNAVAILABLE : YASL_PRIVATE_TRANSFER_FAILED; XDestroyWindow(d->native, r->window); if (s && !s->destroyed) - yasl_surface_complete_paste(&s->base, r->id, status, NULL, 0); + yasl_private_surface_complete_paste(&s->base, r->id, status, NULL, 0); free(r); return; } @@ -1333,14 +1870,14 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event) &probe_count, &probe_left, &probe_data); if (probe_data) XFree(probe_data); if (probe_result == Success && probe_type == d->incr) { - yasl_surface_complete_paste(&s->base, r->id, - YASL_TRANSFER_FAILED, NULL, 0); + yasl_private_surface_complete_paste(&s->base, r->id, + YASL_PRIVATE_TRANSFER_FAILED, NULL, 0); r->target = NULL; r->id = 0; return; } *p = r->next; - if (r->timeout) yasl_loop_del_timer(d->loop, r->timeout); + if (r->timeout) yasl_private_loop_del_timer(d->loop, r->timeout); Atom actual = None; int format = 0; unsigned long count = 0, left = 0; @@ -1389,17 +1926,17 @@ static void selection_notify(struct x11_display *d, XSelectionEvent *event) } if (complete && actual == XA_STRING) { size_t utf8_len; - char *utf8 = yasl_utf8_from_latin1(buf, total, &utf8_len); + char *utf8 = yasl_private_utf8_from_latin1(buf, total, &utf8_len); free(buf); buf = utf8; if (buf) total = utf8_len; } - enum yasl_transfer_status status = YASL_TRANSFER_FAILED; - if (complete && buf && yasl_utf8_validate(buf, total)) - status = YASL_TRANSFER_OK; - yasl_surface_complete_paste(&s->base, r->id, status, - status == YASL_TRANSFER_OK ? buf : NULL, - status == YASL_TRANSFER_OK ? total : 0); + enum yasl_private_transfer_status status = YASL_PRIVATE_TRANSFER_FAILED; + if (complete && buf && yasl_private_utf8_validate(buf, total)) + status = YASL_PRIVATE_TRANSFER_OK; + yasl_private_surface_complete_paste(&s->base, r->id, status, + status == YASL_PRIVATE_TRANSFER_OK ? buf : NULL, + status == YASL_PRIVATE_TRANSFER_OK ? total : 0); free(buf); XDestroyWindow(d->native, r->window); free(r); @@ -1413,7 +1950,7 @@ static void refresh_keyboard(struct x11_display *d) if (!keyboard) return; if (keyboard->names && keyboard->names->keys) { for (unsigned code = keyboard->min_key_code; code <= keyboard->max_key_code; code++) - d->keys[code] = yasl_key_from_xkb_name(keyboard->names->keys[code].name); + d->keys[code] = yasl_private_key_from_xkb_name(keyboard->names->keys[code].name); } XkbFreeKeyboard(keyboard, XkbAllComponentsMask, True); } @@ -1460,18 +1997,18 @@ static void key_event(struct x11_surface *s, XKeyEvent *key, bool repeat) } } uint32_t cp = xkb_keysym_to_utf32((xkb_keysym_t)base); - push(s, (struct yasl_event){ - .type = YASL_EVENT_KEY, + push(s, (struct yasl_private_event){ + .type = YASL_PRIVATE_EVENT_KEY, .key = {.key = key->keycode < 256 ? d->keys[key->keycode] : YASL_KEY_UNKNOWN, - .codepoint = yasl_key_label(cp), .native_code = key->keycode, - .shifted = yasl_key_label(xkb_keysym_to_utf32((xkb_keysym_t)shifted)), + .codepoint = yasl_private_key_label(cp), .native_code = key->keycode, + .shifted = yasl_private_key_label(xkb_keysym_to_utf32((xkb_keysym_t)shifted)), .mods = d->mods, .action = key->type == KeyRelease ? YASL_KEY_RELEASE : repeat ? YASL_KEY_REPEAT : YASL_KEY_PRESS}, }, NULL, 0); if (len > 0 && text && (unsigned char)text[0] >= 0x20 && text[0] != 0x7f && - !(d->mods & (YASL_MOD_CTRL | YASL_MOD_SUPER | YASL_MOD_ALT))) - push(s, (struct yasl_event){.type = YASL_EVENT_TEXT}, text, (size_t)len); + !(d->mods & (YASL_PRIVATE_MOD_CTRL | YASL_PRIVATE_MOD_SUPER | YASL_PRIVATE_MOD_ALT))) + push(s, (struct yasl_private_event){.type = YASL_PRIVATE_EVENT_TEXT}, text, (size_t)len); if (text != local) free(text); } @@ -1480,7 +2017,7 @@ static void handle_client_message(struct x11_display *d, struct x11_surface *s, { if (ev->message_type == d->protocols && (Atom)ev->data.l[0] == d->delete_window) { - yasl_surface_notify_close(&s->base); + yasl_private_surface_notify_close(&s->base); } else if (ev->message_type == d->xdnd_enter) { Window source = (Window)ev->data.l[0]; int version = (int)((unsigned long)ev->data.l[1] >> 24); @@ -1521,19 +2058,19 @@ static void handle_client_message(struct x11_display *d, struct x11_surface *s, XTranslateCoordinates(d->native, RootWindow(d->native, d->screen), target, rx, ry, &wx, &wy, &child); } - struct yasl_vector2i pos = {wx, wy}; + struct yasl_private_vector2i pos = {wx, wy}; if (d->xdnd.surface && d->xdnd.surface != s) { struct x11_surface *old = d->xdnd.surface; - struct yasl_vector2i old_pos = d->xdnd.pos; + struct yasl_private_vector2i old_pos = d->xdnd.pos; d->xdnd.surface = NULL; if (d->xdnd.accepts && !old->destroyed) - yasl_surface_notify_drop(&old->base, YASL_DROP_LEAVE, old_pos); + yasl_private_surface_notify_drop(&old->base, YASL_PRIVATE_DROP_LEAVE, old_pos); } d->xdnd.surface = s; d->xdnd.pos = pos; d->xdnd.time = t; if (d->xdnd.accepts && !s->destroyed) - yasl_surface_notify_drop(&s->base, YASL_DROP_OVER, pos); + yasl_private_surface_notify_drop(&s->base, YASL_PRIVATE_DROP_OVER, pos); if (d->native) { XEvent reply = {0}; @@ -1556,7 +2093,7 @@ static void handle_client_message(struct x11_display *d, struct x11_surface *s, } else if (ev->message_type == d->xdnd_leave) { if (d->xdnd.surface == s) { if (d->xdnd.accepts && !s->destroyed) - yasl_surface_notify_drop(&s->base, YASL_DROP_LEAVE, d->xdnd.pos); + yasl_private_surface_notify_drop(&s->base, YASL_PRIVATE_DROP_LEAVE, d->xdnd.pos); } d->xdnd = (typeof(d->xdnd)){0}; } else if (ev->message_type == d->xdnd_drop) { @@ -1566,7 +2103,7 @@ static void handle_client_message(struct x11_display *d, struct x11_surface *s, bool was_accepting = d->xdnd.accepts && d->xdnd.has_uri_list && (d->xdnd.source == source); if (d->xdnd.surface == s) { if (was_accepting && !s->destroyed) - yasl_surface_notify_drop(&s->base, YASL_DROP_LEAVE, d->xdnd.pos); + yasl_private_surface_notify_drop(&s->base, YASL_PRIVATE_DROP_LEAVE, d->xdnd.pos); } if (was_accepting && !s->destroyed) { d->pending_drop.active = true; @@ -1593,7 +2130,7 @@ static void handle_client_message(struct x11_display *d, struct x11_surface *s, } } -static void dispatch(struct yasl_display *base) +static void dispatch(struct yasl_private_display *base) { struct x11_display *d = (struct x11_display *)base; d->depth++; @@ -1636,11 +2173,11 @@ static void dispatch(struct yasl_display *base) switch (event.type) { case Expose: s->want_draw = true; break; case ConfigureNotify: { - struct yasl_vector2i size = {event.xconfigure.width, event.xconfigure.height}; + struct yasl_private_vector2i size = {event.xconfigure.width, event.xconfigure.height}; if (size.x == s->size.x && size.y == s->size.y) break; if (resize(s, size)) - yasl_surface_notify_configure(&s->base, - (struct yasl_geometry){size, YASL_SCALE_UNIT}); + yasl_private_surface_notify_configure(&s->base, + (struct yasl_private_geometry){size, YASL_PRIVATE_SCALE_UNIT}); break; } case ClientMessage: @@ -1648,12 +2185,16 @@ static void dispatch(struct yasl_display *base) break; case FocusIn: case FocusOut: { bool focused = event.type == FocusIn; - if (s->ic) { if (focused) XSetICFocus(s->ic); else XUnsetICFocus(s->ic); } - if (focused) s->base.state |= YASL_STATE_ACTIVATED; - else s->base.state &= ~YASL_STATE_ACTIVATED; - push(s, (struct yasl_event){ - .type = YASL_EVENT_FOCUS, .focus.focused = focused}, NULL, 0); - yasl_surface_notify_state(&s->base, s->base.state); + s->focused = focused; + if (s->ic) { + if (focused && s->base.text_input_active) XSetICFocus(s->ic); + else XUnsetICFocus(s->ic); + } + if (focused) s->base.state |= YASL_PRIVATE_STATE_ACTIVATED; + else s->base.state &= ~YASL_PRIVATE_STATE_ACTIVATED; + push(s, (struct yasl_private_event){ + .type = YASL_PRIVATE_EVENT_FOCUS, .focus.focused = focused}, NULL, 0); + yasl_private_surface_notify_state(&s->base, s->base.state); break; } case PropertyNotify: @@ -1674,13 +2215,13 @@ static void dispatch(struct yasl_display *base) break; } case MotionNotify: case LeaveNotify: case EnterNotify: case ButtonPress: case ButtonRelease: { - struct yasl_event ev = { - .pointer = {.device = YASL_PTR_DEV_MOUSE}, + struct yasl_private_event ev = { + .pointer = {.device = YASL_PRIVATE_PTR_DEV_MOUSE}, }; unsigned state; if (event.type == MotionNotify) { - ev.pointer.pos = (struct yasl_vector2i){event.xmotion.x, event.xmotion.y}; - ev.type = YASL_EVENT_POINTER_MOTION; + ev.pointer.pos = (struct yasl_private_vector2i){event.xmotion.x, event.xmotion.y}; + ev.type = YASL_PRIVATE_EVENT_POINTER_MOTION; state = event.xmotion.state; if (d->drag.active) { if (d->drag.carried) @@ -1689,38 +2230,41 @@ static void dispatch(struct yasl_display *base) XMoveWindow(d->native, d->drag.icon_window, event.xmotion.x_root - d->drag.hotspot.x, event.xmotion.y_root - d->drag.hotspot.y); - struct yasl_vector2i hpos = ev.pointer.pos; + struct yasl_private_vector2i hpos = ev.pointer.pos; struct x11_surface *hsurf = d->native ? surface_at_root_pos(d, event.xmotion.x_root, event.xmotion.y_root, &hpos) : s; drag_set_hover(d, hsurf, hpos); } } else if (event.type == EnterNotify) { - ev.pointer.pos = (struct yasl_vector2i){event.xcrossing.x, event.xcrossing.y}; - ev.type = YASL_EVENT_POINTER_MOTION; + ev.pointer.pos = (struct yasl_private_vector2i){event.xcrossing.x, event.xcrossing.y}; + ev.type = YASL_PRIVATE_EVENT_POINTER_MOTION; state = event.xcrossing.state; if (d->drag.active) { - struct yasl_vector2i hpos = ev.pointer.pos; + struct yasl_private_vector2i hpos = ev.pointer.pos; struct x11_surface *hsurf = d->native ? surface_at_root_pos(d, event.xcrossing.x_root, event.xcrossing.y_root, &hpos) : s; drag_set_hover(d, hsurf, hpos); } } else if (event.type == LeaveNotify) { - ev.pointer.pos = (struct yasl_vector2i){event.xcrossing.x, event.xcrossing.y}; - ev.type = YASL_EVENT_POINTER_LEAVE; + ev.pointer.pos = (struct yasl_private_vector2i){event.xcrossing.x, event.xcrossing.y}; + ev.type = YASL_PRIVATE_EVENT_POINTER_LEAVE; state = event.xcrossing.state; if (d->drag.active) drag_leave_hover(d, s, ev.pointer.pos); } else { - ev.pointer.pos = (struct yasl_vector2i){event.xbutton.x, event.xbutton.y}; + ev.pointer.pos = (struct yasl_private_vector2i){event.xbutton.x, event.xbutton.y}; state = event.xbutton.state; unsigned button = event.xbutton.button; if (button >= 4 && button <= 7) { if (event.type == ButtonRelease) break; - ev.type = YASL_EVENT_POINTER_SCROLL; + ev.type = YASL_PRIVATE_EVENT_POINTER_SCROLL; if (button <= 5) ev.pointer.scroll.y = button == 4 ? -1 : 1; else ev.pointer.scroll.x = button == 6 ? -1 : 1; + ev.pointer.scroll_valid = 1u; + ev.pointer.scroll_source = 1u; + ev.pointer.scroll_phase = 2u; } else { - ev.type = event.type == ButtonPress ? YASL_EVENT_POINTER_DOWN : YASL_EVENT_POINTER_UP; + ev.type = event.type == ButtonPress ? YASL_PRIVATE_EVENT_POINTER_DOWN : YASL_PRIVATE_EVENT_POINTER_UP; ev.pointer.button = pointer_button(button); if (event.type == ButtonRelease && d->drag.active) drag_drop_hover(d); @@ -1735,13 +2279,13 @@ static void dispatch(struct yasl_display *base) leave(d); } -static bool present(struct yasl_surface *base, const struct yasl_framebuffer *fb, - const struct yasl_damage_list *damage) +static bool present(struct yasl_surface *base, const struct yasl_private_framebuffer *fb, + const struct yasl_private_damage_list *damage) { struct x11_surface *s = (struct x11_surface *)base; if (s->destroyed || !s->drawing || s->presented || !fb || !damage || fb->pixels != s->pixels || fb->size.x != s->size.x || fb->size.y != s->size.y || - damage->n < -1 || damage->n > YASL_DAMAGE_MAX) return false; + damage->n < -1 || damage->n > YASL_PRIVATE_DAMAGE_MAX) return false; struct x11_display *d = (struct x11_display *)base->display; XImage *im = s->image; if (s->shm_active) { @@ -1749,7 +2293,7 @@ static bool present(struct yasl_surface *base, const struct yasl_framebuffer *fb XShmPutImage(d->native, s->window, s->gc, im, 0, 0, 0, 0, (unsigned)s->size.x, (unsigned)s->size.y, False); else for (int i = 0; i < damage->n; i++) { - struct yasl_rect2i r = damage->rects[i]; + struct yasl_private_rect2i r = damage->rects[i]; int64_t right = (int64_t)r.x + r.width, bottom = (int64_t)r.y + r.height; int x = r.x > 0 ? r.x : 0, y = r.y > 0 ? r.y : 0; int w = (int)(right < s->size.x ? right : s->size.x) - x; @@ -1758,7 +2302,7 @@ static bool present(struct yasl_surface *base, const struct yasl_framebuffer *fb XShmPutImage(d->native, s->window, s->gc, im, x, y, x, y, (unsigned)w, (unsigned)h, False); } s->presented = true; - yasl_clock_presented(&s->clock, yasl_loop_now_ns(), false); + yasl_private_clock_presented(&s->clock, yasl_private_loop_now_ns(), false); return true; } bool direct = im->bits_per_pixel == 32 && im->red_mask == 0xff0000 && @@ -1769,7 +2313,7 @@ static bool present(struct yasl_surface *base, const struct yasl_framebuffer *fb y_start = s->size.y; y_end = 0; x_start = s->size.x; x_end = 0; for (int i = 0; i < damage->n; i++) { - struct yasl_rect2i r = damage->rects[i]; + struct yasl_private_rect2i r = damage->rects[i]; int y0 = r.y > 0 ? r.y : 0; int y1 = (r.y + r.height < s->size.y) ? r.y + r.height : s->size.y; int x0 = r.x > 0 ? r.x : 0; @@ -1798,7 +2342,7 @@ static bool present(struct yasl_surface *base, const struct yasl_framebuffer *fb XPutImage(d->native, s->window, s->gc, im, 0, 0, 0, 0, (unsigned)s->size.x, (unsigned)s->size.y); else for (int i = 0; i < damage->n; i++) { - struct yasl_rect2i r = damage->rects[i]; + struct yasl_private_rect2i r = damage->rects[i]; int64_t right = (int64_t)r.x + r.width, bottom = (int64_t)r.y + r.height; int x = r.x > 0 ? r.x : 0, y = r.y > 0 ? r.y : 0; int w = (int)(right < s->size.x ? right : s->size.x) - x; @@ -1807,27 +2351,33 @@ static bool present(struct yasl_surface *base, const struct yasl_framebuffer *fb XPutImage(d->native, s->window, s->gc, im, x, y, x, y, (unsigned)w, (unsigned)h); } s->presented = true; - yasl_clock_presented(&s->clock, yasl_loop_now_ns(), false); + yasl_private_clock_presented(&s->clock, yasl_private_loop_now_ns(), false); return true; } -static void flush(struct yasl_display *base) +static void flush(struct yasl_private_display *base) { struct x11_display *d = (struct x11_display *)base; d->depth++; for (struct x11_surface *s = d->surfaces, *next; s; s = next) { next = s->next; - if (s->destroyed || !s->want_draw || !s->cbs || !s->cbs->draw || s->drawing) continue; + if (s->destroyed || !s->want_draw || s->drawing) continue; + if (s->base.presenter != YASL_PRIVATE_PRESENTER_CPU) { + s->want_draw = false; + yasl_private_surface_notify_refresh(&s->base); + continue; + } + if (!s->cbs || !s->cbs->draw) continue; s->want_draw = false; s->drawing = true; s->presented = false; - s->damage = (struct yasl_damage_list){0}; - s->frame = (struct yasl_frame){ + s->damage = (struct yasl_private_damage_list){0}; + s->frame = (struct yasl_private_frame){ .surface = &s->base, - .target = {.generation = yasl_frame_begin(&s->base), + .target = {.generation = yasl_private_frame_begin(&s->base), .pixels = s->pixels, .size = s->size}, }; - yasl_surface_draw(&s->base, &s->frame); + yasl_private_surface_draw(&s->base, &s->frame); if (!s->destroyed) s->drawing = false; } XFlush(d->native); @@ -1837,11 +2387,11 @@ static void flush(struct yasl_display *base) static int ready(int fd, short events, void *ud) { (void)fd; - struct yasl_display *base = ud; + struct yasl_private_display *base = ud; struct x11_display *d = (struct x11_display *)base; if ((events & (POLLERR | POLLHUP)) && !d->lost) { d->lost = true; - yasl_display_notify_event(base, YASL_EVENT_DISPLAY_LOST); + yasl_private_display_notify_event(base, YASL_PRIVATE_EVENT_DISPLAY_LOST); return 0; } if ((events & POLLIN) && !d->lost) { @@ -1851,7 +2401,7 @@ static int ready(int fd, short events, void *ud) return 0; } -static int dispatch_timeout(struct yasl_display *base, int timeout_ms) +static int dispatch_timeout(struct yasl_private_display *base, int timeout_ms) { struct x11_display *d = (struct x11_display *)base; if (unlikely(!d || !d->native || d->lost)) return -1; @@ -1867,26 +2417,26 @@ static int dispatch_timeout(struct yasl_display *base, int timeout_ms) if (XEventsQueued(d->native, QueuedAlready) > 0) timeout_ms = 0; if (!XPending(d->native) && timeout_ms != 0) { - yasl_loop_step(d->loop, timeout_ms); + yasl_private_loop_step(d->loop, timeout_ms); dispatch(base); } else { - yasl_loop_step(d->loop, 0); + yasl_private_loop_step(d->loop, 0); dispatch(base); } } return 0; } -static struct yasl_display *display_open(struct yasl_loop *loop, - const struct yasl_display_spec *spec) +static struct yasl_private_display *display_open(struct yasl_private_loop *loop, + const struct yasl_private_display_spec *spec) { struct x11_display *d = calloc(1, sizeof(*d)); if (!d) return NULL; - d->base.ops = &yasl_x11_backend; - d->base.callbacks = spec && spec->cbs ? *spec->cbs : (struct yasl_display_cbs){0}; + d->base.ops = &yasl_private_x11_backend; + d->base.callbacks = spec && spec->cbs ? *spec->cbs : (struct yasl_private_display_cbs){0}; d->native = XOpenDisplay(NULL); if (!d->native) { free(d); errno = ENODEV; return NULL; } - XSetErrorHandler(yasl_x11_error_handler); + XSetErrorHandler(yasl_private_x11_error_handler); d->base.native_handle = d->native; d->screen = DefaultScreen(d->native); d->has_shm = XShmQueryExtension(d->native); @@ -1908,8 +2458,15 @@ static struct yasl_display *display_open(struct yasl_loop *loop, d->abs_pressure = XInternAtom(d->native, "Abs Pressure", False); d->abs_tilt_x = XInternAtom(d->native, "Abs Tilt X", False); d->abs_tilt_y = XInternAtom(d->native, "Abs Tilt Y", False); + d->abs_rotation = XInternAtom(d->native, "Abs Rotation", False); + d->abs_distance = XInternAtom(d->native, "Abs Distance", False); + d->abs_tangential = XInternAtom(d->native, "Abs TPressure", False); + d->abs_wheel = XInternAtom(d->native, "Abs Wheel", False); d->tool_type = XInternAtom(d->native, "Wacom Tool Type", False); d->eraser = XInternAtom(d->native, "ERASER", False); + d->brush = XInternAtom(d->native, "BRUSH", False); + d->airbrush = XInternAtom(d->native, "AIRBRUSH", False); + d->pencil = XInternAtom(d->native, "PENCIL", False); unsigned char bits[XIMaskLen(XI_LASTEVENT)] = {0}; XISetMask(bits, XI_HierarchyChanged); XISetMask(bits, XI_DeviceChanged); @@ -1917,7 +2474,7 @@ static struct yasl_display *display_open(struct yasl_loop *loop, XISelectEvents(d->native, RootWindow(d->native, d->screen), &mask, 1); } if (!loop) { - d->loop = yasl_loop_create(); + d->loop = yasl_private_loop_create(); d->owns_loop = true; } else { d->loop = loop; @@ -1926,7 +2483,7 @@ static struct yasl_display *display_open(struct yasl_loop *loop, d->app_id = strdup(spec && spec->app_id ? spec->app_id : "YASL"); d->base.app_id = d->app_id; if (!d->loop || !d->app_id) { - if (d->owns_loop) yasl_loop_destroy(d->loop); + if (d->owns_loop) yasl_private_loop_destroy(d->loop); free(d->app_id); XCloseDisplay(d->native); free(d); @@ -1941,10 +2498,11 @@ static struct yasl_display *display_open(struct yasl_loop *loop, ATOM(utf8, "UTF8_STRING"); ATOM(clipboard, "CLIPBOARD"); ATOM(targets, "TARGETS"); - ATOM(paste_property, "YASL_SELECTION"); + ATOM(paste_property, "YASL_PRIVATE_SELECTION"); ATOM(incr, "INCR"); ATOM(net_name, "_NET_WM_NAME"); ATOM(net_state, "_NET_WM_STATE"); + ATOM(motif_hints, "_MOTIF_WM_HINTS"); ATOM(fullscreen, "_NET_WM_STATE_FULLSCREEN"); ATOM(maximized_h, "_NET_WM_STATE_MAXIMIZED_HORZ"); ATOM(maximized_v, "_NET_WM_STATE_MAXIMIZED_VERT"); @@ -1961,13 +2519,13 @@ static struct yasl_display *display_open(struct yasl_loop *loop, ATOM(uri_list, "text/uri-list"); #undef ATOM if (d->loop) { - d->watch = yasl_loop_add_fd(d->loop, ConnectionNumber(d->native), POLLIN, ready, &d->base); + d->watch = yasl_private_loop_add_fd(d->loop, ConnectionNumber(d->native), POLLIN, ready, &d->base); if (d->watch < 0) { if (d->im) XCloseIM(d->im); XDestroyWindow(d->native, d->selection_window); XCloseDisplay(d->native); free(d->app_id); - if (d->owns_loop) yasl_loop_destroy(d->loop); + if (d->owns_loop) yasl_private_loop_destroy(d->loop); free(d); return NULL; } @@ -1975,21 +2533,24 @@ static struct yasl_display *display_open(struct yasl_loop *loop, return &d->base; } -static void display_close(struct yasl_display *base) +static void display_close(struct yasl_private_display *base) { struct x11_display *d = (struct x11_display *)base; assert(!d->surfaces && "destroy every surface before its display"); drag_cancel(base); cancel_pastes(d, NULL); d->pending_drop.active = false; - if (d->loop && d->watch > 0) yasl_loop_del_fd(d->loop, d->watch); + if (d->loop && d->watch > 0) yasl_private_loop_del_fd(d->loop, d->watch); if (d->owns_loop && d->loop) { - yasl_loop_destroy(d->loop); + yasl_private_loop_destroy(d->loop); d->loop = NULL; d->base.loop = NULL; } if (d->im) XCloseIM(d->im); clear_pointers(d); +#if YASL_HAS_EGL + if (d->egl_ready) eglTerminate(d->egl); +#endif for (size_t i = 0; i < sizeof(d->cursors) / sizeof(d->cursors[0]); i++) { if (d->cursors[i]) { XFreeCursor(d->native, d->cursors[i]); @@ -2004,16 +2565,16 @@ static void display_close(struct yasl_display *base) free(d); } -static uint32_t caps(const struct yasl_display *d) +static uint32_t caps(const struct yasl_private_display *d) { - return YASL_CAP_POPUPS | YASL_CAP_DRAG | YASL_CAP_DRAG_CARRY | YASL_CAP_PLACE_WINDOW | - YASL_CAP_PRIMARY_SELECTION | YASL_CAP_CSD | - (((const struct x11_display *)d)->xi2 ? YASL_CAP_TABLET : 0); + return YASL_PRIVATE_CAP_POPUPS | YASL_PRIVATE_CAP_DRAG | YASL_PRIVATE_CAP_DRAG_CARRY | YASL_PRIVATE_CAP_PLACE_WINDOW | + YASL_PRIVATE_CAP_PRIMARY_SELECTION | YASL_PRIVATE_CAP_CSD | YASL_PRIVATE_CAP_NATIVE_GRAPHICS | + (((const struct x11_display *)d)->xi2 ? YASL_PRIVATE_CAP_TABLET : 0); } -static bool copy_text(struct yasl_display *base, enum yasl_selection sel, const char *text) +static bool copy_text(struct yasl_private_display *base, enum yasl_private_selection sel, const char *text) { - if (!text || (sel != YASL_CLIPBOARD && sel != YASL_PRIMARY)) { + if (!text || (sel != YASL_PRIVATE_CLIPBOARD && sel != YASL_PRIVATE_PRIMARY)) { errno = EINVAL; return false; } @@ -2022,39 +2583,39 @@ static bool copy_text(struct yasl_display *base, enum yasl_selection sel, const if (!value) { errno = ENOMEM; return false; } free(d->selection[sel]); d->selection[sel] = value; - XSetSelectionOwner(d->native, sel == YASL_PRIMARY ? XA_PRIMARY : d->clipboard, + XSetSelectionOwner(d->native, sel == YASL_PRIVATE_PRIMARY ? XA_PRIMARY : d->clipboard, d->selection_window, CurrentTime); XFlush(d->native); return true; } -static void copy(struct yasl_display *base, enum yasl_selection sel, const char *text) +static void copy(struct yasl_private_display *base, enum yasl_private_selection sel, const char *text) { (void)copy_text(base, sel, text); } -static bool request_paste(struct yasl_display *base, enum yasl_selection sel, +static bool request_paste(struct yasl_private_display *base, enum yasl_private_selection sel, struct yasl_surface *target, uint64_t id) { - if (!target || (sel != YASL_CLIPBOARD && sel != YASL_PRIMARY)) { + if (!target || (sel != YASL_PRIVATE_CLIPBOARD && sel != YASL_PRIVATE_PRIMARY)) { errno = EINVAL; return false; } struct x11_display *d = (struct x11_display *)base; struct x11_surface *s = (struct x11_surface *)target; if (s->destroyed || target->display != base) { errno = EINVAL; return false; } - Atom selection = sel == YASL_PRIMARY ? XA_PRIMARY : d->clipboard; + Atom selection = sel == YASL_PRIVATE_PRIMARY ? XA_PRIMARY : d->clipboard; Window owner = XGetSelectionOwner(d->native, selection); if (owner == None) { - yasl_surface_complete_paste(target, id, YASL_TRANSFER_UNAVAILABLE, NULL, 0); + yasl_private_surface_complete_paste(target, id, YASL_PRIVATE_TRANSFER_UNAVAILABLE, NULL, 0); return true; } if (owner == d->selection_window) { const char *text = d->selection[sel]; if (text) - yasl_surface_complete_paste(target, id, YASL_TRANSFER_OK, text, strlen(text)); + yasl_private_surface_complete_paste(target, id, YASL_PRIVATE_TRANSFER_OK, text, strlen(text)); else - yasl_surface_complete_paste(target, id, YASL_TRANSFER_UNAVAILABLE, NULL, 0); + yasl_private_surface_complete_paste(target, id, YASL_PRIVATE_TRANSFER_UNAVAILABLE, NULL, 0); return true; } else { struct paste_request *r = calloc(1, sizeof(*r)); @@ -2067,7 +2628,7 @@ static bool request_paste(struct yasl_display *base, enum yasl_selection sel, r->selection = selection; r->id = id; errno = 0; - r->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, + r->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, paste_timeout, r); if (!r->timeout) { XDestroyWindow(d->native, r->window); @@ -2084,17 +2645,17 @@ static bool request_paste(struct yasl_display *base, enum yasl_selection sel, } } -static void paste(struct yasl_display *base, enum yasl_selection sel, +static void paste(struct yasl_private_display *base, enum yasl_private_selection sel, struct yasl_surface *target) { (void)request_paste(base, sel, target, 0); } -static bool input_poll(struct yasl_surface *base, struct yasl_event *event) +static bool input_poll(struct yasl_surface *base, struct yasl_private_event *event) { struct x11_surface *s = (struct x11_surface *)base; if (s->destroyed) return false; - if (base->display->events) return yasl_events_poll_surface(base, event); + if (base->display->events) return yasl_private_events_poll_surface(base, event); return input_queue_pop(&s->input, event); } @@ -2110,47 +2671,47 @@ static void schedule_redraw(struct yasl_surface *base) s->want_draw = true; struct x11_display *d = (struct x11_display *)base->display; if (d && d->loop) - yasl_loop_kick(d->loop); + yasl_private_loop_kick(d->loop); } } -static struct yasl_display *surface_display(const struct yasl_surface *s) { return s->display; } -static struct yasl_geometry geometry(const struct yasl_surface *s) +static struct yasl_private_display *surface_display(const struct yasl_surface *s) { return s->display; } +static struct yasl_private_geometry geometry(const struct yasl_surface *s) { - return (struct yasl_geometry){((const struct x11_surface *)s)->size, YASL_SCALE_UNIT}; + return (struct yasl_private_geometry){((const struct x11_surface *)s)->size, YASL_PRIVATE_SCALE_UNIT}; } -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 int mods(const struct yasl_private_display *d) { return (int)((const struct x11_display *)d)->mods; } +static void *native_display(struct yasl_private_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; } -static struct yasl_timing timing(const struct yasl_surface *base) +static struct yasl_private_timing timing(const struct yasl_surface *base) { const struct x11_surface *s = (const struct x11_surface *)base; - struct yasl_timing t = {.refresh_ns = s->clock.refresh_ns, + struct yasl_private_timing t = {.refresh_ns = s->clock.refresh_ns, .last_present_ns = s->clock.last_present_ns, - .next_present_ns = yasl_clock_next_present_ns(&s->clock), + .next_present_ns = yasl_private_clock_next_present_ns(&s->clock), .source = s->clock.source}; - yasl_clock_resize_stats(&s->clock, &t.resize); + yasl_private_clock_resize_stats(&s->clock, &t.resize); return t; } static uint64_t frame_time(struct yasl_surface *s, uint64_t now) { - return yasl_clock_frame_timestamp_ns(&((struct x11_surface *)s)->clock, now); + return yasl_private_clock_frame_timestamp_ns(&((struct x11_surface *)s)->clock, now); } -static void cursor(struct yasl_surface *base, enum yasl_cursor cursor) +static void cursor(struct yasl_surface *base, enum yasl_private_cursor cursor) { static const unsigned shapes[] = {XC_left_ptr, XC_xterm, XC_hand2, XC_crosshair, XC_fleur, XC_bottom_right_corner, XC_bottom_left_corner, XC_sb_h_double_arrow, XC_sb_v_double_arrow}; struct x11_surface *s = (struct x11_surface *)base; - if (s->destroyed || (unsigned)cursor > YASL_CURSOR_HIDDEN) return; + if (s->destroyed || (unsigned)cursor > YASL_PRIVATE_CURSOR_HIDDEN) return; struct x11_display *d = (struct x11_display *)base->display; if (!d->native) return; if (!d->cursors[cursor]) { - if (cursor == YASL_CURSOR_HIDDEN) { + if (cursor == YASL_PRIVATE_CURSOR_HIDDEN) { char bits[] = {0}; Pixmap pixmap = XCreateBitmapFromData(d->native, s->window, bits, 1, 1); XColor color = {0}; @@ -2164,7 +2725,7 @@ static void cursor(struct yasl_surface *base, enum yasl_cursor cursor) XDefineCursor(d->native, s->window, d->cursors[cursor]); } -static bool place(struct yasl_surface *base, struct yasl_vector2i at) +static bool place(struct yasl_surface *base, struct yasl_private_vector2i at) { struct x11_surface *s = (struct x11_surface *)base; struct x11_display *d = (struct x11_display *)base->display; @@ -2227,7 +2788,7 @@ static bool xsettings_int(const unsigned char *p, size_t len, const char *name, return false; } -static int double_click_ms(const struct yasl_display *base) +static int double_click_ms(const struct yasl_private_display *base) { const struct x11_display *d = (const struct x11_display *)base; if (d->native) { @@ -2255,12 +2816,12 @@ static int double_click_ms(const struct yasl_display *base) } static bool read_pixels(struct yasl_surface *base, uint32_t *out, size_t cap, - struct yasl_vector2i *size) + struct yasl_private_vector2i *size) { struct x11_surface *s = (struct x11_surface *)base; /* presented stays set from the last draw until the next one starts. */ if (s->destroyed || s->drawing || !s->presented || !s->pixels) return false; - return yasl_copy_frame(s->pixels, s->size, out, cap, size); + return yasl_private_copy_frame(s->pixels, s->size, out, cap, size); } static bool bell(struct yasl_surface *base) @@ -2275,16 +2836,16 @@ static bool bell(struct yasl_surface *base) return true; } -static void drag_end(struct x11_display *d, enum yasl_drag_end how) +static void drag_end(struct x11_display *d, enum yasl_private_drag_end how) { if (!d || !d->drag.active) return; - struct yasl_drag_listener l = d->drag.listener; + struct yasl_private_drag_listener l = d->drag.listener; void *ud = d->drag.ud; struct x11_surface *hover = d->drag.hover; - struct yasl_vector2i hover_pos = d->drag.hover_pos; + struct yasl_private_vector2i hover_pos = d->drag.hover_pos; d->drag.active = false; - d->drag.listener = (struct yasl_drag_listener){0}; + d->drag.listener = (struct yasl_private_drag_listener){0}; d->drag.hover = NULL; d->drag.origin = NULL; d->drag.carried = NULL; @@ -2299,19 +2860,19 @@ static void drag_end(struct x11_display *d, enum yasl_drag_end how) } if (hover && !hover->destroyed) { - if (how == YASL_DRAG_DROPPED) - yasl_surface_notify_drag(&hover->base, YASL_DRAG_DROP, hover_pos); + if (how == YASL_PRIVATE_DRAG_DROPPED) + yasl_private_surface_notify_drag(&hover->base, YASL_PRIVATE_DRAG_DROP, hover_pos); else - yasl_surface_notify_drag(&hover->base, YASL_DRAG_LEAVE, hover_pos); + yasl_private_surface_notify_drag(&hover->base, YASL_PRIVATE_DRAG_LEAVE, hover_pos); } if (l.end) l.end(ud, how); } -static bool drag_start(struct yasl_display *base, struct yasl_surface *origin, - const struct yasl_drag_image *image, - const struct yasl_drag_listener *l, void *ud) +static bool drag_start(struct yasl_private_display *base, struct yasl_surface *origin, + const struct yasl_private_drag_image *image, + const struct yasl_private_drag_listener *l, void *ud) { struct x11_display *d = (struct x11_display *)base; if (!d || !origin || d->drag.active || !l) @@ -2383,12 +2944,12 @@ static bool drag_start(struct yasl_display *base, struct yasl_surface *origin, return true; } -static void drag_cancel(struct yasl_display *base) +static void drag_cancel(struct yasl_private_display *base) { - drag_end((struct x11_display *)base, YASL_DRAG_CANCELLED); + drag_end((struct x11_display *)base, YASL_PRIVATE_DRAG_CANCELLED); } -static bool drag_carry(struct yasl_surface *base, struct yasl_vector2i offset) +static bool drag_carry(struct yasl_surface *base, struct yasl_private_vector2i offset) { struct x11_display *d = (struct x11_display *)base->display; if (!d || !d->drag.active) @@ -2420,7 +2981,7 @@ static bool interactive_move(struct yasl_surface *base) return true; } -static bool interactive_resize(struct yasl_surface *base, enum yasl_resize_edge edge) +static bool interactive_resize(struct yasl_surface *base, enum yasl_private_resize_edge edge) { struct x11_surface *s = (struct x11_surface *)base; struct x11_display *d = (struct x11_display *)base->display; @@ -2432,14 +2993,14 @@ static bool interactive_resize(struct yasl_surface *base, enum yasl_resize_edge int dir; switch (edge) { - case YASL_EDGE_TOP_LEFT: dir = 0; break; - case YASL_EDGE_TOP: dir = 1; break; - case YASL_EDGE_TOP_RIGHT: dir = 2; break; - case YASL_EDGE_RIGHT: dir = 3; break; - case YASL_EDGE_BOTTOM_RIGHT: dir = 4; break; - case YASL_EDGE_BOTTOM: dir = 5; break; - case YASL_EDGE_BOTTOM_LEFT: dir = 6; break; - case YASL_EDGE_LEFT: dir = 7; break; + case YASL_PRIVATE_EDGE_TOP_LEFT: dir = 0; break; + case YASL_PRIVATE_EDGE_TOP: dir = 1; break; + case YASL_PRIVATE_EDGE_TOP_RIGHT: dir = 2; break; + case YASL_PRIVATE_EDGE_RIGHT: dir = 3; break; + case YASL_PRIVATE_EDGE_BOTTOM_RIGHT: dir = 4; break; + case YASL_PRIVATE_EDGE_BOTTOM: dir = 5; break; + case YASL_PRIVATE_EDGE_BOTTOM_LEFT: dir = 6; break; + case YASL_PRIVATE_EDGE_LEFT: dir = 7; break; default: return false; } @@ -2448,7 +3009,7 @@ static bool interactive_resize(struct yasl_surface *base, enum yasl_resize_edge return true; } -static bool show_window_menu(struct yasl_surface *base, struct yasl_vector2i pos) +static bool show_window_menu(struct yasl_surface *base, struct yasl_private_vector2i pos) { struct x11_surface *s = (struct x11_surface *)base; struct x11_display *d = (struct x11_display *)base->display; @@ -2458,7 +3019,18 @@ static bool show_window_menu(struct yasl_surface *base, struct yasl_vector2i pos return true; } -static void set_ime_caret(struct yasl_surface *base, struct yasl_rect2i caret) +static bool surface_update_text_input(struct yasl_surface *base) +{ + struct x11_surface *s = (struct x11_surface *)base; + if (!s || s->destroyed || !base->display) return false; + if (s->ic) { + if (s->focused && base->text_input_active) XSetICFocus(s->ic); + else XUnsetICFocus(s->ic); + } + return true; +} + +static void set_ime_caret(struct yasl_surface *base, struct yasl_private_rect2i caret) { struct x11_surface *s = (struct x11_surface *)base; if (!s) return; @@ -2489,8 +3061,15 @@ static bool ime_preedit_cursor(const struct yasl_surface *base, int *begin, int return true; } -const struct yasl_backend_ops yasl_x11_backend = { - .type = YASL_BACKEND_X11, +const struct yasl_private_backend_ops yasl_private_x11_backend = { +#if YASL_HAS_EGL + .surface_create_gl = create_gl_surface, + .gl_make_current = gl_make_current, + .gl_get_proc_address = gl_get_proc_address, + .gl_swap_interval = gl_swap_interval, + .gl_swap_buffers = gl_swap_buffers, +#endif + .type = YASL_PRIVATE_BACKEND_X11, .display_open = display_open, .display_close = display_close, .display_caps = caps, @@ -2530,6 +3109,7 @@ const struct yasl_backend_ops yasl_x11_backend = { .surface_start_interactive_move = interactive_move, .surface_start_interactive_resize = interactive_resize, .surface_show_window_menu = show_window_menu, + .surface_update_text_input = surface_update_text_input, .surface_set_ime_caret = set_ime_caret, .surface_ime_preedit = ime_preedit, .surface_ime_preedit_cursor = ime_preedit_cursor, diff --git a/tests/benchmark.c b/tests/benchmark.c index 1cea614..8fa8066 100644 --- a/tests/benchmark.c +++ b/tests/benchmark.c @@ -7,12 +7,12 @@ #include <time.h> #include <limits.h> -#include "yasl/yasl.h" -#include "yasl/loop.h" -#include "yasl/timing.h" +#include "common/types.h" +#include "common/private_loop.h" +#include "common/private_timing.h" #include "common/input.h" #if YASL_HAS_WAYLAND -#include "yasl/native/wayland.h" +#include "common/private_native_wayland.h" #endif static inline uint64_t get_time_ns(void) { @@ -48,28 +48,28 @@ static void print_bench_skipped(const char *name, const char *reason) { static void bench_surface_lookup(void) { const int num_surfaces = 16; const int lookups = 10000000; - struct yasl_display_spec spec = { + struct yasl_private_display_spec spec = { .app_id = "org.yasl.benchmark", - .backend = YASL_BACKEND_WAYLAND, + .backend = YASL_PRIVATE_BACKEND_WAYLAND, }; - struct yasl_display *d = yasl_display_open(NULL, &spec); + struct yasl_private_display *d = yasl_private_display_open(NULL, &spec); struct yasl_surface *surfaces[num_surfaces]; memset(surfaces, 0, sizeof(surfaces)); if (d) { for (int i = 0; i < num_surfaces; i++) { - struct yasl_window_spec sspec = { + struct yasl_private_window_spec sspec = { .title = "Benchmark", .size = { .x = 320, .y = 240 }, }; - surfaces[i] = yasl_surface_create_toplevel(d, &sspec); + surfaces[i] = yasl_private_surface_create_toplevel(d, &sspec); } } uint64_t t0 = get_time_ns(); uintptr_t sum = 0; for (int i = 0; i < lookups; i++) { - struct wl_surface *found = yasl_wayland_surface_get_wl_surface(surfaces[i % num_surfaces]); + struct wl_surface *found = yasl_private_wayland_surface_get_wl_surface(surfaces[i % num_surfaces]); sum += (uintptr_t)found; } uint64_t t1 = get_time_ns(); @@ -82,35 +82,35 @@ static void bench_surface_lookup(void) { if (d) { for (int i = 0; i < num_surfaces; i++) { - if (surfaces[i]) yasl_surface_destroy(surfaces[i]); + if (surfaces[i]) yasl_private_surface_destroy(surfaces[i]); } - yasl_display_close(d); + yasl_private_display_close(d); } } /* Benchmark 2: Public Surface Geometry & Refresh Timing */ static void bench_output_follow(void) { const int iters = 10000000; - struct yasl_display_spec spec = { + struct yasl_private_display_spec spec = { .app_id = "org.yasl.benchmark", - .backend = YASL_BACKEND_WAYLAND, + .backend = YASL_PRIVATE_BACKEND_WAYLAND, }; - struct yasl_display *d = yasl_display_open(NULL, &spec); + struct yasl_private_display *d = yasl_private_display_open(NULL, &spec); struct yasl_surface *s = NULL; if (d) { - struct yasl_window_spec sspec = { + struct yasl_private_window_spec sspec = { .title = "Benchmark", .size = { .x = 320, .y = 240 }, }; - s = yasl_surface_create_toplevel(d, &sspec); + s = yasl_private_surface_create_toplevel(d, &sspec); } uint64_t t0 = get_time_ns(); uint64_t sum = 0; for (int i = 0; i < iters; i++) { - struct yasl_geometry geom = yasl_surface_geometry(s); - struct yasl_timing timing = yasl_surface_timing(s); - int dev = yasl_scale_to_device(320.0, geom.scale_120); + struct yasl_private_geometry geom = yasl_private_surface_geometry(s); + struct yasl_private_timing timing = yasl_private_surface_timing(s); + int dev = yasl_private_scale_to_device(320.0, geom.scale_120); sum += (uint64_t)dev + timing.refresh_ns; } uint64_t t1 = get_time_ns(); @@ -121,8 +121,8 @@ static void bench_output_follow(void) { iters, (unsigned long long)sum); print_bench_row("[2] surface_geometry_timing", per_cycle, ms, details); - if (s) yasl_surface_destroy(s); - if (d) yasl_display_close(d); + if (s) yasl_private_surface_destroy(s); + if (d) yasl_private_display_close(d); } #endif @@ -135,8 +135,8 @@ static void bench_input_queue_resize(void) { for (int b = 0; b < batches; b++) { struct input_queue q = {0}; for (int i = 0; i < size; i++) { - struct yasl_event ev = { - .type = YASL_EVENT_POINTER_MOTION, + struct yasl_private_event ev = { + .type = YASL_PRIVATE_EVENT_POINTER_MOTION, .pointer = { .pos = { .x = i, .y = b } }, }; input_queue_push(&q, &ev); @@ -160,13 +160,13 @@ static void bench_input_queue_push_pop(void) { uint64_t t0 = get_time_ns(); for (int r = 0; r < rounds; r++) { for (int i = 0; i < events_per_round; i++) { - struct yasl_event ev = { - .type = YASL_EVENT_POINTER_DOWN, - .pointer = { .pos = { .x = i, .y = r }, .button = YASL_BUTTON_MIDDLE }, + struct yasl_private_event ev = { + .type = YASL_PRIVATE_EVENT_POINTER_DOWN, + .pointer = { .pos = { .x = i, .y = r }, .button = YASL_PRIVATE_BUTTON_MIDDLE }, }; input_queue_push(&q, &ev); } - struct yasl_event out; + struct yasl_private_event out; while (input_queue_pop(&q, &out)) { /* consume */ } @@ -181,22 +181,22 @@ static void bench_input_queue_push_pop(void) { } /* Benchmark 5: Event Loop Timers Add & Delete */ -static uint64_t timer_dummy_cb(void *ud, yasl_timer_id id, uint64_t interval_ns) { +static uint64_t timer_dummy_cb(void *ud, yasl_private_timer_id id, uint64_t interval_ns) { (void)ud; (void)id; (void)interval_ns; return 0; } static void bench_timers(void) { const int iters = 2000000; - struct yasl_loop *loop = yasl_loop_create(); + struct yasl_private_loop *loop = yasl_private_loop_create(); uint64_t t0 = get_time_ns(); for (int i = 0; i < iters; i++) { - yasl_timer_id id = yasl_loop_add_timer(loop, 1000000, timer_dummy_cb, NULL); - yasl_loop_del_timer(loop, id); + yasl_private_timer_id id = yasl_private_loop_add_timer(loop, 1000000, timer_dummy_cb, NULL); + yasl_private_loop_del_timer(loop, id); } uint64_t t1 = get_time_ns(); - yasl_loop_destroy(loop); + yasl_private_loop_destroy(loop); double ms = (double)(t1 - t0) / 1e6; double per_cycle = (double)(t1 - t0) / iters; char details[128]; @@ -207,30 +207,30 @@ static void bench_timers(void) { /* Benchmark 6: Window Cold Create & Teardown */ static void bench_window_cold_create(void) { const int iters = 200; - struct yasl_display_spec dspec = { .app_id = "org.yasl.bench.cold" }; - struct yasl_window_spec sspec = { + struct yasl_private_display_spec dspec = { .app_id = "org.yasl.bench.cold" }; + struct yasl_private_window_spec sspec = { .title = "Benchmark Cold", .size = { .x = 320, .y = 240 }, }; - struct yasl_display *probe = yasl_display_open(NULL, &dspec); + struct yasl_private_display *probe = yasl_private_display_open(NULL, &dspec); if (!probe) { print_bench_skipped("[6] window_cold_create", "no display server"); return; } - yasl_display_close(probe); + yasl_private_display_close(probe); uint64_t t0 = get_time_ns(); int count = 0; for (int i = 0; i < iters; i++) { - struct yasl_display *d = yasl_display_open(NULL, &dspec); + struct yasl_private_display *d = yasl_private_display_open(NULL, &dspec); if (d) { - struct yasl_surface *s = yasl_surface_create_toplevel(d, &sspec); + struct yasl_surface *s = yasl_private_surface_create_toplevel(d, &sspec); if (s) { - yasl_surface_destroy(s); + yasl_private_surface_destroy(s); count++; } - yasl_display_close(d); + yasl_private_display_close(d); } } uint64_t t1 = get_time_ns(); @@ -244,14 +244,14 @@ static void bench_window_cold_create(void) { /* Benchmark 7: Window Warm Create */ static void bench_window_warm_create(void) { const int iters = 2000; - struct yasl_display_spec dspec = { .app_id = "org.yasl.bench.warm" }; - struct yasl_display *d = yasl_display_open(NULL, &dspec); + struct yasl_private_display_spec dspec = { .app_id = "org.yasl.bench.warm" }; + struct yasl_private_display *d = yasl_private_display_open(NULL, &dspec); if (!d) { print_bench_skipped("[7] window_warm_create", "no display server"); return; } - struct yasl_window_spec sspec = { + struct yasl_private_window_spec sspec = { .title = "Benchmark Warm", .size = { .x = 320, .y = 240 }, }; @@ -259,14 +259,14 @@ static void bench_window_warm_create(void) { uint64_t t0 = get_time_ns(); int count = 0; for (int i = 0; i < iters; i++) { - struct yasl_surface *s = yasl_surface_create_toplevel(d, &sspec); + struct yasl_surface *s = yasl_private_surface_create_toplevel(d, &sspec); if (s) { - yasl_surface_destroy(s); + yasl_private_surface_destroy(s); count++; } } uint64_t t1 = get_time_ns(); - yasl_display_close(d); + yasl_private_display_close(d); double ms = (double)(t1 - t0) / 1e6; double per_cycle = count ? (double)(t1 - t0) / count : 0.0; char details[128]; @@ -277,14 +277,14 @@ static void bench_window_warm_create(void) { /* Benchmark 8: Window Shutdown (Single) */ static void bench_window_shutdown_single(void) { const int iters = 2000; - struct yasl_display_spec dspec = { .app_id = "org.yasl.bench.shutdown_single" }; - struct yasl_display *d = yasl_display_open(NULL, &dspec); + struct yasl_private_display_spec dspec = { .app_id = "org.yasl.bench.shutdown_single" }; + struct yasl_private_display *d = yasl_private_display_open(NULL, &dspec); if (!d) { print_bench_skipped("[8] window_shutdown_single", "no display server"); return; } - struct yasl_window_spec sspec = { + struct yasl_private_window_spec sspec = { .title = "Benchmark Shutdown Single", .size = { .x = 320, .y = 240 }, }; @@ -292,16 +292,16 @@ static void bench_window_shutdown_single(void) { uint64_t destroy_time_ns = 0; int count = 0; for (int i = 0; i < iters; i++) { - struct yasl_surface *s = yasl_surface_create_toplevel(d, &sspec); + struct yasl_surface *s = yasl_private_surface_create_toplevel(d, &sspec); if (s) { uint64_t t0 = get_time_ns(); - yasl_surface_destroy(s); + yasl_private_surface_destroy(s); uint64_t t1 = get_time_ns(); destroy_time_ns += (t1 - t0); count++; } } - yasl_display_close(d); + yasl_private_display_close(d); double ms = (double)destroy_time_ns / 1e6; double per_cycle = count ? (double)destroy_time_ns / count : 0.0; char details[128]; @@ -313,14 +313,14 @@ static void bench_window_shutdown_single(void) { static void bench_window_shutdown_all(void) { const int batches = 50; const int batch_size = 64; - struct yasl_display_spec dspec = { .app_id = "org.yasl.bench.shutdown_all" }; - struct yasl_display *d = yasl_display_open(NULL, &dspec); + struct yasl_private_display_spec dspec = { .app_id = "org.yasl.bench.shutdown_all" }; + struct yasl_private_display *d = yasl_private_display_open(NULL, &dspec); if (!d) { print_bench_skipped("[9] window_shutdown_all", "no display server"); return; } - struct yasl_window_spec sspec = { + struct yasl_private_window_spec sspec = { .title = "Benchmark Bulk Window", .size = { .x = 320, .y = 240 }, }; @@ -331,12 +331,12 @@ static void bench_window_shutdown_all(void) { for (int b = 0; b < batches; b++) { for (int i = 0; i < batch_size; i++) { - surfaces[i] = yasl_surface_create_toplevel(d, &sspec); + surfaces[i] = yasl_private_surface_create_toplevel(d, &sspec); } uint64_t t0 = get_time_ns(); for (int i = 0; i < batch_size; i++) { if (surfaces[i]) { - yasl_surface_destroy(surfaces[i]); + yasl_private_surface_destroy(surfaces[i]); surfaces[i] = NULL; total_destroyed++; } @@ -344,7 +344,7 @@ static void bench_window_shutdown_all(void) { uint64_t t1 = get_time_ns(); destroy_time_ns += (t1 - t0); } - yasl_display_close(d); + yasl_private_display_close(d); double ms = (double)destroy_time_ns / 1e6; double per_cycle = total_destroyed ? (double)destroy_time_ns / total_destroyed : 0.0; double per_batch_ns = batches ? (double)destroy_time_ns / batches : 0.0; @@ -359,20 +359,20 @@ static void bench_window_shutdown_all(void) { /* Benchmark 10: Window Titlebar Update */ static void bench_window_title_update(void) { const int iters = 200000; - struct yasl_display_spec dspec = { .app_id = "org.yasl.bench.title" }; - struct yasl_display *d = yasl_display_open(NULL, &dspec); + struct yasl_private_display_spec dspec = { .app_id = "org.yasl.bench.title" }; + struct yasl_private_display *d = yasl_private_display_open(NULL, &dspec); if (!d) { print_bench_skipped("[10] window_titlebar_update", "no display server"); return; } - struct yasl_window_spec sspec = { + struct yasl_private_window_spec sspec = { .title = "Initial Title", .size = { .x = 320, .y = 240 }, }; - struct yasl_surface *s = yasl_surface_create_toplevel(d, &sspec); + struct yasl_surface *s = yasl_private_surface_create_toplevel(d, &sspec); if (!s) { - yasl_display_close(d); + yasl_private_display_close(d); print_bench_skipped("[10] window_titlebar_update", "failed to create surface"); return; } @@ -386,11 +386,11 @@ static void bench_window_title_update(void) { uint64_t t0 = get_time_ns(); for (int i = 0; i < iters; i++) { - yasl_surface_set_title(s, titles[i & 3]); + yasl_private_surface_set_title(s, titles[i & 3]); } uint64_t t1 = get_time_ns(); - yasl_surface_destroy(s); - yasl_display_close(d); + yasl_private_surface_destroy(s); + yasl_private_display_close(d); double ms = (double)(t1 - t0) / 1e6; double per_cycle = (double)(t1 - t0) / iters; char details[128]; @@ -401,14 +401,14 @@ static void bench_window_title_update(void) { /* Benchmark 11: Window Resize / Dimension Reallocation */ static void bench_window_resize(void) { const int iters = 2000; - struct yasl_display_spec dspec = { .app_id = "org.yasl.bench.resize" }; - struct yasl_display *d = yasl_display_open(NULL, &dspec); + struct yasl_private_display_spec dspec = { .app_id = "org.yasl.bench.resize" }; + struct yasl_private_display *d = yasl_private_display_open(NULL, &dspec); if (!d) { print_bench_skipped("[11] window_resize_cycle", "no display server"); return; } - static const struct yasl_vector2i sizes[] = { + static const struct yasl_private_vector2i sizes[] = { { 320, 240 }, { 640, 480 }, { 800, 600 }, @@ -421,20 +421,20 @@ static void bench_window_resize(void) { uint64_t t0 = get_time_ns(); int count = 0; for (int i = 0; i < iters; i++) { - struct yasl_window_spec sspec = { + struct yasl_private_window_spec sspec = { .title = "Resize Bench", .size = sizes[i % num_sizes], }; - struct yasl_surface *s = yasl_surface_create_toplevel(d, &sspec); + struct yasl_surface *s = yasl_private_surface_create_toplevel(d, &sspec); if (s) { - struct yasl_geometry geom = yasl_surface_geometry(s); + struct yasl_private_geometry geom = yasl_private_surface_geometry(s); (void)geom; - yasl_surface_destroy(s); + yasl_private_surface_destroy(s); count++; } } uint64_t t1 = get_time_ns(); - yasl_display_close(d); + yasl_private_display_close(d); double ms = (double)(t1 - t0) / 1e6; double per_cycle = count ? (double)(t1 - t0) / count : 0.0; char details[128]; diff --git a/tests/cocoa/test_cursor.m b/tests/cocoa/test_cursor.m index 4054d59..4c961df 100644 --- a/tests/cocoa/test_cursor.m +++ b/tests/cocoa/test_cursor.m @@ -1,5 +1,5 @@ #import <AppKit/AppKit.h> -#include <yasl/native.h> +#include <common/private_native.h> #include <assert.h> static void assert_transparent(NSCursor *cursor) @@ -15,18 +15,18 @@ static void assert_transparent(NSCursor *cursor) int main(void) { @autoreleasepool { - struct yasl_display *display = yasl_display_open(NULL, NULL); + struct yasl_private_display *display = yasl_private_display_open(NULL, NULL); assert(display); - struct yasl_window_spec spec = {.title = "Cursor test", .size = {320, 240}}; - struct yasl_surface *surface = yasl_surface_create_toplevel(display, &spec); + struct yasl_private_window_spec spec = {.title = "Cursor test", .size = {320, 240}}; + struct yasl_surface *surface = yasl_private_surface_create_toplevel(display, &spec); assert(surface); - NSWindow *window = (NSWindow *)yasl_surface_get_native_handle(surface); + NSWindow *window = (NSWindow *)yasl_private_surface_get_native_handle(surface); NSView *view = window.contentView; NSEvent *cursor = [NSEvent enterExitEventWithType:NSEventTypeCursorUpdate location:NSMakePoint(100, 120) modifierFlags:0 timestamp:0 windowNumber:window.windowNumber context:nil eventNumber:0 trackingNumber:0 userData:NULL]; assert(cursor); - yasl_surface_set_cursor(surface, YASL_CURSOR_HIDDEN); + yasl_private_surface_set_cursor(surface, YASL_PRIVATE_CURSOR_HIDDEN); [view cursorUpdate:cursor]; NSCursor *hidden = [NSCursor currentCursor]; assert_transparent(hidden); @@ -37,21 +37,21 @@ int main(void) [view mouseMoved:motion]; [view cursorUpdate:cursor]; assert([NSCursor currentCursor] == hidden); - struct yasl_event event; + struct yasl_private_event event; bool moved = false; - while (yasl_surface_poll_event(surface, &event)) - if (event.type == YASL_EVENT_POINTER_MOTION) + while (yasl_private_surface_poll_event(surface, &event)) + if (event.type == YASL_PRIVATE_EVENT_POINTER_MOTION) moved = true; assert(moved); [[NSCursor arrowCursor] set]; /* Simulate leaving and re-entering. */ [view cursorUpdate:cursor]; assert([NSCursor currentCursor] == hidden); - yasl_surface_set_cursor(surface, YASL_CURSOR_ARROW); + yasl_private_surface_set_cursor(surface, YASL_PRIVATE_CURSOR_ARROW); [view cursorUpdate:cursor]; assert([NSCursor currentCursor] == [NSCursor arrowCursor]); - yasl_surface_destroy(surface); - yasl_display_close(display); + yasl_private_surface_destroy(surface); + yasl_private_display_close(display); } return 0; } diff --git a/tests/cocoa/test_keyboard.m b/tests/cocoa/test_keyboard.m index f93a745..a41011c 100644 --- a/tests/cocoa/test_keyboard.m +++ b/tests/cocoa/test_keyboard.m @@ -1,5 +1,6 @@ #import <AppKit/AppKit.h> -#include <yasl/native.h> +#include <common/private_native.h> +#include <common/backend.h> #include <assert.h> #include <math.h> #include <string.h> @@ -31,32 +32,40 @@ } @end -static struct yasl_event next(struct yasl_surface *surface, enum yasl_event_type type) +static struct yasl_private_event next(struct yasl_surface *surface, enum yasl_private_event_type type) { - struct yasl_event event; - assert(yasl_surface_poll_event(surface, &event)); + struct yasl_private_event event; + assert(yasl_private_surface_poll_event(surface, &event)); assert(event.type == type && event.surface == surface); return event; } static void empty(struct yasl_surface *surface) { - struct yasl_event event; - assert(!yasl_surface_poll_event(surface, &event)); + struct yasl_private_event event; + assert(!yasl_private_surface_poll_event(surface, &event)); } int main(void) { @autoreleasepool { - struct yasl_display *display = yasl_display_open(NULL, NULL); + struct yasl_private_display *display = yasl_private_display_open(NULL, NULL); assert(display); - struct yasl_window_spec spec = {.title = "Keyboard test", .size = {320, 240}}; - struct yasl_surface *surface = yasl_surface_create_toplevel(display, &spec); + struct yasl_private_window_spec spec = {.title = "Keyboard test", .size = {320, 240}}; + struct yasl_surface *surface = yasl_private_surface_create_toplevel(display, &spec); assert(surface); - NSWindow *window = (NSWindow *)yasl_surface_get_native_handle(surface); + surface->text_input_active = true; + surface->text_surrounding = strdup("A😀B"); + surface->text_length = strlen(surface->text_surrounding); + surface->text_begin = 1; + surface->text_end = 5; + surface->text_revision = 7; + surface->text_generation = 3; + assert(display->ops->surface_update_text_input(surface)); + NSWindow *window = (NSWindow *)yasl_private_surface_get_native_handle(surface); NSView<NSTextInputClient> *view = (id)window.contentView; - struct yasl_event event; - while (yasl_surface_poll_event(surface, &event)) {} + struct yasl_private_event event; + while (yasl_private_surface_poll_event(surface, &event)) {} InputSample *sample = [InputSample new]; NSEvent *native = (NSEvent *)sample; @@ -65,7 +74,7 @@ int main(void) sample.label = @"q"; sample.shiftedLabel = @"Q"; [view keyUp:native]; - event = next(surface, YASL_EVENT_KEY); + event = next(surface, YASL_PRIVATE_EVENT_KEY); assert(event.key.key == YASL_KEY_A && event.key.native_code == 0); assert(event.key.action == YASL_KEY_RELEASE); assert(event.key.codepoint == 'q' && event.key.shifted == 'Q'); @@ -73,29 +82,31 @@ int main(void) sample.label = @"😀"; /* One scalar, two UTF-16 units. */ sample.shiftedLabel = @"ab"; [view keyUp:native]; - event = next(surface, YASL_EVENT_KEY); + event = next(surface, YASL_PRIVATE_EVENT_KEY); assert(event.key.codepoint == 0x1f600 && event.key.shifted == 0); sample.type = NSEventTypeFlagsChanged; sample.keyCode = 56; /* Left Shift. */ sample.modifierFlags = NSEventModifierFlagShift | 0x0002; [view flagsChanged:native]; - event = next(surface, YASL_EVENT_KEY); + event = next(surface, YASL_PRIVATE_EVENT_KEY); assert(event.key.key == YASL_KEY_LEFT_SHIFT && event.key.action == YASL_KEY_PRESS); - assert(event.key.mods == YASL_MOD_SHIFT && !event.key.codepoint && !event.key.shifted); + assert(event.key.mods == YASL_PRIVATE_MOD_SHIFT && !event.key.codepoint && !event.key.shifted); sample.modifierFlags = 0; [view flagsChanged:native]; - assert(next(surface, YASL_EVENT_KEY).key.action == YASL_KEY_RELEASE); + assert(next(surface, YASL_PRIVATE_EVENT_KEY).key.action == YASL_KEY_RELEASE); /* Native text services own preedit and commits. Marked text is not an * insertion, and commits retain all UTF-8 bytes, including non-BMP. */ [view setMarkedText:@"é" selectedRange:NSMakeRange(1, 0) - replacementRange:NSMakeRange(NSNotFound, 0)]; - assert(strcmp(yasl_surface_ime_preedit(surface), "é") == 0); + replacementRange:NSMakeRange(1, 2)]; + assert(strcmp(yasl_private_surface_ime_preedit(surface), "é") == 0); empty(surface); [view insertText:@"😀é" replacementRange:NSMakeRange(NSNotFound, 0)]; - event = next(surface, YASL_EVENT_TEXT); - assert(event.text.len == strlen("😀é") && strcmp(event.text.data, "😀é") == 0); - assert(!yasl_surface_ime_preedit(surface)); + event = next(surface, YASL_PRIVATE_EVENT_TEXT_EDIT); + assert(event.text_edit.len == strlen("😀é") && strcmp(event.text_edit.data, "😀é") == 0); + assert(event.text_edit.begin == 1 && event.text_edit.end == 5); + assert(event.text_edit.revision == 7 && event.text_edit.generation == 3); + assert(!yasl_private_surface_ime_preedit(surface)); empty(surface); /* Exercise the keyDown -> interpretKeyEvents -> insertText route with @@ -104,9 +115,16 @@ int main(void) modifierFlags:0 timestamp:0 windowNumber:window.windowNumber context:nil characters:@"a" charactersIgnoringModifiers:@"a" isARepeat:NO keyCode:0]; [view keyDown:press]; - assert(next(surface, YASL_EVENT_KEY).key.key == YASL_KEY_A); - event = next(surface, YASL_EVENT_TEXT); - assert(event.text.len == 1 && event.text.data[0] == 'a'); + assert(next(surface, YASL_PRIVATE_EVENT_KEY).key.key == YASL_KEY_A); + event = next(surface, YASL_PRIVATE_EVENT_TEXT_EDIT); + assert(event.text_edit.len == 1 && event.text_edit.data[0] == 'a'); + assert(event.text_edit.begin == 1 && event.text_edit.end == 5); + empty(surface); + + surface->text_end = 1; + [view doCommandBySelector:@selector(deleteBackward)]; + event = next(surface, YASL_PRIVATE_EVENT_TEXT_EDIT); + assert(event.text_edit.len == 0 && event.text_edit.begin == 0 && event.text_edit.end == 1); empty(surface); sample.type = NSEventTypeScrollWheel; @@ -114,20 +132,20 @@ int main(void) sample.scrollingDeltaX = -2.5; sample.scrollingDeltaY = 1.25; [view scrollWheel:native]; - event = next(surface, YASL_EVENT_POINTER_SCROLL); + event = next(surface, YASL_PRIVATE_EVENT_POINTER_SCROLL); assert(event.pointer.scroll.x == 0.25 && event.pointer.scroll.y == -0.125); sample.hasPreciseScrollingDeltas = NO; sample.scrollingDeltaX = 1; sample.scrollingDeltaY = -1; [view scrollWheel:native]; - event = next(surface, YASL_EVENT_POINTER_SCROLL); + event = next(surface, YASL_PRIVATE_EVENT_POINTER_SCROLL); assert(event.pointer.scroll.x == -1 && event.pointer.scroll.y == 1); sample.scrollingDeltaX = NAN; [view scrollWheel:native]; empty(surface); [sample release]; - yasl_surface_destroy(surface); - yasl_display_close(display); + yasl_private_surface_destroy(surface); + yasl_private_display_close(display); } return 0; } diff --git a/tests/cocoa/test_native.c b/tests/cocoa/test_native.c index 674513d..9b8f8fa 100644 --- a/tests/cocoa/test_native.c +++ b/tests/cocoa/test_native.c @@ -1,77 +1,77 @@ #define _POSIX_C_SOURCE 200809L -#include "yasl/native.h" -#include "yasl/loop.h" -#include "yasl/yasl.h" +#include "common/private_native.h" +#include "common/private_loop.h" +#include "common/types.h" #include <assert.h> #include <time.h> struct state { - struct yasl_display *display; + struct yasl_private_display *display; struct yasl_surface *surface; unsigned draws; }; -static void draw(struct yasl_surface *surface, struct yasl_frame *frame, void *ud) +static void draw(struct yasl_surface *surface, struct yasl_private_frame *frame, void *ud) { struct state *state = ud; assert(surface == state->surface && frame->surface == surface); - assert(yasl_surface_display(surface) == state->display); + assert(yasl_private_surface_display(surface) == state->display); assert(frame->target.pixels); - struct yasl_native_frame native; - assert(yasl_surface_get_native_frame(surface, &frame->target, &native)); + struct yasl_private_native_frame native; + assert(yasl_private_surface_get_native_frame(surface, &frame->target, &native)); assert(native.drawable); - struct yasl_vector2i size = yasl_surface_geometry(surface).size; + struct yasl_private_vector2i size = yasl_private_surface_geometry(surface).size; assert(size.x == frame->target.size.x && size.y == frame->target.size.y); for (int i = 0; i < size.x * size.y; i++) frame->target.pixels[i] = 0xff4080c0; - struct yasl_damage_list damage = {.n = YASL_DAMAGE_MAX + 1}; - assert(!yasl_surface_present(surface, &frame->target, &damage)); + struct yasl_private_damage_list damage = {.n = YASL_PRIVATE_DAMAGE_MAX + 1}; + assert(!yasl_private_surface_present(surface, &frame->target, &damage)); damage.n = -1; - struct yasl_framebuffer invalid = frame->target; + struct yasl_private_framebuffer invalid = frame->target; invalid.size.x++; - assert(!yasl_surface_present(surface, &invalid, &damage)); + assert(!yasl_private_surface_present(surface, &invalid, &damage)); invalid = frame->target; invalid.generation--; - assert(!yasl_surface_present(surface, &invalid, &damage)); - assert(!yasl_surface_present(surface, &frame->target, NULL)); - assert(yasl_surface_present(surface, &frame->target, &damage)); - assert(!yasl_surface_get_native_frame(surface, &frame->target, &native)); - assert(!yasl_surface_present(surface, &frame->target, &damage)); + assert(!yasl_private_surface_present(surface, &invalid, &damage)); + assert(!yasl_private_surface_present(surface, &frame->target, NULL)); + assert(yasl_private_surface_present(surface, &frame->target, &damage)); + assert(!yasl_private_surface_get_native_frame(surface, &frame->target, &native)); + assert(!yasl_private_surface_present(surface, &frame->target, &damage)); state->draws++; - yasl_surface_destroy(surface); + yasl_private_surface_destroy(surface); state->surface = NULL; } -static void test_window(enum yasl_backend request, bool null_spec) +static void test_window(enum yasl_private_backend request, bool null_spec) { - struct yasl_display_spec spec = {.backend = request}; - struct state state = {.display = yasl_display_open(NULL, null_spec ? NULL : &spec)}; + struct yasl_private_display_spec spec = {.backend = request}; + struct state state = {.display = yasl_private_display_open(NULL, null_spec ? NULL : &spec)}; assert(state.display); - assert(yasl_display_backend(state.display) == YASL_BACKEND_COCOA); - struct yasl_window_cbs callbacks = {.draw = draw}; - struct yasl_window_spec surface = { + assert(yasl_private_display_backend(state.display) == YASL_PRIVATE_BACKEND_COCOA); + struct yasl_private_window_cbs callbacks = {.draw = draw}; + struct yasl_private_window_spec surface = { .title = "Native backend test", .size = {160, 100}, .cbs = &callbacks, .ud = &state, }; - state.surface = yasl_surface_create_toplevel(state.display, &surface); + state.surface = yasl_private_surface_create_toplevel(state.display, &surface); assert(state.surface); assert(!state.draws); callbacks.draw = NULL; - yasl_surface_schedule_redraw(state.surface); - uint64_t deadline = yasl_loop_now_ns() + 3000000000ull; - while (!state.draws && yasl_loop_now_ns() < deadline) { - yasl_display_dispatch(state.display, 0); - yasl_display_flush(state.display); + yasl_private_surface_schedule_redraw(state.surface); + uint64_t deadline = yasl_private_loop_now_ns() + 3000000000ull; + while (!state.draws && yasl_private_loop_now_ns() < deadline) { + yasl_private_display_dispatch(state.display, 0); + yasl_private_display_flush(state.display); nanosleep(&(struct timespec){.tv_nsec = 1000000}, NULL); } assert(state.draws == 1 && !state.surface); - yasl_display_close(state.display); + yasl_private_display_close(state.display); } int main(void) { - test_window(YASL_BACKEND_AUTO, true); - test_window(YASL_BACKEND_AUTO, false); - test_window(YASL_BACKEND_COCOA, false); + test_window(YASL_PRIVATE_BACKEND_AUTO, true); + test_window(YASL_PRIVATE_BACKEND_AUTO, false); + test_window(YASL_PRIVATE_BACKEND_COCOA, false); return 0; } diff --git a/tests/cocoa/test_ownership.m b/tests/cocoa/test_ownership.m index 6d69907..4efd164 100644 --- a/tests/cocoa/test_ownership.m +++ b/tests/cocoa/test_ownership.m @@ -1,7 +1,9 @@ -#include "yasl/native.h" -#include "yasl/native/cocoa.h" +#include "common/private_native.h" +#include "common/private_native_cocoa.h" +#include "common/backend.h" #import <AppKit/AppKit.h> -#include "yasl/yasl.h" +#import <Metal/Metal.h> +#include "common/types.h" #include <assert.h> #include <errno.h> #include <stdio.h> @@ -9,60 +11,98 @@ int main(void) { - struct yasl_display *d = yasl_display_open(NULL, NULL); + struct yasl_private_display *d = yasl_private_display_open(NULL, NULL); assert(d); - struct yasl_window_spec spec = {.title = "Ownership test", .size = {160, 100}}; - struct yasl_surface *s = yasl_surface_create_toplevel(d, &spec); + assert(yasl_private_display_caps(d) & YASL_PRIVATE_CAP_TEXT_INPUT); + struct yasl_private_window_spec spec = {.title = "Ownership test", .size = {160, 100}}; + struct yasl_surface *s = yasl_private_surface_create_toplevel(d, &spec); assert(s); - assert(yasl_display_get_native_handle(d) == (void *)NSApp); - assert(yasl_cocoa_display_get_application(d) == NSApp); - NSWindow *window = (NSWindow *)yasl_surface_get_native_handle(s); - assert(yasl_cocoa_surface_get_window(s) == window); - assert(yasl_cocoa_surface_get_view(s) == window.contentView); + assert(yasl_private_display_get_native_handle(d) == (void *)NSApp); + assert(yasl_private_cocoa_display_get_application(d) == NSApp); + NSWindow *window = (NSWindow *)yasl_private_surface_get_native_handle(s); + assert(yasl_private_cocoa_surface_get_window(s) == window); + assert(yasl_private_cocoa_surface_get_view(s) == window.contentView); assert([window isKindOfClass:[NSWindow class]] && window.contentView); - struct yasl_event event; - while (yasl_display_poll_event(d, &event)) {} + struct yasl_private_event event; + while (yasl_private_display_poll_event(d, &event)) {} - assert(yasl_surface_set_size(s, (struct yasl_vector2i){180, 110})); - assert(yasl_surface_set_fullscreen(s, false)); - assert(yasl_surface_set_maximized(s, false)); - assert(!yasl_surface_set_size(s, (struct yasl_vector2i){0, 100})); + struct yasl_private_window_spec gpu_spec = { + .flags = YASL_PRIVATE_SURFACE_HIDDEN | YASL_PRIVATE_SURFACE_UNDECORATED | + YASL_PRIVATE_SURFACE_FIXED_SIZE, + .external_gpu = true, + .title = "External GPU test", + .size = {160, 100}, + }; + struct yasl_surface *gpu = yasl_private_surface_create_toplevel(d, &gpu_spec); + assert(gpu && gpu->presenter == YASL_PRIVATE_PRESENTER_NONE); + CAMetalLayer *metal = (CAMetalLayer *)d->ops->surface_get_metal_layer(gpu); + assert(metal && [metal isKindOfClass:[CAMetalLayer class]] && !metal.device); + NSWindow *hidden = (NSWindow *)yasl_private_surface_get_native_handle(gpu); + assert(!hidden.isVisible && hidden.styleMask == NSWindowStyleMaskBorderless); + yasl_private_surface_destroy(gpu); + + struct yasl_surface *gl = d->ops->surface_create_gl(d, &gpu_spec, NULL, NULL); + if (gl) { + assert(gl->presenter == YASL_PRIVATE_PRESENTER_GL); + assert(d->ops->gl_make_current(d, gl)); + assert(d->ops->gl_get_proc_address(d, "glClear") != NULL); + assert(d->ops->gl_swap_interval(d, gl, 1)); + assert(d->ops->gl_swap_buffers(d, gl)); + assert(d->ops->gl_make_current(d, NULL)); + d->ops->surface_destroy(gl); + } + + assert(yasl_private_surface_set_size(s, (struct yasl_private_vector2i){180, 110})); + assert(yasl_private_surface_set_fullscreen(s, false)); + assert(yasl_private_surface_set_maximized(s, false)); + assert(!yasl_private_surface_set_size(s, (struct yasl_private_vector2i){0, 100})); assert(errno == EINVAL); - struct yasl_popup_spec popup_spec = { + struct yasl_private_popup_spec popup_spec = { + .positioner_explicit = true, + .anchor_edges = 4, .gravity = 3, .constraints = 63, .size = {80, 40}, .anchor = {10, 10, 2, 2}, }; - struct yasl_surface *popup = yasl_surface_create_popup(s, &popup_spec); + struct yasl_surface *popup = yasl_private_surface_create_popup(s, &popup_spec); assert(popup); - assert(!yasl_surface_set_size(popup, (struct yasl_vector2i){80, 40})); + popup_spec.anchor_edges = 8; + popup_spec.gravity = 5; + assert(yasl_private_surface_popup_reposition(popup, &popup_spec)); + assert(!yasl_private_surface_set_size(popup, (struct yasl_private_vector2i){80, 40})); assert(errno == EINVAL); - assert(!yasl_surface_set_fullscreen(popup, true) && errno == EINVAL); - assert(!yasl_surface_set_maximized(popup, true) && errno == EINVAL); - assert(!yasl_surface_minimize(popup) && errno == EINVAL); - yasl_surface_destroy(popup); - while (yasl_display_poll_event(d, &event)) {} + assert(!yasl_private_surface_set_fullscreen(popup, true) && errno == EINVAL); + assert(!yasl_private_surface_set_maximized(popup, true) && errno == EINVAL); + assert(!yasl_private_surface_minimize(popup) && errno == EINVAL); + yasl_private_surface_destroy(popup); + while (yasl_private_display_poll_event(d, &event)) {} - id<NSTextInputClient> text_input = (id<NSTextInputClient>)yasl_cocoa_surface_get_view(s); + s->text_input_active = true; + s->text_surrounding = strdup("é漢"); + s->text_length = strlen(s->text_surrounding); + s->text_begin = 0; + s->text_end = s->text_length; + assert(d->ops->surface_update_text_input(s)); + id<NSTextInputClient> text_input = (id<NSTextInputClient>)yasl_private_cocoa_surface_get_view(s); [text_input setMarkedText:@"é漢" selectedRange:NSMakeRange(1, 1) replacementRange:NSMakeRange(NSNotFound, 0)]; - assert(strcmp(yasl_surface_ime_preedit(s), "é漢") == 0); + assert(strcmp(yasl_private_surface_ime_preedit(s), "é漢") == 0); [text_input unmarkText]; - assert(yasl_surface_ime_preedit(s) == NULL); - assert(yasl_display_poll_event(d, &event)); - assert(event.type == YASL_EVENT_COMPOSITION && event.composition.active); + assert(yasl_private_surface_ime_preedit(s) == NULL); + assert(yasl_private_display_poll_event(d, &event)); + assert(event.type == YASL_PRIVATE_EVENT_COMPOSITION && event.composition.active); assert(strcmp(event.composition.data, "é漢") == 0); assert(event.composition.begin == 2 && event.composition.end == 5); - assert(yasl_display_poll_event(d, &event)); - assert(event.type == YASL_EVENT_COMPOSITION && !event.composition.active); + assert(yasl_private_display_poll_event(d, &event)); + assert(event.type == YASL_PRIVATE_EVENT_COMPOSITION && !event.composition.active); assert(event.composition.begin == -1 && event.composition.end == -1); - while (yasl_display_poll_event(d, &event)) {} + while (yasl_private_display_poll_event(d, &event)) {} uint64_t request_ids[600]; @autoreleasepool { for (int i = 0; i < 600; i++) { char text[32]; snprintf(text, sizeof(text), "paste-%d", i); - assert(yasl_clipboard_set(d, YASL_CLIPBOARD, text)); - request_ids[i] = yasl_clipboard_request(s, YASL_CLIPBOARD); + assert(yasl_private_clipboard_set(d, YASL_PRIVATE_CLIPBOARD, text)); + request_ids[i] = yasl_private_clipboard_request(s, YASL_PRIVATE_CLIPBOARD); assert(request_ids[i] != 0); memset(text, 'x', sizeof(text)); } @@ -71,32 +111,32 @@ int main(void) for (int i = 0; i < 600; i++) { char text[32]; snprintf(text, sizeof(text), "paste-%d", i); - assert(yasl_display_poll_event(d, &event)); - assert(event.type == YASL_EVENT_PASTE && event.paste.request_id == request_ids[i]); - assert(event.paste.status == YASL_TRANSFER_OK && strcmp(event.paste.data, text) == 0); + assert(yasl_private_display_poll_event(d, &event)); + assert(event.type == YASL_PRIVATE_EVENT_PASTE && event.paste.request_id == request_ids[i]); + assert(event.paste.status == YASL_PRIVATE_TRANSFER_OK && strcmp(event.paste.data, text) == 0); } const char *last = event.paste.data; - assert(!yasl_display_poll_event(d, &event)); - @autoreleasepool { yasl_display_copy(d, YASL_CLIPBOARD, "new selection"); } + assert(!yasl_private_display_poll_event(d, &event)); + @autoreleasepool { yasl_private_display_copy(d, YASL_PRIVATE_CLIPBOARD, "new selection"); } assert(strcmp(last, "paste-599") == 0); - assert(!yasl_clipboard_set(d, YASL_CLIPBOARD, "\xc3")); + assert(!yasl_private_clipboard_set(d, YASL_PRIVATE_CLIPBOARD, "\xc3")); assert(errno == EILSEQ); - assert(yasl_clipboard_set(d, YASL_CLIPBOARD, "")); - uint64_t empty_id = yasl_clipboard_request(s, YASL_CLIPBOARD); - assert(empty_id && yasl_display_poll_event(d, &event)); - assert(event.type == YASL_EVENT_PASTE && event.paste.request_id == empty_id); - assert(event.paste.status == YASL_TRANSFER_OK && !event.paste.len && !event.paste.data[0]); + assert(yasl_private_clipboard_set(d, YASL_PRIVATE_CLIPBOARD, "")); + uint64_t empty_id = yasl_private_clipboard_request(s, YASL_PRIVATE_CLIPBOARD); + assert(empty_id && yasl_private_display_poll_event(d, &event)); + assert(event.type == YASL_PRIVATE_EVENT_PASTE && event.paste.request_id == empty_id); + assert(event.paste.status == YASL_PRIVATE_TRANSFER_OK && !event.paste.len && !event.paste.data[0]); [NSPasteboard.generalPasteboard clearContents]; - uint64_t unavailable_id = yasl_clipboard_request(s, YASL_CLIPBOARD); - assert(unavailable_id && yasl_display_poll_event(d, &event)); - assert(event.type == YASL_EVENT_PASTE && event.paste.request_id == unavailable_id); - assert(event.paste.status == YASL_TRANSFER_UNAVAILABLE && !event.paste.len); - yasl_display_copy(d, YASL_CLIPBOARD, "legacy paste"); - yasl_display_paste(d, YASL_CLIPBOARD, s); - assert(yasl_display_poll_event(d, &event)); - assert(event.type == YASL_EVENT_PASTE && event.paste.request_id == 0); - assert(event.paste.status == YASL_TRANSFER_OK && strcmp(event.text.data, "legacy paste") == 0); - yasl_surface_destroy(s); - yasl_display_close(d); + uint64_t unavailable_id = yasl_private_clipboard_request(s, YASL_PRIVATE_CLIPBOARD); + assert(unavailable_id && yasl_private_display_poll_event(d, &event)); + assert(event.type == YASL_PRIVATE_EVENT_PASTE && event.paste.request_id == unavailable_id); + assert(event.paste.status == YASL_PRIVATE_TRANSFER_UNAVAILABLE && !event.paste.len); + yasl_private_display_copy(d, YASL_PRIVATE_CLIPBOARD, "legacy paste"); + yasl_private_display_paste(d, YASL_PRIVATE_CLIPBOARD, s); + assert(yasl_private_display_poll_event(d, &event)); + assert(event.type == YASL_PRIVATE_EVENT_PASTE && event.paste.request_id == 0); + assert(event.paste.status == YASL_PRIVATE_TRANSFER_OK && strcmp(event.text.data, "legacy paste") == 0); + yasl_private_surface_destroy(s); + yasl_private_display_close(d); return 0; } diff --git a/tests/cocoa/test_tablet.m b/tests/cocoa/test_tablet.m index 715a66e..8eb63d5 100644 --- a/tests/cocoa/test_tablet.m +++ b/tests/cocoa/test_tablet.m @@ -1,5 +1,5 @@ #import <AppKit/AppKit.h> -#include <yasl/native.h> +#include <common/private_native.h> #include <assert.h> /* A strict event double catches querying tablet-only properties on ordinary @@ -15,6 +15,10 @@ @property NSPointingDeviceType tool; @property float samplePressure; @property NSPoint sampleTilt; +@property double sampleRotation; +@property double sampleTangential; +@property NSInteger sampleDeviceID; +@property NSInteger sampleToolID; @end @implementation TabletSample @@ -33,70 +37,106 @@ assert(self.type == NSEventTypeTabletProximity || self.subtype == NSEventSubtypeTabletProximity); return self.tool; } +- (double)rotation { return self.sampleRotation; } +- (double)tangentialPressure { return self.sampleTangential; } +- (NSInteger)pointingDeviceID { return self.sampleDeviceID; } +- (NSInteger)pointingDeviceSerialNumber { return self.sampleToolID; } @end -static struct yasl_event next(struct yasl_surface *surface) +static struct yasl_private_event next(struct yasl_surface *surface, enum yasl_private_event_type type) { - struct yasl_event event; - while (yasl_surface_poll_event(surface, &event)) - return event; - assert(!"Expected a pointer event"); - return (struct yasl_event){0}; + struct yasl_private_event event; + assert(yasl_private_surface_poll_event(surface, &event)); + assert(event.type == type); + return event; } int main(void) { @autoreleasepool { - struct yasl_display *display = yasl_display_open(NULL, NULL); - assert(display && (yasl_display_caps(display) & YASL_CAP_TABLET)); - struct yasl_window_spec spec = {.title = "Tablet test", .size = {320, 240}}; - struct yasl_surface *surface = yasl_surface_create_toplevel(display, &spec); + struct yasl_private_display *display = yasl_private_display_open(NULL, NULL); + assert(display && (yasl_private_display_caps(display) & YASL_PRIVATE_CAP_TABLET)); + struct yasl_private_window_spec spec = {.title = "Tablet test", .size = {320, 240}}; + struct yasl_surface *surface = yasl_private_surface_create_toplevel(display, &spec); assert(surface); - NSWindow *window = (NSWindow *)yasl_surface_get_native_handle(surface); + NSWindow *window = (NSWindow *)yasl_private_surface_get_native_handle(surface); NSView *view = window.contentView; - struct yasl_event event; - while (yasl_surface_poll_event(surface, &event)) {} + struct yasl_private_event event; + while (yasl_private_surface_poll_event(surface, &event)) {} TabletSample *sample = [TabletSample new]; NSEvent *native = (NSEvent *)sample; sample.type = NSEventTypeTabletProximity; sample.enteringProximity = YES; sample.tool = NSPointingDeviceTypePen; + sample.sampleDeviceID = 17; + sample.sampleToolID = 29; [view tabletProximity:native]; sample.type = NSEventTypeMouseMoved; sample.subtype = NSEventSubtypeTabletPoint; sample.locationInWindow = NSMakePoint(100, 120); sample.sampleTilt = NSMakePoint(-0.5, 0.5); + sample.sampleRotation = 37.5; + sample.sampleTangential = -0.25; [view mouseMoved:native]; - struct yasl_event ev = next(surface); - assert(ev.pointer.device == YASL_PTR_DEV_PEN && ev.type == YASL_EVENT_POINTER_MOTION && !ev.pointer.pen.pressure); + struct yasl_private_event pen = next(surface, YASL_PRIVATE_EVENT_PEN); + assert(pen.pen.action == YASL_PRIVATE_PEN_ENTER && pen.pen.device_id == 17 && pen.pen.tool_id == 29); + struct yasl_private_event ev = next(surface, YASL_PRIVATE_EVENT_POINTER_MOTION); + assert(ev.pointer.device == YASL_PRIVATE_PTR_DEV_PEN && ev.type == YASL_PRIVATE_EVENT_POINTER_MOTION && !ev.pointer.pen.pressure); assert(ev.pointer.pen.tilt_x == -45 && ev.pointer.pen.tilt_y == -45); + pen = next(surface, YASL_PRIVATE_EVENT_PEN); + assert(pen.pen.action == YASL_PRIVATE_PEN_MOVE && pen.pen.device_id == 17 && pen.pen.tool_id == 29); + assert((pen.pen.axes_valid & (YASL_PRIVATE_PEN_PRESSURE | YASL_PRIVATE_PEN_TILT)) == + (YASL_PRIVATE_PEN_PRESSURE | YASL_PRIVATE_PEN_TILT)); + assert(pen.pen.rotation == 37.5 && pen.pen.tangential == -0.25); + assert((pen.pen.axes_valid & (YASL_PRIVATE_PEN_ROTATION | YASL_PRIVATE_PEN_TANGENTIAL)) == + (YASL_PRIVATE_PEN_ROTATION | YASL_PRIVATE_PEN_TANGENTIAL)); sample.type = NSEventTypeLeftMouseDown; sample.samplePressure = 0.5; + sample.buttonMask = NSEventButtonMaskPenTip; [view mouseDown:native]; - ev = next(surface); - assert(ev.pointer.device == YASL_PTR_DEV_PEN && ev.type == YASL_EVENT_POINTER_DOWN && ev.pointer.button == YASL_BUTTON_PRIMARY); + ev = next(surface, YASL_PRIVATE_EVENT_POINTER_DOWN); + assert(ev.pointer.device == YASL_PRIVATE_PTR_DEV_PEN && ev.type == YASL_PRIVATE_EVENT_POINTER_DOWN && ev.pointer.button == YASL_PRIVATE_BUTTON_PRIMARY); assert(ev.pointer.pen.pressure == 32767); - struct yasl_vector2i position = ev.pointer.pos; + pen = next(surface, YASL_PRIVATE_EVENT_PEN); + assert(pen.pen.action == YASL_PRIVATE_PEN_DOWN && pen.pen.in_contact && pen.pen.pressure == 0.5); + assert(pen.pen.buttons == YASL_PRIVATE_BUTTON_BIT(YASL_PRIVATE_BUTTON_PRIMARY)); + assert(pen.pen.changed_button == YASL_PRIVATE_BUTTON_BIT(YASL_PRIVATE_BUTTON_PRIMARY)); + struct yasl_private_vector2i position = ev.pointer.pos; /* Native tablet events cover stationary pressure changes. */ sample.type = NSEventTypeTabletPoint; sample.subtype = NSEventSubtypeMouseEvent; sample.buttonMask = NSEventButtonMaskPenTip; sample.samplePressure = 1.5; [view tabletPoint:native]; - ev = next(surface); - assert(ev.type == YASL_EVENT_POINTER_MOTION && ev.pointer.pen.pressure == 65535); + ev = next(surface, YASL_PRIVATE_EVENT_POINTER_MOTION); + assert(ev.type == YASL_PRIVATE_EVENT_POINTER_MOTION && ev.pointer.pen.pressure == 65535); assert(ev.pointer.pos.x == position.x && ev.pointer.pos.y == position.y); + pen = next(surface, YASL_PRIVATE_EVENT_PEN); + assert(pen.pen.action == YASL_PRIVATE_PEN_MOVE && pen.pen.pressure == 1.0); + sample.buttonMask |= NSEventButtonMaskPenLowerSide; + [view tabletPoint:native]; + next(surface, YASL_PRIVATE_EVENT_POINTER_MOTION); + pen = next(surface, YASL_PRIVATE_EVENT_PEN); + assert(pen.pen.action == YASL_PRIVATE_PEN_BUTTON); + assert(pen.pen.buttons == (YASL_PRIVATE_BUTTON_BIT(YASL_PRIVATE_BUTTON_PRIMARY) | + YASL_PRIVATE_BUTTON_BIT(YASL_PRIVATE_BUTTON_SECONDARY))); + assert(pen.pen.changed_button == YASL_PRIVATE_BUTTON_BIT(YASL_PRIVATE_BUTTON_SECONDARY)); + sample.buttonMask = NSEventButtonMaskPenTip; sample.samplePressure = 0; /* Zero does not release a held tip button. */ [view tabletPoint:native]; - assert(next(surface).type == YASL_EVENT_POINTER_MOTION); + assert(next(surface, YASL_PRIVATE_EVENT_POINTER_MOTION).type == YASL_PRIVATE_EVENT_POINTER_MOTION); + next(surface, YASL_PRIVATE_EVENT_PEN); sample.buttonMask = 0; [view tabletPoint:native]; - ev = next(surface); - assert(ev.type == YASL_EVENT_POINTER_UP && !ev.pointer.pen.pressure && ev.pointer.device == YASL_PTR_DEV_PEN); + ev = next(surface, YASL_PRIVATE_EVENT_POINTER_UP); + assert(ev.type == YASL_PRIVATE_EVENT_POINTER_UP && !ev.pointer.pen.pressure && ev.pointer.device == YASL_PRIVATE_PTR_DEV_PEN); + pen = next(surface, YASL_PRIVATE_EVENT_PEN); + assert(pen.pen.action == YASL_PRIVATE_PEN_UP && !pen.pen.in_contact && pen.pen.pressure == 0.0); sample.type = NSEventTypeTabletProximity; sample.enteringProximity = NO; [view tabletProximity:native]; - assert(next(surface).type == YASL_EVENT_POINTER_LEAVE); + assert(next(surface, YASL_PRIVATE_EVENT_POINTER_LEAVE).type == YASL_PRIVATE_EVENT_POINTER_LEAVE); + assert(next(surface, YASL_PRIVATE_EVENT_PEN).pen.action == YASL_PRIVATE_PEN_LEAVE); sample.enteringProximity = YES; sample.tool = NSPointingDeviceTypeEraser; [view tabletProximity:native]; @@ -104,20 +144,25 @@ int main(void) sample.samplePressure = 0.75; sample.buttonMask = NSEventButtonMaskPenTip; [view tabletPoint:native]; - ev = next(surface); - assert(ev.pointer.device == YASL_PTR_DEV_ERASER && ev.type == YASL_EVENT_POINTER_DOWN && ev.pointer.pen.pressure > 0); + pen = next(surface, YASL_PRIVATE_EVENT_PEN); + assert(pen.pen.action == YASL_PRIVATE_PEN_ENTER && pen.pen.tool == 2); + ev = next(surface, YASL_PRIVATE_EVENT_POINTER_DOWN); + assert(ev.pointer.device == YASL_PRIVATE_PTR_DEV_ERASER && ev.type == YASL_PRIVATE_EVENT_POINTER_DOWN && ev.pointer.pen.pressure > 0); + pen = next(surface, YASL_PRIVATE_EVENT_PEN); + assert(pen.pen.action == YASL_PRIVATE_PEN_DOWN && pen.pen.tool == 2 && pen.pen.in_contact); sample.type = NSEventTypeTabletProximity; sample.enteringProximity = NO; [view tabletProximity:native]; - ev = next(surface); - assert(ev.pointer.device == YASL_PTR_DEV_ERASER && ev.type == YASL_EVENT_POINTER_LEAVE && !ev.pointer.pen.pressure); + ev = next(surface, YASL_PRIVATE_EVENT_POINTER_LEAVE); + assert(ev.pointer.device == YASL_PRIVATE_PTR_DEV_ERASER && ev.type == YASL_PRIVATE_EVENT_POINTER_LEAVE && !ev.pointer.pen.pressure); + assert(next(surface, YASL_PRIVATE_EVENT_PEN).pen.action == YASL_PRIVATE_PEN_LEAVE); sample.type = NSEventTypeMouseMoved; [view mouseMoved:native]; - ev = next(surface); - assert(ev.pointer.device == YASL_PTR_DEV_MOUSE && !ev.pointer.pen.pressure); + ev = next(surface, YASL_PRIVATE_EVENT_POINTER_MOTION); + assert(ev.pointer.device == YASL_PRIVATE_PTR_DEV_MOUSE && !ev.pointer.pen.pressure); [sample release]; - yasl_surface_destroy(surface); - yasl_display_close(display); + yasl_private_surface_destroy(surface); + yasl_private_display_close(display); } return 0; } diff --git a/tests/common/test_app.c b/tests/common/test_app.c deleted file mode 100644 index 214d8f2..0000000 --- a/tests/common/test_app.c +++ /dev/null @@ -1,204 +0,0 @@ -#define _DEFAULT_SOURCE -#include "yasl/app.h" - -#include <assert.h> -#include <errno.h> -#include <pthread.h> -#include <stdio.h> -#include <unistd.h> - -struct test_state { - struct yasl_display *display; - struct yasl_surface *surface; - bool init_called; - bool frame_called; - bool user_event_called; - bool timer_called; - bool quit_called; - int frame_count; - yasl_timer_id timer_id; - pthread_t worker; - uint64_t target_id; -}; - -static void *worker_thread(void *arg) -{ - struct test_state *state = arg; - usleep(50000); /* 50ms */ - bool ok = yasl_display_post(state->display, &(struct yasl_message){ - .target_id = state->target_id, .code = 42, .payload = (void *)0x1234}); - assert(ok); - return NULL; -} - -static uint64_t on_timer(void *ud, yasl_timer_id id, uint64_t interval_ns) -{ - (void)id; - (void)interval_ns; - struct test_state *state = ud; - state->timer_called = true; - yasl_surface_request_frame(state->surface); - return 0; /* one-shot */ -} - -static enum yasl_app_result on_init(void *ud, struct yasl_display *d) -{ - struct test_state *state = ud; - state->display = d; - state->init_called = true; - - struct yasl_window_spec spec = { - .title = "App Runner Test", - .size = {320, 240}, - }; - state->surface = yasl_surface_create_toplevel(d, &spec); - assert(state->surface != NULL); - - yasl_surface_set_pacing(state->surface, YASL_PACING_ON_DEMAND); - yasl_surface_set_pointer_mode(state->surface, YASL_POINTER_RELATIVE); - yasl_surface_set_pointer_mode(state->surface, YASL_POINTER_NORMAL); - - state->timer_id = yasl_loop_add_timer(yasl_display_loop(d), YASL_MS_TO_NS(20), on_timer, state); - assert(state->timer_id > 0); - - state->target_id = yasl_surface_id(state->surface); - int err = pthread_create(&state->worker, NULL, worker_thread, state); - assert(err == 0); - - - yasl_surface_request_frame(state->surface); - return YASL_APP_CONTINUE; -} - -static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) -{ - struct test_state *state = ud; - if (ev->type == YASL_EVENT_USER) { - assert(ev->user.code == 42); - assert(ev->user.payload == (void *)0x1234); - state->user_event_called = true; - yasl_surface_request_frame(state->surface); - } else if (ev->type == YASL_EVENT_WINDOW_CLOSE) { - return YASL_APP_QUIT; - } - return YASL_APP_CONTINUE; -} - -static void on_frame(void *ud, struct yasl_surface *s, struct yasl_frame *ctx) -{ - struct test_state *state = ud; - assert(s == state->surface); - assert(ctx->surface == s); - state->frame_called = true; - state->frame_count++; - - if (ctx->target.pixels) { - size_t count = (size_t)ctx->target.size.x * ctx->target.size.y; - for (size_t i = 0; i < count; i++) { - ctx->target.pixels[i] = 0xff204060; - } - bool ok = yasl_frame_present(ctx, NULL, 0); - assert(ok); - } - - if (state->user_event_called) { - yasl_surface_destroy(state->surface); - } -} - -static void on_quit(void *ud, struct yasl_display *d) -{ - (void)d; - struct test_state *state = ud; - assert(!pthread_join(state->worker, NULL)); - state->quit_called = true; -} - -static void present_once(void *ud, struct yasl_surface *s, struct yasl_frame *ctx) -{ - (*(int *)ud)++; - if (ctx->target.pixels) - assert(yasl_frame_present(ctx, NULL, 0)); - yasl_surface_destroy(s); -} - -/* desc.window replaces init: the runner creates the toplevel and draws it. */ -static void test_window_without_init(void) -{ - int frames = 0; - struct yasl_window_spec window = {.title = "App Runner Window", .size = {160, 120}}; - int res = yasl_run(&(struct yasl_app_desc){ - .app_id = "org.yasl.test.app.window", - .userdata = &frames, - .window = &window, - .frame = present_once, - }); - assert(res == 0); - assert(frames == 1); -} - - -static enum yasl_app_result fail_init(void *ud, struct yasl_display *d) -{ - (void)ud; (void)d; - return YASL_APP_ERROR; -} -static void count_quit(void *ud, struct yasl_display *d) -{ - (void)d; - (*(int *)ud)++; -} -static enum yasl_app_result inspect_initial_window(void *ud, struct yasl_display *d) -{ - struct yasl_surface *s = yasl_app_window(d); - assert(s && yasl_surface_userdata(s) == ud); - errno = 0; - assert(!yasl_surface_set_callbacks(s, NULL, NULL) && errno == EBUSY); - return YASL_APP_CONTINUE; -} -static void fail_frame(void *ud, struct yasl_surface *s, struct yasl_frame *frame) -{ - (void)ud; - assert(frame->surface == s && frame->target.stride_bytes == frame->target.size.x * 4); - yasl_app_exit(yasl_surface_display(s), YASL_APP_ERROR); -} -static void test_error_lifecycle(void) -{ - int quits = 0; - assert(yasl_run(&(struct yasl_app_desc){.userdata = &quits, - .init = fail_init, .quit = count_quit}) < 0); - assert(quits == 1 && errno == EIO); - struct yasl_window_spec window = {.size_logical = {160, 120}, .userdata = &quits}; - assert(yasl_run(&(struct yasl_app_desc){.userdata = &quits, .window = &window, - .init = inspect_initial_window, .frame = fail_frame, .quit = count_quit}) < 0); - assert(quits == 2 && errno == EIO); -} - -int main(void) -{ - struct test_state state = {0}; - struct yasl_app_desc desc = { - .app_id = "org.yasl.test.app", - .backend = YASL_BACKEND_AUTO, - .userdata = &state, - .init = on_init, - .event = on_event, - .frame = on_frame, - .quit = on_quit, - }; - - int res = yasl_run(&desc); - if (res < 0 && !state.init_called && - (errno == EPERM || errno == EACCES || errno == ENOENT || - errno == ECONNREFUSED || errno == ENODEV)) return 77; - assert(res == 0); - assert(state.init_called); - assert(state.frame_called); - assert(state.user_event_called); - assert(state.timer_called); - assert(state.quit_called); - test_window_without_init(); - test_error_lifecycle(); - printf("App runner tests passed!\n"); - return 0; -} diff --git a/tests/common/test_backends.c b/tests/common/test_backends.c index 30f9b67..7eba57f 100644 --- a/tests/common/test_backends.c +++ b/tests/common/test_backends.c @@ -1,13 +1,13 @@ #define _POSIX_C_SOURCE 200809L -#include "yasl/native.h" +#include "common/private_native.h" #if YASL_HAS_WAYLAND -#include "yasl/native/wayland.h" +#include "common/private_native_wayland.h" #endif #if YASL_HAS_X11 -#include "yasl/native/x11.h" +#include "common/private_native_x11.h" #endif -#include "yasl/loop.h" -#include "yasl/yasl.h" +#include "common/private_loop.h" +#include "common/types.h" #include <X11/Xatom.h> #include <X11/Xlib.h> @@ -24,14 +24,14 @@ struct window { struct yasl_surface *surface; - struct yasl_display *display; + struct yasl_private_display *display; int draws, configures, closes; bool destroy_on_draw, destroy_on_configure; }; struct pump { - struct yasl_loop *loop; - struct yasl_display *displays[2]; + struct yasl_private_loop *loop; + struct yasl_private_display *displays[2]; int ticks; }; @@ -43,76 +43,76 @@ static int tick(int fd, short events, void *ud) struct pump *p = ud; for (int i = 0; i < 2; i++) { if (p->displays[i]) { - yasl_display_dispatch(p->displays[i], 0); - yasl_display_flush(p->displays[i]); + yasl_private_display_dispatch(p->displays[i], 0); + yasl_private_display_flush(p->displays[i]); } } - if (--p->ticks == 0) yasl_loop_quit(p->loop); + if (--p->ticks == 0) yasl_private_loop_quit(p->loop); return 0; } -static void pump(struct yasl_loop *loop, struct yasl_display *a, struct yasl_display *b) +static void pump(struct yasl_private_loop *loop, struct yasl_private_display *a, struct yasl_private_display *b) { int timer = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); assert(timer >= 0); struct pump p = {.loop = loop, .displays = {a, b}, .ticks = 20}; struct itimerspec spec = {.it_value.tv_nsec = 10000000, .it_interval.tv_nsec = 10000000}; assert(timerfd_settime(timer, 0, &spec, NULL) == 0); - int watch = yasl_loop_add_fd(loop, timer, POLLIN, tick, &p); + int watch = yasl_private_loop_add_fd(loop, timer, POLLIN, tick, &p); assert(watch > 0); - assert(yasl_loop_run(loop) == 0); - assert(yasl_loop_del_fd(loop, watch) == 0); + assert(yasl_private_loop_run(loop) == 0); + assert(yasl_private_loop_del_fd(loop, watch) == 0); close(timer); } -static void draw(struct yasl_surface *s, struct yasl_frame *frame, void *ud) +static void draw(struct yasl_surface *s, struct yasl_private_frame *frame, void *ud) { struct window *w = ud; assert(s == w->surface); assert(frame->surface == s); - assert(yasl_surface_display(s) == w->display); - struct yasl_vector2i size = yasl_surface_geometry(s).size; + assert(yasl_private_surface_display(s) == w->display); + struct yasl_private_vector2i size = yasl_private_surface_geometry(s).size; assert(size.x == frame->target.size.x && size.y == frame->target.size.y); assert(frame->target.pixels); - struct yasl_native_frame native = {0}; - assert(!yasl_surface_get_native_frame(s, &frame->target, &native)); + struct yasl_private_native_frame native = {0}; + assert(!yasl_private_surface_get_native_frame(s, &frame->target, &native)); for (int i = 0; i < size.x * size.y; i++) frame->target.pixels[i] = 0xff4080c0; - struct yasl_damage_list damage = {.n = YASL_DAMAGE_MAX + 1}; - assert(!yasl_surface_present(s, &frame->target, &damage)); + struct yasl_private_damage_list damage = {.n = YASL_PRIVATE_DAMAGE_MAX + 1}; + assert(!yasl_private_surface_present(s, &frame->target, &damage)); damage.n = -2; - assert(!yasl_surface_present(s, &frame->target, &damage)); + assert(!yasl_private_surface_present(s, &frame->target, &damage)); damage.n = 1; damage.rects[0].width = -1; - assert(!yasl_surface_present(s, &frame->target, &damage)); + assert(!yasl_private_surface_present(s, &frame->target, &damage)); damage.n = -1; - struct yasl_framebuffer invalid = frame->target; + struct yasl_private_framebuffer invalid = frame->target; invalid.size.x++; - assert(!yasl_surface_present(s, &invalid, &damage)); + assert(!yasl_private_surface_present(s, &invalid, &damage)); invalid = frame->target; invalid.generation--; - assert(!yasl_surface_present(s, &invalid, &damage)); + assert(!yasl_private_surface_present(s, &invalid, &damage)); invalid = frame->target; invalid.pixels = (uint32_t *)(uintptr_t)1; - assert(!yasl_surface_present(s, &invalid, &damage)); - assert(!yasl_surface_present(s, &frame->target, NULL)); - assert(yasl_surface_present(s, &frame->target, &damage)); - assert(!yasl_surface_present(s, &frame->target, &damage)); + assert(!yasl_private_surface_present(s, &invalid, &damage)); + assert(!yasl_private_surface_present(s, &frame->target, NULL)); + assert(yasl_private_surface_present(s, &frame->target, &damage)); + assert(!yasl_private_surface_present(s, &frame->target, &damage)); w->draws++; - if (w->destroy_on_draw) { yasl_surface_destroy(s); w->surface = NULL; } + if (w->destroy_on_draw) { yasl_private_surface_destroy(s); w->surface = NULL; } } -static void configure(struct yasl_surface *s, struct yasl_geometry geometry, void *ud) +static void configure(struct yasl_surface *s, struct yasl_private_geometry geometry, void *ud) { struct window *w = ud; assert(w->surface == s); - struct yasl_vector2i size = yasl_surface_geometry(s).size; + struct yasl_private_vector2i size = yasl_private_surface_geometry(s).size; assert(geometry.size.x == size.x && geometry.size.y == size.y); - assert(geometry.scale_120 == yasl_surface_geometry(s).scale_120); + assert(geometry.scale_120 == yasl_private_surface_geometry(s).scale_120); w->configures++; - if (w->destroy_on_configure) { yasl_surface_destroy(s); w->surface = NULL; } + if (w->destroy_on_configure) { yasl_private_surface_destroy(s); w->surface = NULL; } } -static void draw_without_present(struct yasl_surface *s, struct yasl_frame *frame, void *ud) +static void draw_without_present(struct yasl_surface *s, struct yasl_private_frame *frame, void *ud) { (void)s; struct window *w = ud; @@ -126,62 +126,62 @@ static void close_window(struct yasl_surface *s, void *ud) struct window *w = ud; assert(s == w->surface); w->closes++; - yasl_surface_destroy(s); + yasl_private_surface_destroy(s); w->surface = NULL; } -static const struct yasl_window_cbs callbacks = { +static const struct yasl_private_window_cbs callbacks = { .draw = draw, .configure = configure, .close = close_window, }; static void attach(struct window *w) { - struct yasl_window_cbs temporary = callbacks; - yasl_surface_set_callbacks(w->surface, &temporary, w); + struct yasl_private_window_cbs temporary = callbacks; + yasl_private_surface_set_callbacks(w->surface, &temporary, w); temporary.draw = NULL; temporary.configure = NULL; temporary.close = NULL; } -static struct window create(struct yasl_display *d) +static struct window create(struct yasl_private_display *d) { - struct yasl_window_spec spec = {.title = "Backend test", .size = {160, 100}}; - struct yasl_surface *s = yasl_surface_create_toplevel(d, &spec); + struct yasl_private_window_spec spec = {.title = "Backend test", .size = {160, 100}}; + struct yasl_surface *s = yasl_private_surface_create_toplevel(d, &spec); assert(s); - assert(yasl_display_get_native_handle(d)); - assert(yasl_surface_get_native_handle(s)); - assert(yasl_surface_display(s) == d); - assert(yasl_surface_timing(s).source == YASL_TIMING_UNKNOWN); + assert(yasl_private_display_get_native_handle(d)); + assert(yasl_private_surface_get_native_handle(s)); + assert(yasl_private_surface_display(s) == d); + assert(yasl_private_surface_timing(s).source == YASL_PRIVATE_TIMING_UNKNOWN); return (struct window){.surface = s, .display = d}; } /* The last presented frame reads back until a later draw replaces it. */ -static void test_read_pixels(struct yasl_loop *loop, struct yasl_display *d, struct window *w) +static void test_read_pixels(struct yasl_private_loop *loop, struct yasl_private_display *d, struct window *w) { - struct yasl_vector2i size = {-1, -1}; - assert(!yasl_surface_read_pixels(w->surface, NULL, 0, NULL)); - assert(!yasl_surface_read_pixels(NULL, NULL, 0, &size) && !size.x && !size.y); - assert(!yasl_surface_read_pixels(w->surface, NULL, 0, &size)); - struct yasl_vector2i extent = yasl_surface_geometry(w->surface).size; + struct yasl_private_vector2i size = {-1, -1}; + assert(!yasl_private_surface_read_pixels(w->surface, NULL, 0, NULL)); + assert(!yasl_private_surface_read_pixels(NULL, NULL, 0, &size) && !size.x && !size.y); + assert(!yasl_private_surface_read_pixels(w->surface, NULL, 0, &size)); + struct yasl_private_vector2i extent = yasl_private_surface_geometry(w->surface).size; assert(size.x == extent.x && size.y == extent.y); size_t count = (size_t)size.x * (size_t)size.y; uint32_t *pixels = calloc(count, sizeof(*pixels)); assert(pixels); - assert(!yasl_surface_read_pixels(w->surface, pixels, count - 1, &size)); + assert(!yasl_private_surface_read_pixels(w->surface, pixels, count - 1, &size)); assert(!pixels[0]); - assert(yasl_surface_read_pixels(w->surface, pixels, count, &size)); + assert(yasl_private_surface_read_pixels(w->surface, pixels, count, &size)); for (size_t i = 0; i < count; i++) assert(pixels[i] == 0xff4080c0); /* Draws that never present must not show through. X11 reuses its one * target; Wayland keeps the presented buffer while the compositor holds it. */ - struct yasl_window_cbs skip = callbacks; + struct yasl_private_window_cbs skip = callbacks; skip.draw = draw_without_present; - yasl_surface_set_callbacks(w->surface, &skip, w); + yasl_private_surface_set_callbacks(w->surface, &skip, w); for (int i = 0; i < 4; i++) { - yasl_surface_schedule_redraw(w->surface); + yasl_private_surface_schedule_redraw(w->surface); pump(loop, d, NULL); - if (yasl_surface_read_pixels(w->surface, pixels, count, &size)) { - assert(yasl_display_backend(d) != YASL_BACKEND_X11); + if (yasl_private_surface_read_pixels(w->surface, pixels, count, &size)) { + assert(yasl_private_display_backend(d) != YASL_PRIVATE_BACKEND_X11); for (size_t j = 0; j < count; j++) assert(pixels[j] == 0xff4080c0); } else { assert(!size.x && !size.y); @@ -189,47 +189,47 @@ static void test_read_pixels(struct yasl_loop *loop, struct yasl_display *d, str } attach(w); - yasl_surface_schedule_redraw(w->surface); + yasl_private_surface_schedule_redraw(w->surface); pump(loop, d, NULL); memset(pixels, 0, count * sizeof(*pixels)); - assert(yasl_surface_read_pixels(w->surface, pixels, count, &size)); + assert(yasl_private_surface_read_pixels(w->surface, pixels, count, &size)); assert(pixels[0] == 0xff4080c0 && pixels[count - 1] == 0xff4080c0); free(pixels); } -static void test_window_spec(struct yasl_loop *loop, struct yasl_display *d) +static void test_window_spec(struct yasl_private_loop *loop, struct yasl_private_display *d) { struct window w = {.display = d, .destroy_on_draw = true}; { - struct yasl_window_cbs temporary = callbacks; - struct yasl_window_spec spec = { + struct yasl_private_window_cbs temporary = callbacks; + struct yasl_private_window_spec spec = { .title = "Callbacks at creation", .size = {160, 100}, .cbs = &temporary, .ud = &w, }; - w.surface = yasl_surface_create_toplevel(d, &spec); + w.surface = yasl_private_surface_create_toplevel(d, &spec); assert(w.surface); assert(!w.draws && !w.configures && !w.closes); /* Neither the spec nor its callback table needs to outlive creation. */ memset(&temporary, 0, sizeof(temporary)); memset(&spec, 0, sizeof(spec)); } - yasl_surface_schedule_redraw(w.surface); + yasl_private_surface_schedule_redraw(w.surface); pump(loop, d, NULL); assert(w.draws == 1 && !w.surface); } -static void x11_behavior(struct yasl_loop *loop, struct yasl_display *d, struct window *w) +static void x11_behavior(struct yasl_private_loop *loop, struct yasl_private_display *d, struct window *w) { - Display *native = yasl_display_get_native_handle(d); - Window window = (Window)(uintptr_t)yasl_surface_get_native_handle(w->surface); + Display *native = yasl_private_display_get_native_handle(d); + Window window = (Window)(uintptr_t)yasl_private_surface_get_native_handle(w->surface); assert(native && window); - assert(yasl_x11_display_get_display(d) == native); - assert(yasl_x11_surface_get_window(w->surface) == (uintptr_t)window); + assert(yasl_private_x11_display_get_display(d) == native); + assert(yasl_private_x11_surface_get_window(w->surface) == (uintptr_t)window); XSync(native, False); XImage *image = XGetImage(native, window, 0, 0, 1, 1, AllPlanes, ZPixmap); assert(image && (XGetPixel(image, 0, 0) & 0xffffff) == 0x4080c0); XDestroyImage(image); - yasl_surface_set_title(w->surface, "Updated title"); + yasl_private_surface_set_title(w->surface, "Updated title"); char *title = NULL; assert(XFetchName(native, window, &title)); assert(strcmp(title, "Updated title") == 0); @@ -238,19 +238,19 @@ static void x11_behavior(struct yasl_loop *loop, struct yasl_display *d, struct XFlush(native); pump(loop, d, NULL); assert(w->configures > 0); - struct yasl_vector2i size = yasl_surface_geometry(w->surface).size; + struct yasl_private_vector2i size = yasl_private_surface_geometry(w->surface).size; assert(size.x == 200 && size.y == 120); /* A server-generated motion verifies the XI2 subscription and cookie * dispatch path, including source-device lookup, rather than SendEvent. */ - struct yasl_event pointer_event; - while (yasl_surface_poll_event(w->surface, &pointer_event)) {} + struct yasl_private_event pointer_event; + while (yasl_private_surface_poll_event(w->surface, &pointer_event)) {} XWarpPointer(native, None, window, 0, 0, 0, 0, 80, 50); XFlush(native); pump(loop, d, NULL); bool saw_motion = false; - while (yasl_surface_poll_event(w->surface, &pointer_event)) { - if (pointer_event.type == YASL_EVENT_POINTER_MOTION) { - assert(pointer_event.pointer.device == YASL_PTR_DEV_MOUSE); + while (yasl_private_surface_poll_event(w->surface, &pointer_event)) { + if (pointer_event.type == YASL_PRIVATE_EVENT_POINTER_MOTION) { + assert(pointer_event.pointer.device == YASL_PRIVATE_PTR_DEV_MOUSE); assert(!pointer_event.pointer.pen.pressure); if (pointer_event.pointer.pos.x == 80 && pointer_event.pointer.pos.y == 50) saw_motion = true; @@ -263,25 +263,25 @@ static void x11_behavior(struct yasl_loop *loop, struct yasl_display *d, struct .keycode = XKeysymToKeycode(native, XK_a)}; XSendEvent(native, window, False, KeyPressMask, &key); XFlush(native); - yasl_display_copy(d, YASL_CLIPBOARD, "clipboard text"); - yasl_display_paste(d, YASL_CLIPBOARD, w->surface); + yasl_private_display_copy(d, YASL_PRIVATE_CLIPBOARD, "clipboard text"); + yasl_private_display_paste(d, YASL_PRIVATE_CLIPBOARD, w->surface); pump(loop, d, NULL); bool saw_key = false, saw_paste = false, saw_text = false; - struct yasl_event event; - while (yasl_surface_poll_event(w->surface, &event)) { + struct yasl_private_event event; + while (yasl_private_surface_poll_event(w->surface, &event)) { assert(event.surface == w->surface); - if (event.type == YASL_EVENT_KEY) { + if (event.type == YASL_PRIVATE_EVENT_KEY) { assert(event.key.codepoint == 'a'); assert(event.key.shifted == 'A'); assert(event.key.native_code == key.xkey.keycode); saw_key = event.key.key == YASL_KEY_A; } - if (event.type == YASL_EVENT_PASTE) { + if (event.type == YASL_PRIVATE_EVENT_PASTE) { assert(event.text.len == strlen("clipboard text")); assert(memcmp(event.text.data, "clipboard text", event.text.len) == 0); saw_paste = true; } - if (event.type == YASL_EVENT_TEXT) { + if (event.type == YASL_PRIVATE_EVENT_TEXT) { assert(event.text.len == 1 && event.text.data[0] == 'a'); saw_text = true; } @@ -297,12 +297,12 @@ static void x11_behavior(struct yasl_loop *loop, struct yasl_display *d, struct XFlush(native); pump(loop, d, NULL); int key_count = 0, text_count = 0; - while (yasl_surface_poll_event(w->surface, &event)) { - if (event.type == YASL_EVENT_KEY) { + while (yasl_private_surface_poll_event(w->surface, &event)) { + if (event.type == YASL_PRIVATE_EVENT_KEY) { assert(event.key.key == YASL_KEY_A); assert(event.key.action == (key_count ? YASL_KEY_RELEASE : YASL_KEY_PRESS)); key_count++; - } else if (event.type == YASL_EVENT_TEXT) { + } else if (event.type == YASL_PRIVATE_EVENT_TEXT) { assert(event.text.len == 1 && event.text.data[0] == 'A'); text_count++; } @@ -313,10 +313,10 @@ static void x11_behavior(struct yasl_loop *loop, struct yasl_display *d, struct XSendEvent(native, window, False, KeyPressMask, &key); XFlush(native); pump(loop, d, NULL); - assert(yasl_surface_poll_event(w->surface, &event)); - assert(event.type == YASL_EVENT_KEY && event.key.key == YASL_KEY_A); - assert(event.key.mods & YASL_MOD_CTRL); - assert(!yasl_surface_poll_event(w->surface, &event)); + assert(yasl_private_surface_poll_event(w->surface, &event)); + assert(event.type == YASL_PRIVATE_EVENT_KEY && event.key.key == YASL_KEY_A); + assert(event.key.mods & YASL_PRIVATE_MOD_CTRL); + assert(!yasl_private_surface_poll_event(w->surface, &event)); int symbols_per_key = 0; KeySym *saved_symbols = XGetKeyboardMapping(native, key.xkey.keycode, 1, &symbols_per_key); @@ -333,11 +333,11 @@ static void x11_behavior(struct yasl_loop *loop, struct yasl_display *d, struct XFlush(native); pump(loop, d, NULL); saw_key = saw_text = false; - while (yasl_surface_poll_event(w->surface, &event)) { - if (event.type == YASL_EVENT_KEY) { + while (yasl_private_surface_poll_event(w->surface, &event)) { + if (event.type == YASL_PRIVATE_EVENT_KEY) { assert(event.key.key == YASL_KEY_A && event.key.codepoint == 'q'); saw_key = true; - } else if (event.type == YASL_EVENT_TEXT) { + } else if (event.type == YASL_PRIVATE_EVENT_TEXT) { assert(event.text.len == 1 && event.text.data[0] == 'q'); saw_text = true; } @@ -359,67 +359,67 @@ static void x11_behavior(struct yasl_loop *loop, struct yasl_display *d, struct XSendEvent(native, window, False, NoEventMask, &wheel); XFlush(native); pump(loop, d, NULL); - assert(yasl_surface_poll_event(w->surface, &event)); - assert(event.type == YASL_EVENT_POINTER_SCROLL); + assert(yasl_private_surface_poll_event(w->surface, &event)); + assert(event.type == YASL_PRIVATE_EVENT_POINTER_SCROLL); assert(event.pointer.scroll.x == (button == 6 ? -1 : button == 7 ? 1 : 0)); assert(event.pointer.scroll.y == (button == 4 ? -1 : button == 5 ? 1 : 0)); - assert(!yasl_surface_poll_event(w->surface, &event)); + assert(!yasl_private_surface_poll_event(w->surface, &event)); } - assert(yasl_surface_timing(w->surface).source == YASL_TIMING_SUBMISSION); - assert(!(yasl_display_caps(d) & YASL_CAP_FRAME_PACING)); + assert(yasl_private_surface_timing(w->surface).source == YASL_PRIVATE_TIMING_SUBMISSION); + assert(!(yasl_private_display_caps(d) & YASL_PRIVATE_CAP_FRAME_PACING)); /* Reuse caller storage, replace the selection, and exceed the old queue * cap before polling: each paste must retain its own original contents. */ for (int i = 0; i < 300; i++) { char text[32]; snprintf(text, sizeof(text), "owned-%d", i); - yasl_display_copy(d, YASL_CLIPBOARD, text); - yasl_display_paste(d, YASL_CLIPBOARD, w->surface); + yasl_private_display_copy(d, YASL_PRIVATE_CLIPBOARD, text); + yasl_private_display_paste(d, YASL_PRIVATE_CLIPBOARD, w->surface); memset(text, 'x', sizeof(text)); } for (int i = 0; i < 300; i++) { char text[32]; snprintf(text, sizeof(text), "owned-%d", i); - assert(yasl_surface_poll_event(w->surface, &event)); - assert(event.type == YASL_EVENT_PASTE && strcmp(event.text.data, text) == 0); + assert(yasl_private_surface_poll_event(w->surface, &event)); + assert(event.type == YASL_PRIVATE_EVENT_PASTE && strcmp(event.text.data, text) == 0); } const char *last = event.text.data; - assert(!yasl_surface_poll_event(w->surface, &event)); - yasl_display_copy(d, YASL_CLIPBOARD, "changed"); + assert(!yasl_private_surface_poll_event(w->surface, &event)); + yasl_private_display_copy(d, YASL_PRIVATE_CLIPBOARD, "changed"); pump(loop, d, NULL); assert(strcmp(last, "owned-299") == 0); - yasl_display_copy(d, YASL_CLIPBOARD, ""); - yasl_display_paste(d, YASL_CLIPBOARD, w->surface); - assert(yasl_surface_poll_event(w->surface, &event)); - assert(event.type == YASL_EVENT_PASTE && !event.text.len && !event.text.data[0]); + yasl_private_display_copy(d, YASL_PRIVATE_CLIPBOARD, ""); + yasl_private_display_paste(d, YASL_PRIVATE_CLIPBOARD, w->surface); + assert(yasl_private_surface_poll_event(w->surface, &event)); + assert(event.type == YASL_PRIVATE_EVENT_PASTE && !event.text.len && !event.text.data[0]); /* Concurrent external conversions must not share a mutable target slot. */ - struct yasl_display_spec ds = {.backend = YASL_BACKEND_X11}; - struct yasl_display *owner = yasl_display_open(loop, &ds); + struct yasl_private_display_spec ds = {.backend = YASL_PRIVATE_BACKEND_X11}; + struct yasl_private_display *owner = yasl_private_display_open(loop, &ds); assert(owner); - yasl_display_copy(owner, YASL_CLIPBOARD, "external"); + yasl_private_display_copy(owner, YASL_PRIVATE_CLIPBOARD, "external"); struct window other = create(d), cancelled = create(d); - yasl_display_paste(d, YASL_CLIPBOARD, w->surface); - yasl_display_paste(d, YASL_CLIPBOARD, other.surface); - yasl_display_paste(d, YASL_CLIPBOARD, cancelled.surface); - yasl_surface_destroy(cancelled.surface); + yasl_private_display_paste(d, YASL_PRIVATE_CLIPBOARD, w->surface); + yasl_private_display_paste(d, YASL_PRIVATE_CLIPBOARD, other.surface); + yasl_private_display_paste(d, YASL_PRIVATE_CLIPBOARD, cancelled.surface); + yasl_private_surface_destroy(cancelled.surface); pump(loop, d, owner); int first_pastes = 0, second_pastes = 0; - while (yasl_surface_poll_event(w->surface, &event)) { - if (event.type == YASL_EVENT_PASTE) { + while (yasl_private_surface_poll_event(w->surface, &event)) { + if (event.type == YASL_PRIVATE_EVENT_PASTE) { assert(strcmp(event.text.data, "external") == 0); first_pastes++; } } - while (yasl_surface_poll_event(other.surface, &event)) { - if (event.type == YASL_EVENT_PASTE) { + while (yasl_private_surface_poll_event(other.surface, &event)) { + if (event.type == YASL_PRIVATE_EVENT_PASTE) { assert(strcmp(event.text.data, "external") == 0); second_pastes++; } } assert(first_pastes == 1 && second_pastes == 1); - yasl_surface_destroy(other.surface); - yasl_display_close(owner); + yasl_private_surface_destroy(other.surface); + yasl_private_display_close(owner); key.xclient = (XClientMessageEvent){.type = ClientMessage, .display = native, .window = window, .message_type = XInternAtom(native, "WM_PROTOCOLS", False), .format = 32, .data.l = {(long)XInternAtom(native, "WM_DELETE_WINDOW", False)}}; @@ -432,64 +432,64 @@ static void x11_behavior(struct yasl_loop *loop, struct yasl_display *d, struct int main(int argc, char **argv) { assert(argc >= 2); - struct yasl_loop *loop = yasl_loop_create(); + struct yasl_private_loop *loop = yasl_private_loop_create(); assert(loop); if (strcmp(argv[1], "failure") == 0) { - struct yasl_display_spec spec = {.backend = (enum yasl_backend)atoi(argv[2])}; - assert(!yasl_display_open(loop, &spec)); + struct yasl_private_display_spec spec = {.backend = (enum yasl_private_backend)atoi(argv[2])}; + assert(!yasl_private_display_open(loop, &spec)); } else if (strcmp(argv[1], "mixed") == 0) { - struct yasl_display_spec spec = {.backend = YASL_BACKEND_WAYLAND}; - struct yasl_display *a = yasl_display_open(loop, &spec); - spec.backend = YASL_BACKEND_X11; - struct yasl_display *b = yasl_display_open(loop, &spec); + struct yasl_private_display_spec spec = {.backend = YASL_PRIVATE_BACKEND_WAYLAND}; + struct yasl_private_display *a = yasl_private_display_open(loop, &spec); + spec.backend = YASL_PRIVATE_BACKEND_X11; + struct yasl_private_display *b = yasl_private_display_open(loop, &spec); assert(a && b); test_window_spec(loop, a); test_window_spec(loop, b); struct window wa = create(a), wb = create(b); #if YASL_HAS_WAYLAND - assert(yasl_wayland_display_get_wl_display(a) != NULL); - assert(yasl_wayland_surface_get_wl_surface(wa.surface) != NULL); - assert(!yasl_x11_display_get_display(a)); + assert(yasl_private_wayland_display_get_wl_display(a) != NULL); + assert(yasl_private_wayland_surface_get_wl_surface(wa.surface) != NULL); + assert(!yasl_private_x11_display_get_display(a)); #endif #if YASL_HAS_X11 - assert(yasl_x11_display_get_display(b) != NULL); - assert(yasl_x11_surface_get_window(wb.surface) != 0); - assert(!yasl_wayland_display_get_wl_display(b)); + assert(yasl_private_x11_display_get_display(b) != NULL); + assert(yasl_private_x11_surface_get_window(wb.surface) != 0); + assert(!yasl_private_wayland_display_get_wl_display(b)); #endif attach(&wa); attach(&wb); pump(loop, a, b); assert(wa.draws && wb.draws); - yasl_surface_destroy(wa.surface); - yasl_surface_destroy(wb.surface); - yasl_display_close(a); - yasl_display_close(b); + yasl_private_surface_destroy(wa.surface); + yasl_private_surface_destroy(wb.surface); + yasl_private_display_close(a); + yasl_private_display_close(b); } else { assert(argc == 4); - struct yasl_display_spec spec = {.backend = (enum yasl_backend)atoi(argv[2])}; - struct yasl_display *d = strcmp(argv[1], "null") == 0 ? - yasl_display_open(loop, NULL) : yasl_display_open(loop, &spec); + struct yasl_private_display_spec spec = {.backend = (enum yasl_private_backend)atoi(argv[2])}; + struct yasl_private_display *d = strcmp(argv[1], "null") == 0 ? + yasl_private_display_open(loop, NULL) : yasl_private_display_open(loop, &spec); assert(d); - assert(yasl_display_backend(d) == (enum yasl_backend)atoi(argv[3])); + assert(yasl_private_display_backend(d) == (enum yasl_private_backend)atoi(argv[3])); struct window w = create(d); pump(loop, d, NULL); assert(w.draws == 0); attach(&w); - yasl_surface_schedule_redraw(w.surface); + yasl_private_surface_schedule_redraw(w.surface); pump(loop, d, NULL); assert(w.draws > 0); test_read_pixels(loop, d, &w); - if (yasl_display_backend(d) == YASL_BACKEND_X11) + if (yasl_private_display_backend(d) == YASL_PRIVATE_BACKEND_X11) x11_behavior(loop, d, &w); - if (w.surface) yasl_surface_destroy(w.surface); + if (w.surface) yasl_private_surface_destroy(w.surface); w = create(d); w.destroy_on_draw = true; attach(&w); pump(loop, d, NULL); assert(w.draws && !w.surface); - yasl_display_close(d); + yasl_private_display_close(d); } - yasl_loop_destroy(loop); + yasl_private_loop_destroy(loop); puts("Backend test passed."); return 0; } diff --git a/tests/common/test_contracts.c b/tests/common/test_contracts.c index d1964ff..be84586 100644 --- a/tests/common/test_contracts.c +++ b/tests/common/test_contracts.c @@ -13,17 +13,17 @@ static void test_timing_clock(void) { - struct yasl_frame_clock clock = {.refresh_ns = YASL_REFRESH_DEFAULT_NS}; - yasl_clock_presented(&clock, 1000000000, true); + struct yasl_private_frame_clock clock = {.refresh_ns = YASL_PRIVATE_REFRESH_DEFAULT_NS}; + yasl_private_clock_presented(&clock, 1000000000, true); assert(clock.last_present_ns == 1000000000); - uint64_t next = yasl_clock_next_present_ns(&clock); + uint64_t next = yasl_private_clock_next_present_ns(&clock); assert(next > 1000000000); - assert(yasl_clock_extend_ms(1000, 1000000000) == 1000000000); - assert(yasl_clock_frame_timestamp_ns(&clock, next) == next); + assert(yasl_private_clock_extend_ms(1000, 1000000000) == 1000000000); + assert(yasl_private_clock_frame_timestamp_ns(&clock, next) == next); - yasl_clock_note_resize(&clock, 500000); - struct yasl_resize_stats stats; - yasl_clock_resize_stats(&clock, &stats); + yasl_private_clock_note_resize(&clock, 500000); + struct yasl_private_resize_stats stats; + yasl_private_clock_resize_stats(&clock, &stats); assert(stats.count == 1); assert(stats.max_us == 500.0); } @@ -31,27 +31,27 @@ static void test_timing_clock(void) 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)); + struct yasl_surface surface = {.state = YASL_PRIVATE_STATE_FULLSCREEN | YASL_PRIVATE_STATE_ACTIVATED}; + assert(!yasl_private_surface_state(NULL)); + assert(yasl_private_surface_state(&surface) == (YASL_PRIVATE_STATE_FULLSCREEN | YASL_PRIVATE_STATE_ACTIVATED)); } static void test_queue(void) { struct input_queue q = {0}; struct yasl_surface surface = {0}; - const enum yasl_event_type kinds[] = { - YASL_EVENT_TEXT, YASL_EVENT_PASTE, YASL_EVENT_DROP, + const enum yasl_private_event_type kinds[] = { + YASL_PRIVATE_EVENT_TEXT, YASL_PRIVATE_EVENT_PASTE, YASL_PRIVATE_EVENT_DROP, }; for (size_t k = 0; k < sizeof(kinds) / sizeof(kinds[0]); k++) { char *source = malloc(4); assert(source); memcpy(source, "a\0b", 4); - struct yasl_event in = {.surface = &surface, .type = kinds[k], .text = {.data = source, .len = 3}}; + struct yasl_private_event in = {.surface = &surface, .type = kinds[k], .text = {.data = source, .len = 3}}; assert(input_queue_push(&q, &in)); memset(source, 'x', 4); free(source); - struct yasl_event out; + struct yasl_private_event out; assert(input_queue_pop(&q, &out)); const char *payload = out.text.data; assert(out.surface == &surface && out.type == kinds[k]); @@ -60,7 +60,7 @@ static void test_queue(void) assert(memcmp(payload, "a\0b", 4) == 0); /* Growth/wraparound must not invalidate a previously polled payload. */ for (unsigned i = 0; i < 4096; i++) { - in = (struct yasl_event){.type = YASL_EVENT_KEY, .key.key = i}; + in = (struct yasl_private_event){.type = YASL_PRIVATE_EVENT_KEY, .key.key = i}; assert(input_queue_push(&q, &in)); } assert(memcmp(payload, "a\0b", 4) == 0); @@ -78,12 +78,12 @@ static void test_queue(void) } assert(!input_queue_pop(&q, &out)); } - struct yasl_event empty = {.surface = &surface, .type = YASL_EVENT_PASTE}; - struct yasl_event out; + struct yasl_private_event empty = {.surface = &surface, .type = YASL_PRIVATE_EVENT_PASTE}; + struct yasl_private_event out; assert(input_queue_push(&q, &empty)); assert(input_queue_pop(&q, &out)); assert(out.text.data && !out.text.len && out.text.data[0] == '\0'); - struct yasl_event in = {.surface = &surface, .type = YASL_EVENT_TEXT, + struct yasl_private_event in = {.surface = &surface, .type = YASL_PRIVATE_EVENT_TEXT, .text = {"owned", 5}}; assert(input_queue_push(&q, &in)); assert(input_queue_pop(&q, &out)); @@ -93,13 +93,13 @@ static void test_queue(void) in.text.len = SIZE_MAX; assert(!input_queue_push(&q, &in)); assert(strcmp(last, "owned") == 0); - in = (struct yasl_event){.surface = &surface, .type = YASL_EVENT_KEY}; + in = (struct yasl_private_event){.surface = &surface, .type = YASL_PRIVATE_EVENT_KEY}; assert(input_queue_push(&q, &in)); - assert(input_queue_pop(&q, &out) && out.type == YASL_EVENT_INPUT_RESET); + assert(input_queue_pop(&q, &out) && out.type == YASL_PRIVATE_EVENT_INPUT_RESET); assert(out.surface == &surface); - assert(input_queue_pop(&q, &out) && out.type == YASL_EVENT_KEY); + assert(input_queue_pop(&q, &out) && out.type == YASL_PRIVATE_EVENT_KEY); assert(!input_queue_pop(&q, &out)); - in = (struct yasl_event){.type = YASL_EVENT_PASTE, .text = {"pending", 7}}; + in = (struct yasl_private_event){.type = YASL_PRIVATE_EVENT_PASTE, .text = {"pending", 7}}; assert(input_queue_push(&q, &in)); input_queue_clear(&q); input_queue_clear(&q); @@ -107,49 +107,49 @@ static void test_queue(void) } static int presents, copies, pastes, creates; -static struct yasl_damage_list received; -static bool present(struct yasl_surface *s, const struct yasl_framebuffer *fb, - const struct yasl_damage_list *damage) +static struct yasl_private_damage_list received; +static bool present(struct yasl_surface *s, const struct yasl_private_framebuffer *fb, + const struct yasl_private_damage_list *damage) { (void)s; (void)fb; presents++; received = *damage; return true; } -static uint32_t caps(const struct yasl_display *d) { (void)d; return 0; } -static void copy(struct yasl_display *d, enum yasl_selection sel, const char *text) +static uint32_t caps(const struct yasl_private_display *d) { (void)d; return 0; } +static void copy(struct yasl_private_display *d, enum yasl_private_selection sel, const char *text) { (void)d; (void)sel; (void)text; copies++; } -static void paste(struct yasl_display *d, enum yasl_selection sel, struct yasl_surface *s) +static void paste(struct yasl_private_display *d, enum yasl_private_selection sel, struct yasl_surface *s) { (void)d; (void)sel; (void)s; pastes++; } -static struct yasl_surface *create(struct yasl_display *d, const struct yasl_window_spec *spec, void *graphics_device) +static struct yasl_surface *create(struct yasl_private_display *d, const struct yasl_private_window_spec *spec, void *graphics_device) { (void)d; (void)spec; (void)graphics_device; creates++; return NULL; } static void *native_device; static int native_queries; static struct yasl_surface native_surface; -static uint32_t native_caps(const struct yasl_display *d) -{ (void)d; return YASL_CAP_NATIVE_GRAPHICS; } -static struct yasl_surface *native_create(struct yasl_display *d, - const struct yasl_window_spec *spec, void *device) +static uint32_t native_caps(const struct yasl_private_display *d) +{ (void)d; return YASL_PRIVATE_CAP_NATIVE_GRAPHICS; } +static struct yasl_surface *native_create(struct yasl_private_display *d, + const struct yasl_private_window_spec *spec, void *device) { (void)spec; native_device = device; native_surface = (struct yasl_surface){.display = d, .frame_generation = 1}; return &native_surface; } -static bool native_frame(struct yasl_surface *s, const struct yasl_framebuffer *fb, - struct yasl_native_frame *out) +static bool native_frame(struct yasl_surface *s, const struct yasl_private_framebuffer *fb, + struct yasl_private_native_frame *out) { (void)s; (void)fb; native_queries++; - *out = (struct yasl_native_frame){.drawable = native_device}; + *out = (struct yasl_private_native_frame){.drawable = native_device}; return true; } -static uint32_t push_buttons(struct input_queue *q, enum yasl_event_type type, - enum yasl_pointer_button button) +static uint32_t push_buttons(struct input_queue *q, enum yasl_private_event_type type, + enum yasl_private_pointer_button button) { - struct yasl_event ev = {.type = type, .pointer.button = button}; + struct yasl_private_event ev = {.type = type, .pointer.button = button}; assert(input_queue_push(q, &ev)); assert(input_queue_pop(q, &ev)); return ev.pointer.buttons; @@ -157,31 +157,31 @@ static uint32_t push_buttons(struct input_queue *q, enum yasl_event_type type, static void test_held_buttons(void) { - const uint32_t primary = YASL_BUTTON_BIT(YASL_BUTTON_PRIMARY); - const uint32_t secondary = YASL_BUTTON_BIT(YASL_BUTTON_SECONDARY); + const uint32_t primary = YASL_PRIVATE_BUTTON_BIT(YASL_PRIVATE_BUTTON_PRIMARY); + const uint32_t secondary = YASL_PRIVATE_BUTTON_BIT(YASL_PRIVATE_BUTTON_SECONDARY); struct input_queue q = {0}; - assert(push_buttons(&q, YASL_EVENT_POINTER_MOTION, 0) == 0); - assert(push_buttons(&q, YASL_EVENT_POINTER_DOWN, YASL_BUTTON_PRIMARY) == primary); - assert(push_buttons(&q, YASL_EVENT_POINTER_MOTION, 0) == primary); - assert(push_buttons(&q, YASL_EVENT_POINTER_DOWN, YASL_BUTTON_SECONDARY) == (primary | secondary)); - assert(push_buttons(&q, YASL_EVENT_POINTER_UP, YASL_BUTTON_PRIMARY) == secondary); - assert(push_buttons(&q, YASL_EVENT_POINTER_SCROLL, 0) == secondary); + assert(push_buttons(&q, YASL_PRIVATE_EVENT_POINTER_MOTION, 0) == 0); + assert(push_buttons(&q, YASL_PRIVATE_EVENT_POINTER_DOWN, YASL_PRIVATE_BUTTON_PRIMARY) == primary); + assert(push_buttons(&q, YASL_PRIVATE_EVENT_POINTER_MOTION, 0) == primary); + assert(push_buttons(&q, YASL_PRIVATE_EVENT_POINTER_DOWN, YASL_PRIVATE_BUTTON_SECONDARY) == (primary | secondary)); + assert(push_buttons(&q, YASL_PRIVATE_EVENT_POINTER_UP, YASL_PRIVATE_BUTTON_PRIMARY) == secondary); + assert(push_buttons(&q, YASL_PRIVATE_EVENT_POINTER_SCROLL, 0) == secondary); /* X11 and Cocoa keep the press across leave; the release still follows. */ - assert(push_buttons(&q, YASL_EVENT_POINTER_LEAVE, 0) == secondary); - assert(push_buttons(&q, YASL_EVENT_POINTER_UP, YASL_BUTTON_SECONDARY) == 0); + assert(push_buttons(&q, YASL_PRIVATE_EVENT_POINTER_LEAVE, 0) == secondary); + assert(push_buttons(&q, YASL_PRIVATE_EVENT_POINTER_UP, YASL_PRIVATE_BUTTON_SECONDARY) == 0); /* A release without a press leaves nothing held. */ - assert(push_buttons(&q, YASL_EVENT_POINTER_UP, YASL_BUTTON_MIDDLE) == 0); + assert(push_buttons(&q, YASL_PRIVATE_EVENT_POINTER_UP, YASL_PRIVATE_BUTTON_MIDDLE) == 0); - assert(push_buttons(&q, YASL_EVENT_POINTER_DOWN, YASL_BUTTON_PRIMARY) == primary); - struct yasl_event reset = {.type = YASL_EVENT_INPUT_RESET}; + assert(push_buttons(&q, YASL_PRIVATE_EVENT_POINTER_DOWN, YASL_PRIVATE_BUTTON_PRIMARY) == primary); + struct yasl_private_event reset = {.type = YASL_PRIVATE_EVENT_INPUT_RESET}; assert(input_queue_push(&q, &reset)); assert(input_queue_pop(&q, &reset)); - assert(push_buttons(&q, YASL_EVENT_POINTER_MOTION, 0) == 0); + assert(push_buttons(&q, YASL_PRIVATE_EVENT_POINTER_MOTION, 0) == 0); q.leave_ends_press = true; - assert(push_buttons(&q, YASL_EVENT_POINTER_DOWN, YASL_BUTTON_PRIMARY) == primary); - assert(push_buttons(&q, YASL_EVENT_POINTER_LEAVE, 0) == 0); - assert(push_buttons(&q, YASL_EVENT_POINTER_MOTION, 0) == 0); + assert(push_buttons(&q, YASL_PRIVATE_EVENT_POINTER_DOWN, YASL_PRIVATE_BUTTON_PRIMARY) == primary); + assert(push_buttons(&q, YASL_PRIVATE_EVENT_POINTER_LEAVE, 0) == 0); + assert(push_buttons(&q, YASL_PRIVATE_EVENT_POINTER_MOTION, 0) == 0); input_queue_clear(&q); } @@ -216,117 +216,117 @@ static void test_drop_paths(void) /* A short buffer reports the length and leaves both outputs alone. */ const char *before = cursor; memcpy(path, "keep", 5); - assert(yasl_drop_next_path(&cursor, path, len) == len); + assert(yasl_private_drop_next_path(&cursor, path, len) == len); assert(cursor == before && strcmp(path, "keep") == 0); - assert(yasl_drop_next_path(&cursor, NULL, 0) == len && cursor == before); - assert(yasl_drop_next_path(&cursor, path, sizeof(path)) == len); + assert(yasl_private_drop_next_path(&cursor, NULL, 0) == len && cursor == before); + assert(yasl_private_drop_next_path(&cursor, path, sizeof(path)) == len); assert(strcmp(path, want[i]) == 0); } - assert(yasl_drop_next_path(&cursor, path, sizeof(path)) == 0 && *cursor == '\0'); - assert(yasl_drop_next_path(&cursor, path, sizeof(path)) == 0); - assert(yasl_drop_next_path(NULL, path, sizeof(path)) == 0); + assert(yasl_private_drop_next_path(&cursor, path, sizeof(path)) == 0 && *cursor == '\0'); + assert(yasl_private_drop_next_path(&cursor, path, sizeof(path)) == 0); + assert(yasl_private_drop_next_path(NULL, path, sizeof(path)) == 0); const char *none = NULL; - assert(yasl_drop_next_path(&none, path, sizeof(path)) == 0); + assert(yasl_private_drop_next_path(&none, path, sizeof(path)) == 0); cursor = list; - assert(yasl_drop_next_path(&cursor, NULL, 8) == 0 && cursor == list); + assert(yasl_private_drop_next_path(&cursor, NULL, 8) == 0 && cursor == list); } static void test_native(void) { - const struct yasl_backend_ops ops = {.display_caps = native_caps, + const struct yasl_private_backend_ops ops = {.display_caps = native_caps, .surface_create_toplevel = native_create, .surface_get_native_frame = native_frame}; - struct yasl_display d = {.ops = &ops}; - struct yasl_window_spec spec = {.size = {100, 80}}; - assert(yasl_surface_create_toplevel(&d, &spec) == &native_surface); + struct yasl_private_display d = {.ops = &ops}; + struct yasl_private_window_spec spec = {.size = {100, 80}}; + assert(yasl_private_surface_create_toplevel(&d, &spec) == &native_surface); assert(!native_device); - assert(yasl_surface_create_native(&d, &spec, &d) == &native_surface); + assert(yasl_private_surface_create_native(&d, &spec, &d) == &native_surface); assert(native_device == &d); - struct yasl_framebuffer fb = {.generation = 1, .size = {100, 80}}; - struct yasl_native_frame out = {0}; - assert(!yasl_surface_get_native_frame(NULL, &fb, &out)); - assert(!yasl_surface_get_native_frame(&native_surface, NULL, &out)); - assert(!yasl_surface_get_native_frame(&native_surface, &fb, NULL)); + struct yasl_private_framebuffer fb = {.generation = 1, .size = {100, 80}}; + struct yasl_private_native_frame out = {0}; + assert(!yasl_private_surface_get_native_frame(NULL, &fb, &out)); + assert(!yasl_private_surface_get_native_frame(&native_surface, NULL, &out)); + assert(!yasl_private_surface_get_native_frame(&native_surface, &fb, NULL)); fb.generation = 0; - assert(!yasl_surface_get_native_frame(&native_surface, &fb, &out)); + assert(!yasl_private_surface_get_native_frame(&native_surface, &fb, &out)); fb.generation = 2; - assert(!yasl_surface_get_native_frame(&native_surface, &fb, &out)); + assert(!yasl_private_surface_get_native_frame(&native_surface, &fb, &out)); assert(!native_queries && !out.drawable); fb.generation = 1; - assert(yasl_surface_get_native_frame(&native_surface, &fb, &out)); + assert(yasl_private_surface_get_native_frame(&native_surface, &fb, &out)); assert(native_queries == 1 && out.drawable == &d); } static void test_validation(void) { - const struct yasl_backend_ops ops = {.surface_present = present, + const struct yasl_private_backend_ops ops = {.surface_present = present, .display_caps = caps, .display_copy = copy, .display_paste = paste, .surface_create_toplevel = create}; - struct yasl_display d = {.ops = &ops}; + struct yasl_private_display d = {.ops = &ops}; struct yasl_surface s = {.display = &d, .frame_generation = 1}; - struct yasl_framebuffer fb = {.generation = 1, .size = {100, 80}}; - struct yasl_damage_list damage = {.n = -2}; - assert(!yasl_surface_present(&s, &fb, &damage)); - damage.n = YASL_DAMAGE_MAX + 1; - assert(!yasl_surface_present(&s, &fb, &damage)); + struct yasl_private_framebuffer fb = {.generation = 1, .size = {100, 80}}; + struct yasl_private_damage_list damage = {.n = -2}; + assert(!yasl_private_surface_present(&s, &fb, &damage)); + damage.n = YASL_PRIVATE_DAMAGE_MAX + 1; + assert(!yasl_private_surface_present(&s, &fb, &damage)); damage.n = 1; - damage.rects[0] = (struct yasl_rect2i){0, 0, -1, 1}; - assert(!yasl_surface_present(&s, &fb, &damage)); - assert(!yasl_surface_present(&s, &fb, NULL)); + damage.rects[0] = (struct yasl_private_rect2i){0, 0, -1, 1}; + assert(!yasl_private_surface_present(&s, &fb, &damage)); + assert(!yasl_private_surface_present(&s, &fb, NULL)); assert(!presents); damage.n = -1; fb.generation = 0; - assert(!yasl_surface_present(&s, &fb, &damage)); + assert(!yasl_private_surface_present(&s, &fb, &damage)); fb.generation = 2; - assert(!yasl_surface_present(&s, &fb, &damage)); + assert(!yasl_private_surface_present(&s, &fb, &damage)); fb.generation = 1; damage.n = 4; - damage.rects[0] = (struct yasl_rect2i){-10, -10, 30, 30}; - damage.rects[1] = (struct yasl_rect2i){90, 70, INT_MAX, INT_MAX}; - damage.rects[2] = (struct yasl_rect2i){INT_MAX, INT_MAX, INT_MAX, INT_MAX}; - damage.rects[3] = (struct yasl_rect2i){INT_MIN, INT_MIN, INT_MAX, INT_MAX}; - assert(yasl_surface_present(&s, &fb, &damage)); + damage.rects[0] = (struct yasl_private_rect2i){-10, -10, 30, 30}; + damage.rects[1] = (struct yasl_private_rect2i){90, 70, INT_MAX, INT_MAX}; + damage.rects[2] = (struct yasl_private_rect2i){INT_MAX, INT_MAX, INT_MAX, INT_MAX}; + damage.rects[3] = (struct yasl_private_rect2i){INT_MIN, INT_MIN, INT_MAX, INT_MAX}; + assert(yasl_private_surface_present(&s, &fb, &damage)); assert(received.n == 2 && received.rects[0].x == 0 && received.rects[0].width == 20); assert(received.rects[1].width == 10 && received.rects[1].height == 10); assert(damage.n == 4 && damage.rects[0].x == -10); damage.n = -1; - assert(yasl_surface_present(&s, &fb, &damage) && received.n == -1); + assert(yasl_private_surface_present(&s, &fb, &damage) && received.n == -1); damage.n = 0; - assert(yasl_surface_present(&s, &fb, &damage) && received.n == 0); - yasl_display_copy(&d, YASL_PRIMARY, "no alias"); - yasl_display_copy(&d, (enum yasl_selection)255, "invalid"); - yasl_display_paste(&d, YASL_PRIMARY, &s); - yasl_display_paste(&d, (enum yasl_selection)255, &s); + assert(yasl_private_surface_present(&s, &fb, &damage) && received.n == 0); + yasl_private_display_copy(&d, YASL_PRIVATE_PRIMARY, "no alias"); + yasl_private_display_copy(&d, (enum yasl_private_selection)255, "invalid"); + yasl_private_display_paste(&d, YASL_PRIVATE_PRIMARY, &s); + yasl_private_display_paste(&d, (enum yasl_private_selection)255, &s); assert(!copies && !pastes); - yasl_display_copy(&d, YASL_CLIPBOARD, "yes"); - yasl_display_paste(&d, YASL_CLIPBOARD, &s); + yasl_private_display_copy(&d, YASL_PRIVATE_CLIPBOARD, "yes"); + yasl_private_display_paste(&d, YASL_PRIVATE_CLIPBOARD, &s); assert(copies == 1 && pastes == 1); - struct yasl_window_spec spec = {.size = {100, 80}}; - assert(!yasl_surface_create_native(&d, &spec, &d) && errno == ENOTSUP); - assert(!yasl_surface_create_native(&d, &spec, NULL) && errno == EINVAL); + struct yasl_private_window_spec spec = {.size = {100, 80}}; + assert(!yasl_private_surface_create_native(&d, &spec, &d) && errno == ENOTSUP); + assert(!yasl_private_surface_create_native(&d, &spec, NULL) && errno == EINVAL); spec.size.x = 0; - assert(!yasl_surface_create_toplevel(&d, &spec) && errno == EINVAL); + assert(!yasl_private_surface_create_toplevel(&d, &spec) && errno == EINVAL); assert(!creates); } static void test_scale(void) { - assert(yasl_scale_to_device(-10, 120) == -10); - assert(yasl_scale_to_device(-0.5, 120) == -1); - assert(yasl_scale_to_device(0.5, 120) == 1); - assert(yasl_scale_to_device(NAN, 120) == 0); - assert(yasl_scale_to_device(INFINITY, 120) == 0); - assert(yasl_scale_to_device(DBL_MAX, 240) == INT_MAX); - assert(yasl_scale_to_logical(-1, 240) == -1); - assert(yasl_scale_to_logical(1, 240) == 1); - assert(yasl_scale_to_logical(INT_MAX, 1) == INT_MAX); - assert(yasl_scale_to_logical(INT_MIN, 1) == INT_MIN); - assert(yasl_scale_to_logical(1, 0) == 0); + assert(yasl_private_scale_to_device(-10, 120) == -10); + assert(yasl_private_scale_to_device(-0.5, 120) == -1); + assert(yasl_private_scale_to_device(0.5, 120) == 1); + assert(yasl_private_scale_to_device(NAN, 120) == 0); + assert(yasl_private_scale_to_device(INFINITY, 120) == 0); + assert(yasl_private_scale_to_device(DBL_MAX, 240) == INT_MAX); + assert(yasl_private_scale_to_logical(-1, 240) == -1); + assert(yasl_private_scale_to_logical(1, 240) == 1); + assert(yasl_private_scale_to_logical(INT_MAX, 1) == INT_MAX); + assert(yasl_private_scale_to_logical(INT_MIN, 1) == INT_MIN); + assert(yasl_private_scale_to_logical(1, 0) == 0); } static int popup_calls; static struct yasl_surface *popup_create(struct yasl_surface *parent, - const struct yasl_popup_spec *spec) + const struct yasl_private_popup_spec *spec) { (void)parent; (void)spec; popup_calls++; @@ -334,7 +334,7 @@ static struct yasl_surface *popup_create(struct yasl_surface *parent, } static bool popup_reposition(struct yasl_surface *popup, - const struct yasl_popup_spec *spec) + const struct yasl_private_popup_spec *spec) { (void)popup; (void)spec; popup_calls++; @@ -342,19 +342,19 @@ static bool popup_reposition(struct yasl_surface *popup, return false; } -static uint32_t popup_caps(const struct yasl_display *d) -{ (void)d; return YASL_CAP_POPUPS; } +static uint32_t popup_caps(const struct yasl_private_display *d) +{ (void)d; return YASL_PRIVATE_CAP_POPUPS; } -static int dispatch_failure(struct yasl_display *d, int timeout) +static int dispatch_failure(struct yasl_private_display *d, int timeout) { (void)d; (void)timeout; return -1; } -static struct yasl_surface *create_failure(struct yasl_display *d, - const struct yasl_window_spec *spec, void *device) +static struct yasl_surface *create_failure(struct yasl_private_display *d, + const struct yasl_private_window_spec *spec, void *device) { (void)d; (void)spec; (void)device; return NULL; } static int drag_starts; -static bool drag_without_listener(struct yasl_display *d, struct yasl_surface *s, - const struct yasl_drag_image *image, const struct yasl_drag_listener *listener, +static bool drag_without_listener(struct yasl_private_display *d, struct yasl_surface *s, + const struct yasl_private_drag_image *image, const struct yasl_private_drag_listener *listener, void *ud) { (void)d; (void)s; (void)image; (void)ud; @@ -366,52 +366,52 @@ static bool drag_without_listener(struct yasl_display *d, struct yasl_surface *s static void test_errors(void) { - const struct yasl_backend_ops ops = {.display_caps = popup_caps, + const struct yasl_private_backend_ops ops = {.display_caps = popup_caps, .surface_create_toplevel = create_failure, .surface_create_popup = popup_create, .surface_popup_reposition = popup_reposition, .display_drag_start = drag_without_listener, .display_dispatch_timeout = dispatch_failure}; - struct yasl_display d = {.ops = &ops}; + struct yasl_private_display d = {.ops = &ops}; struct yasl_surface s = {.display = &d}; - struct yasl_popup_spec spec = {.size = {120, 80}, .anchor = {0, 0, 1, 1}}; - assert(!yasl_surface_create_popup(NULL, &spec) && errno == EINVAL); - assert(!yasl_surface_create_popup(&s, NULL) && errno == EINVAL); + struct yasl_private_popup_spec spec = {.size = {120, 80}, .anchor = {0, 0, 1, 1}}; + assert(!yasl_private_surface_create_popup(NULL, &spec) && errno == EINVAL); + assert(!yasl_private_surface_create_popup(&s, NULL) && errno == EINVAL); spec.anchor.width = -1; /* Must not reach Wayland's protocol marshaler. */ - assert(!yasl_surface_create_popup(&s, &spec) && errno == EINVAL); - assert(!yasl_surface_popup_reposition(&s, &spec) && errno == EINVAL); + assert(!yasl_private_surface_create_popup(&s, &spec) && errno == EINVAL); + assert(!yasl_private_surface_popup_reposition(&s, &spec) && errno == EINVAL); spec.anchor.width = 1; spec.content_offset.x = INT_MAX; spec.content_size.x = INT_MAX; - assert(!yasl_surface_create_popup(&s, &spec) && errno == EINVAL); + assert(!yasl_private_surface_create_popup(&s, &spec) && errno == EINVAL); spec.content_offset.x = spec.content_size.x = 0; spec.size.x = 32768; - assert(!yasl_surface_create_popup(&s, &spec) && errno == EINVAL); + assert(!yasl_private_surface_create_popup(&s, &spec) && errno == EINVAL); assert(!popup_calls); spec.size.x = 120; errno = ERANGE; - assert(!yasl_surface_create_popup(&s, &spec) && errno == ENODEV); - assert(!yasl_surface_popup_reposition(&s, &spec) && errno == EBUSY); + assert(!yasl_private_surface_create_popup(&s, &spec) && errno == ENODEV); + assert(!yasl_private_surface_popup_reposition(&s, &spec) && errno == EBUSY); assert(popup_calls == 2); - const struct yasl_backend_ops unsupported = {0}; + const struct yasl_private_backend_ops unsupported = {0}; d.ops = &unsupported; - assert(!yasl_surface_create_popup(&s, &spec) && errno == ENOTSUP); - assert(!yasl_surface_popup_reposition(&s, &spec) && errno == ENOTSUP); - assert(yasl_display_dispatch(&d, 0) == -1 && errno == ENOTSUP); - assert(yasl_display_dispatch(NULL, 0) == -1 && errno == EINVAL); + assert(!yasl_private_surface_create_popup(&s, &spec) && errno == ENOTSUP); + assert(!yasl_private_surface_popup_reposition(&s, &spec) && errno == ENOTSUP); + assert(yasl_private_display_dispatch(&d, 0) == -1 && errno == ENOTSUP); + assert(yasl_private_display_dispatch(NULL, 0) == -1 && errno == EINVAL); d.ops = &ops; errno = ERANGE; - assert(yasl_display_dispatch(&d, 0) == -1 && errno == EIO); - struct yasl_window_spec toplevel = {.size = {120, 80}}; + assert(yasl_private_display_dispatch(&d, 0) == -1 && errno == EIO); + struct yasl_private_window_spec toplevel = {.size = {120, 80}}; errno = ERANGE; - assert(!yasl_surface_create_toplevel(&d, &toplevel) && errno == ENODEV); - assert(yasl_display_drag_start(&d, &s, NULL, NULL, NULL)); - struct yasl_drag_image image = {.size = {1, 1}}; - assert(!yasl_display_drag_start(&d, &s, &image, NULL, NULL)); + assert(!yasl_private_surface_create_toplevel(&d, &toplevel) && errno == ENODEV); + assert(yasl_private_display_drag_start(&d, &s, NULL, NULL, NULL)); + struct yasl_private_drag_image image = {.size = {1, 1}}; + assert(!yasl_private_display_drag_start(&d, &s, &image, NULL, NULL)); uint32_t pixel = 0; image.pixels = &pixel; image.size.x = INT_MAX; - assert(!yasl_display_drag_start(&d, &s, &image, NULL, NULL)); + assert(!yasl_private_display_drag_start(&d, &s, &image, NULL, NULL)); assert(drag_starts == 1); } diff --git a/tests/common/test_desktop.c b/tests/common/test_desktop.c index 89760fe..a665da5 100644 --- a/tests/common/test_desktop.c +++ b/tests/common/test_desktop.c @@ -1,7 +1,7 @@ #define _GNU_SOURCE #include "common/backend.h" #include "platform/freedesktop/dbus.h" -#include "yasl/desktop.h" +#include "common/private_desktop.h" #include <assert.h> #include <fcntl.h> @@ -39,31 +39,31 @@ static char *read_file(const char *path) static void test_buffers(void) { char exe[PATH_MAX], real[PATH_MAX]; - size_t len = yasl_executable_path(NULL, 0); + size_t len = yasl_private_executable_path(NULL, 0); assert(len > 0); - assert(yasl_executable_path(exe, len) == len); - assert(yasl_executable_path(exe, sizeof(exe)) == len && exe[len] == '\0'); + assert(yasl_private_executable_path(exe, len) == len); + assert(yasl_private_executable_path(exe, sizeof(exe)) == len && exe[len] == '\0'); assert(realpath("/proc/self/exe", real) && strcmp(exe, real) == 0); /* A short buffer stays untouched. */ char small[4] = "abc"; - assert(yasl_executable_path(small, sizeof(small)) == len); + assert(yasl_private_executable_path(small, sizeof(small)) == len); assert(strcmp(small, "abc") == 0); - assert(yasl_executable_path(NULL, 8) == 0); + assert(yasl_private_executable_path(NULL, 8) == 0); - assert(yasl_resource_dir(exe, sizeof(exe)) == 0); + assert(yasl_private_resource_dir(exe, sizeof(exe)) == 0); char locale[64]; setenv("LC_ALL", "", 1); setenv("LC_CTYPE", "de_DE.UTF-8", 1); setenv("LANG", "fr_FR.UTF-8", 1); - assert(yasl_default_locale(locale, sizeof(locale)) == 11); + assert(yasl_private_default_locale(locale, sizeof(locale)) == 11); assert(strcmp(locale, "de_DE.UTF-8") == 0); unsetenv("LC_CTYPE"); - assert(yasl_default_locale(locale, sizeof(locale)) == 11); + assert(yasl_private_default_locale(locale, sizeof(locale)) == 11); assert(strcmp(locale, "fr_FR.UTF-8") == 0); unsetenv("LANG"); unsetenv("LC_ALL"); - assert(yasl_default_locale(locale, sizeof(locale)) == 7); + assert(yasl_private_default_locale(locale, sizeof(locale)) == 7); assert(strcmp(locale, "C.UTF-8") == 0); } @@ -86,14 +86,14 @@ static void test_open_uri(const char *dir) snprintf(path, sizeof(path), "%s:%s", dir, old_path ? old_path : "/usr/bin:/bin"); setenv("PATH", path, 1); - struct yasl_display display = {0}; - assert(!yasl_display_open_uri(NULL, "https://example.com")); - assert(!yasl_display_open_uri(&display, NULL)); - assert(!yasl_display_open_uri(&display, "")); - assert(!yasl_display_open_uri(&display, "example.com")); - assert(!yasl_display_open_uri(&display, "-x:option")); - assert(!yasl_display_open_uri(&display, "1http://example.com")); - assert(!yasl_display_open_uri(&display, "http://example.com/\n")); + struct yasl_private_display display = {0}; + assert(!yasl_private_display_open_uri(NULL, "https://example.com")); + assert(!yasl_private_display_open_uri(&display, NULL)); + assert(!yasl_private_display_open_uri(&display, "")); + assert(!yasl_private_display_open_uri(&display, "example.com")); + assert(!yasl_private_display_open_uri(&display, "-x:option")); + assert(!yasl_private_display_open_uri(&display, "1http://example.com")); + assert(!yasl_private_display_open_uri(&display, "http://example.com/\n")); assert(access(out, F_OK) != 0); /* The opener must not inherit blocked or ignored signals or descriptors. */ @@ -110,7 +110,7 @@ static void test_open_uri(const char *dir) close(null); const char *uri = "https://example.com/a b?c='d'&e=\"f\""; - assert(yasl_display_open_uri(&display, uri)); + assert(yasl_private_display_open_uri(&display, uri)); char *text = NULL; for (int i = 0; i < 500 && !(text = read_file(out)); i++) sleep_ms(10); assert(text); @@ -137,29 +137,29 @@ static void test_open_uri(const char *dir) /* Without xdg-open in PATH, nothing launches. */ setenv("PATH", dir, 1); - assert(!yasl_display_open_uri(&display, "https://example.com")); + assert(!yasl_private_display_open_uri(&display, "https://example.com")); if (old_path) setenv("PATH", old_path, 1); } static void test_appearance_fallback(void) { - assert(yasl_display_double_click_ms(NULL) == YASL_DOUBLE_CLICK_MS_DEFAULT); + assert(yasl_private_display_double_click_ms(NULL) == YASL_PRIVATE_DOUBLE_CLICK_MS_DEFAULT); assert(portal_double_click_ms() == 0); - struct yasl_display display = {0}; - assert(yasl_display_appearance(NULL) == 0); + struct yasl_private_display display = {0}; + assert(yasl_private_display_appearance(NULL) == 0); unsetenv("GTK_THEME"); - assert(yasl_display_appearance(&display) == 0); + assert(yasl_private_display_appearance(&display) == 0); setenv("GTK_THEME", "Adwaita:dark", 1); - assert(yasl_display_appearance(&display) == YASL_APPEARANCE_DARK); + assert(yasl_private_display_appearance(&display) == YASL_PRIVATE_APPEARANCE_DARK); setenv("GTK_THEME", "HighContrast", 1); - assert(yasl_display_appearance(&display) == YASL_APPEARANCE_HIGH_CONTRAST); + assert(yasl_private_display_appearance(&display) == YASL_PRIVATE_APPEARANCE_HIGH_CONTRAST); setenv("GTK_THEME", "HighContrastInverse", 1); - assert(yasl_display_appearance(&display) == - (YASL_APPEARANCE_HIGH_CONTRAST | YASL_APPEARANCE_DARK)); + assert(yasl_private_display_appearance(&display) == + (YASL_PRIVATE_APPEARANCE_HIGH_CONTRAST | YASL_PRIVATE_APPEARANCE_DARK)); unsetenv("GTK_THEME"); - assert(!yasl_display_notify(NULL, "title", "body")); - assert(!yasl_display_notify(&display, NULL, "body")); - assert(!yasl_display_notify(&display, "title", "body")); + assert(!yasl_private_display_notify(NULL, "title", "body")); + assert(!yasl_private_display_notify(&display, NULL, "body")); + assert(!yasl_private_display_notify(&display, "title", "body")); } #if YASL_HAS_DBUS @@ -310,8 +310,8 @@ static void test_bus(const char *address) pthread_t thread; assert(pthread_create(&thread, NULL, serve, &fb) == 0); - struct yasl_display display = {.app_id = "org.example.Test"}; - assert(yasl_display_notify(&display, "Build done", NULL)); + struct yasl_private_display display = {.app_id = "org.example.Test"}; + assert(yasl_private_display_notify(&display, "Build done", NULL)); for (int i = 0; i < 500; i++) { pthread_mutex_lock(&fb.lock); int n = fb.notifications; @@ -328,10 +328,10 @@ static void test_bus(const char *address) /* Portal settings win over GTK_THEME, through ReadOne or the older Read. */ setenv("GTK_THEME", "Adwaita", 1); - uint32_t want = YASL_APPEARANCE_DARK | YASL_APPEARANCE_HIGH_CONTRAST; - assert(yasl_display_appearance(&display) == want); + uint32_t want = YASL_PRIVATE_APPEARANCE_DARK | YASL_PRIVATE_APPEARANCE_HIGH_CONTRAST; + assert(yasl_private_display_appearance(&display) == want); fb.read_one = false; - assert(yasl_display_appearance(&display) == want); + assert(yasl_private_display_appearance(&display) == want); unsetenv("GTK_THEME"); /* A KDE value, sent as a string, wins over the GNOME key. */ diff --git a/tests/common/test_events.c b/tests/common/test_events.c index c8660cb..fcfc118 100644 --- a/tests/common/test_events.c +++ b/tests/common/test_events.c @@ -1,5 +1,4 @@ #include "common/backend.h" -#include "yasl/app.h" #include <assert.h> #include <errno.h> @@ -8,7 +7,7 @@ #include <string.h> static struct input_queue *queue(struct yasl_surface *s) { return &s->input; } -static const struct yasl_backend_ops ops = {.surface_input_queue = queue}; +static const struct yasl_private_backend_ops ops = {.surface_input_queue = queue}; static int released; static pthread_t owner; static void release(void *payload) @@ -18,90 +17,90 @@ static void release(void *payload) released++; } -static void setup(struct yasl_display *d, struct yasl_surface *a, struct yasl_surface *b) +static void setup(struct yasl_private_display *d, struct yasl_surface *a, struct yasl_surface *b) { - *d = (struct yasl_display){.ops = &ops}; + *d = (struct yasl_private_display){.ops = &ops}; *a = (struct yasl_surface){.display = d, .scale_120 = 120}; *b = (struct yasl_surface){.display = d, .scale_120 = 120}; - assert(yasl_events_open(d)); - assert(yasl_events_register(a)); - assert(yasl_events_register(b)); + assert(yasl_private_events_open(d)); + assert(yasl_private_events_register(a)); + assert(yasl_private_events_register(b)); } -static void teardown(struct yasl_display *d, struct yasl_surface *a, struct yasl_surface *b) +static void teardown(struct yasl_private_display *d, struct yasl_surface *a, struct yasl_surface *b) { - yasl_events_unregister(a); - yasl_events_unregister(b); + yasl_private_events_unregister(a); + yasl_private_events_unregister(b); input_queue_clear(&a->input); input_queue_clear(&b->input); - yasl_events_close(d); + yasl_private_events_close(d); } static void test_order_and_lifetimes(void) { - struct yasl_display d; + struct yasl_private_display d; struct yasl_surface a, b; setup(&d, &a, &b); - uint64_t aid = yasl_surface_id(&a), bid = yasl_surface_id(&b); + uint64_t aid = yasl_private_surface_id(&a), bid = yasl_private_surface_id(&b); assert(aid && bid && aid != bid); - yasl_surface_set_userdata(&a, &b); - assert(yasl_surface_userdata(&a) == &b); + yasl_private_surface_set_userdata(&a, &b); + assert(yasl_private_surface_userdata(&a) == &b); char text[] = "text"; - assert(input_queue_push(&a.input, &(struct yasl_event){ - .type = YASL_EVENT_TEXT, .surface = &a, .text = {text, 4}})); + assert(input_queue_push(&a.input, &(struct yasl_private_event){ + .type = YASL_PRIVATE_EVENT_TEXT, .surface = &a, .text = {text, 4}})); memset(text, 'x', 4); - yasl_surface_notify_state(&b, YASL_STATE_MAXIMIZED); - yasl_surface_notify_configure(&a, (struct yasl_geometry){{640, 480}, 180}); - yasl_surface_notify_drag(&b, YASL_DRAG_OVER, (struct yasl_vector2i){3, 4}); - yasl_surface_notify_composition(&a, "hello", 1, 3); - yasl_surface_notify_composition(&a, NULL, -1, -1); - yasl_surface_complete_paste(&b, 42, YASL_TRANSFER_UNAVAILABLE, NULL, 0); - const enum yasl_event_type expected[] = {YASL_EVENT_TEXT, YASL_EVENT_WINDOW_STATE, - YASL_EVENT_WINDOW_RESIZE, YASL_EVENT_DRAG, YASL_EVENT_COMPOSITION, - YASL_EVENT_COMPOSITION, YASL_EVENT_PASTE}; - struct yasl_event ev; + yasl_private_surface_notify_state(&b, YASL_PRIVATE_STATE_MAXIMIZED); + yasl_private_surface_notify_configure(&a, (struct yasl_private_geometry){{640, 480}, 180}); + yasl_private_surface_notify_drag(&b, YASL_PRIVATE_DRAG_OVER, (struct yasl_private_vector2i){3, 4}); + yasl_private_surface_notify_composition(&a, "hello", 1, 3); + yasl_private_surface_notify_composition(&a, NULL, -1, -1); + yasl_private_surface_complete_paste(&b, 42, YASL_PRIVATE_TRANSFER_UNAVAILABLE, NULL, 0); + const enum yasl_private_event_type expected[] = {YASL_PRIVATE_EVENT_TEXT, YASL_PRIVATE_EVENT_WINDOW_STATE, + YASL_PRIVATE_EVENT_WINDOW_RESIZE, YASL_PRIVATE_EVENT_DRAG, YASL_PRIVATE_EVENT_COMPOSITION, + YASL_PRIVATE_EVENT_COMPOSITION, YASL_PRIVATE_EVENT_PASTE}; + struct yasl_private_event ev; uint64_t sequence = 0; for (size_t i = 0; i < sizeof(expected) / sizeof(*expected); i++) { - assert(yasl_display_poll_event(&d, &ev)); + assert(yasl_private_display_poll_event(&d, &ev)); assert(ev.type == expected[i] && ev.sequence > sequence && ev.time_ns); sequence = ev.sequence; if (i == 0) assert(!strcmp(ev.text.data, "text") && ev.surface_id == aid); if (i == 2) assert(ev.resize.scale_changed && ev.resize.geometry.size.x == 640); if (i == 4) assert(ev.composition.active && ev.composition.begin == 1 && !strcmp(ev.composition.data, "hello")); if (i == 5) assert(!ev.composition.active && !ev.composition.len); - if (i == 6) assert(ev.paste.request_id == 42 && ev.paste.status == YASL_TRANSFER_UNAVAILABLE); + if (i == 6) assert(ev.paste.request_id == 42 && ev.paste.status == YASL_PRIVATE_TRANSFER_UNAVAILABLE); } - assert(!yasl_display_poll_event(&d, &ev)); - assert(input_queue_push(&a.input, &(struct yasl_event){ - .type = YASL_EVENT_TEXT, .surface = &a, .text = {"kept", 4}})); - assert(yasl_display_poll_event(&d, &ev)); + assert(!yasl_private_display_poll_event(&d, &ev)); + assert(input_queue_push(&a.input, &(struct yasl_private_event){ + .type = YASL_PRIVATE_EVENT_TEXT, .surface = &a, .text = {"kept", 4}})); + assert(yasl_private_display_poll_event(&d, &ev)); const char *borrowed = ev.text.data; - assert(input_queue_push(&a.input, &(struct yasl_event){.type = YASL_EVENT_KEY, .surface = &a})); - yasl_events_unregister(&a); + assert(input_queue_push(&a.input, &(struct yasl_private_event){.type = YASL_PRIVATE_EVENT_KEY, .surface = &a})); + yasl_private_events_unregister(&a); assert(!strcmp(borrowed, "kept")); - assert(!yasl_display_poll_event(&d, &ev)); + assert(!yasl_private_display_poll_event(&d, &ev)); /* Reusing object memory must never reuse the identity. */ - assert(yasl_events_register(&a)); + assert(yasl_private_events_register(&a)); assert(a.id != aid); errno = 0; - assert(!yasl_display_post(&d, &(struct yasl_message){.target_id = aid})); + assert(!yasl_private_display_post(&d, &(struct yasl_private_message){.target_id = aid})); assert(errno == ENOENT); teardown(&d, &a, &b); } -struct producer { struct yasl_display *display; uint64_t target; }; +struct producer { struct yasl_private_display *display; uint64_t target; }; static void *post_worker(void *ud) { struct producer *producer = ud; for (uint32_t i = 0; i < 32; i++) - assert(yasl_display_post(producer->display, &(struct yasl_message){ + assert(yasl_private_display_post(producer->display, &(struct yasl_private_message){ .target_id = producer->target, .code = i, .payload = &released, .release = release})); return NULL; } static void test_post_cleanup(void) { - struct yasl_display d; + struct yasl_private_display d; struct yasl_surface a, b; setup(&d, &a, &b); owner = pthread_self(); @@ -110,26 +109,26 @@ static void test_post_cleanup(void) struct producer producer = {&d, a.id}; assert(!pthread_create(&worker, NULL, post_worker, &producer)); assert(!pthread_join(worker, NULL)); - struct yasl_event ev; + struct yasl_private_event ev; for (uint32_t i = 0; i < 8; i++) { - assert(yasl_display_poll_event(&d, &ev)); + assert(yasl_private_display_poll_event(&d, &ev)); assert(ev.user.code == i && ev.surface == &a); } assert(released == 7); /* Last event remains borrowed. */ - yasl_events_unregister(&a); + yasl_private_events_unregister(&a); assert(released == 31); /* Undelivered target messages canceled. */ - assert(!yasl_display_poll_event(&d, &ev)); - assert(yasl_display_post(&d, &(struct yasl_message){.payload = &released, .release = release})); - yasl_events_close(&d); + assert(!yasl_private_display_poll_event(&d, &ev)); + assert(yasl_private_display_post(&d, &(struct yasl_private_message){.payload = &released, .release = release})); + yasl_private_events_close(&d); assert(released == 33); /* Pending message and last consumed message. */ errno = 0; - assert(!yasl_display_post(&d, &(struct yasl_message){.payload = &released, .release = release})); + assert(!yasl_private_display_post(&d, &(struct yasl_private_message){.payload = &released, .release = release})); assert(errno == EINVAL && released == 33); } static int damage_count; -static bool present(struct yasl_surface *s, const struct yasl_framebuffer *target, - const struct yasl_damage_list *damage) +static bool present(struct yasl_surface *s, const struct yasl_private_framebuffer *target, + const struct yasl_private_damage_list *damage) { (void)s; (void)target; damage_count = damage->n; @@ -137,64 +136,64 @@ static bool present(struct yasl_surface *s, const struct yasl_framebuffer *targe } static void test_damage(void) { - struct yasl_backend_ops present_ops = {.surface_present = present}; - struct yasl_display d = {.ops = &present_ops}; + struct yasl_private_backend_ops present_ops = {.surface_present = present}; + struct yasl_private_display d = {.ops = &present_ops}; struct yasl_surface s = {.display = &d, .frame_generation = 1, .drawing = true}; - struct yasl_frame frame = {.surface = &s, .target = {.generation = 1, .size = {8, 8}}}; + struct yasl_private_frame frame = {.surface = &s, .target = {.generation = 1, .size = {8, 8}}}; s.active_frame = &frame; - struct yasl_rect2i rects[YASL_DAMAGE_MAX + 1] = {{0}}; - assert(yasl_frame_present(&frame, NULL, 0) && damage_count == -1); - assert(!yasl_frame_present(&frame, NULL, 0) && errno == EALREADY); + struct yasl_private_rect2i rects[YASL_PRIVATE_DAMAGE_MAX + 1] = {{0}}; + assert(yasl_private_frame_present(&frame, NULL, 0) && damage_count == -1); + assert(!yasl_private_frame_present(&frame, NULL, 0) && errno == EALREADY); s.frame_presented = false; - assert(yasl_frame_present(&frame, rects, 0) && damage_count == 0); + assert(yasl_private_frame_present(&frame, rects, 0) && damage_count == 0); s.frame_presented = false; - assert(yasl_frame_present(&frame, rects, YASL_DAMAGE_MAX + 1) && damage_count == -1); + assert(yasl_private_frame_present(&frame, rects, YASL_PRIVATE_DAMAGE_MAX + 1) && damage_count == -1); s.frame_presented = false; - rects[YASL_DAMAGE_MAX].width = -1; + rects[YASL_PRIVATE_DAMAGE_MAX].width = -1; errno = 0; - assert(!yasl_frame_present(&frame, rects, YASL_DAMAGE_MAX + 1) && errno == EINVAL); - assert(!yasl_frame_present(&frame, NULL, 1)); + assert(!yasl_private_frame_present(&frame, rects, YASL_PRIVATE_DAMAGE_MAX + 1) && errno == EINVAL); + assert(!yasl_private_frame_present(&frame, NULL, 1)); s.drawing = false; - assert(!yasl_frame_present(&frame, NULL, 0)); + assert(!yasl_private_frame_present(&frame, NULL, 0)); } static void test_failures(void) { - struct yasl_display d; + struct yasl_private_display d; struct yasl_surface a, b; setup(&d, &a, &b); - assert(!yasl_surface_set_size(&a, (struct yasl_vector2i){0, 10}) && errno == EINVAL); - assert(!yasl_surface_set_size(&a, (struct yasl_vector2i){10, 10}) && errno == ENOTSUP); - assert(!yasl_surface_set_fullscreen(&a, true) && errno == ENOTSUP); - assert(!yasl_surface_set_maximized(&a, true) && errno == ENOTSUP); - assert(!yasl_surface_minimize(&a) && errno == ENOTSUP); + assert(!yasl_private_surface_set_size(&a, (struct yasl_private_vector2i){0, 10}) && errno == EINVAL); + assert(!yasl_private_surface_set_size(&a, (struct yasl_private_vector2i){10, 10}) && errno == ENOTSUP); + assert(!yasl_private_surface_set_fullscreen(&a, true) && errno == ENOTSUP); + assert(!yasl_private_surface_set_maximized(&a, true) && errno == ENOTSUP); + assert(!yasl_private_surface_minimize(&a) && errno == ENOTSUP); a.popup = true; - assert(!yasl_surface_minimize(&a) && errno == EINVAL); - assert(!yasl_clipboard_set(&d, YASL_CLIPBOARD, "text") && errno == ENOTSUP); - assert(!yasl_clipboard_request(&a, YASL_CLIPBOARD) && errno == ENOTSUP); + assert(!yasl_private_surface_minimize(&a) && errno == EINVAL); + assert(!yasl_private_clipboard_set(&d, YASL_PRIVATE_CLIPBOARD, "text") && errno == ENOTSUP); + assert(!yasl_private_clipboard_request(&a, YASL_PRIVATE_CLIPBOARD) && errno == ENOTSUP); /* Queued native event allocation/validation failure must be observable. */ - assert(!yasl_events_push(&d, &a, &(struct yasl_event){ - .type = YASL_EVENT_TEXT, .surface = &a, .text = {"x", SIZE_MAX}})); - assert(yasl_events_error(&d) == ENOMEM); + assert(!yasl_private_events_push(&d, &a, &(struct yasl_private_event){ + .type = YASL_PRIVATE_EVENT_TEXT, .surface = &a, .text = {"x", SIZE_MAX}})); + assert(yasl_private_events_error(&d) == ENOMEM); teardown(&d, &a, &b); } static void test_legacy_poll_does_not_spin(void) { - struct yasl_display d; + struct yasl_private_display d; struct yasl_surface a, b; setup(&d, &a, &b); - yasl_surface_notify_state(&a, YASL_STATE_ACTIVATED); - yasl_surface_notify_close(&b); - assert(yasl_events_pending(&d)); - struct yasl_event ev; - assert(!yasl_surface_poll_event(&a, &ev)); - assert(!yasl_events_pending(&d)); - assert(input_queue_push(&b.input, &(struct yasl_event){.type = YASL_EVENT_KEY, .surface = &b})); - assert(!yasl_surface_poll_event(&a, &ev)); - assert(yasl_surface_poll_event(&b, &ev) && ev.surface == &b); - assert(!yasl_events_pending(&d)); + yasl_private_surface_notify_state(&a, YASL_PRIVATE_STATE_ACTIVATED); + yasl_private_surface_notify_close(&b); + assert(yasl_private_events_pending(&d)); + struct yasl_private_event ev; + assert(!yasl_private_surface_poll_event(&a, &ev)); + assert(!yasl_private_events_pending(&d)); + assert(input_queue_push(&b.input, &(struct yasl_private_event){.type = YASL_PRIVATE_EVENT_KEY, .surface = &b})); + assert(!yasl_private_surface_poll_event(&a, &ev)); + assert(yasl_private_surface_poll_event(&b, &ev) && ev.surface == &b); + assert(!yasl_private_events_pending(&d)); teardown(&d, &a, &b); } diff --git a/tests/common/test_gl.c b/tests/common/test_gl.c new file mode 100644 index 0000000..cd51575 --- /dev/null +++ b/tests/common/test_gl.c @@ -0,0 +1,158 @@ +#define _POSIX_C_SOURCE 200809L +#include <yasl/gl.h> + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +typedef const unsigned char *(*get_string_fn)(unsigned); +typedef void (*clear_color_fn)(float, float, float, float); +typedef void (*clear_fn)(unsigned); +typedef void (*finish_fn)(void); +typedef void (*read_pixels_fn)(int, int, int, int, unsigned, unsigned, void *); +typedef void (*gen_textures_fn)(int, unsigned *); +typedef void (*bind_texture_fn)(unsigned, unsigned); +typedef unsigned char (*is_texture_fn)(unsigned); +typedef void (*delete_textures_fn)(int, const unsigned *); + +enum { GL_VERSION_ = 0x1f02, GL_COLOR_BUFFER_BIT_ = 0x00004000, + GL_RGBA_ = 0x1908, GL_UNSIGNED_BYTE_ = 0x1401, GL_TEXTURE_2D_ = 0x0de1 }; + +static yasl_surface *primary; +static yasl_surface *shared; +static yasl_surface *cpu; + +static void fail(const char *message, int line) +{ + const char *description = NULL; + enum yasl_error error = yasl_get_error(&description); + fprintf(stderr, "GL integration failure at line %d: %s (YASL error %d: %s, native error %d)\n", + line, message, error, description ? description : "none", yasl_get_native_error()); + (void)yasl_gl_make_current(NULL); + if (cpu) yasl_surface_destroy(cpu); + if (shared) yasl_surface_destroy(shared); + if (primary) yasl_surface_destroy(primary); + yasl_shutdown(); + exit(1); +} + +#define REQUIRE(condition, message) do { if (!(condition)) fail((message), __LINE__); } while (0) + +#define LOAD_GL(function, type, symbol) do { \ + union { yasl_gl_proc generic; type typed; } conversion; \ + conversion.generic = yasl_gl_get_proc_address(symbol); \ + REQUIRE(conversion.generic != NULL, "missing OpenGL function " symbol); \ + function = conversion.typed; \ +} while (0) + +static double seconds_now(void) +{ + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) return 0; + return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9; +} + +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); + if (actual_width == width && actual_height == height) return; + yasl_event event = {0}; + int result = yasl_wait_event(&event, 50); + REQUIRE(result >= 0, "waiting for configure event"); + } + char message[128]; + snprintf(message, sizeof(message), "resize did not settle at %dx%d (got %dx%d)", + width, height, actual_width, actual_height); + fail(message, __LINE__); +} + +int main(int argc, char **argv) +{ + REQUIRE(argc == 2, "expected backend argument: wayland or x11"); + if (!strcmp(argv[1], "wayland")) REQUIRE(yasl_set_backend(YASL_BACKEND_WAYLAND), "select Wayland backend"); + else if (!strcmp(argv[1], "x11")) REQUIRE(yasl_set_backend(YASL_BACKEND_X11), "select X11 backend"); + else REQUIRE(false, "backend must be wayland or x11"); + + primary = yasl_gl_surface_create("YASL GL test", 320, 240, 0, NULL, NULL); + REQUIRE(primary != NULL, "create default OpenGL 3.2 surface"); + REQUIRE(yasl_gl_make_current(primary), "make first GL context current"); + + get_string_fn get_string = NULL; + clear_color_fn clear_color = NULL; + clear_fn clear = NULL; + finish_fn finish = NULL; + read_pixels_fn read_pixels = NULL; + gen_textures_fn gen_textures = NULL; + bind_texture_fn bind_texture = NULL; + is_texture_fn is_texture = NULL; + delete_textures_fn delete_textures = NULL; + LOAD_GL(get_string, get_string_fn, "glGetString"); + LOAD_GL(clear_color, clear_color_fn, "glClearColor"); + LOAD_GL(clear, clear_fn, "glClear"); + LOAD_GL(finish, finish_fn, "glFinish"); + LOAD_GL(read_pixels, read_pixels_fn, "glReadPixels"); + LOAD_GL(gen_textures, gen_textures_fn, "glGenTextures"); + LOAD_GL(bind_texture, bind_texture_fn, "glBindTexture"); + LOAD_GL(is_texture, is_texture_fn, "glIsTexture"); + LOAD_GL(delete_textures, delete_textures_fn, "glDeleteTextures"); + const unsigned char *version = get_string(GL_VERSION_); + REQUIRE(version && version[0], "query GL version string"); + + clear_color(0.2f, 0.4f, 0.8f, 1.0f); + clear(GL_COLOR_BUFFER_BIT_); + finish(); + unsigned char pixel[4] = {0}; + int pixel_width = 0, pixel_height = 0; + yasl_surface_get_framebuffer_size(primary, &pixel_width, &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 && + abs((int)pixel[2] - 204) <= 3 && pixel[3] >= 250, + "read rendered pixel from the native GL framebuffer"); + REQUIRE(yasl_gl_swap_interval(primary, 0), "disable swap interval"); + REQUIRE(yasl_gl_swap_buffers(primary), "swap first GL surface"); + + unsigned texture = 0; + gen_textures(1, &texture); + bind_texture(GL_TEXTURE_2D_, texture); + REQUIRE(is_texture(texture), "create a shareable GL texture"); + + shared = yasl_gl_surface_create("YASL GL shared context", 160, 120, 0, NULL, primary); + REQUIRE(shared != NULL, "create shared GL context"); + REQUIRE(yasl_gl_make_current(shared), "make shared context current"); + REQUIRE(is_texture(texture), "verify context shares GL objects"); + delete_textures(1, &texture); + REQUIRE(yasl_gl_make_current(primary), "restore primary GL context"); + + REQUIRE(yasl_surface_request_size(primary, 400, 300), "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); + REQUIRE(abs(pixel_width - (int)lround(400 * scale)) <= 1 && + abs(pixel_height - (int)lround(300 * scale)) <= 1, + "query resized framebuffer dimensions"); + clear_color(0.7f, 0.1f, 0.3f, 1.0f); + clear(GL_COLOR_BUFFER_BIT_); + REQUIRE(yasl_gl_swap_buffers(primary), "swap resized GL surface"); + + const int invalid_attributes[] = {0x7fff, 1, YASL_GL_END}; + REQUIRE(yasl_gl_surface_create("invalid attrs", 100, 100, 0, invalid_attributes, NULL) == NULL, + "reject unknown GL context attributes"); + REQUIRE(yasl_gl_surface_create("invalid CPU GL", 100, 100, YASL_SURFACE_CPU, NULL, NULL) == NULL, + "reject CPU flag on GL surface creation"); + cpu = yasl_surface_create("CPU surface", 100, 100, YASL_SURFACE_CPU); + REQUIRE(cpu != NULL, "create CPU surface for incompatibility check"); + REQUIRE(!yasl_gl_make_current(cpu), "reject CPU surface as a GL context"); + + REQUIRE(yasl_gl_make_current(NULL), "detach GL context before destruction"); + yasl_surface_destroy(cpu); cpu = NULL; + yasl_surface_destroy(shared); shared = NULL; + yasl_surface_destroy(primary); primary = NULL; + yasl_shutdown(); + return 0; +} diff --git a/tests/common/test_gl_options.c b/tests/common/test_gl_options.c new file mode 100644 index 0000000..4bcd521 --- /dev/null +++ b/tests/common/test_gl_options.c @@ -0,0 +1,16 @@ +#include "common/gl_options.h" +#include <assert.h> +#include <stddef.h> +#include <errno.h> +int main(void) +{ + struct yasl_private_gl_options o; + assert(yasl_private_gl_parse_options(NULL,&o));assert(o.major==3&&o.minor==2&&o.red_bits==8); + const int es[]={YASL_PRIVATE_GL_API,YASL_PRIVATE_GL_OPENGL_ES,YASL_PRIVATE_GL_MAJOR,3,YASL_PRIVATE_GL_MINOR,0,YASL_PRIVATE_GL_END}; + assert(yasl_private_gl_parse_options(es,&o)&&o.api==YASL_PRIVATE_GL_OPENGL_ES&&o.minor==0); + const int invalid[]={99,0,YASL_PRIVATE_GL_END}; + assert(!yasl_private_gl_parse_options(invalid,&o)&&errno==EINVAL); + const int negative[]={YASL_PRIVATE_GL_DEPTH_BITS,-1,YASL_PRIVATE_GL_END}; + assert(!yasl_private_gl_parse_options(negative,&o)); + return 0; +} diff --git a/tests/common/test_hello_example.c b/tests/common/test_hello_example.c deleted file mode 100644 index 350b3bb..0000000 --- a/tests/common/test_hello_example.c +++ /dev/null @@ -1,20 +0,0 @@ -/* 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 *frame) -{ - draw(ud, surface, frame); - if (!failed) yasl_surface_destroy(surface); -} - -int main(void) -{ - struct yasl_window_spec window = {.title = "Hello YASL", .size = {640, 480}}; - struct yasl_app_desc desc = { - .app_id = "org.yasl.test.hello", .window = &window, - .frame = draw_once, - }; - return yasl_run(&desc) || failed ? 1 : 0; -} diff --git a/tests/common/test_input_alloc.c b/tests/common/test_input_alloc.c index 7aba4b2..12e7656 100644 --- a/tests/common/test_input_alloc.c +++ b/tests/common/test_input_alloc.c @@ -5,7 +5,7 @@ /* This executable compiles input.c with malloc redirected here. */ static int allocations = -1; -void *yasl_test_malloc(size_t bytes) +void *yasl_private_test_malloc(size_t bytes) { if (allocations == 0) return NULL; if (allocations > 0) allocations--; @@ -13,19 +13,19 @@ void *yasl_test_malloc(size_t bytes) } /* input.c timestamps events, but this executable links no event loop. */ -uint64_t yasl_loop_now_ns(void) { return 1; } +uint64_t yasl_private_loop_now_ns(void) { return 1; } int main(void) { struct yasl_surface *surface = (struct yasl_surface *)(uintptr_t)1; for (int failure = 0; failure < 2; failure++) { struct input_queue q = {0}; - struct yasl_event in = {.surface = surface, .type = YASL_EVENT_PASTE, + struct yasl_private_event in = {.surface = surface, .type = YASL_PRIVATE_EVENT_PASTE, .text = {"payload", 7}}, out; /* Fail either payload allocation or the initial event-array allocation. */ allocations = failure; assert(!input_queue_push(&q, &in)); - assert(input_queue_pop(&q, &out) && out.type == YASL_EVENT_INPUT_RESET); + assert(input_queue_pop(&q, &out) && out.type == YASL_PRIVATE_EVENT_INPUT_RESET); assert(out.surface == surface); assert(!input_queue_pop(&q, &out)); input_queue_clear(&q); @@ -40,15 +40,15 @@ int main(void) assert(strcmp(last, "payload") == 0); assert(!q.count && q.reset_pending); allocations = -1; - in = (struct yasl_event){.surface = surface, .type = YASL_EVENT_KEY, + in = (struct yasl_private_event){.surface = surface, .type = YASL_PRIVATE_EVENT_KEY, .key.action = YASL_KEY_RELEASE}; assert(input_queue_push(&q, &in)); - assert(input_queue_pop(&q, &out) && out.type == YASL_EVENT_INPUT_RESET); + assert(input_queue_pop(&q, &out) && out.type == YASL_PRIVATE_EVENT_INPUT_RESET); assert(input_queue_pop(&q, &out) && out.key.action == YASL_KEY_RELEASE); input_queue_clear(&q); /* A lost event clears held buttons along with the stream. */ - in = (struct yasl_event){.surface = surface, .type = YASL_EVENT_POINTER_DOWN}; + in = (struct yasl_private_event){.surface = surface, .type = YASL_PRIVATE_EVENT_POINTER_DOWN}; assert(input_queue_push(&q, &in) && q.buttons); for (size_t i = 1; i < q.capacity; i++) assert(input_queue_push(&q, &in)); allocations = 0; diff --git a/tests/common/test_keyboard.c b/tests/common/test_keyboard.c index f2fc8f2..7406c1d 100644 --- a/tests/common/test_keyboard.c +++ b/tests/common/test_keyboard.c @@ -9,27 +9,27 @@ int main(void) { /* Same physical positions across native encodings, including keypad and * left/right modifiers. macOS A is code zero, which is not UNKNOWN. */ - assert(yasl_key_from_evdev(30) == YASL_KEY_A); - assert(yasl_key_from_cocoa(kVK_ANSI_A) == YASL_KEY_A); - assert(yasl_key_from_xkb_name("AC01") == YASL_KEY_A); - assert(yasl_key_from_evdev(1) == YASL_KEY_ESCAPE); - assert(yasl_key_from_cocoa(kVK_Escape) == YASL_KEY_ESCAPE); - assert(yasl_key_from_xkb_name("ESC\0") == YASL_KEY_ESCAPE); - assert(yasl_key_from_evdev(96) == YASL_KEY_KP_ENTER); - assert(yasl_key_from_cocoa(kVK_ANSI_KeypadEnter) == YASL_KEY_KP_ENTER); - assert(yasl_key_from_cocoa(kVK_Return) == YASL_KEY_ENTER); - assert(yasl_key_from_evdev(100) == YASL_KEY_RIGHT_ALT); - assert(yasl_key_from_cocoa(kVK_RightOption) == YASL_KEY_RIGHT_ALT); - assert(yasl_key_from_evdev(42) == YASL_KEY_LEFT_SHIFT); - assert(yasl_key_from_cocoa(kVK_Shift) == YASL_KEY_LEFT_SHIFT); - assert(yasl_key_from_evdev(86) == YASL_KEY_ISO_BACKSLASH); - assert(yasl_key_from_cocoa(kVK_ISO_Section) == YASL_KEY_ISO_BACKSLASH); - assert(yasl_key_from_evdev(0) == YASL_KEY_UNKNOWN); - assert(yasl_key_from_evdev(UINT32_MAX) == YASL_KEY_UNKNOWN); - assert(yasl_key_from_cocoa(UINT32_MAX) == YASL_KEY_UNKNOWN); - assert(yasl_key_from_xkb_name("ZZZZ") == YASL_KEY_UNKNOWN); - assert(yasl_key_label(0x1f600) == 0x1f600); - assert(yasl_key_label(0xd800) == 0 && yasl_key_label(0x110000) == 0); - assert(yasl_key_label(0xf700) == 0 && yasl_key_label('\t') == 0); + assert(yasl_private_key_from_evdev(30) == YASL_KEY_A); + assert(yasl_private_key_from_cocoa(kVK_ANSI_A) == YASL_KEY_A); + assert(yasl_private_key_from_xkb_name("AC01") == YASL_KEY_A); + assert(yasl_private_key_from_evdev(1) == YASL_KEY_ESCAPE); + assert(yasl_private_key_from_cocoa(kVK_Escape) == YASL_KEY_ESCAPE); + assert(yasl_private_key_from_xkb_name("ESC\0") == YASL_KEY_ESCAPE); + assert(yasl_private_key_from_evdev(96) == YASL_KEY_KP_ENTER); + assert(yasl_private_key_from_cocoa(kVK_ANSI_KeypadEnter) == YASL_KEY_KP_ENTER); + assert(yasl_private_key_from_cocoa(kVK_Return) == YASL_KEY_ENTER); + assert(yasl_private_key_from_evdev(100) == YASL_KEY_RIGHT_ALT); + assert(yasl_private_key_from_cocoa(kVK_RightOption) == YASL_KEY_RIGHT_ALT); + assert(yasl_private_key_from_evdev(42) == YASL_KEY_LEFT_SHIFT); + assert(yasl_private_key_from_cocoa(kVK_Shift) == YASL_KEY_LEFT_SHIFT); + assert(yasl_private_key_from_evdev(86) == YASL_KEY_ISO_BACKSLASH); + assert(yasl_private_key_from_cocoa(kVK_ISO_Section) == YASL_KEY_ISO_BACKSLASH); + assert(yasl_private_key_from_evdev(0) == YASL_KEY_UNKNOWN); + assert(yasl_private_key_from_evdev(UINT32_MAX) == YASL_KEY_UNKNOWN); + assert(yasl_private_key_from_cocoa(UINT32_MAX) == YASL_KEY_UNKNOWN); + assert(yasl_private_key_from_xkb_name("ZZZZ") == YASL_KEY_UNKNOWN); + assert(yasl_private_key_label(0x1f600) == 0x1f600); + assert(yasl_private_key_label(0xd800) == 0 && yasl_private_key_label(0x110000) == 0); + assert(yasl_private_key_label(0xf700) == 0 && yasl_private_key_label('\t') == 0); return 0; } diff --git a/tests/common/test_loop.c b/tests/common/test_loop.c index f982e5e..5c49691 100644 --- a/tests/common/test_loop.c +++ b/tests/common/test_loop.c @@ -1,6 +1,6 @@ #define _GNU_SOURCE #define _DARWIN_C_SOURCE -#include "yasl/loop.h" +#include "common/private_loop.h" #include <assert.h> #include <errno.h> @@ -29,7 +29,7 @@ static void on_alarm(int signo) } struct post_log { - struct yasl_loop *loop; + struct yasl_private_loop *loop; char order[8]; int count; }; @@ -46,7 +46,7 @@ static void post_a(void *ud) { struct post_log *log = ud; log->order[log->count++] = 'a'; - assert(yasl_loop_post(log->loop, post_c, log)); + assert(yasl_private_loop_post(log->loop, post_c, log)); } static void post_b(void *ud) @@ -57,28 +57,28 @@ static void post_b(void *ud) static void test_post_order(void) { - struct yasl_loop *l = yasl_loop_create(); + struct yasl_private_loop *l = yasl_private_loop_create(); assert(l); - assert(!yasl_loop_post(NULL, post_b, NULL)); - assert(!yasl_loop_post(l, NULL, NULL)); + assert(!yasl_private_loop_post(NULL, post_b, NULL)); + assert(!yasl_private_loop_post(l, NULL, NULL)); struct post_log log = {.loop = l}; - assert(yasl_loop_post(l, post_a, &log)); - assert(yasl_loop_post(l, post_b, &log)); - assert(yasl_loop_step(l, 0) >= 1); + assert(yasl_private_loop_post(l, post_a, &log)); + assert(yasl_private_loop_post(l, post_b, &log)); + assert(yasl_private_loop_step(l, 0) >= 1); /* A post made while posts run waits for a later step. */ assert(log.count == 2 && memcmp(log.order, "ab", 2) == 0); - assert(yasl_loop_step(l, 0) >= 1); + assert(yasl_private_loop_step(l, 0) >= 1); assert(log.count == 3 && log.order[2] == 'c'); /* Destroy drops posts that never ran. */ - assert(yasl_loop_post(l, post_b, &log)); - yasl_loop_destroy(l); + assert(yasl_private_loop_post(l, post_b, &log)); + yasl_private_loop_destroy(l); assert(log.count == 3); } struct thread_ctx { - struct yasl_loop *loop; + struct yasl_private_loop *loop; bool kick; int signo; _Atomic bool ran; @@ -97,35 +97,35 @@ static void *wake_thread(void *ud) if (ctx->signo) kill(getpid(), ctx->signo); else if (ctx->kick) - yasl_loop_kick(ctx->loop); + yasl_private_loop_kick(ctx->loop); else - assert(yasl_loop_post(ctx->loop, mark_ran, ctx)); + assert(yasl_private_loop_post(ctx->loop, mark_ran, ctx)); return NULL; } static void test_cross_thread(void) { - struct yasl_loop *l = yasl_loop_create(); + struct yasl_private_loop *l = yasl_private_loop_create(); assert(l); struct thread_ctx post = {.loop = l}; pthread_t t; assert(pthread_create(&t, NULL, wake_thread, &post) == 0); while (!post.ran) - assert(yasl_loop_step(l, -1) >= 0); + assert(yasl_private_loop_step(l, -1) >= 0); pthread_join(t, NULL); /* A kick ends an indefinite wait without running anything. */ struct thread_ctx kick = {.loop = l, .kick = true}; assert(pthread_create(&t, NULL, wake_thread, &kick) == 0); - assert(yasl_loop_step(l, -1) >= 1); + assert(yasl_private_loop_step(l, -1) >= 1); pthread_join(t, NULL); - yasl_loop_destroy(l); + yasl_private_loop_destroy(l); } struct signal_log { - struct yasl_loop *loop; + struct yasl_private_loop *loop; int seen[NSIG]; int total; bool unwatch; @@ -138,7 +138,7 @@ static void on_signal(void *ud, int signo) log->seen[signo]++; log->total++; if (log->unwatch) - yasl_loop_unwatch_signals(log->loop); + yasl_private_loop_unwatch_signals(log->loop); } static volatile sig_atomic_t previous_handler_hits; @@ -149,10 +149,10 @@ static void previous_handler(int signo) previous_handler_hits++; } -static void step_until(struct yasl_loop *l, const int *counter, int want) +static void step_until(struct yasl_private_loop *l, const int *counter, int want) { while (*counter < want) - assert(yasl_loop_step(l, -1) >= 0); + assert(yasl_private_loop_step(l, -1) >= 0); } static void test_signals(void) @@ -161,30 +161,30 @@ static void test_signals(void) sigemptyset(&prev.sa_mask); assert(sigaction(SIGUSR2, &prev, NULL) == 0); - struct yasl_loop *l = yasl_loop_create(); - struct yasl_loop *other = yasl_loop_create(); + struct yasl_private_loop *l = yasl_private_loop_create(); + struct yasl_private_loop *other = yasl_private_loop_create(); assert(l && other); struct signal_log log = {.loop = l}; const int bad[] = {SIGKILL}; errno = 0; - assert(!yasl_loop_watch_signals(l, bad, 1, on_signal, &log) && errno == EINVAL); + assert(!yasl_private_loop_watch_signals(l, bad, 1, on_signal, &log) && errno == EINVAL); const int sigs[] = {SIGUSR1, SIGUSR2, SIGCHLD}; errno = 0; - assert(!yasl_loop_watch_signals(l, sigs, 0, on_signal, &log) && errno == EINVAL); + assert(!yasl_private_loop_watch_signals(l, sigs, 0, on_signal, &log) && errno == EINVAL); errno = 0; - assert(!yasl_loop_watch_signals(l, sigs, 3, NULL, &log) && errno == EINVAL); - assert(yasl_loop_watch_signals(l, sigs, 3, on_signal, &log)); + assert(!yasl_private_loop_watch_signals(l, sigs, 3, NULL, &log) && errno == EINVAL); + assert(yasl_private_loop_watch_signals(l, sigs, 3, on_signal, &log)); /* One signal belongs to one loop at a time. */ errno = 0; - assert(!yasl_loop_watch_signals(other, sigs, 1, on_signal, &log) && errno == EBUSY); + assert(!yasl_private_loop_watch_signals(other, sigs, 1, on_signal, &log) && errno == EBUSY); /* Deliveries before the callback runs coalesce per signal. */ raise(SIGUSR1); raise(SIGUSR1); raise(SIGUSR2); - assert(yasl_loop_step(l, 0) >= 1); + assert(yasl_private_loop_step(l, 0) >= 1); assert(log.seen[SIGUSR1] == 1 && log.seen[SIGUSR2] == 1); assert(previous_handler_hits == 0); @@ -205,11 +205,11 @@ static void test_signals(void) /* Replacing the watch restores the signals it drops. */ const int only_usr1[] = {SIGUSR1}; - assert(yasl_loop_watch_signals(l, only_usr1, 1, on_signal, &log)); + assert(yasl_private_loop_watch_signals(l, only_usr1, 1, on_signal, &log)); raise(SIGUSR2); assert(previous_handler_hits == 1); - assert(yasl_loop_watch_signals(other, (const int[]){SIGUSR2}, 1, on_signal, &log)); - yasl_loop_unwatch_signals(other); + assert(yasl_private_loop_watch_signals(other, (const int[]){SIGUSR2}, 1, on_signal, &log)); + yasl_private_loop_unwatch_signals(other); assert(previous_handler_hits == 1); raise(SIGUSR2); assert(previous_handler_hits == 2); @@ -221,15 +221,15 @@ static void test_signals(void) struct sigaction now; assert(sigaction(SIGUSR1, NULL, &now) == 0); assert(now.sa_handler == SIG_DFL); - yasl_loop_unwatch_signals(l); + yasl_private_loop_unwatch_signals(l); /* Destroy unwatches. */ log.unwatch = false; - assert(yasl_loop_watch_signals(l, only_usr1, 1, on_signal, &log)); - yasl_loop_destroy(l); + assert(yasl_private_loop_watch_signals(l, only_usr1, 1, on_signal, &log)); + yasl_private_loop_destroy(l); assert(sigaction(SIGUSR1, NULL, &now) == 0); assert(now.sa_handler == SIG_DFL); - yasl_loop_destroy(other); + yasl_private_loop_destroy(other); struct sigaction dfl = {.sa_handler = SIG_DFL}; sigemptyset(&dfl.sa_mask); diff --git a/tests/common/test_selection.c b/tests/common/test_selection.c index d1b3eb8..9772923 100644 --- a/tests/common/test_selection.c +++ b/tests/common/test_selection.c @@ -1,5 +1,5 @@ #define _POSIX_C_SOURCE 200809L -#include "yasl/yasl.h" +#include "common/types.h" #include <assert.h> #include <errno.h> @@ -7,27 +7,27 @@ int main(void) { - struct yasl_display_spec spec = {.backend = (enum yasl_backend)999}; + struct yasl_private_display_spec spec = {.backend = (enum yasl_private_backend)999}; errno = 0; - assert(!yasl_display_open(NULL, &spec)); + assert(!yasl_private_display_open(NULL, &spec)); assert(errno == EINVAL); - assert(yasl_display_backend(NULL) == YASL_BACKEND_AUTO); + assert(yasl_private_display_backend(NULL) == YASL_PRIVATE_BACKEND_AUTO); #if !YASL_HAS_COCOA - spec.backend = YASL_BACKEND_COCOA; + spec.backend = YASL_PRIVATE_BACKEND_COCOA; errno = 0; - assert(!yasl_display_open(NULL, &spec)); + assert(!yasl_private_display_open(NULL, &spec)); assert(errno == ENOSYS); #endif #if !YASL_HAS_WAYLAND - spec.backend = YASL_BACKEND_WAYLAND; + spec.backend = YASL_PRIVATE_BACKEND_WAYLAND; errno = 0; - assert(!yasl_display_open(NULL, &spec)); + assert(!yasl_private_display_open(NULL, &spec)); assert(errno == ENOSYS); #endif #if !YASL_HAS_X11 - spec.backend = YASL_BACKEND_X11; + spec.backend = YASL_PRIVATE_BACKEND_X11; errno = 0; - assert(!yasl_display_open(NULL, &spec)); + assert(!yasl_private_display_open(NULL, &spec)); assert(errno == ENOSYS); #endif return 0; diff --git a/tests/common/test_surface_integration.c b/tests/common/test_surface_integration.c new file mode 100644 index 0000000..2752208 --- /dev/null +++ b/tests/common/test_surface_integration.c @@ -0,0 +1,90 @@ +#define _POSIX_C_SOURCE 200809L +#include <yasl/yasl.h> +#include <yasl/cpu.h> +#include "common/backend.h" +#include <assert.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <stdio.h> +static void drain(void) +{ + yasl_event e;int n=0; + while(yasl_wait_event(&e,0)>0)assert(++n<1000); +} +static void *wake_thread(void *unused) +{ (void)unused;struct timespec delay={0,20000000};nanosleep(&delay,NULL);yasl_wake();return NULL; } +static void *wrong_thread(void *data) +{ assert(!yasl_surface_set_title(data,"wrong"));assert(yasl_get_error(NULL)==YASL_ERROR_WRONG_THREAD);return NULL; } +static int refreshes; +static void refresh(yasl_surface *s,void *ud) +{ + (void)ud;refreshes++; + yasl_event e;assert(yasl_wait_event(&e,0)==-1);assert(yasl_get_error(NULL)==YASL_ERROR_BUSY); + yasl_surface_destroy(s);assert(yasl_get_error(NULL)==YASL_ERROR_BUSY); +} +int main(int argc,char **argv) +{ + assert(argc==2); + enum yasl_backend backend=!strcmp(argv[1],"wayland")?YASL_BACKEND_WAYLAND:!strcmp(argv[1],"x11")?YASL_BACKEND_X11:YASL_BACKEND_COCOA; + assert(yasl_set_backend(backend));assert(yasl_set_app_id("org.yasl.surface-test")); + yasl_surface *s=yasl_surface_create("CPU contract",96,80,YASL_SURFACE_CPU); + if(!s){const char *why;yasl_get_error(&why);fprintf(stderr,"surface: %s native=%d\n",why,yasl_get_native_error());return 1;} + assert(yasl_surface_backend(s)==backend); + /* The first events report the window size, then ask for a frame. */ + yasl_event e;int fw=0,fh=0;bool window=false; + for(int i=0;i<1000;i++) { + assert(yasl_wait_event(&e,i?20:0)>=0); + if(e.type==YASL_EVENT_WINDOW){window=true;fw=e.window.pixel_width;fh=e.window.pixel_height;} + if(e.type==YASL_EVENT_REFRESH){assert(window&&fw>0&&fh>0&&e.surface==s);break;} + } + assert(e.type==YASL_EVENT_REFRESH); + int w,h;yasl_surface_get_framebuffer_size(s,&w,&h);assert(w==fw&&h==fh); + assert(!yasl_set_backend(backend));assert(yasl_get_error(NULL)==YASL_ERROR_BUSY); + int pw,ph;uint32_t *pixels=yasl_cpu_pixels(s,&pw,&ph);assert(pixels&&pw==w&&ph==h); + assert(pixels[0]==0&&pixels[(size_t)w*h-1]==0); + for(size_t i=0;i<(size_t)w*h;i++)pixels[i]=0xff102030u; + pixels[(size_t)3*w+2]=0xffaabbccu; + assert(yasl_cpu_present(s)); + assert(yasl_cpu_pixels(s,&pw,&ph)==pixels&&pixels[0]==0xff102030u&&pixels[(size_t)3*w+2]==0xffaabbccu); + drain(); + pthread_t thread;assert(!pthread_create(&thread,NULL,wrong_thread,s));pthread_join(thread,NULL); + assert(!pthread_create(&thread,NULL,wake_thread,NULL)); + int n=yasl_wait_event(&e,500);assert(n>=0);pthread_join(thread,NULL); + bool ime=yasl_surface_set_text_input(s,true,YASL_TEXT_NORMAL); + if(!ime)assert(yasl_get_error(NULL)==YASL_ERROR_UNSUPPORTED); + bool snapshot=yasl_surface_set_surrounding_text(s,"a\xc3\xa9z",4,1,3,7); + if(ime)assert(snapshot); + else assert(!snapshot&&yasl_get_error(NULL)==YASL_ERROR_UNSUPPORTED); + assert(!yasl_surface_set_surrounding_text(s,"a\xc3\xa9z",4,2,3,8)); + assert(!yasl_surface_set_surrounding_text(s,"\xc0\xaf",2,0,2,8)); + drain(); + struct yasl_private_event pen={.type=YASL_PRIVATE_EVENT_PEN,.surface=s, + .pen={.device_id=10,.tool_id=20,.x=12.5,.y=9.25,.pressure=0.3, + .axes_valid=YASL_PRIVATE_PEN_PRESSURE,.action=YASL_PRIVATE_PEN_DOWN,.in_contact=true}}; + assert(yasl_private_events_push(s->display,s,&pen)); + assert(yasl_wait_event(&e,0)==1&&e.type==YASL_EVENT_PEN&&e.pen.device_id==10&&e.pen.pressure==0.3&&e.pen.in_contact); + struct yasl_private_event text={.type=YASL_PRIVATE_EVENT_TEXT,.surface=s,.text={.data="owned",.len=5}}; + assert(yasl_private_events_push(s->display,s,&text));assert(yasl_wait_event(&e,0)==1&&e.type==YASL_EVENT_TEXT); + assert(e.text.length==5&&!strcmp(e.text.data,"owned")); + /* Unchanged focus folds into no event; a change reports the activated state. */ + unsigned active=yasl_surface_state(s)&YASL_STATE_ACTIVATED; + struct yasl_private_event focus={.type=YASL_PRIVATE_EVENT_FOCUS,.surface=s,.focus={.focused=!active}}; + assert(yasl_private_events_push(s->display,s,&focus));assert(yasl_wait_event(&e,0)==1&&e.type==YASL_EVENT_WINDOW); + assert((e.window.state&YASL_STATE_ACTIVATED)==(active?0u:YASL_STATE_ACTIVATED)&&e.window.pixel_width==w); + assert(yasl_private_events_push(s->display,s,&focus));assert(yasl_wait_event(&e,0)==0&&e.type==YASL_EVENT_NONE); + struct yasl_private_event hover={.type=YASL_PRIVATE_EVENT_DROP_HOVER,.surface=s,.drop={YASL_PRIVATE_DROP_LEAVE,{0,0}}}; + assert(yasl_private_events_push(s->display,s,&hover)); + assert(yasl_wait_event(&e,0)==1&&e.type==YASL_EVENT_DROP&&e.drop.action==YASL_DROP_LEAVE); + yasl_surface_set_refresh_callback(s,refresh,NULL); + assert(yasl_cpu_present(s)); + drain();assert(refreshes>0); + yasl_surface_set_refresh_callback(s,NULL,NULL); + yasl_surface_destroy(s); + assert(!yasl_set_backend(backend)); + s=yasl_surface_create("shared backend remains",64,64,YASL_SURFACE_CPU);assert(s); + yasl_shutdown();assert(yasl_set_backend(backend)); + s=yasl_surface_create("reinitialized",64,64,YASL_SURFACE_CPU);assert(s);yasl_shutdown(); + puts("public surface lifecycle, pixels, ownership, input, wake and refresh passed");return 0; +} diff --git a/tests/common/test_tablet_example.c b/tests/common/test_tablet_example.c deleted file mode 100644 index e291b2a..0000000 --- a/tests/common/test_tablet_example.c +++ /dev/null @@ -1,65 +0,0 @@ -#define main tablet_example_main -#include "../../examples/tablet.c" -#undef main -#include <assert.h> - -static void pointer(struct app *app, enum yasl_event_type type, - enum yasl_pointer_device device, enum yasl_pointer_button button, int x, int y, uint16_t pressure) -{ - struct yasl_event event = { - .type = type, - .pointer = { - .pos = (struct yasl_vector2i){x, y}, - .device = device, - .button = button, - .pen = { .pressure = pressure }, - }, - }; - input(app, &event); -} - -int main(void) -{ - struct app app = {0}; - assert(resize_canvas(&app, (struct yasl_geometry){{400, 300}, 120})); - pointer(&app, YASL_EVENT_POINTER_MOTION, YASL_PTR_DEV_PEN, YASL_BUTTON_BACK, 80, 160, 65535); - /* Hover never paints. */ - assert(app.canvas[(size_t)160 * app.geometry.size.x + 80] == PAPER); - pointer(&app, YASL_EVENT_POINTER_DOWN, YASL_PTR_DEV_PEN, YASL_BUTTON_SECONDARY, 80, 160, 65535); - /* Side button isn't the tip. */ - assert(!app.down && - app.canvas[(size_t)160 * app.geometry.size.x + 80] == PAPER); - pointer(&app, YASL_EVENT_POINTER_DOWN, YASL_PTR_DEV_PEN, YASL_BUTTON_PRIMARY, 80, 160, 0); - assert(app.down && - app.canvas[(size_t)160 * app.geometry.size.x + 80] == INK && - app.canvas[(size_t)160 * app.geometry.size.x + 90] == PAPER); - pointer(&app, YASL_EVENT_POINTER_MOTION, YASL_PTR_DEV_PEN, YASL_BUTTON_PRIMARY, 80, 160, 65535); - /* Stationary pressure widens brush. */ - assert(app.canvas[(size_t)160 * app.geometry.size.x + 100] == INK); - pointer(&app, YASL_EVENT_POINTER_MOTION, YASL_PTR_DEV_PEN, YASL_BUTTON_PRIMARY, 180, 160, 20000); - /* Segments are continuous. */ - assert(app.canvas[(size_t)160 * app.geometry.size.x + 130] == INK); - pointer(&app, YASL_EVENT_POINTER_UP, YASL_PTR_DEV_PEN, YASL_BUTTON_PRIMARY, 180, 160, 0); - pointer(&app, YASL_EVENT_POINTER_MOTION, YASL_PTR_DEV_PEN, YASL_BUTTON_BACK, 250, 160, 65535); - assert(!app.down && app.canvas[(size_t)160 * app.geometry.size.x + 250] == PAPER); - pointer(&app, YASL_EVENT_POINTER_DOWN, YASL_PTR_DEV_ERASER, YASL_BUTTON_PRIMARY, 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_event reset = {.type = YASL_EVENT_INPUT_RESET}; - input(&app, &reset); - pointer(&app, YASL_EVENT_POINTER_MOTION, YASL_PTR_DEV_ERASER, YASL_BUTTON_PRIMARY, 130, 160, 65535); - assert(!app.down && app.canvas[(size_t)160 * app.geometry.size.x + 130] == INK); - pointer(&app, YASL_EVENT_POINTER_DOWN, YASL_PTR_DEV_MOUSE, YASL_BUTTON_PRIMARY, 300, 200, 0); - assert(app.canvas[(size_t)200 * app.geometry.size.x + 300] == INK); - struct yasl_event blur = {.type = YASL_EVENT_FOCUS, .focus.focused = false}; - input(&app, &blur); - assert(!app.down); - assert(resize_canvas(&app, (struct yasl_geometry){{800, 600}, 240})); - /* DPI preserves logical drawing. */ - assert(app.canvas[(size_t)400 * app.geometry.size.x + 600] == INK && - !app.previous); - clear_canvas(&app); - assert(app.canvas[(size_t)400 * app.geometry.size.x + 600] == PAPER); - free(app.canvas); - return 0; -} diff --git a/tests/common/test_timers.c b/tests/common/test_timers.c index 8c92158..2c3542f 100644 --- a/tests/common/test_timers.c +++ b/tests/common/test_timers.c @@ -1,6 +1,6 @@ #define _POSIX_C_SOURCE 200809L #include "common/loop.h" -#include "yasl/yasl.h" +#include "common/types.h" #include <assert.h> #include <stdio.h> @@ -9,15 +9,15 @@ struct test_ctx { int count; uint64_t last_time; - yasl_timer_id id; - yasl_timer_id other_id; - struct yasl_loop *loop; + yasl_private_timer_id id; + yasl_private_timer_id other_id; + struct yasl_private_loop *loop; struct yasl_surface *surface; bool done; }; /* 1. Single-shot timer: returns 0, fires exactly once. */ -static uint64_t oneshot_cb(void *ud, yasl_timer_id id, uint64_t interval_ns) +static uint64_t oneshot_cb(void *ud, yasl_private_timer_id id, uint64_t interval_ns) { struct test_ctx *ctx = ud; assert(id == ctx->id); @@ -27,7 +27,7 @@ static uint64_t oneshot_cb(void *ud, yasl_timer_id id, uint64_t interval_ns) } /* 2. Repeating timer: ticks N times, then returns 0. */ -static uint64_t repeat_cb(void *ud, yasl_timer_id id, uint64_t interval_ns) +static uint64_t repeat_cb(void *ud, yasl_private_timer_id id, uint64_t interval_ns) { struct test_ctx *ctx = ud; assert(id == ctx->id); @@ -41,7 +41,7 @@ static uint64_t repeat_cb(void *ud, yasl_timer_id id, uint64_t interval_ns) } /* 3. Rescheduling timer: changes interval dynamically. */ -static uint64_t reschedule_cb(void *ud, yasl_timer_id id, uint64_t interval_ns) +static uint64_t reschedule_cb(void *ud, yasl_private_timer_id id, uint64_t interval_ns) { struct test_ctx *ctx = ud; assert(id == ctx->id); @@ -58,7 +58,7 @@ static uint64_t reschedule_cb(void *ud, yasl_timer_id id, uint64_t interval_ns) } /* 4. Timer adding another timer from inside callback. */ -static uint64_t child_timer_cb(void *ud, yasl_timer_id id, uint64_t interval_ns) +static uint64_t child_timer_cb(void *ud, yasl_private_timer_id id, uint64_t interval_ns) { (void)interval_ns; struct test_ctx *ctx = ud; @@ -67,40 +67,40 @@ static uint64_t child_timer_cb(void *ud, yasl_timer_id id, uint64_t interval_ns) return 0; } -static uint64_t parent_timer_cb(void *ud, yasl_timer_id id, uint64_t interval_ns) +static uint64_t parent_timer_cb(void *ud, yasl_private_timer_id id, uint64_t interval_ns) { (void)interval_ns; struct test_ctx *ctx = ud; assert(id == ctx->id); ctx->count += 1; - ctx->other_id = yasl_loop_add_timer(ctx->loop, 1000000ull, child_timer_cb, ctx); + ctx->other_id = yasl_private_loop_add_timer(ctx->loop, 1000000ull, child_timer_cb, ctx); assert(ctx->other_id > 0); return 0; } /* 5. Timer deleting another timer from inside callback. */ -static uint64_t victim_timer_cb(void *ud, yasl_timer_id id, uint64_t interval_ns) +static uint64_t victim_timer_cb(void *ud, yasl_private_timer_id id, uint64_t interval_ns) { (void)ud; (void)id; (void)interval_ns; assert(false && "Victim timer should have been cancelled before firing!"); return 0; } -static uint64_t killer_timer_cb(void *ud, yasl_timer_id id, uint64_t interval_ns) +static uint64_t killer_timer_cb(void *ud, yasl_private_timer_id id, uint64_t interval_ns) { (void)id; (void)interval_ns; struct test_ctx *ctx = ud; ctx->count++; - assert(yasl_loop_del_timer(ctx->loop, ctx->other_id)); + assert(yasl_private_loop_del_timer(ctx->loop, ctx->other_id)); return 0; } /* 6. Millisecond convenience conversion uses the same timer callback. */ -static uint64_t ms_timer_cb(void *ud, yasl_timer_id id, uint64_t interval_ns) +static uint64_t ms_timer_cb(void *ud, yasl_private_timer_id id, uint64_t interval_ns) { struct test_ctx *ctx = ud; assert(id == ctx->id); - assert(interval_ns == YASL_MS_TO_NS(5)); + assert(interval_ns == YASL_PRIVATE_MS_TO_NS(5)); ctx->count++; if (ctx->count >= 3) { ctx->done = true; @@ -111,94 +111,94 @@ static uint64_t ms_timer_cb(void *ud, yasl_timer_id id, uint64_t interval_ns) static void test_loop_timers(void) { - struct yasl_loop *l = yasl_loop_create(); + struct yasl_private_loop *l = yasl_private_loop_create(); assert(l); /* Invalid arguments */ - assert(yasl_loop_add_timer(NULL, 1000, oneshot_cb, NULL) == 0); - assert(yasl_loop_add_timer(l, 0, oneshot_cb, NULL) == 0); - assert(yasl_loop_add_timer(l, 1000, NULL, NULL) == 0); - assert(!yasl_loop_del_timer(NULL, 1)); - assert(!yasl_loop_del_timer(l, 0)); - assert(!yasl_loop_del_timer(l, 99999)); - assert(yasl_loop_next_timeout_ms(l) == -1); + assert(yasl_private_loop_add_timer(NULL, 1000, oneshot_cb, NULL) == 0); + assert(yasl_private_loop_add_timer(l, 0, oneshot_cb, NULL) == 0); + assert(yasl_private_loop_add_timer(l, 1000, NULL, NULL) == 0); + assert(!yasl_private_loop_del_timer(NULL, 1)); + assert(!yasl_private_loop_del_timer(l, 0)); + assert(!yasl_private_loop_del_timer(l, 99999)); + assert(yasl_private_loop_next_timeout_ms(l) == -1); /* Test 1: Single-shot */ struct test_ctx ctx1 = { .loop = l }; - ctx1.id = yasl_loop_add_timer(l, 5000000ull, oneshot_cb, &ctx1); + ctx1.id = yasl_private_loop_add_timer(l, 5000000ull, oneshot_cb, &ctx1); assert(ctx1.id > 0); - assert(yasl_loop_next_timeout_ms(l) >= 0); + assert(yasl_private_loop_next_timeout_ms(l) >= 0); while (ctx1.count == 0) { - int handled = yasl_loop_step(l, 50); + int handled = yasl_private_loop_step(l, 50); assert(handled >= 0); } assert(ctx1.count == 1); /* Should be disarmed and removed */ - assert(!yasl_loop_del_timer(l, ctx1.id)); - assert(yasl_loop_next_timeout_ms(l) == -1); + assert(!yasl_private_loop_del_timer(l, ctx1.id)); + assert(yasl_private_loop_next_timeout_ms(l) == -1); /* Test 2: Repeating */ struct test_ctx ctx2 = { .loop = l }; - ctx2.id = yasl_loop_add_timer(l, 2000000ull, repeat_cb, &ctx2); + ctx2.id = yasl_private_loop_add_timer(l, 2000000ull, repeat_cb, &ctx2); assert(ctx2.id > 0); while (!ctx2.done) { - yasl_loop_step(l, 50); + yasl_private_loop_step(l, 50); } assert(ctx2.count == 5); - assert(!yasl_loop_del_timer(l, ctx2.id)); + assert(!yasl_private_loop_del_timer(l, ctx2.id)); /* Test 3: Reschedule dynamically */ struct test_ctx ctx3 = { .loop = l }; - ctx3.id = yasl_loop_add_timer(l, 2000000ull, reschedule_cb, &ctx3); + ctx3.id = yasl_private_loop_add_timer(l, 2000000ull, reschedule_cb, &ctx3); assert(ctx3.id > 0); while (!ctx3.done) { - yasl_loop_step(l, 50); + yasl_private_loop_step(l, 50); } assert(ctx3.count == 2); /* Test 4: Timer cancellation before firing */ struct test_ctx ctx4 = { .loop = l }; - yasl_timer_id cancel_id = yasl_loop_add_timer(l, 5000000ull, oneshot_cb, &ctx4); + yasl_private_timer_id cancel_id = yasl_private_loop_add_timer(l, 5000000ull, oneshot_cb, &ctx4); assert(cancel_id > 0); - assert(yasl_loop_del_timer(l, cancel_id)); - assert(!yasl_loop_del_timer(l, cancel_id)); + assert(yasl_private_loop_del_timer(l, cancel_id)); + assert(!yasl_private_loop_del_timer(l, cancel_id)); /* Step for 10ms; timer should not fire */ - yasl_loop_step(l, 10); + yasl_private_loop_step(l, 10); assert(ctx4.count == 0); /* Test 5: Child timer added inside callback */ struct test_ctx ctx5 = { .loop = l }; - ctx5.id = yasl_loop_add_timer(l, 2000000ull, parent_timer_cb, &ctx5); + ctx5.id = yasl_private_loop_add_timer(l, 2000000ull, parent_timer_cb, &ctx5); while (ctx5.count < 11) { - yasl_loop_step(l, 50); + yasl_private_loop_step(l, 50); } assert(ctx5.count == 11); /* Test 6: Killer timer deletes victim timer before victim fires */ struct test_ctx ctx6 = { .loop = l }; - ctx6.id = yasl_loop_add_timer(l, 1000000ull, killer_timer_cb, &ctx6); - ctx6.other_id = yasl_loop_add_timer(l, 10000000ull, victim_timer_cb, &ctx6); + ctx6.id = yasl_private_loop_add_timer(l, 1000000ull, killer_timer_cb, &ctx6); + ctx6.other_id = yasl_private_loop_add_timer(l, 10000000ull, victim_timer_cb, &ctx6); while (ctx6.count == 0) { - yasl_loop_step(l, 50); + yasl_private_loop_step(l, 50); } assert(ctx6.count == 1); - yasl_loop_step(l, 20); + yasl_private_loop_step(l, 20); /* Test 7: Millisecond conversion */ struct test_ctx ctx7 = { .loop = l }; - ctx7.id = yasl_loop_add_timer(l, YASL_MS_TO_NS(5), ms_timer_cb, &ctx7); + ctx7.id = yasl_private_loop_add_timer(l, YASL_PRIVATE_MS_TO_NS(5), ms_timer_cb, &ctx7); assert(ctx7.id > 0); while (!ctx7.done) { - yasl_loop_step(l, 50); + yasl_private_loop_step(l, 50); } assert(ctx7.count == 3); - yasl_loop_destroy(l); + yasl_private_loop_destroy(l); } /* Surface timer tests */ -static uint64_t surface_timer_repeat_cb(void *ud, yasl_timer_id id, uint64_t interval_ns) +static uint64_t surface_timer_repeat_cb(void *ud, yasl_private_timer_id id, uint64_t interval_ns) { (void)interval_ns; struct test_ctx *ctx = ud; @@ -207,96 +207,96 @@ static uint64_t surface_timer_repeat_cb(void *ud, yasl_timer_id id, uint64_t int return interval_ns; } -static uint64_t surface_suicide_cb(void *ud, yasl_timer_id id, uint64_t interval_ns) +static uint64_t surface_suicide_cb(void *ud, yasl_private_timer_id id, uint64_t interval_ns) { (void)id; (void)interval_ns; struct test_ctx *ctx = ud; ctx->count++; /* Destroy surface from within the timer callback */ - yasl_surface_destroy(ctx->surface); + yasl_private_surface_destroy(ctx->surface); ctx->surface = NULL; return 1000000ull; /* Even if it returns > 0, surface is destroyed */ } static void test_surface_timers(void) { - struct yasl_loop *loop = yasl_loop_create(); + struct yasl_private_loop *loop = yasl_private_loop_create(); assert(loop); - struct yasl_display_spec spec = { .app_id = "org.yasl.timer_test" }; - struct yasl_display *d = yasl_display_open(loop, &spec); + struct yasl_private_display_spec spec = { .app_id = "org.yasl.timer_test" }; + struct yasl_private_display *d = yasl_private_display_open(loop, &spec); if (!d) { /* Headless without display server */ - yasl_loop_destroy(loop); + yasl_private_loop_destroy(loop); return; } - assert(yasl_display_loop(d) == loop); + assert(yasl_private_display_loop(d) == loop); - struct yasl_window_spec sspec = { + struct yasl_private_window_spec sspec = { .title = "Timer Surface", .size = { .x = 160, .y = 100 }, }; - struct yasl_surface *s = yasl_surface_create_toplevel(d, &sspec); + struct yasl_surface *s = yasl_private_surface_create_toplevel(d, &sspec); assert(s); - assert(yasl_display_loop(yasl_surface_display(s)) == loop); + assert(yasl_private_display_loop(yasl_private_surface_display(s)) == loop); /* Application timers use the display's loop. */ struct test_ctx dctx = {0}; - dctx.id = yasl_loop_add_timer(yasl_display_loop(d), 2000000ull, oneshot_cb, &dctx); + dctx.id = yasl_private_loop_add_timer(yasl_private_display_loop(d), 2000000ull, oneshot_cb, &dctx); assert(dctx.id > 0); while (dctx.count == 0) { - yasl_display_dispatch(d, 50); + yasl_private_display_dispatch(d, 50); } assert(dctx.count == 1); /* Surface timer basic repeat & manual delete */ struct test_ctx sctx = {0}; - sctx.id = yasl_surface_add_timer(s, 2000000ull, surface_timer_repeat_cb, &sctx); + sctx.id = yasl_private_surface_add_timer(s, 2000000ull, surface_timer_repeat_cb, &sctx); assert(sctx.id > 0); while (sctx.count < 3) { - yasl_display_dispatch(d, 50); + yasl_private_display_dispatch(d, 50); } assert(sctx.count >= 3); - assert(yasl_surface_del_timer(s, sctx.id)); + assert(yasl_private_surface_del_timer(s, sctx.id)); int count_before = sctx.count; - yasl_display_dispatch(d, 10); + yasl_private_display_dispatch(d, 10); assert(sctx.count == count_before); /* A fired one-shot surface timer is retired, as a loop timer is. Many * of them stay retired after the surface's id list compacts. */ for (int i = 0; i < 64; i++) { struct test_ctx octx = {0}; - octx.id = yasl_surface_add_timer(s, 1000000ull, oneshot_cb, &octx); + octx.id = yasl_private_surface_add_timer(s, 1000000ull, oneshot_cb, &octx); assert(octx.id > 0); while (octx.count == 0) - yasl_display_dispatch(d, 50); - assert(!yasl_surface_del_timer(s, octx.id)); + yasl_private_display_dispatch(d, 50); + assert(!yasl_private_surface_del_timer(s, octx.id)); } /* Surface timer auto-cleanup on surface destruction */ struct test_ctx sctx2 = {0}; - sctx2.id = yasl_surface_add_timer(s, 2000000ull, surface_timer_repeat_cb, &sctx2); + sctx2.id = yasl_private_surface_add_timer(s, 2000000ull, surface_timer_repeat_cb, &sctx2); assert(sctx2.id > 0); - yasl_surface_destroy(s); + yasl_private_surface_destroy(s); /* Step display/loop; timer must not fire on dead surface */ - yasl_display_dispatch(d, 10); + yasl_private_display_dispatch(d, 10); assert(sctx2.count == 0); /* Surface suicide from timer callback */ - s = yasl_surface_create_toplevel(d, &sspec); + s = yasl_private_surface_create_toplevel(d, &sspec); assert(s); struct test_ctx sctx3 = { .surface = s }; - sctx3.id = yasl_surface_add_timer(s, 2000000ull, surface_suicide_cb, &sctx3); + sctx3.id = yasl_private_surface_add_timer(s, 2000000ull, surface_suicide_cb, &sctx3); assert(sctx3.id > 0); while (sctx3.count == 0) { - yasl_display_dispatch(d, 50); + yasl_private_display_dispatch(d, 50); } assert(sctx3.count == 1); assert(sctx3.surface == NULL); - yasl_display_close(d); - yasl_loop_destroy(loop); + yasl_private_display_close(d); + yasl_private_loop_destroy(loop); } int main(void) diff --git a/tests/common/test_utf8.c b/tests/common/test_utf8.c index 72f8904..58bfe9d 100644 --- a/tests/common/test_utf8.c +++ b/tests/common/test_utf8.c @@ -9,19 +9,19 @@ int main(void) for (int i = 1; i <= 255; i++) latin1[i - 1] = (char)i; latin1[255] = '\0'; size_t len = 0, back_len = 0; - char *utf8 = yasl_utf8_from_latin1(latin1, 255, &len); - assert(utf8 && yasl_utf8_validate(utf8, len)); - char *back = yasl_utf8_to_latin1(utf8, &back_len); + char *utf8 = yasl_private_utf8_from_latin1(latin1, 255, &len); + assert(utf8 && yasl_private_utf8_validate(utf8, len)); + char *back = yasl_private_utf8_to_latin1(utf8, &back_len); assert(back && back_len == 255 && memcmp(back, latin1, 256) == 0); free(back); free(utf8); - assert(!yasl_utf8_to_latin1("😀", &len)); - assert(!yasl_utf8_to_latin1("\xc3", &len)); - assert(!yasl_utf8_validate("\xc3", 1)); - assert(!yasl_utf8_validate("\xe2\x82", 2)); - assert(!yasl_utf8_validate("\xf0\x9f\x98", 3)); - assert(!yasl_utf8_validate("\xed\xa0\x80", 3)); /* Surrogate. */ - assert(yasl_utf8_validate("😀", sizeof("😀") - 1)); - assert(yasl_utf8_validate(NULL, 0)); + assert(!yasl_private_utf8_to_latin1("😀", &len)); + assert(!yasl_private_utf8_to_latin1("\xc3", &len)); + assert(!yasl_private_utf8_validate("\xc3", 1)); + assert(!yasl_private_utf8_validate("\xe2\x82", 2)); + assert(!yasl_private_utf8_validate("\xf0\x9f\x98", 3)); + assert(!yasl_private_utf8_validate("\xed\xa0\x80", 3)); /* Surrogate. */ + assert(yasl_private_utf8_validate("😀", sizeof("😀") - 1)); + assert(yasl_private_utf8_validate(NULL, 0)); return 0; } diff --git a/tests/common/test_vulkan.c b/tests/common/test_vulkan.c new file mode 100644 index 0000000..12daa22 --- /dev/null +++ b/tests/common/test_vulkan.c @@ -0,0 +1,389 @@ +/* Public Vulkan WSI integration test. The launcher supplies a real Wayland + * or X11 session (normally Weston/Xwayland); no internal YASL headers are + * used here. */ +#include "yasl/yasl.h" +#include "yasl/vulkan.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static int fail_vk(const char *what, VkResult result) +{ + fprintf(stderr, "vulkan integration: %s failed (VkResult %d)\n", what, result); + return 1; +} + +static bool has_device_extension(VkPhysicalDevice device, const char *name) +{ + uint32_t count = 0; + if (vkEnumerateDeviceExtensionProperties(device, NULL, &count, NULL) != VK_SUCCESS) + return false; + VkExtensionProperties *extensions = count ? calloc(count, sizeof(*extensions)) : NULL; + if (count && !extensions) return false; + bool found = false; + if (vkEnumerateDeviceExtensionProperties(device, NULL, &count, extensions) == VK_SUCCESS) + for (uint32_t i = 0; i < count; i++) + if (strcmp(extensions[i].extensionName, name) == 0) found = true; + free(extensions); + return found; +} + +static int run_test(enum yasl_backend backend) +{ + int status = 1; + yasl_surface *surface = NULL; + VkInstance instance = VK_NULL_HANDLE; + VkSurfaceKHR vk_surface = VK_NULL_HANDLE; + VkDevice device = VK_NULL_HANDLE; + VkSwapchainKHR swapchain = VK_NULL_HANDLE; + VkCommandPool command_pool = VK_NULL_HANDLE; + VkSemaphore acquired = VK_NULL_HANDLE, rendered = VK_NULL_HANDLE; + VkCommandBuffer command = VK_NULL_HANDLE; + + if (!yasl_set_backend(backend)) { + fprintf(stderr, "vulkan integration: cannot select requested backend %d (YASL error %d)\n", + backend, yasl_get_error(NULL)); + goto cleanup; + } + if (!yasl_vulkan_init(vkGetInstanceProcAddr)) { + fprintf(stderr, "vulkan integration: loader initialization failed (YASL error %d)\n", + yasl_get_error(NULL)); + goto cleanup; + } + surface = yasl_surface_create("yasl Vulkan WSI integration", 256, 192, 0); + if (!surface) { + fprintf(stderr, "vulkan integration: surface creation failed (YASL error %d)\n", + yasl_get_error(NULL)); + goto cleanup; + } + if (!(yasl_surface_features(surface) & YASL_CAP_VULKAN)) { + fprintf(stderr, "vulkan integration: requested backend does not advertise Vulkan WSI\n"); + goto cleanup; + } + + uint32_t extension_count = 0; + const char *const *required = yasl_vulkan_instance_extensions(surface, &extension_count); + if (!required || !extension_count) { + fprintf(stderr, "vulkan integration: surface returned no required instance extensions\n"); + goto cleanup; + } + uint32_t available_count = 0; + VkResult vr = vkEnumerateInstanceExtensionProperties(NULL, &available_count, NULL); + if (vr != VK_SUCCESS) { status = fail_vk("vkEnumerateInstanceExtensionProperties", vr); goto cleanup; } + VkExtensionProperties *available = available_count ? calloc(available_count, sizeof(*available)) : NULL; + if (available_count && !available) { fprintf(stderr, "vulkan integration: out of memory\n"); goto cleanup; } + vr = vkEnumerateInstanceExtensionProperties(NULL, &available_count, available); + if (vr != VK_SUCCESS) { + free(available); status = fail_vk("vkEnumerateInstanceExtensionProperties", vr); goto cleanup; + } + for (uint32_t i = 0; i < extension_count; i++) { + bool found = false; + for (uint32_t j = 0; j < available_count; j++) + if (strcmp(required[i], available[j].extensionName) == 0) found = true; + if (!found) { + fprintf(stderr, "vulkan integration: required instance extension unavailable: %s\n", required[i]); + free(available); goto cleanup; + } + } + free(available); + + VkApplicationInfo app = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "yasl Vulkan WSI integration", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "yasl", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = VK_API_VERSION_1_0, + }; + VkInstanceCreateInfo instance_info = { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &app, + .flags = yasl_vulkan_instance_flags(surface), + .enabledExtensionCount = extension_count, + .ppEnabledExtensionNames = required, + }; + vr = vkCreateInstance(&instance_info, NULL, &instance); + if (vr != VK_SUCCESS) { status = fail_vk("vkCreateInstance", vr); goto cleanup; } + vr = yasl_vulkan_create_surface(surface, instance, NULL, &vk_surface); + if (vr != VK_SUCCESS) { status = fail_vk("yasl_vulkan_create_surface", vr); goto cleanup; } + + uint32_t physical_count = 0; + vr = vkEnumeratePhysicalDevices(instance, &physical_count, NULL); + if (vr != VK_SUCCESS || !physical_count) { + status = vr == VK_SUCCESS ? 1 : fail_vk("vkEnumeratePhysicalDevices", vr); + if (vr == VK_SUCCESS) fprintf(stderr, "vulkan integration: no physical devices found\n"); + goto cleanup; + } + VkPhysicalDevice *physical = calloc(physical_count, sizeof(*physical)); + if (!physical) { fprintf(stderr, "vulkan integration: out of memory\n"); goto cleanup; } + vr = vkEnumeratePhysicalDevices(instance, &physical_count, physical); + if (vr != VK_SUCCESS) { free(physical); status = fail_vk("vkEnumeratePhysicalDevices", vr); goto cleanup; } + + VkPhysicalDevice selected = VK_NULL_HANDLE; + uint32_t queue_family = UINT32_MAX; + for (uint32_t p = 0; p < physical_count && !selected; p++) { + if (!has_device_extension(physical[p], VK_KHR_SWAPCHAIN_EXTENSION_NAME)) continue; + uint32_t family_count = 0; + vkGetPhysicalDeviceQueueFamilyProperties(physical[p], &family_count, NULL); + VkQueueFamilyProperties *families = family_count ? calloc(family_count, sizeof(*families)) : NULL; + if (family_count && !families) continue; + vkGetPhysicalDeviceQueueFamilyProperties(physical[p], &family_count, families); + for (uint32_t q = 0; q < family_count; q++) { + VkBool32 present = VK_FALSE; + if ((families[q].queueFlags & VK_QUEUE_GRAPHICS_BIT) && + vkGetPhysicalDeviceSurfaceSupportKHR(physical[p], q, vk_surface, &present) == VK_SUCCESS && present) { + selected = physical[p]; + queue_family = q; + break; + } + } + free(families); + } + free(physical); + if (!selected) { + fprintf(stderr, "vulkan integration: no graphics queue with VK_KHR_swapchain presentation support\n"); + goto cleanup; + } + + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(selected, &properties); + fprintf(stderr, "vulkan integration: using %s\n", properties.deviceName); + VkSurfaceCapabilitiesKHR capabilities; + vr = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(selected, vk_surface, &capabilities); + if (vr != VK_SUCCESS) { status = fail_vk("vkGetPhysicalDeviceSurfaceCapabilitiesKHR", vr); goto cleanup; } + if (!(capabilities.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT)) { + fprintf(stderr, "vulkan integration: surface images cannot be transfer destinations\n"); + goto cleanup; + } + uint32_t format_count = 0; + vr = vkGetPhysicalDeviceSurfaceFormatsKHR(selected, vk_surface, &format_count, NULL); + if (vr != VK_SUCCESS || !format_count) { + status = vr == VK_SUCCESS ? 1 : fail_vk("vkGetPhysicalDeviceSurfaceFormatsKHR", vr); + if (vr == VK_SUCCESS) fprintf(stderr, "vulkan integration: surface has no formats\n"); + goto cleanup; + } + VkSurfaceFormatKHR *formats = calloc(format_count, sizeof(*formats)); + if (!formats) { fprintf(stderr, "vulkan integration: out of memory\n"); goto cleanup; } + vr = vkGetPhysicalDeviceSurfaceFormatsKHR(selected, vk_surface, &format_count, formats); + if (vr != VK_SUCCESS) { free(formats); status = fail_vk("vkGetPhysicalDeviceSurfaceFormatsKHR", vr); goto cleanup; } + VkSurfaceFormatKHR format = formats[0]; + for (uint32_t i = 0; i < format_count; i++) + if (formats[i].format == VK_FORMAT_B8G8R8A8_SRGB && + formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { format = formats[i]; break; } + if (format.format == VK_FORMAT_UNDEFINED) format.format = VK_FORMAT_B8G8R8A8_UNORM; + free(formats); + + VkExtent2D extent = capabilities.currentExtent; + if (extent.width == UINT32_MAX) { + int width = 0, height = 0; + yasl_surface_get_framebuffer_size(surface, &width, &height); + if (width <= 0 || height <= 0) { + fprintf(stderr, "vulkan integration: surface has no drawable framebuffer extent\n"); + goto cleanup; + } + extent.width = (uint32_t)width; + extent.height = (uint32_t)height; + if (extent.width < capabilities.minImageExtent.width) extent.width = capabilities.minImageExtent.width; + if (extent.width > capabilities.maxImageExtent.width) extent.width = capabilities.maxImageExtent.width; + if (extent.height < capabilities.minImageExtent.height) extent.height = capabilities.minImageExtent.height; + if (extent.height > capabilities.maxImageExtent.height) extent.height = capabilities.maxImageExtent.height; + } + uint32_t image_count = capabilities.minImageCount + 1; + if (capabilities.maxImageCount && image_count > capabilities.maxImageCount) + image_count = capabilities.maxImageCount; + VkCompositeAlphaFlagBitsKHR alpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + if (!(capabilities.supportedCompositeAlpha & alpha)) { + const VkCompositeAlphaFlagBitsKHR alphas[] = { + VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, + }; + bool found = false; + for (size_t i = 0; i < sizeof(alphas) / sizeof(alphas[0]); i++) + if (capabilities.supportedCompositeAlpha & alphas[i]) { alpha = alphas[i]; found = true; break; } + if (!found) { fprintf(stderr, "vulkan integration: no supported composite alpha mode\n"); goto cleanup; } + } + uint32_t present_mode_count = 0; + vr = vkGetPhysicalDeviceSurfacePresentModesKHR(selected, vk_surface, &present_mode_count, NULL); + if (vr != VK_SUCCESS || !present_mode_count) { + status = vr == VK_SUCCESS ? 1 : fail_vk("vkGetPhysicalDeviceSurfacePresentModesKHR", vr); + if (vr == VK_SUCCESS) fprintf(stderr, "vulkan integration: surface has no present modes\n"); + goto cleanup; + } + VkPresentModeKHR *present_modes = calloc(present_mode_count, sizeof(*present_modes)); + if (!present_modes) { fprintf(stderr, "vulkan integration: out of memory\n"); goto cleanup; } + vr = vkGetPhysicalDeviceSurfacePresentModesKHR(selected, vk_surface, &present_mode_count, present_modes); + free(present_modes); + if (vr != VK_SUCCESS) { status = fail_vk("vkGetPhysicalDeviceSurfacePresentModesKHR", vr); goto cleanup; } + + float priority = 1.0f; + VkDeviceQueueCreateInfo queue_info = { + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = queue_family, + .queueCount = 1, + .pQueuePriorities = &priority, + }; + const char *device_extensions[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + VkDeviceCreateInfo device_info = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &queue_info, + .enabledExtensionCount = 1, + .ppEnabledExtensionNames = device_extensions, + }; + vr = vkCreateDevice(selected, &device_info, NULL, &device); + if (vr != VK_SUCCESS) { status = fail_vk("vkCreateDevice", vr); goto cleanup; } + VkQueue queue; + vkGetDeviceQueue(device, queue_family, 0, &queue); + + VkSwapchainCreateInfoKHR swap_info = { + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = vk_surface, + .minImageCount = image_count, + .imageFormat = format.format, + .imageColorSpace = format.colorSpace, + .imageExtent = extent, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_TRANSFER_DST_BIT, + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, + .preTransform = capabilities.currentTransform, + .compositeAlpha = alpha, + .presentMode = VK_PRESENT_MODE_FIFO_KHR, + .clipped = VK_TRUE, + }; + vr = vkCreateSwapchainKHR(device, &swap_info, NULL, &swapchain); + if (vr != VK_SUCCESS) { status = fail_vk("vkCreateSwapchainKHR", vr); goto cleanup; } + uint32_t swap_image_count = 0; + vr = vkGetSwapchainImagesKHR(device, swapchain, &swap_image_count, NULL); + if (vr != VK_SUCCESS || !swap_image_count) { + status = vr == VK_SUCCESS ? 1 : fail_vk("vkGetSwapchainImagesKHR", vr); + if (vr == VK_SUCCESS) fprintf(stderr, "vulkan integration: swapchain returned no images\n"); + goto cleanup; + } + VkImage *images = calloc(swap_image_count, sizeof(*images)); + if (!images) { fprintf(stderr, "vulkan integration: out of memory\n"); goto cleanup; } + vr = vkGetSwapchainImagesKHR(device, swapchain, &swap_image_count, images); + if (vr != VK_SUCCESS) { free(images); status = fail_vk("vkGetSwapchainImagesKHR", vr); goto cleanup; } + + VkCommandPoolCreateInfo pool_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, + .queueFamilyIndex = queue_family, + }; + vr = vkCreateCommandPool(device, &pool_info, NULL, &command_pool); + if (vr != VK_SUCCESS) { free(images); status = fail_vk("vkCreateCommandPool", vr); goto cleanup; } + VkCommandBufferAllocateInfo command_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = command_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + vr = vkAllocateCommandBuffers(device, &command_info, &command); + if (vr != VK_SUCCESS) { free(images); status = fail_vk("vkAllocateCommandBuffers", vr); goto cleanup; } + VkSemaphoreCreateInfo semaphore_info = {.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; + vr = vkCreateSemaphore(device, &semaphore_info, NULL, &acquired); + if (vr != VK_SUCCESS) { free(images); status = fail_vk("vkCreateSemaphore(acquired)", vr); goto cleanup; } + vr = vkCreateSemaphore(device, &semaphore_info, NULL, &rendered); + if (vr != VK_SUCCESS) { free(images); status = fail_vk("vkCreateSemaphore(rendered)", vr); goto cleanup; } + + uint32_t image_index = 0; + vr = vkAcquireNextImageKHR(device, swapchain, UINT64_MAX, acquired, VK_NULL_HANDLE, &image_index); + if (vr != VK_SUCCESS && vr != VK_SUBOPTIMAL_KHR) { + free(images); status = fail_vk("vkAcquireNextImageKHR", vr); goto cleanup; + } + VkCommandBufferBeginInfo begin = {.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + vr = vkBeginCommandBuffer(command, &begin); + if (vr != VK_SUCCESS) { free(images); status = fail_vk("vkBeginCommandBuffer", vr); goto cleanup; } + VkImageMemoryBarrier to_clear = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = images[image_index], + .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}, + }; + vkCmdPipelineBarrier(command, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &to_clear); + VkClearColorValue color = {.float32 = {0.08f, 0.18f, 0.32f, 1.0f}}; + VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + vkCmdClearColorImage(command, images[image_index], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &color, 1, &range); + VkImageMemoryBarrier to_present = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = 0, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = images[image_index], + .subresourceRange = range, + }; + vkCmdPipelineBarrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &to_present); + vr = vkEndCommandBuffer(command); + free(images); + if (vr != VK_SUCCESS) { status = fail_vk("vkEndCommandBuffer", vr); goto cleanup; } + VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; + VkSubmitInfo submit = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &acquired, + .pWaitDstStageMask = &wait_stage, + .commandBufferCount = 1, + .pCommandBuffers = &command, + .signalSemaphoreCount = 1, + .pSignalSemaphores = &rendered, + }; + vr = vkQueueSubmit(queue, 1, &submit, VK_NULL_HANDLE); + if (vr != VK_SUCCESS) { status = fail_vk("vkQueueSubmit", vr); goto cleanup; } + VkPresentInfoKHR present = { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &rendered, + .swapchainCount = 1, + .pSwapchains = &swapchain, + .pImageIndices = &image_index, + }; + vr = vkQueuePresentKHR(queue, &present); + if (vr != VK_SUCCESS && vr != VK_SUBOPTIMAL_KHR) { + status = fail_vk("vkQueuePresentKHR", vr); goto cleanup; + } + vr = vkDeviceWaitIdle(device); + if (vr != VK_SUCCESS) { status = fail_vk("vkDeviceWaitIdle", vr); goto cleanup; } + fprintf(stderr, "vulkan integration: created surface, cleared and presented %ux%u swapchain image\n", + extent.width, extent.height); + status = 0; + +cleanup: + if (device) vkDeviceWaitIdle(device); + if (rendered) vkDestroySemaphore(device, rendered, NULL); + if (acquired) vkDestroySemaphore(device, acquired, NULL); + if (command_pool) vkDestroyCommandPool(device, command_pool, NULL); + if (swapchain) vkDestroySwapchainKHR(device, swapchain, NULL); + if (device) vkDestroyDevice(device, NULL); + if (vk_surface) vkDestroySurfaceKHR(instance, vk_surface, NULL); + if (instance) vkDestroyInstance(instance, NULL); + if (surface) yasl_surface_destroy(surface); + yasl_shutdown(); + return status; +} + +int main(int argc, char **argv) +{ + if (argc != 2) { + fprintf(stderr, "usage: %s wayland|x11\n", argv[0]); + return 2; + } + enum yasl_backend backend; + if (strcmp(argv[1], "wayland") == 0) backend = YASL_BACKEND_WAYLAND; + else if (strcmp(argv[1], "x11") == 0) backend = YASL_BACKEND_X11; + else { + fprintf(stderr, "vulkan integration: unsupported backend '%s'\n", argv[1]); + return 2; + } + return run_test(backend); +} diff --git a/tests/helpers/run_surfaces.py b/tests/helpers/run_surfaces.py new file mode 100644 index 0000000..f120e35 --- /dev/null +++ b/tests/helpers/run_surfaces.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +"""Run the public surface/GPU contract tests on private Weston and Xwayland.""" +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import time + +def wait_for(process, ready): + deadline = time.monotonic() + 12 + while time.monotonic() < deadline: + if process.poll() is not None: + raise RuntimeError('Display server stopped before readiness') + if ready(): + return + time.sleep(0.02) + raise TimeoutError('Display server readiness') + +def main(): + weston, xwayland, contract, hello, tablet, gl, vulkan, notepad = sys.argv[1:] + servers = [] + with tempfile.TemporaryDirectory(prefix='yasl-surfaces-') as directory: + root = Path(directory) + env = os.environ.copy() + env.pop('WAYLAND_SOCKET', None) + env.update(XDG_RUNTIME_DIR=directory, WAYLAND_DISPLAY='yasl-test', + XDG_SESSION_TYPE='wayland', LIBGL_ALWAYS_SOFTWARE='1') + lavapipe = next(Path('/usr/share/vulkan/icd.d').glob('*lvp*.json'), None) + if lavapipe: + env['VK_DRIVER_FILES'] = str(lavapipe) + with (root/'weston.log').open('w+') as wl_log, (root/'xwayland.log').open('w+') as x_log: + try: + wl = subprocess.Popen([weston, '--backend=headless', '--renderer=gl', + '--socket=yasl-test', '--idle-time=0', '--no-config', + '--width=1024', '--height=768'], env=env, stdout=wl_log, stderr=wl_log) + servers.append(wl) + wait_for(wl, lambda: (root/'yasl-test').is_socket()) + display_file = root/'display' + with display_file.open('w') as fd: + x = subprocess.Popen([xwayland, '-displayfd', str(fd.fileno()), + '-shm', '-noreset', '-nolisten', 'tcp'], env=env, + pass_fds=(fd.fileno(),), stdout=x_log, stderr=x_log) + servers.append(x) + wait_for(x, lambda: bool(display_file.read_text().strip())) + env['DISPLAY'] = ':' + display_file.read_text().strip() + for backend in ('wayland', 'x11'): + case = env.copy() + if backend == 'x11': + case.pop('WAYLAND_DISPLAY', None) + case['XDG_SESSION_TYPE'] = 'x11' + for binary, args in ((contract,[backend]), (gl,[backend]), + (vulkan,[backend]), (tablet,['--backend',backend,'--smoke-test']), + (notepad,['--smoke-test'])): + if binary == '-': + continue + print('Testing', Path(binary).name, backend, flush=True) + subprocess.run([binary, *args], env=case, check=True, timeout=15) + # hello runs until closed: it must survive its first frames. + if hello == '-': + continue + print('Testing', Path(hello).name, backend, flush=True) + process = subprocess.Popen([hello], env=case) + try: + process.wait(timeout=1) + raise RuntimeError(f'{Path(hello).name} exited with {process.returncode}') + except subprocess.TimeoutExpired: + process.terminate() + process.wait() + except BaseException: + for log in (wl_log,x_log): + log.flush();log.seek(0);print(log.read(), file=sys.stderr) + raise + finally: + for server in reversed(servers): + server.terminate() + try: + server.wait(timeout=5) + except subprocess.TimeoutExpired: + server.kill();server.wait() +if __name__ == '__main__': + main() diff --git a/tests/meson.build b/tests/meson.build index cac62db..4cbf082 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,291 +1,105 @@ -test_exe = executable( - 'yasl-test', - 'test_lifecycle.c', - include_directories: inc, - link_with: yasl_lib.get_static_lib(), - dependencies: deps, -) - -add_test_setup('asan') -add_test_setup('tsan') -add_test_setup('msan') - -test('basic', test_exe) - -shared_dep = declare_dependency( - link_with: yasl_lib.get_shared_lib(), - include_directories: include_directories('../include'), -) - -shared_api_test = executable( - 'yasl-shared-api-test', - 'test_lifecycle.c', - dependencies: shared_dep, -) -test('shared-api', shared_api_test) - +python = find_program('python3', native: true) +shared_dep = declare_dependency(link_with: yasl_lib.get_shared_lib(), + include_directories: include_directories('../include')) +foreach kind : ['static', 'shared'] + dep = kind == 'static' ? yasl_dep : shared_dep + public_test = executable('yasl-' + kind + '-api-test', 'test_lifecycle.c', dependencies: dep) + test(kind + '-api', public_test) +endforeach if add_languages('cpp', native: false, required: false) - cpp = meson.get_compiler('cpp') - cpp_api_test = executable( - 'yasl-cpp-api-test', - 'test_api.cpp', - dependencies: shared_dep, - cpp_args: cpp.get_supported_arguments('-pedantic-errors') + [ - '-DYASL_HAS_WAYLAND=' + (has_wayland ? '1' : '0'), - '-DYASL_HAS_X11=' + (has_x11 ? '1' : '0'), - '-DYASL_HAS_COCOA=' + (has_cocoa ? '1' : '0'), - ], - override_options: ['cpp_std=c++11'], - ) - test('cpp-api', cpp_api_test) + cpp_api = executable('yasl-cpp-api-test', 'test_api.cpp', dependencies: shared_dep, + cpp_args: ['-pedantic-errors'], override_options: ['cpp_std=c++11']) + test('cpp-api', cpp_api) endif - -python = find_program('python3', native: true) -if system == 'linux' or system == 'darwin' - nm = find_program('nm', native: false) - export_args = ['--platform=' + system] - if get_option('b_sanitize').contains('address') - # Clang's ASan instrumentation exports these linker-generated markers. - export_args += ['--allow-export=__start_asan_globals', - '--allow-export=__stop_asan_globals'] - endif - exported_headers = files( - '../include/yasl/yasl.h', - '../include/yasl/app.h', - '../include/yasl/desktop.h', - '../include/yasl/timing.h', - '../include/yasl/loop.h', - '../include/yasl/native.h', - ) - if has_wayland - exported_headers += files('../include/yasl/native/wayland.h') - endif - if has_x11 - exported_headers += files('../include/yasl/native/x11.h') - endif - if has_cocoa - exported_headers += files('../include/yasl/native/cocoa.h') +headers = files('../include/yasl/yasl.h', '../include/yasl/cpu.h', '../include/yasl/gl.h', + '../include/yasl/metal.h', '../include/yasl/desktop.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 - test('public-exports', python, - args: [files('helpers/check_exports.py')] + export_args + - [nm, yasl_lib.get_shared_lib(), exported_headers], - ) +endforeach +nm = find_program('nm') +export_args = ['--platform=' + system] +if get_option('b_sanitize').contains('address') + export_args += ['--allow-export=__start_asan_globals', '--allow-export=__stop_asan_globals'] endif - -contracts_test = executable( - 'yasl-contracts-test', - 'common/test_contracts.c', - include_directories: inc, - dependencies: yasl_dep, -) -test('contracts', contracts_test) - -# Exercise the SDK-independent mappings even when their backend is disabled. -# Enabled backends already provide their mapping through yasl_dep. -keyboard_test_sources = files('common/test_keyboard.c') +test('public-exports', python, args: [files('helpers/check_exports.py')] + export_args + + [nm, yasl_lib.get_shared_lib(), headers]) + +foreach unit : ['contracts', 'utf8', 'timers', 'loop', 'selection', 'events', 'gl_options'] + exe = executable('yasl-' + unit + '-test', 'common/test_' + unit + '.c', + include_directories: inc, dependencies: yasl_dep) + test(unit, exe) +endforeach +keyboard_sources = files('common/test_keyboard.c') if not has_wayland - keyboard_test_sources += files('../src/platform/wayland/keymap.c') + keyboard_sources += files('../src/platform/wayland/keymap.c') endif if not has_x11 - keyboard_test_sources += files('../src/platform/x11/keymap.c') + keyboard_sources += files('../src/platform/x11/keymap.c') endif if not has_cocoa - keyboard_test_sources += files('../src/platform/cocoa/keymap.c') + keyboard_sources += files('../src/platform/cocoa/keymap.c') endif -keyboard_test = executable('yasl-keyboard-test', keyboard_test_sources, - include_directories: inc, dependencies: yasl_dep, -) -test('keyboard', keyboard_test) - -utf8_test = executable('yasl-utf8-test', 'common/test_utf8.c', - include_directories: inc, dependencies: yasl_dep, -) -test('utf8', utf8_test) - -timer_test = executable( - 'yasl-timer-test', - 'common/test_timers.c', - include_directories: inc, - dependencies: yasl_dep, -) -test('timers', timer_test) - -loop_test = executable( - 'yasl-loop-test', - 'common/test_loop.c', - include_directories: inc, - dependencies: [yasl_dep, dependency('threads')], -) -test('loop', loop_test) - +keyboard = executable('yasl-keyboard-test', keyboard_sources, include_directories: inc, dependencies: yasl_dep) +test('keyboard', keyboard) +alloc = executable('yasl-input-alloc-test', ['common/test_input_alloc.c', '../src/common/input.c'], + include_directories: inc, c_args: ['-Dmalloc=yasl_private_test_malloc']) +test('input-allocation-failure', alloc) if system == 'linux' - desktop_test = executable( - 'yasl-desktop-test', - 'common/test_desktop.c', - include_directories: inc, - dependencies: [yasl_dep, dependency('threads')], - ) - # A private session bus keeps the notification and portal checks away - # from the user's desktop. - dbus_run_session = find_program('dbus-run-session', required: false) - if has_dbus and dbus_run_session.found() - test('desktop', dbus_run_session, args: ['--', desktop_test]) + desktop = executable('yasl-desktop-test', 'common/test_desktop.c', include_directories: inc, dependencies: yasl_dep) + dbus_run = find_program('dbus-run-session', required: false) + if has_dbus and dbus_run.found() + test('desktop', dbus_run, args: ['--', desktop]) else - test('desktop', desktop_test) + test('desktop', desktop) endif endif - -app_test = executable( - 'yasl-app-test', - 'common/test_app.c', - include_directories: inc, - dependencies: yasl_dep, -) -test('app-runner', app_test) - -input_alloc_test = executable( - 'yasl-input-alloc-test', - ['common/test_input_alloc.c', '../src/common/input.c'], - include_directories: inc, - c_args: ['-Dmalloc=yasl_test_malloc'], -) -test('input-allocation-failure', input_alloc_test) - -selection_test = executable( - 'yasl-selection-test', - 'common/test_selection.c', - dependencies: yasl_dep, -) -test('selection', selection_test) - -tablet_example_test = executable('yasl-tablet-example-test', 'common/test_tablet_example.c', - dependencies: yasl_dep, -) -test('tablet-example', tablet_example_test) - +if has_wayland + foreach unit : ['state', 'clipboard', 'keyboard', 'input', 'tablet', 'cursor', 'relative_pointer', 'app_input', 'popup_positioner'] + exe = executable('yasl-' + unit + '-wayland-test', 'wayland/test_' + unit + '.c', + include_directories: inc, dependencies: yasl_dep) + test(unit + '-wayland', exe) + endforeach +endif 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, - ) - test('tablet-x11', tablet_x11_test) + foreach unit : ['state', 'tablet'] + exe = executable('yasl-' + unit + '-x11-test', 'x11/test_' + unit + '.c', + include_directories: inc, dependencies: yasl_dep) + test(unit + '-x11', exe) + endforeach if get_option('x11_tablet_tests') xorg = find_program('/usr/lib/Xorg', '/usr/lib/xorg/Xorg', 'Xorg') - native_tablet_test = executable('yasl-tablet-x11-native-test', 'x11/test_tablet_native.c', - dependencies: yasl_dep, - ) - test('tablet-x11-native', python, - args: [files('helpers/run_x11_tablet.py'), xorg, native_tablet_test], timeout: 30, - ) + native_tablet = executable('yasl-tablet-x11-native-test', 'x11/test_tablet_native.c', + include_directories: inc, dependencies: yasl_dep) + test('tablet-x11-native', python, args: [files('helpers/run_x11_tablet.py'), xorg, native_tablet], timeout: 30) endif -elif get_option('x11_tablet_tests') - error('Native X11 tablet tests require the X11 backend.') 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, - ) - test('clipboard-wayland', clipboard_wayland_test) - keyboard_wayland_test = executable('yasl-keyboard-wayland-test', 'wayland/test_keyboard.c', - include_directories: inc, dependencies: yasl_dep, - ) - test('keyboard-wayland', keyboard_wayland_test) - ime_test = executable('yasl-ime-test', 'wayland/test_input.c', - include_directories: inc, - dependencies: yasl_dep, - ) - test('ime-ownership', ime_test) - app_input_test = executable('yasl-app-input-test', 'wayland/test_app_input.c', - include_directories: inc, dependencies: yasl_dep, - ) - test('app-input-destroy', app_input_test) - tablet_wayland_test = executable('yasl-tablet-wayland-test', 'wayland/test_tablet.c', - include_directories: inc, dependencies: yasl_dep, - ) - test('tablet-wayland', tablet_wayland_test) - cursor_wayland_test = executable('yasl-cursor-wayland-test', 'wayland/test_cursor.c', - include_directories: inc, dependencies: yasl_dep, - ) - test('cursor-wayland', cursor_wayland_test) - relative_pointer_test = executable('yasl-relative-pointer-wayland-test', - 'wayland/test_relative_pointer.c', - include_directories: inc, dependencies: yasl_dep, - ) - test('relative-pointer-wayland', relative_pointer_test) -endif - -benchmark_exe = executable( - 'yasl-benchmark', - 'benchmark.c', - include_directories: inc, - dependencies: yasl_dep, -) -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, timeout: 20) - test('interactive-cocoa-window', interactive_example, - args: ['--smoke-test'], timeout: 20, - ) - native_test = executable('yasl-native-test', 'cocoa/test_native.c', - dependencies: shared_dep, - ) - test('native', native_test, timeout: 20) - ownership_test = executable('yasl-ownership-test', 'cocoa/test_ownership.m', - dependencies: [shared_dep, deps], - ) - test('native-ownership', ownership_test, timeout: 20) - keyboard_cocoa_test = executable('yasl-keyboard-cocoa-test', 'cocoa/test_keyboard.m', - dependencies: [shared_dep, deps], - ) - test('keyboard-cocoa', keyboard_cocoa_test, timeout: 20) - tablet_cocoa_test = executable('yasl-tablet-cocoa-test', 'cocoa/test_tablet.m', - dependencies: [shared_dep, deps], - ) - test('tablet-cocoa', tablet_cocoa_test, timeout: 20) - cursor_cocoa_test = executable('yasl-cursor-cocoa-test', 'cocoa/test_cursor.m', - dependencies: [shared_dep, deps], - ) - test('cursor-cocoa', cursor_cocoa_test, timeout: 20) - test('tablet-cocoa-window', tablet_example, - args: ['--backend', 'cocoa', '--smoke-test'], timeout: 20, - ) - else - if not has_wayland or not has_x11 - error('Linux integration tests require Wayland and X11.') - endif - backend_test = executable( - 'yasl-backend-test', - 'common/test_backends.c', - dependencies: [shared_dep, deps], - ) - input_x11_test = executable('yasl-input-x11-test', 'x11/test_input.c', - include_directories: inc, dependencies: yasl_dep, - ) + integration = executable('yasl-surface-integration', 'common/test_surface_integration.c', + include_directories: inc, dependencies: yasl_dep) + gl_integration = executable('yasl-gl-integration', 'common/test_gl.c', + dependencies: yasl_dep) + vulkan_integration = '-' + if has_vulkan + vulkan_integration = executable('yasl-vulkan-integration', 'common/test_vulkan.c', dependencies: yasl_dep) + endif + if system == 'linux' weston = find_program('weston') xwayland = find_program('Xwayland') - test('backends', python, - args: [files('helpers/run_backends.py'), weston, xwayland, - backend_test, timer_test, tablet_example, hello_example_test, input_x11_test, - interactive_example, app_test, app_input_test], - timeout: 60, - ) + test('surface-backends', python, args: [files('helpers/run_surfaces.py'), weston, xwayland, + integration, hello_example, tablet_example, gl_integration, vulkan_integration, notepad_example], timeout: 120) + elif has_cocoa + test('surface-cocoa', integration, args: ['cocoa'], timeout: 30) + foreach unit : ['native', 'ownership', 'keyboard', 'tablet', 'cursor'] + extension = unit == 'native' ? '.c' : '.m' + exe = executable('yasl-' + unit + '-cocoa-test', 'cocoa/test_' + unit + extension, + include_directories: inc, dependencies: yasl_dep) + test(unit + '-cocoa', exe, timeout: 30) + endforeach endif endif - -events_test = executable('yasl-events-test', 'common/test_events.c', - include_directories: inc, dependencies: [yasl_dep, dependency('threads')], -) -test('event-contracts', events_test) diff --git a/tests/test_api.cpp b/tests/test_api.cpp index c5859cd..c0693eb 100644 --- a/tests/test_api.cpp +++ b/tests/test_api.cpp @@ -1,89 +1,26 @@ -#include "yasl/native.h" -#include "yasl/app.h" +#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" +#include <yasl/native/wayland.h> #endif #if YASL_HAS_X11 -#include "yasl/native/x11.h" +#include <yasl/native/x11.h> #endif #if YASL_HAS_COCOA -#include "yasl/native/cocoa.h" +#include <yasl/native/cocoa.h> #endif -#include "yasl/desktop.h" -#include "yasl/loop.h" -#include <yasl/timing.h> -#include <yasl/yasl.h> #include <cassert> - int main() { - yasl_window_cbs callbacks{}; - yasl_window_spec window{}; - window.size = {640, 480}; - window.cbs = &callbacks; - window.ud = &window; - assert(!yasl_surface_create_toplevel(nullptr, &window)); - assert(!yasl_surface_create_native(nullptr, &window, &window)); - - yasl_timing timing = yasl_surface_timing(nullptr); - assert(timing.source == YASL_TIMING_UNKNOWN); - assert(yasl_scale_to_device(10.0, 240) == 20); - yasl_geometry geometry = yasl_surface_geometry(nullptr); - assert(geometry.scale_120 == YASL_SCALE_UNIT); - assert(!yasl_display_get_native_handle(nullptr)); - assert(!yasl_surface_get_native_handle(nullptr)); - yasl_native_frame native{}; - assert(!yasl_surface_get_native_frame(nullptr, nullptr, &native)); - - yasl_loop *loop = yasl_loop_create(); - assert(loop); - assert(yasl_loop_step(loop, 0) >= 0); - yasl_loop_destroy(loop); - - yasl_message message{}; - assert(!yasl_display_post(nullptr, &message)); - assert(!yasl_frame_present(nullptr, nullptr, 0)); - assert(!yasl_display_poll_event(nullptr, nullptr)); - assert(!yasl_surface_set_size(nullptr, {640, 480})); - assert(!yasl_surface_set_fullscreen(nullptr, true)); - assert(!yasl_surface_set_maximized(nullptr, true)); - assert(!yasl_surface_minimize(nullptr)); - assert(!yasl_surface_set_pacing(nullptr, YASL_PACING_CONTINUOUS)); - assert(!yasl_surface_set_callbacks(nullptr, nullptr, nullptr)); - assert(!yasl_clipboard_set(nullptr, YASL_CLIPBOARD, "")); - assert(!yasl_clipboard_request(nullptr, YASL_CLIPBOARD)); - assert(!yasl_app_window(nullptr)); - yasl_app_exit(nullptr, YASL_APP_ERROR); - - yasl_event event{}; - event.key.key = YASL_KEY_A; - assert(event.key.key == YASL_KEY_A); - event.pointer.scroll = {0.25, -0.5}; - assert(event.pointer.scroll.x == 0.25 && event.pointer.scroll.y == -0.5); - assert(!yasl_surface_poll_event(nullptr, &event)); - assert(!yasl_display_drag_start(nullptr, nullptr, nullptr, nullptr, nullptr)); - assert(!yasl_surface_drag_carry(nullptr, {0, 0})); - -#if YASL_HAS_WAYLAND - assert(!yasl_wayland_display_get_wl_display(nullptr)); - assert(!yasl_wayland_surface_get_wl_surface(nullptr)); -#endif - -#if YASL_HAS_X11 - assert(!yasl_x11_display_get_display(nullptr)); - assert(!yasl_x11_surface_get_window(nullptr)); -#endif - -#if YASL_HAS_COCOA - assert(!yasl_cocoa_display_get_application(nullptr)); - assert(!yasl_cocoa_surface_get_window(nullptr)); - assert(!yasl_cocoa_surface_get_view(nullptr)); -#endif - - assert(!yasl_display_open_uri(nullptr, "https://example.com")); - assert(yasl_display_appearance(nullptr) == 0); - assert(!yasl_surface_bell(nullptr)); - assert(yasl_executable_path(nullptr, 0) > 0); - - return 0; + yasl_event e{}; + e.pen.x=1.5;e.pen.pressure=0.25; + assert(e.pen.pressure==0.25); + assert(!yasl_surface_create("invalid",-1,10,0)); + yasl_shutdown();return 0; } diff --git a/tests/test_lifecycle.c b/tests/test_lifecycle.c index 00d1264..a334fed 100644 --- a/tests/test_lifecycle.c +++ b/tests/test_lifecycle.c @@ -1,75 +1,24 @@ -#include "yasl/loop.h" -#include "yasl/yasl.h" +#include <yasl/yasl.h> +#include <yasl/cpu.h> +#include <yasl/gl.h> +#include <yasl/desktop.h> #include <assert.h> -#include <poll.h> -#include <stdio.h> -#include <unistd.h> - -static void test_scaling(void) -{ - assert(yasl_scale_to_device(10.0, 120) == 10); - assert(yasl_scale_to_device(10.0, 240) == 20); - assert(yasl_scale_to_logical(20, 240) == 10); -} - -static int dummy_cb(int fd, short revents, void *ud) -{ - (void)fd; (void)revents; (void)ud; - return 0; -} - -static void test_loop(void) -{ - struct yasl_loop *loop = yasl_loop_create(); - assert(loop != NULL); - - int p[2]; - assert(pipe(p) == 0); - int watch = yasl_loop_add_fd(loop, p[0], POLLIN, dummy_cb, NULL); - assert(watch > 0); - assert(yasl_loop_set_fd_events(loop, watch, POLLIN | POLLOUT) == 0); - - yasl_loop_kick(loop); - int n = yasl_loop_step(loop, 10); - assert(n >= 1); - - assert(yasl_loop_del_fd(loop, watch) == 0); - close(p[0]); - close(p[1]); - - yasl_loop_destroy(loop); -} - -static void test_display_lifecycle_standalone(void) -{ - struct yasl_display_spec spec = { .app_id = "org.yasl.test" }; - struct yasl_display *d = yasl_display_open(NULL, &spec); - if (!d) return; /* Headless CI without Wayland */ - - struct yasl_window_spec sspec = { - .title = "Test Window", - .size = { .x = 320, .y = 240 }, - }; - struct yasl_surface *s = yasl_surface_create_toplevel(d, &sspec); - assert(s != NULL); - assert(yasl_surface_display(s) == d); - - struct yasl_vector2i sz = yasl_surface_geometry(s).size; - assert(sz.x > 0 && sz.y > 0); - - yasl_surface_schedule_redraw(s); - yasl_display_flush(d); - yasl_display_dispatch(d, 0); - - yasl_surface_destroy(s); - yasl_display_close(d); -} - int main(void) { - test_scaling(); - test_loop(); - test_display_lifecycle_standalone(); - printf("All YASL basic tests passed.\n"); - return 0; + yasl_shutdown(); yasl_wake(); + yasl_event e={.type=YASL_EVENT_QUIT}; + assert(yasl_wait_event(&e,0)==-1 && e.type==YASL_EVENT_NONE); + assert(yasl_get_error(NULL)==YASL_ERROR_NOT_INITIALIZED); + assert(!yasl_set_backend((enum yasl_backend)999)); + assert(yasl_get_error(NULL)==YASL_ERROR_INVALID); + assert(!yasl_surface_create("bad",0,64,0)); + assert(!yasl_surface_create("bad",64,64,16)); + assert(yasl_set_app_id("org.yasl.api")); + assert(yasl_set_backend(YASL_BACKEND_AUTO)); + yasl_surface_destroy(NULL); + assert(yasl_time_ns()>0); + const char *description=NULL;yasl_get_error(&description);assert(description); + int w=9,h=9;yasl_surface_get_size(NULL,&w,&h);assert(w==0&&h==0); + assert(yasl_executable_path(NULL,0)>0); + yasl_shutdown();return 0; } diff --git a/tests/wayland/test_app_input.c b/tests/wayland/test_app_input.c index 7e0e9a0..c8e4d2b 100644 --- a/tests/wayland/test_app_input.c +++ b/tests/wayland/test_app_input.c @@ -1,132 +1,78 @@ -/* An input event callback that destroys its own surface. The app runner - * must not touch the surface's entry after the callback, and must not - * deliver the surface's remaining input. Needs a Wayland compositor. */ -#include "platform/wayland/internal.h" -#include "yasl/app.h" +/* The public event queue must retain FIFO order and discard events targeted + * at a surface when that surface is destroyed. Native input is injected + * through the backend queue, without requiring a compositor. */ +#include "yasl/yasl.h" +#include "common/backend.h" #include <assert.h> -#include <errno.h> -#include <stdlib.h> +#include <string.h> -struct test_state { - bool initialized; - int keys; - int buttons, motions, resets; - int end; +static struct input_queue *surface_queue(struct yasl_surface *s) +{ + return &s->input; +} + +static const struct yasl_private_backend_ops test_ops = { + .surface_input_queue = surface_queue, }; -static void push_key(struct yasl_surface *s) +static void setup(struct yasl_private_display *display, struct yasl_surface *surface) { - struct yasl_event ev = { - .surface = s, - .type = YASL_EVENT_KEY, - .key = { .key = YASL_KEY_A, .action = YASL_KEY_PRESS }, - }; - assert(input_queue_push(&((struct wayland_surface *)s)->input, &ev)); + *display = (struct yasl_private_display){.ops = &test_ops}; + *surface = (struct yasl_surface){.display = display, .scale_120 = 120}; + assert(yasl_private_events_open(display)); + assert(yasl_private_events_register(surface)); } -static enum yasl_app_result on_init(void *ud, struct yasl_display *d) +static void destroy_surface(struct yasl_surface *surface) { - struct test_state *state = ud; - state->initialized = true; - if (state->end == 3) { - /* With no event callback, a close request alone stops the runner. */ - struct yasl_surface *s = yasl_surface_create_toplevel(d, - &(struct yasl_window_spec){ .title = "App Close Test", .size = {64, 64} }); - assert(s); - yasl_surface_notify_close(s); - return YASL_APP_CONTINUE; - } - if (state->end) { - if (state->end == 1) - yasl_display_notify_event(d, YASL_EVENT_DISPLAY_QUIT); - else - yasl_display_notify_event(d, YASL_EVENT_DISPLAY_LOST); - 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_event event = {.surface = s, .type = YASL_EVENT_POINTER_DOWN, - .pointer = {.device = YASL_PTR_DEV_ERASER, .button = YASL_BUTTON_PRIMARY, - .pos = {12, 34}, .pen = {.pressure = 50000, .tilt_x = -20, .tilt_y = 30}}}; - assert(input_queue_push(queue, &event)); - event.type = YASL_EVENT_POINTER_MOTION; - event.pointer.pos = (struct yasl_vector2i){15, 39}; - assert(input_queue_push(queue, &event)); - struct yasl_event reset = {.surface = s, .type = YASL_EVENT_INPUT_RESET}; - assert(input_queue_push(queue, &reset)); - event.pointer.device = YASL_PTR_DEV_PEN; - event.pointer.pos = (struct yasl_vector2i){100, 200}; - assert(input_queue_push(queue, &event)); - push_key(s); - push_key(s); - return YASL_APP_CONTINUE; + yasl_private_events_unregister(surface); + input_queue_clear(&surface->input); } -static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) +static void test_fifo_and_destroy(void) { - struct test_state *state = ud; - if (ev->type == YASL_EVENT_POINTER_DOWN) { - assert(ev->pointer.device == YASL_PTR_DEV_ERASER); - assert(ev->pointer.button == YASL_BUTTON_PRIMARY); - assert(ev->pointer.pos.x == 12 && ev->pointer.pos.y == 34); - assert(ev->pointer.pen.pressure == 50000); - assert(ev->pointer.pen.tilt_x == -20 && ev->pointer.pen.tilt_y == 30); - state->buttons++; - } else if (ev->type == YASL_EVENT_POINTER_MOTION) { - if (!state->motions) { - assert(ev->pointer.device == YASL_PTR_DEV_ERASER); - assert(ev->pointer.delta.x == 3 && ev->pointer.delta.y == 5); - } else { - assert(ev->pointer.device == YASL_PTR_DEV_PEN); - assert(!ev->pointer.delta.x && !ev->pointer.delta.y); - } - assert(ev->pointer.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); - } - return YASL_APP_CONTINUE; + struct yasl_private_display display; + struct yasl_surface surface; + setup(&display, &surface); + + assert(input_queue_push(&surface.input, &(struct yasl_private_event){ + .type = YASL_PRIVATE_EVENT_KEY, + .surface = &surface, + .key = {.key = YASL_KEY_A, .action = YASL_KEY_PRESS}, + })); + assert(input_queue_push(&surface.input, &(struct yasl_private_event){ + .type = YASL_PRIVATE_EVENT_TEXT, + .surface = &surface, + .text = {.data = "hello", .len = 5}, + })); + + struct yasl_private_event event; + assert(yasl_private_display_poll_event(&display, &event)); + assert(event.type == YASL_PRIVATE_EVENT_KEY && event.key.key == YASL_KEY_A); + assert(yasl_private_display_poll_event(&display, &event)); + assert(event.type == YASL_PRIVATE_EVENT_TEXT && event.text.len == 5); + assert(!strcmp(event.text.data, "hello")); + + /* Queue multiple events, then destroy the target before consumption. */ + assert(input_queue_push(&surface.input, &(struct yasl_private_event){ + .type = YASL_PRIVATE_EVENT_KEY, .surface = &surface, + .key = {.key = YASL_KEY_B, .action = YASL_KEY_PRESS}, + })); + assert(input_queue_push(&surface.input, &(struct yasl_private_event){ + .type = YASL_PRIVATE_EVENT_TEXT, .surface = &surface, + .text = {.data = "discard", .len = 7}, + })); + destroy_surface(&surface); + assert(!yasl_private_display_poll_event(&display, &event)); + yasl_private_events_close(&display); + + /* Public event names remain the event stream consumed by applications. */ + assert(YASL_EVENT_KEY != YASL_EVENT_TEXT); } int main(void) { - if (!getenv("WAYLAND_DISPLAY")) - return 77; - struct test_state state = {0}; - int res = yasl_run(&(struct yasl_app_desc){ - .app_id = "org.yasl.test.app-input", - .backend = YASL_BACKEND_WAYLAND, - .userdata = &state, - .init = on_init, - .event = on_event, - }); - /* A display variable may exist even when the test sandbox cannot connect - * to that compositor. Keep this as an integration test in that case. */ - if (res < 0 && !state.initialized && - (errno == EACCES || errno == EPERM || errno == ENOENT || - errno == ECONNREFUSED || errno == ENODEV)) - return 77; - 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); - } - state = (struct test_state){.end = 3}; - res = yasl_run(&(struct yasl_app_desc){ - .backend = YASL_BACKEND_WAYLAND, .userdata = &state, .init = on_init, - }); - assert(res == 0); + test_fifo_and_destroy(); return 0; } diff --git a/tests/wayland/test_clipboard.c b/tests/wayland/test_clipboard.c index 1778ad2..0ee74b6 100644 --- a/tests/wayland/test_clipboard.c +++ b/tests/wayland/test_clipboard.c @@ -6,7 +6,7 @@ struct received { int count; size_t len; uint64_t request_id; - enum yasl_transfer_status status; + enum yasl_private_transfer_status status; struct clipboard *clipboard; bool forget_on_receive; char text[32]; @@ -14,12 +14,12 @@ struct received { static void receive(const char *text, size_t len, bool uri_list, struct wl_surface *surface, uint64_t request_id, - enum yasl_transfer_status status, void *ud) + enum yasl_private_transfer_status status, void *ud) { (void)surface; struct received *r = ud; assert(!uri_list && len < sizeof(r->text)); - if (status == YASL_TRANSFER_OK) { + if (status == YASL_PRIVATE_TRANSFER_OK) { assert(text); memcpy(r->text, text, len); r->text[len] = '\0'; @@ -39,7 +39,7 @@ static void receive(const char *text, size_t len, bool uri_list, int main(void) { struct received received = {0}; - struct clipboard c = {.loop = yasl_loop_create(), .receive = receive, .ud = &received}; + struct clipboard c = {.loop = yasl_private_loop_create(), .receive = receive, .ud = &received}; received.clipboard = &c; assert(c.loop); const struct { const char *bytes; size_t len; bool valid; } cases[] = { @@ -55,19 +55,19 @@ int main(void) assert(pipe(fds) == 0); struct transfer *transfer = start_transfer_text(&c, fds[0], false, false, c.text); assert(transfer); - yasl_timer_id timeout_id = transfer->timeout; - assert(timeout_id && yasl_loop_timer_armed(c.loop, timeout_id)); + yasl_private_timer_id timeout_id = transfer->timeout; + assert(timeout_id && yasl_private_loop_timer_armed(c.loop, timeout_id)); transfer->paste = true; transfer->request_id = 42; assert(write(fds[1], cases[i].bytes, cases[i].len) == (ssize_t)cases[i].len); close(fds[1]); for (int steps = 0; c.transfers && steps < 10; steps++) - assert(yasl_loop_step(c.loop, 0) >= 0); + assert(yasl_private_loop_step(c.loop, 0) >= 0); assert(!c.transfers); - assert(!yasl_loop_timer_armed(c.loop, timeout_id)); + assert(!yasl_private_loop_timer_armed(c.loop, timeout_id)); assert(received.count == 1); assert(received.request_id == 42); - assert(received.status == (cases[i].valid ? YASL_TRANSFER_OK : YASL_TRANSFER_FAILED)); + assert(received.status == (cases[i].valid ? YASL_PRIVATE_TRANSFER_OK : YASL_PRIVATE_TRANSFER_FAILED)); if (cases[i].valid) { assert(received.len == cases[i].len); assert(memcmp(received.text, cases[i].bytes, received.len) == 0); @@ -79,8 +79,8 @@ int main(void) assert(pipe(cancel_fds) == 0); struct transfer *pending = start_transfer_text(&c, cancel_fds[0], false, false, NULL); assert(pending); - yasl_timer_id cancel_timeout_id = pending->timeout; - assert(cancel_timeout_id && yasl_loop_timer_armed(c.loop, cancel_timeout_id)); + yasl_private_timer_id cancel_timeout_id = pending->timeout; + assert(cancel_timeout_id && yasl_private_loop_timer_armed(c.loop, cancel_timeout_id)); pending->paste = true; pending->request_id = 99; struct wl_surface *target = (struct wl_surface *)(uintptr_t)1; @@ -88,7 +88,7 @@ int main(void) clipboard_forget_surface(&c, target); close(cancel_fds[1]); assert(!c.transfers && received.count == 0); - assert(!yasl_loop_timer_armed(c.loop, cancel_timeout_id)); + assert(!yasl_private_loop_timer_armed(c.loop, cancel_timeout_id)); received.forget_on_receive = true; int timeout_fds[2]; @@ -99,12 +99,12 @@ int main(void) timed->request_id = 100; struct wl_surface *timeout_target = (struct wl_surface *)(uintptr_t)2; timed->drop_surface = timeout_target; - yasl_timer_id timeout_id = timed->timeout; - assert(yasl_loop_del_timer(c.loop, timeout_id)); + yasl_private_timer_id timeout_id = timed->timeout; + assert(yasl_private_loop_del_timer(c.loop, timeout_id)); (void)transfer_timeout(timed, timeout_id, CLIPBOARD_TRANSFER_TIMEOUT_NS); close(timeout_fds[1]); assert(!c.transfers && received.count == 1); - assert(received.request_id == 100 && received.status == YASL_TRANSFER_FAILED); - yasl_loop_destroy(c.loop); + assert(received.request_id == 100 && received.status == YASL_PRIVATE_TRANSFER_FAILED); + yasl_private_loop_destroy(c.loop); return 0; } diff --git a/tests/wayland/test_cursor.c b/tests/wayland/test_cursor.c index eb729a3..3425821 100644 --- a/tests/wayland/test_cursor.c +++ b/tests/wayland/test_cursor.c @@ -51,9 +51,9 @@ static uint32_t pointer_version(struct wl_pointer *pointer) int main(void) { - struct wayland_display display = {.base.ops = &yasl_wayland_backend}; + struct wayland_display display = {.base.ops = &yasl_private_wayland_backend}; struct wayland_surface surface = {.base.display = &display.base, .conn = &display, - .scale_factor = 1, .cursor = YASL_CURSOR_TEXT}; + .scale_factor = 1, .cursor = YASL_PRIVATE_CURSOR_TEXT}; surface.surface = (struct wl_surface *)&surface; wl_surface_set_user_data(surface.surface, &surface); display.surfaces = &surface; @@ -64,7 +64,7 @@ int main(void) tool.proxy = (struct zwp_tablet_tool_v2 *)&tool; /* The example selects hidden before either input device has entered. */ - yasl_surface_set_cursor(&surface.base, YASL_CURSOR_HIDDEN); + yasl_private_surface_set_cursor(&surface.base, YASL_PRIVATE_CURSOR_HIDDEN); assert(!pointer_hides && !tool_hides); pointer_enter(&seat, seat.pointer, 10, surface.surface, 0, 0); assert(pointer_hides == 1 && pointer_serial == 10); @@ -76,14 +76,16 @@ int main(void) pointer_frame(&seat, seat.pointer); tablet_tool_motion(&tool, tool.proxy, wl_fixed_from_int(56), wl_fixed_from_int(78)); tool_frame(&tool, tool.proxy, 0); - struct yasl_event event; - assert(yasl_surface_poll_event(&surface.base, &event)); - assert(event.type == YASL_EVENT_POINTER_MOTION && event.pointer.device == YASL_PTR_DEV_MOUSE); + struct yasl_private_event event; + assert(yasl_private_surface_poll_event(&surface.base, &event)); + assert(event.type == YASL_PRIVATE_EVENT_POINTER_MOTION && event.pointer.device == YASL_PRIVATE_PTR_DEV_MOUSE); assert(event.pointer.pos.x == 12 && event.pointer.pos.y == 34); - assert(yasl_surface_poll_event(&surface.base, &event)); - assert(event.type == YASL_EVENT_POINTER_MOTION && event.pointer.device == YASL_PTR_DEV_PEN); + assert(yasl_private_surface_poll_event(&surface.base, &event)); + assert(event.type == YASL_PRIVATE_EVENT_PEN && event.pen.action == YASL_PRIVATE_PEN_ENTER); + assert(yasl_private_surface_poll_event(&surface.base, &event)); + assert(event.type == YASL_PRIVATE_EVENT_POINTER_MOTION && event.pointer.device == YASL_PRIVATE_PTR_DEV_PEN); assert(event.pointer.pos.x == 56 && event.pointer.pos.y == 78); - assert(!yasl_surface_poll_event(&surface.base, &event)); + assert(!yasl_private_surface_poll_event(&surface.base, &event)); /* Each new entry needs a request with its own serial. */ pointer_leave(&seat, seat.pointer, 12, surface.surface); @@ -98,20 +100,20 @@ int main(void) /* Restore a visible shape on both currently focused devices. */ pointer_device = seat.cursor_shape_dev = (struct wp_cursor_shape_device_v1 *)&seat; tool.shape_dev = (struct wp_cursor_shape_device_v1 *)&tool; - yasl_surface_set_cursor(&surface.base, YASL_CURSOR_ARROW); + yasl_private_surface_set_cursor(&surface.base, YASL_PRIVATE_CURSOR_ARROW); assert(pointer_shapes == 1 && tool_shapes == 1); assert(pointer_shape == WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); assert(tool_shape == WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); - yasl_surface_set_cursor(&surface.base, YASL_CURSOR_HIDDEN); + yasl_private_surface_set_cursor(&surface.base, YASL_PRIVATE_CURSOR_HIDDEN); assert(pointer_hides == 3 && tool_hides == 3); assert(pointer_shapes == 1 && tool_shapes == 1); /* Updating an unfocused surface must not alter the active cursor. */ struct wayland_surface other = {.base.display = &display.base, .conn = &display}; - yasl_surface_set_cursor(&other.base, YASL_CURSOR_CROSSHAIR); + yasl_private_surface_set_cursor(&other.base, YASL_PRIVATE_CURSOR_CROSSHAIR); assert(pointer_shapes == 1 && tool_shapes == 1); - yasl_surface_set_cursor(&surface.base, (enum yasl_cursor)-1); - yasl_surface_set_cursor(&surface.base, (enum yasl_cursor)(YASL_CURSOR_HIDDEN + 1)); + yasl_private_surface_set_cursor(&surface.base, (enum yasl_private_cursor)-1); + yasl_private_surface_set_cursor(&surface.base, (enum yasl_private_cursor)(YASL_PRIVATE_CURSOR_HIDDEN + 1)); assert(pointer_hides == 3 && tool_hides == 3); assert(pointer_shapes == 1 && tool_shapes == 1); input_queue_clear(&surface.input); diff --git a/tests/wayland/test_input.c b/tests/wayland/test_input.c index f6112b6..2b5592a 100644 --- a/tests/wayland/test_input.c +++ b/tests/wayland/test_input.c @@ -1,17 +1,35 @@ #include "platform/wayland/internal.h" #include <assert.h> +#include <errno.h> +#include <stdlib.h> + +static void test_ime_capability(void) +{ + struct wayland_display display = {0}; + struct wayland_surface w = {.conn = &display}; + assert(!(wayland_display_caps(&display) & YASL_PRIVATE_CAP_TEXT_INPUT)); + errno = 0; + assert(!wayland_surface_update_text_input(&w) && errno == ENOTSUP); + display.text_input_mgr = (struct zwp_text_input_manager_v3 *)&display; + assert(wayland_display_caps(&display) & YASL_PRIVATE_CAP_TEXT_INPUT); + w.base.text_input_active = true; + w.base.text_purpose = 1; /* Public YASL_TEXT_PASSWORD. */ + /* A supported compositor can accept the requested state before a seat + * or keyboard focus has arrived. The enter callback applies it later. */ + assert(wayland_surface_update_text_input(&w)); +} static void test_popup_bounds(void) { struct seat seat = {.input_serial = 1}; - struct wayland_display d = {.base.ops = &yasl_wayland_backend, .seats = &seat}; + struct wayland_display d = {.base.ops = &yasl_private_wayland_backend, .seats = &seat}; struct wayland_surface w = {.base.display = &d.base, .conn = &d}; w.parent = &w; w.content.x = w.content.y = 1; /* Invalid bounds must be rejected before accessing any protocol object. */ w.xdg_surface = (struct xdg_surface *)&w; w.popup = (struct xdg_popup *)&w; - struct yasl_popup_spec specs[] = { + struct yasl_private_popup_spec specs[] = { {.size = {100, 100}, .content_offset = {INT_MAX, 0}, .content_size = {1, 0}}, {.size = {100, 100}, .content_offset = {0, INT_MAX}, .content_size = {0, 1}}, {.size = {100, 100}, .content_offset = {1, 0}, .content_size = {INT_MAX, 0}}, @@ -20,16 +38,18 @@ static void test_popup_bounds(void) {.size = {100, 100}, .anchor = {0, INT_MIN, 1, 1}}, }; for (size_t i = 0; i < sizeof(specs) / sizeof(specs[0]); i++) { - assert(!yasl_surface_create_popup(&w.base, &specs[i])); - assert(!yasl_surface_popup_reposition(&w.base, &specs[i])); + assert(!yasl_private_surface_create_popup(&w.base, &specs[i])); + assert(!yasl_private_surface_popup_reposition(&w.base, &specs[i])); } } int main(void) { + test_ime_capability(); test_popup_bounds(); struct wayland_surface w = {0}; struct seat seat = {.ime_focus = &w}; + w.base.text_input_active = true; wayland_surface_set_callbacks(&w, NULL, NULL); for (int i = 0; i < 600; i++) { char text[32]; @@ -41,11 +61,38 @@ int main(void) for (int i = 0; i < 600; i++) { char text[32]; snprintf(text, sizeof(text), "commit-%d", i); - struct yasl_event event; + struct yasl_private_event event; assert(wayland_surface_poll_event(&w, &event)); - assert(event.type == YASL_EVENT_TEXT && event.surface == &w.base); + assert(event.type == YASL_PRIVATE_EVENT_TEXT && event.surface == &w.base); assert(strcmp(event.text.data, text) == 0); } + w.base.text_input_active = true; + w.base.text_surrounding = strdup("ab\xf0\x9f\x98\x80" "cd"); + w.base.text_length = 8; + w.base.text_begin = 2; + w.base.text_end = 6; + w.base.text_revision = 41; + w.base.text_generation = 9; + text_input_listener.delete_surrounding_text(&seat, NULL, 2, 2); + text_input_listener.commit_string(&seat, NULL, "X"); + text_input_listener.done(&seat, NULL, 601); + struct yasl_private_event edit; + assert(wayland_surface_poll_event(&w, &edit)); + assert(edit.type == YASL_PRIVATE_EVENT_TEXT_EDIT && edit.surface == &w.base); + assert(strcmp(edit.text_edit.data, "X") == 0 && edit.text_edit.len == 1); + assert(edit.text_edit.begin == 2 && edit.text_edit.end == 8); + assert(edit.text_edit.revision == 41 && edit.text_edit.generation == 9); + struct yasl_private_event committed; + assert(!wayland_surface_poll_event(&w, &committed)); + + /* Deletion without a commit replaces the range with the empty string. */ + text_input_listener.delete_surrounding_text(&seat, NULL, 2, 2); + text_input_listener.done(&seat, NULL, 602); + assert(wayland_surface_poll_event(&w, &edit)); + assert(edit.type == YASL_PRIVATE_EVENT_TEXT_EDIT && edit.text_edit.len == 0); + assert(edit.text_edit.data[0] == '\0' && edit.text_edit.begin == 2 && edit.text_edit.end == 8); + assert(!wayland_surface_poll_event(&w, &committed)); input_queue_clear(&w.input); + free(w.base.text_surrounding); return 0; } diff --git a/tests/wayland/test_keyboard.c b/tests/wayland/test_keyboard.c index bce0a0c..ae4647f 100644 --- a/tests/wayland/test_keyboard.c +++ b/tests/wayland/test_keyboard.c @@ -1,18 +1,18 @@ #include "../../src/platform/wayland/keyboard.c" #include <assert.h> -static struct yasl_event next(struct wayland_surface *w, enum yasl_event_type type) +static struct yasl_private_event next(struct wayland_surface *w, enum yasl_private_event_type type) { - struct yasl_event ev; - assert(yasl_surface_poll_event(&w->base, &ev)); + struct yasl_private_event ev; + assert(yasl_private_surface_poll_event(&w->base, &ev)); assert(ev.type == type && ev.surface == &w->base); return ev; } static void empty(struct wayland_surface *w) { - struct yasl_event ev; - assert(!yasl_surface_poll_event(&w->base, &ev)); + struct yasl_private_event ev; + assert(!yasl_private_surface_poll_event(&w->base, &ev)); } static void layout(struct seat *s, const char *name, const char *variant) @@ -43,7 +43,7 @@ static void key(struct seat *s, uint32_t code, bool down) int main(void) { - struct wayland_display d = {.base.ops = &yasl_wayland_backend}; + struct wayland_display d = {.base.ops = &yasl_private_wayland_backend}; d.xkb_ctx = xkb_context_new(0); assert(d.xkb_ctx); struct wayland_surface w = {.base.display = &d.base, .conn = &d}; @@ -59,65 +59,66 @@ int main(void) xkb_compose_table_unref(table); layout(&s, "us", NULL); key(&s, KEY_A, true); - struct yasl_event ev = next(&w, YASL_EVENT_KEY); + struct yasl_private_event ev = next(&w, YASL_PRIVATE_EVENT_KEY); assert(ev.key.key == YASL_KEY_A && ev.key.native_code == KEY_A); assert(ev.key.codepoint == 'a' && ev.key.shifted == 'A' && !ev.key.mods); - assert(strcmp(next(&w, YASL_EVENT_TEXT).text.data, "a") == 0); + assert(strcmp(next(&w, YASL_PRIVATE_EVENT_TEXT).text.data, "a") == 0); modifiers(&s, XKB_MOD_NAME_SHIFT); key(&s, KEY_A, false); /* Modifier changes cannot change physical identity. */ - ev = next(&w, YASL_EVENT_KEY); + ev = next(&w, YASL_PRIVATE_EVENT_KEY); assert(ev.key.key == YASL_KEY_A && ev.key.action == YASL_KEY_RELEASE); - assert(ev.key.mods == YASL_MOD_SHIFT); + assert(ev.key.mods == YASL_PRIVATE_MOD_SHIFT); empty(&w); key(&s, KEY_A, true); - next(&w, YASL_EVENT_KEY); - assert(strcmp(next(&w, YASL_EVENT_TEXT).text.data, "A") == 0); + next(&w, YASL_PRIVATE_EVENT_KEY); + assert(strcmp(next(&w, YASL_PRIVATE_EVENT_TEXT).text.data, "A") == 0); modifiers(&s, XKB_MOD_NAME_CAPS); key(&s, KEY_A, true); - assert(next(&w, YASL_EVENT_KEY).key.mods == YASL_MOD_CAPS); - assert(strcmp(next(&w, YASL_EVENT_TEXT).text.data, "A") == 0); + assert(next(&w, YASL_PRIVATE_EVENT_KEY).key.mods == YASL_PRIVATE_MOD_CAPS); + assert(strcmp(next(&w, YASL_PRIVATE_EVENT_TEXT).text.data, "A") == 0); const char *shortcuts[] = {XKB_MOD_NAME_CTRL, XKB_MOD_NAME_ALT, XKB_MOD_NAME_LOGO}; for (size_t i = 0; i < sizeof(shortcuts)/sizeof(shortcuts[0]); i++) { modifiers(&s, shortcuts[i]); key(&s, KEY_A, true); - next(&w, YASL_EVENT_KEY); + next(&w, YASL_PRIVATE_EVENT_KEY); empty(&w); /* Shortcut characters never leak into committed text. */ } layout(&s, "fr", NULL); key(&s, KEY_A, true); - ev = next(&w, YASL_EVENT_KEY); + ev = next(&w, YASL_PRIVATE_EVENT_KEY); assert(ev.key.key == YASL_KEY_A && ev.key.codepoint == 'q'); - assert(strcmp(next(&w, YASL_EVENT_TEXT).text.data, "q") == 0); + assert(strcmp(next(&w, YASL_PRIVATE_EVENT_TEXT).text.data, "q") == 0); layout(&s, "de", NULL); modifiers(&s, "Mod5"); /* AltGr is text input, not an Alt shortcut. */ key(&s, KEY_E, true); - assert(next(&w, YASL_EVENT_KEY).key.key == YASL_KEY_E); - assert(strcmp(next(&w, YASL_EVENT_TEXT).text.data, "€") == 0); + assert(next(&w, YASL_PRIVATE_EVENT_KEY).key.key == YASL_KEY_E); + assert(strcmp(next(&w, YASL_PRIVATE_EVENT_TEXT).text.data, "€") == 0); layout(&s, "us", "intl"); key(&s, KEY_APOSTROPHE, true); - next(&w, YASL_EVENT_KEY); + next(&w, YASL_PRIVATE_EVENT_KEY); empty(&w); key(&s, KEY_E, true); - next(&w, YASL_EVENT_KEY); - assert(strcmp(next(&w, YASL_EVENT_TEXT).text.data, "é") == 0); + next(&w, YASL_PRIVATE_EVENT_KEY); + assert(strcmp(next(&w, YASL_PRIVATE_EVENT_TEXT).text.data, "é") == 0); assert(keyboard_dispatch_key(&s, &w, KEY_E, YASL_KEY_REPEAT)); - assert(next(&w, YASL_EVENT_KEY).key.action == YASL_KEY_REPEAT); - assert(strcmp(next(&w, YASL_EVENT_TEXT).text.data, "e") == 0); + assert(next(&w, YASL_PRIVATE_EVENT_KEY).key.action == YASL_KEY_REPEAT); + assert(strcmp(next(&w, YASL_PRIVATE_EVENT_TEXT).text.data, "e") == 0); key(&s, KEY_APOSTROPHE, true); - next(&w, YASL_EVENT_KEY); + next(&w, YASL_PRIVATE_EVENT_KEY); keyboard_reset_keys(&s, &w); key(&s, KEY_E, true); - next(&w, YASL_EVENT_KEY); - assert(strcmp(next(&w, YASL_EVENT_TEXT).text.data, "e") == 0); + next(&w, YASL_PRIVATE_EVENT_KEY); + assert(strcmp(next(&w, YASL_PRIVATE_EVENT_TEXT).text.data, "e") == 0); key(&s, KEY_APOSTROPHE, true); - next(&w, YASL_EVENT_KEY); + next(&w, YASL_PRIVATE_EVENT_KEY); s.ime_focus = &w; + w.base.text_input_active = true; text_input_listener.commit_string(&s, NULL, "字"); text_input_listener.done(&s, NULL, 0); - assert(strcmp(next(&w, YASL_EVENT_TEXT).text.data, "字") == 0); + assert(strcmp(next(&w, YASL_PRIVATE_EVENT_TEXT).text.data, "字") == 0); key(&s, KEY_E, true); - next(&w, YASL_EVENT_KEY); - assert(strcmp(next(&w, YASL_EVENT_TEXT).text.data, "e") == 0); + next(&w, YASL_PRIVATE_EVENT_KEY); + assert(strcmp(next(&w, YASL_PRIVATE_EVENT_TEXT).text.data, "e") == 0); empty(&w); xkb_compose_state_unref(s.compose); xkb_state_unref(s.xkb_state); diff --git a/tests/wayland/test_popup_positioner.c b/tests/wayland/test_popup_positioner.c new file mode 100644 index 0000000..86778f6 --- /dev/null +++ b/tests/wayland/test_popup_positioner.c @@ -0,0 +1,93 @@ +/* Capture xdg_positioner requests to verify explicit placement and the legacy + * beside mapping without requiring a compositor. */ +#include "platform/wayland/internal.h" +#include <assert.h> + +struct recorded_positioner { + int width, height; + int rect_x, rect_y, rect_width, rect_height; + uint32_t anchor, gravity, constraints; + bool reactive; +}; + +static struct recorded_positioner record; +static uint64_t fake_positioner_storage[8]; +#define FAKE_POSITIONER ((struct xdg_positioner *)fake_positioner_storage) +static struct xdg_positioner *fake_create(struct xdg_wm_base *base) +{ (void)base; record = (struct recorded_positioner){0}; return FAKE_POSITIONER; } +static void fake_size(struct xdg_positioner *pos, int32_t width, int32_t height) +{ assert(pos == FAKE_POSITIONER); record.width = width; record.height = height; } +static void fake_rect(struct xdg_positioner *pos, int32_t x, int32_t y, int32_t width, int32_t height) +{ assert(pos == FAKE_POSITIONER); record.rect_x=x; record.rect_y=y; record.rect_width=width; record.rect_height=height; } +static void fake_anchor(struct xdg_positioner *pos, uint32_t value) +{ assert(pos == FAKE_POSITIONER); record.anchor = value; } +static void fake_gravity(struct xdg_positioner *pos, uint32_t value) +{ assert(pos == FAKE_POSITIONER); record.gravity = value; } +static void fake_constraints(struct xdg_positioner *pos, uint32_t value) +{ assert(pos == FAKE_POSITIONER); record.constraints = value; } +static uint32_t fake_version(struct xdg_positioner *pos) +{ assert(pos == FAKE_POSITIONER); return XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION; } +static void fake_reactive(struct xdg_positioner *pos) +{ assert(pos == FAKE_POSITIONER); record.reactive = true; } + +#define xdg_wm_base_create_positioner fake_create +#define xdg_positioner_set_size fake_size +#define xdg_positioner_set_anchor_rect fake_rect +#define xdg_positioner_set_anchor fake_anchor +#define xdg_positioner_set_gravity fake_gravity +#define xdg_positioner_set_constraint_adjustment fake_constraints +#define xdg_positioner_get_version fake_version +#define xdg_positioner_set_reactive fake_reactive +#include "../../src/platform/wayland/popup.c" + +int main(void) +{ + struct wayland_display display = {.wm_base = (struct xdg_wm_base *)&record}; + struct wayland_surface parent = { + .scale_120 = 120, + .content = {.x = 3, .y = 4}, + }; + struct wayland_surface popup = { + .conn = &display, + .parent = &parent, + .scale_120 = 120, + .content = {.width = 48, .height = 36}, + }; + struct yasl_private_popup_spec spec = { + .size = {64, 48}, + .anchor = {13, 24, 8, 6}, + .beside = true, + }; + + struct xdg_positioner *positioner = popup_positioner(&popup, &spec); + assert(positioner == FAKE_POSITIONER); + assert(record.width == 48 && record.height == 36); + assert(record.rect_x == 10 && record.rect_y == 20 && + record.rect_width == 8 && record.rect_height == 6); + assert(record.anchor == XDG_POSITIONER_ANCHOR_TOP_RIGHT); + assert(record.gravity == XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT); + assert(record.constraints == (XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y | + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y)); + assert(record.reactive); + + spec.positioner_explicit = true; + spec.anchor_edges = XDG_POSITIONER_ANCHOR_NONE; + spec.gravity = XDG_POSITIONER_GRAVITY_RIGHT; + spec.constraints = XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y; + assert(popup_spec_valid(&parent, &spec)); + positioner = popup_positioner(&popup, &spec); + assert(positioner == FAKE_POSITIONER); + assert(record.anchor == XDG_POSITIONER_ANCHOR_NONE); + assert(record.gravity == XDG_POSITIONER_GRAVITY_RIGHT); + assert(record.constraints == spec.constraints); + + spec.anchor_edges = XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT + 1; + assert(!popup_spec_valid(&parent, &spec)); + spec.anchor_edges = XDG_POSITIONER_ANCHOR_NONE; + spec.constraints = 1u << 9; + assert(!popup_spec_valid(&parent, &spec)); + return 0; +} diff --git a/tests/wayland/test_relative_pointer.c b/tests/wayland/test_relative_pointer.c index bcc41e3..d44238d 100644 --- a/tests/wayland/test_relative_pointer.c +++ b/tests/wayland/test_relative_pointer.c @@ -65,10 +65,10 @@ static void shape_device_destroy(struct wp_cursor_shape_device_v1 *device) { ass #define wl_pointer_release pointer_release #include "../../src/platform/wayland/pointer.c" -static struct yasl_event next_event(struct wayland_surface *w) +static struct yasl_private_event next_event(struct wayland_surface *w) { - struct yasl_event event; - assert(yasl_surface_poll_event(&w->base, &event)); + struct yasl_private_event event; + assert(yasl_private_surface_poll_event(&w->base, &event)); return event; } @@ -85,9 +85,9 @@ static void relative_move(struct seat *s, double dx, double dy) int main(void) { - struct wayland_display display = {.base.ops = &yasl_wayland_backend}; + struct wayland_display display = {.base.ops = &yasl_private_wayland_backend}; struct wayland_surface surface = {.base.display = &display.base, .conn = &display, - .scale_factor = 2, .cursor = YASL_CURSOR_TEXT}; + .scale_factor = 2, .cursor = YASL_PRIVATE_CURSOR_TEXT}; surface.surface = (struct wl_surface *)&surface; wl_surface_set_user_data(surface.surface, &surface); display.surfaces = &surface; @@ -95,10 +95,10 @@ int main(void) seat.pointer = (struct wl_pointer *)&seat; seat.cursor_shape_dev = (struct wp_cursor_shape_device_v1 *)&seat; display.seats = &seat; - struct yasl_event event; + struct yasl_private_event event; /* Without both protocols the surface stays in absolute mode. */ - assert(!yasl_surface_set_pointer_mode(&surface.base, YASL_POINTER_RELATIVE)); + assert(!yasl_private_surface_set_pointer_mode(&surface.base, YASL_PRIVATE_POINTER_RELATIVE)); assert(!surface.relative && !locks); display.relative_pointer_mgr = (struct zwp_relative_pointer_manager_v1 *)&display; display.pointer_constraints = (struct zwp_pointer_constraints_v1 *)&display; @@ -107,10 +107,10 @@ int main(void) pointer_frame(&seat, seat.pointer); assert(pointer_shapes == 1); event = next_event(&surface); - assert(event.type == YASL_EVENT_POINTER_MOTION && event.pointer.pos.x == 10); + assert(event.type == YASL_PRIVATE_EVENT_POINTER_MOTION && event.pointer.pos.x == 10); /* Relative mode locks persistently on the pointer's seat and hides the cursor. */ - assert(yasl_surface_set_pointer_mode(&surface.base, YASL_POINTER_RELATIVE)); + assert(yasl_private_surface_set_pointer_mode(&surface.base, YASL_PRIVATE_POINTER_RELATIVE)); assert(locks == 1 && locked_surface == surface.surface && locked_wl_pointer == seat.pointer); assert(lock_lifetime == ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); assert(pointer_hides == 1 && surface.lock_seat == &seat); @@ -121,14 +121,14 @@ int main(void) event = next_event(&surface); assert(event.pointer.pos.x == 14 && event.pointer.pos.y == 10); assert(event.pointer.delta.x == 4 && event.pointer.delta.y == 0); - assert(!yasl_surface_poll_event(&surface.base, &event)); + assert(!yasl_private_surface_poll_event(&surface.base, &event)); /* Locked: relative samples in device pixels, position frozen at the lock. */ pointer_locked(&surface, surface.pointer_lock); relative_move(&seat, 3.5, -2); relative_move(&seat, 1000, 0); event = next_event(&surface); - assert(event.type == YASL_EVENT_POINTER_MOTION); + assert(event.type == YASL_PRIVATE_EVENT_POINTER_MOTION); assert(event.pointer.pos.x == 14 && event.pointer.pos.y == 10); assert(event.pointer.delta.x == 7 && event.pointer.delta.y == -4); event = next_event(&surface); @@ -137,7 +137,7 @@ int main(void) /* Deactivation (e.g. focus loss) returns to absolute motion without unlocking. */ pointer_unlocked(&surface, surface.pointer_lock); relative_move(&seat, 5, 5); - assert(!yasl_surface_poll_event(&surface.base, &event)); + assert(!yasl_private_surface_poll_event(&surface.base, &event)); move_to(&seat, 8, 5); event = next_event(&surface); assert(event.pointer.delta.x == 2 && event.pointer.delta.y == 0); @@ -145,12 +145,12 @@ int main(void) pointer_locked(&surface, surface.pointer_lock); /* Returning to normal destroys the lock and restores the chosen cursor. */ - assert(yasl_surface_set_pointer_mode(&surface.base, YASL_POINTER_NORMAL)); + assert(yasl_private_surface_set_pointer_mode(&surface.base, YASL_PRIVATE_POINTER_NORMAL)); assert(unlocks == 1 && !surface.pointer_lock && !surface.input.relative); assert(pointer_shapes == 2); - assert(yasl_surface_set_pointer_mode(&surface.base, YASL_POINTER_NORMAL)); + assert(yasl_private_surface_set_pointer_mode(&surface.base, YASL_PRIVATE_POINTER_NORMAL)); assert(unlocks == 1); - assert(yasl_surface_set_pointer_mode(&surface.base, YASL_POINTER_RELATIVE)); + assert(yasl_private_surface_set_pointer_mode(&surface.base, YASL_PRIVATE_POINTER_RELATIVE)); assert(locks == 2 && pointer_hides == 2); pointer_locked(&surface, surface.pointer_lock); diff --git a/tests/wayland/test_state.c b/tests/wayland/test_state.c index 2da26e5..1ce9189 100644 --- a/tests/wayland/test_state.c +++ b/tests/wayland/test_state.c @@ -1,6 +1,20 @@ -#include "../../src/platform/wayland/shell.c" +#include "platform/wayland/internal.h" #include <assert.h> +static uint32_t acknowledged_serial; +static unsigned acknowledge_count; +static void test_ack_configure(struct xdg_surface *surface, uint32_t serial); +#define xdg_surface_ack_configure test_ack_configure +#include "../../src/platform/wayland/shell.c" +#undef xdg_surface_ack_configure + +static void test_ack_configure(struct xdg_surface *surface, uint32_t serial) +{ + (void)surface; + acknowledged_serial = serial; + acknowledge_count++; +} + struct notifications { int count; uint32_t state; @@ -9,7 +23,7 @@ struct notifications { static void state_changed(struct yasl_surface *surface, uint32_t state, void *ud) { struct notifications *notifications = ud; - assert(yasl_surface_state(surface) == state); + assert(yasl_private_surface_state(surface) == state); notifications->count++; notifications->state = state; } @@ -24,33 +38,33 @@ static void configure(struct wayland_surface *surface, uint32_t *states, size_t 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){ + struct wayland_surface surface = {.base.state = YASL_PRIVATE_STATE_CSD}; + wayland_surface_set_callbacks(&surface, &(struct yasl_private_window_cbs){ .state = state_changed}, ¬ifications); - assert(yasl_surface_state(&surface.base) == YASL_STATE_CSD); + assert(yasl_private_surface_state(&surface.base) == YASL_PRIVATE_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)); + assert(notifications.state == (YASL_PRIVATE_STATE_CSD | YASL_PRIVATE_STATE_FULLSCREEN | YASL_PRIVATE_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)); + assert(notifications.state == (YASL_PRIVATE_STATE_FULLSCREEN | YASL_PRIVATE_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)); + assert(notifications.state == (YASL_PRIVATE_STATE_MAXIMIZED | YASL_PRIVATE_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)); + assert(notifications.state == (YASL_PRIVATE_STATE_CSD | YASL_PRIVATE_STATE_MAXIMIZED | YASL_PRIVATE_STATE_SUSPENDED)); /* Each tiled edge maps to the same portable flag; changing edges alone * leaves the reported state unchanged. */ @@ -59,17 +73,38 @@ int main(void) 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)); + assert(notifications.state == (YASL_PRIVATE_STATE_CSD | YASL_PRIVATE_STATE_TILED)); } configure(&surface, edges, 0); assert(notifications.count == 6); - assert(notifications.state == YASL_STATE_CSD); + assert(notifications.state == YASL_PRIVATE_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(yasl_private_surface_state(&surface.base) == + (YASL_PRIVATE_STATE_CSD | YASL_PRIVATE_STATE_FULLSCREEN | YASL_PRIVATE_STATE_ACTIVATED)); assert(notifications.count == 6); + + /* External presentation has no SHM commit callback to acknowledge xdg + * configures. Apply geometry and acknowledge before renderer submission. */ + struct wayland_surface gpu = { + .base.presenter = YASL_PRIVATE_PRESENTER_VULKAN, + .width = 0, .height = 0, + .logical_width = 64, .logical_height = 64, + .req_width = 64, .req_height = 64, + .scale_120 = YASL_PRIVATE_SCALE_UNIT, + .pending_scale_120 = 180, + }; + acknowledge_count = 0; + xdg_surface_configure(&gpu, (struct xdg_surface *)&gpu, 1234); + assert(acknowledge_count == 1 && acknowledged_serial == 1234); + assert(!gpu.has_pending_serial && !gpu.size_pending); + assert(gpu.configured && gpu.width == 96 && gpu.height == 96); + + /* CPU presentation keeps its existing deferred ack with the SHM commit. */ + struct wayland_surface cpu = {.base.presenter = YASL_PRIVATE_PRESENTER_CPU}; + xdg_surface_configure(&cpu, (struct xdg_surface *)&cpu, 5678); + assert(acknowledge_count == 1 && cpu.has_pending_serial && cpu.want_draw); return 0; } diff --git a/tests/wayland/test_tablet.c b/tests/wayland/test_tablet.c index c76ddf8..f47fbfa 100644 --- a/tests/wayland/test_tablet.c +++ b/tests/wayland/test_tablet.c @@ -1,63 +1,101 @@ #include "../../src/platform/wayland/pointer.c" #include <assert.h> -static struct yasl_event next(struct wayland_surface *surface) +static struct yasl_private_event next_raw(struct wayland_surface *surface) { - struct yasl_event event; - assert(yasl_surface_poll_event(&surface->base, &event)); + struct yasl_private_event event; + assert(yasl_private_surface_poll_event(&surface->base, &event)); assert(event.surface == &surface->base); return event; } +static struct yasl_private_event next_pointer(struct wayland_surface *surface) +{ + struct yasl_private_event event; + do { event = next_raw(surface); } while (event.type == YASL_PRIVATE_EVENT_PEN); + return event; +} + int main(void) { - struct wayland_display display = {.base.ops = &yasl_wayland_backend}; + struct wayland_display display = {.base.ops = &yasl_private_wayland_backend}; struct wayland_surface surface = {.base.display = &display.base, .conn = &display, .scale_factor = 1.5}; struct seat seat = {.conn = &display, .tablet_focus = &surface}; - struct tablet_tool tool = {.seat = &seat}; + struct tablet_tool tool = {.seat = &seat, .device_id = 77, .tool_id = 88, .kind = 1}; seat.tool = &tool; tablet_tool_motion(&tool, NULL, wl_fixed_from_int(100), wl_fixed_from_int(120)); tool_pressure(&tool, NULL, 32768); tool_tilt(&tool, NULL, wl_fixed_from_int(-35), wl_fixed_from_int(42)); + tool_distance(&tool, NULL, 16384); + tool_rotation(&tool, NULL, wl_fixed_from_int(17)); + tool_slider(&tool, NULL, -32768); + pen_action_push(&tool, YASL_PRIVATE_PEN_ENTER, 0); tablet_tool_down(&tool, NULL, 1); tool_frame(&tool, NULL, 0); - struct yasl_event ev = next(&surface); - assert(ev.type == YASL_EVENT_POINTER_MOTION && ev.pointer.device == YASL_PTR_DEV_PEN); + struct yasl_private_event pen = next_raw(&surface); + assert(pen.type == YASL_PRIVATE_EVENT_PEN); + assert(pen.pen.action == YASL_PRIVATE_PEN_ENTER && !pen.pen.in_contact); + assert(pen.pen.device_id == 77 && pen.pen.tool_id == 88); + pen = next_raw(&surface); + assert(pen.type == YASL_PRIVATE_EVENT_PEN); + assert(pen.pen.action == YASL_PRIVATE_PEN_DOWN && pen.pen.in_contact); + assert(pen.pen.buttons & YASL_PRIVATE_BUTTON_BIT(YASL_PRIVATE_BUTTON_PRIMARY)); + assert(pen.pen.x == 150 && pen.pen.y == 180); + assert(fabs(pen.pen.pressure - (32768.0 / 65535.0)) < 0.00001); + assert(pen.pen.axes_valid == (YASL_PRIVATE_PEN_PRESSURE | YASL_PRIVATE_PEN_TILT | + YASL_PRIVATE_PEN_DISTANCE | YASL_PRIVATE_PEN_ROTATION | YASL_PRIVATE_PEN_TANGENTIAL)); + assert(pen.pen.axes_changed == pen.pen.axes_valid); + assert(fabs(pen.pen.distance - 16384.0 / 65535.0) < 0.00001); + assert(pen.pen.rotation == 17 && pen.pen.tangential == -32768.0 / 65535.0); + struct yasl_private_event ev = next_pointer(&surface); + assert(ev.type == YASL_PRIVATE_EVENT_POINTER_MOTION && ev.pointer.device == YASL_PRIVATE_PTR_DEV_PEN); assert(ev.pointer.pos.x == 150 && ev.pointer.pos.y == 180 && ev.pointer.pen.pressure == 32768); assert(ev.pointer.pen.tilt_x == -35 && ev.pointer.pen.tilt_y == 42); - ev = next(&surface); - assert(ev.type == YASL_EVENT_POINTER_DOWN && ev.pointer.button == YASL_BUTTON_PRIMARY && ev.pointer.pen.pressure == 32768); + ev = next_pointer(&surface); + assert(ev.type == YASL_PRIVATE_EVENT_POINTER_DOWN && ev.pointer.button == YASL_PRIVATE_BUTTON_PRIMARY && ev.pointer.pen.pressure == 32768); /* Stationary pressure and tilt changes are still motion samples. */ tool_pressure(&tool, NULL, 100000); tool_tilt(&tool, NULL, wl_fixed_from_int(-120), wl_fixed_from_int(120)); tool_frame(&tool, NULL, 0); - ev = next(&surface); - assert(ev.type == YASL_EVENT_POINTER_MOTION && ev.pointer.pen.pressure == 65535); + pen = next_raw(&surface); + assert(pen.type == YASL_PRIVATE_EVENT_PEN && pen.pen.action == YASL_PRIVATE_PEN_MOVE); + assert(pen.pen.axes_changed == (YASL_PRIVATE_PEN_PRESSURE | YASL_PRIVATE_PEN_TILT)); + ev = next_pointer(&surface); + assert(ev.type == YASL_PRIVATE_EVENT_POINTER_MOTION && ev.pointer.pen.pressure == 65535); assert(ev.pointer.pos.x == 150 && ev.pointer.pos.y == 180); assert(ev.pointer.pen.tilt_x == -90 && ev.pointer.pen.tilt_y == 90); tablet_tool_up(&tool, NULL); tool_frame(&tool, NULL, 0); - ev = next(&surface); - assert(ev.type == YASL_EVENT_POINTER_UP && ev.pointer.button == YASL_BUTTON_PRIMARY && !ev.pointer.pen.pressure); + pen = next_raw(&surface); + assert(pen.type == YASL_PRIVATE_EVENT_PEN && pen.pen.action == YASL_PRIVATE_PEN_UP); + assert(!pen.pen.in_contact && pen.pen.pressure == 0); + ev = next_pointer(&surface); + assert(ev.type == YASL_PRIVATE_EVENT_POINTER_UP && ev.pointer.button == YASL_PRIVATE_BUTTON_PRIMARY && !ev.pointer.pen.pressure); tool_type(&tool, NULL, ZWP_TABLET_TOOL_V2_TYPE_ERASER); tool_pressure(&tool, NULL, 22000); tool_frame(&tool, NULL, 0); - assert(next(&surface).pointer.device == YASL_PTR_DEV_ERASER); + pen = next_raw(&surface); + assert(pen.type == YASL_PRIVATE_EVENT_PEN && pen.pen.tool == 2); + assert(next_pointer(&surface).pointer.device == YASL_PRIVATE_PTR_DEV_ERASER); tool_wheel(&tool, NULL, 0, 1); tool_frame(&tool, NULL, 0); - ev = next(&surface); - assert(ev.type == YASL_EVENT_POINTER_SCROLL && ev.pointer.device == YASL_PTR_DEV_ERASER); + pen = next_raw(&surface); + assert(pen.type == YASL_PRIVATE_EVENT_PEN && pen.pen.axes_changed == YASL_PRIVATE_PEN_WHEEL); + ev = next_pointer(&surface); + assert(ev.type == YASL_PRIVATE_EVENT_POINTER_SCROLL && ev.pointer.device == YASL_PRIVATE_PTR_DEV_ERASER); assert(ev.pointer.scroll.x == 0 && ev.pointer.scroll.y == 1); tablet_tool_proximity_out(&tool, NULL); - ev = next(&surface); - assert(ev.type == YASL_EVENT_POINTER_LEAVE && ev.pointer.device == YASL_PTR_DEV_ERASER && !ev.pointer.pen.pressure); + pen = next_raw(&surface); + assert(pen.type == YASL_PRIVATE_EVENT_PEN && pen.pen.action == YASL_PRIVATE_PEN_LEAVE); + ev = next_pointer(&surface); + assert(ev.type == YASL_PRIVATE_EVENT_POINTER_LEAVE && ev.pointer.device == YASL_PRIVATE_PTR_DEV_ERASER && !ev.pointer.pen.pressure); assert(!seat.tool && !seat.tablet_focus && !seat.tool_buttons); - assert(!(yasl_display_caps(&display.base) & YASL_CAP_TABLET)); + assert(!(yasl_private_display_caps(&display.base) & YASL_PRIVATE_CAP_TABLET)); display.tablet_mgr = (void *)&display; /* Capability depends on protocol availability. */ - assert(yasl_display_caps(&display.base) & YASL_CAP_TABLET); + assert(yasl_private_display_caps(&display.base) & YASL_PRIVATE_CAP_TABLET); seat.pointer_focus = &surface; /* A compositor may describe the same detent in three ways. value120 wins, @@ -67,13 +105,32 @@ int main(void) pointer_axis_value120(&seat, NULL, WL_POINTER_AXIS_VERTICAL_SCROLL, 30); pointer_axis_value120(&seat, NULL, WL_POINTER_AXIS_HORIZONTAL_SCROLL, -60); pointer_frame(&seat, NULL); - ev = next(&surface); - assert(ev.type == YASL_EVENT_POINTER_SCROLL && ev.pointer.device == YASL_PTR_DEV_MOUSE); + ev = next_pointer(&surface); + assert(ev.type == YASL_PRIVATE_EVENT_POINTER_SCROLL && ev.pointer.device == YASL_PRIVATE_PTR_DEV_MOUSE); assert(ev.pointer.scroll.x == -0.5 && ev.pointer.scroll.y == 0.25); - struct yasl_event unused; - assert(!yasl_surface_poll_event(&surface.base, &unused)); + assert(ev.pointer.scroll_valid == 1); + assert(ev.pointer.scroll_source == 1 && ev.pointer.scroll_phase == 0); + struct yasl_private_event unused; + assert(!yasl_private_surface_poll_event(&surface.base, &unused)); + pointer_frame(&seat, NULL); + assert(!yasl_private_surface_poll_event(&surface.base, &unused)); + + /* Finger scrolling carries pixel offsets and coherent gesture phases. */ + pointer_axis_source(&seat, NULL, WL_POINTER_AXIS_SOURCE_FINGER); + pointer_axis(&seat, NULL, 0, WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_from_double(1.25)); + pointer_axis(&seat, NULL, 0, WL_POINTER_AXIS_HORIZONTAL_SCROLL, wl_fixed_from_double(-2.5)); + pointer_frame(&seat, NULL); + ev = next_pointer(&surface); + assert(ev.pointer.scroll_phase == 1 && ev.pointer.scroll_source == 2); + assert(ev.pointer.pixel_scroll.x == -3.75 && ev.pointer.pixel_scroll.y == 1.875); + pointer_axis(&seat, NULL, 0, WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_from_double(1.25)); + pointer_frame(&seat, NULL); + ev = next_pointer(&surface); + assert(ev.pointer.scroll_phase == 2 && ev.pointer.scroll.y == 0.125); + pointer_axis_stop(&seat, NULL, 0, WL_POINTER_AXIS_VERTICAL_SCROLL); pointer_frame(&seat, NULL); - assert(!yasl_surface_poll_event(&surface.base, &unused)); + ev = next_pointer(&surface); + assert(ev.pointer.scroll_phase == 3 && ev.pointer.scroll.x == 0 && ev.pointer.scroll.y == 0); /* Stops preserve offsets from the same frame. Repeated tiny movements * must be visible immediately rather than held until a whole detent. */ @@ -83,17 +140,19 @@ int main(void) pointer_axis(&seat, NULL, 0, WL_POINTER_AXIS_HORIZONTAL_SCROLL, wl_fixed_from_double(-2.5)); pointer_axis_stop(&seat, NULL, 0, WL_POINTER_AXIS_VERTICAL_SCROLL); pointer_frame(&seat, NULL); - ev = next(&surface); + ev = next_pointer(&surface); assert(ev.pointer.scroll.x == -0.25 && ev.pointer.scroll.y == 0.125); + assert(ev.pointer.scroll_source == 2 && ev.pointer.scroll_phase == 3); + assert(ev.pointer.pixel_scroll.x == -3.75 && ev.pointer.pixel_scroll.y == 1.875); } pointer_axis_discrete(&seat, NULL, WL_POINTER_AXIS_HORIZONTAL_SCROLL, 2); pointer_frame(&seat, NULL); - ev = next(&surface); + ev = next_pointer(&surface); assert(ev.pointer.scroll.x == 2 && ev.pointer.scroll.y == 0); pointer_axis_value120(&seat, NULL, WL_POINTER_AXIS_VERTICAL_SCROLL, 60); seat_pointer_reset(&seat); pointer_frame(&seat, NULL); - assert(!yasl_surface_poll_event(&surface.base, &unused)); + assert(!yasl_private_surface_poll_event(&surface.base, &unused)); input_queue_clear(&surface.input); return 0; } diff --git a/tests/x11/inputtest-protocol.h b/tests/x11/inputtest-protocol.h index 18f62ba..2a94a55 100644 --- a/tests/x11/inputtest-protocol.h +++ b/tests/x11/inputtest-protocol.h @@ -22,8 +22,8 @@ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef YASL_TEST_INPUTTEST_PROTOCOL_H -#define YASL_TEST_INPUTTEST_PROTOCOL_H +#ifndef YASL_PRIVATE_TEST_INPUTTEST_PROTOCOL_H +#define YASL_PRIVATE_TEST_INPUTTEST_PROTOCOL_H #include <stdint.h> diff --git a/tests/x11/test_input.c b/tests/x11/test_input.c index c2eca37..ccad859 100644 --- a/tests/x11/test_input.c +++ b/tests/x11/test_input.c @@ -8,10 +8,10 @@ static void check_selection_encoding(struct x11_display *d, struct x11_surface * { Window requestor = XCreateSimpleWindow(d->native, DefaultRootWindow(d->native), 0, 0, 1, 1, 0, 0, 0); - Atom property = XInternAtom(d->native, "YASL_TEST_STRING", False); + Atom property = XInternAtom(d->native, "YASL_PRIVATE_TEST_STRING", False); XSelectionRequestEvent request = {.display = d->native, .requestor = requestor, .selection = d->clipboard, .target = XA_STRING, .property = property}; - yasl_display_copy(&d->base, YASL_CLIPBOARD, "café £"); + yasl_private_display_copy(&d->base, YASL_PRIVATE_CLIPBOARD, "café £"); selection_request(d, &request); Atom actual; int format; @@ -22,7 +22,7 @@ static void check_selection_encoding(struct x11_display *d, struct x11_surface * assert(actual == XA_STRING && format == 8 && count == 6); assert(memcmp(data, "caf\xe9 \xa3", count) == 0); XFree(data); - yasl_display_copy(&d->base, YASL_CLIPBOARD, "字"); + yasl_private_display_copy(&d->base, YASL_PRIVATE_CLIPBOARD, "字"); selection_request(d, &request); assert(XGetWindowProperty(d->native, requestor, property, 0, 100, True, AnyPropertyType, &actual, &format, &count, &left, &data) == Success); @@ -44,12 +44,12 @@ static void check_selection_encoding(struct x11_display *d, struct x11_surface * size_t len; int format; const char *expected; - enum yasl_transfer_status status; + enum yasl_private_transfer_status status; } cases[] = { - {XA_STRING, "caf\xe9 \xa3", 6, 8, "café £", YASL_TRANSFER_OK}, - {d->utf8, "", 0, 8, "", YASL_TRANSFER_OK}, - {d->utf8, "\xff", 1, 8, NULL, YASL_TRANSFER_FAILED}, - {d->incr, NULL, 1, 32, NULL, YASL_TRANSFER_FAILED}, + {XA_STRING, "caf\xe9 \xa3", 6, 8, "café £", YASL_PRIVATE_TRANSFER_OK}, + {d->utf8, "", 0, 8, "", YASL_PRIVATE_TRANSFER_OK}, + {d->utf8, "\xff", 1, 8, NULL, YASL_PRIVATE_TRANSFER_FAILED}, + {d->incr, NULL, 1, 32, NULL, YASL_PRIVATE_TRANSFER_FAILED}, }; for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { struct paste_request *pending = calloc(1, sizeof(*pending)); @@ -59,7 +59,7 @@ static void check_selection_encoding(struct x11_display *d, struct x11_surface * 0, 0, 1, 1, 0, 0, 0); pending->target = s; pending->id = (uint64_t)i + 1; - pending->timeout = yasl_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, + pending->timeout = yasl_private_loop_add_timer(d->loop, PASTE_TIMEOUT_NS, paste_timeout, pending); assert(pending->timeout); pending->next = d->pastes; @@ -71,21 +71,21 @@ static void check_selection_encoding(struct x11_display *d, struct x11_surface * cases[i].format, PropModeReplace, bytes, (int)cases[i].len); XSelectionEvent reply = {.requestor = pending->window, .property = property}; selection_notify(d, &reply); - struct yasl_event event; - assert(yasl_surface_poll_event(&s->base, &event)); - assert(event.type == YASL_EVENT_PASTE && event.paste.request_id == i + 1); + struct yasl_private_event event; + assert(yasl_private_surface_poll_event(&s->base, &event)); + assert(event.type == YASL_PRIVATE_EVENT_PASTE && event.paste.request_id == i + 1); assert(event.paste.status == cases[i].status); - if (cases[i].status == YASL_TRANSFER_OK) { + if (cases[i].status == YASL_PRIVATE_TRANSFER_OK) { assert(event.text.len == strlen(cases[i].expected)); assert(strcmp(event.text.data, cases[i].expected) == 0); } if (cases[i].type == d->incr) { assert(d->pastes == pending && !pending->target); - assert(yasl_loop_del_timer(d->loop, pending->timeout)); + assert(yasl_private_loop_del_timer(d->loop, pending->timeout)); pending->timeout = 0; paste_timeout(pending, 0, PASTE_TIMEOUT_NS); } - assert(!yasl_surface_poll_event(&s->base, &event)); + assert(!yasl_private_surface_poll_event(&s->base, &event)); } struct paste_request *rejected = calloc(1, sizeof(*rejected)); @@ -99,10 +99,10 @@ static void check_selection_encoding(struct x11_display *d, struct x11_surface * d->pastes = rejected; XSelectionEvent no_property = {.requestor = rejected->window, .property = None}; selection_notify(d, &no_property); - struct yasl_event event; - assert(yasl_surface_poll_event(&s->base, &event)); - assert(event.type == YASL_EVENT_PASTE && event.paste.request_id == 99); - assert(event.paste.status == YASL_TRANSFER_FAILED); + struct yasl_private_event event; + assert(yasl_private_surface_poll_event(&s->base, &event)); + assert(event.type == YASL_PRIVATE_EVENT_PASTE && event.paste.request_id == 99); + assert(event.paste.status == YASL_PRIVATE_TRANSFER_FAILED); } /* Own the XSETTINGS selection with one Net/DoubleClickTime value. */ @@ -111,7 +111,7 @@ static void check_double_click(struct x11_display *d) /* No settings owner and no portal: the default applies. */ unsetenv("DBUS_SESSION_BUS_ADDRESS"); unsetenv("XDG_RUNTIME_DIR"); - assert(yasl_display_double_click_ms(&d->base) == YASL_DOUBLE_CLICK_MS_DEFAULT); + assert(yasl_private_display_double_click_ms(&d->base) == YASL_PRIVATE_DOUBLE_CLICK_MS_DEFAULT); char name[32]; snprintf(name, sizeof(name), "_XSETTINGS_S%d", d->screen); @@ -129,64 +129,64 @@ static void check_double_click(struct x11_display *d) XChangeProperty(d->native, owner, property, property, 8, PropModeReplace, blob, sizeof(blob)); XSync(d->native, False); - assert(yasl_display_double_click_ms(&d->base) == 250); + assert(yasl_private_display_double_click_ms(&d->base) == 250); XDestroyWindow(d->native, owner); XSync(d->native, False); - assert(yasl_display_double_click_ms(&d->base) == YASL_DOUBLE_CLICK_MS_DEFAULT); + assert(yasl_private_display_double_click_ms(&d->base) == YASL_PRIVATE_DOUBLE_CLICK_MS_DEFAULT); } -static struct yasl_event wait_paste(struct yasl_display *display, uint64_t id) +static struct yasl_private_event wait_paste(struct yasl_private_display *display, uint64_t id) { - struct yasl_event event; - for (int i = 0; i < 256 && yasl_display_poll_event(display, &event); i++) - if (event.type == YASL_EVENT_PASTE && event.paste.request_id == id) + struct yasl_private_event event; + for (int i = 0; i < 256 && yasl_private_display_poll_event(display, &event); i++) + if (event.type == YASL_PRIVATE_EVENT_PASTE && event.paste.request_id == id) return event; assert(!"expected paste completion was not queued"); - return (struct yasl_event){0}; + return (struct yasl_private_event){0}; } static void check_request_completion(struct x11_display *d, struct x11_surface *s) { assert(surface_input_queue(&s->base) == &s->input); - assert(copy_text(&d->base, YASL_CLIPBOARD, "")); + assert(copy_text(&d->base, YASL_PRIVATE_CLIPBOARD, "")); XSync(d->native, False); assert(XGetSelectionOwner(d->native, d->clipboard) == d->selection_window); - assert(request_paste(&d->base, YASL_CLIPBOARD, &s->base, 101)); - struct yasl_event event = wait_paste(&d->base, 101); - assert(event.paste.status == YASL_TRANSFER_OK && event.paste.len == 0); + assert(request_paste(&d->base, YASL_PRIVATE_CLIPBOARD, &s->base, 101)); + struct yasl_private_event event = wait_paste(&d->base, 101); + assert(event.paste.status == YASL_PRIVATE_TRANSFER_OK && event.paste.len == 0); assert(event.paste.data && !event.paste.data[0]); XSetSelectionOwner(d->native, d->clipboard, None, CurrentTime); XSync(d->native, False); - assert(request_paste(&d->base, YASL_CLIPBOARD, &s->base, 102)); + assert(request_paste(&d->base, YASL_PRIVATE_CLIPBOARD, &s->base, 102)); XSync(d->native, False); dispatch(&d->base); event = wait_paste(&d->base, 102); - assert(event.paste.status == YASL_TRANSFER_UNAVAILABLE); + assert(event.paste.status == YASL_PRIVATE_TRANSFER_UNAVAILABLE); Window silent_owner = XCreateSimpleWindow(d->native, DefaultRootWindow(d->native), 0, 0, 1, 1, 0, 0, 0); XSetSelectionOwner(d->native, d->clipboard, silent_owner, CurrentTime); XSync(d->native, False); - assert(request_paste(&d->base, YASL_CLIPBOARD, &s->base, 103)); + assert(request_paste(&d->base, YASL_PRIVATE_CLIPBOARD, &s->base, 103)); struct paste_request *timed = d->pastes; assert(timed && timed->id == 103 && timed->timeout); - assert(yasl_loop_del_timer(d->loop, timed->timeout)); + assert(yasl_private_loop_del_timer(d->loop, timed->timeout)); timed->timeout = 0; paste_timeout(timed, 0, PASTE_TIMEOUT_NS); event = wait_paste(&d->base, 103); - assert(event.paste.status == YASL_TRANSFER_FAILED); + assert(event.paste.status == YASL_PRIVATE_TRANSFER_FAILED); - struct yasl_window_spec spec = {.title = "Cancelled paste", .size = {80, 60}}; - struct yasl_surface *cancel_target = yasl_surface_create_toplevel(&d->base, &spec); + struct yasl_private_window_spec spec = {.title = "Cancelled paste", .size = {80, 60}}; + struct yasl_surface *cancel_target = yasl_private_surface_create_toplevel(&d->base, &spec); assert(cancel_target); - assert(request_paste(&d->base, YASL_CLIPBOARD, cancel_target, 104)); + assert(request_paste(&d->base, YASL_PRIVATE_CLIPBOARD, cancel_target, 104)); assert(d->pastes && d->pastes->target == (struct x11_surface *)cancel_target); - yasl_timer_id timeout = d->pastes->timeout; - yasl_surface_destroy(cancel_target); + yasl_private_timer_id timeout = d->pastes->timeout; + yasl_private_surface_destroy(cancel_target); assert(d->pastes && !d->pastes->target && d->pastes->id == 0); - assert(yasl_loop_del_timer(d->loop, timeout)); + assert(yasl_private_loop_del_timer(d->loop, timeout)); d->pastes->timeout = 0; XSelectionEvent late_reply = { .requestor = d->pastes->window, @@ -200,23 +200,23 @@ static void check_request_completion(struct x11_display *d, struct x11_surface * XSync(d->native, False); } -static struct yasl_event next_composition(struct yasl_display *display) +static struct yasl_private_event next_composition(struct yasl_private_display *display) { - struct yasl_event event; - for (int i = 0; i < 256 && yasl_display_poll_event(display, &event); i++) - if (event.type == YASL_EVENT_COMPOSITION) + struct yasl_private_event event; + for (int i = 0; i < 256 && yasl_private_display_poll_event(display, &event); i++) + if (event.type == YASL_PRIVATE_EVENT_COMPOSITION) return event; assert(!"expected composition snapshot was not queued"); - return (struct yasl_event){0}; + return (struct yasl_private_event){0}; } -static void drain_display_events(struct yasl_display *display) +static void drain_display_events(struct yasl_private_display *display) { - struct yasl_event event; - while (yasl_display_poll_event(display, &event)) {} + struct yasl_private_event event; + while (yasl_private_display_poll_event(display, &event)) {} } -static void check_xim_callbacks(struct yasl_display *display, struct x11_surface *s) +static void check_xim_callbacks(struct yasl_private_display *display, struct x11_surface *s) { const char *old_locale = setlocale(LC_CTYPE, NULL); char *saved_locale = old_locale ? strdup(old_locale) : NULL; @@ -235,7 +235,7 @@ static void check_xim_callbacks(struct yasl_display *display, struct x11_surface }; xim_preedit_draw(NULL, (XPointer)s, (XPointer)&draw); assert(s->ime_preedit && strcmp(s->ime_preedit, "A😀B") == 0); - struct yasl_event event = next_composition(display); + struct yasl_private_event event = next_composition(display); assert(event.composition.active && event.composition.len == 6); assert(event.composition.begin == 5 && event.composition.end == 5); @@ -271,8 +271,8 @@ static void check_xim_callbacks(struct yasl_display *display, struct x11_surface draw.chg_length = 0; xim_preedit_draw(NULL, (XPointer)s, (XPointer)&draw); assert(strcmp(s->ime_preedit, "AéZB") == 0); - struct yasl_event unused; - assert(!yasl_display_poll_event(display, &unused)); + struct yasl_private_event unused; + assert(!yasl_private_display_poll_event(display, &unused)); wchar_t empty[] = {0}; XIMText deletion = {.length = 0, .encoding_is_wchar = True, @@ -305,12 +305,12 @@ static void check_xim_callbacks(struct yasl_display *display, struct x11_surface int main(void) { - struct yasl_display_spec ds = {.backend = YASL_BACKEND_X11}; - struct yasl_display *base = yasl_display_open(NULL, &ds); + struct yasl_private_display_spec ds = {.backend = YASL_PRIVATE_BACKEND_X11}; + struct yasl_private_display *base = yasl_private_display_open(NULL, &ds); assert(base); struct x11_display *d = (struct x11_display *)base; - struct yasl_window_spec spec = {.title = "Keyboard fallback", .size = {100, 100}}; - struct yasl_surface *surface = yasl_surface_create_toplevel(base, &spec); + struct yasl_private_window_spec spec = {.title = "Keyboard fallback", .size = {100, 100}}; + struct yasl_surface *surface = yasl_private_surface_create_toplevel(base, &spec); assert(surface); struct x11_surface *s = (struct x11_surface *)surface; XIC saved_ic = s->ic; @@ -335,22 +335,22 @@ int main(void) input_queue_clear(&s->input); /* Native creation and keymap updates may already have queued focus or * reset input on the shared display stream. Isolate this synthetic key. */ - struct yasl_event event; - while (yasl_surface_poll_event(surface, &event)) {} + struct yasl_private_event event; + while (yasl_private_surface_poll_event(surface, &event)) {} XKeyEvent native = {.type = KeyPress, .display = d->native, .window = s->window, .keycode = code}; key_event(s, &native, false); - assert(yasl_surface_poll_event(surface, &event)); - assert(event.type == YASL_EVENT_KEY && event.key.key == YASL_KEY_A); + assert(yasl_private_surface_poll_event(surface, &event)); + assert(event.type == YASL_PRIVATE_EVENT_KEY && event.key.key == YASL_KEY_A); assert(event.key.codepoint == cases[i].label); - assert(yasl_surface_poll_event(surface, &event)); - assert(event.type == YASL_EVENT_TEXT && event.text.len == strlen(cases[i].text)); + assert(yasl_private_surface_poll_event(surface, &event)); + assert(event.type == YASL_PRIVATE_EVENT_TEXT && event.text.len == strlen(cases[i].text)); assert(strcmp(event.text.data, cases[i].text) == 0); - assert(!yasl_surface_poll_event(surface, &event)); + assert(!yasl_private_surface_poll_event(surface, &event)); native.type = KeyRelease; key_event(s, &native, false); - assert(yasl_surface_poll_event(surface, &event) && event.type == YASL_EVENT_KEY); - assert(!yasl_surface_poll_event(surface, &event)); + assert(yasl_private_surface_poll_event(surface, &event) && event.type == YASL_PRIVATE_EVENT_KEY); + assert(!yasl_private_surface_poll_event(surface, &event)); } XChangeKeyboardMapping(d->native, code, width, saved, 1); XSync(d->native, False); @@ -363,14 +363,14 @@ int main(void) check_xim_callbacks(base, s); /* Test CSD interactive actions, window menu, and IME caret */ - assert(yasl_display_caps(base) & YASL_CAP_CSD); - yasl_surface_set_ime_caret(surface, (struct yasl_rect2i){10, 20, 2, 16}); - assert(yasl_surface_ime_preedit(surface) == NULL); + assert(yasl_private_display_caps(base) & YASL_PRIVATE_CAP_CSD); + yasl_private_surface_set_ime_caret(surface, (struct yasl_private_rect2i){10, 20, 2, 16}); + assert(yasl_private_surface_ime_preedit(surface) == NULL); int begin = -1, end = -1; - assert(!yasl_surface_ime_preedit_cursor(surface, &begin, &end)); - assert(yasl_surface_show_window_menu(surface, (struct yasl_vector2i){50, 50})); + assert(!yasl_private_surface_ime_preedit_cursor(surface, &begin, &end)); + assert(yasl_private_surface_show_window_menu(surface, (struct yasl_private_vector2i){50, 50})); - yasl_surface_destroy(surface); - yasl_display_close(base); + yasl_private_surface_destroy(surface); + yasl_private_display_close(base); return 0; } diff --git a/tests/x11/test_state.c b/tests/x11/test_state.c index abc0851..4696e60 100644 --- a/tests/x11/test_state.c +++ b/tests/x11/test_state.c @@ -31,7 +31,7 @@ int state_property(Display *display, Window window, Atom property, static void state_changed(struct yasl_surface *surface, uint32_t state, void *ud) { (void)ud; - assert(yasl_surface_state(surface) == state); + assert(yasl_private_surface_state(surface) == state); reported_state = state; notifications++; } @@ -93,39 +93,39 @@ static void test_xsettings(void) int main(void) { test_xsettings(); - struct x11_display display = {.base.ops = &yasl_x11_backend, + struct x11_display display = {.base.ops = &yasl_private_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); + .state = YASL_PRIVATE_STATE_ACTIVATED}, .window = 42}; + set_callbacks(&surface.base, &(struct yasl_private_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)); + assert(reported_state == (YASL_PRIVATE_STATE_FULLSCREEN | YASL_PRIVATE_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); + assert(notifications == 2 && reported_state == YASL_PRIVATE_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)); + assert(reported_state == (YASL_PRIVATE_STATE_MAXIMIZED | YASL_PRIVATE_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)); + assert(reported_state == (YASL_PRIVATE_STATE_FULLSCREEN | YASL_PRIVATE_STATE_MAXIMIZED | YASL_PRIVATE_STATE_ACTIVATED)); property_count = 0; update_state(&surface); - assert(notifications == 5 && reported_state == YASL_STATE_ACTIVATED); + assert(notifications == 5 && reported_state == YASL_PRIVATE_STATE_ACTIVATED); update_state(&surface); assert(notifications == 5); return 0; diff --git a/tests/x11/test_tablet.c b/tests/x11/test_tablet.c index 243ae00..be5da4c 100644 --- a/tests/x11/test_tablet.c +++ b/tests/x11/test_tablet.c @@ -1,10 +1,18 @@ /* Exercise native XI2 samples through the backend and public input queue. */ #include "../../src/platform/x11/window.c" -static struct yasl_event next(struct x11_surface *surface) +static struct yasl_private_event next(struct x11_surface *surface) { - struct yasl_event event; - assert(yasl_surface_poll_event(&surface->base, &event)); + struct yasl_private_event event; + /* This backend unit harness owns a standalone input queue, without a + * registered display event FIFO. Read rich private events directly. */ + static unsigned count; + count++; + if (!input_queue_pop(&surface->input, &event)) { + fprintf(stderr, "missing private event at expectation %u (queued=%zu reset=%d)\n", + count, surface->input.count, surface->input.reset_pending); + abort(); + } assert(event.surface == &surface->base); return event; } @@ -13,51 +21,51 @@ struct drag_test_recorder { int over_count; int leave_count; int drop_count; - struct yasl_vector2i last_pos; + struct yasl_private_vector2i last_pos; }; -static void on_test_drag(struct yasl_surface *s, enum yasl_drag_event ev, - struct yasl_vector2i pos, void *ud) +static void on_test_drag(struct yasl_surface *s, enum yasl_private_drag_event ev, + struct yasl_private_vector2i pos, void *ud) { (void)s; struct drag_test_recorder *rec = ud; rec->last_pos = pos; - if (ev == YASL_DRAG_OVER) rec->over_count++; - else if (ev == YASL_DRAG_LEAVE) rec->leave_count++; - else if (ev == YASL_DRAG_DROP) rec->drop_count++; + if (ev == YASL_PRIVATE_DRAG_OVER) rec->over_count++; + else if (ev == YASL_PRIVATE_DRAG_LEAVE) rec->leave_count++; + else if (ev == YASL_PRIVATE_DRAG_DROP) rec->drop_count++; } struct drop_test_recorder { int over_count; int leave_count; - struct yasl_vector2i last_pos; + struct yasl_private_vector2i last_pos; }; -static void on_test_drop(struct yasl_surface *s, enum yasl_drop_event ev, - struct yasl_vector2i pos, void *ud) +static void on_test_drop(struct yasl_surface *s, enum yasl_private_drop_event ev, + struct yasl_private_vector2i pos, void *ud) { (void)s; struct drop_test_recorder *rec = ud; rec->last_pos = pos; - if (ev == YASL_DROP_OVER) rec->over_count++; - else if (ev == YASL_DROP_LEAVE) rec->leave_count++; + if (ev == YASL_PRIVATE_DROP_OVER) rec->over_count++; + else if (ev == YASL_PRIVATE_DROP_LEAVE) rec->leave_count++; } -static void on_test_drag_end(void *ud, enum yasl_drag_end how) +static void on_test_drag_end(void *ud, enum yasl_private_drag_end how) { - enum yasl_drag_end *res = ud; + enum yasl_private_drag_end *res = ud; *res = how; } static void test_x11_gaps(struct x11_display *d, struct x11_surface *s) { /* 1. Caps */ - assert(caps(&d->base) & YASL_CAP_POPUPS); - assert(caps(&d->base) & YASL_CAP_DRAG); - assert(caps(&d->base) & YASL_CAP_DRAG_CARRY); + assert(caps(&d->base) & YASL_PRIVATE_CAP_POPUPS); + assert(caps(&d->base) & YASL_PRIVATE_CAP_DRAG); + assert(caps(&d->base) & YASL_PRIVATE_CAP_DRAG_CARRY); /* 2. Popups: spec validation, calculation, creation, reposition, destruction */ - struct yasl_popup_spec invalid_specs[] = { + struct yasl_private_popup_spec invalid_specs[] = { {.size = {0, 100}}, {.size = {100, 0}}, {.size = {35000, 100}}, @@ -70,7 +78,7 @@ static void test_x11_gaps(struct x11_display *d, struct x11_surface *s) assert(!surface_create_popup(&s->base, &invalid_specs[i])); } - struct yasl_popup_spec valid_spec = { + struct yasl_private_popup_spec valid_spec = { .size = {100, 120}, .anchor = {30, 40, 50, 60}, .beside = false, @@ -78,12 +86,40 @@ static void test_x11_gaps(struct x11_display *d, struct x11_surface *s) }; assert(popup_spec_valid(s, &valid_spec)); int x = 0, y = 0; - popup_calculate_position(d, s, &valid_spec, &x, &y); + struct yasl_private_vector2i actual_size; + popup_calculate_position(d, s, &valid_spec, &x, &y, &actual_size); assert(x == 30 - 5); assert(y == 40 + 60 - 10); + /* Explicit XDG-style anchor/gravity stays exact unless a constraint allows a fit. */ + struct yasl_private_popup_spec positioned = { + .positioner_explicit = true, + .anchor_edges = 2, /* bottom */ + .gravity = 2, /* below */ + .size = {100, 120}, .anchor = {100, 100, 40, 20}, + .content_offset = {5, 10}, + }; + popup_calculate_position(d, s, &positioned, &x, &y, &actual_size); + assert(x == 68 && y == 110 && actual_size.x == 100 && actual_size.y == 120); + positioned.anchor_edges = 4; /* right edge */ + positioned.gravity = 4; /* to the right */ + positioned.anchor = (struct yasl_private_rect2i){1900, 100, 20, 20}; + positioned.constraints = 4; /* flip X */ + popup_calculate_position(d, s, &positioned, &x, &y, &actual_size); + assert(x == 1800 && actual_size.x == 100); + positioned.gravity = 0; + positioned.constraints = 1; /* slide X */ + popup_calculate_position(d, s, &positioned, &x, &y, &actual_size); + assert(x + actual_size.x == 1920); + positioned.content_offset = (struct yasl_private_vector2i){0, 0}; + positioned.gravity = 4; + positioned.constraints = 16; /* resize X */ + positioned.anchor = (struct yasl_private_rect2i){1880, 100, 20, 20}; + popup_calculate_position(d, s, &positioned, &x, &y, &actual_size); + assert(x == 1920 - actual_size.x && actual_size.x == 20); + valid_spec.beside = true; - popup_calculate_position(d, s, &valid_spec, &x, &y); + popup_calculate_position(d, s, &valid_spec, &x, &y, &actual_size); assert(x == 30 + 50 - 5); assert(y == 40 - 10); @@ -94,7 +130,7 @@ static void test_x11_gaps(struct x11_display *d, struct x11_surface *s) assert(popup->parent == s); assert(popup->size.x == 100 && popup->size.y == 120); - struct yasl_popup_spec repo_spec = { + struct yasl_private_popup_spec repo_spec = { .size = {150, 180}, .anchor = {10, 20, 30, 40}, .beside = false, @@ -113,24 +149,24 @@ static void test_x11_gaps(struct x11_display *d, struct x11_surface *s) struct drag_test_recorder rec1 = {0}; struct drag_test_recorder rec2 = {0}; - struct yasl_window_cbs cbs1 = {.drag = on_test_drag}; - struct yasl_window_cbs cbs2 = {.drag = on_test_drag}; + struct yasl_private_window_cbs cbs1 = {.drag = on_test_drag}; + struct yasl_private_window_cbs cbs2 = {.drag = on_test_drag}; set_callbacks(&s->base, &cbs1, &rec1); set_callbacks(&s2.base, &cbs2, &rec2); - enum yasl_drag_end end_reason = (enum yasl_drag_end)-1; - struct yasl_drag_listener l = {.end = on_test_drag_end}; + enum yasl_private_drag_end end_reason = (enum yasl_private_drag_end)-1; + struct yasl_private_drag_listener l = {.end = on_test_drag_end}; assert(drag_start(&d->base, &s->base, NULL, &l, &end_reason)); assert(d->drag.active); - /* Motion on s (window 42) -> YASL_DRAG_OVER on s */ + /* Motion on s (window 42) -> YASL_PRIVATE_DRAG_OVER on s */ XIDeviceEvent mev = {.evtype = XI_Motion, .sourceid = 8, .event = 42, .event_x = 12.0, .event_y = 34.0}; XGenericEventCookie cookie = {.evtype = XI_Motion, .data = &mev}; xi_event(d, &cookie); assert(rec1.over_count == 1); assert(rec1.last_pos.x == 12 && rec1.last_pos.y == 34); - /* Motion on s2 (window 43) -> YASL_DRAG_LEAVE on s, YASL_DRAG_OVER on s2 */ + /* Motion on s2 (window 43) -> YASL_PRIVATE_DRAG_LEAVE on s, YASL_PRIVATE_DRAG_OVER on s2 */ mev.event = 43; mev.event_x = 56.0; mev.event_y = 78.0; @@ -139,12 +175,12 @@ static void test_x11_gaps(struct x11_display *d, struct x11_surface *s) assert(rec2.over_count == 1); assert(rec2.last_pos.x == 56 && rec2.last_pos.y == 78); - /* ButtonRelease -> YASL_DRAG_DROP on s2 and drag end */ + /* ButtonRelease -> YASL_PRIVATE_DRAG_DROP on s2 and drag end */ XIDeviceEvent rel = {.evtype = XI_ButtonRelease, .sourceid = 8, .event = 43, .detail = 1}; cookie = (XGenericEventCookie){.evtype = XI_ButtonRelease, .data = &rel}; xi_event(d, &cookie); assert(rec2.drop_count == 1); - assert(end_reason == YASL_DRAG_DROPPED); + assert(end_reason == YASL_PRIVATE_DRAG_DROPPED); assert(!d->drag.active); /* Drag cancel */ @@ -155,7 +191,7 @@ static void test_x11_gaps(struct x11_display *d, struct x11_surface *s) assert(rec1.over_count == 2); drag_cancel(&d->base); assert(rec1.leave_count == 2); - assert(end_reason == YASL_DRAG_CANCELLED); + assert(end_reason == YASL_PRIVATE_DRAG_CANCELLED); assert(!d->drag.active); /* Hierarchy change cancels drag and pushes reset */ @@ -164,18 +200,18 @@ static void test_x11_gaps(struct x11_display *d, struct x11_surface *s) XIHierarchyEvent hiev = {.evtype = XI_HierarchyChanged, .flags = XISlaveRemoved}; cookie = (XGenericEventCookie){.evtype = XI_HierarchyChanged, .data = &hiev}; xi_event(d, &cookie); - assert(end_reason == YASL_DRAG_CANCELLED); + assert(end_reason == YASL_PRIVATE_DRAG_CANCELLED); assert(!d->drag.active); - struct yasl_event reset_ev; - assert(yasl_surface_poll_event(&s->base, &reset_ev)); - assert(reset_ev.type == YASL_EVENT_INPUT_RESET); + struct yasl_private_event reset_ev; + assert(yasl_private_surface_poll_event(&s->base, &reset_ev)); + assert(reset_ev.type == YASL_PRIVATE_EVENT_INPUT_RESET); /* 4. Drag image parameter */ uint32_t pixels[16] = {0xFF00FF00}; - struct yasl_drag_image img = {.pixels = pixels, .size = {4, 4}, .hotspot = {2, 2}}; + struct yasl_private_drag_image img = {.pixels = pixels, .size = {4, 4}, .hotspot = {2, 2}}; assert(drag_start(&d->base, &s->base, &img, &l, &end_reason)); assert(d->drag.active); - assert(drag_carry(&s->base, (struct yasl_vector2i){10, 10})); + assert(drag_carry(&s->base, (struct yasl_private_vector2i){10, 10})); drag_cancel(&d->base); /* 5. XDND drop-target events and URI parsing */ @@ -246,11 +282,11 @@ static void test_x11_gaps(struct x11_display *d, struct x11_surface *s) assert(drec.leave_count == 2); assert(d->pending_drop.active); - /* Push YASL_EVENT_DROP */ - push(s, (struct yasl_event){.type = YASL_EVENT_DROP}, "file:///sample.png\r\n", 20); - struct yasl_event drop_ev; - assert(yasl_surface_poll_event(&s->base, &drop_ev)); - assert(drop_ev.type == YASL_EVENT_DROP); + /* Push YASL_PRIVATE_EVENT_DROP */ + push(s, (struct yasl_private_event){.type = YASL_PRIVATE_EVENT_DROP}, "file:///sample.png\r\n", 20); + struct yasl_private_event drop_ev; + assert(yasl_private_surface_poll_event(&s->base, &drop_ev)); + assert(drop_ev.type == YASL_PRIVATE_EVENT_DROP); assert(drop_ev.text.len == 20); assert(!strcmp(drop_ev.text.data, "file:///sample.png\r\n")); @@ -262,10 +298,18 @@ static void test_x11_gaps(struct x11_display *d, struct x11_surface *s) int main(void) { + /* External GPU surfaces retain geometry without allocating CPU targets. */ + struct x11_surface external = {0}; + external.base.presenter = YASL_PRIVATE_PRESENTER_NONE; + assert(resize(&external, (struct yasl_private_vector2i){640, 480})); + assert(external.size.x == 640 && external.size.y == 480 && !external.image && !external.pixels); + struct x11_display display = { - .base.ops = &yasl_x11_backend, .abs_pressure = 10, .abs_tilt_x = 11, .abs_tilt_y = 12, + .base.ops = &yasl_private_x11_backend, .abs_pressure = 10, .abs_tilt_x = 11, .abs_tilt_y = 12, }; - struct x11_surface surface = {.base.display = &display.base, .window = 42}; + struct x11_surface surface = {.base = {.display = &display.base, + .creation_flags = YASL_PRIVATE_SURFACE_CPU, + .presenter = YASL_PRIVATE_PRESENTER_CPU}, .window = 42}; display.surfaces = &surface; XIValuatorClassInfo pressure = {.type = XIValuatorClass, .number = 2, .label = 10, .min = 100, .max = 1100, .mode = XIModeAbsolute}; @@ -277,7 +321,7 @@ int main(void) XIDeviceInfo info = {.deviceid = 8, .name = "Tablet pen", .num_classes = 3, .classes = classes}; struct x11_pointer pointer = {0}; pointer_info(&display, &pointer, &info); - assert(pointer.device == YASL_PTR_DEV_PEN); + assert(pointer.device == YASL_PRIVATE_PTR_DEV_PEN); display.pointers = &pointer; /* Cache avoids requiring a real X server. */ unsigned char mask[1] = {0}; @@ -290,9 +334,15 @@ int main(void) .mods.effective = ShiftMask, .valuators = {sizeof(mask), mask, values}}; XGenericEventCookie cookie = {.evtype = XI_ButtonPress, .data = &sample}; xi_event(&display, &cookie); - struct yasl_event ev = next(&surface); - assert(ev.type == YASL_EVENT_POINTER_DOWN && ev.pointer.button == YASL_BUTTON_PRIMARY && ev.pointer.device == YASL_PTR_DEV_PEN); - assert(ev.pointer.pos.x == 201 && ev.pointer.pos.y == 175 && ev.pointer.mods == YASL_MOD_SHIFT); + struct yasl_private_event ev = next(&surface); + assert(ev.type == YASL_PRIVATE_EVENT_PEN && ev.pen.device_id == 8 && !ev.pen.tool_id); + assert(ev.pen.tool == 1 && ev.pen.action == YASL_PRIVATE_PEN_DOWN && ev.pen.in_contact); + assert((ev.pen.axes_valid & (YASL_PRIVATE_PEN_PRESSURE | YASL_PRIVATE_PEN_TILT)) == + (YASL_PRIVATE_PEN_PRESSURE | YASL_PRIVATE_PEN_TILT)); + assert(ev.pen.axes_changed == (YASL_PRIVATE_PEN_PRESSURE | YASL_PRIVATE_PEN_TILT)); + ev = next(&surface); + assert(ev.type == YASL_PRIVATE_EVENT_POINTER_DOWN && ev.pointer.button == YASL_PRIVATE_BUTTON_PRIMARY && ev.pointer.device == YASL_PRIVATE_PTR_DEV_PEN); + assert(ev.pointer.pos.x == 201 && ev.pointer.pos.y == 175 && ev.pointer.mods == YASL_PRIVATE_MOD_SHIFT); assert(ev.pointer.pen.pressure == 32768 && ev.pointer.pen.tilt_x == -35 && ev.pointer.pen.tilt_y == 42); /* XI2 sends only changed axes, with values packed in mask order. */ @@ -302,36 +352,48 @@ int main(void) sample.evtype = cookie.evtype = XI_Motion; xi_event(&display, &cookie); ev = next(&surface); - assert(ev.type == YASL_EVENT_POINTER_MOTION && ev.pointer.pen.pressure == 65535); + assert(ev.type == YASL_PRIVATE_EVENT_PEN && ev.pen.action == YASL_PRIVATE_PEN_MOVE && + ev.pen.axes_valid == (YASL_PRIVATE_PEN_PRESSURE | YASL_PRIVATE_PEN_TILT) && + ev.pen.axes_changed == YASL_PRIVATE_PEN_PRESSURE && ev.pen.pressure == 1.0); + ev = next(&surface); + assert(ev.type == YASL_PRIVATE_EVENT_POINTER_MOTION && ev.pointer.pen.pressure == 65535); assert(ev.pointer.pen.tilt_x == -35 && ev.pointer.pen.tilt_y == 42); values[0] = 5000; xi_event(&display, &cookie); + assert(next(&surface).type == YASL_PRIVATE_EVENT_PEN); assert(next(&surface).pointer.pen.pressure == 65535); values[0] = -50; xi_event(&display, &cookie); + assert(next(&surface).type == YASL_PRIVATE_EVENT_PEN); assert(next(&surface).pointer.pen.pressure == 0); info.name = "Tablet ERASER"; pointer_info(&display, &pointer, &info); - assert(pointer.device == YASL_PTR_DEV_ERASER); + assert(pointer.device == YASL_PRIVATE_PTR_DEV_ERASER); values[0] = 900; sample.evtype = cookie.evtype = XI_ButtonPress; xi_event(&display, &cookie); ev = next(&surface); - assert(ev.pointer.device == YASL_PTR_DEV_ERASER && ev.pointer.pen.pressure > 0); + assert(ev.type == YASL_PRIVATE_EVENT_PEN && ev.pen.tool == 2 && ev.pen.action == YASL_PRIVATE_PEN_DOWN); + ev = next(&surface); + assert(ev.pointer.device == YASL_PRIVATE_PTR_DEV_ERASER && ev.pointer.pen.pressure > 0); /* Release must clear pressure even when the driver omits that valuator. */ sample.valuators.mask_len = 0; sample.evtype = cookie.evtype = XI_ButtonRelease; xi_event(&display, &cookie); ev = next(&surface); - assert(ev.type == YASL_EVENT_POINTER_UP && ev.pointer.button == YASL_BUTTON_PRIMARY && ev.pointer.pen.pressure == 0); - assert(ev.pointer.device == YASL_PTR_DEV_ERASER); + assert(ev.type == YASL_PRIVATE_EVENT_PEN && ev.pen.action == YASL_PRIVATE_PEN_UP && !ev.pen.in_contact); + ev = next(&surface); + assert(ev.type == YASL_PRIVATE_EVENT_POINTER_UP && ev.pointer.button == YASL_PRIVATE_BUTTON_PRIMARY && ev.pointer.pen.pressure == 0); + assert(ev.pointer.device == YASL_PRIVATE_PTR_DEV_ERASER); XIEnterEvent crossing = {.sourceid = 8, .event = 42, .event_x = 200, .event_y = 175}; cookie = (XGenericEventCookie){.evtype = XI_Leave, .data = &crossing}; xi_event(&display, &cookie); ev = next(&surface); - assert(ev.type == YASL_EVENT_POINTER_LEAVE && ev.pointer.device == YASL_PTR_DEV_ERASER && !ev.pointer.pen.pressure); + assert(ev.type == YASL_PRIVATE_EVENT_PEN && ev.pen.action == YASL_PRIVATE_PEN_LEAVE); + ev = next(&surface); + assert(ev.type == YASL_PRIVATE_EVENT_POINTER_LEAVE && ev.pointer.device == YASL_PRIVATE_PTR_DEV_ERASER && !ev.pointer.pen.pressure); /* No tablet axes must produce an ordinary, pressure-free mouse. */ info.num_classes = 0; @@ -340,24 +402,48 @@ int main(void) sample.detail = 3; xi_pointer_event(&surface, &sample); ev = next(&surface); - assert(ev.pointer.device == YASL_PTR_DEV_MOUSE && ev.pointer.button == YASL_BUTTON_SECONDARY && ev.pointer.pen.pressure == 0); + assert(ev.pointer.device == YASL_PRIVATE_PTR_DEV_MOUSE && ev.pointer.button == YASL_PRIVATE_BUTTON_SECONDARY && ev.pointer.pen.pressure == 0); for (int button = 4; button <= 7; button++) { sample.detail = button; sample.evtype = XI_ButtonPress; xi_pointer_event(&surface, &sample); ev = next(&surface); - assert(ev.type == YASL_EVENT_POINTER_SCROLL); + assert(ev.type == YASL_PRIVATE_EVENT_POINTER_SCROLL); assert(ev.pointer.scroll.x == (button == 6 ? -1 : button == 7 ? 1 : 0)); assert(ev.pointer.scroll.y == (button == 4 ? -1 : button == 5 ? 1 : 0)); + assert(ev.pointer.scroll_valid == 1u && ev.pointer.scroll_source == 1u && ev.pointer.scroll_phase == 2u); sample.evtype = XI_ButtonRelease; xi_pointer_event(&surface, &sample); - struct yasl_event unused; - assert(!yasl_surface_poll_event(&surface.base, &unused)); + struct yasl_private_event unused; + assert(!input_queue_pop(&surface.input, &unused)); } + /* XI2 smooth-scroll valuators preserve fractional line data and source. */ + XIValuatorClassInfo scroll_valuator = {.type = XIValuatorClass, .number = 9, + .value = 0, .mode = XIModeRelative}; + XIScrollClassInfo scroll_class = {.type = XIScrollClass, .number = 9, + .scroll_type = XIScrollTypeVertical, .increment = 2.0}; + XIAnyClassInfo *scroll_classes[] = {(void *)&scroll_valuator, (void *)&scroll_class}; + info.name = "Test touchpad"; + info.num_classes = 2; + info.classes = scroll_classes; + struct x11_pointer touchpad = {0}; + pointer_info(&display, &touchpad, &info); + display.pointers = &touchpad; + unsigned char scroll_mask[2] = {0}; + XISetMask(scroll_mask, 9); + double scroll_value[] = {-1.0}; + sample.evtype = XI_Motion; + sample.valuators = (XIValuatorState){sizeof(scroll_mask), scroll_mask, scroll_value}; + xi_pointer_event(&surface, &sample); + assert(next(&surface).type == YASL_PRIVATE_EVENT_POINTER_MOTION); + ev = next(&surface); + assert(ev.type == YASL_PRIVATE_EVENT_POINTER_SCROLL && ev.pointer.scroll_valid == 1u && + ev.pointer.scroll_source == 2u && ev.pointer.scroll.y == -0.5); + input_queue_clear(&surface.input); assert(pointer_button(2) == 1 && pointer_button(8) == 3); - pointer.pressure = (struct tablet_axis){2, 1, 1, 50}; + pointer.pressure = (struct tablet_axis){2, 1, 1, 50, true}; assert(pressure_value(&pointer.pressure) == 0); - pointer.tilt_x = (struct tablet_axis){4, -90, 90, -200}; + pointer.tilt_x = (struct tablet_axis){4, -90, 90, -200, true}; assert(tilt_value(&pointer.tilt_x) == -90); pointer.tilt_x.value = 200; assert(tilt_value(&pointer.tilt_x) == 90); diff --git a/tests/x11/test_tablet_native.c b/tests/x11/test_tablet_native.c index 706a5cc..828082c 100644 --- a/tests/x11/test_tablet_native.c +++ b/tests/x11/test_tablet_native.c @@ -2,7 +2,7 @@ #define main tablet_example_main #include "../../examples/tablet.c" #undef main -#include <yasl/native.h> +#include <common/private_native.h> #include <X11/Xatom.h> #include <X11/Xlib.h> #include <X11/Xutil.h> @@ -55,24 +55,24 @@ static int connect_tool(const char *path) return fd; } -static void native_draw(struct yasl_surface *surface, struct yasl_frame *frame, void *ud) +static void native_draw(struct yasl_surface *surface, struct yasl_private_frame *frame, void *ud) { (void)surface; draw(ud, surface, frame); } -static void native_configure(struct yasl_surface *surface, struct yasl_geometry geometry, void *ud) +static void native_configure(struct yasl_surface *surface, struct yasl_private_geometry geometry, void *ud) { - input(ud, &(struct yasl_event){.surface = surface, .type = YASL_EVENT_WINDOW_RESIZE, + input(ud, &(struct yasl_private_event){.surface = surface, .type = YASL_PRIVATE_EVENT_WINDOW_RESIZE, .resize = {.geometry = geometry}}); } -static void native_input(struct app *app, const struct yasl_event *event) +static void native_input(struct app *app, const struct yasl_private_event *event) { input(app, event); } -static void pump(int fd, struct yasl_display *display, struct yasl_surface *surface, struct app *app) +static void pump(int fd, struct yasl_private_display *display, struct yasl_surface *surface, struct app *app) { xf86ITEventWaitForSync sync = {{sizeof(sync), XF86IT_EVENT_WAIT_FOR_SYNC}}; send_all(fd, &sync, sizeof(sync)); @@ -81,14 +81,14 @@ static void pump(int fd, struct yasl_display *display, struct yasl_surface *surf assert(response.type == XF86IT_RESPONSE_SYNC_FINISHED && response.length == sizeof(response)); /* The driver acknowledgment guarantees input processing has finished. * Round-trip on YASL's X connection before draining its event queue. */ - XSync(yasl_display_get_native_handle(display), False); - assert(yasl_display_dispatch(display, 0) == 0); - struct yasl_event event; - while (yasl_surface_poll_event(surface, &event)) { + XSync(yasl_private_display_get_native_handle(display), False); + assert(yasl_private_display_dispatch(display, 0) == 0); + struct yasl_private_event event; + while (yasl_private_surface_poll_event(surface, &event)) { native_input(app, &event); - yasl_surface_schedule_redraw(surface); + yasl_private_surface_schedule_redraw(surface); } - yasl_display_flush(display); + yasl_private_display_flush(display); assert(!app->failed); } @@ -116,23 +116,23 @@ static void set_eraser_property(Display *display) int main(int argc, char **argv) { assert(argc == 3); /* pen socket, eraser socket */ - 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); + struct app app = {.tablet_supported = true, .backend = YASL_PRIVATE_BACKEND_X11}; + struct yasl_private_display_spec ds = {.backend = YASL_PRIVATE_BACKEND_X11}; + struct yasl_private_display *display = yasl_private_display_open(NULL, &ds); + assert(display && (yasl_private_display_caps(display) & YASL_PRIVATE_CAP_TABLET)); + struct yasl_private_window_spec ss = {.title = "YASL native pressure test", .size = {500, 400}}; + struct yasl_surface *surface = yasl_private_surface_create_toplevel(display, &ss); assert(surface); - 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); - Window window = (Window)(uintptr_t)yasl_surface_get_native_handle(surface); + struct yasl_private_window_cbs cbs = {.draw = native_draw, .configure = native_configure}; + yasl_private_surface_set_callbacks(surface, &cbs, &app); + yasl_private_surface_schedule_redraw(surface); + Display *x = yasl_private_display_get_native_handle(display); + Window window = (Window)(uintptr_t)yasl_private_surface_get_native_handle(surface); XMoveWindow(x, window, 0, 0); XSetInputFocus(x, window, RevertToParent, CurrentTime); XSync(x, False); - assert(yasl_display_dispatch(display, 0) == 0); - yasl_display_flush(display); + assert(yasl_private_display_dispatch(display, 0) == 0); + yasl_private_display_flush(display); set_eraser_property(x); int pen_fd = connect_tool(argv[1]), eraser_fd = connect_tool(argv[2]); xf86ITEventMotion motion = {.header = {sizeof(motion), XF86IT_EVENT_MOTION}, .is_absolute = 1}; @@ -144,7 +144,7 @@ int main(int argc, char **argv) send_all(pen_fd, &proximity, sizeof(proximity)); send_all(pen_fd, &motion, sizeof(motion)); pump(pen_fd, display, surface, &app); - assert(app.visible && !app.down && app.pointer.device == YASL_PTR_DEV_PEN); + assert(app.visible && !app.down && app.pointer.device == YASL_PRIVATE_PTR_DEV_PEN); assert(app.pointer.pos.x == 150 && app.pointer.pos.y == 200); assert(app.canvas[(size_t)200 * app.geometry.size.x + 150] == PAPER); xf86ITEventButton button = {.header = {sizeof(button), XF86IT_EVENT_BUTTON}, @@ -177,7 +177,7 @@ int main(int argc, char **argv) button.valuators.valuators[4] = 1000; send_all(eraser_fd, &button, sizeof(button)); pump(eraser_fd, display, surface, &app); - assert(app.down && app.pointer.device == YASL_PTR_DEV_ERASER && app.pointer.pen.pressure == 65535); + assert(app.down && app.pointer.device == YASL_PRIVATE_PTR_DEV_ERASER && app.pointer.pen.pressure == 65535); assert(app.canvas[(size_t)200 * app.geometry.size.x + 150] == PAPER && app.canvas[(size_t)200 * app.geometry.size.x + 170] == PAPER); button.is_press = 0; @@ -187,8 +187,8 @@ int main(int argc, char **argv) close(pen_fd); close(eraser_fd); free(app.canvas); - yasl_surface_destroy(surface); - yasl_display_close(display); + yasl_private_surface_destroy(surface); + yasl_private_display_close(display); puts("Native Xorg pen pressure, stationary pressure, release, and eraser drawing passed."); return 0; } diff --git a/yasl-gaps-report.md b/yasl-gaps-report.md deleted file mode 100644 index 6078536..0000000 --- a/yasl-gaps-report.md +++ /dev/null @@ -1,179 +0,0 @@ -# yasl gaps for wsap_term - -Date: 2026-10-13 -yasl head checked: `83c2180` -wsap_term state: `3a1439b`. The project builds, all 111 meson tests pass, and -the include-graph check reports no violations. - -## Summary - -All the earlier yasl gaps are closed in code. Drag targets, drag images, and -file drops now exist on every backend. They were checked by reading code, not at -runtime on X11 or macOS. - -The wsap_term audit of `83c2180` asked for six pieces of yasl API. All six -are implemented; see "Requests from the wsap_term audit" below. One gap -remains on the project side. - -## Resolved in yasl - -### 1. Drop-target events on X11 and Cocoa - -Status: **Resolved**. Both backends now send the target events. - -- X11: `drag_set_hover`, `drag_leave_hover`, and `drag_end` in - `src/platform/x11/window.c` send `YASL_DRAG_OVER`, `YASL_DRAG_LEAVE`, and - `YASL_DRAG_DROP`. The XDND target code sends `YASL_DROP_OVER` and - `YASL_DROP_LEAVE`. -- Cocoa: `src/platform/cocoa/window.m` sends all five target events. - -### 2. Drag images on X11 and Cocoa - -Status: **Resolved**. X11 shows the image in an override-redirect window that -follows the pointer (`drag_start` in `src/platform/x11/window.c`). Cocoa shows it -in a borderless image window. No backend ignores the image now. - -### 3. X11 file drops - -Status: **Resolved**. X11 accepts XDND drops of `text/uri-list` and sends -`YASL_EVENT_DROP` (`src/platform/x11/window.c`). - -### 4. IME preedit on X11 - -Status: **Resolved**. `XNSpotLocation` places the candidate window through -`yasl_surface_set_ime_caret`. Preedit text and its cursor are available through -`yasl_surface_ime_preedit` and `yasl_surface_ime_preedit_cursor`. - -### 5. Cocoa popups - -Status: **Resolved** in commit `b20263c`. Child-window popups are implemented, -and `YASL_CAP_POPUPS` is advertised. - -### 6. X11 popups - -Status: **Resolved** in commit `b20263c`. Override-redirect popups are -implemented, and `YASL_CAP_POPUPS` is advertised. - -### 7. Cocoa primary selection - -Status: **Resolved** in commit `b20263c`. A dedicated pasteboard emulates the -primary selection, and `YASL_CAP_PRIMARY_SELECTION` is advertised. - -### 8. X11 and Cocoa CSD and frame-pacing flags - -Status: **Resolved**. - -- Cocoa advertises `YASL_CAP_CSD` and `YASL_CAP_FRAME_PACING`. -- X11 advertises `YASL_CAP_CSD`, with `_NET_WM_MOVERESIZE` move and resize and - `_GTK_SHOW_WINDOW_MENU`. -- `yasl_run` paces continuous X11 surfaces at about 16 ms to avoid a spin loop. - -## Project-side gaps (wsap_term) - -### 9. Reset does not clear the UI pointer press - -yasl's `YASL_EVENT_INPUT_RESET` says the app must clear held keys, buttons, -strokes, and composition state. - -The adapter (`src/ui/yasl/native_input.c`) now clears keyboard and held-button -state. The UI still keeps the press active (`ui_route.c:116-119`, -`selection.c:79`). A press can stay stuck after an X11 device removal, a queue -allocation failure, or a Wayland text allocation failure. - -Suggested fix: send a pointer release to the UI on reset, or add a UI-level -reset. - -## Requests from the wsap_term audit - -All six requests are implemented. Linux paths are built and tested on Wayland -and X11. No Cocoa code was built or run. - -### 1.1 Signal watching - -Status: **Done** in `efca84e`. - -`yasl_loop_watch_signals()` and `yasl_loop_unwatch_signals()` deliver signals -to a callback on the loop thread. The contract the audit asked for: - -- Thread masks: YASL changes no thread's mask. Each signal gets a process-wide - handler with `SA_RESTART` and an empty handler mask. The handler may run on - any thread; it only records the signal and wakes the loop. A signal blocked - in every thread is never delivered. -- Platforms: the same handler-based design runs on Linux and macOS. There is - no `signalfd` path. -- `SIGCHLD`: callbacks run on the loop thread. Stopped and continued children - also raise it. -- One loop can watch a given signal at a time; another loop gets `EBUSY`. - Unwatch restores the saved actions. - -### 1.2 Cross-thread wake or post - -Status: **Done** in `efca84e`. - -- `yasl_loop_kick()` is documented as safe from any thread and from a signal - handler. -- `yasl_loop_post()` runs a function on the loop thread from any thread, in - posting order, with a plain `yasl_loop`. -- Cocoa dispatch did not wake for kicks, posts, or fd watches while waiting - for AppKit events. It now mirrors the loop's descriptors in a kqueue while - it waits. This also fixes `yasl_post_event()` wakeups under `yasl_run()` on - Cocoa. - -### 1.3 Native double-click interval - -Status: **Done** in `ac6f9f5`. - -`yasl_display_double_click_ms()` reads XSETTINGS `Net/DoubleClickTime` on X11, -the KDE `DoubleClickInterval` or GNOME `double-click` setting through the -Settings portal on Wayland (and on X11 without XSETTINGS), and -`NSEvent.doubleClickInterval` on Cocoa. It falls back to 400 ms. - -### 1.4 Held pointer buttons - -Status: **Done** in `cbd1fc7`. - -Every pointer event now has `buttons`, a `YASL_BUTTON_BIT()` mask of the -buttons held after the event. `INPUT_RESET` clears it. Wayland also clears it -on leave, because no release follows. X11 and Cocoa keep it across leave and -still deliver the release. - -### 1.5 Desktop helpers - -Status: **Done** in `d4d13e6`, with protocol code in `305a341`. - -`<yasl/desktop.h>` has `yasl_display_open_uri()`, `yasl_display_notify()`, -`yasl_display_appearance()`, `yasl_surface_bell()`, `yasl_executable_path()`, -`yasl_resource_dir()`, and `yasl_default_locale()`. - -- Linux opens URIs with `xdg-open` in a new session, with default signal - dispositions and no inherited descriptors. -- Notifications and appearance use libdbus-1, an optional dependency behind - `-Ddbus`. With it, the app no longer needs GIO for notifications. -- The bell uses xdg-system-bell on Wayland, the XKB bell on X11, and `NSBeep`. -- `yasl_display_open_uri()` checks only the URI syntax. Scheme policy, such as - `str_url_scheme_allowed`, stays in the app. -- `yasl_default_locale()` on Linux reads `LC_ALL`, `LC_CTYPE`, then `LANG`. - The app's Linux code always returns `en_US.UTF-8` today. - -### 1.6 Smaller items - -Status: **Done**. - -- Drop URIs: `yasl_drop_next_path()` decodes local paths from a drop payload - (`0437815`). -- Framebuffer readback: `yasl_surface_read_pixels()` copies the last presented - CPU frame (`b7944e4`). -- `strndup` warning: IME text is copied with `strlen` and a cap. An LTO release - build is warning-free (`6603122`). -- `yasl_loop_kick` threading: documented with 1.2 (`efca84e`). - -The extra-button encoding in the term subproject belongs to that project, not -to yasl. - -## Not verified - -- Runtime behavior of items 1–3 on X11 and Cocoa. -- Any Cocoa path, including the dispatch wake, desktop services, double-click - interval, readback, and bell. Cocoa needs a macOS build and test. -- Notifications and the bell were not shown on a real desktop. The desktop - test checks the D-Bus messages against a fake service on a private bus. |
