|
|
||
|---|---|---|
| include | ||
| src | ||
| .gitignore | ||
| LICENSE | ||
| README.md | ||
| build.zig | ||
| build.zig.zon | ||
| shell.nix | ||
README.md
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.
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
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:
.dependencies = .{
.uhdk = .{ .path = "vendor/uhdk" },
},
In your build, pass the application's target and optimization options:
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: desktop API, lifecycle, ownership, and threading contracts.
- 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:
#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.
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, 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.