uhdk/README.md

191 lines
9.5 KiB
Markdown

# Uhdk
A small desktop platform library written in Zig 0.16.0 with a C99 ABI.
It provides windows, input and IME, clipboard, file dialogs, drag and drop, menus,
async file operations, file watching, and OpenGL context creation. The macOS
backend uses AppKit, OpenGL, and FSEvents through a private Objective-C bridge.
macOS builds require Xcode Command Line Tools. The Windows Win32/WGL backend
cross-compiles for `x86_64-windows-gnu`; runtime verification on Windows is pending. It requires
Windows 10 version 1703 or later and an OpenGL 3.3-capable driver.
The Linux Wayland preview implements windows, EGL/OpenGL, keyboard/pointer and
text input, clipboard/file drops, portal dialogs/preferences, activation, icons,
file watches, and Plasma app/window menu export, initially tested on KDE Plasma.
Linux context menus use a small software renderer and a bundled 16px Roboto
bitmap; X11 remains unsupported. Desktop dependencies are limited to vendored
ABI headers/protocol bindings and runtime-loaded system libraries. Normal
builds need Zig alone; the optional `shell.nix` supplies maintainer tools and
NixOS runtime library paths. See [Linux support and limitations](src/linux/README.md).
## Experimental desktop input and window observations
macOS uses AppKit editing selectors. Windows and Wayland share Ctrl-based defaults
for word deletion, paragraph/page movement, clipboard actions, undo/redo, Backtab,
Shift+Enter and cancellation. Shift extends selection only for movement. Alt/Super
combinations are not interpreted as Ctrl editing shortcuts. Clipboard shortcuts
work without a menu, with menu actions taking precedence when available.
Applications own execution, Unicode boundaries, layout and undo history. Commands
without customary shortcuts (such as line-boundary deletion on Windows) have no
invented default binding.
Publish an acknowledged text snapshot after consuming text/edit events. On macOS,
further native text input waits after semantic edits, including ignored commands;
acknowledge asynchronous paste after applying its result. Disabling text input or
losing window focus drops that deferred input. Windows IMM emits ordered text
without surrounding-text replacement ranges. Wayland invalidates old IME snapshot
serials after document/selection-changing commands; new surrounding text is
published after acknowledgment. It does not apply AppKit's input-queue mechanism.
`WINDOW_METRICS` is the resize and state-change hook. Geometry state is independent
of minimization. Observation bits indicate which facts are known:
| Backend | Known observations |
| --- | --- |
| macOS | Minimized, live resize, occluded |
| Windows | Minimized, interactive size/move loop |
| Wayland | Resizing; rendering suspension with xdg-shell v6+ |
A clear observation bit means unknown, not false. Wayland cannot report minimized
state or directly undo minimization, and suspension is not proof of occlusion.
Explicitly unavailable Wayland window-manager actions complete with `UNSUPPORTED`.
Render hints do not replace presentation pacing or event processing.
This library is unreleased and experimental; the changed C struct layout requires
rebuilding consumers, without ABI compatibility guarantees. Windows and Linux
runtime qualification must accompany cross-compilation checks.
## Build
```sh
zig build # zig-out/lib/libuhdk.a and zig-out/include/
zig build -Dshared=true # platform shared library and headers
zig build test # run core library tests on the native target
zig build -Dtarget=x86_64-windows-gnu
zig build -Dtarget=x86_64-windows-gnu -Dshared=true
zig build check -Dtarget=x86_64-windows-gnu # compile only
```
## Use from Zig
To use a local checkout at `vendor/uhdk`, add it to `build.zig.zon`:
```zig
.dependencies = .{
.uhdk = .{ .path = "vendor/uhdk" },
},
```
In your build, pass the application's target and optimization options:
```zig
const uhdk = b.dependency("uhdk", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("uhdk", uhdk.module("uhdk"));
```
`@import("uhdk")` provides translated public C declarations, including the bundled
OpenGL and experimental native-handle declarations, and links the library and required system frameworks. The
`uhdk` artifact is also available through `uhdk.artifact("uhdk")` for C consumers
or custom bindings. Set `.shared = true` to build a dynamic library; applications
must arrange its runtime location/rpath.
## C interface
- [uhdk.h](include/uhdk.h): desktop API, lifecycle, ownership, and threading contracts.
- [OPENGL-SOURCES.md](include/OPENGL-SOURCES.md): GLAD generation recipe and Khronos header provenance and licenses.
All UHDK declarations live in `uhdk.h`. Before its first inclusion, define
`UHDK_ENABLE_OPENGL` to include the bundled GLAD 2 OpenGL 3.3 core declarations
and per-context loader table, and/or `UHDK_ENABLE_NATIVE` for the experimental
native-window handle API:
```c
#define UHDK_ENABLE_OPENGL
#define UHDK_ENABLE_NATIVE
#include "uhdk.h"
```
Presence enables each flag; leave it undefined to disable it. These flags select
header declarations, not library backends. UHDK's own context-management API is
always declared. The supplied Zig module enables both sections. The vendored GL
headers remain unmodified dependencies rather than being pasted into `uhdk.h`.
The public interface uses pointer-and-length text, `char` booleans, and `float`
coordinates. The native bridge is private; consumers do not need Objective-C.
The library includes the GLAD loader implementation. With a current context inside
`uhdk_gl_context_begin_frame` / `uhdk_gl_context_end_frame`, call
`gladLoadGLContextUserPtr` with a callback that resolves names using
`uhdk_gl_context_get_proc`. Keep a `GladGLContext` per native context; its members
are named `Clear`, `DrawArrays`, etc. Check `VERSION_3_3` after loading. Do not define
`GLAD_GL_IMPLEMENTATION` in your application when linking libuhdk.
On Windows, the static artifact is `zig-out/lib/uhdk.lib`; shared builds install
`zig-out/bin/uhdk.dll` and its import library under `zig-out/lib/`. Keep the DLL
beside your executable. The first lifecycle caller establishes the Windows
platform thread; its message loop also owns COM dialogs, menus, clipboard and
watch completion. Input uses IMM32 composition; TSF reconversion and pen/touch
are not implemented. Native UTF-16 path bytes are kept separately from the WTF-8
paths passed to Zig I/O. If a native path cannot be exported losslessly as a shell
URI, URI export returns `UNSUPPORTED`; native-path export still preserves it.
## Borrowed event payloads
Events and request results carry `const void *data` plus `data_size` instead of
public unions. Check the event type (or successful request kind), then read the
corresponding named payload struct listed in `uhdk.h`. Check `data_size` before
reading fields; unknown types can be ignored. Payload-free events and unsuccessful
request results have NULL data and zero size. Failure diagnostics remain available
in the request envelope.
```c
if (event->type == UHDK_EVENT_KEY &&
event->data_size >= sizeof(uhdk_key_event)) {
const uhdk_key_event *key = (const uhdk_key_event *)event->data;
/* Read key->logical_key, key->pressed, etc. here. */
}
```
One library-owned scratch arena holds the returned batch, its payload structs,
and nested spans. The next valid application-thread poll resets it, even if that
poll is empty or returns an error. Invalid arguments and wrong-thread calls do
not reset it. App destruction also invalidates everything. Rendering does not
advance this lifetime. Copy data you need to keep; copying an envelope alone
copies only its pointers. Retain file/menu references when needed beyond a poll,
but even retained handles cannot outlive the app.
UHDK is pre-release: API and ABI compatibility with earlier revisions is not
guaranteed. Set creation descriptors to `UHDK_ABI_VERSION`. Event envelopes have
a fixed array stride within each returned batch.
The scratch arena is maintained as UHDK-owned code in
[src/arenas.zig](src/arenas.zig), including its supporting storage and tests.
It originated in Ribtech p2; the MIT license and attribution are retained in that file.
## Optional profiling
`uhdk.h` provides scope, frame, thread-name and named allocation/
free macros. They compile away without evaluating arguments unless the consumer
defines `UHDK_PROFILE_BACKEND` to a quoted adapter header before including UHDK.
An adapter implements the macros documented in that header. Names require static
lifetime; pair zones on the same thread in nesting order and report frees before
releasing memory. Do not put allocation expressions inside profiling macros.
Internal CPU and memory instrumentation is disabled by default. A static consumer
can pass `.tracy = true` to `b.dependency("uhdk", ...)` (or `-Dtracy=true` when
building the archive) and link its own Tracy 0.14.1 client configured with
`TRACY_ENABLE` and `TRACY_ON_DEMAND`. UHDK never fetches or links Tracy itself.
Shared instrumentation is unsupported. Run unit tests with the default
configuration; enabled instrumentation requires the consumer's linked client.
The `UHDK` memory pool measures requested Zig backing allocations, including
objects and arena blocks. Win32 explicit calloc/free blocks use a separate
`UHDK native` pool. OS/framework allocations and GPU memory are excluded.
On-demand capture omits allocations made before connection, so it is not a full
live-heap snapshot. Failed allocations emit nothing; in-place resizes emit
free + alloc. Moving reallocations use alloc/copy/free to preserve event ordering.
Disabled builds use the original allocator directly.