ultra heavy desktop kit a tiny desktop application framework, tastefully small and hopefully has everything you need.
Go to file
Peter Li c0d923cbed macos inputs 2026-09-18 16:30:35 -07:00
include macos inputs 2026-09-18 16:30:35 -07:00
src macos inputs 2026-09-18 16:30:35 -07:00
.gitignore macos implementation. 2026-09-18 02:16:42 -07:00
LICENSE new header parser and generator 2026-09-18 11:29:02 -07:00
README.md macos inputs 2026-09-18 16:30:35 -07:00
build.zig Implement Plasma Wayland window and EGL foundation 2026-09-18 13:17:50 -07:00
build.zig.zon Implement Plasma Wayland window and EGL foundation 2026-09-18 13:17:50 -07:00
shell.nix Add a pinned 16px Roboto bitmap and tiny Linux software canvas 2026-09-18 15:21:15 -07:00

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.